GUI: Move .ui files from goffice resources to glib resources
[gnumeric.git] / src / dialogs / dialog-formula-guru.c
blob1cd7b06616810baab6b2983415497562f6373bd3
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * dialog-function-wizard.c: The formula guru
5 * Authors:
6 * Jody Goldberg <jody@gnome.org>
7 * Andreas J. Guelzow <aguelzow@taliesin.ca>
9 * Copyright (C) 2000 Jody Goldberg (jody@gnome.org)
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License as
13 * published by the Free Software Foundation; either version 2 of the
14 * License, or (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * 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
26 #include <gnumeric-config.h>
27 #include <glib/gi18n-lib.h>
28 #include <gnumeric.h>
29 #include "dialogs.h"
30 #include "help.h"
32 #include <parse-util.h>
33 #include <gui-util.h>
34 #include <workbook.h>
35 #include <sheet.h>
36 #include <sheet-view.h>
37 #include <wbc-gtk.h>
38 #include <workbook-control.h>
39 #include <cell.h>
40 #include <expr.h>
41 #include <expr-impl.h>
42 #include <expr-name.h>
43 #include <func.h>
44 #include <gnm-format.h>
45 #include <goffice/goffice.h>
46 #include <widgets/gnumeric-expr-entry.h>
47 #include <widgets/gnumeric-cell-renderer-expr-entry.h>
49 #include <gtk/gtk.h>
50 #include <locale.h>
51 #include <string.h>
53 #define FORMULA_GURU_KEY "formula-guru-dialog"
54 #define FORMULA_GURU_KEY_DIALOG "formula-guru-dialog"
56 #define MIN_VAR_ARGS_DISPLAYED 2
57 #define AV_FORMULA_SIZE 100
59 typedef struct
61 WBCGtk *wbcg;
62 Workbook *wb;
64 GtkBuilder *gui;
65 GtkWidget *dialog;
66 GtkWidget *ok_button;
67 GtkWidget *selector_button;
68 GtkWidget *clear_button;
69 GtkWidget *zoom_button;
70 GtkWidget *array_button;
71 GtkWidget *main_button_area;
72 GtkWidget *quote_button;
73 GtkTreePath* active_path;
74 char * prefix;
75 char * suffix;
76 GnmParsePos *pos;
78 GtkTreeStore *model;
79 GtkTreeView *treeview;
80 GtkWidget *tooltip_widget;
81 GtkWidget *tooltip_label;
83 gint old_height;
84 gint old_width;
85 gint old_height_request;
86 gint old_width_request;
88 GnumericCellRendererExprEntry *cellrenderer;
89 GtkTreeViewColumn *column;
90 GtkCellEditable *editable;
91 } FormulaGuruState;
93 enum {
94 FUN_ARG_ENTRY,
95 IS_NON_FUN,
96 ARG_NAME,
97 ARG_TYPE,
98 MIN_ARG,
99 MAX_ARG,
100 FUNCTION,
101 ARG_TOOLTIP,
102 NUM_COLUMNS
105 static void dialog_formula_guru_update_parent (GtkTreeIter *child, FormulaGuruState *state,
106 GtkTreePath *origin,
107 gint sel_start, gint sel_length);
109 static void
110 dialog_formula_guru_write (GString *text, FormulaGuruState *state, gint sel_start,
111 gint sel_length)
113 GtkEntry *entry;
115 entry = wbcg_get_entry (state->wbcg);
116 if (state->prefix) {
117 sel_start += g_utf8_strlen (state->prefix, -1);
118 g_string_prepend (text, state->prefix);
120 if (state->suffix)
121 g_string_append (text, state->suffix);
122 gtk_entry_set_text (entry, text->str);
123 gtk_editable_select_region (GTK_EDITABLE (entry), sel_start, sel_start + sel_length);
126 static void
127 dialog_formula_guru_delete_children (GtkTreeIter *parent, FormulaGuruState *state)
129 GtkTreeIter iter;
131 while (gtk_tree_model_iter_children (GTK_TREE_MODEL(state->model),
132 &iter, parent))
133 gtk_tree_store_remove (state->model, &iter);
136 static void
137 dialog_formula_guru_update_this_parent (GtkTreeIter *parent, FormulaGuruState *state,
138 GtkTreePath *origin, gint sel_start, gint sel_length)
140 GString *text = g_string_sized_new (AV_FORMULA_SIZE);
141 gboolean is_non_fun;
142 GnmFunc const *fd;
143 GtkTreeIter iter;
144 gboolean not_first = FALSE;
145 int arg_min, arg_num = 0;
146 gboolean find_origin = TRUE;
148 gtk_tree_model_get (GTK_TREE_MODEL(state->model), parent,
149 IS_NON_FUN, &is_non_fun,
150 FUNCTION, &fd,
151 MIN_ARG, &arg_min,
152 -1);
154 g_return_if_fail (!is_non_fun);
155 g_return_if_fail (fd != NULL);
157 g_string_append (text, gnm_func_get_name (fd, sheet_get_conventions (state->pos->sheet)->localized_function_names));
158 g_string_append (text, "(");
160 if (gtk_tree_model_iter_children (GTK_TREE_MODEL(state->model), &iter, parent)) {
161 do {
162 char *argument;
163 gtk_tree_model_get (GTK_TREE_MODEL(state->model), &iter,
164 FUN_ARG_ENTRY, &argument,
165 -1);
166 if ((argument == NULL || g_utf8_strlen (argument, -1) == 0) && arg_num > arg_min) {
167 g_free (argument);
168 break;
171 if (not_first) {
172 g_string_append_c (text, go_locale_get_arg_sep ());
175 if (find_origin && origin != NULL) {
176 GtkTreePath *b = gtk_tree_model_get_path
177 (GTK_TREE_MODEL (state->model), &iter);
178 if (0 == gtk_tree_path_compare (origin, b)) {
179 sel_start += g_utf8_strlen (text->str, text->len);
180 gtk_tree_path_free (origin);
181 origin = gtk_tree_model_get_path
182 (GTK_TREE_MODEL (state->model), parent);
183 find_origin = FALSE;
185 gtk_tree_path_free (b);
187 if (argument && strlen (argument) > 0) {
188 GnmExprTop const *texpr = gnm_expr_parse_str
189 (argument, state->pos,
190 GNM_EXPR_PARSE_DEFAULT,
191 sheet_get_conventions (state->pos->sheet),
192 NULL);
193 if (texpr == NULL) {
194 g_string_append_c (text, '"');
195 g_string_append (text, argument);
196 g_string_append_c (text, '"');
197 } else {
198 if ((GNM_EXPR_GET_OPER (texpr->expr) == GNM_EXPR_OP_NAME)
199 && expr_name_is_placeholder (texpr->expr->name.name)
200 && gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (state->quote_button))) {
201 g_string_append_c (text, '"');
202 g_string_append (text, argument);
203 g_string_append_c (text, '"');
204 } else
205 g_string_append (text, argument);
206 gnm_expr_top_unref (texpr);
209 g_free (argument);
210 not_first = TRUE;
211 arg_num++;
213 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL(state->model), &iter));
216 g_string_append_c (text, ')');
218 gtk_tree_store_set (state->model, parent,
219 FUN_ARG_ENTRY, text->str,
220 -1);
221 if (origin == NULL) {
222 sel_start = 0;
223 sel_length = g_utf8_strlen (text->str, text->len);
224 origin = gtk_tree_model_get_path (GTK_TREE_MODEL(state->model), parent);
227 if (0 == gtk_tree_store_iter_depth (state->model, parent))
228 dialog_formula_guru_write (text, state, sel_start, sel_length);
230 g_string_free (text, TRUE);
232 dialog_formula_guru_update_parent (parent, state, origin, sel_start, sel_length);
235 static void
236 dialog_formula_guru_update_parent (GtkTreeIter *child, FormulaGuruState *state,
237 GtkTreePath *origin, gint sel_start, gint sel_length)
239 GtkTreeIter iter;
241 if (gtk_tree_model_iter_parent (GTK_TREE_MODEL (state->model), &iter,
242 child)) {
243 dialog_formula_guru_update_this_parent (&iter, state, origin, sel_start,
244 sel_length);
245 } else
246 gtk_tree_path_free (origin);
249 static void
250 dialog_formula_guru_update_this_child (GtkTreeIter *child, FormulaGuruState *state,
251 GtkTreePath *origin, gint sel_start, gint sel_length)
253 GtkTreeIter iter;
254 char *text;
256 if (gtk_tree_model_iter_parent (GTK_TREE_MODEL (state->model), &iter,
257 child)) {
258 if (origin == NULL) {
259 sel_start = 0;
260 gtk_tree_model_get (GTK_TREE_MODEL(state->model), child,
261 FUN_ARG_ENTRY, &text,
262 -1);
263 sel_length = g_utf8_strlen (text, -1);
264 g_free (text);
265 origin = gtk_tree_model_get_path (GTK_TREE_MODEL(state->model), child);
267 dialog_formula_guru_update_this_parent (&iter, state, origin, sel_start,
268 sel_length);
273 static void
274 dialog_formula_guru_adjust_children (GtkTreeIter *parent, GnmFunc const *fd,
275 FormulaGuruState *state)
277 gboolean is_non_fun;
278 GtkTreeIter iter;
279 gint min_arg, max_arg, args = 0, i;
280 char *arg_name;
283 if (fd == NULL) {
284 gtk_tree_model_get (GTK_TREE_MODEL(state->model), parent,
285 IS_NON_FUN, &is_non_fun,
286 FUNCTION, &fd,
287 -1);
288 if (is_non_fun) {
289 while (gtk_tree_model_iter_children (GTK_TREE_MODEL(state->model),
290 &iter, parent))
291 gtk_tree_store_remove (state->model, &iter);
292 return;
295 g_return_if_fail (fd != NULL);
297 gtk_tree_model_get (GTK_TREE_MODEL(state->model), parent,
298 MIN_ARG, &min_arg,
299 MAX_ARG, &max_arg,
300 -1);
301 if (max_arg == G_MAXINT) {
302 args = MAX (MIN_VAR_ARGS_DISPLAYED + min_arg,
303 gtk_tree_model_iter_n_children (GTK_TREE_MODEL(state->model),
304 parent));
305 } else
306 args = max_arg;
308 while (gtk_tree_model_iter_nth_child (GTK_TREE_MODEL(state->model),
309 &iter, parent, args))
310 gtk_tree_store_remove (state->model, &iter);
311 for (i = 0; i < args; i++) {
312 if (!gtk_tree_model_iter_nth_child (GTK_TREE_MODEL(state->model),
313 &iter, parent, i)) {
314 gtk_tree_store_append (state->model, &iter, parent);
315 gtk_tree_store_set (state->model, &iter,
316 FUN_ARG_ENTRY, "",
317 IS_NON_FUN, TRUE,
318 FUNCTION, NULL,
319 MIN_ARG, 0,
320 MAX_ARG, 0,
321 -1);
323 arg_name = gnm_func_get_arg_name (fd, i);
324 if (i >= min_arg && arg_name != NULL) {
325 char *mod_name = g_strdup_printf (_("[%s]"), arg_name);
326 g_free (arg_name);
327 arg_name = mod_name;
330 gtk_tree_store_set (state->model, &iter,
331 ARG_NAME, arg_name,
332 ARG_TOOLTIP, gnm_func_get_arg_description (fd, i),
333 ARG_TYPE, gnm_func_get_arg_type_string (fd, i),
334 -1);
335 g_free (arg_name);
338 dialog_formula_guru_update_this_parent (parent, state, NULL, 0, 0);
342 static void
343 dialog_formula_guru_adjust_varargs (GtkTreeIter *iter, FormulaGuruState *state)
345 GtkTreeIter new_iter, parent;
346 char *arg_name, *arg_type;
347 gint max_arg;
349 new_iter = *iter;
350 if (!gtk_tree_model_iter_next (GTK_TREE_MODEL (state->model), &new_iter) &&
351 gtk_tree_model_iter_parent (GTK_TREE_MODEL (state->model), &parent, iter)) {
352 gtk_tree_model_get (GTK_TREE_MODEL (state->model), &parent,
353 MAX_ARG, &max_arg,
354 -1);
355 if (max_arg == G_MAXINT) {
356 gtk_tree_model_get (GTK_TREE_MODEL (state->model), iter,
357 ARG_NAME, &arg_name,
358 ARG_TYPE, &arg_type,
359 -1);
360 gtk_tree_store_insert_after (state->model, &new_iter, &parent, iter);
361 gtk_tree_store_set (state->model, &new_iter,
362 FUN_ARG_ENTRY, "",
363 IS_NON_FUN, TRUE,
364 FUNCTION, NULL,
365 ARG_NAME, arg_name,
366 ARG_TOOLTIP, "",
367 ARG_TYPE, arg_type,
368 MIN_ARG, 0,
369 MAX_ARG, 0,
370 -1);
371 g_free (arg_name);
372 g_free (arg_type);
378 static gint
379 dialog_formula_guru_load_fd (GtkTreePath *path, GnmFunc *fd,
380 FormulaGuruState *state)
382 GtkTreeIter iter;
383 gint min_arg, max_arg;
384 GtkTreePath *new_path;
386 gnm_func_load_if_stub (fd);
388 if (path == NULL) {
389 gtk_tree_store_clear (state->model);
390 gtk_tree_store_append (state->model, &iter, NULL);
391 } else if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (state->model), &iter, path)) {
392 GtkTreePath *new_path = gtk_tree_path_copy (path);
393 if (gtk_tree_path_prev (new_path) &&
394 gtk_tree_model_get_iter (GTK_TREE_MODEL (state->model),
395 &iter, new_path)) {
396 dialog_formula_guru_adjust_varargs (&iter, state);
397 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (state->model),
398 &iter, path)) {
399 gtk_tree_store_clear (state->model);
400 gtk_tree_path_free (new_path);
401 return 0;
403 } else {
404 gtk_tree_store_clear (state->model);
405 gtk_tree_path_free (new_path);
406 return 0;
408 gtk_tree_path_free (new_path);
411 gnm_func_count_args (fd, &min_arg, &max_arg);
413 gtk_tree_store_set (state->model, &iter,
414 FUN_ARG_ENTRY, "",
415 IS_NON_FUN, FALSE,
416 FUNCTION, fd,
417 MIN_ARG, min_arg,
418 MAX_ARG, max_arg,
419 -1);
420 dialog_formula_guru_adjust_children (&iter, fd, state);
421 dialog_formula_guru_adjust_varargs (&iter, state);
423 new_path = gtk_tree_model_get_path (GTK_TREE_MODEL (state->model),
424 &iter);
425 gtk_tree_view_expand_row (state->treeview, new_path, FALSE);
426 gtk_tree_path_free (new_path);
428 return max_arg;
431 static void
432 dialog_formula_guru_load_string (GtkTreePath * path,
433 char const *argument, FormulaGuruState *state)
435 GtkTreeIter iter;
436 gboolean okay = TRUE;
438 g_return_if_fail (path != NULL);
440 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (state->model),
441 &iter, path)) {
442 GtkTreePath *new_path = gtk_tree_path_copy (path);
444 if (gtk_tree_path_prev (new_path) &&
445 gtk_tree_model_get_iter (GTK_TREE_MODEL (state->model),
446 &iter, new_path)) {
447 dialog_formula_guru_adjust_varargs (&iter, state);
448 okay = gtk_tree_model_get_iter (GTK_TREE_MODEL (state->model),
449 &iter, path);
450 } else
451 okay = FALSE;
452 gtk_tree_path_free (new_path);
455 g_return_if_fail (okay);
457 dialog_formula_guru_delete_children (&iter, state);
458 gtk_tree_store_set (state->model, &iter,
459 FUN_ARG_ENTRY, argument ? argument : "",
460 IS_NON_FUN, TRUE,
461 FUNCTION, NULL,
462 MIN_ARG, 0,
463 MAX_ARG, 0,
464 -1);
466 dialog_formula_guru_update_parent (&iter, state, gtk_tree_model_get_path
467 (GTK_TREE_MODEL (state->model), &iter),
468 0, argument ? g_utf8_strlen (argument, -1) : 0);
471 static void
472 dialog_formula_guru_load_expr (GtkTreePath const *parent_path, gint child_num,
473 GnmExpr const *expr, FormulaGuruState *state)
475 GtkTreePath *path;
477 if (parent_path == NULL)
478 path = gtk_tree_path_new_first ();
479 else {
480 /* gtk_tree_path_copy should have a const argument */
481 path = gtk_tree_path_copy ((GtkTreePath *) parent_path);
482 gtk_tree_path_append_index (path, child_num);
485 switch (GNM_EXPR_GET_OPER (expr)) {
486 case GNM_EXPR_OP_FUNCALL: {
487 int i, max_arg;
488 GtkTreeIter iter;
490 max_arg = dialog_formula_guru_load_fd (path, expr->func.func, state);
491 if (max_arg > expr->func.argc)
492 max_arg = expr->func.argc;
493 for (i = 0; i < max_arg; i++)
494 dialog_formula_guru_load_expr (path, i,
495 expr->func.argv[i],
496 state);
497 gtk_tree_path_append_index (path, MAX (0, i - 1));
498 if (gtk_tree_model_get_iter (GTK_TREE_MODEL (state->model),
499 &iter, path))
500 dialog_formula_guru_adjust_varargs (&iter, state);
502 break;
505 case GNM_EXPR_OP_ANY_BINARY:
506 case GNM_EXPR_OP_UNARY_NEG:
507 default: {
508 char *text = gnm_expr_as_string (expr, state->pos,
509 sheet_get_conventions (state->pos->sheet));
510 dialog_formula_guru_load_string (path, text, state);
511 g_free (text);
512 break;
516 gtk_tree_path_free (path);
519 static void
520 cb_dialog_formula_guru_destroy (FormulaGuruState *state)
522 wbcg_edit_finish (state->wbcg, WBC_EDIT_REJECT, NULL);
524 if (state->model != NULL)
525 g_object_unref (state->model);
526 g_free (state->prefix);
527 g_free (state->suffix);
528 g_free (state->pos);
529 if (state->editable)
530 g_object_unref (state->editable);
531 if (state->gui != NULL)
532 g_object_unref (state->gui);
533 gnm_expr_entry_enable_tips (wbcg_get_entry_logical (state->wbcg));
534 if (state->tooltip_widget) {
535 g_object_unref (state->tooltip_widget);
536 g_object_unref (state->tooltip_label);
538 g_free (state);
542 static void
543 cb_dialog_formula_guru_cancel_clicked (FormulaGuruState *state)
545 wbcg_edit_finish (state->wbcg, WBC_EDIT_REJECT, NULL);
548 static void
549 cb_dialog_formula_guru_zoom_toggled (GtkWidget *button, FormulaGuruState *state)
551 GtkTreeSelection *selection = gtk_tree_view_get_selection (state->treeview);
552 GtkTreeIter iter;
553 GtkTreePath *path;
556 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) {
557 gtk_widget_hide (state->main_button_area);
558 gtk_widget_hide (state->clear_button);
559 gtk_widget_hide (state->selector_button);
560 gtk_tree_view_set_headers_visible (state->treeview, FALSE);
561 gtk_widget_get_size_request (state->dialog,
562 &state->old_width_request,
563 &state->old_height_request);
564 gtk_window_get_size (GTK_WINDOW (state->dialog),
565 &state->old_width,
566 &state->old_height);
567 gtk_widget_set_size_request (state->dialog,state->old_width_request,100);
569 /* FIXME: the ideal `shrunk size' should probably not be hardcoded.*/
570 gtk_window_resize (GTK_WINDOW (state->dialog),state->old_width_request,100);
571 gtk_window_set_resizable (GTK_WINDOW (state->dialog), FALSE);
572 } else {
573 gtk_widget_show (state->main_button_area);
574 gtk_widget_show (state->clear_button);
575 gtk_widget_show (state->selector_button);
576 gtk_tree_view_set_headers_visible (state->treeview, TRUE);
577 gtk_window_set_resizable (GTK_WINDOW (state->dialog), TRUE);
578 gtk_widget_set_size_request (state->dialog,
579 state->old_width_request,
580 state->old_height_request);
581 gtk_window_resize (GTK_WINDOW (state->dialog), state->old_width,
582 state->old_height);
584 /* FIXME: this should keep the selection in sight, unfortunately it does not for */
585 /* the size reduction case. */
586 if (gtk_tree_selection_get_selected (selection, NULL, &iter)) {
587 path = gtk_tree_model_get_path (GTK_TREE_MODEL (state->model), &iter);
588 gtk_tree_view_scroll_to_cell (state->treeview, path, NULL,
589 FALSE, 0, 0);
590 gtk_tree_path_free (path);
593 return;
597 * cb_dialog_formula_guru_selector_clicked:
598 * @button:
599 * @state:
602 static void
603 cb_dialog_formula_guru_selector_clicked (G_GNUC_UNUSED GtkWidget *button,
604 FormulaGuruState *state)
606 GtkTreeSelection *selection = gtk_tree_view_get_selection (state->treeview);
607 GtkTreeModel *model;
608 GtkTreeIter iter;
610 g_return_if_fail (state->active_path == NULL);
612 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
613 state->active_path = gtk_tree_model_get_path (model, &iter);
614 gtk_widget_hide (state->dialog);
615 dialog_function_select (state->wbcg, FORMULA_GURU_KEY);
616 } else
617 g_warning ("We should never be here!?");
619 return;
623 * cb_dialog_formula_guru_clear_clicked:
624 * @button:
625 * @state:
628 static void
629 cb_dialog_formula_guru_clear_clicked (G_GNUC_UNUSED GtkWidget *button,
630 FormulaGuruState *state)
632 GtkTreeSelection *selection = gtk_tree_view_get_selection (state->treeview);
633 GtkTreeModel *model;
634 GtkTreeIter parent;
636 g_return_if_fail (state->active_path == NULL);
638 if (gtk_tree_selection_get_selected (selection, &model, &parent)) {
639 gtk_tree_store_set (state->model, &parent,
640 FUN_ARG_ENTRY, "",
641 IS_NON_FUN, TRUE,
642 FUNCTION, NULL,
643 MIN_ARG, 0,
644 MAX_ARG, 0,
645 -1);
646 dialog_formula_guru_delete_children (&parent, state);
647 dialog_formula_guru_update_parent (&parent, state, gtk_tree_model_get_path
648 (GTK_TREE_MODEL (state->model), &parent), 0, 0);
649 } else
650 g_warning ("We should never be here!?");
651 return;
654 static gboolean
655 dialog_formula_guru_is_array (FormulaGuruState *state)
657 return gtk_toggle_button_get_active
658 (GTK_TOGGLE_BUTTON (state->array_button));
662 * cb_dialog_formula_guru_ok_clicked:
663 * @button:
664 * @state:
666 * Close (destroy) the dialog
668 static void
669 cb_dialog_formula_guru_ok_clicked (G_GNUC_UNUSED GtkWidget *button,
670 FormulaGuruState *state)
672 if (state->cellrenderer->entry)
673 gnumeric_cell_renderer_expr_entry_editing_done (
674 GTK_CELL_EDITABLE (state->cellrenderer->entry),
675 state->cellrenderer);
676 wbcg_edit_finish (state->wbcg,
677 dialog_formula_guru_is_array (state)
678 ? WBC_EDIT_ACCEPT_ARRAY
679 : WBC_EDIT_ACCEPT, NULL);
682 static void
683 cb_dialog_formula_guru_selection_changed (GtkTreeSelection *the_selection,
684 FormulaGuruState *state)
686 GtkTreeIter iter;
687 GtkTreeModel *model;
689 if (!gtk_tree_selection_get_selected (the_selection, &model, &iter)) {
690 gtk_widget_set_sensitive (state->clear_button, FALSE);
691 gtk_widget_set_sensitive (state->selector_button, FALSE);
692 return;
695 gtk_widget_set_sensitive (state->clear_button,
696 0 != gtk_tree_store_iter_depth (state->model,
697 &iter));
698 gtk_widget_set_sensitive (state->selector_button, TRUE);
699 dialog_formula_guru_update_this_child (&iter, state,
700 NULL, 0, 0);
703 /* We shouldn't need that if it weren't for a GTK+ bug*/
704 static void
705 cb_dialog_formula_guru_row_collapsed (G_GNUC_UNUSED GtkTreeView *treeview,
706 G_GNUC_UNUSED GtkTreeIter *iter,
707 G_GNUC_UNUSED GtkTreePath *path,
708 FormulaGuruState *state)
710 GtkTreeSelection *selection = gtk_tree_view_get_selection (state->treeview);
712 cb_dialog_formula_guru_selection_changed (selection, state);
716 static void
717 cb_dialog_formula_guru_edited (G_GNUC_UNUSED GtkCellRendererText *cell,
718 gchar *path_string,
719 gchar *new_text,
720 FormulaGuruState *state)
722 GtkTreeIter iter;
723 GtkTreePath *path;
724 gboolean have_iter = FALSE;
726 path = gtk_tree_path_new_from_string (path_string);
728 have_iter = gtk_tree_model_get_iter (GTK_TREE_MODEL (state->model),
729 &iter, path);
730 gtk_tree_path_free (path);
731 if (!have_iter)
732 return;
733 gtk_tree_store_set (state->model, &iter, FUN_ARG_ENTRY, new_text, -1);
735 if (g_utf8_strlen (new_text, -1) > 0)
736 dialog_formula_guru_adjust_varargs (&iter, state);
739 dialog_formula_guru_update_parent (&iter, state, gtk_tree_model_get_path
740 (GTK_TREE_MODEL (state->model), &iter),
741 0, g_utf8_strlen (new_text, -1));
744 static void
745 cb_dialog_formula_guru_editing_started (G_GNUC_UNUSED GtkCellRenderer *cell,
746 GtkCellEditable *editable,
747 G_GNUC_UNUSED const gchar *path,
748 FormulaGuruState *state)
750 g_object_ref (editable);
751 if (state->editable)
752 g_object_unref (state->editable);
753 state->editable = editable;
756 static gboolean
757 start_editing_cb (GtkTreeView *tree_view,
758 GdkEventButton *event,
759 FormulaGuruState *state)
761 GtkTreePath *path;
762 GtkTreeIter iter;
764 if (event->window != gtk_tree_view_get_bin_window (tree_view))
765 return FALSE;
766 if (state->treeview != tree_view)
767 return FALSE;
769 if (gtk_tree_view_get_path_at_pos (tree_view,
770 (gint) event->x,
771 (gint) event->y,
772 &path, NULL,
773 NULL, NULL) &&
774 gtk_tree_model_get_iter (GTK_TREE_MODEL (state->model),
775 &iter, path))
777 gboolean is_non_fun;
779 gtk_tree_model_get (GTK_TREE_MODEL (state->model), &iter,
780 IS_NON_FUN, &is_non_fun,
781 -1);
783 if (!is_non_fun) {
784 gtk_tree_path_free (path);
785 return FALSE;
788 if (state->editable)
789 gtk_cell_editable_editing_done (state->editable);
791 gtk_widget_grab_focus (GTK_WIDGET (state->treeview));
792 gtk_tree_view_set_cursor (state->treeview,
793 path,
794 state->column,
795 TRUE);
797 gtk_tree_path_free (path);
799 return TRUE;
801 return FALSE;
804 static gboolean
805 cb_dialog_formula_guru_query_tooltip (G_GNUC_UNUSED GtkWidget *widget,
806 gint x,
807 gint y,
808 gboolean keyboard_mode,
809 GtkTooltip *tooltip,
810 gpointer user_data)
812 FormulaGuruState *state = user_data;
813 gint x_ = x;
814 gint y_ = y;
815 GtkTreeIter iter;
816 GtkTreePath *path;
818 if (gtk_tree_view_get_tooltip_context
819 (state->treeview, &x_, &y_, keyboard_mode, NULL, &path, &iter)) {
820 char *markup, *arg_desc;
821 GtkWidget *parent, *window;
823 gtk_tree_model_get (GTK_TREE_MODEL (state->model), &iter,
824 ARG_TOOLTIP, &arg_desc, -1);
825 if (arg_desc == NULL || arg_desc[0]=='\0')
826 return FALSE;
827 if (!state->tooltip_widget) {
828 state->tooltip_label = gtk_label_new ("");
829 state->tooltip_widget
830 = gtk_widget_get_toplevel (state->tooltip_label);
831 gtk_widget_show_all (state->tooltip_widget);
832 g_object_ref (state->tooltip_widget);
833 g_object_ref (state->tooltip_label);
835 gtk_tooltip_set_custom (tooltip, state->tooltip_widget);
836 window = gtk_widget_get_toplevel (state->tooltip_widget);
837 /* Applying to window */
838 gtk_widget_set_app_paintable (window, FALSE);
840 parent = gtk_widget_get_parent (state->tooltip_widget);
841 if (parent != NULL && GTK_IS_BOX (parent)) {
842 gtk_box_set_spacing (GTK_BOX (parent),0);
843 parent = gtk_widget_get_parent (parent);
844 if (parent != NULL && GTK_IS_ALIGNMENT (parent))
845 gtk_alignment_set_padding
846 (GTK_ALIGNMENT (parent),
847 0,0,0,0);
850 markup = gnm_func_convert_markup_to_pango
851 (arg_desc,
852 state->tooltip_label);
853 gtk_label_set_markup (GTK_LABEL (state->tooltip_label), markup);
854 g_free (markup);
855 g_free (arg_desc);
856 gtk_tree_view_set_tooltip_row (state->treeview,
857 tooltip, path);
858 gtk_tree_path_free (path);
859 return TRUE;
861 return FALSE;
864 static gboolean
865 dialog_formula_guru_init (FormulaGuruState *state)
867 GtkWidget *scrolled;
868 GtkTreeViewColumn *column;
869 GtkTreeSelection *selection;
870 GtkCellRenderer *renderer;
872 g_object_set_data (G_OBJECT (state->dialog), FORMULA_GURU_KEY_DIALOG,
873 state);
875 state->tooltip_widget = NULL;
877 /* Set-up treeview */
878 scrolled = go_gtk_builder_get_widget (state->gui, "scrolled");
879 state->model = gtk_tree_store_new (NUM_COLUMNS, G_TYPE_STRING, G_TYPE_BOOLEAN,
880 G_TYPE_STRING, G_TYPE_STRING,
881 G_TYPE_INT, G_TYPE_INT, G_TYPE_POINTER,
882 G_TYPE_STRING);
883 state->treeview = GTK_TREE_VIEW (
884 gtk_tree_view_new_with_model (GTK_TREE_MODEL (state->model)));
885 g_signal_connect (state->treeview,
886 "row_collapsed",
887 G_CALLBACK (cb_dialog_formula_guru_row_collapsed), state);
888 selection = gtk_tree_view_get_selection (state->treeview);
889 gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
890 g_signal_connect (selection,
891 "changed",
892 G_CALLBACK (cb_dialog_formula_guru_selection_changed), state);
894 column = gtk_tree_view_column_new_with_attributes (_("Name"),
895 gnumeric_cell_renderer_text_new (),
896 "text", ARG_NAME, NULL);
897 gtk_tree_view_append_column (state->treeview, column);
898 column = gtk_tree_view_column_new_with_attributes (_("Type"),
899 gnumeric_cell_renderer_text_new (),
900 "text", ARG_TYPE, NULL);
901 gtk_tree_view_append_column (state->treeview, column);
902 renderer = gnumeric_cell_renderer_expr_entry_new (state->wbcg);
903 state->cellrenderer = GNM_CELL_RENDERER_EXPR_ENTRY (renderer);
904 g_signal_connect (G_OBJECT (renderer), "edited",
905 G_CALLBACK (cb_dialog_formula_guru_edited), state);
906 state->editable = NULL;
907 g_signal_connect (G_OBJECT (renderer), "editing-started",
908 G_CALLBACK (cb_dialog_formula_guru_editing_started), state);
909 column = gtk_tree_view_column_new_with_attributes (_("Function/Argument"),
910 renderer,
911 "text", FUN_ARG_ENTRY,
912 "editable", IS_NON_FUN,
913 NULL);
914 state->column = column;
915 gtk_tree_view_append_column (state->treeview, column);
917 gtk_widget_set_has_tooltip (GTK_WIDGET (state->treeview), TRUE);
918 g_signal_connect (G_OBJECT (state->treeview), "query-tooltip",
919 G_CALLBACK (cb_dialog_formula_guru_query_tooltip), state);
921 gtk_tree_view_set_headers_visible (state->treeview, TRUE);
922 gtk_tree_view_set_enable_tree_lines (state->treeview, TRUE);
923 gtk_container_add (GTK_CONTAINER (scrolled), GTK_WIDGET (state->treeview));
925 g_signal_connect (state->treeview,
926 "button_press_event",
927 G_CALLBACK (start_editing_cb), state);
928 /* Finished set-up of treeview */
930 state->quote_button = go_gtk_builder_get_widget (state->gui,
931 "quote-button");
932 state->array_button = go_gtk_builder_get_widget (state->gui,
933 "array_button");
934 gtk_widget_set_sensitive (state->array_button, TRUE);
936 state->ok_button = go_gtk_builder_get_widget (state->gui, "ok_button");
937 gtk_widget_set_sensitive (state->ok_button, TRUE);
938 g_signal_connect (G_OBJECT (state->ok_button),
939 "clicked",
940 G_CALLBACK (cb_dialog_formula_guru_ok_clicked), state);
942 state->selector_button = go_gtk_builder_get_widget (state->gui, "select_func");
943 gtk_widget_set_sensitive (state->selector_button, FALSE);
944 g_signal_connect (G_OBJECT (state->selector_button),
945 "clicked",
946 G_CALLBACK (cb_dialog_formula_guru_selector_clicked), state);
948 state->clear_button = go_gtk_builder_get_widget (state->gui, "trash");
949 gtk_widget_set_sensitive (state->clear_button, FALSE);
950 g_signal_connect (G_OBJECT (state->clear_button),
951 "clicked",
952 G_CALLBACK (cb_dialog_formula_guru_clear_clicked), state);
954 state->zoom_button = go_gtk_builder_get_widget (state->gui, "zoom");
955 gtk_widget_set_sensitive (state->zoom_button, TRUE);
956 g_signal_connect (G_OBJECT (state->zoom_button),
957 "toggled",
958 G_CALLBACK (cb_dialog_formula_guru_zoom_toggled), state);
960 state->main_button_area = go_gtk_builder_get_widget (state->gui, "dialog-action_area2");
962 g_signal_connect_swapped (G_OBJECT (go_gtk_builder_get_widget (state->gui, "cancel_button")),
963 "clicked",
964 G_CALLBACK (cb_dialog_formula_guru_cancel_clicked), state);
966 gnm_init_help_button (
967 go_gtk_builder_get_widget (state->gui, "help_button"),
968 GNUMERIC_HELP_LINK_FORMULA_GURU);
970 wbc_gtk_attach_guru (state->wbcg, state->dialog);
971 g_object_set_data_full (G_OBJECT (state->dialog),
972 "state", state, (GDestroyNotify) cb_dialog_formula_guru_destroy);
974 return FALSE;
977 static void
978 dialog_formula_guru_show (FormulaGuruState *state)
980 GtkTreeIter iter;
982 if ((!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (state->model), &iter)) ||
983 gtk_tree_model_iter_n_children (GTK_TREE_MODEL(state->model), &iter) == 0)
984 wbcg_edit_finish (state->wbcg, WBC_EDIT_ACCEPT, NULL);
985 else
986 gtk_widget_show_all (state->dialog);
990 * dialog_formula_guru
991 * @wbcg: The workbook to use as a parent window.
993 * Pop up a function selector then a formula guru.
995 void
996 dialog_formula_guru (WBCGtk *wbcg, GnmFunc *fd)
998 SheetView *sv;
999 GtkBuilder *gui;
1000 GnmCell *cell;
1001 GtkWidget *dialog;
1002 FormulaGuruState *state;
1003 GnmExpr const *expr = NULL;
1005 g_return_if_fail (wbcg != NULL);
1007 dialog = gnm_dialog_raise_if_exists (wbcg, FORMULA_GURU_KEY);
1009 if (dialog) {
1010 /* We already exist */
1011 state = g_object_get_data (G_OBJECT (dialog),
1012 FORMULA_GURU_KEY_DIALOG);
1013 if (fd) {
1014 if (state->active_path) {
1015 dialog_formula_guru_load_fd (state->active_path, fd, state);
1016 gtk_tree_path_free (state->active_path);
1017 state->active_path = NULL;
1018 } else
1019 dialog_formula_guru_load_fd (NULL, fd, state);
1020 dialog_formula_guru_show (state);
1021 } else {
1022 if (state->active_path) {
1023 gtk_tree_path_free (state->active_path);
1024 state->active_path = NULL;
1027 if (0 == gtk_tree_model_iter_n_children (GTK_TREE_MODEL(state->model),
1028 NULL))
1029 gtk_widget_destroy (state->dialog);
1030 else
1031 dialog_formula_guru_show (state);
1033 return;
1036 /* Get the dialog and check for errors */
1037 gui = gnm_gtk_builder_load ("res:ui/formula-guru.ui", NULL, GO_CMD_CONTEXT (wbcg));
1038 if (gui == NULL)
1039 return;
1041 state = g_new (FormulaGuruState, 1);
1042 state->wbcg = wbcg;
1043 state->wb = wb_control_get_workbook (GNM_WBC (wbcg));
1044 state->gui = gui;
1045 state->active_path = NULL;
1046 state->pos = g_new (GnmParsePos, 1);
1048 gnm_expr_entry_disable_tips (wbcg_get_entry_logical (wbcg));
1050 sv = wb_control_cur_sheet_view (GNM_WBC (wbcg));
1051 cell = sheet_cell_get (sv_sheet (sv), sv->edit_pos.col, sv->edit_pos.row);
1052 if (cell != NULL) {
1053 parse_pos_init_cell (state->pos, cell);
1054 if (gnm_cell_has_expr (cell))
1055 expr = gnm_expr_top_first_funcall (cell->base.texpr);
1056 } else
1057 parse_pos_init_editpos (state->pos, sv);
1060 if (expr == NULL) {
1061 wbcg_edit_start (wbcg, TRUE, TRUE);
1062 state->prefix = g_strdup ("=");
1063 state->suffix = NULL;
1064 } else {
1065 char const *sub_str;
1066 char const *full_str = gtk_entry_get_text (wbcg_get_entry (wbcg));
1067 char *func_str;
1069 func_str = gnm_expr_as_string (expr,
1070 state->pos,
1071 sheet_get_conventions (sv_sheet (sv)));
1073 wbcg_edit_start (wbcg, FALSE, TRUE);
1074 fd = gnm_expr_get_func_def (expr);
1076 sub_str = strstr (full_str, func_str);
1078 g_return_if_fail (sub_str != NULL);
1080 state->prefix = g_strndup (full_str, sub_str - full_str);
1081 state->suffix = g_strdup (sub_str + strlen (func_str));
1082 g_free (func_str);
1085 state->dialog = go_gtk_builder_get_widget (state->gui, "formula_guru");
1086 gnm_dialog_setup_destroy_handlers (GTK_DIALOG (state->dialog),
1087 state->wbcg,
1088 GNM_DIALOG_DESTROY_SHEET_REMOVED);
1091 if (dialog_formula_guru_init (state)) {
1092 go_gtk_notice_dialog (wbcg_toplevel (wbcg), GTK_MESSAGE_ERROR,
1093 _("Could not create the formula guru."));
1094 g_free (state);
1095 return;
1098 gnm_keyed_dialog (state->wbcg, GTK_WINDOW (state->dialog),
1099 FORMULA_GURU_KEY);
1101 gtk_widget_show_all (gtk_dialog_get_content_area (GTK_DIALOG (state->dialog)));
1102 gtk_widget_realize (state->dialog);
1104 if (fd == NULL) {
1105 dialog_function_select (wbcg, FORMULA_GURU_KEY);
1106 return;
1109 if (expr == NULL)
1110 dialog_formula_guru_load_fd (NULL, fd, state);
1111 else {
1112 GtkTreeIter iter;
1113 gtk_tree_store_append (state->model, &iter, NULL);
1114 dialog_formula_guru_load_expr (NULL, 0, expr, state);
1117 gtk_widget_show_all (state->dialog);