/* This file is part of the KDE project Copyright (C) 2002-2003 Norbert Andres (C) 2002-2003 Philipp Mueller (C) 2002 Laurent Montel (C) 2002 John Dailey (C) 2002 Ariya Hidayat (C) 2002 Werner Trobin (C) 2002 Harri Porten 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 #include #include #include #include #include #include #include #include #include #include #include #include #include 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 found 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"