/***************************************************************************
 *   Copyright (C) 2004-2006 by David Saxton                               *
 *   david@bluehaze.org                                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/

#include "canvasmanipulator.h"
#include "canvasitemparts.h"
#include "cnitemgroup.h"
#include "connector.h"
#include "dptext.h"
#include "flowcontainer.h"
#include "icndocument.h"
#include "itemview.h"
#include "mechanicsdocument.h"
#include "mechanicsgroup.h"
#include "mechanicsitem.h"
#include "node.h"
#include "nodegroup.h"
#include "picitem.h"
#include "resizeoverlay.h"

#include "utils.h"
#include <cmath>
#include <cstdlib>

#include <KConfigGroup>
#include <KSharedConfig>

#include <QCursor>
#include <QDebug>
#include <QPainter>
#include <QTimer>

// FIXME: This source file is HUUUGE!!!, contains numerous clases, should be broken down.

// BEGIN class CMManager
CMManager::CMManager(ItemDocument *itemDocument)
    : QObject()
{
    b_allowItemScroll = true;
    p_lastMouseOverResizeHandle = nullptr;
    m_canvasManipulator = nullptr;
    p_itemDocument = itemDocument;
    m_cmState = 0;
    p_lastMouseOverItem = nullptr;
    p_lastItemClicked = nullptr;
    m_drawAction = -1;
    m_allowItemScrollTmr = new QTimer(this);
    connect(m_allowItemScrollTmr, &QTimer::timeout, this, &CMManager::slotAllowItemScroll);

    KConfigGroup grGen = KSharedConfig::openConfig()->group("General");
    slotSetManualRoute(grGen.readEntry("ManualRouting", false));
}

CMManager::~CMManager()
{
    delete m_allowItemScrollTmr;
    delete m_canvasManipulator;

    qDeleteAll(m_manipulatorInfoList);
    m_manipulatorInfoList.clear();
}

void CMManager::addManipulatorInfo(ManipulatorInfo *eventInfo)
{
    if (eventInfo && !m_manipulatorInfoList.contains(eventInfo)) {
        m_manipulatorInfoList.prepend(eventInfo);
    }
}

void CMManager::cancelCurrentManipulation()
{
    delete m_canvasManipulator;
    m_canvasManipulator = nullptr;
    setRepeatedAddId();
}

void CMManager::mousePressEvent(EventInfo eventInfo)
{
    if (m_canvasManipulator) {
        if (m_canvasManipulator->mousePressedRepeat(eventInfo)) {
            delete m_canvasManipulator;
            m_canvasManipulator = nullptr;
        }
        return;
    }

    uint eventState = 0;
    if (eventInfo.isRightClick)
        eventState |= CMManager::es_right_click;

    if (eventInfo.ctrlPressed)
        eventState |= CMManager::es_ctrl_pressed;

    uint itemType = 0;
    uint cnItemType = 0;

    KtlQCanvasItem *qcanvasItem = eventInfo.qcanvasItemClickedOn;

    if (!qcanvasItem)
        itemType = it_none;
    else if (dynamic_cast<Node *>(qcanvasItem))
        itemType = it_node;
    else if (dynamic_cast<ConnectorLine *>(qcanvasItem) || dynamic_cast<Connector *>(qcanvasItem))
        itemType = it_connector;
    else if (dynamic_cast<PinItem *>(qcanvasItem))
        itemType = it_pin;
    else if (dynamic_cast<ResizeHandle *>(qcanvasItem))
        itemType = it_resize_handle;
    else if (DrawPart *drawPartClickedOn = dynamic_cast<DrawPart *>(qcanvasItem)) {
        itemType = it_drawpart;

        if (drawPartClickedOn->mousePressEvent(eventInfo)) {
            p_lastItemClicked = drawPartClickedOn;
            return;
        }

        if (drawPartClickedOn->isMovable())
            cnItemType |= CMManager::isi_isMovable;
    } else if (MechanicsItem *p_mechanicsItemClickedOn = dynamic_cast<MechanicsItem *>(qcanvasItem)) {
        itemType = it_mechanics_item;

        if (p_mechanicsItemClickedOn->mousePressEvent(eventInfo)) {
            p_lastItemClicked = p_mechanicsItemClickedOn;
            return;
        }
    } else {
        if (Widget *widget = dynamic_cast<Widget *>(qcanvasItem))
            qcanvasItem = widget->parent();

        if (CNItem *cnItemClickedOn = dynamic_cast<CNItem *>(qcanvasItem)) {
            itemType = it_canvas_item;

            if (cnItemClickedOn->mousePressEvent(eventInfo)) {
                p_lastItemClicked = cnItemClickedOn;
                return;
            }

            if (cnItemClickedOn->isMovable())
                cnItemType |= CMManager::isi_isMovable;
        }
    }

    // 	uint highestScore=0;
    // 	ManipulatorInfo *best = nullptr;
    const ManipulatorInfoList::iterator end = m_manipulatorInfoList.end();
    for (ManipulatorInfoList::iterator it = m_manipulatorInfoList.begin(); it != end && !m_canvasManipulator; ++it) {
        if ((*it)->m_acceptManipulationPtr(eventState, m_cmState, itemType, cnItemType)) {
            m_canvasManipulator = (*it)->m_createManipulatorPtr(p_itemDocument, this);
        }
    }

    if (m_canvasManipulator) {
        if (m_canvasManipulator->mousePressedInitial(eventInfo)) {
            delete m_canvasManipulator;
            m_canvasManipulator = nullptr;
        }
    }
}

void CMManager::mouseDoubleClickEvent(const EventInfo &eventInfo)
{
    if (m_canvasManipulator) {
        // Translate this into a repeat-click event
        if (m_canvasManipulator->mousePressedRepeat(eventInfo)) {
            delete m_canvasManipulator;
            m_canvasManipulator = nullptr;
        }
        return;
    }

    Item *item = dynamic_cast<Item *>(eventInfo.qcanvasItemClickedOn);
    if (item) {
        item->mouseDoubleClickEvent(eventInfo);
        return;
    }

    Widget *widget = dynamic_cast<Widget *>(eventInfo.qcanvasItemClickedOn);
    if (widget) {
        widget->parent()->mouseDoubleClickEvent(eventInfo);
        return;
    }
}

void CMManager::mouseMoveEvent(const EventInfo &eventInfo)
{
    if (m_canvasManipulator) {
        if (m_canvasManipulator->mouseMoved(eventInfo)) {
            delete m_canvasManipulator;
            m_canvasManipulator = nullptr;
        }
        ItemView *itemView = dynamic_cast<ItemView *>(p_itemDocument->activeView());
        if (itemView)
            itemView->scrollToMouse(eventInfo.pos);
        return;
    }

    // BEGIN
    KtlQCanvasItem *qcnItem = p_itemDocument->itemAtTop(eventInfo.pos);
    Item *item;
    Widget *widget = dynamic_cast<Widget *>(qcnItem);
    if (widget)
        item = widget->parent();
    else
        item = dynamic_cast<Item *>(qcnItem);

    if (p_lastMouseOverItem != (QPointer<Item>)item) {
        QEvent event(QEvent::Leave);

        if (p_lastMouseOverItem)
            p_lastMouseOverItem->leaveEvent(nullptr);

        if (item)
            item->enterEvent(nullptr);

        p_lastMouseOverItem = item;
    }

    // If we clicked on an item, then continue to pass mouse events to that item until we release the mouse...
    if (p_lastItemClicked) {
        p_lastItemClicked->mouseMoveEvent(eventInfo);
    } else if (item) {
        item->mouseMoveEvent(eventInfo);
    }
    // END

    updateCurrentResizeHandle(dynamic_cast<ResizeHandle *>(qcnItem));
}

void CMManager::updateCurrentResizeHandle(ResizeHandle *resizeHandle)
{
    if (p_lastMouseOverResizeHandle != (QPointer<ResizeHandle>)resizeHandle) {
        if (p_lastMouseOverResizeHandle)
            p_lastMouseOverResizeHandle->setHover(false);
        p_lastMouseOverResizeHandle = resizeHandle;
        if (resizeHandle)
            resizeHandle->setHover(true);
    }
}

void CMManager::mouseReleaseEvent(const EventInfo &eventInfo)
{
    // If it returns true, then it has finished its editing operation
    if (m_canvasManipulator && m_canvasManipulator->mouseReleased(eventInfo)) {
        delete m_canvasManipulator;
        m_canvasManipulator = nullptr;
    }

    if (p_lastItemClicked) {
        p_lastItemClicked->mouseReleaseEvent(eventInfo);
        p_lastItemClicked = nullptr;
    }

    updateCurrentResizeHandle(dynamic_cast<ResizeHandle *>(p_itemDocument->itemAtTop(eventInfo.pos)));
}

void CMManager::wheelEvent(const EventInfo &eventInfo)
{
    bool accepted = false;
    if (b_allowItemScroll) {
        KtlQCanvasItem *qcnItem = p_itemDocument->itemAtTop(eventInfo.pos);
        Item *item;
        Widget *widget = dynamic_cast<Widget *>(qcnItem);
        if (widget)
            item = widget->parent();
        else
            item = dynamic_cast<Item *>(qcnItem);
        if (item)
            accepted = item->wheelEvent(eventInfo);
    }
    if (!accepted) {
        // Only allow scrolling of items if we have not just been scrolling the canvas
        b_allowItemScroll = false;
        m_allowItemScrollTmr->stop();
        m_allowItemScrollTmr->setSingleShot(true);
        m_allowItemScrollTmr->start(500 /*,true */);

        ItemView *itemView = dynamic_cast<ItemView *>(p_itemDocument->activeView());
        if (itemView) {
            itemView->cvbEditor()->setPassEventsToView(false);
            itemView->cvbEditor()->contentsWheelEvent(eventInfo.wheelEvent(0, 0));
            itemView->cvbEditor()->setPassEventsToView(true);
        }
    }
}

void CMManager::setDrawAction(int drawAction)
{
    if (m_drawAction == drawAction)
        return;

    m_drawAction = drawAction;
    setCMState(cms_draw, (m_drawAction != -1));
}

void CMManager::slotSetManualRoute(bool manualRoute)
{
    KConfigGroup grGen = KSharedConfig::openConfig()->group("General");
    grGen.writeEntry("ManualRouting", manualRoute);

    setCMState(cms_manual_route, manualRoute);
}

void CMManager::setCMState(CMState type, bool state)
{
    // Set or clear the correct bit
    state ? (m_cmState |= type) : (m_cmState &= (~type));

    if (type == CMManager::cms_manual_route)
        emit manualRoutingChanged(state);
}

void CMManager::setRepeatedAddId(const QString &repeatedId)
{
    m_repeatedItemId = repeatedId;
}
// END class CMManager

// BEGIN class CanvasManipulator
CanvasManipulator::CanvasManipulator(ItemDocument *itemDocument, CMManager *cmManager)
{
    p_itemDocument = itemDocument;
    p_icnDocument = dynamic_cast<ICNDocument *>(itemDocument);
    p_mechanicsDocument = dynamic_cast<MechanicsDocument *>(itemDocument);
    p_canvas = p_itemDocument->canvas();
    // 	b_connectorsAllowedRouting = true;
    p_selectList = p_itemDocument->selectList();
    p_cnItemSelectList = dynamic_cast<CNItemGroup *>(p_selectList);
    p_mechItemSelectList = dynamic_cast<MechanicsGroup *>(p_selectList);
    p_cnItemClickedOn = nullptr;
    p_cmManager = cmManager;

    // connect(itemDocument->canvas(), SIGNAL(resized(const QRect &, const QRect &)), this, SLOT(canvasResized(const QRect &, const QRect &)));
    connect(itemDocument->canvas(), &Canvas::resized, this, &CanvasManipulator::canvasResized);
}

CanvasManipulator::~CanvasManipulator()
{
}

QPoint CanvasManipulator::snapPoint(QPoint point)
{
    point /= 8;
    point *= 8;
    point += QPoint(4, 4);
    return point;
}
// END class CanvasManipulator

CMRepeatedItemAdd::CMRepeatedItemAdd(ItemDocument *itemDocument, CMManager *cmManager)
    : CanvasManipulator(itemDocument, cmManager)
{
}

CMRepeatedItemAdd::~CMRepeatedItemAdd()
{
}

CanvasManipulator *CMRepeatedItemAdd::construct(ItemDocument *itemDocument, CMManager *cmManager)
{
    return new CMRepeatedItemAdd(itemDocument, cmManager);
}

ManipulatorInfo *CMRepeatedItemAdd::manipulatorInfo()
{
    ManipulatorInfo *eventInfo = new ManipulatorInfo();
    eventInfo->m_acceptManipulationPtr = CMRepeatedItemAdd::acceptManipulation;
    eventInfo->m_createManipulatorPtr = CMRepeatedItemAdd::construct;
    return eventInfo;
}

bool CMRepeatedItemAdd::acceptManipulation(uint /*eventState*/, uint cmState, uint /*itemType*/, uint /*cnItemType*/)
{
    return (cmState & CMManager::cms_repeated_add);
}

bool CMRepeatedItemAdd::mousePressedRepeat(const EventInfo &eventInfo)
{
    return mousePressedInitial(eventInfo);
}

bool CMRepeatedItemAdd::mousePressedInitial(const EventInfo &eventInfo)
{
    m_eventInfo = eventInfo;
    if (eventInfo.isRightClick) {
        p_cmManager->setCMState(CMManager::cms_repeated_add, false);
        return true;
    }

    p_icnDocument->addItem(p_cmManager->repeatedItemId(), eventInfo.pos, true);
    p_itemDocument->requestStateSave();
    return false;
}

bool CMRepeatedItemAdd::mouseMoved(const EventInfo & /*eventInfo*/)
{
    return false;
}

bool CMRepeatedItemAdd::mouseReleased(const EventInfo & /*eventInfo*/)
{
    return false;
}

CMRightClick::CMRightClick(ItemDocument *itemDocument, CMManager *cmManager)
    : CanvasManipulator(itemDocument, cmManager)
{
}

CMRightClick::~CMRightClick()
{
}

CanvasManipulator *CMRightClick::construct(ItemDocument *itemDocument, CMManager *cmManager)
{
    return new CMRightClick(itemDocument, cmManager);
}

ManipulatorInfo *CMRightClick::manipulatorInfo()
{
    ManipulatorInfo *eventInfo = new ManipulatorInfo();
    // 	eventInfo->m_eventState.m_activate = CMManager::es_right_click;
    eventInfo->m_acceptManipulationPtr = CMRightClick::acceptManipulation;
    eventInfo->m_createManipulatorPtr = CMRightClick::construct;
    return eventInfo;
}

bool CMRightClick::acceptManipulation(uint eventState, uint /*cmState*/, uint /*itemType*/, uint /*cnItemType*/)
{
    return eventState & CMManager::es_right_click;
}

bool CMRightClick::mousePressedInitial(const EventInfo &eventInfo)
{
    m_eventInfo = eventInfo;
    p_itemDocument->canvasRightClick(eventInfo.globalPos, eventInfo.qcanvasItemClickedOn);
    return true;
}

bool CMRightClick::mouseMoved(const EventInfo & /*eventInfo*/)
{
    return true;
}

bool CMRightClick::mouseReleased(const EventInfo & /*eventInfo*/)
{
    return true;
}

// BEGIN class ConnectorDraw
ConnectorDraw::ConnectorDraw(ItemDocument *itemDocument, CMManager *cmManager)
    : CanvasManipulator(itemDocument, cmManager)
{
    p_startNode = nullptr;
    p_startConnector = nullptr;
    p_endNode = nullptr;
    p_endConnector = nullptr;
}

ConnectorDraw::~ConnectorDraw()
{
}

QColor ConnectorDraw::validConnectionColor()
{
    return QColor(255, 166, 0);
}

QPoint ConnectorDraw::toValidPos(const QPoint &clickPos, Connector *clickedConnector) const
{
    if (!clickedConnector)
        return clickPos;

    const QPointList pointList = clickedConnector->connectorPoints();

    QPointList::const_iterator end = pointList.end();

    double dl[] = {0.5, 8.5, 11.5, 18.0, 23.0}; // various distances rounded up of (0,0) cells, (0,1), etc
    for (unsigned i = 0; i < 5; ++i) {
        for (QPointList::const_iterator it = pointList.begin(); it != end; ++it) {
            if (qpoint_distance(*it, clickPos) <= dl[i])
                return *it;
        }
    }

    return clickPos;
}

Connector *ConnectorDraw::toConnector(Node *node)
{
    if (!node || node->numCon(true, false) < 3)
        return nullptr;

    return node->getAConnector();
}

void ConnectorDraw::grabEndStuff(KtlQCanvasItem *endItem, const QPoint &pos, bool posIsExact)
{
    if (!endItem)
        return;

    CNItem *cnItem = dynamic_cast<CNItem *>(endItem);
    if (cnItem && !posIsExact)
        p_endNode = cnItem->getClosestNode(pos);
    else
        p_endNode = dynamic_cast<Node *>(endItem);

    if (p_endNode && p_endNode->numCon(true, false) > 2) {
        p_endConnector = toConnector(p_endNode);
        p_endNode = nullptr;
    }

    // If the endItem is a node, we have to finish exactly on the end when posIsExact is true
    if (posIsExact && p_endNode && (p_endNode->x() != pos.x() || p_endNode->y() != pos.y()))
        p_endNode = nullptr;

    if (!p_endConnector)
        p_endConnector = dynamic_cast<Connector *>(endItem);
}
// END class ConnectorDraw

// BEGIN class CMAutoConnector
CMAutoConnector::CMAutoConnector(ItemDocument *itemDocument, CMManager *cmManager)
    : ConnectorDraw(itemDocument, cmManager)
{
    m_connectorLine = nullptr;
    p_startNode = nullptr;
    p_startConnector = nullptr;
}

CMAutoConnector::~CMAutoConnector()
{
    delete m_connectorLine;
}

CanvasManipulator *CMAutoConnector::construct(ItemDocument *itemDocument, CMManager *cmManager)
{
    return new CMAutoConnector(itemDocument, cmManager);
}

ManipulatorInfo *CMAutoConnector::manipulatorInfo()
{
    ManipulatorInfo *eventInfo = new ManipulatorInfo();
    eventInfo->m_acceptManipulationPtr = CMAutoConnector::acceptManipulation;
    eventInfo->m_createManipulatorPtr = CMAutoConnector::construct;
    return eventInfo;
}

bool CMAutoConnector::acceptManipulation(uint /*eventState*/, uint cmState, uint itemType, uint /*cnItemType*/)
{
    return (itemType & (CMManager::it_node | CMManager::it_connector)) && !(cmState & CMManager::cms_manual_route);
}

bool CMAutoConnector::mousePressedInitial(const EventInfo &eventInfo)
{
    m_eventInfo = eventInfo;

    p_startNode = dynamic_cast<Node *>(eventInfo.qcanvasItemClickedOn);

    if (p_startNode) {
        m_eventInfo.pos = m_prevPos = p_icnDocument->gridSnap(QPoint((int)p_startNode->x(), (int)p_startNode->y()));
        if (p_startNode->numCon(true, false) > 2) {
            p_startConnector = toConnector(p_startNode);
            p_startNode = nullptr;
        }
    } else if ((p_startConnector = dynamic_cast<Connector *>(eventInfo.qcanvasItemClickedOn))) {
        // 		startConnectorPoint = m_eventInfo.pos = m_prevPos = p_icnDocument->gridSnap(m_eventInfo.pos);
        startConnectorPoint = m_eventInfo.pos = m_prevPos = toValidPos(m_eventInfo.pos, p_startConnector);
    } else
        return true;

    p_icnDocument->unselectAll();

    delete m_connectorLine;
    m_connectorLine = new KtlQCanvasLine(p_canvas);
    m_connectorLine->setPen(QColor(0, 0, 0));
    m_connectorLine->setZ(ItemDocument::Z::ConnectorCreateLine);
    m_connectorLine->show();
    return false;
}

bool CMAutoConnector::mouseMoved(const EventInfo &eventInfo)
{
    const QPoint pos = eventInfo.pos;

    int newX = p_icnDocument->gridSnap(pos.x());
    int newY = p_icnDocument->gridSnap(pos.y());

    bool movedFlag = false;

    if (newX != m_prevPos.x()) {
        m_prevPos.setX(newX);
        movedFlag = true;
    }

    if (newY != m_prevPos.y()) {
        m_prevPos.setY(newY);
        movedFlag = true;
    }

    m_connectorLine->setPoints(m_eventInfo.pos.x(), m_eventInfo.pos.y(), newX, newY);

    if (movedFlag) {
        KtlQCanvasItem *startItem = nullptr;
        if (p_startNode)
            startItem = p_startNode;
        else if (p_startConnector)
            startItem = p_startConnector;

        KtlQCanvasItem *endItem = p_icnDocument->itemAtTop(QPoint(newX, newY));
        if (CNItem *cni = dynamic_cast<CNItem *>(endItem))
            endItem = cni->getClosestNode(QPoint(newX, newY));

        bool validLine = p_icnDocument->canConnect(startItem, endItem);
        m_connectorLine->setPen(validLine ? validConnectionColor() : Qt::black);
    }
    return false;
}

bool CMAutoConnector::mouseReleased(const EventInfo &eventInfo)
{
    const QPoint pos = eventInfo.pos;

    QPoint end = m_connectorLine->endPoint();
    delete m_connectorLine;
    m_connectorLine = nullptr;

    KtlQCanvasItem *qcanvasItem = p_icnDocument->itemAtTop(end);
    if (!qcanvasItem)
        return true;

    grabEndStuff(qcanvasItem, pos, false);

    if (p_startConnector) {
        if (p_endConnector) {
            if (!p_icnDocument->createConnector(p_endConnector, p_startConnector, p_icnDocument->gridSnap(pos), startConnectorPoint))
                return true;
        } else if (p_endNode) {
            if (!p_icnDocument->createConnector(p_endNode, p_startConnector, startConnectorPoint))
                return true;
        } else
            return true;
    } else if (p_startNode) {
        if (p_endConnector) {
            if (!p_icnDocument->createConnector(p_startNode, p_endConnector, p_icnDocument->gridSnap(pos)))
                return true;
        } else if (p_endNode) {
            if (!p_icnDocument->createConnector(p_startNode, p_endNode))
                return true;
        } else
            return true;
    } else
        return true;

    p_itemDocument->requestStateSave();
    return true;
}
// END class CMAutoConnector

// BEGIN class CMManualConnector
CMManualConnector::CMManualConnector(ItemDocument *itemDocument, CMManager *cmManager)
    : ConnectorDraw(itemDocument, cmManager)
{
    m_manualConnectorDraw = nullptr;
}

CMManualConnector::~CMManualConnector()
{
    delete m_manualConnectorDraw;
}

CanvasManipulator *CMManualConnector::construct(ItemDocument *itemDocument, CMManager *cmManager)
{
    return new CMManualConnector(itemDocument, cmManager);
}

ManipulatorInfo *CMManualConnector::manipulatorInfo()
{
    ManipulatorInfo *eventInfo = new ManipulatorInfo();
    eventInfo->m_acceptManipulationPtr = CMManualConnector::acceptManipulation;
    eventInfo->m_createManipulatorPtr = CMManualConnector::construct;
    return eventInfo;
}

bool CMManualConnector::acceptManipulation(uint /*eventState*/, uint cmState, uint itemType, uint /*cnItemType*/)
{
    return (itemType & (CMManager::it_node | CMManager::it_connector)) && (cmState & CMManager::cms_manual_route);
}

bool CMManualConnector::mousePressedInitial(const EventInfo &eventInfo)
{
    if (eventInfo.isRightClick)
        return true;

    m_eventInfo = eventInfo;

    p_icnDocument->unselectAll();

    QPoint sp;

    if ((p_startNode = dynamic_cast<Node *>(eventInfo.qcanvasItemClickedOn))) {
        sp.setX((int)p_startNode->x());
        sp.setY((int)p_startNode->y());
        if (p_startNode->numCon(true, false) > 2) {
            p_startConnector = toConnector(p_startNode);
            p_startNode = nullptr;
        }
    } else {
        p_startConnector = dynamic_cast<Connector *>(eventInfo.qcanvasItemClickedOn);
        sp = toValidPos(eventInfo.pos, p_startConnector);
    }
    startConnectorPoint = sp;

    if (m_manualConnectorDraw)
        delete m_manualConnectorDraw;
    m_manualConnectorDraw = new ManualConnectorDraw(p_icnDocument, sp);
    return false;
}

bool CMManualConnector::mousePressedRepeat(const EventInfo &eventInfo)
{
    m_eventInfo = eventInfo;
    if (eventInfo.isRightClick) {
        return true;
    }
    m_manualConnectorDraw->mouseClicked(p_icnDocument->gridSnap(m_eventInfo.pos));
    return false;
}

bool CMManualConnector::mouseMoved(const EventInfo &eventInfo)
{
    if (!m_manualConnectorDraw)
        return true;

    const QPoint pos = eventInfo.pos;

    int newX = p_icnDocument->gridSnap(pos.x());
    int newY = p_icnDocument->gridSnap(pos.y());

    bool movedFlag = false;

    if (newX != m_prevPos.x()) {
        m_prevPos.setX(newX);
        movedFlag = true;
    }

    if (newY != m_prevPos.y()) {
        m_prevPos.setY(newY);
        movedFlag = true;
    }

    if (movedFlag) {
        KtlQCanvasItem *startItem = nullptr;
        if (p_startNode)
            startItem = p_startNode;
        else if (p_startConnector)
            startItem = p_startConnector;

        KtlQCanvasItem *endItem = p_icnDocument->itemAtTop(QPoint(newX, newY));

        // If the endItem is a node, we have to finish exactly on the end.
        if (Node *node = dynamic_cast<Node *>(endItem)) {
            if (node->x() != newX || node->y() != newY)
                endItem = nullptr;
        }

        bool validLine = p_icnDocument->canConnect(startItem, endItem);

        m_manualConnectorDraw->setColor(validLine ? validConnectionColor() : Qt::black);
        m_manualConnectorDraw->mouseMoved(QPoint(newX, newY));
    }

    return false;
}

bool CMManualConnector::mouseReleased(const EventInfo &eventInfo)
{
    if (!m_manualConnectorDraw)
        return true;

    QPoint pos = p_icnDocument->gridSnap(eventInfo.pos);

    grabEndStuff(m_manualConnectorDraw->mouseClicked(pos), pos, true);

    if (!p_endNode && !p_endConnector)
        return false;

    // Create the points that define the manual route
    QPointList list = m_manualConnectorDraw->pointList();
    delete m_manualConnectorDraw;
    m_manualConnectorDraw = nullptr;

    if (p_startConnector) {
        if (p_endConnector) {
            if (!p_icnDocument->createConnector(p_endConnector, p_startConnector, p_icnDocument->gridSnap(pos), startConnectorPoint, &list))
                return true;
        } else // if (p_endNode)
        {
            if (!p_icnDocument->createConnector(p_endNode, p_startConnector, startConnectorPoint, &list))
                return true;
        }
    } else if (p_startNode) {
        if (p_endConnector) {
            if (!p_icnDocument->createConnector(p_startNode, p_endConnector, p_icnDocument->gridSnap(pos), &list))
                return true;
        } else // if (p_endNode)
        {
            if (!p_icnDocument->createConnector(p_startNode, p_endNode, &list))
                return true;
        }
    } else
        return true;

    p_itemDocument->requestStateSave();
    return true;
}
// END class CMManualConnector

// BEGIN class CMItemMove
CMItemMove::CMItemMove(ItemDocument *itemDocument, CMManager *cmManager)
    : CanvasManipulator(itemDocument, cmManager)
{
    p_flowContainerCandidate = nullptr;
    m_bItemsSnapToGrid = false;
}

CMItemMove::~CMItemMove()
{
}

CanvasManipulator *CMItemMove::construct(ItemDocument *itemDocument, CMManager *cmManager)
{
    return new CMItemMove(itemDocument, cmManager);
}

ManipulatorInfo *CMItemMove::manipulatorInfo()
{
    ManipulatorInfo *eventInfo = new ManipulatorInfo();
    eventInfo->m_acceptManipulationPtr = CMItemMove::acceptManipulation;
    eventInfo->m_createManipulatorPtr = CMItemMove::construct;
    return eventInfo;
}

bool CMItemMove::acceptManipulation(uint eventState, uint /*cmState*/, uint itemType, uint cnItemType)
{
    return ((itemType & CMManager::it_canvas_item) || (itemType & CMManager::it_drawpart)) && (cnItemType & CMManager::isi_isMovable) && !(eventState & CMManager::es_right_click);
}

bool CMItemMove::mousePressedInitial(const EventInfo &eventInfo)
{
    m_eventInfo = eventInfo;
    m_prevPos = eventInfo.pos;

    Item *item = dynamic_cast<Item *>(eventInfo.qcanvasItemClickedOn);

    if (!item)
        return true;

    if (!p_selectList->contains(item)) {
        if (!eventInfo.ctrlPressed)
            p_itemDocument->unselectAll();

        p_itemDocument->select(item);
    } else if (m_eventInfo.ctrlPressed)
        p_itemDocument->unselect(item);

    if (p_selectList->isEmpty())
        return true;

    // We want to allow dragging into FlowContainers if this is a FlowView
    p_flowContainerCandidate = nullptr;
    {
        const ItemList &itemList = p_icnDocument->itemList();
        const ItemList::const_iterator ciEnd = itemList.end();
        for (ItemList::const_iterator it = itemList.begin(); it != ciEnd; ++it) {
            if (FlowContainer *flowContainer = dynamic_cast<FlowContainer *>((Item *)*it))
                flowContainer->setFullBounds(true);
        }
    }

    ItemList itemList = p_cnItemSelectList->items(false);
    itemList.removeAll((Item *)nullptr);

    m_bItemsSnapToGrid = false;
    const ItemList::iterator itemListEnd = itemList.end();
    for (ItemList::iterator it = itemList.begin(); it != itemListEnd; ++it) {
        CNItem *cnItem = dynamic_cast<CNItem *>((Item *)*it);
        if (!cnItem || !cnItem->canvas())
            continue;

        m_bItemsSnapToGrid = true;
    }

    if (m_bItemsSnapToGrid)
        m_prevSnapPoint = this->snapPoint(m_prevPos);
    else
        m_prevSnapPoint = m_prevPos;

    ConnectorList fixedConnectors;
    p_icnDocument->getTranslatable(itemList, &fixedConnectors, &m_translatableConnectors, &m_translatableNodeGroups);

    const ConnectorList::iterator fixedConnectorsEnd = fixedConnectors.end();
    for (ConnectorList::iterator it = fixedConnectors.begin(); it != fixedConnectorsEnd; ++it)
        (*it)->setSemiHidden(true);

    p_flowContainerCandidate = p_icnDocument->flowContainer(eventInfo.pos);

    return false;
}

void CMItemMove::canvasResized(const QRect & /*oldSize*/, const QRect & /*newSize*/)
{
    // QPoint delta = oldSize.topLeft() - newSize.topLeft(); // 2017.10.01 - comment out unused variable

    // 	scrollCanvasToSelection();

    // 	QCursor::setPos( QCursor::pos() + delta );
    // 	m_prevPos += delta;
    // 	m_prevSnapPoint += delta;
}

void CMItemMove::scrollCanvasToSelection()
{
    QRect bound;
    ItemList itemList = p_cnItemSelectList->items(false);
    itemList.removeAll((Item *)nullptr);
    const ItemList::iterator itemListEnd = itemList.end();
    for (ItemList::iterator it = itemList.begin(); it != itemListEnd; ++it)
        bound |= (*it)->boundingRect();

    QPoint scrollToPos = m_prevPos;
    if (m_dx < 0) {
        // Scrolling left
        scrollToPos -= QPoint(bound.left(), 0);
    } else {
        // Scrolling right
        scrollToPos += QPoint(bound.right(), 0);
    }

    if (m_dy < 0) {
        // Scrolling up
        scrollToPos -= QPoint(0, bound.top());
    } else {
        // Scrolling right
        scrollToPos += QPoint(0, bound.bottom());
    }

    ItemView *itemView = dynamic_cast<ItemView *>(p_itemDocument->activeView());
    if (itemView)
        itemView->scrollToMouse(scrollToPos);
}

bool CMItemMove::mouseMoved(const EventInfo &eventInfo)
{
    QPoint pos = eventInfo.pos;

    QPoint snapPoint = pos;
    if (m_bItemsSnapToGrid)
        snapPoint = this->snapPoint(snapPoint);

    int dx = snapPoint.x() - m_prevSnapPoint.x();
    int dy = snapPoint.y() - m_prevSnapPoint.y();

    m_dx = dx;
    m_dy = dy;

    const ItemList itemList = p_cnItemSelectList->items();
    const ItemList::const_iterator end = itemList.end();

    for (ItemList::const_iterator it = itemList.begin(); it != end; ++it) {
        if (!*it || !(*it)->isMovable())
            continue;

        // QRect oldRect = (*it)->boundingRect(); // 2017.10.01 - comment out unused variable
        (*it)->moveBy(dx, dy);
        // QRect newRect = (*it)->boundingRect();
        // QRect merged = oldRect | newRect; // 2017.10.01 - comment out unused variable
    }

    if ((dx != 0) || (dy != 0)) {
        const ConnectorList::iterator frEnd = m_translatableConnectors.end();
        for (ConnectorList::iterator it = m_translatableConnectors.begin(); it != frEnd; ++it)
            (*it)->translateRoute(dx, dy);

        const NodeGroupList::iterator end = m_translatableNodeGroups.end();
        for (NodeGroupList::iterator it = m_translatableNodeGroups.begin(); it != end; ++it)
            (*it)->translate(dx, dy);
    }

    FlowContainer *fc = p_icnDocument->flowContainer(pos);
    if (fc != p_flowContainerCandidate) {
        if (p_flowContainerCandidate) {
            p_flowContainerCandidate->setSelected(false);
            p_flowContainerCandidate = nullptr;
        }
    }

    if (fc) {
        p_flowContainerCandidate = fc;
        p_flowContainerCandidate->setSelected(true);
    }

    p_itemDocument->requestEvent(ItemDocument::ItemDocumentEvent::ResizeCanvasToItems);
    p_canvas->update();
    m_prevPos = pos;
    m_prevSnapPoint = snapPoint;

    // 	scrollCanvasToSelection();

    return false;
}

bool CMItemMove::mouseReleased(const EventInfo &eventInfo)
{
    // Is the release event from a right click (which rotates items)?
    if (eventInfo.isRightClick || eventInfo.isMiddleClick)
        return false;

    QStringList itemIDs;

    const ItemList itemList = p_cnItemSelectList->items();
    const ItemList::const_iterator ilEnd = itemList.end();
    for (ItemList::const_iterator it = itemList.begin(); it != ilEnd; ++it) {
        if (*it)
            itemIDs.append((*it)->id());
    }

    // const QPoint pos = eventInfo.pos; // 2017.10.10 - comment out unused variable

    // And make sure all connectors are properly shown
    const ConnectorList &connectorList = p_icnDocument->connectorList();
    const ConnectorList::const_iterator conEnd = connectorList.end();
    for (ConnectorList::const_iterator it = connectorList.begin(); it != conEnd; ++it) {
        (*it)->setSemiHidden(false);
    }

    if (p_flowContainerCandidate) {
        for (ItemList::const_iterator it = itemList.begin(); it != ilEnd; ++it)
            p_flowContainerCandidate->addChild(*it);

        p_flowContainerCandidate->setSelected(false);
        p_flowContainerCandidate = nullptr;
    } else {
        for (ItemList::const_iterator it = itemList.begin(); it != ilEnd; ++it)
            (*it)->setParentItem(nullptr);
    }

    // And disable the FlowContainers again...
    const ItemList &cnItemList = p_icnDocument->itemList();
    const ItemList::const_iterator end = cnItemList.end();
    for (ItemList::const_iterator it = cnItemList.begin(); it != end; ++it) {
        if (FlowContainer *flowContainer = dynamic_cast<FlowContainer *>((Item *)*it))
            flowContainer->setFullBounds(false);
    }

    if (p_icnDocument)
        p_icnDocument->requestRerouteInvalidatedConnectors();

    if (m_eventInfo.pos != eventInfo.pos)
        p_itemDocument->requestStateSave();

    p_itemDocument->requestEvent(ItemDocument::ItemDocumentEvent::ResizeCanvasToItems);

    return true;
}

bool CMItemMove::mousePressedRepeat(const EventInfo &info)
{
    if (info.isRightClick)
        p_cnItemSelectList->slotRotateCW();
    else if (info.isMiddleClick)
        p_cnItemSelectList->flipHorizontally();

    return false;
}
// END class CMItemMove

CMItemResize::CMItemResize(ItemDocument *itemDocument, CMManager *cmManager)
    : CanvasManipulator(itemDocument, cmManager)
{
}

CMItemResize::~CMItemResize()
{
}

CanvasManipulator *CMItemResize::construct(ItemDocument *itemDocument, CMManager *cmManager)
{
    return new CMItemResize(itemDocument, cmManager);
}

ManipulatorInfo *CMItemResize::manipulatorInfo()
{
    ManipulatorInfo *eventInfo = new ManipulatorInfo();
    // 	eventInfo->m_itemType.m_activate = CMManager::it_canvas_item;
    eventInfo->m_acceptManipulationPtr = CMItemResize::acceptManipulation;
    eventInfo->m_createManipulatorPtr = CMItemResize::construct;
    return eventInfo;
}

bool CMItemResize::acceptManipulation(uint eventState, uint /*cmState*/, uint itemType, uint /*cnItemType*/)
{
    return (itemType & CMManager::it_resize_handle) && !(eventState & CMManager::es_right_click);
}

bool CMItemResize::mousePressedInitial(const EventInfo &eventInfo)
{
    m_eventInfo = eventInfo;
    p_resizeHandle = dynamic_cast<ResizeHandle *>(eventInfo.qcanvasItemClickedOn);
    m_rh_dx = p_resizeHandle->x() - eventInfo.pos.x();
    m_rh_dy = p_resizeHandle->y() - eventInfo.pos.y();
    return false;
}

bool CMItemResize::mouseMoved(const EventInfo &eventInfo)
{
    int _x = int(m_rh_dx + eventInfo.pos.x());
    int _y = int(m_rh_dy + eventInfo.pos.y());

    // Shift pressed == snap to grid
    if (eventInfo.shiftPressed) {
        _x = snapToCanvas(_x);
        _y = snapToCanvas(_y);
    }

    p_resizeHandle->moveRH(_x, _y);
    return false;
}

bool CMItemResize::mouseReleased(const EventInfo & /*eventInfo*/)
{
    if (p_icnDocument)
        p_icnDocument->requestRerouteInvalidatedConnectors();
    p_itemDocument->requestStateSave();
    return true;
}

CMMechItemMove::CMMechItemMove(ItemDocument *itemDocument, CMManager *cmManager)
    : CanvasManipulator(itemDocument, cmManager)
{
}

CMMechItemMove::~CMMechItemMove()
{
}

CanvasManipulator *CMMechItemMove::construct(ItemDocument *itemDocument, CMManager *cmManager)
{
    return new CMMechItemMove(itemDocument, cmManager);
}

ManipulatorInfo *CMMechItemMove::manipulatorInfo()
{
    ManipulatorInfo *eventInfo = new ManipulatorInfo();
    // 	eventInfo->m_itemType.m_activate = CMManager::it_canvas_item;
    eventInfo->m_acceptManipulationPtr = CMMechItemMove::acceptManipulation;
    eventInfo->m_createManipulatorPtr = CMMechItemMove::construct;
    return eventInfo;
}

bool CMMechItemMove::acceptManipulation(uint eventState, uint /*cmState*/, uint itemType, uint /*cnItemType*/)
{
    return ((itemType & CMManager::it_mechanics_item) || (itemType & CMManager::it_drawpart)) && !(eventState & CMManager::es_right_click);
}

bool CMMechItemMove::mousePressedInitial(const EventInfo &eventInfo)
{
    m_eventInfo = eventInfo;
    m_prevPos = eventInfo.pos;

    Item *item = dynamic_cast<Item *>(eventInfo.qcanvasItemClickedOn);
    if (!item)
        return true;

    MechanicsItem *mechItem = dynamic_cast<MechanicsItem *>(eventInfo.qcanvasItemClickedOn);

    if (mechItem)
        m_prevClickedOnSM = mechItem->selectionMode();

    if (eventInfo.shiftPressed) {
        p_mechanicsDocument->unselectAll();
        p_mechanicsDocument->select(item);
        if (mechItem) {
            mechItem->setSelectionMode(MechanicsItem::sm_move);
            mechItem->setParentItem(nullptr);
        }
    } else if (!p_selectList->contains(mechItem)) {
        if (!eventInfo.ctrlPressed)
            p_mechanicsDocument->unselectAll();

        p_mechanicsDocument->select(item);

        if (mechItem)
            mechItem->setSelectionMode(MechanicsItem::sm_move);
    } else {
        if (mechItem)
            mechItem->setSelectionMode(MechanicsItem::sm_move);

        if (m_eventInfo.ctrlPressed)
            p_mechanicsDocument->unselect(item);
    }

    if (p_selectList->isEmpty())
        return true;

    p_mechItemSelectList->setSelectionMode(MechanicsItem::sm_move);
    p_mechItemSelectList->setRaised(true);
    return false;
}

bool CMMechItemMove::mouseMoved(const EventInfo &eventInfo)
{
    const QPoint pos = eventInfo.pos;

    int x = pos.x();
    int y = pos.y();

    const MechItemList itemList = p_mechItemSelectList->toplevelMechItemList();
    const MechItemList::const_iterator ilEnd = itemList.end();
    for (MechItemList::const_iterator it = itemList.begin(); it != ilEnd; ++it) {
        if (*it)
            (*it)->moveBy(x - m_prevPos.x(), y - m_prevPos.y());
    }

    m_prevPos = QPoint(x, y);

    p_canvas->update();
    p_itemDocument->requestEvent(ItemDocument::ItemDocumentEvent::ResizeCanvasToItems);
    return false;
}

bool CMMechItemMove::mouseReleased(const EventInfo &eventInfo)
{
    const QPoint pos = eventInfo.pos;

    int dx = pos.x() - m_eventInfo.pos.x();
    int dy = pos.y() - m_eventInfo.pos.y();

    p_mechItemSelectList->setRaised(false);

    MechanicsItem *mechItem = dynamic_cast<MechanicsItem *>(m_eventInfo.qcanvasItemClickedOn);
    if (dx == 0 && dy == 0) {
        if (mechItem && mechItem->isSelected()) {
            if (m_prevClickedOnSM == MechanicsItem::sm_resize)
                mechItem->setSelectionMode(MechanicsItem::sm_rotate);
            else
                mechItem->setSelectionMode(MechanicsItem::sm_resize);
        }
        p_itemDocument->requestStateSave();
        return true;
    }

    if (mechItem && mechItem->isSelected()) {
        if (m_prevClickedOnSM == MechanicsItem::sm_rotate)
            mechItem->setSelectionMode(MechanicsItem::sm_rotate);
        else
            mechItem->setSelectionMode(MechanicsItem::sm_resize);
    }

    QStringList itemIDs;

    ItemList itemList = p_mechItemSelectList->items();
    const ItemList::iterator ilEnd = itemList.end();
    for (ItemList::iterator it = itemList.begin(); it != ilEnd; ++it) {
        if (*it) {
            itemIDs.append((*it)->id());
        }
    }

    p_mechItemSelectList->setSelectionMode(MechanicsItem::sm_resize);
    p_itemDocument->requestStateSave();
    p_itemDocument->requestEvent(ItemDocument::ItemDocumentEvent::ResizeCanvasToItems);
    return true;
}

// BEGIN class SelectRectangle
SelectRectangle::SelectRectangle(int x, int y, int w, int h, KtlQCanvas *qcanvas)
    : m_x(x)
    , m_y(y)
{
    m_topLine = new KtlQCanvasLine(qcanvas);
    m_rightLine = new KtlQCanvasLine(qcanvas);
    m_bottomLine = new KtlQCanvasLine(qcanvas);
    m_leftLine = new KtlQCanvasLine(qcanvas);
    setSize(w, h);

    KtlQCanvasLine *lines[] = {m_topLine, m_rightLine, m_bottomLine, m_leftLine};
    for (int i = 0; i < 4; ++i) {
        lines[i]->setPen(QPen(QColor(190, 190, 190), 1, Qt::DotLine));
        lines[i]->setZ(ICNDocument::Z::Select);
        lines[i]->show();
    }
}

SelectRectangle::~SelectRectangle()
{
    delete m_topLine;
    delete m_rightLine;
    delete m_bottomLine;
    delete m_leftLine;
}

void SelectRectangle::setSize(int w, int h)
{
    m_topLine->setPoints(m_x, m_y, m_x + w, m_y);
    m_rightLine->setPoints(m_x + w, m_y, m_x + w, m_y + h);
    m_bottomLine->setPoints(m_x + w, m_y + h, m_x, m_y + h);
    m_leftLine->setPoints(m_x, m_y + h, m_x, m_y);
    m_w = w;
    m_h = h;
}

KtlQCanvasItemList SelectRectangle::collisions()
{
    KtlQCanvas *canvas = m_topLine->canvas();

    return canvas->collisions(QRect(m_x, m_y, m_w, m_h));
}
// END class SelectRectangle

// BEGIN class CMSelect
CMSelect::CMSelect(ItemDocument *itemDocument, CMManager *cmManager)
    : CanvasManipulator(itemDocument, cmManager)
{
    m_selectRectangle = nullptr;
}

CMSelect::~CMSelect()
{
    delete m_selectRectangle;
}

CanvasManipulator *CMSelect::construct(ItemDocument *itemDocument, CMManager *cmManager)
{
    return new CMSelect(itemDocument, cmManager);
}

ManipulatorInfo *CMSelect::manipulatorInfo()
{
    ManipulatorInfo *eventInfo = new ManipulatorInfo();
    // 	eventInfo->m_itemType.m_activate = CMManager::it_none;
    eventInfo->m_acceptManipulationPtr = CMSelect::acceptManipulation;
    eventInfo->m_createManipulatorPtr = CMSelect::construct;
    return eventInfo;
}

bool CMSelect::acceptManipulation(uint /*eventState*/, uint /*cmState*/, uint itemType, uint /*cnItemType*/)
{
    return (itemType & CMManager::it_none);
}

bool CMSelect::mousePressedInitial(const EventInfo &eventInfo)
{
    m_eventInfo = eventInfo;

    if (!eventInfo.ctrlPressed) {
        p_itemDocument->unselectAll();
    }

    m_selectRectangle = new SelectRectangle(eventInfo.pos.x(), eventInfo.pos.y(), 0, 0, p_canvas);
    return false;
}

bool CMSelect::mouseMoved(const EventInfo &eventInfo)
{
    QPoint pos = eventInfo.pos;

    m_selectRectangle->setSize(pos.x() - m_eventInfo.pos.x(), pos.y() - m_eventInfo.pos.y());

    if (m_eventInfo.ctrlPressed) {
        p_itemDocument->select(m_selectRectangle->collisions());
    } else if (p_selectList) {
        p_selectList->setItems(m_selectRectangle->collisions());
    }

    if (p_selectList && !p_mechanicsDocument) {
        p_selectList->setSelected(true);
    }
    return false;
}

bool CMSelect::mouseReleased(const EventInfo & /*eventInfo*/)
{
    delete m_selectRectangle;
    m_selectRectangle = nullptr;

    return true;
}
// END class CMSelect

CMItemDrag::CMItemDrag(ItemDocument *itemDocument, CMManager *cmManager)
    : CanvasManipulator(itemDocument, cmManager)
{
    b_dragged = false;
}

CMItemDrag::~CMItemDrag()
{
}

CanvasManipulator *CMItemDrag::construct(ItemDocument *itemDocument, CMManager *cmManager)
{
    return new CMItemDrag(itemDocument, cmManager);
}

ManipulatorInfo *CMItemDrag::manipulatorInfo()
{
    ManipulatorInfo *eventInfo = new ManipulatorInfo();
    // 	eventInfo->m_itemType.m_activate = CMManager::it_canvas_item;
    eventInfo->m_acceptManipulationPtr = CMItemDrag::acceptManipulation;
    eventInfo->m_createManipulatorPtr = CMItemDrag::construct;
    return eventInfo;
}

bool CMItemDrag::acceptManipulation(uint /*eventState*/, uint /*cmState*/, uint itemType, uint cnItemType)
{
    return (itemType & (CMManager::it_canvas_item | CMManager::it_pin)) && !(cnItemType & CMManager::isi_isMovable);
}

bool CMItemDrag::mousePressedInitial(const EventInfo &eventInfo)
{
    m_eventInfo = eventInfo;
    b_dragged = false;
    return false;
}

bool CMItemDrag::mouseMoved(const EventInfo &eventInfo)
{
    const QPoint pos = eventInfo.pos;

    if (b_dragged || pos.x() > (m_eventInfo.pos.x() + 4) || pos.x() < (m_eventInfo.pos.x() - 4) || pos.y() > (m_eventInfo.pos.y() + 4) || pos.y() < (m_eventInfo.pos.y() - 4)) {
        b_dragged = true;

        if (PinItem *pi = dynamic_cast<PinItem *>(m_eventInfo.qcanvasItemClickedOn))
            pi->dragged(pos.x() - m_eventInfo.pos.x());
    }
    return false;
}

bool CMItemDrag::mouseReleased(const EventInfo & /*eventInfo*/)
{
    if (!b_dragged) {
        if (PinItem *pi = dynamic_cast<PinItem *>(m_eventInfo.qcanvasItemClickedOn))
            pi->switchState();
    }

    p_itemDocument->requestStateSave();
    return true;
}

// BEGIN class CanvasEllipseDraw
CanvasEllipseDraw::CanvasEllipseDraw(int x, int y, KtlQCanvas *canvas)
    : KtlQCanvasEllipse(0, 0, canvas)
{
    move(x, y);
}

void CanvasEllipseDraw::drawShape(QPainter &p)
{
    p.drawEllipse(int(x() - width() / 2), int(y() - height() / 2), width(), height());
}
// END class CanvasEllipseDraw

// BEGIN class CMDraw
CMDraw::CMDraw(ItemDocument *itemDocument, CMManager *cmManager)
    : CanvasManipulator(itemDocument, cmManager)
{
    m_pDrawLine = nullptr;
    m_pDrawRectangle = nullptr;
    m_pDrawEllipse = nullptr;
}

CMDraw::~CMDraw()
{
    p_cmManager->setDrawAction(-1);
}

CanvasManipulator *CMDraw::construct(ItemDocument *itemDocument, CMManager *cmManager)
{
    return new CMDraw(itemDocument, cmManager);
}

ManipulatorInfo *CMDraw::manipulatorInfo()
{
    ManipulatorInfo *eventInfo = new ManipulatorInfo();
    eventInfo->m_acceptManipulationPtr = CMDraw::acceptManipulation;
    eventInfo->m_createManipulatorPtr = CMDraw::construct;
    return eventInfo;
}

bool CMDraw::acceptManipulation(uint /*eventState*/, uint cmState, uint /*itemType*/, uint /*cnItemType*/)
{
    return (cmState & CMManager::cms_draw);
}

bool CMDraw::mousePressedInitial(const EventInfo &eventInfo)
{
    m_eventInfo = eventInfo;

    switch ((DrawPart::DrawAction)p_cmManager->drawAction()) {
    case DrawPart::da_text:
    case DrawPart::da_rectangle:
    case DrawPart::da_image: {
        m_pDrawRectangle = new KtlQCanvasRectangle(eventInfo.pos.x(), eventInfo.pos.y(), 0, 0, p_canvas);
        m_pDrawRectangle->setPen(QPen(QColor(0, 0, 0), 1));
        m_pDrawRectangle->setZ(ICNDocument::Z::ConnectorCreateLine);
        m_pDrawRectangle->show();
        break;
    }
    case DrawPart::da_ellipse: {
        m_pDrawEllipse = new CanvasEllipseDraw(eventInfo.pos.x(), eventInfo.pos.y(), p_canvas);
        m_pDrawEllipse->setPen(QPen(QColor(0, 0, 0), 1));
        m_pDrawEllipse->setZ(ICNDocument::Z::ConnectorCreateLine);
        m_pDrawEllipse->show();
        break;
    }
    case DrawPart::da_line:
    case DrawPart::da_arrow: {
        m_pDrawLine = new KtlQCanvasLine(p_canvas);
        m_pDrawLine->setPoints(eventInfo.pos.x(), eventInfo.pos.y(), eventInfo.pos.x(), eventInfo.pos.y());
        m_pDrawLine->setPen(QPen(QColor(0, 0, 0), 1));
        m_pDrawLine->setZ(ICNDocument::Z::ConnectorCreateLine);
        m_pDrawLine->show();
        break;
    }
    default:
        return true;
    }

    return false;
}

bool CMDraw::mouseMoved(const EventInfo &eventInfo)
{
    const QPoint pos = eventInfo.pos;

    if (m_pDrawRectangle)
        m_pDrawRectangle->setSize(pos.x() - m_eventInfo.pos.x(), pos.y() - m_eventInfo.pos.y());

    else if (m_pDrawEllipse) {
        // 		QRect r( m_eventInfo.pos.x(), m_eventInfo.pos.y(), pos.x()-m_eventInfo.pos.x(), pos.y()-m_eventInfo.pos.y() );
        // 		r = r.normalized();
        //
        // 		m_pDrawEllipse->setSize( r.width(), r.height() );
        // 		m_pDrawEllipse->move( r.left()+(r.width()/2), r.top()+(r.height()/2) );

        m_pDrawEllipse->setSize(2 * abs(pos.x() - m_eventInfo.pos.x()), 2 * abs(pos.y() - m_eventInfo.pos.y()));
    }

    else if (m_pDrawLine)
        m_pDrawLine->setPoints(eventInfo.pos.x(), eventInfo.pos.y(), m_pDrawLine->endPoint().x(), m_pDrawLine->endPoint().y());
    else
        return true;

    return false;
}

bool CMDraw::mouseReleased(const EventInfo & /*eventInfo*/)
{
    // const QPoint pos = eventInfo.pos; // 2017.10.01 - comment out unused variable

    QRect sizeRect;

    if (m_pDrawRectangle || m_pDrawEllipse) {
        if (m_pDrawRectangle) {
            sizeRect = m_pDrawRectangle->rect();

            // We have to manually adjust the size rect so that it matches up with what the user has drawn

            sizeRect.setWidth(sizeRect.width() + 1);
            sizeRect.setHeight(sizeRect.height() + 1);

            sizeRect = sizeRect.normalized();

            if (m_pDrawRectangle->rect().width() < 0)
                sizeRect.moveLeft(sizeRect.left() + 1);

            if (m_pDrawRectangle->rect().height() < 0)
                sizeRect.moveTop(sizeRect.top() + 1);
        } else {
            int w = m_pDrawEllipse->width() + 1;
            int h = m_pDrawEllipse->height() + 1;
            int x = int(m_pDrawEllipse->x() - w / 2);
            int y = int(m_pDrawEllipse->y() - h / 2);
            sizeRect = QRect(x, y, w, h).normalized();
        }

        delete m_pDrawRectangle;
        delete m_pDrawEllipse;
        m_pDrawRectangle = nullptr;
        m_pDrawEllipse = nullptr;
    } else if (m_pDrawLine) {
        int sx = m_pDrawLine->startPoint().x();
        int sy = m_pDrawLine->startPoint().y();
        int ex = m_pDrawLine->endPoint().x();
        int ey = m_pDrawLine->endPoint().y();

        sizeRect = QRect(ex, ey, sx - ex, sy - ey);

        delete m_pDrawLine;
        m_pDrawLine = nullptr;
    } else
        return true;

    QString id;
    switch ((DrawPart::DrawAction)p_cmManager->drawAction()) {
    case DrawPart::da_rectangle:
        id = "dp/rectangle";
        break;

    case DrawPart::da_image:
        id = "dp/image";
        break;

    case DrawPart::da_ellipse:
        id = "dp/ellipse";
        break;

    case DrawPart::da_text:
        id = "dp/canvas_text";

        if (sizeRect.width() < 56)
            sizeRect.setWidth(56);

        if (sizeRect.height() < 24)
            sizeRect.setHeight(24);

        break;

    case DrawPart::da_line:
        id = "dp/line";
        break;

    case DrawPart::da_arrow:
        id = "dp/arrow";
        break;
    }

    if (id.isEmpty())
        return true;

    Item *item = p_itemDocument->addItem(id, sizeRect.topLeft(), true);

    if (!item)
        return true;

    item->move(sizeRect.x(), sizeRect.y()); // We call this again as p_itemDocument->addItem will move the item if it is slightly off the canvas.

    item->setSize(0, 0, sizeRect.width(), sizeRect.height());

    p_itemDocument->requestStateSave();
    return true;
}
// END class CMDraw

// BEGIN class ManualConnectorDraw
ManualConnectorDraw::ManualConnectorDraw(ICNDocument *_icnDocument, const QPoint &initialPos)
{
    m_color = Qt::black;

    icnDocument = _icnDocument;
    m_currentPos = m_previousPos = m_initialPos = initialPos;
    p_initialItem = icnDocument->itemAtTop(initialPos);

    b_currentVertical = false;
    b_orientationDefined = false;

    m_connectorLines.append(m_previousCon = new KtlQCanvasLine(icnDocument->canvas()));
    m_connectorLines.append(m_currentCon = new KtlQCanvasLine(icnDocument->canvas()));

    m_currentCon->setPoints(initialPos.x(), initialPos.y(), initialPos.x(), initialPos.y());
    m_previousCon->setPoints(initialPos.x(), initialPos.y(), initialPos.x(), initialPos.y());

    m_currentCon->setPen(m_color);
    m_previousCon->setPen(m_color);

    updateConnectorEnds();

    m_currentCon->show();
    m_previousCon->show();
}

ManualConnectorDraw::~ManualConnectorDraw()
{
    qDeleteAll(m_connectorLines);
    m_connectorLines.clear();
}

void ManualConnectorDraw::setColor(const QColor &color)
{
    m_color = color;

    const QList<KtlQCanvasLine *>::iterator end = m_connectorLines.end();
    for (QList<KtlQCanvasLine *>::iterator it = m_connectorLines.begin(); it != end; ++it)
        (*it)->setPen(m_color);
}

void ManualConnectorDraw::mouseMoved(const QPoint &pos)
{
    if (m_currentPos == pos)
        return;

    if (!b_orientationDefined) {
        QPoint previousStart = m_previousCon->startPoint();

        double distance = std::sqrt(std::pow((double)(m_currentPos.x() - previousStart.x()), 2.) + std::pow((double)(m_currentPos.y() - previousStart.y()), 2.));

        if (distance < 24) {
            b_currentVertical = (std::abs(double(m_currentPos.x() - previousStart.x())) >= std::abs(double(m_currentPos.y() - previousStart.y())));
        }
    }

    m_previousPos = m_currentPos;
    m_currentPos = pos;
    updateConnectorEnds();
}

KtlQCanvasItem *ManualConnectorDraw::mouseClicked(const QPoint &pos)
{
    if (b_orientationDefined)
        b_currentVertical = !b_currentVertical;
    else
        mouseMoved(pos);

    b_orientationDefined = true;

    m_currentPos = pos;

    KtlQCanvasItem *qcanvasItem = icnDocument->itemAtTop(pos);

    if (qcanvasItem && pos != m_initialPos && qcanvasItem != p_initialItem)
        return qcanvasItem;

    m_previousCon = m_currentCon;

    m_connectorLines.append(m_currentCon = new KtlQCanvasLine(icnDocument->canvas()));
    m_currentCon->setPoints(pos.x(), pos.y(), pos.x(), pos.y());
    m_currentCon->setPen(m_color);
    updateConnectorEnds();
    m_currentCon->show();

    return nullptr;
}

void ManualConnectorDraw::updateConnectorEnds()
{
    QPoint pivot = m_currentPos;
    QPoint previousStart = m_previousCon->startPoint();

    if (b_currentVertical) {
        pivot.setY(previousStart.y());
        m_currentCon->setPoints(pivot.x(), pivot.y(), pivot.x(), m_currentPos.y());
    } else {
        pivot.setX(previousStart.x());
        m_currentCon->setPoints(pivot.x(), pivot.y(), m_currentPos.x(), pivot.y());
    }

    m_previousCon->setPoints(previousStart.x(), previousStart.y(), pivot.x(), pivot.y());
}

QPointList ManualConnectorDraw::pointList()
{
    QPointList list;
    list.append(m_initialPos);

    const QList<KtlQCanvasLine *>::iterator end = m_connectorLines.end();
    for (QList<KtlQCanvasLine *>::iterator it = m_connectorLines.begin(); it != end; ++it) {
        list.append((*it)->endPoint());
    }

    return list;
}
// END class ManualConnectorDraw

// BEGIN class ManipulatorInfo
ManipulatorInfo::ManipulatorInfo()
{
}
// END class ManipulatorInfo
