/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "PresenterScrollBar.hxx"
#include "PresenterBitmapContainer.hxx"
#include "PresenterCanvasHelper.hxx"
#include "PresenterGeometryHelper.hxx"
#include "PresenterPaintManager.hxx"
#include "PresenterTimer.hxx"
#include "PresenterUIPainter.hxx"
#include <PresenterHelper.hxx>
#include <com/sun/star/awt/PosSize.hpp>
#include <com/sun/star/awt/XWindowPeer.hpp>
#include <com/sun/star/rendering/CompositeOperation.hpp>
#include <com/sun/star/rendering/XPolyPolygon2D.hpp>

#include <algorithm>
#include <memory>
#include <utility>
#include <math.h>

using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;

const double gnScrollBarGap (10);

namespace sdext::presenter {

//===== PresenterScrollBar::MousePressRepeater ================================

class PresenterScrollBar::MousePressRepeater
    : public std::enable_shared_from_this<MousePressRepeater>
{
public:
    explicit MousePressRepeater (::rtl::Reference<PresenterScrollBar> xScrollBar);
    void Dispose();
    void Start (const PresenterScrollBar::Area& reArea);
    void Stop();
    void SetMouseArea (const PresenterScrollBar::Area& reArea);

private:
    void Callback ();
    void Execute();

    sal_Int32 mnMousePressRepeaterTaskId;
    ::rtl::Reference<PresenterScrollBar> mpScrollBar;
    PresenterScrollBar::Area meMouseArea;
};

//===== PresenterScrollBar ====================================================

std::weak_ptr<PresenterBitmapContainer> PresenterScrollBar::mpSharedBitmaps;

PresenterScrollBar::PresenterScrollBar (
    const Reference<XComponentContext>& rxComponentContext,
    const Reference<awt::XWindow>& rxParentWindow,
    std::shared_ptr<PresenterPaintManager> xPaintManager,
    ::std::function<void (double)>  aThumbMotionListener)
    : PresenterScrollBarInterfaceBase(m_aMutex),
      mxComponentContext(rxComponentContext),
      mpPaintManager(std::move(xPaintManager)),
      mnThumbPosition(0),
      mnTotalSize(0),
      mnThumbSize(0),
      mnLineHeight(10),
      maDragAnchor(-1,-1),
      maThumbMotionListener(std::move(aThumbMotionListener)),
      meButtonDownArea(None),
      meMouseMoveArea(None),
      mbIsNotificationActive(false),
      mpMousePressRepeater(std::make_shared<MousePressRepeater>(this)),
      mpCanvasHelper(new PresenterCanvasHelper())
{
    try
    {
        mxWindow = sd::presenter::PresenterHelper::createWindow(rxParentWindow, false);

        // Make the background transparent.  The slide show paints its own background.
        Reference<awt::XWindowPeer> xPeer (mxWindow, UNO_QUERY_THROW);
        xPeer->setBackground(0xff000000);

        mxWindow->setVisible(true);
        mxWindow->addWindowListener(this);
        mxWindow->addPaintListener(this);
        mxWindow->addMouseListener(this);
        mxWindow->addMouseMotionListener(this);
    }
    catch (RuntimeException&)
    {
    }
}

PresenterScrollBar::~PresenterScrollBar()
{
}

void SAL_CALL PresenterScrollBar::disposing()
{
    mpMousePressRepeater->Dispose();

    if (mxWindow.is())
    {
        mxWindow->removeWindowListener(this);
        mxWindow->removePaintListener(this);
        mxWindow->removeMouseListener(this);
        mxWindow->removeMouseMotionListener(this);

        Reference<lang::XComponent> xComponent = mxWindow;
        mxWindow = nullptr;
        if (xComponent.is())
            xComponent->dispose();
    }

    mpBitmaps.reset();
}

void PresenterScrollBar::SetVisible (const bool bIsVisible)
{
    if (mxWindow.is())
        mxWindow->setVisible(bIsVisible);
}

void PresenterScrollBar::SetPosSize (const css::geometry::RealRectangle2D& rBox)
{
    if (mxWindow.is())
    {
        mxWindow->setPosSize(
            sal_Int32(floor(rBox.X1)),
            sal_Int32(ceil(rBox.Y1)),
            sal_Int32(ceil(rBox.X2-rBox.X1)),
            sal_Int32(floor(rBox.Y2-rBox.Y1)),
            awt::PosSize::POSSIZE);
        UpdateBorders();
    }
}

void PresenterScrollBar::SetThumbPosition (
    double nPosition,
    const bool bAsynchronousUpdate)
{
    nPosition = ValidateThumbPosition(nPosition);

    if (nPosition == mnThumbPosition || mbIsNotificationActive)
        return;

    mnThumbPosition = nPosition;

    UpdateBorders();
    Repaint(GetRectangle(Total), bAsynchronousUpdate);

    mbIsNotificationActive = true;
    try
    {
        maThumbMotionListener(mnThumbPosition);
    }
    catch (Exception&)
    {
    }
    mbIsNotificationActive = false;
}


void PresenterScrollBar::SetTotalSize (const double nTotalSize)
{
    if (mnTotalSize != nTotalSize)
    {
        mnTotalSize = nTotalSize + 1;
        UpdateBorders();
        Repaint(GetRectangle(Total), false);
    }
}

void PresenterScrollBar::SetThumbSize (const double nThumbSize)
{
    OSL_ASSERT(nThumbSize>=0);
    if (mnThumbSize != nThumbSize)
    {
        mnThumbSize = nThumbSize;
        UpdateBorders();
        Repaint(GetRectangle(Total), false);
    }
}


void PresenterScrollBar::SetLineHeight (const double nLineHeight)
{
    mnLineHeight = nLineHeight;
}


void PresenterScrollBar::SetCanvas (const Reference<css::rendering::XCanvas>& rxCanvas)
{
    if (mxCanvas == rxCanvas)
        return;

    mxCanvas = rxCanvas;
    if (!mxCanvas.is())
        return;

    if (mpBitmaps == nullptr)
    {
        mpBitmaps = mpSharedBitmaps.lock();
        if (!mpBitmaps)
        {
            try
            {
                mpBitmaps = std::make_shared<PresenterBitmapContainer>(
                    "PresenterScreenSettings/ScrollBar/Bitmaps",
                    std::shared_ptr<PresenterBitmapContainer>(),
                    mxComponentContext,
                    mxCanvas);
                mpSharedBitmaps = mpBitmaps;
            }
            catch(Exception&)
            {
                OSL_ASSERT(false);
            }
        }
        UpdateBitmaps();
        UpdateBorders();
    }

    Repaint(GetRectangle(Total), false);
}

void PresenterScrollBar::SetBackground (const SharedBitmapDescriptor& rpBackgroundBitmap)
{
    mpBackgroundBitmap = rpBackgroundBitmap;
}

void PresenterScrollBar::CheckValues()
{
    mnThumbPosition = ValidateThumbPosition(mnThumbPosition);
}

double PresenterScrollBar::ValidateThumbPosition (double nPosition)
{
    if (nPosition + mnThumbSize > mnTotalSize)
        nPosition = mnTotalSize - mnThumbSize;
    if (nPosition < 0)
        nPosition = 0;
    return nPosition;
}

void PresenterScrollBar::Paint (
    const awt::Rectangle& rUpdateBox)
{
    if ( ! mxCanvas.is() || ! mxWindow.is())
    {
        OSL_ASSERT(mxCanvas.is());
        OSL_ASSERT(mxWindow.is());
        return;
    }

    if (PresenterGeometryHelper::AreRectanglesDisjoint (rUpdateBox, mxWindow->getPosSize()))
        return;

    PaintBackground(rUpdateBox);
    PaintComposite(rUpdateBox, PagerUp,
        mpPagerStartDescriptor, mpPagerCenterDescriptor, SharedBitmapDescriptor());
    PaintComposite(rUpdateBox, PagerDown,
        SharedBitmapDescriptor(), mpPagerCenterDescriptor, mpPagerEndDescriptor);
    PaintComposite(rUpdateBox, Thumb,
        mpThumbStartDescriptor, mpThumbCenterDescriptor, mpThumbEndDescriptor);
    PaintBitmap(rUpdateBox, PrevButton, mpPrevButtonDescriptor);
    PaintBitmap(rUpdateBox, NextButton, mpNextButtonDescriptor);

    Reference<rendering::XSpriteCanvas> xSpriteCanvas (mxCanvas, UNO_QUERY);
    if (xSpriteCanvas.is())
        xSpriteCanvas->updateScreen(false);
}

//----- XWindowListener -------------------------------------------------------

void SAL_CALL PresenterScrollBar::windowResized (const css::awt::WindowEvent&) {}

void SAL_CALL PresenterScrollBar::windowMoved (const css::awt::WindowEvent&) {}

void SAL_CALL PresenterScrollBar::windowShown (const css::lang::EventObject&) {}

void SAL_CALL PresenterScrollBar::windowHidden (const css::lang::EventObject&) {}

//----- XPaintListener --------------------------------------------------------

void SAL_CALL PresenterScrollBar::windowPaint (const css::awt::PaintEvent& rEvent)
{
    if (mxWindow.is())
    {
        awt::Rectangle aRepaintBox (rEvent.UpdateRect);
        const awt::Rectangle aWindowBox (mxWindow->getPosSize());
        aRepaintBox.X += aWindowBox.X;
        aRepaintBox.Y += aWindowBox.Y;
        Paint(aRepaintBox);

        Reference<rendering::XSpriteCanvas> xSpriteCanvas (mxCanvas, UNO_QUERY);
        if (xSpriteCanvas.is())
            xSpriteCanvas->updateScreen(false);
    }
}

//----- XMouseListener --------------------------------------------------------

void SAL_CALL PresenterScrollBar::mousePressed (const css::awt::MouseEvent& rEvent)
{
    maDragAnchor.X = rEvent.X;
    maDragAnchor.Y = rEvent.Y;
    meButtonDownArea = GetArea(rEvent.X, rEvent.Y);

    mpMousePressRepeater->Start(meButtonDownArea);
}

void SAL_CALL PresenterScrollBar::mouseReleased (const css::awt::MouseEvent&)
{
    mpMousePressRepeater->Stop();

    sd::presenter::PresenterHelper::releaseMouse(mxWindow);
}

void SAL_CALL PresenterScrollBar::mouseEntered (const css::awt::MouseEvent&) {}

void SAL_CALL PresenterScrollBar::mouseExited (const css::awt::MouseEvent&)
{
    if (meMouseMoveArea != None)
    {
        const Area eOldMouseMoveArea (meMouseMoveArea);
        meMouseMoveArea = None;
        Repaint(GetRectangle(eOldMouseMoveArea), true);
    }
    meButtonDownArea = None;
    meMouseMoveArea = None;

    mpMousePressRepeater->Stop();
}

//----- XMouseMotionListener --------------------------------------------------

void SAL_CALL PresenterScrollBar::mouseMoved (const css::awt::MouseEvent& rEvent)
{
    const Area eArea (GetArea(rEvent.X, rEvent.Y));
    if (eArea != meMouseMoveArea)
    {
        const Area eOldMouseMoveArea (meMouseMoveArea);
        meMouseMoveArea = eArea;
        if (eOldMouseMoveArea != None)
            Repaint(GetRectangle(eOldMouseMoveArea), meMouseMoveArea==None);
        if (meMouseMoveArea != None)
            Repaint(GetRectangle(meMouseMoveArea), true);
    }
    mpMousePressRepeater->SetMouseArea(eArea);
}

void SAL_CALL PresenterScrollBar::mouseDragged (const css::awt::MouseEvent& rEvent)
{
    if (meButtonDownArea != Thumb)
        return;

    mpMousePressRepeater->Stop();

    sd::presenter::PresenterHelper::captureMouse(mxWindow);

    const double nDragDistance (GetDragDistance(rEvent.X,rEvent.Y));
    UpdateDragAnchor(nDragDistance);
    if (nDragDistance != 0)
    {
        SetThumbPosition(mnThumbPosition + nDragDistance, false);
    }
}

//----- lang::XEventListener --------------------------------------------------

void SAL_CALL PresenterScrollBar::disposing (const css::lang::EventObject& rEvent)
{
    if (rEvent.Source == mxWindow)
        mxWindow = nullptr;
}


geometry::RealRectangle2D const & PresenterScrollBar::GetRectangle (const Area eArea) const
{
    OSL_ASSERT(eArea>=0 && eArea<AreaCount);

    return maBox[eArea];
}

void PresenterScrollBar::Repaint (
    const geometry::RealRectangle2D& rBox,
    const bool bAsynchronousUpdate)
{
    if (mpPaintManager != nullptr)
        mpPaintManager->Invalidate(
            mxWindow,
            PresenterGeometryHelper::ConvertRectangle(rBox),
            bAsynchronousUpdate);
}

void PresenterScrollBar::PaintBackground(
    const css::awt::Rectangle& rUpdateBox)
{
    if (!mpBackgroundBitmap)
        return;

    const awt::Rectangle aWindowBox (mxWindow->getPosSize());
    mpCanvasHelper->Paint(
        mpBackgroundBitmap,
        mxCanvas,
        rUpdateBox,
        aWindowBox,
        awt::Rectangle());
}

void PresenterScrollBar::PaintBitmap(
    const css::awt::Rectangle& rUpdateBox,
    const Area eArea,
    const SharedBitmapDescriptor& rpBitmaps)
{
    const geometry::RealRectangle2D aLocalBox (GetRectangle(eArea));
    const awt::Rectangle aWindowBox (mxWindow->getPosSize());
    geometry::RealRectangle2D aBox (aLocalBox);
    aBox.X1 += aWindowBox.X;
    aBox.Y1 += aWindowBox.Y;
    aBox.X2 += aWindowBox.X;
    aBox.Y2 += aWindowBox.Y;

    Reference<rendering::XBitmap> xBitmap (GetBitmap(eArea,rpBitmaps));

    if (!xBitmap.is())
        return;

    Reference<rendering::XPolyPolygon2D> xClipPolygon (
        PresenterGeometryHelper::CreatePolygon(
            PresenterGeometryHelper::Intersection(rUpdateBox,
                PresenterGeometryHelper::ConvertRectangle(aBox)),
            mxCanvas->getDevice()));

    const rendering::ViewState aViewState (
        geometry::AffineMatrix2D(1,0,0, 0,1,0),
        xClipPolygon);

    const geometry::IntegerSize2D aBitmapSize (xBitmap->getSize());
    rendering::RenderState aRenderState (
        geometry::AffineMatrix2D(
            1,0,aBox.X1 + (aBox.X2-aBox.X1 - aBitmapSize.Width)/2,
            0,1,aBox.Y1 + (aBox.Y2-aBox.Y1 - aBitmapSize.Height)/2),
        nullptr,
        Sequence<double>(4),
        rendering::CompositeOperation::SOURCE);

    mxCanvas->drawBitmap(
        xBitmap,
        aViewState,
        aRenderState);
}

PresenterScrollBar::Area PresenterScrollBar::GetArea (const double nX, const double nY) const
{
    const geometry::RealPoint2D aPoint(nX, nY);

    if (PresenterGeometryHelper::IsInside(GetRectangle(Pager), aPoint))
    {
        if (PresenterGeometryHelper::IsInside(GetRectangle(Thumb), aPoint))
            return Thumb;
        else if (PresenterGeometryHelper::IsInside(GetRectangle(PagerUp), aPoint))
            return PagerUp;
        else if (PresenterGeometryHelper::IsInside(GetRectangle(PagerDown), aPoint))
            return PagerDown;
    }
    else if (PresenterGeometryHelper::IsInside(GetRectangle(PrevButton), aPoint))
        return PrevButton;
    else if (PresenterGeometryHelper::IsInside(GetRectangle(NextButton), aPoint))
        return NextButton;

    return None;
}

void PresenterScrollBar::UpdateWidthOrHeight (
    sal_Int32& rSize,
    const SharedBitmapDescriptor& rpDescriptor)
{
    if (rpDescriptor)
    {
        Reference<rendering::XBitmap> xBitmap (rpDescriptor->GetNormalBitmap());
        if (xBitmap.is())
        {
            const geometry::IntegerSize2D aBitmapSize (xBitmap->getSize());
            const sal_Int32 nBitmapSize = static_cast<sal_Int32>(GetMinor(aBitmapSize.Width, aBitmapSize.Height));
            if (nBitmapSize > rSize)
                rSize = nBitmapSize;
        }
    }
}

css::uno::Reference<css::rendering::XBitmap> PresenterScrollBar::GetBitmap (
    const Area eArea,
    const SharedBitmapDescriptor& rpBitmaps) const
{
    if (!rpBitmaps)
        return nullptr;
    else
        return rpBitmaps->GetBitmap(GetBitmapMode(eArea));
}

PresenterBitmapContainer::BitmapDescriptor::Mode PresenterScrollBar::GetBitmapMode (
    const Area eArea) const
{
    if (IsDisabled(eArea))
        return PresenterBitmapContainer::BitmapDescriptor::Disabled;
    else if (eArea == meMouseMoveArea)
        return PresenterBitmapContainer::BitmapDescriptor::MouseOver;
    else
        return PresenterBitmapContainer::BitmapDescriptor::Normal;
}

bool PresenterScrollBar::IsDisabled (const Area eArea) const
{
    OSL_ASSERT(eArea>=0 && eArea<AreaCount);

    return ! maEnabledState[eArea];
}

//===== PresenterVerticalScrollBar ============================================

PresenterVerticalScrollBar::PresenterVerticalScrollBar (
    const Reference<XComponentContext>& rxComponentContext,
    const Reference<awt::XWindow>& rxParentWindow,
    const std::shared_ptr<PresenterPaintManager>& rpPaintManager,
    const ::std::function<void (double)>& rThumbMotionListener)
    : PresenterScrollBar(rxComponentContext, rxParentWindow, rpPaintManager, rThumbMotionListener),
      mnScrollBarWidth(0)
{
}

PresenterVerticalScrollBar::~PresenterVerticalScrollBar()
{
}

double PresenterVerticalScrollBar::GetDragDistance (const sal_Int32, const sal_Int32 nY) const
{
    const double nDistance (nY - maDragAnchor.Y);
    if (nDistance == 0)
        return 0;
    else
    {
        const awt::Rectangle aWindowBox (mxWindow->getPosSize());
        const double nBarWidth (aWindowBox.Width);
        const double nPagerHeight (aWindowBox.Height - 2*nBarWidth);
        const double nDragDistance (mnTotalSize / nPagerHeight * nDistance);
        if (nDragDistance + mnThumbPosition < 0)
            return -mnThumbPosition;
        else if (mnThumbPosition + nDragDistance > mnTotalSize-mnThumbSize)
            return mnTotalSize-mnThumbSize-mnThumbPosition;
        else
            return nDragDistance;
    }
}

void PresenterVerticalScrollBar::UpdateDragAnchor (const double nDragDistance)
{
    const awt::Rectangle aWindowBox (mxWindow->getPosSize());
    const double nBarWidth (aWindowBox.Width);
    const double nPagerHeight (aWindowBox.Height - 2*nBarWidth);
    maDragAnchor.Y += nDragDistance * nPagerHeight /  mnTotalSize;
}

sal_Int32 PresenterVerticalScrollBar::GetSize() const
{
    return mnScrollBarWidth;
}

double PresenterVerticalScrollBar::GetMinor (const double nX, const double) const
{
    return nX;
}

void PresenterVerticalScrollBar::UpdateBorders()
{
    const awt::Rectangle aWindowBox (mxWindow->getPosSize());
    double nBottom = aWindowBox.Height;

    if (mpNextButtonDescriptor)
    {
        Reference<rendering::XBitmap> xBitmap (mpNextButtonDescriptor->GetNormalBitmap());
        if (xBitmap.is())
        {
            geometry::IntegerSize2D aSize (xBitmap->getSize());
            maBox[NextButton] = geometry::RealRectangle2D(
                0, nBottom - aSize.Height, aWindowBox.Width, nBottom);
            nBottom -= aSize.Height + gnScrollBarGap;
        }
    }
    if (mpPrevButtonDescriptor)
    {
        Reference<rendering::XBitmap> xBitmap (mpPrevButtonDescriptor->GetNormalBitmap());
        if (xBitmap.is())
        {
            geometry::IntegerSize2D aSize (xBitmap->getSize());
            maBox[PrevButton] = geometry::RealRectangle2D(
                0, nBottom - aSize.Height, aWindowBox.Width, nBottom);
            nBottom -= aSize.Height + gnScrollBarGap;
        }
    }
    const double nPagerHeight (nBottom);
    maBox[Pager] = geometry::RealRectangle2D(
        0,0, aWindowBox.Width, nBottom);
    if (mnTotalSize < 1)
    {
        maBox[Thumb] = maBox[Pager];

        // Set up the enabled/disabled states.
        maEnabledState[PrevButton] = false;
        maEnabledState[PagerUp] = false;
        maEnabledState[NextButton] = false;
        maEnabledState[PagerDown] = false;
        maEnabledState[Thumb] = false;
    }
    else
    {
        const double nThumbSize = ::std::min(mnThumbSize,mnTotalSize);
        const double nThumbPosition = ::std::clamp(mnThumbPosition, 0.0, mnTotalSize - nThumbSize);
        maBox[Thumb] = geometry::RealRectangle2D(
            0, nThumbPosition / mnTotalSize * nPagerHeight,
            aWindowBox.Width,
                (nThumbPosition+nThumbSize) / mnTotalSize * nPagerHeight);

        // Set up the enabled/disabled states.
        maEnabledState[PrevButton] = nThumbPosition>0;
        maEnabledState[PagerUp] = nThumbPosition>0;
        maEnabledState[NextButton] = nThumbPosition+nThumbSize < mnTotalSize;
        maEnabledState[PagerDown] = nThumbPosition+nThumbSize < mnTotalSize;
        maEnabledState[Thumb] = nThumbSize < mnTotalSize;
    }
    maBox[PagerUp] = geometry::RealRectangle2D(
        maBox[Pager].X1, maBox[Pager].Y1, maBox[Pager].X2, maBox[Thumb].Y1-1);
    maBox[PagerDown] = geometry::RealRectangle2D(
        maBox[Pager].X1, maBox[Thumb].Y2+1, maBox[Pager].X2, maBox[Pager].Y2);
    maBox[Total] = PresenterGeometryHelper::Union(
        PresenterGeometryHelper::Union(maBox[PrevButton], maBox[NextButton]),
        maBox[Pager]);
}

void PresenterVerticalScrollBar::UpdateBitmaps()
{
    if (mpBitmaps == nullptr)
        return;

    mpPrevButtonDescriptor = mpBitmaps->GetBitmap(u"Up"_ustr);
    mpNextButtonDescriptor = mpBitmaps->GetBitmap(u"Down"_ustr);
    mpPagerStartDescriptor = mpBitmaps->GetBitmap(u"PagerTop"_ustr);
    mpPagerCenterDescriptor = mpBitmaps->GetBitmap(u"PagerVertical"_ustr);
    mpPagerEndDescriptor = mpBitmaps->GetBitmap(u"PagerBottom"_ustr);
    mpThumbStartDescriptor = mpBitmaps->GetBitmap(u"ThumbTop"_ustr);
    mpThumbCenterDescriptor = mpBitmaps->GetBitmap(u"ThumbVertical"_ustr);
    mpThumbEndDescriptor = mpBitmaps->GetBitmap(u"ThumbBottom"_ustr);

    mnScrollBarWidth = 0;
    UpdateWidthOrHeight(mnScrollBarWidth, mpPrevButtonDescriptor);
    UpdateWidthOrHeight(mnScrollBarWidth, mpNextButtonDescriptor);
    UpdateWidthOrHeight(mnScrollBarWidth, mpPagerStartDescriptor);
    UpdateWidthOrHeight(mnScrollBarWidth, mpPagerCenterDescriptor);
    UpdateWidthOrHeight(mnScrollBarWidth, mpPagerEndDescriptor);
    UpdateWidthOrHeight(mnScrollBarWidth, mpThumbStartDescriptor);
    UpdateWidthOrHeight(mnScrollBarWidth, mpThumbCenterDescriptor);
    UpdateWidthOrHeight(mnScrollBarWidth, mpThumbEndDescriptor);
    if (mnScrollBarWidth == 0)
        mnScrollBarWidth = 20;
}

void PresenterVerticalScrollBar::PaintComposite(
    const css::awt::Rectangle& rUpdateBox,
    const Area eArea,
    const SharedBitmapDescriptor& rpStartBitmaps,
    const SharedBitmapDescriptor& rpCenterBitmaps,
    const SharedBitmapDescriptor& rpEndBitmaps)
{
    const awt::Rectangle aWindowBox (mxWindow->getPosSize());
    geometry::RealRectangle2D aBox (GetRectangle(eArea));
    aBox.X1 += aWindowBox.X;
    aBox.Y1 += aWindowBox.Y;
    aBox.X2 += aWindowBox.X;
    aBox.Y2 += aWindowBox.Y;

    // Get bitmaps and sizes.

    PresenterUIPainter::PaintVerticalBitmapComposite(
        mxCanvas,
        rUpdateBox,
        (eArea == Thumb
            ? PresenterGeometryHelper::ConvertRectangleWithConstantSize(aBox)
            : PresenterGeometryHelper::ConvertRectangle(aBox)),
        GetBitmap(eArea, rpStartBitmaps),
        GetBitmap(eArea, rpCenterBitmaps),
        GetBitmap(eArea, rpEndBitmaps));
}

//===== PresenterScrollBar::MousePressRepeater ================================

PresenterScrollBar::MousePressRepeater::MousePressRepeater (
    ::rtl::Reference<PresenterScrollBar> xScrollBar)
    : mnMousePressRepeaterTaskId(PresenterTimer::NotAValidTaskId),
      mpScrollBar(std::move(xScrollBar)),
      meMouseArea(PresenterScrollBar::None)
{
}

void PresenterScrollBar::MousePressRepeater::Dispose()
{
    Stop();
    mpScrollBar = nullptr;
}

void PresenterScrollBar::MousePressRepeater::Start (const PresenterScrollBar::Area& reArea)
{
    meMouseArea = reArea;

    if (mnMousePressRepeaterTaskId == PresenterTimer::NotAValidTaskId)
    {
        // Execute key press operation at least this one time.
        Execute();

        // Schedule repeated executions.
        auto xSelf(shared_from_this());
        mnMousePressRepeaterTaskId = PresenterTimer::ScheduleRepeatedTask (
            mpScrollBar->GetComponentContext(),
            [xSelf=std::move(xSelf)] (TimeValue const&) { return xSelf->Callback(); },
            500000000,
            250000000);
    }
    else
    {
        // There is already an active repeating task.
    }
}

void PresenterScrollBar::MousePressRepeater::Stop()
{
    if (mnMousePressRepeaterTaskId != PresenterTimer::NotAValidTaskId)
    {
        const sal_Int32 nTaskId (mnMousePressRepeaterTaskId);
        mnMousePressRepeaterTaskId = PresenterTimer::NotAValidTaskId;
        PresenterTimer::CancelTask(nTaskId);
    }
}

void PresenterScrollBar::MousePressRepeater::SetMouseArea(const PresenterScrollBar::Area& reArea)
{
    if (meMouseArea != reArea)
    {
        if (mnMousePressRepeaterTaskId != PresenterTimer::NotAValidTaskId)
        {
            Stop();
        }
    }
}

void PresenterScrollBar::MousePressRepeater::Callback ()
{
    if (!mpScrollBar)
    {
        Stop();
        return;
    }

    Execute();
}

void PresenterScrollBar::MousePressRepeater::Execute()
{
    const double nThumbPosition (mpScrollBar->GetThumbPosition());
    switch (meMouseArea)
    {
        case PrevButton:
            mpScrollBar->SetThumbPosition(nThumbPosition - mpScrollBar->GetLineHeight(), true);
            break;

        case NextButton:
            mpScrollBar->SetThumbPosition(nThumbPosition + mpScrollBar->GetLineHeight(), true);
            break;

        case PagerUp:
            mpScrollBar->SetThumbPosition(nThumbPosition - mpScrollBar->GetThumbSize()*0.8, true);
            break;

        case PagerDown:
            mpScrollBar->SetThumbPosition(nThumbPosition + mpScrollBar->GetThumbSize()*0.8, true);
            break;

        default:
            break;
    }
}

} // end of namespace ::sdext::presenter

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
