/**************************************************************
 *
 * 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
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_sc.hxx"

//----------------------------------------------------------------------------

#include "rangelst.hxx"
#include "scitems.hxx"
#include <sfx2/bindings.hxx>
#include <sfx2/imagemgr.hxx>
#include <svl/zforlist.hxx>
#include <vcl/msgbox.hxx>
#include <vcl/svapp.hxx>

#include "uiitems.hxx"
#include "reffact.hxx"
#include "docsh.hxx"
#include "docfunc.hxx"
#include "cell.hxx"
#include "rangeutl.hxx"
#include "scresid.hxx"
#include "convuno.hxx"
#include "unonames.hxx"
#include "solveroptions.hxx"
#include "solverutil.hxx"
#include "optsolver.hrc"

#include "optsolver.hxx"

#include <com/sun/star/sheet/Solver.hpp>
#include <com/sun/star/sheet/XSolverDescription.hpp>

using namespace com::sun::star;

//----------------------------------------------------------------------------

ScSolverProgressDialog::ScSolverProgressDialog( Window* pParent )
    : ModelessDialog( pParent, ScResId( RID_SCDLG_SOLVER_PROGRESS ) ),
    maFtProgress    ( this, ScResId( FT_PROGRESS ) ),
    maFtTime        ( this, ScResId( FT_TIMELIMIT ) ),
    maFlButtons     ( this, ScResId( FL_BUTTONS ) ),
    maBtnOk         ( this, ScResId( BTN_OK ) )
{
    maBtnOk.Enable(sal_False);
    FreeResource();
}

ScSolverProgressDialog::~ScSolverProgressDialog()
{
}

void ScSolverProgressDialog::HideTimeLimit()
{
    maFtTime.Hide();
}

void ScSolverProgressDialog::SetTimeLimit( sal_Int32 nSeconds )
{
    String aOld = maFtTime.GetText();
    String aNew = aOld.GetToken(0,'#');
    aNew += String::CreateFromInt32( nSeconds );
    aNew += aOld.GetToken(1,'#');
    maFtTime.SetText( aNew );
}

//----------------------------------------------------------------------------

ScSolverNoSolutionDialog::ScSolverNoSolutionDialog( Window* pParent, const String& rErrorText )
    : ModalDialog( pParent, ScResId( RID_SCDLG_SOLVER_NOSOLUTION ) ),
    maFtNoSolution  ( this, ScResId( FT_NOSOLUTION ) ),
    maFtErrorText   ( this, ScResId( FT_ERRORTEXT ) ),
    maFlButtons     ( this, ScResId( FL_BUTTONS ) ),
    maBtnOk         ( this, ScResId( BTN_OK ) )
{
    maFtErrorText.SetText( rErrorText );
    FreeResource();
}

ScSolverNoSolutionDialog::~ScSolverNoSolutionDialog()
{
}

//----------------------------------------------------------------------------

ScSolverSuccessDialog::ScSolverSuccessDialog( Window* pParent, const String& rSolution )
    : ModalDialog( pParent, ScResId( RID_SCDLG_SOLVER_SUCCESS ) ),
    maFtSuccess     ( this, ScResId( FT_SUCCESS ) ),
    maFtResult      ( this, ScResId( FT_RESULT ) ),
    maFtQuestion    ( this, ScResId( FT_QUESTION ) ),
    maFlButtons     ( this, ScResId( FL_BUTTONS ) ),
    maBtnOk         ( this, ScResId( BTN_OK ) ),
    maBtnCancel     ( this, ScResId( BTN_CANCEL ) )
{
    String aMessage = maFtResult.GetText();
    aMessage.Append( (sal_Char) ' ' );
    aMessage.Append( rSolution );
    maFtResult.SetText( aMessage );
    FreeResource();
}

ScSolverSuccessDialog::~ScSolverSuccessDialog()
{
}

//----------------------------------------------------------------------------

ScCursorRefEdit::ScCursorRefEdit( ScAnyRefDlg* pParent, const ResId& rResId ) :
    formula::RefEdit( pParent, pParent, rResId )
{
}

void ScCursorRefEdit::SetCursorLinks( const Link& rUp, const Link& rDown )
{
    maCursorUpLink = rUp;
    maCursorDownLink = rDown;
}

void ScCursorRefEdit::KeyInput( const KeyEvent& rKEvt )
{
    KeyCode aCode = rKEvt.GetKeyCode();
    bool bUp = (aCode.GetCode() == KEY_UP);
    bool bDown = (aCode.GetCode() == KEY_DOWN);
    if ( !aCode.IsShift() && !aCode.IsMod1() && !aCode.IsMod2() && ( bUp || bDown ) )
    {
        if ( bUp )
            maCursorUpLink.Call( this );
        else
            maCursorDownLink.Call( this );
    }
    else
        formula::RefEdit::KeyInput( rKEvt );
}

//----------------------------------------------------------------------------

ScOptSolverSave::ScOptSolverSave( const String& rObjective, sal_Bool bMax, sal_Bool bMin, sal_Bool bValue,
                             const String& rTarget, const String& rVariable,
                             const std::vector<ScOptConditionRow>& rConditions,
                             const String& rEngine,
                             const uno::Sequence<beans::PropertyValue>& rProperties ) :
    maObjective( rObjective ),
    mbMax( bMax ),
    mbMin( bMin ),
    mbValue( bValue ),
    maTarget( rTarget ),
    maVariable( rVariable ),
    maConditions( rConditions ),
    maEngine( rEngine ),
    maProperties( rProperties )
{
}

//============================================================================
//  class ScOptSolverDlg
//----------------------------------------------------------------------------

ScOptSolverDlg::ScOptSolverDlg( SfxBindings* pB, SfxChildWindow* pCW, Window* pParent,
                          ScDocShell* pDocSh, ScAddress aCursorPos )

    :   ScAnyRefDlg         ( pB, pCW, pParent, RID_SCDLG_OPTSOLVER ),
        //
        maFtObjectiveCell   ( this, ScResId( FT_OBJECTIVECELL ) ),
        maEdObjectiveCell   ( this, this, ScResId( ED_OBJECTIVECELL ) ),
        maRBObjectiveCell   ( this, ScResId( IB_OBJECTIVECELL ), &maEdObjectiveCell, this ),
        maFtDirection       ( this, ScResId( FT_DIRECTION ) ),
        maRbMax             ( this, ScResId( RB_MAX ) ),
        maRbMin             ( this, ScResId( RB_MIN ) ),
        maRbValue           ( this, ScResId( RB_VALUE ) ),
        maEdTargetValue     ( this, this, ScResId( ED_TARGET ) ),
        maRBTargetValue     ( this, ScResId( IB_TARGET ), &maEdTargetValue, this ),
        maFtVariableCells   ( this, ScResId( FT_VARIABLECELLS ) ),
        maEdVariableCells   ( this, this, ScResId( ED_VARIABLECELLS ) ),
        maRBVariableCells   ( this, ScResId( IB_VARIABLECELLS ), &maEdVariableCells, this),
        maFlConditions      ( this, ScResId( FL_CONDITIONS ) ),
        maFtCellRef         ( this, ScResId( FT_CELLREF ) ),
        maEdLeft1           ( this, ScResId( ED_LEFT1 ) ),
        maRBLeft1           ( this, ScResId( IB_LEFT1 ), &maEdLeft1, this ),
        maFtOperator        ( this, ScResId( FT_OPERATOR ) ),
        maLbOp1             ( this, ScResId( LB_OP1 ) ),
        maFtConstraint      ( this, ScResId( FT_CONSTRAINT ) ),
        maEdRight1          ( this, ScResId( ED_RIGHT1 ) ),
        maRBRight1          ( this, ScResId( IB_RIGHT1 ), &maEdRight1, this ),
        maBtnDel1           ( this, ScResId( IB_DELETE1 ) ),
        maEdLeft2           ( this, ScResId( ED_LEFT2 ) ),
        maRBLeft2           ( this, ScResId( IB_LEFT2 ), &maEdLeft2, this ),
        maLbOp2             ( this, ScResId( LB_OP2 ) ),
        maEdRight2          ( this, ScResId( ED_RIGHT2 ) ),
        maRBRight2          ( this, ScResId( IB_RIGHT2 ), &maEdRight2, this ),
        maBtnDel2           ( this, ScResId( IB_DELETE2 ) ),
        maEdLeft3           ( this, ScResId( ED_LEFT3 ) ),
        maRBLeft3           ( this, ScResId( IB_LEFT3 ), &maEdLeft3, this ),
        maLbOp3             ( this, ScResId( LB_OP3 ) ),
        maEdRight3          ( this, ScResId( ED_RIGHT3 ) ),
        maRBRight3          ( this, ScResId( IB_RIGHT3 ), &maEdRight3, this ),
        maBtnDel3           ( this, ScResId( IB_DELETE3 ) ),
        maEdLeft4           ( this, ScResId( ED_LEFT4 ) ),
        maRBLeft4           ( this, ScResId( IB_LEFT4 ), &maEdLeft4, this ),
        maLbOp4             ( this, ScResId( LB_OP4 ) ),
        maEdRight4          ( this, ScResId( ED_RIGHT4 ) ),
        maRBRight4          ( this, ScResId( IB_RIGHT4 ), &maEdRight4, this ),
        maBtnDel4           ( this, ScResId( IB_DELETE4 ) ),
        maScrollBar         ( this, ScResId( SB_SCROLL ) ),
        maFlButtons         ( this, ScResId( FL_BUTTONS ) ),
        maBtnOpt            ( this, ScResId( BTN_OPTIONS ) ),
        maBtnHelp           ( this, ScResId( BTN_HELP ) ),
        maBtnCancel         ( this, ScResId( BTN_CLOSE ) ),
        maBtnSolve          ( this, ScResId( BTN_SOLVE ) ),
        maInputError        ( ScResId( STR_INVALIDINPUT ) ),
        maConditionError    ( ScResId( STR_INVALIDCONDITION ) ),
        //
        mpDocShell          ( pDocSh ),
        mpDoc               ( pDocSh->GetDocument() ),
        mnCurTab            ( aCursorPos.Tab() ),
        mpEdActive          ( NULL ),
        mbDlgLostFocus      ( false ),
        nScrollPos          ( 0 )
{
    mpLeftEdit[0]    = &maEdLeft1;
    mpLeftButton[0]  = &maRBLeft1;
    mpRightEdit[0]   = &maEdRight1;
    mpRightButton[0] = &maRBRight1;
    mpOperator[0]    = &maLbOp1;
    mpDelButton[0]   = &maBtnDel1;

    mpLeftEdit[1]    = &maEdLeft2;
    mpLeftButton[1]  = &maRBLeft2;
    mpRightEdit[1]   = &maEdRight2;
    mpRightButton[1] = &maRBRight2;
    mpOperator[1]    = &maLbOp2;
    mpDelButton[1]   = &maBtnDel2;

    mpLeftEdit[2]    = &maEdLeft3;
    mpLeftButton[2]  = &maRBLeft3;
    mpRightEdit[2]   = &maEdRight3;
    mpRightButton[2] = &maRBRight3;
    mpOperator[2]    = &maLbOp3;
    mpDelButton[2]   = &maBtnDel3;

    mpLeftEdit[3]    = &maEdLeft4;
    mpLeftButton[3]  = &maRBLeft4;
    mpRightEdit[3]   = &maEdRight4;
    mpRightButton[3] = &maRBRight4;
    mpOperator[3]    = &maLbOp4;
    mpDelButton[3]   = &maBtnDel4;

    maRbMax.SetAccessibleRelationMemberOf(&maFtDirection);
    maRbMin.SetAccessibleRelationMemberOf(&maFtDirection);
    maRbValue.SetAccessibleRelationMemberOf(&maFtDirection);
    maEdLeft2.SetAccessibleName(maFtCellRef.GetText());
    maLbOp2.SetAccessibleName(maFtOperator.GetText());
    maEdRight2.SetAccessibleName(maFtConstraint.GetText());
    maEdLeft3.SetAccessibleName(maFtCellRef.GetText());
    maLbOp3.SetAccessibleName(maFtOperator.GetText());
    maEdRight3.SetAccessibleName(maFtConstraint.GetText());
    maEdLeft4.SetAccessibleName(maFtCellRef.GetText());
    maLbOp4.SetAccessibleName(maFtOperator.GetText());
    maEdRight4.SetAccessibleName(maFtConstraint.GetText());

    Init( aCursorPos );
    FreeResource();
}

//----------------------------------------------------------------------------

ScOptSolverDlg::~ScOptSolverDlg()
{
}

//----------------------------------------------------------------------------

void ScOptSolverDlg::Init(const ScAddress& rCursorPos)
{
    // Get the "Delete Rows" commandimagelist images from sfx instead of
    // adding a second copy to sc (see ScTbxInsertCtrl::StateChanged)

    rtl::OUString aSlotURL( RTL_CONSTASCII_USTRINGPARAM( "slot:" ));
    aSlotURL += rtl::OUString::valueOf( sal_Int32( SID_DEL_ROWS ) );
    uno::Reference<frame::XFrame> xFrame = GetBindings().GetActiveFrame();
    Image aDelNm = ::GetImage( xFrame, aSlotURL, sal_False, sal_False );
    Image aDelHC = ::GetImage( xFrame, aSlotURL, sal_False, sal_True );     // high contrast

    for ( sal_uInt16 nRow = 0; nRow < EDIT_ROW_COUNT; ++nRow )
    {
        mpDelButton[nRow]->SetModeImage( aDelNm, BMP_COLOR_NORMAL );
        mpDelButton[nRow]->SetModeImage( aDelHC, BMP_COLOR_HIGHCONTRAST );
    }

    maBtnOpt.SetClickHdl( LINK( this, ScOptSolverDlg, BtnHdl ) );
    maBtnCancel.SetClickHdl( LINK( this, ScOptSolverDlg, BtnHdl ) );
    maBtnSolve.SetClickHdl( LINK( this, ScOptSolverDlg, BtnHdl ) );

    Link aLink = LINK( this, ScOptSolverDlg, GetFocusHdl );
    maEdObjectiveCell.SetGetFocusHdl( aLink );
    maRBObjectiveCell.SetGetFocusHdl( aLink );
    maEdTargetValue.SetGetFocusHdl( aLink );
    maRBTargetValue.SetGetFocusHdl( aLink );
    maEdVariableCells.SetGetFocusHdl( aLink );
    maRBVariableCells.SetGetFocusHdl( aLink );
    maRbValue.SetGetFocusHdl( aLink );
    for ( sal_uInt16 nRow = 0; nRow < EDIT_ROW_COUNT; ++nRow )
    {
        mpLeftEdit[nRow]->SetGetFocusHdl( aLink );
        mpLeftButton[nRow]->SetGetFocusHdl( aLink );
        mpRightEdit[nRow]->SetGetFocusHdl( aLink );
        mpRightButton[nRow]->SetGetFocusHdl( aLink );
        mpOperator[nRow]->SetGetFocusHdl( aLink );
    }

    aLink = LINK( this, ScOptSolverDlg, LoseFocusHdl );
    maEdObjectiveCell.SetLoseFocusHdl( aLink );
    maRBObjectiveCell.SetLoseFocusHdl( aLink );
    maEdTargetValue.  SetLoseFocusHdl( aLink );
    maRBTargetValue.  SetLoseFocusHdl( aLink );
    maEdVariableCells.SetLoseFocusHdl( aLink );
    maRBVariableCells.SetLoseFocusHdl( aLink );
    for ( sal_uInt16 nRow = 0; nRow < EDIT_ROW_COUNT; ++nRow )
    {
        mpLeftEdit[nRow]->SetLoseFocusHdl( aLink );
        mpLeftButton[nRow]->SetLoseFocusHdl( aLink );
        mpRightEdit[nRow]->SetLoseFocusHdl( aLink );
        mpRightButton[nRow]->SetLoseFocusHdl( aLink );
    }

    Link aCursorUp = LINK( this, ScOptSolverDlg, CursorUpHdl );
    Link aCursorDown = LINK( this, ScOptSolverDlg, CursorDownHdl );
    Link aCondModify = LINK( this, ScOptSolverDlg, CondModifyHdl );
    for ( sal_uInt16 nRow = 0; nRow < EDIT_ROW_COUNT; ++nRow )
    {
        mpLeftEdit[nRow]->SetCursorLinks( aCursorUp, aCursorDown );
        mpRightEdit[nRow]->SetCursorLinks( aCursorUp, aCursorDown );
        mpLeftEdit[nRow]->SetModifyHdl( aCondModify );
        mpRightEdit[nRow]->SetModifyHdl( aCondModify );
        mpDelButton[nRow]->SetClickHdl( LINK( this, ScOptSolverDlg, DelBtnHdl ) );
        mpOperator[nRow]->SetSelectHdl( LINK( this, ScOptSolverDlg, SelectHdl ) );
    }
    maEdTargetValue.SetModifyHdl( LINK( this, ScOptSolverDlg, TargetModifyHdl ) );

    maScrollBar.SetEndScrollHdl( LINK( this, ScOptSolverDlg, ScrollHdl ) );
    maScrollBar.SetScrollHdl( LINK( this, ScOptSolverDlg, ScrollHdl ) );

    maScrollBar.SetPageSize( EDIT_ROW_COUNT );
    maScrollBar.SetVisibleSize( EDIT_ROW_COUNT );
    maScrollBar.SetLineSize( 1 );
    // Range is set in ShowConditions

    // get available solver implementations
    //! sort by descriptions?
    ScSolverUtil::GetImplementations( maImplNames, maDescriptions );
    sal_Int32 nImplCount = maImplNames.getLength();

    const ScOptSolverSave* pOldData = mpDocShell->GetSolverSaveData();
    if ( pOldData )
    {
        maEdObjectiveCell.SetRefString( pOldData->GetObjective() );
        maRbMax.Check( pOldData->GetMax() );
        maRbMin.Check( pOldData->GetMin() );
        maRbValue.Check( pOldData->GetValue() );
        maEdTargetValue.SetRefString( pOldData->GetTarget() );
        maEdVariableCells.SetRefString( pOldData->GetVariable() );
        maConditions = pOldData->GetConditions();
        maEngine = pOldData->GetEngine();
        maProperties = pOldData->GetProperties();
    }
    else
    {
        maRbMax.Check();
        String aCursorStr;
        if ( !mpDoc->GetRangeAtBlock( ScRange(rCursorPos), &aCursorStr ) )
            rCursorPos.Format( aCursorStr, SCA_ABS, NULL, mpDoc->GetAddressConvention() );
        maEdObjectiveCell.SetRefString( aCursorStr );
        if ( nImplCount > 0 )
            maEngine = maImplNames[0];  // use first implementation
    }
    ShowConditions();

    maEdObjectiveCell.GrabFocus();
    mpEdActive = &maEdObjectiveCell;
}

//----------------------------------------------------------------------------

void ScOptSolverDlg::ReadConditions()
{
    for ( sal_uInt16 nRow = 0; nRow < EDIT_ROW_COUNT; ++nRow )
    {
        ScOptConditionRow aRowEntry;
        aRowEntry.aLeftStr = mpLeftEdit[nRow]->GetText();
        aRowEntry.aRightStr = mpRightEdit[nRow]->GetText();
        aRowEntry.nOperator = mpOperator[nRow]->GetSelectEntryPos();

        long nVecPos = nScrollPos + nRow;
        if ( nVecPos >= (long)maConditions.size() && !aRowEntry.IsDefault() )
            maConditions.resize( nVecPos + 1 );

        if ( nVecPos < (long)maConditions.size() )
            maConditions[nVecPos] = aRowEntry;

        // remove default entries at the end
        size_t nSize = maConditions.size();
        while ( nSize > 0 && maConditions[ nSize-1 ].IsDefault() )
            --nSize;
        maConditions.resize( nSize );
    }
}

void ScOptSolverDlg::ShowConditions()
{
    for ( sal_uInt16 nRow = 0; nRow < EDIT_ROW_COUNT; ++nRow )
    {
        ScOptConditionRow aRowEntry;

        long nVecPos = nScrollPos + nRow;
        if ( nVecPos < (long)maConditions.size() )
            aRowEntry = maConditions[nVecPos];

        mpLeftEdit[nRow]->SetRefString( aRowEntry.aLeftStr );
        mpRightEdit[nRow]->SetRefString( aRowEntry.aRightStr );
        mpOperator[nRow]->SelectEntryPos( aRowEntry.nOperator );
    }

    // allow to scroll one page behind the visible or stored rows
    long nVisible = nScrollPos + EDIT_ROW_COUNT;
    long nMax = std::max( nVisible, (long) maConditions.size() );
    maScrollBar.SetRange( Range( 0, nMax + EDIT_ROW_COUNT ) );
    maScrollBar.SetThumbPos( nScrollPos );

    EnableButtons();
}

void ScOptSolverDlg::EnableButtons()
{
    for ( sal_uInt16 nRow = 0; nRow < EDIT_ROW_COUNT; ++nRow )
    {
        long nVecPos = nScrollPos + nRow;
        mpDelButton[nRow]->Enable( nVecPos < (long)maConditions.size() );
    }
}

//----------------------------------------------------------------------------

sal_Bool ScOptSolverDlg::Close()
{
    return DoClose( ScOptSolverDlgWrapper::GetChildWindowId() );
}

//----------------------------------------------------------------------------

void ScOptSolverDlg::SetActive()
{
    if ( mbDlgLostFocus )
    {
        mbDlgLostFocus = false;
        if( mpEdActive )
            mpEdActive->GrabFocus();
    }
    else
    {
        GrabFocus();
    }
    RefInputDone();
}

//----------------------------------------------------------------------------

void ScOptSolverDlg::SetReference( const ScRange& rRef, ScDocument* pDocP )
{
    if( mpEdActive )
    {
        if ( rRef.aStart != rRef.aEnd )
            RefInputStart(mpEdActive);

        // "target"/"value": single cell
        bool bSingle = ( mpEdActive == &maEdObjectiveCell || mpEdActive == &maEdTargetValue );

        String aStr;
        ScAddress aAdr = rRef.aStart;
        ScRange aNewRef( rRef );
        if ( bSingle )
            aNewRef.aEnd = aAdr;

        String aName;
        if ( pDocP->GetRangeAtBlock( aNewRef, &aName ) )            // named range: show name
            aStr = aName;
        else                                                        // format cell/range reference
        {
            sal_uInt16 nFmt = ( aAdr.Tab() == mnCurTab ) ? SCA_ABS : SCA_ABS_3D;
            if ( bSingle )
                aAdr.Format( aStr, nFmt, pDocP, pDocP->GetAddressConvention() );
            else
                rRef.Format( aStr, nFmt | SCR_ABS, pDocP, pDocP->GetAddressConvention() );
        }

        // variable cells can be several ranges, so only the selection is replaced
        if ( mpEdActive == &maEdVariableCells )
        {
            String aVal = mpEdActive->GetText();
            Selection aSel = mpEdActive->GetSelection();
            aSel.Justify();
            aVal.Erase( (xub_StrLen)aSel.Min(), (xub_StrLen)aSel.Len() );
            aVal.Insert( aStr, (xub_StrLen)aSel.Min() );
            Selection aNewSel( aSel.Min(), aSel.Min()+aStr.Len() );
            mpEdActive->SetRefString( aVal );
            mpEdActive->SetSelection( aNewSel );
        }
        else
            mpEdActive->SetRefString( aStr );

        ReadConditions();
        EnableButtons();

        // select "Value of" if a ref is input into "target" edit
        if ( mpEdActive == &maEdTargetValue )
            maRbValue.Check();
    }
}

//----------------------------------------------------------------------------

sal_Bool ScOptSolverDlg::IsRefInputMode() const
{
    return mpEdActive != NULL;
}

//----------------------------------------------------------------------------
// Handler:

IMPL_LINK( ScOptSolverDlg, BtnHdl, PushButton*, pBtn )
{
    if ( pBtn == &maBtnSolve || pBtn == &maBtnCancel )
    {
        bool bSolve = ( pBtn == &maBtnSolve );

        SetDispatcherLock( sal_False );
        SwitchToDocument();

        bool bClose = true;
        if ( bSolve )
            bClose = CallSolver();

        if ( bClose )
        {
            // Close: write dialog settings to DocShell for subsequent calls
            ReadConditions();
            ScOptSolverSave aSave(
                maEdObjectiveCell.GetText(), maRbMax.IsChecked(), maRbMin.IsChecked(), maRbValue.IsChecked(),
                maEdTargetValue.GetText(), maEdVariableCells.GetText(), maConditions, maEngine, maProperties );
            mpDocShell->SetSolverSaveData( aSave );
            Close();
        }
        else
        {
            // no solution -> dialog is kept open
            SetDispatcherLock( sal_True );
        }
    }
    else if ( pBtn == &maBtnOpt )
    {
        //! move options dialog to UI lib?
        ScSolverOptionsDialog* pOptDlg =
            new ScSolverOptionsDialog( this, maImplNames, maDescriptions, maEngine, maProperties );
        if ( pOptDlg->Execute() == RET_OK )
        {
            maEngine = pOptDlg->GetEngine();
            maProperties = pOptDlg->GetProperties();
        }
        delete pOptDlg;
    }

    return 0;
}

//----------------------------------------------------------------------------

IMPL_LINK( ScOptSolverDlg, GetFocusHdl, Control*, pCtrl )
{
    Edit* pEdit = NULL;
    mpEdActive = NULL;

    if( pCtrl == &maEdObjectiveCell || pCtrl == &maRBObjectiveCell )
        pEdit = mpEdActive = &maEdObjectiveCell;
    else if( pCtrl == &maEdTargetValue || pCtrl == &maRBTargetValue )
        pEdit = mpEdActive = &maEdTargetValue;
    else if( pCtrl == &maEdVariableCells || pCtrl == &maRBVariableCells )
        pEdit = mpEdActive = &maEdVariableCells;
    for ( sal_uInt16 nRow = 0; nRow < EDIT_ROW_COUNT; ++nRow )
    {
        if( pCtrl == mpLeftEdit[nRow] || pCtrl == mpLeftButton[nRow] )
            pEdit = mpEdActive = mpLeftEdit[nRow];
        else if( pCtrl == mpRightEdit[nRow] || pCtrl == mpRightButton[nRow] )
            pEdit = mpEdActive = mpRightEdit[nRow];
        else if( pCtrl == mpOperator[nRow] )    // focus on "operator" list box
            mpEdActive = mpRightEdit[nRow];     // use right edit for ref input, but don't change selection
    }
    if( pCtrl == &maRbValue )                   // focus on "Value of" radio button
        mpEdActive = &maEdTargetValue;          // use value edit for ref input, but don't change selection

    if( pEdit )
        pEdit->SetSelection( Selection( 0, SELECTION_MAX ) );

    return 0;
}

//----------------------------------------------------------------------------

IMPL_LINK( ScOptSolverDlg, LoseFocusHdl, Control*, EMPTYARG )
{
    mbDlgLostFocus = !IsActive();
    return 0;
}

//----------------------------------------------------------------------------

IMPL_LINK( ScOptSolverDlg, DelBtnHdl, PushButton*, pBtn )
{
    for ( sal_uInt16 nRow = 0; nRow < EDIT_ROW_COUNT; ++nRow )
        if( pBtn == mpDelButton[nRow] )
        {
            sal_Bool bHadFocus = pBtn->HasFocus();

            ReadConditions();
            long nVecPos = nScrollPos + nRow;
            if ( nVecPos < (long)maConditions.size() )
            {
                maConditions.erase( maConditions.begin() + nVecPos );
                ShowConditions();

                if ( bHadFocus && !pBtn->IsEnabled() )
                {
                    // If the button is disabled, focus would normally move to the next control,
                    // (left edit of the next row). Move it to left edit of this row instead.

                    mpEdActive = mpLeftEdit[nRow];
                    mpEdActive->GrabFocus();
                }
            }
        }

    return 0;
}

//----------------------------------------------------------------------------

IMPL_LINK( ScOptSolverDlg, TargetModifyHdl, Edit*, EMPTYARG )
{
    // modify handler for the target edit:
    //  select "Value of" if something is input into the edit
    if ( maEdTargetValue.GetText().Len() )
        maRbValue.Check();
    return 0;
}

IMPL_LINK( ScOptSolverDlg, CondModifyHdl, Edit*, EMPTYARG )
{
    // modify handler for the condition edits, just to enable/disable "delete" buttons
    ReadConditions();
    EnableButtons();
    return 0;
}

IMPL_LINK( ScOptSolverDlg, SelectHdl, ListBox*, EMPTYARG )
{
    // select handler for operator list boxes, just to enable/disable "delete" buttons
    ReadConditions();
    EnableButtons();
    return 0;
}

IMPL_LINK( ScOptSolverDlg, ScrollHdl, ScrollBar*, EMPTYARG )
{
    ReadConditions();
    nScrollPos = maScrollBar.GetThumbPos();
    ShowConditions();
    if( mpEdActive )
        mpEdActive->SetSelection( Selection( 0, SELECTION_MAX ) );
    return 0;
}

IMPL_LINK( ScOptSolverDlg, CursorUpHdl, ScCursorRefEdit*, pEdit )
{
    if ( pEdit == mpLeftEdit[0] || pEdit == mpRightEdit[0] )
    {
        if ( nScrollPos > 0 )
        {
            ReadConditions();
            --nScrollPos;
            ShowConditions();
            if( mpEdActive )
                mpEdActive->SetSelection( Selection( 0, SELECTION_MAX ) );
        }
    }
    else
    {
        formula::RefEdit* pFocus = NULL;
        for ( sal_uInt16 nRow = 1; nRow < EDIT_ROW_COUNT; ++nRow )      // second row or below: move focus
        {
            if ( pEdit == mpLeftEdit[nRow] )
                pFocus = mpLeftEdit[nRow-1];
            else if ( pEdit == mpRightEdit[nRow] )
                pFocus = mpRightEdit[nRow-1];
        }
        if (pFocus)
        {
            mpEdActive = pFocus;
            pFocus->GrabFocus();
        }
    }

    return 0;
}

IMPL_LINK( ScOptSolverDlg, CursorDownHdl, ScCursorRefEdit*, pEdit )
{
    if ( pEdit == mpLeftEdit[EDIT_ROW_COUNT-1] || pEdit == mpRightEdit[EDIT_ROW_COUNT-1] )
    {
        //! limit scroll position?
        ReadConditions();
        ++nScrollPos;
        ShowConditions();
        if( mpEdActive )
            mpEdActive->SetSelection( Selection( 0, SELECTION_MAX ) );
    }
    else
    {
        formula::RefEdit* pFocus = NULL;
        for ( sal_uInt16 nRow = 0; nRow+1 < EDIT_ROW_COUNT; ++nRow )      // before last row: move focus
        {
            if ( pEdit == mpLeftEdit[nRow] )
                pFocus = mpLeftEdit[nRow+1];
            else if ( pEdit == mpRightEdit[nRow] )
                pFocus = mpRightEdit[nRow+1];
        }
        if (pFocus)
        {
            mpEdActive = pFocus;
            pFocus->GrabFocus();
        }
    }

    return 0;
}

//----------------------------------------------------------------------------

void ScOptSolverDlg::ShowError( bool bCondition, formula::RefEdit* pFocus )
{
    String aMessage = bCondition ? maConditionError : maInputError;
    ErrorBox( this, WinBits( WB_OK | WB_DEF_OK ), aMessage ).Execute();
    if (pFocus)
    {
        mpEdActive = pFocus;
        pFocus->GrabFocus();
    }
}

//----------------------------------------------------------------------------

bool ScOptSolverDlg::ParseRef( ScRange& rRange, const String& rInput, bool bAllowRange )
{
    ScRangeUtil aRangeUtil;
    ScAddress::Details aDetails(mpDoc->GetAddressConvention(), 0, 0);
    sal_uInt16 nFlags = rRange.ParseAny( rInput, mpDoc, aDetails );
    if ( nFlags & SCA_VALID )
    {
        if ( (nFlags & SCA_TAB_3D) == 0 )
            rRange.aStart.SetTab( mnCurTab );
        if ( (nFlags & SCA_TAB2_3D) == 0 )
            rRange.aEnd.SetTab( rRange.aStart.Tab() );
        return ( bAllowRange || rRange.aStart == rRange.aEnd );
    }
    else if ( aRangeUtil.MakeRangeFromName( rInput, mpDoc, mnCurTab, rRange, RUTL_NAMES, aDetails ) )
        return ( bAllowRange || rRange.aStart == rRange.aEnd );

    return false;   // not recognized
}

bool ScOptSolverDlg::FindTimeout( sal_Int32& rTimeout )
{
    bool bFound = false;

    if ( !maProperties.getLength() )
        maProperties = ScSolverUtil::GetDefaults( maEngine );   // get property defaults from component

    sal_Int32 nPropCount = maProperties.getLength();
    for (sal_Int32 nProp=0; nProp<nPropCount && !bFound; ++nProp)
    {
        const beans::PropertyValue& rValue = maProperties[nProp];
        if ( rValue.Name.equalsAscii( SC_UNONAME_TIMEOUT ) )
            bFound = ( rValue.Value >>= rTimeout );
    }
    return bFound;
}

bool ScOptSolverDlg::CallSolver()       // return true -> close dialog after calling
{
    // show progress dialog

    ScSolverProgressDialog aProgress( this );
    sal_Int32 nTimeout = 0;
    if ( FindTimeout( nTimeout ) )
        aProgress.SetTimeLimit( nTimeout );
    else
        aProgress.HideTimeLimit();
    aProgress.Show();
    aProgress.Update();
    aProgress.Sync();
    // try to make sure the progress dialog is painted before continuing
    Application::Reschedule(true);

    // collect solver parameters

    ReadConditions();

    uno::Reference<sheet::XSpreadsheetDocument> xDocument( mpDocShell->GetModel(), uno::UNO_QUERY );

    ScRange aObjRange;
    if ( !ParseRef( aObjRange, maEdObjectiveCell.GetText(), false ) )
    {
        ShowError( false, &maEdObjectiveCell );
        return false;
    }
    table::CellAddress aObjective( aObjRange.aStart.Tab(), aObjRange.aStart.Col(), aObjRange.aStart.Row() );

    // "changing cells" can be several ranges
    ScRangeList aVarRanges;
    if ( !ParseWithNames( aVarRanges, maEdVariableCells.GetText(), mpDoc ) )
    {
        ShowError( false, &maEdVariableCells );
        return false;
    }
    uno::Sequence<table::CellAddress> aVariables;
    sal_Int32 nVarPos = 0;
       sal_uLong nRangeCount = aVarRanges.Count();
    for (sal_uLong nRangePos=0; nRangePos<nRangeCount; ++nRangePos)
    {
        ScRange aRange(*aVarRanges.GetObject(nRangePos));
        aRange.Justify();
        SCTAB nTab = aRange.aStart.Tab();

        // resolve into single cells

        sal_Int32 nAdd = ( aRange.aEnd.Col() - aRange.aStart.Col() + 1 ) *
                         ( aRange.aEnd.Row() - aRange.aStart.Row() + 1 );
        aVariables.realloc( nVarPos + nAdd );

        for (SCROW nRow = aRange.aStart.Row(); nRow <= aRange.aEnd.Row(); ++nRow)
            for (SCCOL nCol = aRange.aStart.Col(); nCol <= aRange.aEnd.Col(); ++nCol)
                aVariables[nVarPos++] = table::CellAddress( nTab, nCol, nRow );
    }

    uno::Sequence<sheet::SolverConstraint> aConstraints;
    sal_Int32 nConstrPos = 0;
    for ( std::vector<ScOptConditionRow>::const_iterator aConstrIter = maConditions.begin();
          aConstrIter != maConditions.end(); ++aConstrIter )
    {
        if ( aConstrIter->aLeftStr.Len() )
        {
            sheet::SolverConstraint aConstraint;
            // order of list box entries must match enum values
            aConstraint.Operator = static_cast<sheet::SolverConstraintOperator>(aConstrIter->nOperator);

            ScRange aLeftRange;
            if ( !ParseRef( aLeftRange, aConstrIter->aLeftStr, true ) )
            {
                ShowError( true, NULL );
                return false;
            }

            bool bIsRange = false;
            ScRange aRightRange;
            if ( ParseRef( aRightRange, aConstrIter->aRightStr, true ) )
            {
                if ( aRightRange.aStart == aRightRange.aEnd )
                    aConstraint.Right <<= table::CellAddress( aRightRange.aStart.Tab(),
                                                              aRightRange.aStart.Col(), aRightRange.aStart.Row() );
                else if ( aRightRange.aEnd.Col()-aRightRange.aStart.Col() == aLeftRange.aEnd.Col()-aLeftRange.aStart.Col() &&
                          aRightRange.aEnd.Row()-aRightRange.aStart.Row() == aLeftRange.aEnd.Row()-aLeftRange.aStart.Row() )
                    bIsRange = true;    // same size as "left" range, resolve into single cells
                else
                {
                    ShowError( true, NULL );
                    return false;
                }
            }
            else
            {
                sal_uInt32 nFormat = 0;     //! explicit language?
                double fValue = 0.0;
                if ( mpDoc->GetFormatTable()->IsNumberFormat( aConstrIter->aRightStr, nFormat, fValue ) )
                    aConstraint.Right <<= fValue;
                else if ( aConstraint.Operator != sheet::SolverConstraintOperator_INTEGER &&
                          aConstraint.Operator != sheet::SolverConstraintOperator_BINARY )
                {
                    ShowError( true, NULL );
                    return false;
                }
            }

            // resolve into single cells

            sal_Int32 nAdd = ( aLeftRange.aEnd.Col() - aLeftRange.aStart.Col() + 1 ) *
                             ( aLeftRange.aEnd.Row() - aLeftRange.aStart.Row() + 1 );
            aConstraints.realloc( nConstrPos + nAdd );

            for (SCROW nRow = aLeftRange.aStart.Row(); nRow <= aLeftRange.aEnd.Row(); ++nRow)
                for (SCCOL nCol = aLeftRange.aStart.Col(); nCol <= aLeftRange.aEnd.Col(); ++nCol)
                {
                    aConstraint.Left = table::CellAddress( aLeftRange.aStart.Tab(), nCol, nRow );
                    if ( bIsRange )
                        aConstraint.Right <<= table::CellAddress( aRightRange.aStart.Tab(),
                            aRightRange.aStart.Col() + ( nCol - aLeftRange.aStart.Col() ),
                            aRightRange.aStart.Row() + ( nRow - aLeftRange.aStart.Row() ) );

                    aConstraints[nConstrPos++] = aConstraint;
                }
        }
    }

    sal_Bool bMaximize = maRbMax.IsChecked();
    if ( maRbValue.IsChecked() )
    {
        // handle "value of" with an additional constraint (and then minimize)

        sheet::SolverConstraint aConstraint;
        aConstraint.Left     = aObjective;
        aConstraint.Operator = sheet::SolverConstraintOperator_EQUAL;

        String aValStr = maEdTargetValue.GetText();
        ScRange aRightRange;
        if ( ParseRef( aRightRange, aValStr, false ) )
            aConstraint.Right <<= table::CellAddress( aRightRange.aStart.Tab(),
                                                      aRightRange.aStart.Col(), aRightRange.aStart.Row() );
        else
        {
            sal_uInt32 nFormat = 0;     //! explicit language?
            double fValue = 0.0;
            if ( mpDoc->GetFormatTable()->IsNumberFormat( aValStr, nFormat, fValue ) )
                aConstraint.Right <<= fValue;
            else
            {
                ShowError( false, &maEdTargetValue );
                return false;
            }
        }

        aConstraints.realloc( nConstrPos + 1 );
        aConstraints[nConstrPos++] = aConstraint;
    }

    // copy old document values

    sal_Int32 nVarCount = aVariables.getLength();
    uno::Sequence<double> aOldValues;
    aOldValues.realloc( nVarCount );
    for (nVarPos=0; nVarPos<nVarCount; ++nVarPos)
    {
        ScAddress aCellPos;
        ScUnoConversion::FillScAddress( aCellPos, aVariables[nVarPos] );
        aOldValues[nVarPos] = mpDoc->GetValue( aCellPos );
    }

    // create and initialize solver

    uno::Reference<sheet::XSolver> xSolver = ScSolverUtil::GetSolver( maEngine );
    DBG_ASSERT( xSolver.is(), "can't get solver component" );
    if ( !xSolver.is() )
        return false;

    xSolver->setDocument( xDocument );
    xSolver->setObjective( aObjective );
    xSolver->setVariables( aVariables );
    xSolver->setConstraints( aConstraints );
    xSolver->setMaximize( bMaximize );

    // set options
    uno::Reference<beans::XPropertySet> xOptProp(xSolver, uno::UNO_QUERY);
    if ( xOptProp.is() )
    {
        sal_Int32 nPropCount = maProperties.getLength();
        for (sal_Int32 nProp=0; nProp<nPropCount; ++nProp)
        {
            const beans::PropertyValue& rValue = maProperties[nProp];
            try
            {
                xOptProp->setPropertyValue( rValue.Name, rValue.Value );
            }
            catch ( uno::Exception & )
            {
                DBG_ERRORFILE("Exception in solver option property");
            }
        }
    }

    xSolver->solve();
    sal_Bool bSuccess = xSolver->getSuccess();

    aProgress.Hide();
    bool bClose = false;
    bool bRestore = true;   // restore old values unless a solution is accepted
    if ( bSuccess )
    {
        // put solution into document so it is visible when asking
        uno::Sequence<double> aSolution = xSolver->getSolution();
        if ( aSolution.getLength() == nVarCount )
        {
            mpDocShell->LockPaint();
            ScDocFunc aFunc(*mpDocShell);
            for (nVarPos=0; nVarPos<nVarCount; ++nVarPos)
            {
                ScAddress aCellPos;
                ScUnoConversion::FillScAddress( aCellPos, aVariables[nVarPos] );
                aFunc.PutCell( aCellPos, new ScValueCell( aSolution[nVarPos] ), sal_True );
            }
            mpDocShell->UnlockPaint();
        }
        //! else error?

        // take formatted result from document (result value from component is ignored)
        String aResultStr;
        mpDoc->GetString( (SCCOL)aObjective.Column, (SCROW)aObjective.Row, (SCTAB)aObjective.Sheet, aResultStr );
        ScSolverSuccessDialog aDialog( this, aResultStr );
        if ( aDialog.Execute() == RET_OK )
        {
            // keep results and close dialog
            bRestore = false;
            bClose = true;
        }
    }
    else
    {
        rtl::OUString aError;
        uno::Reference<sheet::XSolverDescription> xDesc( xSolver, uno::UNO_QUERY );
        if ( xDesc.is() )
            aError = xDesc->getStatusDescription();         // error description from component
        ScSolverNoSolutionDialog aDialog( this, aError );
        aDialog.Execute();
    }

    if ( bRestore )         // restore old values
    {
        mpDocShell->LockPaint();
        ScDocFunc aFunc(*mpDocShell);
        for (nVarPos=0; nVarPos<nVarCount; ++nVarPos)
        {
            ScAddress aCellPos;
            ScUnoConversion::FillScAddress( aCellPos, aVariables[nVarPos] );
            aFunc.PutCell( aCellPos, new ScValueCell( aOldValues[nVarPos] ), sal_True );
        }
        mpDocShell->UnlockPaint();
    }

    return bClose;
}