/***************************** 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 "MvQFeatureCommand.h"

#include <algorithm>

#include <QGuiApplication>
#include <QDebug>
#include <QClipboard>
#include <QLabel>
#include <QMimeData>
#include <QPlainTextEdit>
#include <QTextEdit>
#include <QUndoCommand>

#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>

#include "FeatureStore.h"
#include "MvQFeatureCommandTarget.h"
#include "MvQFeatureCurveItem.h"
#include "MvQFeatureFactory.h"
#include "MvQFeatureItem.h"
#include "MvQFeatureRibbonEditor.h"
#include "MvQFeatureSelector.h"
#include "MvQFeatureTextItem.h"
#include "MvQFileDialog.h"
#include "WsType.h"
#include "MvQJson.h"
#include "MvQPlotView.h"
#include "MvQStreamOper.h"
#include "ObjectList.h"
#include "PlotMod.h"
#include "MvLog.h"
#include "MvMiscellaneous.h"
#include "WsDecoder.h"
#include "WsType.h"

#include "MgQLayoutItem.h"
#include "MgQPlotScene.h"
#include "MgQRootItem.h"
#include "MgQSceneItem.h"

//#define FEATURECOMMAND_DEBUG_

class MvQFeatureCommand;

MvQFeatureHandler* MvQFeatureHandler::instance_{nullptr};

//==================================================
//
//  MvQFeatureMimeData
//
//==================================================

class MvQFeatureMimeData : public QMimeData
{
public:
    MvQFeatureMimeData(const QJsonDocument& doc);
    QStringList formats() const override { return formats_; }

protected:
    QStringList formats_{"metview/feature"};
};


MvQFeatureMimeData::MvQFeatureMimeData(const QJsonDocument& doc)
{
    //    formats_ << "text/plain";
    auto d = doc.toJson();
    setData("metview/feature", d);
}

struct FeatureAddSpec
{
    enum Mode
    {
        WstMode,
        DocMode
    };
    FeatureAddSpec(WsType* wst) :
        wst(wst) {}
    FeatureAddSpec(QJsonDocument doc) :
        mode(DocMode), doc(doc) {}

    Mode mode{WstMode};
    WsType* wst{nullptr};
    QJsonDocument doc;
};

//==================================================
//
//  The command factory
//
//==================================================

class MvQFeatureCommandFactory
{
public:
    MvQFeatureCommandFactory(QString);
    MvQFeatureCommandFactory(const MvQFeatureFactory&) = delete;
    MvQFeatureCommandFactory& operator=(const MvQFeatureFactory&) = delete;
    virtual ~MvQFeatureCommandFactory() = default;

    virtual MvQFeatureCommand* make(MvQFeatureItemPtr item) = 0;
    static MvQFeatureCommand* create(QString name, MvQFeatureItemPtr item);

protected:
    QString name_;
};

template <class T>
class MvQFeatureCommandMaker : public MvQFeatureCommandFactory
{
public:
    using MvQFeatureCommandFactory::MvQFeatureCommandFactory;

private:
    MvQFeatureCommand* make(MvQFeatureItemPtr item) override
    {
        return new T({item});
    }
};

static std::map<QString, MvQFeatureCommandFactory*>* makers = nullptr;

MvQFeatureCommandFactory::MvQFeatureCommandFactory(QString name) :
    name_(name)
{
    if (!makers)
        makers = new std::map<QString, MvQFeatureCommandFactory*>;

    (*makers)[name] = this;
}

MvQFeatureCommand* MvQFeatureCommandFactory::create(QString name, MvQFeatureItemPtr item)
{
    MvQFeatureCommand* cmd = nullptr;
    auto it = makers->find(name);
    if (it == makers->end()) {
    }
    else {
        cmd = (*it).second->make(item);
    }
    Q_ASSERT(cmd);
    return cmd;
}

//==================================================
//
//  MvQFeatureCommand
//
//==================================================

class MvQFeatureCommand : public QUndoCommand
{
public:
    MvQFeatureCommand(MvQFeatureItemList items, QString name, QUndoCommand* parent = nullptr);
    bool match(const QUndoCommand* other) const;

protected:
    void generateText();
    QJsonDocument itemsToJson() const;

    MvQFeatureItemList items_;
    QString name_;
    bool firstCall_{true};
};

class MvQFeatureAddCommand : public MvQFeatureCommand
{
public:
    MvQFeatureAddCommand(MvQFeatureItemList items, QUndoCommand* parent = nullptr) :
        MvQFeatureCommand(items, "add", parent) {}
    void undo() override;
    void redo() override;
};

class MvQFeatureRemoveCommand : public MvQFeatureCommand
{
public:
    MvQFeatureRemoveCommand(MvQFeatureItemList items, QUndoCommand* parent = nullptr) :
        MvQFeatureCommand(items, "remove", parent) {}
    void undo() override;
    void redo() override;
};

class MvQFeatureDuplicateCommand : public MvQFeatureCommand
{
public:
    MvQFeatureDuplicateCommand(MvQFeatureItemList items, QUndoCommand* parent = nullptr) :
        MvQFeatureCommand(items, "duplicate", parent) {}
    void undo() override;
    void redo() override;

protected:
    MvQFeatureItemList resItems_;
};

class MvQFeatureCopyCommand : public MvQFeatureCommand
{
public:
    MvQFeatureCopyCommand(MvQFeatureItemList items, QUndoCommand* parent = nullptr) :
        MvQFeatureCommand(items, "copy", parent) {}
    void undo() override;
    void redo() override;
};

class MvQFeatureCutCommand : public MvQFeatureCommand
{
public:
    MvQFeatureCutCommand(MvQFeatureItemList items, QUndoCommand* parent = nullptr) :
        MvQFeatureCommand(items, "cut", parent) {}
    void undo() override;
    void redo() override;
};

class MvQFeatureToFrontCommand : public MvQFeatureCommand
{
public:
    MvQFeatureToFrontCommand(MvQFeatureItemList items, QUndoCommand* parent = nullptr) :
        MvQFeatureCommand(items, "to_front", parent) {}
    void undo() override;
    void redo() override;

protected:
    float oriZVal_{-1.};
};

class MvQFeatureToForwardCommand : public MvQFeatureCommand
{
public:
    MvQFeatureToForwardCommand(MvQFeatureItemList items, QUndoCommand* parent = nullptr) :
        MvQFeatureCommand(items, "to_forward", parent) {}
    void undo() override;
    void redo() override;
};

class MvQFeatureToBackCommand : public MvQFeatureCommand
{
public:
    MvQFeatureToBackCommand(MvQFeatureItemList items, QUndoCommand* parent = nullptr) :
        MvQFeatureCommand(items, "to_back", parent) {}
    void undo() override;
    void redo() override;

protected:
    float oriZVal_{-1.};
};

class MvQFeatureToBackwardCommand : public MvQFeatureCommand
{
public:
    MvQFeatureToBackwardCommand(MvQFeatureItemList items, QUndoCommand* parent = nullptr) :
        MvQFeatureCommand(items, "to_backward", parent) {}
    void undo() override;
    void redo() override;
};

class MvQFeatureFlipCommand : public MvQFeatureCommand
{
public:
    MvQFeatureFlipCommand(MvQFeatureItemList items, QUndoCommand* parent = nullptr) :
        MvQFeatureCommand(items, "flip_front", parent) {}
    void undo() override;
    void redo() override;
};

class MvQFeatureFrontSubTypeCommand : public MvQFeatureCommand
{
public:
    MvQFeatureFrontSubTypeCommand(MvQFeatureItemList items, QString targetSubType, QUndoCommand* parent = nullptr) :
        MvQFeatureCommand(items, "front_subtype", parent),
        targetSubType_(targetSubType.toStdString()) {}
    void undo() override;
    void redo() override;

protected:
    std::string targetSubType_;
    QList<std::string> oriSubTypes_;
};

class MvQFeatureStyleCommand : public MvQFeatureCommand
{
public:
    MvQFeatureStyleCommand(const MvRequest&, StyleEditInfo info, MvQFeatureItemPtr item, QUndoCommand* parent = nullptr);
    void undo() override;
    void redo() override;
    bool mergeWith(const QUndoCommand* other) override;
    int id() const override { return 1; }

protected:
    MvRequest req_;
    MvRequest oriReq_;
    StyleEditInfo info_;
};

class MvQFeatureTextCommand : public MvQFeatureCommand
{
public:
    MvQFeatureTextCommand(QString text, MvQFeatureItemPtr item, QUndoCommand* parent = 0);
    void undo() override;
    void redo() override;
    bool mergeWith(const QUndoCommand* other) override;
    int id() const override { return 2; }

protected:
    QString text_;
    QString oriText_;
};

class MvQFeatureAddNodeCommand : public MvQFeatureCommand
{
public:
    MvQFeatureAddNodeCommand(int nodeIndex, bool before, MvQFeatureItemPtr item, QUndoCommand* parent = nullptr);
    void undo() override;
    void redo() override;

protected:
    int nodeIndex_{-1};
    bool before_{true};
};

class MvQFeatureRemoveNodeCommand : public MvQFeatureCommand
{
public:
    MvQFeatureRemoveNodeCommand(int nodeIndex, MvQFeatureItemPtr item, QUndoCommand* parent = nullptr);
    void undo() override;
    void redo() override;

protected:
    int nodeIndex_{-1};
    MvQFeatureGeometry geom_;
};

class MvQFeatureMoveNodeCommand : public MvQFeatureCommand
{
public:
    MvQFeatureMoveNodeCommand(const QPointF& scenePos, int nodeIndex, bool start, MvQFeatureItemPtr item, QUndoCommand* parent = nullptr);
    void undo() override;
    void redo() override;
    bool mergeWith(const QUndoCommand* other) override;
    int id() const override { return 3; }

protected:
    int nodeIndex_{-1};
    bool start_{false};
    MvQFeatureGeometry oriGeom_;
    MvQFeatureGeometry targetGeom_;
};

class MvQFeatureMoveByCommand : public MvQFeatureCommand
{
public:
    MvQFeatureMoveByCommand(const QPointF& delta, bool start, MvQFeatureItemList items, QUndoCommand* parent = nullptr);
    void undo() override;
    void redo() override;
    bool mergeWith(const QUndoCommand* other) override;
    int id() const override { return 4; }

protected:
    QPointF delta_;
    bool start_{false};
    QList<MvQFeatureGeometry> oriGeom_;
    QList<MvQFeatureGeometry> targetGeom_;
};

class MvQFeatureResizeCommand : public MvQFeatureCommand
{
public:
    MvQFeatureResizeCommand(const QRectF& newRect, const QRectF& oriRect, bool start, MvQFeatureItemList items, QUndoCommand* parent = nullptr);
    void undo() override;
    void redo() override;
    bool mergeWith(const QUndoCommand* other) override;
    int id() const override { return 5; }

protected:
    QPointF delta_;
    bool start_{false};
    QList<MvQFeatureGeometry> oriGeom_;
    QList<MvQFeatureGeometry> targetGeom_;
};

//==================================================
//
//  MvQFeatureCommand
//
//==================================================

MvQFeatureCommand::MvQFeatureCommand(MvQFeatureItemList items, QString name, QUndoCommand* parent) :
    QUndoCommand(parent),
    items_(items),
    name_(name)
{
    generateText();
}

bool MvQFeatureCommand::match(const QUndoCommand* other) const
{
    if (id() == other->id()) {
        auto o = static_cast<const MvQFeatureCommand*>(other);
        if (items_.size() == o->items_.size()) {
            for (int i = 0; i < items_.size(); i++) {
                if (items_[i] != o->items_[i]) {
                    return false;
                }
            }
        }
        return true;
    }
    return false;
}

void MvQFeatureCommand::generateText()
{
    QString s = name_;
    if (items_.size() == 1 && items_[0]) {
        s += " " + items_[0]->typeName();
    }
    else {
        s += " " + QString::number(items_.size()) + " items";
    }
    return setText(s);
}

QJsonDocument MvQFeatureCommand::itemsToJson() const
{
    QJsonDocument doc;
    QJsonArray a;
    for (const auto& item : items_) {
        QJsonObject o;
        item->toJson(o);
        a << o;
    }
    doc.setArray(a);
    return doc;
}

//==================================================
//
//  MvQFeatureAddCommand
//
//==================================================

void MvQFeatureAddCommand::redo()
{
    MvQFeatureHandler::Instance()->addToScene(items_, true);
}

void MvQFeatureAddCommand::undo()
{
    MvQFeatureHandler::Instance()->removeFromScene(items_);
}

//==================================================
//
//  MvQFeatureRemoveCommand
//
//==================================================

void MvQFeatureRemoveCommand::redo()
{
    MvQFeatureHandler::Instance()->removeFromScene(items_);
}

void MvQFeatureRemoveCommand::undo()
{
    MvQFeatureHandler::Instance()->addToScene(items_);
}

//==================================================
//
//  MvQFeatureDuplicateCommand
//
//==================================================

void MvQFeatureDuplicateCommand::redo()
{
    if (firstCall_) {
        firstCall_ = false;
        resItems_ = MvQFeatureHandler::Instance()->performDuplicate(items_);
    }
    MvQFeatureHandler::Instance()->addToScene(resItems_);
}

void MvQFeatureDuplicateCommand::undo()
{
    MvQFeatureHandler::Instance()->removeFromScene(resItems_);
}

//==================================================
//
//  MvQFeatureCopyCommand
//
//==================================================

void MvQFeatureCopyCommand::redo()
{
    auto* data = new MvQFeatureMimeData(itemsToJson());
    QGuiApplication::clipboard()->setMimeData(data);
}

void MvQFeatureCopyCommand::undo()
{
    QGuiApplication::clipboard()->clear();
}

//==================================================
//
//  MvQFeatureCutCommand
//
//==================================================

void MvQFeatureCutCommand::redo()
{
    auto* data = new MvQFeatureMimeData(itemsToJson());
    QGuiApplication::clipboard()->setMimeData(data);
    MvQFeatureHandler::Instance()->removeFromScene(items_);
}

void MvQFeatureCutCommand::undo()
{
    MvQFeatureHandler::Instance()->addToScene(items_);
    QGuiApplication::clipboard()->clear();
}

//==================================================
//
//  MvQFeatureToFrontCommand
//
//==================================================

void MvQFeatureToFrontCommand::redo()
{
    Q_ASSERT(items_.size() == 1);
    auto theItem = items_[0];

#ifdef FEATURECOMMAND_DEBUG_
    qDebug() << "MvQFeatureToFrontCommand " << theItem->typeName() << "oriZVal:" << oriZVal_;
#endif
    if (!theItem->isPoint()) {
        if (oriZVal_ < 0) {
            oriZVal_ = theItem->realZValue();
        }

        for (auto item : MvQFeatureHandler::Instance()->features()) {
            if (!item->isPoint() && item != theItem && item->realZValue() > oriZVal_) {
#ifdef FEATURECOMMAND_DEBUG_
                qDebug() << " " << item->typeName() << " " << item->realZValue() << "->" << item->realZValue() - MvQFeatureItem::zValueIncrement();
#endif
                item->setRealZValue(item->realZValue() - MvQFeatureItem::zValueIncrement());
            }
        }
#ifdef FEATURECOMMAND_DEBUG_
        qDebug() << "final z:" << MvQFeatureItem::nextZValue() - MvQFeatureItem::zValueIncrement();
#endif
        theItem->setRealZValue(MvQFeatureItem::nextZValue() - MvQFeatureItem::zValueIncrement());
#ifdef FEATURECOMMAND_DEBUG_
        qDebug() << "  ->" << theItem->realZValue() << theItem->zValue();
#endif
    }
}

void MvQFeatureToFrontCommand::undo()
{
    Q_ASSERT(items_.size() == 1);
    auto theItem = items_[0];

    if (!theItem->isPoint()) {
        for (auto item : MvQFeatureHandler::Instance()->features()) {
            if (!item->isPoint() && item != theItem && item->realZValue() >= oriZVal_) {
                item->setRealZValue(item->realZValue() + item->MvQFeatureItem::zValueIncrement());
            }
        }
        theItem->setRealZValue(oriZVal_);
    }
}

//==================================================
//
//  MvQFeatureToForwardCommand
//
//==================================================

void MvQFeatureToForwardCommand::redo()
{
    Q_ASSERT(items_.size() == 1);
    auto theItem = items_[0];
    if (!theItem->isPoint()) {
        MvQFeatureHandler::Instance()->swapByZValue(theItem, theItem->realZValue() + MvQFeatureItem::zValueIncrement());
    }
}

void MvQFeatureToForwardCommand::undo()
{
    Q_ASSERT(items_.size() == 1);
    auto theItem = items_[0];
    if (!theItem->isPoint()) {
        MvQFeatureHandler::Instance()->swapByZValue(theItem, theItem->realZValue() - MvQFeatureItem::zValueIncrement());
    }
}

//==================================================
//
//  MvQFeatureToBackCommand
//
//==================================================

void MvQFeatureToBackCommand::redo()
{
    Q_ASSERT(items_.size() == 1);
    auto theItem = items_[0];
    if (!theItem->isPoint()) {
        if (oriZVal_ < 0) {
            oriZVal_ = theItem->realZValue();
        }

        for (auto item : MvQFeatureHandler::Instance()->features()) {
            if (!item->isPoint() && item != theItem && item->realZValue() < oriZVal_) {
                item->setRealZValue(item->realZValue() + MvQFeatureItem::zValueIncrement());
            }
        }
        theItem->setRealZValue(MvQFeatureItem::bottomZValue());
    }
}

void MvQFeatureToBackCommand::undo()
{
    Q_ASSERT(items_.size() == 1);
    auto theItem = items_[0];
    if (!theItem->isPoint()) {
        for (auto item : MvQFeatureHandler::Instance()->features()) {
            if (!item->isPoint() && item != theItem && item->realZValue() <= oriZVal_) {
                item->setRealZValue(item->realZValue() - item->MvQFeatureItem::zValueIncrement());
            }
        }
        theItem->setRealZValue(oriZVal_);
    }
}

//==================================================
//
//  MvQFeatureToBackwardCommand
//
//==================================================

void MvQFeatureToBackwardCommand::redo()
{
    Q_ASSERT(items_.size() == 1);
    auto theItem = items_[0];
    if (!theItem->isPoint()) {
        MvQFeatureHandler::Instance()->swapByZValue(theItem, theItem->realZValue() - MvQFeatureItem::zValueIncrement());
    }
}

void MvQFeatureToBackwardCommand::undo()
{
    Q_ASSERT(items_.size() == 1);
    auto theItem = items_[0];
    if (!theItem->isPoint()) {
        MvQFeatureHandler::Instance()->swapByZValue(theItem, theItem->realZValue() + MvQFeatureItem::zValueIncrement());
    }
}

//==================================================
//
//  MvQFeatureFlipCommand
//
//==================================================

void MvQFeatureFlipCommand::redo()
{
    for (auto item : items_) {
        item->flipSymbolDirection();
    }
}

void MvQFeatureFlipCommand::undo()
{
    for (auto item : items_) {
        item->flipSymbolDirection();
    }
}

//==================================================
//
//  MvQFeatureFrontSubTypeCommand
//
//==================================================

void MvQFeatureFrontSubTypeCommand::redo()
{
    if (firstCall_) {
        firstCall_ = false;
        for (auto item : items_) {
            oriSubTypes_ << item->frontSubType();
        }
    }

    for (auto item : items_) {
        item->setFrontSubType(targetSubType_);
    }
}

void MvQFeatureFrontSubTypeCommand::undo()
{
    for (int i = 0; i < items_.size(); i++) {
        items_[i]->setFrontSubType(oriSubTypes_[i]);
    }
}

//==================================================
//
//  MvQFeatureStyleCommand
//
//==================================================

MvQFeatureStyleCommand::MvQFeatureStyleCommand(const MvRequest& req, StyleEditInfo info, MvQFeatureItemPtr item, QUndoCommand* parent) :
    MvQFeatureCommand({item}, "change_style", parent),
    req_(req),
    oriReq_(item->styleRequest()),
    info_(info)
{
}

void MvQFeatureStyleCommand::redo()
{
    Q_ASSERT(items_.size() == 1);
    items_[0]->setStyle(req_);
}

void MvQFeatureStyleCommand::undo()
{
    items_[0]->setStyle(oriReq_);
}

bool MvQFeatureStyleCommand::mergeWith(const QUndoCommand* other)
{
    if (match(other)) {
        auto o = static_cast<const MvQFeatureStyleCommand*>(other);
        if (o && info_ == StyleEditInfo::TransparencyStyleEdit && o->info_ == info_) {
            req_ = o->req_;
            return true;
        }
    }
    return false;
}

//==================================================
//
//  MvQFeatureTextCommand
//
//==================================================

MvQFeatureTextCommand::MvQFeatureTextCommand(QString text, MvQFeatureItemPtr item, QUndoCommand* parent) :
    MvQFeatureCommand({item}, "edit_text", parent),
    text_(text)
{
    Q_ASSERT(items_.size() == 1);
    oriText_ = items_[0]->text();
}

void MvQFeatureTextCommand::redo()
{
    Q_ASSERT(items_.size() == 1);
    items_[0]->setText(text_);
}

void MvQFeatureTextCommand::undo()
{
    Q_ASSERT(items_.size() == 1);
    items_[0]->setText(oriText_);
}

bool MvQFeatureTextCommand::mergeWith(const QUndoCommand* other)
{
    if (match(other)) {
        auto o = static_cast<const MvQFeatureTextCommand*>(other);
        if (o) {
            if ((text_.endsWith(" ") && o->text_.endsWith(" ")) ||
                (!text_.endsWith(" ") && !o->text_.endsWith(" "))) {
                text_ = o->text_;
                return true;
            }
        }
    }
    return false;
}

//==================================================
//
//  MvQFeatureAddNodeCommand
//
//==================================================

MvQFeatureAddNodeCommand::MvQFeatureAddNodeCommand(int nodeIndex, bool before, MvQFeatureItemPtr item, QUndoCommand* parent) :
    MvQFeatureCommand({item}, "add_node", parent),
    nodeIndex_(nodeIndex),
    before_(before)
{
}

void MvQFeatureAddNodeCommand::redo()
{
    Q_ASSERT(items_.size() == 1);
    items_[0]->addNode(nodeIndex_, before_);
}

void MvQFeatureAddNodeCommand::undo()
{
    items_[0]->removeNode(nodeIndex_);
}

//==================================================
//
//  MvQFeatureRemoveNodeCommand
//
//==================================================

MvQFeatureRemoveNodeCommand::MvQFeatureRemoveNodeCommand(int nodeIndex, MvQFeatureItemPtr item, QUndoCommand* parent) :
    MvQFeatureCommand({item}, "remove_node", parent),
    nodeIndex_(nodeIndex),
    geom_(items_[0]->getGeometry())
{
}

void MvQFeatureRemoveNodeCommand::redo()
{
    Q_ASSERT(items_.size() == 1);
    items_[0]->removeNode(nodeIndex_);
}

void MvQFeatureRemoveNodeCommand::undo()
{
    items_[0]->addNode(nodeIndex_, geom_.pointsScenePos_[nodeIndex_]);
}

//==================================================
//
//  MvQFeatureMoveNodeCommand
//
//==================================================

MvQFeatureMoveNodeCommand::MvQFeatureMoveNodeCommand(const QPointF& scenePos, int nodeIndex, bool start, MvQFeatureItemPtr item, QUndoCommand* parent) :
    MvQFeatureCommand({item}, "move_node", parent),
    nodeIndex_(nodeIndex),
    start_(start)
{
    Q_ASSERT(nodeIndex_ != -1);
    Q_ASSERT(items_.size() > 0);
    oriGeom_ = items_[0]->getGeometry();
    item->moveNodeTo(nodeIndex_, scenePos);
    targetGeom_ = items_[0]->getGeometry();
}

void MvQFeatureMoveNodeCommand::redo()
{
    if (firstCall_) {
        firstCall_ = false;
    }
    else {
        Q_ASSERT(items_.size() == 1);
        items_[0]->moveNodeTo(nodeIndex_, targetGeom_.pointsScenePos_[nodeIndex_]);
    }
}

void MvQFeatureMoveNodeCommand::undo()
{
    items_[0]->moveNodeTo(nodeIndex_, oriGeom_.pointsScenePos_[nodeIndex_]);
}

bool MvQFeatureMoveNodeCommand::mergeWith(const QUndoCommand* other)
{
    if (match(other)) {
        auto o = static_cast<const MvQFeatureMoveNodeCommand*>(other);
        if (o && nodeIndex_ == o->nodeIndex_ && !o->start_) {
            targetGeom_ = o->targetGeom_;
            return true;
        }
    }
    return false;
}

//==================================================
//
//  MvQFeatureMoveByCommand
//
//==================================================

MvQFeatureMoveByCommand::MvQFeatureMoveByCommand(const QPointF& delta, bool start, MvQFeatureItemList items, QUndoCommand* parent) :
    MvQFeatureCommand(items, "move", parent),
    start_(start)
{
    bool validDelta = fabs(delta.x()) > 1E-10 || fabs(delta.y()) > 1E-10;
    for (auto item : items_) {
        oriGeom_ << item->getGeometry();
        if (validDelta) {
            item->moveBy(delta);
        }
        targetGeom_ << item->getGeometry();
    }
}

void MvQFeatureMoveByCommand::redo()
{
    if (firstCall_) {
        firstCall_ = false;
    }
    else {
        for (int i = 0; i < items_.size(); i++) {
            items_[i]->moveTo(targetGeom_[i]);
        }
        MvQFeatureHandler::Instance()->adjustSelection();
    }
}

void MvQFeatureMoveByCommand::undo()
{
    for (int i = 0; i < items_.size(); i++) {
        items_[i]->moveTo(oriGeom_[i]);
    }
    MvQFeatureHandler::Instance()->adjustSelection();
}

bool MvQFeatureMoveByCommand::mergeWith(const QUndoCommand* other)
{
    if (match(other)) {
        auto o = static_cast<const MvQFeatureMoveByCommand*>(other);
        if (o && !o->start_) {
            for (int i = 0; i < items_.size(); i++) {
                targetGeom_[i] = o->targetGeom_[i];
            }
            return true;
        }
    }
    return false;
}

//==================================================
//
//  MvQFeatureResizeCommand
//
//==================================================

MvQFeatureResizeCommand::MvQFeatureResizeCommand(const QRectF& newRect, const QRectF& oriRect, bool start, MvQFeatureItemList items, QUndoCommand* parent) :
    MvQFeatureCommand(items, "resize", parent),
    start_(start)
{
    // bool validDelta = fabs(delta.x()) > 1E-10 || fabs(delta.y()) > 1E-10;
    for (auto item : items_) {
        oriGeom_ << item->getGeometry();
        item->adjustSizeToSelector(newRect, oriRect);
        targetGeom_ << item->getGeometry();
    }
}

void MvQFeatureResizeCommand::redo()
{
    if (firstCall_) {
        firstCall_ = false;
    }
    else {
        for (int i = 0; i < items_.size(); i++) {
            items_[i]->resizeTo(targetGeom_[i]);
        }
        MvQFeatureHandler::Instance()->adjustSelection();
    }
}

void MvQFeatureResizeCommand::undo()
{
    for (int i = 0; i < items_.size(); i++) {
        items_[i]->resizeTo(oriGeom_[i]);
    }
    MvQFeatureHandler::Instance()->adjustSelection();
}

bool MvQFeatureResizeCommand::mergeWith(const QUndoCommand* other)
{
    if (match(other)) {
        auto o = static_cast<const MvQFeatureResizeCommand*>(other);
        if (o && !o->start_) {
            for (int i = 0; i < items_.size(); i++) {
                targetGeom_[i] = o->targetGeom_[i];
            }
            return true;
        }
    }
    return false;
}

//==================================================
//
//  MvQFeatureHandler
//
//==================================================

MvQFeatureHandler* MvQFeatureHandler::Instance()
{
    if (!instance_) {
        instance_ = new MvQFeatureHandler();
    }
    return instance_;
}

void MvQFeatureHandler::setView(MvQPlotView* v)
{
    view_ = v;
}

void MvQFeatureHandler::setUndoStack(QUndoStack* s)
{
    undoStack_ = s;
}

void MvQFeatureHandler::setRibbonEditor(MvQFeatureRibbonEditor* e)
{
    ribbonEditor_ = e;
}

void MvQFeatureHandler::setInfoWidget(QPlainTextEdit* w)
{
    infoWidget_ = w;
}

void MvQFeatureHandler::init()
{
    if (!initDone_) {
        Q_ASSERT(view_);
        if (!selector_) {
            selector_ = new MvQFeatureSelector(
                view_, this, rootItem());
        }
        // this will create the ribbon editor
        view_->initFeatureInterface();
        Q_ASSERT(ribbonEditor_);
        initDone_ = true;
    }
}

void MvQFeatureHandler::updateFeatureInfo(MvQFeatureItemPtr item)
{
    if (infoWidget_ && item) {
        infoWidget_->clear();
        infoWidget_->appendHtml(item->describe());
    }
}

QGraphicsItem* MvQFeatureHandler::rootItem() const
{
    return view_->plotScene_->annotationRootItem();
}

void MvQFeatureHandler::clearSelection()
{
    if (selector_) {
        selector_->clear();
    }
}

void MvQFeatureHandler::checkSelection()
{
    if (selector_) {
        selector_->selectionChanged();
        if (ribbonEditor_) {
            if (auto item = selector_->firstItem()) {
                ribbonEditor_->edit(item);
            }
            else if (!textEditor_) {
                ribbonEditor_->finishEdit();
            }
        }
        if (infoWidget_) {
            updateFeatureInfo(selector_->firstItem());
        }
    }
}

void MvQFeatureHandler::adjustSelection()
{
    if (selector_) {
        selector_->adjustToItems();
    }
}

MgQLayoutItem* MvQFeatureHandler::layoutItemAt(const QPointF& scPos) const
{
    MgQSceneItem* sceneItem = nullptr;
    MgQLayoutItem* layoutItem = nullptr;
    if (view_->plotScene_->identifyPos(scPos, &sceneItem, &layoutItem)) {
        layoutItem = sceneItem->firstProjectorItem();
    }
    else if (view_->plotScene_->sceneItems().size() == 1) {
        layoutItem = view_->plotScene_->sceneItems()[0]->firstProjectorItem();
    }
    return layoutItem;
}

bool MvQFeatureHandler::isOutOfView(const QPointF& scPos, MgQLayoutItem* layoutItem) const
{
    bool outOfView = true;
    if (layoutItem) {
        QPointF coord;
        layoutItem->mapFromSceneToGeoCoords(scPos, coord);
        QPointF scPosA = scPos;
        if (layoutItem->containsSceneCoords(scPosA) && MvQFeatureItem::isValidPoint(coord)) {
            outOfView = false;
        }
    }
    return outOfView;
}

void MvQFeatureHandler::currentLayout(MvQFeatureItemPtr item, MgQLayoutItem** layoutItem) const
{
    QPointF scPos = item->scenePos();
    MgQSceneItem* sceneItem = nullptr;
    *layoutItem = nullptr;
    if (view_->plotScene_->identifyPos(scPos, &sceneItem, layoutItem)) {
        *layoutItem = sceneItem->firstProjectorItem();
    }
    else if (view_->plotScene_->sceneItems().size() == 1) {
        *layoutItem = view_->plotScene_->sceneItems()[0]->firstProjectorItem();
    }
}

bool MvQFeatureHandler::canBeAddedToScene(MvQFeatureItemPtr item, const QPointF& scPos, MgQLayoutItem** layoutItem) const
{
    *layoutItem = layoutItemAt(scPos);
    if ((!*layoutItem || isOutOfView(scPos, *layoutItem)) &&
        !item->canBeCreatedOutOfView()) {
        return false;
    }
    return true;
}

bool MvQFeatureHandler::canBePastedToScene(MvQFeatureItemPtr item, const QPointF& scPos, MgQLayoutItem** layoutItem) const
{
    *layoutItem = layoutItemAt(scPos);
    if ((!*layoutItem || isOutOfView(scPos, *layoutItem)) &&
        !item->canBePastedOutOfView()) {
        return false;
    }
    return true;
}

bool MvQFeatureHandler::hasLayout() const
{
    if (view_ && view_->plotScene_->sceneItems().size() >= 1) {
        return view_->plotScene_->sceneItems()[0]->firstProjectorItem() != nullptr;
    }
    return false;
}

// add from json using the stored geocoordinates
void MvQFeatureHandler::registerToAddAtClick(WsType* wst)
{
    init();
    featureToAddAtClick_.reset();
    featureToAddAtClick_ = std::make_shared<FeatureAddSpec>(wst);
    view_->enterFeatureAddMode();
    clearSelection();
}

// add from json using the stored geocoordinates
void MvQFeatureHandler::registerToAddAtClick(const QJsonDocument& doc)
{
    init();
    featureToAddAtClick_.reset();
    featureToAddAtClick_ = std::make_shared<FeatureAddSpec>(doc);
    view_->enterFeatureAddMode();
    clearSelection();
}

// At item by clicking in the view. This works when there is an item
// registered to be added.
void MvQFeatureHandler::addFromView(QPoint pos)
{
    Q_ASSERT(initDone_);
    if (featureToAddAtClick_) {
        if (featureToAddAtClick_->mode == FeatureAddSpec::WstMode) {
            add(featureToAddAtClick_->wst, pos);
        }
        else {
            Q_ASSERT(view_);
            QPointF scPos = view_->mapToScene(pos);
            paste(featureToAddAtClick_->doc, scPos);
        }
        featureToAddAtClick_.reset();
    }
}

// Add item using the stored geocoordinates.
void MvQFeatureHandler::addFromJson(const QJsonDocument& doc)
{
    init();
    if (doc.isObject()) {
        addFromJson(doc.object());
    }
    else if (doc.isArray()) {
        auto a = doc.array();
        for (auto v : a) {
            addFromJson(v.toObject());
        }
    }
}

// Add item using the stored geocoordinates.
void MvQFeatureHandler::addFromJson(const QJsonArray& a)
{
    init();
    for (auto v : a) {
        addFromJson(v.toObject());
    }
}

// Add item using the stored geocoordinates.
void MvQFeatureHandler::addFromJson(const QJsonObject& obj)
{
    init();
    if (auto item = MvQFeatureFactory::create(obj)) {
#ifdef FEATURECOMMAND_DEBUG_
        MvLog().dbg() << "Added obj=" << obj;
        MvLog().dbg() << "Added coord=" << item->geoCoord();
#endif
        QPointF scPos;
        MgQLayoutItem* layoutItem = layoutItemAt(scPos);
        addToScene(item);
        // we simply reproject the item since it has proper geocords
        item->projectionChanged(layoutItem, true);
        undoStack_->push(new MvQFeatureAddCommand({item}));
    }
}

// This can come from a drop or from the direct visualisation of a feature icon
// in Desktop
void MvQFeatureHandler::addFromRequest(const MvRequest& rIn)
{
#ifdef FEATURECOMMAND_DEBUG_
    MvLog().dbg() << MV_FN_INFO;
#endif
    init();
    MvRequest r = ObjectList::ExpandRequest(rIn, EXPAND_DEFAULTS);
#ifdef FEATURECOMMAND_DEBUG_
    r.print();
#endif

    // When we directly visualise an icon it is possible that the
    // geoview scene is not yet constructed, i.e. we do not have a
    // layout. In this case we store the item doc and only try to add it
    // when the first reset ends!

    if (const char* ch = r.getVerb()) {
        if (strcmp(ch, "WS_COLLECTION") == 0) {
#ifdef FEATURECOMMAND_DEBUG_
            MvLog().dbg() << "collection";
#endif
            if (const char* chPath = r("PATH")) {
                QJsonArray a;
                WsDecoder::readCollection(QString(chPath), a);
                // MvLog().dbg() <<"array=" << a.size();
                if (a.size() > 0) {
                    if (hasLayout()) {
                        addFromJson(a);
                    }
                    else {
                        featureToAddAtGeo_.reset();
                        QJsonDocument d;
                        d.setArray(a);
                        featureToAddAtGeo_ = std::make_shared<FeatureAddSpec>(d);
                    }
                }
            }
            return;
        }
        else {
            QJsonDocument doc;
            WsDecoder::toJson(r, doc);
#ifdef FEATURECOMMAND_DEBUG_
            MvLog().dbg() << "json=" << QString(doc.toJson());
#endif
            if (!doc.isEmpty() && doc.isObject()) {
                if (hasLayout()) {
                    addFromJson(doc.object());
                }
                else {
                    featureToAddAtGeo_.reset();
                    featureToAddAtGeo_ = std::make_shared<FeatureAddSpec>(doc);
                }
            }
        }
    }
}

// Add item specified by its type at view pos
void MvQFeatureHandler::add(WsType* feature, QPoint pos)
{
    init();
    if (feature) {
        QPointF scPos = view_->mapToScene(pos);
        if (auto item = MvQFeatureFactory::create(feature)) {
            MgQLayoutItem* layoutItem = nullptr;
            if (!canBeAddedToScene(item, scPos, &layoutItem)) {
                MvLog().popup().warn() << "Cannot create " << item->typeName() << " items outside the view area!";
                return;
            }
            addToScene(item);
            // place the item at the given scene position (the geocoords will be
            // determined from the scene position using the projection). At
            // this point all the curves are empty and the users will create their control
            // points interactivaley.
            item->initScenePosition(layoutItem, scPos);
            undoStack_->push(new MvQFeatureAddCommand({item}));
            item->initContents();
        }
    }
}

MvQFeatureItemList MvQFeatureHandler::performDuplicate(MvQFeatureItemList items)
{
    MvQFeatureItemList resItems;
    bool first = true;
    for (auto& item : items) {
        if (auto cl = MvQFeatureFactory::clone(item)) {
            QPointF offset(20, 20);
            auto scPos = item->scenePos() + offset;
            MgQLayoutItem* layoutItem = nullptr;
            if (canBePastedToScene(cl, scPos, &layoutItem)) {
                if (first) {
                    clearSelection();
                    first = false;
                }
                addToScene(cl);
                cl->initScenePosition(layoutItem, scPos);
                resItems << cl;
            }
            else {
                // delete cl;
                resItems << nullptr;
            }
        }
        else {
            resItems << cl;
        }
    }
    return resItems;
}
// Paste a set of items at the given scene pos.
void MvQFeatureHandler::paste(const QJsonDocument& doc, const QPointF& scPos)
{
    init();
    QJsonDocument docArr;
    Q_ASSERT(selector_);

    // make sure we have an array of json objects (each representing an item)
    if (!doc.isArray()) {
        QJsonArray a{doc.object()};
        docArr.setArray(a);
    }
    else {
        docArr = doc;
    }

    if (docArr.isArray()) {
        clearSelection();
        QPointF firstScPos;
        MvQFeatureItemList items;
        for (auto v : docArr.array()) {
            auto obj = v.toObject();
            if (auto item = MvQFeatureFactory::create(obj)) {
                MgQLayoutItem* layoutItem = nullptr;
                if (canBePastedToScene(item, scPos, &layoutItem)) {
                    // determine offset relative to paste scene position
                    auto objScPos = WsDecoder::scenePos(obj);
                    if (items.size() == 0) {
                        firstScPos = objScPos;
                    }
                    QPointF delta = objScPos - firstScPos;
                    addToScene(item);
                    // place the item at the given scene position (the geocoords will be
                    // updated from the scene position using the projection). At
                    // this point all the curves are fully defined and all their control
                    // points have a proper geocoord to be updated.
                    item->initScenePosition(layoutItem, scPos + delta);
                    items << item;
                }
            }
        }

        if (items.size() > 0) {
            undoStack_->push(new MvQFeatureAddCommand(items));
        }
    }
}

// This must be the only place where we use a raw pointer to an item!
// Everywhere else a shared pointer must be used!!!!
void MvQFeatureHandler::removeFromScene(MvQFeatureItemList items)
{
    Q_ASSERT(selector_);
    selector_->clear();
    selector_->suspend(true);
    for (auto item : items) {
        if (item) {
            view_->scene()->removeItem(item.get());
            features_.removeOne(item);
            // NOTE: after calling removeItem the item is still selected!
        }
    }
    selector_->suspend(false);
    // TODO: do we need to call it?
    checkSelection();
}

void MvQFeatureHandler::addToScene(MvQFeatureItemPtr item)
{
    addToScene(MvQFeatureItemList{item});
}

void MvQFeatureHandler::addToScene(MvQFeatureItemList items, bool visible)
{
    for (auto item : items) {
        if (item && !features_.contains(item)) {
            item->setParentItem(rootItem());
            if (visible) {
                item->setVisible(true);
            }
            features_ << item;
        }
    }
}

void MvQFeatureHandler::addFromClipboard(const QPointF& scenePos)
{
    auto m = QGuiApplication::clipboard()->mimeData();
    if (m && m->formats().contains("metview/feature")) {
        const auto* mimeData =
            static_cast<const MvQFeatureMimeData*>(m);
        if (mimeData) {
            //            if (mimeData->clipboardAction() == MvQFeatureMimeData::CopyAction) {
            if (true) {
                auto ba = mimeData->data("metview/feature");
                auto doc = QJsonDocument::fromJson(ba);
                paste(doc, scenePos);
            }
        }
    }
}

void MvQFeatureHandler::runCommand(const MvQFeatureCommandTarget& sel, QString cmd)
{
#ifdef FEATURECOMMAND_DEBUG_
    qDebug() << "MvQFeatureHandler::runCommand:" << cmd << sel.scenePos();
#endif
    if (cmd.startsWith("extra:")) {
        //        int idx = cmd.mid(6).toInt();
        // return (idx >=0 && idx < topLevelActions.size())?topLevelActions[idx]:nullptr;
    }
    else if (!cmd.isEmpty()) {
        if (cmd == "paste") {
            addFromClipboard(sel.scenePos());
        }
        else if (cmd == "save_scene") {
            saveSceneToDisk(sel.scenePos());
        }
        else {
#ifdef FEATURECOMMAND_DEBUG_
            qDebug() << " tagetItem:" << sel.targetItem();
#endif
            if (sel.targetItem()) {
                handleItemCommand(sel.targetItem(), cmd, sel.nodeIndex(), sel.scenePos());
            }
            else if (sel.inSelector()) {
                Q_ASSERT(selector_);
#ifdef FEATURECOMMAND_DEBUG_
                qDebug() << " selector handles command!";
#endif
                handleSelectorCommand(cmd, sel.scenePos());
            }
        }
    }
}

void MvQFeatureHandler::handleItemCommand(MvQFeatureItemPtr targetItem, QString cmd, int nodeIndex, const QPointF& /*scenePos*/)
{
    // item commands
    if (nodeIndex == -1) {
        if (cmd == "edit") {
            targetItem->editStyle();
        }
        else if (cmd == "save_user") {
            FeatureStore::Instance()->add(targetItem);
        }
        else if (cmd == "save_disc") {
            saveItemToDisk(targetItem);
        }
        else if (cmd == "surface" || cmd == "upper" || cmd == "frontogenesis" || cmd == "frontolysis") {
            undoStack_->push(new MvQFeatureFrontSubTypeCommand({targetItem}, cmd));
        }
        else {
            auto obj = MvQFeatureCommandFactory::create(cmd, targetItem);
            if (obj) {
                undoStack_->push(obj);
            }
        }
        // (curve) node commands
    }
    else {
        if (cmd == "add_before" || cmd == "add_after") {
            undoStack_->push(new MvQFeatureAddNodeCommand(
                nodeIndex, cmd == "add_before", targetItem));
        }
        else if (cmd == "delete") {
            undoStack_->push(new MvQFeatureRemoveNodeCommand(
                nodeIndex, targetItem));
        }
    }

    updateFeatureInfo(targetItem);
}

void MvQFeatureHandler::handleSelectorCommand(QString cmd, const QPointF& /*scenePos*/)
{
    if (cmd == "duplicate") {
        undoStack_->push(new MvQFeatureDuplicateCommand(selector_->allItems()));
    }
    else if (cmd == "copy") {
        undoStack_->push(new MvQFeatureCopyCommand(selector_->allItems()));
    }
    else if (cmd == "cut") {
        undoStack_->push(new MvQFeatureCutCommand(selector_->allItems()));
    }
    else if (cmd == "delete") {
        undoStack_->push(new MvQFeatureRemoveCommand(selector_->allItems()));
    }
}

void MvQFeatureHandler::identifyComandTarget(MvQFeatureCommandTarget* ct)
{
    if (selector_ && !selector_->identifyCommandTarget(ct)) {
        for (auto item : features_) {
            if (item->identifyCommandTarget(ct)) {
                break;
            }
        }
    }
}

void MvQFeatureHandler::swapByZValue(MvQFeatureItemPtr item, float z)
{
    if (!item->isPoint()) {
        const float eps = MvQFeatureItem::zValueIncrement() / 1000;
        for (auto v : features_) {
            if (!v->isPoint() && fabs(v->realZValue() - z) < eps) {
                auto zThis = item->realZValue();
                v->setRealZValue(zThis);
                item->setRealZValue(z);
                return;
            }
        }
        item->setRealZValue(z);
    }
}

void MvQFeatureHandler::updateStyle(MvQFeatureItemPtr item, const MvRequest& req, StyleEditInfo info)
{
    undoStack_->push(new MvQFeatureStyleCommand(
        req, info, item));
}

void MvQFeatureHandler::setText(MvQFeatureItemPtr item, QString text)
{
    undoStack_->push(new MvQFeatureTextCommand(
        text, item));
}

void MvQFeatureHandler::editText(MvQFeatureItemPtr itemBase)
{
    auto item = std::dynamic_pointer_cast<MvQFeatureTextItem>(itemBase);
    if (item && !textEditor_) {
#ifdef FEATURECOMMAND_DEBUG_
        qDebug() << "create editor";
#endif
        textEditor_ = new MvQFeatureTextEditor(item);
        view_->scene()->addItem(textEditor_);
#ifdef FEATURECOMMAND_DEBUG_
        qDebug() << "editor pos=" << item->pos();
        qDebug() << "item pos:" << item->pos() << "textpos:" << item->textPos();
#endif
        textEditor_->initPos();
        textEditor_->grabMouse();
        view_->scene()->setFocusItem(textEditor_, Qt::MouseFocusReason);
        textEditor_->editor()->setFocus(Qt::MouseFocusReason);
        ribbonEditor_->edit(item);
    }
}

void MvQFeatureHandler::removeTextEditor()
{
    if (textEditor_) {
        textEditor_->finish();
        view_->scene()->removeItem(textEditor_);
        textEditor_->deleteLater();
        textEditor_ = nullptr;
        ribbonEditor_->finishEdit();
    }
}

// close feature text editor if we click outside the editor
bool MvQFeatureHandler::checkMousePressForTextEditor(const QPointF& scenePos)
{
    if (textEditor_) {
        if (!textEditor_->containsScenePos(scenePos)) {
            removeTextEditor();
            return true;
        }
    }
    return false;
}

void MvQFeatureHandler::checkMouseMove(QMouseEvent* viewEvent)
{
    if (textEditor_ && view_) {
        textEditor_->adjustCursorToMouseLeave(viewEvent);
    }
}

void MvQFeatureHandler::dragTextEditor(MvQFeatureTextEditor* editor)
{
    Q_ASSERT(textEditor_ == editor);
    if (textEditor_ == editor) {
        if (textEditor_->item()) {
            auto item = textEditor_->item();
            removeTextEditor();
            item->setSelected(true);
        }
    }
}

void MvQFeatureHandler::moveNode(MvQFeatureItemPtr curve, const QPointF& pp, int nodeIndex, bool start)
{
    undoStack_->push(new MvQFeatureMoveNodeCommand(
        pp, nodeIndex, start, curve));
    updateFeatureInfo(curve);
}

void MvQFeatureHandler::moveBy(MvQFeatureItemList items, const QPointF& delta, bool start)
{
    if (std::all_of(items.cbegin(), items.cend(), [](auto v) { return v->isGeoLocked(); })) {
        return;
    }

    undoStack_->push(new MvQFeatureMoveByCommand(
        delta, start, items));
    if (items.size() > 0) {
        updateFeatureInfo(items[0]);
    }
}

void MvQFeatureHandler::resize(MvQFeatureItemList items, const QRectF& newRect, const QRectF& oriRect, bool start)
{
    undoStack_->push(new MvQFeatureResizeCommand(
        newRect, oriRect, start, items));

    if (items.size() > 0) {
        updateFeatureInfo(items[0]);
    }
}

void MvQFeatureHandler::resetBegin()
{
    clearSelection();
    // TODO: this should only be done on zoom or when the projection
    // changes.
    if (undoStack_) {
        undoStack_->clear();
    }
    for (auto sym : features_) {
        sym->prepareForProjectionChange();
    }
}

void MvQFeatureHandler::resetEnd()
{
    for (auto sym : features_) {
        foreach (MgQSceneItem* item, view_->plotScene_->sceneItems()) {
            MgQLayoutItem* projItem = item->firstProjectorItem();
            if (item->layout().id() == sym->sceneId()) {
                sym->projectionChanged(projItem, false);
                break;
            }
            else if (item->layout().id().empty() && projItem &&
                     sym->sceneId() == projItem->layout().id()) {
                sym->projectionChanged(item, false);
                break;
            }
        }
    }

    if (featureToAddAtGeo_ && featureToAddAtGeo_->mode == FeatureAddSpec::DocMode && hasLayout()) {
        addFromJson(featureToAddAtGeo_->doc);
        featureToAddAtGeo_.reset();
    }
}

bool MvQFeatureHandler::canAcceptMouseEvent() const
{
    return (view_) ? (!view_->inFeatureAddMode()) : false;
}

void MvQFeatureHandler::setDragCursor() const
{
    if (view_ && canAcceptMouseEvent()) {
        view_->viewport()->setCursor(QCursor(Qt::SizeAllCursor));
    }
}

void MvQFeatureHandler::setCurvePointCursor() const
{
    if (view_ && canAcceptMouseEvent()) {
        view_->viewport()->setCursor(QCursor(Qt::CrossCursor));
    }
}

void MvQFeatureHandler::resetCursor() const
{
    if (view_ && canAcceptMouseEvent()) {
        view_->viewport()->setCursor(QCursor());
    }
}

void MvQFeatureHandler::saveItemToDisk(MvQFeatureItemPtr item)
{
    QJsonObject obj;
    item->toJson(obj);
    QJsonDocument doc;
    doc.setObject(obj);
    saveToDisk(doc);
}

void MvQFeatureHandler::saveSceneToDisk(const QPointF& scPos)
{
    if (auto layout = layoutItemAt(scPos)) {
        QJsonArray a;
        for (auto item : features_) {
            if (item->sceneId() == layout->layout().id()) {
                QJsonObject obj;
                item->toJson(obj);
                a << obj;
            }
        }
        if (!a.isEmpty()) {
            QJsonDocument doc;
            WsDecoder::toJsonCollection(a, doc);
            saveToDisk(doc);
        }
    }
}

void MvQFeatureHandler::saveToDisk(const QJsonDocument& doc)
{
    QString startDir = QString::fromStdString(PlotMod::Instance().currentWorkDir());
    MvQFileDialog dialog(startDir, "Save weather symbol to disk");
    dialog.setAcceptMode(QFileDialog::AcceptSave);
    //    dialog.setFileMode(QFileDialog::AnyFile);
    dialog.setLabelText(QFileDialog::Accept, "Save");

    if (dialog.exec() == QDialog::Accepted) {
        QStringList lst = dialog.selectedFiles();
        if (lst.count() > 0) {
            QFile f(lst[0]);
            if (f.open(QIODevice::WriteOnly | QIODevice::Text)) {
                f.write(doc.toJson());
            }
        }
    }
}

static MvQFeatureCommandMaker<MvQFeatureRemoveCommand> cmdMaker1("delete");
static MvQFeatureCommandMaker<MvQFeatureDuplicateCommand> cmdMaker2("duplicate");
static MvQFeatureCommandMaker<MvQFeatureCopyCommand> cmdMaker3("copy");
static MvQFeatureCommandMaker<MvQFeatureCutCommand> cmdMaker4("cut");
static MvQFeatureCommandMaker<MvQFeatureToFrontCommand> cmdMaker5("to_front");
static MvQFeatureCommandMaker<MvQFeatureToForwardCommand> cmdMaker6("to_forward");
static MvQFeatureCommandMaker<MvQFeatureToBackCommand> cmdMaker7("to_back");
static MvQFeatureCommandMaker<MvQFeatureToBackwardCommand> cmdMaker8("to_backward");
static MvQFeatureCommandMaker<MvQFeatureFlipCommand> cmdMaker9("flip");
