1 /*
2 * wbc-gtk-edit.c: Keeps track of the cell editing process.
3 *
4 * Copyright (C) 2006-2007 Jody Goldberg (jody@gnome.org)
5 * Copyright (C) 2000-2005 Miguel de Icaza (miguel@novell.com)
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) version 3.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
20 * USA
21 */
22 #include <gnumeric-config.h>
23 #include <gnumeric.h>
24
25 #include <gnm-pane-impl.h>
26 #include <wbc-gtk-impl.h>
27 #include <workbook-view.h>
28 #include <workbook-priv.h>
29 #include <application.h>
30 #include <clipboard.h>
31 #include <complete-sheet.h>
32 #include <commands.h>
33 #include <gnumeric-conf.h>
34 #include <mstyle.h>
35 #include <style-color.h>
36 #include <sheet-control-gui-priv.h>
37 #include <sheet-style.h>
38 #include <sheet-view.h>
39 #include <sheet.h>
40 #include <cell.h>
41 #include <expr.h>
42 #include <gnm-format.h>
43 #include <number-match.h>
44 #include <parse-util.h>
45 #include <ranges.h>
46 #include <selection.h>
47 #include <validation.h>
48 #include <value.h>
49 #include <widgets/gnm-expr-entry.h>
50 #include <gui-util.h>
51 #include <command-context.h>
52
53 #include <goffice/goffice.h>
54 #include <glib/gi18n-lib.h>
55 #include <string.h>
56
57 #define GNM_RESPONSE_REMOVE -1000
58
59 /*
60 * Shuts down the auto completion engine
61 */
62 void
wbcg_auto_complete_destroy(WBCGtk * wbcg)63 wbcg_auto_complete_destroy (WBCGtk *wbcg)
64 {
65 g_free (wbcg->auto_complete_text);
66 wbcg->auto_complete_text = NULL;
67
68 if (wbcg->edit_line.signal_changed) {
69 g_signal_handler_disconnect (wbcg_get_entry (wbcg),
70 wbcg->edit_line.signal_changed);
71 wbcg->edit_line.signal_changed = 0;
72 }
73
74 if (wbcg->auto_complete != NULL) {
75 g_object_unref (wbcg->auto_complete);
76 wbcg->auto_complete = NULL;
77 }
78
79 wbcg->auto_completing = FALSE;
80 }
81
82 /**
83 * wbcg_edit_finish:
84 * @wbcg: #WBCGtk
85 * @result: what should we do with the content
86 * @showed_dialog: (optional) (out): indicates whether a dialog was displayed.
87 *
88 * Returns: %TRUE if editing completed successfully, or we were no editing.
89 **/
90 gboolean
wbcg_edit_finish(WBCGtk * wbcg,WBCEditResult result,gboolean * showed_dialog)91 wbcg_edit_finish (WBCGtk *wbcg, WBCEditResult result,
92 gboolean *showed_dialog)
93 {
94 Sheet *sheet;
95 SheetView *sv;
96 WorkbookControl *wbc;
97 WorkbookView *wbv;
98
99 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), FALSE);
100
101 wbc = GNM_WBC (wbcg);
102 wbv = wb_control_view (wbc);
103
104 wbcg_focus_cur_scg (wbcg);
105
106 gnm_expr_entry_close_tips (wbcg_get_entry_logical (wbcg));
107
108 if (showed_dialog != NULL)
109 *showed_dialog = FALSE;
110
111 /* Remove the range selection cursor if it exists */
112 if (NULL != wbcg->rangesel)
113 scg_rangesel_stop (wbcg->rangesel, result == WBC_EDIT_REJECT);
114
115 if (!wbcg_is_editing (wbcg)) {
116 /* We may have a guru up even if we are not editing. remove it.
117 * Do NOT remove until later it if we are editing, it is possible
118 * that we may want to continue editing.
119 */
120 if (wbcg->edit_line.guru != NULL) {
121 GtkWidget *w = wbcg->edit_line.guru;
122 wbc_gtk_detach_guru (wbcg);
123 gtk_widget_destroy (w);
124 }
125
126 return TRUE;
127 }
128
129 g_return_val_if_fail (IS_SHEET (wbcg->editing_sheet), TRUE);
130
131 sheet = wbcg->editing_sheet;
132 sv = sheet_get_view (sheet, wbv);
133
134 /* Save the results before changing focus */
135 if (result != WBC_EDIT_REJECT) {
136 ValidationStatus valid = GNM_VALIDATION_STATUS_VALID;
137 char *free_txt = NULL;
138 char const *txt;
139 GnmStyle const *mstyle;
140 char const *expr_txt = NULL;
141 GOFormat const *fmt;
142 GnmValue *value;
143 GOUndo *u = NULL;
144 GSList *selection = selection_get_ranges (sv, FALSE);
145 GnmParsePos pp;
146 GnmExprTop const *texpr = NULL;
147
148 parse_pos_init_editpos (&pp, sv);
149
150 /* Array only works on single range. */
151 if (result == WBC_EDIT_ACCEPT_ARRAY &&
152 (selection == NULL || selection->next != NULL))
153 result = WBC_EDIT_ACCEPT_RANGE;
154
155 /******* Check whether we would split a range ********/
156
157 switch (result) {
158 case (WBC_EDIT_ACCEPT_RANGE):
159 case (WBC_EDIT_ACCEPT_ARRAY): {
160 if (sheet_ranges_split_region (sheet, selection,
161 GO_CMD_CONTEXT (wbc), _("Set Text"))) {
162 range_fragment_free (selection);
163 if (showed_dialog != NULL)
164 *showed_dialog = TRUE;
165 return FALSE;
166 }
167
168 if (result == WBC_EDIT_ACCEPT_ARRAY &&
169 sheet_range_contains_merges_or_arrays
170 (sheet, selection->data,
171 GO_CMD_CONTEXT (wbc), _("Set Text"),
172 TRUE, FALSE)) {
173 range_fragment_free (selection);
174 if (showed_dialog != NULL)
175 *showed_dialog = TRUE;
176 return FALSE;
177 }
178
179 break;
180 }
181 case (WBC_EDIT_ACCEPT_WO_AC):
182 case (WBC_EDIT_ACCEPT): {
183 GnmCell const *cell = sheet_cell_get
184 (sheet, sv->edit_pos.col, sv->edit_pos.row);
185 if (gnm_cell_is_nonsingleton_array (cell)) {
186 gnm_cmd_context_error_splits_array (GO_CMD_CONTEXT (wbc),
187 _("Set Text"), NULL);
188 if (showed_dialog != NULL)
189 *showed_dialog = TRUE;
190 range_fragment_free (selection);
191 return FALSE;
192 }
193 break;
194 }
195 case (WBC_EDIT_REJECT):
196 default:
197 /* We should not be able to get here! */
198 break;
199 }
200
201
202 /******* Check whether the range is locked ********/
203
204 switch (result) {
205 case (WBC_EDIT_ACCEPT_RANGE):
206 case (WBC_EDIT_ACCEPT_ARRAY): {
207 if (cmd_selection_is_locked_effective (sheet, selection, wbc,
208 _("Set Text"))) {
209 range_fragment_free (selection);
210 if (showed_dialog != NULL)
211 *showed_dialog = TRUE;
212 return FALSE;
213 }
214 break;
215 }
216 case (WBC_EDIT_ACCEPT_WO_AC):
217 case (WBC_EDIT_ACCEPT): {
218 GnmRange r;
219 r.end = r.start = pp.eval;
220
221 if (cmd_cell_range_is_locked_effective (sheet, &r, wbc,
222 _("Set Text"))) {
223 range_fragment_free (selection);
224 if (showed_dialog != NULL)
225 *showed_dialog = TRUE;
226 return FALSE;
227 }
228 break;
229 }
230 case (WBC_EDIT_REJECT):
231 default:
232 /* We should not be able to get here! */
233 break;
234 }
235 /*****************************************************/
236
237 txt = wbcg_edit_get_display_text (wbcg);
238 mstyle = sheet_style_get (sheet, sv->edit_pos.col, sv->edit_pos.row);
239 fmt = gnm_cell_get_format (sheet_cell_fetch (sheet, sv->edit_pos.col,
240 sv->edit_pos.row));
241
242 value = format_match (txt, fmt, sheet_date_conv (sheet));
243 if (value == NULL)
244 expr_txt = gnm_expr_char_start_p (txt);
245 else
246 value_release (value);
247
248 /* NOTE : do not modify gnm_expr_char_start_p to exclude "-"
249 * it _can_ start an expression, which is required for rangesel
250 * it just isn't an expression. */
251 if (expr_txt != NULL && *expr_txt != '\0' && strcmp (expr_txt, "-")) {
252 GnmExprTop const *texpr_test = NULL;
253 GnmParseError perr;
254
255 parse_error_init (&perr);
256 texpr_test = gnm_expr_parse_str (expr_txt,
257 &pp, GNM_EXPR_PARSE_DEFAULT, NULL, &perr);
258 /* Try adding a single extra closing paren to see if it helps */
259 if (texpr_test == NULL && perr.err != NULL &&
260 perr.err->code == PERR_MISSING_PAREN_CLOSE) {
261 GnmParseError tmp_err;
262 char *tmp = g_strconcat (txt, ")", NULL);
263 parse_error_init (&tmp_err);
264 texpr_test = gnm_expr_parse_str (gnm_expr_char_start_p (tmp),
265 &pp, GNM_EXPR_PARSE_DEFAULT,
266 NULL, &tmp_err);
267 parse_error_free (&tmp_err);
268
269 if (texpr_test != NULL) {
270 txt = free_txt = tmp;
271 expr_txt = gnm_expr_char_start_p (txt);
272 } else
273 g_free (tmp);
274 }
275
276 if (texpr_test == NULL && perr.err != NULL) {
277 ValidationStatus reedit;
278
279 /* set focus _before_ selection. gtk2 seems to
280 * screw with selection in gtk_entry_grab_focus
281 * (no longer required now that we clear
282 * gtk-entry-select-on-focus) */
283 gtk_window_set_focus (wbcg_toplevel (wbcg),
284 (GtkWidget *) wbcg_get_entry (wbcg));
285
286 if (perr.begin_char != 0 || perr.end_char != 0) {
287 int offset = expr_txt - txt;
288 gtk_editable_select_region (GTK_EDITABLE (wbcg_get_entry (wbcg)),
289 offset + perr.begin_char,
290 offset + perr.end_char);
291 } else
292 gtk_editable_set_position (
293 GTK_EDITABLE (wbcg_get_entry (wbcg)), -1);
294
295 reedit = wb_control_validation_msg (GNM_WBC (wbcg),
296 GNM_VALIDATION_STYLE_PARSE_ERROR, NULL,
297 perr.err->message);
298 if (showed_dialog != NULL)
299 *showed_dialog = TRUE;
300
301 parse_error_free (&perr);
302 if (reedit == GNM_VALIDATION_STATUS_INVALID_EDIT) {
303 range_fragment_free (selection);
304 return FALSE;
305 }
306 /* restore focus to sheet , or we'll leave edit
307 * mode only to jump right back in the new
308 * cell because it looks like someone just
309 * focused on the edit line (eg hit F2) */
310 wbcg_focus_cur_scg (wbcg);
311 }
312 if (texpr_test != NULL)
313 gnm_expr_top_unref (texpr_test);
314 }
315
316 /* We only enter an array formula if the text is a formula */
317 if (result == WBC_EDIT_ACCEPT_ARRAY && !expr_txt)
318 result = WBC_EDIT_ACCEPT_RANGE;
319
320 if (result == WBC_EDIT_ACCEPT_ARRAY) {
321 GnmParsePos pp_array;
322 GnmRange *r = selection->data;
323
324 parse_pos_init (&pp_array, sheet->workbook, sheet, r->start.col, r->start.row);
325
326 if ((texpr = gnm_expr_parse_str
327 (expr_txt, &pp_array, GNM_EXPR_PARSE_DEFAULT,
328 sheet_get_conventions (sheet), NULL)) == NULL)
329 result = WBC_EDIT_ACCEPT_RANGE;
330 }
331
332 /* We need to save the information that we will temporarily overwrite */
333 /* We then assign the information. No need to worry about formatting */
334 /* Finally we can check the validation! */
335
336 switch (result) {
337 case (WBC_EDIT_ACCEPT_RANGE): {
338 GSList *l;
339
340 for (l = selection; l != NULL; l = l->next) {
341 GnmRange *r = l->data;
342 u = go_undo_combine (u, clipboard_copy_range_undo (sheet, r));
343 }
344 for (l = selection; l != NULL; l = l->next) {
345 GnmRange *r = l->data;
346 /* We do this separately since there may be overlap between ranges */
347 sheet_range_set_text (&pp, r, txt);
348 valid = gnm_validation_eval_range (wbc, sheet, &sv->edit_pos, r,
349 showed_dialog);
350 if (valid != GNM_VALIDATION_STATUS_VALID)
351 break;
352 }
353 break;
354 }
355 case (WBC_EDIT_ACCEPT_ARRAY): {
356 GnmRange *r = selection->data;
357
358 u = go_undo_combine (u, clipboard_copy_range_undo (sheet, r));
359 if (texpr) {
360 gnm_expr_top_ref (texpr);
361 gnm_cell_set_array_formula (sheet,
362 r->start.col, r->start.row,
363 r->end.col, r->end.row,
364 texpr);
365 sheet_region_queue_recalc (sheet, r);
366 }
367 valid = gnm_validation_eval_range (wbc, sheet, &sv->edit_pos, r,
368 showed_dialog);
369 break;
370 }
371 case (WBC_EDIT_ACCEPT_WO_AC):
372 case (WBC_EDIT_ACCEPT): {
373 GnmRange r;
374 GnmCell *cell;
375
376 range_init_cellpos (&r, &sv->edit_pos);
377 u = clipboard_copy_range_undo (sheet, &r);
378
379 cell = sheet_cell_fetch (sheet,
380 sv->edit_pos.col,
381 sv->edit_pos.row);
382 sheet_cell_set_text (cell, txt, wbcg->edit_line.markup);
383 valid = gnm_validation_eval (wbc, mstyle, sheet, &sv->edit_pos, showed_dialog);
384 break;
385 }
386 case (WBC_EDIT_REJECT):
387 default:
388 /* We should not be able to get here! */
389 break;
390 }
391
392 range_fragment_free (selection);
393
394 /* We need to rebuild the original info first. */
395
396 go_undo_undo (u);
397 g_object_unref (u);
398
399 /* Now we can respond to our validation information */
400
401 if (valid != GNM_VALIDATION_STATUS_VALID) {
402 result = WBC_EDIT_REJECT;
403 if (valid == GNM_VALIDATION_STATUS_INVALID_EDIT) {
404 gtk_window_set_focus (wbcg_toplevel (wbcg),
405 (GtkWidget *) wbcg_get_entry (wbcg));
406 g_free (free_txt);
407 if (texpr != NULL)
408 gnm_expr_top_unref (texpr);
409 return FALSE;
410 }
411 } else {
412 if (result == WBC_EDIT_ACCEPT_ARRAY) {
413 cmd_area_set_array_expr (wbc, sv, texpr);
414
415 } else {
416 PangoAttrList *res_markup = wbcg->edit_line.markup
417 ? pango_attr_list_copy (wbcg->edit_line.markup)
418 : NULL;
419 if (result == WBC_EDIT_ACCEPT)
420 cmd_set_text (wbc, sheet, &sv->edit_pos, txt, res_markup, TRUE);
421 else if (result == WBC_EDIT_ACCEPT_WO_AC)
422 cmd_set_text (wbc, sheet, &sv->edit_pos, txt, res_markup, FALSE);
423 else
424 cmd_area_set_text (wbc, sv, txt, res_markup);
425 if (res_markup)
426 pango_attr_list_unref (res_markup);
427 }
428 }
429 if (texpr != NULL)
430 gnm_expr_top_unref (texpr);
431 g_free (free_txt);
432 } else {
433 if (sv == wb_control_cur_sheet_view (wbc)) {
434 /* Redraw the cell contents in case there was a span */
435 GnmRange tmp; tmp.start = tmp.end = sv->edit_pos;
436 sheet_range_bounding_box (sv->sheet, &tmp);
437 gnm_sheet_view_redraw_range (wb_control_cur_sheet_view (wbc), &tmp);
438 }
439
440 /* Reload the entry widget with the original contents */
441 wb_view_edit_line_set (wbv, wbc);
442 }
443
444 /* Stop editing */
445 wbcg->editing = FALSE;
446 wbcg->editing_sheet = NULL;
447 wbcg->editing_cell = NULL;
448
449 if (wbcg->edit_line.guru != NULL) {
450 GtkWidget *w = wbcg->edit_line.guru;
451 wbc_gtk_detach_guru (wbcg);
452 gtk_widget_destroy (w);
453 }
454
455 if (wbcg->edit_line.signal_insert) {
456 g_signal_handler_disconnect (wbcg_get_entry (wbcg),
457 wbcg->edit_line.signal_insert);
458 wbcg->edit_line.signal_insert = 0;
459 }
460 if (wbcg->edit_line.signal_delete) {
461 g_signal_handler_disconnect (wbcg_get_entry (wbcg),
462 wbcg->edit_line.signal_delete);
463 wbcg->edit_line.signal_delete = 0;
464 }
465 if (wbcg->edit_line.signal_cursor_pos) {
466 g_signal_handler_disconnect (wbcg_get_entry (wbcg),
467 wbcg->edit_line.signal_cursor_pos);
468 wbcg->edit_line.signal_cursor_pos = 0;
469 }
470 if (wbcg->edit_line.signal_selection_bound) {
471 g_signal_handler_disconnect (wbcg_get_entry (wbcg),
472 wbcg->edit_line.signal_selection_bound);
473 wbcg->edit_line.signal_selection_bound = 0;
474 }
475
476 if (wbcg->edit_line.cell_attrs != NULL) {
477 pango_attr_list_unref (wbcg->edit_line.cell_attrs);
478 wbcg->edit_line.cell_attrs = NULL;
479 }
480
481 if (wbcg->edit_line.markup) {
482 pango_attr_list_unref (wbcg->edit_line.markup);
483 wbcg->edit_line.markup = NULL;
484 }
485
486 if (wbcg->edit_line.full_content != NULL) {
487 pango_attr_list_unref (wbcg->edit_line.full_content);
488 wbcg->edit_line.full_content = NULL;
489 }
490
491 if (wbcg->edit_line.cur_fmt) {
492 pango_attr_list_unref (wbcg->edit_line.cur_fmt);
493 wbcg->edit_line.cur_fmt = NULL;
494 }
495
496 /* set pos to 0, to ensure that if we start editing by clicking on the
497 * editline at the last position, we'll get the right style feedback */
498 gtk_editable_set_position ((GtkEditable *) wbcg_get_entry (wbcg), 0);
499
500 wb_control_update_action_sensitivity (wbc);
501
502 if (!sheet->workbook->during_destruction) {
503 /* restore focus to original sheet in case things were being selected
504 * on a different page. Do no go through the view, rangesel is
505 * specific to the control. */
506 wb_control_sheet_focus (wbc, sheet);
507 /* Only the edit sheet has an edit cursor */
508 scg_edit_stop (wbcg_cur_scg (wbcg));
509 }
510 wbcg_auto_complete_destroy (wbcg);
511 wb_control_style_feedback (wbc, NULL); /* in case markup messed with things */
512
513 return TRUE;
514 }
515
516 static void
workbook_edit_complete_notify(char const * text,void * closure)517 workbook_edit_complete_notify (char const *text, void *closure)
518 {
519 WBCGtk *wbcg = closure;
520
521 g_free (wbcg->auto_complete_text);
522 wbcg->auto_complete_text = g_strdup (text);
523
524 scg_reload_item_edits (wbcg_cur_scg (wbcg));
525 }
526
527 static void
cb_entry_changed(G_GNUC_UNUSED GtkEntry * entry,WBCGtk * wbcg)528 cb_entry_changed (G_GNUC_UNUSED GtkEntry *entry, WBCGtk *wbcg)
529 {
530 char const *text;
531 int text_len;
532 WorkbookView *wbv = wb_control_view (GNM_WBC (wbcg));
533
534 text = gtk_entry_get_text (wbcg_get_entry (wbcg));
535 text_len = strlen (text);
536
537 if (text_len > wbcg->auto_max_size)
538 wbcg->auto_max_size = text_len;
539
540 if (wbv->do_auto_completion && wbcg->auto_completing)
541 gnm_complete_start (GNM_COMPLETE (wbcg->auto_complete), text);
542 }
543
544 static gboolean
cb_set_attr_list_len(PangoAttribute * a,gpointer len_bytes)545 cb_set_attr_list_len (PangoAttribute *a, gpointer len_bytes)
546 {
547 a->start_index = 0;
548 a->end_index = GPOINTER_TO_INT (len_bytes);
549 return FALSE;
550 }
551
552 static void
cb_entry_insert_text(GtkEditable * editable,gchar const * text,gint len_bytes,gint * pos_in_chars,WBCGtk * wbcg)553 cb_entry_insert_text (GtkEditable *editable,
554 gchar const *text,
555 gint len_bytes,
556 gint *pos_in_chars,
557 WBCGtk *wbcg)
558 {
559 char const *str = gtk_entry_get_text (GTK_ENTRY (editable));
560 int pos_in_bytes = g_utf8_offset_to_pointer (str, *pos_in_chars) - str;
561
562 if (wbcg->auto_completing &&
563 len_bytes != 0 &&
564 (!g_unichar_isalpha (g_utf8_get_char (text)) ||
565 *pos_in_chars != gtk_entry_get_text_length (GTK_ENTRY (editable)))) {
566 wbcg->auto_completing = FALSE;
567 }
568
569 if (wbcg->edit_line.full_content) {
570 (void)pango_attr_list_filter (wbcg->edit_line.cur_fmt,
571 cb_set_attr_list_len,
572 GINT_TO_POINTER (len_bytes));
573
574 go_pango_attr_list_open_hole (wbcg->edit_line.full_content,
575 pos_in_bytes, len_bytes);
576 pango_attr_list_splice (wbcg->edit_line.full_content,
577 wbcg->edit_line.cur_fmt,
578 pos_in_bytes, 0);
579
580 go_pango_attr_list_open_hole (wbcg->edit_line.markup,
581 pos_in_bytes, len_bytes);
582 pango_attr_list_splice (wbcg->edit_line.markup,
583 wbcg->edit_line.cur_fmt,
584 pos_in_bytes, 0);
585 }
586 }
587
588 static GSList *
attrs_at_byte(PangoAttrList * alist,gint bytepos)589 attrs_at_byte (PangoAttrList *alist, gint bytepos)
590 {
591 PangoAttrIterator *iter = pango_attr_list_get_iterator (alist);
592 GSList *attrs = NULL;
593
594 do {
595 gint start, end;
596 pango_attr_iterator_range (iter, &start, &end);
597 if (start <= bytepos && bytepos < end) {
598 attrs = pango_attr_iterator_get_attrs (iter);
599 break;
600 }
601 } while (pango_attr_iterator_next (iter));
602 pango_attr_iterator_destroy (iter);
603
604 return attrs;
605 }
606
607 /* Find the markup to be used for new characters. */
608 static void
set_cur_fmt(WBCGtk * wbcg,int target_pos_in_bytes)609 set_cur_fmt (WBCGtk *wbcg, int target_pos_in_bytes)
610 {
611 PangoAttrList *new_list = pango_attr_list_new ();
612 GSList *ptr, *attrs = attrs_at_byte (wbcg->edit_line.markup, target_pos_in_bytes);
613
614 for (ptr = attrs; ptr != NULL ; ptr = ptr->next) {
615 PangoAttribute *attr = ptr->data;
616 attr->start_index = 0;
617 attr->end_index = INT_MAX;
618 pango_attr_list_change (new_list, attr);
619 }
620 g_slist_free (attrs);
621 if (wbcg->edit_line.cur_fmt)
622 pango_attr_list_unref (wbcg->edit_line.cur_fmt);
623 wbcg->edit_line.cur_fmt = new_list;
624 }
625
626 static void
cb_entry_cursor_pos(WBCGtk * wbcg)627 cb_entry_cursor_pos (WBCGtk *wbcg)
628 {
629 gint start, end, target_pos_in_chars, target_pos_in_bytes;
630 GtkEditable *entry = GTK_EDITABLE (wbcg_get_entry (wbcg));
631 char const *str = gtk_entry_get_text (GTK_ENTRY (entry));
632 int edit_pos = gtk_editable_get_position (entry);
633
634 if (str[0] == 0)
635 return;
636
637 if (edit_pos != gtk_entry_get_text_length (GTK_ENTRY (entry))) {
638 /* The cursor is no longer at the end. */
639 wbcg->auto_completing = FALSE;
640 }
641
642 if (!wbcg->edit_line.full_content)
643 return;
644
645 /* 1) Use first selected character if there is a selection
646 * 2) Use the character just before the edit pos if it exists
647 * 3) Use the first character */
648 if (gtk_editable_get_selection_bounds (entry, &start, &end))
649 target_pos_in_chars = start;
650 else {
651 target_pos_in_chars = edit_pos;
652 if (target_pos_in_chars > 0)
653 target_pos_in_chars--;
654 }
655
656 target_pos_in_bytes = g_utf8_offset_to_pointer (str, target_pos_in_chars) - str;
657
658 /* Make bold/italic/etc buttons show the right thing. */
659 {
660 GnmStyle *style = gnm_style_new ();
661 GSList *ptr, *attrs = attrs_at_byte (wbcg->edit_line.full_content, target_pos_in_bytes);
662 for (ptr = attrs; ptr != NULL ; ptr = ptr->next) {
663 PangoAttribute *attr = ptr->data;
664 gnm_style_set_from_pango_attribute (style, attr);
665 pango_attribute_destroy (attr);
666 }
667 wb_control_style_feedback (GNM_WBC (wbcg), style);
668 gnm_style_unref (style);
669 g_slist_free (attrs);
670 }
671
672 set_cur_fmt (wbcg, target_pos_in_bytes);
673 }
674
675 static void
cb_entry_delete_text(GtkEditable * editable,gint start_pos,gint end_pos,WBCGtk * wbcg)676 cb_entry_delete_text (GtkEditable *editable,
677 gint start_pos,
678 gint end_pos,
679 WBCGtk *wbcg)
680 {
681 if (wbcg->auto_completing)
682 wbcg_auto_complete_destroy (wbcg);
683
684 if (wbcg->edit_line.full_content) {
685 char const *str = gtk_entry_get_text (GTK_ENTRY (editable));
686 guint start_pos_in_bytes =
687 g_utf8_offset_to_pointer (str, start_pos) - str;
688 guint end_pos_in_bytes =
689 g_utf8_offset_to_pointer (str, end_pos) - str;
690 guint len_bytes = end_pos_in_bytes - start_pos_in_bytes;
691
692 go_pango_attr_list_erase (wbcg->edit_line.full_content,
693 start_pos_in_bytes,
694 len_bytes);
695 go_pango_attr_list_erase (wbcg->edit_line.markup,
696 start_pos_in_bytes,
697 len_bytes);
698 cb_entry_cursor_pos (wbcg);
699 }
700 }
701
702 static void
wbcg_edit_init_markup(WBCGtk * wbcg,PangoAttrList * markup)703 wbcg_edit_init_markup (WBCGtk *wbcg, PangoAttrList *markup)
704 {
705 SheetView const *sv;
706 char const *text;
707 GnmStyle const *style;
708
709 g_return_if_fail (wbcg->edit_line.full_content == NULL);
710
711 wbcg->edit_line.markup = markup;
712
713 sv = wb_control_cur_sheet_view (GNM_WBC (wbcg));
714 style = sheet_style_get (sv->sheet, sv->edit_pos.col, sv->edit_pos.row);
715 wbcg->edit_line.cell_attrs = gnm_style_generate_attrs_full (style);
716
717 wbcg->edit_line.full_content = pango_attr_list_copy (wbcg->edit_line.cell_attrs);
718 pango_attr_list_splice (wbcg->edit_line.full_content, markup, 0, 0);
719
720 text = gtk_entry_get_text (wbcg_get_entry (wbcg));
721 set_cur_fmt (wbcg, strlen (text) - 1);
722 }
723
724 struct cb_set_or_unset {
725 const PangoAttribute *attr;
726 gboolean set_in_ref;
727 };
728
729 static gboolean
cb_set_or_unset(PangoAttribute * attr,gpointer _data)730 cb_set_or_unset (PangoAttribute *attr, gpointer _data)
731 {
732 struct cb_set_or_unset *data = _data;
733 if (pango_attribute_equal (attr, data->attr))
734 data->set_in_ref = TRUE;
735 return FALSE;
736 }
737
738 static void
set_or_unset(PangoAttrList * dst,const PangoAttribute * attr,PangoAttrList * ref)739 set_or_unset (PangoAttrList *dst, const PangoAttribute *attr,
740 PangoAttrList *ref)
741 {
742 struct cb_set_or_unset data;
743
744 data.attr = attr;
745 data.set_in_ref = FALSE;
746 (void)pango_attr_list_filter (ref, cb_set_or_unset, &data);
747
748 if (data.set_in_ref)
749 go_pango_attr_list_unset (dst,
750 attr->start_index, attr->end_index,
751 attr->klass->type);
752 else
753 pango_attr_list_change (dst, pango_attribute_copy (attr));
754 }
755
756 /**
757 * wbcg_edit_add_markup:
758 * @wbcg: #WBCGtk
759 * @attr: #PangoAttribute
760 *
761 * Absorbs the ref to @attr.
762 **/
763 void
wbcg_edit_add_markup(WBCGtk * wbcg,PangoAttribute * attr)764 wbcg_edit_add_markup (WBCGtk *wbcg, PangoAttribute *attr)
765 {
766 GObject *entry = (GObject *)wbcg_get_entry (wbcg);
767 if (wbcg->edit_line.full_content == NULL)
768 wbcg_edit_init_markup (wbcg, pango_attr_list_new ());
769
770 if (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry),
771 &attr->start_index, &attr->end_index)) {
772 char const *str = gtk_entry_get_text (GTK_ENTRY (entry));
773
774 attr->start_index = g_utf8_offset_to_pointer (str, attr->start_index) - str;
775 attr->end_index = g_utf8_offset_to_pointer (str, attr->end_index) - str;
776 set_or_unset (wbcg->edit_line.full_content, attr,
777 wbcg->edit_line.cell_attrs);
778 set_or_unset (wbcg->edit_line.markup, attr,
779 wbcg->edit_line.cell_attrs);
780 }
781
782 /* the format to use when inserting text, we will resize it later */
783 attr->start_index = 0;
784 attr->end_index = INT_MAX;
785 set_or_unset (wbcg->edit_line.cur_fmt, attr,
786 wbcg->edit_line.cell_attrs);
787 pango_attribute_destroy (attr);
788 wbc_gtk_markup_changer (wbcg);
789 }
790
791 /**
792 * wbcg_edit_get_markup:
793 * @wbcg: #WBCGtk
794 *
795 * Returns: a potentially NULL PangoAttrList of the current markup while
796 * editing. The list belongs to @wbcg and should not be freed.
797 **/
798 PangoAttrList *
wbcg_edit_get_markup(WBCGtk * wbcg,gboolean full)799 wbcg_edit_get_markup (WBCGtk *wbcg, gboolean full)
800 {
801 return full ? wbcg->edit_line.full_content : wbcg->edit_line.markup;
802 }
803
804
805 static void
cb_warn_toggled(GtkToggleButton * button,gboolean * b)806 cb_warn_toggled (GtkToggleButton *button, gboolean *b)
807 {
808 *b = gtk_toggle_button_get_active (button);
809 }
810
811 /**
812 * wbcg_edit_start:
813 * @wbcg: The workbook to be edited.
814 * @blankp: If true, erase current cell contents first. If false, leave the
815 * contents alone.
816 * @cursorp: If true, create an editing cursor in the current sheet. (If
817 * false, the text will be editing in the edit box above the sheet,
818 * but this is not handled by this function.)
819 *
820 * Initiate editing of a cell in the sheet. Note that we have two modes of
821 * editing:
822 * 1) in-cell editing when you just start typing, and
823 * 2) above sheet editing when you hit F2.
824 *
825 * Returns: %TRUE if we did indeed start editing. Returns %FALSE if the
826 * cell-to-be-edited was locked.
827 */
828 gboolean
wbcg_edit_start(WBCGtk * wbcg,gboolean blankp,gboolean cursorp)829 wbcg_edit_start (WBCGtk *wbcg,
830 gboolean blankp, gboolean cursorp)
831 {
832 /* We could save this, but the situation is rare, if confusing. */
833 static gboolean warn_on_text_format = TRUE;
834 SheetView *sv;
835 SheetControlGUI *scg;
836 GnmCell *cell;
837 char *text = NULL;
838 int col, row;
839 WorkbookView *wbv;
840 int cursor_pos = -1;
841
842 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), FALSE);
843
844 if (wbcg_is_editing (wbcg))
845 return TRUE;
846
847 /* Avoid recursion, and do not begin editing if a guru is up */
848 if (wbcg->inside_editing || wbc_gtk_get_guru (wbcg) != NULL)
849 return TRUE;
850 wbcg->inside_editing = TRUE;
851
852 wbv = wb_control_view (GNM_WBC (wbcg));
853 sv = wb_control_cur_sheet_view (GNM_WBC (wbcg));
854 scg = wbcg_cur_scg (wbcg);
855
856 col = sv->edit_pos.col;
857 row = sv->edit_pos.row;
858
859 /* don't edit a locked cell */
860 /* TODO : extend this to disable edits that cannot succeed
861 * like editing a single cell of an array. I think we have enough
862 * information if we look at the selection.
863 */
864 if (wb_view_is_protected (wbv, TRUE) &&
865 gnm_style_get_contents_locked (sheet_style_get (sv->sheet, col, row))) {
866 char *pos = g_strdup_printf ( _("%s!%s is locked"),
867 sv->sheet->name_quoted, cell_coord_name (col, row));
868 go_cmd_context_error_invalid (GO_CMD_CONTEXT (wbcg), pos,
869 wb_view_is_protected (wbv, FALSE)
870 ? _("Unprotect the workbook to enable editing.")
871 : _("Unprotect the sheet to enable editing."));
872 wbcg->inside_editing = FALSE;
873 g_free (pos);
874 return FALSE;
875 }
876
877 cell = sheet_cell_get (sv->sheet, col, row);
878 if (cell &&
879 warn_on_text_format &&
880 go_format_is_text (gnm_cell_get_format (cell)) &&
881 (gnm_cell_has_expr (cell) || !VALUE_IS_STRING (cell->value))) {
882 gint res; /* Using GtkResponseType would yield a warning on the switch */
883 GtkWidget *check;
884 GtkWidget *align;
885
886 GtkWidget *d = gnm_message_dialog_create
887 (wbcg_toplevel (wbcg),
888 GTK_DIALOG_DESTROY_WITH_PARENT,
889 GTK_MESSAGE_WARNING,
890 _("You are about to edit a cell with \"text\" format."),
891 _("The cell does not currently contain text, though, so if "
892 "you go on editing then the contents will be turned into "
893 "text."));
894 gtk_dialog_add_button (GTK_DIALOG (d), GTK_STOCK_EDIT, GTK_RESPONSE_OK);
895 go_gtk_dialog_add_button
896 (GTK_DIALOG (d), _("Remove format"), GTK_STOCK_REMOVE,
897 GNM_RESPONSE_REMOVE);
898 gtk_dialog_add_button (GTK_DIALOG (d), GNM_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
899 gtk_dialog_set_default_response (GTK_DIALOG (d), GTK_RESPONSE_CANCEL);
900
901 check = gtk_check_button_new_with_label (_("Show this dialog next time."));
902 g_signal_connect (check, "toggled", G_CALLBACK (cb_warn_toggled), &warn_on_text_format);
903 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), TRUE);
904 align = gtk_alignment_new (0.5, 0.5, 0, 0);
905 gtk_container_add (GTK_CONTAINER (align), check);
906 gtk_widget_show_all (align);
907 gtk_box_pack_end (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (d))), align, TRUE, TRUE, 0);
908 res = go_gtk_dialog_run (GTK_DIALOG (d), wbcg_toplevel (wbcg));
909
910 switch (res) {
911 case GNM_RESPONSE_REMOVE: {
912 GnmStyle *style = gnm_style_new ();
913 gnm_style_set_format (style, go_format_general ());
914 if (!cmd_selection_format (GNM_WBC (wbcg),
915 style, NULL, NULL))
916 break;
917 /* Fall through. */
918 }
919 default:
920 case GTK_RESPONSE_CANCEL:
921 wbcg->inside_editing = FALSE;
922 return FALSE;
923 case GTK_RESPONSE_OK:
924 break;
925 }
926 }
927
928 gnm_app_clipboard_unant ();
929
930 if (blankp)
931 gtk_entry_set_text (wbcg_get_entry (wbcg), "");
932 else if (cell != NULL) {
933 gboolean quoted = FALSE;
934
935 text = gnm_cell_get_text_for_editing (cell, "ed, &cursor_pos);
936
937 if (text)
938 gtk_entry_set_text (wbcg_get_entry (wbcg), text);
939
940 if (cell->value != NULL) {
941 GOFormat const *fmt = VALUE_FMT (cell->value);
942 if (fmt != NULL && go_format_is_markup (fmt)) {
943 PangoAttrList *markup =
944 pango_attr_list_copy ((PangoAttrList *)go_format_get_markup (fmt));
945 if (quoted)
946 go_pango_attr_list_open_hole (markup, 0, 1);
947 wbcg_edit_init_markup (wbcg, markup);
948 }
949 }
950 }
951
952 gnm_expr_entry_set_scg (wbcg->edit_line.entry, scg);
953 gnm_expr_entry_set_flags (wbcg->edit_line.entry,
954 GNM_EE_SHEET_OPTIONAL | GNM_EE_FORMULA_ONLY,
955 GNM_EE_SINGLE_RANGE | GNM_EE_SHEET_OPTIONAL | GNM_EE_FORMULA_ONLY | GNM_EE_FORCE_REL_REF | GNM_EE_FORCE_ABS_REF);
956 scg_edit_start (scg);
957
958 /* Redraw the cell contents in case there was a span */
959 sheet_redraw_region (sv->sheet, col, row, col, row);
960
961 if (cursorp && /* autocompletion code will not work in the edit line */
962 wbv->do_auto_completion &&
963 (text == NULL || g_unichar_isalpha (g_utf8_get_char (text)))) {
964 wbcg->auto_complete = gnm_complete_sheet_new (
965 sv->sheet, col, row,
966 workbook_edit_complete_notify, wbcg);
967 wbcg->auto_completing = TRUE;
968 wbcg->auto_max_size = 0;
969 } else
970 wbcg->auto_complete = NULL;
971
972 /* Give the focus to the edit line */
973 if (!cursorp)
974 gtk_window_set_focus (wbcg_toplevel (wbcg),
975 (GtkWidget *) wbcg_get_entry (wbcg));
976
977 wbcg->editing = TRUE;
978 wbcg->editing_sheet = sv->sheet;
979 wbcg->editing_cell = cell;
980
981 /* If this assert fails, it means editing was not shut down
982 * properly before
983 */
984 g_return_val_if_fail (wbcg->edit_line.signal_changed == 0, TRUE);
985 wbcg->edit_line.signal_changed = g_signal_connect (
986 G_OBJECT (wbcg_get_entry (wbcg)),
987 "changed",
988 G_CALLBACK (cb_entry_changed), wbcg);
989 wbcg->edit_line.signal_insert = g_signal_connect (
990 G_OBJECT (wbcg_get_entry (wbcg)),
991 "insert-text",
992 G_CALLBACK (cb_entry_insert_text), wbcg);
993 wbcg->edit_line.signal_delete = g_signal_connect (
994 G_OBJECT (wbcg_get_entry (wbcg)),
995 "delete-text",
996 G_CALLBACK (cb_entry_delete_text), wbcg);
997 wbcg->edit_line.signal_cursor_pos = g_signal_connect_swapped (
998 G_OBJECT (wbcg_get_entry (wbcg)),
999 "notify::cursor-position",
1000 G_CALLBACK (cb_entry_cursor_pos), wbcg);
1001 wbcg->edit_line.signal_selection_bound = g_signal_connect_swapped (
1002 G_OBJECT (wbcg_get_entry (wbcg)),
1003 "notify::selection-bound",
1004 G_CALLBACK (cb_entry_cursor_pos), wbcg);
1005
1006 g_free (text);
1007 wb_control_update_action_sensitivity (GNM_WBC (wbcg));
1008
1009 wbcg->inside_editing = FALSE;
1010
1011 gtk_editable_set_position (GTK_EDITABLE (wbcg_get_entry (wbcg)), cursor_pos);
1012
1013 return TRUE;
1014 }
1015
1016 /**
1017 * wbcg_insert_object:
1018 * @wbcg: #WBCGtk *
1019 * @so: The object the needs to be placed
1020 *
1021 * Takes a newly created #SheetObject that has not yet been realized and
1022 * prepares to place it on the sheet.
1023 *
1024 * NOTE : Absorbs a reference to the object.
1025 **/
1026 void
wbcg_insert_object(WBCGtk * wbcg,SheetObject * so)1027 wbcg_insert_object (WBCGtk *wbcg, SheetObject *so)
1028 {
1029 int i, npages;
1030 SheetControlGUI *scg;
1031
1032 g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
1033 g_return_if_fail (GNM_IS_SO (so));
1034
1035 wbcg_insert_object_clear (wbcg);
1036
1037 npages = wbcg_get_n_scg (wbcg);
1038 for (i = 0; i < npages; i++) {
1039 if (NULL != (scg = wbcg_get_nth_scg (wbcg, i))) {
1040 scg_object_unselect (scg, NULL);
1041 scg_cursor_visible (scg, FALSE);
1042 scg_set_display_cursor (scg);
1043 sc_unant (GNM_SHEET_CONTROL (scg));
1044 }
1045 }
1046 /* we can't set wbcg->new_object before now because if one sheet has a
1047 * selected object, the new object will be destroyed by the loop
1048 * above. See #669648. */
1049 wbcg->new_object = so;
1050 wb_control_update_action_sensitivity (GNM_WBC (wbcg));
1051 }
1052
1053 /**
1054 * wbcg_insert_object_clear:
1055 * @wbcg: #WBCGtk
1056 *
1057 * If we are preparing to insert a new object, unref the object, and restore
1058 * a normal state to the scgs that was changed in wbcg_insert_object
1059 * (e.g., visibility of cursors)
1060 **/
1061 void
wbcg_insert_object_clear(WBCGtk * wbcg)1062 wbcg_insert_object_clear (WBCGtk *wbcg)
1063 {
1064 g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
1065
1066 if (NULL != wbcg->new_object) {
1067 int i, npages;
1068 SheetControlGUI *scg;
1069
1070 g_object_unref (wbcg->new_object);
1071 wbcg->new_object = NULL;
1072
1073 npages = wbcg_get_n_scg (wbcg);
1074 for (i = 0; i < npages; i++)
1075 if (NULL != (scg = wbcg_get_nth_scg (wbcg, i)))
1076 scg_cursor_visible (scg, TRUE);
1077 }
1078 }
1079
1080
1081 /**
1082 * wbcg_get_entry:
1083 * @wbcg: #WBCGtk
1084 *
1085 * Returns: (transfer none): the #GtkEntry associated with the current GnmExprEntry
1086 **/
1087 GtkEntry *
wbcg_get_entry(WBCGtk const * wbcg)1088 wbcg_get_entry (WBCGtk const *wbcg)
1089 {
1090 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), NULL);
1091 g_return_val_if_fail (wbcg != NULL, NULL);
1092
1093 return gnm_expr_entry_get_entry (wbcg->edit_line.entry);
1094 }
1095
1096 /**
1097 * wbcg_get_entry_logical:
1098 * @wbcg: #WBCGtk
1099 *
1100 * Returns: (transfer none): the logical (allowing redirection via
1101 * wbcg_set_entry for gurus) #GnmExprEntry
1102 **/
1103 GnmExprEntry *
wbcg_get_entry_logical(WBCGtk const * wbcg)1104 wbcg_get_entry_logical (WBCGtk const *wbcg)
1105 {
1106 g_return_val_if_fail (wbcg != NULL, NULL);
1107
1108 if (wbcg->edit_line.temp_entry != NULL)
1109 return wbcg->edit_line.temp_entry;
1110
1111 return wbcg->edit_line.entry;
1112 }
1113
1114 /**
1115 * wbcg_get_entry_underlying:
1116 * @wbcg: #WBCGtk
1117 *
1118 * Returns: (transfer none): the #GtkEntry associated with the logical
1119 * #GnmExprEntry.
1120 **/
1121 GtkWidget *
wbcg_get_entry_underlying(WBCGtk const * wbcg)1122 wbcg_get_entry_underlying (WBCGtk const *wbcg)
1123 {
1124 GnmExprEntry *ee = wbcg_get_entry_logical (wbcg);
1125 GtkEntry *entry = gnm_expr_entry_get_entry (ee);
1126 return GTK_WIDGET (entry);
1127 }
1128
1129 void
wbcg_set_entry(WBCGtk * wbcg,GnmExprEntry * entry)1130 wbcg_set_entry (WBCGtk *wbcg, GnmExprEntry *entry)
1131 {
1132 g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
1133
1134 if (wbcg->edit_line.temp_entry != entry) {
1135 scg_rangesel_stop (wbcg_cur_scg (wbcg), FALSE);
1136 wbcg->edit_line.temp_entry = entry;
1137 }
1138 }
1139
1140 /**
1141 * wbcg_entry_has_logical:
1142 * @wbcg: #WBCGtk
1143 *
1144 * Returns: %TRUE if wbcg_set_entry has redirected the edit_entry.
1145 **/
1146 gboolean
wbcg_entry_has_logical(WBCGtk const * wbcg)1147 wbcg_entry_has_logical (WBCGtk const *wbcg)
1148 {
1149 return (wbcg->edit_line.temp_entry != NULL);
1150 }
1151
1152 /****************************************************************************/
1153
1154 static void
wbcg_edit_attach_guru_main(WBCGtk * wbcg,GtkWidget * guru)1155 wbcg_edit_attach_guru_main (WBCGtk *wbcg, GtkWidget *guru)
1156 {
1157 WorkbookControl *wbc = GNM_WBC (wbcg);
1158
1159 g_return_if_fail (guru != NULL);
1160 g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
1161 g_return_if_fail (wbcg->edit_line.guru == NULL);
1162
1163 /* Make sure we don't have anything anted.
1164 * this protects against two anted regions showing up
1165 */
1166 gnm_app_clipboard_unant ();
1167
1168 /* don't set end 'End' mode when a dialog comes up */
1169 wbcg_set_end_mode (wbcg, FALSE);
1170
1171 wbcg->edit_line.guru = guru;
1172 gtk_editable_set_editable (GTK_EDITABLE (wbcg_get_entry (wbcg)), FALSE);
1173 wb_control_update_action_sensitivity (wbc);
1174 wb_control_menu_state_update (wbc, MS_GURU_MENU_ITEMS);
1175
1176 g_signal_connect_object (guru, "destroy",
1177 G_CALLBACK (wbc_gtk_detach_guru), wbcg, G_CONNECT_SWAPPED);
1178 }
1179
1180 static void
cb_guru_set_focus(G_GNUC_UNUSED GtkWidget * window,GtkWidget * focus_widget,WBCGtk * wbcg)1181 cb_guru_set_focus (G_GNUC_UNUSED GtkWidget *window,
1182 GtkWidget *focus_widget, WBCGtk *wbcg)
1183 {
1184 GnmExprEntry *gee = NULL;
1185 if (focus_widget != NULL &&
1186 GNM_EXPR_ENTRY_IS (gtk_widget_get_parent (focus_widget)))
1187 gee = GNM_EXPR_ENTRY (gtk_widget_get_parent (focus_widget));
1188 wbcg_set_entry (wbcg, gee);
1189 }
1190
1191 /****************************************************************************/
1192
1193 void
wbc_gtk_attach_guru(WBCGtk * wbcg,GtkWidget * guru)1194 wbc_gtk_attach_guru (WBCGtk *wbcg, GtkWidget *guru)
1195 {
1196 g_return_if_fail (guru != NULL);
1197 g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
1198
1199 wbcg_edit_attach_guru_main (wbcg, guru);
1200 g_signal_connect_object (G_OBJECT (guru), "set-focus",
1201 G_CALLBACK (cb_guru_set_focus), wbcg, 0);
1202 }
1203
1204 void
wbc_gtk_attach_guru_with_unfocused_rs(WBCGtk * wbcg,GtkWidget * guru,GnmExprEntry * gee)1205 wbc_gtk_attach_guru_with_unfocused_rs (WBCGtk *wbcg, GtkWidget *guru,
1206 GnmExprEntry *gee)
1207 {
1208 g_return_if_fail (guru != NULL);
1209 g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
1210
1211 wbcg_edit_attach_guru_main (wbcg, guru);
1212
1213 if (gnm_conf_get_dialogs_rs_unfocused ()) {
1214 if (gee)
1215 wbcg_set_entry (wbcg, gee);
1216 } else
1217 g_signal_connect (G_OBJECT (guru), "set-focus",
1218 G_CALLBACK (cb_guru_set_focus), wbcg);
1219 }
1220
1221 void
wbc_gtk_detach_guru(WBCGtk * wbcg)1222 wbc_gtk_detach_guru (WBCGtk *wbcg)
1223 {
1224 WorkbookControl *wbc = GNM_WBC (wbcg);
1225
1226 g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
1227
1228 /* don't sit end 'End' mode when a dialog comes up */
1229 wbcg_set_end_mode (wbcg, FALSE);
1230 if (wbcg->edit_line.guru == NULL)
1231 return;
1232
1233 wbcg_set_entry (wbcg, NULL);
1234 wbcg->edit_line.guru = NULL;
1235 gtk_editable_set_editable (GTK_EDITABLE (wbcg_get_entry (wbcg)), TRUE);
1236 wb_control_update_action_sensitivity (wbc);
1237 wb_control_menu_state_update (wbc, MS_GURU_MENU_ITEMS);
1238 }
1239
1240 /**
1241 * wbc_gtk_get_guru:
1242 * @wbcg: #WBCGtk
1243 *
1244 * Returns: (transfer none): the guru attached to the workbook view.
1245 **/
1246 GtkWidget *
wbc_gtk_get_guru(WBCGtk const * wbcg)1247 wbc_gtk_get_guru (WBCGtk const *wbcg)
1248 {
1249 return wbcg->edit_line.guru;
1250 }
1251
1252 /****************************************************************************/
1253
1254 static gboolean
auto_complete_matches(WBCGtk * wbcg)1255 auto_complete_matches (WBCGtk *wbcg)
1256 {
1257 if (!wbcg->auto_completing || wbcg->auto_complete_text == NULL)
1258 return FALSE;
1259 else {
1260 GtkEntry *entry = wbcg_get_entry (wbcg);
1261 char const *text = gtk_entry_get_text (entry);
1262 size_t len = strlen (text);
1263 return strncmp (text, wbcg->auto_complete_text, len) == 0;
1264 }
1265 }
1266
1267 /*
1268 * Returns the text that must be shown by the editing entry, takes
1269 * into account the auto-completion text.
1270 */
1271 char const *
wbcg_edit_get_display_text(WBCGtk * wbcg)1272 wbcg_edit_get_display_text (WBCGtk *wbcg)
1273 {
1274 if (auto_complete_matches (wbcg))
1275 return wbcg->auto_complete_text;
1276 else
1277 return gtk_entry_get_text (wbcg_get_entry (wbcg));
1278 }
1279
1280 void
wbc_gtk_init_editline(WBCGtk * wbcg)1281 wbc_gtk_init_editline (WBCGtk *wbcg)
1282 {
1283 g_assert (GNM_IS_WBC_GTK (wbcg));
1284 g_assert (wbcg->edit_line.entry == NULL);
1285
1286 wbcg->edit_line.entry = g_object_new (GNM_EXPR_ENTRY_TYPE,
1287 "with-icon", FALSE,
1288 "wbcg", wbcg,
1289 NULL);
1290 wbcg->edit_line.temp_entry = NULL;
1291 wbcg->edit_line.guru = NULL;
1292 wbcg->edit_line.signal_changed = 0;
1293 wbcg->edit_line.full_content = NULL;
1294 wbcg->edit_line.markup = NULL;
1295 wbcg->edit_line.cur_fmt = NULL;
1296 }
1297