1 
2 /*
3  * wbc-gtk.c: A gtk based WorkbookControl
4  *
5  * Copyright (C) 2000-2007 Jody Goldberg (jody@gnome.org)
6  * Copyright (C) 2006-2012 Morten Welinder (terra@gnome.org)
7  *
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.
12  *
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.
17  *
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
22  */
23 #include <gnumeric-config.h>
24 #include <gnumeric.h>
25 #include <wbc-gtk-impl.h>
26 #include <workbook-view.h>
27 #include <workbook-priv.h>
28 #include <gui-util.h>
29 #include <gutils.h>
30 #include <gui-file.h>
31 #include <sheet-control-gui-priv.h>
32 #include <sheet.h>
33 #include <sheet-private.h>
34 #include <sheet-view.h>
35 #include <sheet-style.h>
36 #include <sheet-conditions.h>
37 #include <sheet-filter.h>
38 #include <commands.h>
39 #include <dependent.h>
40 #include <application.h>
41 #include <history.h>
42 #include <func.h>
43 #include <value.h>
44 #include <style-font.h>
45 #include <gnm-format.h>
46 #include <expr.h>
47 #include <style-color.h>
48 #include <style-border.h>
49 #include <gnumeric-conf.h>
50 #include <dialogs/dialogs.h>
51 #include <gui-clipboard.h>
52 #include <libgnumeric.h>
53 #include <gnm-pane-impl.h>
54 #include <graph.h>
55 #include <selection.h>
56 #include <file-autoft.h>
57 #include <ranges.h>
58 #include <tools/analysis-auto-expression.h>
59 #include <sheet-object-cell-comment.h>
60 #include <print-info.h>
61 #include <expr-name.h>
62 
63 #include <goffice/goffice.h>
64 #include <gsf/gsf-impl-utils.h>
65 #include <gsf/gsf-doc-meta-data.h>
66 #include <gdk/gdkkeysyms-compat.h>
67 #include <gnm-i18n.h>
68 #include <string.h>
69 
70 #define GET_GUI_ITEM(i_) (gpointer)(gtk_builder_get_object(wbcg->gui, (i_)))
71 
72 #define	SHEET_CONTROL_KEY "SheetControl"
73 
74 #define AUTO_EXPR_SAMPLE "Sumerage = -012345678901234"
75 
76 
77 enum {
78 	WBG_GTK_PROP_0,
79 	WBG_GTK_PROP_AUTOSAVE_PROMPT,
80 	WBG_GTK_PROP_AUTOSAVE_TIME
81 };
82 
83 enum {
84 	WBC_GTK_MARKUP_CHANGED,
85 	WBC_GTK_LAST_SIGNAL
86 };
87 
88 enum {
89 	TARGET_URI_LIST,
90 	TARGET_SHEET
91 };
92 
93 
94 static gboolean debug_tab_order;
95 static char const *uifilename = NULL;
96 static GnmActionEntry const *extra_actions = NULL;
97 static int extra_actions_nb;
98 static guint wbc_gtk_signals[WBC_GTK_LAST_SIGNAL];
99 static GObjectClass *parent_class = NULL;
100 
101 static gboolean
wbcg_ui_update_begin(WBCGtk * wbcg)102 wbcg_ui_update_begin (WBCGtk *wbcg)
103 {
104 	g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), FALSE);
105 	g_return_val_if_fail (!wbcg->updating_ui, FALSE);
106 
107 	return (wbcg->updating_ui = TRUE);
108 }
109 
110 static void
wbcg_ui_update_end(WBCGtk * wbcg)111 wbcg_ui_update_end (WBCGtk *wbcg)
112 {
113 	g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
114 	g_return_if_fail (wbcg->updating_ui);
115 
116 	wbcg->updating_ui = FALSE;
117 }
118 
119 /****************************************************************************/
120 
121 G_MODULE_EXPORT void
set_uifilename(char const * name,GnmActionEntry const * actions,int nb)122 set_uifilename (char const *name, GnmActionEntry const *actions, int nb)
123 {
124 	uifilename = name;
125 	extra_actions = actions;
126 	extra_actions_nb = nb;
127 }
128 
129 /**
130  * wbcg_find_action:
131  * @wbcg: the workbook control gui
132  * @name: name of action
133  *
134  * Returns: (transfer none): The action with the given name
135  **/
136 GtkAction *
wbcg_find_action(WBCGtk * wbcg,const char * name)137 wbcg_find_action (WBCGtk *wbcg, const char *name)
138 {
139 	GtkAction *a;
140 
141 	a = gtk_action_group_get_action (wbcg->actions, name);
142 	if (a == NULL)
143 		a = gtk_action_group_get_action (wbcg->permanent_actions, name);
144 	if (a == NULL)
145 		a = gtk_action_group_get_action (wbcg->semi_permanent_actions, name);
146 	if (a == NULL)
147 		a = gtk_action_group_get_action (wbcg->data_only_actions, name);
148 	if (a == NULL)
149 		a = gtk_action_group_get_action (wbcg->font_actions, name);
150 	if (a == NULL)
151 		a = gtk_action_group_get_action (wbcg->toolbar.actions, name);
152 
153 	return a;
154 }
155 
156 static void
wbc_gtk_set_action_sensitivity(WBCGtk * wbcg,char const * action,gboolean sensitive)157 wbc_gtk_set_action_sensitivity (WBCGtk *wbcg,
158 				char const *action, gboolean sensitive)
159 {
160 	GtkAction *a = wbcg_find_action (wbcg, action);
161 	g_object_set (G_OBJECT (a), "sensitive", sensitive, NULL);
162 }
163 
164 /* NOTE : The semantics of prefix and suffix seem contrived.  Why are we
165  * handling it at this end ?  That stuff should be done in the undo/redo code
166  **/
167 static void
wbc_gtk_set_action_label(WBCGtk * wbcg,char const * action,char const * prefix,char const * suffix,char const * new_tip)168 wbc_gtk_set_action_label (WBCGtk *wbcg,
169 			  char const *action,
170 			  char const *prefix,
171 			  char const *suffix,
172 			  char const *new_tip)
173 {
174 	GtkAction *a = wbcg_find_action (wbcg, action);
175 
176 	if (prefix != NULL) {
177 		char *text;
178 		gboolean is_suffix = (suffix != NULL);
179 
180 		text = is_suffix ? g_strdup_printf ("%s: %s", prefix, suffix) : (char *) prefix;
181 		g_object_set (G_OBJECT (a),
182 			      "label",	   text,
183 			      "sensitive", is_suffix,
184 			      NULL);
185 		if (is_suffix)
186 			g_free (text);
187 	} else
188 		g_object_set (G_OBJECT (a), "label", suffix, NULL);
189 
190 	if (new_tip != NULL)
191 		g_object_set (G_OBJECT (a), "tooltip", new_tip, NULL);
192 }
193 
194 static void
wbc_gtk_set_toggle_action_state(WBCGtk * wbcg,char const * action,gboolean state)195 wbc_gtk_set_toggle_action_state (WBCGtk *wbcg,
196 				 char const *action, gboolean state)
197 {
198 	GtkAction *a = wbcg_find_action (wbcg, action);
199 	gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (a), state);
200 }
201 
202 /****************************************************************************/
203 
204 static SheetControlGUI *
wbcg_get_scg(WBCGtk * wbcg,Sheet * sheet)205 wbcg_get_scg (WBCGtk *wbcg, Sheet *sheet)
206 {
207 	SheetControlGUI *scg;
208 	int i, npages;
209 
210 	if (sheet == NULL || wbcg->snotebook == NULL)
211 		return NULL;
212 
213 	npages = wbcg_get_n_scg (wbcg);
214 	if (npages == 0) {
215 		/*
216 		 * This can happen during construction when the clipboard is
217 		 * being cleared.  Ctrl-C Ctrl-Q.
218 		 */
219 		return NULL;
220 	}
221 
222 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
223 	g_return_val_if_fail (sheet->index_in_wb >= 0, NULL);
224 
225 	scg = wbcg_get_nth_scg (wbcg, sheet->index_in_wb);
226 	if (NULL != scg && scg_sheet (scg) == sheet)
227 		return scg;
228 
229 	/*
230 	 * index_in_wb is probably not accurate because we are in the
231 	 * middle of removing or adding a sheet.
232 	 */
233 	for (i = 0; i < npages; i++) {
234 		scg = wbcg_get_nth_scg (wbcg, i);
235 		if (NULL != scg && scg_sheet (scg) == sheet)
236 			return scg;
237 	}
238 
239 	g_warning ("Failed to find scg for sheet %s", sheet->name_quoted);
240 	return NULL;
241 }
242 
243 static SheetControlGUI *
get_scg(const GtkWidget * w)244 get_scg (const GtkWidget *w)
245 {
246 	return g_object_get_data (G_OBJECT (w), SHEET_CONTROL_KEY);
247 }
248 
249 static GSList *
get_all_scgs(WBCGtk * wbcg)250 get_all_scgs (WBCGtk *wbcg)
251 {
252 	int i, n = gtk_notebook_get_n_pages (wbcg->snotebook);
253 	GSList *l = NULL;
254 
255 	for (i = 0; i < n; i++) {
256 		GtkWidget *w = gtk_notebook_get_nth_page (wbcg->snotebook, i);
257 		SheetControlGUI *scg = get_scg (w);
258 		l = g_slist_prepend (l, scg);
259 	}
260 
261 	return g_slist_reverse (l);
262 }
263 
264 /* Autosave */
265 
266 static gboolean
cb_autosave(WBCGtk * wbcg)267 cb_autosave (WBCGtk *wbcg)
268 {
269 	WorkbookView *wb_view;
270 
271 	g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), FALSE);
272 
273 	wb_view = wb_control_view (GNM_WBC (wbcg));
274 
275 	if (wb_view == NULL)
276 		return FALSE;
277 
278 	if (wbcg->autosave_time > 0 &&
279 	    go_doc_is_dirty (wb_view_get_doc (wb_view))) {
280 		if (wbcg->autosave_prompt && !dialog_autosave_prompt (wbcg))
281 			return TRUE;
282 		gui_file_save (wbcg, wb_view);
283 	}
284 	return TRUE;
285 }
286 
287 /**
288  * wbcg_rangesel_possible:
289  * @wbcg: the workbook control gui
290  *
291  * Returns true if the cursor keys should be used to select
292  * a cell range (if the cursor is in a spot in the expression
293  * where it makes sense to have a cell reference), false if not.
294  **/
295 gboolean
wbcg_rangesel_possible(WBCGtk const * wbcg)296 wbcg_rangesel_possible (WBCGtk const *wbcg)
297 {
298 	g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), FALSE);
299 
300 	/* Already range selecting */
301 	if (wbcg->rangesel != NULL)
302 		return TRUE;
303 
304 	/* Rangesel requires that we be editing somthing */
305 	if (!wbcg_is_editing (wbcg) && !wbcg_entry_has_logical (wbcg))
306 		return FALSE;
307 
308 	return gnm_expr_entry_can_rangesel (wbcg_get_entry_logical (wbcg));
309 }
310 
311 gboolean
wbcg_is_editing(WBCGtk const * wbcg)312 wbcg_is_editing (WBCGtk const *wbcg)
313 {
314 	g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), FALSE);
315 	return wbcg->editing;
316 }
317 
318 static void
wbcg_autosave_cancel(WBCGtk * wbcg)319 wbcg_autosave_cancel (WBCGtk *wbcg)
320 {
321 	if (wbcg->autosave_timer != 0) {
322 		g_source_remove (wbcg->autosave_timer);
323 		wbcg->autosave_timer = 0;
324 	}
325 }
326 
327 static void
wbcg_autosave_activate(WBCGtk * wbcg)328 wbcg_autosave_activate (WBCGtk *wbcg)
329 {
330 	wbcg_autosave_cancel (wbcg);
331 
332 	if (wbcg->autosave_time > 0) {
333 		int secs = MIN (wbcg->autosave_time, G_MAXINT / 1000);
334 		wbcg->autosave_timer =
335 			g_timeout_add (secs * 1000,
336 				       (GSourceFunc) cb_autosave,
337 				       wbcg);
338 	}
339 }
340 
341 static void
wbcg_set_autosave_time(WBCGtk * wbcg,int secs)342 wbcg_set_autosave_time (WBCGtk *wbcg, int secs)
343 {
344 	if (secs == wbcg->autosave_time)
345 		return;
346 
347 	wbcg->autosave_time = secs;
348 	wbcg_autosave_activate (wbcg);
349 }
350 
351 /****************************************************************************/
352 
353 static void
wbcg_edit_line_set(WorkbookControl * wbc,char const * text)354 wbcg_edit_line_set (WorkbookControl *wbc, char const *text)
355 {
356 	GtkEntry *entry = wbcg_get_entry ((WBCGtk*)wbc);
357 	gtk_entry_set_text (entry, text);
358 }
359 
360 static void
wbcg_edit_selection_descr_set(WorkbookControl * wbc,char const * text)361 wbcg_edit_selection_descr_set (WorkbookControl *wbc, char const *text)
362 {
363 	WBCGtk *wbcg = (WBCGtk *)wbc;
364 	gtk_entry_set_text (GTK_ENTRY (wbcg->selection_descriptor), text);
365 }
366 
367 static void
wbcg_update_action_sensitivity(WorkbookControl * wbc)368 wbcg_update_action_sensitivity (WorkbookControl *wbc)
369 {
370 	WBCGtk *wbcg = WBC_GTK (wbc);
371 	SheetControlGUI	   *scg = wbcg_cur_scg (wbcg);
372 	gboolean edit_object = scg != NULL &&
373 		(scg->selected_objects != NULL || wbcg->new_object != NULL ||
374 		 scg_sheet (scg)->sheet_type == GNM_SHEET_OBJECT);
375 	gboolean enable_actions = TRUE;
376 	gboolean enable_edit_ok_cancel = FALSE;
377 
378 	if (edit_object || wbcg->edit_line.guru != NULL)
379 		enable_actions = FALSE;
380 	else if (wbcg_is_editing (wbcg)) {
381 		enable_actions = FALSE;
382 		enable_edit_ok_cancel = TRUE;
383 	}
384 
385 	/* These are only sensitive while editing */
386 	gtk_widget_set_sensitive (wbcg->ok_button, enable_edit_ok_cancel);
387 	gtk_widget_set_sensitive (wbcg->cancel_button, enable_edit_ok_cancel);
388 	gtk_widget_set_sensitive (wbcg->func_button, enable_actions);
389 
390 	if (wbcg->snotebook) {
391 		gboolean tab_context_menu =
392 			enable_actions ||
393 			scg_sheet (scg)->sheet_type == GNM_SHEET_OBJECT;
394 		int i, N = wbcg_get_n_scg (wbcg);
395 		for (i = 0; i < N; i++) {
396 			GtkWidget *label =
397 				gnm_notebook_get_nth_label (wbcg->bnotebook, i);
398 			g_object_set_data (G_OBJECT (label), "editable",
399 					   GINT_TO_POINTER (tab_context_menu));
400 		}
401 	}
402 
403 	g_object_set (G_OBJECT (wbcg->actions),
404 		"sensitive", enable_actions,
405 		NULL);
406 	g_object_set (G_OBJECT (wbcg->font_actions),
407 		"sensitive", enable_actions || enable_edit_ok_cancel,
408 		NULL);
409 
410 	if (scg && scg_sheet (scg)->sheet_type == GNM_SHEET_OBJECT) {
411 		g_object_set (G_OBJECT (wbcg->data_only_actions),
412 			"sensitive", FALSE,
413 			NULL);
414 		g_object_set (G_OBJECT (wbcg->semi_permanent_actions),
415 			"sensitive",TRUE,
416 			NULL);
417 		gtk_widget_set_sensitive (GTK_WIDGET (wbcg->edit_line.entry), FALSE);
418 		gtk_widget_set_sensitive (GTK_WIDGET (wbcg->selection_descriptor), FALSE);
419 	} else {
420 		g_object_set (G_OBJECT (wbcg->data_only_actions),
421 			"sensitive", TRUE,
422 			NULL);
423 		g_object_set (G_OBJECT (wbcg->semi_permanent_actions),
424 			"sensitive", enable_actions,
425 			NULL);
426 		gtk_widget_set_sensitive (GTK_WIDGET (wbcg->edit_line.entry), TRUE);
427 		gtk_widget_set_sensitive (GTK_WIDGET (wbcg->selection_descriptor), TRUE);
428 	}
429 }
430 
431 void
wbcg_insert_sheet(GtkWidget * unused,WBCGtk * wbcg)432 wbcg_insert_sheet (GtkWidget *unused, WBCGtk *wbcg)
433 {
434 	WorkbookControl *wbc = GNM_WBC (wbcg);
435 	Sheet *sheet = wb_control_cur_sheet (wbc);
436 	Workbook *wb = sheet->workbook;
437 	WorkbookSheetState *old_state = workbook_sheet_state_new (wb);
438 	/* Use same size as current sheet.  */
439 	workbook_sheet_add (wb, sheet->index_in_wb,
440 			    gnm_sheet_get_max_cols (sheet),
441 			    gnm_sheet_get_max_rows (sheet));
442 	cmd_reorganize_sheets (wbc, old_state, sheet);
443 }
444 
445 void
wbcg_append_sheet(GtkWidget * unused,WBCGtk * wbcg)446 wbcg_append_sheet (GtkWidget *unused, WBCGtk *wbcg)
447 {
448 	WorkbookControl *wbc = GNM_WBC (wbcg);
449 	Sheet *sheet = wb_control_cur_sheet (wbc);
450 	Workbook *wb = sheet->workbook;
451 	WorkbookSheetState *old_state = workbook_sheet_state_new (wb);
452 	/* Use same size as current sheet.  */
453 	workbook_sheet_add (wb, -1,
454 			    gnm_sheet_get_max_cols (sheet),
455 			    gnm_sheet_get_max_rows (sheet));
456 	cmd_reorganize_sheets (wbc, old_state, sheet);
457 }
458 
459 void
wbcg_clone_sheet(GtkWidget * unused,WBCGtk * wbcg)460 wbcg_clone_sheet (GtkWidget *unused, WBCGtk *wbcg)
461 {
462 	WorkbookControl *wbc = GNM_WBC (wbcg);
463 	Sheet *sheet = wb_control_cur_sheet (wbc);
464 	Workbook *wb = sheet->workbook;
465 	WorkbookSheetState *old_state = workbook_sheet_state_new (wb);
466 	Sheet *new_sheet = sheet_dup (sheet);
467 	workbook_sheet_attach_at_pos (wb, new_sheet, sheet->index_in_wb + 1);
468 	/* See workbook_sheet_add:  */
469 	g_signal_emit_by_name (G_OBJECT (wb), "sheet_added", 0);
470 	cmd_reorganize_sheets (wbc, old_state, sheet);
471 	g_object_unref (new_sheet);
472 }
473 
474 static void
cb_show_sheet(SheetControlGUI * scg)475 cb_show_sheet (SheetControlGUI *scg)
476 {
477 	WBCGtk *wbcg = scg->wbcg;
478 	int page_number = gtk_notebook_page_num (wbcg->snotebook,
479 						 GTK_WIDGET (scg->grid));
480 	gnm_notebook_set_current_page (wbcg->bnotebook, page_number);
481 }
482 
483 
484 
cb_sheets_manage(SheetControlGUI * scg)485 static void cb_sheets_manage (SheetControlGUI *scg) { dialog_sheet_order (scg->wbcg); }
cb_sheets_insert(SheetControlGUI * scg)486 static void cb_sheets_insert (SheetControlGUI *scg) { wbcg_insert_sheet (NULL, scg->wbcg); }
cb_sheets_add(SheetControlGUI * scg)487 static void cb_sheets_add    (SheetControlGUI *scg) { wbcg_append_sheet (NULL, scg->wbcg); }
cb_sheets_clone(SheetControlGUI * scg)488 static void cb_sheets_clone  (SheetControlGUI *scg) { wbcg_clone_sheet  (NULL, scg->wbcg); }
cb_sheets_rename(SheetControlGUI * scg)489 static void cb_sheets_rename (SheetControlGUI *scg) { dialog_sheet_rename (scg->wbcg, scg_sheet (scg)); }
cb_sheets_resize(SheetControlGUI * scg)490 static void cb_sheets_resize (SheetControlGUI *scg) { dialog_sheet_resize (scg->wbcg); }
491 
492 
493 static gint
cb_by_scg_sheet_name(gconstpointer a_,gconstpointer b_)494 cb_by_scg_sheet_name (gconstpointer a_, gconstpointer b_)
495 {
496 	const SheetControlGUI *a = a_;
497 	const SheetControlGUI *b = b_;
498 	Sheet *sa = scg_sheet (a);
499 	Sheet *sb = scg_sheet (b);
500 
501 	return g_utf8_collate (sa->name_unquoted, sb->name_unquoted);
502 }
503 
504 
505 static void
sheet_menu_label_run(SheetControlGUI * scg,GdkEvent * event)506 sheet_menu_label_run (SheetControlGUI *scg, GdkEvent *event)
507 {
508 	enum { CM_MULTIPLE = 1, CM_DATA_SHEET = 2 };
509 	struct SheetTabMenu {
510 		char const *text;
511 		void (*function) (SheetControlGUI *scg);
512 		int flags;
513 		int submenu;
514 	} const sheet_label_context_actions [] = {
515 		{ N_("Manage Sheets..."), &cb_sheets_manage,	0, 0},
516 		{ NULL, NULL, 0, 0 },
517 		{ N_("Insert"),		  &cb_sheets_insert,	0, 0 },
518 		{ N_("Append"),		  &cb_sheets_add,	0, 0 },
519 		{ N_("Duplicate"),	  &cb_sheets_clone,	0, 0 },
520 		{ N_("Remove"),		  &scg_delete_sheet_if_possible, CM_MULTIPLE, 0 },
521 		{ N_("Rename"),		  &cb_sheets_rename,	0, 0 },
522 		{ N_("Resize..."),        &cb_sheets_resize,    CM_DATA_SHEET, 0 },
523 		{ N_("Select"),           NULL,                 0, 1 },
524 		{ N_("Select (sorted)"),  NULL,                 0, 2 }
525 	};
526 
527 	unsigned int ui;
528 	GtkWidget *item, *menu = gtk_menu_new ();
529 	GtkWidget *guru = wbc_gtk_get_guru (scg_wbcg (scg));
530 	unsigned int N_visible, pass;
531 	GtkWidget *submenus[2 + 1];
532 	GSList *scgs = get_all_scgs (scg->wbcg);
533 
534 	for (pass = 1; pass <= 2; pass++) {
535 		GSList *l;
536 
537 		submenus[pass] = gtk_menu_new ();
538 		N_visible = 0;
539 		for (l = scgs; l; l = l->next) {
540 			SheetControlGUI *scg1 = l->data;
541 			Sheet *sheet = scg_sheet (scg1);
542 			if (!sheet_is_visible (sheet))
543 				continue;
544 
545 			N_visible++;
546 
547 			item = gtk_menu_item_new_with_label (sheet->name_unquoted);
548 			g_signal_connect_swapped (G_OBJECT (item), "activate",
549 						  G_CALLBACK (cb_show_sheet), scg1);
550 			gtk_menu_shell_append (GTK_MENU_SHELL (submenus[pass]), item);
551 			gtk_widget_show (item);
552 		}
553 
554 		scgs = g_slist_sort (scgs, cb_by_scg_sheet_name);
555 	}
556 	g_slist_free (scgs);
557 
558 	for (ui = 0; ui < G_N_ELEMENTS (sheet_label_context_actions); ui++) {
559 		const struct SheetTabMenu *it =
560 			sheet_label_context_actions + ui;
561 		gboolean inactive =
562 			((it->flags & CM_MULTIPLE) && N_visible <= 1) ||
563 			((it->flags & CM_DATA_SHEET) && scg_sheet (scg)->sheet_type != GNM_SHEET_DATA) ||
564 			(!it->submenu && guru != NULL);
565 
566 		item = it->text
567 			? gtk_menu_item_new_with_label (_(it->text))
568 			: gtk_separator_menu_item_new ();
569 		if (it->function)
570 			g_signal_connect_swapped (G_OBJECT (item), "activate",
571 						  G_CALLBACK (it->function), scg);
572 		if (it->submenu)
573 			gtk_menu_item_set_submenu (GTK_MENU_ITEM (item),
574 						   submenus[it->submenu]);
575 
576 		gtk_widget_set_sensitive (item, !inactive);
577 		gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
578 		gtk_widget_show (item);
579 	}
580 
581 	gnumeric_popup_menu (GTK_MENU (menu), event);
582 }
583 
584 /**
585  * cb_sheet_label_button_press:
586  *
587  * Invoked when the user has clicked on the sheet name widget.
588  * This takes care of switching to the notebook that contains the label
589  */
590 static gboolean
cb_sheet_label_button_press(GtkWidget * widget,GdkEvent * event,SheetControlGUI * scg)591 cb_sheet_label_button_press (GtkWidget *widget, GdkEvent *event,
592 			     SheetControlGUI *scg)
593 {
594 	WBCGtk *wbcg = scg->wbcg;
595 	gint page_number;
596 
597 	if (event->type != GDK_BUTTON_PRESS)
598 		return FALSE;
599 
600 	page_number = gtk_notebook_page_num (wbcg->snotebook,
601 					     GTK_WIDGET (scg->grid));
602 	gnm_notebook_set_current_page (wbcg->bnotebook, page_number);
603 
604 	if (event->button.button == 1 || NULL != wbcg->rangesel)
605 		return FALSE;
606 
607 	if (event->button.button == 3) {
608 		if ((scg_wbcg (scg))->edit_line.guru == NULL)
609 			scg_object_unselect (scg, NULL);
610 		if (g_object_get_data (G_OBJECT (widget), "editable")) {
611 			sheet_menu_label_run (scg, event);
612 			scg_take_focus (scg);
613 			return TRUE;
614 		}
615 	}
616 
617 	return FALSE;
618 }
619 
620 static void
cb_sheet_label_drag_data_get(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint info,guint time)621 cb_sheet_label_drag_data_get (GtkWidget *widget, GdkDragContext *context,
622 			      GtkSelectionData *selection_data,
623 			      guint info, guint time)
624 {
625 	SheetControlGUI *scg = get_scg (widget);
626 	g_return_if_fail (GNM_IS_SCG (scg));
627 
628 	scg_drag_data_get (scg, selection_data);
629 }
630 
631 static void
cb_sheet_label_drag_data_received(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * data,guint info,guint time,WBCGtk * wbcg)632 cb_sheet_label_drag_data_received (GtkWidget *widget, GdkDragContext *context,
633 				   gint x, gint y, GtkSelectionData *data, guint info, guint time,
634 				   WBCGtk *wbcg)
635 {
636 	GtkWidget *w_source;
637 	SheetControlGUI *scg_src, *scg_dst;
638 	Sheet *s_src, *s_dst;
639 
640 	g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
641 	g_return_if_fail (GTK_IS_WIDGET (widget));
642 
643 	w_source = gtk_drag_get_source_widget (context);
644 	if (!w_source) {
645 		g_warning ("Not yet implemented!"); /* Different process */
646 		return;
647 	}
648 
649 	scg_src = get_scg (w_source);
650 	g_return_if_fail (scg_src != NULL);
651 	s_src = scg_sheet (scg_src);
652 
653 	scg_dst = get_scg (widget);
654 	g_return_if_fail (scg_dst != NULL);
655 	s_dst = scg_sheet (scg_dst);
656 
657 	if (s_src == s_dst) {
658 		/* Nothing */
659 	} else if (s_src->workbook == s_dst->workbook) {
660 		/* Move within workbook */
661 		Workbook *wb = s_src->workbook;
662 		int p_src = s_src->index_in_wb;
663 		int p_dst = s_dst->index_in_wb;
664 		WorkbookSheetState *old_state = workbook_sheet_state_new (wb);
665 		workbook_sheet_move (s_src, p_dst - p_src);
666 		cmd_reorganize_sheets (GNM_WBC (wbcg),
667 				       old_state,
668 				       s_src);
669 	} else {
670 		g_return_if_fail (GNM_IS_SCG (gtk_selection_data_get_data (data)));
671 
672 		/* Different workbook, same process */
673 		g_warning ("Not yet implemented!");
674 	}
675 }
676 
677 /*
678  * Not currently reachable, I believe.  We use the notebook's dragging.
679  */
680 static void
cb_sheet_label_drag_begin(GtkWidget * widget,GdkDragContext * context,WBCGtk * wbcg)681 cb_sheet_label_drag_begin (GtkWidget *widget, GdkDragContext *context,
682 			   WBCGtk *wbcg)
683 {
684 	GtkWidget *arrow, *image;
685 
686 	g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
687 
688 	/* Create the arrow. */
689 	arrow = gtk_window_new (GTK_WINDOW_POPUP);
690 	gtk_window_set_screen (GTK_WINDOW (arrow),
691 			       gtk_widget_get_screen (widget));
692 	gtk_widget_realize (arrow);
693 	image = gtk_image_new_from_resource ("/org/gnumeric/gnumeric/images/sheet_move_marker.png");
694 	gtk_widget_show (image);
695 	gtk_container_add (GTK_CONTAINER (arrow), image);
696 	g_object_ref_sink (arrow);
697 	g_object_set_data (G_OBJECT (widget), "arrow", arrow);
698 }
699 
700 static void
cb_sheet_label_drag_end(GtkWidget * widget,GdkDragContext * context,WBCGtk * wbcg)701 cb_sheet_label_drag_end (GtkWidget *widget, GdkDragContext *context,
702 			 WBCGtk *wbcg)
703 {
704 	GtkWidget *arrow;
705 
706 	g_return_if_fail (GNM_IS_WBC (wbcg));
707 
708 	/* Destroy the arrow. */
709 	arrow = g_object_get_data (G_OBJECT (widget), "arrow");
710 	gtk_widget_destroy (arrow);
711 	g_object_unref (arrow);
712 	g_object_set_data (G_OBJECT (widget), "arrow", NULL);
713 }
714 
715 static void
cb_sheet_label_drag_leave(GtkWidget * widget,GdkDragContext * context,guint time,WBCGtk * wbcg)716 cb_sheet_label_drag_leave (GtkWidget *widget, GdkDragContext *context,
717 			   guint time, WBCGtk *wbcg)
718 {
719 	GtkWidget *w_source, *arrow;
720 
721 	/* Hide the arrow. */
722 	w_source = gtk_drag_get_source_widget (context);
723 	if (w_source) {
724 		arrow = g_object_get_data (G_OBJECT (w_source), "arrow");
725 		gtk_widget_hide (arrow);
726 	}
727 }
728 
729 static gboolean
cb_sheet_label_drag_motion(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,WBCGtk * wbcg)730 cb_sheet_label_drag_motion (GtkWidget *widget, GdkDragContext *context,
731 			    gint x, gint y, guint time, WBCGtk *wbcg)
732 {
733 	SheetControlGUI *scg_src, *scg_dst;
734 	GtkWidget *w_source, *arrow, *window;
735 	gint root_x, root_y, pos_x, pos_y;
736 	GtkAllocation wa, wsa;
737 
738 	g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), FALSE);
739 	g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), FALSE);
740 
741 	/* Make sure we are really hovering over another label. */
742 	w_source = gtk_drag_get_source_widget (context);
743 	if (!w_source)
744 		return FALSE;
745 
746 	arrow = g_object_get_data (G_OBJECT (w_source), "arrow");
747 
748 	scg_src = get_scg (w_source);
749 	scg_dst = get_scg (widget);
750 
751 	if (scg_src == scg_dst) {
752 		gtk_widget_hide (arrow);
753 		return (FALSE);
754 	}
755 
756 	/* Move the arrow to the correct position and show it. */
757 	window = gtk_widget_get_ancestor (widget, GTK_TYPE_WINDOW);
758 	gtk_window_get_position (GTK_WINDOW (window), &root_x, &root_y);
759 	gtk_widget_get_allocation (widget ,&wa);
760 	pos_x = root_x + wa.x;
761 	pos_y = root_y + wa.y;
762 	gtk_widget_get_allocation (w_source ,&wsa);
763 	if (wsa.x < wa.x)
764 		pos_x += wa.width;
765 	gtk_window_move (GTK_WINDOW (arrow), pos_x, pos_y);
766 	gtk_widget_show (arrow);
767 
768 	return (TRUE);
769 }
770 
771 static void
set_dir(GtkWidget * w,GtkTextDirection * dir)772 set_dir (GtkWidget *w, GtkTextDirection *dir)
773 {
774 	gtk_widget_set_direction (w, *dir);
775 	if (GTK_IS_CONTAINER (w))
776 		gtk_container_foreach (GTK_CONTAINER (w),
777 				       (GtkCallback)&set_dir,
778 				       dir);
779 }
780 
781 static void
wbcg_set_direction(SheetControlGUI const * scg)782 wbcg_set_direction (SheetControlGUI const *scg)
783 {
784 	GtkWidget *w = (GtkWidget *)scg->wbcg->snotebook;
785 	gboolean text_is_rtl = scg_sheet (scg)->text_is_rtl;
786 	GtkTextDirection dir = text_is_rtl
787 		? GTK_TEXT_DIR_RTL
788 		: GTK_TEXT_DIR_LTR;
789 
790 	if (dir != gtk_widget_get_direction (w))
791 		set_dir (w, &dir);
792 	if (scg->hs)
793 		g_object_set (scg->hs, "inverted", text_is_rtl, NULL);
794 }
795 
796 static void
cb_direction_change(G_GNUC_UNUSED Sheet * null_sheet,G_GNUC_UNUSED GParamSpec * null_pspec,SheetControlGUI const * scg)797 cb_direction_change (G_GNUC_UNUSED Sheet *null_sheet,
798 		     G_GNUC_UNUSED GParamSpec *null_pspec,
799 		     SheetControlGUI const *scg)
800 {
801 	if (scg && scg == wbcg_cur_scg (scg->wbcg))
802 		wbcg_set_direction (scg);
803 }
804 
805 static void
wbcg_update_menu_feedback(WBCGtk * wbcg,Sheet const * sheet)806 wbcg_update_menu_feedback (WBCGtk *wbcg, Sheet const *sheet)
807 {
808 	g_return_if_fail (IS_SHEET (sheet));
809 
810 	if (!wbcg_ui_update_begin (wbcg))
811 		return;
812 
813 	wbc_gtk_set_toggle_action_state (wbcg,
814 		"SheetDisplayFormulas", sheet->display_formulas);
815 	wbc_gtk_set_toggle_action_state (wbcg,
816 		"SheetHideZeros", sheet->hide_zero);
817 	wbc_gtk_set_toggle_action_state (wbcg,
818 		"SheetHideGridlines", sheet->hide_grid);
819 	wbc_gtk_set_toggle_action_state (wbcg,
820 		"SheetHideColHeader", sheet->hide_col_header);
821 	wbc_gtk_set_toggle_action_state (wbcg,
822 		"SheetHideRowHeader", sheet->hide_row_header);
823 	wbc_gtk_set_toggle_action_state (wbcg,
824 		"SheetDisplayOutlines", sheet->display_outlines);
825 	wbc_gtk_set_toggle_action_state (wbcg,
826 		"SheetOutlineBelow", sheet->outline_symbols_below);
827 	wbc_gtk_set_toggle_action_state (wbcg,
828 		"SheetOutlineRight", sheet->outline_symbols_right);
829 	wbc_gtk_set_toggle_action_state (wbcg,
830 		"SheetUseR1C1", sheet->convs->r1c1_addresses);
831 	wbcg_ui_update_end (wbcg);
832 }
833 
834 static void
cb_zoom_change(Sheet * sheet,G_GNUC_UNUSED GParamSpec * null_pspec,WBCGtk * wbcg)835 cb_zoom_change (Sheet *sheet,
836 		G_GNUC_UNUSED GParamSpec *null_pspec,
837 		WBCGtk *wbcg)
838 {
839 	if (wbcg_ui_update_begin (wbcg)) {
840 		int pct = sheet->last_zoom_factor_used * 100 + .5;
841 		char *label = g_strdup_printf ("%d%%", pct);
842 		go_action_combo_text_set_entry (wbcg->zoom_haction, label,
843 			GO_ACTION_COMBO_SEARCH_CURRENT);
844 		g_free (label);
845 		wbcg_ui_update_end (wbcg);
846 	}
847 }
848 
849 static void
cb_notebook_switch_page(G_GNUC_UNUSED GtkNotebook * notebook_,G_GNUC_UNUSED GtkWidget * page_,guint page_num,WBCGtk * wbcg)850 cb_notebook_switch_page (G_GNUC_UNUSED GtkNotebook *notebook_,
851 			 G_GNUC_UNUSED GtkWidget *page_,
852 			 guint page_num, WBCGtk *wbcg)
853 {
854 	Sheet *sheet;
855 	SheetControlGUI *new_scg;
856 
857 	g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
858 
859 	/* Ignore events during destruction */
860 	if (wbcg->snotebook == NULL)
861 		return;
862 
863 	if (debug_tab_order)
864 		g_printerr ("Notebook page switch\n");
865 
866 	/* While initializing adding the sheets will trigger page changes, but
867 	 * we do not actually want to change the focus sheet for the view
868 	 */
869 	if (wbcg->updating_ui)
870 		return;
871 
872 	/* If we are not at a subexpression boundary then finish editing */
873 	if (NULL != wbcg->rangesel)
874 		scg_rangesel_stop (wbcg->rangesel, TRUE);
875 
876 	/*
877 	 * Make snotebook follow bnotebook.  This should be the only place
878 	 * that changes pages for snotebook.
879 	 */
880 	gtk_notebook_set_current_page (wbcg->snotebook, page_num);
881 
882 	new_scg = wbcg_get_nth_scg (wbcg, page_num);
883 	wbcg_set_direction (new_scg);
884 
885 	if (wbcg_is_editing (wbcg) && wbcg_rangesel_possible (wbcg)) {
886 		/*
887 		 * When we are editing, sheet changes are not done fully.
888 		 * We revert to the original sheet later.
889 		 *
890 		 * On the other hand, when we are selecting a range for a
891 		 * dialog, we do change sheet fully.
892 		 */
893 		scg_take_focus (new_scg);
894 		return;
895 	}
896 
897 	gnm_expr_entry_set_scg (wbcg->edit_line.entry, new_scg);
898 
899 	/*
900 	 * Make absolutely sure the expression doesn't get 'lost',
901 	 * if it's invalid then prompt the user and don't switch
902 	 * the notebook page.
903 	 */
904 	if (wbcg_is_editing (wbcg)) {
905 		guint prev = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (wbcg->snotebook),
906 								 "previous_page"));
907 
908 		if (prev == page_num)
909 			return;
910 
911 		if (!wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT, NULL))
912 			gnm_notebook_set_current_page (wbcg->bnotebook,
913 						       prev);
914 		else
915 			/* Looks silly, but is really necessary */
916 			gnm_notebook_set_current_page (wbcg->bnotebook,
917 						       page_num);
918 
919 		return;
920 	}
921 
922 	g_object_set_data (G_OBJECT (wbcg->snotebook), "previous_page",
923 			   GINT_TO_POINTER (gtk_notebook_get_current_page (wbcg->snotebook)));
924 
925 	/* if we are not selecting a range for an expression update */
926 	sheet = wbcg_focus_cur_scg (wbcg);
927 	if (sheet != wbcg_cur_sheet (wbcg)) {
928 		wbcg_update_menu_feedback (wbcg, sheet);
929 		sheet_flag_status_update_range (sheet, NULL);
930 		sheet_update (sheet);
931 		wb_view_sheet_focus (wb_control_view (GNM_WBC (wbcg)), sheet);
932 		cb_zoom_change (sheet, NULL, wbcg);
933 	}
934 }
935 
936 static gboolean
cb_bnotebook_button_press(GtkWidget * widget,GdkEventButton * event)937 cb_bnotebook_button_press (GtkWidget *widget, GdkEventButton *event)
938 {
939 	if (event->type == GDK_2BUTTON_PRESS && event->button == 1) {
940 		/*
941 		 * Eat the click so cb_paned_button_press doesn't see it.
942 		 * see bug #607794.
943 		 */
944 		return TRUE;
945 	}
946 
947 	return FALSE;
948 }
949 
950 static void
cb_bnotebook_page_reordered(GtkNotebook * notebook,GtkWidget * child,int page_num,WBCGtk * wbcg)951 cb_bnotebook_page_reordered (GtkNotebook *notebook, GtkWidget *child,
952 			     int page_num, WBCGtk *wbcg)
953 {
954 	GtkNotebook *snotebook = GTK_NOTEBOOK (wbcg->snotebook);
955 	int old = gtk_notebook_get_current_page (snotebook);
956 
957 	if (wbcg->updating_ui)
958 		return;
959 
960 	if (debug_tab_order)
961 		g_printerr ("Reordered %d -> %d\n", old, page_num);
962 
963 	if (old != page_num) {
964 		WorkbookControl * wbc = GNM_WBC (wbcg);
965 		Workbook *wb = wb_control_get_workbook (wbc);
966 		Sheet *sheet = workbook_sheet_by_index (wb, old);
967 		WorkbookSheetState * old_state = workbook_sheet_state_new(wb);
968 		workbook_sheet_move (sheet, page_num - old);
969 		cmd_reorganize_sheets (wbc, old_state, sheet);
970 	}
971 }
972 
973 
974 static void
wbc_gtk_create_notebook_area(WBCGtk * wbcg)975 wbc_gtk_create_notebook_area (WBCGtk *wbcg)
976 {
977 	GtkWidget *placeholder;
978 
979 	wbcg->bnotebook = g_object_new (GNM_NOTEBOOK_TYPE,
980 					"can-focus", FALSE,
981 					NULL);
982 	g_object_ref (wbcg->bnotebook);
983 
984 	g_signal_connect_after (G_OBJECT (wbcg->bnotebook),
985 		"switch_page",
986 		G_CALLBACK (cb_notebook_switch_page), wbcg);
987 	g_signal_connect (G_OBJECT (wbcg->bnotebook),
988 			  "button-press-event",
989 			  G_CALLBACK (cb_bnotebook_button_press),
990 			  NULL);
991 	g_signal_connect (G_OBJECT (wbcg->bnotebook),
992 			  "page-reordered",
993 			  G_CALLBACK (cb_bnotebook_page_reordered),
994 			  wbcg);
995 	placeholder = gtk_paned_get_child1 (wbcg->tabs_paned);
996 	if (placeholder)
997 		gtk_widget_destroy (placeholder);
998 	gtk_paned_pack1 (wbcg->tabs_paned, GTK_WIDGET (wbcg->bnotebook), FALSE, TRUE);
999 
1000 	gtk_widget_show_all (GTK_WIDGET (wbcg->tabs_paned));
1001 }
1002 
1003 
1004 static void
wbcg_menu_state_sheet_count(WBCGtk * wbcg)1005 wbcg_menu_state_sheet_count (WBCGtk *wbcg)
1006 {
1007 	int const sheet_count = gnm_notebook_get_n_visible (wbcg->bnotebook);
1008 	/* Should we enable commands requiring multiple sheets */
1009 	gboolean const multi_sheet = (sheet_count > 1);
1010 
1011 	wbc_gtk_set_action_sensitivity (wbcg, "SheetRemove", multi_sheet);
1012 }
1013 
1014 static void
cb_sheet_direction_change(Sheet * sheet,G_GNUC_UNUSED GParamSpec * pspec,GtkAction * a)1015 cb_sheet_direction_change (Sheet *sheet,
1016 			   G_GNUC_UNUSED GParamSpec *pspec,
1017 			   GtkAction *a)
1018 {
1019 	g_object_set (a,
1020 		      "icon-name", (sheet->text_is_rtl
1021 				    ? "format-text-direction-rtl"
1022 				    : "format-text-direction-ltr"),
1023 		      NULL);
1024 }
1025 
1026 static void
cb_sheet_tab_change(Sheet * sheet,G_GNUC_UNUSED GParamSpec * pspec,GtkWidget * widget)1027 cb_sheet_tab_change (Sheet *sheet,
1028 		     G_GNUC_UNUSED GParamSpec *pspec,
1029 		     GtkWidget *widget)
1030 {
1031 	GdkRGBA cfore, cback;
1032 	SheetControlGUI *scg = get_scg (widget);
1033 
1034 	g_return_if_fail (GNM_IS_SCG (scg));
1035 
1036 	/* We're lazy and just set all relevant attributes.  */
1037 	g_object_set (widget,
1038 		      "label", sheet->name_unquoted,
1039 		      "background-color",
1040 		      (sheet->tab_color
1041 		       ? go_color_to_gdk_rgba (sheet->tab_color->go_color,
1042 					       &cback)
1043 		       : NULL),
1044 		      "text-color",
1045 		      (sheet->tab_text_color
1046 		       ? go_color_to_gdk_rgba (sheet->tab_text_color->go_color,
1047 					       &cfore)
1048 		       : NULL),
1049 		      NULL);
1050 }
1051 
1052 static void
cb_toggle_menu_item_changed(Sheet * sheet,G_GNUC_UNUSED GParamSpec * pspec,WBCGtk * wbcg)1053 cb_toggle_menu_item_changed (Sheet *sheet,
1054 			     G_GNUC_UNUSED GParamSpec *pspec,
1055 			     WBCGtk *wbcg)
1056 {
1057 	/* We're lazy and just update all.  */
1058 	wbcg_update_menu_feedback (wbcg, sheet);
1059 }
1060 
1061 static void
cb_sheet_visibility_change(Sheet * sheet,G_GNUC_UNUSED GParamSpec * pspec,SheetControlGUI * scg)1062 cb_sheet_visibility_change (Sheet *sheet,
1063 			    G_GNUC_UNUSED GParamSpec *pspec,
1064 			    SheetControlGUI *scg)
1065 {
1066 	gboolean viz;
1067 
1068 	g_return_if_fail (GNM_IS_SCG (scg));
1069 
1070 	viz = sheet_is_visible (sheet);
1071 	gtk_widget_set_visible (GTK_WIDGET (scg->grid), viz);
1072 	gtk_widget_set_visible (GTK_WIDGET (scg->label), viz);
1073 
1074 	wbcg_menu_state_sheet_count (scg->wbcg);
1075 }
1076 
1077 static void
disconnect_sheet_focus_signals(WBCGtk * wbcg)1078 disconnect_sheet_focus_signals (WBCGtk *wbcg)
1079 {
1080 	SheetControlGUI *scg = wbcg->active_scg;
1081 	Sheet *sheet;
1082 
1083 	if (!scg)
1084 		return;
1085 
1086 	sheet = scg_sheet (scg);
1087 
1088 #if 0
1089 	g_printerr ("Disconnecting focus for %s with scg=%p\n", sheet->name_unquoted, scg);
1090 #endif
1091 
1092 	g_signal_handlers_disconnect_by_func (sheet, cb_toggle_menu_item_changed, wbcg);
1093 	g_signal_handlers_disconnect_by_func (sheet, cb_direction_change, scg);
1094 	g_signal_handlers_disconnect_by_func (sheet, cb_zoom_change, wbcg);
1095 
1096 	wbcg->active_scg = NULL;
1097 }
1098 
1099 static void
disconnect_sheet_signals(SheetControlGUI * scg)1100 disconnect_sheet_signals (SheetControlGUI *scg)
1101 {
1102 	WBCGtk *wbcg = scg->wbcg;
1103 	Sheet *sheet = scg_sheet (scg);
1104 
1105 	if (scg == wbcg->active_scg)
1106 		disconnect_sheet_focus_signals (wbcg);
1107 
1108 #if 0
1109 	g_printerr ("Disconnecting all for %s with scg=%p\n", sheet->name_unquoted, scg);
1110 #endif
1111 
1112 	g_signal_handlers_disconnect_by_func (sheet, cb_sheet_direction_change,
1113 					      wbcg_find_action (wbcg, "SheetDirection"));
1114 	g_signal_handlers_disconnect_by_func (sheet, cb_sheet_tab_change, scg->label);
1115 	g_signal_handlers_disconnect_by_func (sheet, cb_sheet_visibility_change, scg);
1116 }
1117 
1118 static void
wbcg_sheet_add(WorkbookControl * wbc,SheetView * sv)1119 wbcg_sheet_add (WorkbookControl *wbc, SheetView *sv)
1120 {
1121 	static GtkTargetEntry const drag_types[] = {
1122 		{ (char *)"GNUMERIC_SHEET", GTK_TARGET_SAME_APP, TARGET_SHEET },
1123 		{ (char *)"UTF8_STRING", 0, 0 },
1124 		{ (char *)"image/svg+xml", 0, 0 },
1125 		{ (char *)"image/x-wmf", 0, 0 },
1126 		{ (char *)"image/x-emf", 0, 0 },
1127 		{ (char *)"image/png", 0, 0 },
1128 		{ (char *)"image/jpeg", 0, 0 },
1129 		{ (char *)"image/bmp", 0, 0 }
1130 	};
1131 
1132 	WBCGtk *wbcg = (WBCGtk *)wbc;
1133 	SheetControlGUI *scg;
1134 	Sheet		*sheet   = sv_sheet (sv);
1135 	gboolean	 visible = sheet_is_visible (sheet);
1136 
1137 	g_return_if_fail (wbcg != NULL);
1138 
1139 	scg = sheet_control_gui_new (sv, wbcg);
1140 
1141 	g_object_set_data (G_OBJECT (scg->grid), SHEET_CONTROL_KEY, scg);
1142 
1143 	g_object_set_data (G_OBJECT (scg->label), SHEET_CONTROL_KEY, scg);
1144 
1145 	/* do not preempt the editable label handler */
1146 	g_signal_connect_after (G_OBJECT (scg->label),
1147 		"button_press_event",
1148 		G_CALLBACK (cb_sheet_label_button_press), scg);
1149 
1150 	/* Drag & Drop */
1151 	gtk_drag_source_set (scg->label, GDK_BUTTON1_MASK | GDK_BUTTON3_MASK,
1152 			drag_types, G_N_ELEMENTS (drag_types),
1153 			GDK_ACTION_MOVE);
1154 	gtk_drag_dest_set (scg->label, GTK_DEST_DEFAULT_ALL,
1155 			drag_types, G_N_ELEMENTS (drag_types),
1156 			GDK_ACTION_MOVE);
1157 	g_object_connect (G_OBJECT (scg->label),
1158 		"signal::drag_begin", G_CALLBACK (cb_sheet_label_drag_begin), wbcg,
1159 		"signal::drag_end", G_CALLBACK (cb_sheet_label_drag_end), wbcg,
1160 		"signal::drag_leave", G_CALLBACK (cb_sheet_label_drag_leave), wbcg,
1161 		"signal::drag_data_get", G_CALLBACK (cb_sheet_label_drag_data_get), NULL,
1162 		"signal::drag_data_received", G_CALLBACK (cb_sheet_label_drag_data_received), wbcg,
1163 		"signal::drag_motion", G_CALLBACK (cb_sheet_label_drag_motion), wbcg,
1164 		NULL);
1165 
1166 	gtk_widget_show (scg->label);
1167 	gtk_widget_show_all (GTK_WIDGET (scg->grid));
1168 	if (!visible) {
1169 		gtk_widget_hide (GTK_WIDGET (scg->grid));
1170 		gtk_widget_hide (GTK_WIDGET (scg->label));
1171 	}
1172 	g_object_connect (G_OBJECT (sheet),
1173 			  "signal::notify::visibility", cb_sheet_visibility_change, scg,
1174 			  "signal::notify::name", cb_sheet_tab_change, scg->label,
1175 			  "signal::notify::tab-foreground", cb_sheet_tab_change, scg->label,
1176 			  "signal::notify::tab-background", cb_sheet_tab_change, scg->label,
1177 			  "signal::notify::text-is-rtl", cb_sheet_direction_change, wbcg_find_action (wbcg, "SheetDirection"),
1178 			  NULL);
1179 
1180 	if (wbcg_ui_update_begin (wbcg)) {
1181 		/*
1182 		 * Just let wbcg_sheet_order_changed deal with where to put
1183 		 * it.
1184 		 */
1185 		int pos = -1;
1186 		gtk_notebook_insert_page (wbcg->snotebook,
1187 					  GTK_WIDGET (scg->grid), NULL,
1188 					  pos);
1189 		gnm_notebook_insert_tab (wbcg->bnotebook,
1190 					 GTK_WIDGET (scg->label),
1191 					 pos);
1192 		wbcg_menu_state_sheet_count (wbcg);
1193 		wbcg_ui_update_end (wbcg);
1194 	}
1195 
1196 	scg_adjust_preferences (scg);
1197 	if (sheet == wb_control_cur_sheet (wbc)) {
1198 		scg_take_focus (scg);
1199 		wbcg_set_direction (scg);
1200 		cb_zoom_change (sheet, NULL, wbcg);
1201 		cb_toggle_menu_item_changed (sheet, NULL, wbcg);
1202 	}
1203 }
1204 
1205 static void
wbcg_sheet_remove(WorkbookControl * wbc,Sheet * sheet)1206 wbcg_sheet_remove (WorkbookControl *wbc, Sheet *sheet)
1207 {
1208 	WBCGtk *wbcg = (WBCGtk *)wbc;
1209 	SheetControlGUI *scg = wbcg_get_scg (wbcg, sheet);
1210 
1211 	/* During destruction we may have already removed the notebook */
1212 	if (scg == NULL)
1213 		return;
1214 
1215 	disconnect_sheet_signals (scg);
1216 
1217 	gtk_widget_destroy (GTK_WIDGET (scg->label));
1218 	gtk_widget_destroy (GTK_WIDGET (scg->grid));
1219 
1220 	wbcg_menu_state_sheet_count (wbcg);
1221 }
1222 
1223 static void
wbcg_sheet_focus(WorkbookControl * wbc,Sheet * sheet)1224 wbcg_sheet_focus (WorkbookControl *wbc, Sheet *sheet)
1225 {
1226 	WBCGtk *wbcg = (WBCGtk *)wbc;
1227 	SheetControlGUI *scg = wbcg_get_scg (wbcg, sheet);
1228 
1229 	if (scg) {
1230 		int n = gtk_notebook_page_num (wbcg->snotebook,
1231 					       GTK_WIDGET (scg->grid));
1232 		gnm_notebook_set_current_page (wbcg->bnotebook, n);
1233 
1234 		if (wbcg->rangesel == NULL)
1235 			gnm_expr_entry_set_scg (wbcg->edit_line.entry, scg);
1236 	}
1237 
1238 	disconnect_sheet_focus_signals (wbcg);
1239 
1240 	if (sheet) {
1241 		wbcg_update_menu_feedback (wbcg, sheet);
1242 
1243 		if (scg)
1244 			wbcg_set_direction (scg);
1245 
1246 #if 0
1247 		g_printerr ("Connecting for %s with scg=%p\n", sheet->name_unquoted, scg);
1248 #endif
1249 
1250 		g_object_connect
1251 			(G_OBJECT (sheet),
1252 			 "signal::notify::display-formulas", cb_toggle_menu_item_changed, wbcg,
1253 			 "signal::notify::display-zeros", cb_toggle_menu_item_changed, wbcg,
1254 			 "signal::notify::display-grid", cb_toggle_menu_item_changed, wbcg,
1255 			 "signal::notify::display-column-header", cb_toggle_menu_item_changed, wbcg,
1256 			 "signal::notify::display-row-header", cb_toggle_menu_item_changed, wbcg,
1257 			 "signal::notify::display-outlines", cb_toggle_menu_item_changed, wbcg,
1258 			 "signal::notify::display-outlines-below", cb_toggle_menu_item_changed, wbcg,
1259 			 "signal::notify::display-outlines-right", cb_toggle_menu_item_changed, wbcg,
1260 			 "signal::notify::text-is-rtl", cb_direction_change, scg,
1261 			 "signal::notify::zoom-factor", cb_zoom_change, wbcg,
1262 			 NULL);
1263 
1264 		wbcg->active_scg = scg;
1265 	}
1266 }
1267 
1268 static gint
by_sheet_index(gconstpointer a,gconstpointer b)1269 by_sheet_index (gconstpointer a, gconstpointer b)
1270 {
1271 	SheetControlGUI *scga = (SheetControlGUI *)a;
1272 	SheetControlGUI *scgb = (SheetControlGUI *)b;
1273 	return scg_sheet (scga)->index_in_wb - scg_sheet (scgb)->index_in_wb;
1274 }
1275 
1276 static void
wbcg_sheet_order_changed(WBCGtk * wbcg)1277 wbcg_sheet_order_changed (WBCGtk *wbcg)
1278 {
1279 	if (wbcg_ui_update_begin (wbcg)) {
1280 		GSList *l, *scgs;
1281 		int i;
1282 
1283 		/* Reorder all tabs so they end up in index_in_wb order. */
1284 		scgs = g_slist_sort (get_all_scgs (wbcg), by_sheet_index);
1285 
1286 		for (i = 0, l = scgs; l; l = l->next, i++) {
1287 			SheetControlGUI *scg = l->data;
1288 			gtk_notebook_reorder_child (wbcg->snotebook,
1289 						    GTK_WIDGET (scg->grid),
1290 						    i);
1291 			gnm_notebook_move_tab (wbcg->bnotebook,
1292 					       GTK_WIDGET (scg->label),
1293 					       i);
1294 		}
1295 		g_slist_free (scgs);
1296 
1297 		wbcg_ui_update_end (wbcg);
1298 	}
1299 }
1300 
1301 static void
wbcg_update_title(WBCGtk * wbcg)1302 wbcg_update_title (WBCGtk *wbcg)
1303 {
1304 	GODoc *doc = wb_control_get_doc (GNM_WBC (wbcg));
1305 	char *basename = doc->uri ? go_basename_from_uri (doc->uri) : NULL;
1306 	char *title = g_strconcat
1307 		(go_doc_is_dirty (doc) ? "*" : "",
1308 		 basename ? basename : doc->uri,
1309 		 _(" - Gnumeric"),
1310 		 NULL);
1311 	gtk_window_set_title (wbcg_toplevel (wbcg), title);
1312 	g_free (title);
1313 	g_free (basename);
1314 }
1315 
1316 static void
wbcg_sheet_remove_all(WorkbookControl * wbc)1317 wbcg_sheet_remove_all (WorkbookControl *wbc)
1318 {
1319 	WBCGtk *wbcg = (WBCGtk *)wbc;
1320 
1321 	if (wbcg->snotebook != NULL) {
1322 		GtkNotebook *tmp = wbcg->snotebook;
1323 		GSList *l, *all = get_all_scgs (wbcg);
1324 		SheetControlGUI *current = wbcg_cur_scg (wbcg);
1325 
1326 		/* Clear notebook to disable updates as focus changes for pages
1327 		 * during destruction */
1328 		wbcg->snotebook = NULL;
1329 
1330 		/* Be sure we are no longer editing */
1331 		wbcg_edit_finish (wbcg, WBC_EDIT_REJECT, NULL);
1332 
1333 		for (l = all; l; l = l->next) {
1334 			SheetControlGUI *scg = l->data;
1335 			disconnect_sheet_signals (scg);
1336 			if (scg != current) {
1337 				gtk_widget_destroy (GTK_WIDGET (scg->label));
1338 				gtk_widget_destroy (GTK_WIDGET (scg->grid));
1339 			}
1340 		}
1341 
1342 		g_slist_free (all);
1343 
1344 		/* Do current scg last.  */
1345 		if (current) {
1346 			gtk_widget_destroy (GTK_WIDGET (current->label));
1347 			gtk_widget_destroy (GTK_WIDGET (current->grid));
1348 		}
1349 
1350 		wbcg->snotebook = tmp;
1351 	}
1352 }
1353 
1354 static double
color_diff(const GdkRGBA * a,const GdkRGBA * b)1355 color_diff (const GdkRGBA *a, const GdkRGBA *b)
1356 {
1357 	/* Ignoring alpha.  */
1358 	return ((a->red - b->red) * (a->red - b->red) +
1359 		(a->green - b->green) * (a->green - b->green) +
1360 		(a->blue - b->blue) * (a->blue - b->blue));
1361 }
1362 
1363 
1364 static gboolean
cb_adjust_foreground_attributes(PangoAttribute * attribute,gpointer data)1365 cb_adjust_foreground_attributes (PangoAttribute *attribute,
1366 				 gpointer data)
1367 {
1368 	const GdkRGBA *back = data;
1369 
1370 	if (attribute->klass->type == PANGO_ATTR_FOREGROUND) {
1371 		PangoColor *pfore = &((PangoAttrColor *)attribute)->color;
1372 		GdkRGBA fore;
1373 		const double threshold = 0.01;
1374 
1375 		fore.red = pfore->red / 65535.0;
1376 		fore.green = pfore->green / 65535.0;
1377 		fore.blue = pfore->blue / 65535.0;
1378 
1379 		if (color_diff (&fore, back) < threshold) {
1380 			static const GdkRGBA black = { 0, 0, 0, 1 };
1381 			static const GdkRGBA white = { 1, 1, 1, 1 };
1382 			double back_norm = color_diff (back, &black);
1383 			double f = 0.2;
1384 			const GdkRGBA *ref =
1385 				back_norm > 0.75 ? &black : &white;
1386 
1387 #define DO_CHANNEL(channel)						\
1388 do {									\
1389 	double val = fore.channel * (1 - f) + ref->channel * f;		\
1390 	pfore->channel = CLAMP (val, 0, 1) * 65535;			\
1391 } while (0)
1392 			DO_CHANNEL(red);
1393 			DO_CHANNEL(green);
1394 			DO_CHANNEL(blue);
1395 #undef DO_CHANNEL
1396 		}
1397 	}
1398 	return FALSE;
1399 }
1400 
1401 static void
adjust_foreground_attributes(PangoAttrList * attrs,GtkWidget * w)1402 adjust_foreground_attributes (PangoAttrList *attrs, GtkWidget *w)
1403 {
1404 	GdkRGBA c;
1405 	GtkStyleContext *ctxt = gtk_widget_get_style_context (w);
1406 
1407 	gtk_style_context_get_background_color (ctxt, GTK_STATE_FLAG_NORMAL,
1408 						&c);
1409 
1410 	if (0)
1411 		g_printerr ("back=%s\n", gdk_rgba_to_string (&c));
1412 
1413 	pango_attr_list_unref
1414 		(pango_attr_list_filter
1415 		 (attrs,
1416 		  cb_adjust_foreground_attributes,
1417 		  &c));
1418 }
1419 
1420 
1421 static void
wbcg_auto_expr_value_changed(WorkbookView * wbv,G_GNUC_UNUSED GParamSpec * pspec,WBCGtk * wbcg)1422 wbcg_auto_expr_value_changed (WorkbookView *wbv,
1423 			      G_GNUC_UNUSED GParamSpec *pspec,
1424 			      WBCGtk *wbcg)
1425 {
1426 	GtkLabel *lbl = GTK_LABEL (wbcg->auto_expr_label);
1427 	GnmValue const *v = wbv->auto_expr.value;
1428 
1429 	if (v) {
1430 		GOFormat const *format = VALUE_FMT (v);
1431 		GString *str = g_string_new (wbv->auto_expr.descr);
1432 		PangoAttrList *attrs = NULL;
1433 
1434 		g_string_append (str, " = ");
1435 
1436 		if (format) {
1437 			PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET (wbcg->toplevel), NULL);
1438 			gsize old_len = str->len;
1439 			GODateConventions const *date_conv = workbook_date_conv (wb_view_get_workbook (wbv));
1440 			int max_width = go_format_is_general (format) && VALUE_IS_NUMBER (v)
1441 				? 14
1442 				: strlen (AUTO_EXPR_SAMPLE) - g_utf8_strlen (str->str, -1);
1443 			GOFormatNumberError err =
1444 				format_value_layout (layout, format, v,
1445 						     max_width, date_conv);
1446 			switch (err) {
1447 			case GO_FORMAT_NUMBER_OK:
1448 			case GO_FORMAT_NUMBER_DATE_ERROR: {
1449 				PangoAttrList *atl;
1450 
1451 				go_pango_translate_layout (layout); /* translating custom attributes */
1452 				g_string_append (str, pango_layout_get_text (layout));
1453 				/* We need to shift the attribute list  */
1454 				atl = pango_attr_list_ref (pango_layout_get_attributes (layout));
1455 				if (atl != NULL) {
1456 					attrs = pango_attr_list_new ();
1457 					pango_attr_list_splice
1458 						(attrs, atl, old_len,
1459 						 str->len - old_len);
1460 					pango_attr_list_unref (atl);
1461 					/* Adjust colours to make text visible. */
1462 					adjust_foreground_attributes
1463 						(attrs,
1464 						 gtk_widget_get_parent (GTK_WIDGET (lbl)));
1465 				}
1466 				break;
1467 			}
1468 			default:
1469 			case GO_FORMAT_NUMBER_INVALID_FORMAT:
1470 				g_string_append (str,  _("Invalid format"));
1471 				break;
1472 			}
1473 			g_object_unref (layout);
1474 		} else
1475 			g_string_append (str, value_peek_string (v));
1476 
1477 		gtk_label_set_text (lbl, str->str);
1478 		gtk_label_set_attributes (lbl, attrs);
1479 
1480 		pango_attr_list_unref (attrs);
1481 		g_string_free (str, TRUE);
1482 	} else {
1483 		gtk_label_set_text (lbl, "");
1484 		gtk_label_set_attributes (lbl, NULL);
1485 	}
1486 }
1487 
1488 static void
wbcg_scrollbar_visibility(WorkbookView * wbv,G_GNUC_UNUSED GParamSpec * pspec,WBCGtk * wbcg)1489 wbcg_scrollbar_visibility (WorkbookView *wbv,
1490 			   G_GNUC_UNUSED GParamSpec *pspec,
1491 			   WBCGtk *wbcg)
1492 {
1493 	SheetControlGUI *scg = wbcg_cur_scg (wbcg);
1494 	scg_adjust_preferences (scg);
1495 }
1496 
1497 static void
wbcg_notebook_tabs_visibility(WorkbookView * wbv,G_GNUC_UNUSED GParamSpec * pspec,WBCGtk * wbcg)1498 wbcg_notebook_tabs_visibility (WorkbookView *wbv,
1499 			       G_GNUC_UNUSED GParamSpec *pspec,
1500 			       WBCGtk *wbcg)
1501 {
1502 	gtk_widget_set_visible (GTK_WIDGET (wbcg->bnotebook),
1503 				wbv->show_notebook_tabs);
1504 }
1505 
1506 
1507 static void
wbcg_menu_state_update(WorkbookControl * wbc,int flags)1508 wbcg_menu_state_update (WorkbookControl *wbc, int flags)
1509 {
1510 	WBCGtk *wbcg = (WBCGtk *)wbc;
1511 	SheetControlGUI *scg = wbcg_cur_scg (wbcg);
1512 	SheetView const *sv  = wb_control_cur_sheet_view (wbc);
1513 	Sheet const *sheet = wb_control_cur_sheet (wbc);
1514 	gboolean const has_guru = wbc_gtk_get_guru (wbcg) != NULL;
1515 	gboolean edit_object = scg != NULL &&
1516 		(scg->selected_objects != NULL || wbcg->new_object != NULL);
1517 	gboolean has_print_area;
1518 
1519 	if (MS_INSERT_COLS & flags)
1520 		wbc_gtk_set_action_sensitivity (wbcg, "InsertColumns",
1521 			sv->enable_insert_cols);
1522 	if (MS_INSERT_ROWS & flags)
1523 		wbc_gtk_set_action_sensitivity (wbcg, "InsertRows",
1524 			sv->enable_insert_rows);
1525 	if (MS_INSERT_CELLS & flags)
1526 		wbc_gtk_set_action_sensitivity (wbcg, "InsertCells",
1527 			sv->enable_insert_cells);
1528 	if (MS_SHOWHIDE_DETAIL & flags) {
1529 		wbc_gtk_set_action_sensitivity (wbcg, "DataOutlineShowDetail",
1530 			sheet->priv->enable_showhide_detail);
1531 		wbc_gtk_set_action_sensitivity (wbcg, "DataOutlineHideDetail",
1532 			sheet->priv->enable_showhide_detail);
1533 	}
1534 	if (MS_PASTE_SPECIAL & flags)
1535 		wbc_gtk_set_action_sensitivity (wbcg, "EditPasteSpecial",
1536 			// Inter-process paste special is now allowed
1537 			!gnm_app_clipboard_is_cut () &&
1538 			!edit_object);
1539 	if (MS_PRINT_SETUP & flags)
1540 		wbc_gtk_set_action_sensitivity (wbcg, "FilePageSetup", !has_guru);
1541 	if (MS_SEARCH_REPLACE & flags)
1542 		wbc_gtk_set_action_sensitivity (wbcg, "EditReplace", !has_guru);
1543 	if (MS_DEFINE_NAME & flags) {
1544 		wbc_gtk_set_action_sensitivity (wbcg, "EditNames", !has_guru);
1545 		wbc_gtk_set_action_sensitivity (wbcg, "InsertNames", !has_guru);
1546 	}
1547 	if (MS_CONSOLIDATE & flags)
1548 		wbc_gtk_set_action_sensitivity (wbcg, "DataConsolidate", !has_guru);
1549 	if (MS_FILTER_STATE_CHANGED & flags)
1550 		wbc_gtk_set_action_sensitivity (wbcg, "DataFilterShowAll", sheet->has_filtered_rows);
1551 	if (MS_SHOW_PRINTAREA & flags) {
1552 		GnmRange *print_area = sheet_get_nominal_printarea (sheet);
1553 		has_print_area = (print_area != NULL);
1554 		g_free (print_area);
1555 		wbc_gtk_set_action_sensitivity (wbcg, "FilePrintAreaClear", has_print_area);
1556 		wbc_gtk_set_action_sensitivity (wbcg, "FilePrintAreaShow", has_print_area);
1557 	}
1558 	if (MS_PAGE_BREAKS & flags) {
1559 		gint col = sv->edit_pos.col;
1560 		gint row = sv->edit_pos.row;
1561 		GnmPrintInformation *pi = sheet->print_info;
1562 		char const* new_label = NULL;
1563 		char const *new_tip = NULL;
1564 
1565 		if (pi->page_breaks.v != NULL &&
1566 		    gnm_page_breaks_get_break (pi->page_breaks.v, col) == GNM_PAGE_BREAK_MANUAL) {
1567 			new_label = _("Remove Column Page Break");
1568 			new_tip = _("Remove the page break to the left of the current column");
1569 		} else {
1570 			new_label = _("Add Column Page Break");
1571 			new_tip = _("Add a page break to the left of the current column");
1572 		}
1573 		wbc_gtk_set_action_label (wbcg, "FilePrintAreaToggleColPageBreak",
1574 					  NULL, new_label, new_tip);
1575 		if (pi->page_breaks.h != NULL &&
1576 		    gnm_page_breaks_get_break (pi->page_breaks.h, col) == GNM_PAGE_BREAK_MANUAL) {
1577 			new_label = _("Remove Row Page Break");
1578 			new_tip = _("Remove the page break above the current row");
1579 		} else {
1580 			new_label = _("Add Row Page Break");
1581 			new_tip = _("Add a page break above current row");
1582 		}
1583 		wbc_gtk_set_action_label (wbcg, "FilePrintAreaToggleRowPageBreak",
1584 					  NULL, new_label, new_tip);
1585 		wbc_gtk_set_action_sensitivity (wbcg, "FilePrintAreaToggleRowPageBreak",
1586 						row != 0);
1587 		wbc_gtk_set_action_sensitivity (wbcg, "FilePrintAreaToggleColPageBreak",
1588 						col != 0);
1589 		wbc_gtk_set_action_sensitivity (wbcg, "FilePrintAreaClearAllPageBreak",
1590 						print_info_has_manual_breaks (sheet->print_info));
1591 	}
1592 	if (MS_SELECT_OBJECT & flags) {
1593 		wbc_gtk_set_action_sensitivity (wbcg, "EditSelectObject",
1594 						sheet->sheet_objects != NULL);
1595 	}
1596 
1597 	if (MS_FREEZE_VS_THAW & flags) {
1598 		/* Cheat and use the same accelerator for both states because
1599 		 * we don't reset it when the label changes */
1600 		char const* label = gnm_sheet_view_is_frozen (sv)
1601 			? _("Un_freeze Panes")
1602 			: _("_Freeze Panes");
1603 		char const *new_tip = gnm_sheet_view_is_frozen (sv)
1604 			? _("Unfreeze the top left of the sheet")
1605 			: _("Freeze the top left of the sheet");
1606 		wbc_gtk_set_action_label (wbcg, "ViewFreezeThawPanes", NULL, label, new_tip);
1607 	}
1608 
1609 	if (MS_ADD_VS_REMOVE_FILTER & flags) {
1610 		gboolean const has_filter = (NULL != gnm_sheet_view_editpos_in_filter (sv));
1611 		GnmFilter *f = gnm_sheet_view_selection_intersects_filter_rows (sv);
1612 		char const* label;
1613 		char const *new_tip;
1614 		gboolean active = TRUE;
1615 		GnmRange *r = NULL;
1616 
1617 		if ((!has_filter) && (NULL != f)) {
1618 			gchar *nlabel = NULL;
1619 			if (NULL != (r = gnm_sheet_view_selection_extends_filter (sv, f))) {
1620 				active = TRUE;
1621 				nlabel = g_strdup_printf
1622 					(_("Extend _Auto Filter to %s"),
1623 					 range_as_string (r));
1624 				new_tip = _("Extend the existing filter.");
1625 				wbc_gtk_set_action_label
1626 					(wbcg, "DataAutoFilter", NULL,
1627 					 nlabel, new_tip);
1628 				g_free (r);
1629 			} else {
1630 				active = FALSE;
1631 				nlabel = g_strdup_printf
1632 					(_("Auto Filter blocked by %s"),
1633 					 range_as_string (&f->r));
1634 				new_tip = _("The selection intersects an "
1635 					    "existing auto filter.");
1636 				wbc_gtk_set_action_label
1637 					(wbcg, "DataAutoFilter", NULL,
1638 					 nlabel, new_tip);
1639 			}
1640 			g_free (nlabel);
1641 		} else {
1642 			label = has_filter
1643 				? _("Remove _Auto Filter")
1644 				: _("Add _Auto Filter");
1645 			new_tip = has_filter
1646 				? _("Remove a filter")
1647 				: _("Add a filter");
1648 			wbc_gtk_set_action_label (wbcg, "DataAutoFilter", NULL, label, new_tip);
1649 		}
1650 
1651 		wbc_gtk_set_action_sensitivity (wbcg, "DataAutoFilter", active);
1652 	}
1653 	if (MS_COMMENT_LINKS & flags) {
1654 		gboolean has_comment
1655 			= (sheet_get_comment (sheet, &sv->edit_pos) != NULL);
1656 		gboolean has_link;
1657 		GnmRange rge;
1658 		range_init_cellpos (&rge, &sv->edit_pos);
1659 		has_link = (NULL !=
1660 			    sheet_style_region_contains_link (sheet, &rge));
1661 		wbc_gtk_set_action_sensitivity
1662 			(wbcg, "EditComment", has_comment);
1663 		wbc_gtk_set_action_sensitivity
1664 			(wbcg, "EditHyperlink", has_link);
1665 	}
1666 
1667 	if (MS_COMMENT_LINKS_RANGE & flags) {
1668 		GSList *l;
1669 		int count = 0;
1670 		gboolean has_links = FALSE, has_comments = FALSE;
1671 		gboolean sel_is_vector = FALSE;
1672 		SheetView *sv = scg_view (scg);
1673 		for (l = sv->selections;
1674 		     l != NULL; l = l->next) {
1675 			GnmRange const *r = l->data;
1676 			GSList *objs;
1677 			GnmStyleList *styles;
1678 			if (!has_links) {
1679 				styles = sheet_style_collect_hlinks
1680 					(sheet, r);
1681 				has_links = (styles != NULL);
1682 				style_list_free (styles);
1683 			}
1684 			if (!has_comments) {
1685 				objs = sheet_objects_get
1686 					(sheet, r, GNM_CELL_COMMENT_TYPE);
1687 				has_comments = (objs != NULL);
1688 				g_slist_free (objs);
1689 			}
1690 			if((count++ > 1) && has_comments && has_links)
1691 				break;
1692 		}
1693 		wbc_gtk_set_action_sensitivity
1694 			(wbcg, "EditClearHyperlinks", has_links);
1695 		wbc_gtk_set_action_sensitivity
1696 			(wbcg, "EditClearComments", has_comments);
1697 		if (count == 1) {
1698 			GnmRange const *r = sv->selections->data;
1699 			sel_is_vector = (range_width (r) == 1 ||
1700 					 range_height (r) == 1) &&
1701 				!range_is_singleton (r);
1702  		}
1703 		wbc_gtk_set_action_sensitivity
1704 			(wbcg, "InsertSortDecreasing", sel_is_vector);
1705 		wbc_gtk_set_action_sensitivity
1706 			(wbcg, "InsertSortIncreasing", sel_is_vector);
1707 	}
1708 	if (MS_FILE_EXPORT_IMPORT & flags) {
1709 		Workbook *wb = wb_control_get_workbook (wbc);
1710 		gboolean has_export_info = workbook_get_file_exporter (wb) &&
1711 			workbook_get_last_export_uri (wb);
1712 		wbc_gtk_set_action_sensitivity (wbcg, "DataExportRepeat", has_export_info);
1713 		if (has_export_info) {
1714 			gchar *base = go_basename_from_uri (workbook_get_last_export_uri (wb));
1715 			gchar *new_label = g_strdup_printf (_("Repeat Export to %s"),
1716 							    base);
1717 			g_free (base);
1718 			wbc_gtk_set_action_label (wbcg, "DataExportRepeat", NULL,
1719 						  new_label, N_("Repeat the last data export"));
1720 			g_free (new_label);
1721 		} else
1722 			wbc_gtk_set_action_label (wbcg, "DataExportRepeat", NULL,
1723 						  N_("Repeat Export"), N_("Repeat the last data export"));
1724 	}
1725 	{
1726 		gboolean const has_slicer = (NULL != gnm_sheet_view_editpos_in_slicer (sv));
1727 		char const* label = has_slicer
1728 			? _("Remove _Data Slicer")
1729 			: _("Create _Data Slicer");
1730 		char const *new_tip = has_slicer
1731 			? _("Remove a Data Slicer")
1732 			: _("Create a Data Slicer");
1733 		wbc_gtk_set_action_label (wbcg, "DataSlicer", NULL, label, new_tip);
1734 		wbc_gtk_set_action_sensitivity (wbcg, "DataSlicerRefresh", has_slicer);
1735 		wbc_gtk_set_action_sensitivity (wbcg, "DataSlicerEdit", has_slicer);
1736 	}
1737 }
1738 
1739 static void
wbcg_undo_redo_labels(WorkbookControl * wbc,char const * undo,char const * redo)1740 wbcg_undo_redo_labels (WorkbookControl *wbc, char const *undo, char const *redo)
1741 {
1742 	WBCGtk *wbcg = (WBCGtk *)wbc;
1743 	g_return_if_fail (wbcg != NULL);
1744 
1745 	wbc_gtk_set_action_label (wbcg, "Redo", _("_Redo"), redo, NULL);
1746 	wbc_gtk_set_action_label (wbcg, "Undo", _("_Undo"), undo, NULL);
1747 	wbc_gtk_set_action_sensitivity (wbcg, "Repeat", undo != NULL);
1748 }
1749 
1750 static void
wbcg_paste_from_selection(WorkbookControl * wbc,GnmPasteTarget const * pt)1751 wbcg_paste_from_selection (WorkbookControl *wbc, GnmPasteTarget const *pt)
1752 {
1753 	gnm_x_request_clipboard ((WBCGtk *)wbc, pt);
1754 }
1755 
1756 static gboolean
wbcg_claim_selection(WorkbookControl * wbc)1757 wbcg_claim_selection (WorkbookControl *wbc)
1758 {
1759 	WBCGtk *wbcg = (WBCGtk *)wbc;
1760 	GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (wbcg_toplevel (wbcg)));
1761 	return gnm_x_claim_clipboard (display);
1762 }
1763 
1764 static int
wbcg_show_save_dialog(WBCGtk * wbcg,Workbook * wb)1765 wbcg_show_save_dialog (WBCGtk *wbcg, Workbook *wb)
1766 {
1767 	GtkWidget *d;
1768 	char *msg;
1769 	char const *wb_uri = go_doc_get_uri (GO_DOC (wb));
1770 	int ret = 0;
1771 
1772 	if (wb_uri) {
1773 		char *base    = go_basename_from_uri (wb_uri);
1774 		char *display = g_markup_escape_text (base, -1);
1775 		msg = g_strdup_printf (
1776 			_("Save changes to workbook '%s' before closing?"),
1777 			display);
1778 		g_free (base);
1779 		g_free (display);
1780 	} else {
1781 		msg = g_strdup (_("Save changes to workbook before closing?"));
1782 	}
1783 
1784 	d = gnm_message_dialog_create (wbcg_toplevel (wbcg),
1785 					 GTK_DIALOG_DESTROY_WITH_PARENT,
1786 					 GTK_MESSAGE_WARNING,
1787 					 msg,
1788 					 _("If you close without saving, changes will be discarded."));
1789 	atk_object_set_role (gtk_widget_get_accessible (d), ATK_ROLE_ALERT);
1790 
1791 	go_gtk_dialog_add_button (GTK_DIALOG(d), _("Discard"),
1792 				  GTK_STOCK_DELETE, GTK_RESPONSE_NO);
1793 	go_gtk_dialog_add_button (GTK_DIALOG(d), _("Don't close"),
1794 				  GNM_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
1795 
1796 	gtk_dialog_add_button (GTK_DIALOG(d), GNM_STOCK_SAVE, GTK_RESPONSE_YES);
1797 	gtk_dialog_set_default_response (GTK_DIALOG (d), GTK_RESPONSE_YES);
1798 	ret = go_gtk_dialog_run (GTK_DIALOG (d), wbcg_toplevel (wbcg));
1799 	g_free (msg);
1800 
1801 	return ret;
1802 }
1803 
1804 /*
1805  * wbcg_close_if_user_permits : If the workbook is dirty the user is
1806  *		prompted to see if they should exit.
1807  *
1808  * Returns:
1809  * 0) canceled
1810  * 1) closed
1811  * 2) -
1812  * 3) save any future dirty
1813  * 4) do not save any future dirty
1814  */
1815 static int
wbcg_close_if_user_permits(WBCGtk * wbcg,WorkbookView * wb_view)1816 wbcg_close_if_user_permits (WBCGtk *wbcg, WorkbookView *wb_view)
1817 {
1818 	gboolean   can_close = TRUE;
1819 	gboolean   done      = FALSE;
1820 	int        iteration = 0;
1821 	int        button = 0;
1822 	Workbook  *wb = wb_view_get_workbook (wb_view);
1823 	static int in_can_close;
1824 
1825 	g_return_val_if_fail (GNM_IS_WORKBOOK (wb), 0);
1826 
1827 	if (in_can_close)
1828 		return 0;
1829 	in_can_close = TRUE;
1830 
1831 	while (go_doc_is_dirty (GO_DOC (wb)) && !done) {
1832 		iteration++;
1833 		button = wbcg_show_save_dialog(wbcg, wb);
1834 
1835 		switch (button) {
1836 		case GTK_RESPONSE_YES:
1837 			done = gui_file_save (wbcg, wb_view);
1838 			break;
1839 
1840 		case GNM_RESPONSE_SAVE_ALL:
1841 			done = gui_file_save (wbcg, wb_view);
1842 			break;
1843 
1844 		case GTK_RESPONSE_NO:
1845 			done      = TRUE;
1846 			go_doc_set_dirty (GO_DOC (wb), FALSE);
1847 			break;
1848 
1849 		case GNM_RESPONSE_DISCARD_ALL:
1850 			done      = TRUE;
1851 			go_doc_set_dirty (GO_DOC (wb), FALSE);
1852 			break;
1853 
1854 		default:  /* CANCEL */
1855 			can_close = FALSE;
1856 			done      = TRUE;
1857 			break;
1858 		}
1859 	}
1860 
1861 	in_can_close = FALSE;
1862 
1863 	if (can_close) {
1864 		gnm_x_store_clipboard_if_needed (wb);
1865 		g_object_unref (wb);
1866 		switch (button) {
1867 		case GNM_RESPONSE_SAVE_ALL:
1868 			return 3;
1869 		case GNM_RESPONSE_DISCARD_ALL:
1870 			return 4;
1871 		default:
1872 			return 1;
1873 		}
1874 	} else
1875 		return 0;
1876 }
1877 
1878 /**
1879  * wbc_gtk_close:
1880  * @wbcg: #WBCGtk
1881  *
1882  * Returns: %TRUE if the control should NOT be closed.
1883  */
1884 gboolean
wbc_gtk_close(WBCGtk * wbcg)1885 wbc_gtk_close (WBCGtk *wbcg)
1886 {
1887 	WorkbookView *wb_view = wb_control_view (GNM_WBC (wbcg));
1888 
1889 	g_return_val_if_fail (GNM_IS_WORKBOOK_VIEW (wb_view), TRUE);
1890 	g_return_val_if_fail (wb_view->wb_controls != NULL, TRUE);
1891 
1892 	/* If we were editing when the quit request came make sure we don't
1893 	 * lose any entered text
1894 	 */
1895 	if (!wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT, NULL))
1896 		return TRUE;
1897 
1898 	/* If something is still using the control
1899 	 * eg progress meter for a new book */
1900 	if (G_OBJECT (wbcg)->ref_count > 1)
1901 		return TRUE;
1902 
1903 	/* This is the last control */
1904 	if (wb_view->wb_controls->len <= 1) {
1905 		Workbook *wb = wb_view_get_workbook (wb_view);
1906 
1907 		g_return_val_if_fail (GNM_IS_WORKBOOK (wb), TRUE);
1908 		g_return_val_if_fail (wb->wb_views != NULL, TRUE);
1909 
1910 		/* This is the last view */
1911 		if (wb->wb_views->len <= 1) {
1912 			if (wbcg_close_if_user_permits (wbcg, wb_view) == 0)
1913 				return TRUE;
1914 			return FALSE;
1915 		}
1916 
1917 		g_object_unref (wb_view);
1918 	} else
1919 		g_object_unref (wbcg);
1920 
1921 	gnm_app_flag_windows_changed_ ();
1922 
1923 	return FALSE;
1924 }
1925 
1926 static void
cb_cancel_input(WBCGtk * wbcg)1927 cb_cancel_input (WBCGtk *wbcg)
1928 {
1929 	wbcg_edit_finish (wbcg, WBC_EDIT_REJECT, NULL);
1930 }
1931 
1932 static void
cb_accept_input(WBCGtk * wbcg)1933 cb_accept_input (WBCGtk *wbcg)
1934 {
1935 	wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT, NULL);
1936 }
1937 
1938 static void
cb_accept_input_wo_ac(WBCGtk * wbcg)1939 cb_accept_input_wo_ac (WBCGtk *wbcg)
1940 {
1941 	wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT_WO_AC, NULL);
1942 }
1943 
1944 static void
cb_accept_input_array(WBCGtk * wbcg)1945 cb_accept_input_array (WBCGtk *wbcg)
1946 {
1947 	wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT_ARRAY, NULL);
1948 }
1949 
1950 static void
cb_accept_input_selected_cells(WBCGtk * wbcg)1951 cb_accept_input_selected_cells (WBCGtk *wbcg)
1952 {
1953 	wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT_RANGE, NULL);
1954 }
1955 
1956 static void
cb_accept_input_selected_merged(WBCGtk * wbcg)1957 cb_accept_input_selected_merged (WBCGtk *wbcg)
1958 {
1959 	Sheet *sheet = wbcg->editing_sheet;
1960 
1961 #warning FIXME: this creates 2 undo items!
1962 	if (wbcg_is_editing (wbcg) &&
1963 	    wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT, NULL)) {
1964 		WorkbookControl *wbc = GNM_WBC (wbcg);
1965 		WorkbookView	*wbv = wb_control_view (wbc);
1966 		SheetView *sv = sheet_get_view (sheet, wbv);
1967 		GnmRange sel = *(selection_first_range (sv, NULL, NULL));
1968 		GSList *selection = g_slist_prepend (NULL, &sel);
1969 
1970 		cmd_merge_cells	(wbc, sheet, selection, FALSE);
1971 		g_slist_free (selection);
1972 	}
1973 }
1974 
1975 static gboolean
cb_accept_input_menu_sensitive_selected_cells(WBCGtk * wbcg)1976 cb_accept_input_menu_sensitive_selected_cells (WBCGtk *wbcg)
1977 {
1978 	WorkbookControl *wbc = GNM_WBC (wbcg);
1979 	WorkbookView	*wbv = wb_control_view (wbc);
1980 	SheetView *sv = sheet_get_view (wbcg->editing_sheet, wbv);
1981 	gboolean result = TRUE;
1982 	GSList	*selection = selection_get_ranges (sv, FALSE), *l;
1983 
1984 	for (l = selection; l != NULL; l = l->next) {
1985 		GnmRange const *sel = l->data;
1986 		if (sheet_range_splits_array
1987 		    (wbcg->editing_sheet, sel, NULL, NULL, NULL)) {
1988 			result = FALSE;
1989 			break;
1990 		}
1991 	}
1992 	range_fragment_free (selection);
1993 	return result;
1994 }
1995 
1996 static gboolean
cb_accept_input_menu_sensitive_selected_merged(WBCGtk * wbcg)1997 cb_accept_input_menu_sensitive_selected_merged (WBCGtk *wbcg)
1998 {
1999 	WorkbookControl *wbc = GNM_WBC (wbcg);
2000 	WorkbookView	*wbv = wb_control_view (wbc);
2001 	SheetView *sv = sheet_get_view (wbcg->editing_sheet, wbv);
2002 	GnmRange const *sel = selection_first_range (sv, NULL, NULL);
2003 
2004 	return (sel && !range_is_singleton (sel) &&
2005 		sv->edit_pos.col == sel->start.col &&
2006 		sv->edit_pos.row == sel->start.row &&
2007 		!sheet_range_splits_array
2008 		(wbcg->editing_sheet, sel, NULL, NULL, NULL));
2009 }
2010 
2011 static void
cb_accept_input_menu(GtkMenuToolButton * button,WBCGtk * wbcg)2012 cb_accept_input_menu (GtkMenuToolButton *button, WBCGtk *wbcg)
2013 {
2014 	GtkWidget *menu = gtk_menu_tool_button_get_menu (button);
2015 	GList     *l, *children = gtk_container_get_children (GTK_CONTAINER (menu));
2016 
2017 	struct AcceptInputMenu {
2018 		gchar const *text;
2019 		void (*function) (WBCGtk *wbcg);
2020 		gboolean (*sensitive) (WBCGtk *wbcg);
2021 	} const accept_input_actions [] = {
2022 		{ N_("Enter in current cell"),       cb_accept_input,
2023 		  NULL },
2024 		{ N_("Enter in current cell without autocorrection"), cb_accept_input_wo_ac,
2025 		  NULL },
2026 /* 		{ N_("Enter on all non-hidden sheets"), cb_accept_input_sheets,  */
2027 /* 		  cb_accept_input_menu_sensitive_sheets}, */
2028 /* 		{ N_("Enter on multiple sheets..."), cb_accept_input_selected_sheets,  */
2029 /* 		  cb_accept_input_menu_sensitive_selected_sheets }, */
2030 		{ NULL,                              NULL, NULL },
2031 		{ N_("Enter in current range merged"), cb_accept_input_selected_merged,
2032 		  cb_accept_input_menu_sensitive_selected_merged },
2033 		{ NULL,                              NULL, NULL },
2034 		{ N_("Enter in selected ranges"), cb_accept_input_selected_cells,
2035 		  cb_accept_input_menu_sensitive_selected_cells },
2036 		{ N_("Enter in selected ranges as array"), cb_accept_input_array,
2037 		  cb_accept_input_menu_sensitive_selected_cells },
2038 	};
2039 	unsigned int ui;
2040 	GtkWidget *item;
2041 	const struct AcceptInputMenu *it;
2042 
2043 	if (children == NULL)
2044 		for (ui = 0; ui < G_N_ELEMENTS (accept_input_actions); ui++) {
2045 			it = accept_input_actions + ui;
2046 
2047 			if (it->text) {
2048 				item = gtk_image_menu_item_new_with_label
2049 					(_(it->text));
2050 				if (it->function)
2051 					g_signal_connect_swapped
2052 						(G_OBJECT (item), "activate",
2053 						 G_CALLBACK (it->function),
2054 						 wbcg);
2055 				if (wbcg->editing_sheet) {
2056 					if (it->sensitive)
2057 						gtk_widget_set_sensitive
2058 							(item, (it->sensitive) (wbcg));
2059 					else
2060 						gtk_widget_set_sensitive (item, TRUE);
2061 				} else
2062 					gtk_widget_set_sensitive (item, FALSE);
2063 			} else
2064 				item = gtk_separator_menu_item_new ();
2065 			gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2066 			gtk_widget_show (item);
2067 		}
2068 	else
2069 		for (ui = 0, l = children;
2070 		     ui < G_N_ELEMENTS (accept_input_actions) && l != NULL;
2071 		     ui++, l = l->next) {
2072 			it = accept_input_actions + ui;
2073 			if (wbcg->editing_sheet) {
2074 				if (it->sensitive)
2075 					gtk_widget_set_sensitive
2076 						(GTK_WIDGET (l->data),
2077 						 (it->sensitive) (wbcg));
2078 				else
2079 					gtk_widget_set_sensitive
2080 						(GTK_WIDGET (l->data), TRUE);
2081 			} else
2082 					gtk_widget_set_sensitive (l->data, FALSE);
2083 		}
2084 
2085 
2086 	g_list_free (children);
2087 }
2088 
2089 static gboolean
cb_editline_focus_in(GtkWidget * w,GdkEventFocus * event,WBCGtk * wbcg)2090 cb_editline_focus_in (GtkWidget *w, GdkEventFocus *event,
2091 		      WBCGtk *wbcg)
2092 {
2093 	if (!wbcg_is_editing (wbcg))
2094 		if (!wbcg_edit_start (wbcg, FALSE, TRUE)) {
2095 #if 0
2096 			GtkEntry *entry = GTK_ENTRY (w);
2097 #endif
2098 			wbcg_focus_cur_scg (wbcg);
2099 #warning GTK3: what can we do there for gtk3?
2100 #if 0
2101 			entry->in_drag = FALSE;
2102 			/*
2103 			 * ->button is private, ugh.  Since the text area
2104 			 * never gets a release event, there seems to be
2105 			 * no official way of returning the widget to its
2106 			 * correct state.
2107 			 */
2108 			entry->button = 0;
2109 #endif
2110 			return TRUE;
2111 		}
2112 
2113 	return FALSE;
2114 }
2115 
2116 static void
cb_statusbox_activate(GtkEntry * entry,WBCGtk * wbcg)2117 cb_statusbox_activate (GtkEntry *entry, WBCGtk *wbcg)
2118 {
2119 	WorkbookControl *wbc = GNM_WBC (wbcg);
2120 	wb_control_parse_and_jump (wbc, gtk_entry_get_text (entry));
2121 	wbcg_focus_cur_scg (wbcg);
2122 	wb_view_selection_desc (wb_control_view (wbc), TRUE, wbc);
2123 }
2124 
2125 static gboolean
cb_statusbox_focus(GtkEntry * entry,GdkEventFocus * event,WBCGtk * wbcg)2126 cb_statusbox_focus (GtkEntry *entry, GdkEventFocus *event,
2127 		    WBCGtk *wbcg)
2128 {
2129 	gtk_editable_select_region (GTK_EDITABLE (entry), 0, 0);
2130 	return FALSE;
2131 }
2132 
2133 /******************************************************************************/
2134 
2135 static void
dump_size_tree(GtkWidget * w,gpointer indent_)2136 dump_size_tree (GtkWidget *w, gpointer indent_)
2137 {
2138 	int indent = GPOINTER_TO_INT (indent_);
2139 	int h1, h2;
2140 	GtkAllocation a;
2141 
2142 	g_printerr ("%*s", indent, "");
2143 	if (gtk_widget_get_name (w))
2144 		g_printerr ("\"%s\" ", gtk_widget_get_name (w));
2145 
2146 	gtk_widget_get_preferred_height (w, &h1, &h2);
2147 	gtk_widget_get_allocation (w, &a);
2148 
2149 	g_printerr ("%s %p viz=%d act=%dx%d minheight=%d natheight=%d\n",
2150 		    g_type_name_from_instance ((GTypeInstance *)w), w,
2151 		    gtk_widget_get_visible (w),
2152 		    a.width, a.height,
2153 		    h1, h2);
2154 
2155 	if (GTK_IS_CONTAINER (w)) {
2156 		gtk_container_foreach (GTK_CONTAINER (w),
2157 				       dump_size_tree,
2158 				       GINT_TO_POINTER (indent + 2));
2159 	}
2160 }
2161 
2162 
2163 static void
dump_colrow_sizes(Sheet * sheet)2164 dump_colrow_sizes (Sheet *sheet)
2165 {
2166 	static const char *what[2] = { "col", "row" };
2167 	int pass;
2168 	for (pass = 0; pass < 2; pass++) {
2169 		gboolean is_cols = (pass == 0);
2170 		ColRowCollection *crc = is_cols ? &sheet->cols : &sheet->rows;
2171 		int i;
2172 
2173 		g_printerr ("Dumping %s sizes, max_used=%d\n",
2174 			    what[pass], crc->max_used);
2175 		for (i = -1; i <= crc->max_used; i++) {
2176 			ColRowInfo const *cri = (i >= 0)
2177 				? sheet_colrow_get (sheet, i, is_cols)
2178 				: sheet_colrow_get_default (sheet, is_cols);
2179 			g_printerr ("%s %5d : ", what[pass], i);
2180 			if (cri == NULL) {
2181 				g_printerr ("default\n");
2182 			} else {
2183 				g_printerr ("pts=%-6g  px=%-3d%s%s%s%s%s%s\n",
2184 					    cri->size_pts, cri->size_pixels,
2185 					    cri->is_default ? "  def" : "",
2186 					    cri->is_collapsed ? "  clps" : "",
2187 					    cri->hard_size ? "  hard" : "",
2188 					    cri->visible ? "  viz" : "",
2189 					    cri->in_filter ? "  filt" : "",
2190 					    cri->in_advanced_filter ? "  afilt" : "");
2191 			}
2192 		}
2193 	}
2194 }
2195 
2196 
2197 static void
cb_workbook_debug_info(WBCGtk * wbcg)2198 cb_workbook_debug_info (WBCGtk *wbcg)
2199 {
2200 	Workbook *wb = wb_control_get_workbook (GNM_WBC (wbcg));
2201 
2202 	if (gnm_debug_flag ("notebook-size"))
2203 		dump_size_tree (GTK_WIDGET (wbcg_toplevel (wbcg)), GINT_TO_POINTER (0));
2204 
2205 	if (gnm_debug_flag ("deps")) {
2206 		dependents_dump (wb);
2207 	}
2208 
2209 	if (gnm_debug_flag ("colrow")) {
2210 		dump_colrow_sizes (wbcg_cur_sheet (wbcg));
2211 	}
2212 
2213 	if (gnm_debug_flag ("expr-sharer")) {
2214 		GnmExprSharer *es = workbook_share_expressions (wb, FALSE);
2215 		gnm_expr_sharer_report (es);
2216 		gnm_expr_sharer_unref (es);
2217 	}
2218 
2219 	if (gnm_debug_flag ("style-optimize")) {
2220 		workbook_optimize_style (wb);
2221 	}
2222 
2223 	if (gnm_debug_flag ("sheet-conditions")) {
2224 		WORKBOOK_FOREACH_SHEET(wb, sheet, {
2225 			sheet_conditions_dump (sheet);
2226 		});
2227 	}
2228 
2229 	if (gnm_debug_flag ("name-collections")) {
2230 		gnm_named_expr_collection_dump (wb->names, "workbook");
2231 		WORKBOOK_FOREACH_SHEET(wb, sheet, {
2232 			gnm_named_expr_collection_dump (sheet->names,
2233 							sheet->name_unquoted);
2234 		});
2235 	}
2236 }
2237 
2238 static void
cb_autofunction(WBCGtk * wbcg)2239 cb_autofunction (WBCGtk *wbcg)
2240 {
2241 	GtkEntry *entry;
2242 	gchar const *txt;
2243 
2244 	if (wbcg_is_editing (wbcg))
2245 		return;
2246 
2247 	entry = wbcg_get_entry (wbcg);
2248 	txt = gtk_entry_get_text (entry);
2249 	if (strncmp (txt, "=", 1)) {
2250 		if (!wbcg_edit_start (wbcg, TRUE, TRUE))
2251 			return; /* attempt to edit failed */
2252 		gtk_entry_set_text (entry, "=");
2253 		gtk_editable_set_position (GTK_EDITABLE (entry), 1);
2254 	} else {
2255 		if (!wbcg_edit_start (wbcg, FALSE, TRUE))
2256 			return; /* attempt to edit failed */
2257 
2258 		/* FIXME : This is crap!
2259 		 * When the function druid is more complete use that.
2260 		 */
2261 		gtk_editable_set_position (GTK_EDITABLE (entry),
2262 					   gtk_entry_get_text_length (entry)-1);
2263 	}
2264 }
2265 
2266 /*
2267  * We must not crash on focus=NULL. We're called like that as a result of
2268  * gtk_window_set_focus (toplevel, NULL) if the first sheet view is destroyed
2269  * just after being created. This happens e.g when we cancel a file import or
2270  * the import fails.
2271  */
2272 static void
cb_set_focus(GtkWindow * window,GtkWidget * focus,WBCGtk * wbcg)2273 cb_set_focus (GtkWindow *window, GtkWidget *focus, WBCGtk *wbcg)
2274 {
2275 	if (focus && !gtk_window_get_focus (window))
2276 		wbcg_focus_cur_scg (wbcg);
2277 }
2278 
2279 /***************************************************************************/
2280 
2281 static gboolean
cb_scroll_wheel(GtkWidget * w,GdkEventScroll * event,WBCGtk * wbcg)2282 cb_scroll_wheel (GtkWidget *w, GdkEventScroll *event,
2283 		 WBCGtk *wbcg)
2284 {
2285 	SheetControlGUI *scg = wbcg_get_scg (wbcg, wbcg_focus_cur_scg (wbcg));
2286 	Sheet		*sheet = scg_sheet (scg);
2287 	/* scroll always operates on pane 0 */
2288 	GnmPane *pane = scg_pane (scg, 0);
2289 	gboolean go_horiz = (event->direction == GDK_SCROLL_LEFT ||
2290 			     event->direction == GDK_SCROLL_RIGHT);
2291 	gboolean go_back = (event->direction == GDK_SCROLL_UP ||
2292 			    event->direction == GDK_SCROLL_LEFT);
2293 
2294 	if (!pane ||
2295 	    !gtk_widget_get_realized (w) ||
2296 	    event->direction == GDK_SCROLL_SMOOTH)
2297 		return FALSE;
2298 
2299 	if ((event->state & GDK_SHIFT_MASK))
2300 		go_horiz = !go_horiz;
2301 
2302 	if ((event->state & GDK_CONTROL_MASK)) {	/* zoom */
2303 		int zoom = (int)(sheet->last_zoom_factor_used * 100. + .5) - 10;
2304 
2305 		if ((zoom % 15) != 0) {
2306 			zoom = 15 * (int)(zoom/15);
2307 			if (go_back)
2308 				zoom += 15;
2309 		} else {
2310 			if (go_back)
2311 				zoom += 15;
2312 			else
2313 				zoom -= 15;
2314 		}
2315 
2316 		if (0 <= zoom && zoom <= 390)
2317 			cmd_zoom (GNM_WBC (wbcg), g_slist_append (NULL, sheet),
2318 				  (double) (zoom + 10) / 100);
2319 	} else if (go_horiz) {
2320 		int col = (pane->last_full.col - pane->first.col) / 4;
2321 		if (col < 1)
2322 			col = 1;
2323 		if (go_back)
2324 			col = pane->first.col - col;
2325 		else
2326 			col = pane->first.col + col;
2327 		scg_set_left_col (pane->simple.scg, col);
2328 	} else {
2329 		int row = (pane->last_full.row - pane->first.row) / 4;
2330 		if (row < 1)
2331 			row = 1;
2332 		if (go_back)
2333 			row = pane->first.row - row;
2334 		else
2335 			row = pane->first.row + row;
2336 		scg_set_top_row (pane->simple.scg, row);
2337 	}
2338 	return TRUE;
2339 }
2340 
2341 /*
2342  * Make current control size the default. Toplevel would resize
2343  * spontaneously. This makes it stay the same size until user resizes.
2344  */
2345 static void
cb_realize(GtkWindow * toplevel,WBCGtk * wbcg)2346 cb_realize (GtkWindow *toplevel, WBCGtk *wbcg)
2347 {
2348 	GtkAllocation ta;
2349 
2350 	g_return_if_fail (GTK_IS_WINDOW (toplevel));
2351 
2352 	gtk_widget_get_allocation (GTK_WIDGET (toplevel), &ta);
2353 	gtk_window_set_default_size (toplevel, ta.width, ta.height);
2354 
2355 	/* if we are already initialized set the focus.  Without this loading a
2356 	 * multpage book sometimes leaves focus on the last book rather than
2357 	 * the current book.  Which leads to a slew of errors for keystrokes
2358 	 * until focus is corrected.
2359 	 */
2360 	if (wbcg->snotebook) {
2361 		wbcg_focus_cur_scg (wbcg);
2362 		wbcg_update_menu_feedback (wbcg, wbcg_cur_sheet (wbcg));
2363 	}
2364 }
2365 
2366 static void
cb_css_parse_error(GtkCssProvider * css,GtkCssSection * section,GError * err)2367 cb_css_parse_error (GtkCssProvider *css, GtkCssSection *section, GError *err)
2368 {
2369 	if (g_error_matches (err, GTK_CSS_PROVIDER_ERROR,
2370 			     GTK_CSS_PROVIDER_ERROR_DEPRECATED) &&
2371 	    !gnm_debug_flag ("css"))
2372 		return;
2373 
2374 	g_warning ("Theme parsing error: %s", err->message);
2375 }
2376 
2377 struct css_provider_data {
2378 	GtkCssProvider *css;
2379 	GSList *screens;
2380 };
2381 
2382 static void
cb_unload_providers(gpointer data_)2383 cb_unload_providers (gpointer data_)
2384 {
2385 	struct css_provider_data *data = data_;
2386 	GSList *l;
2387 
2388 	for (l = data->screens; l; l = l->next) {
2389 		GdkScreen *screen = l->data;
2390 		gtk_style_context_remove_provider_for_screen
2391 			(screen, GTK_STYLE_PROVIDER (data->css));
2392 	}
2393 	g_slist_free (data->screens);
2394 	g_object_unref (data->css);
2395 	g_free (data);
2396 }
2397 
2398 static void
cb_screen_changed(GtkWidget * widget)2399 cb_screen_changed (GtkWidget *widget)
2400 {
2401 	GdkScreen *screen = gtk_widget_get_screen (widget);
2402 	GObject *app = gnm_app_get_app ();
2403 	const char *app_key = "css-provider";
2404 	struct css_provider_data *data;
2405 
2406 	data = g_object_get_data (app, app_key);
2407 	if (!data) {
2408 		gboolean debug = gnm_debug_flag ("css");
2409 		gboolean q_dark = gnm_theme_is_dark (widget);
2410 		const char *resource = "/org/gnumeric/gnumeric/ui/gnumeric.css";
2411 		GBytes *cssbytes;
2412 		char *csstext;
2413 		GHashTable *vars = g_hash_table_new (g_str_hash, g_str_equal);
2414 
2415 		cssbytes = g_resources_lookup_data (resource, 0, NULL);
2416 		if (q_dark)
2417 			g_hash_table_insert (vars,
2418 					     (gpointer)"DARK",
2419 					     (gpointer)"1");
2420 		csstext = gnm_cpp (g_bytes_get_data (cssbytes, NULL), vars);
2421 
2422 		data = g_new (struct css_provider_data, 1);
2423 		data->css = gtk_css_provider_new ();
2424 		data->screens = NULL;
2425 
2426 		if (debug)
2427 			g_printerr ("Loading style from %s\n", resource);
2428 		else
2429 			g_signal_connect (data->css, "parsing-error",
2430 					  G_CALLBACK (cb_css_parse_error),
2431 					  NULL);
2432 
2433 		gtk_css_provider_load_from_data	(data->css, csstext, -1, NULL);
2434 		g_object_set_data_full (app, app_key, data, cb_unload_providers);
2435 		g_bytes_unref (cssbytes);
2436 		g_free (csstext);
2437 	}
2438 
2439 	if (screen && !g_slist_find (data->screens, screen)) {
2440 		gtk_style_context_add_provider_for_screen
2441 			(screen,
2442 			 GTK_STYLE_PROVIDER (data->css),
2443 			 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2444 		data->screens = g_slist_prepend (data->screens, screen);
2445 	}
2446 }
2447 
2448 void
wbcg_set_status_text(WBCGtk * wbcg,char const * text)2449 wbcg_set_status_text (WBCGtk *wbcg, char const *text)
2450 {
2451 	g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
2452 	gtk_statusbar_pop (GTK_STATUSBAR (wbcg->status_text), 0);
2453 	gtk_statusbar_push (GTK_STATUSBAR (wbcg->status_text), 0, text);
2454 }
2455 
2456 static void
set_visibility(WBCGtk * wbcg,char const * action_name,gboolean visible)2457 set_visibility (WBCGtk *wbcg,
2458 		char const *action_name,
2459 		gboolean visible)
2460 {
2461 	GtkWidget *w = g_hash_table_lookup (wbcg->visibility_widgets, action_name);
2462 	if (w)
2463 		gtk_widget_set_visible (w, visible);
2464 	wbc_gtk_set_toggle_action_state (wbcg, action_name, visible);
2465 }
2466 
2467 
2468 void
wbcg_toggle_visibility(WBCGtk * wbcg,GtkToggleAction * action)2469 wbcg_toggle_visibility (WBCGtk *wbcg, GtkToggleAction *action)
2470 {
2471 	if (!wbcg->updating_ui && wbcg_ui_update_begin (wbcg)) {
2472 		char const *name = gtk_action_get_name (GTK_ACTION (action));
2473 		set_visibility (wbcg, name,
2474 				gtk_toggle_action_get_active (action));
2475 		wbcg_ui_update_end (wbcg);
2476 	}
2477 }
2478 
2479 static void
cb_visibility(char const * action,GtkWidget * orig_widget,WBCGtk * new_wbcg)2480 cb_visibility (char const *action, GtkWidget *orig_widget, WBCGtk *new_wbcg)
2481 {
2482 	set_visibility (new_wbcg, action, gtk_widget_get_visible (orig_widget));
2483 }
2484 
2485 void
wbcg_copy_toolbar_visibility(WBCGtk * new_wbcg,WBCGtk * wbcg)2486 wbcg_copy_toolbar_visibility (WBCGtk *new_wbcg,
2487 			      WBCGtk *wbcg)
2488 {
2489 	g_hash_table_foreach (wbcg->visibility_widgets,
2490 		(GHFunc)cb_visibility, new_wbcg);
2491 }
2492 
2493 
2494 void
wbcg_set_end_mode(WBCGtk * wbcg,gboolean flag)2495 wbcg_set_end_mode (WBCGtk *wbcg, gboolean flag)
2496 {
2497 	g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
2498 
2499 	if (!wbcg->last_key_was_end != !flag) {
2500 		const char *txt = flag ? _("END") : "";
2501 		wbcg_set_status_text (wbcg, txt);
2502 		wbcg->last_key_was_end = flag;
2503 	}
2504 }
2505 
2506 static PangoFontDescription *
settings_get_font_desc(GtkSettings * settings)2507 settings_get_font_desc (GtkSettings *settings)
2508 {
2509 	PangoFontDescription *font_desc;
2510 	char *font_str;
2511 
2512 	g_object_get (settings, "gtk-font-name", &font_str, NULL);
2513 	font_desc = pango_font_description_from_string (
2514 		font_str ? font_str : "sans 10");
2515 	g_free (font_str);
2516 
2517 	return font_desc;
2518 }
2519 
2520 static void
cb_update_item_bar_font(GtkWidget * w)2521 cb_update_item_bar_font (GtkWidget *w)
2522 {
2523 	SheetControlGUI *scg = get_scg (w);
2524 	sc_resize ((SheetControl *)scg, TRUE);
2525 }
2526 
2527 static void
cb_desktop_font_changed(GtkSettings * settings,GParamSpec * pspec,WBCGtk * wbcg)2528 cb_desktop_font_changed (GtkSettings *settings, GParamSpec *pspec,
2529 			 WBCGtk *wbcg)
2530 {
2531 	if (wbcg->font_desc)
2532 		pango_font_description_free (wbcg->font_desc);
2533 	wbcg->font_desc = settings_get_font_desc (settings);
2534 	gtk_container_foreach (GTK_CONTAINER (wbcg->snotebook),
2535 			       (GtkCallback)cb_update_item_bar_font, NULL);
2536 }
2537 
2538 static GdkScreen *
wbcg_get_screen(WBCGtk * wbcg)2539 wbcg_get_screen (WBCGtk *wbcg)
2540 {
2541 	return gtk_widget_get_screen (wbcg->notebook_area);
2542 }
2543 
2544 static GtkSettings *
wbcg_get_gtk_settings(WBCGtk * wbcg)2545 wbcg_get_gtk_settings (WBCGtk *wbcg)
2546 {
2547 	return gtk_settings_get_for_screen (wbcg_get_screen (wbcg));
2548 }
2549 
2550 /* ------------------------------------------------------------------------- */
2551 
2552 static int
show_gui(WBCGtk * wbcg)2553 show_gui (WBCGtk *wbcg)
2554 {
2555 	SheetControlGUI *scg;
2556 	WorkbookView *wbv = wb_control_view (GNM_WBC (wbcg));
2557 	int sx, sy;
2558 	gdouble fx, fy;
2559 	GdkRectangle rect;
2560 	GdkScreen *screen = wbcg_get_screen (wbcg);
2561 
2562 	/* In a Xinerama setup, we want the geometry of the actual display
2563 	 * unit, if available. See bug 59902.  */
2564 	gdk_screen_get_monitor_geometry (screen, 0, &rect);
2565 	sx = MAX (rect.width, 600);
2566 	sy = MAX (rect.height, 200);
2567 
2568 	fx = gnm_conf_get_core_gui_window_x ();
2569 	fy = gnm_conf_get_core_gui_window_y ();
2570 
2571 	/* Successfully parsed geometry string and urged WM to comply */
2572 	if (NULL != wbcg->preferred_geometry && NULL != wbcg->toplevel &&
2573 	    gtk_window_parse_geometry (GTK_WINDOW (wbcg->toplevel),
2574 				       wbcg->preferred_geometry)) {
2575 		g_free (wbcg->preferred_geometry);
2576 		wbcg->preferred_geometry = NULL;
2577 	} else if (wbcg->snotebook != NULL &&
2578 		   wbv != NULL &&
2579 		   (wbv->preferred_width > 0 || wbv->preferred_height > 0)) {
2580 		/* Set grid size to preferred width */
2581 		int pwidth = MIN(wbv->preferred_width, gdk_screen_get_width (screen));
2582 		int pheight = MIN(wbv->preferred_height, gdk_screen_get_height (screen));
2583 		GtkRequisition requisition;
2584 
2585 		pwidth = pwidth > 0 ? pwidth : -1;
2586 		pheight = pheight > 0 ? pheight : -1;
2587 		gtk_widget_set_size_request (GTK_WIDGET (wbcg->notebook_area),
2588 					     pwidth, pheight);
2589 		gtk_widget_get_preferred_size (GTK_WIDGET (wbcg->toplevel),
2590 					 &requisition, NULL);
2591 		/* We want to test if toplevel is bigger than screen.
2592 		 * gtk_widget_size_request tells us the space
2593 		 * allocated to the  toplevel proper, but not how much is
2594 		 * need for WM decorations or a possible panel.
2595 		 *
2596 		 * The test below should very rarely maximize when there is
2597 		 * actually room on the screen.
2598 		 *
2599 		 * We maximize instead of resizing for two reasons:
2600 		 * - The preferred width / height is restored with one click on
2601 		 *   unmaximize.
2602 		 * - We don't have to guess what size we should resize to.
2603 		 */
2604 		if (requisition.height + 20 > rect.height ||
2605 		    requisition.width > rect.width) {
2606 			gtk_window_maximize (GTK_WINDOW (wbcg->toplevel));
2607 		} else {
2608 			gtk_window_set_default_size
2609 				(wbcg_toplevel (wbcg),
2610 				 requisition.width, requisition.height);
2611 		}
2612 	} else {
2613 		/* Use default */
2614 		gtk_window_set_default_size (wbcg_toplevel (wbcg), sx * fx, sy * fy);
2615 	}
2616 
2617 	scg = wbcg_cur_scg (wbcg);
2618 	if (scg)
2619 		wbcg_set_direction (scg);
2620 
2621 	gtk_widget_show (GTK_WIDGET (wbcg_toplevel (wbcg)));
2622 
2623 	/* rehide headers if necessary */
2624 	if (NULL != scg && wbcg_cur_sheet (wbcg))
2625 		scg_adjust_preferences (scg);
2626 
2627 	gtk_widget_set_size_request (GTK_WIDGET (wbcg->notebook_area),
2628 				     -1, -1);
2629 	return FALSE;
2630 }
2631 
2632 static GtkWidget *
wbcg_get_label_for_position(WBCGtk * wbcg,GtkWidget * source,gint x)2633 wbcg_get_label_for_position (WBCGtk *wbcg, GtkWidget *source,
2634 			     gint x)
2635 {
2636 	guint n, i;
2637 	GtkWidget *last_visible = NULL;
2638 
2639 	g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), NULL);
2640 
2641 	n = wbcg_get_n_scg (wbcg);
2642 	for (i = 0; i < n; i++) {
2643 		GtkWidget *label = gnm_notebook_get_nth_label (wbcg->bnotebook, i);
2644 		int x0, x1;
2645 		GtkAllocation la;
2646 
2647 		if (!gtk_widget_get_visible (label))
2648 			continue;
2649 
2650 		gtk_widget_get_allocation (label, &la);
2651 		x0 = la.x;
2652 		x1 = x0 + la.width;
2653 
2654 		if (x <= x1) {
2655 			/*
2656 			 * We are left of this label's right edge.  Use it
2657 			 * even if we are far left of the label.
2658 			 */
2659 			return label;
2660 		}
2661 
2662 		last_visible = label;
2663 	}
2664 
2665 	return last_visible;
2666 }
2667 
2668 static gboolean
wbcg_is_local_drag(WBCGtk * wbcg,GtkWidget * source_widget)2669 wbcg_is_local_drag (WBCGtk *wbcg, GtkWidget *source_widget)
2670 {
2671 	GtkWidget *top = (GtkWidget *)wbcg_toplevel (wbcg);
2672 	return GNM_IS_PANE (source_widget) &&
2673 		gtk_widget_get_toplevel (source_widget) == top;
2674 }
2675 static gboolean
cb_wbcg_drag_motion(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,WBCGtk * wbcg)2676 cb_wbcg_drag_motion (GtkWidget *widget, GdkDragContext *context,
2677 		     gint x, gint y, guint time, WBCGtk *wbcg)
2678 {
2679 	GtkWidget *source_widget = gtk_drag_get_source_widget (context);
2680 
2681 	if (GNM_IS_NOTEBOOK (gtk_widget_get_parent (source_widget))) {
2682 		/* The user wants to reorder sheets. We simulate a
2683 		 * drag motion over a label.
2684 		 */
2685 		GtkWidget *label = wbcg_get_label_for_position (wbcg, source_widget, x);
2686 		return cb_sheet_label_drag_motion (label, context, x, y,
2687 						   time, wbcg);
2688 	} else if (wbcg_is_local_drag (wbcg, source_widget))
2689 		gnm_pane_object_autoscroll (GNM_PANE (source_widget),
2690 			context, x, y, time);
2691 
2692 	return TRUE;
2693 }
2694 
2695 static void
cb_wbcg_drag_leave(GtkWidget * widget,GdkDragContext * context,guint time,WBCGtk * wbcg)2696 cb_wbcg_drag_leave (GtkWidget *widget, GdkDragContext *context,
2697 		    guint time, WBCGtk *wbcg)
2698 {
2699 	GtkWidget *source_widget = gtk_drag_get_source_widget (context);
2700 
2701 	g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
2702 
2703 	if (GNM_IS_NOTEBOOK (gtk_widget_get_parent (source_widget)))
2704 		gtk_widget_hide (
2705 			g_object_get_data (G_OBJECT (source_widget), "arrow"));
2706 	else if (wbcg_is_local_drag (wbcg, source_widget))
2707 		gnm_pane_slide_stop (GNM_PANE (source_widget));
2708 }
2709 
2710 static void
cb_wbcg_drag_data_received(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection_data,guint info,guint time,WBCGtk * wbcg)2711 cb_wbcg_drag_data_received (GtkWidget *widget, GdkDragContext *context,
2712 			    gint x, gint y, GtkSelectionData *selection_data,
2713 			    guint info, guint time, WBCGtk *wbcg)
2714 {
2715 	gchar *target_type = gdk_atom_name (gtk_selection_data_get_target (selection_data));
2716 
2717 	if (!strcmp (target_type, "text/uri-list")) { /* filenames from nautilus */
2718 		scg_drag_data_received (wbcg_cur_scg (wbcg),
2719 			 gtk_drag_get_source_widget (context), 0, 0,
2720 			 selection_data);
2721 	} else if (!strcmp (target_type, "GNUMERIC_SHEET")) {
2722 		/* The user wants to reorder the sheets but hasn't dropped
2723 		 * the sheet onto a label. Never mind. We figure out
2724 		 * where the arrow is currently located and simulate a drop
2725 		 * on that label.  */
2726 		GtkWidget *label = wbcg_get_label_for_position (wbcg,
2727 			gtk_drag_get_source_widget (context), x);
2728 		cb_sheet_label_drag_data_received (label, context, x, y,
2729 						   selection_data, info, time, wbcg);
2730 	} else {
2731 		GtkWidget *source_widget = gtk_drag_get_source_widget (context);
2732 		if (wbcg_is_local_drag (wbcg, source_widget))
2733 			g_printerr ("autoscroll complete - stop it\n");
2734 		else
2735 			scg_drag_data_received (wbcg_cur_scg (wbcg),
2736 				source_widget, 0, 0, selection_data);
2737 	}
2738 	g_free (target_type);
2739 }
2740 
cb_cs_go_up(WBCGtk * wbcg)2741 static void cb_cs_go_up  (WBCGtk *wbcg)
2742 { wb_control_navigate_to_cell (GNM_WBC (wbcg), navigator_top); }
cb_cs_go_down(WBCGtk * wbcg)2743 static void cb_cs_go_down  (WBCGtk *wbcg)
2744 { wb_control_navigate_to_cell (GNM_WBC (wbcg), navigator_bottom); }
cb_cs_go_left(WBCGtk * wbcg)2745 static void cb_cs_go_left  (WBCGtk *wbcg)
2746 { wb_control_navigate_to_cell (GNM_WBC (wbcg), navigator_first); }
cb_cs_go_right(WBCGtk * wbcg)2747 static void cb_cs_go_right  (WBCGtk *wbcg)
2748 { wb_control_navigate_to_cell (GNM_WBC (wbcg), navigator_last); }
cb_cs_go_to_cell(WBCGtk * wbcg)2749 static void cb_cs_go_to_cell  (WBCGtk *wbcg) { dialog_goto_cell (wbcg); }
2750 
2751 static void
wbc_gtk_cell_selector_popup(G_GNUC_UNUSED GtkEntry * entry,G_GNUC_UNUSED GtkEntryIconPosition icon_pos,G_GNUC_UNUSED GdkEvent * event,gpointer data)2752 wbc_gtk_cell_selector_popup (G_GNUC_UNUSED GtkEntry *entry,
2753 			     G_GNUC_UNUSED GtkEntryIconPosition icon_pos,
2754 			     G_GNUC_UNUSED GdkEvent *event,
2755 			     gpointer data)
2756 {
2757 	if (event->type == GDK_BUTTON_PRESS) {
2758 		WBCGtk *wbcg = data;
2759 
2760 		struct CellSelectorMenu {
2761 			gchar const *text;
2762 			void (*function) (WBCGtk *wbcg);
2763 		} const cell_selector_actions [] = {
2764 			{ N_("Go to Top"),      &cb_cs_go_up      },
2765 			{ N_("Go to Bottom"),   &cb_cs_go_down    },
2766 			{ N_("Go to First"),    &cb_cs_go_left    },
2767 			{ N_("Go to Last"),     &cb_cs_go_right   },
2768 			{ NULL, NULL },
2769 			{ N_("Go to Cell..."),  &cb_cs_go_to_cell }
2770 		};
2771 		unsigned int ui;
2772 		GtkWidget *item, *menu = gtk_menu_new ();
2773 		gboolean active = (!wbcg_is_editing (wbcg) &&
2774 				   NULL == wbc_gtk_get_guru (wbcg));
2775 
2776 		for (ui = 0; ui < G_N_ELEMENTS (cell_selector_actions); ui++) {
2777 			const struct CellSelectorMenu *it =
2778 				cell_selector_actions + ui;
2779 			if (it->text)
2780 				item = gtk_image_menu_item_new_with_label
2781 					(_(it->text));
2782 			else
2783 				item = gtk_separator_menu_item_new ();
2784 
2785 			if (it->function)
2786 				g_signal_connect_swapped
2787 					(G_OBJECT (item), "activate",
2788 					 G_CALLBACK (it->function), wbcg);
2789 			gtk_widget_set_sensitive (item, active);
2790 			gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2791 			gtk_widget_show (item);
2792 		}
2793 
2794 		gnumeric_popup_menu (GTK_MENU (menu), event);
2795 	}
2796 }
2797 
2798 static void
wbc_gtk_create_edit_area(WBCGtk * wbcg)2799 wbc_gtk_create_edit_area (WBCGtk *wbcg)
2800 {
2801 	GtkToolItem *item;
2802 	GtkEntry *entry;
2803 	int len;
2804 	GtkWidget *debug_button;
2805 
2806 	wbc_gtk_init_editline (wbcg);
2807 	entry = wbcg_get_entry (wbcg);
2808 
2809 	/* Set a reasonable width for the selection box. */
2810 	len = gnm_widget_measure_string
2811 		(GTK_WIDGET (wbcg_toplevel (wbcg)),
2812 		 cell_coord_name (GNM_MAX_COLS - 1, GNM_MAX_ROWS - 1));
2813 	/*
2814 	 * Add a little extra since font might be proportional and since
2815 	 * we also put user defined names there.
2816 	 */
2817 	len = len * 3 / 2;
2818 	gtk_widget_set_size_request (wbcg->selection_descriptor, len, -1);
2819 
2820 	g_signal_connect_swapped (wbcg->cancel_button,
2821 				  "clicked", G_CALLBACK (cb_cancel_input),
2822 				  wbcg);
2823 
2824 	g_signal_connect_swapped (wbcg->ok_button,
2825 				  "clicked", G_CALLBACK (cb_accept_input),
2826 				  wbcg);
2827 	gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (wbcg->ok_button),
2828 				       gtk_menu_new ());
2829 	gtk_menu_tool_button_set_arrow_tooltip_text
2830 		(GTK_MENU_TOOL_BUTTON (wbcg->ok_button),
2831 		 _("Accept change in multiple cells"));
2832 	g_signal_connect (wbcg->ok_button,
2833 			  "show-menu", G_CALLBACK (cb_accept_input_menu),
2834 			  wbcg);
2835 
2836 	g_signal_connect_swapped (wbcg->func_button,
2837 				  "clicked", G_CALLBACK (cb_autofunction),
2838 				  wbcg);
2839 
2840 	/* Dependency debugger */
2841 	debug_button = GET_GUI_ITEM ("debug_button");
2842 	if (gnm_debug_flag ("notebook-size") ||
2843 	    gnm_debug_flag ("deps") ||
2844 	    gnm_debug_flag ("colrow") ||
2845 	    gnm_debug_flag ("expr-sharer") ||
2846 	    gnm_debug_flag ("style-optimize") ||
2847 	    gnm_debug_flag ("sheet-conditions") ||
2848 	    gnm_debug_flag ("name-collections")) {
2849 		g_signal_connect_swapped (debug_button,
2850 					  "clicked", G_CALLBACK (cb_workbook_debug_info),
2851 					  wbcg);
2852 	} else {
2853 		gtk_widget_destroy (debug_button);
2854 	}
2855 
2856 	item = GET_GUI_ITEM ("edit_line_entry_item");
2857 	gtk_container_add (GTK_CONTAINER (item),
2858 			   GTK_WIDGET (wbcg->edit_line.entry));
2859 	gtk_widget_show_all (GTK_WIDGET (item));
2860 
2861 	/* Do signal setup for the editing input line */
2862 	g_signal_connect (G_OBJECT (entry),
2863 		"focus-in-event",
2864 		G_CALLBACK (cb_editline_focus_in), wbcg);
2865 
2866 	/* status box */
2867 	g_signal_connect (G_OBJECT (wbcg->selection_descriptor),
2868 		"activate",
2869 		G_CALLBACK (cb_statusbox_activate), wbcg);
2870 	g_signal_connect (G_OBJECT (wbcg->selection_descriptor),
2871 		"focus-out-event",
2872 		G_CALLBACK (cb_statusbox_focus), wbcg);
2873 
2874 	gtk_entry_set_icon_from_icon_name
2875 		(GTK_ENTRY (wbcg->selection_descriptor),
2876 		 GTK_ENTRY_ICON_SECONDARY, "go-jump");
2877 	gtk_entry_set_icon_sensitive
2878 		(GTK_ENTRY (wbcg->selection_descriptor),
2879 		 GTK_ENTRY_ICON_SECONDARY, TRUE);
2880 	gtk_entry_set_icon_activatable
2881 		(GTK_ENTRY (wbcg->selection_descriptor),
2882 		 GTK_ENTRY_ICON_SECONDARY, TRUE);
2883 
2884 	g_signal_connect (G_OBJECT (wbcg->selection_descriptor),
2885 			  "icon-press",
2886 			  G_CALLBACK
2887 			  (wbc_gtk_cell_selector_popup),
2888 			  wbcg);
2889 }
2890 
2891 static int
wbcg_validation_msg(WorkbookControl * wbc,ValidationStyle v,char const * title,char const * msg)2892 wbcg_validation_msg (WorkbookControl *wbc, ValidationStyle v,
2893 		     char const *title, char const *msg)
2894 {
2895 	WBCGtk *wbcg = (WBCGtk *)wbc;
2896 	ValidationStatus res0, res1 = GNM_VALIDATION_STATUS_VALID; /* supress warning */
2897 	char const *btn0, *btn1;
2898 	GtkMessageType  type;
2899 	GtkWidget  *dialog;
2900 	int response;
2901 
2902 	switch (v) {
2903 	case GNM_VALIDATION_STYLE_STOP:
2904 		res0 = GNM_VALIDATION_STATUS_INVALID_EDIT;
2905 		btn0 = _("_Re-Edit");
2906 		res1 = GNM_VALIDATION_STATUS_INVALID_DISCARD;
2907 		btn1 = _("_Discard");
2908 		type = GTK_MESSAGE_ERROR;
2909 		break;
2910 	case GNM_VALIDATION_STYLE_WARNING:
2911 		res0 = GNM_VALIDATION_STATUS_VALID;
2912 		btn0 = _("_Accept");
2913 		res1 = GNM_VALIDATION_STATUS_INVALID_DISCARD;
2914 		btn1 = _("_Discard");
2915 		type = GTK_MESSAGE_WARNING;
2916 		break;
2917 	case GNM_VALIDATION_STYLE_INFO:
2918 		res0 = GNM_VALIDATION_STATUS_VALID;
2919 		btn0 = GNM_STOCK_OK;
2920 		btn1 = NULL;
2921 		type = GTK_MESSAGE_INFO;
2922 		break;
2923 	case GNM_VALIDATION_STYLE_PARSE_ERROR:
2924 		res0 = GNM_VALIDATION_STATUS_INVALID_EDIT;
2925 		btn0 = _("_Re-Edit");
2926 		res1 = GNM_VALIDATION_STATUS_VALID;
2927 		btn1 = _("_Accept");
2928 		type = GTK_MESSAGE_ERROR;
2929 		break;
2930 
2931 	default:
2932 		g_assert_not_reached ();
2933 	}
2934 
2935 	dialog = gtk_message_dialog_new (wbcg_toplevel (wbcg),
2936 		GTK_DIALOG_DESTROY_WITH_PARENT,
2937 		type, GTK_BUTTONS_NONE, "%s", msg);
2938 	gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2939 		btn0, GTK_RESPONSE_YES,
2940 		btn1, GTK_RESPONSE_NO,
2941 		NULL);
2942 	/* TODO : what to use if nothing is specified ? */
2943 	/* TODO : do we want the document name here too ? */
2944 	if (title)
2945 		gtk_window_set_title (GTK_WINDOW (dialog), title);
2946 	gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_NO);
2947 	response = go_gtk_dialog_run (GTK_DIALOG (dialog),
2948 				      wbcg_toplevel (wbcg));
2949 	return ((response == GTK_RESPONSE_NO || response == GTK_RESPONSE_CANCEL) ? res1 : res0);
2950 }
2951 
2952 #define DISCONNECT(obj,field)						\
2953 	if (wbcg->field) {						\
2954 		if (obj)						\
2955 			g_signal_handler_disconnect (obj, wbcg->field);	\
2956 		wbcg->field = 0;					\
2957 	}
2958 
2959 static void
wbcg_view_changed(WBCGtk * wbcg,G_GNUC_UNUSED GParamSpec * pspec,Workbook * old_wb)2960 wbcg_view_changed (WBCGtk *wbcg,
2961 		   G_GNUC_UNUSED GParamSpec *pspec,
2962 		   Workbook *old_wb)
2963 {
2964 	WorkbookControl *wbc = GNM_WBC (wbcg);
2965 	Workbook *wb = wb_control_get_workbook (wbc);
2966 	WorkbookView *wbv = wb_control_view (wbc);
2967 
2968 	/* Reconnect self because we need to change data.  */
2969 	DISCONNECT (wbc, sig_view_changed);
2970 	wbcg->sig_view_changed =
2971 		g_signal_connect_object
2972 		(G_OBJECT (wbc),
2973 		 "notify::view",
2974 		 G_CALLBACK (wbcg_view_changed),
2975 		 wb,
2976 		 0);
2977 
2978 	DISCONNECT (wbcg->sig_wbv, sig_auto_expr_text);
2979 	DISCONNECT (wbcg->sig_wbv, sig_auto_expr_attrs);
2980 	DISCONNECT (wbcg->sig_wbv, sig_show_horizontal_scrollbar);
2981 	DISCONNECT (wbcg->sig_wbv, sig_show_vertical_scrollbar);
2982 	DISCONNECT (wbcg->sig_wbv, sig_show_notebook_tabs);
2983 	if (wbcg->sig_wbv)
2984 		g_object_remove_weak_pointer (wbcg->sig_wbv,
2985 					      &wbcg->sig_wbv);
2986 	wbcg->sig_wbv = wbv;
2987 	if (wbv) {
2988 		g_object_add_weak_pointer (wbcg->sig_wbv,
2989 					   &wbcg->sig_wbv);
2990 		wbcg->sig_auto_expr_text =
2991 			g_signal_connect_object
2992 			(G_OBJECT (wbv),
2993 			 "notify::auto-expr-value",
2994 			 G_CALLBACK (wbcg_auto_expr_value_changed),
2995 			 wbcg,
2996 			 0);
2997 		wbcg_auto_expr_value_changed (wbv, NULL, wbcg);
2998 
2999 		wbcg->sig_show_horizontal_scrollbar =
3000 			g_signal_connect_object
3001 			(G_OBJECT (wbv),
3002 			 "notify::show-horizontal-scrollbar",
3003 			 G_CALLBACK (wbcg_scrollbar_visibility),
3004 			 wbcg,
3005 			 0);
3006 		wbcg->sig_show_vertical_scrollbar =
3007 			g_signal_connect_object
3008 			(G_OBJECT (wbv),
3009 			 "notify::show-vertical-scrollbar",
3010 			 G_CALLBACK (wbcg_scrollbar_visibility),
3011 			 wbcg,
3012 			 0);
3013 		wbcg->sig_show_notebook_tabs =
3014 			g_signal_connect_object
3015 			(G_OBJECT (wbv),
3016 			 "notify::show-notebook-tabs",
3017 			 G_CALLBACK (wbcg_notebook_tabs_visibility),
3018 			 wbcg,
3019 			 0);
3020 		wbcg_notebook_tabs_visibility (wbv, NULL, wbcg);
3021 	}
3022 
3023 	DISCONNECT (old_wb, sig_sheet_order);
3024 	DISCONNECT (old_wb, sig_notify_uri);
3025 	DISCONNECT (old_wb, sig_notify_dirty);
3026 
3027 	if (wb) {
3028 		wbcg->sig_sheet_order =
3029 			g_signal_connect_object
3030 			(G_OBJECT (wb),
3031 			 "sheet-order-changed",
3032 			 G_CALLBACK (wbcg_sheet_order_changed),
3033 			 wbcg, G_CONNECT_SWAPPED);
3034 
3035 		wbcg->sig_notify_uri =
3036 			g_signal_connect_object
3037 			(G_OBJECT (wb),
3038 			 "notify::uri",
3039 			 G_CALLBACK (wbcg_update_title),
3040 			 wbcg, G_CONNECT_SWAPPED);
3041 
3042 		wbcg->sig_notify_dirty =
3043 			g_signal_connect_object
3044 			(G_OBJECT (wb),
3045 			 "notify::dirty",
3046 			 G_CALLBACK (wbcg_update_title),
3047 			 wbcg, G_CONNECT_SWAPPED);
3048 
3049 		wbcg_update_title (wbcg);
3050 	}
3051 }
3052 
3053 #undef DISCONNECT
3054 
3055 /***************************************************************************/
3056 
3057 static GOActionComboStack *
ur_stack(WorkbookControl * wbc,gboolean is_undo)3058 ur_stack (WorkbookControl *wbc, gboolean is_undo)
3059 {
3060 	WBCGtk *wbcg = (WBCGtk *)wbc;
3061 	return is_undo ? wbcg->undo_haction : wbcg->redo_haction;
3062 }
3063 
3064 static void
wbc_gtk_undo_redo_truncate(WorkbookControl * wbc,int n,gboolean is_undo)3065 wbc_gtk_undo_redo_truncate (WorkbookControl *wbc, int n, gboolean is_undo)
3066 {
3067 	go_action_combo_stack_truncate (ur_stack (wbc, is_undo), n);
3068 }
3069 
3070 static void
wbc_gtk_undo_redo_pop(WorkbookControl * wbc,gboolean is_undo)3071 wbc_gtk_undo_redo_pop (WorkbookControl *wbc, gboolean is_undo)
3072 {
3073 	go_action_combo_stack_pop (ur_stack (wbc, is_undo), 1);
3074 }
3075 
3076 static void
wbc_gtk_undo_redo_push(WorkbookControl * wbc,gboolean is_undo,char const * text,gpointer key)3077 wbc_gtk_undo_redo_push (WorkbookControl *wbc, gboolean is_undo,
3078 			char const *text, gpointer key)
3079 {
3080 	go_action_combo_stack_push (ur_stack (wbc, is_undo), text, key);
3081 }
3082 
3083 /****************************************************************************/
3084 
3085 static void
set_font_name_feedback(GtkAction * act,const char * family)3086 set_font_name_feedback (GtkAction *act, const char *family)
3087 {
3088 	PangoFontDescription *desc = pango_font_description_new ();
3089 	pango_font_description_set_family (desc, family);
3090 	wbcg_font_action_set_font_desc (act, desc);
3091 	pango_font_description_free (desc);
3092 }
3093 
3094 static void
set_font_size_feedback(GtkAction * act,double size)3095 set_font_size_feedback (GtkAction *act, double size)
3096 {
3097 	PangoFontDescription *desc = pango_font_description_new ();
3098 	pango_font_description_set_size (desc, size * PANGO_SCALE);
3099 	wbcg_font_action_set_font_desc (act, desc);
3100 	pango_font_description_free (desc);
3101 }
3102 
3103 /****************************************************************************/
3104 
3105 static WorkbookControl *
wbc_gtk_control_new(G_GNUC_UNUSED WorkbookControl * wbc,WorkbookView * wbv,Workbook * wb,gpointer extra)3106 wbc_gtk_control_new (G_GNUC_UNUSED WorkbookControl *wbc,
3107 		     WorkbookView *wbv,
3108 		     Workbook *wb,
3109 		     gpointer extra)
3110 {
3111 	return (WorkbookControl *)wbc_gtk_new (wbv, wb,
3112 		extra ? GDK_SCREEN (extra) : NULL, NULL);
3113 }
3114 
3115 static void
wbc_gtk_init_state(WorkbookControl * wbc)3116 wbc_gtk_init_state (WorkbookControl *wbc)
3117 {
3118 	WorkbookView *wbv  = wb_control_view (wbc);
3119 	WBCGtk       *wbcg = WBC_GTK (wbc);
3120 
3121 	/* Share a colour history for all a view's controls */
3122 	go_action_combo_color_set_group (wbcg->back_color, wbv);
3123 	go_action_combo_color_set_group (wbcg->fore_color, wbv);
3124 }
3125 
3126 static void
wbc_gtk_style_feedback_real(WorkbookControl * wbc,GnmStyle const * changes)3127 wbc_gtk_style_feedback_real (WorkbookControl *wbc, GnmStyle const *changes)
3128 {
3129 	WorkbookView	*wb_view = wb_control_view (wbc);
3130 	WBCGtk		*wbcg = (WBCGtk *)wbc;
3131 
3132 	g_return_if_fail (wb_view != NULL);
3133 
3134 	if (!wbcg_ui_update_begin (WBC_GTK (wbc)))
3135 		return;
3136 
3137 	if (changes == NULL)
3138 		changes = wb_view->current_style;
3139 
3140 	if (gnm_style_is_element_set (changes, MSTYLE_FONT_BOLD))
3141 		gtk_toggle_action_set_active (wbcg->font.bold,
3142 			gnm_style_get_font_bold (changes));
3143 	if (gnm_style_is_element_set (changes, MSTYLE_FONT_ITALIC))
3144 		gtk_toggle_action_set_active (wbcg->font.italic,
3145 			gnm_style_get_font_italic (changes));
3146 	if (gnm_style_is_element_set (changes, MSTYLE_FONT_UNDERLINE)) {
3147 		gtk_toggle_action_set_active (wbcg->font.underline,
3148 			gnm_style_get_font_uline (changes) == UNDERLINE_SINGLE);
3149 		gtk_toggle_action_set_active (wbcg->font.d_underline,
3150 			gnm_style_get_font_uline (changes) == UNDERLINE_DOUBLE);
3151 		gtk_toggle_action_set_active (wbcg->font.sl_underline,
3152 			gnm_style_get_font_uline (changes) == UNDERLINE_SINGLE_LOW);
3153 		gtk_toggle_action_set_active (wbcg->font.dl_underline,
3154 			gnm_style_get_font_uline (changes) == UNDERLINE_DOUBLE_LOW);
3155 	}
3156 	if (gnm_style_is_element_set (changes, MSTYLE_FONT_STRIKETHROUGH))
3157 		gtk_toggle_action_set_active (wbcg->font.strikethrough,
3158 			gnm_style_get_font_strike (changes));
3159 
3160 	if (gnm_style_is_element_set (changes, MSTYLE_FONT_SCRIPT)) {
3161 		gtk_toggle_action_set_active (wbcg->font.superscript,
3162 			gnm_style_get_font_script (changes) == GO_FONT_SCRIPT_SUPER);
3163 		gtk_toggle_action_set_active (wbcg->font.subscript,
3164 			gnm_style_get_font_script (changes) == GO_FONT_SCRIPT_SUB);
3165 	} else {
3166 		gtk_toggle_action_set_active (wbcg->font.superscript, FALSE);
3167 		gtk_toggle_action_set_active (wbcg->font.subscript, FALSE);
3168 	}
3169 
3170 	if (gnm_style_is_element_set (changes, MSTYLE_ALIGN_H)) {
3171 		GnmHAlign align = gnm_style_get_align_h (changes);
3172 		gtk_toggle_action_set_active (wbcg->h_align.left,
3173 			align == GNM_HALIGN_LEFT);
3174 		gtk_toggle_action_set_active (wbcg->h_align.center,
3175 			align == GNM_HALIGN_CENTER);
3176 		gtk_toggle_action_set_active (wbcg->h_align.right,
3177 			align == GNM_HALIGN_RIGHT);
3178 		gtk_toggle_action_set_active (wbcg->h_align.center_across_selection,
3179 			align == GNM_HALIGN_CENTER_ACROSS_SELECTION);
3180 		go_action_combo_pixmaps_select_id (wbcg->halignment, align);
3181 	}
3182 	if (gnm_style_is_element_set (changes, MSTYLE_ALIGN_V)) {
3183 		GnmVAlign align = gnm_style_get_align_v (changes);
3184 		gtk_toggle_action_set_active (wbcg->v_align.top,
3185 			align == GNM_VALIGN_TOP);
3186 		gtk_toggle_action_set_active (wbcg->v_align.bottom,
3187 			align == GNM_VALIGN_BOTTOM);
3188 		gtk_toggle_action_set_active (wbcg->v_align.center,
3189 			align == GNM_VALIGN_CENTER);
3190 		go_action_combo_pixmaps_select_id (wbcg->valignment, align);
3191 	}
3192 
3193 	if (gnm_style_is_element_set (changes, MSTYLE_FONT_SIZE)) {
3194 		set_font_size_feedback (wbcg->font_name_haction,
3195 					gnm_style_get_font_size (changes));
3196 		set_font_size_feedback (wbcg->font_name_vaction,
3197 					gnm_style_get_font_size (changes));
3198 	}
3199 
3200 	if (gnm_style_is_element_set (changes, MSTYLE_FONT_NAME)) {
3201 		set_font_name_feedback (wbcg->font_name_haction,
3202 					gnm_style_get_font_name (changes));
3203 		set_font_name_feedback (wbcg->font_name_vaction,
3204 					gnm_style_get_font_name (changes));
3205 	}
3206 
3207 	wbcg_ui_update_end (WBC_GTK (wbc));
3208 }
3209 
3210 static gint
cb_wbc_gtk_style_feedback(WBCGtk * gtk)3211 cb_wbc_gtk_style_feedback (WBCGtk *gtk)
3212 {
3213 	wbc_gtk_style_feedback_real ((WorkbookControl *)gtk, NULL);
3214 	gtk->idle_update_style_feedback = 0;
3215 	return FALSE;
3216 }
3217 static void
wbc_gtk_style_feedback(WorkbookControl * wbc,GnmStyle const * changes)3218 wbc_gtk_style_feedback (WorkbookControl *wbc, GnmStyle const *changes)
3219 {
3220 	WBCGtk *wbcg = (WBCGtk *)wbc;
3221 
3222 	if (changes)
3223 		wbc_gtk_style_feedback_real (wbc, changes);
3224 	else if (0 == wbcg->idle_update_style_feedback)
3225 		wbcg->idle_update_style_feedback = g_timeout_add (200,
3226 			(GSourceFunc) cb_wbc_gtk_style_feedback, wbc);
3227 }
3228 
3229 static void
cb_handlebox_dock_status(GtkHandleBox * hb,GtkToolbar * toolbar,gpointer pattached)3230 cb_handlebox_dock_status (GtkHandleBox *hb,
3231 			  GtkToolbar *toolbar, gpointer pattached)
3232 {
3233 	gboolean attached = GPOINTER_TO_INT (pattached);
3234 	gtk_toolbar_set_show_arrow (toolbar, attached);
3235 }
3236 
3237 static char const *
get_accel_label(GtkMenuItem * item,guint * key)3238 get_accel_label (GtkMenuItem *item, guint *key)
3239 {
3240 	GList *children = gtk_container_get_children (GTK_CONTAINER (item));
3241 	GList *l;
3242 	char const *res = NULL;
3243 
3244 	*key = GDK_KEY_VoidSymbol;
3245 	for (l = children; l; l = l->next) {
3246 		GtkWidget *w = l->data;
3247 
3248 		if (GTK_IS_ACCEL_LABEL (w)) {
3249 			*key = gtk_label_get_mnemonic_keyval (GTK_LABEL (w));
3250 			res = gtk_label_get_label (GTK_LABEL (w));
3251 			break;
3252 		}
3253 	}
3254 
3255 	g_list_free (children);
3256 	return res;
3257 }
3258 
3259 static void
check_underlines(GtkWidget * w,char const * path)3260 check_underlines (GtkWidget *w, char const *path)
3261 {
3262 	GList *children = gtk_container_get_children (GTK_CONTAINER (w));
3263 	GHashTable *used = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_free);
3264 	GList *l;
3265 
3266 	for (l = children; l; l = l->next) {
3267 		GtkMenuItem *item = GTK_MENU_ITEM (l->data);
3268 		GtkWidget *sub = gtk_menu_item_get_submenu (item);
3269 		guint key;
3270 		char const *label = get_accel_label (item, &key);
3271 
3272 		if (sub) {
3273 			char *newpath = g_strconcat (path, *path ? "->" : "", label, NULL);
3274 			check_underlines (sub, newpath);
3275 			g_free (newpath);
3276 		}
3277 
3278 		if (key != GDK_KEY_VoidSymbol) {
3279 			char const *prev = g_hash_table_lookup (used, GUINT_TO_POINTER (key));
3280 			if (prev) {
3281 				/* xgettext: Translators: if this warning shows up when
3282 				 * running Gnumeric in your locale, the underlines need
3283 				 * to be moved in strings representing menu entries.
3284 				 * One slightly tricky point here is that in certain cases,
3285 				 * the same menu entry shows up in more than one menu.
3286 				 */
3287 				g_warning (_("In the `%s' menu, the key `%s' is used for both `%s' and `%s'."),
3288 					   path, gdk_keyval_name (key), prev, label);
3289 			} else
3290 				g_hash_table_insert (used, GUINT_TO_POINTER (key), g_strdup (label));
3291 		}
3292 	}
3293 
3294 	g_list_free (children);
3295 	g_hash_table_destroy (used);
3296 }
3297 
3298 /****************************************************************************/
3299 /* window list menu */
3300 
3301 static void
cb_window_menu_activate(GObject * action,WBCGtk * wbcg)3302 cb_window_menu_activate (GObject *action, WBCGtk *wbcg)
3303 {
3304 	gtk_window_present (wbcg_toplevel (wbcg));
3305 }
3306 
3307 static unsigned
regenerate_window_menu(WBCGtk * gtk,Workbook * wb,unsigned i)3308 regenerate_window_menu (WBCGtk *gtk, Workbook *wb, unsigned i)
3309 {
3310 	int k, count;
3311 	char *basename = GO_DOC (wb)->uri
3312 		? go_basename_from_uri (GO_DOC (wb)->uri)
3313 		: NULL;
3314 
3315 	/* How many controls are there?  */
3316 	count = 0;
3317 	WORKBOOK_FOREACH_CONTROL (wb, wbv, wbc, {
3318 		if (GNM_IS_WBC_GTK (wbc))
3319 			count++;
3320 	});
3321 
3322 	k = 1;
3323 	WORKBOOK_FOREACH_CONTROL (wb, wbv, wbc, {
3324 		if (i >= 20)
3325 			return i;
3326 		if (GNM_IS_WBC_GTK (wbc) && basename) {
3327 			GString *label = g_string_new (NULL);
3328 			char *name;
3329 			char const *s;
3330 			GtkActionEntry entry;
3331 
3332 			if (i < 10) g_string_append_c (label, '_');
3333 			g_string_append_printf (label, "%d ", i);
3334 
3335 			for (s = basename; *s; s++) {
3336 				if (*s == '_')
3337 					g_string_append_c (label, '_');
3338 				g_string_append_c (label, *s);
3339 			}
3340 
3341 			if (count > 1)
3342 				g_string_append_printf (label, " #%d", k++);
3343 
3344 			entry.name = name = g_strdup_printf ("WindowListEntry%d", i);
3345 			entry.stock_id = NULL;
3346 			entry.label = label->str;
3347 			entry.accelerator = NULL;
3348 			entry.tooltip = NULL;
3349 			entry.callback = G_CALLBACK (cb_window_menu_activate);
3350 
3351 			gtk_action_group_add_actions (gtk->windows.actions,
3352 				&entry, 1, wbc);
3353 
3354 			g_string_free (label, TRUE);
3355 			g_free (name);
3356 			i++;
3357 		}});
3358 	g_free (basename);
3359 	return i;
3360 }
3361 
3362 static void
cb_regenerate_window_menu(WBCGtk * gtk)3363 cb_regenerate_window_menu (WBCGtk *gtk)
3364 {
3365 	Workbook *wb = wb_control_get_workbook (GNM_WBC (gtk));
3366 	GList const *ptr;
3367 	unsigned i;
3368 
3369 	/* This can happen during exit.  */
3370 	if (!wb)
3371 		return;
3372 
3373 	if (gtk->windows.merge_id != 0)
3374 		gtk_ui_manager_remove_ui (gtk->ui, gtk->windows.merge_id);
3375 	gtk->windows.merge_id = gtk_ui_manager_new_merge_id (gtk->ui);
3376 
3377 	if (gtk->windows.actions != NULL) {
3378 		gtk_ui_manager_remove_action_group (gtk->ui,
3379 			gtk->windows.actions);
3380 		g_object_unref (gtk->windows.actions);
3381 	}
3382 	gtk->windows.actions = gtk_action_group_new ("WindowList");
3383 
3384 	gtk_ui_manager_insert_action_group (gtk->ui, gtk->windows.actions, 0);
3385 
3386 	/* create the actions */
3387 	i = regenerate_window_menu (gtk, wb, 1); /* current wb first */
3388 	for (ptr = gnm_app_workbook_list (); ptr != NULL ; ptr = ptr->next)
3389 		if (ptr->data != wb)
3390 			i = regenerate_window_menu (gtk, ptr->data, i);
3391 
3392 	/* merge them in */
3393 	while (i-- > 1) {
3394 		char *name = g_strdup_printf ("WindowListEntry%d", i);
3395 		gtk_ui_manager_add_ui (gtk->ui, gtk->windows.merge_id,
3396 			"/menubar/View/Windows", name, name,
3397 			GTK_UI_MANAGER_AUTO, TRUE);
3398 		g_free (name);
3399 	}
3400 }
3401 
3402 typedef struct {
3403 	GtkActionGroup *actions;
3404 	guint		merge_id;
3405 } CustomUIHandle;
3406 
3407 static void
cb_custom_ui_handler(GObject * gtk_action,WorkbookControl * wbc)3408 cb_custom_ui_handler (GObject *gtk_action, WorkbookControl *wbc)
3409 {
3410 	GnmAction *action = g_object_get_data (gtk_action, "GnmAction");
3411 
3412 	g_return_if_fail (action != NULL);
3413 	g_return_if_fail (action->handler != NULL);
3414 
3415 	action->handler (action, wbc, action->data);
3416 }
3417 
3418 static void
cb_add_custom_ui(G_GNUC_UNUSED GnmApp * app,GnmAppExtraUI * extra_ui,WBCGtk * gtk)3419 cb_add_custom_ui (G_GNUC_UNUSED GnmApp *app,
3420 		  GnmAppExtraUI *extra_ui, WBCGtk *gtk)
3421 {
3422 	CustomUIHandle  *details;
3423 	GSList		*ptr;
3424 	GError          *error = NULL;
3425 	const char *ui_substr;
3426 
3427 	details = g_new0 (CustomUIHandle, 1);
3428 	details->actions = gtk_action_group_new (extra_ui->group_name);
3429 
3430 	for (ptr = extra_ui->actions; ptr != NULL ; ptr = ptr->next) {
3431 		GnmAction *action = ptr->data;
3432 		GtkAction *res;
3433 		GtkActionEntry entry;
3434 
3435 		entry.name = action->id;
3436 		entry.stock_id = action->icon_name;
3437 		entry.label = action->label;
3438 		entry.accelerator = NULL;
3439 		entry.tooltip = NULL;
3440 		entry.callback = G_CALLBACK (cb_custom_ui_handler);
3441 		gtk_action_group_add_actions (details->actions, &entry, 1, gtk);
3442 		res = gtk_action_group_get_action (details->actions, action->id);
3443 		g_object_set_data (G_OBJECT (res), "GnmAction", action);
3444 	}
3445 	gtk_ui_manager_insert_action_group (gtk->ui, details->actions, 0);
3446 
3447 	ui_substr = strstr (extra_ui->layout, "<ui>");
3448 	if (ui_substr == extra_ui->layout)
3449 		ui_substr = NULL;
3450 
3451 	details->merge_id = gtk_ui_manager_add_ui_from_string
3452 		(gtk->ui, extra_ui->layout, -1, ui_substr ? NULL : &error);
3453 	if (details->merge_id == 0 && ui_substr) {
3454 		/* Work around bug 569724.  */
3455 		details->merge_id = gtk_ui_manager_add_ui_from_string
3456 			(gtk->ui, ui_substr, -1, &error);
3457 	}
3458 
3459 	if (error) {
3460 		g_message ("building menus failed: %s", error->message);
3461 		g_error_free (error);
3462 		gtk_ui_manager_remove_action_group (gtk->ui, details->actions);
3463 		g_object_unref (details->actions);
3464 		g_free (details);
3465 	} else {
3466 		g_hash_table_insert (gtk->custom_uis, extra_ui, details);
3467 	}
3468 }
3469 static void
cb_remove_custom_ui(G_GNUC_UNUSED GnmApp * app,GnmAppExtraUI * extra_ui,WBCGtk * gtk)3470 cb_remove_custom_ui (G_GNUC_UNUSED GnmApp *app,
3471 		     GnmAppExtraUI *extra_ui, WBCGtk *gtk)
3472 {
3473 	CustomUIHandle *details = g_hash_table_lookup (gtk->custom_uis, extra_ui);
3474 	if (NULL != details) {
3475 		gtk_ui_manager_remove_ui (gtk->ui, details->merge_id);
3476 		gtk_ui_manager_remove_action_group (gtk->ui, details->actions);
3477 		g_object_unref (details->actions);
3478 		g_hash_table_remove (gtk->custom_uis, extra_ui);
3479 	}
3480 }
3481 
3482 static void
cb_init_extra_ui(GnmAppExtraUI * extra_ui,WBCGtk * gtk)3483 cb_init_extra_ui (GnmAppExtraUI *extra_ui, WBCGtk *gtk)
3484 {
3485 	cb_add_custom_ui (NULL, extra_ui, gtk);
3486 }
3487 
3488 /****************************************************************************/
3489 /* Toolbar menu */
3490 
3491 static void
set_toolbar_style_for_position(GtkToolbar * tb,GtkPositionType pos)3492 set_toolbar_style_for_position (GtkToolbar *tb, GtkPositionType pos)
3493 {
3494 	GtkWidget *box = gtk_widget_get_parent (GTK_WIDGET (tb));
3495 
3496 	static const GtkOrientation orientations[] = {
3497 		GTK_ORIENTATION_VERTICAL, GTK_ORIENTATION_VERTICAL,
3498 		GTK_ORIENTATION_HORIZONTAL, GTK_ORIENTATION_HORIZONTAL
3499 	};
3500 
3501 	gtk_orientable_set_orientation (GTK_ORIENTABLE (tb),
3502 					orientations[pos]);
3503 
3504 	if (GTK_IS_HANDLE_BOX (box)) {
3505 		static const GtkPositionType hdlpos[] = {
3506 			GTK_POS_TOP, GTK_POS_TOP,
3507 			GTK_POS_LEFT, GTK_POS_LEFT
3508 		};
3509 
3510 		gtk_handle_box_set_handle_position (GTK_HANDLE_BOX (box),
3511 						    hdlpos[pos]);
3512 	}
3513 	if (pos == GTK_POS_TOP || pos == GTK_POS_BOTTOM)
3514 		g_object_set (G_OBJECT (tb), "hexpand", TRUE, "vexpand", FALSE, NULL);
3515 	else
3516 		g_object_set (G_OBJECT (tb), "vexpand", TRUE, "hexpand", FALSE, NULL);
3517 }
3518 
3519 static void
set_toolbar_position(GtkToolbar * tb,GtkPositionType pos,WBCGtk * gtk)3520 set_toolbar_position (GtkToolbar *tb, GtkPositionType pos, WBCGtk *gtk)
3521 {
3522 	GtkWidget *box = gtk_widget_get_parent (GTK_WIDGET (tb));
3523 	GtkContainer *zone = GTK_CONTAINER (gtk_widget_get_parent (GTK_WIDGET (box)));
3524 	GtkContainer *new_zone = GTK_CONTAINER (gtk->toolbar_zones[pos]);
3525 	char const *name = g_object_get_data (G_OBJECT (box), "name");
3526 	const char *key = "toolbar-order";
3527 	int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (box), key));
3528 	GList *children, *l;
3529 	int cpos = 0;
3530 
3531 	if (zone == new_zone)
3532 		return;
3533 
3534 	g_object_ref (box);
3535 	if (zone)
3536 		gtk_container_remove (zone, box);
3537 	set_toolbar_style_for_position (tb, pos);
3538 
3539 	children = gtk_container_get_children (new_zone);
3540 	for (l = children; l; l = l->next) {
3541 		GObject *child = l->data;
3542 		int nc = GPOINTER_TO_INT (g_object_get_data (child, key));
3543 		if (nc < n) cpos++;
3544 	}
3545 	g_list_free (children);
3546 
3547 	gtk_container_add (new_zone, box);
3548 	gtk_container_child_set (new_zone, box, "position", cpos, NULL);
3549 
3550 	g_object_unref (box);
3551 
3552 	if (zone && name)
3553 		gnm_conf_set_toolbar_position (name, pos);
3554 }
3555 
3556 static void
cb_set_toolbar_position(GtkMenuItem * item,WBCGtk * gtk)3557 cb_set_toolbar_position (GtkMenuItem *item, WBCGtk *gtk)
3558 {
3559 	GtkToolbar *tb = g_object_get_data (G_OBJECT (item), "toolbar");
3560 	GtkPositionType side = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "side"));
3561 
3562 	if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)))
3563 		set_toolbar_position (tb, side, gtk);
3564 }
3565 
3566 static void
cb_tcm_hide(GtkWidget * widget,GtkWidget * box)3567 cb_tcm_hide (GtkWidget *widget, GtkWidget *box)
3568 {
3569 	gtk_widget_hide (box);
3570 }
3571 
3572 static void
toolbar_context_menu(GtkToolbar * tb,WBCGtk * gtk,GdkEvent * event)3573 toolbar_context_menu (GtkToolbar *tb, WBCGtk *gtk, GdkEvent *event)
3574 {
3575 	GtkWidget *box = gtk_widget_get_parent (GTK_WIDGET (tb));
3576 	GtkWidget *zone = gtk_widget_get_parent (GTK_WIDGET (box));
3577 	GtkWidget *menu = gtk_menu_new ();
3578 	GtkWidget *item;
3579 	GSList *group = NULL;
3580 	size_t ui;
3581 
3582 	static const struct {
3583 		char const *text;
3584 		GtkPositionType pos;
3585 	} pos_items[] = {
3586 		{ N_("Display toolbar above sheets"), GTK_POS_TOP },
3587 		{ N_("Display toolbar to the left of sheets"), GTK_POS_LEFT },
3588 		{ N_("Display toolbar to the right of sheets"), GTK_POS_RIGHT }
3589 	};
3590 
3591 	if (gnm_debug_flag ("toolbar-size"))
3592 		dump_size_tree (GTK_WIDGET (tb), GINT_TO_POINTER (0));
3593 
3594 	for (ui = 0; ui < G_N_ELEMENTS (pos_items); ui++) {
3595 		char const *text = _(pos_items[ui].text);
3596 		GtkPositionType pos = pos_items[ui].pos;
3597 
3598 		item = gtk_radio_menu_item_new_with_label (group, text);
3599 		group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
3600 
3601 		gtk_check_menu_item_set_active
3602 			(GTK_CHECK_MENU_ITEM (item),
3603 			 (zone == gtk->toolbar_zones[pos]));
3604 
3605 		gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
3606 		g_object_set_data (G_OBJECT (item), "toolbar", tb);
3607 		g_object_set_data (G_OBJECT (item), "side", GINT_TO_POINTER (pos));
3608 		g_signal_connect (G_OBJECT (item), "activate",
3609 				  G_CALLBACK (cb_set_toolbar_position),
3610 				  gtk);
3611 	}
3612 
3613 	item = gtk_separator_menu_item_new ();
3614 	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
3615 
3616 	item = gtk_menu_item_new_with_label (_("Hide"));
3617 	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
3618 	g_signal_connect (G_OBJECT (item), "activate",
3619 			  G_CALLBACK (cb_tcm_hide),
3620 			  box);
3621 
3622 	gtk_widget_show_all (menu);
3623 	gnumeric_popup_menu (GTK_MENU (menu), event);
3624 }
3625 
3626 static gboolean
cb_toolbar_button_press(GtkToolbar * tb,GdkEvent * event,WBCGtk * gtk)3627 cb_toolbar_button_press (GtkToolbar *tb, GdkEvent *event, WBCGtk *gtk)
3628 {
3629 	if (event->type == GDK_BUTTON_PRESS &&
3630 	    event->button.button == 3) {
3631 		toolbar_context_menu (tb, gtk, event);
3632 		return TRUE;
3633 	}
3634 
3635 	return FALSE;
3636 }
3637 
3638 static gboolean
cb_handlebox_button_press(GtkHandleBox * hdlbox,GdkEvent * event,WBCGtk * gtk)3639 cb_handlebox_button_press (GtkHandleBox *hdlbox, GdkEvent *event, WBCGtk *gtk)
3640 {
3641 	if (event->type == GDK_BUTTON_PRESS &&
3642 	    event->button.button == 3) {
3643 		GtkToolbar *tb = GTK_TOOLBAR (gtk_bin_get_child (GTK_BIN (hdlbox)));
3644 		toolbar_context_menu (tb, gtk, event);
3645 		return TRUE;
3646 	}
3647 
3648 	return FALSE;
3649 }
3650 
3651 
3652 static void
cb_toolbar_activate(GtkToggleAction * action,WBCGtk * wbcg)3653 cb_toolbar_activate (GtkToggleAction *action, WBCGtk *wbcg)
3654 {
3655 	wbcg_toggle_visibility (wbcg, action);
3656 }
3657 
3658 static void
cb_toolbar_box_visible(GtkWidget * box,G_GNUC_UNUSED GParamSpec * pspec,WBCGtk * wbcg)3659 cb_toolbar_box_visible (GtkWidget *box, G_GNUC_UNUSED GParamSpec *pspec,
3660 			WBCGtk *wbcg)
3661 {
3662 	GtkToggleAction *toggle_action = g_object_get_data (
3663 		G_OBJECT (box), "toggle_action");
3664 	char const *name = g_object_get_data (G_OBJECT (box), "name");
3665 	gboolean visible = gtk_widget_get_visible (box);
3666 
3667 	gtk_toggle_action_set_active (toggle_action, visible);
3668 	if (!wbcg->is_fullscreen) {
3669 		/*
3670 		 * We do not persist changes made going-to/while-in/leaving
3671 		 * fullscreen mode.
3672 		 */
3673 		gnm_conf_set_toolbar_visible (name, visible);
3674 	}
3675 }
3676 
3677 static struct ToolbarInfo {
3678 	const char *name;
3679 	const char *menu_text;
3680 	const char *accel;
3681 } toolbar_info[] = {
3682 	{ "StandardToolbar", N_("Standard Toolbar"), "<control>7" },
3683 	{ "FormatToolbar", N_("Format Toolbar"), NULL },
3684 	{ "ObjectToolbar", N_("Object Toolbar"), NULL },
3685 	{ NULL, NULL, NULL }
3686 };
3687 
3688 
3689 static void
cb_add_menus_toolbars(G_GNUC_UNUSED GtkUIManager * ui,GtkWidget * w,WBCGtk * gtk)3690 cb_add_menus_toolbars (G_GNUC_UNUSED GtkUIManager *ui,
3691 		       GtkWidget *w, WBCGtk *gtk)
3692 {
3693 	if (GTK_IS_TOOLBAR (w)) {
3694 		WBCGtk *wbcg = (WBCGtk *)gtk;
3695 		char const *name = gtk_widget_get_name (w);
3696 		GtkToggleActionEntry entry;
3697 		char *toggle_name = g_strconcat ("ViewMenuToolbar", name, NULL);
3698 		char *tooltip = g_strdup_printf (_("Show/Hide toolbar %s"), _(name));
3699 		gboolean visible = gnm_conf_get_toolbar_visible (name);
3700 		int n = g_hash_table_size (wbcg->visibility_widgets);
3701 		GtkWidget *vw;
3702 		const struct ToolbarInfo *ti;
3703 		GtkWidget *box;
3704 		GtkPositionType pos = gnm_conf_get_toolbar_position (name);
3705 
3706 		// See bug 761142.  This isn't supposed to be necessary.
3707 		gtk_style_context_invalidate (gtk_widget_get_style_context (w));
3708 
3709 		if (gnm_conf_get_detachable_toolbars ()) {
3710 			box = gtk_handle_box_new ();
3711 			g_object_connect (box,
3712 				"signal::child_attached", G_CALLBACK (cb_handlebox_dock_status), GINT_TO_POINTER (TRUE),
3713 				"signal::child_detached", G_CALLBACK (cb_handlebox_dock_status), GINT_TO_POINTER (FALSE),
3714 				NULL);
3715 		} else
3716 			box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
3717 		g_signal_connect (G_OBJECT (w),
3718 				  "button_press_event",
3719 				  G_CALLBACK (cb_toolbar_button_press),
3720 				  gtk);
3721 		g_signal_connect (G_OBJECT (box),
3722 				  "button_press_event",
3723 				  G_CALLBACK (cb_handlebox_button_press),
3724 				  gtk);
3725 
3726 		gtk_container_add (GTK_CONTAINER (box), w);
3727 		gtk_widget_show_all (box);
3728 		if (!visible)
3729 			gtk_widget_hide (box);
3730 		g_object_set_data (G_OBJECT (box), "toolbar-order",
3731 				   GINT_TO_POINTER (n));
3732 		set_toolbar_position (GTK_TOOLBAR (w), pos, gtk);
3733 
3734 		g_signal_connect (box,
3735 				  "notify::visible",
3736 				  G_CALLBACK (cb_toolbar_box_visible),
3737 				  gtk);
3738 		g_object_set_data_full (G_OBJECT (box), "name",
3739 					g_strdup (name),
3740 					(GDestroyNotify)g_free);
3741 
3742 		vw = box;
3743 		g_hash_table_insert (wbcg->visibility_widgets,
3744 				     g_strdup (toggle_name),
3745 				     g_object_ref (vw));
3746 
3747 		gtk_toolbar_set_show_arrow (GTK_TOOLBAR (w), TRUE);
3748 		gtk_toolbar_set_style (GTK_TOOLBAR (w), GTK_TOOLBAR_ICONS);
3749 		gtk_toolbar_set_icon_size (GTK_TOOLBAR (w), GTK_ICON_SIZE_SMALL_TOOLBAR);
3750 
3751 		entry.name = toggle_name;
3752 		entry.stock_id = NULL;
3753 		entry.label = name;
3754 		entry.accelerator = NULL;
3755 		entry.tooltip = tooltip;
3756 		entry.callback = G_CALLBACK (cb_toolbar_activate);
3757 		entry.is_active = visible;
3758 
3759 		for (ti = toolbar_info; ti->name; ti++) {
3760 			if (strcmp (name, ti->name) == 0) {
3761 				entry.label = _(ti->menu_text);
3762 				entry.accelerator = ti->accel;
3763 				break;
3764 			}
3765 		}
3766 
3767 		gtk_action_group_add_toggle_actions (gtk->toolbar.actions,
3768 			&entry, 1, wbcg);
3769 		g_object_set_data (G_OBJECT (box), "toggle_action",
3770 			gtk_action_group_get_action (gtk->toolbar.actions, toggle_name));
3771 		gtk_ui_manager_add_ui (gtk->ui, gtk->toolbar.merge_id,
3772 			"/menubar/View/Toolbars", toggle_name, toggle_name,
3773 			GTK_UI_MANAGER_AUTO, FALSE);
3774 		wbcg->hide_for_fullscreen =
3775 			g_slist_prepend (wbcg->hide_for_fullscreen,
3776 					 gtk_action_group_get_action (gtk->toolbar.actions,
3777 								      toggle_name));
3778 
3779 		g_free (tooltip);
3780 		g_free (toggle_name);
3781 	} else {
3782 		gtk_box_pack_start (GTK_BOX (gtk->menu_zone), w, FALSE, TRUE, 0);
3783 		gtk_widget_show_all (w);
3784 	}
3785 }
3786 
3787 static void
cb_clear_menu_tip(GOCmdContext * cc)3788 cb_clear_menu_tip (GOCmdContext *cc)
3789 {
3790 	go_cmd_context_progress_message_set (cc, " ");
3791 }
3792 
3793 static void
cb_show_menu_tip(GtkWidget * proxy,GOCmdContext * cc)3794 cb_show_menu_tip (GtkWidget *proxy, GOCmdContext *cc)
3795 {
3796 	GtkAction *action = g_object_get_data (G_OBJECT (proxy), "GtkAction");
3797 	char *tip = NULL;
3798 	g_object_get (action, "tooltip", &tip, NULL);
3799 	if (tip) {
3800 		go_cmd_context_progress_message_set (cc, _(tip));
3801 		g_free (tip);
3802 	} else
3803 		cb_clear_menu_tip (cc);
3804 }
3805 
3806 static void
cb_connect_proxy(G_GNUC_UNUSED GtkUIManager * ui,GtkAction * action,GtkWidget * proxy,GOCmdContext * cc)3807 cb_connect_proxy (G_GNUC_UNUSED GtkUIManager *ui,
3808 		  GtkAction    *action,
3809 		  GtkWidget    *proxy,
3810 		  GOCmdContext *cc)
3811 {
3812 	/* connect whether there is a tip or not it may change later */
3813 	if (GTK_IS_MENU_ITEM (proxy)) {
3814 		g_object_set_data (G_OBJECT (proxy), "GtkAction", action);
3815 		g_object_connect (proxy,
3816 			"signal::select",  G_CALLBACK (cb_show_menu_tip), cc,
3817 			"swapped_signal::deselect", G_CALLBACK (cb_clear_menu_tip), cc,
3818 			NULL);
3819 	}
3820 }
3821 
3822 static void
cb_disconnect_proxy(G_GNUC_UNUSED GtkUIManager * ui,G_GNUC_UNUSED GtkAction * action,GtkWidget * proxy,GOCmdContext * cc)3823 cb_disconnect_proxy (G_GNUC_UNUSED GtkUIManager *ui,
3824 		     G_GNUC_UNUSED GtkAction    *action,
3825 		     GtkWidget    *proxy,
3826 		     GOCmdContext *cc)
3827 {
3828 	if (GTK_IS_MENU_ITEM (proxy)) {
3829 		g_object_set_data (G_OBJECT (proxy), "GtkAction", NULL);
3830 		g_object_disconnect (proxy,
3831 			"any_signal::select",  G_CALLBACK (cb_show_menu_tip), cc,
3832 			"any_signal::deselect", G_CALLBACK (cb_clear_menu_tip), cc,
3833 			NULL);
3834 	}
3835 }
3836 
3837 static void
cb_post_activate(G_GNUC_UNUSED GtkUIManager * manager,GtkAction * action,WBCGtk * wbcg)3838 cb_post_activate (G_GNUC_UNUSED GtkUIManager *manager, GtkAction *action, WBCGtk *wbcg)
3839 {
3840 	if (!wbcg_is_editing (wbcg) && strcmp(gtk_action_get_name (action), "EditGotoCellIndicator") != 0)
3841 		wbcg_focus_cur_scg (wbcg);
3842 }
3843 
3844 static void
cb_wbcg_window_state_event(GtkWidget * widget,GdkEventWindowState * event,WBCGtk * wbcg)3845 cb_wbcg_window_state_event (GtkWidget           *widget,
3846 			    GdkEventWindowState *event,
3847 			    WBCGtk  *wbcg)
3848 {
3849 	gboolean new_val = (event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) != 0;
3850 	if (!(event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) ||
3851 	    new_val == wbcg->is_fullscreen ||
3852 	    wbcg->updating_ui)
3853 		return;
3854 
3855 	wbc_gtk_set_toggle_action_state (wbcg, "ViewFullScreen", new_val);
3856 
3857 	if (new_val) {
3858 		GSList *l;
3859 
3860 		wbcg->is_fullscreen = TRUE;
3861 		for (l = wbcg->hide_for_fullscreen; l; l = l->next) {
3862 			GtkToggleAction *ta = l->data;
3863 			GOUndo *u;
3864 			gboolean active = gtk_toggle_action_get_active (ta);
3865 			u = go_undo_binary_new
3866 				(ta, GUINT_TO_POINTER (active),
3867 				 (GOUndoBinaryFunc)gtk_toggle_action_set_active,
3868 				 NULL, NULL);
3869 			wbcg->undo_for_fullscreen =
3870 				go_undo_combine (wbcg->undo_for_fullscreen, u);
3871 			gtk_toggle_action_set_active (ta, FALSE);
3872 		}
3873 	} else {
3874 		if (wbcg->undo_for_fullscreen) {
3875 			go_undo_undo (wbcg->undo_for_fullscreen);
3876 			g_object_unref (wbcg->undo_for_fullscreen);
3877 			wbcg->undo_for_fullscreen = NULL;
3878 		}
3879 		wbcg->is_fullscreen = FALSE;
3880 	}
3881 }
3882 
3883 /****************************************************************************/
3884 
3885 static void
cb_auto_expr_cell_changed(GtkWidget * item,WBCGtk * wbcg)3886 cb_auto_expr_cell_changed (GtkWidget *item, WBCGtk *wbcg)
3887 {
3888 	WorkbookView *wbv = wb_control_view (GNM_WBC (wbcg));
3889 	const GnmEvalPos *ep;
3890 	GnmExprTop const *texpr;
3891 	GnmValue const *v;
3892 
3893 	if (wbcg->updating_ui)
3894 		return;
3895 
3896 	ep = g_object_get_data (G_OBJECT (item), "evalpos");
3897 
3898 	g_object_set (wbv,
3899 		      "auto-expr-func", NULL,
3900 		      "auto-expr-descr", NULL,
3901 		      "auto-expr-eval-pos", ep,
3902 		      NULL);
3903 
3904 	/* Now we have the expression set.  */
3905 	texpr = wbv->auto_expr.dep.base.texpr;
3906 	v = gnm_expr_top_get_constant (texpr);
3907 	if (v)
3908 		g_object_set (wbv,
3909 			      "auto-expr-descr", value_peek_string (v),
3910 			      NULL);
3911 }
3912 
3913 static void
cb_auto_expr_changed(GtkWidget * item,WBCGtk * wbcg)3914 cb_auto_expr_changed (GtkWidget *item, WBCGtk *wbcg)
3915 {
3916 	const GnmFunc *func;
3917 	const char *descr;
3918 	WorkbookView *wbv = wb_control_view (GNM_WBC (wbcg));
3919 
3920 	if (wbcg->updating_ui)
3921 		return;
3922 
3923 	func = g_object_get_data (G_OBJECT (item), "func");
3924 	descr = g_object_get_data (G_OBJECT (item), "descr");
3925 
3926 	g_object_set (wbv,
3927 		      "auto-expr-func", func,
3928 		      "auto-expr-descr", descr,
3929 		      "auto-expr-eval-pos", NULL,
3930 		      NULL);
3931 }
3932 
3933 static void
cb_auto_expr_precision_toggled(GtkWidget * item,WBCGtk * wbcg)3934 cb_auto_expr_precision_toggled (GtkWidget *item, WBCGtk *wbcg)
3935 {
3936 	WorkbookView *wbv = wb_control_view (GNM_WBC (wbcg));
3937 	if (wbcg->updating_ui)
3938 		return;
3939 
3940 	go_object_toggle (wbv, "auto-expr-max-precision");
3941 }
3942 
3943 static void
cb_auto_expr_insert_formula(WBCGtk * wbcg,gboolean below)3944 cb_auto_expr_insert_formula (WBCGtk *wbcg, gboolean below)
3945 {
3946 	SheetControlGUI *scg = wbcg_cur_scg (wbcg);
3947 	GnmRange const *selection = selection_first_range (scg_view (scg), NULL, NULL);
3948 	GnmRange output;
3949 	GnmRange *input;
3950 	gboolean multiple, use_last_cr;
3951 	data_analysis_output_t *dao;
3952 	analysis_tools_data_auto_expression_t *specs;
3953 
3954 	g_return_if_fail (selection != NULL);
3955 
3956 	if (below) {
3957 		multiple = (range_width (selection) > 1);
3958 		output = *selection;
3959 		range_normalize (&output);
3960 		output.start.row = output.end.row;
3961 		use_last_cr = (range_height (selection) > 1) && sheet_is_region_empty (scg_sheet (scg), &output);
3962 		if (!use_last_cr) {
3963 			if (range_translate (&output, scg_sheet (scg), 0, 1))
3964 				return;
3965 			if (multiple && gnm_sheet_get_last_col (scg_sheet (scg)) > output.end.col)
3966 				output.end.col++;
3967 		}
3968 		input = gnm_range_dup (selection);
3969 		range_normalize (input);
3970 		if (use_last_cr)
3971 			input->end.row--;
3972 	} else {
3973 		multiple = (range_height (selection) > 1);
3974 		output = *selection;
3975 		range_normalize (&output);
3976 		output.start.col = output.end.col;
3977 		use_last_cr = (range_width (selection) > 1) && sheet_is_region_empty (scg_sheet (scg), &output);
3978 		if (!use_last_cr) {
3979 			if (range_translate (&output, scg_sheet (scg), 1, 0))
3980 				return;
3981 			if (multiple && gnm_sheet_get_last_row (scg_sheet (scg)) > output.end.row)
3982 				output.end.row++;
3983 		}
3984 		input = gnm_range_dup (selection);
3985 		range_normalize (input);
3986 		if (use_last_cr)
3987 			input->end.col--;
3988 	}
3989 
3990 
3991 	dao = dao_init (NULL, RangeOutput);
3992 	dao->start_col         = output.start.col;
3993 	dao->start_row         = output.start.row;
3994 	dao->cols              = range_width (&output);
3995 	dao->rows              = range_height (&output);
3996 	dao->sheet             = scg_sheet (scg);
3997 	dao->autofit_flag      = FALSE;
3998 	dao->put_formulas      = TRUE;
3999 
4000 	specs = g_new0 (analysis_tools_data_auto_expression_t, 1);
4001 	specs->base.wbc = GNM_WBC (wbcg);
4002 	specs->base.input = g_slist_prepend (NULL, value_new_cellrange_r (scg_sheet (scg), input));
4003 	g_free (input);
4004 	specs->base.group_by = below ? GROUPED_BY_COL : GROUPED_BY_ROW;
4005 	specs->base.labels = FALSE;
4006 	specs->multiple = multiple;
4007 	specs->below = below;
4008 	specs->func = NULL;
4009 	g_object_get (G_OBJECT (wb_control_view (GNM_WBC (wbcg))),
4010 		      "auto-expr-func", &(specs->func), NULL);
4011 	if (specs->func == NULL) {
4012 		specs->func =  gnm_func_lookup_or_add_placeholder ("sum");
4013 		gnm_func_inc_usage (specs->func);
4014 	}
4015 
4016 	cmd_analysis_tool (GNM_WBC (wbcg), scg_sheet (scg),
4017 			   dao, specs, analysis_tool_auto_expression_engine,
4018 			   TRUE);
4019 }
4020 
4021 static void
cb_auto_expr_insert_formula_below(G_GNUC_UNUSED GtkWidget * item,WBCGtk * wbcg)4022 cb_auto_expr_insert_formula_below (G_GNUC_UNUSED GtkWidget *item, WBCGtk *wbcg)
4023 {
4024 	cb_auto_expr_insert_formula (wbcg, TRUE);
4025 }
4026 
4027 static void
cb_auto_expr_insert_formula_to_side(G_GNUC_UNUSED GtkWidget * item,WBCGtk * wbcg)4028 cb_auto_expr_insert_formula_to_side (G_GNUC_UNUSED GtkWidget *item, WBCGtk *wbcg)
4029 {
4030 	cb_auto_expr_insert_formula (wbcg, FALSE);
4031 }
4032 
4033 
4034 static gboolean
cb_select_auto_expr(GtkWidget * widget,GdkEvent * event,WBCGtk * wbcg)4035 cb_select_auto_expr (GtkWidget *widget, GdkEvent *event, WBCGtk *wbcg)
4036 {
4037 	/*
4038 	 * WARNING * WARNING * WARNING
4039 	 *
4040 	 * Keep the functions in lower case.
4041 	 * We currently register the functions in lower case and some locales
4042 	 * (notably tr_TR) do not have the same encoding for tolower that
4043 	 * locale C does.
4044 	 *
4045 	 * eg tolower ('I') != 'i'
4046 	 * Which would break function lookup when looking up for function 'selectIon'
4047 	 * when it was registered as 'selection'
4048 	 *
4049 	 * WARNING * WARNING * WARNING
4050 	 */
4051 	static struct {
4052 		char const * const displayed_name;
4053 		char const * const function;
4054 	} const quick_compute_routines [] = {
4055 		{ N_("Sum"),	       "sum" },
4056 		{ N_("Min"),	       "min" },
4057 		{ N_("Max"),	       "max" },
4058 		{ N_("Average"),       "average" },
4059 		{ N_("Count"),         "count" },
4060 		{ NULL, NULL }
4061 	};
4062 
4063 	WorkbookView *wbv = wb_control_view (GNM_WBC (wbcg));
4064 	Sheet *sheet = wb_view_cur_sheet (wbv);
4065 	GtkWidget *item, *menu;
4066 	int i;
4067 	char *cell_item;
4068 	GnmCellPos const *pos;
4069 	GnmEvalPos ep;
4070 
4071 	if (event->button.button != 3)
4072 		return FALSE;
4073 
4074 	menu = gtk_menu_new ();
4075 
4076 	for (i = 0; quick_compute_routines[i].displayed_name; i++) {
4077 		GnmParsePos pp;
4078 		char const *fname = quick_compute_routines[i].function;
4079 		char const *dispname =
4080 			_(quick_compute_routines[i].displayed_name);
4081 		GnmExprTop const *new_auto_expr;
4082 		GtkWidget *item;
4083 		char *expr_txt;
4084 
4085 		/* Test the expression...  */
4086 		parse_pos_init (&pp, sheet->workbook, sheet, 0, 0);
4087 		expr_txt = g_strconcat (fname, "(",
4088 					parsepos_as_string (&pp),
4089 					")",  NULL);
4090 		new_auto_expr = gnm_expr_parse_str
4091 			(expr_txt, &pp, GNM_EXPR_PARSE_DEFAULT,
4092 			 sheet_get_conventions (sheet), NULL);
4093 		g_free (expr_txt);
4094 		if (!new_auto_expr)
4095 			continue;
4096 		gnm_expr_top_unref (new_auto_expr);
4097 
4098 		item = gtk_menu_item_new_with_label (dispname);
4099 		g_object_set_data (G_OBJECT (item),
4100 				   "func", gnm_func_lookup (fname, NULL));
4101 		g_object_set_data (G_OBJECT (item),
4102 				   "descr", (gpointer)dispname);
4103 		g_signal_connect (G_OBJECT (item),
4104 			"activate",
4105 			G_CALLBACK (cb_auto_expr_changed), wbcg);
4106 		gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4107 		gtk_widget_show (item);
4108 	}
4109 
4110 	item = gtk_separator_menu_item_new ();
4111 	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4112 	gtk_widget_show (item);
4113 
4114 	pos = &(scg_view (wbcg_cur_scg (wbcg)))->edit_pos;
4115 	eval_pos_init_pos (&ep, sheet, pos);
4116 	cell_item = g_strdup_printf (_("Content of %s"), cellpos_as_string (pos));
4117 	item = gtk_menu_item_new_with_label (cell_item);
4118 	g_free (cell_item);
4119 	g_object_set_data_full (G_OBJECT (item),
4120 				"evalpos", g_memdup (&ep, sizeof (ep)),
4121 				(GDestroyNotify)g_free);
4122 	g_signal_connect (G_OBJECT (item), "activate",
4123 		G_CALLBACK (cb_auto_expr_cell_changed), wbcg);
4124 	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4125 	gtk_widget_show (item);
4126 
4127 	item = gtk_separator_menu_item_new ();
4128 	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4129 	gtk_widget_show (item);
4130 
4131 	item = gtk_check_menu_item_new_with_label (_("Use Maximum Precision"));
4132 	gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
4133 		wbv->auto_expr.use_max_precision);
4134 	g_signal_connect (G_OBJECT (item), "activate",
4135 		G_CALLBACK (cb_auto_expr_precision_toggled), wbcg);
4136 	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4137 	gtk_widget_show (item);
4138 
4139 	item = gtk_separator_menu_item_new ();
4140 	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4141 	gtk_widget_show (item);
4142 
4143 	item = gtk_menu_item_new_with_label (_("Insert Formula Below"));
4144 	g_signal_connect (G_OBJECT (item), "activate",
4145 		G_CALLBACK (cb_auto_expr_insert_formula_below), wbcg);
4146 	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4147 	gtk_widget_show (item);
4148 
4149 	item = gtk_menu_item_new_with_label (_("Insert Formula to Side"));
4150 	g_signal_connect (G_OBJECT (item), "activate",
4151 		G_CALLBACK (cb_auto_expr_insert_formula_to_side), wbcg);
4152 	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4153 	gtk_widget_show (item);
4154 
4155 	gnumeric_popup_menu (GTK_MENU (menu), event);
4156 	return TRUE;
4157 }
4158 
4159 static void
wbc_gtk_create_status_area(WBCGtk * wbcg)4160 wbc_gtk_create_status_area (WBCGtk *wbcg)
4161 {
4162 	GtkWidget *ebox;
4163 
4164 	g_object_ref (wbcg->auto_expr_label);
4165 	gtk_label_set_max_width_chars (GTK_LABEL (wbcg->auto_expr_label),
4166 				       strlen (AUTO_EXPR_SAMPLE));
4167 	gtk_widget_set_size_request
4168 		(wbcg->auto_expr_label,
4169 		 gnm_widget_measure_string (GTK_WIDGET (wbcg->toplevel),
4170 					    AUTO_EXPR_SAMPLE),
4171 		 -1);
4172 
4173 	gtk_widget_set_size_request
4174 		(wbcg->status_text,
4175 		 gnm_widget_measure_string (GTK_WIDGET (wbcg->toplevel),
4176 					    "W") * 5,
4177 		 -1);
4178 	ebox = GET_GUI_ITEM ("auto_expr_event_box");
4179 	gtk_style_context_add_class (gtk_widget_get_style_context (ebox),
4180 				     "auto-expr");
4181 	g_signal_connect (G_OBJECT (ebox),
4182 		"button_press_event",
4183 		G_CALLBACK (cb_select_auto_expr), wbcg);
4184 
4185 	g_hash_table_insert (wbcg->visibility_widgets,
4186 			     g_strdup ("ViewStatusbar"),
4187 			     g_object_ref (wbcg->status_area));
4188 
4189 	/* disable statusbar by default going to fullscreen */
4190 	wbcg->hide_for_fullscreen =
4191 		g_slist_prepend (wbcg->hide_for_fullscreen,
4192 				 wbcg_find_action (wbcg, "ViewStatusbar"));
4193 	g_assert (wbcg->hide_for_fullscreen->data);
4194 }
4195 
4196 /****************************************************************************/
4197 
4198 static void
cb_file_history_activate(GObject * action,WBCGtk * wbcg)4199 cb_file_history_activate (GObject *action, WBCGtk *wbcg)
4200 {
4201 	gui_file_read (wbcg, g_object_get_data (action, "uri"), NULL, NULL);
4202 }
4203 
4204 static void
wbc_gtk_reload_recent_file_menu(WBCGtk * wbcg)4205 wbc_gtk_reload_recent_file_menu (WBCGtk *wbcg)
4206 {
4207 	WBCGtk *gtk = (WBCGtk *)wbcg;
4208 	GSList *history, *ptr;
4209 	unsigned i;
4210 	gboolean any_history;
4211 	GtkAction *full_history;
4212 
4213 	if (gtk->file_history.merge_id != 0)
4214 		gtk_ui_manager_remove_ui (gtk->ui, gtk->file_history.merge_id);
4215 	gtk->file_history.merge_id = gtk_ui_manager_new_merge_id (gtk->ui);
4216 
4217 	if (gtk->file_history.actions != NULL) {
4218 		gtk_ui_manager_remove_action_group (gtk->ui,
4219 			gtk->file_history.actions);
4220 		g_object_unref (gtk->file_history.actions);
4221 	}
4222 	gtk->file_history.actions = gtk_action_group_new ("FileHistory");
4223 
4224 	/* create the actions */
4225 	history = gnm_app_history_get_list (3);
4226 	any_history = (history != NULL);
4227 	for (i = 1, ptr = history; ptr != NULL ; ptr = ptr->next, i++) {
4228 		GtkActionEntry entry;
4229 		GtkAction *action;
4230 		char const *uri = ptr->data;
4231 		char *name = g_strdup_printf ("FileHistoryEntry%d", i);
4232 		char *label = gnm_history_item_label (uri, i);
4233 		char *filename = go_filename_from_uri (uri);
4234 		char *filename_utf8 = filename ? g_filename_to_utf8 (filename, -1, NULL, NULL, NULL) : NULL;
4235 		char *tooltip = g_strdup_printf (_("Open %s"), filename_utf8 ? filename_utf8 : uri);
4236 
4237 		entry.name = name;
4238 		entry.stock_id = NULL;
4239 		entry.label = label;
4240 		entry.accelerator = NULL;
4241 		entry.tooltip = tooltip;
4242 		entry.callback = G_CALLBACK (cb_file_history_activate);
4243 		gtk_action_group_add_actions (gtk->file_history.actions,
4244 			&entry, 1, (WBCGtk *)wbcg);
4245 		action = gtk_action_group_get_action (gtk->file_history.actions,
4246 						      name);
4247 		g_object_set_data_full (G_OBJECT (action), "uri",
4248 			g_strdup (uri), (GDestroyNotify)g_free);
4249 
4250 		g_free (name);
4251 		g_free (label);
4252 		g_free (filename);
4253 		g_free (filename_utf8);
4254 		g_free (tooltip);
4255 	}
4256 	g_slist_free_full (history, (GDestroyNotify)g_free);
4257 
4258 	gtk_ui_manager_insert_action_group (gtk->ui, gtk->file_history.actions, 0);
4259 
4260 	/* merge them in */
4261 	while (i-- > 1) {
4262 		char *name = g_strdup_printf ("FileHistoryEntry%d", i);
4263 		gtk_ui_manager_add_ui (gtk->ui, gtk->file_history.merge_id,
4264 			"/menubar/File/FileHistory", name, name,
4265 			GTK_UI_MANAGER_AUTO, TRUE);
4266 		g_free (name);
4267 	}
4268 
4269 	full_history = wbcg_find_action (wbcg, "FileHistoryFull");
4270 	g_object_set (G_OBJECT (full_history), "sensitive", any_history, NULL);
4271 }
4272 
4273 static void
cb_new_from_template(GObject * action,WBCGtk * wbcg)4274 cb_new_from_template (GObject *action, WBCGtk *wbcg)
4275 {
4276 	const char *uri = g_object_get_data (action, "uri");
4277 	gnm_gui_file_template (wbcg, uri);
4278 }
4279 
4280 static void
add_template_dir(const char * path,GHashTable * h)4281 add_template_dir (const char *path, GHashTable *h)
4282 {
4283 	GDir *dir;
4284 	const char *name;
4285 
4286 	dir = g_dir_open (path, 0, NULL);
4287 	if (!dir)
4288 		return;
4289 
4290 	while ((name = g_dir_read_name (dir))) {
4291 		char *fullname = g_build_filename (path, name, NULL);
4292 
4293 		/*
4294 		 * Unconditionally remove, so we can link to /dev/null
4295 		 * and cause a system file to be hidden.
4296 		 */
4297 		g_hash_table_remove (h, name);
4298 
4299 		if (g_file_test (fullname, G_FILE_TEST_IS_REGULAR)) {
4300 			char *uri = go_filename_to_uri (fullname);
4301 			g_hash_table_insert (h, g_strdup (name), uri);
4302 		}
4303 		g_free (fullname);
4304 	}
4305 	g_dir_close (dir);
4306 }
4307 
4308 static void
wbc_gtk_reload_templates(WBCGtk * gtk)4309 wbc_gtk_reload_templates (WBCGtk *gtk)
4310 {
4311 	unsigned i;
4312 	GSList *l, *names;
4313 	char *path;
4314 	GHashTable *h;
4315 
4316 	if (gtk->templates.merge_id != 0)
4317 		gtk_ui_manager_remove_ui (gtk->ui, gtk->templates.merge_id);
4318 	gtk->templates.merge_id = gtk_ui_manager_new_merge_id (gtk->ui);
4319 
4320 	if (gtk->templates.actions != NULL) {
4321 		gtk_ui_manager_remove_action_group (gtk->ui,
4322 			gtk->templates.actions);
4323 		g_object_unref (gtk->templates.actions);
4324 	}
4325 	gtk->templates.actions = gtk_action_group_new ("TemplateList");
4326 
4327 	gtk_ui_manager_insert_action_group (gtk->ui, gtk->templates.actions, 0);
4328 
4329 	h = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
4330 
4331 	path = g_build_filename (gnm_sys_data_dir (), "templates", NULL);
4332 	add_template_dir (path, h);
4333 	g_free (path);
4334 
4335 	/* Possibly override the above with user templates without version.  */
4336 	path = g_build_filename (gnm_usr_dir (FALSE), "templates", NULL);
4337 	add_template_dir (path, h);
4338 	g_free (path);
4339 
4340 	/* Possibly override the above with user templates with version.  */
4341 	path = g_build_filename (gnm_usr_dir (TRUE), "templates", NULL);
4342 	add_template_dir (path, h);
4343 	g_free (path);
4344 
4345 	names = g_slist_sort (go_hash_keys (h), (GCompareFunc)g_utf8_collate);
4346 
4347 	for (i = 1, l = names; l; l = l->next) {
4348 		const char *uri = g_hash_table_lookup (h, l->data);
4349 		GString *label = g_string_new (NULL);
4350 		GtkActionEntry entry;
4351 		char *gname;
4352 		const char *gpath;
4353 		char *basename = go_basename_from_uri (uri);
4354 		const char *s;
4355 		GtkAction *action;
4356 
4357 		if (i < 10) g_string_append_c (label, '_');
4358 		g_string_append_printf (label, "%d ", i);
4359 
4360 		for (s = basename; *s; s++) {
4361 			if (*s == '_') g_string_append_c (label, '_');
4362 			g_string_append_c (label, *s);
4363 		}
4364 
4365 		entry.name = gname = g_strdup_printf ("Template%d", i);
4366 		entry.stock_id = NULL;
4367 		entry.label = label->str;
4368 		entry.accelerator = NULL;
4369 		entry.tooltip = NULL;
4370 		entry.callback = G_CALLBACK (cb_new_from_template);
4371 
4372 		gtk_action_group_add_actions (gtk->templates.actions,
4373 					      &entry, 1, gtk);
4374 
4375 		action = gtk_action_group_get_action (gtk->templates.actions,
4376 						      entry.name);
4377 
4378 		g_object_set_data_full (G_OBJECT (action), "uri",
4379 			g_strdup (uri), (GDestroyNotify)g_free);
4380 
4381 
4382 		gpath = "/menubar/File/Templates";
4383 		gtk_ui_manager_add_ui (gtk->ui, gtk->templates.merge_id,
4384 				       gpath, gname, gname,
4385 				       GTK_UI_MANAGER_AUTO, FALSE);
4386 
4387 		g_string_free (label, TRUE);
4388 		g_free (gname);
4389 		g_free (basename);
4390 		i++;
4391 	}
4392 
4393 	g_slist_free (names);
4394 	g_hash_table_destroy (h);
4395 }
4396 
4397 gboolean
wbc_gtk_load_templates(WBCGtk * wbcg)4398 wbc_gtk_load_templates (WBCGtk *wbcg)
4399 {
4400 	if (wbcg->templates.merge_id == 0) {
4401 		wbc_gtk_reload_templates (wbcg);
4402 	}
4403 
4404 	wbcg->template_loader_handler = 0;
4405 	return FALSE;
4406 }
4407 
4408 static void
wbcg_set_toplevel(WBCGtk * wbcg,GtkWidget * w)4409 wbcg_set_toplevel (WBCGtk *wbcg, GtkWidget *w)
4410 {
4411 	static GtkTargetEntry const drag_types[] = {
4412 		{ (char *) "text/uri-list", 0, TARGET_URI_LIST },
4413 		{ (char *) "GNUMERIC_SHEET", 0, TARGET_SHEET },
4414 		{ (char *) "GNUMERIC_SAME_PROC", GTK_TARGET_SAME_APP, 0 }
4415 	};
4416 
4417 	g_return_if_fail (wbcg->toplevel == NULL);
4418 
4419 	wbcg->toplevel = w;
4420 	w = GTK_WIDGET (wbcg_toplevel (wbcg));
4421 	g_return_if_fail (GTK_IS_WINDOW (w));
4422 
4423 	g_object_set (G_OBJECT (w),
4424 		"resizable", TRUE,
4425 		NULL);
4426 
4427 	g_signal_connect_data (w, "delete_event",
4428 		G_CALLBACK (wbc_gtk_close), wbcg, NULL,
4429 		G_CONNECT_AFTER | G_CONNECT_SWAPPED);
4430 	g_signal_connect_after (w, "set_focus",
4431 		G_CALLBACK (cb_set_focus), wbcg);
4432 	g_signal_connect (w, "scroll-event",
4433 		G_CALLBACK (cb_scroll_wheel), wbcg);
4434 	g_signal_connect (w, "realize",
4435 		G_CALLBACK (cb_realize), wbcg);
4436 	g_signal_connect (w, "screen-changed",
4437 		G_CALLBACK (cb_screen_changed), NULL);
4438 	cb_screen_changed (w);
4439 
4440 	/* Setup a test of Drag and Drop */
4441 	gtk_drag_dest_set (GTK_WIDGET (w),
4442 		GTK_DEST_DEFAULT_ALL, drag_types, G_N_ELEMENTS (drag_types),
4443 		GDK_ACTION_COPY | GDK_ACTION_MOVE);
4444 	gtk_drag_dest_add_image_targets (GTK_WIDGET (w));
4445 	gtk_drag_dest_add_text_targets (GTK_WIDGET (w));
4446 	g_object_connect (G_OBJECT (w),
4447 		"signal::drag-leave",	G_CALLBACK (cb_wbcg_drag_leave), wbcg,
4448 		"signal::drag-data-received", G_CALLBACK (cb_wbcg_drag_data_received), wbcg,
4449 		"signal::drag-motion",	G_CALLBACK (cb_wbcg_drag_motion), wbcg,
4450 #if 0
4451 		"signal::drag-data-get", G_CALLBACK (wbcg_drag_data_get), wbc,
4452 #endif
4453 		NULL);
4454 }
4455 
4456 /***************************************************************************/
4457 
4458 static void
wbc_gtk_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)4459 wbc_gtk_get_property (GObject *object, guint property_id,
4460 		      GValue *value, GParamSpec *pspec)
4461 {
4462 	WBCGtk *wbcg = (WBCGtk *)object;
4463 
4464 	switch (property_id) {
4465 	case WBG_GTK_PROP_AUTOSAVE_PROMPT:
4466 		g_value_set_boolean (value, wbcg->autosave_prompt);
4467 		break;
4468 	case WBG_GTK_PROP_AUTOSAVE_TIME:
4469 		g_value_set_int (value, wbcg->autosave_time);
4470 		break;
4471 	default:
4472 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
4473 		break;
4474 	}
4475 }
4476 
4477 static void
wbc_gtk_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)4478 wbc_gtk_set_property (GObject *object, guint property_id,
4479 		   const GValue *value, GParamSpec *pspec)
4480 {
4481 	WBCGtk *wbcg = (WBCGtk *)object;
4482 
4483 	switch (property_id) {
4484 	case WBG_GTK_PROP_AUTOSAVE_PROMPT:
4485 		wbcg->autosave_prompt = g_value_get_boolean (value);
4486 		break;
4487 	case WBG_GTK_PROP_AUTOSAVE_TIME:
4488 		wbcg_set_autosave_time (wbcg, g_value_get_int (value));
4489 		break;
4490 	default:
4491 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
4492 		break;
4493 	}
4494 }
4495 
4496 static void
wbc_gtk_finalize(GObject * obj)4497 wbc_gtk_finalize (GObject *obj)
4498 {
4499 	WBCGtk *wbcg = WBC_GTK (obj);
4500 
4501 	if (wbcg->idle_update_style_feedback != 0)
4502 		g_source_remove (wbcg->idle_update_style_feedback);
4503 
4504 	if (wbcg->template_loader_handler != 0) {
4505 		g_source_remove (wbcg->template_loader_handler);
4506 		wbcg->template_loader_handler = 0;
4507 	}
4508 
4509 	if (wbcg->file_history.merge_id != 0)
4510 		gtk_ui_manager_remove_ui (wbcg->ui, wbcg->file_history.merge_id);
4511 	g_clear_object (&wbcg->file_history.actions);
4512 
4513 	if (wbcg->toolbar.merge_id != 0)
4514 		gtk_ui_manager_remove_ui (wbcg->ui, wbcg->toolbar.merge_id);
4515 	g_clear_object (&wbcg->toolbar.actions);
4516 
4517 	if (wbcg->windows.merge_id != 0)
4518 		gtk_ui_manager_remove_ui (wbcg->ui, wbcg->windows.merge_id);
4519 	g_clear_object (&wbcg->windows.actions);
4520 
4521 	if (wbcg->templates.merge_id != 0)
4522 		gtk_ui_manager_remove_ui (wbcg->ui, wbcg->templates.merge_id);
4523 	g_clear_object (&wbcg->templates.actions);
4524 
4525 	{
4526 		GSList *l, *uis = go_hash_keys (wbcg->custom_uis);
4527 		for (l = uis; l; l = l->next) {
4528 			GnmAppExtraUI *extra_ui = l->data;
4529 			cb_remove_custom_ui (NULL, extra_ui, wbcg);
4530 		}
4531 		g_slist_free (uis);
4532 	}
4533 
4534 	g_hash_table_destroy (wbcg->custom_uis);
4535 	wbcg->custom_uis = NULL;
4536 
4537 	g_clear_object (&wbcg->zoom_vaction);
4538 	g_clear_object (&wbcg->zoom_haction);
4539 	g_clear_object (&wbcg->borders);
4540 	g_clear_object (&wbcg->fore_color);
4541 	g_clear_object (&wbcg->back_color);
4542 	g_clear_object (&wbcg->font_name_haction);
4543 	g_clear_object (&wbcg->font_name_vaction);
4544 	g_clear_object (&wbcg->redo_haction);
4545 	g_clear_object (&wbcg->redo_vaction);
4546 	g_clear_object (&wbcg->undo_haction);
4547 	g_clear_object (&wbcg->undo_vaction);
4548 	g_clear_object (&wbcg->halignment);
4549 	g_clear_object (&wbcg->valignment);
4550 	g_clear_object (&wbcg->actions);
4551 	g_clear_object (&wbcg->permanent_actions);
4552 	g_clear_object (&wbcg->font_actions);
4553 	g_clear_object (&wbcg->data_only_actions);
4554 	g_clear_object (&wbcg->semi_permanent_actions);
4555 	g_clear_object (&wbcg->ui);
4556 
4557 	/* Disconnect signals that would attempt to change things during
4558 	 * destruction.
4559 	 */
4560 
4561 	wbcg_autosave_cancel (wbcg);
4562 
4563 	if (wbcg->bnotebook != NULL)
4564 		g_signal_handlers_disconnect_by_func (
4565 			G_OBJECT (wbcg->bnotebook),
4566 			G_CALLBACK (cb_notebook_switch_page), wbcg);
4567 	g_clear_object (&wbcg->bnotebook);
4568 
4569 	g_signal_handlers_disconnect_by_func (
4570 		G_OBJECT (wbcg->toplevel),
4571 		G_CALLBACK (cb_set_focus), wbcg);
4572 
4573 	wbcg_auto_complete_destroy (wbcg);
4574 
4575 	gtk_window_set_focus (wbcg_toplevel (wbcg), NULL);
4576 
4577 	if (wbcg->toplevel != NULL) {
4578 		gtk_widget_destroy (wbcg->toplevel);
4579 		wbcg->toplevel = NULL;
4580 	}
4581 
4582 	if (wbcg->font_desc) {
4583 		pango_font_description_free (wbcg->font_desc);
4584 		wbcg->font_desc = NULL;
4585 	}
4586 
4587 	g_clear_object (&wbcg->auto_expr_label);
4588 
4589 	g_hash_table_destroy (wbcg->visibility_widgets);
4590 	g_clear_object (&wbcg->undo_for_fullscreen);
4591 
4592 	g_slist_free (wbcg->hide_for_fullscreen);
4593 	wbcg->hide_for_fullscreen = NULL;
4594 
4595 
4596 	g_free (wbcg->preferred_geometry);
4597 	wbcg->preferred_geometry = NULL;
4598 
4599 	g_clear_object (&wbcg->gui);
4600 
4601 	parent_class->finalize (obj);
4602 }
4603 
4604 /***************************************************************************/
4605 
4606 typedef struct {
4607 	GnmExprEntry *entry;
4608 	GogDataset *dataset;
4609 	int dim_i;
4610 	gboolean suppress_update;
4611 	GogDataType data_type;
4612 	gboolean changed;
4613 
4614 	gulong dataset_changed_handler;
4615 	gulong entry_update_handler;
4616 	guint idle;
4617 } GraphDimEditor;
4618 
4619 static void
cb_graph_dim_editor_update(GnmExprEntry * gee,G_GNUC_UNUSED gboolean user_requested,GraphDimEditor * editor)4620 cb_graph_dim_editor_update (GnmExprEntry *gee,
4621 			    G_GNUC_UNUSED gboolean user_requested,
4622 			    GraphDimEditor *editor)
4623 {
4624 	GOData *data = NULL;
4625 	Sheet *sheet;
4626 	SheetControlGUI *scg;
4627 	editor->changed = FALSE;
4628 
4629 	/* Ignore changes while we are insensitive. useful for displaying
4630 	 * values, without storing them as Data.  Also ignore updates if the
4631 	 * dataset has been cleared via the weakref handler  */
4632 	if (!gtk_widget_is_sensitive (GTK_WIDGET (gee)) ||
4633 	    editor->dataset == NULL)
4634 		return;
4635 
4636 	scg = gnm_expr_entry_get_scg (gee);
4637 	sheet = scg_sheet (scg);
4638 
4639 	/* If we are setting something */
4640 	if (!gnm_expr_entry_is_blank (editor->entry)) {
4641 		GnmParsePos pos;
4642 		GnmParseError  perr;
4643 		GnmExprTop const *texpr;
4644 		GnmExprParseFlags flags =
4645 			(editor->data_type == GOG_DATA_VECTOR)?
4646 				GNM_EXPR_PARSE_PERMIT_MULTIPLE_EXPRESSIONS |
4647 				GNM_EXPR_PARSE_UNKNOWN_NAMES_ARE_STRINGS:
4648 				GNM_EXPR_PARSE_UNKNOWN_NAMES_ARE_STRINGS;
4649 
4650 		parse_error_init (&perr);
4651 		/* Setting start_sel=FALSE to avoid */
4652 		/* https://bugzilla.gnome.org/show_bug.cgi?id=658223 */
4653 		texpr = gnm_expr_entry_parse (editor->entry,
4654 			parse_pos_init_sheet (&pos, sheet),
4655 			&perr, FALSE, flags);
4656 
4657 		/* TODO : add some error dialogs split out
4658 		 * the code in workbook_edit to add parens.  */
4659 		if (texpr == NULL) {
4660 			if (editor->data_type == GOG_DATA_SCALAR)
4661 				texpr = gnm_expr_top_new_constant (
4662 					value_new_string (
4663 						gnm_expr_entry_get_text (editor->entry)));
4664 			else {
4665 				g_return_if_fail (perr.err != NULL);
4666 
4667 				wb_control_validation_msg (GNM_WBC (scg_wbcg (scg)),
4668 					GNM_VALIDATION_STYLE_INFO, NULL, perr.err->message);
4669 				parse_error_free (&perr);
4670 				gtk_editable_select_region (GTK_EDITABLE (gnm_expr_entry_get_entry (editor->entry)), 0, G_MAXINT);
4671 				editor->changed = TRUE;
4672 				return;
4673 			}
4674 		}
4675 
4676 		switch (editor->data_type) {
4677 		case GOG_DATA_SCALAR:
4678 			data = gnm_go_data_scalar_new_expr (sheet, texpr);
4679 			break;
4680 		case GOG_DATA_VECTOR:
4681 			data = gnm_go_data_vector_new_expr (sheet, texpr);
4682 			break;
4683 		case GOG_DATA_MATRIX:
4684 			data = gnm_go_data_matrix_new_expr (sheet, texpr);
4685 		}
4686 	}
4687 
4688 	/* The SheetObjectGraph does the magic to link things in */
4689 	editor->suppress_update = TRUE;
4690 	gog_dataset_set_dim (editor->dataset, editor->dim_i, data, NULL);
4691 	editor->suppress_update = FALSE;
4692 }
4693 
4694 static gboolean
cb_update_idle(GraphDimEditor * editor)4695 cb_update_idle (GraphDimEditor *editor)
4696 {
4697 	cb_graph_dim_editor_update (editor->entry, FALSE, editor);
4698 	editor->idle = 0;
4699 	return FALSE;
4700 }
4701 
4702 static void
graph_dim_cancel_idle(GraphDimEditor * editor)4703 graph_dim_cancel_idle (GraphDimEditor *editor)
4704 {
4705 	if (editor->idle) {
4706 		g_source_remove (editor->idle);
4707 		editor->idle = 0;
4708 	}
4709 }
4710 
4711 static gboolean
cb_graph_dim_entry_focus_out_event(G_GNUC_UNUSED GtkEntry * ignored,G_GNUC_UNUSED GdkEventFocus * event,GraphDimEditor * editor)4712 cb_graph_dim_entry_focus_out_event (G_GNUC_UNUSED GtkEntry	*ignored,
4713 				    G_GNUC_UNUSED GdkEventFocus	*event,
4714 				    GraphDimEditor		*editor)
4715 {
4716 	if (!editor->changed)
4717 		return FALSE;
4718 	graph_dim_cancel_idle (editor);
4719 	editor->idle = g_idle_add ((GSourceFunc) cb_update_idle, editor);
4720 
4721 	return FALSE;
4722 }
4723 
4724 static void
cb_graph_dim_entry_changed(GraphDimEditor * editor)4725 cb_graph_dim_entry_changed (GraphDimEditor *editor)
4726 {
4727 	editor->changed = TRUE;
4728 }
4729 
4730 static void
set_entry_contents(GnmExprEntry * entry,GOData * val)4731 set_entry_contents (GnmExprEntry *entry, GOData *val)
4732 {
4733 	if (GNM_IS_GO_DATA_SCALAR (val)) {
4734 		GnmValue const *v = gnm_expr_top_get_constant (gnm_go_data_get_expr (val));
4735 		if (v && VALUE_IS_NUMBER (v)) {
4736 			double d = go_data_get_scalar_value (val);
4737 			GODateConventions const *date_conv = go_data_date_conv (val);
4738 			gog_data_editor_set_value_double (GOG_DATA_EDITOR (entry),
4739 							  d, date_conv);
4740 			return;
4741 		}
4742 	}
4743 
4744 	if (GO_IS_DATA_SCALAR (val) && go_data_has_value (val)) {
4745 		double d = go_data_get_scalar_value (val);
4746 		GODateConventions const *date_conv = go_data_date_conv (val);
4747 		gog_data_editor_set_value_double (GOG_DATA_EDITOR (entry),
4748 		                                  d, date_conv);
4749 			return;
4750 	}
4751 
4752 	{
4753 		SheetControlGUI *scg = gnm_expr_entry_get_scg (entry);
4754 		Sheet const *sheet = scg_sheet (scg);
4755 		char *txt = go_data_serialize (val, (gpointer)sheet->convs);
4756 		gnm_expr_entry_load_from_text (entry, txt);
4757 		g_free (txt);
4758 	}
4759 }
4760 
4761 static void
cb_dataset_changed(GogDataset * dataset,gboolean resize,GraphDimEditor * editor)4762 cb_dataset_changed (GogDataset *dataset,
4763 		    gboolean resize,
4764 		    GraphDimEditor *editor)
4765 {
4766 	GOData *val = gog_dataset_get_dim (dataset, editor->dim_i);
4767 	if (val != NULL && !editor->suppress_update) {
4768 		g_signal_handler_block (editor->entry,
4769 					editor->entry_update_handler);
4770 		set_entry_contents (editor->entry, val);
4771 		g_signal_handler_unblock (editor->entry,
4772 					  editor->entry_update_handler);
4773 	}
4774 }
4775 
4776 static void
cb_dim_editor_weakref_notify(GraphDimEditor * editor,GogDataset * dataset)4777 cb_dim_editor_weakref_notify (GraphDimEditor *editor, GogDataset *dataset)
4778 {
4779 	g_return_if_fail (editor->dataset == dataset);
4780 	editor->dataset = NULL;
4781 }
4782 
4783 static void
graph_dim_editor_free(GraphDimEditor * editor)4784 graph_dim_editor_free (GraphDimEditor *editor)
4785 {
4786 	graph_dim_cancel_idle (editor);
4787 	if (editor->dataset) {
4788 		g_signal_handler_disconnect (editor->dataset, editor->dataset_changed_handler);
4789 		g_object_weak_unref (G_OBJECT (editor->dataset),
4790 			(GWeakNotify) cb_dim_editor_weakref_notify, editor);
4791 	}
4792 	g_free (editor);
4793 }
4794 
4795 static GogDataEditor *
wbcg_data_allocator_editor(GogDataAllocator * dalloc,GogDataset * dataset,int dim_i,GogDataType data_type)4796 wbcg_data_allocator_editor (GogDataAllocator *dalloc,
4797 			    GogDataset *dataset, int dim_i, GogDataType data_type)
4798 {
4799 	WBCGtk *wbcg = WBC_GTK (dalloc);
4800 	GraphDimEditor *editor;
4801 	GOData *val;
4802 
4803 	editor = g_new (GraphDimEditor, 1);
4804 	editor->dataset		= dataset;
4805 	editor->dim_i		= dim_i;
4806 	editor->suppress_update = FALSE;
4807 	editor->data_type	= data_type;
4808 	editor->entry		= gnm_expr_entry_new (wbcg, TRUE);
4809 	editor->idle            = 0;
4810 	editor->changed		= FALSE;
4811 	g_object_weak_ref (G_OBJECT (editor->dataset),
4812 		(GWeakNotify) cb_dim_editor_weakref_notify, editor);
4813 
4814 	gnm_expr_entry_set_update_policy (editor->entry,
4815 		GNM_UPDATE_DISCONTINUOUS);
4816 
4817 	val = gog_dataset_get_dim (dataset, dim_i);
4818 	if (val != NULL) {
4819 		set_entry_contents (editor->entry, val);
4820 	}
4821 
4822 	gnm_expr_entry_set_flags (editor->entry, GNM_EE_FORCE_ABS_REF, GNM_EE_MASK);
4823 
4824 	editor->entry_update_handler = g_signal_connect (G_OBJECT (editor->entry),
4825 		"update",
4826 		G_CALLBACK (cb_graph_dim_editor_update), editor);
4827 	g_signal_connect (G_OBJECT (gnm_expr_entry_get_entry (editor->entry)),
4828 		"focus-out-event",
4829 		G_CALLBACK (cb_graph_dim_entry_focus_out_event), editor);
4830 	g_signal_connect_swapped (G_OBJECT (gnm_expr_entry_get_entry (editor->entry)),
4831 		"changed",
4832 		G_CALLBACK (cb_graph_dim_entry_changed), editor);
4833 	editor->dataset_changed_handler = g_signal_connect (G_OBJECT (editor->dataset),
4834 		"changed", G_CALLBACK (cb_dataset_changed), editor);
4835 	g_object_set_data_full (G_OBJECT (editor->entry),
4836 		"editor", editor, (GDestroyNotify) graph_dim_editor_free);
4837 
4838 	return GOG_DATA_EDITOR (editor->entry);
4839 }
4840 
4841 static void
wbcg_data_allocator_allocate(GogDataAllocator * dalloc,GogPlot * plot)4842 wbcg_data_allocator_allocate (GogDataAllocator *dalloc, GogPlot *plot)
4843 {
4844 	SheetControlGUI *scg = wbcg_cur_scg (WBC_GTK (dalloc));
4845 	sv_selection_to_plot (scg_view (scg), plot);
4846 }
4847 
4848 
4849 static void
wbcg_go_plot_data_allocator_init(GogDataAllocatorClass * iface)4850 wbcg_go_plot_data_allocator_init (GogDataAllocatorClass *iface)
4851 {
4852 	iface->editor	  = wbcg_data_allocator_editor;
4853 	iface->allocate   = wbcg_data_allocator_allocate;
4854 }
4855 
4856 /*************************************************************************/
4857 static char *
wbcg_get_password(GOCmdContext * cc,char const * filename)4858 wbcg_get_password (GOCmdContext *cc, char const* filename)
4859 {
4860 	WBCGtk *wbcg = WBC_GTK (cc);
4861 
4862 	return dialog_get_password (wbcg_toplevel (wbcg), filename);
4863 }
4864 static void
wbcg_set_sensitive(GOCmdContext * cc,gboolean sensitive)4865 wbcg_set_sensitive (GOCmdContext *cc, gboolean sensitive)
4866 {
4867 	GtkWindow *toplevel = wbcg_toplevel (WBC_GTK (cc));
4868 	if (toplevel != NULL)
4869 		gtk_widget_set_sensitive (GTK_WIDGET (toplevel), sensitive);
4870 }
4871 static void
wbcg_error_error(GOCmdContext * cc,GError * err)4872 wbcg_error_error (GOCmdContext *cc, GError *err)
4873 {
4874 	go_gtk_notice_dialog (wbcg_toplevel (WBC_GTK (cc)),
4875 			      GTK_MESSAGE_ERROR,
4876 			      "%s", err->message);
4877 }
4878 
4879 static void
wbcg_error_error_info(GOCmdContext * cc,GOErrorInfo * error)4880 wbcg_error_error_info (GOCmdContext *cc, GOErrorInfo *error)
4881 {
4882 	gnm_go_error_info_dialog_show (
4883 		wbcg_toplevel (WBC_GTK (cc)), error);
4884 }
4885 
4886 static void
wbcg_error_error_info_list(GOCmdContext * cc,GSList * errs)4887 wbcg_error_error_info_list (GOCmdContext *cc, GSList *errs)
4888 {
4889 	gnm_go_error_info_list_dialog_show
4890 		(wbcg_toplevel (WBC_GTK (cc)), errs);
4891 }
4892 
4893 static void
wbcg_progress_set(GOCmdContext * cc,double val)4894 wbcg_progress_set (GOCmdContext *cc, double val)
4895 {
4896 	WBCGtk *wbcg = WBC_GTK (cc);
4897 	gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (wbcg->progress_bar), val);
4898 }
4899 static void
wbcg_progress_message_set(GOCmdContext * cc,gchar const * msg)4900 wbcg_progress_message_set (GOCmdContext *cc, gchar const *msg)
4901 {
4902 	WBCGtk *wbcg = WBC_GTK (cc);
4903 	gtk_progress_bar_set_text (GTK_PROGRESS_BAR (wbcg->progress_bar), msg);
4904 }
4905 static void
wbcg_gnm_cmd_context_init(GOCmdContextClass * iface)4906 wbcg_gnm_cmd_context_init (GOCmdContextClass *iface)
4907 {
4908 	iface->get_password	    = wbcg_get_password;
4909 	iface->set_sensitive	    = wbcg_set_sensitive;
4910 	iface->error.error	    = wbcg_error_error;
4911 	iface->error.error_info	    = wbcg_error_error_info;
4912 	iface->error.error_info_list	    = wbcg_error_error_info_list;
4913 	iface->progress_set	    = wbcg_progress_set;
4914 	iface->progress_message_set = wbcg_progress_message_set;
4915 }
4916 
4917 /*************************************************************************/
4918 
4919 static void
wbc_gtk_class_init(GObjectClass * gobject_class)4920 wbc_gtk_class_init (GObjectClass *gobject_class)
4921 {
4922 	WorkbookControlClass *wbc_class =
4923 		GNM_WBC_CLASS (gobject_class);
4924 
4925 	g_return_if_fail (wbc_class != NULL);
4926 
4927 	debug_tab_order = gnm_debug_flag ("tab-order");
4928 
4929 	parent_class = g_type_class_peek_parent (gobject_class);
4930 	gobject_class->get_property	= wbc_gtk_get_property;
4931 	gobject_class->set_property	= wbc_gtk_set_property;
4932 	gobject_class->finalize		= wbc_gtk_finalize;
4933 
4934 	wbc_class->edit_line_set	= wbcg_edit_line_set;
4935 	wbc_class->selection_descr_set	= wbcg_edit_selection_descr_set;
4936 	wbc_class->update_action_sensitivity = wbcg_update_action_sensitivity;
4937 
4938 	wbc_class->sheet.add        = wbcg_sheet_add;
4939 	wbc_class->sheet.remove	    = wbcg_sheet_remove;
4940 	wbc_class->sheet.focus	    = wbcg_sheet_focus;
4941 	wbc_class->sheet.remove_all = wbcg_sheet_remove_all;
4942 
4943 	wbc_class->undo_redo.labels	= wbcg_undo_redo_labels;
4944 	wbc_class->undo_redo.truncate	= wbc_gtk_undo_redo_truncate;
4945 	wbc_class->undo_redo.pop	= wbc_gtk_undo_redo_pop;
4946 	wbc_class->undo_redo.push	= wbc_gtk_undo_redo_push;
4947 
4948 	wbc_class->menu_state.update	= wbcg_menu_state_update;
4949 
4950 	wbc_class->claim_selection	= wbcg_claim_selection;
4951 	wbc_class->paste_from_selection	= wbcg_paste_from_selection;
4952 	wbc_class->validation_msg	= wbcg_validation_msg;
4953 
4954 	wbc_class->control_new		= wbc_gtk_control_new;
4955 	wbc_class->init_state		= wbc_gtk_init_state;
4956 	wbc_class->style_feedback	= wbc_gtk_style_feedback;
4957 
4958         g_object_class_install_property (gobject_class,
4959 		 WBG_GTK_PROP_AUTOSAVE_PROMPT,
4960 		 g_param_spec_boolean ("autosave-prompt",
4961 				       P_("Autosave prompt"),
4962 				       P_("Ask about autosave?"),
4963 				       FALSE,
4964 				       GSF_PARAM_STATIC | G_PARAM_READWRITE));
4965         g_object_class_install_property (gobject_class,
4966 		 WBG_GTK_PROP_AUTOSAVE_TIME,
4967 		 g_param_spec_int ("autosave-time",
4968 				   P_("Autosave time in seconds"),
4969 				   P_("Seconds before autosave"),
4970 				   0, G_MAXINT, 0,
4971 				   GSF_PARAM_STATIC | G_PARAM_READWRITE));
4972 
4973 	wbc_gtk_signals [WBC_GTK_MARKUP_CHANGED] = g_signal_new ("markup-changed",
4974 		GNM_WBC_GTK_TYPE,
4975 		G_SIGNAL_RUN_LAST,
4976 		G_STRUCT_OFFSET (WBCGtkClass, markup_changed),
4977 		NULL, NULL,
4978 		g_cclosure_marshal_VOID__VOID,
4979 		G_TYPE_NONE,
4980 		0, G_TYPE_NONE);
4981 
4982 	gtk_window_set_default_icon_name ("gnumeric");
4983 }
4984 
4985 static void
wbc_gtk_init(GObject * obj)4986 wbc_gtk_init (GObject *obj)
4987 {
4988 	WBCGtk		*wbcg = (WBCGtk *)obj;
4989 	GError		*error = NULL;
4990 	char		*uifile;
4991 	unsigned	 i;
4992 	GEnumClass      *posclass;
4993 	GtkStyleContext *ctxt;
4994 	guint            merge_id;
4995 
4996 	wbcg->gui = gnm_gtk_builder_load ("res:ui/wbcg.ui", NULL, NULL);
4997 	wbcg->cancel_button = GET_GUI_ITEM ("cancel_button");
4998 	wbcg->ok_button = GET_GUI_ITEM ("ok_button");
4999 	wbcg->func_button = GET_GUI_ITEM ("func_button");
5000 	wbcg->progress_bar = GET_GUI_ITEM ("progress_bar");
5001 	wbcg->auto_expr_label = GET_GUI_ITEM ("auto_expr_label");
5002 	wbcg->status_text = GET_GUI_ITEM ("status_text");
5003 	wbcg->tabs_paned = GET_GUI_ITEM ("tabs_paned");
5004 	wbcg->status_area = GET_GUI_ITEM ("status_area");
5005 	wbcg->notebook_area = GET_GUI_ITEM ("notebook_area");
5006 	wbcg->snotebook = GET_GUI_ITEM ("snotebook");
5007 	wbcg->selection_descriptor = GET_GUI_ITEM ("selection_descriptor");
5008 	wbcg->menu_zone = GET_GUI_ITEM ("menu_zone");
5009 	wbcg->toolbar_zones[GTK_POS_TOP] = GET_GUI_ITEM ("toolbar_zone_top");
5010 	wbcg->toolbar_zones[GTK_POS_BOTTOM] = NULL;
5011 	wbcg->toolbar_zones[GTK_POS_LEFT] = GET_GUI_ITEM ("toolbar_zone_left");
5012 	wbcg->toolbar_zones[GTK_POS_RIGHT] = GET_GUI_ITEM ("toolbar_zone_right");
5013 	wbcg->updating_ui = FALSE;
5014 
5015 	posclass = G_ENUM_CLASS (g_type_class_peek (gtk_position_type_get_type ()));
5016 	for (i = 0; i < posclass->n_values; i++) {
5017 		GEnumValue const *ev = posclass->values + i;
5018 		GtkWidget *zone = wbcg->toolbar_zones[ev->value];
5019 		GtkStyleContext *ctxt;
5020 		if (!zone)
5021 			continue;
5022 		ctxt = gtk_widget_get_style_context (zone);
5023 		gtk_style_context_add_class (ctxt, "toolbarzone");
5024 		gtk_style_context_add_class (ctxt, ev->value_nick);
5025 	}
5026 
5027 	wbcg->visibility_widgets = g_hash_table_new_full (g_str_hash,
5028 		g_str_equal, (GDestroyNotify)g_free, (GDestroyNotify)g_object_unref);
5029 	wbcg->undo_for_fullscreen = NULL;
5030 	wbcg->hide_for_fullscreen = NULL;
5031 
5032 	wbcg->autosave_prompt = FALSE;
5033 	wbcg->autosave_time = 0;
5034 	wbcg->autosave_timer = 0;
5035 
5036 	/* We are not in edit mode */
5037 	wbcg->editing	    = FALSE;
5038 	wbcg->editing_sheet = NULL;
5039 	wbcg->editing_cell  = NULL;
5040 
5041 	wbcg->new_object = NULL;
5042 
5043 	wbcg->idle_update_style_feedback = 0;
5044 
5045 	wbcg_set_toplevel (wbcg, GET_GUI_ITEM ("toplevel"));
5046 	ctxt = gtk_widget_get_style_context (GTK_WIDGET (wbcg_toplevel (wbcg)));
5047 	gtk_style_context_add_class (ctxt, "gnumeric");
5048 
5049 	g_signal_connect (wbcg_toplevel (wbcg), "window_state_event",
5050 			  G_CALLBACK (cb_wbcg_window_state_event),
5051 			  wbcg);
5052 
5053 	wbc_gtk_init_actions (wbcg);
5054 	wbcg->ui = gtk_ui_manager_new ();
5055 	g_object_connect (wbcg->ui,
5056 		"signal::add_widget",	 G_CALLBACK (cb_add_menus_toolbars), wbcg,
5057 		"signal::connect_proxy",    G_CALLBACK (cb_connect_proxy), wbcg,
5058 		"signal::disconnect_proxy", G_CALLBACK (cb_disconnect_proxy), wbcg,
5059 		"signal::post_activate", G_CALLBACK (cb_post_activate), wbcg,
5060 		NULL);
5061 	if (extra_actions)
5062 		gnm_action_group_add_actions (wbcg->actions, extra_actions,
5063 		                              extra_actions_nb, wbcg);
5064 	gtk_ui_manager_insert_action_group (wbcg->ui, wbcg->permanent_actions, 0);
5065 	gtk_ui_manager_insert_action_group (wbcg->ui, wbcg->actions, 0);
5066 	gtk_ui_manager_insert_action_group (wbcg->ui, wbcg->font_actions, 0);
5067 	gtk_ui_manager_insert_action_group (wbcg->ui, wbcg->data_only_actions, 0);
5068 	gtk_ui_manager_insert_action_group (wbcg->ui, wbcg->semi_permanent_actions, 0);
5069 	gtk_window_add_accel_group (wbcg_toplevel (wbcg),
5070 		gtk_ui_manager_get_accel_group (wbcg->ui));
5071 
5072 	if (uifilename) {
5073 		if (strncmp (uifilename, "res:", 4) == 0)
5074 			uifile = g_strdup (uifilename);
5075 		else
5076 			uifile = g_build_filename (gnm_sys_data_dir (),
5077 						   uifilename,
5078 						   NULL);
5079 	} else
5080 		uifile = g_strdup ("res:/org/gnumeric/gnumeric/ui/GNOME_Gnumeric-gtk.xml");
5081 
5082 	if (strncmp (uifile, "res:", 4) == 0)
5083 		merge_id = gtk_ui_manager_add_ui_from_resource
5084 			(wbcg->ui, uifile + 4, &error);
5085 	else
5086 		merge_id = gtk_ui_manager_add_ui_from_file
5087 			(wbcg->ui, uifile, &error);
5088 	if (!merge_id) {
5089 		g_message ("building menus failed: %s", error->message);
5090 		g_error_free (error);
5091 	}
5092 
5093 	g_free (uifile);
5094 
5095 	wbcg->custom_uis = g_hash_table_new_full (g_direct_hash, g_direct_equal,
5096 						 NULL, g_free);
5097 
5098 	wbcg->file_history.actions = NULL;
5099 	wbcg->file_history.merge_id = 0;
5100 
5101 	wbcg->toolbar.merge_id = gtk_ui_manager_new_merge_id (wbcg->ui);
5102 	wbcg->toolbar.actions = gtk_action_group_new ("Toolbars");
5103 	gtk_ui_manager_insert_action_group (wbcg->ui, wbcg->toolbar.actions, 0);
5104 
5105 	wbcg->windows.actions = NULL;
5106 	wbcg->windows.merge_id = 0;
5107 
5108 	wbcg->templates.actions = NULL;
5109 	wbcg->templates.merge_id = 0;
5110 
5111 	gnm_app_foreach_extra_ui ((GFunc) cb_init_extra_ui, wbcg);
5112 	g_object_connect ((GObject *) gnm_app_get_app (),
5113 		"swapped-object-signal::window-list-changed",
5114 			G_CALLBACK (cb_regenerate_window_menu), wbcg,
5115 		"object-signal::custom-ui-added",
5116 			G_CALLBACK (cb_add_custom_ui), wbcg,
5117 		"object-signal::custom-ui-removed",
5118 			G_CALLBACK (cb_remove_custom_ui), wbcg,
5119 		NULL);
5120 
5121 	gtk_ui_manager_ensure_update (wbcg->ui);
5122 
5123 	/* updates the undo/redo menu labels before check_underlines
5124 	 * to avoid problems like #324692. */
5125 	wb_control_undo_redo_labels (GNM_WBC (wbcg), NULL, NULL);
5126 	if (GNM_VERSION_MAJOR % 2 != 0 ||
5127 	    gnm_debug_flag ("underlines")) {
5128 		gtk_container_foreach (GTK_CONTAINER (wbcg->menu_zone),
5129 				       (GtkCallback)check_underlines,
5130 				       (gpointer)"");
5131 	}
5132 
5133 	wbcg_set_autosave_time (wbcg, gnm_conf_get_core_workbook_autosave_time ());
5134 }
5135 
5136 GSF_CLASS_FULL (WBCGtk, wbc_gtk, NULL, NULL, wbc_gtk_class_init, NULL,
5137 	wbc_gtk_init, GNM_WBC_TYPE, 0,
5138 	GSF_INTERFACE (wbcg_go_plot_data_allocator_init, GOG_TYPE_DATA_ALLOCATOR);
5139 	GSF_INTERFACE (wbcg_gnm_cmd_context_init, GO_TYPE_CMD_CONTEXT))
5140 
5141 /******************************************************************************/
5142 
5143 void
wbc_gtk_markup_changer(WBCGtk * wbcg)5144 wbc_gtk_markup_changer (WBCGtk *wbcg)
5145 {
5146 	g_signal_emit (G_OBJECT (wbcg), wbc_gtk_signals [WBC_GTK_MARKUP_CHANGED], 0);
5147 }
5148 
5149 /******************************************************************************/
5150 /**
5151  * wbc_gtk_new:
5152  * @optional_view: (allow-none): #WorkbookView
5153  * @optional_wb: (allow-none) (transfer full): #Workbook
5154  * @optional_screen: (allow-none): #GdkScreen.
5155  * @optional_geometry: (allow-none): string.
5156  *
5157  * Returns: (transfer none): (allow-none):  the new #WBCGtk or %NULL.
5158  **/
5159 WBCGtk *
wbc_gtk_new(WorkbookView * optional_view,Workbook * optional_wb,GdkScreen * optional_screen,gchar * optional_geometry)5160 wbc_gtk_new (WorkbookView *optional_view,
5161 	     Workbook *optional_wb,
5162 	     GdkScreen *optional_screen,
5163 	     gchar *optional_geometry)
5164 {
5165 	Sheet *sheet;
5166 	WorkbookView *wbv;
5167 	WBCGtk *wbcg = g_object_new (wbc_gtk_get_type (), NULL);
5168 	WorkbookControl *wbc = (WorkbookControl *)wbcg;
5169 
5170 	wbcg->preferred_geometry = g_strdup (optional_geometry);
5171 
5172 	wbc_gtk_create_edit_area (wbcg);
5173 	wbc_gtk_create_status_area (wbcg);
5174 	wbc_gtk_reload_recent_file_menu (wbcg);
5175 
5176 	g_signal_connect_object (gnm_app_get_app (),
5177 		"notify::file-history-list",
5178 		G_CALLBACK (wbc_gtk_reload_recent_file_menu), wbcg, G_CONNECT_SWAPPED);
5179 
5180 	wb_control_set_view (wbc, optional_view, optional_wb);
5181 	wbv = wb_control_view (wbc);
5182 	sheet = wbv->current_sheet;
5183 	if (sheet != NULL) {
5184 		wb_control_menu_state_update (wbc, MS_ALL);
5185 		wb_control_update_action_sensitivity (wbc);
5186 		wb_control_style_feedback (wbc, NULL);
5187 		cb_zoom_change (sheet, NULL, wbcg);
5188 	}
5189 
5190 	wbc_gtk_create_notebook_area (wbcg);
5191 
5192 	wbcg_view_changed (wbcg, NULL, NULL);
5193 
5194 	if (optional_screen)
5195 		gtk_window_set_screen (wbcg_toplevel (wbcg), optional_screen);
5196 
5197 	/* Postpone showing the GUI, so that we may resize it freely. */
5198 	g_idle_add ((GSourceFunc)show_gui, wbcg);
5199 
5200 	/* Load this later when thing have calmed down.  If this does not
5201 	   trigger by the time the file menu is activated, then the UI is
5202 	   updated right then -- and that looks sub-optimal because the
5203 	   "Templates" menu is empty (and thus not shown) until the
5204 	   update is done. */
5205 	wbcg->template_loader_handler =
5206 		g_timeout_add (1000, (GSourceFunc)wbc_gtk_load_templates, wbcg);
5207 
5208 	wb_control_init_state (wbc);
5209 	return wbcg;
5210 }
5211 
5212 /**
5213  * wbcg_toplevel:
5214  * @wbcg: #WBCGtk
5215  *
5216  * Returns: (transfer none): the toplevel #GtkWindow.
5217  **/
5218 GtkWindow *
wbcg_toplevel(WBCGtk * wbcg)5219 wbcg_toplevel (WBCGtk *wbcg)
5220 {
5221 	g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), NULL);
5222 	return GTK_WINDOW (wbcg->toplevel);
5223 }
5224 
5225 void
wbcg_set_transient(WBCGtk * wbcg,GtkWindow * window)5226 wbcg_set_transient (WBCGtk *wbcg, GtkWindow *window)
5227 {
5228 	go_gtk_window_set_transient (wbcg_toplevel (wbcg), window);
5229 }
5230 
5231 int
wbcg_get_n_scg(WBCGtk const * wbcg)5232 wbcg_get_n_scg (WBCGtk const *wbcg)
5233 {
5234 	return (GTK_IS_NOTEBOOK (wbcg->snotebook))?
5235 		gtk_notebook_get_n_pages (wbcg->snotebook): -1;
5236 }
5237 
5238 /**
5239  * wbcg_get_nth_scg:
5240  * @wbcg: #WBCGtk
5241  * @i:
5242  *
5243  * Returns: (transfer none):  the scg associated with the @i-th tab in
5244  * @wbcg's notebook.
5245  * NOTE : @i != scg->sv->sheet->index_in_wb
5246  **/
5247 SheetControlGUI *
wbcg_get_nth_scg(WBCGtk * wbcg,int i)5248 wbcg_get_nth_scg (WBCGtk *wbcg, int i)
5249 {
5250 	SheetControlGUI *scg;
5251 	GtkWidget *w;
5252 
5253 	g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), NULL);
5254 
5255 	if (NULL != wbcg->snotebook &&
5256 	    NULL != (w = gtk_notebook_get_nth_page (wbcg->snotebook, i)) &&
5257 	    NULL != (scg = get_scg (w)) &&
5258 	    NULL != scg->grid &&
5259 	    NULL != scg_sheet (scg) &&
5260 	    NULL != scg_view (scg))
5261 		return scg;
5262 
5263 	return NULL;
5264 }
5265 
5266 #warning merge these and clarfy whether we want the visible scg, or the logical (view) scg
5267 /**
5268  * wbcg_focus_cur_scg:
5269  * @wbcg: The workbook control to operate on.
5270  *
5271  * A utility routine to safely ensure that the keyboard focus
5272  * is attached to the item-grid.  This is required when a user
5273  * edits a combo-box or and entry-line which grab focus.
5274  *
5275  * It is called for zoom, font name/size, and accept/cancel for the editline.
5276  * Returns: (transfer none): the sheet.
5277  **/
5278 Sheet *
wbcg_focus_cur_scg(WBCGtk * wbcg)5279 wbcg_focus_cur_scg (WBCGtk *wbcg)
5280 {
5281 	SheetControlGUI *scg;
5282 
5283 	g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), NULL);
5284 
5285 	if (wbcg->snotebook == NULL)
5286 		return NULL;
5287 
5288 	scg = wbcg_get_nth_scg (wbcg,
5289 		gtk_notebook_get_current_page (wbcg->snotebook));
5290 
5291 	g_return_val_if_fail (scg != NULL, NULL);
5292 
5293 	scg_take_focus (scg);
5294 	return scg_sheet (scg);
5295 }
5296 
5297 /**
5298  * wbcg_cur_scg:
5299  * @wbcg: #WBCGtk
5300  *
5301  * Returns: (transfer none): the current #SheetControlGUI.
5302  **/
5303 SheetControlGUI *
wbcg_cur_scg(WBCGtk * wbcg)5304 wbcg_cur_scg (WBCGtk *wbcg)
5305 {
5306 	return wbcg_get_scg (wbcg, wbcg_cur_sheet (wbcg));
5307 }
5308 
5309 /**
5310  * wbcg_cur_sheet:
5311  * @wbcg: #WBCGtk
5312  *
5313  * Returns: (transfer none): the current #Sheet.
5314  **/
5315 Sheet *
wbcg_cur_sheet(WBCGtk * wbcg)5316 wbcg_cur_sheet (WBCGtk *wbcg)
5317 {
5318 	return wb_control_cur_sheet (GNM_WBC (wbcg));
5319 }
5320 
5321 PangoFontDescription *
wbcg_get_font_desc(WBCGtk * wbcg)5322 wbcg_get_font_desc (WBCGtk *wbcg)
5323 {
5324 	g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), NULL);
5325 
5326 	if (!wbcg->font_desc) {
5327 		GtkSettings *settings = wbcg_get_gtk_settings (wbcg);
5328 		wbcg->font_desc = settings_get_font_desc (settings);
5329 		g_signal_connect_object (settings, "notify::gtk-font-name",
5330 					 G_CALLBACK (cb_desktop_font_changed),
5331 					 wbcg, 0);
5332 	}
5333 	return wbcg->font_desc;
5334 }
5335 
5336 /**
5337  * wbcg_find_for_workbook:
5338  * @wb: #Workbook
5339  * @candidate: a candidate #WBCGtk
5340  * @pref_screen: the preferred screen.
5341  * @pref_display: the preferred display.
5342  *
5343  * Returns: (transfer none): the found #WBCGtk or %NULL.
5344  **/
5345 WBCGtk *
wbcg_find_for_workbook(Workbook * wb,WBCGtk * candidate,GdkScreen * pref_screen,GdkDisplay * pref_display)5346 wbcg_find_for_workbook (Workbook *wb,
5347 			WBCGtk *candidate,
5348 			GdkScreen *pref_screen,
5349 			GdkDisplay *pref_display)
5350 {
5351 	gboolean has_screen, has_display;
5352 
5353 	g_return_val_if_fail (GNM_IS_WORKBOOK (wb), NULL);
5354 	g_return_val_if_fail (candidate == NULL || GNM_IS_WBC_GTK (candidate), NULL);
5355 
5356 	if (candidate && wb_control_get_workbook (GNM_WBC (candidate)) == wb)
5357 		return candidate;
5358 
5359 	if (!pref_screen && candidate)
5360 		pref_screen = wbcg_get_screen (candidate);
5361 
5362 	if (!pref_display && pref_screen)
5363 		pref_display = gdk_screen_get_display (pref_screen);
5364 
5365 	candidate = NULL;
5366 	has_screen = FALSE;
5367 	has_display = FALSE;
5368 	WORKBOOK_FOREACH_CONTROL(wb, wbv, wbc, {
5369 		if (GNM_IS_WBC_GTK (wbc)) {
5370 			WBCGtk *wbcg = WBC_GTK (wbc);
5371 			GdkScreen *screen = wbcg_get_screen (wbcg);
5372 			GdkDisplay *display = gdk_screen_get_display (screen);
5373 
5374 			if (pref_screen == screen && !has_screen) {
5375 				has_screen = has_display = TRUE;
5376 				candidate = wbcg;
5377 			} else if (pref_display == display && !has_display) {
5378 				has_display = TRUE;
5379 				candidate = wbcg;
5380 			} else if (!candidate)
5381 				candidate = wbcg;
5382 		}
5383 	});
5384 
5385 	return candidate;
5386 }
5387 
5388 void
wbcg_focus_current_cell_indicator(WBCGtk const * wbcg)5389 wbcg_focus_current_cell_indicator (WBCGtk const *wbcg)
5390 {
5391 	gtk_widget_grab_focus (GTK_WIDGET (wbcg->selection_descriptor));
5392 	gtk_editable_select_region (GTK_EDITABLE (wbcg->selection_descriptor), 0, -1);
5393 }
5394