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, &quoted, &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