5 * Jukka-Pekka Iivonen <iivonen@iki.fi>
6 * Morten Welinder (terra@gnome.org)
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, see <https://www.gnu.org/licenses/>.
22 #include <gnumeric-config.h>
23 #include <glib/gi18n-lib.h>
25 #include <dialogs/dialogs.h>
26 #include <dialogs/help.h>
33 #include <dependent.h>
34 #include <gnm-format.h>
38 #include <number-match.h>
39 #include <parse-util.h>
41 #include <workbook-control.h>
43 #include <workbook-view.h>
44 #include <tools/goal-seek.h>
46 #include <widgets/gnumeric-expr-entry.h>
47 #include <selection.h>
48 #include <application.h>
53 static const gnm_float max_range_val
= GNM_const(1e24
);
55 #define MAX_CELL_NAME_LEN 20
56 #define GOALSEEK_KEY "goal-seek-dialog"
61 GnmExprEntry
*set_cell_entry
;
62 GnmExprEntry
*change_cell_entry
;
63 GtkWidget
*to_value_entry
;
64 GtkWidget
*at_least_entry
;
65 GtkWidget
*at_most_entry
;
66 GtkWidget
*close_button
;
67 GtkWidget
*cancel_button
;
68 GtkWidget
*apply_button
;
69 GtkWidget
*target_value_label
;
70 GtkWidget
*current_value_label
;
71 GtkWidget
*solution_label
;
72 GtkWidget
*result_label
;
73 GtkWidget
*result_grid
;
77 gnm_float target_value
;
84 GtkWidget
*warning_dialog
;
89 static GnmGoalSeekStatus
90 gnumeric_goal_seek (GoalSeekState
*state
)
92 GnmGoalSeekData seekdata
;
93 GnmGoalSeekCellData celldata
;
95 goal_seek_initialize (&seekdata
);
96 seekdata
.xmin
= state
->xmin
;
97 seekdata
.xmax
= state
->xmax
;
99 celldata
.xcell
= state
->change_cell
;
100 celldata
.ycell
= state
->set_cell
;
101 celldata
.ytarget
= state
->target_value
;
103 return gnm_goal_seek_cell (&seekdata
, &celldata
);
107 cb_dialog_destroy (GoalSeekState
*state
)
109 if (!state
->cancelled
110 && state
->old_value
!= NULL
111 && state
->old_cell
!= NULL
) {
112 cmd_goal_seek (GNM_WBC(state
->wbcg
),
113 state
->old_cell
, state
->old_value
, NULL
);
114 state
->old_value
= NULL
;
117 value_release (state
->old_value
);
118 if (state
->gui
!= NULL
)
119 g_object_unref (state
->gui
);
121 wbcg_edit_finish (state
->wbcg
, WBC_EDIT_REJECT
, NULL
);
126 cb_dialog_cancel_clicked (G_GNUC_UNUSED GtkWidget
*button
,
127 GoalSeekState
*state
)
129 state
->cancelled
= TRUE
;
131 if ((state
->old_cell
!= NULL
) && (state
->old_value
!= NULL
)) {
132 sheet_cell_set_value (state
->old_cell
, state
->old_value
);
133 state
->old_value
= NULL
;
137 gtk_widget_destroy (state
->dialog
);
141 cb_dialog_close_clicked (G_GNUC_UNUSED GtkWidget
*button
,
142 GoalSeekState
*state
)
144 gtk_widget_destroy (state
->dialog
);
148 * cb_dialog_apply_clicked:
152 * Close (destroy) the dialog
155 cb_dialog_apply_clicked (G_GNUC_UNUSED GtkWidget
*button
,
156 GoalSeekState
*state
)
159 GnmGoalSeekStatus status
;
161 GnmRangeRef
const *r
;
162 GOFormat
const *format
;
164 if (state
->warning_dialog
!= NULL
)
165 gtk_widget_destroy (state
->warning_dialog
);
168 target
= gnm_expr_entry_parse_as_value (state
->set_cell_entry
,
170 if (target
== NULL
) {
171 go_gtk_notice_nonmodal_dialog (GTK_WINDOW(state
->dialog
),
172 &(state
->warning_dialog
),
174 _("You should introduce a valid cell "
175 "name in 'Set Cell:'!"));
176 gnm_expr_entry_grab_focus (state
->set_cell_entry
, TRUE
);
179 r
= &target
->v_range
.cell
;
180 state
->set_cell
= sheet_cell_get (r
->a
.sheet
, r
->a
.col
, r
->a
.row
);
181 value_release (target
);
182 if (state
->set_cell
== NULL
|| !gnm_cell_has_expr (state
->set_cell
)) {
183 go_gtk_notice_nonmodal_dialog (GTK_WINDOW(state
->dialog
),
184 &(state
->warning_dialog
),
186 _("The cell named in 'Set Cell:' "
187 "must contain a formula!"));
188 gnm_expr_entry_grab_focus (state
->set_cell_entry
, TRUE
);
193 target
= gnm_expr_entry_parse_as_value (state
->change_cell_entry
,
195 if (target
== NULL
) {
196 go_gtk_notice_nonmodal_dialog (GTK_WINDOW(state
->dialog
),
197 &(state
->warning_dialog
),
199 _("You should introduce a valid cell "
200 "name in 'By Changing Cell:'!"));
201 gnm_expr_entry_grab_focus (state
->change_cell_entry
, TRUE
);
205 r
= &target
->v_range
.cell
;
206 state
->change_cell
= sheet_cell_fetch (r
->a
.sheet
, r
->a
.col
, r
->a
.row
);
207 value_release (target
);
208 if (gnm_cell_has_expr (state
->change_cell
)) {
209 go_gtk_notice_nonmodal_dialog (GTK_WINDOW(state
->dialog
),
210 &(state
->warning_dialog
),
212 _("The cell named in 'By changing cell' "
213 "must not contain a formula."));
214 gnm_expr_entry_grab_focus (state
->change_cell_entry
, TRUE
);
219 format
= gnm_style_get_format (gnm_cell_get_style (state
->set_cell
));
220 if (entry_to_float_with_format (GTK_ENTRY(state
->to_value_entry
),
221 &state
->target_value
, TRUE
, format
)){
222 go_gtk_notice_nonmodal_dialog (GTK_WINDOW(state
->dialog
),
223 &(state
->warning_dialog
),
225 _("The value given in 'To Value:' "
227 focus_on_entry (GTK_ENTRY(state
->to_value_entry
));
231 format
= gnm_style_get_format (gnm_cell_get_style (state
->change_cell
));
232 if (entry_to_float_with_format (GTK_ENTRY(state
->at_least_entry
),
233 &state
->xmin
, TRUE
, format
)) {
234 state
->xmin
= -max_range_val
;
235 gtk_entry_set_text (GTK_ENTRY (state
->at_least_entry
), "");
238 if (entry_to_float_with_format (GTK_ENTRY(state
->at_most_entry
), &state
->xmax
,
240 state
->xmax
= +max_range_val
;
241 gtk_entry_set_text (GTK_ENTRY (state
->at_most_entry
), "");
244 if ((state
->old_cell
!= NULL
) && (state
->old_value
!= NULL
)) {
245 sheet_cell_set_value (state
->old_cell
, state
->old_value
);
246 state
->old_value
= NULL
;
249 state
->old_cell
= state
->change_cell
;
250 state
->old_value
= value_dup (state
->change_cell
->value
);
252 status
= gnumeric_goal_seek (state
);
258 const char *actual_str
;
259 const char *solution_str
;
260 GOFormat
*format
= go_format_general ();
261 GnmValue
*error_value
= value_new_float (state
->target_value
-
262 value_get_as_float (state
->set_cell
->value
));
263 char *target_str
= format_value (format
, error_value
, -1,
264 workbook_date_conv (state
->wb
));
265 gtk_label_set_text (GTK_LABEL (state
->target_value_label
), target_str
);
267 value_release (error_value
);
270 g_strdup_printf (_("Goal seeking with cell %s found a solution."),
271 cell_name (state
->set_cell
));
272 gtk_label_set_text (GTK_LABEL (state
->result_label
), status_str
);
275 /* FIXME? Do a format? */
276 actual_str
= state
->set_cell
->value
277 ? value_peek_string (state
->set_cell
->value
)
279 gtk_label_set_text (GTK_LABEL (state
->current_value_label
), actual_str
);
281 solution_str
= state
->change_cell
->value
282 ? value_peek_string (state
->change_cell
->value
)
284 gtk_label_set_text (GTK_LABEL (state
->solution_label
), solution_str
);
291 g_strdup_printf (_("Goal seeking with cell %s did not find a solution."),
292 cell_name (state
->set_cell
));
293 gtk_label_set_text (GTK_LABEL (state
->result_label
), status_str
);
295 gtk_label_set_text (GTK_LABEL (state
->current_value_label
), "");
296 gtk_label_set_text (GTK_LABEL (state
->solution_label
), "");
297 gtk_label_set_text (GTK_LABEL (state
->target_value_label
), "");
300 state
->cancelled
= FALSE
;
302 gtk_widget_show (state
->result_grid
);
315 dialog_realized (G_GNUC_UNUSED GtkWidget
*dialog
,
316 GoalSeekState
*state
)
318 gtk_widget_hide (state
->result_grid
);
322 * dialog_preload_selection:
329 dialog_preload_selection (GoalSeekState
*state
, GnmExprEntry
*entry
)
333 sel
= selection_first_range
334 (wb_control_cur_sheet_view
335 (GNM_WBC (state
->wbcg
)), NULL
, NULL
);
337 gnm_expr_entry_load_from_range (entry
,
345 * Create the dialog (guru).
349 dialog_init (GoalSeekState
*state
)
353 state
->dialog
= go_gtk_builder_get_widget (state
->gui
, "GoalSeek");
354 if (state
->dialog
== NULL
)
357 gnm_dialog_setup_destroy_handlers (GTK_DIALOG (state
->dialog
),
359 GNM_DIALOG_DESTROY_CURRENT_SHEET_REMOVED
);
361 state
->close_button
= go_gtk_builder_get_widget (state
->gui
, "closebutton");
362 g_signal_connect (G_OBJECT (state
->close_button
),
364 G_CALLBACK (cb_dialog_close_clicked
), state
);
366 state
->cancel_button
= go_gtk_builder_get_widget (state
->gui
, "cancelbutton");
367 g_signal_connect (G_OBJECT (state
->cancel_button
),
369 G_CALLBACK (cb_dialog_cancel_clicked
), state
);
370 state
->apply_button
= go_gtk_builder_get_widget (state
->gui
, "applybutton");
371 g_signal_connect (G_OBJECT (state
->apply_button
),
373 G_CALLBACK (cb_dialog_apply_clicked
), state
);
375 gnm_init_help_button (
376 go_gtk_builder_get_widget (state
->gui
, "helpbutton"),
377 GNUMERIC_HELP_LINK_GOAL_SEEK
);
379 state
->to_value_entry
= go_gtk_builder_get_widget (state
->gui
, "to_value_entry");
380 state
->at_least_entry
= go_gtk_builder_get_widget (state
->gui
, "at_least-entry");
381 state
->at_most_entry
= go_gtk_builder_get_widget (state
->gui
, "at_most-entry");
382 state
->target_value_label
= go_gtk_builder_get_widget (state
->gui
, "target-value");
383 gtk_label_set_justify (GTK_LABEL (state
->target_value_label
), GTK_JUSTIFY_RIGHT
);
384 state
->current_value_label
= go_gtk_builder_get_widget (state
->gui
, "current-value");
385 gtk_label_set_justify (GTK_LABEL (state
->current_value_label
), GTK_JUSTIFY_RIGHT
);
386 state
->solution_label
= go_gtk_builder_get_widget (state
->gui
, "solution");
387 gtk_label_set_justify (GTK_LABEL (state
->solution_label
), GTK_JUSTIFY_RIGHT
);
389 state
->result_label
= go_gtk_builder_get_widget (state
->gui
, "result-label");
390 state
->result_grid
= go_gtk_builder_get_widget (state
->gui
, "result-grid");
392 grid
= GTK_GRID (go_gtk_builder_get_widget (state
->gui
, "goal-grid"));
393 state
->set_cell_entry
= gnm_expr_entry_new (state
->wbcg
, TRUE
);
394 gnm_expr_entry_set_flags (state
->set_cell_entry
,
395 GNM_EE_SINGLE_RANGE
|
396 GNM_EE_FORCE_ABS_REF
,
398 gtk_grid_attach (grid
, GTK_WIDGET (state
->set_cell_entry
),
400 gtk_widget_set_hexpand (GTK_WIDGET (state
->set_cell_entry
), TRUE
);
401 gnm_editable_enters (GTK_WINDOW (state
->dialog
),
402 GTK_WIDGET (state
->set_cell_entry
));
403 dialog_preload_selection (state
, state
->set_cell_entry
);
404 gtk_widget_show (GTK_WIDGET (state
->set_cell_entry
));
406 state
->change_cell_entry
= gnm_expr_entry_new (state
->wbcg
, TRUE
);
407 gnm_expr_entry_set_flags (state
->change_cell_entry
,
408 GNM_EE_SINGLE_RANGE
|
409 GNM_EE_FORCE_ABS_REF
,
411 gtk_grid_attach (grid
, GTK_WIDGET (state
->change_cell_entry
),
413 gtk_widget_set_hexpand (GTK_WIDGET (state
->change_cell_entry
), TRUE
);
414 gnm_editable_enters (GTK_WINDOW (state
->dialog
),
415 GTK_WIDGET (state
->change_cell_entry
));
416 dialog_preload_selection (state
, state
->change_cell_entry
);
417 gtk_widget_show (GTK_WIDGET (state
->change_cell_entry
));
420 g_signal_connect (G_OBJECT (state
->dialog
),
422 G_CALLBACK (dialog_realized
), state
);
424 state
->old_value
= NULL
;
425 state
->old_cell
= NULL
;
427 wbc_gtk_attach_guru (state
->wbcg
, state
->dialog
);
428 g_object_set_data_full (G_OBJECT (state
->dialog
),
429 "state", state
, (GDestroyNotify
) cb_dialog_destroy
);
431 gnm_expr_entry_grab_focus (state
->set_cell_entry
, FALSE
);
437 * We need a horizontal strip of 5 cells containing:
446 dialog_goal_seek_test (Sheet
*sheet
, const GnmRange
*range
)
451 GnmGoalSeekStatus status
;
453 g_return_if_fail (range
->start
.row
== range
->end
.row
);
454 g_return_if_fail (range
->start
.col
+ 4 == range
->end
.col
);
456 memset (&state
, 0, sizeof (state
));
457 r
= range
->start
.row
;
458 c
= range
->start
.col
;
460 state
.wb
= sheet
->workbook
;
463 state
.set_cell
= sheet_cell_fetch (sheet
, c
+ 0, r
);
464 state
.change_cell
= sheet_cell_fetch (sheet
, c
+ 1, r
);
465 state
.old_value
= value_dup (state
.change_cell
->value
);
467 cell
= sheet_cell_fetch (sheet
, c
+ 2, r
);
468 state
.target_value
= value_get_as_float (cell
->value
);
470 cell
= sheet_cell_fetch (sheet
, c
+ 3, r
);
471 state
.xmin
= VALUE_IS_EMPTY (cell
->value
)
473 : value_get_as_float (cell
->value
);
475 cell
= sheet_cell_fetch (sheet
, c
+ 4, r
);
476 state
.xmax
= VALUE_IS_EMPTY (cell
->value
)
478 : value_get_as_float (cell
->value
);
480 status
= gnumeric_goal_seek (&state
);
481 if (status
== GOAL_SEEK_OK
) {
484 sheet_cell_set_value (state
.change_cell
,
485 value_new_error_VALUE (NULL
));
488 value_release (state
.old_value
);
496 * Create the dialog (guru).
500 dialog_goal_seek (WBCGtk
*wbcg
, Sheet
*sheet
)
502 GoalSeekState
*state
;
505 g_return_if_fail (IS_SHEET (sheet
));
510 g_object_get_data (G_OBJECT (sheet
), "ssconvert-goal-seek");
512 Sheet
*start_sheet
, *end_sheet
;
516 gnm_rangeref_normalize (range
,
517 eval_pos_init_sheet (&ep
, sheet
),
518 &start_sheet
, &end_sheet
,
520 g_return_if_fail (start_sheet
== sheet
);
522 dialog_goal_seek_test (sheet
, &r
);
527 g_return_if_fail (wbcg
!= NULL
);
529 /* Only pop up one copy per workbook */
530 if (gnm_dialog_raise_if_exists (wbcg
, GOALSEEK_KEY
))
532 gui
= gnm_gtk_builder_load ("res:ui/goalseek.ui", NULL
, GO_CMD_CONTEXT (wbcg
));
536 state
= g_new (GoalSeekState
, 1);
538 state
->wb
= wb_control_get_workbook (GNM_WBC (wbcg
));
539 state
->sheet
= sheet
;
541 state
->warning_dialog
= NULL
;
542 state
->cancelled
= TRUE
;
544 if (dialog_init (state
)) {
545 go_gtk_notice_dialog (wbcg_toplevel (wbcg
), GTK_MESSAGE_ERROR
,
546 _("Could not create the Goal-Seek dialog."));
551 gnm_keyed_dialog (state
->wbcg
, GTK_WINDOW (state
->dialog
),
554 gtk_widget_show (state
->dialog
);