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