1 /*
2  * dialog-tabulate.c:
3  *   Dialog for making tables of function dependcies.
4  *
5  * Author:
6  *   COPYRIGHT (C) Morten Welinder (terra@gnome.org)
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License as
10  * published by the Free Software Foundation; either version 2 of the
11  * License, or (at your option) version 3.
12  *
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.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
21  * USA
22  */
23 
24 #include <gnumeric-config.h>
25 #include <glib/gi18n-lib.h>
26 #include <gnumeric.h>
27 #include <dialogs/dialogs.h>
28 #include <dialogs/help.h>
29 
30 #include <gui-util.h>
31 #include <widgets/gnm-expr-entry.h>
32 #include <tools/tabulate.h>
33 #include <wbc-gtk.h>
34 #include <ranges.h>
35 #include <value.h>
36 #include <sheet.h>
37 #include <mstyle.h>
38 #include <workbook.h>
39 #include <mathfunc.h>
40 #include <cell.h>
41 #include <commands.h>
42 #include <gnm-format.h>
43 #include <number-match.h>
44 #include <mstyle.h>
45 #include <style-border.h>
46 #include <sheet-style.h>
47 #include <style-color.h>
48 
49 #include <string.h>
50 
51 #define TABULATE_KEY "tabulate-dialog"
52 
53 /* ------------------------------------------------------------------------- */
54 
55 enum {
56 	COL_CELL = 0,
57 	COL_MIN,
58 	COL_MAX,
59 	COL_STEP
60 };
61 
62 typedef struct {
63 	WBCGtk *wbcg;
64 	Sheet *sheet;
65 
66 	GtkBuilder *gui;
67 	GtkDialog *dialog;
68 
69 	GtkGrid *grid;
70 	GnmExprEntry *resultrangetext;
71 } DialogState;
72 
73 static const char * const mode_group[] = {
74 	"mode_visual",
75 	"mode_coordinate",
76 	NULL
77 };
78 
79 /* ------------------------------------------------------------------------- */
80 
81 static void
non_model_dialog(WBCGtk * wbcg,GtkDialog * dialog,const char * key)82 non_model_dialog (WBCGtk *wbcg,
83 		  GtkDialog *dialog,
84 		  const char *key)
85 {
86 	gnm_keyed_dialog (wbcg, GTK_WINDOW (dialog), key);
87 
88 	gtk_widget_show (GTK_WIDGET (dialog));
89 }
90 
91 static GnmCell *
single_cell(Sheet * sheet,GnmExprEntry * gee)92 single_cell (Sheet *sheet, GnmExprEntry *gee)
93 {
94 	int col, row;
95 	gboolean issingle;
96 	GnmValue *v = gnm_expr_entry_parse_as_value (gee, sheet);
97 
98 	if (!v) return NULL;
99 
100 	col = v->v_range.cell.a.col;
101 	row = v->v_range.cell.a.row;
102 	issingle = (col == v->v_range.cell.b.col && row == v->v_range.cell.b.row);
103 
104 	value_release (v);
105 
106 	if (issingle)
107 		return sheet_cell_fetch (sheet, col, row);
108 	else
109 		return NULL;
110 }
111 
112 static int
get_grid_float_entry(GtkGrid * g,int y,int x,GnmCell * cell,gnm_float * number,GtkEntry ** wp,gboolean with_default,gnm_float default_float)113 get_grid_float_entry (GtkGrid *g, int y, int x, GnmCell *cell, gnm_float *number,
114 		       GtkEntry **wp, gboolean with_default, gnm_float default_float)
115 {
116 	GOFormat const *format;
117 	GtkWidget *w = gtk_grid_get_child_at (g, x, y + 1);
118 
119 	g_return_val_if_fail (GTK_IS_ENTRY (w), 3);
120 
121 	*wp = GTK_ENTRY (w);
122 	format = gnm_cell_get_format (cell);
123 
124 	return (with_default?
125 	        entry_to_float_with_format_default (*wp, number, TRUE, format,
126 	                                            default_float):
127 			entry_to_float_with_format (*wp, number, TRUE, format));
128 }
129 
130 static void
cb_dialog_destroy(DialogState * dd)131 cb_dialog_destroy (DialogState *dd)
132 {
133 	g_object_unref (dd->gui);
134 	memset (dd, 0, sizeof (*dd));
135 	g_free (dd);
136 }
137 
138 static void
cancel_clicked(G_GNUC_UNUSED GtkWidget * widget,DialogState * dd)139 cancel_clicked (G_GNUC_UNUSED GtkWidget *widget, DialogState *dd)
140 {
141 	GtkDialog *dialog = dd->dialog;
142 	gtk_widget_destroy (GTK_WIDGET (dialog));
143 }
144 
145 static void
tabulate_ok_clicked(G_GNUC_UNUSED GtkWidget * widget,DialogState * dd)146 tabulate_ok_clicked (G_GNUC_UNUSED GtkWidget *widget, DialogState *dd)
147 {
148 	GtkDialog *dialog = dd->dialog;
149 	GnmCell *resultcell;
150 	int dims = 0;
151 	int row;
152 	gboolean with_coordinates;
153 	GnmTabulateInfo *data;
154 	/* we might get the 4 below from the positon of some of the widgets inside the grid */
155 	int nrows = 4;
156 	GnmCell **cells;
157 	gnm_float *minima, *maxima, *steps;
158 
159 	cells = g_new (GnmCell *, nrows);
160 	minima = g_new (gnm_float, nrows);
161 	maxima = g_new (gnm_float, nrows);
162 	steps = g_new (gnm_float, nrows);
163 
164 	for (row = 1; row < nrows; row++) {
165 		GtkEntry *e_w;
166 		GnmExprEntry *w = GNM_EXPR_ENTRY (gtk_grid_get_child_at (dd->grid, COL_CELL, row + 1));
167 
168 		if (!w || gnm_expr_entry_is_blank (w))
169 			continue;
170 
171 		cells[dims] = single_cell (dd->sheet, w);
172 		if (!cells[dims]) {
173 			go_gtk_notice_dialog (GTK_WINDOW (dd->dialog),
174 					 GTK_MESSAGE_ERROR,
175 					 _("You should introduce a single valid cell as dependency cell"));
176 			gnm_expr_entry_grab_focus (GNM_EXPR_ENTRY (w), TRUE);
177 			goto error;
178 		}
179 		if (gnm_cell_has_expr (cells[dims])) {
180 			go_gtk_notice_dialog (GTK_WINDOW (dd->dialog),
181 					 GTK_MESSAGE_ERROR,
182 					 _("The dependency cells should not contain an expression"));
183 			gnm_expr_entry_grab_focus (GNM_EXPR_ENTRY (w), TRUE);
184 			goto error;
185 		}
186 
187 		if (get_grid_float_entry (dd->grid, row, COL_MIN, cells[dims],
188 					   &(minima[dims]), &e_w, FALSE, 0.0)) {
189 			go_gtk_notice_dialog (GTK_WINDOW (dd->dialog),
190 					 GTK_MESSAGE_ERROR,
191 					 _("You should introduce a valid number as minimum"));
192 			focus_on_entry (e_w);
193 			goto error;
194 		}
195 
196 		if (get_grid_float_entry (dd->grid, row, COL_MAX, cells[dims],
197 					   &(maxima[dims]), &e_w, FALSE, 0.0)) {
198 			go_gtk_notice_dialog (GTK_WINDOW (dd->dialog),
199 					 GTK_MESSAGE_ERROR,
200 					 _("You should introduce a valid number as maximum"));
201 			focus_on_entry (e_w);
202 			goto error;
203 		}
204 
205 		if (maxima[dims] < minima[dims]) {
206 			go_gtk_notice_dialog (GTK_WINDOW (dd->dialog),
207 					 GTK_MESSAGE_ERROR,
208 					 _("The maximum value should be bigger than the minimum"));
209 			focus_on_entry (e_w);
210 			goto error;
211 		}
212 
213 		if (get_grid_float_entry (dd->grid, row, COL_STEP, cells[dims],
214 					   &(steps[dims]), &e_w, TRUE, 1.0)) {
215 			go_gtk_notice_dialog (GTK_WINDOW (dd->dialog),
216 					 GTK_MESSAGE_ERROR,
217 					 _("You should introduce a valid number as step size"));
218 			focus_on_entry (e_w);
219 			goto error;
220 		}
221 
222 		if (steps[dims] <= 0) {
223 			go_gtk_notice_dialog (GTK_WINDOW (dd->dialog),
224 					 GTK_MESSAGE_ERROR,
225 					 _("The step size should be positive"));
226 			focus_on_entry (e_w);
227 			goto error;
228 		}
229 
230 		dims++;
231 	}
232 
233 	if (dims == 0) {
234 		go_gtk_notice_dialog (GTK_WINDOW (dd->dialog),
235 				 GTK_MESSAGE_ERROR,
236 				 _("You should introduce one or more dependency cells"));
237 		goto error;
238 	}
239 
240 	{
241 		resultcell = single_cell (dd->sheet, dd->resultrangetext);
242 
243 		if (!resultcell) {
244 			go_gtk_notice_dialog (GTK_WINDOW (dd->dialog),
245 					 GTK_MESSAGE_ERROR,
246 					 _("You should introduce a single valid cell as result cell"));
247 			gnm_expr_entry_grab_focus (dd->resultrangetext, TRUE);
248 			goto error;
249 		}
250 
251 		if (!gnm_cell_has_expr (resultcell)) {
252 			go_gtk_notice_dialog (GTK_WINDOW (dd->dialog),
253 					 GTK_MESSAGE_ERROR,
254 					 _("The target cell should contain an expression"));
255 			gnm_expr_entry_grab_focus (dd->resultrangetext, TRUE);
256 			goto error;
257 		}
258 	}
259 
260 	{
261 		int i = gnm_gui_group_value (dd->gui, mode_group);
262 		with_coordinates = (i == -1) ? TRUE : (gboolean)i;
263 	}
264 
265 	data = g_new (GnmTabulateInfo, 1);
266 	data->target = resultcell;
267 	data->dims = dims;
268 	data->cells = cells;
269 	data->minima = minima;
270 	data->maxima = maxima;
271 	data->steps = steps;
272 	data->with_coordinates = with_coordinates;
273 
274 	if (!cmd_tabulate (GNM_WBC (dd->wbcg), data)) {
275 		gtk_widget_destroy (GTK_WIDGET (dialog));
276 		return;
277 	}
278 
279 	g_free (data);
280  error:
281 	g_free (minima);
282 	g_free (maxima);
283 	g_free (steps);
284 	g_free (cells);
285 }
286 
287 void
dialog_tabulate(WBCGtk * wbcg,Sheet * sheet)288 dialog_tabulate (WBCGtk *wbcg, Sheet *sheet)
289 {
290 	GtkBuilder *gui;
291 	GtkDialog *dialog;
292 	DialogState *dd;
293 	int i;
294 
295 	g_return_if_fail (wbcg != NULL);
296 
297 	/* Only one guru per workbook. */
298 	if (wbc_gtk_get_guru (wbcg))
299 		return;
300 
301 	if (gnm_dialog_raise_if_exists (wbcg, TABULATE_KEY))
302 		return;
303 	gui = gnm_gtk_builder_load ("res:ui/tabulate.ui", NULL, GO_CMD_CONTEXT (wbcg));
304         if (gui == NULL)
305                 return;
306 
307 	dialog = GTK_DIALOG (go_gtk_builder_get_widget (gui, "tabulate_dialog"));
308 
309 	dd = g_new (DialogState, 1);
310 	dd->wbcg = wbcg;
311 	dd->gui = gui;
312 	dd->dialog = dialog;
313 	dd->sheet = sheet;
314 
315 	dd->grid = GTK_GRID (go_gtk_builder_get_widget (gui, "main-grid"));
316 	/* we might get the 4 below from the positon of some of the widgets inside the grid */
317 	for (i = 1; i < 4; i++) {
318 		GnmExprEntry *ge = gnm_expr_entry_new (wbcg, TRUE);
319 		gnm_expr_entry_set_flags (ge,
320 			GNM_EE_SINGLE_RANGE | GNM_EE_SHEET_OPTIONAL,
321 			GNM_EE_MASK);
322 
323 		gtk_grid_attach (dd->grid, GTK_WIDGET (ge), COL_CELL, i + 1, 1, 1);
324 		gtk_widget_set_margin_left (GTK_WIDGET (ge), 18);
325 		gtk_widget_show (GTK_WIDGET (ge));
326 	}
327 
328 	dd->resultrangetext = gnm_expr_entry_new (wbcg, TRUE);
329 	gnm_expr_entry_set_flags (dd->resultrangetext,
330 		GNM_EE_SINGLE_RANGE | GNM_EE_SHEET_OPTIONAL,
331 		GNM_EE_MASK);
332 	gtk_grid_attach (dd->grid, GTK_WIDGET (dd->resultrangetext), 0, 6, 4, 1);
333 	gtk_widget_set_margin_left (GTK_WIDGET (dd->resultrangetext), 18);
334 	gtk_widget_show (GTK_WIDGET (dd->resultrangetext));
335 
336 	g_signal_connect (G_OBJECT (go_gtk_builder_get_widget (gui, "ok_button")),
337 		"clicked",
338 		G_CALLBACK (tabulate_ok_clicked), dd);
339 
340 	g_signal_connect (G_OBJECT (go_gtk_builder_get_widget (gui, "cancel_button")),
341 		"clicked",
342 		G_CALLBACK (cancel_clicked), dd);
343 /* FIXME: Add correct helpfile address */
344 	gnm_init_help_button (
345 		go_gtk_builder_get_widget (gui, "help_button"),
346 		GNUMERIC_HELP_LINK_TABULATE);
347 	g_object_set_data_full (G_OBJECT (dialog),
348 		"state", dd, (GDestroyNotify) cb_dialog_destroy);
349 
350 	gnm_dialog_setup_destroy_handlers (dialog, wbcg,
351 					   GNM_DIALOG_DESTROY_SHEET_REMOVED);
352 
353 	gtk_widget_show_all (gtk_dialog_get_content_area (dialog));
354 	wbc_gtk_attach_guru (wbcg, GTK_WIDGET (dialog));
355 
356 	non_model_dialog (wbcg, dialog, TABULATE_KEY);
357 }
358 
359