1 /*
2  * gnm-expr-entry.c: An entry widget specialized to handle expressions
3  * and ranges.
4  *
5  * Author:
6  *   Jon K�re Hellan (hellan@acm.org)
7  */
8 
9 #include <gnumeric-config.h>
10 #include <gnm-i18n.h>
11 #include <gnumeric.h>
12 #include <widgets/gnm-expr-entry.h>
13 
14 #include <wbc-gtk-impl.h>
15 #include <sheet-control-gui-priv.h>
16 #include <gnm-pane.h>
17 #include <sheet-merge.h>
18 #include <parse-util.h>
19 #include <gui-util.h>
20 #include <ranges.h>
21 #include <value.h>
22 #include <expr.h>
23 #include <func.h>
24 #include <dependent.h>
25 #include <sheet.h>
26 #include <sheet-style.h>
27 #include <workbook.h>
28 #include <sheet-view.h>
29 #include <selection.h>
30 #include <commands.h>
31 #include <gnm-format.h>
32 #include <number-match.h>
33 #include <gnm-datetime.h>
34 #include <gnumeric-conf.h>
35 #include <dead-kittens.h>
36 #include <dialogs/dialogs.h>
37 #include <goffice/goffice.h>
38 
39 #include <gsf/gsf-impl-utils.h>
40 #include <gdk/gdkkeysyms.h>
41 #include <string.h>
42 
43 #define UNICODE_LEFT_TRIANGLE "\xe2\x97\x80"
44 #define UNICODE_RIGHT_TRIANGLE "\xe2\x96\xb6"
45 #define UNICODE_CROSS_AND_SKULLBONES "\xe2\x98\xa0"
46 #define UNICODE_ELLIPSIS "\xe2\x80\xa6"
47 #define UNICODE_ELLIPSIS_VERT "\xe2\x8b\xae"
48 #define UNICODE_ARROW_UP "\xe2\x87\xa7"
49 #define UNICODE_CHECKMARK "\342\234\223"
50 
51 #warning We should replace these token names with the correct values
52    enum yytokentype {
53      STRING = 258,
54      QUOTED_STRING = 259,
55      CONSTANT = 260,
56      RANGEREF = 261,
57      INTERSECT = 268,
58      ARG_SEP = 269,
59      INVALID_TOKEN = 273
60    };
61 #define TOKEN_UNMATCHED_APOSTROPHE INVALID_TOKEN
62 
63 GType
gnm_update_type_get_type(void)64 gnm_update_type_get_type (void)
65 {
66   static GType etype = 0;
67   if (etype == 0) {
68     static const GEnumValue values[] = {
69       { GNM_UPDATE_CONTINUOUS, "GNM_UPDATE_CONTINUOUS", "continuous" },
70       { GNM_UPDATE_DISCONTINUOUS, "GNM_UPDATE_DISCONTINUOUS", "discontinuous" },
71       { GNM_UPDATE_DELAYED, "GNM_UPDATE_DELAYED", "delayed" },
72       { 0, NULL, NULL }
73     };
74     etype = g_enum_register_static (g_intern_static_string ("GnmUpdateType"), values);
75   }
76   return etype;
77 }
78 
79 typedef struct {
80 	GnmRangeRef ref;
81 	int	    text_start;
82 	int	    text_end;
83 	gboolean    is_valid;
84 } Rangesel;
85 
86 struct GnmExprEntry_ {
87 	GtkBox	parent;
88 
89 	GtkEntry		*entry;
90 	GtkWidget               *calendar_combo;
91 	gulong                   calendar_combo_changed;
92 	GtkWidget		*icon;
93 	SheetControlGUI		*scg;	/* the source of the edit */
94 	Sheet			*sheet;	/* from scg */
95 	GnmParsePos		 pp;	/* from scg->sv */
96 	WBCGtk			*wbcg;	/* from scg */
97 	Rangesel		 rangesel;
98 
99 	GnmExprEntryFlags	 flags;
100 	int			 freeze_count;
101 
102 	GnmUpdateType		 update_policy;
103 	guint			 update_timeout_id;
104 
105 	gboolean                 is_cell_renderer;  /* as cell_editable */
106 	gboolean                 editing_canceled;  /* as cell_editable */
107 	gboolean                 ignore_changes; /* internal mutex */
108 
109 	gboolean                 feedback_disabled;
110 	GnmLexerItem            *lexer_items;
111 	GnmExprTop const        *texpr;
112 	struct {
113 		GtkWidget       *tooltip;
114 		GnmFunc         *fd;
115 		gint             args;
116 		gboolean         had_stuff;
117 		gulong           handlerid;
118 		guint            timerid;
119 		gboolean         enabled;
120 		gboolean         is_expr;
121 		gboolean         completion_se_valid;
122 		gchar           *completion;
123 		guint            completion_start;
124 		guint            completion_end;
125 	}                        tooltip;
126 
127 	GOFormat const *constant_format;
128 };
129 
130 typedef struct _GnmExprEntryClass {
131 	GtkBoxClass base;
132 
133 	void (* update)   (GnmExprEntry *gee, gboolean user_requested_update);
134 	void (* changed)  (GnmExprEntry *gee);
135 	void (* activate) (GnmExprEntry *gee);
136 } GnmExprEntryClass;
137 
138 /* Signals */
139 enum {
140 	UPDATE,
141 	CHANGED,
142 	ACTIVATE,
143 	LAST_SIGNAL
144 };
145 
146 /* Properties */
147 enum {
148 	PROP_0,
149 	PROP_UPDATE_POLICY,
150 	PROP_WITH_ICON,
151 	PROP_TEXT,
152 	PROP_FLAGS,
153 	PROP_SCG,
154 	PROP_WBCG,
155 	PROP_CONSTANT_FORMAT,
156 	PROP_EDITING_CANCELED
157 };
158 
159 static guint signals[LAST_SIGNAL] = { 0 };
160 
161 static void gee_set_value_double (GogDataEditor *editor, double val,
162 				  GODateConventions const *date_conv);
163 
164 /* Internal routines
165  */
166 static void     gee_rangesel_reset (GnmExprEntry *gee);
167 static void     gee_rangesel_update_text (GnmExprEntry *gee);
168 static void     gee_detach_scg (GnmExprEntry *gee);
169 static void     gee_remove_update_timer (GnmExprEntry *range);
170 static void     cb_gee_notify_cursor_position (GnmExprEntry *gee);
171 
172 static gboolean gee_debug;
173 static GtkWidgetClass *parent_class = NULL;
174 
175 static gboolean
gee_is_editing(GnmExprEntry * gee)176 gee_is_editing (GnmExprEntry *gee)
177 {
178 	return (gee != NULL && gee->wbcg != NULL && wbcg_is_editing (gee->wbcg));
179 }
180 
181 static GnmConventions const *
gee_convs(const GnmExprEntry * gee)182 gee_convs (const GnmExprEntry *gee)
183 {
184 	return sheet_get_conventions (gee->sheet);
185 }
186 
187 static inline void
gee_force_abs_rel(GnmExprEntry * gee)188 gee_force_abs_rel (GnmExprEntry *gee)
189 {
190 	Rangesel *rs = &gee->rangesel;
191 	rs->is_valid = FALSE;
192 	if ((gee->flags & GNM_EE_FORCE_ABS_REF))
193 		rs->ref.a.col_relative = rs->ref.b.col_relative =
194 			rs->ref.a.row_relative = rs->ref.b.row_relative = FALSE;
195         else if ((gee->flags & GNM_EE_FORCE_REL_REF))
196 		rs->ref.a.col_relative = rs->ref.b.col_relative =
197 			rs->ref.a.row_relative = rs->ref.b.row_relative = TRUE;
198 }
199 
200 static void
gee_rangesel_reset(GnmExprEntry * gee)201 gee_rangesel_reset (GnmExprEntry *gee)
202 {
203 	Rangesel *rs = &gee->rangesel;
204 
205 	rs->text_start = 0;
206 	rs->text_end = 0;
207 	memset (&rs->ref, 0, sizeof (rs->ref));
208 	rs->ref.a.col_relative =
209 	rs->ref.b.col_relative =
210 	rs->ref.a.row_relative =
211 	rs->ref.b.row_relative = ((gee->flags & (GNM_EE_FORCE_ABS_REF|GNM_EE_DEFAULT_ABS_REF)) == 0);
212 
213 	rs->is_valid = FALSE;
214 }
215 
216 static void
gee_destroy(GtkWidget * widget)217 gee_destroy (GtkWidget *widget)
218 {
219 	GnmExprEntry *gee = GNM_EXPR_ENTRY (widget);
220 	gee_remove_update_timer (gee);
221 	gee_detach_scg (gee);
222 	((GtkWidgetClass *)(parent_class))->destroy (widget);
223 }
224 
225 static void
cb_icon_clicked(GtkButton * icon,GnmExprEntry * entry)226 cb_icon_clicked (GtkButton *icon,
227 		 GnmExprEntry *entry)
228 {
229 	GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (entry));
230 
231 	/* TODO special-case GnmExprEntry being directly packed
232 	 * into a GtkWindow. Currently, we just use it in
233 	 * GtkDialogs so the current window child widget
234 	 * is never identical to the entry when it is
235 	 * not rolled up.
236 	 */
237 
238 	if (toplevel != NULL && gtk_widget_is_toplevel (toplevel)) {
239 		GtkWidget *old_entry_parent;
240 		GtkWidget *old_toplevel_child;
241 		GParamSpec **container_props_pspec;
242 		GArray *container_props;
243 
244 		g_assert (GTK_IS_WINDOW (toplevel));
245 
246 		if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (icon))) {
247 			int width, height;
248 			guint n;
249 
250 			/* roll-up request */
251 
252 			old_toplevel_child = gtk_bin_get_child (GTK_BIN (toplevel));
253 			g_assert (GTK_IS_WIDGET (old_toplevel_child));
254 
255 			old_entry_parent = gtk_widget_get_parent (GTK_WIDGET (entry));
256 			g_assert (GTK_IS_CONTAINER (old_entry_parent));
257 
258 			g_object_set_data_full (G_OBJECT (entry), "old_entry_parent",
259 						g_object_ref (old_entry_parent),
260 						(GDestroyNotify) g_object_unref);
261 
262 			g_return_if_fail ((GtkWidget *) entry != old_toplevel_child);
263 
264 			g_object_set_data_full (G_OBJECT (entry), "old_toplevel_child",
265 						g_object_ref (old_toplevel_child),
266 						(GDestroyNotify) g_object_unref);
267 
268 			gtk_window_get_size (GTK_WINDOW (toplevel), &width, &height);
269 			g_object_set_data (G_OBJECT (entry), "old_window_width", GUINT_TO_POINTER (width));
270 			g_object_set_data (G_OBJECT (entry), "old_window_height", GUINT_TO_POINTER (height));
271 			g_object_set_data (G_OBJECT (entry), "old_default",
272 					   gtk_window_get_default_widget (GTK_WINDOW (toplevel)));
273 
274 			container_props = NULL;
275 
276 			container_props_pspec = gtk_container_class_list_child_properties
277 					(G_OBJECT_GET_CLASS (old_entry_parent), &n);
278 
279 			if (container_props_pspec[0] != NULL) {
280 				guint ui;
281 
282 				container_props = g_array_sized_new (FALSE, TRUE, sizeof (GValue), n);
283 
284 				for (ui = 0; ui < n; ui++) {
285 					GValue value = G_VALUE_INIT;
286 					g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (container_props_pspec[ui]));
287 
288 					gtk_container_child_get_property (GTK_CONTAINER (old_entry_parent), GTK_WIDGET (entry),
289 									  g_param_spec_get_name (container_props_pspec[ui]),
290 									  &value);
291 					g_array_append_val (container_props, value);
292 				}
293 			}
294 
295 			g_object_set_data_full (G_OBJECT (entry), "container_props",
296 						container_props,
297 						(GDestroyNotify) g_array_unref);
298 			g_object_set_data_full (G_OBJECT (entry), "container_props_pspec",
299 						container_props_pspec,
300 						(GDestroyNotify) g_free);
301 
302 			gtk_container_remove (GTK_CONTAINER (toplevel), old_toplevel_child);
303 			gtk_widget_reparent (GTK_WIDGET (entry), toplevel);
304 
305 			gtk_widget_grab_focus (GTK_WIDGET (entry->entry));
306 			gtk_widget_set_can_default (GTK_WIDGET (icon), TRUE);
307 			gtk_widget_grab_default (GTK_WIDGET (icon));
308 
309 			gtk_window_resize (GTK_WINDOW (toplevel), 1, 1);
310 
311 		} else {
312 			int i;
313 			gpointer default_widget;
314 
315 			/* reset rolled-up window */
316 
317 			old_toplevel_child = g_object_get_data (G_OBJECT (entry), "old_toplevel_child");
318 			g_assert (GTK_IS_WIDGET (old_toplevel_child));
319 
320 			old_entry_parent = g_object_get_data (G_OBJECT (entry), "old_entry_parent");
321 			g_assert (GTK_IS_CONTAINER (old_entry_parent));
322 
323 			g_object_ref (entry);
324 			gtk_container_remove (GTK_CONTAINER (toplevel), GTK_WIDGET (entry));
325 			gtk_container_add (GTK_CONTAINER (toplevel), old_toplevel_child);
326 			gtk_container_add (GTK_CONTAINER (old_entry_parent), GTK_WIDGET (entry));
327 			g_object_unref (entry);
328 
329 			container_props = g_object_get_data (G_OBJECT (entry), "container_props");
330 			container_props_pspec = g_object_get_data (G_OBJECT (entry), "container_props_pspec");
331 
332 			for (i = 0; container_props_pspec[i] != NULL; i++) {
333 				gtk_container_child_set_property (GTK_CONTAINER (old_entry_parent), GTK_WIDGET (entry),
334 								  g_param_spec_get_name (container_props_pspec[i]),
335 								  &g_array_index (container_props, GValue, i));
336 			}
337 
338 			gtk_window_resize (GTK_WINDOW (toplevel),
339 					   GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (entry), "old_window_width")),
340 					   GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (entry), "old_window_height")));
341 			default_widget = g_object_get_data (G_OBJECT (entry), "old_default");
342 			if (default_widget != NULL) {
343 				gtk_window_set_default (GTK_WINDOW (toplevel), GTK_WIDGET (default_widget));
344 				g_object_set_data (G_OBJECT (entry), "old_default", NULL);
345 			}
346 
347 			g_object_set_data (G_OBJECT (entry), "old_entry_parent", NULL);
348 			g_object_set_data (G_OBJECT (entry), "old_toplevel_child", NULL);
349 			g_object_set_data (G_OBJECT (entry), "container_props", NULL);
350 			g_object_set_data (G_OBJECT (entry), "container_props_pspec", NULL);
351 		}
352 	} else {
353 		g_warning ("GnmExprEntry button was clicked, but entry has no toplevel parent.");
354 	}
355 }
356 
357 static GnmValue *
get_matched_value(GnmExprEntry * gee)358 get_matched_value (GnmExprEntry *gee)
359 {
360 	GODateConventions const *date_conv =
361 		sheet_date_conv (gee->sheet);
362 	const char *text = gnm_expr_entry_get_text (gee);
363 
364 	return format_match_number (text, gee->constant_format, date_conv);
365 }
366 
367 
368 static void
gee_update_calendar(GnmExprEntry * gee)369 gee_update_calendar (GnmExprEntry *gee)
370 {
371 	GDate date;
372 	GnmValue *v;
373 	GODateConventions const *date_conv =
374 		sheet_date_conv (gee->sheet);
375 
376 	if (!gee->calendar_combo)
377 		return;
378 
379 	v = get_matched_value (gee);
380 	if (!v)
381 		return;
382 
383 	if (datetime_value_to_g (&date, v, date_conv)) {
384 		g_signal_handler_block (gee->calendar_combo,
385 					gee->calendar_combo_changed);
386 		go_calendar_button_set_date
387 			(GO_CALENDAR_BUTTON (gee->calendar_combo),
388 			 &date);
389 		g_signal_handler_unblock (gee->calendar_combo,
390 					  gee->calendar_combo_changed);
391 	}
392 
393 	value_release (v);
394 }
395 
396 static void
cb_calendar_changed(GOCalendarButton * calb,GnmExprEntry * gee)397 cb_calendar_changed (GOCalendarButton *calb, GnmExprEntry *gee)
398 {
399 	GDate date;
400 	GODateConventions const *date_conv =
401 		sheet_date_conv (gee->sheet);
402 	int serial;
403 
404 	if (!go_calendar_button_get_date (calb, &date))
405 		return;
406 
407 	serial = go_date_g_to_serial (&date, date_conv);
408 
409 	gee_set_value_double (GOG_DATA_EDITOR (gee), serial, date_conv);
410 }
411 
412 static void
gee_set_format(GnmExprEntry * gee,GOFormat const * fmt)413 gee_set_format (GnmExprEntry *gee, GOFormat const *fmt)
414 {
415 	if (fmt == gee->constant_format)
416 		return;
417 
418 	if (fmt) go_format_ref (fmt);
419 	go_format_unref (gee->constant_format);
420 	gee->constant_format = fmt;
421 
422 	if (gee_debug)
423 		g_printerr ("Setting format %s\n",
424 			    fmt ? go_format_as_XL (fmt) : "-");
425 
426 	if (fmt && go_format_is_date (fmt)) {
427 		if (!gee->calendar_combo) {
428 			gee->calendar_combo = go_calendar_button_new ();
429 			gtk_widget_show (gee->calendar_combo);
430 			gtk_box_pack_start (GTK_BOX (gee), gee->calendar_combo,
431 					    FALSE, TRUE, 0);
432 			gee->calendar_combo_changed =
433 				g_signal_connect (G_OBJECT (gee->calendar_combo),
434 						  "changed",
435 						  G_CALLBACK (cb_calendar_changed),
436 						  gee);
437 			gee_update_calendar (gee);
438 		}
439 	} else {
440 		if (gee->calendar_combo) {
441 			gtk_widget_destroy (gee->calendar_combo);
442 			gee->calendar_combo = NULL;
443 			gee->calendar_combo_changed = 0;
444 		}
445 	}
446 
447 	g_object_notify (G_OBJECT (gee), "constant-format");
448 }
449 
450 static void
gee_set_with_icon(GnmExprEntry * gee,gboolean with_icon)451 gee_set_with_icon (GnmExprEntry *gee, gboolean with_icon)
452 {
453 	gboolean has_icon = (gee->icon != NULL);
454 	with_icon = !!with_icon;
455 
456 	if (has_icon == with_icon)
457 		return;
458 
459 	if (with_icon) {
460 		gee->icon = gtk_toggle_button_new ();
461 		gtk_container_add (GTK_CONTAINER (gee->icon),
462 				   gtk_image_new_from_icon_name ("gnumeric-exprentry",
463 								 GTK_ICON_SIZE_MENU));
464 		gtk_box_pack_end (GTK_BOX (gee), gee->icon, FALSE, FALSE, 0);
465 		gtk_widget_show_all (gee->icon);
466 		g_signal_connect (gee->icon, "clicked",
467 				  G_CALLBACK (cb_icon_clicked), gee);
468 	} else
469 		gtk_widget_destroy (gee->icon);
470 }
471 
472 static void
gee_set_property(GObject * object,guint prop_id,GValue const * value,GParamSpec * pspec)473 gee_set_property (GObject      *object,
474 		  guint         prop_id,
475 		  GValue const *value,
476 		  GParamSpec   *pspec)
477 {
478 	GnmExprEntry *gee = GNM_EXPR_ENTRY (object);
479 	switch (prop_id) {
480 	case PROP_UPDATE_POLICY:
481 		gnm_expr_entry_set_update_policy (gee, g_value_get_enum (value));
482 		break;
483 
484 	case PROP_WITH_ICON:
485 		gee_set_with_icon (gee, g_value_get_boolean (value));
486 		break;
487 
488 	case PROP_TEXT: {
489 		const char *new_txt = g_value_get_string (value);
490 		const char *old_txt = gnm_expr_entry_get_text (gee);
491 		if (go_str_compare (new_txt, old_txt)) {
492 			gnm_expr_entry_load_from_text (gee, new_txt);
493 			gnm_expr_entry_signal_update (gee, FALSE);
494 		}
495 		break;
496 	}
497 
498 	case PROP_FLAGS:
499 		gnm_expr_entry_set_flags (gee,
500 			g_value_get_uint (value), GNM_EE_MASK);
501 		break;
502 	case PROP_SCG:
503 		gnm_expr_entry_set_scg (gee,
504 			GNM_SCG (g_value_get_object (value)));
505 		break;
506 	case PROP_WBCG:
507 		g_return_if_fail (gee->wbcg == NULL);
508 		gee->wbcg = WBC_GTK (g_value_get_object (value));
509 		break;
510 	case PROP_CONSTANT_FORMAT:
511 		gee_set_format (gee, g_value_get_boxed (value));
512 		break;
513 	case PROP_EDITING_CANCELED:
514 		gee->editing_canceled = g_value_get_boolean (value);
515 	default:
516 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
517 	}
518 }
519 
520 static void
gee_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)521 gee_get_property (GObject      *object,
522 		  guint         prop_id,
523 		  GValue       *value,
524 		  GParamSpec   *pspec)
525 {
526 	GnmExprEntry *gee = GNM_EXPR_ENTRY (object);
527 	switch (prop_id) {
528 	case PROP_UPDATE_POLICY:
529 		g_value_set_enum (value, gee->update_policy);
530 		break;
531 	case PROP_WITH_ICON:
532 		g_value_set_boolean (value, gee->icon != NULL);
533 		break;
534 	case PROP_TEXT:
535 		g_value_set_string (value, gnm_expr_entry_get_text (gee));
536 		break;
537 	case PROP_FLAGS:
538 		g_value_set_uint (value, gee->flags);
539 		break;
540 	case PROP_SCG:
541 		g_value_set_object (value, G_OBJECT (gee->scg));
542 		break;
543 	case PROP_WBCG:
544 		g_value_set_object (value, G_OBJECT (gee->wbcg));
545 		break;
546 	case PROP_CONSTANT_FORMAT:
547 		g_value_set_boxed (value, (gpointer)gee->constant_format);
548 		break;
549 	case PROP_EDITING_CANCELED:
550 		g_value_set_boolean (value, gee->editing_canceled);
551 	default:
552 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
553 	}
554 }
555 
556 static void
cb_entry_activate(GnmExprEntry * gee)557 cb_entry_activate (GnmExprEntry *gee)
558 {
559 	g_signal_emit (G_OBJECT (gee), signals[ACTIVATE], 0);
560 	gnm_expr_entry_signal_update (gee, TRUE);
561 }
562 
563 static void
gee_destroy_feedback_range(GnmExprEntry * gee)564 gee_destroy_feedback_range (GnmExprEntry *gee)
565 {
566 	WBCGtk *wbcg = scg_wbcg (gee->scg);
567 	int page, pages = wbcg_get_n_scg (wbcg);
568 
569 	for (page = 0; page < pages; page++) {
570 		SheetControlGUI *scg = wbcg_get_nth_scg (wbcg, page);
571 		SCG_FOREACH_PANE (scg, pane,
572 				  gnm_pane_expr_cursor_stop (pane););
573 	}
574 }
575 
576 static void
gnm_expr_entry_colour_ranges(GnmExprEntry * gee,int start,int end,GnmRangeRef * rr,int colour,PangoAttrList ** attrs,gboolean insert_cursor)577 gnm_expr_entry_colour_ranges (GnmExprEntry *gee, int start, int end, GnmRangeRef *rr, int colour,
578 			      PangoAttrList **attrs, gboolean insert_cursor)
579 {
580 	static const GOColor colours[] = {
581 		GO_COLOR_FROM_RGB (0x00, 0xff, 0x00),
582 		GO_COLOR_FROM_RGB (0x00, 0x00, 0xff),
583 		GO_COLOR_FROM_RGB (0xff, 0x00, 0x00),
584 		GO_COLOR_FROM_RGB (0x00, 0x80, 0x80),
585 		GO_COLOR_FROM_RGB (0xa0, 0xa0, 0x00),
586 		GO_COLOR_FROM_RGB (0xa0, 0x00, 0xa0)
587 	};
588 	PangoAttribute *at;
589 	GnmRange r;
590 	GnmRange const *merge; /*[#127415]*/
591 	Sheet *start_sheet, *end_sheet;
592 	Sheet *sheet = scg_sheet (gee->scg);
593 	SheetControlGUI *scg = NULL;
594 
595 	if (rr->a.sheet->workbook != gee->sheet->workbook) {
596 		/* We should show the range in an external workbook! */
597 		return;
598 	}
599 
600 	if (*attrs == NULL)
601 		*attrs = pango_attr_list_new ();
602 
603 	colour = colour % G_N_ELEMENTS (colours);
604 
605 	gnm_rangeref_normalize_pp (rr, &gee->pp,
606 				   &start_sheet,
607 				   &end_sheet,
608 				   &r);
609 	if (start_sheet != end_sheet)
610 		return;
611 	if (insert_cursor) {
612 		if (range_is_singleton  (&r) &&
613 		    NULL != (merge = gnm_sheet_merge_is_corner
614 			     (start_sheet, &r.start)))
615 			r = *merge;
616 		if (start_sheet == sheet)
617 			scg = gee->scg;
618 		else {
619 			WBCGtk *wbcg = scg_wbcg (gee->scg);
620 			scg = wbcg_get_nth_scg (wbcg, start_sheet->index_in_wb);
621 		}
622 
623 		SCG_FOREACH_PANE (scg, pane, gnm_pane_expr_cursor_bound_set
624 				  (pane, &r, colours[colour]););
625 	}
626 
627 	at = go_color_to_pango (colours[colour], TRUE);
628 	at->start_index = start;
629 	at->end_index = end;
630 
631 	pango_attr_list_change (*attrs, at);
632 }
633 
634 /* WARNING : DO NOT CALL THIS FROM FROM UPDATE.  It may create another
635  *           canvas-item which would in turn call update and confuse the
636  *           canvas.
637  */
638 static void
gee_scan_for_range(GnmExprEntry * gee)639 gee_scan_for_range (GnmExprEntry *gee)
640 {
641 	PangoAttrList *attrs = NULL;
642 
643 	parse_pos_init_editpos (&gee->pp, scg_view (gee->scg));
644 	gee_destroy_feedback_range (gee);
645 	if (!gee->feedback_disabled && gee_is_editing (gee) && gee->lexer_items != NULL) {
646 		GnmLexerItem *gli = gee->lexer_items;
647 		int colour = 1; /* We start with 1 since GINT_TO_POINTER (0) == NULL */
648 		GHashTable *hash = g_hash_table_new_full ((GHashFunc) gnm_rangeref_hash,
649 							  (GEqualFunc) gnm_rangeref_equal,
650 							  g_free,
651 							  NULL);
652 		do {
653 			if (gli->token == RANGEREF) {
654 				char const *text = gtk_entry_get_text (gee->entry);
655 				char *rtext = g_strndup (text + gli->start,
656 							 gli->end - gli->start);
657 				char const *tmp;
658 				GnmRangeRef rr;
659 				tmp = rangeref_parse (&rr, rtext,
660 						      &gee->pp, gee_convs (gee));
661 				if (tmp != rtext) {
662 					gpointer val;
663 					gint this_colour;
664 					gboolean insert_cursor;
665 					if (rr.a.sheet == NULL)
666 						rr.a.sheet = gee->sheet;
667 					if (rr.b.sheet == NULL)
668 						rr.b.sheet = rr.a.sheet;
669 					val = g_hash_table_lookup (hash, &rr);
670 					if (val == NULL) {
671 						GnmRangeRef *rrr = gnm_rangeref_dup (&rr);
672 						this_colour = colour++;
673 						g_hash_table_insert (hash, rrr, GINT_TO_POINTER (this_colour));
674 						insert_cursor = TRUE;
675 					} else {
676 						this_colour = GPOINTER_TO_INT (val);
677 						insert_cursor = FALSE;
678 					}
679 					gnm_expr_entry_colour_ranges (gee, gli->start, gli->end, &rr,
680 								      this_colour, &attrs, insert_cursor);
681 				}
682 				g_free (rtext);
683 			}
684 		} while (gli++->token != 0);
685 		g_hash_table_destroy (hash);
686 	}
687 	if (attrs)
688 		g_object_set_data_full (G_OBJECT (gee->entry), "gnm:range-attributes", attrs,
689 					(GDestroyNotify) pango_attr_list_unref);
690 	else
691 		g_object_set_data (G_OBJECT (gee->entry), "gnm:range-attributes", NULL);
692 }
693 
694 static void
gee_update_env(GnmExprEntry * gee)695 gee_update_env (GnmExprEntry *gee)
696 {
697 	if (!gee->ignore_changes) {
698 		if (NULL != gee->scg &&
699 #warning why do we want this dichotomy
700 		    !gee->is_cell_renderer &&
701 		    !gnm_expr_entry_can_rangesel (gee))
702 			scg_rangesel_stop (gee->scg, FALSE);
703 
704 		if (gnm_expr_char_start_p (gtk_entry_get_text (gee->entry)))
705 			gee_scan_for_range (gee);
706 	}
707 
708 }
709 
710 static gboolean
gee_delete_tooltip(GnmExprEntry * gee,gboolean remove_completion)711 gee_delete_tooltip (GnmExprEntry *gee, gboolean remove_completion)
712 {
713 	gboolean has_tooltip = (gee->tooltip.tooltip != NULL &&
714 				gee->tooltip.timerid == 0);
715 
716 	if (gee->tooltip.timerid) {
717 		g_source_remove (gee->tooltip.timerid);
718 		gee->tooltip.timerid = 0;
719 	}
720 	if (gee->tooltip.tooltip) {
721 		gtk_widget_destroy (gee->tooltip.tooltip);
722 		gee->tooltip.tooltip = NULL;
723 	}
724 	if (gee->tooltip.fd) {
725 		gnm_func_dec_usage (gee->tooltip.fd);
726 		gee->tooltip.fd = NULL;
727 	}
728 	if (gee->tooltip.handlerid != 0 && gee->entry != NULL) {
729 		g_signal_handler_disconnect (gtk_widget_get_toplevel
730 					     (GTK_WIDGET (gee->entry)),
731 					     gee->tooltip.handlerid);
732 		gee->tooltip.handlerid = 0;
733 	}
734 	if (remove_completion) {
735 		g_free (gee->tooltip.completion);
736 		gee->tooltip.completion = NULL;
737 		gee->tooltip.completion_se_valid = FALSE;
738 	}
739 	return has_tooltip;
740 }
741 
742 void
gnm_expr_entry_close_tips(GnmExprEntry * gee)743 gnm_expr_entry_close_tips  (GnmExprEntry *gee)
744 {
745 	if (gee != NULL)
746 		gee_delete_tooltip (gee, FALSE);
747 }
748 
749 static gboolean
750 cb_gee_focus_out_event (GtkWidget         *widget,
751 			GdkEventFocus     *event,
752 			gpointer           user_data);
753 
754 static gboolean
cb_show_tooltip(gpointer user_data)755 cb_show_tooltip (gpointer user_data)
756 {
757 	GnmExprEntry *gee = GNM_EXPR_ENTRY (user_data);
758 	gtk_widget_show_all (gee->tooltip.tooltip);
759 	gee->tooltip.timerid = 0;
760 	return FALSE;
761 }
762 
763 
764 static GtkWidget *
gee_create_tooltip(GnmExprEntry * gee,gchar const * str,gchar const * marked_str,gboolean set_tabs)765 gee_create_tooltip (GnmExprEntry *gee, gchar const *str,
766 		    gchar const *marked_str, gboolean set_tabs)
767 {
768 	GtkWidget *toplevel, *label, *tip;
769 	gint root_x = 0, root_y = 0;
770 	GtkAllocation allocation;
771 	GdkWindow *gdkw;
772 	gchar *markup = NULL;
773 	GString *string;
774 	GtkTextBuffer *buffer;
775 	PangoAttrList *attr_list = NULL;
776 	char *text = NULL;
777 
778 	toplevel = gtk_widget_get_toplevel (GTK_WIDGET (gee->entry));
779 	gtk_widget_add_events(toplevel, GDK_FOCUS_CHANGE_MASK);
780 	if (gee->tooltip.handlerid == 0)
781 		gee->tooltip.handlerid = g_signal_connect
782 			(G_OBJECT (toplevel), "focus-out-event",
783 			 G_CALLBACK (cb_gee_focus_out_event), gee);
784 
785 	label = gnm_convert_to_tooltip (toplevel, gtk_text_view_new ());
786 	tip = gtk_widget_get_toplevel (label);
787 
788 	gtk_style_context_add_class (gtk_widget_get_style_context (label),
789 				     "function-help");
790 
791 	if (str)
792 		markup = gnm_func_convert_markup_to_pango (str, label);
793 	string = g_string_new (markup);
794 	if (marked_str)
795 		g_string_append (string, marked_str);
796 	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (label));
797 
798 	if (pango_parse_markup (string->str, -1, 0,
799 				&attr_list, &text,
800 				NULL, NULL)) {
801 		go_create_std_tags_for_buffer (buffer);
802 		gtk_text_buffer_set_text (buffer, text, -1);
803 		gnm_load_pango_attributes_into_buffer (attr_list, buffer, text);
804 		g_free (text);
805 		pango_attr_list_unref (attr_list);
806 	} else
807 		gtk_text_buffer_set_text (buffer, string->str, -1);
808 	g_free (markup);
809 	g_string_free (string, TRUE);
810 
811 	if (set_tabs) {
812 		PangoTabArray *tabs;
813 		tabs = pango_tab_array_new_with_positions
814 			(5, TRUE,
815 			 PANGO_TAB_LEFT, 20,
816 			 PANGO_TAB_LEFT, 140,
817 			 PANGO_TAB_LEFT, 160,
818 			 PANGO_TAB_LEFT, 180,
819 			 PANGO_TAB_LEFT, 200);
820 		gtk_text_view_set_tabs (GTK_TEXT_VIEW (label), tabs);
821 		pango_tab_array_free (tabs);
822 	}
823 
824 	gdkw = gtk_widget_get_window (GTK_WIDGET (gee->entry));
825 	gdk_window_get_origin (gdkw, &root_x, &root_y);
826 	gtk_widget_get_allocation (GTK_WIDGET (gee->entry), &allocation);
827 
828 	gtk_window_move (GTK_WINDOW (tip),
829 			 root_x + allocation.x,
830 			 root_y + allocation.y + allocation.height);
831 
832 	return tip;
833 }
834 
835 static void
gee_set_tooltip_argument(GString * str,char * arg,gboolean optional)836 gee_set_tooltip_argument (GString *str, char *arg, gboolean optional)
837 {
838 	if (optional)
839 		g_string_append_c (str, '[');
840 	g_string_append (str, arg);
841 	if (optional)
842 		g_string_append_c (str, ']');
843 }
844 
845 static void
gee_set_tooltip(GnmExprEntry * gee,GnmFunc * fd,gint args,gboolean had_stuff)846 gee_set_tooltip (GnmExprEntry *gee, GnmFunc *fd, gint args, gboolean had_stuff)
847 {
848 	GString *str;
849 	gchar sep = go_locale_get_arg_sep ();
850 	gint min, max, i;
851 	gboolean first = TRUE;
852 	char *extra = NULL;
853 	gboolean localized_function_names = gee->sheet->convs->localized_function_names;
854 	const char *fdname;
855 
856 	gnm_func_load_if_stub (fd);
857 	gnm_func_count_args (fd, &min, &max);
858 
859 	if ((gee->tooltip.fd)
860 	    && (gee->tooltip.fd == fd && gee->tooltip.args == args
861 		&& gee->tooltip.had_stuff == (max == 0 && args == 0 && had_stuff)))
862 			return;
863 	gee_delete_tooltip (gee, FALSE);
864 
865 	gee->tooltip.fd = fd;
866 	gnm_func_inc_usage (gee->tooltip.fd);
867 
868 	fdname = gnm_func_get_name (fd, localized_function_names);
869 
870 	str = g_string_new (fdname);
871 	g_string_append_c (str, '(');
872 
873 	for (i = 0; i < max; i++) {
874 		char *arg_name = gnm_func_get_arg_name
875 			(fd, i);
876 		if (arg_name != NULL) {
877 			if (first)
878 				first = FALSE;
879 			else
880 				g_string_append_c (str, sep);
881 			if (i == args) {
882 				extra = g_strdup_printf
883 					(_("%s: %s"),
884 					 arg_name,
885 					 gnm_func_get_arg_description (fd, i));
886 				g_string_append (str, UNICODE_RIGHT_TRIANGLE);
887 			}
888 			gee_set_tooltip_argument (str, arg_name, i >= min);
889 			if (i == args)
890 				g_string_append (str, UNICODE_LEFT_TRIANGLE);
891 			g_free (arg_name);
892 		} else
893 			break;
894 	}
895 	if (i < max) {
896 		if (!first)
897 			g_string_append_c (str, sep);
898 		g_string_append
899 			(str, (args >= i && args < max)
900 			 ? UNICODE_RIGHT_TRIANGLE UNICODE_ELLIPSIS UNICODE_LEFT_TRIANGLE
901 			 : UNICODE_ELLIPSIS);
902 	}
903 	if (max == 0 && args == 0 && !had_stuff) {
904 		extra = g_strdup_printf (_("%s takes no arguments"),
905 					 fdname);
906 	} else if (args >= max) {
907 		g_string_append (str, UNICODE_RIGHT_TRIANGLE UNICODE_CROSS_AND_SKULLBONES UNICODE_LEFT_TRIANGLE);
908 		extra = g_strdup_printf (_("Too many arguments for %s"),
909 					 fdname);
910 	}
911 	g_string_append_c (str, ')');
912 	if (extra) {
913 		g_string_append_c (str, '\n');
914 		g_string_append (str, extra);
915 		g_free (extra);
916 	}
917 
918 	gee->tooltip.tooltip = gee_create_tooltip
919 		(gee, str->str, _("\n\n<i>Ctrl-F4 to close tooltip</i>"), FALSE);
920 	gtk_widget_show_all (gee->tooltip.tooltip);
921 	gee->tooltip.args = args;
922 	gee->tooltip.had_stuff = (max == 0 && args == 0 && had_stuff);
923 
924 	g_string_free (str, TRUE);
925 }
926 
927 static gboolean
gee_set_tooltip_completion(GnmExprEntry * gee,GSList * list,guint start,guint end)928 gee_set_tooltip_completion (GnmExprEntry *gee, GSList *list, guint start, guint end)
929 {
930 	GString *str;
931 	GString *str_marked;
932 	gint i = 0;
933 	gint max = 10;
934 	GSList *list_c = list;
935 	gchar const *name = NULL;
936 	gboolean show_tool_tip, had_tool_tip;
937 	gboolean localized_function_names = gee->sheet->convs->localized_function_names;
938 
939 	had_tool_tip = gee_delete_tooltip (gee, TRUE);
940 
941 	str = g_string_new (NULL);
942 	for (; list_c != NULL && ++i < max; list_c = list_c->next) {
943 		GnmFunc *fd = list_c->data;
944 		name = gnm_func_get_name (fd, localized_function_names);
945 		if ((end - start) < (guint) g_utf8_strlen (name, -1))
946 			/* xgettext: the first %s is a function name and */
947 			/* the second %s the function description */
948 			g_string_append_printf (str, _("\t%s \t%s\n"), name,
949 						gnm_func_get_description (fd));
950 		else {
951 			/* xgettext: the first %s is a function name and */
952 			/* the second %s the function description */
953 			g_string_append_printf (str, _("\342\234\223\t%s \t%s\n"), name,
954 						gnm_func_get_description (fd));
955 			i--;
956 		}
957 	}
958 
959 	str_marked = g_string_new (NULL);
960 	if (i == max)
961 		g_string_append (str_marked, "\t" UNICODE_ELLIPSIS_VERT "\n");
962 	if (i == 1) {
963 		gee->tooltip.completion
964 			= g_strdup (name);
965 		/*xgettext: short form for: "type F4-key to complete the name"*/
966 		g_string_append (str_marked, _("\n\t<i>F4 to complete</i>"));
967 	} else if (i > 1)
968 		/*xgettext: short form for: "type shift-F4-keys to select the completion"*/
969 		g_string_append (str_marked, _("\n\t<i>\342\207\247F4 to select</i>"));
970 	else
971 		g_string_truncate (str, str->len - 1);
972 	gee->tooltip.completion_start = start;
973 	gee->tooltip.completion_end = end;
974 	gee->tooltip.completion_se_valid = TRUE;
975 	show_tool_tip = gnm_conf_get_core_gui_editing_function_name_tooltips ();
976 	if (show_tool_tip) {
977 		gee->tooltip.tooltip = gee_create_tooltip
978 			(gee, str->str, str_marked->str, TRUE);
979 		if (had_tool_tip)
980 			gtk_widget_show_all (gee->tooltip.tooltip);
981 		else
982 			gee->tooltip.timerid = g_timeout_add_full
983 				(G_PRIORITY_DEFAULT, 750,
984 				 cb_show_tooltip,
985 				 gee,
986 				 NULL);
987 	}
988 	g_string_free (str, TRUE);
989 	g_string_free (str_marked, TRUE);
990 	g_slist_free_full (list, (GDestroyNotify) gnm_func_dec_usage);
991 	return show_tool_tip;
992 }
993 
994 static void
gee_dump_lexer(GnmLexerItem * gli)995 gee_dump_lexer (GnmLexerItem *gli) {
996 	g_printerr ("************\n");
997 	do {
998 		g_printerr ("%2" G_GSIZE_FORMAT " to %2" G_GSIZE_FORMAT ": %d\n",
999 			    gli->start, gli->end, gli->token);
1000 	} while (gli++->token != 0);
1001 	g_printerr ("************\n");
1002 
1003 }
1004 
1005 static gint
func_def_cmp(gconstpointer a_,gconstpointer b_,gpointer user)1006 func_def_cmp (gconstpointer a_, gconstpointer b_, gpointer user)
1007 {
1008 	GnmFunc const * const a = (GnmFunc const * const)a_;
1009 	GnmFunc const * const b = (GnmFunc const * const)b_;
1010 	GnmExprEntry *gee = user;
1011 	gboolean localized = gee->sheet->convs->localized_function_names;
1012 
1013 	return g_utf8_collate (gnm_func_get_name (a, localized),
1014 			       gnm_func_get_name (b, localized));
1015 }
1016 
1017 
1018 static void
gee_update_lexer_items(GnmExprEntry * gee)1019 gee_update_lexer_items (GnmExprEntry *gee)
1020 {
1021 	GtkEditable *editable = GTK_EDITABLE (gee->entry);
1022 	char *str = gtk_editable_get_chars (editable, 0, -1);
1023 	Sheet *sheet = scg_sheet (gee->scg);
1024 	GOFormat const *format;
1025 	gboolean forced_text;
1026 
1027 	g_free (gee->lexer_items);
1028 	gee->lexer_items = NULL;
1029 
1030 	if (gee->texpr != NULL) {
1031 		gnm_expr_top_unref (gee->texpr);
1032 		gee->texpr = NULL;
1033 	}
1034 
1035 	parse_pos_init_editpos (&gee->pp, scg_view (gee->scg));
1036 	format = gnm_style_get_format
1037 		(sheet_style_get (sheet, gee->pp.eval.col, gee->pp.eval.row));
1038 	forced_text = ((format != NULL) && go_format_is_text (format));
1039 
1040 	if (!gee->feedback_disabled && !forced_text) {
1041 		gee->texpr = gnm_expr_parse_str
1042 			((str[0] == '=') ? str+1 : str,
1043 			 &gee->pp, GNM_EXPR_PARSE_DEFAULT
1044 			 | GNM_EXPR_PARSE_UNKNOWN_NAMES_ARE_STRINGS,
1045 			 sheet_get_conventions (sheet), NULL);
1046 	}
1047 
1048 	gee->tooltip.is_expr =  (!forced_text) &&
1049 		(NULL != gnm_expr_char_start_p (str));
1050 	if (!(gee->flags & GNM_EE_SINGLE_RANGE)) {
1051 		gee->lexer_items = gnm_expr_lex_all
1052 			(str, &gee->pp,
1053 			 GNM_EXPR_PARSE_UNKNOWN_NAMES_ARE_STRINGS,
1054 			 NULL);
1055 		if (gnm_debug_flag ("functooltip"))
1056 			gee_dump_lexer (gee->lexer_items);
1057 	}
1058 	g_free (str);
1059 }
1060 
1061 static GnmLexerItem *
gee_duplicate_lexer_items(GnmLexerItem * gli)1062 gee_duplicate_lexer_items (GnmLexerItem *gli)
1063 {
1064 	int n = 1;
1065 	GnmLexerItem *gli_c = gli;
1066 
1067 	while (gli_c->token != 0) {
1068 		gli_c++;
1069 		n++;
1070 	}
1071 
1072 	return g_memdup (gli, n * sizeof (GnmLexerItem));
1073 }
1074 
1075 static void
gee_check_tooltip(GnmExprEntry * gee)1076 gee_check_tooltip (GnmExprEntry *gee)
1077 {
1078 	GtkEditable *editable = GTK_EDITABLE (gee->entry);
1079 	gint  end, args = 0;
1080 	guint end_t;
1081 	char *str;
1082 	gboolean stuff = FALSE, completion_se_set = FALSE;
1083 	GnmLexerItem *gli, *gli_c;
1084 	int last_token = 0;
1085 
1086 	if (gee->lexer_items == NULL || !gee->tooltip.enabled ||
1087 	    (!gee->tooltip.is_expr && !gee->is_cell_renderer)) {
1088 		gee_delete_tooltip (gee, TRUE);
1089 		return;
1090 	}
1091 
1092 	end = gtk_editable_get_position (editable);
1093 
1094 	if (end == 0) {
1095 		gee_delete_tooltip (gee, TRUE);
1096 		return;
1097 	}
1098 
1099 	str = gtk_editable_get_chars (editable, 0, -1);
1100 	end_t = g_utf8_offset_to_pointer (str, end) - str;
1101 
1102 
1103 	gli_c = gli = gee_duplicate_lexer_items (gee->lexer_items);
1104 
1105 	/*
1106 	 * If we have an open string at the end of the entry, we
1107 	 * need to adjust.
1108 	 */
1109 
1110 	for (; gli->token != 0; gli++) {
1111 		if (gli->start >= end_t) {
1112 			gli->token = 0;
1113 			break;
1114 		}
1115 		if (gli->token != TOKEN_UNMATCHED_APOSTROPHE)
1116 			continue;
1117 		if (gli->start == 0)
1118 			goto not_found;
1119 		gli->token = 0;
1120 		stuff = TRUE;
1121 		break;
1122 	}
1123 	if (gli > gli_c)
1124 		gli--;
1125 	if (gli > gli_c)
1126 		last_token = (gli - 1)->token;
1127 
1128 	/* This creates the completion tooltip */
1129 	if (!stuff &&
1130 	    gli->token == STRING &&
1131 	    last_token != CONSTANT &&
1132 	    last_token != '$') {
1133 		guint start_t = gli->start;
1134 		char *prefix;
1135 		GSList *list;
1136 
1137 		end_t = gli->end;
1138 		prefix = g_strndup (str + start_t, end_t - start_t);
1139 		list = gnm_func_lookup_prefix
1140 			(prefix, gee->sheet->workbook,
1141 			 gee_convs (gee)->localized_function_names);
1142 		g_free (prefix);
1143 		if (list != NULL) {
1144 			list = g_slist_sort_with_data
1145 				(list,
1146 				 func_def_cmp,
1147 				 gee);
1148 			if (gee_set_tooltip_completion
1149 			    (gee, list, start_t, end_t)) {
1150 				g_free (str);
1151 				g_free (gli_c);
1152 				return;
1153 			}
1154 		} else {
1155 			g_free (gee->tooltip.completion);
1156 			gee->tooltip.completion = NULL;
1157 			gee->tooltip.completion_start = start_t;
1158 			gee->tooltip.completion_end = end_t;
1159 			gee->tooltip.completion_se_valid = TRUE;
1160 		}
1161 		completion_se_set = TRUE;
1162 	} else {
1163 		g_free (gee->tooltip.completion);
1164 		gee->tooltip.completion = NULL;
1165 		gee->tooltip.completion_se_valid = FALSE;
1166 	}
1167 
1168 
1169 	if (!gnm_conf_get_core_gui_editing_function_argument_tooltips ())
1170 		goto not_found;
1171 
1172 	if (gnm_debug_flag ("functooltip"))
1173 		g_printerr ("Last token considered is %d from %2"
1174 			    G_GSIZE_FORMAT " to %2" G_GSIZE_FORMAT ".\n",
1175 			    gli->token, gli->start, gli->end);
1176 
1177 
1178 	while (gli->start > 1) {
1179 		switch (gli->token) {
1180 		case '(':
1181 			if ((gli - 1)->token == STRING) {
1182 				gint start_t = (gli - 1)->start;
1183 				gint end_t = (gli - 1)->end;
1184 				char *name = g_strndup (str + start_t,
1185 							end_t - start_t);
1186 				GnmFunc	*fd = gee_convs (gee)->localized_function_names
1187 					? gnm_func_lookup_localized (name, NULL)
1188 					: gnm_func_lookup (name, NULL);
1189 				g_free (name);
1190 				if (fd != NULL) {
1191 					gee_set_tooltip (gee, fd, args, stuff);
1192 					g_free (str);
1193 					g_free (gli_c);
1194 					return;
1195 				}
1196 			}
1197 			stuff = TRUE;
1198 			args = 0;
1199 			break;
1200 		case '{':
1201 			stuff = (args == 0);
1202 			args = 0;
1203 			break;
1204 		case ')': {
1205 			gint para = 1;
1206 			gli--;
1207 			while (gli->start > 1 && para > 0) {
1208 				switch (gli->token) {
1209 				case ')':
1210 					para++;
1211 					break;
1212 				case '(':
1213 					para--;
1214 					break;
1215 				default:
1216 					break;
1217 				}
1218 				gli--;
1219 			}
1220 			gli++;
1221 			stuff = (args == 0);
1222 			break;
1223 		}
1224 		case '}': {
1225 			gint para = 1;
1226 			gli--;
1227 			while (gli->start > 1 && para > 0) {
1228 				switch (gli->token) {
1229 				case '}':
1230 					para++;
1231 					break;
1232 				case '{':
1233 					para--;
1234 					break;
1235 				default:
1236 					break;
1237 				}
1238 				gli--;
1239 			}
1240 			gli++;
1241 			stuff = (args == 0);
1242 			break;
1243 		}
1244 		case ARG_SEP:
1245 			args++;
1246 			break;
1247 		default:
1248 			stuff = (args == 0);
1249 			break;
1250 		}
1251 		if (gli->start > 1)
1252 			gli--;
1253 	}
1254 
1255  not_found:
1256 	g_free (str);
1257 	g_free (gli_c);
1258 	gee_delete_tooltip (gee, !completion_se_set);
1259 	return;
1260 }
1261 
1262 static gboolean
cb_gee_focus_out_event(G_GNUC_UNUSED GtkWidget * widget,G_GNUC_UNUSED GdkEventFocus * event,gpointer user_data)1263 cb_gee_focus_out_event (G_GNUC_UNUSED GtkWidget     *widget,
1264 			G_GNUC_UNUSED GdkEventFocus *event,
1265 			gpointer                     user_data)
1266 {
1267 	gee_delete_tooltip (user_data, FALSE);
1268 	return FALSE;
1269 }
1270 
1271 static void
cb_gee_notify_cursor_position(GnmExprEntry * gee)1272 cb_gee_notify_cursor_position (GnmExprEntry *gee)
1273 {
1274 	gee_update_env (gee);
1275 	gee_check_tooltip (gee);
1276 }
1277 
1278 static void
cb_entry_changed(GnmExprEntry * gee)1279 cb_entry_changed (GnmExprEntry *gee)
1280 {
1281 	gee_update_lexer_items (gee);
1282 	gee_update_env (gee);
1283 	gee_update_calendar (gee);
1284 	gee_check_tooltip (gee);
1285 	g_signal_emit (G_OBJECT (gee), signals[CHANGED], 0);
1286 }
1287 
1288 static gboolean
cb_gee_key_press_event(GtkEntry * entry,GdkEventKey * event,GnmExprEntry * gee)1289 cb_gee_key_press_event (GtkEntry	*entry,
1290 			GdkEventKey	*event,
1291 			GnmExprEntry	*gee)
1292 {
1293 	WBCGtk *wbcg  = gee->wbcg;
1294 	gboolean is_enter = FALSE;
1295 	int state = gnm_filter_modifiers (event->state);
1296 
1297 	switch (event->keyval) {
1298 	case GDK_KEY_Up:	case GDK_KEY_KP_Up:
1299 	case GDK_KEY_Down:	case GDK_KEY_KP_Down:
1300 		if (gee->is_cell_renderer)
1301 			return FALSE;
1302 		/* Ignore these keys */
1303 		return TRUE;
1304 		/* GDK_KEY_F2 starts editing */
1305 		/* GDK_KEY_F3 opens the paste names dialog */
1306 	case GDK_KEY_F4: {
1307 		/* Cycle absolute reference mode through the sequence rel/rel,
1308 		 * abs/abs, rel/abs, abs/rel and back to rel/rel. Update text
1309 		 * displayed in entry.
1310 		 */
1311 		/* Shift F4 provides the paste names dialog based on the current name */
1312 		/* Control F4 closes the tooltips */
1313 		Rangesel *rs = &gee->rangesel;
1314 		gboolean c, r;
1315 
1316 		if (state == GDK_SHIFT_MASK) {
1317 			if (gee->tooltip.completion_se_valid)
1318 				dialog_function_select_paste
1319 					(gee->wbcg,
1320 					 gee->tooltip.completion_start,
1321 					 gee->tooltip.completion_end);
1322 			else
1323 				dialog_function_select_paste
1324 					(gee->wbcg, -1, -1);
1325 			return TRUE;
1326 		}
1327 		if (state == GDK_CONTROL_MASK) {
1328 			gnm_expr_entry_close_tips (gee);
1329 			return TRUE;
1330 		}
1331 
1332 		if (gee->tooltip.completion != NULL) {
1333 			guint start = gee->tooltip.completion_start;
1334 			guint end = gee->tooltip.completion_end;
1335 			gint new_start = (gint) start;
1336 			GtkEditable *editable = GTK_EDITABLE (gee->entry);
1337 
1338 			gtk_editable_insert_text (editable,
1339 						  gee->tooltip.completion,
1340 						  strlen (gee->tooltip.completion),
1341 						  &new_start);
1342 			gtk_editable_delete_text (editable, new_start,
1343 						  end + new_start - start);
1344 			gtk_editable_set_position (editable, new_start);
1345 			return TRUE;
1346 		}
1347 
1348 		/* FIXME: since the range can't have changed we should just be able to */
1349 		/*        look it up rather than reparse */
1350 
1351 		/* Look for a range */
1352 		if (!rs->is_valid || rs->text_start >= rs->text_end)
1353 			gnm_expr_entry_find_range (gee);
1354 
1355 		/* no range found */
1356 		if (!rs->is_valid || rs->text_start >= rs->text_end)
1357 			return TRUE;
1358 
1359 		if ((GNM_EE_FORCE_ABS_REF | GNM_EE_FORCE_REL_REF) & gee->flags)
1360 			return TRUE;
1361 
1362 		c = rs->ref.a.col_relative;
1363 		r = rs->ref.a.row_relative;
1364 		gnm_cellref_set_col_ar (&rs->ref.a, &gee->pp, !c);
1365 		gnm_cellref_set_col_ar (&rs->ref.b, &gee->pp, !c);
1366 		gnm_cellref_set_row_ar (&rs->ref.a, &gee->pp, c^r);
1367 		gnm_cellref_set_row_ar (&rs->ref.b, &gee->pp, c^r);
1368 
1369 		gee_rangesel_update_text (gee);
1370 
1371 		return TRUE;
1372 	}
1373 
1374 	case GDK_KEY_Escape:
1375 		if (gee->is_cell_renderer) {
1376 			gtk_entry_set_editing_cancelled (entry, TRUE);
1377 			gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (gee));
1378 			gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (gee));
1379 			return TRUE;
1380 		} else
1381 			wbcg_edit_finish (wbcg, WBC_EDIT_REJECT, NULL);
1382 		return TRUE;
1383 
1384 	case GDK_KEY_KP_Enter:
1385 	case GDK_KEY_Return:
1386 		if (gee->is_cell_renderer)
1387 			return FALSE;
1388 		/* Is this the right way to append a newline ?? */
1389 		if (state == GDK_MOD1_MASK) {
1390 			gint pos = gtk_editable_get_position (GTK_EDITABLE (entry));
1391 			gtk_editable_insert_text (GTK_EDITABLE (entry), "\n", 1, &pos);
1392 			gtk_editable_set_position (GTK_EDITABLE (entry), pos);
1393 			return TRUE;
1394 		}
1395 
1396 		/* Ctrl-enter is only applicable for the main entry */
1397 		if (!wbcg_is_editing (wbcg))
1398 			break;
1399 
1400 		is_enter = TRUE;
1401 		/* fall through */
1402 
1403 	case GDK_KEY_Tab:
1404 	case GDK_KEY_ISO_Left_Tab:
1405 	case GDK_KEY_KP_Tab:
1406 		/* Tab is only applicable for the main entry */
1407 		 if (gee->is_cell_renderer || !wbcg_is_editing (wbcg))
1408 			break;
1409 		{
1410 		SheetView *sv;
1411 		WBCEditResult result;
1412 
1413 		if (is_enter && (state & GDK_CONTROL_MASK))
1414 			result = (state & GDK_SHIFT_MASK) ? WBC_EDIT_ACCEPT_ARRAY : WBC_EDIT_ACCEPT_RANGE;
1415 		else
1416 			result = WBC_EDIT_ACCEPT;
1417 
1418 		/* Be careful to restore the editing sheet if we are editing */
1419 		sv = sheet_get_view (wbcg->editing_sheet,
1420 			wb_control_view (GNM_WBC (wbcg)));
1421 
1422 		/* move the edit pos for normal entry */
1423 		if (wbcg_edit_finish (wbcg, result, NULL) && result == WBC_EDIT_ACCEPT) {
1424 			GODirection dir = gnm_conf_get_core_gui_editing_enter_moves_dir ();
1425 			if (!is_enter || dir != GO_DIRECTION_NONE) {
1426 				gboolean forward = TRUE;
1427 				gboolean horizontal = TRUE;
1428 				if (is_enter) {
1429 					horizontal = go_direction_is_horizontal (dir);
1430 					forward = go_direction_is_forward (dir);
1431 				}
1432 
1433 				if (event->state & GDK_SHIFT_MASK)
1434 					forward = !forward;
1435 
1436 				sv_selection_walk_step (sv, forward, horizontal);
1437 
1438 				/* invalidate, in case Enter direction changes */
1439 				if (is_enter)
1440 					sv->first_tab_col = -1;
1441 				gnm_sheet_view_update (sv);
1442 			}
1443 		}
1444 		return TRUE;
1445 	}
1446 
1447 	case GDK_KEY_KP_Separator:
1448 	case GDK_KEY_KP_Decimal: {
1449 		GtkEditable *editable = GTK_EDITABLE (entry);
1450 		gint start, end, l;
1451 		GString const* s = go_locale_get_decimal ();
1452 		gchar const* decimal = s->str;
1453 		l = s->len;
1454 		gtk_editable_get_selection_bounds (editable, &start, &end);
1455 		gtk_editable_delete_text (editable, start, end);
1456 		gtk_editable_insert_text (editable, decimal, l, &start);
1457 		gtk_editable_set_position (editable, start);
1458 		return TRUE;
1459 	}
1460 
1461 	case GDK_KEY_F9: {
1462 		/* Replace selection by its evaluated result.  */
1463 		GtkEditable *editable = GTK_EDITABLE (entry);
1464 		gint start, end;
1465 		char *str;
1466 		GnmExprTop const *texpr;
1467 		Sheet *sheet = gee->pp.sheet;
1468 
1469 		gtk_editable_get_selection_bounds (editable, &start, &end);
1470 		if (end <= start)
1471 			return FALSE;
1472 		str = gtk_editable_get_chars (editable, start, end);
1473 
1474 		texpr = gnm_expr_parse_str (str, &gee->pp,
1475 					    GNM_EXPR_PARSE_DEFAULT,
1476 					    gee_convs (gee),
1477 					    NULL);
1478 		if (texpr) {
1479 			GnmValue *v;
1480 			GnmEvalPos ep;
1481 			char *cst;
1482 			GnmExpr const *expr;
1483 
1484 			eval_pos_init_pos (&ep, sheet, &gee->pp.eval);
1485 			v = gnm_expr_top_eval (texpr, &ep, GNM_EXPR_EVAL_SCALAR_NON_EMPTY);
1486 			gnm_expr_top_unref (texpr);
1487 
1488 			/*
1489 			 * Turn the value into an expression so we get
1490 			 * the right syntax.
1491 			 */
1492 			expr = gnm_expr_new_constant (v);
1493 			cst = gnm_expr_as_string (expr, &gee->pp,
1494 						  gee_convs (gee));
1495 			gnm_expr_free (expr);
1496 
1497 			gtk_editable_delete_text (editable, start, end);
1498 			gtk_editable_insert_text (editable, cst, -1, &start);
1499 			gtk_editable_set_position (editable, start);
1500 
1501 			g_free (cst);
1502 		}
1503 
1504 		g_free (str);
1505 		return TRUE;
1506 	}
1507 
1508 	default:
1509 		break;
1510 	}
1511 
1512 	return FALSE;
1513 }
1514 
1515 static gboolean
cb_gee_button_press_event(G_GNUC_UNUSED GtkEntry * entry,G_GNUC_UNUSED GdkEventButton * event,GnmExprEntry * gee)1516 cb_gee_button_press_event (G_GNUC_UNUSED GtkEntry *entry,
1517 			   G_GNUC_UNUSED GdkEventButton *event,
1518 			   GnmExprEntry *gee)
1519 {
1520 	g_return_val_if_fail (GNM_EXPR_ENTRY_IS (gee), FALSE);
1521 
1522 	if (gee->scg) {
1523 		scg_rangesel_stop (gee->scg, FALSE);
1524 		gnm_expr_entry_find_range (gee);
1525 		g_signal_emit (G_OBJECT (gee), signals[CHANGED], 0);
1526 	}
1527 
1528 	return FALSE;
1529 }
1530 
1531 static gboolean
gee_mnemonic_activate(GtkWidget * w,G_GNUC_UNUSED gboolean group_cycling)1532 gee_mnemonic_activate (GtkWidget *w, G_GNUC_UNUSED gboolean group_cycling)
1533 {
1534 	GnmExprEntry *gee = GNM_EXPR_ENTRY (w);
1535 	gtk_widget_grab_focus (GTK_WIDGET (gee->entry));
1536 	return TRUE;
1537 }
1538 
1539 static void
gee_init(GnmExprEntry * gee)1540 gee_init (GnmExprEntry *gee)
1541 {
1542 	gee->editing_canceled = FALSE;
1543 	gee->is_cell_renderer = FALSE;
1544 	gee->ignore_changes = FALSE;
1545 	gee->flags = 0;
1546 	gee->scg = NULL;
1547 	gee->sheet = NULL;
1548 	gee->wbcg = NULL;
1549 	gee->freeze_count = 0;
1550 	gee->update_timeout_id = 0;
1551 	gee->update_policy = GNM_UPDATE_CONTINUOUS;
1552 	gee->feedback_disabled = FALSE;
1553 	gee->lexer_items = NULL;
1554 	gee->texpr = NULL;
1555 	gee->tooltip.tooltip = NULL;
1556 	gee->tooltip.fd = NULL;
1557 	gee->tooltip.handlerid = 0;
1558 	gee->tooltip.enabled = TRUE;
1559 	gee_rangesel_reset (gee);
1560 
1561 	gee->entry = GTK_ENTRY (gtk_entry_new ());
1562 
1563 	/* Disable selecting the entire content when the widget gets focus */
1564 	g_object_set (gtk_widget_get_settings (GTK_WIDGET (gee->entry)),
1565 		      "gtk-entry-select-on-focus", FALSE,
1566 		      NULL);
1567 
1568 	g_signal_connect_swapped (G_OBJECT (gee->entry), "activate",
1569 		G_CALLBACK (cb_entry_activate), gee);
1570 	g_signal_connect_swapped (G_OBJECT (gee->entry), "changed",
1571 		G_CALLBACK (cb_entry_changed), gee);
1572 	g_signal_connect (G_OBJECT (gee->entry), "key_press_event",
1573 		G_CALLBACK (cb_gee_key_press_event), gee);
1574 	g_signal_connect (G_OBJECT (gee->entry), "button_press_event",
1575 		G_CALLBACK (cb_gee_button_press_event), gee);
1576 	g_signal_connect_swapped (G_OBJECT (gee->entry), "notify::cursor-position",
1577 		G_CALLBACK (cb_gee_notify_cursor_position), gee);
1578 	gtk_box_pack_start (GTK_BOX (gee), GTK_WIDGET (gee->entry),
1579 		TRUE, TRUE, 0);
1580 	gtk_widget_show (GTK_WIDGET (gee->entry));
1581 }
1582 
1583 static void
gee_finalize(GObject * obj)1584 gee_finalize (GObject *obj)
1585 {
1586 	GnmExprEntry *gee = (GnmExprEntry *)obj;
1587 
1588 	go_format_unref (gee->constant_format);
1589 	gee_delete_tooltip (gee, TRUE);
1590 	g_free (gee->lexer_items);
1591 	if (gee->texpr != NULL)
1592 		gnm_expr_top_unref (gee->texpr);
1593 
1594 	((GObjectClass *)parent_class)->finalize (obj);
1595 }
1596 
1597 static void
gee_set_value_double(GogDataEditor * editor,double val,GODateConventions const * date_conv)1598 gee_set_value_double (GogDataEditor *editor, double val,
1599 		      GODateConventions const *date_conv)
1600 {
1601 	GnmExprEntry *gee = GNM_EXPR_ENTRY (editor);
1602 	GnmValue *v = value_new_float (val);
1603 	char *txt = format_value (gee->constant_format, v, -1, date_conv);
1604 
1605 	value_release (v);
1606 
1607 	if (*txt == 0) {
1608 		g_free (txt);
1609 		txt = g_strdup_printf ("%g", val);
1610 	}
1611 
1612 	if (gee_debug)
1613 		g_printerr ("Setting text %s\n", txt);
1614 
1615 	g_object_set (G_OBJECT (editor), "text", txt, NULL);
1616 
1617 	g_free (txt);
1618 }
1619 
1620 static void
gee_data_editor_set_format(GogDataEditor * deditor,GOFormat const * fmt)1621 gee_data_editor_set_format (GogDataEditor *deditor, GOFormat const *fmt)
1622 {
1623 	GnmExprEntry *gee = (GnmExprEntry *)deditor;
1624 	GnmValue *v;
1625 	GODateConventions const *date_conv =
1626 		sheet_date_conv (gee->sheet);
1627 
1628 	if (fmt == gee->constant_format)
1629 		return;
1630 
1631 	v = get_matched_value (gee);
1632 
1633 	gee_set_format (gee, fmt);
1634 
1635 	if (v && VALUE_IS_FLOAT (v)) {
1636 		char *txt = format_value (gee->constant_format, v,
1637 					  -1, date_conv);
1638 		gtk_entry_set_text (gee->entry, txt);
1639 		g_free (txt);
1640 	}
1641 
1642 	value_release (v);
1643 }
1644 
1645 static void
gee_go_plot_data_editor_init(GogDataEditorClass * iface)1646 gee_go_plot_data_editor_init (GogDataEditorClass *iface)
1647 {
1648 	iface->set_format = gee_data_editor_set_format;
1649 	iface->set_value_double = gee_set_value_double;
1650 }
1651 
1652 
1653 static void
gee_class_init(GObjectClass * gobject_class)1654 gee_class_init (GObjectClass *gobject_class)
1655 {
1656 	GtkWidgetClass *widget_class = (GtkWidgetClass *)gobject_class;
1657 
1658 	parent_class = g_type_class_peek_parent (gobject_class);
1659 
1660 	gobject_class->set_property	= gee_set_property;
1661 	gobject_class->get_property	= gee_get_property;
1662 	gobject_class->finalize		= gee_finalize;
1663 	widget_class->destroy		= gee_destroy;
1664 	widget_class->mnemonic_activate = gee_mnemonic_activate;
1665 
1666 	signals[UPDATE] = g_signal_new ("update",
1667 		GNM_EXPR_ENTRY_TYPE,
1668 		G_SIGNAL_RUN_LAST,
1669 		G_STRUCT_OFFSET (GnmExprEntryClass, update),
1670 		NULL, NULL,
1671 		g_cclosure_marshal_VOID__BOOLEAN,
1672 		G_TYPE_NONE,
1673 		1, G_TYPE_BOOLEAN);
1674 	signals[CHANGED] = g_signal_new ("changed",
1675 		GNM_EXPR_ENTRY_TYPE,
1676 		G_SIGNAL_RUN_LAST,
1677 		G_STRUCT_OFFSET (GnmExprEntryClass, changed),
1678 		NULL, NULL,
1679 		g_cclosure_marshal_VOID__VOID,
1680 		G_TYPE_NONE, 0);
1681 	signals[ACTIVATE] =
1682 		g_signal_new ("activate",
1683 		G_OBJECT_CLASS_TYPE (gobject_class),
1684 		G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1685 		G_STRUCT_OFFSET (GnmExprEntryClass, activate),
1686 		NULL, NULL,
1687 		g_cclosure_marshal_VOID__VOID,
1688 		G_TYPE_NONE, 0);
1689 
1690 
1691 	g_object_class_override_property
1692 		(gobject_class, PROP_EDITING_CANCELED, "editing-canceled");
1693 
1694 	g_object_class_install_property
1695 		(gobject_class, PROP_UPDATE_POLICY,
1696 		 g_param_spec_enum ("update-policy",
1697 				    P_("Update policy"),
1698 				    P_("How frequently changes to the entry should be applied"),
1699 				    GNM_TYPE_UPDATE_TYPE, GNM_UPDATE_CONTINUOUS,
1700 				    GSF_PARAM_STATIC | G_PARAM_READWRITE));
1701 
1702 	g_object_class_install_property
1703 		(gobject_class, PROP_WITH_ICON,
1704 		 g_param_spec_boolean ("with-icon",
1705 				       P_("With icon"),
1706 				       P_("Should there be an icon to the right of the entry?"),
1707 				       TRUE,
1708 				       GSF_PARAM_STATIC | G_PARAM_READWRITE));
1709 
1710 	g_object_class_install_property
1711 		(gobject_class, PROP_TEXT,
1712 		 g_param_spec_string ("text",
1713 				      P_("Text"),
1714 				      P_("The contents of the entry"),
1715 				      "",
1716 				      GSF_PARAM_STATIC | G_PARAM_READWRITE));
1717 
1718 	g_object_class_install_property
1719 		(gobject_class, PROP_FLAGS,
1720 		 g_param_spec_uint ("flags", NULL, NULL,
1721 				    0, GNM_EE_MASK, 0,
1722 				    GSF_PARAM_STATIC | G_PARAM_READWRITE));
1723 
1724 	g_object_class_install_property
1725 		(gobject_class, PROP_SCG,
1726 		 g_param_spec_object ("scg",
1727 				      P_("SheetControlGUI"),
1728 				      P_("The GUI container associated with the entry."),
1729 				      GNM_SCG_TYPE,
1730 				      GSF_PARAM_STATIC | G_PARAM_READWRITE));
1731 
1732 	g_object_class_install_property
1733 		(gobject_class, PROP_WBCG,
1734 		 g_param_spec_object ("wbcg",
1735 				      P_("WBCGtk"),
1736 				      P_("The toplevel GUI container associated with the entry."),
1737 				      GNM_WBC_GTK_TYPE,
1738 				      GSF_PARAM_STATIC | G_PARAM_READWRITE));
1739 
1740 	g_object_class_install_property
1741 		(gobject_class, PROP_CONSTANT_FORMAT,
1742 		 g_param_spec_boxed ("constant-format",
1743 				     P_("Constant Format"),
1744 				     P_("Format for constants"),
1745 				     go_format_get_type (),
1746 				     GSF_PARAM_STATIC | G_PARAM_READWRITE));
1747 
1748 	gee_debug = gnm_debug_flag ("gee");
1749 }
1750 
1751 /***************************************************************************/
1752 
1753 static void
gee_editable_start_editing(GtkCellEditable * cell_editable,G_GNUC_UNUSED GdkEvent * event)1754 gee_editable_start_editing (GtkCellEditable *cell_editable,
1755 			    G_GNUC_UNUSED GdkEvent *event)
1756 {
1757 	GtkEntry *entry = gnm_expr_entry_get_entry (GNM_EXPR_ENTRY (cell_editable));
1758 	GNM_EXPR_ENTRY (cell_editable)->is_cell_renderer = TRUE;
1759 	g_signal_connect_swapped (G_OBJECT (entry), "activate",
1760 		G_CALLBACK (gtk_cell_editable_editing_done), cell_editable);
1761 	gtk_widget_grab_focus (GTK_WIDGET (entry));
1762 }
1763 
1764 static void
gee_cell_editable_init(GtkCellEditableIface * iface)1765 gee_cell_editable_init (GtkCellEditableIface *iface)
1766 {
1767 	iface->start_editing = gee_editable_start_editing;
1768 }
1769 /***************************************************************************/
1770 
1771 GSF_CLASS_FULL (GnmExprEntry, gnm_expr_entry,
1772 		NULL, NULL, gee_class_init, NULL,
1773 		gee_init, GTK_TYPE_BOX, 0,
1774 		GSF_INTERFACE (gee_cell_editable_init, GTK_TYPE_CELL_EDITABLE);
1775 		GSF_INTERFACE (gee_go_plot_data_editor_init, GOG_TYPE_DATA_EDITOR))
1776 
1777 /**
1778  * gee_prepare_range :
1779  * @gee:
1780  * @dst:
1781  *
1782  * Adjust @dst as necessary to conform to @gee's requirements
1783  * Produces the _logical_ range, a merge is displayed as only the topleft.
1784  **/
1785 static void
gee_prepare_range(GnmExprEntry const * gee,GnmRangeRef * dst)1786 gee_prepare_range (GnmExprEntry const *gee, GnmRangeRef *dst)
1787 {
1788 	Rangesel const *rs = &gee->rangesel;
1789 
1790 	*dst = rs->ref;
1791 
1792 	if (dst->a.sheet == NULL && !(gee->flags & GNM_EE_SHEET_OPTIONAL))
1793 		dst->a.sheet = gee->sheet;
1794 	if (gee->flags & GNM_EE_FULL_ROW) {
1795 		dst->a.col = 0;
1796 		dst->b.col = gnm_sheet_get_last_col (gee->sheet);
1797 	}
1798 	if (gee->flags & GNM_EE_FULL_COL) {
1799 		dst->a.row = 0;
1800 		dst->b.row = gnm_sheet_get_last_row (gee->sheet);
1801 	}
1802 
1803 	/* special case a single merge to be only corner */
1804 	if (!(gee->flags & (GNM_EE_FULL_ROW|GNM_EE_FULL_COL))) {
1805 		GnmEvalPos ep;
1806 		GnmRange r;
1807 		GnmRange const *merge;
1808 		Sheet *start_sheet, *end_sheet;
1809 		gnm_rangeref_normalize(dst,
1810 			eval_pos_init_pos (&ep, gee->sheet, &gee->pp.eval),
1811 			&start_sheet, &end_sheet,
1812 			&r);
1813 		merge = gnm_sheet_merge_is_corner (gee->sheet, &r.start);
1814 		if (merge != NULL && range_equal (merge, &r))
1815 			dst->b = dst->a;
1816 	}
1817 }
1818 
1819 static char *
gee_rangesel_make_text(GnmExprEntry const * gee)1820 gee_rangesel_make_text (GnmExprEntry const *gee)
1821 {
1822 	GnmRangeRef ref;
1823 	GnmConventionsOut out;
1824 
1825 	gee_prepare_range (gee, &ref);
1826 
1827 	out.accum = g_string_new (NULL);
1828 	out.pp    = &gee->pp;
1829 	out.convs = gee_convs (gee);
1830 	rangeref_as_string (&out, &ref);
1831 	return g_string_free (out.accum, FALSE);
1832 }
1833 
1834 static void
gee_rangesel_update_text(GnmExprEntry * gee)1835 gee_rangesel_update_text (GnmExprEntry *gee)
1836 {
1837 	GtkEditable *editable = GTK_EDITABLE (gee->entry);
1838 	Rangesel *rs = &gee->rangesel;
1839 	int len;
1840 	char *text = gee_rangesel_make_text (gee);
1841 
1842 	g_return_if_fail (!gee->ignore_changes);
1843 
1844 	gee->ignore_changes = TRUE;
1845 	if (rs->text_end > rs->text_start) {
1846 		if (text == NULL)
1847 			gtk_editable_delete_text (editable,
1848 						  rs->text_start,
1849 						  rs->text_end);
1850 		else
1851 			/* We don't call gtk_editable_delete_text since we don't want */
1852 			/* to emit a signal yet */
1853 			GTK_EDITABLE_GET_IFACE (gee->entry)->delete_text (editable,
1854 									  rs->text_start,
1855 									  rs->text_end);
1856 		rs->text_end = rs->text_start;
1857 		gtk_editable_set_position (GTK_EDITABLE (gee->entry), rs->text_end);
1858 	} else
1859 		rs->text_start = rs->text_end =
1860 			gtk_editable_get_position (GTK_EDITABLE (gee->entry));
1861 
1862 	if (text != NULL) {
1863 		/* Set the cursor at the end.  It looks nicer */
1864 		len = strlen (text);
1865 
1866 		gtk_editable_insert_text (editable, text, len, &rs->text_end);
1867 		gtk_editable_set_position (editable, rs->text_end);
1868 		g_free (text);
1869 	}
1870 
1871 	gee->ignore_changes = FALSE;
1872 }
1873 
1874 static void
gee_find_lexer_token(GnmLexerItem const * gli,guint token_pos,GnmLexerItem const ** gli_before,GnmLexerItem const ** gli_after)1875 gee_find_lexer_token (GnmLexerItem const *gli, guint token_pos,
1876 		      GnmLexerItem const **gli_before, GnmLexerItem const **gli_after)
1877 {
1878 	*gli_before = *gli_after = NULL;
1879 	if (gli->token == 0)
1880 		return;
1881 	if (gli->start == token_pos) {
1882 		*gli_after = gli;
1883 		return;
1884 	}
1885 	while (gli->token != 0) {
1886 		if (gli->start < token_pos && token_pos < gli->end) {
1887 			*gli_before = *gli_after = gli;
1888 			return;
1889 		}
1890 		if (gli->start == token_pos) {
1891 			*gli_before = gli - 1;
1892 			*gli_after = gli;
1893 			return;
1894 		}
1895 		if (gli->end == token_pos) {
1896 			*gli_before = gli;
1897 			*gli_after = ((gli + 1)->token != 0) ? (gli + 1) : NULL;
1898 			return;
1899 		}
1900 		gli++;
1901 	}
1902 	*gli_before = gli - 1;
1903 	return;
1904 }
1905 
1906 /**
1907  * gnm_expr_entry_find_range:
1908  * @gee:   a #GnmExprEntry
1909  *
1910  * Look at the current selection to see how much of it needs to be changed when
1911  * selecting a range.
1912  **/
1913 gboolean
gnm_expr_entry_find_range(GnmExprEntry * gee)1914 gnm_expr_entry_find_range (GnmExprEntry *gee)
1915 {
1916 	gboolean  single, formula_only;
1917 	char const *text, *cursor, *tmp, *ptr;
1918 	char *rs_text;
1919 	GnmRangeRef  range;
1920 	Rangesel *rs;
1921 	int len, token_pos;
1922 	GnmLexerItem const *gli, *gli_before, *gli_after;
1923 
1924 	g_return_val_if_fail (gee != NULL, FALSE);
1925 
1926 	single = (gee->flags & GNM_EE_SINGLE_RANGE) != 0;
1927 	rs = &gee->rangesel;
1928 	memset (rs, 0, sizeof (*rs));
1929 	rs->ref.a.col_relative = rs->ref.a.row_relative = TRUE;
1930 	rs->ref.b.col_relative = rs->ref.b.row_relative = TRUE;
1931 	gee_force_abs_rel (gee);
1932 
1933 	text = gtk_entry_get_text (gee->entry);
1934 	if (text == NULL)
1935 		return TRUE;
1936 
1937 	formula_only = (gee->flags & GNM_EE_FORMULA_ONLY) != 0;
1938 	if (formula_only && !gnm_expr_char_start_p (text))
1939 		return FALSE;
1940 
1941 	len = g_utf8_strlen (text, -1);
1942 
1943 	if (single) {
1944 		GnmRangeRef range;
1945 		rs->text_start = 0;
1946 		rs->text_end = len;
1947 		tmp = rangeref_parse (&range, text, &gee->pp, gee_convs (gee));
1948 		if (tmp != text) {
1949 			rs->is_valid = TRUE;
1950 			rs->ref = range;
1951 		}
1952 		return TRUE;
1953 	}
1954 
1955 	cursor = g_utf8_offset_to_pointer
1956 		(text, gtk_editable_get_position (GTK_EDITABLE (gee->entry)));
1957 
1958 	ptr = gnm_expr_char_start_p (text);
1959 	if (ptr == NULL)
1960 		ptr = text;
1961 
1962 	if (gnm_debug_flag ("rangeselection"))
1963 		g_printerr ("text: >%s< -- cursor: >%s<\n", text, cursor);
1964 
1965 	if (ptr[0] == '\0') {
1966 		rs->text_end = rs->text_start =
1967 			g_utf8_pointer_to_offset
1968 			(text, ptr);
1969 		return TRUE;
1970 	}
1971 
1972 	if (gee->lexer_items == NULL)
1973 		gee_update_lexer_items (gee);
1974 	g_return_val_if_fail (gee->lexer_items != NULL, FALSE);
1975 
1976 	gli = gee->lexer_items;
1977 	while (gli->token != 0 && gli->start < (guint) (ptr - text))
1978 		gli++;
1979 
1980 	if (gli->token == 0) {
1981 		rs->text_start = g_utf8_pointer_to_offset
1982 			(text, ptr);
1983 		rs->text_end = len;
1984 		return TRUE;
1985 	}
1986 
1987 	token_pos = cursor - text;
1988 
1989 	gee_find_lexer_token (gli, (guint)token_pos, &gli_before, &gli_after);
1990 
1991 	if (gnm_debug_flag ("rangeselection")) {
1992 		g_printerr ("before: %p -- after: %p\n", gli_before, gli_after);
1993 		if (gli_before)
1994 			g_printerr ("before token: %d\n", gli_before->token);
1995 		if (gli_after)
1996 			g_printerr ("after token: %d\n", gli_after->token);
1997 	}
1998 
1999 	if (gli_before == NULL && gli_after == NULL)
2000 		return FALSE;
2001 
2002 	if (gli_before == gli_after) {
2003 		if ((gli_after + 1)->token == '(' ||
2004 		    (gli_after + 1)->token == '{')
2005 			return FALSE;
2006 		if (gli < gli_before &&
2007 		    ((gli_before - 1)->token == ')' ||
2008 		     (gli_before - 1)->token == '}'))
2009 			return FALSE;
2010 		rs->text_start = g_utf8_pointer_to_offset
2011 			(text, text + gli_before->start);
2012 		rs->text_end   = g_utf8_pointer_to_offset
2013 			(text, text + gli_before->end);
2014 	} else if (gli_before != NULL && gli_after != NULL) {
2015 		switch (gli_before->token) {
2016 		case STRING:
2017 		case QUOTED_STRING:
2018 		case CONSTANT:
2019 		case RANGEREF:
2020 		case INVALID_TOKEN:
2021 			if (gli_after->token == '(' ||
2022 			    gli_after->token == '{')
2023 				return FALSE;
2024 			rs->text_start = g_utf8_pointer_to_offset
2025 				(text, text + gli_before->start);
2026 			rs->text_end   = g_utf8_pointer_to_offset
2027 				(text, text + gli_before->end);
2028 			break;
2029 		default:
2030 			switch (gli_after->token) {
2031 			case STRING:
2032 			case QUOTED_STRING:
2033 			case CONSTANT:
2034 			case RANGEREF:
2035 			case INVALID_TOKEN:
2036 				rs->text_start = g_utf8_pointer_to_offset
2037 					(text, text + gli_after->start);
2038 				rs->text_end   = g_utf8_pointer_to_offset
2039 					(text, text + gli_after->end);
2040 				break;
2041 			default:
2042 				rs->text_start = g_utf8_pointer_to_offset
2043 					(text, text + gli_before->end);
2044 				rs->text_end   = g_utf8_pointer_to_offset
2045 					(text, text + gli_after->start);
2046 				break;
2047 			}
2048 		}
2049 	} else if (gli_before == NULL)
2050 		switch (gli_after->token) {
2051 		case STRING:
2052 		case QUOTED_STRING:
2053 		case CONSTANT:
2054 		case RANGEREF:
2055 		case INVALID_TOKEN:
2056 			if ((gli_after + 1)->token == '(' ||
2057 			    (gli_after + 1)->token == '{')
2058 				return FALSE;
2059 			rs->text_start = g_utf8_pointer_to_offset
2060 				(text, text + gli_after->start);
2061 			rs->text_end   = g_utf8_pointer_to_offset
2062 				(text, text + gli_after->end);
2063 			break;
2064 		default:
2065 			rs->text_end = rs->text_start =
2066 				g_utf8_pointer_to_offset
2067 				(text, text + gli_after->start);
2068 			break;
2069 		}
2070 	else switch (gli_before->token) {
2071 		case STRING:
2072 		case QUOTED_STRING:
2073 		case CONSTANT:
2074 		case RANGEREF:
2075 		case INVALID_TOKEN:
2076 			if (gli < gli_before &&
2077 			    ((gli_before - 1)->token == ')' ||
2078 			     (gli_before - 1)->token == '}'))
2079 				return FALSE;
2080 			rs->text_start = g_utf8_pointer_to_offset
2081 				(text, text + gli_before->start);
2082 			rs->text_end   = g_utf8_pointer_to_offset
2083 				(text, text + gli_before->end);
2084 			break;
2085 		case ')':
2086 		case '}':
2087 			return FALSE;
2088 		default:
2089 			rs->text_end = rs->text_start =
2090 				g_utf8_pointer_to_offset
2091 				(text, text + gli_before->start);
2092 			break;
2093 		}
2094 
2095 	if (gnm_debug_flag ("rangeselection"))
2096 		g_printerr ("characters from %d to %d\n",
2097 			    rs->text_start, rs->text_end);
2098 
2099 	rs_text = gtk_editable_get_chars (GTK_EDITABLE (gee->entry),
2100 					  rs->text_start, rs->text_end);
2101 	tmp = rangeref_parse (&range, rs_text, &gee->pp, gee_convs (gee));
2102 	g_free (rs_text);
2103 	if (tmp != rs_text) {
2104 		rs->is_valid = TRUE;
2105 		rs->ref = range;
2106 	}
2107 	return TRUE;
2108 }
2109 
2110 /**
2111  * gnm_expr_entry_rangesel_stop:
2112  * @gee:   a #GnmExprEntry
2113  * @clear_string: clear string flag
2114  *
2115  * Perform the appropriate action when a range selection has been completed.
2116  **/
2117 void
gnm_expr_entry_rangesel_stop(GnmExprEntry * gee,gboolean clear_string)2118 gnm_expr_entry_rangesel_stop (GnmExprEntry *gee,
2119 			      gboolean clear_string)
2120 {
2121 	Rangesel *rs;
2122 
2123 	g_return_if_fail (GNM_EXPR_ENTRY_IS (gee));
2124 
2125 	rs = &gee->rangesel;
2126 	if (clear_string && rs->text_end > rs->text_start)
2127 		gtk_editable_delete_text (GTK_EDITABLE (gee->entry),
2128 					  rs->text_start, rs->text_end);
2129 
2130 	if (!(gee->flags & GNM_EE_SINGLE_RANGE) || clear_string)
2131 		gee_rangesel_reset (gee);
2132 }
2133 
2134 /***************************************************************************/
2135 
2136 static void
cb_scg_destroy(GnmExprEntry * gee,SheetControlGUI * scg)2137 cb_scg_destroy (GnmExprEntry *gee, SheetControlGUI *scg)
2138 {
2139 	g_return_if_fail (scg == gee->scg);
2140 
2141 	gee_rangesel_reset (gee);
2142 	gee->scg = NULL;
2143 	gee->sheet = NULL;
2144 }
2145 
2146 static void
gee_detach_scg(GnmExprEntry * gee)2147 gee_detach_scg (GnmExprEntry *gee)
2148 {
2149 	if (gee->scg != NULL) {
2150 		g_object_weak_unref (G_OBJECT (gee->scg),
2151 				     (GWeakNotify) cb_scg_destroy, gee);
2152 		gee->scg = NULL;
2153 		gee->sheet = NULL;
2154 	}
2155 }
2156 
2157 /***************************************************************************/
2158 
2159 typedef struct {
2160 	GnmExprEntry *gee;
2161 	gboolean user_requested;
2162 } GEETimerClosure;
2163 
2164 static gboolean
cb_gee_update_timeout(GEETimerClosure const * info)2165 cb_gee_update_timeout (GEETimerClosure const *info)
2166 {
2167 	info->gee->update_timeout_id = 0;
2168 	g_signal_emit (G_OBJECT (info->gee), signals[UPDATE], 0,
2169 		       info->user_requested);
2170 	return FALSE;
2171 }
2172 
2173 static void
gee_remove_update_timer(GnmExprEntry * gee)2174 gee_remove_update_timer (GnmExprEntry *gee)
2175 {
2176 	if (gee->update_timeout_id != 0) {
2177 		g_source_remove (gee->update_timeout_id);
2178 		gee->update_timeout_id = 0;
2179 	}
2180 }
2181 
2182 static void
gee_reset_update_timer(GnmExprEntry * gee,gboolean user_requested)2183 gee_reset_update_timer (GnmExprEntry *gee, gboolean user_requested)
2184 {
2185 	GEETimerClosure *dat = g_new (GEETimerClosure, 1);
2186 	gee_remove_update_timer (gee);
2187 	dat->gee = gee;
2188 	dat->user_requested = user_requested;
2189 	gee->update_timeout_id = g_timeout_add_full (G_PRIORITY_DEFAULT, 300,
2190 		(GSourceFunc) cb_gee_update_timeout, dat, g_free);
2191 }
2192 
2193 /**
2194  * gnm_expr_entry_signal_update:
2195  * @gee:
2196  * @user_requested: is the update requested by the user (eg activation)
2197  *
2198  * Higher level operations know when they are logically complete and can notify
2199  * GnmExprEntry clients.  For example, button-up after a drag selection
2200  * indicates a logical end to the change and offers a good time to update.
2201  **/
2202 void
gnm_expr_entry_signal_update(GnmExprEntry * gee,gboolean user_requested)2203 gnm_expr_entry_signal_update (GnmExprEntry *gee, gboolean user_requested)
2204 {
2205 	gee_reset_update_timer (gee, user_requested);
2206 }
2207 
2208 /**
2209  * gnm_expr_entry_set_update_policy:
2210  * @gee: a #GnmExprEntry
2211  * @policy: update policy
2212  *
2213  * Sets the update policy for the expr-entry. #GNM_UPDATE_CONTINUOUS means that
2214  * anytime the entry's content changes, the update signal will be emitted.
2215  * #GNM_UPDATE_DELAYED means that the signal will be emitted after a brief
2216  * timeout when no changes occur, so updates are spaced by a short time rather
2217  * than continuous. #GNM_UPDATE_DISCONTINUOUS means that the signal will only
2218  * be emitted when the user releases the button and ends the rangeselection.
2219  *
2220  **/
2221 void
gnm_expr_entry_set_update_policy(GnmExprEntry * gee,GnmUpdateType policy)2222 gnm_expr_entry_set_update_policy (GnmExprEntry *gee,
2223 				       GnmUpdateType  policy)
2224 {
2225 	g_return_if_fail (GNM_EXPR_ENTRY_IS (gee));
2226 
2227 	if (gee->update_policy == policy)
2228 		return;
2229 	gee->update_policy = policy;
2230 	g_object_notify (G_OBJECT (gee), "update-policy");
2231 }
2232 
2233 /**
2234  * gnm_expr_entry_new:
2235  * @wbcg: #WBCGtk
2236  * @with_icon: append a rollup icon to the end of the entry
2237  *
2238  * Creates a new #GnmExprEntry, which is an entry widget with support
2239  * for range selections.
2240  * The entry is created with default flag settings which are suitable for use
2241  * in many dialogs, but see #gnm_expr_entry_set_flags.
2242  *
2243  * Return value: a new #GnmExprEntry.
2244  **/
2245 GnmExprEntry *
gnm_expr_entry_new(WBCGtk * wbcg,gboolean with_icon)2246 gnm_expr_entry_new (WBCGtk *wbcg, gboolean with_icon)
2247 {
2248 	return g_object_new (GNM_EXPR_ENTRY_TYPE,
2249 			     "scg",	  wbcg_cur_scg (wbcg),
2250 			     "with-icon", with_icon,
2251 			     NULL);
2252 }
2253 
2254 void
gnm_expr_entry_freeze(GnmExprEntry * gee)2255 gnm_expr_entry_freeze (GnmExprEntry *gee)
2256 {
2257 	g_return_if_fail (GNM_EXPR_ENTRY_IS (gee));
2258 
2259 	gee->freeze_count++;
2260 }
2261 
2262 void
gnm_expr_entry_thaw(GnmExprEntry * gee)2263 gnm_expr_entry_thaw (GnmExprEntry *gee)
2264 {
2265 	g_return_if_fail (GNM_EXPR_ENTRY_IS (gee));
2266 
2267 	if (gee->freeze_count > 0 && (--gee->freeze_count) == 0) {
2268 		gee_rangesel_update_text (gee);
2269 		switch (gee->update_policy) {
2270 		case GNM_UPDATE_DELAYED :
2271 			gee_reset_update_timer (gee, FALSE);
2272 			break;
2273 
2274 		default :
2275 		case GNM_UPDATE_DISCONTINUOUS :
2276 			if (gee->scg->rangesel.active)
2277 				break;
2278 		case GNM_UPDATE_CONTINUOUS:
2279 			g_signal_emit (G_OBJECT (gee), signals[UPDATE], 0, FALSE);
2280 		}
2281 	}
2282 }
2283 
2284 /**
2285  * gnm_expr_entry_set_flags:
2286  * @gee: a #GnmExprEntry
2287  * @flags:      bitmap of flag values
2288  * @mask:       bitmap with ones for flags to be changed
2289  *
2290  * Changes the flags specified in @mask to values given in @flags.
2291  *
2292  * Flags (%FALSE by default):
2293  * %GNM_EE_SINGLE_RANGE      Entry will only hold a single range.
2294  * %GNM_EE_ABS_COL           Column reference must be absolute.
2295  * %GNM_EE_ABS_ROW           Row reference must be absolute.
2296  * %GNM_EE_FULL_COL          GnmRange consists of full columns.
2297  * %GNM_EE_FULL_ROW          GnmRange consists of full rows.
2298  * %GNM_EE_SHEET_OPTIONAL    Current sheet name not auto-added.
2299  **/
2300 void
gnm_expr_entry_set_flags(GnmExprEntry * gee,GnmExprEntryFlags flags,GnmExprEntryFlags mask)2301 gnm_expr_entry_set_flags (GnmExprEntry *gee,
2302 			  GnmExprEntryFlags flags,
2303 			  GnmExprEntryFlags mask)
2304 {
2305 	GnmExprEntryFlags newflags;
2306 	g_return_if_fail (GNM_EXPR_ENTRY_IS (gee));
2307 
2308 	newflags = (gee->flags & ~mask) | (flags & mask);
2309 	if (gee->flags == newflags)
2310 		return;
2311 
2312 	gee->flags = newflags;
2313 	gee_rangesel_reset (gee);
2314 }
2315 
2316 /**
2317  * gnm_expr_entry_set_scg:
2318  * @gee: a #GnmExprEntry
2319  * @scg: a #SheetControlGUI
2320  *
2321  * Associates the entry with a SheetControlGUI. The entry widget
2322  * automatically removes the association when the SheetControlGUI is
2323  * destroyed.
2324  **/
2325 void
gnm_expr_entry_set_scg(GnmExprEntry * gee,SheetControlGUI * scg)2326 gnm_expr_entry_set_scg (GnmExprEntry *gee, SheetControlGUI *scg)
2327 {
2328 	g_return_if_fail (GNM_EXPR_ENTRY_IS (gee));
2329 	g_return_if_fail (scg == NULL || GNM_IS_SCG (scg));
2330 
2331 	if ((gee->flags & GNM_EE_SINGLE_RANGE) || scg != gee->scg)
2332 		gee_rangesel_reset (gee);
2333 
2334 	gee_detach_scg (gee);
2335 	gee->scg = scg;
2336 	if (scg) {
2337 		g_object_weak_ref (G_OBJECT (gee->scg),
2338 				   (GWeakNotify) cb_scg_destroy, gee);
2339 		gee->sheet = sc_sheet (GNM_SHEET_CONTROL (scg));
2340 		parse_pos_init_editpos (&gee->pp, scg_view (gee->scg));
2341 		gee->wbcg = scg_wbcg (gee->scg);
2342 	} else
2343 		gee->sheet = NULL;
2344 
2345 	if (gee_debug)
2346 		g_printerr ("Setting gee (%p)->sheet = %s\n",
2347 			    gee, gee->sheet->name_unquoted);
2348 }
2349 
2350 /**
2351  * gnm_expr_entry_get_scg:
2352  * @gee:
2353  *
2354  * Returns: (transfer none): the associated #SheetControlGUI.
2355  **/
2356 SheetControlGUI *
gnm_expr_entry_get_scg(GnmExprEntry * gee)2357 gnm_expr_entry_get_scg (GnmExprEntry *gee)
2358 {
2359 	return gee->scg;
2360 }
2361 
2362 /**
2363  * gnm_expr_entry_load_from_text:
2364  * @gee:
2365  * @txt:
2366  */
2367 void
gnm_expr_entry_load_from_text(GnmExprEntry * gee,char const * txt)2368 gnm_expr_entry_load_from_text (GnmExprEntry *gee, char const *txt)
2369 {
2370 	g_return_if_fail (GNM_EXPR_ENTRY_IS (gee));
2371 	/* We have nowhere to store the text while frozen. */
2372 	g_return_if_fail (gee->freeze_count == 0);
2373 
2374 	gee_rangesel_reset (gee);
2375 
2376 	if (gee_debug)
2377 		g_printerr ("Setting entry text: [%s]\n", txt);
2378 
2379 	gtk_entry_set_text (gee->entry, txt);
2380 	gee_delete_tooltip (gee, TRUE);
2381 }
2382 
2383 /**
2384  * gnm_expr_entry_load_from_dep:
2385  * @gee: a #GnmExprEntry
2386  * @dep: A dependent
2387  *
2388  * Sets the text of the entry, and removes saved information about earlier
2389  * range selections.
2390  **/
2391 void
gnm_expr_entry_load_from_dep(GnmExprEntry * gee,GnmDependent const * dep)2392 gnm_expr_entry_load_from_dep (GnmExprEntry *gee, GnmDependent const *dep)
2393 {
2394 	g_return_if_fail (GNM_EXPR_ENTRY_IS (gee));
2395 	g_return_if_fail (dep != NULL);
2396 	/* We have nowhere to store the text while frozen. */
2397 	g_return_if_fail (gee->freeze_count == 0);
2398 
2399 	if (dep->texpr != NULL) {
2400 		char *text;
2401 		GnmParsePos pp;
2402 
2403 		parse_pos_init_dep (&pp, dep);
2404 		text = gnm_expr_top_as_string (dep->texpr, &pp,
2405 					       gee_convs (gee));
2406 
2407 		gee_rangesel_reset (gee);
2408 		gtk_entry_set_text (gee->entry, text);
2409 		gee->rangesel.text_end = strlen (text);
2410 
2411 		g_free (text);
2412 		gee_delete_tooltip (gee, TRUE);
2413 	} else
2414 		gnm_expr_entry_load_from_text (gee, "");
2415 }
2416 
2417 /**
2418  * gnm_expr_entry_load_from_expr:
2419  * @gee: a #GnmExprEntry
2420  * @texpr: An expression
2421  * @pp: The parse position
2422  *
2423  * Sets the text of the entry, and removes saved information about earlier
2424  * range selections.
2425  **/
2426 void
gnm_expr_entry_load_from_expr(GnmExprEntry * gee,GnmExprTop const * texpr,GnmParsePos const * pp)2427 gnm_expr_entry_load_from_expr (GnmExprEntry *gee,
2428 			       GnmExprTop const *texpr,
2429 			       GnmParsePos const *pp)
2430 {
2431 	g_return_if_fail (GNM_EXPR_ENTRY_IS (gee));
2432 	/* We have nowhere to store the text while frozen. */
2433 	g_return_if_fail (gee->freeze_count == 0);
2434 
2435 	if (texpr != NULL) {
2436 		char *text = gnm_expr_top_as_string
2437 			(texpr, pp, gee_convs (gee));
2438 		gee_rangesel_reset (gee);
2439 		if (gee_debug)
2440 			g_printerr ("Setting entry text: [%s]\n", text);
2441 		gtk_entry_set_text (gee->entry, text);
2442 		gee->rangesel.text_end = strlen (text);
2443 		g_free (text);
2444 		gee_delete_tooltip (gee, TRUE);
2445 	} else
2446 		gnm_expr_entry_load_from_text (gee, "");
2447 }
2448 
2449 /**
2450  * gnm_expr_entry_load_from_range:
2451  * @gee: a #GnmExprEntry
2452  * @r:          a #GnmRange
2453  * @sheet:      a #sheet
2454  *
2455  * Returns: true if displayed range is different from input range. false
2456  * otherwise.
2457  *
2458  * Sets the range selection and displays it in the entry text. If the widget
2459  * already contains a range selection, the new text replaces the
2460  * old. Otherwise, it is inserted at @pos.
2461  **/
2462 gboolean
gnm_expr_entry_load_from_range(GnmExprEntry * gee,Sheet * sheet,GnmRange const * r)2463 gnm_expr_entry_load_from_range (GnmExprEntry *gee,
2464 				Sheet *sheet, GnmRange const *r)
2465 {
2466 	Rangesel *rs;
2467 	GnmRangeRef ref;
2468 	gboolean needs_change = FALSE;
2469 
2470 	g_return_val_if_fail (GNM_EXPR_ENTRY_IS (gee), FALSE);
2471 	g_return_val_if_fail (IS_SHEET (sheet), FALSE);
2472 	g_return_val_if_fail (r != NULL, FALSE);
2473 
2474 	needs_change =  (gee->flags & GNM_EE_FULL_COL &&
2475 			 !range_is_full (r, sheet, TRUE)) ||
2476 			(gee->flags & GNM_EE_FULL_ROW &&
2477 			 !range_is_full (r, sheet, FALSE));
2478 
2479 	rs = &gee->rangesel;
2480 	ref = rs->ref;
2481 	ref.a.col = r->start.col; if (rs->ref.a.col_relative) ref.a.col -= gee->pp.eval.col;
2482 	ref.b.col = r->end.col;   if (rs->ref.b.col_relative) ref.b.col -= gee->pp.eval.col;
2483 	ref.a.row = r->start.row; if (rs->ref.a.row_relative) ref.a.row -= gee->pp.eval.row;
2484 	ref.b.row = r->end.row;   if (rs->ref.b.row_relative) ref.b.row -= gee->pp.eval.row;
2485 
2486 	if (rs->ref.a.col == ref.a.col &&
2487 	    rs->ref.b.col == ref.b.col &&
2488 	    rs->ref.a.row == ref.a.row &&
2489 	    rs->ref.b.row == ref.b.row &&
2490 	    rs->ref.a.sheet == sheet &&
2491 	    (rs->ref.b.sheet == NULL || rs->ref.b.sheet == sheet))
2492 		return needs_change; /* FIXME ??? */
2493 
2494 	rs->ref.a.col = ref.a.col;
2495 	rs->ref.b.col = ref.b.col;
2496 	rs->ref.a.row = ref.a.row;
2497 	rs->ref.b.row = ref.b.row;
2498 	rs->ref.a.sheet =
2499 		(sheet != gee->sheet || !(gee->flags & GNM_EE_SHEET_OPTIONAL)) ? sheet : NULL;
2500 	rs->ref.b.sheet = NULL;
2501 
2502 	if (gee->freeze_count == 0)
2503 		gee_rangesel_update_text (gee);
2504 
2505 	rs->is_valid = TRUE; /* we just loaded it up */
2506 
2507 	return needs_change;
2508 }
2509 
2510 /**
2511  * gnm_expr_entry_get_rangesel:
2512  * @gee: a #GnmExprEntry
2513  * @r: (out): address to receive #GnmRange
2514  * @sheet: (out) (optional) (transfer none): address to receive #sheet
2515  *
2516  * Get the range selection. GnmRange is copied, Sheet is not. If sheet
2517  * argument is NULL, the corresponding value is not returned.
2518  *
2519  * Returns: %TRUE if the returned range is indeed valid.
2520  * The resulting range is normalized.
2521  **/
2522 gboolean
gnm_expr_entry_get_rangesel(GnmExprEntry const * gee,GnmRange * r,Sheet ** sheet)2523 gnm_expr_entry_get_rangesel (GnmExprEntry const *gee,
2524 			     GnmRange *r, Sheet **sheet)
2525 {
2526 	GnmRangeRef ref;
2527 	Rangesel const *rs = &gee->rangesel;
2528 
2529 	g_return_val_if_fail (GNM_EXPR_ENTRY_IS (gee), FALSE);
2530 
2531 	gee_prepare_range (gee, &ref);
2532 
2533 	ref.a.sheet = eval_sheet (rs->ref.a.sheet, gee->sheet);
2534 	ref.b.sheet = eval_sheet (rs->ref.b.sheet, ref.a.sheet);
2535 
2536 	/* TODO : does not handle 3d, neither does this interface
2537 	 * should probably scrap the interface in favour of returning a
2538 	 * rangeref.
2539 	 */
2540 	if (sheet)
2541 		*sheet = ref.a.sheet;
2542 
2543 	if (r != NULL) {
2544 		gnm_cellpos_init_cellref (&r->start, &ref.a, &gee->pp.eval, ref.a.sheet);
2545 		gnm_cellpos_init_cellref (&r->end, &ref.b, &gee->pp.eval, ref.b.sheet);
2546 		range_normalize (r);
2547 	}
2548 
2549 	return rs->is_valid;
2550 }
2551 
2552 /**
2553  * gnm_expr_entry_can_rangesel:
2554  * @gee:   a #GnmExprEntry
2555  *
2556  * Returns: %TRUE if a range selection is meaningful at current position.
2557  **/
2558 gboolean
gnm_expr_entry_can_rangesel(GnmExprEntry * gee)2559 gnm_expr_entry_can_rangesel (GnmExprEntry *gee)
2560 {
2561 	char const *text;
2562 
2563 	g_return_val_if_fail (GNM_EXPR_ENTRY_IS (gee), FALSE);
2564 
2565 	if (wbc_gtk_get_guru (gee->wbcg) != NULL &&
2566 	    gee == gee->wbcg->edit_line.entry)
2567 		return FALSE;
2568 
2569 	text = gtk_entry_get_text (gee->entry);
2570 
2571 	/* We need to be editing an expression */
2572 	if (wbc_gtk_get_guru (gee->wbcg) == NULL &&
2573 	    gnm_expr_char_start_p (text) == NULL)
2574 		return FALSE;
2575 
2576 	return (gnm_expr_entry_find_range (gee));
2577 }
2578 
2579 /**
2580  * gnm_expr_entry_parse:
2581  * @gee: the entry
2582  * @pp: a parse position
2583  * @start_sel: start range selection when things change.
2584  * @flags:
2585  *
2586  * Attempts to parse the content of the entry line honouring
2587  * the flags.
2588  */
2589 GnmExprTop const *
gnm_expr_entry_parse(GnmExprEntry * gee,GnmParsePos const * pp,GnmParseError * perr,gboolean start_sel,GnmExprParseFlags flags)2590 gnm_expr_entry_parse (GnmExprEntry *gee, GnmParsePos const *pp,
2591 		      GnmParseError *perr, gboolean start_sel,
2592 		      GnmExprParseFlags flags)
2593 {
2594 	char const *text;
2595 	char *str;
2596 	GnmExprTop const *texpr;
2597 
2598 	g_return_val_if_fail (GNM_EXPR_ENTRY_IS (gee), NULL);
2599 
2600 	text = gtk_entry_get_text (gee->entry);
2601 
2602 	if (text == NULL || text[0] == '\0')
2603 		return NULL;
2604 
2605 	if (gee_debug)
2606 		g_printerr ("Parsing %s\n", text);
2607 
2608 	if ((gee->flags & GNM_EE_FORCE_ABS_REF))
2609 		flags |= GNM_EXPR_PARSE_FORCE_ABSOLUTE_REFERENCES;
2610 	else if ((gee->flags & GNM_EE_FORCE_REL_REF))
2611 		flags |= GNM_EXPR_PARSE_FORCE_RELATIVE_REFERENCES;
2612 	if (!(gee->flags & GNM_EE_SHEET_OPTIONAL))
2613 		flags |= GNM_EXPR_PARSE_FORCE_EXPLICIT_SHEET_REFERENCES;
2614 
2615 	/* First try parsing as a value.  */
2616 	{
2617 		GnmValue *v = get_matched_value (gee);
2618 		if (v) {
2619 			GODateConventions const *date_conv =
2620 				sheet_date_conv (gee->sheet);
2621 			GnmExprTop const *texpr = gnm_expr_top_new_constant (v);
2622 			char *str = format_value (gee->constant_format, v, -1, date_conv);
2623 			if (gee_debug)
2624 				g_printerr ("Setting entry text: [%s]\n", str);
2625 			gtk_entry_set_text (gee->entry, str);
2626 			g_free (str);
2627 			return texpr;
2628 		}
2629 	}
2630 
2631 	/* Failing that, try as an expression.  */
2632 	texpr = gnm_expr_parse_str (text, pp, flags,
2633 				    gee_convs (gee), perr);
2634 
2635 	if (texpr == NULL)
2636 		return NULL;
2637 
2638 	if (gee->flags & GNM_EE_SINGLE_RANGE) {
2639 		GnmValue *range = gnm_expr_top_get_range (texpr);
2640 		if (range == NULL) {
2641 			if (perr != NULL) {
2642 				perr->err = g_error_new (1, PERR_SINGLE_RANGE,
2643 					_("Expecting a single range"));
2644 				perr->begin_char = perr->end_char   = 0;
2645 			}
2646 			gnm_expr_top_unref (texpr);
2647 			return NULL;
2648 		}
2649 		value_release (range);
2650 	}
2651 
2652 	/* Reset the entry in case something changed */
2653 	str = (flags & GNM_EXPR_PARSE_PERMIT_MULTIPLE_EXPRESSIONS)
2654 		? gnm_expr_top_multiple_as_string (texpr, pp, gee_convs (gee))
2655 		: gnm_expr_top_as_string (texpr, pp, gee_convs (gee));
2656 
2657 	if (strcmp (str, text)) {
2658 		SheetControlGUI *scg = wbcg_cur_scg (gee->wbcg);
2659 		Rangesel const *rs = &gee->rangesel;
2660 		if (gee == wbcg_get_entry_logical (gee->wbcg) &&
2661 		    start_sel && sc_sheet (GNM_SHEET_CONTROL (scg)) == rs->ref.a.sheet) {
2662 			scg_rangesel_bound (scg,
2663 				rs->ref.a.col, rs->ref.a.row,
2664 				rs->ref.b.col, rs->ref.b.row);
2665 		} else {
2666 			if (gee_debug)
2667 				g_printerr ("Setting entry text: [%s]\n", str);
2668 			gtk_entry_set_text (gee->entry, str);
2669 		}
2670 	}
2671 	g_free (str);
2672 
2673 	return texpr;
2674 }
2675 
2676 /**
2677  * gnm_expr_entry_get_text:
2678  * @gee:
2679  *
2680  * A small convenience routine.  Think long and hard before using this.
2681  * There are lots of parse routines that serve the common case.
2682  *
2683  * Returns: The content of the entry.  Caller should not modify the result.
2684  **/
2685 char const *
gnm_expr_entry_get_text(GnmExprEntry const * gee)2686 gnm_expr_entry_get_text	(GnmExprEntry const *gee)
2687 {
2688 	g_return_val_if_fail (GNM_EXPR_ENTRY_IS (gee), NULL);
2689 	return gtk_entry_get_text (gee->entry);
2690 }
2691 
2692 /**
2693  * gnm_expr_entry_parse_as_value:
2694  * @gee: GnmExprEntry
2695  * @sheet: the sheet where the cell range is evaluated.
2696  *
2697  * Returns a (GnmValue *) of type VALUE_CELLRANGE if the @range was
2698  *	successfully parsed or %NULL on failure.
2699  */
2700 GnmValue *
gnm_expr_entry_parse_as_value(GnmExprEntry * gee,Sheet * sheet)2701 gnm_expr_entry_parse_as_value (GnmExprEntry *gee, Sheet *sheet)
2702 {
2703 	GnmParsePos pp;
2704 	GnmExprParseFlags flags = GNM_EXPR_PARSE_UNKNOWN_NAMES_ARE_STRINGS;
2705 	GnmValue *v;
2706 	const char *txt;
2707 
2708 	g_return_val_if_fail (GNM_EXPR_ENTRY_IS (gee), NULL);
2709 
2710 	if ((gee->flags & GNM_EE_FORCE_ABS_REF))
2711 		flags |= GNM_EXPR_PARSE_FORCE_ABSOLUTE_REFERENCES;
2712 	else if ((gee->flags & GNM_EE_FORCE_REL_REF))
2713 		flags |= GNM_EXPR_PARSE_FORCE_RELATIVE_REFERENCES;
2714 	if (!(gee->flags & GNM_EE_SHEET_OPTIONAL))
2715 		flags |= GNM_EXPR_PARSE_FORCE_EXPLICIT_SHEET_REFERENCES;
2716 
2717 	txt = gtk_entry_get_text (gnm_expr_entry_get_entry (gee));
2718 
2719 	parse_pos_init_sheet (&pp, sheet);
2720 	v = value_new_cellrange_parsepos_str (&pp, txt, flags);
2721 
2722 	if (!v && (gee->flags & GNM_EE_CONSTANT_ALLOWED)) {
2723 		GODateConventions const *date_conv =
2724 			sheet ? sheet_date_conv (sheet) : NULL;
2725 		v = format_match_number (txt, NULL, date_conv);
2726 	}
2727 
2728 	return v;
2729 }
2730 
2731 /**
2732  * gnm_expr_entry_parse_as_list:
2733  * @gee: GnmExprEntry
2734  * @sheet: the sheet where the cell range is evaluated. This really only needed if
2735  *         the range given does not include a sheet specification.
2736  *
2737  * Returns: (element-type GnmValue) (transfer full): a (GSList *)
2738  *	or NULL on failure.
2739  */
2740 GSList *
gnm_expr_entry_parse_as_list(GnmExprEntry * gee,Sheet * sheet)2741 gnm_expr_entry_parse_as_list (GnmExprEntry *gee, Sheet *sheet)
2742 {
2743 	g_return_val_if_fail (GNM_EXPR_ENTRY_IS (gee), NULL);
2744 
2745 	return global_range_list_parse (sheet,
2746 		gtk_entry_get_text (gnm_expr_entry_get_entry (gee)));
2747 }
2748 
2749 /**
2750  * gnm_expr_entry_get_entry:
2751  * @gee: #GnmExprEntry
2752  *
2753  * Returns: (transfer none): the associated #GtkEntry.
2754  **/
2755 GtkEntry *
gnm_expr_entry_get_entry(GnmExprEntry * gee)2756 gnm_expr_entry_get_entry (GnmExprEntry *gee)
2757 {
2758 	g_return_val_if_fail (GNM_EXPR_ENTRY_IS (gee), NULL);
2759 
2760 	return gee->entry;
2761 }
2762 
2763 gboolean
gnm_expr_entry_is_cell_ref(GnmExprEntry * gee,Sheet * sheet,gboolean allow_multiple_cell)2764 gnm_expr_entry_is_cell_ref (GnmExprEntry *gee, Sheet *sheet,
2765 			    gboolean allow_multiple_cell)
2766 {
2767         GnmValue *val;
2768 	gboolean res;
2769 
2770 	g_return_val_if_fail (GNM_EXPR_ENTRY_IS (gee), FALSE);
2771 
2772 	val = gnm_expr_entry_parse_as_value (gee, sheet);
2773         if (val == NULL)
2774 		return FALSE;
2775 
2776 	res = ((VALUE_IS_CELLRANGE (val)) &&
2777 	       (allow_multiple_cell ||
2778 		((val->v_range.cell.a.col == val->v_range.cell.b.col) &&
2779 		 (val->v_range.cell.a.row == val->v_range.cell.b.row))));
2780 	value_release (val);
2781 	return res;
2782 
2783 }
2784 
2785 gboolean
gnm_expr_entry_is_blank(GnmExprEntry * gee)2786 gnm_expr_entry_is_blank	(GnmExprEntry *gee)
2787 {
2788 	GtkEntry *entry;
2789 	char const *text;
2790 
2791 	g_return_val_if_fail (GNM_EXPR_ENTRY_IS (gee), FALSE);
2792 
2793 	entry = gnm_expr_entry_get_entry (gee);
2794 
2795 	text = gtk_entry_get_text (entry);
2796 	if (text == NULL)
2797 		return TRUE;
2798 
2799 	while (*text) {
2800 		if (!g_unichar_isspace (g_utf8_get_char (text)))
2801 			return FALSE;
2802 		text = g_utf8_next_char (text);
2803 	}
2804 
2805 	return TRUE;
2806 }
2807 
2808 char *
gnm_expr_entry_global_range_name(GnmExprEntry * gee,Sheet * sheet)2809 gnm_expr_entry_global_range_name (GnmExprEntry *gee, Sheet *sheet)
2810 {
2811 	GnmValue *val;
2812 	char *text = NULL;
2813 
2814 	g_return_val_if_fail (GNM_EXPR_ENTRY_IS (gee), NULL);
2815 
2816 	val = gnm_expr_entry_parse_as_value (gee, sheet);
2817 	if (val != NULL) {
2818 		if (VALUE_IS_CELLRANGE (val))
2819 			text = value_get_as_string (val);
2820 		value_release (val);
2821 	}
2822 
2823 	return text;
2824 }
2825 
2826 void
gnm_expr_entry_grab_focus(GnmExprEntry * gee,gboolean select_all)2827 gnm_expr_entry_grab_focus (GnmExprEntry *gee, gboolean select_all)
2828 {
2829 	g_return_if_fail (GNM_EXPR_ENTRY_IS (gee));
2830 
2831 	gtk_widget_grab_focus (GTK_WIDGET (gee->entry));
2832 	if (select_all) {
2833 		gtk_editable_set_position (GTK_EDITABLE (gee->entry), -1);
2834 		gtk_editable_select_region (GTK_EDITABLE (gee->entry), 0, -1);
2835 	}
2836 }
2837 
2838 gboolean
gnm_expr_entry_editing_canceled(GnmExprEntry * gee)2839 gnm_expr_entry_editing_canceled (GnmExprEntry *gee)
2840 {
2841 	g_return_val_if_fail (GNM_EXPR_ENTRY_IS (gee), TRUE);
2842 
2843 	return gee->editing_canceled;
2844 }
2845 
2846 /*****************************************************************************/
2847 
2848 void
gnm_expr_entry_disable_tips(GnmExprEntry * gee)2849 gnm_expr_entry_disable_tips (GnmExprEntry *gee)
2850 {
2851 	g_return_if_fail (gee != NULL);
2852 	gee_delete_tooltip (gee, TRUE);
2853 	gee->tooltip.enabled = FALSE;
2854 }
2855 
2856 void
gnm_expr_entry_enable_tips(GnmExprEntry * gee)2857 gnm_expr_entry_enable_tips (GnmExprEntry *gee)
2858 {
2859 	g_return_if_fail (gee != NULL);
2860 	gee->tooltip.enabled = TRUE;
2861 }
2862 
2863