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