/* -*- 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 <sal/config.h>

#include <algorithm>

#include "PosSizePropertyPanel.hxx"
#include <sal/log.hxx>
#include <svx/svxids.hrc>
#include <sfx2/dispatch.hxx>
#include <sfx2/bindings.hxx>
#include <sfx2/module.hxx>
#include <sfx2/viewsh.hxx>
#include <sfx2/objsh.hxx>
#include <sfx2/viewfrm.hxx>
#include <sfx2/weldutils.hxx>
#include <svx/dialcontrol.hxx>
#include <svx/dialmgr.hxx>
#include <svx/rectenum.hxx>
#include <svx/sdangitm.hxx>
#include <unotools/viewoptions.hxx>
#include <unotools/localedatawrapper.hxx>
#include <utility>
#include <vcl/canvastools.hxx>
#include <vcl/fieldvalues.hxx>
#include <svl/intitem.hxx>
#include <svx/strings.hrc>
#include <svx/svdpagv.hxx>
#include <svx/svdview.hxx>
#include <svx/transfrmhelper.hxx>
#include <boost/property_tree/ptree.hpp>

#include <svtools/unitconv.hxx>
#include <bitmaps.hlst>

using namespace css;
using namespace css::uno;

constexpr OUString USERITEM_NAME = u"FitItem"_ustr;

namespace svx::sidebar {

PosSizePropertyPanel::PosSizePropertyPanel(
    weld::Widget* pParent,
    const css::uno::Reference<css::frame::XFrame>& rxFrame,
    SfxBindings* pBindings,
    css::uno::Reference<css::ui::XSidebar> xSidebar)
:   PanelLayout(pParent, u"PosSizePropertyPanel"_ustr, u"svx/ui/sidebarpossize.ui"_ustr),
    m_aRatioTop(ConnectorType::Top),
    m_aRatioBottom(ConnectorType::Bottom),
    mxFtPosX(m_xBuilder->weld_label(u"horizontallabel"_ustr)),
    mxMtrPosX(m_xBuilder->weld_metric_spin_button(u"horizontalpos"_ustr, FieldUnit::CM)),
    mxFtPosY(m_xBuilder->weld_label(u"verticallabel"_ustr)),
    mxMtrPosY(m_xBuilder->weld_metric_spin_button(u"verticalpos"_ustr, FieldUnit::CM)),
    mxFtWidth(m_xBuilder->weld_label(u"widthlabel"_ustr)),
    mxMtrWidth(m_xBuilder->weld_metric_spin_button(u"selectwidth"_ustr, FieldUnit::CM)),
    mxFtHeight(m_xBuilder->weld_label(u"heightlabel"_ustr)),
    mxMtrHeight(m_xBuilder->weld_metric_spin_button(u"selectheight"_ustr, FieldUnit::CM))
    , mxCbxScale(m_xBuilder->weld_check_button(u"ratio"_ustr))
    , m_xCbxScaleImg(m_xBuilder->weld_image(u"imRatio"_ustr))
    , m_xImgRatioTop(new weld::CustomWeld(*m_xBuilder, u"daRatioTop"_ustr, m_aRatioTop))
    , m_xImgRatioBottom(new weld::CustomWeld(*m_xBuilder, u"daRatioBottom"_ustr, m_aRatioBottom))
    , mxFtAngle(m_xBuilder->weld_label(u"rotationlabel"_ustr))
    , mxMtrAngle(m_xBuilder->weld_metric_spin_button(u"rotation"_ustr, FieldUnit::DEGREE)),
    mxCtrlDial(new DialControl),
    mxDial(new weld::CustomWeld(*m_xBuilder, u"orientationcontrol"_ustr, *mxCtrlDial)),
    mxFtFlip(m_xBuilder->weld_label(u"fliplabel"_ustr)),
    mxFlipTbx(m_xBuilder->weld_toolbar(u"selectrotationtype"_ustr)),
    mxFlipDispatch(new ToolbarUnoDispatcher(*mxFlipTbx, *m_xBuilder, rxFrame)),
    mxArrangeTbx(m_xBuilder->weld_toolbar(u"arrangetoolbar"_ustr)),
    mxArrangeDispatch(new ToolbarUnoDispatcher(*mxArrangeTbx, *m_xBuilder, rxFrame)),
    mxArrangeTbx2(m_xBuilder->weld_toolbar(u"arrangetoolbar2"_ustr)),
    mxArrangeDispatch2(new ToolbarUnoDispatcher(*mxArrangeTbx2, *m_xBuilder, rxFrame)),
    mxAlignTbx(m_xBuilder->weld_toolbar(u"aligntoolbar"_ustr)),
    mxAlignDispatch(new ToolbarUnoDispatcher(*mxAlignTbx, *m_xBuilder, rxFrame)),
    mxAlignTbx2(m_xBuilder->weld_toolbar(u"aligntoolbar2"_ustr)),
    mxAlignDispatch2(new ToolbarUnoDispatcher(*mxAlignTbx2, *m_xBuilder, rxFrame)),
    mxBtnEditOLEObject(m_xBuilder->weld_button(u"btnEditObject"_ustr)),
    mpView(nullptr),
    mlOldWidth(1),
    mlOldHeight(1),
    mlRotX(0),
    mlRotY(0),
    mePoolUnit(),
    meDlgUnit(FieldUnit::INCH), // #i124409# init with fallback default
    mbFieldMetricOutDated(true),
    maTransfPosXControl(SID_ATTR_TRANSFORM_POS_X, *pBindings, *this),
    maTransfPosYControl(SID_ATTR_TRANSFORM_POS_Y, *pBindings, *this),
    maTransfWidthControl(SID_ATTR_TRANSFORM_WIDTH, *pBindings, *this),
    maTransfHeightControl(SID_ATTR_TRANSFORM_HEIGHT, *pBindings, *this),
    maSvxAngleControl( SID_ATTR_TRANSFORM_ANGLE, *pBindings, *this),
    maRotXControl(SID_ATTR_TRANSFORM_ROT_X, *pBindings, *this),
    maRotYControl(SID_ATTR_TRANSFORM_ROT_Y, *pBindings, *this),
    maProPosControl(SID_ATTR_TRANSFORM_PROTECT_POS, *pBindings, *this),
    maProSizeControl(SID_ATTR_TRANSFORM_PROTECT_SIZE, *pBindings, *this),
    maAutoWidthControl(SID_ATTR_TRANSFORM_AUTOWIDTH, *pBindings, *this),
    maAutoHeightControl(SID_ATTR_TRANSFORM_AUTOHEIGHT, *pBindings, *this),
    m_aMetricCtl(SID_ATTR_METRIC, *pBindings, *this),
    mpBindings(pBindings),
    mbSizeProtected(false),
    mbPositionProtected(false),
    mbAutoWidth(false),
    mbAutoHeight(false),
    mbAdjustEnabled(false),
    mbMtrPosXBlanked(false),
    mbMtrPosYBlanked(false),
    mbMtrWidthBlanked(false),
    mbMtrHeightBlanked(false),
    mbMtrAngleBlanked(false),
    mxSidebar(std::move(xSidebar))
{
    Initialize();

    // A guesstimate of the longest label in the various sidebar panes to use
    // to get this pane's contents to align with them, for lack of a better
    // solution
    auto nWidth = mxFtWidth->get_preferred_size().Width();
    OUString sLabel = mxFtWidth->get_label();
    mxFtWidth->set_label(SvxResId(RID_SVXSTR_TRANSPARENCY));
    nWidth = std::max(nWidth, mxFtWidth->get_preferred_size().Width());;
    mxFtWidth->set_label(sLabel);
    mxFtWidth->set_size_request(nWidth, -1);

    // vertical alignment = fill makes the drawingarea expand the associated spinedits so we have to size it here
    const sal_Int16 aHeight
        = static_cast<sal_Int16>(std::max(int(mxCbxScale->get_preferred_size().getHeight() / 2
                                              - mxMtrWidth->get_preferred_size().getHeight() / 2),
                                          12));
    const sal_Int16 aWidth
        = static_cast<sal_Int16>(mxCbxScale->get_preferred_size().getWidth() / 2);
    m_xImgRatioTop->set_size_request(aWidth, aHeight);
    m_xImgRatioBottom->set_size_request(aWidth, aHeight);
    //init needed for gtk3
    m_xCbxScaleImg->set_from_icon_name(mxCbxScale->get_active() ? RID_SVXBMP_LOCKED
                                                                : RID_SVXBMP_UNLOCKED);

    mpBindings->Update( SID_ATTR_METRIC );
    mpBindings->Update( SID_ATTR_TRANSFORM_WIDTH );
    mpBindings->Update( SID_ATTR_TRANSFORM_HEIGHT );
    mpBindings->Update( SID_ATTR_TRANSFORM_PROTECT_SIZE );
}

PosSizePropertyPanel::~PosSizePropertyPanel()
{
    mxFtPosX.reset();
    mxMtrPosX.reset();
    mxFtPosY.reset();
    mxMtrPosY.reset();
    mxFtWidth.reset();
    mxMtrWidth.reset();
    mxFtHeight.reset();
    mxMtrHeight.reset();
    mxCbxScale.reset();
    mxFtAngle.reset();
    mxMtrAngle.reset();
    mxDial.reset();
    mxCtrlDial.reset();
    mxFtFlip.reset();
    mxFlipDispatch.reset();
    mxFlipTbx.reset();
    mxAlignDispatch.reset();
    mxAlignDispatch2.reset();
    mxAlignTbx.reset();
    mxAlignTbx2.reset();
    mxArrangeDispatch.reset();
    mxArrangeDispatch2.reset();
    mxArrangeTbx.reset();
    mxArrangeTbx2.reset();
    mxBtnEditOLEObject.reset();

    maTransfPosXControl.dispose();
    maTransfPosYControl.dispose();
    maTransfWidthControl.dispose();
    maTransfHeightControl.dispose();

    maSvxAngleControl.dispose();
    maRotXControl.dispose();
    maRotYControl.dispose();
    maProPosControl.dispose();
    maProSizeControl.dispose();
    maAutoWidthControl.dispose();
    maAutoHeightControl.dispose();
    m_aMetricCtl.dispose();
}

namespace
{
    bool hasText(const SdrView& rSdrView)
    {
        const SdrMarkList& rMarkList = rSdrView.GetMarkedObjectList();

        if(1 == rMarkList.GetMarkCount())
        {
            const SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj();
            const SdrObjKind eKind(pObj->GetObjIdentifier());

            if((pObj->GetObjInventor() == SdrInventor::Default) && (SdrObjKind::Text == eKind || SdrObjKind::TitleText == eKind || SdrObjKind::OutlineText == eKind))
            {
                const SdrTextObj* pSdrTextObj = DynCastSdrTextObj(pObj);

                if(pSdrTextObj && pSdrTextObj->HasText())
                {
                    return true;
                }
            }
        }

        return false;
    }
} // end of anonymous namespace


void PosSizePropertyPanel::Initialize()
{
    //Position : Horizontal / Vertical
    mxMtrPosX->connect_value_changed( LINK( this, PosSizePropertyPanel, ChangePosXHdl ) );
    mxMtrPosY->connect_value_changed( LINK( this, PosSizePropertyPanel, ChangePosYHdl ) );

    //Size : Width / Height
    mxMtrWidth->connect_value_changed( LINK( this, PosSizePropertyPanel, ChangeWidthHdl ) );
    mxMtrHeight->connect_value_changed( LINK( this, PosSizePropertyPanel, ChangeHeightHdl ) );

    //Size : Keep ratio
    mxCbxScale->connect_toggled( LINK( this, PosSizePropertyPanel, ClickAutoHdl ) );

    //rotation control
    mxCtrlDial->SetLinkedField(mxMtrAngle.get(), 2);
    mxCtrlDial->SetModifyHdl(LINK( this, PosSizePropertyPanel, RotationHdl));

    //use same logic as DialControl_Impl::SetSize
    weld::DrawingArea* pDrawingArea = mxCtrlDial->GetDrawingArea();
    int nDim = (std::min<int>(pDrawingArea->get_approximate_digit_width() * 6,
                              pDrawingArea->get_text_height() * 3) - 1) | 1;
    Size aSize(nDim, nDim);
    pDrawingArea->set_size_request(aSize.Width(), aSize.Height());
    mxCtrlDial->Init(aSize);

    mxBtnEditOLEObject->connect_clicked( LINK( this, PosSizePropertyPanel, ClickObjectEditHdl ) );

    SfxViewShell* pCurSh = SfxViewShell::Current();
    if ( pCurSh )
        mpView = pCurSh->GetDrawView();
    else
        mpView = nullptr;

    if ( mpView != nullptr )
    {
        maUIScale = mpView->GetModel().GetUIScale();
        mbAdjustEnabled = hasText(*mpView);
    }

    mePoolUnit = maTransfWidthControl.GetCoreMetric();
}

std::unique_ptr<PanelLayout> PosSizePropertyPanel::Create (
    weld::Widget* pParent,
    const css::uno::Reference<css::frame::XFrame>& rxFrame,
    SfxBindings* pBindings,
    const css::uno::Reference<css::ui::XSidebar>& rxSidebar)
{
    if (pParent == nullptr)
        throw lang::IllegalArgumentException(u"no parent Window given to PosSizePropertyPanel::Create"_ustr, nullptr, 0);
    if ( ! rxFrame.is())
        throw lang::IllegalArgumentException(u"no XFrame given to PosSizePropertyPanel::Create"_ustr, nullptr, 1);
    if (pBindings == nullptr)
        throw lang::IllegalArgumentException(u"no SfxBindings given to PosSizePropertyPanel::Create"_ustr, nullptr, 2);

    return std::make_unique<PosSizePropertyPanel>(pParent, rxFrame, pBindings, rxSidebar);
}

void PosSizePropertyPanel::HandleContextChange(
    const vcl::EnumContext& rContext)
{
    if (maContext == rContext)
    {
        // Nothing to do.
        return;
    }

    maContext = rContext;

    bool bShowPosition = false;
    bool bShowAngle = false;
    bool bShowFlip = false;
    bool bShowEditObject = false;
    bool bShowArrangeTbx2 = false;

    switch (maContext.GetCombinedContext_DI())
    {
        case CombinedEnumContext(Application::WriterVariants, Context::Draw):
            bShowAngle = true;
            bShowFlip = true;
            bShowArrangeTbx2 = true;
            break;

        case CombinedEnumContext(Application::WriterVariants, Context::Graphic):
            bShowFlip = true;
            bShowAngle = true; // RotGrfFlyFrame: Writer FlyFrames for Graphics now support angle
            break;

        case CombinedEnumContext(Application::Calc, Context::Draw):
        case CombinedEnumContext(Application::Calc, Context::DrawLine):
        case CombinedEnumContext(Application::Calc, Context::Graphic):
        case CombinedEnumContext(Application::DrawImpress, Context::Draw):
        case CombinedEnumContext(Application::DrawImpress, Context::DrawLine):
        case CombinedEnumContext(Application::DrawImpress, Context::TextObject):
        case CombinedEnumContext(Application::DrawImpress, Context::Graphic):
            bShowPosition = true;
            bShowAngle = true;
            bShowFlip = true;
            break;

        case CombinedEnumContext(Application::WriterVariants, Context::OLE):
            bShowEditObject = true;
            break;

        case CombinedEnumContext(Application::Calc, Context::OLE):
        case CombinedEnumContext(Application::DrawImpress, Context::OLE):
            bShowPosition = true;
            bShowEditObject = true;
            break;

        case CombinedEnumContext(Application::Calc, Context::Chart):
        case CombinedEnumContext(Application::Calc, Context::Form):
        case CombinedEnumContext(Application::Calc, Context::Media):
        case CombinedEnumContext(Application::Calc, Context::MultiObject):
        case CombinedEnumContext(Application::DrawImpress, Context::Media):
        case CombinedEnumContext(Application::DrawImpress, Context::Form):
        case CombinedEnumContext(Application::DrawImpress, Context::ThreeDObject):
        case CombinedEnumContext(Application::DrawImpress, Context::MultiObject):
            bShowPosition = true;
            break;
    }

    // Position
    mxFtPosX->set_visible(bShowPosition);
    mxMtrPosX->set_visible(bShowPosition);
    mxFtPosY->set_visible(bShowPosition);
    mxMtrPosY->set_visible(bShowPosition);

    // Rotation
    mxFtAngle->set_visible(bShowAngle);
    mxMtrAngle->set_visible(bShowAngle);
    mxCtrlDial->set_visible(bShowAngle);

    // Flip
    mxFtFlip->set_visible(bShowFlip);
    mxFlipTbx->set_visible(bShowFlip);

    // Edit Object
    mxBtnEditOLEObject->set_visible(bShowEditObject);

    // Arrange tool bar 2
    mxArrangeTbx2->set_visible(bShowArrangeTbx2);

    if (mxSidebar.is())
        mxSidebar->requestLayout();
}


IMPL_LINK_NOARG( PosSizePropertyPanel, ChangeWidthHdl, weld::MetricSpinButton&, void )
{
    if( mxCbxScale->get_active() &&
        mxCbxScale->get_sensitive() )
    {
        tools::Long nHeight = static_cast<tools::Long>( (static_cast<double>(mlOldHeight) * static_cast<double>(mxMtrWidth->get_value(FieldUnit::NONE))) / static_cast<double>(mlOldWidth) );
        if( nHeight <= mxMtrHeight->get_max( FieldUnit::NONE ) )
        {
            mxMtrHeight->set_value( nHeight, FieldUnit::NONE );
        }
        else
        {
            nHeight = static_cast<tools::Long>(mxMtrHeight->get_max( FieldUnit::NONE ));
            mxMtrHeight->set_value(nHeight, FieldUnit::NONE);
            const tools::Long nWidth = static_cast<tools::Long>( (static_cast<double>(mlOldWidth) * static_cast<double>(nHeight)) / static_cast<double>(mlOldHeight) );
            mxMtrWidth->set_value( nWidth, FieldUnit::NONE );
        }
    }
    executeSize();
}


IMPL_LINK_NOARG( PosSizePropertyPanel, ChangeHeightHdl, weld::MetricSpinButton&, void )
{
    if( mxCbxScale->get_active() &&
        mxCbxScale->get_sensitive() )
    {
        tools::Long nWidth = static_cast<tools::Long>( (static_cast<double>(mlOldWidth) * static_cast<double>(mxMtrHeight->get_value(FieldUnit::NONE))) / static_cast<double>(mlOldHeight) );
        if( nWidth <= mxMtrWidth->get_max( FieldUnit::NONE ) )
        {
            mxMtrWidth->set_value( nWidth, FieldUnit::NONE );
        }
        else
        {
            nWidth = static_cast<tools::Long>(mxMtrWidth->get_max( FieldUnit::NONE ));
            mxMtrWidth->set_value( nWidth, FieldUnit::NONE );
            const tools::Long nHeight = static_cast<tools::Long>( (static_cast<double>(mlOldHeight) * static_cast<double>(nWidth)) / static_cast<double>(mlOldWidth) );
            mxMtrHeight->set_value( nHeight, FieldUnit::NONE );
        }
    }
    executeSize();
}


IMPL_LINK_NOARG( PosSizePropertyPanel, ChangePosXHdl, weld::MetricSpinButton&, void )
{
    if ( mxMtrPosX->get_value_changed_from_saved())
    {
        tools::Long lX = GetCoreValue( *mxMtrPosX, mePoolUnit );

        Fraction aUIScale = mpView->GetModel().GetUIScale();
        lX = tools::Long( lX * aUIScale );

        SfxInt32Item aPosXItem( SID_ATTR_TRANSFORM_POS_X,static_cast<sal_uInt32>(lX));

        GetBindings()->GetDispatcher()->ExecuteList(
            SID_ATTR_TRANSFORM, SfxCallMode::RECORD, { &aPosXItem });
    }
}

IMPL_LINK_NOARG( PosSizePropertyPanel, ChangePosYHdl, weld::MetricSpinButton&, void )
{
    if ( mxMtrPosY->get_value_changed_from_saved() )
    {
        tools::Long lY = GetCoreValue( *mxMtrPosY, mePoolUnit );

        Fraction aUIScale = mpView->GetModel().GetUIScale();
        lY = tools::Long( lY * aUIScale );

        SfxInt32Item aPosYItem( SID_ATTR_TRANSFORM_POS_Y,static_cast<sal_uInt32>(lY));

        GetBindings()->GetDispatcher()->ExecuteList(
            SID_ATTR_TRANSFORM, SfxCallMode::RECORD, { &aPosYItem });
    }
}

IMPL_LINK_NOARG( PosSizePropertyPanel, ClickAutoHdl, weld::Toggleable&, void )
{
    m_xCbxScaleImg->set_from_icon_name(mxCbxScale->get_active() ? RID_SVXBMP_LOCKED : RID_SVXBMP_UNLOCKED);
    if ( mxCbxScale->get_active() )
    {
        mlOldWidth  = std::max(GetCoreValue(*mxMtrWidth,  mePoolUnit), SAL_CONST_INT64(1));
        mlOldHeight = std::max(GetCoreValue(*mxMtrHeight, mePoolUnit), SAL_CONST_INT64(1));
    }

    // mxCbxScale must synchronized with that on Position and Size tabpage on Shape Properties dialog
    SvtViewOptions aPageOpt(EViewType::TabPage, u"cui/ui/possizetabpage/PositionAndSize"_ustr);
    aPageOpt.SetUserItem( USERITEM_NAME, css::uno::Any( OUString::number( int(mxCbxScale->get_active()) ) ) );
}

IMPL_LINK_NOARG( PosSizePropertyPanel, RotationHdl, DialControl&, void )
{
    Degree100 nTmp = mxCtrlDial->GetRotation();

    // #i123993# Need to take UIScale into account when executing rotations
    const double fUIScale(mpView ? double(mpView->GetModel().GetUIScale()) : 1.0);
    SdrAngleItem aAngleItem( SID_ATTR_TRANSFORM_ANGLE, nTmp);
    SfxInt32Item aRotXItem( SID_ATTR_TRANSFORM_ROT_X, basegfx::fround(mlRotX * fUIScale));
    SfxInt32Item aRotYItem( SID_ATTR_TRANSFORM_ROT_Y, basegfx::fround(mlRotY * fUIScale));

    GetBindings()->GetDispatcher()->ExecuteList(SID_ATTR_TRANSFORM,
            SfxCallMode::RECORD, { &aAngleItem, &aRotXItem, &aRotYItem });
}

IMPL_STATIC_LINK_NOARG( PosSizePropertyPanel, ClickObjectEditHdl, weld::Button&, void )
{
    SfxViewShell* pCurSh = SfxViewShell::Current();
    if ( pCurSh)
    {
        pCurSh->DoVerb( -1 );
    }
}

namespace
{
    void limitWidth(weld::MetricSpinButton& rMetricSpinButton)
    {
        // space is limited in the sidebar, so limit MetricSpinButtons to a width of 7 digits
        const int nMaxDigits = 7;

        weld::SpinButton& rSpinButton = rMetricSpinButton.get_widget();
        rSpinButton.set_width_chars(std::min(rSpinButton.get_width_chars(), nMaxDigits));
    }
}

void PosSizePropertyPanel::NotifyItemUpdate(
    sal_uInt16 nSID,
    SfxItemState eState,
    const SfxPoolItem* pState)
{
    mxFtAngle->set_sensitive(true);
    mxMtrAngle->set_sensitive(true);
    mxDial->set_sensitive(true);
    mxFtFlip->set_sensitive(true);
    mxFlipTbx->set_sensitive(true);

    const SfxUInt32Item*    pWidthItem;
    const SfxUInt32Item*    pHeightItem;

    SfxViewShell* pCurSh = SfxViewShell::Current();
    if ( pCurSh )
        mpView = pCurSh->GetDrawView();
    else
        mpView = nullptr;

    if ( mpView == nullptr )
        return;

    mbAdjustEnabled = hasText(*mpView);

    // Pool unit and dialog unit may have changed, make sure that we
    // have the current values.
    mePoolUnit = maTransfWidthControl.GetCoreMetric();

    switch (nSID)
    {
        case SID_ATTR_TRANSFORM_WIDTH:
            if ( SfxItemState::DEFAULT == eState )
            {
                pWidthItem = dynamic_cast< const SfxUInt32Item* >(pState);

                if(pWidthItem)
                {
                    tools::Long lOldWidth1 = tools::Long( pWidthItem->GetValue() / maUIScale );
                    SetFieldUnit( *mxMtrWidth, meDlgUnit, true );
                    SetMetricValue( *mxMtrWidth, lOldWidth1, mePoolUnit );
                    limitWidth(*mxMtrWidth);
                    mlOldWidth = lOldWidth1;
                    mxMtrWidth->save_value();
                    if (mbMtrWidthBlanked)
                    {
                        mxMtrWidth->reformat();
                        mbMtrWidthBlanked = false;
                    }
                    break;
                }
            }
            mbMtrWidthBlanked = true;
            break;

        case SID_ATTR_TRANSFORM_HEIGHT:
            if ( SfxItemState::DEFAULT == eState )
            {
                pHeightItem = dynamic_cast< const SfxUInt32Item* >(pState);

                if(pHeightItem)
                {
                    tools::Long nTmp = tools::Long( pHeightItem->GetValue() / maUIScale);
                    SetFieldUnit( *mxMtrHeight, meDlgUnit, true );
                    SetMetricValue( *mxMtrHeight, nTmp, mePoolUnit );
                    limitWidth(*mxMtrHeight);
                    mlOldHeight = nTmp;
                    mxMtrHeight->save_value();
                    if (mbMtrHeightBlanked)
                    {
                        mxMtrHeight->reformat();
                        mbMtrHeightBlanked = false;
                    }
                    break;
                }
            }
            mbMtrHeightBlanked = true;
            break;

        case SID_ATTR_TRANSFORM_POS_X:
            if(SfxItemState::DEFAULT == eState)
            {
                const SfxInt32Item* pItem = dynamic_cast< const SfxInt32Item* >(pState);

                if(pItem)
                {
                    tools::Long nTmp = tools::Long(pItem->GetValue() / maUIScale);
                    SetFieldUnit( *mxMtrPosX, meDlgUnit, true );
                    SetMetricValue( *mxMtrPosX, nTmp, mePoolUnit );
                    limitWidth(*mxMtrPosX);
                    mxMtrPosX->save_value();
                    if (mbMtrPosXBlanked)
                    {
                        mxMtrPosX->reformat();
                        mbMtrPosXBlanked = false;
                    }
                    break;
                }
            }
            mbMtrPosXBlanked = true;
            break;

        case SID_ATTR_TRANSFORM_POS_Y:
            if(SfxItemState::DEFAULT == eState)
            {
                const SfxInt32Item* pItem = dynamic_cast< const SfxInt32Item* >(pState);

                if(pItem)
                {
                    tools::Long nTmp = tools::Long(pItem->GetValue() / maUIScale);
                    SetFieldUnit( *mxMtrPosY, meDlgUnit, true );
                    SetMetricValue( *mxMtrPosY, nTmp, mePoolUnit );
                    limitWidth(*mxMtrPosY);
                    mxMtrPosY->save_value();
                    if (mbMtrPosYBlanked)
                    {
                        mxMtrPosY->reformat();
                        mbMtrPosYBlanked = false;
                    }
                    break;
                }
            }
            mbMtrPosYBlanked = true;
            break;

        case SID_ATTR_TRANSFORM_ROT_X:
            if (SfxItemState::DEFAULT == eState)
            {
                const SfxInt32Item* pItem = dynamic_cast< const SfxInt32Item* >(pState);

                if(pItem)
                {
                    mlRotX = pItem->GetValue();
                    mlRotX = tools::Long( mlRotX / maUIScale );
                }
            }
            break;

        case SID_ATTR_TRANSFORM_ROT_Y:
            if (SfxItemState::DEFAULT == eState)
            {
                const SfxInt32Item* pItem = dynamic_cast< const SfxInt32Item* >(pState);

                if(pItem)
                {
                    mlRotY = pItem->GetValue();
                    mlRotY = tools::Long( mlRotY / maUIScale );
                }
            }
            break;

        case SID_ATTR_TRANSFORM_PROTECT_POS:
            if(SfxItemState::DEFAULT == eState)
            {
                const SfxBoolItem* pItem = dynamic_cast< const SfxBoolItem* >(pState);

                if(pItem)
                {
                    // record the state of position protect
                    mbPositionProtected = pItem->GetValue();
                    break;
                }
            }

            mbPositionProtected = false;
            break;

        case SID_ATTR_TRANSFORM_PROTECT_SIZE:
            if(SfxItemState::DEFAULT == eState)
            {
                const SfxBoolItem* pItem = dynamic_cast< const SfxBoolItem* >(pState);

                if(pItem)
                {
                    // record the state of size protect
                    mbSizeProtected = pItem->GetValue();
                    break;
                }
            }

            mbSizeProtected = false;
            break;

        case SID_ATTR_TRANSFORM_AUTOWIDTH:
            if(SfxItemState::DEFAULT == eState)
            {
                const SfxBoolItem* pItem = dynamic_cast< const SfxBoolItem* >(pState);

                if(pItem)
                {
                    mbAutoWidth = pItem->GetValue();
                }
            }
            break;

        case SID_ATTR_TRANSFORM_AUTOHEIGHT:
            if(SfxItemState::DEFAULT == eState)
            {
                const SfxBoolItem* pItem = dynamic_cast< const SfxBoolItem* >(pState);

                if(pItem)
                {
                    mbAutoHeight = pItem->GetValue();
                }
            }
            break;

        case SID_ATTR_TRANSFORM_ANGLE:
            if (eState >= SfxItemState::DEFAULT)
            {
                const SdrAngleItem* pItem = dynamic_cast< const SdrAngleItem* >(pState);

                if(pItem)
                {
                    Degree100 nTmp = NormAngle36000(pItem->GetValue());

                    mxMtrAngle->set_value(nTmp.get(), FieldUnit::DEGREE);
                    mxCtrlDial->SetRotation(nTmp);

                    if (mbMtrAngleBlanked)
                    {
                        mxMtrAngle->reformat();
                        mbMtrAngleBlanked = false;
                    }

                    break;
                }
            }
            mbMtrAngleBlanked = true;
            mxCtrlDial->SetRotation( 0_deg100 );
            break;

        case SID_ATTR_METRIC:
        {
            const Fraction aUIScale(mpView->GetModel().GetUIScale());
            MetricState(eState, pState, aUIScale);
            UpdateUIScale(aUIScale);
            mbFieldMetricOutDated = false;
            break;
        }
        default:
            break;
    }

    const sal_Int32 nCombinedContext(maContext.GetCombinedContext_DI());
    const SdrMarkList& rMarkList = mpView->GetMarkedObjectList();

    switch (rMarkList.GetMarkCount())
    {
        case 0:
            break;

        case 1:
        {
            const SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj();
            const SdrObjKind eKind(pObj->GetObjIdentifier());

            if(((nCombinedContext == CombinedEnumContext(Application::DrawImpress, Context::Draw)
               || nCombinedContext == CombinedEnumContext(Application::DrawImpress, Context::TextObject)
                 ) && SdrObjKind::Edge == eKind)
               || SdrObjKind::Caption == eKind)
            {
                mxFtAngle->set_sensitive(false);
                mxMtrAngle->set_sensitive(false);
                mxDial->set_sensitive(false);
                mxFlipTbx->set_sensitive(false);
                mxFtFlip->set_sensitive(false);
            }
            break;
        }

        default:
        {
            sal_uInt16 nMarkObj = 0;
            bool isNoEdge = true;

            while(isNoEdge && rMarkList.GetMark(nMarkObj))
            {
                const SdrObject* pObj = rMarkList.GetMark(nMarkObj)->GetMarkedSdrObj();
                const SdrObjKind eKind(pObj->GetObjIdentifier());

                if(((nCombinedContext == CombinedEnumContext(Application::DrawImpress, Context::Draw)
                  || nCombinedContext == CombinedEnumContext(Application::DrawImpress, Context::TextObject)
                     ) && SdrObjKind::Edge == eKind)
                  || SdrObjKind::Caption == eKind)
                {
                    isNoEdge = false;
                    break;
                }
                nMarkObj++;
            }

            if(!isNoEdge)
            {
                mxFtAngle->set_sensitive(false);
                mxMtrAngle->set_sensitive(false);
                mxDial->set_sensitive(false);
                mxFlipTbx->set_sensitive(false);
                mxFtFlip->set_sensitive(false);
            }
            break;
        }
    }

    if(nCombinedContext == CombinedEnumContext(Application::DrawImpress, Context::TextObject))
    {
        mxFlipTbx->set_sensitive(false);
        mxFtFlip->set_sensitive(false);
    }

    DisableControls();

    // mxCbxScale must synchronized with that on Position and Size tabpage on Shape Properties dialog
    SvtViewOptions aPageOpt(EViewType::TabPage, u"cui/ui/possizetabpage/PositionAndSize"_ustr);
    OUString  sUserData;
    css::uno::Any  aUserItem = aPageOpt.GetUserItem( USERITEM_NAME );
    OUString aTemp;
    if ( aUserItem >>= aTemp )
        sUserData = aTemp;
    mxCbxScale->set_active(static_cast<bool>(sUserData.toInt32()));
    m_xCbxScaleImg->set_from_icon_name(mxCbxScale->get_active() ? RID_SVXBMP_LOCKED : RID_SVXBMP_UNLOCKED);
}

void PosSizePropertyPanel::GetControlState(const sal_uInt16 nSID, boost::property_tree::ptree& rState)
{
    weld::MetricSpinButton* pControl = nullptr;
    switch (nSID)
    {
        case SID_ATTR_TRANSFORM_POS_X:
            pControl = mxMtrPosX.get();
            break;
        case SID_ATTR_TRANSFORM_POS_Y:
            pControl = mxMtrPosY.get();
            break;
        case SID_ATTR_TRANSFORM_WIDTH:
            pControl = mxMtrWidth.get();
            break;
        case SID_ATTR_TRANSFORM_HEIGHT:
            pControl = mxMtrHeight.get();
            break;
    }

    if (pControl && !pControl->get_text().isEmpty())
    {
        OUString sValue = Application::GetSettings().GetNeutralLocaleDataWrapper().
            getNum(pControl->get_value(pControl->get_unit()), pControl->get_digits(), false, false);
        rState.put(pControl->get_buildable_name().toUtf8().getStr(), sValue.toUtf8().getStr());
    }
}

void PosSizePropertyPanel::executeSize()
{
    if ( !mxMtrWidth->get_value_changed_from_saved() && !mxMtrHeight->get_value_changed_from_saved())
        return;

    Fraction aUIScale = mpView->GetModel().GetUIScale();

    // get Width
    double nWidth = static_cast<double>(mxMtrWidth->get_value(FieldUnit::MM_100TH));
    tools::Long lWidth = tools::Long(nWidth * static_cast<double>(aUIScale));
    lWidth = OutputDevice::LogicToLogic( lWidth, MapUnit::Map100thMM, mePoolUnit );
    lWidth = static_cast<tools::Long>(mxMtrWidth->denormalize( lWidth ));

    // get Height
    double nHeight = static_cast<double>(mxMtrHeight->get_value(FieldUnit::MM_100TH));
    tools::Long lHeight = tools::Long(nHeight * static_cast<double>(aUIScale));
    lHeight = OutputDevice::LogicToLogic( lHeight, MapUnit::Map100thMM, mePoolUnit );
    lHeight = static_cast<tools::Long>(mxMtrHeight->denormalize( lHeight ));

    // put Width & Height to itemset
    SfxUInt32Item aWidthItem( SID_ATTR_TRANSFORM_WIDTH, static_cast<sal_uInt32>(lWidth));
    SfxUInt32Item aHeightItem( SID_ATTR_TRANSFORM_HEIGHT, static_cast<sal_uInt32>(lHeight));
    SfxUInt16Item aPointItem (SID_ATTR_TRANSFORM_SIZE_POINT, sal_uInt16(RectPoint::LT));
    const sal_Int32 nCombinedContext(maContext.GetCombinedContext_DI());

    if( nCombinedContext == CombinedEnumContext(Application::WriterVariants, Context::Graphic)
        || nCombinedContext == CombinedEnumContext(Application::WriterVariants, Context::OLE)
        )
    {
        GetBindings()->GetDispatcher()->ExecuteList(SID_ATTR_TRANSFORM,
            SfxCallMode::RECORD, { &aWidthItem, &aHeightItem, &aPointItem });
    }
    else
    {
        if ( (mxMtrWidth->get_value_changed_from_saved()) && (mxMtrHeight->get_value_changed_from_saved()))
            GetBindings()->GetDispatcher()->ExecuteList(SID_ATTR_TRANSFORM,
                SfxCallMode::RECORD, { &aWidthItem, &aHeightItem, &aPointItem });
        else if( mxMtrWidth->get_value_changed_from_saved())
            GetBindings()->GetDispatcher()->ExecuteList(SID_ATTR_TRANSFORM,
                SfxCallMode::RECORD, { &aWidthItem, &aPointItem });
        else if ( mxMtrHeight->get_value_changed_from_saved())
            GetBindings()->GetDispatcher()->ExecuteList(SID_ATTR_TRANSFORM,
                SfxCallMode::RECORD, { &aHeightItem, &aPointItem });
    }
}

void PosSizePropertyPanel::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
{
    if (meDlgUnit != GetCurrentUnit(SfxItemState::DEFAULT, nullptr))
    {
        mpBindings->Update( SID_ATTR_METRIC );
    }

    PanelLayout::DumpAsPropertyTree(rJsonWriter);
}

void PosSizePropertyPanel::MetricState(SfxItemState eState, const SfxPoolItem* pState, const Fraction& rUIScale)
{
    bool bPosXBlank = false;
    bool bPosYBlank = false;
    bool bWidthBlank = false;
    bool bHeightBlank = false;

    // #i124409# use the given Item to get the correct UI unit and initialize it
    // and the Fields using it
    FieldUnit eDlgUnit = GetCurrentUnit(eState, pState);
    mbFieldMetricOutDated |= (eDlgUnit != meDlgUnit || maUIScale != rUIScale);
    if (!mbFieldMetricOutDated)
        return;
    meDlgUnit = eDlgUnit;

    if (mxMtrPosX->get_text().isEmpty())
        bPosXBlank = true;
    SetFieldUnit( *mxMtrPosX, meDlgUnit, true );
    if (bPosXBlank)
    {
        mbMtrPosXBlanked = true;
    }

    if (mxMtrPosY->get_text().isEmpty())
        bPosYBlank = true;
    SetFieldUnit( *mxMtrPosY, meDlgUnit, true );
    if (bPosYBlank)
    {
        mbMtrPosYBlanked = true;
    }

    SetPosSizeMinMax(rUIScale);

    if (mxMtrWidth->get_text().isEmpty())
        bWidthBlank = true;
    SetFieldUnit( *mxMtrWidth, meDlgUnit, true );
    if (bWidthBlank)
    {
        mbMtrWidthBlanked = true;
    }

    if (mxMtrHeight->get_text().isEmpty())
        bHeightBlank = true;
    SetFieldUnit( *mxMtrHeight, meDlgUnit, true );
    if (bHeightBlank)
    {
        mbMtrHeightBlanked = true;
    }
}


FieldUnit PosSizePropertyPanel::GetCurrentUnit( SfxItemState eState, const SfxPoolItem* pState )
{
    FieldUnit eUnit = FieldUnit::NONE;

    if ( pState && eState >= SfxItemState::DEFAULT )
    {
        eUnit = static_cast<FieldUnit>(static_cast<const SfxUInt16Item*>(pState)->GetValue());
    }
    else
    {
        SfxViewFrame* pFrame = SfxViewFrame::Current();
        SfxObjectShell* pSh = nullptr;
        if ( pFrame )
            pSh = pFrame->GetObjectShell();
        if ( pSh )
        {
            SfxModule* pModule = pSh->GetModule();
            if ( pModule )
            {
                eUnit = pModule->GetFieldUnit();
            }
            else
            {
                SAL_WARN("svx.sidebar", "GetModuleFieldUnit(): no module found");
            }
        }
    }

    return eUnit;
}


void PosSizePropertyPanel::DisableControls()
{
    const bool bZeroDimension = mlOldWidth == 0 || mlOldHeight == 0;

    if( mbPositionProtected )
    {
        // the position is protected("Position protect" option in modal dialog is checked),
        // disable all the Position controls in sidebar
        mxFtPosX->set_sensitive(false);
        mxMtrPosX->set_sensitive(false);
        mxFtPosY->set_sensitive(false);
        mxMtrPosY->set_sensitive(false);
        mxFtAngle->set_sensitive(false);
        mxMtrAngle->set_sensitive(false);
        mxDial->set_sensitive(false);
        mxFtFlip->set_sensitive(false);
        mxFlipTbx->set_sensitive(false);

        mxFtWidth->set_sensitive(false);
        mxMtrWidth->set_sensitive(false);
        mxFtHeight->set_sensitive(false);
        mxMtrHeight->set_sensitive(false);
        mxCbxScale->set_sensitive(false);
    }
    else
    {
        mxFtPosX->set_sensitive(true);
        mxMtrPosX->set_sensitive(true);
        mxFtPosY->set_sensitive(true);
        mxMtrPosY->set_sensitive(true);

        if( mbSizeProtected )
        {
            mxFtWidth->set_sensitive(false);
            mxMtrWidth->set_sensitive(false);
            mxFtHeight->set_sensitive(false);
            mxMtrHeight->set_sensitive(false);
            mxCbxScale->set_sensitive(false);
        }
        else
        {
            if( mbAdjustEnabled )
            {
                if( mbAutoWidth )
                {
                    mxFtWidth->set_sensitive(false);
                    mxMtrWidth->set_sensitive(false);
                    mxCbxScale->set_sensitive(false);
                }
                else
                {
                    mxFtWidth->set_sensitive(true);
                    mxMtrWidth->set_sensitive(true);
                }
                if( mbAutoHeight )
                {
                    mxFtHeight->set_sensitive(false);
                    mxMtrHeight->set_sensitive(false);
                    mxCbxScale->set_sensitive(false);
                }
                else
                {
                    mxFtHeight->set_sensitive(true);
                    mxMtrHeight->set_sensitive(true);
                }
                if( !mbAutoWidth && !mbAutoHeight )
                    mxCbxScale->set_sensitive(true);
            }
            else
            {
                mxFtWidth->set_sensitive(true);
                mxMtrWidth->set_sensitive(true);
                mxFtHeight->set_sensitive(true);
                mxMtrHeight->set_sensitive(true);
                mxCbxScale->set_sensitive(true);
            }
        }
    }

    // tdf#150743 Disable Keep ratio if there is zero width/height
    if (bZeroDimension)
        mxCbxScale->set_sensitive(false);
}

void PosSizePropertyPanel::SetPosSizeMinMax(const Fraction& rUIScale)
{
    SdrPageView* pPV = mpView->GetSdrPageView();
    if (!pPV)
        return;
    tools::Rectangle aTmpRect(mpView->GetAllMarkedRect());
    pPV->LogicToPagePos(aTmpRect);
    maRect = vcl::unotools::b2DRectangleFromRectangle(aTmpRect);

    tools::Rectangle aTmpRect2(mpView->GetWorkArea());
    pPV->LogicToPagePos(aTmpRect2);
    maWorkArea = vcl::unotools::b2DRectangleFromRectangle(aTmpRect2);

    TransfrmHelper::ScaleRect(maWorkArea, rUIScale);
    TransfrmHelper::ScaleRect(maRect, rUIScale);

    const sal_uInt16 nDigits(mxMtrPosX->get_digits());
    TransfrmHelper::ConvertRect( maWorkArea, nDigits, mePoolUnit, meDlgUnit );
    TransfrmHelper::ConvertRect( maRect, nDigits, mePoolUnit, meDlgUnit );

    double fLeft(maWorkArea.getMinX());
    double fTop(maWorkArea.getMinY());
    double fRight(maWorkArea.getMaxX());
    double fBottom(maWorkArea.getMaxY());

    // seems that sidebar defaults to top left reference point
    // and there's no way to set it to something else
    fRight  -= maRect.getWidth();
    fBottom -= maRect.getHeight();

    const double fMaxLong(static_cast<double>(vcl::ConvertAndScaleValue( LONG_MAX, 0, MapUnit::Map100thMM, meDlgUnit ) - 1));
    fLeft = std::clamp(fLeft, -fMaxLong, fMaxLong);
    fRight = std::clamp(fRight, -fMaxLong, fMaxLong);
    fTop = std::clamp(fTop, - fMaxLong, fMaxLong);
    fBottom = std::clamp(fBottom, -fMaxLong, fMaxLong);

    mxMtrPosX->set_range(basegfx::fround64(fLeft), basegfx::fround64(fRight), FieldUnit::NONE);
    limitWidth(*mxMtrPosX);
    mxMtrPosY->set_range(basegfx::fround64(fTop), basegfx::fround64(fBottom), FieldUnit::NONE);
    limitWidth(*mxMtrPosY);

    double fMaxWidth = maWorkArea.getWidth() - (maRect.getWidth() - fLeft);
    double fMaxHeight = maWorkArea.getHeight() - (maRect.getHeight() - fTop);
    mxMtrWidth->set_max(std::min<sal_Int64>(INT_MAX, basegfx::fround64(fMaxWidth*100)), FieldUnit::NONE);
    limitWidth(*mxMtrWidth);
    mxMtrHeight->set_max(std::min<sal_Int64>(INT_MAX, basegfx::fround64(fMaxHeight*100)), FieldUnit::NONE);
    limitWidth(*mxMtrHeight);
}

void PosSizePropertyPanel::UpdateUIScale(const Fraction& rUIScale)
{
    if (maUIScale == rUIScale)
        return;

    // UI scale has changed.

    // Remember the new UI scale.
    maUIScale = rUIScale;

    // The content of the position and size boxes is only updated when item changes are notified.
    // Request such notifications without changing the actual item values.
    GetBindings()->Invalidate(SID_ATTR_TRANSFORM_POS_X, true);
    GetBindings()->Invalidate(SID_ATTR_TRANSFORM_POS_Y, true);
    GetBindings()->Invalidate(SID_ATTR_TRANSFORM_WIDTH, true);
    GetBindings()->Invalidate(SID_ATTR_TRANSFORM_HEIGHT, true);
}


} // end of namespace svx::sidebar

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