/* -*- 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 "standardcontrol.hxx"
#include "pcrcommon.hxx"

#include <com/sun/star/beans/IllegalTypeException.hpp>
#include <com/sun/star/lang/IllegalArgumentException.hpp>
#include <com/sun/star/util/DateTime.hpp>
#include <com/sun/star/util/Date.hpp>
#include <com/sun/star/util/Time.hpp>
#include <com/sun/star/util/Color.hpp>
#include <com/sun/star/util/MeasureUnit.hpp>
#include <com/sun/star/inspection/PropertyControlType.hpp>
#include <comphelper/string.hxx>
#include <o3tl/float_int_conversion.hxx>
#include <toolkit/helper/vclunohelper.hxx>


// ugly dependencies for the OColorControl
#include <svx/svxids.hrc>

#include <tools/datetime.hxx>
#include <unotools/datetime.hxx>
#include <o3tl/string_view.hxx>

#include <limits>
#include <memory>

namespace pcr
{
    using namespace ::com::sun::star;
    using namespace ::com::sun::star::uno;
    using namespace ::com::sun::star::awt;
    using namespace ::com::sun::star::lang;
    using namespace ::com::sun::star::util;
    using namespace ::com::sun::star::beans;
    using namespace ::com::sun::star::inspection;

    //= OTimeControl
    OTimeControl::OTimeControl(std::unique_ptr<weld::FormattedSpinButton> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly)
        : OTimeControl_Base(PropertyControlType::TimeField, std::move(xBuilder), std::move(xWidget), bReadOnly)
        , m_xFormatter(new weld::TimeFormatter(*getTypedControlWindow()))
    {
        m_xFormatter->SetExtFormat(ExtTimeFieldFormat::LongDuration);
    }

    void SAL_CALL OTimeControl::setValue( const Any& _rValue )
    {
        util::Time aUNOTime;
        if ( !( _rValue >>= aUNOTime ) )
        {
            getTypedControlWindow()->set_text(u""_ustr);
            m_xFormatter->SetTime(tools::Time(tools::Time::EMPTY));
        }
        else
        {
            m_xFormatter->SetTime(::tools::Time(aUNOTime));
        }
    }

    Any SAL_CALL OTimeControl::getValue()
    {
        Any aPropValue;
        if ( !getTypedControlWindow()->get_text().isEmpty() )
        {
            aPropValue <<= m_xFormatter->GetTime().GetUNOTime();
        }
        return aPropValue;
    }

    Type SAL_CALL OTimeControl::getValueType()
    {
        return ::cppu::UnoType<util::Time>::get();
    }

    //= ODateControl
    ODateControl::ODateControl(std::unique_ptr<weld::Container> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly)
        : ODateControl_Base(PropertyControlType::DateField, std::move(xBuilder), std::move(xWidget), bReadOnly)
        , m_xEntry(m_xBuilder->weld_entry(u"entry"_ustr))
        , m_xCalendarBox(std::make_unique<SvtCalendarBox>(m_xBuilder->weld_menu_button(u"button"_ustr), false))
    {
        m_xEntryFormatter.reset(new weld::DateFormatter(*m_xEntry));

        m_xEntryFormatter->SetStrictFormat(true);
        m_xEntryFormatter->SetMin(::Date(1, 1, 1600));
        m_xEntryFormatter->SetMax(::Date(1, 1, 9999));

        m_xEntryFormatter->SetExtDateFormat(ExtDateFieldFormat::SystemShortYYYY);
        m_xEntryFormatter->EnableEmptyField(true);

        m_xCalendarBox->connect_activated(LINK(this, ODateControl, ActivateHdl));

        m_xCalendarBox->get_button().connect_toggled(LINK(this, ODateControl, ToggleHdl));
    }

    void SAL_CALL ODateControl::disposing()
    {
        m_xEntryFormatter.reset();
        m_xEntry.reset();
        m_xCalendarBox.reset();
        ODateControl_Base::disposing();
    }

    void SAL_CALL ODateControl::setValue( const Any& _rValue )
    {
        util::Date aUNODate;
        if ( !( _rValue >>= aUNODate ) )
        {
            m_xEntry->set_text(OUString());
        }
        else
        {
            ::Date aDate( aUNODate.Day, aUNODate.Month, aUNODate.Year );
            m_xEntryFormatter->SetDate(aDate);
        }
    }

    IMPL_LINK_NOARG(ODateControl, ActivateHdl, SvtCalendarBox&, void)
    {
        m_xEntryFormatter->SetDate(m_xCalendarBox->get_date());
        setModified();
        m_xEntry->grab_focus();
    }

    IMPL_LINK(ODateControl, ToggleHdl, weld::Toggleable&, rToggle, void)
    {
        if (!rToggle.get_active())
            return;
        ::Date aDate = m_xEntryFormatter->GetDate();
        if (aDate.IsEmpty())
        {
            // with an empty date preselect today in the calendar
            aDate = ::Date(::Date::SYSTEM);
        }
        m_xCalendarBox->set_date(aDate);
    }

    Any SAL_CALL ODateControl::getValue()
    {
        Any aPropValue;
        if (!m_xEntry->get_text().isEmpty())
        {
            ::Date aDate(m_xEntryFormatter->GetDate());
            aPropValue <<= aDate.GetUNODate();
        }
        return aPropValue;
    }

    Type SAL_CALL ODateControl::getValueType()
    {
        return ::cppu::UnoType<util::Date>::get();
    }

    //= OEditControl
    OEditControl::OEditControl(std::unique_ptr<weld::Entry> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bPW, bool bReadOnly)
        : OEditControl_Base( bPW ? PropertyControlType::CharacterField : PropertyControlType::TextField, std::move(xBuilder), std::move(xWidget), bReadOnly )
    {
        m_bIsPassword = bPW;

        auto pWidget = getTypedControlWindow();
        pWidget->set_sensitive(true);
        pWidget->set_editable(!bReadOnly);

        if (m_bIsPassword)
           pWidget->set_max_length( 1 );
    }

    void SAL_CALL OEditControl::setValue( const Any& _rValue )
    {
        OUString sText;
        if ( m_bIsPassword )
        {
            sal_Int16 nValue = 0;
            _rValue >>= nValue;
            if ( nValue )
            {
                sText = OUString(static_cast<sal_Unicode>(nValue));
            }
        }
        else
            _rValue >>= sText;

        getTypedControlWindow()->set_text( sText );
    }

    Any SAL_CALL OEditControl::getValue()
    {
        Any aPropValue;

        OUString sText( getTypedControlWindow()->get_text() );
        if ( m_bIsPassword )
        {
            if ( !sText.isEmpty() )
                aPropValue <<= static_cast<sal_Int16>(sText[0]);
        }
        else
            aPropValue <<= sText;

        return aPropValue;
    }

    Type SAL_CALL OEditControl::getValueType()
    {
        return m_bIsPassword ? ::cppu::UnoType<sal_Int16>::get() : ::cppu::UnoType<OUString>::get();
    }

    void OEditControl::setModified()
    {
        OEditControl_Base::setModified();

        // for password controls, we fire a commit for every single change
        if ( m_bIsPassword )
            notifyModifiedValue();
    }

    static sal_Int64 ImplCalcLongValue( double nValue, sal_uInt16 nDigits )
    {
        double n = nValue;
        for ( sal_uInt16 d = 0; d < nDigits; ++d )
            n *= 10;

        return o3tl::saturating_cast<sal_Int64>(n);
    }

    static double ImplCalcDoubleValue(sal_Int64 nValue, sal_uInt16 nDigits )
    {
        double n = nValue;
        for ( sal_uInt16 d = 0; d < nDigits; ++d )
            n /= 10;
        return n;
    }

    ODateTimeControl::ODateTimeControl(std::unique_ptr<weld::Container> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly)
        : ODateTimeControl_Base(PropertyControlType::DateTimeField, std::move(xBuilder), std::move(xWidget), bReadOnly)
        , m_xDate(std::make_unique<SvtCalendarBox>(m_xBuilder->weld_menu_button(u"datefield"_ustr)))
        , m_xTime(m_xBuilder->weld_formatted_spin_button(u"timefield"_ustr))
        , m_xFormatter(new weld::TimeFormatter(*m_xTime))
    {
        m_xFormatter->SetExtFormat(ExtTimeFieldFormat::LongDuration);
    }

    void SAL_CALL ODateTimeControl::setValue( const Any& _rValue )
    {
        if ( !_rValue.hasValue() )
        {
            m_xDate->set_date(::Date(::Date::SYSTEM));
            m_xTime->set_text(u""_ustr);
            m_xFormatter->SetTime(tools::Time(tools::Time::EMPTY));
        }
        else
        {
            util::DateTime aUNODateTime;
            OSL_VERIFY( _rValue >>= aUNODateTime );

            ::DateTime aDateTime( ::DateTime::EMPTY );
            ::utl::typeConvert( aUNODateTime, aDateTime );

            m_xDate->set_date(aDateTime);
            m_xFormatter->SetTime(aDateTime);
        }
    }

    Any SAL_CALL ODateTimeControl::getValue()
    {
        Any aPropValue;
        if (!m_xTime->get_text().isEmpty())
        {
            ::DateTime aDateTime(m_xDate->get_date(), m_xFormatter->GetTime());

            util::DateTime aUNODateTime;
            ::utl::typeConvert( aDateTime, aUNODateTime );

            aPropValue <<= aUNODateTime;
        }
        return aPropValue;
    }

    Type SAL_CALL ODateTimeControl::getValueType()
    {
        return ::cppu::UnoType<util::DateTime>::get();
    }

    //= OHyperlinkControl
    OHyperlinkControl::OHyperlinkControl(std::unique_ptr<weld::Container> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly)
        : OHyperlinkControl_Base(PropertyControlType::HyperlinkField, std::move(xBuilder), std::move(xWidget), bReadOnly)
        , m_xEntry(m_xBuilder->weld_entry(u"entry"_ustr))
        , m_xButton(m_xBuilder->weld_button(u"button"_ustr))
        , m_aActionListeners(m_aMutex)
    {
        auto pWidget = getTypedControlWindow();
        pWidget->set_sensitive(true);
        m_xEntry->set_editable(!bReadOnly);

        m_xButton->connect_clicked(LINK(this, OHyperlinkControl, OnHyperlinkClicked));
    }

    Any SAL_CALL OHyperlinkControl::getValue()
    {
        OUString sText = m_xEntry->get_text();
        return Any( sText );
    }

    void SAL_CALL OHyperlinkControl::setValue( const Any& _value )
    {
        OUString sText;
        _value >>= sText;
        m_xEntry->set_text( sText );
    }

    Type SAL_CALL OHyperlinkControl::getValueType()
    {
        return ::cppu::UnoType<OUString>::get();
    }

    void SAL_CALL OHyperlinkControl::addActionListener( const Reference< XActionListener >& listener )
    {
        if ( listener.is() )
            m_aActionListeners.addInterface( listener );
    }

    void SAL_CALL OHyperlinkControl::removeActionListener( const Reference< XActionListener >& listener )
    {
        m_aActionListeners.removeInterface( listener );
    }

    void SAL_CALL OHyperlinkControl::disposing()
    {
        m_xButton.reset();
        m_xEntry.reset();
        OHyperlinkControl_Base::disposing();

        EventObject aEvent( *this );
        m_aActionListeners.disposeAndClear( aEvent );
    }

    IMPL_LINK_NOARG( OHyperlinkControl, OnHyperlinkClicked, weld::Button&, void )
    {
        ActionEvent aEvent( *this, u"clicked"_ustr );
        m_aActionListeners.forEach< XActionListener >(
            [&aEvent] (uno::Reference<awt::XActionListener> const& xListener)
                { return xListener->actionPerformed(aEvent); });
    }

    //= ONumericControl
    ONumericControl::ONumericControl(std::unique_ptr<weld::MetricSpinButton> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly)
        : ONumericControl_Base(PropertyControlType::NumericField, std::move(xBuilder), std::move(xWidget), bReadOnly)
        , m_eValueUnit( FieldUnit::NONE )
        , m_nFieldToUNOValueFactor( 1 )
    {
        Optional< double > value( getMaxValue() );
        value.Value = -value.Value;
        setMinValue( value );
    }

    ::sal_Int16 SAL_CALL ONumericControl::getDecimalDigits()
    {
        return getTypedControlWindow()->get_digits();
    }

    void SAL_CALL ONumericControl::setDecimalDigits( ::sal_Int16 decimaldigits )
    {
        weld::MetricSpinButton* pControlWindow = getTypedControlWindow();
        sal_Int64 min, max;
        pControlWindow->get_range(min, max, FieldUnit::NONE);
        pControlWindow->set_digits(decimaldigits);
        pControlWindow->set_range(min, max, FieldUnit::NONE);
    }

    Optional< double > SAL_CALL ONumericControl::getMinValue()
    {
        Optional< double > aReturn( true, 0 );

        sal_Int64 minValue = getTypedControlWindow()->get_min(FieldUnit::NONE);
        if ( minValue == std::numeric_limits<sal_Int64>::min() )
            aReturn.IsPresent = false;
        else
            aReturn.Value = static_cast<double>(minValue);

        return aReturn;
    }

    void SAL_CALL ONumericControl::setMinValue( const Optional< double >& _minvalue )
    {
        if ( !_minvalue.IsPresent )
            getTypedControlWindow()->set_min( std::numeric_limits<sal_Int64>::min(), FieldUnit::NONE );
        else
            getTypedControlWindow()->set_min( impl_apiValueToFieldValue_nothrow( _minvalue.Value ) , m_eValueUnit);
    }

    Optional< double > SAL_CALL ONumericControl::getMaxValue()
    {
        Optional< double > aReturn( true, 0 );

        sal_Int64 maxValue = getTypedControlWindow()->get_max(FieldUnit::NONE);
        if ( maxValue == std::numeric_limits<sal_Int64>::max() )
            aReturn.IsPresent = false;
        else
            aReturn.Value = static_cast<double>(maxValue);

        return aReturn;
    }

    void SAL_CALL ONumericControl::setMaxValue( const Optional< double >& _maxvalue )
    {
        if ( !_maxvalue.IsPresent )
            getTypedControlWindow()->set_max( std::numeric_limits<sal_Int64>::max(), FieldUnit::NONE );
        else
            getTypedControlWindow()->set_max( impl_apiValueToFieldValue_nothrow( _maxvalue.Value ), m_eValueUnit );
    }

    ::sal_Int16 SAL_CALL ONumericControl::getDisplayUnit()
    {
        return VCLUnoHelper::ConvertToMeasurementUnit( getTypedControlWindow()->get_unit(), 1 );
    }

    void SAL_CALL ONumericControl::setDisplayUnit( ::sal_Int16 _displayunit )
    {
        if ( ( _displayunit < MeasureUnit::MM_100TH ) || ( _displayunit > MeasureUnit::PERCENT ) )
            throw IllegalArgumentException();
        if  (   ( _displayunit == MeasureUnit::MM_100TH )
            ||  ( _displayunit == MeasureUnit::MM_10TH )
            ||  ( _displayunit == MeasureUnit::INCH_1000TH )
            ||  ( _displayunit == MeasureUnit::INCH_100TH )
            ||  ( _displayunit == MeasureUnit::INCH_10TH )
            ||  ( _displayunit == MeasureUnit::PERCENT )
            )
            throw IllegalArgumentException();

        sal_Int16 nDummyFactor = 1;
        FieldUnit eFieldUnit = VCLUnoHelper::ConvertToFieldUnit( _displayunit, nDummyFactor );
        if ( nDummyFactor != 1 )
            // everything which survived the checks above should result in a factor of 1, i.e.,
            // it should have a direct counterpart as FieldUnit
            throw RuntimeException();
        getTypedControlWindow()->set_unit(eFieldUnit);
    }

    ::sal_Int16 SAL_CALL ONumericControl::getValueUnit()
    {
        return VCLUnoHelper::ConvertToMeasurementUnit( m_eValueUnit, m_nFieldToUNOValueFactor );
    }

    void SAL_CALL ONumericControl::setValueUnit( ::sal_Int16 _valueunit )
    {
        if ( ( _valueunit < MeasureUnit::MM_100TH ) || ( _valueunit > MeasureUnit::PERCENT ) )
            throw IllegalArgumentException();
        m_eValueUnit = VCLUnoHelper::ConvertToFieldUnit( _valueunit, m_nFieldToUNOValueFactor );
    }

    void SAL_CALL ONumericControl::setValue( const Any& _rValue )
    {
        if ( !_rValue.hasValue() )
        {
            getTypedControlWindow()->set_text( u""_ustr );
        }
        else
        {
            double nValue( 0 );
            OSL_VERIFY( _rValue >>= nValue );
            auto nControlValue = impl_apiValueToFieldValue_nothrow( nValue );
            getTypedControlWindow()->set_value( nControlValue, m_eValueUnit );
        }
    }

    sal_Int64 ONumericControl::impl_apiValueToFieldValue_nothrow( double _nApiValue ) const
    {
        sal_Int64 nControlValue = ImplCalcLongValue( _nApiValue, getTypedControlWindow()->get_digits() );
        nControlValue /= m_nFieldToUNOValueFactor;
        return nControlValue;
    }

    double ONumericControl::impl_fieldValueToApiValue_nothrow(sal_Int64 nFieldValue) const
    {
        double nApiValue = ImplCalcDoubleValue( nFieldValue, getTypedControlWindow()->get_digits() );
        nApiValue *= m_nFieldToUNOValueFactor;
        return nApiValue;
    }

    Any SAL_CALL ONumericControl::getValue()
    {
        Any aPropValue;
        if ( !getTypedControlWindow()->get_text().isEmpty() )
        {
            double nValue = impl_fieldValueToApiValue_nothrow( getTypedControlWindow()->get_value( m_eValueUnit ) );
            if (nValue)
                aPropValue <<= nValue;
        }
        return aPropValue;
    }

    Type SAL_CALL ONumericControl::getValueType()
    {
        return ::cppu::UnoType<double>::get();
    }

    //= OColorControl
    OColorControl::OColorControl(std::unique_ptr<ColorListBox> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly)
        : OColorControl_Base(PropertyControlType::ColorListBox, std::move(xBuilder), std::move(xWidget), bReadOnly)
    {
        getTypedControlWindow()->SetSlotId(SID_FM_CTL_PROPERTIES);
    }

    void SAL_CALL OColorControl::setValue( const Any& _rValue )
    {
        css::util::Color nColor = sal_uInt32(COL_TRANSPARENT);
        if (_rValue.hasValue())
            _rValue >>= nColor;
        getTypedControlWindow()->SelectEntry(::Color(ColorTransparency, nColor));
    }

    Any SAL_CALL OColorControl::getValue()
    {
        Any aPropValue;
        ::Color aRgbCol = getTypedControlWindow()->GetSelectEntryColor();
        if (aRgbCol == COL_TRANSPARENT)
            return aPropValue;
        aPropValue <<= aRgbCol;
        return aPropValue;
    }

    Type SAL_CALL OColorControl::getValueType()
    {
        return ::cppu::UnoType<sal_Int32>::get();
    }

    void OColorControl::setModified()
    {
        OColorControl_Base::setModified();

        // fire a commit
        notifyModifiedValue();
    }

    //= OListboxControl
    OListboxControl::OListboxControl(std::unique_ptr<weld::ComboBox> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly)
        : OListboxControl_Base(PropertyControlType::ListBox, std::move(xBuilder), std::move(xWidget), bReadOnly)
    {
    }

    Any SAL_CALL OListboxControl::getValue()
    {
        OUString sControlValue( getTypedControlWindow()->get_active_text() );

        Any aPropValue;
        if ( !sControlValue.isEmpty() )
            aPropValue <<= sControlValue;
        return aPropValue;
    }

    Type SAL_CALL OListboxControl::getValueType()
    {
        return ::cppu::UnoType<OUString>::get();
    }

    void SAL_CALL OListboxControl::setValue( const Any& _rValue )
    {
        if ( !_rValue.hasValue() )
            getTypedControlWindow()->set_active(-1);
        else
        {
            OUString sSelection;
            _rValue >>= sSelection;

            if (getTypedControlWindow()->find_text(sSelection) == -1)
                getTypedControlWindow()->insert_text(0, sSelection);

            if (sSelection != getTypedControlWindow()->get_active_text())
                getTypedControlWindow()->set_active_text(sSelection);
        }
    }

    void SAL_CALL OListboxControl::clearList()
    {
        getTypedControlWindow()->clear();
    }

    void SAL_CALL OListboxControl::prependListEntry( const OUString& NewEntry )
    {
        getTypedControlWindow()->insert_text(0, NewEntry);
    }

    void SAL_CALL OListboxControl::appendListEntry( const OUString& NewEntry )
    {
        getTypedControlWindow()->append_text(NewEntry);
    }

    Sequence< OUString > SAL_CALL OListboxControl::getListEntries()
    {
        const sal_Int32 nCount = getTypedControlWindow()->get_count();
        Sequence< OUString > aRet(nCount);
        OUString* pIter = aRet.getArray();
        for (sal_Int32 i = 0; i < nCount ; ++i,++pIter)
            *pIter = getTypedControlWindow()->get_text(i);

        return aRet;
    }

    void OListboxControl::setModified()
    {
        OListboxControl_Base::setModified();

        // fire a commit
        notifyModifiedValue();
    }

    constexpr int nDefaultWidthDigits = 30;

    //= OComboboxControl
    OComboboxControl::OComboboxControl(std::unique_ptr<weld::ComboBox> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly)
        : OComboboxControl_Base(PropertyControlType::ComboBox, std::move(xBuilder), std::move(xWidget), bReadOnly)
    {
        weld::ComboBox* pComboBox = getTypedControlWindow();
        pComboBox->connect_changed( LINK( this, OComboboxControl, OnEntrySelected ) );
        pComboBox->set_entry_width_chars(nDefaultWidthDigits);
    }

    void SAL_CALL OComboboxControl::setValue( const Any& _rValue )
    {
        OUString sText;
        _rValue >>= sText;
        weld::ComboBox* pControlWindow = getTypedControlWindow();
        // tdf#138701 leave current cursor valid if the contents won't change
        if (pControlWindow->get_active_text() != sText)
            pControlWindow->set_entry_text(sText);
    }

    Any SAL_CALL OComboboxControl::getValue()
    {
        return Any( getTypedControlWindow()->get_active_text() );
    }

    Type SAL_CALL OComboboxControl::getValueType()
    {
        return ::cppu::UnoType<OUString>::get();
    }

    void SAL_CALL OComboboxControl::clearList()
    {
        getTypedControlWindow()->clear();
    }

    void SAL_CALL OComboboxControl::prependListEntry( const OUString& NewEntry )
    {
        getTypedControlWindow()->insert_text(0, NewEntry);
    }

    void SAL_CALL OComboboxControl::appendListEntry( const OUString& NewEntry )
    {
        getTypedControlWindow()->append_text( NewEntry );
    }

    Sequence< OUString > SAL_CALL OComboboxControl::getListEntries(  )
    {
        const sal_Int32 nCount = getTypedControlWindow()->get_count();
        Sequence< OUString > aRet(nCount);
        OUString* pIter = aRet.getArray();
        for (sal_Int32 i = 0; i < nCount ; ++i,++pIter)
            *pIter = getTypedControlWindow()->get_text(i);

        return aRet;
    }

    IMPL_LINK_NOARG( OComboboxControl, OnEntrySelected, weld::ComboBox&, void )
    {
        // fire a commit
        notifyModifiedValue();
    }

    namespace
    {

        StlSyntaxSequence< OUString > lcl_convertMultiLineToList( std::u16string_view _rCompsedTextWithLineBreaks )
        {
            sal_Int32 nLines = comphelper::string::getTokenCount(_rCompsedTextWithLineBreaks, '\n');
            StlSyntaxSequence< OUString > aStrings( nLines );
            if (nLines)
            {
                StlSyntaxSequence< OUString >::iterator stringItem = aStrings.begin();
                sal_Int32 nIdx {0};
                do
                {
                    *stringItem = o3tl::getToken(_rCompsedTextWithLineBreaks, 0, '\n', nIdx );
                    ++stringItem;
                }
                while (nIdx>0);
            }
            return aStrings;
        }

        OUString lcl_convertListToMultiLine( const StlSyntaxSequence< OUString >& _rStrings )
        {
            OUStringBuffer sMultiLineText;
            for (   StlSyntaxSequence< OUString >::const_iterator item = _rStrings.begin();
                    item != _rStrings.end();
                )
            {
                sMultiLineText.append(*item);
                if ( ++item != _rStrings.end() )
                    sMultiLineText.append("\n");
            }
            return sMultiLineText.makeStringAndClear();
        }


        OUString lcl_convertListToDisplayText( const StlSyntaxSequence< OUString >& _rStrings )
        {
            OUStringBuffer aComposed;
            for (   StlSyntaxSequence< OUString >::const_iterator strings = _rStrings.begin();
                    strings != _rStrings.end();
                    ++strings
                )
            {
                if ( strings != _rStrings.begin() )
                    aComposed.append( ';' );
                aComposed.append( "\"" + *strings + "\"" );
            }
            return aComposed.makeStringAndClear();
        }
    }

    void OMultilineEditControl::CheckEntryTextViewMisMatch()
    {
        // if there are newlines or something else which the entry cannot show, then make
        // just the multiline dropdown editable as the canonical source for text
        m_xEntry->set_sensitive(m_xEntry->get_text() == m_xTextView->get_text());
    }

    void OMultilineEditControl::SetStringListValue(const StlSyntaxSequence<OUString>& rStrings)
    {
        m_xEntry->set_text(lcl_convertListToDisplayText(rStrings));
        m_xTextView->set_text(lcl_convertListToMultiLine(rStrings));
        CheckEntryTextViewMisMatch();
    }

    StlSyntaxSequence<OUString> OMultilineEditControl::GetStringListValue() const
    {
        return lcl_convertMultiLineToList(m_xTextView->get_text());
    }

    void OMultilineEditControl::SetTextValue(const OUString& rText)
    {
        OSL_PRECOND( m_nOperationMode == eMultiLineText, "OMultilineEditControl::SetTextValue: illegal call!" );

        m_xTextView->set_text(rText);
        m_xEntry->set_text(rText);
        CheckEntryTextViewMisMatch();
    }

    OUString OMultilineEditControl::GetTextValue() const
    {
        OSL_PRECOND( m_nOperationMode == eMultiLineText, "OMultilineEditControl::GetTextValue: illegal call!" );
        return m_xTextView->get_text();
    }

    //= OMultilineEditControl
    OMultilineEditControl::OMultilineEditControl(std::unique_ptr<weld::Container> xWidget, std::unique_ptr<weld::Builder> xBuilder, MultiLineOperationMode eMode, bool bReadOnly)
        : OMultilineEditControl_Base(eMode == eMultiLineText ? PropertyControlType::MultiLineTextField : PropertyControlType::StringListField,
                                    std::move(xBuilder), std::move(xWidget), bReadOnly)
        , m_nOperationMode(eMode)
        , m_xEntry(m_xBuilder->weld_entry(u"entry"_ustr))
        , m_xButton(m_xBuilder->weld_menu_button(u"button"_ustr))
        , m_xPopover(m_xBuilder->weld_widget(u"popover"_ustr))
        , m_xTextView(m_xBuilder->weld_text_view(u"textview"_ustr))
        , m_xOk(m_xBuilder->weld_button(u"ok"_ustr))
    {
        m_xButton->set_popover(m_xPopover.get());
        m_xTextView->set_size_request(m_xTextView->get_approximate_digit_width() * nDefaultWidthDigits, m_xTextView->get_height_rows(8));
        m_xOk->connect_clicked(LINK(this, OMultilineEditControl, ButtonHandler));
    }

    IMPL_LINK_NOARG(OMultilineEditControl, TextViewModifiedHdl, weld::TextView&, void)
    {
        // tdf#139070 during editing update the entry to look like how it will
        // look once editing is finished so that the default behaviour of vcl
        // to strip newlines and the default behaviour of gtk to show a newline
        // symbol is suppressed
        OUString sText = m_xTextView->get_text();
        auto aSeq = lcl_convertMultiLineToList(sText);
        if (aSeq.getLength() > 1)
            m_xEntry->set_text(lcl_convertListToDisplayText(aSeq));
        else
            m_xEntry->set_text(sText);
        CheckEntryTextViewMisMatch();
        setModified();
    }

    void OMultilineEditControl::editChanged()
    {
        m_xTextView->set_text(m_xEntry->get_text());
        CheckEntryTextViewMisMatch();
        setModified();
    }

    IMPL_LINK_NOARG(OMultilineEditControl, ButtonHandler, weld::Button&, void)
    {
        m_xButton->set_active(false);
        notifyModifiedValue();
    }

    void SAL_CALL OMultilineEditControl::setValue( const Any& _rValue )
    {
        impl_checkDisposed_throw();

        switch (m_nOperationMode)
        {
            case eMultiLineText:
            {
                OUString sText;
                if ( !( _rValue >>= sText ) && _rValue.hasValue() )
                    throw IllegalTypeException();
                SetTextValue(sText);
                break;
            }
            case eStringList:
            {
                Sequence< OUString > aStringLines;
                if ( !( _rValue >>= aStringLines ) && _rValue.hasValue() )
                    throw IllegalTypeException();
                SetStringListValue( StlSyntaxSequence<OUString>(aStringLines) );
                break;
            }
        }
    }

    Any SAL_CALL OMultilineEditControl::getValue()
    {
        impl_checkDisposed_throw();

        Any aValue;
        switch (m_nOperationMode)
        {
            case eMultiLineText:
                aValue <<= GetTextValue();
                break;
            case eStringList:
                aValue <<= GetStringListValue();
                break;
        }
        return aValue;
    }

    Type SAL_CALL OMultilineEditControl::getValueType()
    {
        if (m_nOperationMode == eMultiLineText)
            return ::cppu::UnoType<OUString>::get();
        return cppu::UnoType<Sequence< OUString >>::get();
    }

} // namespace pcr

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