1 /*
2 * dialog-consolidate.c : implementation of the consolidation dialog.
3 *
4 * Copyright (C) Almer S. Tigelaar <almer@gnome.org>
5 * Copyright (C) Andreas J. Guelzow <aguelzow@taliesin.ca>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, see <https://www.gnu.org/licenses/>.
19 */
20
21 #include <gnumeric-config.h>
22 #include <glib/gi18n-lib.h>
23 #include <gnumeric.h>
24 #include <dialogs/dialogs.h>
25 #include <dialogs/help.h>
26 #include <dialogs/tool-dialogs.h>
27
28 #include <commands.h>
29 #include <consolidate.h>
30 #include <func.h>
31 #include <gui-util.h>
32 #include <ranges.h>
33 #include <value.h>
34 #include <sheet-view.h>
35 #include <selection.h>
36 #include <widgets/gnm-expr-entry.h>
37 #include <widgets/gnm-cell-renderer-expr-entry.h>
38 #include <widgets/gnm-dao.h>
39 #include <wbc-gtk.h>
40 #include <dialogs/dao-gui-utils.h>
41 #include <tools/dao.h>
42
43 #include <string.h>
44
45
46 #define CONSOLIDATE_KEY "consolidate-dialog"
47
48 enum {
49 SOURCE_COLUMN,
50 PIXMAP_COLUMN,
51 IS_EDITABLE_COLUMN,
52 NUM_COLUMNS
53 };
54
55 typedef struct {
56 GnmGenericToolState base;
57
58 GtkComboBox *function;
59
60 GtkTreeView *source_view;
61 GtkTreeModel *source_areas;
62 GnmCellRendererExprEntry *cellrenderer;
63 GdkPixbuf *pixmap;
64
65 GtkButton *clear;
66 GtkButton *delete;
67
68 GtkCheckButton *labels_row;
69 GtkCheckButton *labels_col;
70 GtkCheckButton *labels_copy;
71
72 int areas_index; /* Select index in sources clist */
73 char *construct_error; /* If set an error occurred in construct_consolidate */
74 } ConsolidateState;
75
76 static void dialog_set_button_sensitivity (G_GNUC_UNUSED GtkWidget *dummy,
77 ConsolidateState *state);
78
79 /**
80 * adjust_source_areas
81 *
82 * ensures that we have exactly 2 empty rows
83 *
84 *
85 **/
86 static void
adjust_source_areas(ConsolidateState * state)87 adjust_source_areas (ConsolidateState *state)
88 {
89 int i = 0;
90 int cnt_empty = 2;
91 GtkTreeIter iter;
92
93 if (gtk_tree_model_get_iter_first
94 (state->source_areas, &iter)) {
95 do {
96 char *source;
97
98 gtk_tree_model_get (state->source_areas,
99 &iter,
100 SOURCE_COLUMN, &source,
101 -1);
102 if (strlen(source) == 0)
103 cnt_empty--;
104 g_free (source);
105 } while (gtk_tree_model_iter_next
106 (state->source_areas,&iter));
107 }
108
109 for (i = 0; i < cnt_empty; i++) {
110 gtk_list_store_append (GTK_LIST_STORE(state->source_areas),
111 &iter);
112 gtk_list_store_set (GTK_LIST_STORE(state->source_areas),
113 &iter,
114 IS_EDITABLE_COLUMN, TRUE,
115 SOURCE_COLUMN, "",
116 PIXMAP_COLUMN, state->pixmap,
117 -1);
118 }
119 dialog_set_button_sensitivity (NULL, state);
120 }
121
122 /**
123 * construct_consolidate:
124 *
125 * Builts a new Consolidate structure from the
126 * state of all the widgets in the dialog, this can
127 * be used to actually "execute" a consolidation
128 **/
129 static GnmConsolidate *
construct_consolidate(ConsolidateState * state,data_analysis_output_t * dao)130 construct_consolidate (ConsolidateState *state, data_analysis_output_t *dao)
131 {
132 GnmConsolidate *cs = gnm_consolidate_new ();
133 GnmConsolidateMode mode = 0;
134 char const *func;
135 GnmValue *range_value;
136 GtkTreeIter iter;
137 gboolean has_iter;
138
139 switch (gtk_combo_box_get_active (state->function)) {
140 case 0 : func = "SUM"; break;
141 case 1 : func = "MIN"; break;
142 case 2 : func = "MAX"; break;
143 case 3 : func = "AVERAGE"; break;
144 case 4 : func = "COUNT"; break;
145 case 5 : func = "PRODUCT"; break;
146 case 6 : func = "STDEV"; break;
147 case 7 : func = "STDEVP"; break;
148 case 8 : func = "VAR"; break;
149 case 9 : func = "VARP"; break;
150 default :
151 func = NULL;
152 g_warning ("Unknown function index!");
153 }
154
155 gnm_consolidate_set_function (cs, gnm_func_lookup (func, NULL));
156
157 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (state->labels_row)))
158 mode |= CONSOLIDATE_COL_LABELS;
159 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (state->labels_col)))
160 mode |= CONSOLIDATE_ROW_LABELS;
161
162 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (state->labels_copy)))
163 mode |= CONSOLIDATE_COPY_LABELS;
164 if (!dao_put_formulas (dao))
165 mode |= CONSOLIDATE_PUT_VALUES;
166
167 gnm_consolidate_set_mode (cs, mode);
168
169 g_return_val_if_fail (gtk_tree_model_iter_n_children
170 (state->source_areas,
171 NULL)> 2, NULL);
172
173 has_iter = gtk_tree_model_get_iter_first (state->source_areas,
174 &iter);
175 g_return_val_if_fail (has_iter, NULL);
176 do {
177 char *source;
178
179 gtk_tree_model_get (state->source_areas,
180 &iter,
181 SOURCE_COLUMN, &source,
182 -1);
183 if (strlen(source) != 0) {
184 range_value = value_new_cellrange_str (state->base.sheet, source);
185
186 if (range_value == NULL) {
187 state->construct_error = g_strdup_printf (
188 _("Specification %s "
189 "does not define a region"),
190 source);
191 g_free (source);
192 gnm_consolidate_free (cs, FALSE);
193 return NULL;
194 }
195 if (!gnm_consolidate_add_source (cs, range_value)) {
196 state->construct_error = g_strdup_printf (
197 _("Source region %s overlaps "
198 "with the destination region"),
199 source);
200 g_free (source);
201 gnm_consolidate_free (cs, FALSE);
202 return NULL;
203 }
204 }
205 g_free (source);
206 } while (gtk_tree_model_iter_next
207 (state->source_areas,&iter));
208
209 return cs;
210 }
211
212 /***************************************************************************************/
213
214 /**
215 * dialog_set_button_sensitivity:
216 * @dummy:
217 * @state:
218 *
219 **/
220 static void
dialog_set_button_sensitivity(G_GNUC_UNUSED GtkWidget * dummy,ConsolidateState * state)221 dialog_set_button_sensitivity (G_GNUC_UNUSED GtkWidget *dummy,
222 ConsolidateState *state)
223 {
224 gboolean ready;
225
226 ready = gnm_dao_is_ready (GNM_DAO (state->base.gdao))
227 && (gtk_tree_model_iter_n_children
228 (state->source_areas, NULL)> 2);
229 gtk_widget_set_sensitive (GTK_WIDGET (state->base.ok_button), ready);
230 return;
231 }
232
233 static void
cb_selection_changed(G_GNUC_UNUSED GtkTreeSelection * ignored,ConsolidateState * state)234 cb_selection_changed (G_GNUC_UNUSED GtkTreeSelection *ignored,
235 ConsolidateState *state)
236 {
237 GtkTreeIter iter;
238 GtkTreeSelection *selection = gtk_tree_view_get_selection (state->source_view);
239
240 gtk_widget_set_sensitive (GTK_WIDGET(state->delete),
241 gtk_tree_selection_get_selected (selection, NULL, &iter));
242 }
243
244
245 static void
cb_source_edited(G_GNUC_UNUSED GtkCellRendererText * cell,gchar * path_string,gchar * new_text,ConsolidateState * state)246 cb_source_edited (G_GNUC_UNUSED GtkCellRendererText *cell,
247 gchar *path_string,
248 gchar *new_text,
249 ConsolidateState *state)
250 {
251 GtkTreeIter iter;
252 GtkTreePath *path;
253
254 path = gtk_tree_path_new_from_string (path_string);
255
256 if (gtk_tree_model_get_iter (state->source_areas, &iter, path))
257 gtk_list_store_set (GTK_LIST_STORE(state->source_areas),
258 &iter, SOURCE_COLUMN, new_text, -1);
259 else
260 g_warning ("Did not get a valid iterator");
261
262 gtk_tree_path_free (path);
263 adjust_source_areas (state);
264 }
265
266 static void
cb_dialog_destroy(ConsolidateState * state)267 cb_dialog_destroy (ConsolidateState *state)
268 {
269 if (state->pixmap != NULL)
270 g_object_unref (state->pixmap);
271 if (state->construct_error != NULL) {
272 g_warning ("The construct error was not freed, this should not happen!");
273 g_free (state->construct_error);
274 }
275 }
276
277 static void
cb_consolidate_ok_clicked(GtkWidget * button,ConsolidateState * state)278 cb_consolidate_ok_clicked (GtkWidget *button, ConsolidateState *state)
279 {
280 GnmConsolidate *cs;
281 data_analysis_output_t *dao;
282
283 if (state->cellrenderer->entry)
284 gnm_cell_renderer_expr_entry_editing_done (
285 GTK_CELL_EDITABLE (state->cellrenderer->entry),
286 state->cellrenderer);
287
288 if (state->base.warning_dialog != NULL)
289 gtk_widget_destroy (state->base.warning_dialog);
290
291 dao = parse_output ((GnmGenericToolState *)state, NULL);
292 cs = construct_consolidate (state, dao);
293
294 /*
295 * If something went wrong consolidate_construct
296 * return NULL and sets the state->construct_error to
297 * a suitable error message
298 */
299 if (cs == NULL) {
300 go_gtk_notice_nonmodal_dialog (GTK_WINDOW (state->base.dialog),
301 &state->base.warning_dialog,
302 GTK_MESSAGE_ERROR,
303 "%s", state->construct_error);
304 g_free (state->construct_error);
305 g_free (dao);
306 state->construct_error = NULL;
307
308 return;
309 }
310
311 if (gnm_consolidate_check_destination (cs, dao)) {
312 if (!cmd_analysis_tool (GNM_WBC (state->base.wbcg),
313 state->base.sheet,
314 dao, cs, gnm_tool_consolidate_engine,
315 FALSE) &&
316 (button == state->base.ok_button))
317 gtk_widget_destroy (state->base.dialog);
318 } else {
319 go_gtk_notice_nonmodal_dialog (GTK_WINDOW (state->base.dialog),
320 &state->base.warning_dialog,
321 GTK_MESSAGE_ERROR,
322 _("The output range overlaps "
323 "with the input ranges."));
324 g_free (dao);
325 gnm_consolidate_free (cs, FALSE);
326 }
327 }
328
329 static void
cb_source_changed(G_GNUC_UNUSED GtkEntry * ignored,ConsolidateState * state)330 cb_source_changed (G_GNUC_UNUSED GtkEntry *ignored,
331 ConsolidateState *state)
332 {
333 g_return_if_fail (state != NULL);
334
335 }
336
337 static void
cb_clear_clicked(G_GNUC_UNUSED GtkButton * button,ConsolidateState * state)338 cb_clear_clicked (G_GNUC_UNUSED GtkButton *button,
339 ConsolidateState *state)
340 {
341 g_return_if_fail (state != NULL);
342
343 if (state->cellrenderer->entry)
344 gnm_cell_renderer_expr_entry_editing_done (
345 GTK_CELL_EDITABLE (state->cellrenderer->entry),
346 state->cellrenderer);
347
348 gtk_list_store_clear (GTK_LIST_STORE(state->source_areas));
349 adjust_source_areas (state);
350
351 dialog_set_button_sensitivity (NULL, state);
352 }
353
354 static void
cb_delete_clicked(G_GNUC_UNUSED GtkButton * button,ConsolidateState * state)355 cb_delete_clicked (G_GNUC_UNUSED GtkButton *button,
356 ConsolidateState *state)
357 {
358 GtkTreeIter sel_iter;
359 GtkTreeSelection *selection =
360 gtk_tree_view_get_selection (state->source_view);
361
362 if (state->cellrenderer->entry)
363 gnm_cell_renderer_expr_entry_editing_done (
364 GTK_CELL_EDITABLE (state->cellrenderer->entry),
365 state->cellrenderer);
366 if (!gtk_tree_selection_get_selected (selection, NULL, &sel_iter))
367 return;
368 gtk_list_store_remove (GTK_LIST_STORE(state->source_areas),
369 &sel_iter);
370 adjust_source_areas (state);
371
372 dialog_set_button_sensitivity (NULL, state);
373 }
374
375 static void
cb_labels_toggled(G_GNUC_UNUSED GtkCheckButton * button,ConsolidateState * state)376 cb_labels_toggled (G_GNUC_UNUSED GtkCheckButton *button,
377 ConsolidateState *state)
378 {
379 gboolean copy_labels =
380 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (state->labels_row)) ||
381 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (state->labels_col));
382
383 gtk_widget_set_sensitive (GTK_WIDGET (state->labels_copy), copy_labels);
384 if (!copy_labels)
385 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (state->labels_copy), FALSE);
386 }
387
388 /***************************************************************************************/
389
390 static void
connect_signal_labels_toggled(ConsolidateState * state,GtkCheckButton * button)391 connect_signal_labels_toggled (ConsolidateState *state, GtkCheckButton *button)
392 {
393 g_signal_connect (G_OBJECT (button),
394 "toggled",
395 G_CALLBACK (cb_labels_toggled), state);
396 }
397
398 static void
setup_widgets(ConsolidateState * state,GtkBuilder * gui)399 setup_widgets (ConsolidateState *state, GtkBuilder *gui)
400 {
401 GtkTreeViewColumn *column;
402 GtkTreeSelection *selection;
403 GtkCellRenderer *renderer;
404
405 state->function = go_gtk_builder_combo_box_init_text (gui, "function");
406 gtk_combo_box_set_active (state->function, 0);
407
408 /* Begin: Source Areas View*/
409 state->source_view = GTK_TREE_VIEW (go_gtk_builder_get_widget
410 (gui,
411 "source_treeview"));
412 state->source_areas = GTK_TREE_MODEL(gtk_list_store_new
413 (NUM_COLUMNS,
414 G_TYPE_STRING,
415 GDK_TYPE_PIXBUF,
416 G_TYPE_INT));
417 gtk_tree_view_set_model (state->source_view,
418 state->source_areas);
419 g_object_unref (state->source_areas);
420
421 selection = gtk_tree_view_get_selection
422 (state->source_view );
423 gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
424
425 renderer = gnm_cell_renderer_expr_entry_new (state->base.wbcg);
426 state->cellrenderer =
427 GNM_CELL_RENDERER_EXPR_ENTRY (renderer);
428 column = gtk_tree_view_column_new_with_attributes
429 ("", renderer,
430 "text", SOURCE_COLUMN,
431 "editable", IS_EDITABLE_COLUMN,
432 NULL);
433 g_signal_connect (G_OBJECT (renderer), "edited",
434 G_CALLBACK (cb_source_edited), state);
435 gtk_tree_view_column_set_expand (column, TRUE);
436 gtk_tree_view_append_column (state->source_view, column);
437 column = gtk_tree_view_column_new_with_attributes
438 ("", gtk_cell_renderer_pixbuf_new (),
439 "pixbuf", PIXMAP_COLUMN, NULL);
440 gtk_tree_view_append_column (state->source_view, column);
441 /* End: Source Areas View*/
442
443 state->clear = GTK_BUTTON (go_gtk_builder_get_widget (gui, "clear"));
444 state->delete = GTK_BUTTON (go_gtk_builder_get_widget (gui, "delete"));
445
446 state->labels_row = GTK_CHECK_BUTTON (go_gtk_builder_get_widget (gui, "labels_row"));
447 state->labels_col = GTK_CHECK_BUTTON (go_gtk_builder_get_widget (gui, "labels_col"));
448 state->labels_copy = GTK_CHECK_BUTTON (go_gtk_builder_get_widget (gui, "labels_copy"));
449
450 cb_selection_changed (NULL, state);
451 g_signal_connect (selection,
452 "changed",
453 G_CALLBACK (cb_selection_changed), state);
454 g_signal_connect (G_OBJECT (state->clear),
455 "clicked",
456 G_CALLBACK (cb_clear_clicked), state);
457 g_signal_connect (G_OBJECT (state->delete),
458 "clicked",
459 G_CALLBACK (cb_delete_clicked), state);
460
461 connect_signal_labels_toggled (state, state->labels_row);
462 connect_signal_labels_toggled (state, state->labels_col);
463 connect_signal_labels_toggled (state, state->labels_copy);
464
465 }
466
467 static gboolean
add_source_area(SheetView * sv,GnmRange const * r,gpointer closure)468 add_source_area (SheetView *sv, GnmRange const *r, gpointer closure)
469 {
470 ConsolidateState *state = closure;
471 char *range_name;
472 GtkTreeIter iter;
473
474 if (range_is_singleton (r))
475 return TRUE;
476
477 range_name = global_range_name (sv_sheet (sv), r);
478
479 gtk_list_store_prepend (GTK_LIST_STORE(state->source_areas),
480 &iter);
481 gtk_list_store_set (GTK_LIST_STORE(state->source_areas),
482 &iter,
483 IS_EDITABLE_COLUMN, TRUE,
484 SOURCE_COLUMN, range_name,
485 PIXMAP_COLUMN, state->pixmap,
486 -1);
487 g_free (range_name);
488
489 return TRUE;
490 }
491
492 /**
493 * dialog_consolidate_tool_init:
494 * @state:
495 *
496 * Create the dialog (guru).
497 *
498 **/
499 static void
dialog_consolidate_tool_init(ConsolidateState * state)500 dialog_consolidate_tool_init (ConsolidateState *state)
501 {
502 state->areas_index = -1;
503
504 setup_widgets (state, state->base.gui);
505 state->pixmap = go_gtk_widget_render_icon_pixbuf
506 (GTK_WIDGET (state->base.dialog),
507 "gnumeric-exprentry",
508 GTK_ICON_SIZE_LARGE_TOOLBAR);
509
510 /* Dynamic initialization */
511 cb_source_changed (NULL, state);
512 cb_labels_toggled (state->labels_row, state);
513
514 /*
515 * When there are non-singleton selections add them all to the
516 * source range list for convenience
517 */
518 sv_selection_foreach (state->base.sv, &add_source_area, state);
519
520 adjust_source_areas (state);
521 dialog_set_button_sensitivity (NULL, state);
522 state->base.state_destroy = (state_destroy_t)cb_dialog_destroy;
523 }
524
525 void
dialog_consolidate(WBCGtk * wbcg)526 dialog_consolidate (WBCGtk *wbcg)
527 {
528 ConsolidateState *state;
529 SheetView *sv;
530 Sheet *sheet;
531
532 g_return_if_fail (wbcg != NULL);
533 sv = wb_control_cur_sheet_view (GNM_WBC (wbcg));
534 sheet = sv_sheet (sv);
535
536 /* Only pop up one copy per workbook */
537 if (gnm_dialog_raise_if_exists (wbcg, CONSOLIDATE_KEY)) {
538 return;
539 }
540
541 /* Primary static initialization */
542 state = g_new0 (ConsolidateState, 1);
543
544 if (dialog_tool_init ((GnmGenericToolState *)state, wbcg, sheet,
545 GNUMERIC_HELP_LINK_CONSOLIDATE,
546 "res:ui/consolidate.ui", "Consolidate",
547 _("Could not create the Consolidate dialog."),
548 CONSOLIDATE_KEY,
549 G_CALLBACK (cb_consolidate_ok_clicked),
550 NULL,
551 G_CALLBACK (dialog_set_button_sensitivity),
552 0))
553 return;
554
555 gnm_dao_set_put (GNM_DAO (state->base.gdao), TRUE, TRUE);
556 dialog_consolidate_tool_init (state);
557 gtk_widget_show (GTK_WIDGET (state->base.dialog));
558 }
559