/* -*- 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 <vcl/commandevent.hxx>
#include <vcl/event.hxx>
#include <vcl/decoview.hxx>
#include <vcl/toolkit/spinfld.hxx>
#include <vcl/settings.hxx>
#include <vcl/uitest/uiobject.hxx>
#include <sal/log.hxx>
 
#include <spin.hxx>
#include <svdata.hxx>
 
namespace {
 
void ImplGetSpinbuttonValue(vcl::Window* pWin,
                            const tools::Rectangle& rUpperRect, const tools::Rectangle& rLowerRect,
                            bool bUpperIn, bool bLowerIn, bool bUpperEnabled, bool bLowerEnabled,
                            bool bHorz, SpinbuttonValue& rValue )
{
    // convert spinbutton data to a SpinbuttonValue structure for native painting
 
    rValue.maUpperRect = rUpperRect;
    rValue.maLowerRect = rLowerRect;
 
    Point aPointerPos = pWin->GetPointerPosPixel();
 
    ControlState nState = ControlState::ENABLED;
    if (bUpperIn)
        nState |= ControlState::PRESSED;
    if (!pWin->IsEnabled() || !bUpperEnabled)
        nState &= ~ControlState::ENABLED;
    if (pWin->HasFocus())
        nState |= ControlState::FOCUSED;
    if (pWin->IsMouseOver() && rUpperRect.Contains(aPointerPos))
        nState |= ControlState::ROLLOVER;
    rValue.mnUpperState = nState;
 
    nState = ControlState::ENABLED;
    if (bLowerIn)
        nState |= ControlState::PRESSED;
    if (!pWin->IsEnabled() || !bLowerEnabled)
        nState &= ~ControlState::ENABLED;
    if (pWin->HasFocus())
        nState |= ControlState::FOCUSED;
    // for overlapping spins: highlight only one
    if (pWin->IsMouseOver() && rLowerRect.Contains(aPointerPos) && !rUpperRect.Contains(aPointerPos))
        nState |= ControlState::ROLLOVER;
    rValue.mnLowerState = nState;
 
    rValue.mnUpperPart = bHorz ? ControlPart::ButtonLeft : ControlPart::ButtonUp;
    rValue.mnLowerPart = bHorz ? ControlPart::ButtonRight : ControlPart::ButtonDown;
}
 
bool ImplDrawNativeSpinfield(vcl::RenderContext& rRenderContext, vcl::Window const * pWin, const SpinbuttonValue& rSpinbuttonValue)
{
    bool bNativeOK = false;
 
    if (rRenderContext.IsNativeControlSupported(ControlType::Spinbox, ControlPart::Entire) &&
        // there is just no useful native support for spinfields with dropdown
        !(pWin->GetStyle() & WB_DROPDOWN))
    {
        if (rRenderContext.IsNativeControlSupported(ControlType::Spinbox, rSpinbuttonValue.mnUpperPart) &&
            rRenderContext.IsNativeControlSupported(ControlType::Spinbox, rSpinbuttonValue.mnLowerPart))
        {
            // only paint the embedded spin buttons, all buttons are painted at once
            tools::Rectangle aUpperAndLowerButtons( rSpinbuttonValue.maUpperRect.GetUnion( rSpinbuttonValue.maLowerRect ) );
            bNativeOK = rRenderContext.DrawNativeControl(ControlType::Spinbox, ControlPart::AllButtons, aUpperAndLowerButtons,
                                                         ControlState::ENABLED, rSpinbuttonValue, OUString());
        }
        else
        {
            // paint the spinbox as a whole, use borderwindow to have proper clipping
            vcl::Window* pBorder = pWin->GetWindow(GetWindowType::Border);
 
            // to not overwrite everything, set the button region as clipregion to the border window
            tools::Rectangle aClipRect(rSpinbuttonValue.maLowerRect);
            aClipRect.Union(rSpinbuttonValue.maUpperRect);
 
            vcl::RenderContext* pContext = &rRenderContext;
            vcl::Region oldRgn;
            Point aPt;
            Size aSize(pBorder->GetOutputSizePixel());    // the size of the border window, i.e., the whole control
            tools::Rectangle aNatRgn(aPt, aSize);
 
            if (!pWin->SupportsDoubleBuffering())
            {
                // convert from screen space to borderwin space
                aClipRect.SetPos(pBorder->ScreenToOutputPixel(pWin->OutputToScreenPixel(aClipRect.TopLeft())));
 
                oldRgn = pBorder->GetOutDev()->GetClipRegion();
                pBorder->GetOutDev()->SetClipRegion(vcl::Region(aClipRect));
 
                pContext = pBorder->GetOutDev();
            }
 
            tools::Rectangle aBound, aContent;
            if (!ImplGetSVData()->maNWFData.mbCanDrawWidgetAnySize &&
                pContext->GetNativeControlRegion(ControlType::Spinbox, ControlPart::Entire,
                                                aNatRgn, ControlState::NONE, rSpinbuttonValue,
                                                aBound, aContent))
            {
                aSize = aContent.GetSize();
            }
 
            tools::Rectangle aRgn(aPt, aSize);
            if (pWin->SupportsDoubleBuffering())
            {
                // convert from borderwin space, to the pWin's space
                aRgn.SetPos(pWin->ScreenToOutputPixel(pBorder->OutputToScreenPixel(aRgn.TopLeft())));
            }
 
            bNativeOK = pContext->DrawNativeControl(ControlType::Spinbox, ControlPart::Entire, aRgn,
                                                   ControlState::ENABLED, rSpinbuttonValue, OUString());
 
            if (!pWin->SupportsDoubleBuffering())
                pBorder->GetOutDev()->SetClipRegion(oldRgn);
        }
    }
    return bNativeOK;
}
 
bool ImplDrawNativeSpinbuttons(vcl::RenderContext& rRenderContext, const SpinbuttonValue& rSpinbuttonValue)
{
    bool bNativeOK = false;
 
    if (rRenderContext.IsNativeControlSupported(ControlType::SpinButtons, ControlPart::Entire))
    {
        tools::Rectangle aArea = rSpinbuttonValue.maUpperRect.GetUnion(rSpinbuttonValue.maLowerRect);
        // only paint the standalone spin buttons, all buttons are painted at once
        bNativeOK = rRenderContext.DrawNativeControl(ControlType::SpinButtons, ControlPart::AllButtons, aArea,
                                                     ControlState::ENABLED, rSpinbuttonValue, OUString());
    }
    return bNativeOK;
}
 
}
 
void ImplDrawSpinButton(vcl::RenderContext& rRenderContext, vcl::Window* pWindow,
                        const tools::Rectangle& rUpperRect, const tools::Rectangle& rLowerRect,
                        bool bUpperIn, bool bLowerIn, bool bUpperEnabled, bool bLowerEnabled,
                        bool bHorz, bool bMirrorHorz)
{
    bool bNativeOK = false;
 
    if (pWindow)
    {
        // are we drawing standalone spin buttons or members of a spinfield ?
        ControlType aControl = ControlType::SpinButtons;
        switch (pWindow->GetType())
        {
            case WindowType::EDIT:
            case WindowType::MULTILINEEDIT:
            case WindowType::PATTERNFIELD:
            case WindowType::METRICFIELD:
            case WindowType::CURRENCYFIELD:
            case WindowType::DATEFIELD:
            case WindowType::TIMEFIELD:
            case WindowType::SPINFIELD:
            case WindowType::FORMATTEDFIELD:
                aControl = ControlType::Spinbox;
                break;
            default:
                aControl = ControlType::SpinButtons;
                break;
        }
 
        SpinbuttonValue aValue;
        ImplGetSpinbuttonValue(pWindow, rUpperRect, rLowerRect,
                               bUpperIn, bLowerIn, bUpperEnabled, bLowerEnabled,
                               bHorz, aValue);
 
        if( aControl == ControlType::Spinbox )
            bNativeOK = ImplDrawNativeSpinfield(rRenderContext, pWindow, aValue);
        else if( aControl == ControlType::SpinButtons )
            bNativeOK = ImplDrawNativeSpinbuttons(rRenderContext, aValue);
    }
 
    if (bNativeOK)
        return;
 
    ImplDrawUpDownButtons(rRenderContext,
                          rUpperRect, rLowerRect,
                          bUpperIn, bLowerIn, bUpperEnabled, bLowerEnabled,
                          bHorz, bMirrorHorz);
}
 
void ImplDrawUpDownButtons(vcl::RenderContext& rRenderContext,
                           const tools::Rectangle& rUpperRect, const tools::Rectangle& rLowerRect,
                           bool bUpperIn, bool bLowerIn, bool bUpperEnabled, bool bLowerEnabled,
                           bool bHorz, bool bMirrorHorz)
{
    DecorationView aDecoView(&rRenderContext);
 
    SymbolType eType1, eType2;
 
    if ( bHorz )
    {
        eType1 = bMirrorHorz ? SymbolType::SPIN_RIGHT : SymbolType::SPIN_LEFT;
        eType2 = bMirrorHorz ? SymbolType::SPIN_LEFT : SymbolType::SPIN_RIGHT;
    }
    else
    {
        eType1 = SymbolType::SPIN_UP;
        eType2 = SymbolType::SPIN_DOWN;
    }
 
    DrawButtonFlags nStyle = DrawButtonFlags::NoLeftLightBorder;
    // draw upper/left Button
    if (bUpperIn)
        nStyle |= DrawButtonFlags::Pressed;
 
    tools::Rectangle aUpRect = aDecoView.DrawButton(rUpperRect, nStyle);
 
    nStyle = DrawButtonFlags::NoLeftLightBorder;
    // draw lower/right Button
    if (bLowerIn)
        nStyle |= DrawButtonFlags::Pressed;
 
    tools::Rectangle aLowRect = aDecoView.DrawButton(rLowerRect, nStyle);
 
     // make use of additional default edge
    aUpRect.AdjustLeft( -1 );
    aUpRect.AdjustTop( -1 );
    aUpRect.AdjustRight( 1 );
    aUpRect.AdjustBottom( 1 );
    aLowRect.AdjustLeft( -1 );
    aLowRect.AdjustTop( -1 );
    aLowRect.AdjustRight( 1 );
    aLowRect.AdjustBottom( 1 );
 
    // draw into the edge, so that something is visible if the rectangle is too small
    if (aUpRect.GetHeight() < 4)
    {
        aUpRect.AdjustRight( 1 );
        aUpRect.AdjustBottom( 1 );
        aLowRect.AdjustRight( 1 );
        aLowRect.AdjustBottom( 1 );
    }
 
    // calculate Symbol size
    tools::Long nTempSize1 = aUpRect.GetWidth();
    tools::Long nTempSize2 = aLowRect.GetWidth();
    if (std::abs( nTempSize1-nTempSize2 ) == 1)
    {
        if (nTempSize1 > nTempSize2)
            aUpRect.AdjustLeft( 1 );
        else
            aLowRect.AdjustLeft( 1 );
    }
    nTempSize1 = aUpRect.GetHeight();
    nTempSize2 = aLowRect.GetHeight();
    if (std::abs(nTempSize1 - nTempSize2) == 1)
    {
        if (nTempSize1 > nTempSize2)
            aUpRect.AdjustTop( 1 );
        else
            aLowRect.AdjustTop( 1 );
    }
 
    const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
 
    DrawSymbolFlags nSymStyle = DrawSymbolFlags::NONE;
    if (!bUpperEnabled)
        nSymStyle |= DrawSymbolFlags::Disable;
    aDecoView.DrawSymbol(aUpRect, eType1, rStyleSettings.GetButtonTextColor(), nSymStyle);
 
    nSymStyle = DrawSymbolFlags::NONE;
    if (!bLowerEnabled)
        nSymStyle |= DrawSymbolFlags::Disable;
    aDecoView.DrawSymbol(aLowRect, eType2, rStyleSettings.GetButtonTextColor(), nSymStyle);
}
 
void SpinField::ImplInitSpinFieldData()
{
    mpEdit.disposeAndClear();
    mbSpin          = false;
    mbRepeat        = false;
    mbUpperIn       = false;
    mbLowerIn       = false;
    mbInitialUp     = false;
    mbInitialDown   = false;
    mbInDropDown    = false;
    mbUpperEnabled  = true;
    mbLowerEnabled  = true;
}
 
void SpinField::ImplInit(vcl::Window* pParent, WinBits nWinStyle)
{
    Edit::ImplInit( pParent, nWinStyle );
 
    if (!(nWinStyle & (WB_SPIN | WB_DROPDOWN)))
        return;
 
    mbSpin = true;
 
    // Some themes want external spin buttons, therefore the main
    // spinfield should not overdraw the border between its encapsulated
    // edit field and the spin buttons
    if ((nWinStyle & WB_SPIN) && ImplUseNativeBorder(*GetOutDev(), nWinStyle))
    {
        SetBackground();
        mpEdit.set(VclPtr<Edit>::Create(this, WB_NOBORDER));
        mpEdit->SetBackground();
    }
    else
        mpEdit.set(VclPtr<Edit>::Create(this, WB_NOBORDER));
 
    mpEdit->EnableRTL(false);
    mpEdit->SetPosPixel(Point());
    mpEdit->Show();
 
    SetSubEdit(mpEdit);
 
    maRepeatTimer.SetInvokeHandler(LINK( this, SpinField, ImplTimeout));
    maRepeatTimer.SetTimeout(MouseSettings::GetButtonStartRepeat());
    if (nWinStyle & WB_REPEAT)
        mbRepeat = true;
 
    SetCompoundControl(true);
}
 
SpinField::SpinField(vcl::Window* pParent, WinBits nWinStyle, WindowType nType) :
    Edit(nType), maRepeatTimer("SpinField maRepeatTimer")
{
    ImplInitSpinFieldData();
    ImplInit(pParent, nWinStyle);
}
 
SpinField::~SpinField()
{
    disposeOnce();
}
 
void SpinField::dispose()
{
    mpEdit.disposeAndClear();
 
    Edit::dispose();
}
 
void SpinField::Up()
{
    ImplCallEventListenersAndHandler( VclEventId::SpinfieldUp, [this] () { maUpHdlLink.Call(*this); } );
}
 
void SpinField::Down()
{
    ImplCallEventListenersAndHandler( VclEventId::SpinfieldDown, [this] () { maDownHdlLink.Call(*this); } );
}
 
void SpinField::First()
{
    ImplCallEventListenersAndHandler(VclEventId::SpinfieldFirst, nullptr);
}
 
void SpinField::Last()
{
    ImplCallEventListenersAndHandler(VclEventId::SpinfieldLast, nullptr);
}
 
void SpinField::MouseButtonDown( const MouseEvent& rMEvt )
{
    if (!HasFocus() && (!mpEdit || !mpEdit->HasFocus()))
    {
        GrabFocus();
    }
 
    if (!IsReadOnly())
    {
        if (maUpperRect.Contains(rMEvt.GetPosPixel()))
        {
            mbUpperIn   = true;
            mbInitialUp = true;
            Invalidate(maUpperRect);
        }
        else if (maLowerRect.Contains(rMEvt.GetPosPixel()))
        {
            mbLowerIn    = true;
            mbInitialDown = true;
            Invalidate(maLowerRect);
        }
        else if (maDropDownRect.Contains(rMEvt.GetPosPixel()))
        {
            // put DropDownButton to the right
            mbInDropDown = ShowDropDown( !mbInDropDown );
            Invalidate(tools::Rectangle(Point(), GetOutputSizePixel()));
        }
 
        if (mbUpperIn || mbLowerIn)
        {
            CaptureMouse();
            if (mbRepeat)
                maRepeatTimer.Start();
            return;
        }
    }
 
    Edit::MouseButtonDown(rMEvt);
}
 
void SpinField::MouseButtonUp(const MouseEvent& rMEvt)
{
    ReleaseMouse();
    mbInitialUp = mbInitialDown = false;
    maRepeatTimer.Stop();
    maRepeatTimer.SetTimeout(MouseSettings::GetButtonStartRepeat());
 
    if (mbUpperIn)
    {
        mbUpperIn = false;
        Invalidate(maUpperRect);
        Up();
    }
    else if (mbLowerIn)
    {
        mbLowerIn = false;
        Invalidate(maLowerRect);
        Down();
    }
 
    Edit::MouseButtonUp(rMEvt);
}
 
void SpinField::MouseMove(const MouseEvent& rMEvt)
{
    if (rMEvt.IsLeft())
    {
        if (mbInitialUp)
        {
            bool bNewUpperIn = maUpperRect.Contains(rMEvt.GetPosPixel());
            if (bNewUpperIn != mbUpperIn)
            {
                if (bNewUpperIn)
                {
                    if (mbRepeat)
                        maRepeatTimer.Start();
                }
                else
                    maRepeatTimer.Stop();
 
                mbUpperIn = bNewUpperIn;
                Invalidate(maUpperRect);
            }
        }
        else if (mbInitialDown)
        {
            bool bNewLowerIn = maLowerRect.Contains(rMEvt.GetPosPixel());
            if (bNewLowerIn != mbLowerIn)
            {
                if (bNewLowerIn)
                {
                    if (mbRepeat)
                        maRepeatTimer.Start();
                }
                else
                    maRepeatTimer.Stop();
 
                mbLowerIn = bNewLowerIn;
                Invalidate(maLowerRect);
            }
        }
    }
 
    Edit::MouseMove(rMEvt);
}
 
bool SpinField::EventNotify(NotifyEvent& rNEvt)
{
    bool bDone = false;
    if (rNEvt.GetType() == NotifyEventType::KEYINPUT)
    {
        const KeyEvent& rKEvt = *rNEvt.GetKeyEvent();
        if (!IsReadOnly())
        {
            sal_uInt16 nMod = rKEvt.GetKeyCode().GetModifier();
            switch (rKEvt.GetKeyCode().GetCode())
            {
                case KEY_UP:
                {
                    if (!nMod)
                    {
                        Up();
                        bDone = true;
                    }
                }
                break;
                case KEY_DOWN:
                {
                    if (!nMod)
                    {
                        Down();
                        bDone = true;
                    }
                    else if ((nMod == KEY_MOD2) && !mbInDropDown && (GetStyle() & WB_DROPDOWN))
                    {
                        mbInDropDown = ShowDropDown(true);
                        Invalidate(tools::Rectangle(Point(), GetOutputSizePixel()));
                        bDone = true;
                    }
                }
                break;
                case KEY_PAGEUP:
                {
                    if (!nMod)
                    {
                        Last();
                        bDone = true;
                    }
                }
                break;
                case KEY_PAGEDOWN:
                {
                    if (!nMod)
                    {
                        First();
                        bDone = true;
                    }
                }
                break;
            }
        }
    }
 
    if (rNEvt.GetType() == NotifyEventType::COMMAND)
    {
        if ((rNEvt.GetCommandEvent()->GetCommand() == CommandEventId::Wheel) && !IsReadOnly())
        {
            const Point& rMousePos = rNEvt.GetCommandEvent()->GetMousePosPixel();
            bool bMouseHovered = maUpperRect.Contains(rMousePos) || maLowerRect.Contains(rMousePos);
            if (!bMouseHovered && mpEdit)
            {
                const tools::Rectangle aEditRect(mpEdit->GetPosPixel(), mpEdit->GetSizePixel());
                bMouseHovered = aEditRect.Contains(rMousePos);
            }
 
            MouseWheelBehaviour nWheelBehavior(GetSettings().GetMouseSettings().GetWheelBehavior());
            if (bMouseHovered
                && (nWheelBehavior == MouseWheelBehaviour::ALWAYS
                    || (nWheelBehavior == MouseWheelBehaviour::FocusOnly && HasChildPathFocus())))
            {
                const CommandWheelData* pData = rNEvt.GetCommandEvent()->GetWheelData();
                if (pData->GetMode() == CommandWheelMode::SCROLL)
                {
                    if (pData->GetDelta() < 0)
                        Down();
                    else
                        Up();
                    bDone = true;
 
                    if (!HasChildPathFocus())
                        GrabFocus();
                }
            }
            else
                bDone = false;  // don't eat this event, let the default handling happen (i.e. scroll the context)
        }
    }
 
    return bDone || Edit::EventNotify(rNEvt);
}
 
void SpinField::FillLayoutData() const
{
    if (mbSpin)
    {
        mxLayoutData.emplace();
        AppendLayoutData(*GetSubEdit());
        GetSubEdit()->SetLayoutDataParent(this);
    }
    else
        Edit::FillLayoutData();
}
 
void SpinField::SetUpperEnabled(bool bEnabled)
{
    if (mbUpperEnabled == bEnabled)
        return;
 
    mbUpperEnabled = bEnabled;
 
    if (mbSpin)
        Invalidate(maUpperRect);
}
 
void SpinField::SetLowerEnabled(bool bEnabled)
{
    if (mbLowerEnabled == bEnabled)
        return;
 
    mbLowerEnabled = bEnabled;
 
    if (mbSpin)
        Invalidate(maLowerRect);
}
 
void SpinField::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
{
    if (mbSpin)
    {
        bool bEnabled = IsEnabled();
        bool bUpperEnabled = bEnabled && IsUpperEnabled();
        bool bLowerEnabled = bEnabled && IsLowerEnabled();
        ImplDrawSpinButton(rRenderContext, this, maUpperRect, maLowerRect,
                           mbUpperIn && bUpperEnabled, mbLowerIn && bLowerEnabled,
                           bUpperEnabled, bLowerEnabled);
    }
 
    if (GetStyle() & WB_DROPDOWN)
    {
        DecorationView aView(&rRenderContext);
 
        DrawButtonFlags nStyle = DrawButtonFlags::NoLightBorder;
        if (mbInDropDown)
            nStyle |= DrawButtonFlags::Pressed;
        tools::Rectangle aInnerRect = aView.DrawButton(maDropDownRect, nStyle);
 
        DrawSymbolFlags nSymbolStyle = IsEnabled() ? DrawSymbolFlags::NONE : DrawSymbolFlags::Disable;
        aView.DrawSymbol(aInnerRect, SymbolType::SPIN_DOWN, rRenderContext.GetSettings().GetStyleSettings().GetButtonTextColor(), nSymbolStyle);
    }
 
    Edit::Paint(rRenderContext, rRect);
}
 
void SpinField::ImplCalcButtonAreas(const OutputDevice* pDev, const Size& rOutSz, tools::Rectangle& rDDArea,
                                    tools::Rectangle& rSpinUpArea, tools::Rectangle& rSpinDownArea)
{
    const StyleSettings& rStyleSettings = pDev->GetSettings().GetStyleSettings();
 
    Size aSize = rOutSz;
    Size aDropDownSize;
 
    if (GetStyle() & WB_DROPDOWN)
    {
        tools::Long nW = rStyleSettings.GetScrollBarSize();
        nW = GetDrawPixel( pDev, nW );
        aDropDownSize = Size( CalcZoom( nW ), aSize.Height() );
        aSize.AdjustWidth( -(aDropDownSize.Width()) );
        rDDArea = tools::Rectangle( Point( aSize.Width(), 0 ), aDropDownSize );
        rDDArea.AdjustTop( -1 );
    }
    else
        rDDArea.SetEmpty();
 
    // calculate sizes according to the height
    if (GetStyle() & WB_SPIN)
    {
        tools::Long nBottom1 = aSize.Height()/2;
        tools::Long nBottom2 = aSize.Height()-1;
        tools::Long nTop2 = nBottom1;
        if ( !(aSize.Height() & 0x01) )
            nBottom1--;
 
        bool bNativeRegionOK = false;
        tools::Rectangle aContentUp, aContentDown;
 
        if ((pDev->GetOutDevType() == OUTDEV_WINDOW) &&
            // there is just no useful native support for spinfields with dropdown
            ! (GetStyle() & WB_DROPDOWN) &&
            IsNativeControlSupported(ControlType::Spinbox, ControlPart::Entire))
        {
            vcl::Window *pWin = pDev->GetOwnerWindow();
            vcl::Window *pBorder = pWin->GetWindow( GetWindowType::Border );
 
            // get the system's spin button size
            ImplControlValue aControlValue;
            tools::Rectangle aBound;
            Point aPoint;
 
            // use the full extent of the control
            tools::Rectangle aArea( aPoint, pBorder->GetOutputSizePixel() );
 
            bNativeRegionOK =
                pWin->GetNativeControlRegion(ControlType::Spinbox, ControlPart::ButtonUp,
                    aArea, ControlState::NONE, aControlValue, aBound, aContentUp) &&
                pWin->GetNativeControlRegion(ControlType::Spinbox, ControlPart::ButtonDown,
                    aArea, ControlState::NONE, aControlValue, aBound, aContentDown);
 
            if (bNativeRegionOK)
            {
                // convert back from border space to local coordinates
                aPoint = pBorder->ScreenToOutputPixel( pWin->OutputToScreenPixel( aPoint ) );
                aContentUp.Move(-aPoint.X(), -aPoint.Y());
                aContentDown.Move(-aPoint.X(), -aPoint.Y());
            }
        }
 
        if (bNativeRegionOK)
        {
            rSpinUpArea = aContentUp;
            rSpinDownArea = aContentDown;
        }
        else
        {
            aSize.AdjustWidth( -(CalcZoom( GetDrawPixel( pDev, rStyleSettings.GetSpinSize() ) )) );
 
            rSpinUpArea = tools::Rectangle( aSize.Width(), 0, rOutSz.Width()-aDropDownSize.Width()-1, nBottom1 );
            rSpinDownArea = tools::Rectangle( rSpinUpArea.Left(), nTop2, rSpinUpArea.Right(), nBottom2 );
        }
    }
    else
    {
        rSpinUpArea.SetEmpty();
        rSpinDownArea.SetEmpty();
    }
}
 
void SpinField::Resize()
{
    if (!mbSpin)
        return;
 
    Control::Resize();
    Size aSize = GetOutputSizePixel();
    bool bSubEditPositioned = false;
 
    if (GetStyle() & (WB_SPIN | WB_DROPDOWN))
    {
        ImplCalcButtonAreas( GetOutDev(), aSize, maDropDownRect, maUpperRect, maLowerRect );
 
        ImplControlValue aControlValue;
        Point aPoint;
        tools::Rectangle aContent, aBound;
 
        // use the full extent of the control
        vcl::Window *pBorder = GetWindow( GetWindowType::Border );
        tools::Rectangle aArea( aPoint, pBorder->GetOutputSizePixel() );
 
        // adjust position and size of the edit field
        if (GetNativeControlRegion(ControlType::Spinbox, ControlPart::SubEdit, aArea, ControlState::NONE,
                                   aControlValue, aBound, aContent) &&
            // there is just no useful native support for spinfields with dropdown
            !(GetStyle() & WB_DROPDOWN))
        {
            // convert back from border space to local coordinates
            aPoint = pBorder->ScreenToOutputPixel(OutputToScreenPixel(aPoint));
            aContent.Move(-aPoint.X(), -aPoint.Y());
 
            // use the themes drop down size
            mpEdit->SetPosPixel( aContent.TopLeft() );
            bSubEditPositioned = true;
            aSize = aContent.GetSize();
        }
        else
        {
            if (maUpperRect.IsEmpty())
            {
                SAL_WARN_IF( maDropDownRect.IsEmpty(), "vcl", "SpinField::Resize: SPIN && DROPDOWN, but all empty rects?" );
                aSize.setWidth( maDropDownRect.Left() );
            }
            else
                aSize.setWidth( maUpperRect.Left() );
        }
    }
 
    if (!bSubEditPositioned)
    {
        // this moves our sub edit if RTL gets switched
        mpEdit->SetPosPixel(Point());
    }
    mpEdit->SetSizePixel(aSize);
 
    if (GetStyle() & WB_SPIN)
        Invalidate(tools::Rectangle(maUpperRect.TopLeft(), maLowerRect.BottomRight()));
    if (GetStyle() & WB_DROPDOWN)
        Invalidate(maDropDownRect);
}
 
void SpinField::StateChanged(StateChangedType nType)
{
    Edit::StateChanged(nType);
 
    if (nType == StateChangedType::Enable)
    {
        if (mbSpin || (GetStyle() & WB_DROPDOWN))
        {
            mpEdit->Enable(IsEnabled());
 
            if (mbSpin)
            {
                Invalidate(maLowerRect);
                Invalidate(maUpperRect);
            }
            if (GetStyle() & WB_DROPDOWN)
                Invalidate(maDropDownRect);
        }
    }
    else if (nType == StateChangedType::Style)
    {
        if (GetStyle() & WB_REPEAT)
            mbRepeat = true;
        else
            mbRepeat = false;
    }
    else if (nType == StateChangedType::Zoom)
    {
        Resize();
        if (mpEdit)
            mpEdit->SetZoom(GetZoom());
        Invalidate();
    }
    else if (nType == StateChangedType::ControlFont)
    {
        if (mpEdit)
            mpEdit->SetControlFont(GetControlFont());
        Invalidate();
    }
    else if (nType == StateChangedType::ControlForeground)
    {
        if (mpEdit)
            mpEdit->SetControlForeground(GetControlForeground());
        Invalidate();
    }
    else if (nType == StateChangedType::ControlBackground)
    {
        if (mpEdit)
            mpEdit->SetControlBackground(GetControlBackground());
        Invalidate();
    }
    else if( nType == StateChangedType::Mirroring )
    {
        if (mpEdit)
            mpEdit->CompatStateChanged(StateChangedType::Mirroring);
        Resize();
    }
}
 
void SpinField::DataChanged( const DataChangedEvent& rDCEvt )
{
    Edit::DataChanged(rDCEvt);
 
    if ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
        (rDCEvt.GetFlags() & AllSettingsFlags::STYLE))
    {
        Resize();
        Invalidate();
    }
}
 
tools::Rectangle* SpinField::ImplFindPartRect(const Point& rPt)
{
    if (maUpperRect.Contains(rPt))
        return &maUpperRect;
    else if (maLowerRect.Contains(rPt))
        return &maLowerRect;
    else
        return nullptr;
}
 
bool SpinField::PreNotify(NotifyEvent& rNEvt)
{
    if (rNEvt.GetType() == NotifyEventType::MOUSEMOVE)
    {
        const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
        if (pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged())
        {
            // trigger redraw if mouse over state has changed
            if( IsNativeControlSupported(ControlType::Spinbox, ControlPart::Entire) ||
                IsNativeControlSupported(ControlType::Spinbox, ControlPart::AllButtons) )
            {
                tools::Rectangle* pRect = ImplFindPartRect( GetPointerPosPixel() );
                tools::Rectangle* pLastRect = ImplFindPartRect( GetLastPointerPosPixel() );
                if( pRect != pLastRect || (pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow()) )
                {
                    if (!IsNativeWidgetEnabled() ||
                        !IsNativeControlSupported(ControlType::Editbox, ControlPart::Entire))
                    {
                        // paint directly
                        vcl::Region aRgn( GetOutDev()->GetActiveClipRegion() );
                        if (pLastRect)
                        {
                            GetOutDev()->SetClipRegion(vcl::Region(*pLastRect));
                            Invalidate(*pLastRect);
                            GetOutDev()->SetClipRegion( aRgn );
                        }
                        if (pRect)
                        {
                            GetOutDev()->SetClipRegion(vcl::Region(*pRect));
                            Invalidate(*pRect);
                            GetOutDev()->SetClipRegion( aRgn );
                        }
                    }
                }
            }
        }
    }
 
    return Edit::PreNotify(rNEvt);
}
 
void SpinField::EndDropDown()
{
    mbInDropDown = false;
    Invalidate(tools::Rectangle(Point(), GetOutputSizePixel()));
}
 
bool SpinField::ShowDropDown( bool )
{
    return false;
}
 
Size SpinField::CalcMinimumSizeForText(const OUString &rString) const
{
    Size aSz = Edit::CalcMinimumSizeForText(rString);
 
    if ( GetStyle() & WB_DROPDOWN )
        aSz.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize() );
    if ( GetStyle() & WB_SPIN )
    {
        ImplControlValue aControlValue;
        tools::Rectangle aArea( Point(), Size(100, aSz.Height()));
        tools::Rectangle aEntireBound, aEntireContent, aEditBound, aEditContent;
        if (
               GetNativeControlRegion(ControlType::Spinbox, ControlPart::Entire,
                   aArea, ControlState::NONE, aControlValue, aEntireBound, aEntireContent) &&
               GetNativeControlRegion(ControlType::Spinbox, ControlPart::SubEdit,
                   aArea, ControlState::NONE, aControlValue, aEditBound, aEditContent)
           )
        {
            aSz.AdjustWidth(aEntireContent.GetWidth() - aEditContent.GetWidth());
        }
        else
        {
            aSz.AdjustWidth(maUpperRect.GetWidth() );
        }
    }
 
    return aSz;
}
 
Size SpinField::CalcMinimumSize() const
{
    return CalcMinimumSizeForText(GetText());
}
 
Size SpinField::GetOptimalSize() const
{
    return CalcMinimumSize();
}
 
Size SpinField::CalcSize(sal_Int32 nChars) const
{
    Size aSz = Edit::CalcSize( nChars );
 
    if ( GetStyle() & WB_DROPDOWN )
        aSz.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize() );
    if ( GetStyle() & WB_SPIN )
        aSz.AdjustWidth(GetSettings().GetStyleSettings().GetSpinSize() );
 
    return aSz;
}
 
IMPL_LINK( SpinField, ImplTimeout, Timer*, pTimer, void )
{
    if ( pTimer->GetTimeout() == static_cast<sal_uInt64>(MouseSettings::GetButtonStartRepeat()) )
    {
        pTimer->SetTimeout( GetSettings().GetMouseSettings().GetButtonRepeat() );
        pTimer->Start();
    }
    else
    {
        if ( mbInitialUp )
            Up();
        else
            Down();
    }
}
 
void SpinField::Draw(OutputDevice* pDev, const Point& rPos, SystemTextColorFlags nFlags)
{
    Edit::Draw(pDev, rPos, nFlags);
 
    WinBits nFieldStyle = GetStyle();
    if ( (nFlags & SystemTextColorFlags::NoControls ) || !( nFieldStyle & (WB_SPIN|WB_DROPDOWN) ) )
        return;
 
    Point aPos = pDev->LogicToPixel( rPos );
    Size aSize = GetSizePixel();
    AllSettings aOldSettings = pDev->GetSettings();
 
    pDev->Push();
    pDev->SetMapMode();
 
    tools::Rectangle aDD, aUp, aDown;
    ImplCalcButtonAreas(pDev, aSize, aDD, aUp, aDown);
    aDD.Move(aPos.X(), aPos.Y());
    aUp.Move(aPos.X(), aPos.Y());
    aUp.AdjustTop( 1 );
    aDown.Move(aPos.X(), aPos.Y());
 
    Color aButtonTextColor;
    if (nFlags & SystemTextColorFlags::Mono)
        aButtonTextColor = COL_BLACK;
    else
        aButtonTextColor = GetSettings().GetStyleSettings().GetButtonTextColor();
 
    if (GetStyle() & WB_DROPDOWN)
    {
        DecorationView aView( pDev );
        tools::Rectangle aInnerRect = aView.DrawButton( aDD, DrawButtonFlags::NoLightBorder );
        DrawSymbolFlags nSymbolStyle = IsEnabled() ? DrawSymbolFlags::NONE : DrawSymbolFlags::Disable;
        aView.DrawSymbol(aInnerRect, SymbolType::SPIN_DOWN, aButtonTextColor, nSymbolStyle);
    }
 
    if (GetStyle() & WB_SPIN)
    {
        ImplDrawSpinButton(*pDev, this, aUp, aDown, false, false);
    }
 
    pDev->Pop();
    pDev->SetSettings(aOldSettings);
 
}
 
FactoryFunction SpinField::GetUITestFactory() const
{
    return SpinFieldUIObject::create;
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'Union' is required to be utilized.

V1048 The 'aControl' variable was assigned the same value.