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