You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koffice/kspread/dialogs/kspread_dlg_goalseek.cc

487 lines
15 KiB

/* This file is part of the KDE project
Copyright (C) 2002-2003 Norbert Andres <nandres@web.de>
(C) 2002-2003 Philipp Mueller <philipp.mueller@gmx.de>
(C) 2002 Laurent Montel <montel@kde.org>
(C) 2002 John Dailey <dailey@vt.edu>
(C) 2002 Ariya Hidayat <ariya@kde.org>
(C) 2002 Werner Trobin <trobin@kde.org>
(C) 2002 Harri Porten <porten@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kspread_dlg_goalseek.h"
#include "kspread_canvas.h"
#include "kspread_cell.h"
#include "kspread_doc.h"
#include "kspread_map.h"
#include "selection.h"
#include "kspread_sheet.h"
#include "kspread_undo.h"
#include "kspread_util.h"
#include "kspread_view.h"
#include <kapplication.h>
#include <kdebug.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kstdguiitem.h>
#include <kpushbutton.h>
#include <qframe.h>
#include <qlabel.h>
#include <qlayout.h>
#include <qlineedit.h>
#include <qtooltip.h>
#include <qvariant.h>
#include <qwhatsthis.h>
#include <math.h>
using namespace KSpread;
GoalSeekDialog::GoalSeekDialog( View * parent, QPoint const & marker,
const char * name, bool, WFlags fl )
: KDialog( parent, name, false, fl ),
m_pView( parent ),
m_maxIter( 1000 ),
m_restored( true ),
m_focus(0),
m_anchor( m_pView->selectionInfo()->anchor() ),
m_marker( m_pView->selectionInfo()->marker() ),
m_selection( m_pView->selectionInfo()->selection() )
{
setWFlags( Qt::WDestructiveClose );
if ( !name )
setName( "GoalSeekDialog" );
resize( 458, 153 );
setCaption( i18n( "Goal Seek" ) );
setSizeGripEnabled( true );
GoalSeekDialogLayout = new QGridLayout( this, 1, 1, 11, 6, "GoalSeekDialogLayout");
m_startFrame = new QFrame( this, "m_startFrame" );
m_startFrame->setFrameShape( QFrame::StyledPanel );
m_startFrame->setFrameShadow( QFrame::Raised );
m_startFrameLayout = new QGridLayout( m_startFrame, 1, 1, 11, 6, "m_startFrameLayout");
QLabel * TextLabel4 = new QLabel( m_startFrame, "TextLabel4" );
TextLabel4->setText( i18n( "To value:" ) );
m_startFrameLayout->addWidget( TextLabel4, 1, 0 );
m_targetValueEdit = new QLineEdit( m_startFrame, "m_targetValueEdit" );
m_startFrameLayout->addWidget( m_targetValueEdit, 1, 1 );
m_targetEdit = new QLineEdit( m_startFrame, "m_targetEdit" );
m_startFrameLayout->addWidget( m_targetEdit, 0, 1 );
m_targetEdit->setText( Cell::name( marker.x(), marker.y() ) );
m_sourceEdit = new QLineEdit( m_startFrame, "m_sourceEdit" );
m_startFrameLayout->addWidget( m_sourceEdit, 2, 1 );
QLabel * TextLabel5 = new QLabel( m_startFrame, "TextLabel5" );
TextLabel5->setText( i18n( "By changing cell:" ) );
m_startFrameLayout->addWidget( TextLabel5, 2, 0 );
QLabel * TextLabel3 = new QLabel( m_startFrame, "TextLabel3" );
TextLabel3->setText( i18n( "Set cell:" ) );
m_startFrameLayout->addWidget( TextLabel3, 0, 0 );
GoalSeekDialogLayout->addWidget( m_startFrame, 0, 0 );
QVBoxLayout * Layout5 = new QVBoxLayout( 0, 0, 6, "Layout5");
m_buttonOk = new QPushButton( this, "m_buttonOk" );
m_buttonOk->setText( i18n( "&Start" ) );
m_buttonOk->setAccel( 276824143 );
m_buttonOk->setAutoDefault( TRUE );
m_buttonOk->setDefault( TRUE );
Layout5->addWidget( m_buttonOk );
m_buttonCancel = new KPushButton( KStdGuiItem::cancel(), this, "m_buttonCancel" );
m_buttonCancel->setAccel( 276824131 );
m_buttonCancel->setAutoDefault( TRUE );
Layout5->addWidget( m_buttonCancel );
QSpacerItem* spacer = new QSpacerItem( 20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding );
Layout5->addItem( spacer );
GoalSeekDialogLayout->addMultiCellLayout( Layout5, 0, 1, 1, 1 );
m_resultFrame = new QFrame( this, "m_resultFrame" );
m_resultFrame->setFrameShape( QFrame::StyledPanel );
m_resultFrame->setFrameShadow( QFrame::Raised );
m_resultFrame->setMinimumWidth( 350 );
m_resultFrameLayout = new QGridLayout( m_resultFrame, 1, 1, 11, 6, "m_resultFrameLayout");
m_currentValueLabel = new QLabel( m_resultFrame, "m_currentValueLabel" );
m_currentValueLabel->setText( i18n( "Current value:" ) );
m_resultFrameLayout->addWidget( m_currentValueLabel, 2, 0 );
m_newValueDesc = new QLabel( m_resultFrame, "m_newValueDesc" );
m_newValueDesc->setText( i18n( "New value:" ) );
m_resultFrameLayout->addWidget( m_newValueDesc, 1, 0 );
m_newValue = new QLabel( m_resultFrame, "m_newValue" );
m_newValue->setText( "m_targetValueEdit" );
m_resultFrameLayout->addWidget( m_newValue, 1, 1 );
m_currentValue = new QLabel( m_resultFrame, "m_currentValue" );
m_currentValue->setText( "m_currentValue" );
m_resultFrameLayout->addWidget( m_currentValue, 2, 1 );
m_resultText = new QLabel( m_resultFrame, "m_resultText" );
m_resultText->setText( "Goal seeking with cell <cell> found <a | no> solution:" );
m_resultText->setAlignment( int( QLabel::WordBreak | QLabel::AlignVCenter ) );
m_resultFrameLayout->addMultiCellWidget( m_resultText, 0, 0, 0, 1 );
// GoalSeekDialogLayout->addWidget( m_resultFrame, 1, 0 );
m_resultFrame->hide();
m_sheetName = m_pView->activeSheet()->sheetName();
// Allow the user to select cells on the spreadsheet.
m_pView->canvasWidget()->startChoose();
qApp->installEventFilter( this );
// signals and slots connections
connect( m_buttonOk, SIGNAL( clicked() ), this, SLOT( buttonOkClicked() ) );
connect( m_buttonCancel, SIGNAL( clicked() ), this, SLOT( buttonCancelClicked() ) );
connect( m_pView->choice(), SIGNAL(changed(const Region&)),
this, SLOT(slotSelectionChanged()));
// tab order
setTabOrder( m_targetEdit, m_targetValueEdit );
setTabOrder( m_targetValueEdit, m_sourceEdit );
setTabOrder( m_sourceEdit, m_buttonOk );
setTabOrder( m_buttonOk, m_buttonCancel );
}
GoalSeekDialog::~GoalSeekDialog()
{
kdDebug() << "~GoalSeekDialog" << endl;
if ( !m_restored )
{
m_pView->doc()->emitBeginOperation( false );
m_sourceCell->setValue(m_oldSource);
m_targetCell->setCalcDirtyFlag();
m_targetCell->calc();
m_pView->slotUpdateView( m_pView->activeSheet() );
}
}
bool GoalSeekDialog::eventFilter( QObject* obj, QEvent* ev )
{
if ( obj == m_targetValueEdit && ev->type() == QEvent::FocusIn )
m_focus = m_targetValueEdit;
else if ( obj == m_targetEdit && ev->type() == QEvent::FocusIn )
m_focus = m_targetEdit;
else if ( obj == m_sourceEdit && ev->type() == QEvent::FocusIn )
m_focus = m_sourceEdit;
else
return FALSE;
if ( m_focus )
m_pView->canvasWidget()->startChoose();
return FALSE;
}
void GoalSeekDialog::closeEvent ( QCloseEvent * e )
{
e->accept();
}
void GoalSeekDialog::slotSelectionChanged()
{
if ( !m_focus )
return;
if (m_pView->choice()->isValid())
{
QString area = m_pView->choice()->name();
m_focus->setText( area );
}
}
void GoalSeekDialog::buttonOkClicked()
{
Doc * pDoc = m_pView->doc();
pDoc->emitBeginOperation( false );
if (m_maxIter > 0)
{
Sheet * sheet = m_pView->activeSheet();
Point source( m_sourceEdit->text(), sheet->workbook(), sheet );
if (!source.isValid())
{
KMessageBox::error( this, i18n("Cell reference is invalid.") );
m_sourceEdit->selectAll();
m_sourceEdit->setFocus();
m_pView->slotUpdateView( m_pView->activeSheet() );
return;
}
Point target( m_targetEdit->text(), sheet->workbook(), sheet );
if (!target.isValid())
{
KMessageBox::error( this, i18n("Cell reference is invalid.") );
m_targetEdit->selectAll();
m_targetEdit->setFocus();
m_pView->slotUpdateView( m_pView->activeSheet() );
return;
}
bool ok = false;
double goal = m_pView->doc()->locale()->readNumber(m_targetValueEdit->text(), &ok );
if ( !ok )
{
KMessageBox::error( this, i18n("Target value is invalid.") );
m_targetValueEdit->selectAll();
m_targetValueEdit->setFocus();
m_pView->slotUpdateView( m_pView->activeSheet() );
return;
}
m_sourceCell = source.cell();
m_targetCell = target.cell();
if ( !m_sourceCell->value().isNumber() )
{
KMessageBox::error( this, i18n("Source cell must contain a numeric value.") );
m_sourceEdit->selectAll();
m_sourceEdit->setFocus();
m_pView->slotUpdateView( m_pView->activeSheet() );
return;
}
if ( !m_targetCell->isFormula() )
{
KMessageBox::error( this, i18n("Target cell must contain a formula.") );
m_targetEdit->selectAll();
m_targetEdit->setFocus();
m_pView->slotUpdateView( m_pView->activeSheet() );
return;
}
m_buttonOk->setText( i18n("&OK") );
m_buttonOk->setEnabled(false);
m_buttonCancel->setEnabled(false);
GoalSeekDialogLayout->addWidget( m_resultFrame, 0, 0 );
m_startFrame->hide();
m_resultFrame->show();
if ( m_startFrame->width() > 350 )
m_resultFrame->setMinimumWidth( m_startFrame->width() );
m_restored = false;
startCalc( m_sourceCell->value().asFloat(), goal );
m_pView->slotUpdateView( m_pView->activeSheet() );
return;
}
else
{
if ( !pDoc->undoLocked() )
{
UndoSetText * undo
= new UndoSetText( pDoc, m_pView->activeSheet(), QString::number(m_oldSource),
m_sourceCell->column(), m_sourceCell->row(),
m_sourceCell->formatType() );
pDoc->addCommand( undo );
}
m_restored = true;
}
chooseCleanup();
m_pView->slotUpdateView( m_pView->activeSheet() );
accept();
}
void GoalSeekDialog::buttonCancelClicked()
{
if ( !m_restored )
{
m_pView->doc()->emitBeginOperation( false );
m_sourceCell->setValue(m_oldSource);
m_targetCell->setCalcDirtyFlag();
m_targetCell->calc();
m_restored = true;
m_pView->slotUpdateView( m_pView->activeSheet() );
}
chooseCleanup();
reject();
}
void GoalSeekDialog::chooseCleanup()
{
m_pView->canvasWidget()->endChoose();
Sheet * sheet = 0;
// Switch back to the old sheet
if ( m_pView->activeSheet()->sheetName() != m_sheetName )
{
sheet = m_pView->doc()->map()->findSheet( m_sheetName );
if ( sheet )
m_pView->setActiveSheet( sheet );
}
else
sheet = m_pView->activeSheet();
// Revert the marker to its original position
m_pView->selectionInfo()->initialize(QRect(m_marker, m_anchor));//, sheet );
}
void GoalSeekDialog::startCalc(double _start, double _goal)
{
m_resultText->setText( i18n( "Starting..." ) );
m_newValueDesc->setText( i18n( "Iteration:" ) );
// lets be optimistic
bool ok = true;
// TODO: make this configurable
double eps = 0.0000001;
double startA = 0.0, startB;
double resultA, resultB;
// save old value
m_oldSource = m_sourceCell->value().asFloat();
resultA = _goal;
// initialize start value
startB = _start;
double x = startB + 0.5;
// while the result is not close enough to zero
// or while the max number of iterations is not reached...
while ( fabs( resultA ) > eps && ( m_maxIter >= 0 ) )
{
startA = startB;
startB = x;
m_sourceCell->setValue(startA);
// m_sourceCell->updateDepending();
m_sourceCell->setCalcDirtyFlag();
m_targetCell->calc( false );
resultA = m_targetCell->value().asFloat() - _goal;
// kdDebug() << "Target A: " << m_targetCell->value().asFloat() << ", " << m_targetCell->text() << " Calc: " << resultA << endl;
m_sourceCell->setValue(startB);
// m_sourceCell->updateDepending();
m_sourceCell->setCalcDirtyFlag();
m_targetCell->calc( false );
resultB = m_targetCell->value().asFloat() - _goal;
/*
kdDebug() << "Target B: " << m_targetCell->value().asFloat() << ", " << m_targetCell->text() << " Calc: " << resultB << endl;
kdDebug() << "Iteration: " << m_maxIter << ", StartA: " << startA
<< ", ResultA: " << resultA << " (eps: " << eps << "), StartB: "
<< startB << ", ResultB: " << resultB << endl;
*/
// find zero with secant method (rough implementation was provided by Franz-Xaver Meier):
// if the function returns the same for two different
// values we have something like a horizontal line
// => can't get zero.
if ( resultB == resultA )
{
// kdDebug() << " resultA == resultB" << endl;
if ( fabs( resultA ) < eps )
{
ok = true;
break;
}
ok = false;
break;
}
// Point of intersection of secant with x-axis
x = ( startA * resultB - startB * resultA ) / ( resultB - resultA );
if ( fabs(x) > 100000000 )
{
// kdDebug() << "fabs(x) > 100000000: " << x << endl;
ok = false;
break;
}
// kdDebug() << "X: " << x << ", fabs (resultA): " << fabs(resultA) << ", Real start: " << startA << ", Real result: " << resultA << ", Iteration: " << m_maxIter << endl;
--m_maxIter;
if ( m_maxIter % 20 == 0 )
m_newValue->setText( QString::number(m_maxIter) );
}
m_newValueDesc->setText( i18n( "New value:" ) );
if ( ok )
{
m_sourceCell->setValue( startA );
m_sourceCell->setCalcDirtyFlag();
m_sourceCell->sheet()->setRegionPaintDirty(m_sourceCell->cellRect());
// m_targetCell->setCalcDirtyFlag();
m_targetCell->calc( false );
m_resultText->setText( i18n( "Goal seeking with cell %1 found a solution:" ).arg( m_sourceEdit->text() ) );
m_newValue->setText( m_pView->doc()->locale()->formatNumber( startA ) );
m_currentValue->setText( m_pView->doc()->locale()->formatNumber( m_oldSource ) );
m_restored = false;
}
else
{
// restore the old value
m_sourceCell->setValue( m_oldSource );
m_targetCell->setCalcDirtyFlag();
m_sourceCell->sheet()->setRegionPaintDirty(m_sourceCell->cellRect());
m_targetCell->calc( false );
m_resultText->setText( i18n( "Goal seeking with cell %1 has found NO solution." ).arg( m_sourceEdit->text() ) );
m_newValue->setText( "" );
m_currentValue->setText( m_pView->doc()->locale()->formatNumber( m_oldSource ) );
m_restored = true;
}
m_buttonOk->setEnabled( true );
m_buttonCancel->setEnabled( true );
m_maxIter = 0;
}
#include "kspread_dlg_goalseek.moc"