// SPDX-License-Identifier: LGPL-2.1-or-later
//
// SPDX-FileCopyrightText: 2009 Andrew Manson <g.real.ate@gmail.com>
// SPDX-FileCopyrightText: 2013 Thibaut Gridel <tgridel@free.fr>
// SPDX-FileCopyrightText: 2014 Calin Cruceru <crucerucalincristian@gmail.com>
//

// Self
#include "AnnotatePlugin.h"

// Qt
#include <QAction>
#include <QActionGroup>
#include <QApplication>
#include <QColor>
#include <QFileDialog>
#include <QMessageBox>
#include <QtAlgorithms>

// Marble
#include "AreaAnnotation.h"
#include "DownloadOsmDialog.h"
#include "EditGroundOverlayDialog.h"
#include "EditPlacemarkDialog.h"
#include "EditPolygonDialog.h"
#include "EditPolylineDialog.h"
#include "GeoDataDocument.h"
#include "GeoDataLabelStyle.h"
#include "GeoDataLatLonAltBox.h"
#include "GeoDataLatLonBox.h"
#include "GeoDataLineStyle.h"
#include "GeoDataLinearRing.h"
#include "GeoDataParser.h"
#include "GeoDataPlacemark.h"
#include "GeoDataPoint.h"
#include "GeoDataPolyStyle.h"
#include "GeoDataPolygon.h"
#include "GeoDataStyle.h"
#include "GeoDataTreeModel.h"
#include "GeoPainter.h"
#include "GeoWriter.h"
#include "KmlElementDictionary.h"
#include "MarbleDebug.h"
#include "MarbleModel.h"
#include "MarblePlacemarkModel.h"
#include "MarbleWidget.h"
#include "MarbleWidgetPopupMenu.h"
#include "MergingPolygonNodesAnimation.h"
#include "MergingPolylineNodesAnimation.h"
#include "ParsingRunnerManager.h"
#include "PlacemarkTextAnnotation.h"
#include "PolylineAnnotation.h"
#include "SceneGraphicsTypes.h"
#include "TextureLayer.h"
#include "ViewportParams.h"
#include "osm/OsmPlacemarkData.h"

namespace Marble
{

AnnotatePlugin::AnnotatePlugin(const MarbleModel *model)
    : RenderPlugin(model)
    , m_isInitialized(false)
    , m_widgetInitialized(false)
    , m_marbleWidget(nullptr)
    , m_overlayRmbMenu(nullptr)
    , m_polygonRmbMenu(nullptr)
    , m_nodeRmbMenu(nullptr)
    , m_textAnnotationRmbMenu(nullptr)
    , m_polylineRmbMenu(nullptr)
    , m_annotationDocument(nullptr)
    , m_movedItem(nullptr)
    , m_focusItem(nullptr)
    , m_polylinePlacemark(nullptr)
    , m_polygonPlacemark(nullptr)
    , m_clipboardItem(nullptr)
    , m_drawingPolygon(false)
    , m_drawingPolyline(false)
    , m_addingPlacemark(false)
    , m_editingDialogIsShown(false)
{
    setEnabled(true);
    setVisible(true);
    connect(this, &RenderPlugin::visibilityChanged, this, &AnnotatePlugin::enableModel);
}

AnnotatePlugin::~AnnotatePlugin()
{
    qDeleteAll(m_graphicsItems);
    if (m_marbleWidget) {
        m_marbleWidget->model()->treeModel()->removeDocument(m_annotationDocument);
        disconnect(this, SIGNAL(mouseMoveGeoPosition(QString)), m_marbleWidget, SIGNAL(mouseMoveGeoPosition(QString)));
    }

    delete m_overlayRmbMenu;
    delete m_polygonRmbMenu;
    delete m_nodeRmbMenu;
    delete m_textAnnotationRmbMenu;
    delete m_polylineRmbMenu;

    delete m_annotationDocument;
    // delete m_networkAccessManager;

    delete m_clipboardItem;
    qDeleteAll(m_actions);
}

QStringList AnnotatePlugin::backendTypes() const
{
    return QStringList(QStringLiteral("annotation"));
}

QString AnnotatePlugin::renderPolicy() const
{
    return QStringLiteral("ALWAYS");
}

QStringList AnnotatePlugin::renderPosition() const
{
    return QStringList(QStringLiteral("ALWAYS_ON_TOP"));
}

QString AnnotatePlugin::name() const
{
    return tr("Annotation");
}

QString AnnotatePlugin::guiString() const
{
    return tr("&Annotation");
}

QString AnnotatePlugin::nameId() const
{
    return QStringLiteral("annotation");
}

QString AnnotatePlugin::description() const
{
    return tr("Draws annotations on maps with placemarks or polygons.");
}

QString AnnotatePlugin::version() const
{
    return QStringLiteral("1.0");
}

QString AnnotatePlugin::copyrightYears() const
{
    return QStringLiteral("2009, 2013");
}

QList<PluginAuthor> AnnotatePlugin::pluginAuthors() const
{
    return QList<PluginAuthor>() << PluginAuthor(QStringLiteral("Andrew Manson"), QStringLiteral("g.real.ate@gmail.com"))
                                 << PluginAuthor(QStringLiteral("Thibaut Gridel"), QStringLiteral("tgridel@free.fr"))
                                 << PluginAuthor(QStringLiteral("Calin Cruceru"), QStringLiteral("crucerucalincristian@gmail.com"));
}

QIcon AnnotatePlugin::icon() const
{
    return QIcon(QStringLiteral(":/icons/draw-placemark.png"));
}

void AnnotatePlugin::initialize()
{
    if (!m_isInitialized) {
        m_widgetInitialized = false;

        delete m_polygonPlacemark;
        m_polygonPlacemark = nullptr;

        delete m_movedItem;
        m_movedItem = nullptr;

        m_drawingPolygon = false;
        m_drawingPolyline = false;
        m_addingPlacemark = false;

        delete m_annotationDocument;
        m_annotationDocument = new GeoDataDocument;

        m_annotationDocument->setName(tr("Annotations"));
        m_annotationDocument->setDocumentRole(UserDocument);

        // Default polygon style
        GeoDataStyle::Ptr defaultPolygonStyle(new GeoDataStyle);
        GeoDataPolyStyle polyStyle;
        GeoDataLineStyle edgeStyle;
        GeoDataLabelStyle labelStyle;
        QColor polygonColor = QApplication::palette().highlight().color();
        QColor edgeColor = QApplication::palette().light().color();
        QColor labelColor = QApplication::palette().brightText().color();
        polygonColor.setAlpha(80);
        polyStyle.setColor(polygonColor);
        edgeStyle.setColor(edgeColor);
        labelStyle.setColor(labelColor);
        defaultPolygonStyle->setId(QStringLiteral("polygon"));
        defaultPolygonStyle->setPolyStyle(polyStyle);
        defaultPolygonStyle->setLineStyle(edgeStyle);
        defaultPolygonStyle->setLabelStyle(labelStyle);
        m_annotationDocument->addStyle(defaultPolygonStyle);

        // Default polyline style
        GeoDataStyle::Ptr defaultPolylineStyle(new GeoDataStyle);
        GeoDataLineStyle lineStyle;
        QColor polylineColor = Qt::white;
        lineStyle.setColor(polylineColor);
        lineStyle.setWidth(1);
        defaultPolylineStyle->setId(QStringLiteral("polyline"));
        defaultPolylineStyle->setLineStyle(lineStyle);
        defaultPolylineStyle->setLabelStyle(labelStyle);
        m_annotationDocument->addStyle(defaultPolylineStyle);

        m_isInitialized = true;
    }
}

bool AnnotatePlugin::isInitialized() const
{
    return m_isInitialized;
}

QString AnnotatePlugin::runtimeTrace() const
{
    return QStringLiteral("Annotate Items: %1").arg(m_annotationDocument->size());
}

const QList<QActionGroup *> *AnnotatePlugin::actionGroups() const
{
    return &m_actions;
}

bool AnnotatePlugin::render(GeoPainter *painter, ViewportParams *viewport, const QString &renderPos, GeoSceneLayer *layer)
{
    Q_UNUSED(renderPos);
    Q_UNUSED(layer);

    QListIterator<SceneGraphicsItem *> iter(m_graphicsItems);
    while (iter.hasNext()) {
        iter.next()->paint(painter, viewport, QStringLiteral("Annotation"), -1);
    }

    return true;
}

void AnnotatePlugin::enableModel(bool enabled)
{
    if (enabled) {
        if (m_marbleWidget) {
            setupActions(m_marbleWidget);
            m_marbleWidget->model()->treeModel()->addDocument(m_annotationDocument);
        }
    } else {
        setupActions(nullptr);
        if (m_marbleWidget) {
            m_marbleWidget->model()->treeModel()->removeDocument(m_annotationDocument);
        }
    }
}

void AnnotatePlugin::setAddingPolygonHole(bool enabled)
{
    if (enabled) {
        announceStateChanged(SceneGraphicsItem::AddingPolygonHole);
    } else {
        announceStateChanged(SceneGraphicsItem::Editing);
    }
}

void AnnotatePlugin::setAddingNodes(bool enabled)
{
    if (enabled) {
        announceStateChanged(SceneGraphicsItem::AddingNodes);
    } else {
        announceStateChanged(SceneGraphicsItem::Editing);
    }
}

void AnnotatePlugin::setAreaAvailable()
{
    static_cast<AreaAnnotation *>(m_focusItem)->setBusy(false);
    announceStateChanged(SceneGraphicsItem::Editing);

    enableAllActions(m_actions.first());
    disableFocusActions();
    enableActionsOnItemType(QLatin1StringView(SceneGraphicsTypes::SceneGraphicAreaAnnotation));
    Q_EMIT repaintNeeded();
}

void AnnotatePlugin::setPolylineAvailable()
{
    static_cast<PolylineAnnotation *>(m_focusItem)->setBusy(false);
    announceStateChanged(SceneGraphicsItem::Editing);

    enableAllActions(m_actions.first());
    disableFocusActions();
    enableActionsOnItemType(QLatin1StringView(SceneGraphicsTypes::SceneGraphicPolylineAnnotation));
    Q_EMIT repaintNeeded();
}

void AnnotatePlugin::askToRemoveFocusItem()
{
    const int result = QMessageBox::question(m_marbleWidget,
                                             QObject::tr("Remove current item"),
                                             QObject::tr("Are you sure you want to remove the current item?"),
                                             QMessageBox::Yes | QMessageBox::No);
    if (result == QMessageBox::Yes) {
        removeFocusItem();
    }
}

void AnnotatePlugin::removeFocusItem()
{
    // Ground Overlays will always be a special case..
    if (m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicGroundOverlay) {
        for (int i = 0; i < m_groundOverlayModel.rowCount(); ++i) {
            const QModelIndex index = m_groundOverlayModel.index(i, 0);
            GeoDataGroundOverlay *overlay =
                dynamic_cast<GeoDataGroundOverlay *>(qvariant_cast<GeoDataObject *>(index.data(MarblePlacemarkModel::ObjectPointerRole)));

            m_marbleWidget->model()->treeModel()->removeFeature(overlay);
        }

        clearOverlayFrames();
    } else {
        disableFocusActions();

        m_graphicsItems.removeAll(m_focusItem);
        m_marbleWidget->model()->treeModel()->removeFeature(m_focusItem->placemark());

        delete m_focusItem->placemark();
        delete m_focusItem;
        m_movedItem = nullptr;
        m_focusItem = nullptr;
    }
}

void AnnotatePlugin::clearAnnotations()
{
    const int result = QMessageBox::question(m_marbleWidget,
                                             QObject::tr("Clear all annotations"),
                                             QObject::tr("Are you sure you want to clear all annotations?"),
                                             QMessageBox::Yes | QMessageBox::Cancel);

    if (result == QMessageBox::Yes) {
        disableFocusActions();
        qDeleteAll(m_graphicsItems);
        m_graphicsItems.clear();
        m_marbleWidget->model()->treeModel()->removeDocument(m_annotationDocument);
        m_annotationDocument->clear();
        m_marbleWidget->model()->treeModel()->addDocument(m_annotationDocument);

        m_movedItem = nullptr;
        m_focusItem = nullptr;
    }
}

void AnnotatePlugin::saveAnnotationFile()
{
    const QString filename = QFileDialog::getSaveFileName(nullptr,
                                                          tr("Save Annotation File"),
                                                          QString(),
                                                          tr("All Supported Files (*.kml *.osm);;"
                                                             "KML file (*.kml);;"
                                                             "Open Street Map file (*.osm)"));
    if (!filename.isNull()) {
        GeoWriter writer;
        // FIXME: This should be consistent with the way the loading is done.
        if (filename.endsWith(QLatin1StringView(".kml"), Qt::CaseInsensitive)) {
            writer.setDocumentType(QLatin1StringView(kml::kmlTag_nameSpaceOgc22));
        } else if (filename.endsWith(QLatin1StringView(".osm"), Qt::CaseInsensitive)) {
            // "0.6" is the current version of osm, it is used to identify the osm writer
            // The reference value is kept in plugins/runner/osm/OsmElementDictionary.hz
            writer.setDocumentType(QStringLiteral("0.6"));
        }

        QFile file(filename);
        file.open(QIODevice::WriteOnly);
        if (!writer.write(&file, m_annotationDocument)) {
            mDebug() << "Could not write the file " << filename;
        }
        file.close();
    }
}

void AnnotatePlugin::loadAnnotationFile()
{
    const QString filename = QFileDialog::getOpenFileName(nullptr,
                                                          tr("Open Annotation File"),
                                                          QString(),
                                                          tr("All Supported Files (*.kml *.osm);;"
                                                             "Kml Annotation file (*.kml);;"
                                                             "Open Street Map file (*.osm)"));
    if (filename.isNull()) {
        return;
    } else {
        openAnnotationFile(filename);
    }
}

void AnnotatePlugin::downloadOsm()
{
    QPointer<DownloadOsmDialog> dialog = new DownloadOsmDialog(m_marbleWidget, this);
    dialog->show();
}

void AnnotatePlugin::openAnnotationFile(const QString &filename)
{
    if (filename.isNull()) {
        return;
    }
    ParsingRunnerManager manager(m_marbleWidget->model()->pluginManager(), this);
    GeoDataDocument *document = manager.openFile(filename);
    Q_ASSERT(document);

    // FIXME: The same problem as in the case of copying/cutting graphic items applies here:
    // the files do not load properly because the geometry copy is not a deep copy.
    const auto features = document->featureList();
    for (GeoDataFeature *feature : features) {
        if (const GeoDataPlacemark *placemark = geodata_cast<GeoDataPlacemark>(feature)) {
            auto newPlacemark = new GeoDataPlacemark(*placemark);

            if (geodata_cast<GeoDataPoint>(placemark->geometry())) {
                auto placemark = new PlacemarkTextAnnotation(newPlacemark);
                m_graphicsItems.append(placemark);
            } else if (geodata_cast<GeoDataPolygon>(placemark->geometry())) {
                newPlacemark->setParent(m_annotationDocument);
                if (!placemark->styleUrl().isEmpty()) {
                    newPlacemark->setStyleUrl(placemark->styleUrl());
                }
                auto polygonAnnotation = new AreaAnnotation(newPlacemark);
                m_graphicsItems.append(polygonAnnotation);
            } else if (geodata_cast<GeoDataLineString>(placemark->geometry())) {
                newPlacemark->setParent(m_annotationDocument);
                if (!placemark->styleUrl().isEmpty()) {
                    newPlacemark->setStyleUrl(placemark->styleUrl());
                }
                auto polylineAnnotation = new PolylineAnnotation(newPlacemark);
                m_graphicsItems.append(polylineAnnotation);
            }
            m_marbleWidget->model()->treeModel()->addFeature(m_annotationDocument, newPlacemark);
        } else if (const GeoDataGroundOverlay *overlay = geodata_cast<GeoDataGroundOverlay>(feature)) {
            auto newOverlay = new GeoDataGroundOverlay(*overlay);
            m_marbleWidget->model()->treeModel()->addFeature(m_annotationDocument, newOverlay);
            displayOverlayFrame(newOverlay);
        }
    }
    m_marbleWidget->centerOn(document->latLonAltBox());

    delete document;
    Q_EMIT repaintNeeded(QRegion());
}

bool AnnotatePlugin::eventFilter(QObject *watched, QEvent *event)
{
    if (!m_widgetInitialized) {
        auto marbleWidget = qobject_cast<MarbleWidget *>(watched);

        if (marbleWidget) {
            m_marbleWidget = marbleWidget;

            addContextItems();
            setupGroundOverlayModel();
            setupOverlayRmbMenu();
            setupPolygonRmbMenu();
            setupPolylineRmbMenu();
            setupNodeRmbMenu();
            setupTextAnnotationRmbMenu();
            setupActions(marbleWidget);

            m_marbleWidget->model()->treeModel()->addDocument(m_annotationDocument);
            m_widgetInitialized = true;

            connect(this, SIGNAL(mouseMoveGeoPosition(QString)), m_marbleWidget, SIGNAL(mouseMoveGeoPosition(QString)));

            return true;
        }
        return false;
    }

    // Accept mouse and key press events.
    if (event->type() != QEvent::MouseButtonPress && event->type() != QEvent::MouseButtonRelease && event->type() != QEvent::MouseMove
        && event->type() != QEvent::KeyPress && event->type() != QEvent::KeyRelease) {
        return false;
    }

    // Handle key press events.
    if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
        auto const keyEvent = static_cast<QKeyEvent *>(event);
        Q_ASSERT(keyEvent);

        if (m_focusItem
            && (m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicAreaAnnotation
                || m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicPolylineAnnotation)) {
            if (keyEvent->type() == QEvent::KeyPress && keyEvent->key() == Qt::Key_Control) {
                announceStateChanged(SceneGraphicsItem::MergingNodes);
            }

            if (keyEvent->type() == QEvent::KeyRelease && keyEvent->key() == Qt::Key_Control) {
                if ((m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicAreaAnnotation && static_cast<AreaAnnotation *>(m_focusItem)->isBusy())
                    || (m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicPolylineAnnotation
                        && static_cast<PolylineAnnotation *>(m_focusItem)->isBusy())) {
                    return true;
                }

                announceStateChanged(SceneGraphicsItem::Editing);
            }
        }

        // If we have an item which has the focus and the Escape key is pressed, set item's focus
        // to false.
        if (m_focusItem && keyEvent->type() == QEvent::KeyPress && keyEvent->key() == Qt::Key_Escape && !m_editingDialogIsShown) {
            disableFocusActions();
            m_focusItem->setFocus(false);
            m_marbleWidget->model()->treeModel()->updateFeature(m_focusItem->placemark());
            m_focusItem = nullptr;
            return true;
        }

        // If we have an item which has the focus and the Delete key is pressed, delete the item
        if (m_focusItem && keyEvent->type() == QEvent::KeyPress && keyEvent->key() == Qt::Key_Delete && !m_editingDialogIsShown) {
            askToRemoveFocusItem();
            return true;
        }

        return false;
    }

    // Handle mouse events.
    auto const mouseEvent = dynamic_cast<QMouseEvent *>(event);
    Q_ASSERT(mouseEvent);

    // Get the geocoordinates from mouse pos screen coordinates.
    qreal lon, lat;
    const bool isOnGlobe = m_marbleWidget->geoCoordinates(mouseEvent->pos().x(), mouseEvent->pos().y(), lon, lat, GeoDataCoordinates::Radian);
    if (!isOnGlobe) {
        return false;
    }

    // Deal with adding polygons and polylines.
    if ((m_drawingPolygon && handleDrawingPolygon(mouseEvent)) || (m_drawingPolyline && handleDrawingPolyline(mouseEvent))) {
        return true;
    }

    // It is important to deal with Ground Overlay mouse release event here because it uses the
    // texture layer in order to make the rendering more efficient.
    if (mouseEvent->type() == QEvent::MouseButtonRelease && m_groundOverlayModel.rowCount()) {
        handleReleaseOverlay(mouseEvent);
    }

    // It is important to deal with the MouseMove event here because it changes the state of the
    // selected item irrespective of the longitude/latitude the cursor moved to (excepting when
    // it is outside the globe, which is treated above).
    if (mouseEvent->type() == QEvent::MouseMove && m_movedItem && handleMovingSelectedItem(mouseEvent)) {
        setupCursor(m_movedItem);
        return true;
    }

    // Pass the event to Graphic Items.
    for (SceneGraphicsItem *item : std::as_const(m_graphicsItems)) {
        if (!item->containsPoint(mouseEvent->pos())) {
            continue;
        }

        // If an edit dialog is visible, do not permit right clicking on items.
        if (m_editingDialogIsShown && mouseEvent->type() == QEvent::MouseButtonPress && mouseEvent->button() == Qt::RightButton) {
            return true;
        }

        if (!item->hasFocus() && item->graphicType() != SceneGraphicsTypes::SceneGraphicGroundOverlay) {
            if (mouseEvent->type() == QEvent::MouseButtonPress && mouseEvent->button() == Qt::LeftButton) {
                item->setFocus(true);
                disableFocusActions();
                enableActionsOnItemType(QLatin1StringView(item->graphicType()));

                if (m_focusItem && m_focusItem != item) {
                    m_focusItem->setFocus(false);
                    if (m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicGroundOverlay) {
                        clearOverlayFrames();
                    }
                }
                m_focusItem = item;
                m_marbleWidget->model()->treeModel()->updateFeature(item->placemark());
                return true;
            }

            return false;
        }

        if (item->sceneEvent(event)) {
            setupCursor(item);

            if (mouseEvent->type() == QEvent::MouseButtonPress) {
                handleSuccessfulPressEvent(mouseEvent, item);
            } else if (mouseEvent->type() == QEvent::MouseMove) {
                handleSuccessfulHoverEvent(mouseEvent, item);
            } else if (mouseEvent->type() == QEvent::MouseButtonRelease) {
                handleSuccessfulReleaseEvent(mouseEvent, item);
            }

            handleRequests(mouseEvent, item);
            return true;
        }
    }

    // If the event gets here, it most probably means it is a map interaction event, or something
    // that has nothing to do with the annotate plugin items. We "deal" with this situation because,
    // for example, we may need to deselect some selected items.
    handleUncaughtEvents(mouseEvent);

    return false;
}

bool AnnotatePlugin::handleDrawingPolygon(QMouseEvent *mouseEvent)
{
    const GeoDataCoordinates coords = mouseGeoDataCoordinates(mouseEvent);

    if (mouseEvent->type() == QEvent::MouseMove) {
        setupCursor(nullptr);

        Q_EMIT mouseMoveGeoPosition(coords.toString());

        return true;
    } else if (mouseEvent->button() == Qt::LeftButton && mouseEvent->type() == QEvent::MouseButtonPress) {
        m_marbleWidget->model()->treeModel()->removeFeature(m_polygonPlacemark);
        auto poly = dynamic_cast<GeoDataPolygon *>(m_polygonPlacemark->geometry());
        poly->outerBoundary().append(coords);
        m_marbleWidget->model()->treeModel()->addFeature(m_annotationDocument, m_polygonPlacemark);
        Q_EMIT nodeAdded(coords);

        return true;
    }

    return false;
}

bool AnnotatePlugin::handleDrawingPolyline(QMouseEvent *mouseEvent)
{
    const GeoDataCoordinates coords = mouseGeoDataCoordinates(mouseEvent);

    if (mouseEvent->type() == QEvent::MouseMove) {
        setupCursor(nullptr);

        Q_EMIT mouseMoveGeoPosition(coords.toString());

        return true;
    } else if (mouseEvent->button() == Qt::LeftButton && mouseEvent->type() == QEvent::MouseButtonPress) {
        m_marbleWidget->model()->treeModel()->removeFeature(m_polylinePlacemark);
        auto line = dynamic_cast<GeoDataLineString *>(m_polylinePlacemark->geometry());
        line->append(coords);
        m_marbleWidget->model()->treeModel()->addFeature(m_annotationDocument, m_polylinePlacemark);
        Q_EMIT nodeAdded(coords);

        return true;
    }

    return false;
}

void AnnotatePlugin::handleReleaseOverlay(QMouseEvent *mouseEvent)
{
    const GeoDataCoordinates coords = mouseGeoDataCoordinates(mouseEvent);

    for (int i = 0; i < m_groundOverlayModel.rowCount(); ++i) {
        const QModelIndex index = m_groundOverlayModel.index(i, 0);
        GeoDataGroundOverlay *overlay =
            dynamic_cast<GeoDataGroundOverlay *>(qvariant_cast<GeoDataObject *>(index.data(MarblePlacemarkModel::ObjectPointerRole)));

        if (overlay->latLonBox().contains(coords)) {
            if (mouseEvent->button() == Qt::LeftButton) {
                displayOverlayFrame(overlay);
            } else if (mouseEvent->button() == Qt::RightButton) {
                showOverlayRmbMenu(overlay, mouseEvent->position().x(), mouseEvent->position().y());
            }
        }
    }
}

bool AnnotatePlugin::handleMovingSelectedItem(QMouseEvent *mouseEvent)
{
    // Handling easily the mouse move by calling for each scene graphic item their own mouseMoveEvent
    // handler and updating their feature.
    if (m_movedItem->sceneEvent(mouseEvent)) {
        m_marbleWidget->model()->treeModel()->updateFeature(m_movedItem->placemark());
        Q_EMIT itemMoved(m_movedItem->placemark());
        if (m_movedItem->graphicType() == SceneGraphicsTypes::SceneGraphicTextAnnotation) {
            Q_EMIT placemarkMoved();
        }

        const GeoDataCoordinates coords = mouseGeoDataCoordinates(mouseEvent);
        Q_EMIT mouseMoveGeoPosition(coords.toString());

        return true;
    }

    return false;
}

void AnnotatePlugin::handleSuccessfulPressEvent(QMouseEvent *mouseEvent, SceneGraphicsItem *item)
{
    Q_UNUSED(mouseEvent);

    // Update the item's placemark.
    m_marbleWidget->model()->treeModel()->updateFeature(item->placemark());

    // Store a pointer to the item for possible following move events only if its state is
    // either 'Editing' or 'AddingNodes' and the mouse left button has been used.
    if ((item->state() == SceneGraphicsItem::Editing || item->state() == SceneGraphicsItem::AddingNodes) && mouseEvent->button() == Qt::LeftButton) {
        m_movedItem = item;
    }
}

void AnnotatePlugin::handleSuccessfulHoverEvent(QMouseEvent *mouseEvent, SceneGraphicsItem *item)
{
    Q_UNUSED(mouseEvent);
    m_marbleWidget->model()->treeModel()->updateFeature(item->placemark());
}

void AnnotatePlugin::handleSuccessfulReleaseEvent(QMouseEvent *mouseEvent, SceneGraphicsItem *item)
{
    Q_UNUSED(mouseEvent);
    // The item gets 'deselected' (from moving) at mouse release.
    m_movedItem = nullptr;

    // Update the item's placemark.
    m_marbleWidget->model()->treeModel()->updateFeature(item->placemark());
}

void AnnotatePlugin::handleRequests(QMouseEvent *mouseEvent, SceneGraphicsItem *item)
{
    if (item->graphicType() == SceneGraphicsTypes::SceneGraphicAreaAnnotation) {
        auto const area = static_cast<AreaAnnotation *>(item);

        if (area->request() == SceneGraphicsItem::ShowPolygonRmbMenu) {
            showPolygonRmbMenu(mouseEvent->pos().x(), mouseEvent->pos().y());
        } else if (area->request() == SceneGraphicsItem::ShowNodeRmbMenu) {
            showNodeRmbMenu(mouseEvent->pos().x(), mouseEvent->pos().y());
        } else if (area->request() == SceneGraphicsItem::StartPolygonAnimation) {
            QPointer<MergingPolygonNodesAnimation> animation = area->animation();

            connect(animation, SIGNAL(nodesMoved()), this, SIGNAL(repaintNeeded()));
            connect(animation, SIGNAL(animationFinished()), this, SLOT(setAreaAvailable()));

            area->setBusy(true);
            disableActions(m_actions.first());
            animation->startAnimation();
        } else if (area->request() == SceneGraphicsItem::OuterInnerMergingWarning) {
            QMessageBox::warning(m_marbleWidget,
                                 tr("Operation not permitted"),
                                 tr("Cannot merge a node from polygon's outer boundary "
                                    "with a node from one of its inner boundaries."));
        } else if (area->request() == SceneGraphicsItem::InnerInnerMergingWarning) {
            QMessageBox::warning(m_marbleWidget,
                                 tr("Operation not permitted"),
                                 tr("Cannot merge two nodes from two different inner "
                                    "boundaries."));
        } else if (area->request() == SceneGraphicsItem::InvalidShapeWarning) {
            QMessageBox::warning(m_marbleWidget,
                                 tr("Operation not permitted"),
                                 tr("Cannot merge the selected nodes. Most probably "
                                    "this would make the polygon's outer boundary not "
                                    "contain all its inner boundary nodes."));
        } else if (area->request() == SceneGraphicsItem::RemovePolygonRequest) {
            removeFocusItem();
        } else if (area->request() == SceneGraphicsItem::ChangeCursorPolygonNodeHover) {
            m_marbleWidget->setCursor(Qt::PointingHandCursor);
        } else if (area->request() == SceneGraphicsItem::ChangeCursorPolygonBodyHover) {
            m_marbleWidget->setCursor(Qt::SizeAllCursor);
        }
    } else if (item->graphicType() == SceneGraphicsTypes::SceneGraphicPolylineAnnotation) {
        auto const polyline = static_cast<PolylineAnnotation *>(item);

        if (polyline->request() == SceneGraphicsItem::ShowPolylineRmbMenu) {
            showPolylineRmbMenu(mouseEvent->position().x(), mouseEvent->position().y());
        } else if (polyline->request() == SceneGraphicsItem::ShowNodeRmbMenu) {
            showNodeRmbMenu(mouseEvent->position().x(), mouseEvent->position().y());
        } else if (polyline->request() == SceneGraphicsItem::StartPolylineAnimation) {
            QPointer<MergingPolylineNodesAnimation> animation = polyline->animation();

            connect(animation, SIGNAL(nodesMoved()), this, SIGNAL(repaintNeeded()));
            connect(animation, SIGNAL(animationFinished()), this, SLOT(setPolylineAvailable()));

            polyline->setBusy(true);
            disableActions(m_actions.first());
            animation->startAnimation();
        } else if (polyline->request() == SceneGraphicsItem::RemovePolylineRequest) {
            removeFocusItem();
        } else if (polyline->request() == SceneGraphicsItem::ChangeCursorPolylineNodeHover) {
            m_marbleWidget->setCursor(Qt::PointingHandCursor);
        } else if (polyline->request() == SceneGraphicsItem::ChangeCursorPolylineLineHover) {
            m_marbleWidget->setCursor(Qt::SizeAllCursor);
        }
    } else if (item->graphicType() == SceneGraphicsTypes::SceneGraphicTextAnnotation) {
        auto const textAnnotation = static_cast<PlacemarkTextAnnotation *>(item);

        if (textAnnotation->request() == SceneGraphicsItem::ShowPlacemarkRmbMenu) {
            showTextAnnotationRmbMenu(mouseEvent->position().x(), mouseEvent->position().y());
        } else if (textAnnotation->request() == SceneGraphicsItem::ChangeCursorPlacemarkHover) {
            m_marbleWidget->setCursor(Qt::SizeAllCursor);
        }
    } else if (item->graphicType() == SceneGraphicsTypes::SceneGraphicGroundOverlay) {
        auto const groundOverlay = static_cast<GroundOverlayFrame *>(item);

        if (groundOverlay->request() == SceneGraphicsItem::ChangeCursorOverlayVerticalHover) {
            m_marbleWidget->setCursor(Qt::SizeVerCursor);
        } else if (groundOverlay->request() == SceneGraphicsItem::ChangeCursorOverlayHorizontalHover) {
            m_marbleWidget->setCursor(Qt::SizeHorCursor);
        } else if (groundOverlay->request() == SceneGraphicsItem::ChangeCursorOverlayBDiagHover) {
            m_marbleWidget->setCursor(Qt::SizeBDiagCursor);
        } else if (groundOverlay->request() == SceneGraphicsItem::ChangeCursorOverlayFDiagHover) {
            m_marbleWidget->setCursor(Qt::SizeFDiagCursor);
        } else if (groundOverlay->request() == SceneGraphicsItem::ChangeCursorOverlayBodyHover) {
            m_marbleWidget->setCursor(Qt::SizeAllCursor);
        } else if (groundOverlay->request() == SceneGraphicsItem::ChangeCursorOverlayRotateHover) {
            m_marbleWidget->setCursor(Qt::CrossCursor);
        }
    }
}

void AnnotatePlugin::handleUncaughtEvents(QMouseEvent *mouseEvent)
{
    Q_UNUSED(mouseEvent);

    // If the event is not caught by any of the annotate plugin specific items, clear the frames
    // (which have the meaning of deselecting the overlay).
    if (!m_groundOverlayFrames.isEmpty() && mouseEvent->type() != QEvent::MouseMove && mouseEvent->type() != QEvent::MouseButtonRelease) {
        clearOverlayFrames();
    }

    if (m_focusItem && m_focusItem->graphicType() != SceneGraphicsTypes::SceneGraphicGroundOverlay) {
        if ((m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicAreaAnnotation && static_cast<AreaAnnotation *>(m_focusItem)->isBusy())
            || (m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicPolylineAnnotation && static_cast<PolylineAnnotation *>(m_focusItem)->isBusy())) {
            return;
        }

        m_focusItem->dealWithItemChange(nullptr);
        m_marbleWidget->model()->treeModel()->updateFeature(m_focusItem->placemark());

        if (mouseEvent->type() == QEvent::MouseButtonPress) {
            m_focusItem->setFocus(false);
            disableFocusActions();
            announceStateChanged(SceneGraphicsItem::Editing);
            m_marbleWidget->model()->treeModel()->updateFeature(m_focusItem->placemark());
            m_focusItem = nullptr;
        }
    }
}

void AnnotatePlugin::setupActions(MarbleWidget *widget)
{
    qDeleteAll(m_actions);
    m_actions.clear();

    if (!widget) {
        return;
    }

    auto group = new QActionGroup(nullptr);
    group->setExclusive(true);

    auto selectItem = new QAction(QIcon(QStringLiteral(":/icons/edit-select.png")), tr("Select Item"), this);
    selectItem->setCheckable(true);
    selectItem->setChecked(true);

    auto drawPolygon = new QAction(QIcon(QStringLiteral(":/icons/draw-polygon.png")), tr("Add Polygon"), this);
    connect(drawPolygon, &QAction::triggered, this, &AnnotatePlugin::addPolygon);

    auto addHole = new QAction(QIcon(QStringLiteral(":/icons/polygon-draw-hole.png")), tr("Add Polygon Hole"), this);
    addHole->setCheckable(true);
    addHole->setEnabled(false);
    connect(addHole, &QAction::toggled, this, &AnnotatePlugin::setAddingPolygonHole);

    auto addNodes = new QAction(QIcon(QStringLiteral(":/icons/polygon-add-nodes.png")), tr("Add Nodes"), this);
    addNodes->setCheckable(true);
    addNodes->setEnabled(false);
    connect(addNodes, &QAction::toggled, this, &AnnotatePlugin::setAddingNodes);

    auto addTextAnnotation = new QAction(QIcon(QStringLiteral(":/icons/add-placemark.png")), tr("Add Placemark"), this);
    connect(addTextAnnotation, &QAction::triggered, this, &AnnotatePlugin::addTextAnnotation);

    auto addPath = new QAction(QIcon(QStringLiteral(":/icons/draw-path.png")), tr("Add Path"), this);
    connect(addPath, &QAction::triggered, this, &AnnotatePlugin::addPolyline);

    auto addOverlay = new QAction(QIcon(QStringLiteral(":/icons/draw-overlay.png")), tr("Add Ground Overlay"), this);
    connect(addOverlay, &QAction::triggered, this, &AnnotatePlugin::addOverlay);

    auto removeItem = new QAction(QIcon(QStringLiteral(":/icons/edit-delete-shred.png")), tr("Remove Item"), this);
    removeItem->setEnabled(false);
    connect(removeItem, &QAction::triggered, this, &AnnotatePlugin::askToRemoveFocusItem);

    auto loadAnnotationFile = new QAction(QIcon(QStringLiteral(":/icons/open-for-editing.png")), tr("Load Annotation File"), this);
    connect(loadAnnotationFile, &QAction::triggered, this, &AnnotatePlugin::loadAnnotationFile);

    auto saveAnnotationFile = new QAction(QIcon(QStringLiteral(":/icons/document-save-as.png")), tr("Save Annotation File"), this);
    connect(saveAnnotationFile, &QAction::triggered, this, &AnnotatePlugin::saveAnnotationFile);

    auto clearAnnotations = new QAction(QIcon(QStringLiteral(":/icons/remove.png")), tr("Clear all Annotations"), this);
    auto downloadOsm = new QAction(QIcon(QStringLiteral(":/icons/download.png")), tr("Download OpenStreetMap Data"), this);
    connect(downloadOsm, &QAction::triggered, this, &AnnotatePlugin::downloadOsm);
    downloadOsm->setToolTip(tr("Download OpenStreetMap data of the visible region"));
    connect(drawPolygon, &QAction::toggled, clearAnnotations, &QAction::setDisabled);
    connect(clearAnnotations, &QAction::triggered, this, &AnnotatePlugin::clearAnnotations);

    auto sep1 = new QAction(this);
    sep1->setSeparator(true);
    auto sep2 = new QAction(this);
    sep2->setSeparator(true);
    sep2->setObjectName("toolbarSeparator");
    auto sep3 = new QAction(this);
    sep3->setSeparator(true);
    auto sep4 = new QAction(this);
    sep4->setSeparator(true);

    group->addAction(loadAnnotationFile);
    group->addAction(saveAnnotationFile);
    group->addAction(sep1);
    group->addAction(addTextAnnotation);
    group->addAction(drawPolygon);
    group->addAction(addPath);
    group->addAction(addOverlay);
    group->addAction(sep2);
    group->addAction(selectItem);
    group->addAction(addHole);
    group->addAction(addNodes);
    group->addAction(removeItem);
    group->addAction(sep3);
    group->addAction(clearAnnotations);
    group->addAction(downloadOsm);
    group->addAction(sep4);

    m_actions.append(group);

    Q_EMIT actionGroupsChanged();
}

void AnnotatePlugin::disableActions(QActionGroup *group)
{
    for (int i = 0; i < group->actions().size(); ++i) {
        if (group->actions().at(i)->text() != tr("Select Item")) {
            group->actions().at(i)->setEnabled(false);
        } else {
            group->actions().at(i)->setEnabled(true);
        }
    }
}

void AnnotatePlugin::enableAllActions(QActionGroup *group)
{
    for (int i = 0; i < group->actions().size(); ++i) {
        group->actions().at(i)->setEnabled(true);
    }
}

void AnnotatePlugin::enableActionsOnItemType(const QString &type)
{
    if (type == QLatin1StringView(SceneGraphicsTypes::SceneGraphicAreaAnnotation)) {
        m_actions.first()->actions().at(9)->setEnabled(true);
        m_actions.first()->actions().at(10)->setEnabled(true);
    } else if (type == QLatin1StringView(SceneGraphicsTypes::SceneGraphicPolylineAnnotation)) {
        m_actions.first()->actions().at(10)->setEnabled(true);
    }

    m_actions.first()->actions().at(11)->setEnabled(true);
}

void AnnotatePlugin::disableFocusActions()
{
    m_actions.first()->actions().at(8)->setChecked(true);

    m_actions.first()->actions().at(9)->setEnabled(false);
    m_actions.first()->actions().at(10)->setEnabled(false);
    m_actions.first()->actions().at(11)->setEnabled(false);
}

void AnnotatePlugin::addContextItems()
{
    MarbleWidgetPopupMenu *const menu = m_marbleWidget->popupMenu();

    m_pasteGraphicItem = new QAction(tr("Paste"), this);
    m_pasteGraphicItem->setVisible(false);
    connect(m_pasteGraphicItem, SIGNAL(triggered()), SLOT(pasteItem()));

    auto separator = new QAction(this);
    separator->setSeparator(true);

    bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
    if (!smallScreen) {
        menu->addAction(Qt::RightButton, m_pasteGraphicItem);
        menu->addAction(Qt::RightButton, separator);
    }
}

void AnnotatePlugin::setupTextAnnotationRmbMenu()
{
    delete m_textAnnotationRmbMenu;
    m_textAnnotationRmbMenu = new QMenu;

    auto cutItem = new QAction(tr("Cut"), m_textAnnotationRmbMenu);
    m_textAnnotationRmbMenu->addAction(cutItem);
    connect(cutItem, &QAction::triggered, this, &AnnotatePlugin::cutItem);

    auto copyItem = new QAction(tr("Copy"), m_textAnnotationRmbMenu);
    m_textAnnotationRmbMenu->addAction(copyItem);
    connect(copyItem, &QAction::triggered, this, &AnnotatePlugin::copyItem);

    auto removeItem = new QAction(tr("Remove"), m_textAnnotationRmbMenu);
    m_textAnnotationRmbMenu->addAction(removeItem);
    connect(removeItem, &QAction::triggered, this, &AnnotatePlugin::askToRemoveFocusItem);

    m_textAnnotationRmbMenu->addSeparator();

    auto properties = new QAction(tr("Properties"), m_textAnnotationRmbMenu);
    m_textAnnotationRmbMenu->addAction(properties);
    connect(properties, &QAction::triggered, this, &AnnotatePlugin::editTextAnnotation);
}

void AnnotatePlugin::showTextAnnotationRmbMenu(qreal x, qreal y)
{
    m_textAnnotationRmbMenu->popup(m_marbleWidget->mapToGlobal(QPoint(x, y)));
}

void AnnotatePlugin::editTextAnnotation()
{
    QPointer<EditPlacemarkDialog> dialog = new EditPlacemarkDialog(m_focusItem->placemark(), &m_osmRelations, m_marbleWidget);
    connect(dialog, SIGNAL(textAnnotationUpdated(GeoDataFeature *)), m_marbleWidget->model()->treeModel(), SLOT(updateFeature(GeoDataFeature *)));
    connect(this, SIGNAL(placemarkMoved()), dialog, SLOT(updateDialogFields()));
    connect(dialog, SIGNAL(finished(int)), this, SLOT(stopEditingTextAnnotation(int)));
    connect(dialog, SIGNAL(relationCreated(OsmPlacemarkData)), this, SLOT(addRelation(OsmPlacemarkData)));

    dialog->setLabelColor(dynamic_cast<PlacemarkTextAnnotation *>(m_focusItem)->labelColor());

    disableActions(m_actions.first());
    dialog->show();
    m_editingDialogIsShown = true;
    m_editedItem = m_focusItem;
}

void AnnotatePlugin::addTextAnnotation()
{
    m_addingPlacemark = true;

    // Get the normalized coordinates of the focus point. There will be automatically added a new
    // placemark.
    qreal lat = m_marbleWidget->focusPoint().latitude();
    qreal lon = m_marbleWidget->focusPoint().longitude();
    GeoDataCoordinates::normalizeLonLat(lon, lat);

    auto placemark = new GeoDataPlacemark;
    placemark->setCoordinate(lon, lat);
    placemark->setVisible(true);
    placemark->setBalloonVisible(false);
    m_marbleWidget->model()->treeModel()->addFeature(m_annotationDocument, placemark);

    auto textAnnotation = new PlacemarkTextAnnotation(placemark);
    textAnnotation->setFocus(true);
    m_graphicsItems.append(textAnnotation);

    QPointer<EditPlacemarkDialog> dialog = new EditPlacemarkDialog(placemark, &m_osmRelations, m_marbleWidget);

    connect(dialog, SIGNAL(textAnnotationUpdated(GeoDataFeature *)), m_marbleWidget->model()->treeModel(), SLOT(updateFeature(GeoDataFeature *)));
    connect(this, SIGNAL(placemarkMoved()), dialog, SLOT(updateDialogFields()));
    connect(dialog, SIGNAL(finished(int)), this, SLOT(stopEditingTextAnnotation(int)));
    connect(dialog, SIGNAL(relationCreated(OsmPlacemarkData)), this, SLOT(addRelation(OsmPlacemarkData)));

    if (m_focusItem) {
        m_focusItem->setFocus(false);
        if (m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicGroundOverlay) {
            clearOverlayFrames();
        }
    }
    m_focusItem = textAnnotation;
    m_editedItem = textAnnotation;
    disableActions(m_actions.first());

    dialog->move(m_marbleWidget->mapToGlobal(QPoint(0, 0)));
    dialog->show();
    m_editingDialogIsShown = true;
}

void AnnotatePlugin::stopEditingTextAnnotation(int result)
{
    m_focusItem = m_editedItem;
    m_editedItem = nullptr;
    announceStateChanged(SceneGraphicsItem::Editing);
    enableAllActions(m_actions.first());
    disableFocusActions();

    if (!result && m_addingPlacemark) {
        removeFocusItem();
    } else {
        enableActionsOnItemType(QLatin1StringView(SceneGraphicsTypes::SceneGraphicTextAnnotation));
    }

    m_addingPlacemark = false;
    m_editingDialogIsShown = false;
}

void AnnotatePlugin::setupGroundOverlayModel()
{
    m_editingDialogIsShown = false;
    m_groundOverlayModel.setSourceModel(m_marbleWidget->model()->groundOverlayModel());
    m_groundOverlayModel.setDynamicSortFilter(true);
    m_groundOverlayModel.setSortRole(MarblePlacemarkModel::PopularityIndexRole);
    m_groundOverlayModel.sort(0, Qt::AscendingOrder);
}

void AnnotatePlugin::setupOverlayRmbMenu()
{
    delete m_overlayRmbMenu;
    m_overlayRmbMenu = new QMenu;

    auto editOverlay = new QAction(tr("Properties"), m_overlayRmbMenu);
    m_overlayRmbMenu->addAction(editOverlay);
    connect(editOverlay, SIGNAL(triggered()), this, SLOT(editOverlay()));

    m_overlayRmbMenu->addSeparator();

    auto removeOverlay = new QAction(tr("Remove"), m_overlayRmbMenu);
    m_overlayRmbMenu->addAction(removeOverlay);
    connect(removeOverlay, SIGNAL(triggered()), this, SLOT(removeOverlay()));
}

void AnnotatePlugin::addOverlay()
{
    auto overlay = new GeoDataGroundOverlay();
    qreal centerLongitude = m_marbleWidget->viewport()->centerLongitude() * RAD2DEG;
    qreal centerLatitude = m_marbleWidget->viewport()->centerLatitude() * RAD2DEG;
    GeoDataLatLonAltBox box = m_marbleWidget->viewport()->viewLatLonAltBox();
    qreal maxDelta = 20;
    qreal deltaLongitude = qMin(box.width(GeoDataCoordinates::Degree), maxDelta);
    qreal deltaLatitude = qMin(box.height(GeoDataCoordinates::Degree), maxDelta);
    qreal north = centerLatitude + deltaLatitude / 4;
    qreal south = centerLatitude - deltaLatitude / 4;
    qreal west = centerLongitude - deltaLongitude / 4;
    qreal east = centerLongitude + deltaLongitude / 4;
    overlay->latLonBox().setBoundaries(north, south, east, west, GeoDataCoordinates::Degree);
    overlay->setName(tr("Untitled Ground Overlay"));
    QPointer<EditGroundOverlayDialog> dialog = new EditGroundOverlayDialog(overlay, m_marbleWidget->textureLayer(), m_marbleWidget);
    dialog->exec();
    if (dialog->result()) {
        m_marbleWidget->model()->treeModel()->addFeature(m_annotationDocument, overlay);
        displayOverlayFrame(overlay);
    } else {
        delete overlay;
    }
    delete dialog;
}

void AnnotatePlugin::showOverlayRmbMenu(GeoDataGroundOverlay *overlay, qreal x, qreal y)
{
    m_rmbOverlay = overlay;
    m_overlayRmbMenu->popup(m_marbleWidget->mapToGlobal(QPoint(x, y)));
}

void AnnotatePlugin::editOverlay()
{
    displayOverlayFrame(m_rmbOverlay);

    QPointer<EditGroundOverlayDialog> dialog = new EditGroundOverlayDialog(m_rmbOverlay, m_marbleWidget->textureLayer(), m_marbleWidget);
    connect(dialog, SIGNAL(groundOverlayUpdated(GeoDataGroundOverlay *)), this, SLOT(updateOverlayFrame(GeoDataGroundOverlay *)));

    dialog->exec();
    delete dialog;
}

void AnnotatePlugin::removeOverlay()
{
    m_marbleWidget->model()->treeModel()->removeFeature(m_rmbOverlay);
    clearOverlayFrames();
}

void AnnotatePlugin::displayOverlayFrame(GeoDataGroundOverlay *overlay)
{
    if (m_groundOverlayFrames.keys().contains(overlay)) {
        return;
    }

    auto polygon = new GeoDataPolygon(Tessellate);
    polygon->outerBoundary().setTessellate(true);

    auto rectangle_placemark = new GeoDataPlacemark;
    rectangle_placemark->setGeometry(polygon);
    rectangle_placemark->setParent(m_annotationDocument);
    rectangle_placemark->setStyleUrl(QStringLiteral("#polygon"));

    m_marbleWidget->model()->treeModel()->addFeature(m_annotationDocument, rectangle_placemark);

    auto frame = new GroundOverlayFrame(rectangle_placemark, overlay, m_marbleWidget->textureLayer());
    m_graphicsItems.append(frame);
    m_groundOverlayFrames.insert(overlay, frame);

    if (m_focusItem) {
        m_focusItem->setFocus(false);
    }
    m_focusItem = frame;
    enableActionsOnItemType(QLatin1StringView(SceneGraphicsTypes::SceneGraphicGroundOverlay));
}

void AnnotatePlugin::updateOverlayFrame(GeoDataGroundOverlay *overlay)
{
    GroundOverlayFrame *frame = static_cast<GroundOverlayFrame *>(m_groundOverlayFrames.value(overlay));
    if (frame) {
        frame->update();
    }
}

void AnnotatePlugin::clearOverlayFrames()
{
    for (GeoDataGroundOverlay *overlay : m_groundOverlayFrames.keys()) {
        GroundOverlayFrame *frame = static_cast<GroundOverlayFrame *>(m_groundOverlayFrames.value(overlay));
        m_graphicsItems.removeAll(m_groundOverlayFrames.value(overlay));
        m_marbleWidget->model()->treeModel()->removeFeature(frame->placemark());
        delete frame->placemark();
        delete frame;
    }

    m_groundOverlayFrames.clear();
    m_focusItem = nullptr;
    disableFocusActions();
}

void AnnotatePlugin::setupPolygonRmbMenu()
{
    delete m_polygonRmbMenu;
    m_polygonRmbMenu = new QMenu;

    auto deselectNodes = new QAction(tr("Deselect All Nodes"), m_polygonRmbMenu);
    m_polygonRmbMenu->addAction(deselectNodes);
    connect(deselectNodes, SIGNAL(triggered()), this, SLOT(deselectNodes()));

    auto deleteAllSelected = new QAction(tr("Delete All Selected Nodes"), m_polygonRmbMenu);
    m_polygonRmbMenu->addAction(deleteAllSelected);
    connect(deleteAllSelected, SIGNAL(triggered()), this, SLOT(deleteSelectedNodes()));

    m_polygonRmbMenu->addSeparator();

    auto cutPolygon = new QAction(tr("Cut"), m_polygonRmbMenu);
    m_polygonRmbMenu->addAction(cutPolygon);
    connect(cutPolygon, SIGNAL(triggered()), this, SLOT(cutItem()));

    auto copyPolygon = new QAction(tr("Copy"), m_polygonRmbMenu);
    m_polygonRmbMenu->addAction(copyPolygon);
    connect(copyPolygon, SIGNAL(triggered()), this, SLOT(copyItem()));

    auto removePolygon = new QAction(tr("Remove"), m_polygonRmbMenu);
    m_polygonRmbMenu->addAction(removePolygon);
    connect(removePolygon, SIGNAL(triggered()), this, SLOT(askToRemoveFocusItem()));

    m_polygonRmbMenu->addSeparator();

    auto showEditDialog = new QAction(tr("Properties"), m_polygonRmbMenu);
    m_polygonRmbMenu->addAction(showEditDialog);
    connect(showEditDialog, SIGNAL(triggered()), this, SLOT(editPolygon()));
}

void AnnotatePlugin::showPolygonRmbMenu(qreal x, qreal y)
{
    // We need to store the coordinates from where the rmb menu is shown so that in case of
    // selecting Copy/Cut, we can move the polygon.
    qreal lon, lat;
    m_marbleWidget->geoCoordinates(x, y, lon, lat, GeoDataCoordinates::Radian);
    m_fromWhereToCopy = GeoDataCoordinates(lon, lat);

    if (!static_cast<AreaAnnotation *>(m_focusItem)->hasNodesSelected()) {
        m_polygonRmbMenu->actions().at(1)->setEnabled(false);
        m_polygonRmbMenu->actions().at(0)->setEnabled(false);
    } else {
        m_polygonRmbMenu->actions().at(1)->setEnabled(true);
        m_polygonRmbMenu->actions().at(0)->setEnabled(true);
    }

    m_polygonRmbMenu->popup(m_marbleWidget->mapToGlobal(QPoint(x, y)));
}

void AnnotatePlugin::addPolygon()
{
    m_drawingPolygon = true;

    auto poly = new GeoDataPolygon(Tessellate);
    poly->outerBoundary().setTessellate(true);

    m_polygonPlacemark = new GeoDataPlacemark;
    m_polygonPlacemark->setGeometry(poly);
    m_polygonPlacemark->setParent(m_annotationDocument);
    m_polygonPlacemark->setStyleUrl(QStringLiteral("#polygon"));

    m_marbleWidget->model()->treeModel()->addFeature(m_annotationDocument, m_polygonPlacemark);

    auto polygon = new AreaAnnotation(m_polygonPlacemark);
    polygon->setState(SceneGraphicsItem::DrawingPolygon);
    polygon->setFocus(true);
    m_graphicsItems.append(polygon);
    m_marbleWidget->update();

    QPointer<EditPolygonDialog> dialog = new EditPolygonDialog(m_polygonPlacemark, &m_osmRelations, m_marbleWidget);

    connect(dialog, SIGNAL(polygonUpdated(GeoDataFeature *)), m_marbleWidget->model()->treeModel(), SLOT(updateFeature(GeoDataFeature *)));
    connect(dialog, SIGNAL(finished(int)), this, SLOT(stopEditingPolygon(int)));
    connect(this, SIGNAL(nodeAdded(GeoDataCoordinates)), dialog, SLOT(handleAddingNode(GeoDataCoordinates)));
    connect(dialog, SIGNAL(relationCreated(OsmPlacemarkData)), this, SLOT(addRelation(OsmPlacemarkData)));

    // If there is another graphic item marked as 'selected' when pressing 'Add Polygon', change the focus of
    // that item.
    if (m_focusItem) {
        m_focusItem->setFocus(false);
        if (m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicGroundOverlay) {
            clearOverlayFrames();
        }
    }
    m_focusItem = polygon;
    m_editedItem = polygon;
    disableActions(m_actions.first());

    dialog->move(m_marbleWidget->mapToGlobal(QPoint(0, 0)));
    dialog->show();
    m_editingDialogIsShown = true;
}

void AnnotatePlugin::stopEditingPolygon(int result)
{
    m_focusItem = m_editedItem;
    m_editedItem = nullptr;
    announceStateChanged(SceneGraphicsItem::Editing);
    enableAllActions(m_actions.first());
    disableFocusActions();

    if (!result && m_drawingPolygon) {
        removeFocusItem();
    } else {
        enableActionsOnItemType(QLatin1StringView(SceneGraphicsTypes::SceneGraphicAreaAnnotation));
    }

    m_editingDialogIsShown = false;
    m_drawingPolygon = false;
    m_polygonPlacemark = nullptr;
}

void AnnotatePlugin::deselectNodes()
{
    if (m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicAreaAnnotation) {
        auto const area = static_cast<AreaAnnotation *>(m_focusItem);
        area->deselectAllNodes();

        if (area->request() == SceneGraphicsItem::NoRequest) {
            m_marbleWidget->model()->treeModel()->updateFeature(area->placemark());
        }
    } else if (m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicPolylineAnnotation) {
        auto const polyline = static_cast<PolylineAnnotation *>(m_focusItem);
        polyline->deselectAllNodes();

        if (polyline->request() == SceneGraphicsItem::NoRequest) {
            m_marbleWidget->model()->treeModel()->updateFeature(polyline->placemark());
        }
    }
}

void AnnotatePlugin::deleteSelectedNodes()
{
    if (m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicAreaAnnotation) {
        auto const area = static_cast<AreaAnnotation *>(m_focusItem);
        area->deleteAllSelectedNodes();
    } else if (m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicPolylineAnnotation) {
        auto const polyline = static_cast<PolylineAnnotation *>(m_focusItem);
        polyline->deleteAllSelectedNodes();
    }

    if (m_focusItem->request() == SceneGraphicsItem::NoRequest) {
        m_marbleWidget->model()->treeModel()->updateFeature(m_focusItem->placemark());
    } else if (m_focusItem->request() == SceneGraphicsItem::RemovePolygonRequest || m_focusItem->request() == SceneGraphicsItem::RemovePolylineRequest) {
        removeFocusItem();
    } else if (m_focusItem->request() == SceneGraphicsItem::InvalidShapeWarning) {
        QMessageBox::warning(m_marbleWidget,
                             tr("Operation not permitted"),
                             tr("Cannot delete one of the selected nodes. Most probably "
                                "this would make the polygon's outer boundary not "
                                "contain all its inner boundary nodes."));
    }
}

void AnnotatePlugin::editPolygon()
{
    auto dialog = new EditPolygonDialog(m_focusItem->placemark(), &m_osmRelations, m_marbleWidget);

    connect(dialog, SIGNAL(polygonUpdated(GeoDataFeature *)), m_marbleWidget->model()->treeModel(), SLOT(updateFeature(GeoDataFeature *)));
    connect(dialog, SIGNAL(finished(int)), this, SLOT(stopEditingPolygon(int)));
    connect(this, SIGNAL(itemMoved(GeoDataPlacemark *)), dialog, SLOT(handleItemMoving(GeoDataPlacemark *)));
    connect(dialog, SIGNAL(relationCreated(OsmPlacemarkData)), this, SLOT(addRelation(OsmPlacemarkData)));

    disableActions(m_actions.first());

    dialog->move(m_marbleWidget->mapToGlobal(QPoint(0, 0)));
    dialog->show();
    m_editingDialogIsShown = true;
    m_editedItem = m_focusItem;
}

void AnnotatePlugin::setupNodeRmbMenu()
{
    delete m_nodeRmbMenu;
    m_nodeRmbMenu = new QMenu;

    auto selectNode = new QAction(tr("Select Node"), m_nodeRmbMenu);
    m_nodeRmbMenu->addAction(selectNode);
    connect(selectNode, &QAction::triggered, this, &AnnotatePlugin::selectNode);

    auto deleteNode = new QAction(tr("Delete Node"), m_nodeRmbMenu);
    m_nodeRmbMenu->addAction(deleteNode);
    connect(deleteNode, &QAction::triggered, this, &AnnotatePlugin::deleteNode);
}

void AnnotatePlugin::showNodeRmbMenu(qreal x, qreal y)
{
    // Check whether the node is already selected; we change the text of the action
    // accordingly.
    bool isSelected = false;
    if ((m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicAreaAnnotation && static_cast<AreaAnnotation *>(m_focusItem)->clickedNodeIsSelected())
        || (m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicPolylineAnnotation
            && static_cast<PolylineAnnotation *>(m_focusItem)->clickedNodeIsSelected())) {
        isSelected = true;
    }

    m_nodeRmbMenu->actions().first()->setText(isSelected ? tr("Deselect Node") : tr("Select Node"));
    m_nodeRmbMenu->popup(m_marbleWidget->mapToGlobal(QPoint(x, y)));
}

void AnnotatePlugin::selectNode()
{
    if (m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicAreaAnnotation) {
        auto const area = static_cast<AreaAnnotation *>(m_focusItem);
        area->changeClickedNodeSelection();
    } else if (m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicPolylineAnnotation) {
        auto const polyline = static_cast<PolylineAnnotation *>(m_focusItem);
        polyline->changeClickedNodeSelection();
    }

    if (m_focusItem->request() == SceneGraphicsItem::NoRequest) {
        m_marbleWidget->model()->treeModel()->updateFeature(m_focusItem->placemark());
    }
}

void AnnotatePlugin::deleteNode()
{
    if (m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicAreaAnnotation) {
        auto area = static_cast<AreaAnnotation *>(m_focusItem);
        area->deleteClickedNode();
    } else if (m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicPolylineAnnotation) {
        auto polyline = static_cast<PolylineAnnotation *>(m_focusItem);
        polyline->deleteClickedNode();
    }

    if (m_focusItem->request() == SceneGraphicsItem::NoRequest) {
        m_marbleWidget->model()->treeModel()->updateFeature(m_focusItem->placemark());
    } else if (m_focusItem->request() == SceneGraphicsItem::RemovePolygonRequest || m_focusItem->request() == SceneGraphicsItem::RemovePolylineRequest) {
        removeFocusItem();
    } else if (m_focusItem->request() == SceneGraphicsItem::InvalidShapeWarning) {
        QMessageBox::warning(m_marbleWidget,
                             tr("Operation not permitted"),
                             tr("Cannot delete one of the selected nodes. Most probably "
                                "this would make the polygon's outer boundary not "
                                "contain all its inner boundary nodes."));
    }
}

void AnnotatePlugin::setupPolylineRmbMenu()
{
    delete m_polylineRmbMenu;
    m_polylineRmbMenu = new QMenu;

    auto deselectNodes = new QAction(tr("Deselect All Nodes"), m_polylineRmbMenu);
    m_polylineRmbMenu->addAction(deselectNodes);
    connect(deselectNodes, SIGNAL(triggered()), this, SLOT(deselectNodes()));

    auto deleteAllSelected = new QAction(tr("Delete All Selected Nodes"), m_polylineRmbMenu);
    m_polylineRmbMenu->addAction(deleteAllSelected);
    connect(deleteAllSelected, SIGNAL(triggered()), this, SLOT(deleteSelectedNodes()));

    m_polylineRmbMenu->addSeparator();

    auto cutItem = new QAction(tr("Cut"), m_polylineRmbMenu);
    m_polylineRmbMenu->addAction(cutItem);
    connect(cutItem, SIGNAL(triggered()), this, SLOT(cutItem()));

    auto copyItem = new QAction(tr("Copy"), m_polylineRmbMenu);
    m_polylineRmbMenu->addAction(copyItem);
    connect(copyItem, SIGNAL(triggered()), this, SLOT(copyItem()));

    auto removeItem = new QAction(tr("Remove"), m_polylineRmbMenu);
    m_polylineRmbMenu->addAction(removeItem);
    connect(removeItem, SIGNAL(triggered()), this, SLOT(askToRemoveFocusItem()));

    m_polylineRmbMenu->addSeparator();

    auto properties = new QAction(tr("Properties"), m_polylineRmbMenu);
    m_polylineRmbMenu->addAction(properties);
    connect(properties, SIGNAL(triggered()), this, SLOT(editPolyline()));
}

void AnnotatePlugin::showPolylineRmbMenu(qreal x, qreal y)
{
    qreal lon, lat;
    m_marbleWidget->geoCoordinates(x, y, lon, lat, GeoDataCoordinates::Radian);
    m_fromWhereToCopy = GeoDataCoordinates(lon, lat);

    if (!static_cast<PolylineAnnotation *>(m_focusItem)->hasNodesSelected()) {
        m_polylineRmbMenu->actions().at(1)->setEnabled(false);
        m_polylineRmbMenu->actions().at(0)->setEnabled(false);
    } else {
        m_polylineRmbMenu->actions().at(1)->setEnabled(true);
        m_polylineRmbMenu->actions().at(0)->setEnabled(true);
    }

    m_polylineRmbMenu->popup(m_marbleWidget->mapToGlobal(QPoint(x, y)));
}

void AnnotatePlugin::editPolyline()
{
    QPointer<EditPolylineDialog> dialog = new EditPolylineDialog(m_focusItem->placemark(), &m_osmRelations, m_marbleWidget);
    connect(dialog, SIGNAL(polylineUpdated(GeoDataFeature *)), m_marbleWidget->model()->treeModel(), SLOT(updateFeature(GeoDataFeature *)));
    connect(dialog, SIGNAL(finished(int)), this, SLOT(stopEditingPolyline(int)));
    connect(this, SIGNAL(itemMoved(GeoDataPlacemark *)), dialog, SLOT(handleItemMoving(GeoDataPlacemark *)));
    connect(dialog, SIGNAL(relationCreated(OsmPlacemarkData)), this, SLOT(addRelation(OsmPlacemarkData)));

    disableActions(m_actions.first());
    dialog->show();
    m_editingDialogIsShown = true;
    m_editedItem = m_focusItem;
}

void AnnotatePlugin::addPolyline()
{
    m_drawingPolyline = true;

    m_polylinePlacemark = new GeoDataPlacemark;
    m_polylinePlacemark->setGeometry(new GeoDataLineString(Tessellate));
    m_polylinePlacemark->setParent(m_annotationDocument);
    m_polylinePlacemark->setStyleUrl(QStringLiteral("#polyline"));

    m_marbleWidget->model()->treeModel()->addFeature(m_annotationDocument, m_polylinePlacemark);

    auto polyline = new PolylineAnnotation(m_polylinePlacemark);
    polyline->setState(SceneGraphicsItem::DrawingPolyline);
    polyline->setFocus(true);
    m_graphicsItems.append(polyline);
    m_marbleWidget->update();

    QPointer<EditPolylineDialog> dialog = new EditPolylineDialog(m_polylinePlacemark, &m_osmRelations, m_marbleWidget);

    connect(dialog, SIGNAL(polylineUpdated(GeoDataFeature *)), m_marbleWidget->model()->treeModel(), SLOT(updateFeature(GeoDataFeature *)));
    connect(dialog, SIGNAL(finished(int)), this, SLOT(stopEditingPolyline(int)));
    connect(this, SIGNAL(nodeAdded(GeoDataCoordinates)), dialog, SLOT(handleAddingNode(GeoDataCoordinates)));
    connect(dialog, SIGNAL(relationCreated(OsmPlacemarkData)), this, SLOT(addRelation(OsmPlacemarkData)));

    if (m_focusItem) {
        m_focusItem->setFocus(false);
        if (m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicGroundOverlay) {
            clearOverlayFrames();
        }
    }
    m_focusItem = polyline;
    m_editedItem = m_focusItem;
    disableActions(m_actions.first());

    dialog->move(m_marbleWidget->mapToGlobal(QPoint(0, 0)));
    dialog->show();
    m_editingDialogIsShown = true;
}

void AnnotatePlugin::stopEditingPolyline(int result)
{
    m_focusItem = m_editedItem;
    m_editedItem = nullptr;
    announceStateChanged(SceneGraphicsItem::Editing);
    enableAllActions(m_actions.first());
    disableFocusActions();

    if (!result && m_drawingPolyline) {
        removeFocusItem();
    } else {
        enableActionsOnItemType(QLatin1StringView(SceneGraphicsTypes::SceneGraphicPolylineAnnotation));
    }

    m_editingDialogIsShown = false;
    m_drawingPolyline = false;
    m_polylinePlacemark = nullptr;
}

void AnnotatePlugin::addRelation(const OsmPlacemarkData &relationData)
{
    m_osmRelations.insert(relationData.id(), relationData);
}

void AnnotatePlugin::announceStateChanged(SceneGraphicsItem::ActionState newState)
{
    for (SceneGraphicsItem *item : std::as_const(m_graphicsItems)) {
        item->setState(newState);
        m_marbleWidget->model()->treeModel()->updateFeature(item->placemark());
    }
}

void AnnotatePlugin::setupCursor(SceneGraphicsItem *item)
{
    if (!item || item->state() == SceneGraphicsItem::AddingNodes) {
        m_marbleWidget->setCursor(Qt::DragCopyCursor);
    } else {
        // Nothing to do. The other cursor changes were moved to the handleRequests() section.
    }
}

void AnnotatePlugin::cutItem()
{
    disableFocusActions();

    // If there is already an item copied/cut, free its memory and replace it with this one.
    // The same applies when copying.
    if (m_clipboardItem) {
        delete m_clipboardItem->placemark();
        delete m_clipboardItem;
        m_clipboardItem = nullptr;
    }

    m_clipboardItem = m_focusItem;
    m_pasteGraphicItem->setVisible(true);

    m_graphicsItems.removeAll(m_focusItem);
    m_marbleWidget->model()->treeModel()->removeFeature(m_focusItem->placemark());

    m_focusItem = nullptr;
}

void AnnotatePlugin::copyItem()
{
    if (m_clipboardItem) {
        delete m_clipboardItem->placemark();
        delete m_clipboardItem;
        m_clipboardItem = nullptr;
    }

    // Just copy the placemark and instantiate a new object based on its graphic type.
    // FIXME: Here is obvious a problem in the case of AreaAnnotation (when copying a
    // placemark which has a GeoDataPolygon geometry?). Later Edit: The same applies for
    // polylines (GeoDataLineString geometries).
    auto placemark = new GeoDataPlacemark(*m_focusItem->placemark());
    if (m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicAreaAnnotation) {
        m_clipboardItem = new AreaAnnotation(placemark);
    } else if (m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicTextAnnotation) {
        m_clipboardItem = new PlacemarkTextAnnotation(placemark);
    } else if (m_focusItem->graphicType() == SceneGraphicsTypes::SceneGraphicPolylineAnnotation) {
        m_clipboardItem = new PolylineAnnotation(placemark);
    }

    m_pasteGraphicItem->setVisible(true);
}

void AnnotatePlugin::pasteItem()
{
    const QPoint eventPoint = m_marbleWidget->popupMenu()->mousePosition();

    qreal lon, lat;
    m_marbleWidget->geoCoordinates(eventPoint.x(), eventPoint.y(), lon, lat, GeoDataCoordinates::Radian);
    const GeoDataCoordinates newCoords(lon, lat);

    m_clipboardItem->move(m_fromWhereToCopy, newCoords);
    m_marbleWidget->model()->treeModel()->addFeature(m_annotationDocument, m_clipboardItem->placemark());
    m_graphicsItems.append(m_clipboardItem);

    m_clipboardItem->setFocus(true);
    enableActionsOnItemType(QLatin1StringView(m_clipboardItem->graphicType()));
    m_focusItem = m_clipboardItem;
    m_clipboardItem = nullptr;

    m_pasteGraphicItem->setVisible(false);
}

const GeoDataCoordinates AnnotatePlugin::mouseGeoDataCoordinates(QMouseEvent *mouseEvent) const
{
    qreal lon, lat;
    m_marbleWidget->geoCoordinates(mouseEvent->pos().x(), mouseEvent->pos().y(), lon, lat, GeoDataCoordinates::Radian);
    return GeoDataCoordinates(lon, lat);
}

}

#include "moc_AnnotatePlugin.cpp"
