/*
 * Copyright (C) 2015-2018 Département de l'Instruction Publique (DIP-SEM)
 *
 * Copyright (C) 2013 Open Education Foundation
 *
 * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour
 * l'Education Numérique en Afrique (GIP ENA)
 *
 * This file is part of OpenBoard.
 *
 * OpenBoard 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, version 3 of the License,
 * with a specific linking exception for the OpenSSL project's
 * "OpenSSL" library (or with modified versions of it that use the
 * same license as the "OpenSSL" library).
 *
 * OpenBoard is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with OpenBoard. If not, see <http://www.gnu.org/licenses/>.
 */




#include <QtGui>
#include <QtSvg>
#include <QDrag>

#include "UBGraphicsItemDelegate.h"
#include "UBGraphicsMediaItemDelegate.h"
#include "UBGraphicsDelegateFrame.h"
#include "UBGraphicsScene.h"
#include "UBGraphicsItemUndoCommand.h"
#include "UBGraphicsItemTransformUndoCommand.h"

#include "board/UBBoardController.h" // TODO UB 4.x clean that dependency
#include "board/UBBoardView.h" // TODO UB 4.x clean that dependency

#include "core/UBApplication.h"
#include "core/UBApplicationController.h"
#include "core/UBDisplayManager.h"
#include "core/UBSettings.h"
#include "core/UBPersistenceManager.h"

#include "document/UBDocumentProxy.h"

#include "UBGraphicsWidgetItem.h"

#include "domain/UBGraphicsTextItem.h"
#include "domain/UBGraphicsMediaItem.h"
#include "domain/UBGraphicsGroupContainerItem.h"

#include "web/UBWebController.h"

#include "frameworks/UBFileSystemUtils.h"
#include "board/UBDrawingController.h"

#include "core/memcheck.h"

DelegateButton::DelegateButton(const QString & fileName, QGraphicsItem* pDelegated, QGraphicsItem * parent, Qt::WindowFrameSection section)
    : QGraphicsSvgItem(fileName, parent)
    , mDelegated(pDelegated)
    , mIsTransparentToMouseEvent(false)
    , mIsPressed(false)
    , mProgressTimerId(-1)
    , mPressProgres(0)
    , mShowProgressIndicator(false)
    , mButtonAlignmentSection(section)
{
    setAcceptedMouseButtons(Qt::LeftButton);
    setData(UBGraphicsItemData::ItemLayerType, QVariant(UBItemLayerType::Control));
    setCacheMode(QGraphicsItem::NoCache); /* because of SANKORE-1017: this allows pixmap to be refreshed when grabbing window, thus teacher screen is synchronized with main screen. */
}

DelegateButton::~DelegateButton()
{
    // NOOP
}

void DelegateButton::setFileName(const QString & fileName)
{
    QGraphicsSvgItem::setSharedRenderer(new QSvgRenderer (fileName, this));
}

void DelegateButton::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    if (mShowProgressIndicator) {
        QTimer::singleShot(300, this, SLOT(startShowProgress()));
    }

    mIsPressed = true;

    // make sure delegate is selected, to avoid control being hidden
    mPressedTime = QTime::currentTime();

    event->setAccepted(!mIsTransparentToMouseEvent);
}

void DelegateButton::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    if (mShowProgressIndicator && mProgressTimerId != -1) {
        killTimer(mProgressTimerId);
        mPressProgres = 0;
    }

    mIsPressed = false;
    int timeto = qAbs(QTime::currentTime().msecsTo(mPressedTime));

    if (timeto < UBSettings::longClickInterval) {
        emit clicked();
    } else {
        emit longClicked();
    }

    event->setAccepted(!mIsTransparentToMouseEvent);

    update();
}

void DelegateButton::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    painter->save();
    painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
    QGraphicsSvgItem::paint(painter, option, widget);
    painter->restore();

    if (mIsPressed && mShowProgressIndicator) {
        QPen pen;
        pen.setBrush(Qt::white);
        pen.setWidth(3);
        painter->save();
        painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
        painter->setPen(pen);

        int spanAngle = qMin(mPressProgres, UBSettings::longClickInterval) * 360 / UBSettings::longClickInterval;
        painter->drawArc(option->rect.adjusted(pen.width(), pen.width(), -pen.width(), -pen.width()), 16 * 90, -16 * spanAngle);

        painter->restore();
    }
}

void DelegateButton::timerEvent(QTimerEvent *event)
{
    if (event->timerId() == mProgressTimerId) {
        mPressProgres = qAbs(QTime::currentTime().msecsTo(mPressedTime));
        update();
    }
}

void DelegateButton::startShowProgress()
{
    if (mIsPressed) {
         mProgressTimerId = startTimer(37);
    }
}

UBGraphicsItemDelegate::UBGraphicsItemDelegate(QGraphicsItem* pDelegated, QObject * parent, UBGraphicsFlags fls)
    : QObject(parent)
    , mDelegated(pDelegated)
    , mDeleteButton(NULL)
    , mDuplicateButton(NULL)
    , mMenuButton(NULL)
    , mZOrderUpButton(0)
    , mZOrderDownButton(0)
    , mMenu(0)
    , mLockAction(0)
    , mShowOnDisplayAction(0)
    , mGotoContentSourceAction(0)
    , mFrame(0)
    , mFrameWidth(UBSettings::settings()->objectFrameWidth)
    , mAntiScaleRatio(1.0)
    , mToolBarItem(NULL)
    , mMimeData(NULL)
{
    setUBFlags(fls);
    connect(UBApplication::boardController, SIGNAL(zoomChanged(qreal)), this, SLOT(onZoomChanged()));
}

void UBGraphicsItemDelegate::createControls()
{
    if (testUBFlags(GF_TOOLBAR_USED) && !mToolBarItem)
        mToolBarItem = new UBGraphicsToolBarItem(mDelegated);

    if (!mFrame) {
        mFrame = new UBGraphicsDelegateFrame(this, QRectF(0, 0, 0, 0), mFrameWidth, testUBFlags(GF_RESPECT_RATIO), testUBFlags(GF_TITLE_BAR_USED));
        mFrame->hide();
        mFrame->setFlag(QGraphicsItem::ItemIsSelectable, true);
    }

    if (!mDeleteButton) {
        mDeleteButton = new DelegateButton(":/images/close.svg", mDelegated, mFrame, Qt::TopLeftSection);
        mButtons << mDeleteButton;
        connect(mDeleteButton, SIGNAL(clicked()), this, SLOT(remove()));
        if (testUBFlags(GF_DUPLICATION_ENABLED)){
            mDuplicateButton = new DelegateButton(":/images/duplicate.svg", mDelegated, mFrame, Qt::TopLeftSection);
            connect(mDuplicateButton, SIGNAL(clicked(bool)), this, SLOT(duplicate()));
            mButtons << mDuplicateButton;
        }
    }

    if (!mMenuButton) {
        mMenuButton = new DelegateButton(":/images/menu.svg", mDelegated, mFrame, Qt::TopLeftSection);
        connect(mMenuButton, SIGNAL(clicked()), this, SLOT(showMenu()));
        mButtons << mMenuButton;
    }

    if (!mZOrderUpButton) {
        mZOrderUpButton = new DelegateButton(":/images/z_layer_up.svg", mDelegated, mFrame, Qt::BottomLeftSection);
        mZOrderUpButton->setShowProgressIndicator(true);
        connect(mZOrderUpButton, SIGNAL(clicked()), this, SLOT(increaseZLevelUp()));
        connect(mZOrderUpButton, SIGNAL(longClicked()), this, SLOT(increaseZlevelTop()));
        mButtons << mZOrderUpButton;
    }

    if (!mZOrderDownButton) {
        mZOrderDownButton = new DelegateButton(":/images/z_layer_down.svg", mDelegated, mFrame, Qt::BottomLeftSection);
        mZOrderDownButton->setShowProgressIndicator(true);
        connect(mZOrderDownButton, SIGNAL(clicked()), this, SLOT(increaseZLevelDown()));
        connect(mZOrderDownButton, SIGNAL(longClicked()), this, SLOT(increaseZlevelBottom()));
        mButtons << mZOrderDownButton;
    }



    buildButtons();

    foreach(DelegateButton* button, mButtons)
    {
        button->hide();
        button->setFlag(QGraphicsItem::ItemIsSelectable, true);
    }
}

void UBGraphicsItemDelegate::freeControls()
{
    QGraphicsScene *controlsScene = delegated()->scene();
    Q_ASSERT(controlsScene);

    UB_FREE_CONTROL(mFrame, controlsScene);
    freeButtons();
}

void UBGraphicsItemDelegate::showControls()
{
    mAntiScaleRatio = 1 / (UBApplication::boardController->systemScaleFactor() * UBApplication::boardController->currentZoom());
    positionHandles();
}

bool UBGraphicsItemDelegate::controlsExist() const
{
    return mFrame
            && mDeleteButton
            && mMenuButton
            && mZOrderUpButton
            && mZOrderDownButton
            ;
}

UBGraphicsItemDelegate::~UBGraphicsItemDelegate()
{
    if (UBApplication::boardController)
        disconnect(UBApplication::boardController, SIGNAL(zoomChanged(qreal)), this, SLOT(onZoomChanged()));
    // do not release mMimeData.
    // the mMimeData is owned by QDrag since the setMimeData call as specified in the documentation
}



QVariant UBGraphicsItemDelegate::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{
    UBGraphicsScene *ubScene = castUBGraphicsScene();
    switch (static_cast<int>(change)) {

    case QGraphicsItem::ItemSelectedHasChanged : {
        if (ubScene) {
            if (value.toBool()) { //selected(true)
                ubScene->setSelectedZLevel(delegated());
            } else {
                ubScene->setOwnZlevel(delegated());
                freeControls();
            }
        }

    } break;
    case QGraphicsItem::ItemVisibleHasChanged :
    {
        bool shownOnDisplay = mDelegated->data(UBGraphicsItemData::ItemLayerType).toInt() != UBItemLayerType::Control;
        showHide(shownOnDisplay);
        break;
    }
    case QGraphicsItem::ItemPositionHasChanged :
    case QGraphicsItem::ItemTransformHasChanged :
    case QGraphicsItem::ItemZValueHasChanged :
        if (!controlsExist()) {
            break;
        }
        mAntiScaleRatio = 1 / (UBApplication::boardController->systemScaleFactor() * UBApplication::boardController->currentZoom());
        positionHandles();
        if (ubScene) {
            ubScene->setModified(true);
        }
        break;
    }

    return value;
}

UBGraphicsScene *UBGraphicsItemDelegate::castUBGraphicsScene()
{
    UBGraphicsScene *castScene = dynamic_cast<UBGraphicsScene*>(delegated()->scene());

    return castScene;
}

/** Used to render custom data after the main "Paint" operation is finished */
void UBGraphicsItemDelegate::postpaint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(widget)
    if (option->state & QStyle::State_Selected && !controlsExist()) {
        painter->save();
        painter->setPen(Qt::NoPen);
        painter->setBrush(QColor(0x88, 0x88, 0x88, 0x77));
        painter->drawRect(option->rect);

        painter->restore();
    }
}

bool UBGraphicsItemDelegate::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    mDragStartPosition = event->pos();

    startUndoStep();

    if (!delegated()->isSelected())
    {
        delegated()->setSelected(true);
        return true;
    }
    else
    {
        return false;
    }
}

void UBGraphicsItemDelegate::setMimeData(QMimeData *mimeData)
{
    mMimeData = mimeData;
}

bool UBGraphicsItemDelegate::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    if(mMimeData) {
        QDrag* mDrag = new QDrag(event->widget());
        mDrag->setMimeData(mMimeData);
        if (!mDragPixmap.isNull()) {
            mDrag->setPixmap(mDragPixmap);
            mDrag->setHotSpot(mDragPixmap.rect().center());
        }
        mDrag->exec();
        mDragPixmap = QPixmap();
        return true;
    }
    return false;
}

bool UBGraphicsItemDelegate::wheelEvent(QGraphicsSceneWheelEvent *event)
{
    Q_UNUSED(event);
    if( delegated()->isSelected() )
        return true;
    else
        return false;
}

bool UBGraphicsItemDelegate::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    Q_UNUSED(event);

    //Deselect all the rest selected items if no ctrl key modifier
    if (delegated()->scene()
            && delegated()->scene()->selectedItems().count()
            && event->modifiers() != Qt::ControlModifier) {
        foreach (QGraphicsItem *item, delegated()->scene()->selectedItems()) {
            if (item != delegated()) {
                item->setSelected(false);
            }
        }
    }

    commitUndoStep();

    return true;
}

void UBGraphicsItemDelegate::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
    Q_UNUSED(event)

}

void UBGraphicsItemDelegate::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
    Q_UNUSED(event)

}

bool UBGraphicsItemDelegate::keyPressEvent(QKeyEvent *event)
{
    Q_UNUSED(event);
    return true;
}

bool UBGraphicsItemDelegate::keyReleaseEvent(QKeyEvent *event)
{
    Q_UNUSED(event);
    return true;
}

QGraphicsItem *UBGraphicsItemDelegate::delegated()
{
    QGraphicsItem *curDelegate = 0;
    if (mDelegated->parentItem() && mDelegated->parentItem()->type() == UBGraphicsGroupContainerItem::Type) {
        curDelegate = mDelegated->parentItem(); // considering delegated item as an item's group which contains everything
    } else {
        curDelegate = mDelegated;
    }

    return curDelegate;
}

void UBGraphicsItemDelegate::positionHandles()
{
    if (!controlsExist()) {
        return;
    }

    if (mDelegated->isSelected()) {
        bool shownOnDisplay = mDelegated->data(UBGraphicsItemData::ItemLayerType).toInt() != UBItemLayerType::Control;
        showHide(shownOnDisplay);
        mDelegated->setData(UBGraphicsItemData::ItemLocked, QVariant(isLocked()));
        updateFrame();

        if (UBStylusTool::Play != UBDrawingController::drawingController()->stylusTool())
            mFrame->show();

        updateButtons(true);

        if (mToolBarItem && mToolBarItem->isVisibleOnBoard())
        {
            mToolBarItem->positionHandles();
            mToolBarItem->update();
            mToolBarItem->show();
        }
    } else {
        foreach(DelegateButton* button, mButtons)
            button->hide();

        if(mFrame)
            mFrame->hide();

        if (mToolBarItem)
            mToolBarItem->hide();
    }
}

void UBGraphicsItemDelegate::setZOrderButtonsVisible(bool visible)
{
    if (visible) {
        updateFrame();
        updateButtons();

        QPointF newUpPoint = mFrame->mapToItem(mDelegated, mZOrderUpButton->pos());
        QPointF newDownPoint = mFrame->mapToItem(mDelegated, mZOrderDownButton->pos());


        mZOrderUpButton->setParentItem(mDelegated);
        mZOrderDownButton->setParentItem(mDelegated);

        mZOrderUpButton->setPos(newUpPoint + QPointF(0,0));
        mZOrderDownButton->setPos(newDownPoint + QPointF(0,0));

        mZOrderUpButton->show();
        mZOrderDownButton->show();

    } else {
        mZOrderUpButton->hide();
        mZOrderDownButton->hide();
    }
}

void UBGraphicsItemDelegate::remove(bool canUndo)
{
    UBGraphicsScene* scene = dynamic_cast<UBGraphicsScene*>(mDelegated->scene());
    if (scene)
    {
        if (mFrame && !mFrame->scene() && mDelegated->scene())
        {
            mDelegated->scene()->addItem(mFrame);
            mFrame->setAntiScale(mAntiScaleRatio);
            mFrame->positionHandles();
            updateButtons(true);

            foreach(DelegateButton* button, mButtons) {
                scene->removeItem(button);
            }
            scene->removeItem(mFrame);
        }

        /* this is performed because when removing delegated from scene while it contains flash content, segfault happens because of QGraphicsScene::removeItem() */
        UBGraphicsWidgetItem *mDelegated_casted = dynamic_cast<UBGraphicsWidgetItem*>(mDelegated);
        if (mDelegated_casted)
            mDelegated_casted->setHtml(QString());

        scene->removeItem(mDelegated);

        if (canUndo)
        {
            UBGraphicsItemUndoCommand *uc = new UBGraphicsItemUndoCommand(scene, mDelegated, 0);
            UBApplication::undoStack->push(uc);
        }
    }
}


bool UBGraphicsItemDelegate::isLocked() const
{
    return mDelegated->data(UBGraphicsItemData::ItemLocked).toBool();
}


void UBGraphicsItemDelegate::duplicate()
{
    UBApplication::boardController->duplicateItem(dynamic_cast<UBItem*>(delegated()));
}

void UBGraphicsItemDelegate::increaseZLevelUp()
{
    UBGraphicsScene *curScene = castUBGraphicsScene();
    if (curScene) {
        curScene->changeZLevelTo(delegated(), UBZLayerController::up, true);
    }
}
void UBGraphicsItemDelegate::increaseZlevelTop()
{
    UBGraphicsScene *curScene = castUBGraphicsScene();
    if (curScene) {
        curScene->changeZLevelTo(delegated(), UBZLayerController::top, true);
    }
}
void UBGraphicsItemDelegate::increaseZLevelDown()
{
    UBGraphicsScene *curScene = castUBGraphicsScene();
    if (curScene) {
        curScene->changeZLevelTo(delegated(), UBZLayerController::down, true);
    }
}
void UBGraphicsItemDelegate::increaseZlevelBottom()
{
    UBGraphicsScene *curScene = castUBGraphicsScene();
    if (curScene) {
        curScene->changeZLevelTo(delegated(), UBZLayerController::bottom, true);
    }
}

void UBGraphicsItemDelegate::lock(bool locked)
{
    setLockedRecurs(locked, mDelegated);
    mDelegated->update();

    positionHandles();
    mFrame->positionHandles();
}


void UBGraphicsItemDelegate::setLockedRecurs(const QVariant &pLock, QGraphicsItem *pItem)
{
    pItem->setData(UBGraphicsItemData::ItemLocked, pLock);
    foreach (QGraphicsItem *insideItem, pItem->childItems())
    {
        setLockedRecurs(pLock, insideItem);
    }
}

void UBGraphicsItemDelegate::showHide(bool show)
{
    QVariant showFlag = QVariant(show ? UBItemLayerType::Object : UBItemLayerType::Control);
    showHideRecurs(showFlag, mDelegated);
    mDelegated->update();

    emit showOnDisplayChanged(show);
}

void UBGraphicsItemDelegate::showHideRecurs(const QVariant &pShow, QGraphicsItem *pItem)
{
    pItem->setData(UBGraphicsItemData::ItemLayerType, pShow);
    foreach (QGraphicsItem *insideItem, pItem->childItems()) {
        showHideRecurs(pShow, insideItem);
    }
}

/**
 * @brief Set delegate as background for the scene, replacing any existing background.
 */
void UBGraphicsItemDelegate::setAsBackground()
{
    UBGraphicsScene* scene = castUBGraphicsScene();
    QGraphicsItem* item = delegated();

    if (scene && item) {
        startUndoStep();

        item->resetTransform();
        item->setPos(item->sceneBoundingRect().width()/-2., item->sceneBoundingRect().height()/-2.);

        scene->setAsBackgroundObject(item, true);

        UBGraphicsItemTransformUndoCommand *uc =
                new UBGraphicsItemTransformUndoCommand(mDelegated,
                                                       mPreviousPosition,
                                                       mPreviousTransform,
                                                       mPreviousZValue,
                                                       mPreviousSize,
                                                       true);
        UBApplication::undoStack->push(uc);
    }
}

void UBGraphicsItemDelegate::gotoContentSource()
{
    UBItem* item = dynamic_cast<UBItem*>(mDelegated);

    if(item && !item->sourceUrl().isEmpty())
    {
        UBApplication::applicationController->showInternet();
        UBApplication::webController->loadUrl(item->sourceUrl());
    }
}

void UBGraphicsItemDelegate::startUndoStep()
{
    mPreviousPosition = mDelegated->pos();
    mPreviousTransform = mDelegated->transform();
    mPreviousZValue = mDelegated->zValue();
    UBResizableGraphicsItem* resizableItem = dynamic_cast<UBResizableGraphicsItem*>(mDelegated);

    if (resizableItem)
        mPreviousSize = resizableItem->size();
    else
        mPreviousSize = QSizeF();
}


void UBGraphicsItemDelegate::commitUndoStep()
{
    UBResizableGraphicsItem* resizableItem = dynamic_cast<UBResizableGraphicsItem*>(mDelegated);

    if (mDelegated->pos() != mPreviousPosition
        || mDelegated->transform() != mPreviousTransform
        || mDelegated->zValue() != mPreviousZValue
        || (resizableItem && resizableItem->size() != mPreviousSize))
    {
        UBGraphicsItemTransformUndoCommand *uc =
                new UBGraphicsItemTransformUndoCommand(mDelegated,
                                                       mPreviousPosition,
                                                       mPreviousTransform,
                                                       mPreviousZValue,
                                                       mPreviousSize);

        UBApplication::undoStack->push(uc);
    }
}

void UBGraphicsItemDelegate::onZoomChanged()
{
    mAntiScaleRatio = 1 / (UBApplication::boardController->systemScaleFactor() * UBApplication::boardController->currentZoom());

    positionHandles();
}

void UBGraphicsItemDelegate::buildButtons()
{
}

void UBGraphicsItemDelegate::freeButtons()
{
   //Previously deleted with the frame
   // Rimplement for some specific behavior
   mButtons.clear();
   mDeleteButton = 0;
   mMenuButton = 0;
   mZOrderUpButton = 0;
   mZOrderDownButton = 0;
}

void UBGraphicsItemDelegate::decorateMenu(QMenu* menu)
{
    mLockAction = menu->addAction(tr("Locked"), this, SLOT(lock(bool)));
    QIcon lockIcon;
    lockIcon.addPixmap(QPixmap(":/images/locked.svg"), QIcon::Normal, QIcon::On);
    lockIcon.addPixmap(QPixmap(":/images/unlocked.svg"), QIcon::Normal, QIcon::Off);
    mLockAction->setIcon(lockIcon);
    mLockAction->setCheckable(true);

    mShowOnDisplayAction = mMenu->addAction(tr("Visible on Extended Screen"), this, SLOT(showHide(bool)));
    mShowOnDisplayAction->setCheckable(true);

    QIcon showIcon;
    showIcon.addPixmap(QPixmap(":/images/eyeOpened.svg"), QIcon::Normal, QIcon::On);
    showIcon.addPixmap(QPixmap(":/images/eyeClosed.svg"), QIcon::Normal, QIcon::Off);
    mShowOnDisplayAction->setIcon(showIcon);

    if (delegated()->data(UBGraphicsItemData::ItemCanBeSetAsBackground).toBool()) {
        mSetAsBackgroundAction = mMenu->addAction(tr("Set as background"), this, SLOT(setAsBackground()));
        mSetAsBackgroundAction->setCheckable(false);

        QIcon backgroundIcon;
        backgroundIcon.addPixmap(QPixmap(":/images/setAsBackground.svg"), QIcon::Normal, QIcon::On);
        mSetAsBackgroundAction->setIcon(backgroundIcon);
    }

    if (testUBFlags(GF_SHOW_CONTENT_SOURCE))
    {
        mGotoContentSourceAction = menu->addAction(tr("Go to Content Source"), this, SLOT(gotoContentSource()));

        QIcon sourceIcon;
        sourceIcon.addPixmap(QPixmap(":/images/toolbar/internet.png"), QIcon::Normal, QIcon::On);
        mGotoContentSourceAction->setIcon(sourceIcon);
    }
}

void UBGraphicsItemDelegate::updateMenuActionState()
{
    if (mLockAction)
        mLockAction->setChecked(isLocked());

    if (mShowOnDisplayAction)
    {
        bool isControl = mDelegated->data(UBGraphicsItemData::ItemLayerType) == UBItemLayerType::Control;
        mShowOnDisplayAction->setChecked(!isControl);
    }

    if (mGotoContentSourceAction)
    {
        UBItem* item = dynamic_cast<UBItem*>(mDelegated);
        mGotoContentSourceAction->setEnabled(item && !item->sourceUrl().isEmpty());
    }
}

void UBGraphicsItemDelegate::showMenu()
{
    if (!mMenu)
    {
        mMenu = new QMenu(UBApplication::boardController->controlView());
        decorateMenu(mMenu);
    }

    updateMenuActionState();

    UBBoardView* cv = UBApplication::boardController->controlView();
    QRect pinPos = cv->mapFromScene(mMenuButton->sceneBoundingRect()).boundingRect();

    mMenu->exec(cv->mapToGlobal(pinPos.bottomRight()));
}

void UBGraphicsItemDelegate::setLocked(bool pLocked)
{
    Q_ASSERT(mDelegated);

    if (mDelegated) {
        mDelegated->setData(UBGraphicsItemData::ItemLocked, QVariant(pLocked));
    }
}

void UBGraphicsItemDelegate::updateFrame()
{
    if (mFrame && !mFrame->scene() && mDelegated->scene())
        mDelegated->scene()->addItem(mFrame);

    mFrame->setAntiScale(mAntiScaleRatio);
    mFrame->positionHandles();
}

void UBGraphicsItemDelegate::updateButtons(bool showUpdated)
{
    QTransform tr;
    tr.scale(mAntiScaleRatio, mAntiScaleRatio);

    mDeleteButton->setParentItem(mFrame);
    mDeleteButton->setTransform(tr);

    qreal topX = mFrame->rect().left() - mDeleteButton->renderer()->viewBox().width() * mAntiScaleRatio / 2;
    qreal topY = mFrame->rect().top() - mDeleteButton->renderer()->viewBox().height() * mAntiScaleRatio / 2;

    qreal bottomX = mFrame->rect().left() - mDeleteButton->renderer()->viewBox().width() * mAntiScaleRatio / 2;
    qreal bottomY = mFrame->rect().bottom() - mDeleteButton->renderer()->viewBox().height() * mAntiScaleRatio / 2;

    mDeleteButton->setPos(topX, topY);

    if (!mDeleteButton->scene())
    {
        if (mDelegated->scene())
            mDelegated->scene()->addItem(mDeleteButton);
    }

    if (showUpdated)
        mDeleteButton->show();

    int i = 1, j = 0, k = 0, l = 0;
    int frameButtonHeight = mDeleteButton->boundingRect().size().height();
    qreal topXTitleBar = topX + (1.6 * mFrameWidth * mAntiScaleRatio);
    qreal topYTitleBar = topY + frameButtonHeight *mAntiScaleRatio;

#ifndef Q_OS_LINUX
    topYTitleBar += 5;
#endif

    while ((i + j + k + l) < mButtons.size())  {
        DelegateButton* button = mButtons[i + j + k + l];

        if (button->getSection() == Qt::TopLeftSection) {
            button->setParentItem(mFrame);
            button->setPos(topX + (i++ * 1.6 * mFrameWidth * mAntiScaleRatio), topY);
            button->setTransform(tr);
        }
        else if (button->getSection() == Qt::BottomLeftSection) {
            button->setParentItem(mFrame);
            button->setPos(bottomX + (++j * 1.6 * mFrameWidth * mAntiScaleRatio), bottomY);
            button->setTransform(tr);
        }
        else if (button->getSection() == Qt::TitleBarArea){
            button->setParentItem(mFrame);
            button->setPos(topXTitleBar + (k++ * (frameButtonHeight + 5) * mAntiScaleRatio), topYTitleBar);
            button->setTransform(tr);
            button->setTransform(QTransform::fromScale(0.8, 0.8), true);
        }
        else if(button->getSection() == Qt::NoSection){
            ++l;
        }
        if (!button->scene())
        {
            if (mDelegated->scene())
                mDelegated->scene()->addItem(button);
        }
        if (showUpdated) {
            button->show();
            button->setZValue(delegated()->zValue());
        }
    }
}

void UBGraphicsItemDelegate::setButtonsVisible(bool visible)
{
    foreach(DelegateButton* pButton, mButtons){
        pButton->setVisible(visible);
    }
}

void UBGraphicsItemDelegate::setUBFlags(UBGraphicsFlags pf)
{
    mFlags = pf;

    Q_ASSERT (mDelegated);
    if (!mDelegated) {
        return;
    }

    if (testUBFlags(GF_FLIPPABLE_ALL_AXIS)) {
        mDelegated->setData(UBGraphicsItemData::ItemFlippable, QVariant(testUBFlags(GF_FLIPPABLE_ALL_AXIS)));
    }

    if (testUBFlags(GF_REVOLVABLE)) {
        mDelegated->setData(UBGraphicsItemData::ItemRotatable, QVariant(testUBFlags(GF_REVOLVABLE)));
    }
}

void UBGraphicsItemDelegate::setUBFlag(UBGraphicsFlags pf, bool set)
{
    UBGraphicsFlags fls = mFlags;
    set ? fls |= pf : fls &= ~pf;

    setUBFlags(fls);
}

UBGraphicsToolBarItem::UBGraphicsToolBarItem(QGraphicsItem * parent) :
    QGraphicsRectItem(parent),
    mShifting(true),
    mVisible(false),
    mMinWidth(200),
    mInitialHeight(26),
    mElementsPadding(2)
{
    QRectF rect = this->rect();
    rect.setHeight(mInitialHeight);
    rect.setWidth(parent->boundingRect().width());
    this->setRect(rect);

    setPen(Qt::NoPen);
    hide();

    update();
}


void UBGraphicsToolBarItem::positionHandles()
{
    int itemXOffset = 0;
    foreach (QGraphicsItem* item, mItemsOnToolBar)
    {
        item->setPos(itemXOffset, 0);
        itemXOffset += (item->boundingRect().width()+mElementsPadding);
        item->show();
    }
}

void UBGraphicsToolBarItem::update()
{
    QGraphicsRectItem::update();
}

void UBGraphicsToolBarItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(option);
    Q_UNUSED(widget);

    QPainterPath path;
    path.addRoundedRect(rect(), 10, 10);

    setBrush(QBrush(UBSettings::paletteColor));

    painter->fillPath(path, brush());
}

MediaTimer::MediaTimer(QGraphicsItem * parent): QGraphicsRectItem(parent)
{
    val        = 0;
    smallPoint = false;
    setNumDigits(6);
}

MediaTimer::~MediaTimer()
{}
void MediaTimer::positionHandles()
{
    digitSpace = smallPoint ? 2 : 1;
    ySegLen    = rect().height()*5/12;
    xSegLen    = ySegLen*2/3;
    segLen     = xSegLen;
    xAdvance   = segLen*(5 + digitSpace)/5;
    xOffset    = (rect().width() - ndigits*xAdvance + segLen/5)/2;
    yOffset    = rect().height() - ySegLen*2;

    setRect(rect().x(), rect().y(), xOffset + xAdvance*ndigits, rect().height());
}

void MediaTimer::drawString(const QString &s, QPainter &p,
                                   QBitArray *newPoints, bool newString)
{
    QPoint  pos;

    for (int i=0;  i<ndigits; i++) {
        pos = QPoint(xOffset + xAdvance*i, yOffset);
        if (newString)
            drawDigit(pos, p, segLen, s[i].toLatin1(), digitStr[i].toLatin1());
        else
            drawDigit(pos, p, segLen, s[i].toLatin1());
        if (newPoints) {
            char newPoint = newPoints->testBit(i) ? '.' : ' ';
            if (newString) {
                char oldPoint = points.testBit(i) ? '.' : ' ';
                drawDigit(pos, p, segLen, newPoint, oldPoint);
            } else {
                drawDigit(pos, p, segLen, newPoint);
            }
        }
    }
    if (newString) {
        digitStr = s;
        digitStr.truncate(ndigits);
        if (newPoints)
            points = *newPoints;
    }
}

void MediaTimer::drawDigit(const QPoint &pos, QPainter &p, int segLen,
                                  char newCh, char oldCh)
{
     char updates[18][2];        // can hold 2 times number of segments, only
                                 // first 9 used if segment table is correct
     int  nErases;
     int  nUpdates;
     const char *segs;
     int  i,j;

     const char erase      = 0;
     const char draw       = 1;
     const char leaveAlone = 2;

     segs = getSegments(oldCh);
     for (nErases=0; segs[nErases] != 99; nErases++) {
         updates[nErases][0] = erase;            // get segments to erase to
         updates[nErases][1] = segs[nErases];    // remove old char
     }
     nUpdates = nErases;
     segs = getSegments(newCh);
     for(i = 0 ; segs[i] != 99 ; i++) {
         for (j=0;  j<nErases; j++)
             if (segs[i] == updates[j][1]) {   // same segment ?
                 updates[j][0] = leaveAlone;     // yes, already on screen
                 break;
             }
         if (j == nErases) {                   // if not already on screen
             updates[nUpdates][0] = draw;
             updates[nUpdates][1] = segs[i];
             nUpdates++;
         }
     }
     for (i=0; i<nUpdates; i++) {
         if (updates[i][0] == draw)
             drawSegment(pos, updates[i][1], p, segLen);
         if (updates[i][0] == erase)
             drawSegment(pos, updates[i][1], p, segLen, true);
     }
}

char MediaTimer::segments [][8] =
        {
              { 0, 1, 2, 4, 5, 6,99, 0},             // 0    0
              { 2, 5,99, 0, 0, 0, 0, 0},             // 1    1
              { 0, 2, 3, 4, 6,99, 0, 0},             // 2    2
              { 0, 2, 3, 5, 6,99, 0, 0},             // 3    3
              { 1, 2, 3, 5,99, 0, 0, 0},             // 4    4
              { 0, 1, 3, 5, 6,99, 0, 0},             // 5    5
              { 0, 1, 3, 4, 5, 6,99, 0},             // 6    6
              { 0, 2, 5,99, 0, 0, 0, 0},             // 7    7
              { 0, 1, 2, 3, 4, 5, 6,99},             // 8    8
              { 0, 1, 2, 3, 5, 6,99, 0},             // 9    9
              { 8, 9,99, 0, 0, 0, 0, 0},             // 10   :
              {99, 0, 0, 0, 0, 0, 0, 0}              // 11   empty
        };

const char* MediaTimer::getSegments(char ch)               // gets list of segments for ch
{
     if (ch >= '0' && ch <= '9')
        return segments[ch - '0'];
     if (ch == ':')
        return segments[10];
     if (ch == ' ')
        return segments[11];

     return NULL;
}

void MediaTimer::drawSegment(const QPoint &pos, char segmentNo, QPainter &p,
                                    int segLen, bool erase)
{
    Q_UNUSED(erase);

    QPoint ppt;
    QPoint pt = pos;
    int width = segLen/5;

#define LINETO(X,Y) addPoint(a, QPoint(pt.x() + (X),pt.y() + (Y)))
#define LIGHT
#define DARK

    QPolygon a(0);
    switch (segmentNo) {
    case 0 :
        ppt = pt;
        LIGHT;
        LINETO(segLen - 1,0);
        DARK;
        LINETO(segLen - width - 1,width);
        LINETO(width,width);
        LINETO(0,0);
        break;
    case 1 :
        pt += QPoint(0 , 1);
        ppt = pt;
        LIGHT;
        LINETO(width,width);
        DARK;
        LINETO(width,segLen - width/2 - 2);
        LINETO(0,segLen - 2);
        LIGHT;
        LINETO(0,0);
        break;
    case 2 :
        pt += QPoint(segLen - 1 , 1);
        ppt = pt;
        DARK;
        LINETO(0,segLen - 2);
        LINETO(-width,segLen - width/2 - 2);
        LIGHT;
        LINETO(-width,width);
        LINETO(0,0);
        break;
    case 3 :
        pt += QPoint(0 , segLen);
        ppt = pt;
        LIGHT;
        LINETO(width,-width/2);
        LINETO(segLen - width - 1,-width/2);
        LINETO(segLen - 1,0);
        DARK;
        if (width & 1) {            // adjust for integer division error
            LINETO(segLen - width - 3,width/2 + 1);
            LINETO(width + 2,width/2 + 1);
        } else {
            LINETO(segLen - width - 1,width/2);
            LINETO(width,width/2);
        }
        LINETO(0,0);
        break;
    case 4 :
        pt += QPoint(0 , segLen + 1);
        ppt = pt;
        LIGHT;
        LINETO(width,width/2);
        DARK;
        LINETO(width,segLen - width - 2);
        LINETO(0,segLen - 2);
        LIGHT;
        LINETO(0,0);
        break;
    case 5 :
        pt += QPoint(segLen - 1 , segLen + 1);
        ppt = pt;
        DARK;
        LINETO(0,segLen - 2);
        LINETO(-width,segLen - width - 2);
        LIGHT;
        LINETO(-width,width/2);
        LINETO(0,0);
        break;
    case 6 :
        pt += QPoint(0 , segLen*2);
        ppt = pt;
        LIGHT;
        LINETO(width,-width);
        LINETO(segLen - width - 1,-width);
        LINETO(segLen - 1,0);
        DARK;
        LINETO(0,0);
        break;
    case 7 :
        pt += QPoint(segLen/2 , segLen*2);
        ppt = pt;
        DARK;
        LINETO(width,0);
        LINETO(width,-width);
        LIGHT;
        LINETO(0,-width);
        LINETO(0,0);
        break;
    case 8 :
        pt += QPoint(segLen/2 - width/2 + 1 , segLen/2 + width);
        ppt = pt;
        DARK;
        LINETO(width,0);
        LINETO(width,-width);
        LIGHT;
        LINETO(0,-width);
        LINETO(0,0);
        break;
    case 9 :
        pt += QPoint(segLen/2 - width/2 + 1 , 3*segLen/2 + width);
        ppt = pt;
        DARK;
        LINETO(width,0);
        LINETO(width,-width);
        LIGHT;
        LINETO(0,-width);
        LINETO(0,0);
        break;
    default :
        break;
    }
    // End exact copy
    p.setPen(Qt::white);
    p.setBrush(Qt::white);
    p.drawPolygon(a);
    p.setBrush(Qt::NoBrush);

    pt = pos;

#undef LINETO
#undef LIGHT
#undef DARK
}

void MediaTimer::addPoint(QPolygon &a, const QPoint &p)
{
    uint n = a.size();
    a.resize(n + 1);
    a.setPoint(n, p);
}

void MediaTimer::paint(QPainter *p, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(option);
    Q_UNUSED(widget);

    if (smallPoint)
        drawString(digitStr, *p, &points, false);
    else
        drawString(digitStr, *p, 0, false);
}

void MediaTimer::internalSetString(const QString& s)
{
    QString buffer;
    int i;
    int len = s.length();
    QBitArray newPoints(ndigits);

    if (!smallPoint) {
        if (len == ndigits)
            buffer = s;
        else
            buffer = s.right(ndigits).rightJustified(ndigits, QLatin1Char(' '));
    } else {
        int  index = -1;
        bool lastWasPoint = true;
        newPoints.clearBit(0);
        for (i=0; i<len; i++) {
            if (s[i] == QLatin1Char('.')) {
                if (lastWasPoint) {           // point already set for digit?
                    if (index == ndigits - 1) // no more digits
                        break;
                    index++;
                    buffer[index] = QLatin1Char(' ');        // 2 points in a row, add space
                }
                newPoints.setBit(index);        // set decimal point
                lastWasPoint = true;
            } else {
                if (index == ndigits - 1)
                    break;
                index++;
                buffer[index] = s[i];
                newPoints.clearBit(index);      // decimal point default off
                lastWasPoint = false;
            }
        }
        if (index < ((int) ndigits) - 1) {
            for(i=index; i>=0; i--) {
                buffer[ndigits - 1 - index + i] = buffer[i];
                newPoints.setBit(ndigits - 1 - index + i,
                                   newPoints.testBit(i));
            }
            for(i=0; i<ndigits-index-1; i++) {
                buffer[i] = QLatin1Char(' ');
                newPoints.clearBit(i);
            }
        }
    }

    if (buffer == digitStr)
        return;

    digitStr = buffer;
    if (smallPoint)
        points = newPoints;
    update();
}

void MediaTimer::display(const QString &s)
{
    val = 0;
    bool ok = false;
    double v = s.toDouble(&ok);
    if (ok)
        val = v;
    internalSetString(s);
}

void MediaTimer::setNumDigits(int numDigits)
{
    if (numDigits > 99) {
        qWarning("QLCDNumber::setNumDigits: Max 99 digits allowed");
        numDigits = 99;
    }
    if (numDigits < 0) {
        qWarning("QLCDNumber::setNumDigits: Min 0 digits allowed");
        numDigits = 0;
    }
    if (digitStr.isNull()) {                  // from constructor
        ndigits = numDigits + numDigits/2 - 1;
        digitStr.fill(QLatin1Char(' '), ndigits);
        points.fill(0, ndigits);
        digitStr[ndigits - 1] = QLatin1Char('0');            // "0" is the default number
    } else {
        if (numDigits == ndigits)             // no change
            return;
        register int i;
        int dif;
        if (numDigits > ndigits) {            // expand
            dif = numDigits - ndigits;
            QString buf;
            buf.fill(QLatin1Char(' '), dif);
            digitStr.insert(0, buf);
            points.resize(numDigits);
            for (i=numDigits-1; i>=dif; i--)
                points.setBit(i, points.testBit(i-dif));
            for (i=0; i<dif; i++)
                points.clearBit(i);
        } else {                                        // shrink
            dif = ndigits - numDigits;
            digitStr = digitStr.right(numDigits);
            QBitArray tmpPoints = points;
            points.resize(numDigits);
            for (i=0; i<(int)numDigits; i++)
                points.setBit(i, tmpPoints.testBit(i+dif));
        }
        ndigits = numDigits;
        update();
    }
    positionHandles();
}

DelegateMediaControl::DelegateMediaControl(UBGraphicsMediaItem* pDelegated, QGraphicsItem * parent)
    : QGraphicsRectItem(parent)
    , mDelegate(pDelegated)
    , mDisplayCurrentTime(false)
    , mCurrentTimeInMs(0)
    , mTotalTimeInMs(0)
    , mStartWidth(200)
    , mSeecAreaBorderHeight(0)
{
    setAcceptedMouseButtons(Qt::LeftButton);
    setBrush(QBrush(Qt::white));
    setPen(Qt::NoPen);
    setData(UBGraphicsItemData::ItemLayerType, QVariant(UBItemLayerType::Control));

    lcdTimer = new MediaTimer(this);

    update();
}


void DelegateMediaControl::paint(QPainter *painter,
        const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(option);
    Q_UNUSED(widget);

    QPainterPath path;

    path.addRoundedRect(mSeecArea, mSeecArea.height()/2, mSeecArea.height()/2);
    painter->fillPath(path, brush());

    qreal frameWidth = mSeecArea.height() / 2;
    int position = frameWidth;

    if (mTotalTimeInMs > 0)
    {
        position = frameWidth + ((mSeecArea.width() - (2 * frameWidth)) / mTotalTimeInMs) * mCurrentTimeInMs;
    }

    int clearance = 2;
    int radius = frameWidth-clearance;
    QRectF r(position - radius, clearance+mSeecAreaBorderHeight, radius * 2, radius * 2);

    painter->setBrush(UBSettings::documentViewLightColor);
    painter->setPen(Qt::black);
    painter->drawEllipse(r);
}


QPainterPath DelegateMediaControl::shape() const
{
    QPainterPath path;
    path.addRoundedRect(mSeecArea, mSeecArea.height()/2, mSeecArea.height()/2);
    return path;
}

void DelegateMediaControl::positionHandles()
{
    QTime tTotal;
    tTotal = tTotal.addMSecs(mTotalTimeInMs);
    mLCDTimerArea.setHeight(parentItem()->boundingRect().height());
    int digitsCount = 2;
    int timerWidth = mLCDTimerArea.height();

    mDisplayFormat = "ss";


    //Explanation at least the second and minutes are diplayed
    mDisplayFormat = "mm:" + mDisplayFormat;
    digitsCount += 3;
    timerWidth += mLCDTimerArea.height();

    if (tTotal.hour() > 0)
    {
        mDisplayFormat = "hh:" + mDisplayFormat;
        digitsCount += 3;
        timerWidth += mLCDTimerArea.height();
    }

    lcdTimer->setNumDigits(digitsCount);

    mLCDTimerArea.setWidth(timerWidth);
    lcdTimer->setRect(mLCDTimerArea);

    lcdTimer->setPos(rect().width() - mLCDTimerArea.width(), 0);

    mSeecAreaBorderHeight = rect().height()/20;
    mSeecArea.setWidth(rect().width()-mLCDTimerArea.width()-2);
    mSeecArea.setHeight(rect().height()-2*mSeecAreaBorderHeight);
    mSeecArea.setY(mSeecAreaBorderHeight);
}

void DelegateMediaControl::update()
{
    QTime tCurrent;
    tCurrent = tCurrent.addMSecs(mCurrentTimeInMs < 0 ? 0 : mCurrentTimeInMs);


    lcdTimer->display(tCurrent.toString(mDisplayFormat));

    QGraphicsRectItem::update();
}

void DelegateMediaControl::updateTicker(qint64 time )
{
    mCurrentTimeInMs = time;
    update();
}


void DelegateMediaControl::totalTimeChanged(qint64 newTotalTime)
{
    if (mTotalTimeInMs != newTotalTime)
    {
        mTotalTimeInMs = newTotalTime;
        positionHandles();
        update();
    }
}


void DelegateMediaControl::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
   qreal frameWidth =  mSeecArea.height()/2;
    if (boundingRect().contains(event->pos() - QPointF(frameWidth,0))
        && boundingRect().contains(event->pos() + QPointF(frameWidth,0)))
    {
        mDisplayCurrentTime = true;
        seekToMousePos(event->pos());
        this->update();
        event->accept();
        emit used();
    }
}

void DelegateMediaControl::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    qreal frameWidth = mSeecArea.height() / 2;
    if (boundingRect().contains(event->pos() - QPointF(frameWidth,0))
        && boundingRect().contains(event->pos() + QPointF(frameWidth,0)))
    {
        seekToMousePos(event->pos());
        this->update();
        event->accept();
        emit used();
    }
}

void DelegateMediaControl::seekToMousePos(QPointF mousePos)
{
    qreal minX, length;
    qreal frameWidth = rect().height() / 2;

    minX = frameWidth;
    length = mSeecArea.width() - mSeecArea.height();

    qreal mouseX = mousePos.x();
    if (mouseX >= (mSeecArea.width() - mSeecArea.height()/2))
        mouseX = mSeecArea.width() - mSeecArea.height()/2;

    if (mTotalTimeInMs > 0 && length > 0 && mDelegate
        && mDelegate->isMediaSeekable())
    {
        qint64 tickPos = (mTotalTimeInMs/length)* (mouseX - minX);
        mDelegate->setMediaPos(tickPos);

        //OSX is a bit lazy
        updateTicker(tickPos);
    }
    emit used();
}

void DelegateMediaControl::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    mDisplayCurrentTime = false;
    this->update();
    event->accept();
}
