File indexing completed on 2024-05-12 16:35:19

0001 /* This file is part of the KDE project
0002    Copyright (C) 2002-2003 Norbert Andres <nandres@web.de>
0003              (C) 2002-2003 Philipp Mueller <philipp.mueller@gmx.de>
0004              (C) 2002 Laurent Montel <montel@kde.org>
0005              (C) 2002 John Dailey <dailey@vt.edu>
0006              (C) 2002 Ariya Hidayat <ariya@kde.org>
0007              (C) 2002 Werner Trobin <trobin@kde.org>
0008              (C) 2002 Harri Porten <porten@kde.org>
0009 
0010    This library is free software; you can redistribute it and/or
0011    modify it under the terms of the GNU Library General Public
0012    License as published by the Free Software Foundation; either
0013    version 2 of the License, or (at your option) any later version.
0014 
0015    This library is distributed in the hope that it will be useful,
0016    but WITHOUT ANY WARRANTY; without even the implied warranty of
0017    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0018    Library General Public License for more details.
0019 
0020    You should have received a copy of the GNU Library General Public License
0021    along with this library; see the file COPYING.LIB.  If not, write to
0022    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0023    Boston, MA 02110-1301, USA.
0024 */
0025 
0026 #include "GoalSeekDialog.h"
0027 
0028 #include <ktextedit.h>
0029 #include <klocale.h>
0030 
0031 #include "CalculationSettings.h"
0032 #include "Cell.h"
0033 #include "ui/RegionSelector.h"
0034 #include "Formula.h"
0035 #include "Map.h"
0036 #include "ui/Selection.h"
0037 #include "Sheet.h"
0038 #include "Util.h"
0039 
0040 // commands
0041 #include "commands/DataManipulators.h"
0042 
0043 #include "ui_GoalSeekWidget.h"
0044 
0045 #include <kmessagebox.h>
0046 
0047 #include <QCloseEvent>
0048 
0049 #include <math.h>
0050 
0051 using namespace Calligra::Sheets;
0052 
0053 class GoalSeekDialog::Private
0054 {
0055 public:
0056     Selection   * selection;
0057     Cell          sourceCell;
0058     Cell          targetCell;
0059     double        result;
0060     int           maxIter;
0061     double        oldSource;
0062     bool          firstRun;
0063 
0064     Ui::GoalSeekWidget widget;
0065 };
0066 
0067 
0068 GoalSeekDialog::GoalSeekDialog(QWidget* parent, Selection* selection)
0069         : KoDialog(parent)
0070         , d(new Private)
0071 {
0072     d->selection = selection;
0073     d->result = 0.0;
0074     d->maxIter = 1000; // TODO make this configurable
0075     d->oldSource = 0.0;
0076     d->firstRun = true;
0077 
0078     setButtons(Ok | Cancel);
0079     enableButtonOk(false);
0080     setModal(false);
0081 
0082     setObjectName(QLatin1String("GoalSeekDialog"));
0083 
0084     setWindowTitle(i18n("Goal Seek"));
0085 
0086     QWidget* mainWidget = new QWidget(this);
0087     d->widget.setupUi(mainWidget);
0088     d->widget.selector1->setDialog(this);
0089     d->widget.selector1->setSelection(d->selection);
0090     d->widget.selector1->setSelectionMode(RegionSelector::SingleCell);
0091     d->widget.selector2->setDialog(this);
0092     d->widget.selector2->setSelection(d->selection);
0093     d->widget.selector2->setSelectionMode(RegionSelector::SingleCell);
0094     d->widget.selector3->setDialog(this);
0095     d->widget.selector3->setSelection(d->selection);
0096     d->widget.selector3->setSelectionMode(RegionSelector::SingleCell);
0097     d->widget.preview->hide();
0098     setMainWidget(mainWidget);
0099 
0100     // connections
0101     connect(d->widget.selector1->textEdit(), SIGNAL(textChanged()),
0102             this, SLOT(textChanged()));
0103     connect(d->widget.selector2->textEdit(), SIGNAL(textChanged()),
0104             this, SLOT(textChanged()));
0105     connect(d->widget.selector3->textEdit(), SIGNAL(textChanged()),
0106             this, SLOT(textChanged()));
0107 
0108     // Allow the user to select cells on the spreadsheet.
0109 //   d->selection->canvasWidget()->startChoose();
0110 }
0111 
0112 GoalSeekDialog::~GoalSeekDialog()
0113 {
0114     delete d;
0115 }
0116 
0117 void GoalSeekDialog::closeEvent(QCloseEvent * e)
0118 {
0119     d->selection->endReferenceSelection();
0120     e->accept();
0121     deleteLater();
0122 }
0123 
0124 void GoalSeekDialog::textChanged()
0125 {
0126     d->widget.preview->hide();
0127     const bool s1 = !d->widget.selector1->textEdit()->toPlainText().isEmpty();
0128     const bool s2 = !d->widget.selector2->textEdit()->toPlainText().isEmpty();
0129     const bool s3 = !d->widget.selector3->textEdit()->toPlainText().isEmpty();
0130     enableButtonOk(s1 && s2 && s3);
0131 }
0132 
0133 void GoalSeekDialog::accept()
0134 {
0135     if (!d->widget.preview->isVisible()) {
0136         Sheet * sheet = d->selection->activeSheet();
0137 
0138         const Region source(d->widget.selector3->textEdit()->toPlainText(), sheet->map(), sheet);
0139         if (!source.isValid() || !source.isSingular()) {
0140             KMessageBox::error(this, i18n("Cell reference is invalid."));
0141             d->widget.selector3->textEdit()->selectAll();
0142             d->widget.selector3->textEdit()->setFocus();
0143 
0144             d->selection->emitModified();
0145             return;
0146         }
0147 
0148         const Region target(d->widget.selector1->textEdit()->toPlainText(), sheet->map(), sheet);
0149         if (!target.isValid() || !target.isSingular()) {
0150             KMessageBox::error(this, i18n("Cell reference is invalid."));
0151             d->widget.selector1->textEdit()->selectAll();
0152             d->widget.selector1->textEdit()->setFocus();
0153 
0154             d->selection->emitModified();
0155             return;
0156         }
0157 
0158         bool ok = false;
0159         double goal = d->selection->activeSheet()->map()->calculationSettings()->locale()->readNumber(d->widget.selector2->textEdit()->toPlainText(), &ok);
0160         if (!ok) {
0161             KMessageBox::error(this, i18n("Target value is invalid."));
0162             d->widget.selector2->textEdit()->selectAll();
0163             d->widget.selector2->textEdit()->setFocus();
0164 
0165             d->selection->emitModified();
0166             return;
0167         }
0168 
0169         d->sourceCell = Cell(source.firstSheet(), source.firstRange().topLeft());
0170         d->targetCell = Cell(target.firstSheet(), target.firstRange().topLeft());
0171 
0172         if (!d->sourceCell.value().isNumber()) {
0173             KMessageBox::error(this, i18n("Source cell must contain a numeric value."));
0174             d->widget.selector3->textEdit()->selectAll();
0175             d->widget.selector3->textEdit()->setFocus();
0176 
0177             d->selection->emitModified();
0178             return;
0179         }
0180 
0181         if (!d->targetCell.isFormula()) {
0182             KMessageBox::error(this, i18n("Target cell must contain a formula."));
0183             d->widget.selector1->textEdit()->selectAll();
0184             d->widget.selector1->textEdit()->setFocus();
0185 
0186             d->selection->emitModified();
0187             return;
0188         }
0189 
0190         enableButtonOk(false);
0191         enableButtonCancel(false);
0192 
0193         d->widget.preview->show();
0194 
0195         startCalc(numToDouble(d->sourceCell.value().asFloat()), goal);
0196         d->selection->emitModified();
0197         return;
0198     }
0199 
0200     // Reset the value for a proper undo value.
0201     const Value value = d->sourceCell.value();
0202     d->sourceCell.setValue(Value(d->oldSource));
0203     Sheet *const sheet = d->selection->activeSheet();
0204     DataManipulator *const command = new DataManipulator();
0205     command->setSheet(sheet);
0206     command->add(Region(d->sourceCell.cellPosition(), sheet));
0207     command->setValue(value);
0208     sheet->map()->addCommand(command);
0209 
0210     d->selection->endReferenceSelection();
0211     d->selection->emitModified();
0212     deleteLater();
0213 }
0214 
0215 void GoalSeekDialog::reject()
0216 {
0217     if (d->widget.preview->isVisible()) {
0218         d->sourceCell.setValue(Value(d->oldSource));
0219     }
0220 
0221     deleteLater();
0222 }
0223 
0224 void GoalSeekDialog::startCalc(double _start, double _goal)
0225 {
0226     d->widget.label4->setText(i18n("Starting..."));
0227     d->widget.label5->setText(i18n("Iteration:"));
0228 
0229     // lets be optimistic
0230     bool ok = true;
0231 
0232     // TODO: make this configurable
0233     double eps = 0.0000001;
0234 
0235     double startA = 0.0, startB;
0236     double resultA, resultB;
0237 
0238     // save old value
0239     if (d->firstRun) {
0240         d->firstRun = false;
0241         d->oldSource = numToDouble(d->sourceCell.value().asFloat());
0242     }
0243     resultA = numToDouble(d->targetCell.value().asFloat()) - _goal;
0244 
0245     // initialize start value
0246     startB = _start;
0247     double x = startB + 0.5;
0248 
0249     int iterations = d->maxIter;
0250     const Formula formula = d->targetCell.formula();
0251 
0252     // while the result is not close enough to zero
0253     // or while the max number of iterations is not reached...
0254     while (fabs(resultA) > eps && (iterations >= 0)) {
0255         startA = startB;
0256         startB = x;
0257 
0258         d->sourceCell.setValue(Value(startA));
0259         const double targetValueA = numToDouble(formula.eval().asFloat());
0260         resultA = targetValueA - _goal;
0261 //         debugSheets << "Target A:" << targetValueA << "," << d->targetCell.userInput() << "Calc:" << resultA;
0262 
0263         d->sourceCell.setValue(Value(startB));
0264         const double targetValueB = numToDouble(formula.eval().asFloat());
0265         resultB = targetValueB - _goal;
0266 //         debugSheets << "Target B:" << targetValueB << "," << d->targetCell.userInput() << "Calc:" << resultB;
0267 
0268 //         debugSheets <<"Iteration:" << iterations <<", StartA:" << startA
0269 //                  << ", ResultA: " << resultA << " (eps: " << eps << "), StartB: "
0270 //                  << startB << ", ResultB: " << resultB << endl;
0271 
0272 
0273         // find zero with secant method (rough implementation was provided by Franz-Xaver Meier):
0274         // if the function returns the same for two different
0275         // values we have something like a horizontal line
0276         // => can't get zero.
0277         if (resultB == resultA) {
0278 //         debugSheets <<" resultA == resultB";
0279             if (fabs(resultA) < eps) {
0280                 ok = true;
0281                 break;
0282             }
0283 
0284             ok = false;
0285             break;
0286         }
0287 
0288         // Point of intersection of secant with x-axis
0289         x = (startA * resultB - startB * resultA) / (resultB - resultA);
0290 
0291         if (fabs(x) > 100000000) {
0292 //             debugSheets <<"fabs(x) > 100000000:" << x;
0293             ok = false;
0294             break;
0295         }
0296 
0297 //         debugSheets <<"X:" << x <<", fabs (resultA):" << fabs(resultA) <<", Real start:" << startA <<", Real result:" << resultA <<", Iteration:" << iterations;
0298 
0299         --iterations;
0300         if (iterations % 20 == 0)
0301             d->widget.newValue->setText(QString::number(iterations));
0302     }
0303 
0304     d->widget.label5->setText(i18n("New value:"));
0305     if (ok) {
0306         d->sourceCell.setValue(Value(startA));
0307 
0308         d->widget.label4->setText(i18n("Goal seeking with cell %1 found a solution:",
0309                                        d->widget.selector3->textEdit()->toPlainText()));
0310         d->widget.newValue->setText(d->selection->activeSheet()->map()->calculationSettings()->locale()->formatNumber(startA));
0311         d->widget.currentValue->setText(d->selection->activeSheet()->map()->calculationSettings()->locale()->formatNumber(d->oldSource));
0312     } else {
0313         // restore the old value
0314         d->sourceCell.setValue(Value(d->oldSource));
0315         d->widget.label4->setText(i18n("Goal seeking with cell %1 has found NO solution.",
0316                                        d->widget.selector3->textEdit()->toPlainText()));
0317         d->widget.newValue->setText("");
0318         d->widget.currentValue->setText(d->selection->activeSheet()->map()->calculationSettings()->locale()->formatNumber(d->oldSource));
0319     }
0320 
0321     enableButtonOk(ok);
0322     enableButtonCancel(true);
0323 }