gnumeric-expr-entry.c 74.1 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 "gnm-i18n.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
#warning We should replace these token names with the correct values
   enum yytokentype {
     STRING = 258,
     QUOTED_STRING = 259,
     CONSTANT = 260,
     RANGEREF = 261,
     INTERSECT = 268,
     ARG_SEP = 269,
62
     INVALID_TOKEN = 273
63
   };
64
#define TOKEN_UNMATCHED_APOSTROPHE INVALID_TOKEN
65

66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
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;
}
81

82
typedef struct {
Jody Goldberg's avatar
Jody Goldberg committed
83
	GnmRangeRef ref;
84 85 86
	int	    text_start;
	int	    text_end;
	gboolean    is_valid;
87 88
} Rangesel;

89
struct _GnmExprEntry {
90
	GtkBox	parent;
91 92

	GtkEntry		*entry;
93
	GtkWidget               *calendar_combo;
94
	gulong                   calendar_combo_changed;
95
	GtkWidget		*icon;
96 97
	SheetControlGUI		*scg;	/* the source of the edit */
	Sheet			*sheet;	/* from scg */
98
	GnmParsePos		 pp;	/* from scg->sv */
99
	WBCGtk			*wbcg;	/* from scg */
100 101
	Rangesel		 rangesel;

102
	GnmExprEntryFlags	 flags;
103 104
	int			 freeze_count;

105
	GnmUpdateType		 update_policy;
106
	guint			 update_timeout_id;
107

108 109 110
	gboolean                 is_cell_renderer;  /* as cell_editable */
	gboolean                 editing_canceled;  /* as cell_editable */
	gboolean                 ignore_changes; /* internal mutex */
111

112 113 114
	gboolean                 feedback_disabled;
	GnmLexerItem            *lexer_items;
	GnmExprTop const        *texpr;
115 116 117
	struct {
		GtkWidget       *tooltip;
		GnmFunc         *fd;
118
		gint             args;
119
		gboolean         had_stuff;
120
		gulong           handlerid;
121
		guint            timerid;
122
		gboolean         enabled;
123
		gboolean         is_expr;
124
		gboolean         completion_se_valid;
125 126 127
		gchar           *completion;
		guint            completion_start;
		guint            completion_end;
128
	}                        tooltip;
129 130

	GOFormat const *constant_format;
131 132
};

133
typedef struct _GnmExprEntryClass {
134
	GtkBoxClass base;
135

Jody Goldberg's avatar
Jody Goldberg committed
136 137 138
	void (* update)   (GnmExprEntry *gee, gboolean user_requested_update);
	void (* changed)  (GnmExprEntry *gee);
	void (* activate) (GnmExprEntry *gee);
139
} GnmExprEntryClass;
140

141 142
/* Signals */
enum {
143 144
	UPDATE,
	CHANGED,
145
	ACTIVATE,
146 147 148
	LAST_SIGNAL
};

149 150 151
/* Properties */
enum {
	PROP_0,
152 153
	PROP_UPDATE_POLICY,
	PROP_WITH_ICON,
154
	PROP_TEXT,
155
	PROP_FLAGS,
Jody Goldberg's avatar
Jody Goldberg committed
156
	PROP_SCG,
157
	PROP_WBCG,
158 159
	PROP_CONSTANT_FORMAT,
	PROP_EDITING_CANCELED
160 161
};

162 163 164 165
static guint signals[LAST_SIGNAL] = { 0 };

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

167 168 169 170 171 172
/* 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);
173
static void     cb_gee_notify_cursor_position (GnmExprEntry *gee);
174

175
static gboolean gee_debug;
176
static GtkWidgetClass *parent_class = NULL;
177

178 179 180 181 182 183
static gboolean
gee_is_editing (GnmExprEntry *gee)
{
	return (gee != NULL && gee->wbcg != NULL && wbcg_is_editing (gee->wbcg));
}

184 185 186 187 188 189
static GnmConventions const *
gee_convs (const GnmExprEntry *gee)
{
	return sheet_get_conventions (gee->sheet);
}

190 191 192 193
static inline void
gee_force_abs_rel (GnmExprEntry *gee)
{
	Rangesel *rs = &gee->rangesel;
194
	rs->is_valid = FALSE;
195 196 197
	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;
198
        else if ((gee->flags & GNM_EE_FORCE_REL_REF))
199 200 201 202
		rs->ref.a.col_relative = rs->ref.b.col_relative =
			rs->ref.a.row_relative = rs->ref.b.row_relative = TRUE;
}

203
static void
204
gee_rangesel_reset (GnmExprEntry *gee)
205 206 207 208 209
{
	Rangesel *rs = &gee->rangesel;

	rs->text_start = 0;
	rs->text_end = 0;
210
	memset (&rs->ref, 0, sizeof (rs->ref));
211 212 213 214
	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);
215

216
	rs->is_valid = FALSE;
217 218 219
}

static void
220
gee_destroy (GtkWidget *widget)
221
{
222
	GnmExprEntry *gee = GNM_EXPR_ENTRY (widget);
Jody Goldberg's avatar
Jody Goldberg committed
223 224
	gee_remove_update_timer (gee);
	gee_detach_scg (gee);
225
	((GtkWidgetClass *)(parent_class))->destroy (widget);
226 227
}

228 229 230 231 232 233 234 235 236 237 238 239 240
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.
	 */

241
	if (toplevel != NULL && gtk_widget_is_toplevel (toplevel)) {
242 243 244
		GtkWidget *old_entry_parent;
		GtkWidget *old_toplevel_child;
		GParamSpec **container_props_pspec;
245
		GArray *container_props;
246 247 248 249

		g_assert (GTK_IS_WINDOW (toplevel));

		if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (icon))) {
250 251
			int width, height;
			guint n;
252 253 254 255 256 257 258 259 260 261

			/* 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",
262 263
						g_object_ref (old_entry_parent),
						(GDestroyNotify) g_object_unref);
264 265 266 267

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

			g_object_set_data_full (G_OBJECT (entry), "old_toplevel_child",
268 269
						g_object_ref (old_toplevel_child),
						(GDestroyNotify) g_object_unref);
270 271 272 273

			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));
274
			g_object_set_data (G_OBJECT (entry), "old_default",
275
					   gtk_window_get_default_widget (GTK_WINDOW (toplevel)));
276 277 278 279 280 281

			container_props = NULL;

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

282
			if (container_props_pspec[0] != NULL) {
283
				guint ui;
284

285
				container_props = g_array_sized_new (FALSE, TRUE, sizeof (GValue), n);
286

287
				for (ui = 0; ui < n; ui++) {
288
					GValue value = G_VALUE_INIT;
289
					g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (container_props_pspec[ui]));
290 291

					gtk_container_child_get_property (GTK_CONTAINER (old_entry_parent), GTK_WIDGET (entry),
292
									  g_param_spec_get_name (container_props_pspec[ui]),
293
									  &value);
294
					g_array_append_val (container_props, value);
295 296 297 298 299
				}
			}

			g_object_set_data_full (G_OBJECT (entry), "container_props",
						container_props,
300
						(GDestroyNotify) g_array_unref);
301 302 303 304 305 306 307 308
			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));
309 310 311
			gtk_widget_set_can_default (GTK_WIDGET (icon), TRUE);
			gtk_widget_grab_default (GTK_WIDGET (icon));

312 313 314 315
			gtk_window_resize (GTK_WINDOW (toplevel), 1, 1);

		} else {
			int i;
316
			gpointer default_widget;
317 318 319 320 321 322 323 324 325

			/* 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));

326
			g_object_ref (entry);
327 328 329
			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));
330
			g_object_unref (entry);
331 332 333 334

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

335 336 337
			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]),
338
								  &g_array_index (container_props, GValue, i));
339 340 341 342 343
			}

			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")));
344 345 346 347 348
			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);
			}
349 350 351 352 353 354 355 356 357 358 359

			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.");
	}
}

360 361 362 363
static GnmValue *
get_matched_value (GnmExprEntry *gee)
{
	GODateConventions const *date_conv =
364
		sheet_date_conv (gee->sheet);
365 366 367 368 369 370 371 372 373 374 375 376
	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 =
377
		sheet_date_conv (gee->sheet);
378 379 380 381 382 383 384 385

	if (!gee->calendar_combo)
		return;

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

386 387 388
	if (datetime_value_to_g (&date, v, date_conv)) {
		g_signal_handler_block (gee->calendar_combo,
					gee->calendar_combo_changed);
389 390 391
		go_calendar_button_set_date
			(GO_CALENDAR_BUTTON (gee->calendar_combo),
			 &date);
392 393 394
		g_signal_handler_unblock (gee->calendar_combo,
					  gee->calendar_combo_changed);
	}
395 396 397 398 399 400 401 402 403

	value_release (v);
}

static void
cb_calendar_changed (GOCalendarButton *calb, GnmExprEntry *gee)
{
	GDate date;
	GODateConventions const *date_conv =
404
		sheet_date_conv (gee->sheet);
405 406 407 408 409
	int serial;

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

410
	serial = go_date_g_to_serial (&date, date_conv);
411 412 413 414

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

415 416 417 418 419 420 421 422 423 424
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;

425
	if (gee_debug)
426 427
		g_printerr ("Setting format %s\n",
			    fmt ? go_format_as_XL (fmt) : "-");
428

429 430 431 432 433 434
	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);
435 436 437 438 439
			gee->calendar_combo_changed =
				g_signal_connect (G_OBJECT (gee->calendar_combo),
						  "changed",
						  G_CALLBACK (cb_calendar_changed),
						  gee);
440 441 442 443 444 445
			gee_update_calendar (gee);
		}
	} else {
		if (gee->calendar_combo) {
			gtk_widget_destroy (gee->calendar_combo);
			gee->calendar_combo = NULL;
446
			gee->calendar_combo_changed = 0;
447 448 449
		}
	}

450 451 452
	g_object_notify (G_OBJECT (gee), "constant-format");
}

453 454 455 456 457 458 459 460 461 462 463 464
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),
465 466
				   gtk_image_new_from_icon_name ("gnumeric-exprentry",
								 GTK_ICON_SIZE_MENU));
467 468 469 470 471 472 473 474
		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);
}

475 476 477 478 479 480
static void
gee_set_property (GObject      *object,
		  guint         prop_id,
		  GValue const *value,
		  GParamSpec   *pspec)
{
481
	GnmExprEntry *gee = GNM_EXPR_ENTRY (object);
482 483
	switch (prop_id) {
	case PROP_UPDATE_POLICY:
484
		gnm_expr_entry_set_update_policy (gee, g_value_get_enum (value));
485 486 487
		break;

	case PROP_WITH_ICON:
488
		gee_set_with_icon (gee, g_value_get_boolean (value));
489 490
		break;

491 492 493 494 495 496 497
	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);
		}
498
		break;
499
	}
500

501 502 503 504
	case PROP_FLAGS:
		gnm_expr_entry_set_flags (gee,
			g_value_get_uint (value), GNM_EE_MASK);
		break;
505
	case PROP_SCG:
506
		gnm_expr_entry_set_scg (gee,
Morten Welinder's avatar
Morten Welinder committed
507
			GNM_SCG (g_value_get_object (value)));
508
		break;
Jody Goldberg's avatar
Jody Goldberg committed
509 510
	case PROP_WBCG:
		g_return_if_fail (gee->wbcg == NULL);
511
		gee->wbcg = WBC_GTK (g_value_get_object (value));
Jody Goldberg's avatar
Jody Goldberg committed
512
		break;
513
	case PROP_CONSTANT_FORMAT:
514
		gee_set_format (gee, g_value_get_boxed (value));
515
		break;
516 517
	case PROP_EDITING_CANCELED:
		gee->editing_canceled = g_value_get_boolean (value);
518 519 520 521 522 523 524 525 526 527 528
	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)
{
529
	GnmExprEntry *gee = GNM_EXPR_ENTRY (object);
530 531 532 533 534 535 536
	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;
537 538 539
	case PROP_TEXT:
		g_value_set_string (value, gnm_expr_entry_get_text (gee));
		break;
540 541 542
	case PROP_FLAGS:
		g_value_set_uint (value, gee->flags);
		break;
543 544
	case PROP_SCG:
		g_value_set_object (value, G_OBJECT (gee->scg));
545
		break;
Jody Goldberg's avatar
Jody Goldberg committed
546 547 548
	case PROP_WBCG:
		g_value_set_object (value, G_OBJECT (gee->wbcg));
		break;
549
	case PROP_CONSTANT_FORMAT:
550
		g_value_set_boxed (value, (gpointer)gee->constant_format);
551
		break;
552 553
	case PROP_EDITING_CANCELED:
		g_value_set_boolean (value, gee->editing_canceled);
554 555 556 557 558 559
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
	}
}

static void
560
cb_entry_activate (GnmExprEntry *gee)
561
{
562
	g_signal_emit (G_OBJECT (gee), signals[ACTIVATE], 0);
Jody Goldberg's avatar
Jody Goldberg committed
563
	gnm_expr_entry_signal_update (gee, TRUE);
564 565 566
}

static void
567 568
gee_destroy_feedback_range (GnmExprEntry *gee)
{
569 570 571 572 573 574 575 576
	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););
	}
577 578
}

579
static void
580
gnm_expr_entry_colour_ranges (GnmExprEntry *gee, int start, int end, GnmRangeRef *rr, int colour,
581
			      PangoAttrList **attrs, gboolean insert_cursor)
582
{
583 584 585 586 587 588 589 590
	static const GOColor colours[] = {
		GO_COLOR_FROM_RGB (0x00, 0xff, 0x00),
		GO_COLOR_FROM_RGB (0x00, 0x00, 0xff),
		GO_COLOR_FROM_RGB (0xff, 0x00, 0x00),
		GO_COLOR_FROM_RGB (0x00, 0x80, 0x80),
		GO_COLOR_FROM_RGB (0xa0, 0xa0, 0x00),
		GO_COLOR_FROM_RGB (0xa0, 0x00, 0xa0)
	};
591 592 593 594 595
	PangoAttribute *at;
	GnmRange r;
	GnmRange const *merge; /*[#127415]*/
	Sheet *start_sheet, *end_sheet;
	Sheet *sheet = scg_sheet (gee->scg);
596
	SheetControlGUI *scg = NULL;
597

598 599 600 601 602
	if (rr->a.sheet->workbook != gee->sheet->workbook) {
		/* We should show the range in an external workbook! */
		return;
	}

603 604 605
	if (*attrs == NULL)
		*attrs = pango_attr_list_new ();

606 607 608 609 610 611
	colour = colour % G_N_ELEMENTS (colours);

	gnm_rangeref_normalize_pp (rr, &gee->pp,
				   &start_sheet,
				   &end_sheet,
				   &r);
612
	if (start_sheet != end_sheet)
613
		return;
614 615 616 617 618
	if (insert_cursor) {
		if (range_is_singleton  (&r) &&
		    NULL != (merge = gnm_sheet_merge_is_corner
			     (start_sheet, &r.start)))
			r = *merge;
619
		if (start_sheet == sheet)
620 621 622 623 624
			scg = gee->scg;
		else {
			WBCGtk *wbcg = scg_wbcg (gee->scg);
			scg = wbcg_get_nth_scg (wbcg, start_sheet->index_in_wb);
		}
625 626

		SCG_FOREACH_PANE (scg, pane, gnm_pane_expr_cursor_bound_set
627
				  (pane, &r, colours[colour]););
628
	}
629

630
	at = go_color_to_pango (colours[colour], TRUE);
631 632
	at->start_index = start;
	at->end_index = end;
633

634
	pango_attr_list_change (*attrs, at);
635 636
}

637 638 639 640 641 642 643
/* 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)
{
644 645
	PangoAttrList *attrs = NULL;

646
	parse_pos_init_editpos (&gee->pp, scg_view (gee->scg));
647
	gee_destroy_feedback_range (gee);
648 649
	if (!gee->feedback_disabled && gee_is_editing (gee) && gee->lexer_items != NULL) {
		GnmLexerItem *gli = gee->lexer_items;
650 651 652 653 654
		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);
655 656 657
		do {
			if (gli->token == RANGEREF) {
				char const *text = gtk_entry_get_text (gee->entry);
658
				char *rtext = g_strndup (text + gli->start,
659 660 661
							 gli->end - gli->start);
				char const *tmp;
				GnmRangeRef rr;
662
				tmp = rangeref_parse (&rr, rtext,
663
						      &gee->pp, gee_convs (gee));
664 665 666 667 668 669 670 671 672 673 674 675 676 677
				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;
678
					} else {
679 680 681
						this_colour = GPOINTER_TO_INT (val);
						insert_cursor = FALSE;
					}
682
					gnm_expr_entry_colour_ranges (gee, gli->start, gli->end, &rr,
683 684
								      this_colour, &attrs, insert_cursor);
				}
685
				g_free (rtext);
686
			}
687 688
		} while (gli++->token != 0);
		g_hash_table_destroy (hash);
689
	}
690
	if (attrs)
691
		g_object_set_data_full (G_OBJECT (gee->entry), "gnm:range-attributes", attrs,
692 693
					(GDestroyNotify) pango_attr_list_unref);
	else
694
		g_object_set_data (G_OBJECT (gee->entry), "gnm:range-attributes", NULL);
695 696 697 698
}

static void
gee_update_env (GnmExprEntry *gee)
699
{
700
	if (!gee->ignore_changes) {
701 702 703 704
		if (NULL != gee->scg &&
#warning why do we want this dichotomy
		    !gee->is_cell_renderer &&
		    !gnm_expr_entry_can_rangesel (gee))
705
			scg_rangesel_stop (gee->scg, FALSE);
706 707 708

		if (gnm_expr_char_start_p (gtk_entry_get_text (gee->entry)))
			gee_scan_for_range (gee);
709 710
	}

711 712
}

713
static gboolean
714
gee_delete_tooltip (GnmExprEntry *gee, gboolean remove_completion)
715
{
716
	gboolean has_tooltip = (gee->tooltip.tooltip != NULL &&
717 718 719 720 721 722
				gee->tooltip.timerid == 0);

	if (gee->tooltip.timerid) {
		g_source_remove (gee->tooltip.timerid);
		gee->tooltip.timerid = 0;
	}
723 724 725 726 727 728 729 730
	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
731
	if (gee->tooltip.handlerid != 0 && gee->entry != NULL) {
732
		g_signal_handler_disconnect (gtk_widget_get_toplevel
733 734 735 736
					     (GTK_WIDGET (gee->entry)),
					     gee->tooltip.handlerid);
		gee->tooltip.handlerid = 0;
	}
737
	if (remove_completion) {
738 739
		g_free (gee->tooltip.completion);
		gee->tooltip.completion = NULL;
740
		gee->tooltip.completion_se_valid = FALSE;
741
	}
742
	return has_tooltip;
743 744
}

745
void
746 747 748
gnm_expr_entry_close_tips  (GnmExprEntry *gee)
{
	if (gee != NULL)
749
		gee_delete_tooltip (gee, FALSE);
750 751
}

752
static gboolean
753 754 755
cb_gee_focus_out_event (GtkWidget         *widget,
			GdkEventFocus     *event,
			gpointer           user_data);
756

757
static gboolean
758 759 760 761 762 763 764 765 766
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;
}


767
static GtkWidget *
Morten Welinder's avatar
Morten Welinder committed
768
gee_create_tooltip (GnmExprEntry *gee, gchar const *str,
769
		    gchar const *marked_str, gboolean set_tabs)
770
{
771
	GtkWidget *toplevel, *label, *tip;
772
	gint root_x = 0, root_y = 0;
773 774
	GtkAllocation allocation;
	GdkWindow *gdkw;
775 776
	gchar *markup = NULL;
	GString *string;
777 778 779
	GtkTextBuffer *buffer;
	PangoAttrList *attr_list = NULL;
	char *text = NULL;
780

781 782
	toplevel = gtk_widget_get_toplevel (GTK_WIDGET (gee->entry));
	gtk_widget_add_events(toplevel, GDK_FOCUS_CHANGE_MASK);
783
	if (gee->tooltip.handlerid == 0)
784
		gee->tooltip.handlerid = g_signal_connect
785 786
			(G_OBJECT (toplevel), "focus-out-event",
			 G_CALLBACK (cb_gee_focus_out_event), gee);
787

Morten Welinder's avatar
Morten Welinder committed
788
	label = gnm_convert_to_tooltip (toplevel, gtk_text_view_new ());
789 790
	tip = gtk_widget_get_toplevel (label);

791 792 793
	gtk_style_context_add_class (gtk_widget_get_style_context (label),
				     "function-help");

Morten Welinder's avatar
Morten Welinder committed
794
	if (str)
795
		markup = gnm_func_convert_markup_to_pango (str, label);
796 797 798
	string = g_string_new (markup);
	if (marked_str)
		g_string_append (string, marked_str);
799 800 801 802 803
	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (label));

	if (pango_parse_markup (string->str, -1, 0,
				&attr_list, &text,
				NULL, NULL)) {
804
		go_create_std_tags_for_buffer (buffer);
805 806 807 808 809 810
		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);
811
	g_free (markup);
812
	g_string_free (string, TRUE);
813

814 815
	if (set_tabs) {
		PangoTabArray *tabs;
Morten Welinder's avatar
Morten Welinder committed
816
		tabs = pango_tab_array_new_with_positions
817 818 819 820 821 822 823
			(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);
824 825 826
		pango_tab_array_free (tabs);
	}

827 828 829
	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);
830

831 832
	gtk_window_move (GTK_WINDOW (tip),
			 root_x + allocation.x,
833
			 root_y + allocation.y + allocation.height);
834 835

	return tip;
836 837
}

838 839 840 841 842 843 844 845 846 847
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, ']');
}

848
static void
849
gee_set_tooltip (GnmExprEntry *gee, GnmFunc *fd, gint args, gboolean had_stuff)
850 851 852 853 854
{
	GString *str;
	gchar sep = go_locale_get_arg_sep ();
	gint min, max, i;
	gboolean first = TRUE;
Morten Welinder's avatar
Morten Welinder committed
855
	char *extra = NULL;
856 857
	gboolean localized_function_names = gee->sheet->convs->localized_function_names;
	const char *fdname;
858

859
	gnm_func_load_if_stub (fd);
Morten Welinder's avatar
Morten Welinder committed
860
	gnm_func_count_args (fd, &min, &max);
861

Morten Welinder's avatar
Morten Welinder committed
862
	if ((gee->tooltip.fd)
863 864
	    && (gee->tooltip.fd == fd && gee->tooltip.args == args
		&& gee->tooltip.had_stuff == (max == 0 && args == 0 && had_stuff)))
865
			return;
866
	gee_delete_tooltip (gee, FALSE);
867 868

	gee->tooltip.fd = fd;
869
	gnm_func_ref (gee->tooltip.fd);
870

871 872 873
	fdname = gnm_func_get_name (fd, localized_function_names);

	str = g_string_new (fdname);
874 875 876
	g_string_append_c (str, '(');

	for (i = 0; i < max; i++) {
Morten Welinder's avatar
Morten Welinder committed
877
		char *arg_name = gnm_func_get_arg_name
878 879 880 881 882 883
			(fd, i);
		if (arg_name != NULL) {
			if (first)
				first = FALSE;
			else
				g_string_append_c (str, sep);
884
			if (i == args) {
Morten Welinder's avatar
Morten Welinder committed
885 886 887 888
				extra = g_strdup_printf
					(_("%s: %s"),
					 arg_name,
					 gnm_func_get_arg_description (fd, i));
889
				g_string_append (str, UNICODE_RIGHT_TRIANGLE);
Morten Welinder's avatar
Morten Welinder committed
890 891 892
			}
			gee_set_tooltip_argument (str, arg_name, i >= min);
			if (i == args)
893
				g_string_append (str, UNICODE_LEFT_TRIANGLE);
894 895 896 897 898 899 900
			g_free (arg_name);
		} else
			break;
	}
	if (i < max) {
		if (!first)
			g_string_append_c (str, sep);
901
		g_string_append
Morten Welinder's avatar
Morten Welinder committed
902
			(str, (args >= i && args < max)
903
			 ? UNICODE_RIGHT_TRIANGLE UNICODE_ELLIPSIS UNICODE_LEFT_TRIANGLE
Morten Welinder's avatar
Morten Welinder committed
904 905
			 : UNICODE_ELLIPSIS);
	}
906
	if (max == 0 && args == 0 && !had_stuff) {
907
		extra = g_strdup_printf (_("%s takes no arguments"),
908
					 fdname);
909
	} else if (args >= max) {
910
		g_string_append (str, UNICODE_RIGHT_TRIANGLE UNICODE_CROSS_AND_SKULLBONES UNICODE_LEFT_TRIANGLE);
Morten Welinder's avatar
Morten Welinder committed
911
		extra = g_strdup_printf (_("Too many arguments for %s"),
912
					 fdname);
913 914
	}
	g_string_append_c (str, ')');
Morten Welinder's avatar
Morten Welinder committed
915 916 917 918 919
	if (extra) {
		g_string_append_c (str, '\n');
		g_string_append (str, extra);
		g_free (extra);
	}
920

Morten Welinder's avatar
Morten Welinder committed
921
	gee->tooltip.tooltip = gee_create_tooltip
922
		(gee, str->str, _("\n\n<i>Ctrl-F4 to close tooltip</i>"), FALSE);
923
	gtk_widget_show_all (gee->tooltip.tooltip);
924
	gee->tooltip.args = args;
925
	gee->tooltip.had_stuff = (max == 0 && args == 0 && had_stuff);
926

927 928 929
	g_string_free (str, TRUE);
}

930
static gboolean
931
gee_set_tooltip_completion (GnmExprEntry *gee, GSList *list, guint start, guint end)
932 933
{
	GString *str;
934
	GString *str_marked;
935 936
	gint i = 0;
	gint max = 10;
937
	GSList *list_c = list;
938
	gchar const *name = NULL;
939
	gboolean show_tool_tip, had_tool_tip;
940
	gboolean localized_function_names = gee->sheet->convs->localized_function_names;
941

942
	had_tool_tip = gee_delete_tooltip (gee, TRUE);
943 944

	str = g_string_new (NULL);
945
	for (; list_c != NULL && ++i < max; list_c = list_c->next) {
946
		GnmFunc *fd = list_c->data;
947
		name = gnm_func_get_name (fd, localized_function_names);
948 949 950
		if ((end - start) < (guint) g_utf8_strlen (name, -1))
			/* xgettext: the first %s is a function name and */
			/* the second %s the function description */
951
			g_string_append_printf (str, _("\t%s \t%s\n"), name,
952 953 954 955
						gnm_func_get_description (fd));
		else {
			/* xgettext: the first %s is a function name and */
			/* the second %s the function description */
956
			g_string_append_printf (str, _("\342\234\223\t%s \t%s\n"), name,
957 958 959
						gnm_func_get_description (fd));
			i--;
		}
960
	}
961 962 963

	str_marked = g_string_new (NULL);
	if (i == max)
964
		g_string_append (str_marked, "\t" UNICODE_ELLIPSIS_VERT "\n");
965
	if (i == 1) {
Morten Welinder's avatar
Morten Welinder committed
966
		gee->tooltip.completion
967
			= g_strdup (name);
968
		/*xgettext: short form for: "type F4-key to complete the name"*/
969
		g_string_append (str_marked, _("\n\t<i>F4 to complete</i>"));
970
	} else if (i > 1)
971
		/*xgettext: short form for: "type shift-F4-keys to select the completion"*/
972
		g_string_append (str_marked, _("\n\t<i>\342\207\247F4 to select</i>"));
973 974
	else
		g_string_truncate (str, str->len - 1);
975 976 977
	gee->tooltip.completion_start = start;
	gee->tooltip.completion_end = end;
	gee->tooltip.completion_se_valid = TRUE;
978
	show_tool_tip = gnm_conf_get_core_gui_editing_function_name_tooltips ();
979
	if (show_tool_tip) {
Morten Welinder's avatar
Morten Welinder committed
980
		gee->tooltip.tooltip = gee_create_tooltip
981
			(gee, str->str, str_marked->str, TRUE);
982 983 984
		if (had_tool_tip)
			gtk_widget_show_all (gee->tooltip.tooltip);
		else
985 986 987 988
			gee->tooltip.timerid = g_timeout_add_full
				(G_PRIORITY_DEFAULT, 750,
				 cb_show_tooltip,
				 gee,
989 990
				 NULL);
	}
991
	g_string_free (str, TRUE);
992
	g_string_free (str_marked, TRUE);
993
	g_slist_free_full (list, (GDestroyNotify) gnm_func_unref);
994
	return show_tool_tip;
995 996
}

997 998
static void
gee_dump_lexer (GnmLexerItem *gli) {
999
	g_printerr ("************\n");
1000
	do {
1001 1002
		g_printerr ("%2" G_GSIZE_FORMAT " to %2" G_GSIZE_FORMAT ": %d\n",
			    gli->start, gli->end, gli->token);
1003
	} while (gli++->token != 0);
1004
	g_printerr ("************\n");
Morten Welinder's avatar
Morten Welinder committed
1005

1006 1007
}

1008 1009
static gint
func_def_cmp (gconstpointer a_, gconstpointer b_, gpointer user)
1010
{
1011 1012 1013 1014 1015 1016 1017
	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));
1018 1019
}

1020 1021 1022 1023 1024 1025 1026

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);
1027 1028
	GOFormat const *format;
	gboolean forced_text;
1029 1030 1031 1032 1033 1034 1035 1036 1037

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

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

1038
	parse_pos_init_editpos (&gee->pp, scg_view (gee->scg));
1039 1040 1041
	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));
1042

1043
	if (!gee->feedback_disabled && !forced_text) {
1044
		gee->texpr = gnm_expr_parse_str
1045
			((str[0] == '=') ? str+1 : str,
1046
			 &gee->pp, GNM_EXPR_PARSE_DEFAULT
1047
			 | GNM_EXPR_PARSE_UNKNOWN_NAMES_ARE_STRINGS,
1048 1049 1050
			 sheet_get_conventions (sheet), NULL);
	}

1051 1052
	gee->tooltip.is_expr =  (!forced_text) &&
		(NULL != gnm_expr_char_start_p (str));
1053
	if (!(gee->flags & GNM_EE_SINGLE_RANGE)) {
Morten Welinder's avatar
Morten Welinder committed
1054
		gee->lexer_items = gnm_expr_lex_all
1055 1056 1057 1058 1059 1060
			(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
1061
	g_free (str);
1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077
}

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));
}

1078 1079 1080 1081
static void
gee_check_tooltip (GnmExprEntry *gee)
{
	GtkEditable *editable = GTK_EDITABLE (gee->entry);
1082
	gint  end, args = 0;
1083
	guint end_t;
1084
	char *str;
1085
	gboolean stuff = FALSE, completion_se_set = FALSE;
1086
	GnmLexerItem *gli, *gli_c;
1087
	int last_token = 0;
1088

Morten Welinder's avatar
Morten Welinder committed
1089
	if (gee->lexer_items == NULL || !gee->tooltip.enabled ||
1090
	    (!gee->tooltip.is_expr && !gee->is_cell_renderer)) {
1091
		gee_delete_tooltip (gee, TRUE);
1092
		return;
1093
	}
1094

1095 1096 1097
	end = gtk_editable_get_position (editable);

	if (end == 0) {
1098
		gee_delete_tooltip (gee, TRUE);
1099 1100
		return;
	}
1101

1102 1103
	str = gtk_editable_get_chars (editable, 0, -1);
	end_t = g_utf8_offset_to_pointer (str, end) - str;
1104

1105 1106

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

1108 1109
	/*
	 * If we have an open string at the end of the entry, we
1110
	 * need to adjust.
1111
	 */
Morten Welinder's avatar
Morten Welinder committed
1112

1113
	for (; gli->token != 0; gli++) {
1114 1115 1116 1117
		if (gli->start >= end_t) {
			gli->token = 0;
			break;
		}
1118
		if (gli->token != TOKEN_UNMATCHED_APOSTROPHE)
1119 1120 1121 1122 1123
			continue;
		if (gli->start == 0)
			goto not_found;
		gli->token = 0;
		stuff = TRUE;
1124
		break;
1125
	}
1126 1127
	if (gli > gli_c)
		gli--;
1128 1129
	if (gli > gli_c)
		last_token = (gli - 1)->token;
1130

1131
	/* This creates the completion tooltip */
1132 1133 1134 1135
	if (!stuff &&
	    gli->token == STRING &&
	    last_token != CONSTANT &&
	    last_token != '$') {
1136 1137 1138 1139 1140 1141
		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
1142
		list = gnm_func_lookup_prefix
1143 1144
			(prefix, gee->sheet->workbook,
			 gee_convs (gee)->localized_function_names);
1145 1146
		g_free (prefix);
		if (list != NULL) {
1147
			list = g_slist_sort_with_data
Morten Welinder's avatar
Morten Welinder committed
1148
				(list,
1149 1150
				 func_def_cmp,
				 gee);
Morten Welinder's avatar
Morten Welinder committed
1151
			if (gee_set_tooltip_completion
1152 1153 1154 1155 1156
			    (gee, list, start_t, end_t)) {
				g_free (str);
				g_free (gli_c);
				return;
			}
1157
		} else {
1158 1159
			g_free (gee->tooltip.completion);
			gee->tooltip.completion = NULL;
1160 1161 1162
			gee->tooltip.completion_start = start_t;
			gee->tooltip.completion_end = end_t;
			gee->tooltip.completion_se_valid = TRUE;
1163
		}
Morten Welinder's avatar
Morten Welinder committed
1164
		completion_se_set = TRUE;
1165 1166 1167
	} else {
		g_free (gee->tooltip.completion);
		gee->tooltip.completion = NULL;
Morten Welinder's avatar
Morten Welinder committed
1168
		gee->tooltip.completion_se_valid = FALSE;
1169
	}
Morten Welinder's avatar
Morten Welinder committed
1170

1171 1172 1173

	if (!gnm_conf_get_core_gui_editing_function_argument_tooltips ())
		goto not_found;
1174

1175
	if (gnm_debug_flag ("functooltip"))
1176 1177 1178
		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
1179

1180