diff options
Diffstat (limited to 'kspread/dialogs/kspread_dlg_goalseek.cpp')
-rw-r--r-- | kspread/dialogs/kspread_dlg_goalseek.cpp | 486 |
1 files changed, 486 insertions, 0 deletions
diff --git a/kspread/dialogs/kspread_dlg_goalseek.cpp b/kspread/dialogs/kspread_dlg_goalseek.cpp new file mode 100644 index 000000000..b45525ad2 --- /dev/null +++ b/kspread/dialogs/kspread_dlg_goalseek.cpp @@ -0,0 +1,486 @@ +/* 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 <tdeapplication.h> +#include <kdebug.h> +#include <tdelocale.h> +#include <tdemessagebox.h> +#include <kstdguiitem.h> +#include <kpushbutton.h> + +#include <tqframe.h> +#include <tqlabel.h> +#include <tqlayout.h> +#include <tqlineedit.h> +#include <tqtooltip.h> +#include <tqvariant.h> +#include <tqwhatsthis.h> + +#include <math.h> + +using namespace KSpread; + +GoalSeekDialog::GoalSeekDialog( View * parent, TQPoint 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( TQt::WDestructiveClose ); + + if ( !name ) + setName( "GoalSeekDialog" ); + + resize( 458, 153 ); + setCaption( i18n( "Goal Seek" ) ); + setSizeGripEnabled( true ); + + GoalSeekDialogLayout = new TQGridLayout( this, 1, 1, 11, 6, "GoalSeekDialogLayout"); + + m_startFrame = new TQFrame( this, "m_startFrame" ); + m_startFrame->setFrameShape( TQFrame::StyledPanel ); + m_startFrame->setFrameShadow( TQFrame::Raised ); + m_startFrameLayout = new TQGridLayout( m_startFrame, 1, 1, 11, 6, "m_startFrameLayout"); + + TQLabel * TextLabel4 = new TQLabel( m_startFrame, "TextLabel4" ); + TextLabel4->setText( i18n( "To value:" ) ); + m_startFrameLayout->addWidget( TextLabel4, 1, 0 ); + + m_targetValueEdit = new TQLineEdit( m_startFrame, "m_targetValueEdit" ); + m_startFrameLayout->addWidget( m_targetValueEdit, 1, 1 ); + + m_targetEdit = new TQLineEdit( m_startFrame, "m_targetEdit" ); + m_startFrameLayout->addWidget( m_targetEdit, 0, 1 ); + m_targetEdit->setText( Cell::name( marker.x(), marker.y() ) ); + + m_sourceEdit = new TQLineEdit( m_startFrame, "m_sourceEdit" ); + m_startFrameLayout->addWidget( m_sourceEdit, 2, 1 ); + + TQLabel * TextLabel5 = new TQLabel( m_startFrame, "TextLabel5" ); + TextLabel5->setText( i18n( "By changing cell:" ) ); + + m_startFrameLayout->addWidget( TextLabel5, 2, 0 ); + + TQLabel * TextLabel3 = new TQLabel( m_startFrame, "TextLabel3" ); + TextLabel3->setText( i18n( "Set cell:" ) ); + + m_startFrameLayout->addWidget( TextLabel3, 0, 0 ); + GoalSeekDialogLayout->addWidget( m_startFrame, 0, 0 ); + + TQVBoxLayout * Layout5 = new TQVBoxLayout( 0, 0, 6, "Layout5"); + + m_buttonOk = new TQPushButton( 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 ); + TQSpacerItem* spacer = new TQSpacerItem( 20, 20, TQSizePolicy::Minimum, TQSizePolicy::Expanding ); + Layout5->addItem( spacer ); + + GoalSeekDialogLayout->addMultiCellLayout( Layout5, 0, 1, 1, 1 ); + + m_resultFrame = new TQFrame( this, "m_resultFrame" ); + m_resultFrame->setFrameShape( TQFrame::StyledPanel ); + m_resultFrame->setFrameShadow( TQFrame::Raised ); + m_resultFrame->setMinimumWidth( 350 ); + m_resultFrameLayout = new TQGridLayout( m_resultFrame, 1, 1, 11, 6, "m_resultFrameLayout"); + + m_currentValueLabel = new TQLabel( m_resultFrame, "m_currentValueLabel" ); + m_currentValueLabel->setText( i18n( "Current value:" ) ); + + m_resultFrameLayout->addWidget( m_currentValueLabel, 2, 0 ); + + m_newValueDesc = new TQLabel( m_resultFrame, "m_newValueDesc" ); + m_newValueDesc->setText( i18n( "New value:" ) ); + + m_resultFrameLayout->addWidget( m_newValueDesc, 1, 0 ); + + m_newValue = new TQLabel( m_resultFrame, "m_newValue" ); + m_newValue->setText( "m_targetValueEdit" ); + + m_resultFrameLayout->addWidget( m_newValue, 1, 1 ); + + m_currentValue = new TQLabel( m_resultFrame, "m_currentValue" ); + m_currentValue->setText( "m_currentValue" ); + + m_resultFrameLayout->addWidget( m_currentValue, 2, 1 ); + + m_resultText = new TQLabel( m_resultFrame, "m_resultText" ); + m_resultText->setText( "Goal seeking with cell <cell> found <a | no> solution:" ); + m_resultText->setAlignment( int( TQLabel::WordBreak | TQLabel::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(); + + tqApp->installEventFilter( this ); + + // signals and slots connections + connect( m_buttonOk, TQT_SIGNAL( clicked() ), this, TQT_SLOT( buttonOkClicked() ) ); + connect( m_buttonCancel, TQT_SIGNAL( clicked() ), this, TQT_SLOT( buttonCancelClicked() ) ); + + connect( m_pView->choice(), TQT_SIGNAL(changed(const Region&)), + this, TQT_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( TQObject* obj, TQEvent* ev ) +{ + if ( TQT_BASE_OBJECT(obj) == TQT_BASE_OBJECT(m_targetValueEdit) && ev->type() == TQEvent::FocusIn ) + m_focus = m_targetValueEdit; + else if ( TQT_BASE_OBJECT(obj) == TQT_BASE_OBJECT(m_targetEdit) && ev->type() == TQEvent::FocusIn ) + m_focus = m_targetEdit; + else if ( TQT_BASE_OBJECT(obj) == TQT_BASE_OBJECT(m_sourceEdit) && ev->type() == TQEvent::FocusIn ) + m_focus = m_sourceEdit; + else + return FALSE; + + if ( m_focus ) + m_pView->canvasWidget()->startChoose(); + + return FALSE; +} + +void GoalSeekDialog::closeEvent ( TQCloseEvent * e ) +{ + e->accept(); +} + +void GoalSeekDialog::slotSelectionChanged() +{ + if ( !m_focus ) + return; + + if (m_pView->choice()->isValid()) + { + TQString 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(), TQString::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(TQRect(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( TQString::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" + |