1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * dialog-scenarios.c: Create, edit, and view scenarios.
6 * Jukka-Pekka Iivonen <jiivonen@hutcs.cs.hut.fi>
8 * (C) Copyright 2003 by Jukka-Pekka Iivonen <jiivonen@hutcs.cs.hut.fi>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, see <https://www.gnu.org/licenses/>.
23 #include <gnumeric-config.h>
24 #include <glib/gi18n-lib.h>
31 #include <workbook-control.h>
35 #include <dao-gui-utils.h>
41 #include "scenarios.h"
44 #include <goffice/goffice.h>
47 #define DARK_GRAY GO_COLOR_GREY(51) /* "gray20" */
48 #define LIGHT_GRAY GO_COLOR_GREY(199) /* "gray78" */
51 GenericToolState base
;
53 GtkWidget
*show_button
;
54 GtkWidget
*delete_button
;
55 GtkWidget
*summary_button
;
56 GtkWidget
*name_entry
;
58 GtkWidget
*scenarios_treeview
;
59 GSList
*new_report_sheets
;
65 typedef GnmValue
* (*ScenarioValueCB
) (int col
, int row
, GnmValue
*v
, gpointer data
);
68 scenario_for_each_value (GnmScenario
*s
, ScenarioValueCB fn
, gpointer data
)
72 /* Ok button pressed. */
74 scenario_manager_ok (Sheet
*sheet
)
76 GList
*l
, *scenarios
= g_list_copy (sheet
->scenarios
);
78 /* Update scenarios (free the deleted ones). */
79 for (l
= scenarios
; l
; l
= l
->next
) {
80 GnmScenario
*sc
= l
->data
;
81 if (g_object_get_data (G_OBJECT (sc
), "marked_deleted")) {
82 gnm_sheet_scenario_remove (sc
->sheet
, sc
);
85 g_list_free (scenarios
);
87 sheet_redraw_all (sheet
, TRUE
);
90 /* Cancel button pressed. */
92 scenario_recover_all (Sheet
*sheet
)
96 for (l
= sheet
->scenarios
; l
; l
= l
->next
) {
97 GnmScenario
*sc
= l
->data
;
98 g_object_set_data (G_OBJECT (sc
),
99 "marked_deleted", GUINT_TO_POINTER (FALSE
));
103 /* Scenario: Create summary report ***************************************/
106 rm_fun_cb (gpointer key
, gpointer value
, gpointer user_data
)
112 data_analysis_output_t dao
;
115 GHashTable
*names
; /* A hash table for cell names->row. */
122 summary_cb (int col
, int row
, GnmValue
*v
, summary_cb_t
*p
)
124 char *tmp
= dao_find_name (p
->sheet
, col
, row
);
127 /* Check if some of the previous scenarios already included that
128 * cell. If so, it's row will be put into *index. */
129 index
= g_hash_table_lookup (p
->names
, tmp
);
131 dao_set_cell_value (&p
->dao
, 2 + p
->col
, 3 + *index
,
134 /* Set the colors. */
135 dao_set_colors (&p
->dao
, 2 + p
->col
, 3 + *index
,
136 2 + p
->col
, 3 + *index
,
137 gnm_color_new_go (GO_COLOR_BLACK
),
138 gnm_color_new_go (LIGHT_GRAY
));
145 /* Changing cell name. */
146 dao_set_cell (&p
->dao
, 0, 3 + p
->row
, tmp
);
148 /* GnmValue of the cell in this scenario. */
149 dao_set_cell_value (&p
->dao
, 2 + p
->col
, 3 + p
->row
,
152 /* Current value of the cell. */
153 cell
= sheet_cell_fetch (p
->sheet
, col
, row
);
154 dao_set_cell_value (&p
->dao
, 1, 3 + p
->row
,
155 value_dup (cell
->value
));
157 /* Set the colors. */
158 dao_set_colors (&p
->dao
, 2 + p
->col
, 3 + p
->row
,
159 2 + p
->col
, 3 + p
->row
,
160 gnm_color_new_go (GO_COLOR_BLACK
),
161 gnm_color_new_go (LIGHT_GRAY
));
163 /* Insert row number into the hash table. */
166 g_hash_table_insert (p
->names
, tmp
, r
);
168 /* Increment the nbr of rows. */
176 scenario_summary_res_cells (WorkbookControl
*wbc
, GSList
*results
,
182 scenario_summary (WorkbookControl
*wbc
,
189 GList
*scenarios
= sheet
->scenarios
;
191 /* Initialize: Currently only new sheet output supported. */
192 dao_init_new_sheet (&cb
.dao
);
193 dao_prepare_output (wbc
, &cb
.dao
, _("Scenario Summary"));
196 dao_set_cell (&cb
.dao
, 1, 1, _("Current Values"));
197 dao_set_cell (&cb
.dao
, 0, 2, _("Changing Cells:"));
199 /* Go through all scenarios. */
201 cb
.names
= g_hash_table_new (g_str_hash
, g_str_equal
);
203 cb
.results
= results
;
204 for (cb
.col
= 0, cur
= scenarios
; cur
!= NULL
; cb
.col
++,
206 GnmScenario
*s
= cur
->data
;
209 dao_set_cell (&cb
.dao
, 2 + cb
.col
, 1, s
->name
);
211 scenario_for_each_value (s
, (ScenarioValueCB
) summary_cb
, &cb
);
214 /* Set the alignment of names of the changing cells to be right. */
215 dao_set_align (&cb
.dao
, 0, 3, 0, 2 + cb
.row
, GNM_HALIGN_RIGHT
,
220 scenario_summary_res_cells (wbc
, results
, &cb
);
222 /* Destroy the hash table. */
223 g_hash_table_foreach (cb
.names
, (GHFunc
) rm_fun_cb
, NULL
);
224 g_hash_table_destroy (cb
.names
);
226 /* Clean up the report output. */
227 dao_set_bold (&cb
.dao
, 0, 0, 0, 2 + cb
.row
);
228 dao_autofit_columns (&cb
.dao
);
229 dao_set_cell (&cb
.dao
, 0, 0, _("Scenario Summary"));
231 dao_set_colors (&cb
.dao
, 0, 0, cb
.col
+ 1, 1,
232 gnm_color_new_go (GO_COLOR_WHITE
),
233 gnm_color_new_go (DARK_GRAY
));
234 dao_set_colors (&cb
.dao
, 0, 2, 0, 2 + cb
.row
,
235 gnm_color_new_go (GO_COLOR_BLACK
),
236 gnm_color_new_go (LIGHT_GRAY
));
238 dao_set_align (&cb
.dao
, 1, 1, cb
.col
+ 1, 1, GNM_HALIGN_RIGHT
,
241 *new_sheet
= cb
.dao
.sheet
;
244 /********* Scenario Add UI **********************************************/
247 scenario_name_used (const GList
*scenarios
, const gchar
*name
)
249 while (scenarios
!= NULL
) {
250 const GnmScenario
*s
= scenarios
->data
;
252 if (strcmp (s
->name
, name
) == 0)
254 scenarios
= scenarios
->next
;
260 * A scenario name should have at least one printable character.
263 check_name (const gchar
*n
)
265 while (*n
&& g_unichar_isspace (g_utf8_get_char (n
)))
266 n
= g_utf8_next_char (n
);
275 * scenarios_ok_clicked_cb:
279 * Run the scenario manager tool.
282 scenario_add_ok_clicked_cb (G_GNUC_UNUSED GtkWidget
*button
,
283 ScenariosState
*state
)
285 data_analysis_output_t dao
;
286 WorkbookControl
*wbc
;
289 GnmValue
*cell_range
;
290 GtkWidget
*entry
, *comment_view
;
292 GtkTextIter start
, end
;
296 cell_range
= gnm_expr_entry_parse_as_value
297 (GNM_EXPR_ENTRY (state
->base
.input_entry
), state
->base
.sheet
);
299 if (!cell_range
|| !gnm_sheet_range_from_value (&sr
, cell_range
)) {
300 go_gtk_notice_dialog (GTK_WINDOW (state
->base
.dialog
),
302 _("Invalid changing cells"));
303 gnm_expr_entry_grab_focus (state
->base
.input_entry
, TRUE
);
307 if (sr
.sheet
&& sr
.sheet
!= state
->base
.sheet
) {
308 go_gtk_notice_dialog (GTK_WINDOW (state
->base
.dialog
),
310 _("Changing cells should be on the current "
312 gnm_expr_entry_grab_focus (state
->base
.input_entry
, TRUE
);
315 entry
= go_gtk_builder_get_widget (state
->base
.gui
, "name_entry");
317 name
= g_strdup (gtk_entry_get_text (GTK_ENTRY (entry
)));
318 if (scenario_name_used (state
->base
.sheet
->scenarios
, name
)) {
320 go_gtk_notice_dialog (GTK_WINDOW (state
->base
.dialog
),
322 _("Scenario name already used"));
324 } else if (check_name (name
)) {
326 go_gtk_notice_dialog (GTK_WINDOW (state
->base
.dialog
),
328 _("Invalid scenario name"));
332 comment_view
= go_gtk_builder_get_widget (state
->base
.gui
, "comment_view");
333 buf
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (comment_view
));
334 gtk_text_buffer_get_start_iter (buf
, &start
);
335 gtk_text_buffer_get_end_iter (buf
, &end
);
336 comment
= g_strdup (gtk_text_buffer_get_text (buf
, &start
, &end
,
339 dao_init_new_sheet (&dao
);
340 dao
.sheet
= state
->base
.sheet
;
342 wbc
= GNM_WBC (state
->base
.wbcg
);
344 sc
= gnm_sheet_scenario_new (state
->base
.sheet
, name
);
345 if (comment
&& comment
[0])
346 gnm_scenario_set_comment (sc
, comment
);
347 gnm_scenario_add_area (sc
, &sr
);
349 cmd_scenario_add (wbc
, sc
, state
->base
.sheet
);
353 gtk_widget_destroy (state
->base
.dialog
);
355 value_release (cell_range
);
360 * scenario_add_update_sensitivity_cb:
364 * Update the dialog widgets sensitivity
367 scenario_add_update_sensitivity_cb (G_GNUC_UNUSED GtkWidget
*dummy
,
368 ScenariosState
*state
)
370 gtk_widget_set_sensitive (state
->base
.ok_button
, TRUE
);
374 dialog_scenario_add (WBCGtk
*wbcg
)
376 ScenariosState
*state
;
377 WorkbookControl
*wbc
;
378 GtkWidget
*comment_view
;
379 char const *error_str
= _("Could not create the Scenario Add dialog.");
385 wbc
= GNM_WBC (wbcg
);
387 /* Only pop up one copy per workbook */
388 if (gnm_dialog_raise_if_exists (wbcg
, "ScenarioAdd"))
391 state
= g_new (ScenariosState
, 1);
393 if (dialog_tool_init (&state
->base
, wbcg
, wb_control_cur_sheet (wbc
),
394 GNUMERIC_HELP_LINK_SCENARIOS_ADD
,
395 "res:ui/scenario-add.ui", "ScenarioAdd",
398 G_CALLBACK (scenario_add_ok_clicked_cb
), NULL
,
399 G_CALLBACK (scenario_add_update_sensitivity_cb
),
400 GNM_EE_SHEET_OPTIONAL
))
406 state
->name_entry
= go_gtk_builder_get_widget (state
->base
.gui
, "name_entry");
407 if (state
->name_entry
== NULL
)
410 comment_view
= go_gtk_builder_get_widget (state
->base
.gui
, "comment_view");
411 if (comment_view
== NULL
)
413 buf
= g_string_new (NULL
);
414 g_string_append_printf (buf
, _("Created on "));
415 dao_append_date (buf
);
416 gtk_text_buffer_set_text (gtk_text_view_get_buffer
417 (GTK_TEXT_VIEW (comment_view
)), buf
->str
,
419 g_string_free (buf
, FALSE
);
421 state
->base
.gdao
= NULL
;
423 gnm_dialog_setup_destroy_handlers (GTK_DIALOG (state
->base
.dialog
),
425 GNM_DIALOG_DESTROY_SHEET_REMOVED
);
427 gnm_editable_enters (GTK_WINDOW (state
->base
.dialog
),
428 GTK_WIDGET (state
->name_entry
));
429 scenario_add_update_sensitivity_cb (NULL
, state
);
430 tool_load_selection ((GenericToolState
*)state
, TRUE
);
434 /********* Scenario Manager UI ******************************************/
437 update_comment (ScenariosState
*state
, const gchar
*cells
,
438 const gchar
*comment
)
443 /* Update changing cells box */
444 w
= go_gtk_builder_get_widget (state
->base
.gui
, "changing_cells_entry");
445 gtk_entry_set_text (GTK_ENTRY (w
), cells
);
447 /* Update comment text view */
448 w
= go_gtk_builder_get_widget (state
->base
.gui
, "comment_view");
449 buf
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (w
));
451 gtk_text_buffer_set_text (buf
, comment
, strlen (comment
));
455 set_selection_state (ScenariosState
*state
, gboolean f
)
457 /* Set the sensitivies to FALSE since no selections have been made */
458 gtk_widget_set_sensitive (state
->show_button
, f
);
459 gtk_widget_set_sensitive (state
->delete_button
, f
);
462 GtkTreeSelection
*selection
;
469 selection
= gtk_tree_view_get_selection
471 (state
->scenarios_treeview
));
472 if (!gtk_tree_selection_get_selected (selection
, NULL
, &iter
))
474 model
= gtk_tree_view_get_model
476 (state
->scenarios_treeview
));
478 gtk_tree_model_get (GTK_TREE_MODEL (model
), &iter
,
481 sc
= gnm_sheet_scenario_find (state
->base
.sheet
, name
);
484 cells_txt
= gnm_scenario_get_range_str (sc
);
485 update_comment (state
, cells_txt
, sc
->comment
);
488 update_comment (state
, "", "");
492 update_scenarios_treeview (GtkWidget
*view
, GList
*scenarios
)
498 store
= gtk_list_store_new (1, G_TYPE_STRING
);
500 while (scenarios
!= NULL
) {
501 GnmScenario
*s
= scenarios
->data
;
503 gtk_list_store_append (store
, &iter
);
504 gtk_list_store_set (store
, &iter
, 0, s
->name
, -1);
505 scenarios
= scenarios
->next
;
507 path
= gtk_tree_path_new_from_string ("0");
508 if (gtk_tree_model_get_iter (GTK_TREE_MODEL (store
),
514 gtk_tree_path_free (path
);
516 gtk_tree_view_set_model (GTK_TREE_VIEW (view
),
517 GTK_TREE_MODEL (store
));
518 g_object_unref (store
);
519 gtk_tree_view_append_column
520 (GTK_TREE_VIEW (view
),
521 gtk_tree_view_column_new_with_attributes
523 gtk_cell_renderer_text_new (), "text", 0, NULL
));
527 * scenarios_update_sensitivity_cb:
531 * Update the dialog widgets sensitivity
534 scenarios_update_sensitivity_cb (G_GNUC_UNUSED GtkWidget
*dummy
,
535 ScenariosState
*state
)
537 gtk_widget_set_sensitive (state
->base
.ok_button
, TRUE
);
542 scenario_manager_free (ScenariosState
*state
)
544 g_slist_free (state
->new_report_sheets
);
545 state
->new_report_sheets
= NULL
;
548 g_object_unref (state
->undo
);
554 * scenarios_ok_clicked_cb:
558 * Run the scenario manager tool.
561 scenarios_ok_clicked_cb (G_GNUC_UNUSED GtkWidget
*button
,
562 ScenariosState
*state
)
564 if (state
->current
) {
565 WorkbookControl
*wbc
= GNM_WBC (state
->base
.wbcg
);
566 cmd_scenario_mngr (wbc
, state
->current
, state
->undo
);
569 scenario_manager_ok (state
->base
.sheet
);
571 scenario_manager_free (state
);
572 gtk_widget_destroy (state
->base
.dialog
);
578 restore_old_values (ScenariosState
*state
)
582 if (state
->undo
== NULL
)
585 cc
= GO_CMD_CONTEXT (state
->base
.wbcg
);
586 go_undo_undo_with_data (state
->undo
, cc
);
587 g_object_unref (state
->undo
);
589 state
->current
= NULL
;
593 * scenarios_cancel_clicked_cb:
597 * Cancel clicked on the scenario manager tool.
600 scenarios_cancel_clicked_cb (G_GNUC_UNUSED GtkWidget
*button
,
601 ScenariosState
*state
)
604 WorkbookControl
*wbc
;
606 restore_old_values (state
);
608 wbc
= GNM_WBC (state
->base
.wbcg
);
610 /* Remove report sheets created on this dialog session. */
611 for (cur
= state
->new_report_sheets
; cur
!= NULL
;
613 Sheet
*sheet
= cur
->data
;
615 /* Check that if the focus is on a deleted sheet. */
616 if (wb_control_cur_sheet (wbc
) == sheet
)
617 wb_control_sheet_focus (wbc
, state
->base
.sheet
);
619 /* Delete a report sheet. */
620 workbook_sheet_delete (sheet
);
623 /* Recover the deleted scenarios. */
624 scenario_recover_all (state
->base
.sheet
);
626 scenario_manager_free (state
);
627 gtk_widget_destroy (state
->base
.dialog
);
631 scenarios_show_clicked_cb (G_GNUC_UNUSED GtkWidget
*button
,
632 ScenariosState
*state
)
634 GtkTreeSelection
*selection
;
639 selection
= gtk_tree_view_get_selection
640 (GTK_TREE_VIEW (state
->scenarios_treeview
));
641 if (!gtk_tree_selection_get_selected (selection
, NULL
, &iter
))
643 model
= gtk_tree_view_get_model
644 (GTK_TREE_VIEW (state
->scenarios_treeview
));
646 gtk_tree_model_get (GTK_TREE_MODEL (model
), &iter
, 0, &value
, -1);
648 restore_old_values (state
);
650 state
->current
= gnm_sheet_scenario_find (state
->base
.sheet
, value
);
651 state
->undo
= gnm_scenario_apply (state
->current
);
655 scenarios_delete_clicked_cb (G_GNUC_UNUSED GtkWidget
*button
,
656 ScenariosState
*state
)
658 data_analysis_output_t dao
;
659 GtkTreeSelection
*selection
;
663 gboolean all_deleted
;
667 restore_old_values (state
);
669 selection
= gtk_tree_view_get_selection
670 (GTK_TREE_VIEW (state
->scenarios_treeview
));
671 dao_init_new_sheet (&dao
);
672 dao
.sheet
= state
->base
.sheet
;
673 if (!gtk_tree_selection_get_selected (selection
, NULL
, &iter
))
675 model
= gtk_tree_view_get_model
676 (GTK_TREE_VIEW (state
->scenarios_treeview
));
678 gtk_tree_model_get (GTK_TREE_MODEL (model
), &iter
, 0, &value
, -1);
680 gtk_list_store_remove (GTK_LIST_STORE (model
), &iter
);
682 sc
= gnm_sheet_scenario_find (state
->base
.sheet
, value
);
684 g_object_set_data (G_OBJECT (sc
),
685 "marked_deleted", GUINT_TO_POINTER (TRUE
));
687 set_selection_state (state
, FALSE
);
690 for (l
= state
->base
.sheet
->scenarios
; l
&& all_deleted
; l
= l
->next
) {
691 GnmScenario
*sc
= l
->data
;
692 if (!g_object_get_data (G_OBJECT (sc
), "marked_deleted"))
696 gtk_widget_set_sensitive
697 (state
->summary_button
, !all_deleted
);
701 scenarios_summary_clicked_cb (G_GNUC_UNUSED GtkWidget
*button
,
702 ScenariosState
*state
)
707 restore_old_values (state
);
709 results
= gnm_expr_entry_parse_as_list (
710 GNM_EXPR_ENTRY (state
->base
.input_entry
), state
->base
.sheet
);
712 if (results
== NULL
) {
713 go_gtk_notice_dialog (GTK_WINDOW (state
->base
.dialog
),
715 _("Results entry did not contain valid "
720 scenario_summary (GNM_WBC (state
->base
.wbcg
), state
->base
.sheet
,
721 results
, &new_sheet
);
723 state
->new_report_sheets
=
724 g_slist_prepend (state
->new_report_sheets
,
727 g_slist_free_full (results
, (GDestroyNotify
)value_release
);
731 cb_selection_changed (G_GNUC_UNUSED GtkTreeSelection
*selection
,
732 ScenariosState
*state
)
734 set_selection_state (state
, TRUE
);
738 init_scenario_buttons (ScenariosState
*state
)
742 go_gtk_builder_get_widget (state
->base
.gui
, "show_button");
743 if (state
->show_button
== NULL
)
745 g_signal_connect (G_OBJECT (state
->show_button
),
747 G_CALLBACK (scenarios_show_clicked_cb
), state
);
750 state
->delete_button
=
751 go_gtk_builder_get_widget (state
->base
.gui
, "delete_button");
752 if (state
->delete_button
== NULL
)
754 g_signal_connect (G_OBJECT (state
->delete_button
),
756 G_CALLBACK (scenarios_delete_clicked_cb
), state
);
759 state
->summary_button
=
760 go_gtk_builder_get_widget (state
->base
.gui
, "summary_button");
761 if (state
->summary_button
== NULL
)
763 g_signal_connect (G_OBJECT (state
->summary_button
),
765 G_CALLBACK (scenarios_summary_clicked_cb
),
768 set_selection_state (state
, FALSE
);
774 dialog_scenarios (WBCGtk
*wbcg
)
776 ScenariosState
*state
;
777 WorkbookControl
*wbc
;
780 GtkTreeSelection
*select
;
781 char const *error_str
= _("Could not create the Scenarios dialog.");
783 g_return_if_fail (wbcg
!= NULL
);
785 wbc
= GNM_WBC (wbcg
);
786 sheet
= wb_control_cur_sheet (wbc
);
788 state
= g_new (ScenariosState
, 1);
789 state
->new_report_sheets
= NULL
;
790 state
->current
= NULL
;
792 state
->base
.wb
= wb_control_get_workbook (wbc
);
794 if (dialog_tool_init (&state
->base
, wbcg
, sheet
,
795 GNUMERIC_HELP_LINK_SCENARIOS_VIEW
,
796 "res:ui/scenario-manager.ui", "Scenarios",
797 error_str
, "Scenarios",
798 G_CALLBACK (scenarios_ok_clicked_cb
),
799 G_CALLBACK (scenarios_cancel_clicked_cb
),
800 G_CALLBACK (scenarios_update_sensitivity_cb
),
804 if (init_scenario_buttons (state
))
807 state
->scenarios_treeview
= go_gtk_builder_get_widget
808 (state
->base
.gui
, "scenarios_treeview");
809 if (state
->scenarios_treeview
== NULL
)
812 w
= go_gtk_builder_get_widget (state
->base
.gui
, "changing_cells_entry");
815 gtk_widget_set_sensitive (w
, FALSE
);
816 w
= go_gtk_builder_get_widget (state
->base
.gui
, "comment_view");
820 gtk_widget_set_sensitive (w
, FALSE
);
822 if (state
->base
.sheet
->scenarios
== NULL
)
823 gtk_widget_set_sensitive
824 (state
->summary_button
, FALSE
);
826 update_scenarios_treeview (state
->scenarios_treeview
,
828 select
= gtk_tree_view_get_selection
829 (GTK_TREE_VIEW (state
->scenarios_treeview
));
830 g_signal_connect (select
, "changed",
831 G_CALLBACK (cb_selection_changed
), state
);
833 scenarios_update_sensitivity_cb (NULL
, state
);
834 gtk_widget_show (state
->base
.dialog
);
839 go_gtk_notice_dialog (wbcg_toplevel (wbcg
), GTK_MESSAGE_ERROR
,