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

#ifdef SC_DLLIMPLEMENTATION
#undef SC_DLLIMPLEMENTATION
#endif

#include <com/sun/star/sheet/TableValidationVisibility.hpp>
#include <comphelper/string.hxx>
#include <svl/stritem.hxx>
#include <svl/eitem.hxx>
#include <svl/intitem.hxx>
#include <sfx2/app.hxx>
#include <o3tl/string_view.hxx>

#include <scresid.hxx>
#include <strings.hrc>

#include <stringutil.hxx>
#include <validat.hxx>
#include <validate.hxx>
#include <compiler.hxx>
#include <formula/opcode.hxx>

// cell range picker
#include <tabvwsh.hxx>
#include <sfx2/viewfrm.hxx>
#include <sfx2/childwin.hxx>
#include <reffact.hxx>
#include <comphelper/lok.hxx>

#include <vcl/tabs.hrc>

/*  Position indexes for "Allow" list box.
    They do not map directly to ScValidationMode and can safely be modified to
    change the order of the list box entries. */
#define SC_VALIDDLG_ALLOW_ANY       0
#define SC_VALIDDLG_ALLOW_WHOLE     1
#define SC_VALIDDLG_ALLOW_DECIMAL   2
#define SC_VALIDDLG_ALLOW_DATE      3
#define SC_VALIDDLG_ALLOW_TIME      4
#define SC_VALIDDLG_ALLOW_RANGE     5
#define SC_VALIDDLG_ALLOW_LIST      6
#define SC_VALIDDLG_ALLOW_TEXTLEN   7
#define SC_VALIDDLG_ALLOW_CUSTOM    8

/*  Position indexes for "Data" list box.
    They do not map directly to ScConditionMode and can safely be modified to
    change the order of the list box entries. */
#define SC_VALIDDLG_DATA_EQUAL        0
#define SC_VALIDDLG_DATA_LESS         1
#define SC_VALIDDLG_DATA_GREATER      2
#define SC_VALIDDLG_DATA_EQLESS       3
#define SC_VALIDDLG_DATA_EQGREATER    4
#define SC_VALIDDLG_DATA_NOTEQUAL     5
#define SC_VALIDDLG_DATA_VALIDRANGE   6
#define SC_VALIDDLG_DATA_INVALIDRANGE 7
#define SC_VALIDDLG_DATA_DIRECT       8

namespace ValidListType = css::sheet::TableValidationVisibility;

const WhichRangesContainer ScTPValidationValue::pValueRanges(svl::Items<
    FID_VALID_LISTTYPE, FID_VALID_LISTTYPE,
    FID_VALID_MODE, FID_VALID_ERRTEXT
>);

static bool isLOKMobilePhone()
{
    if (!comphelper::LibreOfficeKit::isActive())
        return false;
    SfxViewShell* pViewShell = SfxViewShell::Current();
    return pViewShell && pViewShell->isLOKMobilePhone();
}

ScValidationDlg::ScValidationDlg(weld::Window* pParent, const SfxItemSet* pArgSet,
    ScTabViewShell *pTabViewSh)
    : ScValidationDlgBase(pParent,
        u"modules/scalc/ui/validationdialog.ui"_ustr, u"ValidationDialog"_ustr, pArgSet, nullptr)
    , m_pTabVwSh(pTabViewSh)
    , m_sValuePageId(u"criteria"_ustr)
    , m_bOwnRefHdlr(false)
    , m_bRefInputting(false)
    , m_xHBox(m_xBuilder->weld_container(u"refinputbox"_ustr))
{
    AddTabPage(m_sValuePageId, TabResId(RID_TAB_VALIDITY_CRITERIA.aLabel),
               ScTPValidationValue::Create, RID_L + RID_TAB_VALIDITY_CRITERIA.sIconName);
    AddTabPage(u"inputhelp"_ustr, TabResId(RID_TAB_VALIDITY_INPUT.aLabel),
               ScTPValidationHelp::Create, RID_L + RID_TAB_VALIDITY_INPUT.sIconName);
    AddTabPage(u"erroralert"_ustr, TabResId(RID_TAB_VALIDITY_ERROR.aLabel),
               ScTPValidationError::Create, RID_L + RID_TAB_VALIDITY_ERROR.sIconName);

    if (isLOKMobilePhone())
    {
        m_xBuilder->weld_button(u"cancel"_ustr)->hide();
        m_xBuilder->weld_button(u"help"_ustr)->hide();
    }
}

void ScValidationDlg::EndDialog(int nResponse)
{
    // tdf#155708 - do not close, just hide validation window if we click in another sheet
    if (nResponse == nCloseResponseToJustHide && getDialog()->get_visible())
    {
        getDialog()->hide();
        return;
    }
    // tdf#137215 ensure original modality of true is restored before dialog loop ends
    if (m_bOwnRefHdlr)
        RemoveRefDlg(true);
    ScValidationDlgBase::EndDialog(nResponse);
}

void ScValidationDlg::ImplDestroy()
{
    if (m_bOwnRefHdlr)
        RemoveRefDlg(false);
}

ScValidationDlg::~ScValidationDlg()
{
    suppress_fun_call_w_exception(ImplDestroy());
}

void ScTPValidationValue::SetReferenceHdl( const ScRange&rRange , const ScDocument& rDoc )
{
    if ( rRange.aStart != rRange.aEnd )
        if ( ScValidationDlg *pValidationDlg = GetValidationDlg() )
            if( m_pRefEdit )
                pValidationDlg->RefInputStart( m_pRefEdit );

    if ( m_pRefEdit )
    {
        OUString aStr(rRange.Format(rDoc, ScRefFlags::RANGE_ABS_3D, rDoc.GetAddressConvention()));
        m_pRefEdit->SetRefString( aStr );
    }
}

void ScTPValidationValue:: SetActiveHdl()
{
    if ( m_pRefEdit ) m_pRefEdit->GrabFocus();

    if ( ScValidationDlg *pValidationDlg = GetValidationDlg() )
        if( m_pRefEdit )
        {
            pValidationDlg->RefInputDone();
        }
}

void ScTPValidationValue::RefInputStartPreHdl( formula::RefEdit* pEdit, const formula::RefButton* pButton )
{
    ScValidationDlg *pValidationDlg = GetValidationDlg();
    if (!pValidationDlg)
        return;

    weld::Container* pNewParent = pValidationDlg->get_refinput_shrink_parent();
    if (pEdit == m_pRefEdit && pNewParent != m_pRefEditParent)
    {
        m_xRefGrid->move(m_pRefEdit->GetWidget(), pNewParent);
        m_pRefEditParent = pNewParent;
    }

    if (pNewParent != m_pBtnRefParent)
    {
        // if Edit SetParent but button not, the tab order will be
        // incorrect, so move button anyway, and restore
        // parent later in order to restore the tab order. But
        // hide it if it's moved but unwanted.
        m_xRefGrid->move(m_xBtnRef->GetWidget(), pNewParent);
        m_xBtnRef->GetWidget()->set_visible(pButton == m_xBtnRef.get());
        m_pBtnRefParent = pNewParent;
    }

    pNewParent->show();
}

void ScTPValidationValue::RefInputDonePostHdl()
{
    if (ScValidationDlg *pValidationDlg = GetValidationDlg())
    {
        weld::Container* pOldParent = pValidationDlg->get_refinput_shrink_parent();

        if (m_pRefEdit && m_pRefEditParent != m_xRefGrid.get())
        {
            pOldParent->move(m_pRefEdit->GetWidget(), m_xRefGrid.get());
            m_pRefEditParent = m_xRefGrid.get();
        }

        if (m_pBtnRefParent != m_xRefGrid.get())
        {
            pOldParent->move(m_xBtnRef->GetWidget(), m_xRefGrid.get());
            m_xBtnRef->GetWidget()->show();
            m_pBtnRefParent = m_xRefGrid.get();
        }

        pOldParent->hide();
        ScViewData& rViewData = pValidationDlg->GetTabViewShell()->GetViewData();
        SCTAB nCurTab = rViewData.CurrentTabForData();
        SCTAB nRefTab = rViewData.GetRefTabNo();
        // If RefInput switched to a different sheet from the data sheet,
        // switch back: fdo#53920
        if ( nCurTab != nRefTab )
        {
             rViewData.GetViewShell()->SetTabNo( nRefTab );
        }
    }

    if (m_pRefEdit && !m_pRefEdit->GetWidget()->has_focus())
        m_pRefEdit->GrabFocus();
}

namespace {

/** Converts the passed ScValidationMode to the position in the list box. */
sal_uInt16 lclGetPosFromValMode( ScValidationMode eValMode )
{
    sal_uInt16 nLbPos = SC_VALIDDLG_ALLOW_ANY;
    switch( eValMode )
    {
        case SC_VALID_ANY:      nLbPos = SC_VALIDDLG_ALLOW_ANY;     break;
        case SC_VALID_WHOLE:    nLbPos = SC_VALIDDLG_ALLOW_WHOLE;   break;
        case SC_VALID_DECIMAL:  nLbPos = SC_VALIDDLG_ALLOW_DECIMAL; break;
        case SC_VALID_DATE:     nLbPos = SC_VALIDDLG_ALLOW_DATE;    break;
        case SC_VALID_TIME:     nLbPos = SC_VALIDDLG_ALLOW_TIME;    break;
        case SC_VALID_TEXTLEN:  nLbPos = SC_VALIDDLG_ALLOW_TEXTLEN; break;
        case SC_VALID_LIST:     nLbPos = SC_VALIDDLG_ALLOW_RANGE;   break;
        case SC_VALID_CUSTOM:   nLbPos = SC_VALIDDLG_ALLOW_CUSTOM;  break;
        default:    OSL_FAIL( "lclGetPosFromValMode - unknown validity mode" );
    }
    return nLbPos;
}

/** Converts the passed list box position to an ScValidationMode. */
ScValidationMode lclGetValModeFromPos( sal_uInt16 nLbPos )
{
    ScValidationMode eValMode = SC_VALID_ANY;
    switch( nLbPos )
    {
        case SC_VALIDDLG_ALLOW_ANY:     eValMode = SC_VALID_ANY;        break;
        case SC_VALIDDLG_ALLOW_WHOLE:   eValMode = SC_VALID_WHOLE;      break;
        case SC_VALIDDLG_ALLOW_DECIMAL: eValMode = SC_VALID_DECIMAL;    break;
        case SC_VALIDDLG_ALLOW_DATE:    eValMode = SC_VALID_DATE;       break;
        case SC_VALIDDLG_ALLOW_TIME:    eValMode = SC_VALID_TIME;       break;
        case SC_VALIDDLG_ALLOW_RANGE:   eValMode = SC_VALID_LIST;       break;
        case SC_VALIDDLG_ALLOW_LIST:    eValMode = SC_VALID_LIST;       break;
        case SC_VALIDDLG_ALLOW_TEXTLEN: eValMode = SC_VALID_TEXTLEN;    break;
        case SC_VALIDDLG_ALLOW_CUSTOM:  eValMode = SC_VALID_CUSTOM;     break;
        default:    OSL_FAIL( "lclGetValModeFromPos - invalid list box position" );
    }
    return eValMode;
}

/** Converts the passed ScConditionMode to the position in the list box. */
sal_uInt16 lclGetPosFromCondMode( ScConditionMode eCondMode )
{
    sal_uInt16 nLbPos = SC_VALIDDLG_DATA_EQUAL;
    switch( eCondMode )
    {
        case ScConditionMode::NONE:          // may occur in old XML files after Excel import
        case ScConditionMode::Equal:         nLbPos = SC_VALIDDLG_DATA_EQUAL;        break;
        case ScConditionMode::Less:          nLbPos = SC_VALIDDLG_DATA_LESS;         break;
        case ScConditionMode::Greater:       nLbPos = SC_VALIDDLG_DATA_GREATER;      break;
        case ScConditionMode::EqLess:        nLbPos = SC_VALIDDLG_DATA_EQLESS;       break;
        case ScConditionMode::EqGreater:     nLbPos = SC_VALIDDLG_DATA_EQGREATER;    break;
        case ScConditionMode::NotEqual:      nLbPos = SC_VALIDDLG_DATA_NOTEQUAL;     break;
        case ScConditionMode::Between:       nLbPos = SC_VALIDDLG_DATA_VALIDRANGE;      break;
        case ScConditionMode::NotBetween:    nLbPos = SC_VALIDDLG_DATA_INVALIDRANGE;   break;
        case ScConditionMode::Direct:        nLbPos = SC_VALIDDLG_DATA_DIRECT;         break;
        default:    OSL_FAIL( "lclGetPosFromCondMode - unknown condition mode" );
    }
    return nLbPos;
}

/** Converts the passed list box position to an ScConditionMode. */
ScConditionMode lclGetCondModeFromPos( sal_uInt16 nLbPos )
{
    ScConditionMode eCondMode = ScConditionMode::Equal;
    switch( nLbPos )
    {
        case SC_VALIDDLG_DATA_EQUAL:        eCondMode = ScConditionMode::Equal;      break;
        case SC_VALIDDLG_DATA_LESS:         eCondMode = ScConditionMode::Less;       break;
        case SC_VALIDDLG_DATA_GREATER:      eCondMode = ScConditionMode::Greater;    break;
        case SC_VALIDDLG_DATA_EQLESS:       eCondMode = ScConditionMode::EqLess;     break;
        case SC_VALIDDLG_DATA_EQGREATER:    eCondMode = ScConditionMode::EqGreater;  break;
        case SC_VALIDDLG_DATA_NOTEQUAL:     eCondMode = ScConditionMode::NotEqual;   break;
        case SC_VALIDDLG_DATA_VALIDRANGE:      eCondMode = ScConditionMode::Between;    break;
        case SC_VALIDDLG_DATA_INVALIDRANGE:   eCondMode = ScConditionMode::NotBetween; break;
        case SC_VALIDDLG_DATA_DIRECT:         eCondMode = ScConditionMode::Direct;   break;
        default:    OSL_FAIL( "lclGetCondModeFromPos - invalid list box position" );
    }
    return eCondMode;
}

/** Converts line feed separated string to a formula with strings separated by semicolons.
    @descr  Keeps all empty strings.
    Example: abc\ndef\n\nghi -> "abc";"def";"";"ghi".
    @param rFmlaStr  (out-param) The converted formula string. */
void lclGetFormulaFromStringList( OUString& rFmlaStr, std::u16string_view rStringList, sal_Unicode cFmlaSep )
{
    rFmlaStr.clear();
    if (!rStringList.empty())
    {
        sal_Int32 nIdx {0};
        do
        {
            OUString aToken {o3tl::getToken(rStringList, 0, '\n', nIdx )};
            ScGlobal::AddQuotes( aToken, '"' );
            rFmlaStr = ScGlobal::addToken(rFmlaStr, aToken, cFmlaSep);
        }
        while (nIdx>0);
    }
    if( rFmlaStr.isEmpty() )
        rFmlaStr = "\"\"";
}

/** Converts formula with strings separated by semicolons to line feed separated string.
    @descr  Keeps all empty strings. Ignores all empty tokens (multiple semicolons).
    Example: "abc";;;"def";"";"ghi" -> abc\ndef\n\nghi.
    @param rStringList  (out-param) The converted line feed separated string list.
    @return  true = Conversion successful. */
bool lclGetStringListFromFormula( OUString& rStringList, const OUString& rFmlaStr, sal_Unicode cFmlaSep )
{
    static constexpr OUStringLiteral aQuotes( u"\"\"" );

    rStringList.clear();
    bool bIsStringList = !rFmlaStr.isEmpty();
    bool bTokenAdded = false;

    for ( sal_Int32 nStringIx = 0; bIsStringList && nStringIx>=0; )
    {
        OUString aToken( ScStringUtil::GetQuotedToken(rFmlaStr, 0, aQuotes, cFmlaSep, nStringIx ) );
        aToken = comphelper::string::strip(aToken, ' ');
        if( !aToken.isEmpty() )      // ignore empty tokens, i.e. "a";;"b"
        {
            bIsStringList = ScGlobal::IsQuoted( aToken, '"' );
            if( bIsStringList )
            {
                ScGlobal::EraseQuotes( aToken, '"' );
                rStringList = ScGlobal::addToken(rStringList, aToken, '\n', 1, bTokenAdded);
                bTokenAdded = true;
            }
        }
    }

    return bIsStringList;
}

} // namespace

ScTPValidationValue::ScTPValidationValue(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rArgSet)
    : SfxTabPage(pPage, pController, u"modules/scalc/ui/validationcriteriapage.ui"_ustr,
                 u"ValidationCriteriaPage"_ustr, &rArgSet)
    , maStrMin(ScResId(SCSTR_VALID_MINIMUM))
    , maStrMax(ScResId(SCSTR_VALID_MAXIMUM))
    , maStrValue(ScResId(SCSTR_VALID_VALUE))
    , maStrFormula(ScResId(SCSTR_VALID_FORMULA))
    , maStrRange(ScResId(SCSTR_VALID_RANGE))
    , maStrList(ScResId(SCSTR_VALID_LIST))
    , m_pRefEdit(nullptr)
    , m_xLbAllow(m_xBuilder->weld_combo_box(u"allow"_ustr))
    , m_xCbAllow(m_xBuilder->weld_check_button(u"allowempty"_ustr))
    , m_xCbCaseSens(m_xBuilder->weld_check_button(u"casesens"_ustr))
    , m_xCbShow(m_xBuilder->weld_check_button(u"showlist"_ustr))
    , m_xCbSort(m_xBuilder->weld_check_button(u"sortascend"_ustr))
    , m_xFtValue(m_xBuilder->weld_label(u"valueft"_ustr))
    , m_xLbValue(m_xBuilder->weld_combo_box(u"data"_ustr))
    , m_xFtMin(m_xBuilder->weld_label(u"minft"_ustr))
    , m_xMinGrid(m_xBuilder->weld_widget(u"mingrid"_ustr))
    , m_xEdMin(new formula::RefEdit(m_xBuilder->weld_entry(u"min"_ustr)))
    , m_xEdList(m_xBuilder->weld_text_view(u"minlist"_ustr))
    , m_xFtMax(m_xBuilder->weld_label(u"maxft"_ustr))
    , m_xEdMax(new formula::RefEdit(m_xBuilder->weld_entry(u"max"_ustr)))
    , m_xFtHint(m_xBuilder->weld_label(u"hintft"_ustr))
    , m_xBtnRef(new formula::RefButton(m_xBuilder->weld_button(u"validref"_ustr)))
    , m_xRefGrid(m_xBuilder->weld_grid(u"refgrid"_ustr))
    , m_pRefEditParent(m_xRefGrid.get())
    , m_pBtnRefParent(m_xRefGrid.get())
{
    m_xEdMin->SetReferences(nullptr, m_xFtMin.get());

    Size aSize(m_xEdList->get_approximate_digit_width() * 40,
               m_xEdList->get_height_rows(10));
    m_xEdList->set_size_request(aSize.Width(), aSize.Height());
    m_xEdMax->SetReferences(nullptr, m_xFtMax.get());

    m_xBtnRef->SetClickHdl(LINK(this, ScTPValidationValue, ClickHdl));

    //lock in the max size initial config
    aSize = m_xContainer->get_preferred_size();
    m_xContainer->set_size_request(aSize.Width(), aSize.Height());

    Init();

    // list separator in formulas
    OUString aListSep = ::ScCompiler::GetNativeSymbol( ocSep );
    OSL_ENSURE( aListSep.getLength() == 1, "ScTPValidationValue::ScTPValidationValue - list separator error" );
    mcFmlaSep = aListSep.getLength() ? aListSep[0] : ';';
    m_xBtnRef->GetWidget()->hide(); // cell range picker
}

ScTPValidationValue::~ScTPValidationValue()
{
    m_xEdMin.reset();
    m_xEdMax.reset();
    m_xBtnRef.reset();
}

void ScTPValidationValue::Init()
{
    m_xLbAllow->connect_changed( LINK( this, ScTPValidationValue, SelectHdl ) );
    m_xLbValue->connect_changed( LINK( this, ScTPValidationValue, SelectHdl ) );
    m_xCbShow->connect_toggled( LINK( this, ScTPValidationValue, CheckHdl ) );

    // cell range picker
    m_xEdMin->SetGetFocusHdl( LINK( this, ScTPValidationValue, EditSetFocusHdl ) );
    m_xEdMin->SetLoseFocusHdl( LINK( this, ScTPValidationValue, KillEditFocusHdl ) );
    m_xEdMax->SetGetFocusHdl( LINK( this, ScTPValidationValue, EditSetFocusHdl ) );
    m_xEdMax->SetLoseFocusHdl( LINK( this, ScTPValidationValue, KillEditFocusHdl ) );
    m_xBtnRef->SetLoseFocusHdl( LINK( this, ScTPValidationValue, KillButtonFocusHdl ) );

    m_xLbAllow->set_active( SC_VALIDDLG_ALLOW_ANY );
    m_xLbValue->set_active( SC_VALIDDLG_DATA_EQUAL );

    SelectHdl( *m_xLbAllow );
    CheckHdl( *m_xCbShow );
}

std::unique_ptr<SfxTabPage> ScTPValidationValue::Create(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet* rArgSet)
{
    return std::make_unique<ScTPValidationValue>(pPage, pController, *rArgSet);
}

void ScTPValidationValue::Reset( const SfxItemSet* rArgSet )
{
    sal_uInt16 nLbPos = SC_VALIDDLG_ALLOW_ANY;
    if( const SfxUInt16Item* pItem = rArgSet->GetItemIfSet( FID_VALID_MODE ) )
        nLbPos = lclGetPosFromValMode( static_cast< ScValidationMode >( pItem->GetValue() ) );
    m_xLbAllow->set_active( nLbPos );

    nLbPos = SC_VALIDDLG_DATA_EQUAL;
    if( const SfxUInt16Item* pItem = rArgSet->GetItemIfSet( FID_VALID_CONDMODE ) )
        nLbPos = lclGetPosFromCondMode( static_cast< ScConditionMode >( pItem->GetValue() ) );
    m_xLbValue->set_active( nLbPos );

    // *** check boxes ***
    bool bCheck = true;
    if( const SfxBoolItem* pItem = rArgSet->GetItemIfSet( FID_VALID_BLANK ) )
        bCheck = pItem->GetValue();
    m_xCbAllow->set_active( bCheck );

    bool bCaseSensetive = false;
    if (const SfxBoolItem* pItem = rArgSet->GetItemIfSet( FID_VALID_CASESENS ) )
        bCaseSensetive = pItem->GetValue();
    m_xCbCaseSens->set_active( bCaseSensetive );

    sal_Int32 nListType = ValidListType::UNSORTED;
    if( const SfxInt16Item* pItem = rArgSet->GetItemIfSet( FID_VALID_LISTTYPE ) )
        nListType = pItem->GetValue();
    m_xCbShow->set_active( nListType != ValidListType::INVISIBLE );
    m_xCbSort->set_active( nListType == ValidListType::SORTEDASCENDING );

    // *** formulas ***
    OUString aFmlaStr;
    if ( const SfxStringItem* pItem = rArgSet->GetItemIfSet( FID_VALID_VALUE1 ) )
        aFmlaStr = pItem->GetValue();
    SetFirstFormula( aFmlaStr );

    aFmlaStr.clear();
    if ( const SfxStringItem* pItem = rArgSet->GetItemIfSet( FID_VALID_VALUE2 ) )
        aFmlaStr = pItem->GetValue();
    SetSecondFormula( aFmlaStr );

    SelectHdl( *m_xLbAllow );
    CheckHdl( *m_xCbShow );
}

bool ScTPValidationValue::FillItemSet( SfxItemSet* rArgSet )
{
    sal_Int16 nListType = m_xCbShow->get_active() ?
        (m_xCbSort->get_active() ? ValidListType::SORTEDASCENDING : ValidListType::UNSORTED) :
        ValidListType::INVISIBLE;

    const sal_Int32 nLbPos = m_xLbAllow->get_active();
    bool bCustom = (nLbPos == SC_VALIDDLG_ALLOW_CUSTOM);
    ScConditionMode eCondMode = bCustom ?
            ScConditionMode::Direct : lclGetCondModeFromPos( m_xLbValue->get_active() );

    rArgSet->Put( SfxUInt16Item( FID_VALID_MODE, sal::static_int_cast<sal_uInt16>(
                    lclGetValModeFromPos( nLbPos ) ) ) );
    rArgSet->Put( SfxUInt16Item( FID_VALID_CONDMODE, sal::static_int_cast<sal_uInt16>( eCondMode ) ) );
    rArgSet->Put( SfxStringItem( FID_VALID_VALUE1, GetFirstFormula() ) );
    rArgSet->Put( SfxStringItem( FID_VALID_VALUE2, GetSecondFormula() ) );
    rArgSet->Put( SfxBoolItem( FID_VALID_BLANK, m_xCbAllow->get_active() ) );
    rArgSet->Put( SfxBoolItem( FID_VALID_CASESENS, m_xCbCaseSens->get_active() ) );
    rArgSet->Put( SfxInt16Item( FID_VALID_LISTTYPE, nListType ) );
    return true;
}

OUString ScTPValidationValue::GetFirstFormula() const
{
    OUString aFmlaStr;
    if( m_xLbAllow->get_active() == SC_VALIDDLG_ALLOW_LIST )
        lclGetFormulaFromStringList( aFmlaStr, m_xEdList->get_text(), mcFmlaSep );
    else
        aFmlaStr = m_xEdMin->GetText();
    return aFmlaStr;
}

OUString ScTPValidationValue::GetSecondFormula() const
{
    return m_xEdMax->GetText();
}

void ScTPValidationValue::SetFirstFormula( const OUString& rFmlaStr )
{
    // try if formula is a string list, validation mode must already be set
    OUString aStringList;
    if( (m_xLbAllow->get_active() == SC_VALIDDLG_ALLOW_RANGE) &&
        lclGetStringListFromFormula( aStringList, rFmlaStr, mcFmlaSep ) )
    {
        m_xEdList->set_text( aStringList );
        m_xEdMin->SetText( OUString() );
        // change validation mode to string list
        m_xLbAllow->set_active( SC_VALIDDLG_ALLOW_LIST );
    }
    else
    {
        m_xEdMin->SetText( rFmlaStr );
        m_xEdList->set_text( OUString() );
    }
}

void ScTPValidationValue::SetSecondFormula( const OUString& rFmlaStr )
{
    m_xEdMax->SetText( rFmlaStr );
}

ScValidationDlg * ScTPValidationValue::GetValidationDlg()
{
    return dynamic_cast<ScValidationDlg*>(GetDialogController());
}

void ScTPValidationValue::SetupRefDlg()
{
    ScValidationDlg *pValidationDlg = GetValidationDlg();
    if( !pValidationDlg )
        return;

    if( !pValidationDlg->SetupRefDlg() )
        return;

    pValidationDlg->SetHandler( this );
    pValidationDlg->SetSetRefHdl( static_cast<ScRefHandlerHelper::PFUNCSETREFHDLTYPE>( &ScTPValidationValue::SetReferenceHdl ) );
    pValidationDlg->SetSetActHdl( static_cast<ScRefHandlerHelper::PCOMMONHDLTYPE>( &ScTPValidationValue::SetActiveHdl ) );
    pValidationDlg->SetRefInputStartPreHdl( static_cast<ScRefHandlerHelper::PINPUTSTARTDLTYPE>( &ScTPValidationValue::RefInputStartPreHdl ) );
    pValidationDlg->SetRefInputDonePostHdl( static_cast<ScRefHandlerHelper::PCOMMONHDLTYPE>( &ScTPValidationValue::RefInputDonePostHdl ) );

    weld::Label* pLabel = nullptr;

    if (m_xEdMax->GetWidget()->get_visible())
    {
        m_pRefEdit = m_xEdMax.get();
        pLabel = m_xFtMax.get();
    }
    else if (m_xEdMin->GetWidget()->get_visible())
    {
        m_pRefEdit = m_xEdMin.get();
        pLabel = m_xFtMin.get();
    }

    if (m_pRefEdit && !m_pRefEdit->GetWidget()->has_focus())
        m_pRefEdit->GrabFocus();

    if( m_pRefEdit )
        m_pRefEdit->SetReferences( pValidationDlg, pLabel );

    m_xBtnRef->SetReferences( pValidationDlg, m_pRefEdit );
}

void ScTPValidationValue::RemoveRefDlg(bool bRestoreModal)
{
    ScValidationDlg *pValidationDlg = GetValidationDlg();
    if( !pValidationDlg )
        return;

    if( !pValidationDlg->RemoveRefDlg(bRestoreModal) )
        return;

    pValidationDlg->SetHandler( nullptr );
    pValidationDlg->SetSetRefHdl( nullptr );
    pValidationDlg->SetSetActHdl( nullptr );
    pValidationDlg->SetRefInputStartPreHdl( nullptr );
    pValidationDlg->SetRefInputDonePostHdl( nullptr );

    if( m_pRefEdit )
        m_pRefEdit->SetReferences(nullptr);
    m_pRefEdit = nullptr;

    m_xBtnRef->SetReferences( nullptr, nullptr );
}

IMPL_LINK_NOARG(ScTPValidationValue, EditSetFocusHdl, formula::RefEdit&, void)
{
    const sal_Int32 nPos = m_xLbAllow->get_active();

    if ( nPos == SC_VALIDDLG_ALLOW_RANGE )
    {
        SetupRefDlg();
    }
}

IMPL_LINK( ScTPValidationValue, KillEditFocusHdl, formula::RefEdit&, rWnd, void )
{
    if (&rWnd != m_pRefEdit)
        return;
    if( ScValidationDlg *pValidationDlg = GetValidationDlg() )
    {
        if (pValidationDlg->IsChildFocus() && !pValidationDlg->IsRefInputting())
        {
            if( ( !m_pRefEdit || !m_pRefEdit->GetWidget()->has_focus()) && !m_xBtnRef->GetWidget()->has_focus() )
            {
                RemoveRefDlg(true);
            }
        }
    }
}

IMPL_LINK( ScTPValidationValue, KillButtonFocusHdl, formula::RefButton&, rWnd, void )
{
    if( &rWnd != m_xBtnRef.get())
        return;
    if( ScValidationDlg *pValidationDlg = GetValidationDlg() )
        if (pValidationDlg->IsChildFocus() && !pValidationDlg->IsRefInputting())
            if( ( !m_pRefEdit || !m_pRefEdit->GetWidget()->has_focus()) && !m_xBtnRef->GetWidget()->has_focus() )
            {
                RemoveRefDlg(true);
            }
}

IMPL_LINK_NOARG(ScTPValidationValue, SelectHdl, weld::ComboBox&, void)
{
    const sal_Int32 nLbPos = m_xLbAllow->get_active();
    bool bEnable = (nLbPos != SC_VALIDDLG_ALLOW_ANY);
    bool bRange = (nLbPos == SC_VALIDDLG_ALLOW_RANGE);
    bool bList = (nLbPos == SC_VALIDDLG_ALLOW_LIST);
    bool bCustom = (nLbPos == SC_VALIDDLG_ALLOW_CUSTOM);

    m_xCbAllow->set_sensitive( bEnable );   // Empty cell
    m_xCbCaseSens->set_sensitive( bEnable &&
        (bRange || bList || bCustom) ); // Case Sensitive
    m_xFtValue->set_sensitive( bEnable );
    m_xLbValue->set_sensitive( bEnable );
    m_xFtMin->set_sensitive( bEnable );
    m_xEdMin->GetWidget()->set_sensitive( bEnable );
    m_xEdList->set_sensitive( bEnable );
    m_xFtMax->set_sensitive( bEnable );
    m_xEdMax->GetWidget()->set_sensitive( bEnable );

    bool bShowMax = false;

    if( bRange )
        m_xFtMin->set_label( maStrRange );
    else if( bList )
        m_xFtMin->set_label( maStrList );
    else if( bCustom )
        m_xFtMin->set_label( maStrFormula );
    else
    {
        switch( m_xLbValue->get_active() )
        {
            case SC_VALIDDLG_DATA_EQUAL:
            case SC_VALIDDLG_DATA_NOTEQUAL:     m_xFtMin->set_label( maStrValue );  break;

            case SC_VALIDDLG_DATA_LESS:
            case SC_VALIDDLG_DATA_EQLESS:       m_xFtMin->set_label( maStrMax );    break;

            case SC_VALIDDLG_DATA_VALIDRANGE:
            case SC_VALIDDLG_DATA_INVALIDRANGE:   bShowMax = true;
                [[fallthrough]];
            case SC_VALIDDLG_DATA_GREATER:
            case SC_VALIDDLG_DATA_EQGREATER:    m_xFtMin->set_label( maStrMin );    break;

            default:
                OSL_FAIL( "ScTPValidationValue::SelectHdl - unknown condition mode" );
        }
    }

    m_xCbCaseSens->set_visible( bRange || bList || bCustom ); // Case Sensitive
    m_xCbShow->set_visible( bRange || bList );
    m_xCbSort->set_visible( bRange || bList );
    m_xFtValue->set_visible( !bRange && !bList && !bCustom);
    m_xLbValue->set_visible( !bRange && !bList && !bCustom );
    m_xEdMin->GetWidget()->set_visible( !bList );
    m_xEdList->set_visible( bList );
    m_xMinGrid->set_vexpand( bList );
    m_xFtMax->set_visible( bShowMax );
    m_xEdMax->GetWidget()->set_visible( bShowMax );
    m_xFtHint->set_visible( bRange );
    m_xBtnRef->GetWidget()->set_visible( bRange );  // cell range picker
}

IMPL_LINK_NOARG(ScTPValidationValue, CheckHdl, weld::Toggleable&, void)
{
    m_xCbSort->set_sensitive( m_xCbShow->get_active() );
}

// Input Help Page

ScTPValidationHelp::ScTPValidationHelp(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rArgSet)
    : SfxTabPage(pPage, pController, u"modules/scalc/ui/validationhelptabpage.ui"_ustr, u"ValidationHelpTabPage"_ustr, &rArgSet)
    , m_xTsbHelp(m_xBuilder->weld_check_button(u"tsbhelp"_ustr))
    , m_xEdtTitle(m_xBuilder->weld_entry(u"title"_ustr))
    , m_xEdInputHelp(m_xBuilder->weld_text_view(u"inputhelp_text"_ustr))
{
    m_xEdInputHelp->set_size_request(m_xEdInputHelp->get_approximate_digit_width() * 40, m_xEdInputHelp->get_height_rows(13));
}

ScTPValidationHelp::~ScTPValidationHelp()
{
}

std::unique_ptr<SfxTabPage> ScTPValidationHelp::Create(weld::Container* pPage, weld::DialogController* pController,
                                              const SfxItemSet* rArgSet)
{
    return std::make_unique<ScTPValidationHelp>(pPage, pController, *rArgSet);
}

void ScTPValidationHelp::Reset( const SfxItemSet* rArgSet )
{
    if ( const SfxBoolItem* pItem = rArgSet->GetItemIfSet( FID_VALID_SHOWHELP ) )
        m_xTsbHelp->set_state( pItem->GetValue() ? TRISTATE_TRUE : TRISTATE_FALSE );
    else
        m_xTsbHelp->set_state( TRISTATE_FALSE );

    if ( const SfxStringItem* pItem = rArgSet->GetItemIfSet( FID_VALID_HELPTITLE ) )
        m_xEdtTitle->set_text( pItem->GetValue() );
    else
        m_xEdtTitle->set_text( OUString() );

    if ( const SfxStringItem* pItem = rArgSet->GetItemIfSet( FID_VALID_HELPTEXT ) )
        m_xEdInputHelp->set_text( pItem->GetValue() );
    else
        m_xEdInputHelp->set_text( OUString() );
}

bool ScTPValidationHelp::FillItemSet( SfxItemSet* rArgSet )
{
    rArgSet->Put( SfxBoolItem( FID_VALID_SHOWHELP, m_xTsbHelp->get_state() == TRISTATE_TRUE ) );
    rArgSet->Put( SfxStringItem( FID_VALID_HELPTITLE, m_xEdtTitle->get_text() ) );
    rArgSet->Put( SfxStringItem( FID_VALID_HELPTEXT, m_xEdInputHelp->get_text() ) );

    return true;
}

// Error Alert Page

ScTPValidationError::ScTPValidationError(weld::Container* pPage, weld::DialogController* pController,
                                         const SfxItemSet& rArgSet)

    :   SfxTabPage      ( pPage, pController,
                          u"modules/scalc/ui/erroralerttabpage.ui"_ustr, u"ErrorAlertTabPage"_ustr,
                          &rArgSet )
    , m_xTsbShow(m_xBuilder->weld_check_button(u"tsbshow"_ustr))
    , m_xLbAction(m_xBuilder->weld_combo_box(u"actionCB"_ustr))
    , m_xBtnSearch(m_xBuilder->weld_button(u"browseBtn"_ustr))
    , m_xEdtTitle(m_xBuilder->weld_entry(u"erroralert_title"_ustr))
    , m_xFtError(m_xBuilder->weld_label(u"errormsg_label"_ustr))
    , m_xFtTitle(m_xBuilder->weld_label(u"title_label"_ustr))
    , m_xFtAction(m_xBuilder->weld_label(u"action_label"_ustr))
    , m_xEdError(m_xBuilder->weld_text_view(u"errorMsg"_ustr))
{
    m_xEdError->set_size_request(m_xEdError->get_approximate_digit_width() * 40, m_xEdError->get_height_rows(12));
    Init();
}

ScTPValidationError::~ScTPValidationError()
{
}

void ScTPValidationError::Init()
{
    m_xTsbShow->connect_toggled(LINK(this, ScTPValidationError, ToggleErrorMessage));
    m_xLbAction->connect_changed(LINK(this, ScTPValidationError, SelectActionHdl));
    m_xBtnSearch->connect_clicked(LINK( this, ScTPValidationError, ClickSearchHdl));

    m_xLbAction->set_active(0);

    SelectActionHdl(*m_xLbAction);
}

std::unique_ptr<SfxTabPage> ScTPValidationError::Create(weld::Container* pPage, weld::DialogController* pController,
                                               const SfxItemSet* rArgSet)
{
    return std::make_unique<ScTPValidationError>(pPage, pController, *rArgSet);
}

void ScTPValidationError::Reset( const SfxItemSet* rArgSet )
{
    if ( const SfxBoolItem* pItem = rArgSet->GetItemIfSet( FID_VALID_SHOWERR ) )
        m_xTsbShow->set_state( pItem->GetValue() ? TRISTATE_TRUE : TRISTATE_FALSE );
    else
        m_xTsbShow->set_state( TRISTATE_TRUE );   // check by default

    if ( const SfxUInt16Item* pItem = rArgSet->GetItemIfSet( FID_VALID_ERRSTYLE ) )
        m_xLbAction->set_active( pItem->GetValue() );
    else
        m_xLbAction->set_active( 0 );

    if ( const SfxStringItem* pItem = rArgSet->GetItemIfSet( FID_VALID_ERRTITLE ) )
        m_xEdtTitle->set_text( pItem->GetValue() );
    else
        m_xEdtTitle->set_text( OUString() );

    if ( const SfxStringItem* pItem = rArgSet->GetItemIfSet( FID_VALID_ERRTEXT ) )
        m_xEdError->set_text( pItem->GetValue() );
    else
        m_xEdError->set_text( OUString() );

    SelectActionHdl(*m_xLbAction);
}

bool ScTPValidationError::FillItemSet( SfxItemSet* rArgSet )
{
    rArgSet->Put( SfxBoolItem( FID_VALID_SHOWERR, m_xTsbShow->get_state() == TRISTATE_TRUE ) );
    rArgSet->Put( SfxUInt16Item( FID_VALID_ERRSTYLE, m_xLbAction->get_active() ) );
    rArgSet->Put( SfxStringItem( FID_VALID_ERRTITLE, m_xEdtTitle->get_text() ) );
    rArgSet->Put( SfxStringItem( FID_VALID_ERRTEXT, m_xEdError->get_text() ) );

    return true;
}

IMPL_LINK_NOARG(ScTPValidationError, ToggleErrorMessage, weld::Toggleable&, void)
{
    bool const bEnable(m_xTsbShow->get_active());
    m_xLbAction->set_sensitive(bEnable);
    m_xEdtTitle->set_sensitive(bEnable);
    m_xEdError->set_sensitive(bEnable);
    m_xFtError->set_sensitive(bEnable);
    m_xFtTitle->set_sensitive(bEnable);
    m_xFtAction->set_sensitive(bEnable);
}

IMPL_LINK_NOARG(ScTPValidationError, SelectActionHdl, weld::ComboBox&, void)
{
    ScValidErrorStyle eStyle = static_cast<ScValidErrorStyle>(m_xLbAction->get_active());
    bool bMacro = ( eStyle == SC_VALERR_MACRO );

    m_xBtnSearch->set_sensitive( bMacro );
    m_xFtError->set_sensitive( !bMacro );
    m_xEdError->set_sensitive( !bMacro );
}

IMPL_LINK_NOARG(ScTPValidationError, ClickSearchHdl, weld::Button&, void)
{
    // Use static SfxApplication method to bring up selector dialog for
    // choosing a script
    OUString aScriptURL = SfxApplication::ChooseScript(GetFrameWeld());

    if ( !aScriptURL.isEmpty() )
    {
        m_xEdtTitle->set_text( aScriptURL );
    }
}

bool ScValidationDlg::EnterRefStatus()
{
    ScTabViewShell *pTabViewShell = GetTabViewShell();

    if( !pTabViewShell ) return false;

    sal_uInt16 nId  = SLOTID;
    SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame();
    SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId );

    if (pWnd && pWnd->GetController().get() != this) pWnd = nullptr;

    ScModule::get()->SetRefDialog(nId, pWnd == nullptr);

    return true;
}

bool ScValidationDlg::LeaveRefStatus()
{
    ScTabViewShell *pTabViewShell = GetTabViewShell();

    if( !pTabViewShell ) return false;

    sal_uInt16 nId  = SLOTID;
    SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame();
    if (rViewFrm.GetChildWindow(nId))
    {
        DoClose( nId );
    }
    return true;
}

bool ScValidationDlg::SetupRefDlg()
{
    if ( m_bOwnRefHdlr ) return false;
    if( EnterRefMode() )
    {
        SetModal( false );
        m_bOwnRefHdlr = true;
        return EnterRefStatus();
    }

    return false;
}

bool ScValidationDlg::RemoveRefDlg( bool bRestoreModal /* = true */ )
{
    bool bVisLock = false;
    bool bFreeWindowLock = false;

    ScTabViewShell *pTabVwSh = GetTabViewShell();

    if( !pTabVwSh ) return false;

    if ( SfxChildWindow* pWnd = pTabVwSh->GetViewFrame().GetChildWindow( SID_VALIDITY_REFERENCE ) )
    {
        bVisLock = static_cast<ScValidityRefChildWin*>(pWnd)->LockVisible( true );
        bFreeWindowLock = static_cast<ScValidityRefChildWin*>(pWnd)->LockFreeWindow( true );
    }

    if ( !m_bOwnRefHdlr ) return false;
    if( LeaveRefStatus() && LeaveRefMode() )
    {
        m_bOwnRefHdlr = false;

        if( bRestoreModal )
        {
            SetModal( true );
        }
    }

    if ( SfxChildWindow* pWnd = pTabVwSh->GetViewFrame().GetChildWindow( SID_VALIDITY_REFERENCE ) )
    {
        static_cast<ScValidityRefChildWin*>(pWnd)->LockVisible( bVisLock );
        static_cast<ScValidityRefChildWin*>(pWnd)->LockFreeWindow( bFreeWindowLock );
    }

    return true;
}

IMPL_LINK_NOARG(ScTPValidationValue, ClickHdl, formula::RefButton&, void)
{
    SetupRefDlg();
}

bool ScValidationDlg::IsChildFocus() const
{
    return m_xDialog->has_toplevel_focus();
}

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