/* -*- 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: */

V764 Possible incorrect order of arguments passed to 'clamp' function. Inspect the second argument, which corresponds to the lower boundary parameter.

V764 Possible incorrect order of arguments passed to 'clamp' function. Inspect the second argument, which corresponds to the lower boundary parameter.

V764 Possible incorrect order of arguments passed to 'clamp' function. Inspect the second argument, which corresponds to the lower boundary parameter.

V764 Possible incorrect order of arguments passed to 'clamp' function. Inspect the second argument, which corresponds to the lower boundary parameter.