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

 Copyright 2021 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 "MvQFeatureRibbonEditor.h"

#include <algorithm>
#include <map>

#include <QDebug>
#include <QButtonGroup>
#include <QColorDialog>
#include <QComboBox>
#include <QFontDatabase>
#include <QFrame>
#include <QLabel>
#include <QPainter>
#include <QPushButton>
#include <QSpinBox>
#include <QTimer>
#include <QToolButton>
#include <QWidgetAction>

#include "IconClass.h"
#include "MvIconParameter.h"
#include "MvQColourWidget.h"
#include "MvQFeatureItem.h"
#include "IconProvider.h"
#include "TransparencyWidget.h"
#include "MvQTheme.h"
#include "MvLog.h"

QMap<QString, QList<QColor>> ColourSelector::recentCols_;

//#define RIBBONEDITOR_DEBUG_

//==================================================
//
//  RibbonItemFactory
//
//==================================================

class RibbonItemFactory
{
public:
    RibbonItemFactory(const std::string& name);
    RibbonItemFactory(const RibbonItemFactory&) = delete;
    RibbonItemFactory& operator=(const RibbonItemFactory&) = delete;
    virtual ~RibbonItemFactory() = default;

    virtual RibbonEditItem* make(const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey) = 0;
    static RibbonEditItem* create(const std::string& name, const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey);

protected:
    std::string name_;
};

template <class T>
class RibbonItemMaker : public RibbonItemFactory
{
public:
    using RibbonItemFactory::RibbonItemFactory;

private:
    RibbonEditItem* make(const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey) override
    {
        return new T(param, layout, parent, typeKey);
    }
};

static std::map<std::string, RibbonItemFactory*>* makers = nullptr;

RibbonItemFactory::RibbonItemFactory(const std::string& name) :
    name_(name)
{
    if (!makers)
        makers = new std::map<std::string, RibbonItemFactory*>;

    (*makers)[name] = this;
}

RibbonEditItem* RibbonItemFactory::create(const std::string& name, const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey)
{
    RibbonEditItem* cmd = nullptr;
    auto it = makers->find(name);
    if (it == makers->end()) {
    }
    else {
        cmd = (*it).second->make(param, layout, parent, typeKey);
    }
    Q_ASSERT(cmd);
    return cmd;
}


//=============================================
//
// RibbonEditItem
//
//=============================================

RibbonEditItem::RibbonEditItem(const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey) :
    param_(param),
    parentWidget_(parent),
    layout_(layout)
{
    if (param_.name() != nullptr) {
        name_ = QString(param_.name());
    }
    idKey_ = typeKey + ":" + name_;
}

void RibbonEditItem::addLabel()
{
    if (const char* ch = param_.ribbon_label()) {
        label_ = new QLabel(QString(ch) + ":", parentWidget_);
        layout_->addWidget(label_);
    }
}

void RibbonEditItem::fixedFontAndHeight(QFont& font, int& h)
{
    font = QFont();
    font.setPointSize(font.pointSize() - 1);
    auto fm = QFontMetrics(font);
    h = fm.height() + 4;
}

//=============================================
//
// RibbonControlItem
//
//=============================================

RibbonControlItem::RibbonControlItem(const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey) :
    RibbonEditItem(param, layout, parent, typeKey)
{
    tb_ = new QToolButton(parent);
    tb_->setCheckable(true);
    tb_->setChecked(false);
    tb_->setAutoRaise(true);

    param_.scan(*this);
    Q_ASSERT(values_.count() == 2);
    if (values_[0] != "ON") {
        onIndex_ = true;
    }

    layout_->addWidget(tb_);

    connect(tb_, SIGNAL(clicked(bool)),
            this, SLOT(buttonClicked(bool)));
}

void RibbonControlItem::grey(bool st)
{
    tb_->setVisible(!st);
}

void RibbonControlItem::next(const MvIconParameter&, const char* first, const char* second)
{
    if (first) {
        values_ << ((second) ? QString(second) : QString(first));
    }
}

void RibbonControlItem::buttonClicked(bool)
{
    if (!ignoreChange_) {
        index_ = (index_ == 0) ? 1 : 0;
        if (!tooltipChecked_.isEmpty() && !tooltipUnchecked_.isEmpty()) {
            tb_->setToolTip((tb_->isChecked() ? tooltipChecked_ : tooltipUnchecked_));
        }
        emit edited();
    }
}

void RibbonControlItem::setValue(const MvRequest& req)
{
    ignoreChange_ = true;
    std::string v;
    req.getValue(param_.name(), v, true);
    QString s = QString::fromStdString(v);
    int idx = values_.indexOf(s);
    if (idx != -1) {
        index_ = idx;
    }
    tb_->setChecked(index_ == onIndex_);
    if (!tooltipChecked_.isEmpty() && !tooltipUnchecked_.isEmpty()) {
        tb_->setToolTip((tb_->isChecked() ? tooltipChecked_ : tooltipUnchecked_));
    }
    ignoreChange_ = false;
}

void RibbonControlItem::updateRequest(MvRequest& req)
{
    req(param_.name()) = values_[index_].toStdString().c_str();
}


RibbonBoldFontControlItem::RibbonBoldFontControlItem(const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey) :
    RibbonControlItem(param, layout, parent, typeKey)
{
    QIcon ic(QPixmap(":uPlot/bold_font.svg"));
    tb_->setIcon(ic);
    tb_->setToolTip("Bold");
}

RibbonItalicFontControlItem::RibbonItalicFontControlItem(const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey) :
    RibbonControlItem(param, layout, parent, typeKey)
{
    QIcon ic(QPixmap(":uPlot/italic_font.svg"));
    tb_->setIcon(ic);
    tb_->setToolTip("Italic");
}

RibbonUnderlineFontControlItem::RibbonUnderlineFontControlItem(const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey) :
    RibbonControlItem(param, layout, parent, typeKey)
{
    QIcon ic(QPixmap(":uPlot/underline_font.svg"));
    tb_->setIcon(ic);
    tb_->setToolTip("Underline");
}

RibbonOutlineControlItem::RibbonOutlineControlItem(const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey) :
    RibbonControlItem(param, layout, parent, typeKey)
{
    QIcon ic(QPixmap(":uPlot/pen.svg"));
    // ic.addPixmap(QPixmap(":uPlot/pen_off.svg"),QIcon::Normal,QIcon::On);
    tb_->setIcon(ic);
    tooltipChecked_ = "Click to disable outline";
    tooltipUnchecked_ = "Click to enable outline";
    tb_->setToolTip(tooltipUnchecked_);
}

RibbonFillControlItem::RibbonFillControlItem(const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey) :
    RibbonControlItem(param, layout, parent, typeKey)
{
    QIcon ic(QPixmap(":uPlot/paintcan.svg"));
    // ic.addPixmap(QPixmap(":uPlot/paintcan_off.svg"),QIcon::Normal,QIcon::On);
    tb_->setIcon(ic);

    tooltipChecked_ = "Click to disable fill";
    tooltipUnchecked_ = "Click to enable fill";
    tb_->setToolTip(tooltipUnchecked_);
}

//=============================================
//
// RibbonFlipStateItem
//
//=============================================

RibbonFlipStateItem::RibbonFlipStateItem(const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey) :
    RibbonEditItem(param, layout, parent, typeKey)
{
    pb_ = new QPushButton(parent);
    if (const char* ch = param_.ribbon_label()) {
        pb_->setText(QString(ch));
    }
    else {
        pb_->setText(QString::fromStdString(param_.beautifiedName()));
    }

    QFont f;
    int h;
    fixedFontAndHeight(f, h);
    pb_->setFont(f);

    param_.scan(*this);
    Q_ASSERT(values_.count() == 2);

    layout_->addWidget(pb_);

    connect(pb_, SIGNAL(clicked()),
            this, SLOT(buttonClicked()));
}

void RibbonFlipStateItem::grey(bool st)
{
    pb_->setVisible(!st);
}

void RibbonFlipStateItem::next(const MvIconParameter&, const char* first, const char* second)
{
    if (first) {
        values_ << ((second) ? QString(second) : QString(first));
    }
}

void RibbonFlipStateItem::buttonClicked()
{
    if (!ignoreChange_) {
        index_ = (index_ == 0) ? 1 : 0;
        emit edited();
    }
}

void RibbonFlipStateItem::setValue(const MvRequest& req)
{
    ignoreChange_ = true;
    std::string v;
    req.getValue(param_.name(), v, true);
    QString s = QString::fromStdString(v);
    int idx = values_.indexOf(s);
    if (idx != -1) {
        index_ = idx;
    }
    ignoreChange_ = false;
}

void RibbonFlipStateItem::updateRequest(MvRequest& req)
{
    req(param_.name()) = values_[index_].toStdString().c_str();
}

//=============================================
//
// RibbonSizeSpinItem
//
//=============================================

RibbonSizeSpinItem::RibbonSizeSpinItem(const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey) :
    RibbonEditItem(param, layout, parent, typeKey)
{
    addLabel();

    spin_ = new QSpinBox(parent);
    spin_->setKeyboardTracking(false);

    const char* ch = param_.ribbon_size_range();
    int m1 = 1, m2 = 100;  // default range
    if (ch) {
        auto s = QString(ch);
        auto lst = s.split(":");
        if (lst.size() == 2) {
            m1 = lst[0].toInt();
            m2 = lst[1].toInt();
            Q_ASSERT(m1 < m2);
            Q_ASSERT(m1 > 0);
        }
    }
    spin_->setMinimum(m1);
    spin_->setMaximum(m2);

    layout_->addWidget(spin_);

    QFont f;
    int h = 0;
    fixedFontAndHeight(f, h);
    spin_->setFont(f);
    if (label_) {
        label_->setFont(f);
    }

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

void RibbonSizeSpinItem::grey(bool st)
{
    if (label_) {
        label_->setVisible(!st);
    }
    spin_->setVisible(!st);
}

void RibbonSizeSpinItem::valueChanged(int /*value*/)
{
    if (!ignoreChange_) {
        emit edited();
    }
}

void RibbonSizeSpinItem::setValue(const MvRequest& req)
{
    ignoreChange_ = true;
    std::string v;
    req.getValue(param_.name(), v, true);
    if (v.empty()) {
        const std::vector<std::string>& d = param_.defaults();
        if (d.size() > 0) {
            v = d[0];
        }
    }

    int iv = static_cast<int>(QString::fromStdString(v).toFloat());
    if (iv < spin_->minimum()) {
        iv = spin_->minimum();
    }
    else if (iv > spin_->maximum()) {
        iv = spin_->maximum();
    }
    spin_->setValue(iv);
    ignoreChange_ = false;
}

void RibbonSizeSpinItem::updateRequest(MvRequest& req)
{
    req(param_.name()) = spin_->value();
}

//=============================================
//
// RibbonComboBoxItem
//
//=============================================

RibbonComboBoxItem::RibbonComboBoxItem(const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey) :
    RibbonEditItem(param, layout, parent, typeKey)
{
    addLabel();

    cb_ = new QComboBox(parent);
    param_.scan(*this);

    layout_->addWidget(cb_);

    QFont f;
    int h = 0;
    fixedFontAndHeight(f, h);
    cb_->setFont(f);
    // cb_->setFixedHeight(h);

    connect(cb_, SIGNAL(currentTextChanged(QString)),
            this, SLOT(valueChanged(QString)));
}

void RibbonComboBoxItem::grey(bool st)
{
    cb_->setVisible(!st);
    if (label_) {
        label_->setVisible(!st);
    }
}

void RibbonComboBoxItem::next(const MvIconParameter&, const char* first, const char* second)
{
    if (!first)
        return;

    QString dataStr = (second) ? QString(second) : QString(first);
    cb_->addItem(QString::fromStdString(param_.beautifiedName(first)), dataStr);
}

QString RibbonComboBoxItem::value() const
{
    auto index = cb_->currentIndex();
    if (index >= 0 && index < cb_->count()) {
        return cb_->itemData(index).toString();
    }
    return {};
}

void RibbonComboBoxItem::valueChanged(QString)
{
    if (!ignoreChange_) {
        emit edited();
    }
}

void RibbonComboBoxItem::setValue(const MvRequest& req)
{
    ignoreChange_ = true;
    std::string v;
    req.getValue(param_.name(), v, true);
    QString s = QString::fromStdString(v);
    bool found = false;
    for (int i = 0; i < cb_->count(); i++) {
        if (cb_->itemData(i).toString() == s) {
            cb_->setCurrentIndex(i);
            found = true;
            break;
        }
    }
    if (!found && useClosest_) {
        setClosestIndex(s);
    }
    ignoreChange_ = false;
}

void RibbonComboBoxItem::setClosestIndex(QString s)
{
    int ref = s.toInt();
    if (ref > 0) {
        int d = 10000;
        int idx = -1;
        for (int i = 0; i < cb_->count(); i++) {
            int v = cb_->itemData(i).toString().toInt();
            if (v > 0) {
                auto dAct = abs(v - ref);
                if (dAct < d) {
                    d = dAct;
                    idx = i;
                }
            }
        }
        if (idx != -1) {
            cb_->setCurrentIndex(idx);
        }
    }
}

void RibbonComboBoxItem::updateRequest(MvRequest& req)
{
#ifdef RIBBONEDITOR_DEBUG_
    qDebug() << "RibbonComboBoxItem::updateRequest param:" << param_.name() << "value:" << value();
#endif
    req(param_.name()) = value().toStdString().c_str();
}

//=============================================
//
// RibbonFontSizeItem
//
//=============================================

RibbonFontSizeItem::RibbonFontSizeItem(const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey) :
    RibbonComboBoxItem(param, layout, parent, typeKey)
{
    useClosest_ = true;
    cb_->clear();
    std::vector<int> values_ = {8, 9, 10, 11, 12, 14, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 72, 96};
    for (auto v : values_) {
        auto s = QString::number(v);
        cb_->addItem(s, s);
    }
}

//=============================================
//
// RibbonFontFamilyItem
//
//=============================================

RibbonFontFamilyItem::RibbonFontFamilyItem(const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey) :
    RibbonComboBoxItem(param, layout, parent, typeKey)
{
    //    QFontDatabase db;
    //    Q_FOREACH(QString s, db.families(QFontDatabase::Latin))
    //        cb_->addItem(s, s);
}

//=============================================
//
// RibbonFontStyleItem
//
//=============================================

RibbonFontStyleItem::RibbonFontStyleItem(const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey) :
    RibbonEditItem(param, layout, parent, typeKey)
{
    auto w = new QWidget(parent);
    auto hb = new QHBoxLayout(w);
    hb->setContentsMargins(0, 0, 0, 0);
    hb->setSpacing(0);

    boldTb_ = new QToolButton(parent);
    boldTb_->setIcon(QPixmap(":/uPlot/bold_font.svg"));
    boldTb_->setCheckable(true);
    boldTb_->setAutoRaise(true);

    italicTb_ = new QToolButton(parent);
    italicTb_->setIcon(QPixmap(":/uPlot/italic_font.svg"));
    italicTb_->setCheckable(true);
    italicTb_->setAutoRaise(true);

    underlineTb_ = new QToolButton(parent);
    underlineTb_->setIcon(QPixmap(":/uPlot/underline_font.svg"));
    underlineTb_->setCheckable(true);
    underlineTb_->setAutoRaise(true);

    hb->addWidget(boldTb_);
    hb->addWidget(italicTb_);
    hb->addWidget(underlineTb_);
    layout_->addWidget(w);

    connect(boldTb_, SIGNAL(clicked(bool)),
            this, SLOT(valueChanged(bool)));

    connect(italicTb_, SIGNAL(clicked(bool)),
            this, SLOT(valueChanged(bool)));

    connect(underlineTb_, SIGNAL(clicked(bool)),
            this, SLOT(valueChanged(bool)));
}

void RibbonFontStyleItem::grey(bool st)
{
    boldTb_->setVisible(!st);
    italicTb_->setVisible(!st);
    underlineTb_->setVisible(!st);
}

void RibbonFontStyleItem::valueChanged(bool)
{
    if (!ignoreChange_) {
        emit edited();
    }
}

void RibbonFontStyleItem::setValue(const MvRequest& req)
{
    ignoreChange_ = true;
    std::string v;
    req.getValue(param_.name(), v, true);
    boldTb_->setChecked(v == "BOLD" || v == "BOLDITALIC");
    italicTb_->setChecked(v == "ITALIC" || v == "BOLDITALIC");
    underlineTb_->setChecked(v == "UNDERLINE");
    ignoreChange_ = false;
}

void RibbonFontStyleItem::updateRequest(MvRequest& req)
{
    std::string v("NORMAL");
    if (boldTb_->isChecked() && italicTb_->isChecked()) {
        v = "BOLDITALIC";
    }
    else if (boldTb_->isChecked()) {
        v = "BOLD";
    }
    else if (italicTb_->isChecked()) {
        v = "ITALIC";
    }
    else if (underlineTb_->isChecked()) {
        v = "UNDERLINE";
    }
    req(param_.name()) = v.c_str();
}

//=============================================
//
// RibbonFontAlignmentItem
//
//=============================================

RibbonTextAlignmentItem::RibbonTextAlignmentItem(const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey) :
    RibbonEditItem(param, layout, parent, typeKey)
{
    auto w = new QWidget(parent);
    auto hb = new QHBoxLayout(w);
    hb->setContentsMargins(0, 0, 0, 0);
    hb->setSpacing(0);

    bGroup_ = new QButtonGroup(parent);
    keys_ << "LEFT"
          << "CENTRE"
          << "RIGHT"
          << "JUSTIFY";
    QStringList tooltip;
    tooltip << "Align left"
            << "Align centre"
            << "Align right"
            << "Justify text";

    for (int i = 0; i < keys_.size(); i++) {
        auto tb = new QToolButton(parent);
        tb->setIcon(QPixmap(":/uPlot/align_" + keys_[i].toLower() + ".svg"));
        tb->setCheckable(true);
        tb->setAutoRaise(true);
        tb->setToolTip(tooltip[i]);
        bGroup_->addButton(tb, i);
        hb->addWidget(tb);
    }

    layout_->addWidget(w);
    connect(bGroup_, SIGNAL(buttonClicked(QAbstractButton*)),
            this, SLOT(buttonClicked(QAbstractButton*)));
}

void RibbonTextAlignmentItem::grey(bool st)
{
    for (auto b : bGroup_->buttons()) {
        b->setVisible(!st);
    }
}

void RibbonTextAlignmentItem::buttonClicked(QAbstractButton*)
{
    if (!ignoreChange_) {
        emit edited();
    }
}

void RibbonTextAlignmentItem::setValue(const MvRequest& req)
{
    ignoreChange_ = true;
    std::string v;
    req.getValue(param_.name(), v, true);
    QString sv = QString::fromStdString(v);
    int idx = keys_.indexOf(sv);
    if (idx != -1) {
        auto b = bGroup_->button(idx);
        if (b) {
            b->setChecked(true);
        }
    }
    ignoreChange_ = false;
}

void RibbonTextAlignmentItem::updateRequest(MvRequest& req)
{
    int i = bGroup_->checkedId();
    if (i >= 0 && i < keys_.size()) {
        std::string v = keys_[i].toStdString();
        req(param_.name()) = v.c_str();
    }
}


//=============================================
//
// RibbonLineStyleItem
//
//=============================================

RibbonLineStyleItem::RibbonLineStyleItem(const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey) :
    RibbonComboBoxItem(param, layout, parent, typeKey)
{
    QFont f;
    int h = 0;
    fixedFontAndHeight(f, h);
    cb_->setFont(f);
    cb_->setIconSize(QSize(60, h - 4));
    loadPixmaps();
}

void RibbonLineStyleItem::loadPixmaps()
{
    std::map<QString, Qt::PenStyle> lineStyleDef = {
        {"solid", Qt::SolidLine},
        {"dash", Qt::DashLine},
        {"dot", Qt::DotLine},
        {"dash_dot", Qt::DashDotLine},
        {"dash_dot_dot", Qt::DashDotDotLine}};

    for (int i = 0; i < cb_->count(); i++) {
        auto style = cb_->itemData(i).toString().toLower();
        auto pix = QPixmap(cb_->iconSize());
        pix.fill(Qt::transparent);
        auto it = lineStyleDef.find(style);
        if (it != lineStyleDef.end()) {
            renderLine(&pix, it->second);
            cb_->setItemIcon(i, QIcon(pix));
        }
        cb_->setItemText(i, "");
    }
}

void RibbonLineStyleItem::renderLine(QPixmap* pix, Qt::PenStyle penStyle)
{
    int margin = 4;
    auto h = pix->height();
    auto w = pix->width();
    auto painter = QPainter(pix);
    painter.setPen(QPen(MvQTheme::colour("uplot", "ribbon_pen"), 2, penStyle));
    painter.drawLine(margin, h / 2, w - margin, h / 2);
}

//=============================================
//
// RibbonLineWidthItem
//
//=============================================

RibbonLineWidthItem::RibbonLineWidthItem(const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey) :
    RibbonComboBoxItem(param, layout, parent, typeKey)
{
    useClosest_ = true;
    QFont f;
    int h = 0;
    fixedFontAndHeight(f, h);
    cb_->setFont(f);
    cb_->setIconSize(QSize(60, h - 4));
    cb_->clear();
    for (int i = 0; i < 8; i++) {
        cb_->addItem(QString::number(i + 1) + " px", QString::number(i + 1));
    }

    loadPixmaps();
}

void RibbonLineWidthItem::loadPixmaps()
{
    for (int i = 0; i < cb_->count(); i++) {
        auto lw = cb_->itemData(i).toInt();
        auto pix = QPixmap(cb_->iconSize());
        pix.fill(Qt::transparent);
        renderLine(&pix, lw);
        cb_->setItemIcon(i, QIcon(pix));
    }
}

void RibbonLineWidthItem::renderLine(QPixmap* pix, int lw)
{
    int margin = 4;
    auto h = pix->height();
    auto w = pix->width();
    auto painter = QPainter(pix);
    painter.setPen(QPen(MvQTheme::colour("uplot", "ribbon_pen"), lw));
    painter.drawLine(margin, h / 2, w - margin, h / 2);
}


//=============================================
//
// RibbonLineStartEndItem
//
//=============================================

RibbonLineStartEndItem::RibbonLineStartEndItem(const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey, bool start) :
    RibbonComboBoxItem(param, layout, parent, typeKey),
    start_(start)
{
    QFont f;
    int h = 0;
    fixedFontAndHeight(f, h);
    cb_->setFont(f);
    cb_->setIconSize(QSize(35, h - 4));
    loadPixmaps();
}

void RibbonLineStartEndItem::loadPixmaps()
{
    for (int i = 0; i < cb_->count(); i++) {
        auto style = cb_->itemData(i).toString().toLower();
        auto pix = QPixmap(cb_->iconSize());
        pix.fill(Qt::transparent);
        renderLine(&pix, style);
        cb_->setItemIcon(i, QIcon(pix));
        cb_->setItemText(i, "");
    }
}

void RibbonLineStartEndItem::renderLine(QPixmap* pix, QString style)
{
    int margin = 4;
    auto h = pix->height();
    auto w = pix->width();
    auto painter = QPainter(pix);
    painter.setPen(QPen(MvQTheme::colour("uplot", "ribbon_pen"), 2));
    float xp = w - margin;
    float yp = h / 2;
    painter.drawLine(margin, h / 2, xp, yp);

    if (style == "none") {
        return;
    }

    painter.setRenderHint(QPainter::Antialiasing, true);

    if (start_) {
        xp = margin;
    }

    float aw = 10;
    float ah = sin(3.14 * 25. / 180.) * aw;
    if (style == "open_arrow") {
        if (!start_) {
            painter.drawLine(xp, yp, xp - aw, yp - ah);
            painter.drawLine(xp, yp, xp - aw, yp + ah);
        }
        else {
            painter.drawLine(xp, yp, xp + aw, yp - ah);
            painter.drawLine(xp, yp, xp + aw, yp + ah);
        }
    }
    else if (style == "filled_arrow") {
        QPolygonF p;
        if (!start_) {
            p << QPointF(xp, yp) << QPointF(xp - aw, yp - ah) << QPointF(xp - aw, yp + ah);
        }
        else {
            p << QPointF(xp, yp) << QPointF(xp + aw, yp - ah) << QPointF(xp + aw, yp + ah);
        }
        painter.setPen(Qt::NoPen);
        painter.setBrush(MvQTheme::colour("uplot", "ribbon_pen"));
        painter.drawPolygon(p);
    }
}

//=============================================
//
// RibbonColourItem
//
//=============================================

RibbonColourItem::RibbonColourItem(const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey) :
    RibbonEditItem(param, layout, parent, typeKey)
{
    tb_ = new QToolButton(parent);
    layout_->addWidget(tb_);

    selector_ = new ColourSelector(idKey_, parent);
    tb_->setPopupMode(QToolButton::InstantPopup);
    auto wa = new QWidgetAction(this);
    wa->setDefaultWidget(selector_);
    tb_->addAction(wa);

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

    connect(selector_, SIGNAL(transparencySelected(QColor)),
            this, SLOT(transparencySelected(QColor)));
}

void RibbonColourItem::grey(bool st)
{
    tb_->setVisible(!st);
}

void RibbonColourItem::setValue(const MvRequest& req)
{
    ignoreChange_ = true;
    std::string v;
    req.getValue(param_.name(), v, true);

    if (v.empty() && !colour_.isValid()) {
        auto df = param_.defaults();
        if (df.size() > 0) {
            v = df[0];
        }
    }
    if (!v.empty()) {
        colour_ = MvQPalette::magics(v);
        selector_->init(colour_);
        updatePixmap();
    }
    ignoreChange_ = false;
}

void RibbonColourItem::updateRequest(MvRequest& req)
{
    auto s = MvQPalette::toString(colour_);
    req(param_.name()) = s.c_str();
}

void RibbonColourItem::colourSelected(QColor col)
{
    colour_ = col;
    updatePixmap();
    if (!ignoreChange_) {
        emit edited();
    }
}

void RibbonColourItem::transparencySelected(QColor col)
{
    colour_ = col;
    updatePixmap();
    if (!ignoreChange_) {
        emit transparencyEdited();
    }
}

void RibbonColourItem::updatePixmap()
{
    int id = IconProvider::add(":/uPlot/pen.svg", "pen");
    auto base = IconProvider::pixmap(id, 18);

    int w = 24;
    pix_ = QPixmap(w, w);
    pix_.fill(Qt::transparent);
    // pix_.fill(Qt::white);
    QPainter p(&pix_);
    // p.drawPixmap(3,1, base);
    // QRect r(2, 18, w-4, 6);
    QRect r(4, 4, w - 8, w - 8);
    p.fillRect(r, colour_);
    tb_->setIcon(QIcon(pix_));
}

//=============================================
//
// RibbonColourFontItem
//
//=============================================

RibbonColourFontItem::RibbonColourFontItem(const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey) :
    RibbonColourItem(param, layout, parent, typeKey)
{
    basePixId_ = IconProvider::add(":/uPlot/letter_a.svg", "letter_a");
}

void RibbonColourFontItem::updatePixmap()
{
    auto basePix = IconProvider::pixmap(basePixId_, 18);

    int w = 24;
    pix_ = QPixmap(w, w);
    pix_.fill(Qt::transparent);
    // pix_.fill(Qt::white);
    QPainter p(&pix_);
    p.drawPixmap(3, 1, basePix);
    QRect r(2, 18, w - 4, 6);
    // QRect r(4, 4, w-8, w-8);
    p.fillRect(r, colour_);
    tb_->setIcon(QIcon(pix_));
}

//=============================================
//
// RibbonGeolockControlItem
//
//=============================================

RibbonGeolockControlItem::RibbonGeolockControlItem(const MvIconParameter& param, QLayout* layout, QWidget* parent, QString typeKey) :
    RibbonControlItem(param, layout, parent, typeKey)
{
    QIcon ic(QPixmap(":uPlot/geolock.svg"));
    tb_->setIcon(ic);
    tb_->setToolTip("Geolock");
}

//=============================================
//
// ColourSelector
//
//=============================================

ColourSelector::ColourSelector(QString idKey, QWidget* parent) :
    QWidget(parent),
    idKey_(idKey)
{
    auto font = QFont();
    font.setPointSize(font.pointSize() - 1);
    setFont(font);

    auto vb = new QVBoxLayout(this);

    // grey scale
    std::vector<int> shades = {0, 67, 102, 153, 183, 204, 217, 226, 244, 255};
    QList<MvQColourItem> items;
    for (auto i : shades) {
        items << MvQColourItem(QColor(i, i, i), "grey");
    }

    auto grid = new MvQCompactColourGrid(128, items, MvQCompactColourGrid::SingleRowMode, this);
    vb->addWidget(grid);

    connect(grid, SIGNAL(selected(QColor)),
            this, SLOT(valueChanged(QColor)));

    // grid
    shades = {-70, -50, -20, -5, 15, 40};
    items.clear();
    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});

    grid = new MvQCompactColourGrid(128, items, MvQCompactColourGrid::ColumnMode, this);
    vb->addWidget(grid);

    connect(grid, SIGNAL(selected(QColor)),
            this, SLOT(valueChanged(QColor)));

    // standard colours
    addSeparator(vb);
    auto label = new QLabel("Standard colours", this);
    label->setFont(font);
    vb->addWidget(label);

    items.clear();
    items << MvQColourItem(QColor(255, 0, 0), "red");
    items << MvQColourItem(QColor(255, 192, 3), "orange");
    items << MvQColourItem(QColor(255, 255, 0), "yellow");
    items << MvQColourItem(QColor(0, 255, 0), "green");
    items << MvQColourItem(QColor(146, 208, 81), "mid-green");
    items << MvQColourItem(QColor(0, 255, 255), "cyan");
    items << MvQColourItem(QColor(91, 141, 211), "mid-blue");
    items << MvQColourItem(QColor(0, 0, 255), "blue");
    items << MvQColourItem(QColor(138, 43, 226), "violet");
    items << MvQColourItem(QColor(255, 0, 255), "magenta");

    grid = new MvQCompactColourGrid(128, items, MvQCompactColourGrid::SingleRowMode, this);
    vb->addWidget(grid);

    connect(grid, SIGNAL(selected(QColor)),
            this, SLOT(valueChanged(QColor)));

    // most recent
    addSeparator(vb);
    label = new QLabel("Most recent", this);
    label->setFont(font);
    vb->addWidget(label);

    items.clear();
    recentGrid_ = new MvQCompactColourGrid(128, items, MvQCompactColourGrid::SingleRowMode, this);
    vb->addWidget(recentGrid_);

    connect(recentGrid_, SIGNAL(selected(QColor)),
            this, SLOT(recentChanged(QColor)));

    // transparency
    addSeparator(vb);
    label = new QLabel("Transparency", this);
    label->setFont(font);
    vb->addWidget(label);

    tr_ = new TransparencyWidget(this, font);
    vb->addWidget(tr_);

    connect(tr_, SIGNAL(valueChanged(int)),
            this, SLOT(transparencyChanged(int)));

    // custom
    addSeparator(vb);
    auto tb = new QToolButton(this);
    tb->setText(tr("More colours ..."));
    tb->setFont(font);
    vb->addWidget(tb);

    connect(tb, SIGNAL(clicked()),
            this, SLOT(addCustomColour()));
}

void ColourSelector::init(QColor col)
{
    currentColour_ = col;
    addToRecent(col);
    tr_->initAlpha(col.alpha());
}

void ColourSelector::valueChanged(QColor col)
{
    currentColour_ = col;
    addToRecent(col);
    currentColour_.setAlpha(tr_->alphaValue());
    emit selected(currentColour_);
}

void ColourSelector::addToRecent(QColor col)
{
    auto& rc = recentCols_[idKey_];
    if (!rc.contains(col)) {
        rc.push_front(col);
        while (rc.count() > maxRecentNum_) {
            rc.pop_back();
        }
        recentGrid_->replace(rc);
    }
    else if (recentGrid_->itemCount() == 0) {
        recentGrid_->replace(rc);
    }
}

void ColourSelector::recentChanged(QColor col)
{
    currentColour_ = col;
    currentColour_.setAlpha(tr_->alphaValue());
    emit selected(currentColour_);
}

void ColourSelector::transparencyChanged(int /*value*/)
{
    currentColour_.setAlpha(tr_->alphaValue());
    emit transparencySelected(currentColour_);
}

void ColourSelector::addCustomColour()
{
    // we need to do this delayed call. Otherwise when the colour is selected from the
    // custom dialog and we click again on the colour control item (QToolButton) an empty
    // menu comes up and we need to click again to see the colour selector on the menu.
    parentWidget()->close();
    QTimer::singleShot(0, this, SLOT(delayedCustomColourCall()));
}

void ColourSelector::delayedCustomColourCall()
{
    QColor c = QColorDialog::getColor(currentColour_, this);
    if (c.isValid()) {
        valueChanged(c);
    }
}

void ColourSelector::addSeparator(QVBoxLayout* vb)
{
    auto hr = new QFrame(this);
    hr->setFrameStyle(QFrame::HLine);
    hr->setFrameShadow(QFrame::Sunken);
    vb->addWidget(hr);
}

//=============================================
//
// MvQFeatureRibbonEditor
//
//=============================================

MvQFeatureRibbonEditor::MvQFeatureRibbonEditor(QWidget* parent) :
    QWidget(parent)
{
    layout_ = new QHBoxLayout(this);
    layout_->setContentsMargins(0, 0, 0, 0);
    layout_->setSpacing(2);

    QFont f;
    int h = 0;
    RibbonEditItem::fixedFontAndHeight(f, h);
    setFont(f);
    setFixedHeight(h + 6);

    showEmpty();
}

MvQFeatureRibbonEditor::~MvQFeatureRibbonEditor()
{
    clear();
}

void MvQFeatureRibbonEditor::showEmpty()
{
    auto label = new QLabel("No editable feature selected", this);
    label->setFont(font());
    layout_->addWidget(label);
}

void MvQFeatureRibbonEditor::edit(MvQFeatureItemPtr feature)
{
    if (feature_ == feature) {
        return;
    }
    if (feature_) {
        feature_->removeObserver(this);
    }

    setEnabled(true);
    feature_ = feature;
    feature_->addObserver(this);
    std::string verb;

    const char* ch = feature_->styleRequest().getVerb();
    if (ch) {
        verb = std::string(ch);
    }
#ifdef RIBBONEDITOR_DEBUG_
    MvLog().dbg() << MV_FN_INFO << "verb:" << verb;
    feature_->styleRequest().print();
#endif
    if (!verb.empty()) {
        if (verb != verb_) {
            clearItems();
            verb_ = verb;
            build();
        }
        load();
        if (items_.size() == 0) {
            clearItems();
            showEmpty();
        }
    }
    else {
        clear();
        showEmpty();
    }
}

void MvQFeatureRibbonEditor::finishEdit()
{
    // we keep the verb and disbale the gui, which still will be visible so the
    // size of the plot widget area will not change!
#ifdef RIBBONEDITOR_DEBUG_
    qDebug() << "MvQFeatureRibbonEditor::finishEdit";
#endif
    //    setEnabled(false);
    //    feature_ = nullptr;
    clear();
    showEmpty();
}

void MvQFeatureRibbonEditor::clear()
{
#ifdef RIBBONEDITOR_DEBUG_
    qDebug() << "MvQFeatureRibbonEditor::clear";
#endif
    clearItems();
    if (feature_) {
        feature_->removeObserver(this);
    }
    feature_ = nullptr;
    verb_.clear();
}

void MvQFeatureRibbonEditor::clearItems()
{
    QLayoutItem* child = nullptr;
    while ((child = layout_->takeAt(0)) != nullptr) {
        delete child->widget();
        delete child;
    }
    items_.clear();
}

void MvQFeatureRibbonEditor::build()
{
    auto ic = IconClass::find(verb_);
    if (ic) {
        ic->language().scan(*this);
    }

    layout_->addStretch(1);
}

void MvQFeatureRibbonEditor::load()
{
#ifdef RIBBONEDITOR_DEBUG_
    qDebug() << "MvQFeatureRibbonEditor::load request:";
    feature_->styleRequest().print();
#endif

    for (auto it : items_) {
        it->setValue(feature_->styleRequest());
    }
    MvRequest req = feature_->styleRequest();
    checkRules(req);
}

void MvQFeatureRibbonEditor::next(const MvIconParameter& p)
{
    if (const char* ch = p.ribbon_interface()) {
        // qDebug() << "  ribbon ->" << c;
        std::string rt(ch);
        addItem(rt, p);
    }
}

void MvQFeatureRibbonEditor::addItem(const std::string& rType, const MvIconParameter& p)
{
    if (auto* item = RibbonItemFactory::create(rType, p, layout_, this, feature_->typeName())) {
        if (const char* ch = p.ribbon_separator()) {
            if (strcmp(ch, "after") == 0) {
                addSeparator();
            }
        }
        items_ << item;
        connect(item, SIGNAL(edited()),
                this, SLOT(itemEdited()));

        connect(item, SIGNAL(transparencyEdited()),
                this, SLOT(itemTransparencyEdited()));
    }
}

void MvQFeatureRibbonEditor::itemEdited()
{
#ifdef RIBBONEDITOR_DEBUG_
    qDebug() << "MvQFeatureRibbonEditor::itemEdited";
#endif
    MvRequest req;
    buildRequest(req);
    feature_->styleEditedInRibbon(req);
#ifdef RIBBONEDITOR_DEBUG_
    qDebug() << " visible:" << feature_->isVisible() << feature_->pos() << feature_->boundingRect();
#endif
}

void MvQFeatureRibbonEditor::itemTransparencyEdited()
{
    MvRequest req;
    buildRequest(req);
    feature_->styleEditedInRibbon(req, StyleEditInfo::TransparencyStyleEdit);
}

void MvQFeatureRibbonEditor::buildRequest(MvRequest& req)
{
    req = feature_->styleRequest();
    for (auto it : items_) {
        it->updateRequest(req);
    }
    checkRules(req);
}

void MvQFeatureRibbonEditor::checkRules(const MvRequest& req)
{
    MvRequest r;
    auto ic = IconClass::find(verb_);
    if (ic) {
        r = ic->language().expand(req, EXPAND_DEFAULTS | EXPAND_2ND_NAME);
    }
    std::vector<std::string> values;
    r.getValue("_UNSET", values, true);
    for (auto it : items_) {
        bool g = std::find(values.begin(), values.end(), it->paramName().toStdString()) != values.end();
        it->grey(g);
    }
}

void MvQFeatureRibbonEditor::addSeparator()
{
    auto w = new QFrame(this);
    w->setFrameStyle(QFrame::VLine);
    w->setFrameShadow(QFrame::Sunken);
    layout_->addWidget(w);
}

void MvQFeatureRibbonEditor::notifyFeatureRequestChanged(MvQFeatureItemPtr)
{
    load();
}

static RibbonItemMaker<RibbonFontFamilyItem> mk1("font-family");
static RibbonItemMaker<RibbonFontSizeItem> mk2("font_size");
static RibbonItemMaker<RibbonBoldFontControlItem> mk3("font_bold");
static RibbonItemMaker<RibbonItalicFontControlItem> mk4("font_italic");
static RibbonItemMaker<RibbonUnderlineFontControlItem> mk5("font_underline");
static RibbonItemMaker<RibbonTextAlignmentItem> mk6("text_alignment");
static RibbonItemMaker<RibbonComboBoxItem> mk7("combo");
static RibbonItemMaker<RibbonFlipStateItem> mk8("flip_state");
static RibbonItemMaker<RibbonColourItem> mk9("colour");
static RibbonItemMaker<RibbonColourFontItem> mk10("colour_font");
static RibbonItemMaker<RibbonLineStyleItem> mk12("line_style");
static RibbonItemMaker<RibbonLineWidthItem> mk13("line_width");
static RibbonItemMaker<RibbonLineStartItem> mk14("line_start");
static RibbonItemMaker<RibbonLineEndItem> mk15("line_end");
static RibbonItemMaker<RibbonOutlineControlItem> mk16("control_outline");
static RibbonItemMaker<RibbonFillControlItem> mk17("control_fill");
static RibbonItemMaker<RibbonSizeSpinItem> mk18("size_spin");
static RibbonItemMaker<RibbonGeolockControlItem> mk19("geolock");
