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