gnumeric-expr-entry.c 75.5 KB
Newer Older
1 2
/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

3 4
/*
 * gnumeric-expr-entry.c: An entry widget specialized to handle expressions
5
 * and ranges.
6 7 8 9 10
 *
 * Author:
 *   Jon Kre Hellan (hellan@acm.org)
 */

11
#include <gnumeric-config.h>
12
#include <glib/gi18n-lib.h>
13
#include <gnumeric.h>
14
#include "gnumeric-expr-entry.h"
15

16
#include <wbc-gtk-impl.h>
17
#include <sheet-control-gui-priv.h>
18
#include <gnm-pane.h>
19 20 21 22 23 24
#include <sheet-merge.h>
#include <parse-util.h>
#include <gui-util.h>
#include <ranges.h>
#include <value.h>
#include <expr.h>
25
#include <func.h>
26
#include <dependent.h>
27
#include <sheet.h>
28
#include <sheet-style.h>
29
#include <workbook.h>
30
#include <sheet-view.h>
31
#include <selection.h>
32
#include <commands.h>
33
#include <gnm-format.h>
34
#include <number-match.h>
35
#include <gnm-datetime.h>
36
#include <gnumeric-conf.h>
37
#include <dead-kittens.h>
38
#include <dialogs/dialogs.h>
39
#include <goffice/goffice.h>
Jody Goldberg's avatar
Jody Goldberg committed
40

Jody Goldberg's avatar
Jody Goldberg committed
41
#include <gsf/gsf-impl-utils.h>
42
#include <gtk/gtk.h>
43
#include <gdk/gdkkeysyms.h>
Jody Goldberg's avatar
Jody Goldberg committed
44
#include <string.h>
45

46 47
#define UNICODE_LEFT_TRIANGLE "\xe2\x97\x80"
#define UNICODE_RIGHT_TRIANGLE "\xe2\x96\xb6"
Morten Welinder's avatar
Morten Welinder committed
48 49
#define UNICODE_CROSS_AND_SKULLBONES "\xe2\x98\xa0"
#define UNICODE_ELLIPSIS "\xe2\x80\xa6"
50
#define UNICODE_ELLIPSIS_VERT "\xe2\x8b\xae"
51
#define UNICODE_ARROW_UP "\xe2\x87\xa7"
52
#define UNICODE_CHECKMARK "\342\234\223"
Morten Welinder's avatar
Morten Welinder committed
53

54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
#warning We should replace these token names with the correct values
   enum yytokentype {
     STRING = 258,
     QUOTED_STRING = 259,
     CONSTANT = 260,
     RANGEREF = 261,
     tok_GTE = 262,
     tok_LTE = 263,
     tok_NE = 264,
     tok_AND = 265,
     tok_OR = 266,
     tok_NOT = 267,
     INTERSECT = 268,
     ARG_SEP = 269,
     ARRAY_COL_SEP = 270,
     ARRAY_ROW_SEP = 271,
     SHEET_SEP = 272,
     INVALID_TOKEN = 273,
     tok_RIGHT_EXP = 274,
     tok_LEFT_EXP = 275,
     tok_PLUS = 276,
     tok_NEG = 277,
     RANGE_INTERSECT = 278,
     RANGE_SEP = 279
   };
#define  TOKEN_UNMATCHED_APOSTROPHY INVALID_TOKEN
#define  TOKEN_PARENTHESIS_OPEN 40
#define  TOKEN_PARENTHESIS_CLOSED 41
#define  TOKEN_BRACE_OPEN 123
#define  TOKEN_BRACE_CLOSED 125

85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
GType
gnm_update_type_get_type (void)
{
  static GType etype = 0;
  if (etype == 0) {
    static const GEnumValue values[] = {
      { GNM_UPDATE_CONTINUOUS, "GNM_UPDATE_CONTINUOUS", "continuous" },
      { GNM_UPDATE_DISCONTINUOUS, "GNM_UPDATE_DISCONTINUOUS", "discontinuous" },
      { GNM_UPDATE_DELAYED, "GNM_UPDATE_DELAYED", "delayed" },
      { 0, NULL, NULL }
    };
    etype = g_enum_register_static (g_intern_static_string ("GnmUpdateType"), values);
  }
  return etype;
}
100

101
typedef struct {
Jody Goldberg's avatar
Jody Goldberg committed
102
	GnmRangeRef ref;
103 104 105
	int	    text_start;
	int	    text_end;
	gboolean    is_valid;
106 107
} Rangesel;

108
struct _GnmExprEntry {
109
	GtkBox	parent;
110 111

	GtkEntry		*entry;
112
	GtkWidget               *calendar_combo;
113
	gulong                   calendar_combo_changed;
114
	GtkWidget		*icon;
115 116
	SheetControlGUI		*scg;	/* the source of the edit */
	Sheet			*sheet;	/* from scg */
117
	GnmParsePos		 pp;	/* from scg->sv */
118
	WBCGtk			*wbcg;	/* from scg */
119 120
	Rangesel		 rangesel;

121
	GnmExprEntryFlags	 flags;
122 123
	int			 freeze_count;

124
	GnmUpdateType		 update_policy;
125
	guint			 update_timeout_id;
126

127 128 129
	gboolean                 is_cell_renderer;  /* as cell_editable */
	gboolean                 editing_canceled;  /* as cell_editable */
	gboolean                 ignore_changes; /* internal mutex */
130

131 132 133
	gboolean                 feedback_disabled;
	GnmLexerItem            *lexer_items;
	GnmExprTop const        *texpr;
134 135 136
	struct {
		GtkWidget       *tooltip;
		GnmFunc         *fd;
137
		gint             args;
138
		gboolean         had_stuff;
139
		gulong           handlerid;
140
		guint            timerid;
141
		gboolean         enabled;
142
		gboolean         is_expr;
143
		gboolean         completion_se_valid;
144 145 146
		gchar           *completion;
		guint            completion_start;
		guint            completion_end;
147
	}                        tooltip;
148 149

	GOFormat const *constant_format;
150 151
};

152
typedef struct _GnmExprEntryClass {
153
	GtkBoxClass base;
154

Jody Goldberg's avatar
Jody Goldberg committed
155 156 157
	void (* update)   (GnmExprEntry *gee, gboolean user_requested_update);
	void (* changed)  (GnmExprEntry *gee);
	void (* activate) (GnmExprEntry *gee);
158
} GnmExprEntryClass;
159

160 161
/* Signals */
enum {
162 163
	UPDATE,
	CHANGED,
164
	ACTIVATE,
165 166 167
	LAST_SIGNAL
};

168 169 170
/* Properties */
enum {
	PROP_0,
171 172
	PROP_UPDATE_POLICY,
	PROP_WITH_ICON,
173
	PROP_TEXT,
174
	PROP_FLAGS,
Jody Goldberg's avatar
Jody Goldberg committed
175
	PROP_SCG,
176
	PROP_WBCG,
177 178
	PROP_CONSTANT_FORMAT,
	PROP_EDITING_CANCELED
179 180
};

181 182 183 184
static guint signals[LAST_SIGNAL] = { 0 };

static void gee_set_value_double (GogDataEditor *editor, double val,
				  GODateConventions const *date_conv);
185

186 187 188 189 190 191
/* Internal routines
 */
static void     gee_rangesel_reset (GnmExprEntry *gee);
static void     gee_rangesel_update_text (GnmExprEntry *gee);
static void     gee_detach_scg (GnmExprEntry *gee);
static void     gee_remove_update_timer (GnmExprEntry *range);
192
static void     cb_gee_notify_cursor_position (GnmExprEntry *gee);
193

194
static gboolean gee_debug;
195
static GtkWidgetClass *parent_class = NULL;
196

197 198 199 200 201 202
static gboolean
gee_is_editing (GnmExprEntry *gee)
{
	return (gee != NULL && gee->wbcg != NULL && wbcg_is_editing (gee->wbcg));
}

203 204 205 206 207 208
static GnmConventions const *
gee_convs (const GnmExprEntry *gee)
{
	return sheet_get_conventions (gee->sheet);
}

209 210 211 212
static inline void
gee_force_abs_rel (GnmExprEntry *gee)
{
	Rangesel *rs = &gee->rangesel;
213
	rs->is_valid = FALSE;
214 215 216
	if ((gee->flags & GNM_EE_FORCE_ABS_REF))
		rs->ref.a.col_relative = rs->ref.b.col_relative =
			rs->ref.a.row_relative = rs->ref.b.row_relative = FALSE;
217
        else if ((gee->flags & GNM_EE_FORCE_REL_REF))
218 219 220 221
		rs->ref.a.col_relative = rs->ref.b.col_relative =
			rs->ref.a.row_relative = rs->ref.b.row_relative = TRUE;
}

222
static void
223
gee_rangesel_reset (GnmExprEntry *gee)
224 225 226 227 228
{
	Rangesel *rs = &gee->rangesel;

	rs->text_start = 0;
	rs->text_end = 0;
229
	memset (&rs->ref, 0, sizeof (rs->ref));
230 231 232 233
	rs->ref.a.col_relative =
	rs->ref.b.col_relative =
	rs->ref.a.row_relative =
	rs->ref.b.row_relative = ((gee->flags & (GNM_EE_FORCE_ABS_REF|GNM_EE_DEFAULT_ABS_REF)) == 0);
234

235
	rs->is_valid = FALSE;
236 237 238
}

static void
239
gee_destroy (GtkWidget *widget)
240
{
241
	GnmExprEntry *gee = GNM_EXPR_ENTRY (widget);
Jody Goldberg's avatar
Jody Goldberg committed
242 243
	gee_remove_update_timer (gee);
	gee_detach_scg (gee);
244
	((GtkWidgetClass *)(parent_class))->destroy (widget);
245 246
}

247 248 249 250 251 252 253 254 255 256 257 258 259
static void
cb_icon_clicked (GtkButton *icon,
		 GnmExprEntry *entry)
{
	GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (entry));

	/* TODO special-case GnmExprEntry being directly packed
	 * into a GtkWindow. Currently, we just use it in
	 * GtkDialogs so the current window child widget
	 * is never identical to the entry when it is
	 * not rolled up.
	 */

260
	if (toplevel != NULL && gtk_widget_is_toplevel (toplevel)) {
261 262 263 264 265 266 267 268
		GtkWidget *old_entry_parent;
		GtkWidget *old_toplevel_child;
		GParamSpec **container_props_pspec;
		GValueArray *container_props;

		g_assert (GTK_IS_WINDOW (toplevel));

		if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (icon))) {
269 270
			int width, height;
			guint n;
271 272 273 274 275 276 277 278 279 280

			/* roll-up request */

			old_toplevel_child = gtk_bin_get_child (GTK_BIN (toplevel));
			g_assert (GTK_IS_WIDGET (old_toplevel_child));

			old_entry_parent = gtk_widget_get_parent (GTK_WIDGET (entry));
			g_assert (GTK_IS_CONTAINER (old_entry_parent));

			g_object_set_data_full (G_OBJECT (entry), "old_entry_parent",
281 282
						g_object_ref (old_entry_parent),
						(GDestroyNotify) g_object_unref);
283 284 285 286

			g_return_if_fail ((GtkWidget *) entry != old_toplevel_child);

			g_object_set_data_full (G_OBJECT (entry), "old_toplevel_child",
287 288
						g_object_ref (old_toplevel_child),
						(GDestroyNotify) g_object_unref);
289 290 291 292

			gtk_window_get_size (GTK_WINDOW (toplevel), &width, &height);
			g_object_set_data (G_OBJECT (entry), "old_window_width", GUINT_TO_POINTER (width));
			g_object_set_data (G_OBJECT (entry), "old_window_height", GUINT_TO_POINTER (height));
293
			g_object_set_data (G_OBJECT (entry), "old_default",
294
					   gtk_window_get_default_widget (GTK_WINDOW (toplevel)));
295 296 297 298 299 300

			container_props = NULL;

			container_props_pspec = gtk_container_class_list_child_properties
					(G_OBJECT_GET_CLASS (old_entry_parent), &n);

301
			if (container_props_pspec[0] != NULL) {
302
				guint ui;
303 304 305

				container_props = g_value_array_new (n);

306
				for (ui = 0; ui < n; ui++) {
307
					GValue value = G_VALUE_INIT;
308
					g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (container_props_pspec[ui]));
309 310

					gtk_container_child_get_property (GTK_CONTAINER (old_entry_parent), GTK_WIDGET (entry),
311
									  g_param_spec_get_name (container_props_pspec[ui]),
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
									  &value);
					g_value_array_append (container_props, &value);
				}
			}

			g_object_set_data_full (G_OBJECT (entry), "container_props",
						container_props,
						(GDestroyNotify) g_value_array_free);
			g_object_set_data_full (G_OBJECT (entry), "container_props_pspec",
						container_props_pspec,
						(GDestroyNotify) g_free);

			gtk_container_remove (GTK_CONTAINER (toplevel), old_toplevel_child);
			gtk_widget_reparent (GTK_WIDGET (entry), toplevel);

			gtk_widget_grab_focus (GTK_WIDGET (entry->entry));
328 329 330
			gtk_widget_set_can_default (GTK_WIDGET (icon), TRUE);
			gtk_widget_grab_default (GTK_WIDGET (icon));

331 332 333 334
			gtk_window_resize (GTK_WINDOW (toplevel), 1, 1);

		} else {
			int i;
335
			gpointer default_widget;
336 337 338 339 340 341 342 343 344

			/* reset rolled-up window */

			old_toplevel_child = g_object_get_data (G_OBJECT (entry), "old_toplevel_child");
			g_assert (GTK_IS_WIDGET (old_toplevel_child));

			old_entry_parent = g_object_get_data (G_OBJECT (entry), "old_entry_parent");
			g_assert (GTK_IS_CONTAINER (old_entry_parent));

345
			g_object_ref (entry);
346 347 348
			gtk_container_remove (GTK_CONTAINER (toplevel), GTK_WIDGET (entry));
			gtk_container_add (GTK_CONTAINER (toplevel), old_toplevel_child);
			gtk_container_add (GTK_CONTAINER (old_entry_parent), GTK_WIDGET (entry));
349
			g_object_unref (entry);
350 351 352 353

			container_props = g_object_get_data (G_OBJECT (entry), "container_props");
			container_props_pspec = g_object_get_data (G_OBJECT (entry), "container_props_pspec");

354 355 356 357
			for (i = 0; container_props_pspec[i] != NULL; i++) {
				gtk_container_child_set_property (GTK_CONTAINER (old_entry_parent), GTK_WIDGET (entry),
								  g_param_spec_get_name (container_props_pspec[i]),
								  g_value_array_get_nth (container_props, i));
358 359 360 361 362
			}

			gtk_window_resize (GTK_WINDOW (toplevel),
					   GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (entry), "old_window_width")),
					   GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (entry), "old_window_height")));
363 364 365 366 367
			default_widget = g_object_get_data (G_OBJECT (entry), "old_default");
			if (default_widget != NULL) {
				gtk_window_set_default (GTK_WINDOW (toplevel), GTK_WIDGET (default_widget));
				g_object_set_data (G_OBJECT (entry), "old_default", NULL);
			}
368 369 370 371 372 373 374 375 376 377 378

			g_object_set_data (G_OBJECT (entry), "old_entry_parent", NULL);
			g_object_set_data (G_OBJECT (entry), "old_toplevel_child", NULL);
			g_object_set_data (G_OBJECT (entry), "container_props", NULL);
			g_object_set_data (G_OBJECT (entry), "container_props_pspec", NULL);
		}
	} else {
		g_warning ("GnmExprEntry button was clicked, but entry has no toplevel parent.");
	}
}

379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
static GnmValue *
get_matched_value (GnmExprEntry *gee)
{
	GODateConventions const *date_conv =
		workbook_date_conv (gee->sheet->workbook);
	const char *text = gnm_expr_entry_get_text (gee);

	return format_match_number (text, gee->constant_format, date_conv);
}


static void
gee_update_calendar (GnmExprEntry *gee)
{
	GDate date;
	GnmValue *v;
	GODateConventions const *date_conv =
		workbook_date_conv (gee->sheet->workbook);

	if (!gee->calendar_combo)
		return;

	v = get_matched_value (gee);
	if (!v)
		return;

405 406 407
	if (datetime_value_to_g (&date, v, date_conv)) {
		g_signal_handler_block (gee->calendar_combo,
					gee->calendar_combo_changed);
408 409 410
		go_calendar_button_set_date
			(GO_CALENDAR_BUTTON (gee->calendar_combo),
			 &date);
411 412 413
		g_signal_handler_unblock (gee->calendar_combo,
					  gee->calendar_combo_changed);
	}
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428

	value_release (v);
}

static void
cb_calendar_changed (GOCalendarButton *calb, GnmExprEntry *gee)
{
	GDate date;
	GODateConventions const *date_conv =
		workbook_date_conv (gee->sheet->workbook);
	int serial;

	if (!go_calendar_button_get_date (calb, &date))
		return;

429
	serial = go_date_g_to_serial (&date, date_conv);
430 431 432 433

	gee_set_value_double (GOG_DATA_EDITOR (gee), serial, date_conv);
}

434 435 436 437 438 439 440 441 442 443
static void
gee_set_format (GnmExprEntry *gee, GOFormat const *fmt)
{
	if (fmt == gee->constant_format)
		return;

	if (fmt) go_format_ref (fmt);
	go_format_unref (gee->constant_format);
	gee->constant_format = fmt;

444
	if (gee_debug)
445 446
		g_printerr ("Setting format %s\n",
			    fmt ? go_format_as_XL (fmt) : "-");
447

448 449 450 451 452 453
	if (fmt && go_format_is_date (fmt)) {
		if (!gee->calendar_combo) {
			gee->calendar_combo = go_calendar_button_new ();
			gtk_widget_show (gee->calendar_combo);
			gtk_box_pack_start (GTK_BOX (gee), gee->calendar_combo,
					    FALSE, TRUE, 0);
454 455 456 457 458
			gee->calendar_combo_changed =
				g_signal_connect (G_OBJECT (gee->calendar_combo),
						  "changed",
						  G_CALLBACK (cb_calendar_changed),
						  gee);
459 460 461 462 463 464
			gee_update_calendar (gee);
		}
	} else {
		if (gee->calendar_combo) {
			gtk_widget_destroy (gee->calendar_combo);
			gee->calendar_combo = NULL;
465
			gee->calendar_combo_changed = 0;
466 467 468
		}
	}

469 470 471
	g_object_notify (G_OBJECT (gee), "constant-format");
}

472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
static void
gee_set_with_icon (GnmExprEntry *gee, gboolean with_icon)
{
	gboolean has_icon = (gee->icon != NULL);
	with_icon = !!with_icon;

	if (has_icon == with_icon)
		return;

	if (with_icon) {
		gee->icon = gtk_toggle_button_new ();
		gtk_container_add (GTK_CONTAINER (gee->icon),
				   gtk_image_new_from_stock ("Gnumeric_ExprEntry",
							     GTK_ICON_SIZE_MENU));
		gtk_box_pack_end (GTK_BOX (gee), gee->icon, FALSE, FALSE, 0);
		gtk_widget_show_all (gee->icon);
		g_signal_connect (gee->icon, "clicked",
				  G_CALLBACK (cb_icon_clicked), gee);
	} else
		gtk_widget_destroy (gee->icon);
}

494 495 496 497 498 499
static void
gee_set_property (GObject      *object,
		  guint         prop_id,
		  GValue const *value,
		  GParamSpec   *pspec)
{
500
	GnmExprEntry *gee = GNM_EXPR_ENTRY (object);
501 502
	switch (prop_id) {
	case PROP_UPDATE_POLICY:
503
		gnm_expr_entry_set_update_policy (gee, g_value_get_enum (value));
504 505 506
		break;

	case PROP_WITH_ICON:
507
		gee_set_with_icon (gee, g_value_get_boolean (value));
508 509
		break;

510 511 512 513 514 515 516
	case PROP_TEXT: {
		const char *new_txt = g_value_get_string (value);
		const char *old_txt = gnm_expr_entry_get_text (gee);
		if (go_str_compare (new_txt, old_txt)) {
			gnm_expr_entry_load_from_text (gee, new_txt);
			gnm_expr_entry_signal_update (gee, FALSE);
		}
517
		break;
518
	}
519

520 521 522 523
	case PROP_FLAGS:
		gnm_expr_entry_set_flags (gee,
			g_value_get_uint (value), GNM_EE_MASK);
		break;
524
	case PROP_SCG:
525
		gnm_expr_entry_set_scg (gee,
526
			SHEET_CONTROL_GUI (g_value_get_object (value)));
527
		break;
Jody Goldberg's avatar
Jody Goldberg committed
528 529
	case PROP_WBCG:
		g_return_if_fail (gee->wbcg == NULL);
530
		gee->wbcg = WBC_GTK (g_value_get_object (value));
Jody Goldberg's avatar
Jody Goldberg committed
531
		break;
532 533 534
	case PROP_CONSTANT_FORMAT:
		gee_set_format (gee, g_value_get_pointer (value));
		break;
535 536
	case PROP_EDITING_CANCELED:
		gee->editing_canceled = g_value_get_boolean (value);
537 538 539 540 541 542 543 544 545 546 547
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
	}
}

static void
gee_get_property (GObject      *object,
		  guint         prop_id,
		  GValue       *value,
		  GParamSpec   *pspec)
{
548
	GnmExprEntry *gee = GNM_EXPR_ENTRY (object);
549 550 551 552 553 554 555
	switch (prop_id) {
	case PROP_UPDATE_POLICY:
		g_value_set_enum (value, gee->update_policy);
		break;
	case PROP_WITH_ICON:
		g_value_set_boolean (value, gee->icon != NULL);
		break;
556 557 558
	case PROP_TEXT:
		g_value_set_string (value, gnm_expr_entry_get_text (gee));
		break;
559 560 561
	case PROP_FLAGS:
		g_value_set_uint (value, gee->flags);
		break;
562 563
	case PROP_SCG:
		g_value_set_object (value, G_OBJECT (gee->scg));
564
		break;
Jody Goldberg's avatar
Jody Goldberg committed
565 566 567
	case PROP_WBCG:
		g_value_set_object (value, G_OBJECT (gee->wbcg));
		break;
568 569 570
	case PROP_CONSTANT_FORMAT:
		g_value_set_pointer (value, (gpointer)gee->constant_format);
		break;
571 572
	case PROP_EDITING_CANCELED:
		g_value_set_boolean (value, gee->editing_canceled);
573 574 575 576 577 578
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
	}
}

static void
579
cb_entry_activate (GnmExprEntry *gee)
580
{
581
	g_signal_emit (G_OBJECT (gee), signals[ACTIVATE], 0);
Jody Goldberg's avatar
Jody Goldberg committed
582
	gnm_expr_entry_signal_update (gee, TRUE);
583 584 585
}

static void
586 587
gee_destroy_feedback_range (GnmExprEntry *gee)
{
588 589 590 591 592 593 594 595
	WBCGtk *wbcg = scg_wbcg (gee->scg);
	int page, pages = wbcg_get_n_scg (wbcg);

	for (page = 0; page < pages; page++) {
		SheetControlGUI *scg = wbcg_get_nth_scg (wbcg, page);
		SCG_FOREACH_PANE (scg, pane,
				  gnm_pane_expr_cursor_stop (pane););
	}
596 597
}

598
static void
599
gnm_expr_entry_colour_ranges (GnmExprEntry *gee, int start, int end, GnmRangeRef *rr, int colour,
600
			      PangoAttrList **attrs, gboolean insert_cursor)
601 602 603 604 605 606
{
	static struct {
		guint16 red;
		guint16 green;
		guint16 blue;
		gchar const *name;
607 608
	} colours[] = {{0x0, 0xFFFF, 0x0, "00:ff:00:ff"},
		       {0x0, 0x0, 0xFFFF, "00:00:ff:ff"},
609 610 611
		       {0xFFFF, 0x0, 0x0, "ff:00:00:ff"},
		       {0x0, 0x80FF, 0x80FF, "00:80:80:ff"},
		       {0xA0FF, 0xA0FF, 0x0, "a0:a0:00:ff"},
612 613 614 615 616 617
		       {0xA0FF, 0x0, 0xA0FF, "a0:00:a0:ff"}};
	PangoAttribute *at;
	GnmRange r;
	GnmRange const *merge; /*[#127415]*/
	Sheet *start_sheet, *end_sheet;
	Sheet *sheet = scg_sheet (gee->scg);
618
	SheetControlGUI *scg = NULL;
619

620 621 622 623 624
	if (rr->a.sheet->workbook != gee->sheet->workbook) {
		/* We should show the range in an external workbook! */
		return;
	}

625 626 627
	if (*attrs == NULL)
		*attrs = pango_attr_list_new ();

628 629 630 631 632 633
	colour = colour % G_N_ELEMENTS (colours);

	gnm_rangeref_normalize_pp (rr, &gee->pp,
				   &start_sheet,
				   &end_sheet,
				   &r);
634
	if (start_sheet != end_sheet)
635
		return;
636 637 638 639 640
	if (insert_cursor) {
		if (range_is_singleton  (&r) &&
		    NULL != (merge = gnm_sheet_merge_is_corner
			     (start_sheet, &r.start)))
			r = *merge;
641
		if (start_sheet == sheet)
642 643 644 645 646
			scg = gee->scg;
		else {
			WBCGtk *wbcg = scg_wbcg (gee->scg);
			scg = wbcg_get_nth_scg (wbcg, start_sheet->index_in_wb);
		}
647 648

		SCG_FOREACH_PANE (scg, pane, gnm_pane_expr_cursor_bound_set
649 650
				  (pane, &r, colours[colour].name););
	}
651

652
	at = pango_attr_foreground_new (colours[colour].red, colours[colour].green,
653
					colours[colour].blue);
654 655
	at->start_index = start;
	at->end_index = end;
656

657
	pango_attr_list_change (*attrs, at);
658 659
}

660 661 662 663 664 665 666
/* WARNING : DO NOT CALL THIS FROM FROM UPDATE.  It may create another
 *           canvas-item which would in turn call update and confuse the
 *           canvas.
 */
static void
gee_scan_for_range (GnmExprEntry *gee)
{
667 668
	PangoAttrList *attrs = NULL;

669
	parse_pos_init_editpos (&gee->pp, scg_view (gee->scg));
670
	gee_destroy_feedback_range (gee);
671 672
	if (!gee->feedback_disabled && gee_is_editing (gee) && gee->lexer_items != NULL) {
		GnmLexerItem *gli = gee->lexer_items;
673 674 675 676 677
		int colour = 1; /* We start with 1 since GINT_TO_POINTER (0) == NULL */
		GHashTable *hash = g_hash_table_new_full ((GHashFunc) gnm_rangeref_hash,
							  (GEqualFunc) gnm_rangeref_equal,
							  g_free,
							  NULL);
678 679 680
		do {
			if (gli->token == RANGEREF) {
				char const *text = gtk_entry_get_text (gee->entry);
681
				char *rtext = g_strndup (text + gli->start,
682 683 684
							 gli->end - gli->start);
				char const *tmp;
				GnmRangeRef rr;
685
				tmp = rangeref_parse (&rr, rtext,
686
						      &gee->pp, gee_convs (gee));
687 688 689 690 691 692 693 694 695 696 697 698 699 700 701
				if (tmp != rtext) {
					gpointer val;
					gint this_colour;
					gboolean insert_cursor;
					if (rr.a.sheet == NULL)
						rr.a.sheet = gee->sheet;
					if (rr.b.sheet == NULL)
						rr.b.sheet = rr.a.sheet;
					val = g_hash_table_lookup (hash, &rr);
					if (val == NULL) {
						GnmRangeRef *rrr = gnm_rangeref_dup (&rr);
						this_colour = colour++;
						g_hash_table_insert (hash, rrr, GINT_TO_POINTER (this_colour));
						insert_cursor = TRUE;
						rtext = NULL;
702
					} else {
703 704 705
						this_colour = GPOINTER_TO_INT (val);
						insert_cursor = FALSE;
					}
706
					gnm_expr_entry_colour_ranges (gee, gli->start, gli->end, &rr,
707 708
								      this_colour, &attrs, insert_cursor);
				}
709
				g_free (rtext);
710
			}
711 712
		} while (gli++->token != 0);
		g_hash_table_destroy (hash);
713
	}
714
	if (attrs)
715
		g_object_set_data_full (G_OBJECT (gee->entry), "gnm:range-attributes", attrs,
716 717
					(GDestroyNotify) pango_attr_list_unref);
	else
718
		g_object_set_data (G_OBJECT (gee->entry), "gnm:range-attributes", NULL);
719 720 721 722
}

static void
gee_update_env (GnmExprEntry *gee)
723
{
724
	if (!gee->ignore_changes) {
725 726 727 728
		if (NULL != gee->scg &&
#warning why do we want this dichotomy
		    !gee->is_cell_renderer &&
		    !gnm_expr_entry_can_rangesel (gee))
729
			scg_rangesel_stop (gee->scg, FALSE);
730 731 732

		if (gnm_expr_char_start_p (gtk_entry_get_text (gee->entry)))
			gee_scan_for_range (gee);
733 734
	}

735 736
}

737
static gboolean
738
gee_delete_tooltip (GnmExprEntry *gee, gboolean remove_completion)
739
{
740
	gboolean has_tooltip = (gee->tooltip.tooltip != NULL &&
741 742 743 744 745 746
				gee->tooltip.timerid == 0);

	if (gee->tooltip.timerid) {
		g_source_remove (gee->tooltip.timerid);
		gee->tooltip.timerid = 0;
	}
747 748 749 750 751 752 753 754
	if (gee->tooltip.tooltip) {
		gtk_widget_destroy (gee->tooltip.tooltip);
		gee->tooltip.tooltip = NULL;
	}
	if (gee->tooltip.fd) {
		gnm_func_unref (gee->tooltip.fd);
		gee->tooltip.fd = NULL;
	}
Andreas J. Guelzow 's avatar
Andreas J. Guelzow committed
755
	if (gee->tooltip.handlerid != 0 && gee->entry != NULL) {
756
		g_signal_handler_disconnect (gtk_widget_get_toplevel
757 758 759 760
					     (GTK_WIDGET (gee->entry)),
					     gee->tooltip.handlerid);
		gee->tooltip.handlerid = 0;
	}
761
	if (remove_completion) {
762 763
		g_free (gee->tooltip.completion);
		gee->tooltip.completion = NULL;
764
		gee->tooltip.completion_se_valid = FALSE;
765
	}
766
	return has_tooltip;
767 768
}

769
void
770 771 772
gnm_expr_entry_close_tips  (GnmExprEntry *gee)
{
	if (gee != NULL)
773
		gee_delete_tooltip (gee, FALSE);
774 775
}

776
static gboolean
777 778 779
cb_gee_focus_out_event (GtkWidget         *widget,
			GdkEventFocus     *event,
			gpointer           user_data);
780

781
static gboolean
782 783 784 785 786 787 788 789 790
cb_show_tooltip (gpointer user_data)
{
	GnmExprEntry *gee = GNM_EXPR_ENTRY (user_data);
	gtk_widget_show_all (gee->tooltip.tooltip);
	gee->tooltip.timerid = 0;
	return FALSE;
}


791
static GtkWidget *
Morten Welinder's avatar
Morten Welinder committed
792
gee_create_tooltip (GnmExprEntry *gee, gchar const *str,
793
		    gchar const *marked_str, gboolean set_tabs)
794
{
795
	GtkWidget *toplevel, *label, *tip;
796
	gint root_x = 0, root_y = 0;
797 798
	GtkAllocation allocation;
	GdkWindow *gdkw;
799 800
	gchar *markup = NULL;
	GString *string;
801 802 803
	GtkTextBuffer *buffer;
	PangoAttrList *attr_list = NULL;
	char *text = NULL;
804

805 806
	toplevel = gtk_widget_get_toplevel (GTK_WIDGET (gee->entry));
	gtk_widget_add_events(toplevel, GDK_FOCUS_CHANGE_MASK);
807
	if (gee->tooltip.handlerid == 0)
808
		gee->tooltip.handlerid = g_signal_connect
809 810
			(G_OBJECT (toplevel), "focus-out-event",
			 G_CALLBACK (cb_gee_focus_out_event), gee);
811

812
	label = gnumeric_convert_to_tooltip (toplevel, gnumeric_create_tooltip_text_view_widget ());
813 814
	tip = gtk_widget_get_toplevel (label);

Morten Welinder's avatar
Morten Welinder committed
815
	if (str)
816 817 818 819
		markup = gnm_func_convert_markup_to_pango (str);
	string = g_string_new (markup);
	if (marked_str)
		g_string_append (string, marked_str);
820 821 822 823 824
	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (label));

	if (pango_parse_markup (string->str, -1, 0,
				&attr_list, &text,
				NULL, NULL)) {
825
		go_create_std_tags_for_buffer (buffer);
826 827 828 829 830 831
		gtk_text_buffer_set_text (buffer, text, -1);
		gnm_load_pango_attributes_into_buffer (attr_list, buffer, text);
		g_free (text);
		pango_attr_list_unref (attr_list);
	} else
		gtk_text_buffer_set_text (buffer, string->str, -1);
832
	g_free (markup);
833
	g_string_free (string, TRUE);
834

835 836
	if (set_tabs) {
		PangoTabArray *tabs;
Morten Welinder's avatar
Morten Welinder committed
837
		tabs = pango_tab_array_new_with_positions
838 839 840 841 842 843 844
			(5, TRUE,
			 PANGO_TAB_LEFT, 20,
			 PANGO_TAB_LEFT, 140,
			 PANGO_TAB_LEFT, 160,
			 PANGO_TAB_LEFT, 180,
			 PANGO_TAB_LEFT, 200);
		gtk_text_view_set_tabs (GTK_TEXT_VIEW (label), tabs);
845 846 847
		pango_tab_array_free (tabs);
	}

848 849 850
	gdkw = gtk_widget_get_window (GTK_WIDGET (gee->entry));
	gdk_window_get_origin (gdkw, &root_x, &root_y);
	gtk_widget_get_allocation (GTK_WIDGET (gee->entry), &allocation);
851

852 853
	gtk_window_move (GTK_WINDOW (tip),
			 root_x + allocation.x,
854
			 root_y + allocation.y + allocation.height);
855 856

	return tip;
857 858
}

859 860 861 862 863 864 865 866 867 868
static void
gee_set_tooltip_argument (GString *str, char *arg, gboolean optional)
{
	if (optional)
		g_string_append_c (str, '[');
	g_string_append (str, arg);
	if (optional)
		g_string_append_c (str, ']');
}

869
static void
870
gee_set_tooltip (GnmExprEntry *gee, GnmFunc *fd, gint args, gboolean had_stuff)
871 872 873 874 875
{
	GString *str;
	gchar sep = go_locale_get_arg_sep ();
	gint min, max, i;
	gboolean first = TRUE;
Morten Welinder's avatar
Morten Welinder committed
876
	char *extra = NULL;
877 878
	gboolean localized_function_names = gee->sheet->convs->localized_function_names;
	const char *fdname;
879

880 881 882
	gnm_func_load_if_stub (fd);
	function_def_count_args (fd, &min, &max);

Morten Welinder's avatar
Morten Welinder committed
883
	if ((gee->tooltip.fd)
884 885
	    && (gee->tooltip.fd == fd && gee->tooltip.args == args
		&& gee->tooltip.had_stuff == (max == 0 && args == 0 && had_stuff)))
886
			return;
887
	gee_delete_tooltip (gee, FALSE);
888 889

	gee->tooltip.fd = fd;
890
	gnm_func_ref (gee->tooltip.fd);
891

892 893 894
	fdname = gnm_func_get_name (fd, localized_function_names);

	str = g_string_new (fdname);
895 896 897 898 899 900 901 902 903 904
	g_string_append_c (str, '(');

	for (i = 0; i < max; i++) {
		char *arg_name = function_def_get_arg_name
			(fd, i);
		if (arg_name != NULL) {
			if (first)
				first = FALSE;
			else
				g_string_append_c (str, sep);
905
			if (i == args) {
Morten Welinder's avatar
Morten Welinder committed
906 907 908 909
				extra = g_strdup_printf
					(_("%s: %s"),
					 arg_name,
					 gnm_func_get_arg_description (fd, i));
910
				g_string_append (str, UNICODE_RIGHT_TRIANGLE);
Morten Welinder's avatar
Morten Welinder committed
911 912 913
			}
			gee_set_tooltip_argument (str, arg_name, i >= min);
			if (i == args)
914
				g_string_append (str, UNICODE_LEFT_TRIANGLE);
915 916 917 918 919 920 921
			g_free (arg_name);
		} else
			break;
	}
	if (i < max) {
		if (!first)
			g_string_append_c (str, sep);
922
		g_string_append
Morten Welinder's avatar
Morten Welinder committed
923
			(str, (args >= i && args < max)
924
			 ? UNICODE_RIGHT_TRIANGLE UNICODE_ELLIPSIS UNICODE_LEFT_TRIANGLE
Morten Welinder's avatar
Morten Welinder committed
925 926
			 : UNICODE_ELLIPSIS);
	}
927
	if (max == 0 && args == 0 && !had_stuff) {
928
		extra = g_strdup_printf (_("%s takes no arguments"),
929
					 fdname);
930
	} else if (args >= max) {
931
		g_string_append (str, UNICODE_RIGHT_TRIANGLE UNICODE_CROSS_AND_SKULLBONES UNICODE_LEFT_TRIANGLE);
Morten Welinder's avatar
Morten Welinder committed
932
		extra = g_strdup_printf (_("Too many arguments for %s"),
933
					 fdname);
934 935
	}
	g_string_append_c (str, ')');
Morten Welinder's avatar
Morten Welinder committed
936 937 938 939 940
	if (extra) {
		g_string_append_c (str, '\n');
		g_string_append (str, extra);
		g_free (extra);
	}
941

Morten Welinder's avatar
Morten Welinder committed
942
	gee->tooltip.tooltip = gee_create_tooltip
943
		(gee, str->str, _("\n\n<i>Ctrl-F4 to close tooltip</i>"), FALSE);
944
	gtk_widget_show_all (gee->tooltip.tooltip);
945
	gee->tooltip.args = args;
946
	gee->tooltip.had_stuff = (max == 0 && args == 0 && had_stuff);
947

948 949 950
	g_string_free (str, TRUE);
}

951
static gboolean
952
gee_set_tooltip_completion (GnmExprEntry *gee, GSList *list, guint start, guint end)
953 954
{
	GString *str;
955
	GString *str_marked;
956 957
	gint i = 0;
	gint max = 10;
958
	GSList *list_c = list;
959
	gchar const *name = NULL;
960
	gboolean show_tool_tip, had_tool_tip;
961
	gboolean localized_function_names = gee->sheet->convs->localized_function_names;
962

963
	had_tool_tip = gee_delete_tooltip (gee, TRUE);
964 965

	str = g_string_new (NULL);
966
	for (; list_c != NULL && ++i < max; list_c = list_c->next) {
967
		GnmFunc *fd = list_c->data;
968
		name = gnm_func_get_name (fd, localized_function_names);
969 970 971
		if ((end - start) < (guint) g_utf8_strlen (name, -1))
			/* xgettext: the first %s is a function name and */
			/* the second %s the function description */
972
			g_string_append_printf (str, _("\t%s \t%s\n"), name,
973 974 975 976
						gnm_func_get_description (fd));
		else {
			/* xgettext: the first %s is a function name and */
			/* the second %s the function description */
977
			g_string_append_printf (str, _("\342\234\223\t%s \t%s\n"), name,
978 979 980
						gnm_func_get_description (fd));
			i--;
		}
981
	}
982 983 984

	str_marked = g_string_new (NULL);
	if (i == max)
985
		g_string_append (str_marked, "\t" UNICODE_ELLIPSIS_VERT "\n");
986
	if (i == 1) {
Morten Welinder's avatar
Morten Welinder committed
987
		gee->tooltip.completion
988
			= g_strdup (name);
989
		/*xgettext: short form for: "type F4-key to complete the name"*/
990
		g_string_append (str_marked, _("\n\t<i>F4 to complete</i>"));
991
	} else if (i > 1)
992
		/*xgettext: short form for: "type shift-F4-keys to select the completion"*/
993
		g_string_append (str_marked, _("\n\t<i>\xe2\x87\xa7""F4 to select</i>"));
994 995
	else
		g_string_truncate (str, str->len - 1);
996 997 998
	gee->tooltip.completion_start = start;
	gee->tooltip.completion_end = end;
	gee->tooltip.completion_se_valid = TRUE;
999
	show_tool_tip = gnm_conf_get_core_gui_editing_function_name_tooltips ();
1000
	if (show_tool_tip) {
Morten Welinder's avatar
Morten Welinder committed
1001
		gee->tooltip.tooltip = gee_create_tooltip
1002
			(gee, str->str, str_marked->str, TRUE);
1003 1004 1005
		if (had_tool_tip)
			gtk_widget_show_all (gee->tooltip.tooltip);
		else
1006 1007 1008 1009
			gee->tooltip.timerid = g_timeout_add_full
				(G_PRIORITY_DEFAULT, 750,
				 cb_show_tooltip,
				 gee,
1010 1011
				 NULL);
	}
1012
	g_string_free (str, TRUE);
1013
	g_string_free (str_marked, TRUE);
1014
	g_slist_free_full (list, (GDestroyNotify) gnm_func_unref);
1015
	return show_tool_tip;
1016 1017
}

1018 1019
static void
gee_dump_lexer (GnmLexerItem *gli) {
1020
	g_printerr ("************\n");
1021
	do {
1022 1023
		g_printerr ("%2" G_GSIZE_FORMAT " to %2" G_GSIZE_FORMAT ": %d\n",
			    gli->start, gli->end, gli->token);
1024
	} while (gli++->token != 0);
1025
	g_printerr ("************\n");
Morten Welinder's avatar
Morten Welinder committed
1026

1027 1028
}

1029 1030
static gint
func_def_cmp (gconstpointer a_, gconstpointer b_, gpointer user)
1031
{
1032 1033 1034 1035 1036 1037 1038
	GnmFunc const * const a = (GnmFunc const * const)a_;
	GnmFunc const * const b = (GnmFunc const * const)b_;
	GnmExprEntry *gee = user;
	gboolean localized = gee->sheet->convs->localized_function_names;

	return g_utf8_collate (gnm_func_get_name (a, localized),
			       gnm_func_get_name (b, localized));
1039 1040
}

1041 1042 1043 1044 1045 1046 1047

static void
gee_update_lexer_items (GnmExprEntry *gee)
{
	GtkEditable *editable = GTK_EDITABLE (gee->entry);
	char *str = gtk_editable_get_chars (editable, 0, -1);
	Sheet *sheet = scg_sheet (gee->scg);
1048 1049
	GOFormat const *format;
	gboolean forced_text;
1050 1051 1052 1053 1054 1055 1056 1057 1058

	g_free (gee->lexer_items);
	gee->lexer_items = NULL;

	if (gee->texpr != NULL) {
		gnm_expr_top_unref (gee->texpr);
		gee->texpr = NULL;
	}

1059
	parse_pos_init_editpos (&gee->pp, scg_view (gee->scg));
1060 1061 1062
	format = gnm_style_get_format
		(sheet_style_get (sheet, gee->pp.eval.col, gee->pp.eval.row));
	forced_text = ((format != NULL) && go_format_is_text (format));
1063

1064
	if (!gee->feedback_disabled && !forced_text) {
1065
		gee->texpr = gnm_expr_parse_str
1066
			((str[0] == '=') ? str+1 : str,
1067
			 &gee->pp, GNM_EXPR_PARSE_DEFAULT
1068
			 | GNM_EXPR_PARSE_UNKNOWN_NAMES_ARE_STRINGS,
1069 1070 1071
			 sheet_get_conventions (sheet), NULL);
	}

1072 1073
	gee->tooltip.is_expr =  (!forced_text) &&
		(NULL != gnm_expr_char_start_p (str));
1074
	if (!(gee->flags & GNM_EE_SINGLE_RANGE)) {
Morten Welinder's avatar
Morten Welinder committed
1075
		gee->lexer_items = gnm_expr_lex_all
1076 1077 1078 1079 1080 1081
			(str, &gee->pp,
			 GNM_EXPR_PARSE_UNKNOWN_NAMES_ARE_STRINGS,
			 NULL);
		if (gnm_debug_flag ("functooltip"))
			gee_dump_lexer (gee->lexer_items);
	}
Morten Welinder's avatar
Morten Welinder committed
1082
	g_free (str);
1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098
}

static GnmLexerItem *
gee_duplicate_lexer_items (GnmLexerItem *gli)
{
	int n = 1;
	GnmLexerItem *gli_c = gli;

	while (gli_c->token != 0) {
		gli_c++;
		n++;
	}

	return g_memdup (gli, n * sizeof (GnmLexerItem));
}

1099 1100 1101 1102
static void
gee_check_tooltip (GnmExprEntry *gee)
{
	GtkEditable *editable = GTK_EDITABLE (gee->entry);
1103
	gint  end, args = 0;
1104
	guint end_t;
1105
	char *str;
1106
	gboolean stuff = FALSE, completion_se_set = FALSE;
1107
	GnmLexerItem *gli, *gli_c;
1108
	int last_token = 0;
1109

Morten Welinder's avatar
Morten Welinder committed
1110
	if (gee->lexer_items == NULL || !gee->tooltip.enabled ||
1111
	    (!gee->tooltip.is_expr && !gee->is_cell_renderer)) {
1112
		gee_delete_tooltip (gee, TRUE);
1113
		return;
1114
	}
1115

1116 1117 1118
	end = gtk_editable_get_position (editable);

	if (end == 0) {
1119
		gee_delete_tooltip (gee, TRUE);
1120 1121
		return;
	}
1122

1123 1124
	str = gtk_editable_get_chars (editable, 0, -1);
	end_t = g_utf8_offset_to_pointer (str, end) - str;
1125

1126 1127

	gli_c = gli = gee_duplicate_lexer_items (gee->lexer_items);
1128

1129 1130
	/*
	 * If we have an open string at the end of the entry, we
1131
	 * need to adjust.
1132
	 */
Morten Welinder's avatar
Morten Welinder committed
1133

1134
	for (; gli->token != 0; gli++) {
1135 1136 1137 1138
		if (gli->start >= end_t) {
			gli->token = 0;
			break;
		}
1139 1140 1141 1142 1143 1144
		if (gli->token != TOKEN_UNMATCHED_APOSTROPHY)
			continue;
		if (gli->start == 0)
			goto not_found;
		gli->token = 0;
		stuff = TRUE;
1145
		break;
1146
	}
1147 1148
	if (gli > gli_c)
		gli--;
1149 1150
	if (gli > gli_c)
		last_token = (gli - 1)->token;
1151

1152
	/* This creates the completion tooltip */
1153 1154 1155 1156
	if (!stuff &&
	    gli->token == STRING &&
	    last_token != CONSTANT &&
	    last_token != '$') {
1157 1158 1159 1160 1161 1162
		guint start_t = gli->start;
		char *prefix;
		GSList *list;

		end_t = gli->end;
		prefix = g_strndup (str + start_t, end_t - start_t);
Morten Welinder's avatar
Morten Welinder committed
1163
		list = gnm_func_lookup_prefix
1164 1165 1166
			(prefix, gee->sheet->workbook);
		g_free (prefix);
		if (list != NULL) {
1167
			list = g_slist_sort_with_data
Morten Welinder's avatar
Morten Welinder committed
1168
				(list,
1169 1170
				 func_def_cmp,
				 gee);
Morten Welinder's avatar
Morten Welinder committed
1171
			if (gee_set_tooltip_completion
1172 1173 1174 1175 1176
			    (gee, list, start_t, end_t)) {
				g_free (str);
				g_free (gli_c);
				return;
			}
1177
		} else {
1178 1179
			g_free (gee->tooltip.completion);
			gee->tooltip.completion = NULL;
1180 1181 1182
			gee->tooltip.completion_start = start_t;
			gee->tooltip.completion_end = end_t;
			gee->tooltip.completion_se_valid = TRUE;
1183
		}
Morten Welinder's avatar
Morten Welinder committed
1184
		completion_se_set = TRUE;
1185 1186 1187
	} else {
		g_free (gee->tooltip.completion);
		gee->tooltip.completion = NULL;
Morten Welinder's avatar
Morten Welinder committed
1188
		gee->tooltip.completion_se_valid = FALSE;
1189
	}
Morten Welinder's avatar
Morten Welinder committed
1190

1191 1192 1193

	if (!gnm_conf_get_core_gui_editing_function_argument_tooltips ())
		goto not_found;
1194

1195
	if (gnm_debug_flag ("functooltip"))
1196 1197 1198
		g_printerr ("Last token considered is %d from %2"
			    G_GSIZE_FORMAT " to %2" G_GSIZE_FORMAT ".\n",
			    gli->token, gli->start, gli->end);
Morten Welinder's avatar
Morten Welinder committed
1199

1200

1201 1202 1203
	while (gli->start > 1) {
		switch (gli->token) {
		case TOKEN_PARENTHESIS_OPEN:
1204
			if ((gli - 1)->token == STRING) {
1205 1206
				gint start_t = (gli - 1)->start;
				gint end_t = (gli - 1)->end;
Morten Welinder's avatar
Morten Welinder committed
1207
				char *name = g_strndup (str + start_t,
1208 1209 1210 1211 1212 1213 1214 1215
							end_t - start_t);
				GnmFunc	*fd = gnm_func_lookup (name, NULL);
				g_free (name);
				if (fd != NULL) {
					gee_set_tooltip (gee, fd, args, stuff);
					g_free (str);
					g_free (gli_c);
					return;
1216 1217
				}
			}
1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233
			stuff = TRUE;
			args = 0;
			break;
		case TOKEN_BRACE_OPEN:
			stuff = (args == 0);
			args = 0;
			break;
		case TOKEN_PARENTHESIS_CLOSED: {
			gint para = 1;
			gli--;
			while (gli->start > 1 && para > 0) {
				switch (gli->token) {
				case TOKEN_PARENTHESIS_CLOSED:
					para++;
					break;
				case TOKEN_PARENTHESIS_OPEN:
Morten Welinder's avatar
Morten Welinder committed
1234
					para--;
1235 1236 1237 1238 1239
					break;
				default:
					break;
				}
				gli--;
1240
			}
1241 1242 1243
			gli++;
			stuff = (args == 0);
			break;
1244
		}
1245 1246 1247 1248 1249 1250