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