dialog-formula-guru.c 28.5 KB
Newer Older
1
/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
Michael Meeks's avatar
Michael Meeks committed
2
/*
3 4
 * dialog-function-wizard.c:  The formula guru
 *
5
 * Authors:
6
 *  Jody Goldberg <jody@gnome.org>
7 8
 *  Andreas J. Guelzow <aguelzow@taliesin.ca>
 *
9
 * Copyright (C) 2000 Jody Goldberg (jody@gnome.org)
Michael Meeks's avatar
Michael Meeks committed
10
 *
11 12 13 14
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
Michael Meeks's avatar
Michael Meeks committed
15
 *
16 17 18 19 20 21 22
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
23
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
24
 * USA
Michael Meeks's avatar
Michael Meeks committed
25
 */
26
#include <gnumeric-config.h>
27
#include <glib/gi18n.h>
28 29
#include <gnumeric.h>
#include "dialogs.h"
30
#include "help.h"
31 32 33 34 35

#include <parse-util.h>
#include <gui-util.h>
#include <workbook.h>
#include <sheet.h>
36
#include <sheet-view.h>
37 38 39 40
#include <workbook-edit.h>
#include <workbook-control.h>
#include <cell.h>
#include <expr.h>
41
#include <expr-impl.h>
42
#include <func.h>
43
#include <gnm-format.h>
44
#include <widgets/gnumeric-expr-entry.h>
45
#include <widgets/gnumeric-cell-renderer-expr-entry.h>
46

47
#include <gdk/gdkkeysyms.h>
48 49 50 51
#include <gtk/gtktreestore.h>
#include <gtk/gtktreeview.h>
#include <gtk/gtktreeselection.h>
#include <gtk/gtktogglebutton.h>
52
#include <locale.h>
53
#include <string.h>
Michael Meeks's avatar
Michael Meeks committed
54

55 56 57
#define FORMULA_GURU_KEY "formula-guru-dialog"
#define FORMULA_GURU_KEY_DIALOG "formula-guru-dialog"

58
#define MIN_VAR_ARGS_DISPLAYED 2
59
#define AV_FORMULA_SIZE 100
60

61 62
typedef struct
{
63 64
	WorkbookControlGUI  *wbcg;
	Workbook  *wb;
65

66
	GladeXML  *gui;
67 68
	GtkWidget *dialog;
	GtkWidget *ok_button;
69 70
	GtkWidget *selector_button;
	GtkWidget *clear_button;
71
	GtkWidget *zoom_button;
72
	GtkWidget *array_button;
73
	GtkWidget *main_button_area;
74
	GtkTreePath* active_path;
Andreas J. Guelzow's avatar
Andreas J. Guelzow committed
75
	char * prefix;
76
	char * suffix;
Morten Welinder's avatar
Morten Welinder committed
77
	GnmParsePos  *pos;
78 79 80 81

	GtkTreeStore  *model;
	GtkTreeView   *treeview;

82 83 84 85 86
	gint old_height;
	gint old_width;
	gint old_height_request;
	gint old_width_request;

87
	GnumericCellRendererExprEntry *cellrenderer;
88
	GtkTreeViewColumn *column;
89 90 91 92 93 94 95 96 97 98 99
} FormulaGuruState;

enum {
	FUN_ARG_ENTRY,
	IS_NON_FUN,
	ARG_NAME,
	ARG_TYPE,
	MIN_ARG,
	MAX_ARG,
	FUNCTION,
	NUM_COLMNS
100
};
Michael Meeks's avatar
Michael Meeks committed
101

102
static void dialog_formula_guru_update_parent (GtkTreeIter *child, FormulaGuruState *state,
103
					       GtkTreePath *origin,
104
					       gint sel_start, gint sel_length);
105

Andreas J. Guelzow's avatar
Andreas J. Guelzow committed
106
static void
107 108
dialog_formula_guru_write (GString *text, FormulaGuruState *state, gint sel_start,
			   gint sel_length)
Andreas J. Guelzow's avatar
Andreas J. Guelzow committed
109 110 111 112
{
	GtkEntry *entry;

	entry = wbcg_get_entry (state->wbcg);
113 114
	if (state->prefix) {
		sel_start += g_utf8_strlen (state->prefix, -1);
115
		g_string_prepend (text, state->prefix);
116
	}
117 118
	if (state->suffix)
		g_string_append (text, state->suffix);
Andreas J. Guelzow's avatar
Andreas J. Guelzow committed
119
	gtk_entry_set_text (entry, text->str);
120
	gtk_editable_select_region (GTK_EDITABLE (entry), sel_start, sel_start + sel_length);
Andreas J. Guelzow's avatar
Andreas J. Guelzow committed
121 122
}

123 124 125 126 127
static void
dialog_formula_guru_delete_children (GtkTreeIter *parent, FormulaGuruState *state)
{
	GtkTreeIter iter;

128 129
	while (gtk_tree_model_iter_children (GTK_TREE_MODEL(state->model),
					     &iter, parent))
130 131 132
		gtk_tree_store_remove (state->model, &iter);
}

133
static void
134
dialog_formula_guru_update_this_parent (GtkTreeIter *parent, FormulaGuruState *state,
135
					GtkTreePath *origin, gint sel_start, gint sel_length)
136
{
137 138
	GString  *text = g_string_sized_new  (AV_FORMULA_SIZE);
	gboolean is_non_fun;
139
	GnmFunc const *fd;
140 141 142
	GtkTreeIter iter;
	gboolean not_first = FALSE;
	int arg_min, arg_num = 0;
143
	gboolean find_origin = TRUE;
144 145 146 147 148 149 150 151 152 153

	gtk_tree_model_get (GTK_TREE_MODEL(state->model), parent,
			    IS_NON_FUN, &is_non_fun,
			    FUNCTION, &fd,
			    MIN_ARG, &arg_min,
			    -1);

	g_return_if_fail (!is_non_fun);
	g_return_if_fail (fd != NULL);

154
	text = g_string_append (text, gnm_func_get_name (fd));
155
	text = g_string_append (text, "(");
156 157 158

	if (gtk_tree_model_iter_children (GTK_TREE_MODEL(state->model), &iter, parent)) {
		do {
159
			char *argument;
160 161 162
			gtk_tree_model_get (GTK_TREE_MODEL(state->model), &iter,
					    FUN_ARG_ENTRY, &argument,
					    -1);
163 164
			if ((argument == NULL  || g_utf8_strlen (argument, -1) == 0) && arg_num > arg_min) {
				g_free (argument);
165
				break;
166 167
			}

168 169 170
			if (not_first) {
				text = g_string_append_c (text, format_get_arg_sep ());
			}
171

172
			if (find_origin && origin != NULL) {
173
				GtkTreePath *b = gtk_tree_model_get_path
174 175 176 177
					(GTK_TREE_MODEL (state->model), &iter);
				if (0 == gtk_tree_path_compare (origin, b)) {
					sel_start += g_utf8_strlen (text->str, text->len);
					gtk_tree_path_free (origin);
178
					origin = gtk_tree_model_get_path
179 180 181 182 183 184
						(GTK_TREE_MODEL (state->model), parent);
					find_origin = FALSE;
				}
				gtk_tree_path_free (b);
			}
			if (argument && strlen (argument) > 0)
185 186 187 188
				text = g_string_append (text, argument);
			g_free (argument);
			not_first = TRUE;
			arg_num++;
189

190
		} while (gtk_tree_model_iter_next (GTK_TREE_MODEL(state->model), &iter));
191 192
	}

193
	text = g_string_append_c (text, ')');
194

195 196
	gtk_tree_store_set (state->model, parent,
			    FUN_ARG_ENTRY, text->str,
197
			    -1);
198 199 200 201 202
	if (origin == NULL) {
		sel_start = 0;
		sel_length = g_utf8_strlen (text->str, text->len);
		origin = gtk_tree_model_get_path (GTK_TREE_MODEL(state->model), parent);
	}
203

Andreas J. Guelzow's avatar
Andreas J. Guelzow committed
204
	if (0 ==  gtk_tree_store_iter_depth (state->model, parent))
205
		dialog_formula_guru_write (text, state, sel_start, sel_length);
Andreas J. Guelzow's avatar
Andreas J. Guelzow committed
206

207
	g_string_free (text, TRUE);
208

209
	dialog_formula_guru_update_parent (parent, state, origin, sel_start, sel_length);
210 211
}

Michael Meeks's avatar
Michael Meeks committed
212
static void
213
dialog_formula_guru_update_parent (GtkTreeIter *child, FormulaGuruState *state,
214
				   GtkTreePath *origin, gint sel_start, gint sel_length)
Michael Meeks's avatar
Michael Meeks committed
215
{
216
	GtkTreeIter iter;
217

218 219
	if (gtk_tree_model_iter_parent (GTK_TREE_MODEL (state->model), &iter,
					child)) {
220
		dialog_formula_guru_update_this_parent (&iter, state, origin, sel_start,
221
							sel_length);
222
	} else
223 224 225 226 227
		gtk_tree_path_free (origin);
}

static void
dialog_formula_guru_update_this_child (GtkTreeIter *child, FormulaGuruState *state,
228
				   GtkTreePath *origin, gint sel_start, gint sel_length)
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
{
	GtkTreeIter iter;
	char *text;

	if (gtk_tree_model_iter_parent (GTK_TREE_MODEL (state->model), &iter,
					child)) {
		if (origin == NULL) {
			sel_start = 0;
			gtk_tree_model_get (GTK_TREE_MODEL(state->model), child,
					    FUN_ARG_ENTRY, &text,
					    -1);
			sel_length = g_utf8_strlen (text, -1);
			g_free (text);
			origin = gtk_tree_model_get_path (GTK_TREE_MODEL(state->model), child);
		}
244
		dialog_formula_guru_update_this_parent (&iter, state, origin, sel_start,
245
						   sel_length);
Michael Meeks's avatar
Michael Meeks committed
246
	}
247 248 249
}


250
static void
251
dialog_formula_guru_adjust_children (GtkTreeIter *parent, GnmFunc const *fd,
252
				     FormulaGuruState *state)
253
{
254 255 256 257
	gboolean is_non_fun;
	GtkTreeIter iter;
	gint min_arg, max_arg, args = 0, i;
	char *arg_name;
258

259 260 261 262 263 264 265 266 267 268 269 270 271 272

	if (fd == NULL) {
		gtk_tree_model_get (GTK_TREE_MODEL(state->model), parent,
				    IS_NON_FUN, &is_non_fun,
				    FUNCTION, &fd,
				    -1);
		if (is_non_fun) {
			while  (gtk_tree_model_iter_children (GTK_TREE_MODEL(state->model),
						       &iter, parent))
				gtk_tree_store_remove (state->model, &iter);
			return;
		}
	}
	g_return_if_fail (fd != NULL);
273

274 275 276 277 278
	gtk_tree_model_get (GTK_TREE_MODEL(state->model), parent,
			    MIN_ARG, &min_arg,
			    MAX_ARG, &max_arg,
			    -1);
	if (max_arg == G_MAXINT) {
279
		args = MAX (MIN_VAR_ARGS_DISPLAYED + min_arg,
280 281
			    gtk_tree_model_iter_n_children (GTK_TREE_MODEL(state->model),
							    parent));
282
	} else
283
		args = max_arg;
284

285 286 287 288 289
	while  (gtk_tree_model_iter_nth_child (GTK_TREE_MODEL(state->model),
					       &iter, parent, args))
		gtk_tree_store_remove (state->model, &iter);
	for (i = 0; i < args; i++) {
		if (!gtk_tree_model_iter_nth_child (GTK_TREE_MODEL(state->model),
290
						    &iter, parent, i)) {
291
			gtk_tree_store_append (state->model, &iter, parent);
292 293 294 295 296 297 298 299
			gtk_tree_store_set (state->model, &iter,
					    FUN_ARG_ENTRY, "",
					    IS_NON_FUN, TRUE,
					    FUNCTION, NULL,
					    MIN_ARG, 0,
					    MAX_ARG, 0,
					    -1);
		}
300 301 302 303 304
		arg_name = function_def_get_arg_name (fd, i);
		if (i >= min_arg && arg_name != NULL) {
			char *mod_name = g_strdup_printf (_("[%s]"), arg_name);
			g_free (arg_name);
			arg_name = mod_name;
305
		}
306 307 308 309 310
		gtk_tree_store_set (state->model, &iter,
				    ARG_NAME, arg_name,
				    ARG_TYPE, function_def_get_arg_type_string (fd, i),
				    -1);
		g_free (arg_name);
Michael Meeks's avatar
Michael Meeks committed
311
	}
312

313
	dialog_formula_guru_update_this_parent (parent, state, NULL, 0, 0);
Michael Meeks's avatar
Michael Meeks committed
314 315
}

316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351

static void
dialog_formula_guru_adjust_varargs (GtkTreeIter *iter, FormulaGuruState *state)
{
	GtkTreeIter new_iter, parent;
	char *arg_name, *arg_type;
	gint max_arg;

	new_iter = *iter;
	if (!gtk_tree_model_iter_next (GTK_TREE_MODEL (state->model), &new_iter) &&
	    gtk_tree_model_iter_parent (GTK_TREE_MODEL (state->model), &parent, iter)) {
		gtk_tree_model_get (GTK_TREE_MODEL (state->model), &parent,
				    MAX_ARG, &max_arg,
				    -1);
		if (max_arg == G_MAXINT) {
			gtk_tree_model_get (GTK_TREE_MODEL (state->model), iter,
					    ARG_NAME, &arg_name,
					    ARG_TYPE, &arg_type,
					    -1);
			gtk_tree_store_insert_after (state->model, &new_iter, &parent, iter);
			gtk_tree_store_set (state->model, &new_iter,
					    FUN_ARG_ENTRY, "",
					    IS_NON_FUN, TRUE,
					    FUNCTION, NULL,
					    ARG_NAME, arg_name,
					    ARG_TYPE, arg_type,
					    MIN_ARG, 0,
					    MAX_ARG, 0,
					    -1);
			g_free (arg_name);
			g_free (arg_type);
		}
	}
}


352
static void
353
dialog_formula_guru_load_fd (GtkTreePath *path, GnmFunc const *fd,
354
			     FormulaGuruState *state)
355
{
356 357 358 359 360 361
	GtkTreeIter iter;
	TokenizedHelp *help = tokenized_help_new (fd);
	char const *f_syntax = tokenized_help_find (help, "SYNTAX");
	gint min_arg, max_arg;
	GtkTreePath *new_path;

362
	if (path == NULL) {
363
		gtk_tree_store_clear (state->model);
364
		gtk_tree_store_append (state->model, &iter, NULL);
365 366
	} else if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (state->model), &iter, path)) {
		GtkTreePath *new_path = gtk_tree_path_copy (path);
367 368
		if (gtk_tree_path_prev (new_path) &&
		    gtk_tree_model_get_iter (GTK_TREE_MODEL (state->model),
369 370
				      &iter, new_path)) {
			dialog_formula_guru_adjust_varargs (&iter, state);
371
			if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (state->model),
372 373 374 375 376
						      &iter, path)) {
				gtk_tree_store_clear (state->model);
				gtk_tree_path_free (new_path);
				return;
			}
377
 		}
378 379
		gtk_tree_path_free (new_path);
	}
380 381 382 383 384 385 386 387 388 389 390 391 392

	function_def_count_args (fd, &min_arg, &max_arg);

	gtk_tree_store_set (state->model, &iter,
			    FUN_ARG_ENTRY, f_syntax,
			    IS_NON_FUN, FALSE,
			    FUNCTION, fd,
			    MIN_ARG, min_arg,
			    MAX_ARG, max_arg,
			    -1);
	tokenized_help_destroy (help);

	dialog_formula_guru_adjust_children (&iter, fd, state);
393 394
	dialog_formula_guru_adjust_varargs (&iter, state);

395 396 397 398
	new_path = gtk_tree_model_get_path (GTK_TREE_MODEL (state->model),
                                             &iter);
	gtk_tree_view_expand_row (state->treeview, new_path, FALSE);
	gtk_tree_path_free (new_path);
399

400

401 402
}

403 404 405 406 407
static void
dialog_formula_guru_load_string (GtkTreePath * path,
				 char const *argument, FormulaGuruState *state)
{
	GtkTreeIter iter;
408
	gboolean okay = TRUE;
409 410 411

	g_return_if_fail (path != NULL);

412
	if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (state->model),
413 414 415
				      &iter, path)) {
		GtkTreePath *new_path = gtk_tree_path_copy (path);

416 417
		if (gtk_tree_path_prev (new_path) &&
		    gtk_tree_model_get_iter (GTK_TREE_MODEL (state->model),
418 419
				      &iter, new_path)) {
			dialog_formula_guru_adjust_varargs (&iter, state);
420
			okay = gtk_tree_model_get_iter (GTK_TREE_MODEL (state->model),
421 422 423 424 425 426
							&iter, path);
 		} else
			okay = FALSE;
		gtk_tree_path_free (new_path);
	}

427
	g_return_if_fail (okay);
428 429 430 431 432 433 434 435 436 437

	dialog_formula_guru_delete_children (&iter, state);
	gtk_tree_store_set (state->model, &iter,
			    FUN_ARG_ENTRY, argument ? argument : "",
			    IS_NON_FUN, TRUE,
			    FUNCTION, NULL,
			    MIN_ARG, 0,
			    MAX_ARG, 0,
			    -1);

438 439
	dialog_formula_guru_update_parent (&iter, state, gtk_tree_model_get_path
					   (GTK_TREE_MODEL (state->model), &iter),
440
					   0, argument ? g_utf8_strlen (argument, -1) : 0);
441 442 443
}

static void
444
dialog_formula_guru_load_expr (GtkTreePath const *parent_path, gint child_num,
445
			       GnmExpr const *expr, FormulaGuruState *state)
446 447 448 449
{
	GtkTreePath *path;
	char *text;
	GSList *args;
450
	GtkTreeIter iter;
451 452 453 454 455 456 457 458 459 460 461
	int i;

	if (parent_path == NULL)
		path = gtk_tree_path_new_first ();
	else {
		/* gtk_tree_path_copy should have a const argument */
		path = gtk_tree_path_copy ((GtkTreePath *) parent_path);
		gtk_tree_path_append_index (path, child_num);
	}

	switch (expr->any.oper) {
462
	case GNM_EXPR_OP_FUNCALL:
463
		dialog_formula_guru_load_fd (path, expr->func.func, state);
464
		for (args = expr->func.arg_list, i = 0; args; args = args->next, i++)
465 466
			dialog_formula_guru_load_expr (path, i,
						       (GnmExpr const *) args->data,
467
						       state);
468 469 470 471
		gtk_tree_path_append_index (path, i - 1);
		if (gtk_tree_model_get_iter (GTK_TREE_MODEL (state->model),
                                             &iter, path))
			dialog_formula_guru_adjust_varargs (&iter, state);
472

473
		break;
474 475
	case GNM_EXPR_OP_ANY_BINARY:
	case GNM_EXPR_OP_UNARY_NEG:
476
	default:
477 478
		text = gnm_expr_as_string (expr, state->pos,
			gnm_expr_conventions_default);
479 480 481
		dialog_formula_guru_load_string (path, text, state);
		g_free (text);
		break;
482

483 484 485 486
	}
	gtk_tree_path_free (path);
}

487 488 489 490 491 492 493 494 495

/**
 * dialog_function_select_destroy:
 * @window:
 * @state:
 *
 * Destroy the dialog and associated data structures.
 *
 **/
496 497
static void
dialog_formula_guru_destroy (FormulaGuruState *state)
498
{
499
	wbcg_edit_detach_guru (state->wbcg);
500
	wbcg_edit_finish (state->wbcg, WBC_EDIT_REJECT, NULL);
501

Andreas J. Guelzow's avatar
Andreas J. Guelzow committed
502 503
	g_free (state->prefix);
	state->prefix = NULL;
504 505 506 507
	g_free (state->suffix);
	state->suffix = NULL;
	g_free (state->pos);
	state->pos = NULL;
Andreas J. Guelzow's avatar
Andreas J. Guelzow committed
508

509 510 511
	if (state->gui != NULL) {
		g_object_unref (G_OBJECT (state->gui));
		state->gui = NULL;
512
	}
513 514
	state->dialog = NULL;
	g_free (state);
515 516
}

Andreas J. Guelzow's avatar
Andreas J. Guelzow committed
517

518
static void
519
cb_dialog_formula_guru_cancel_clicked (FormulaGuruState *state)
520
{
521
	wbcg_edit_finish (state->wbcg, WBC_EDIT_REJECT, NULL);
522 523
}

524 525 526 527 528 529
static void
cb_dialog_formula_guru_zoom_toggled (GtkWidget *button, FormulaGuruState *state)
{
	GtkTreeSelection *selection = gtk_tree_view_get_selection (state->treeview);
	GtkTreeIter iter;
	GtkTreePath *path;
530

531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558

	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) {
		gtk_widget_hide (state->main_button_area);
		gtk_widget_hide (state->clear_button);
		gtk_widget_hide (state->selector_button);
		gtk_tree_view_set_headers_visible (state->treeview, FALSE);
		gtk_widget_get_size_request (state->dialog,
					     &state->old_width_request,
					     &state->old_height_request);
		gtk_window_get_size (GTK_WINDOW (state->dialog),
				     &state->old_width,
				     &state->old_height);
		gtk_widget_set_size_request (state->dialog,state->old_width_request,100);

		/* FIXME: the ideal `shrunk size' should probably not be hardcoded.*/
		gtk_window_resize (GTK_WINDOW (state->dialog),state->old_width_request,100);
		gtk_window_set_resizable (GTK_WINDOW (state->dialog), FALSE);
	} else {
		gtk_widget_show (state->main_button_area);
		gtk_widget_show (state->clear_button);
		gtk_widget_show (state->selector_button);
		gtk_tree_view_set_headers_visible (state->treeview, TRUE);
		gtk_window_set_resizable (GTK_WINDOW (state->dialog), TRUE);
		gtk_widget_set_size_request (state->dialog,
					     state->old_width_request,
					     state->old_height_request);
		gtk_window_resize (GTK_WINDOW (state->dialog), state->old_width,
				   state->old_height);
559
	}
560 561 562 563 564 565 566 567 568 569 570 571
	/* FIXME: this should keep the selection in sight, unfortunately it does not for */
	/* the size reduction case.                                                      */
	if (gtk_tree_selection_get_selected (selection, NULL, &iter)) {
		path = gtk_tree_model_get_path (GTK_TREE_MODEL (state->model), &iter);
		gtk_tree_view_scroll_to_cell (state->treeview, path, NULL,
                                             FALSE, 0, 0);
		gtk_tree_path_free (path);
	}

	return;
}

572 573 574 575 576 577
/**
 * cb_dialog_formula_guru_selector_clicked:
 * @button:
 * @state:
 *
 **/
578
static void
Morten Welinder's avatar
Morten Welinder committed
579
cb_dialog_formula_guru_selector_clicked (G_GNUC_UNUSED GtkWidget *button,
580
					 FormulaGuruState *state)
581
{
582 583 584
	GtkTreeSelection *selection = gtk_tree_view_get_selection (state->treeview);
	GtkTreeModel *model;
	GtkTreeIter iter;
585

586
	g_return_if_fail (state->active_path == NULL);
587

588 589 590 591 592 593
	if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
		state->active_path = gtk_tree_model_get_path (model, &iter);
		gtk_widget_hide (state->dialog);
		dialog_function_select (state->wbcg, FORMULA_GURU_KEY);
	} else
	    g_warning ("We should never be here!?");
594

595
	return;
596 597
}

598
/**
Andreas J. Guelzow's avatar
Andreas J. Guelzow committed
599
 * cb_dialog_formula_guru_clear_clicked:
600 601 602 603
 * @button:
 * @state:
 *
 **/
604
static void
Morten Welinder's avatar
Morten Welinder committed
605
cb_dialog_formula_guru_clear_clicked (G_GNUC_UNUSED GtkWidget *button,
606
				      FormulaGuruState *state)
607
{
608 609
	GtkTreeSelection *selection = gtk_tree_view_get_selection (state->treeview);
	GtkTreeModel *model;
610
	GtkTreeIter parent;
611 612 613 614 615 616 617 618 619 620 621

	g_return_if_fail (state->active_path == NULL);

	if (gtk_tree_selection_get_selected (selection, &model, &parent)) {
		gtk_tree_store_set (state->model, &parent,
				    FUN_ARG_ENTRY, "",
				    IS_NON_FUN, TRUE,
				    FUNCTION, NULL,
				    MIN_ARG, 0,
				    MAX_ARG, 0,
				    -1);
622
		dialog_formula_guru_delete_children (&parent, state);
623
		dialog_formula_guru_update_parent (&parent, state, gtk_tree_model_get_path
624
					   (GTK_TREE_MODEL (state->model), &parent), 0, 0);
625 626 627
	} else
	    g_warning ("We should never be here!?");
	return;
628 629
}

630 631 632 633 634 635
static gboolean
dialog_formula_guru_is_array (FormulaGuruState *state)
{
	return gtk_toggle_button_get_active 
		(GTK_TOGGLE_BUTTON (state->array_button));
}
636 637 638 639 640 641 642 643

/**
 * cb_dialog_formula_guru_ok_clicked:
 * @button:
 * @state:
 *
 * Close (destroy) the dialog
 **/
644
static void
Morten Welinder's avatar
Morten Welinder committed
645
cb_dialog_formula_guru_ok_clicked (G_GNUC_UNUSED GtkWidget *button,
646
				   FormulaGuruState *state)
Michael Meeks's avatar
Michael Meeks committed
647
{
648 649 650 651
	if (state->cellrenderer->entry)
		gnumeric_cell_renderer_expr_entry_editing_done (
			GTK_CELL_EDITABLE (state->cellrenderer->entry),
			state->cellrenderer);
652 653 654 655
	wbcg_edit_finish (state->wbcg, 
			  dialog_formula_guru_is_array (state)
			  ? WBC_EDIT_ACCEPT_ARRAY
			  : WBC_EDIT_ACCEPT, NULL);
Michael Meeks's avatar
Michael Meeks committed
656 657
}

658
static void
659
cb_dialog_formula_guru_selection_changed (GtkTreeSelection *the_selection,
660
					     FormulaGuruState *state)
Michael Meeks's avatar
Michael Meeks committed
661
{
662 663
	GtkTreeIter iter;
	GtkTreeModel *model;
664

665 666 667
	if (!gtk_tree_selection_get_selected (the_selection, &model, &iter)) {
		gtk_widget_set_sensitive (state->clear_button, FALSE);
		gtk_widget_set_sensitive (state->selector_button, FALSE);
668
		return;
669 670
	}

671
	gtk_widget_set_sensitive (state->clear_button,
672 673 674
				  0 != gtk_tree_store_iter_depth (state->model,
								  &iter));
	gtk_widget_set_sensitive (state->selector_button, TRUE);
675 676
	dialog_formula_guru_update_this_child (&iter, state,
						NULL, 0, 0);
Michael Meeks's avatar
Michael Meeks committed
677 678
}

679
/* We shouldn't need that if it weren't for a GTK+ bug*/
680
static void
Morten Welinder's avatar
Morten Welinder committed
681 682 683
cb_dialog_formula_guru_row_collapsed (G_GNUC_UNUSED GtkTreeView *treeview,
				      G_GNUC_UNUSED GtkTreeIter *iter,
				      G_GNUC_UNUSED GtkTreePath *path,
684
				      FormulaGuruState *state)
685
{
686 687 688
	GtkTreeSelection *selection = gtk_tree_view_get_selection (state->treeview);

	cb_dialog_formula_guru_selection_changed (selection, state);
689 690
}

691

692
static void
Morten Welinder's avatar
Morten Welinder committed
693
cb_dialog_formula_guru_edited (G_GNUC_UNUSED GtkCellRendererText *cell,
694 695 696
	gchar               *path_string,
	gchar               *new_text,
        FormulaGuruState    *state)
697
{
698 699
	GtkTreeIter iter;
	GtkTreePath *path;
700

701
	path = gtk_tree_path_new_from_string (path_string);
702

703 704
	gtk_tree_model_get_iter (GTK_TREE_MODEL (state->model), &iter, path);
	gtk_tree_store_set (state->model, &iter, FUN_ARG_ENTRY, new_text, -1);
705

706 707 708
	if (g_utf8_strlen (new_text, -1) > 0)
		dialog_formula_guru_adjust_varargs (&iter, state);

709
	gtk_tree_path_free (path);
710

711 712
	dialog_formula_guru_update_parent (&iter, state, gtk_tree_model_get_path
					   (GTK_TREE_MODEL (state->model), &iter),
713
					   0, g_utf8_strlen (new_text, -1));
714 715
}

716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781
/* Bad bad hack to be removed with Gtk2.2 */
/* The idea to this code is due to Jonathan Blandford */

typedef struct
{
	GtkTreePath *path;
	FormulaGuruState *state;
} IdleData;

static gboolean
real_start_editing_cb (IdleData *idle_data)
{
  gtk_widget_grab_focus (GTK_WIDGET (idle_data->state->treeview));
  gtk_tree_view_set_cursor (idle_data->state->treeview,
			    idle_data->path,
			    idle_data->state->column,
			    TRUE);

  gtk_tree_path_free (idle_data->path);
  g_free (idle_data);
  return FALSE;
}

static gboolean
start_editing_cb (GtkTreeView      *tree_view,
		  GdkEventButton   *event,
		  FormulaGuruState *state)
{
  GtkTreePath *path;
  GtkTreeIter iter;

  if (event->window != gtk_tree_view_get_bin_window (tree_view))
    return FALSE;
  if (state->treeview != tree_view)
    return FALSE;

  if (gtk_tree_view_get_path_at_pos (tree_view,
				     (gint) event->x,
				     (gint) event->y,
				     &path, NULL,
				     NULL, NULL) &&
      gtk_tree_model_get_iter (GTK_TREE_MODEL (state->model),
			       &iter, path))
    {
      IdleData *idle_data;
      gboolean is_non_fun;

      gtk_tree_model_get (GTK_TREE_MODEL (state->model), &iter,
			  IS_NON_FUN, &is_non_fun,
			  -1);

      if (!is_non_fun)
	      return FALSE;

      idle_data = g_new (IdleData, 1);
      idle_data->path = path;
      idle_data->state = state;

      g_signal_stop_emission_by_name (G_OBJECT (tree_view), "button_press_event");
      g_idle_add ((GSourceFunc) real_start_editing_cb, idle_data);
      return TRUE;
    }
  return FALSE;
}

/* End of bad bad hack*/
782

783
static gboolean
784
dialog_formula_guru_init (FormulaGuruState *state)
785
{
786 787 788 789 790
	GtkWidget *scrolled;
	GtkTreeViewColumn *column;
	GtkTreeSelection *selection;
	GtkCellRenderer *renderer;

791
	g_object_set_data (G_OBJECT (state->dialog), FORMULA_GURU_KEY_DIALOG,
792 793 794 795 796
			   state);

	/* Set-up treeview */
	scrolled = glade_xml_get_widget (state->gui, "scrolled");
	state->model = gtk_tree_store_new (NUM_COLMNS, G_TYPE_STRING, G_TYPE_BOOLEAN,
797
					   G_TYPE_STRING, G_TYPE_STRING,
798 799 800 801 802 803 804 805 806
					   G_TYPE_INT, G_TYPE_INT, G_TYPE_POINTER);
	state->treeview = GTK_TREE_VIEW (
		gtk_tree_view_new_with_model (GTK_TREE_MODEL (state->model)));
	g_signal_connect (state->treeview,
		"row_collapsed",
		G_CALLBACK (cb_dialog_formula_guru_row_collapsed), state);
	selection = gtk_tree_view_get_selection (state->treeview);
	gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
	g_signal_connect (selection,
807
		"changed",
808 809 810
		G_CALLBACK (cb_dialog_formula_guru_selection_changed), state);

	column = gtk_tree_view_column_new_with_attributes (_("Name"),
811
							   gnumeric_cell_renderer_text_new (),
812 813 814
							   "text", ARG_NAME, NULL);
	gtk_tree_view_append_column (state->treeview, column);
	column = gtk_tree_view_column_new_with_attributes (_("Type"),
815
							   gnumeric_cell_renderer_text_new (),
816 817
							   "text", ARG_TYPE, NULL);
	gtk_tree_view_append_column (state->treeview, column);
818
	renderer = gnumeric_cell_renderer_expr_entry_new (state->wbcg);
819
	state->cellrenderer = GNUMERIC_CELL_RENDERER_EXPR_ENTRY (renderer);
820 821 822 823
	g_signal_connect (G_OBJECT (renderer), "edited",
			  G_CALLBACK (cb_dialog_formula_guru_edited), state);
	column = gtk_tree_view_column_new_with_attributes (_("Function/Argument"),
							   renderer,
824
							   "text", FUN_ARG_ENTRY,
825 826
							   "editable", IS_NON_FUN,
							   NULL);
827
	state->column = column;
828 829 830
	gtk_tree_view_append_column (state->treeview, column);
	gtk_tree_view_set_headers_visible (state->treeview, TRUE);
	gtk_container_add (GTK_CONTAINER (scrolled), GTK_WIDGET (state->treeview));
831 832 833 834

	g_signal_connect (state->treeview,
			  "button_press_event",
			  G_CALLBACK (start_editing_cb), state),
835

836 837
	/* Finished set-up of treeview */

838 839 840 841
	state->array_button = glade_xml_get_widget (state->gui, 
						    "array_button");
	gtk_widget_set_sensitive (state->array_button, TRUE);

842
	state->ok_button = glade_xml_get_widget (state->gui, "ok_button");
Andreas J. Guelzow's avatar
Andreas J. Guelzow committed
843
	gtk_widget_set_sensitive (state->ok_button, TRUE);
844 845 846
	g_signal_connect (G_OBJECT (state->ok_button),
		"clicked",
		G_CALLBACK (cb_dialog_formula_guru_ok_clicked), state);
847

848 849 850 851 852
	state->selector_button = glade_xml_get_widget (state->gui, "select_func");
	gtk_widget_set_sensitive (state->selector_button, FALSE);
	g_signal_connect (G_OBJECT (state->selector_button),
		"clicked",
		G_CALLBACK (cb_dialog_formula_guru_selector_clicked), state);
853

854 855 856 857 858
	state->clear_button = glade_xml_get_widget (state->gui, "trash");
	gtk_widget_set_sensitive (state->clear_button, FALSE);
	g_signal_connect (G_OBJECT (state->clear_button),
		"clicked",
		G_CALLBACK (cb_dialog_formula_guru_clear_clicked), state);
859

860 861 862 863 864 865 866 867
	state->zoom_button = glade_xml_get_widget (state->gui, "zoom");
	gtk_widget_set_sensitive (state->zoom_button, TRUE);
	g_signal_connect (G_OBJECT (state->zoom_button),
		"toggled",
		G_CALLBACK (cb_dialog_formula_guru_zoom_toggled), state);

	state->main_button_area = glade_xml_get_widget (state->gui, "dialog-action_area2");

868
	g_signal_connect_swapped (G_OBJECT (glade_xml_get_widget (state->gui, "cancel_button")),
869 870 871 872 873
		"clicked",
		G_CALLBACK (cb_dialog_formula_guru_cancel_clicked), state);

	gnumeric_init_help_button (
		glade_xml_get_widget (state->gui, "help_button"),
874
		GNUMERIC_HELP_LINK_FORMULA_GURU);
875 876
	g_object_set_data_full (G_OBJECT (state->dialog),
		"state", state, (GDestroyNotify) dialog_formula_guru_destroy);
Jody Goldberg's avatar
Jody Goldberg committed
877

878
	wbcg_edit_attach_guru (state->wbcg, state->dialog);
879 880

	return FALSE;
Jody Goldberg's avatar
Jody Goldberg committed
881 882
}

883 884 885 886 887
static void
dialog_formula_guru_show (FormulaGuruState *state)
{
	GtkTreeIter iter;

888
	if ((!gtk_tree_model_get_iter_first   (GTK_TREE_MODEL (state->model), &iter)) ||
889
	    gtk_tree_model_iter_n_children (GTK_TREE_MODEL(state->model), &iter) == 0)
890
		wbcg_edit_finish (state->wbcg, WBC_EDIT_ACCEPT, NULL);
891 892 893 894
	else
		gtk_widget_show_all (state->dialog);
}

895
/**
896
 * dialog_formula_guru
897
 * @wbcg : The workbook to use as a parent window.
898
 *
899
 * Pop up a function selector then a formula guru.
900
 */
Jody Goldberg's avatar
Jody Goldberg committed
901
void
902
dialog_formula_guru (WorkbookControlGUI *wbcg, GnmFunc const *fd)
Jody Goldberg's avatar
Jody Goldberg committed
903
{
Jody Goldberg's avatar
Jody Goldberg committed
904
	SheetView *sv;
905
	GladeXML  *gui;
906
	GnmCell	  *cell;
907
	GtkWidget *dialog;
908
	FormulaGuruState *state;
909
	GnmExpr const *expr = NULL;
Jody Goldberg's avatar
Jody Goldberg committed
910

911
	g_return_if_fail (wbcg != NULL);
Michael Meeks's avatar
Michael Meeks committed
912

913 914 915 916 917 918 919 920 921 922 923 924 925
	dialog = gnumeric_dialog_raise_if_exists (wbcg, FORMULA_GURU_KEY);

	if (dialog) {
		/* We already exist */
		state = g_object_get_data (G_OBJECT (dialog),
					   FORMULA_GURU_KEY_DIALOG);
		if (fd) {
			if (state->active_path) {
				dialog_formula_guru_load_fd (state->active_path, fd, state);
				gtk_tree_path_free (state->active_path);
				state->active_path = NULL;
			} else
				dialog_formula_guru_load_fd (NULL, fd, state);
926
			dialog_formula_guru_show (state);
927 928 929 930 931
		} else {
			if (state->active_path) {
				gtk_tree_path_free (state->active_path);
				state->active_path = NULL;
			}
932

933 934
			if (0 == gtk_tree_model_iter_n_children (GTK_TREE_MODEL(state->model),
								 NULL))
935 936 937
				gtk_widget_destroy (state->dialog);
			else
				dialog_formula_guru_show (state);
938 939
		}
		return;
940
	}
941

942
	/* Get the dialog and check for errors */
943
	gui = gnm_glade_xml_new (GO_CMD_CONTEXT (wbcg),
944 945 946 947
		"formula-guru.glade", NULL, NULL);
	if (gui == NULL)
		return;

948 949
	state = g_new (FormulaGuruState, 1);
	state->wbcg  = wbcg;
950 951
	state->wb    = wb_control_workbook (WORKBOOK_CONTROL (wbcg));
	state->gui   = gui;
952
	state->active_path = NULL;
953
	state->pos = NULL;
954

Jody Goldberg's avatar
Jody Goldberg committed
955 956
	sv = wb_control_cur_sheet_view (WORKBOOK_CONTROL (wbcg));
	cell = sheet_cell_get (sv_sheet (sv), sv->edit_pos.col, sv->edit_pos.row);
957 958 959
	if (cell != NULL && cell_has_expr (cell))
		expr = gnm_expr_first_func (cell->base.expression);

960 961
	if (expr == NULL) {
		wbcg_edit_start (wbcg, TRUE, TRUE);
962
		state->prefix = g_strdup ("=");
963 964 965 966 967
		state->suffix = NULL;
	} else {
		char const *sub_str;
		char const *full_str = gtk_entry_get_text (wbcg_get_entry (wbcg));
		char *func_str;
968

Morten Welinder's avatar
Morten Welinder committed
969
		state->pos = g_new (GnmParsePos, 1);
970
		func_str = gnm_expr_as_string (expr,
971 972
			parse_pos_init_cell (state->pos, cell),
			gnm_expr_conventions_default);
973 974

		wbcg_edit_start (wbcg, FALSE, TRUE);
975
		fd = gnm_expr_get_func_def (expr);
976 977 978 979

		sub_str = strstr (full_str, func_str);

		g_return_if_fail (sub_str != NULL);
980

981 982
		state->prefix = g_strndup (full_str, sub_str - full_str);
		state->suffix = g_strdup (sub_str + strlen (func_str));
983
		g_free (func_str);
984 985
	}

986
	state->dialog = glade_xml_get_widget (state->gui, "formula_guru");
987

988
	if (dialog_formula_guru_init (state)) {
Jody Goldberg's avatar
Jody Goldberg committed
989
		go_gtk_notice_dialog (wbcg_toplevel (wbcg), GTK_MESSAGE_ERROR,
990 991
				 _("Could not create the formula guru."));
		g_free (state);
992 993
		return;
	}
994

995 996
	gnumeric_keyed_dialog (state->wbcg, GTK_WINDOW (state->dialog),
			       FORMULA_GURU_KEY);
997

998 999 1000 1001 1002 1003 1004 1005
	gtk_widget_show_all (GTK_DIALOG (state->dialog)->vbox);
	gtk_widget_realize (state->dialog);

	if (fd == NULL) {
		dialog_function_select (wbcg, FORMULA_GURU_KEY);
		return;
	}

1006 1007 1008 1009 1010 1011 1012
	if (expr == NULL)
		dialog_formula_guru_load_fd (NULL, fd, state);
	else {
		GtkTreeIter iter;
		gtk_tree_store_append (state->model, &iter, NULL);
		dialog_formula_guru_load_expr (NULL, 0, expr, state);
	}
1013

1014
	gtk_widget_show_all (state->dialog);
1015
	return;
Michael Meeks's avatar
Michael Meeks committed
1016
}