1 /*
2  * gui-util.c:  Various GUI utility functions.
3  *
4  * Author:
5  *  Miguel de Icaza (miguel@gnu.org)
6  */
7 
8 #include <gnumeric-config.h>
9 #include <glib/gi18n-lib.h>
10 #include <gnumeric.h>
11 #include <libgnumeric.h>
12 #include <gui-util.h>
13 
14 #include <gutils.h>
15 #include <parse-util.h>
16 #include <style.h>
17 #include <style-color.h>
18 #include <value.h>
19 #include <number-match.h>
20 #include <gnm-format.h>
21 #include <application.h>
22 #include <workbook.h>
23 #include <libgnumeric.h>
24 #include <wbc-gtk.h>
25 #include <widgets/gnm-expr-entry.h>
26 
27 #include <goffice/goffice.h>
28 #include <atk/atkrelation.h>
29 #include <atk/atkrelationset.h>
30 #include <gdk/gdkkeysyms.h>
31 
32 #include <string.h>
33 
34 #define ERROR_INFO_MAX_LEVEL 9
35 #define ERROR_INFO_TAG_NAME "errorinfotag%i"
36 
37 static void
insert_error_info(GtkTextBuffer * text,GOErrorInfo * error,gint level)38 insert_error_info (GtkTextBuffer* text, GOErrorInfo *error, gint level)
39 {
40 	gchar *message = (gchar *) go_error_info_peek_message (error);
41 	GSList *details_list, *l;
42 	GtkTextIter start, last;
43 	gchar *tag_name = g_strdup_printf (ERROR_INFO_TAG_NAME,
44 					   MIN (level, ERROR_INFO_MAX_LEVEL));
45 	if (message == NULL)
46 		message = g_strdup (_("Multiple errors\n"));
47 	else
48 		message = g_strdup_printf ("%s\n", message);
49 	gtk_text_buffer_get_bounds (text, &start, &last);
50 	gtk_text_buffer_insert_with_tags_by_name (text, &last,
51 						  message, -1,
52 						  tag_name, NULL);
53 	g_free (tag_name);
54 	g_free (message);
55 	details_list = go_error_info_peek_details (error);
56 	for (l = details_list; l != NULL; l = l->next) {
57 		GOErrorInfo *detail_error = l->data;
58 		insert_error_info (text, detail_error, level + 1);
59 	}
60 	return;
61 }
62 
63 /**
64  * gnumeric_go_error_info_list_dialog_create:
65  * @errs: (element-type GOErrorInfo):
66  *
67  * SHOULD BE IN GOFFICE
68  * Returns: (transfer full): the newly allocated dialog.
69  */
70 static GtkWidget *
gnumeric_go_error_info_list_dialog_create(GSList * errs)71 gnumeric_go_error_info_list_dialog_create (GSList *errs)
72 {
73 	GtkWidget *dialog;
74 	GtkWidget *scrolled_window;
75 	GtkTextView *view;
76 	GtkTextBuffer *text;
77 	GtkMessageType mtype;
78 	gint bf_lim = 1;
79 	gint i;
80 	GdkScreen *screen;
81 	GSList *l, *lf;
82 	int severity = 0, this_severity;
83 	gboolean message_null = TRUE;
84 
85 	for (l = errs; l != NULL; l = l->next) {
86 		GOErrorInfo *err = l->data;
87 		if (go_error_info_peek_message (err)!= NULL)
88 			message_null = FALSE;
89 		this_severity = go_error_info_peek_severity (err);
90 		if (this_severity > severity)
91 			severity = this_severity;
92 	}
93 	lf = g_slist_copy (errs);
94 	lf = g_slist_reverse (lf);
95 
96 	if (message_null)
97 		bf_lim++;
98 
99 	mtype = GTK_MESSAGE_ERROR;
100 	if (severity < GO_ERROR)
101 		mtype = GTK_MESSAGE_WARNING;
102 	dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_DESTROY_WITH_PARENT,
103 					 mtype, GTK_BUTTONS_CLOSE, " ");
104 	screen = gtk_widget_get_screen (dialog);
105 	gtk_widget_set_size_request (dialog,
106 				     gdk_screen_get_width (screen) / 3,
107 				     gdk_screen_get_width (screen) / 4);
108 	scrolled_window = gtk_scrolled_window_new (NULL, NULL);
109 	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
110 					GTK_POLICY_AUTOMATIC,
111 					GTK_POLICY_AUTOMATIC);
112 	gtk_scrolled_window_set_shadow_type
113 		(GTK_SCROLLED_WINDOW (scrolled_window),
114 		 GTK_SHADOW_ETCHED_IN);
115 	view = GTK_TEXT_VIEW (gtk_text_view_new ());
116 	gtk_text_view_set_wrap_mode (view, GTK_WRAP_WORD);
117 	gtk_text_view_set_editable (view, FALSE);
118 	gtk_text_view_set_cursor_visible (view, FALSE);
119 
120 	gtk_text_view_set_pixels_below_lines
121 		(view, gtk_text_view_get_pixels_inside_wrap (view) + 3);
122 	text = gtk_text_view_get_buffer (view);
123 	for (i = ERROR_INFO_MAX_LEVEL; i-- > 0;) {
124 		gchar *tag_name = g_strdup_printf (ERROR_INFO_TAG_NAME, i);
125 		gtk_text_buffer_create_tag
126 			(text, tag_name,
127 			 "left_margin", i * 12,
128 			 "right_margin", i * 12,
129 			 "weight", ((i < bf_lim)
130 				    ? PANGO_WEIGHT_BOLD
131 				    : PANGO_WEIGHT_NORMAL),
132 			 NULL);
133 		g_free (tag_name);
134 	}
135 	for (l = lf; l != NULL; l = l->next) {
136 		GOErrorInfo *err = l->data;
137 		insert_error_info (text, err, 0);
138 	}
139 	g_slist_free (lf);
140 
141 	gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (view));
142 	gtk_widget_show_all (GTK_WIDGET (scrolled_window));
143 	gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), scrolled_window, TRUE, TRUE, 0);
144 
145 	gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CLOSE);
146 	return dialog;
147 }
148 
149 /**
150  * gnm_go_error_info_dialog_create:
151  *
152  * SHOULD BE IN GOFFICE
153  * Returns: (transfer full): the newly allocated dialog.
154  */
155 GtkWidget *
gnm_go_error_info_dialog_create(GOErrorInfo * error)156 gnm_go_error_info_dialog_create (GOErrorInfo *error)
157 {
158 	GSList *l = g_slist_append (NULL, error);
159 	GtkWidget *w = gnumeric_go_error_info_list_dialog_create (l);
160 	g_slist_free (l);
161 	return w;
162 }
163 
164 /**
165  * gnm_go_error_info_dialog_show:
166  *
167  */
168 void
gnm_go_error_info_dialog_show(GtkWindow * parent,GOErrorInfo * error)169 gnm_go_error_info_dialog_show (GtkWindow *parent, GOErrorInfo *error)
170 {
171 	GtkWidget *dialog = gnm_go_error_info_dialog_create (error);
172 	go_gtk_dialog_run (GTK_DIALOG (dialog), parent);
173 }
174 
175 /**
176  * gnm_go_error_info_list_dialog_show:
177  * @parent:
178  * @errs: (element-type GOErrorInfo):
179  *
180  */
181 void
gnm_go_error_info_list_dialog_show(GtkWindow * parent,GSList * errs)182 gnm_go_error_info_list_dialog_show (GtkWindow *parent,
183 					 GSList *errs)
184 {
185 	GtkWidget *dialog = gnumeric_go_error_info_list_dialog_create (errs);
186 	go_gtk_dialog_run (GTK_DIALOG (dialog), parent);
187 }
188 
189 
190 typedef struct {
191 	WBCGtk *wbcg;
192 	GtkWidget	   *dialog;
193 	char const *key;
194 	gboolean freed;
195 } KeyedDialogContext;
196 
197 static void
cb_free_keyed_dialog_context(KeyedDialogContext * ctxt)198 cb_free_keyed_dialog_context (KeyedDialogContext *ctxt)
199 {
200 	if (ctxt->freed)
201 		return;
202 	ctxt->freed = TRUE;
203 
204 	/*
205 	 * One of these causes a recursive call which will do nothing due to
206 	 * ->freed.
207 	 */
208 	g_object_set_data (G_OBJECT (ctxt->wbcg), ctxt->key, NULL);
209 	g_object_set_data (G_OBJECT (ctxt->dialog), "KeyedDialog", NULL);
210 	g_free (ctxt);
211 }
212 
213 static void
cb_keyed_dialog_destroy(GtkDialog * dialog)214 cb_keyed_dialog_destroy (GtkDialog *dialog)
215 {
216 	/*
217 	 * gtk-builder likes to hold refs on objects.  That interferes
218 	 * with the way we handle finalization of dialogs' state.
219 	 * Trigger this now.
220 	 */
221 	g_object_set_data (G_OBJECT (dialog), "state", NULL);
222 }
223 
224 static gint
cb_keyed_dialog_keypress(GtkWidget * dialog,GdkEventKey * event,G_GNUC_UNUSED gpointer user)225 cb_keyed_dialog_keypress (GtkWidget *dialog, GdkEventKey *event,
226 			  G_GNUC_UNUSED gpointer user)
227 {
228 	if (event->keyval == GDK_KEY_Escape) {
229 		gtk_widget_destroy (GTK_WIDGET (dialog));
230 		return TRUE;
231 	}
232 	return FALSE;
233 }
234 
235 #define SAVE_SIZES_SCREEN_KEY "geometry-hash"
236 
237 static gboolean debug_dialog_size;
238 
239 static void
cb_save_sizes(GtkWidget * dialog,const GtkAllocation * allocation,const char * key)240 cb_save_sizes (GtkWidget *dialog,
241 	       const GtkAllocation *allocation,
242 	       const char *key)
243 {
244 	GdkRectangle *r;
245 	GdkScreen *screen = gtk_widget_get_screen (dialog);
246 	GHashTable *h = g_object_get_data (G_OBJECT (screen),
247 					   SAVE_SIZES_SCREEN_KEY);
248 	GdkWindow *window = gtk_widget_get_window (dialog);
249 
250 	if (!h) {
251 		h = g_hash_table_new_full (g_str_hash, g_str_equal,
252 					   (GDestroyNotify)g_free,
253 					   (GDestroyNotify)g_free);
254 		/*
255 		 * We hang this on the screen because pixel sizes make
256 		 * no sense across screens.
257 		 *
258 		 * ANYONE WHO CHANGES THIS CODE TO SAVE THESE SIZES ON EXIT
259 		 * AND RELOADS THEM ON STARTUP WILL GET TARRED AND FEATHERED.
260 		 * -- MW, 20071113
261 		 */
262 		g_object_set_data_full (G_OBJECT (screen),
263 					SAVE_SIZES_SCREEN_KEY, h,
264 					(GDestroyNotify)g_hash_table_destroy);
265 	}
266 
267 	r = g_memdup (allocation, sizeof (*allocation));
268 	if (window)
269 		gdk_window_get_position (gtk_widget_get_window (dialog), &r->x, &r->y);
270 
271 	if (debug_dialog_size) {
272 		g_printerr ("Saving %s to %dx%d at (%d,%d)\n",
273 			    key, r->width, r->height, r->x, r->y);
274 	}
275 	g_hash_table_replace (h, g_strdup (key), r);
276 }
277 
278 void
gnm_restore_window_geometry(GtkWindow * dialog,const char * key)279 gnm_restore_window_geometry (GtkWindow *dialog, const char *key)
280 {
281 	GtkWidget *top = gtk_widget_get_toplevel (GTK_WIDGET (dialog));
282 	GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (dialog));
283 	GHashTable *h = g_object_get_data (G_OBJECT (screen), SAVE_SIZES_SCREEN_KEY);
284 	GdkRectangle *allocation = h ? g_hash_table_lookup (h, key) : NULL;
285 
286 	debug_dialog_size = gnm_debug_flag ("dialog-size");
287 
288 	if (allocation) {
289 		if (debug_dialog_size)
290 			g_printerr ("Restoring %s to %dx%d at (%d,%d)\n",
291 				    key, allocation->width, allocation->height,
292 				    allocation->x, allocation->y);
293 
294 		gtk_window_move
295 			(GTK_WINDOW (top),
296 			 allocation->x, allocation->y);
297 		gtk_window_set_default_size
298 			(GTK_WINDOW (top),
299 			 allocation->width, allocation->height);
300 	}
301 
302 	g_signal_connect (G_OBJECT (dialog), "size-allocate",
303 			  G_CALLBACK (cb_save_sizes),
304 			  (gpointer)key);
305 }
306 
307 /**
308  * gnm_keyed_dialog:
309  * @wbcg:    A WBCGtk
310  * @dialog:  A transient window
311  * @key:     A key to identify the dialog
312  *
313  * Make dialog a transient child of wbcg, attaching to wbcg object data to
314  * identify the dialog. The object data makes it possible to ensure that
315  * only one dialog of a kind can be displayed for a wbcg. Deallocation of
316  * the object data is managed here.
317  **/
318 void
gnm_keyed_dialog(WBCGtk * wbcg,GtkWindow * dialog,char const * key)319 gnm_keyed_dialog (WBCGtk *wbcg, GtkWindow *dialog, char const *key)
320 {
321 	KeyedDialogContext *ctxt;
322 
323 	g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
324 	g_return_if_fail (GTK_IS_WINDOW (dialog));
325 	g_return_if_fail (key != NULL);
326 
327 	wbcg_set_transient (wbcg, dialog);
328 
329 	go_dialog_guess_alternative_button_order (GTK_DIALOG (dialog));
330 
331 	ctxt = g_new (KeyedDialogContext, 1);
332 	ctxt->wbcg   = wbcg;
333 	ctxt->dialog = GTK_WIDGET (dialog);
334 	ctxt->key  = key;
335 	ctxt->freed = FALSE;
336 	g_object_set_data_full (G_OBJECT (wbcg), key, ctxt,
337 				(GDestroyNotify)cb_free_keyed_dialog_context);
338 	g_object_set_data_full (G_OBJECT (dialog), "KeyedDialog", ctxt,
339 				(GDestroyNotify)cb_free_keyed_dialog_context);
340 	g_signal_connect (G_OBJECT (dialog), "key_press_event",
341 			  G_CALLBACK (cb_keyed_dialog_keypress), NULL);
342 	g_signal_connect (G_OBJECT (dialog), "destroy",
343 			  G_CALLBACK (cb_keyed_dialog_destroy), NULL);
344 
345 	gnm_restore_window_geometry (dialog, key);
346 }
347 
348 /**
349  * gnm_dialog_raise_if_exists:
350  * @wbcg:    A WBCGtk
351  * @key:     A key to identify the dialog
352  *
353  * Raise the dialog identified by key if it is registered on the wbcg.
354  *
355  * Returns: (transfer none) (type GtkDialog) (nullable): existing dialog
356  **/
357 gpointer
gnm_dialog_raise_if_exists(WBCGtk * wbcg,char const * key)358 gnm_dialog_raise_if_exists (WBCGtk *wbcg, char const *key)
359 {
360 	KeyedDialogContext *ctxt;
361 
362 	g_return_val_if_fail (wbcg != NULL, NULL);
363 	g_return_val_if_fail (key != NULL, NULL);
364 
365 	/* Ensure we only pop up one copy per workbook */
366 	ctxt = g_object_get_data (G_OBJECT (wbcg), key);
367 	if (ctxt && GTK_IS_WINDOW (ctxt->dialog)) {
368 		gdk_window_raise (gtk_widget_get_window (ctxt->dialog));
369 		return ctxt->dialog;
370 	} else
371 		return NULL;
372 }
373 
374 static gboolean
cb_activate_default(GtkWindow * window)375 cb_activate_default (GtkWindow *window)
376 {
377 	GtkWidget *dw = gtk_window_get_default_widget (window);
378 	/*
379 	 * gtk_window_activate_default has a bad habit of trying
380 	 * to activate the focus widget.
381 	 */
382 	return dw && gtk_widget_is_sensitive (dw) &&
383 		gtk_window_activate_default (window);
384 }
385 
386 
387 /**
388  * gnm_editable_enters:
389  * @window: dialog to affect.
390  * @editable: Editable to affect.
391  *
392  * Make the "activate" signal of an editable click the default dialog button.
393  *
394  * This is a literal copy of gnome_dialog_editable_enters, but not restricted
395  * to GnomeDialogs.
396  *
397  * Normally if there's an editable widget (such as #GtkEntry) in your
398  * dialog, pressing Enter will activate the editable rather than the
399  * default dialog button. However, in most cases, the user expects to
400  * type something in and then press enter to close the dialog. This
401  * function enables that behavior.
402  *
403  **/
404 void
gnm_editable_enters(GtkWindow * window,GtkWidget * w)405 gnm_editable_enters (GtkWindow *window, GtkWidget *w)
406 {
407 	g_return_if_fail (GTK_IS_WINDOW(window));
408 
409 	/* because I really do not feel like changing all the calls to this routine */
410 	if (GNM_EXPR_ENTRY_IS (w))
411 		w = GTK_WIDGET (gnm_expr_entry_get_entry (GNM_EXPR_ENTRY (w)));
412 
413 	g_signal_connect_swapped (G_OBJECT (w),
414 		"activate",
415 		G_CALLBACK (cb_activate_default), window);
416 }
417 
418 /**
419  * gnm_gtk_radio_group_get_selected:
420  * @radio_group: (element-type GtkRadioButton): list of radio buttons.
421  *
422  * Returns: the index of the selected radio button starting from list end.
423  **/
424 int
gnm_gtk_radio_group_get_selected(GSList * radio_group)425 gnm_gtk_radio_group_get_selected (GSList *radio_group)
426 {
427 	GSList *l;
428 	int i, c;
429 
430 	g_return_val_if_fail (radio_group != NULL, 0);
431 
432 	c = g_slist_length (radio_group);
433 
434 	for (i = 0, l = radio_group; l; l = l->next, i++){
435 		GtkRadioButton *button = l->data;
436 
437 		if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
438 			return c - i - 1;
439 	}
440 
441 	return 0;
442 }
443 
444 
445 int
gnm_gui_group_value(gpointer gui,char const * const group[])446 gnm_gui_group_value (gpointer gui, char const * const group[])
447 {
448 	int i;
449 	for (i = 0; group[i]; i++) {
450 		GtkWidget *w = go_gtk_builder_get_widget (gui, group[i]);
451 		if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w)))
452 			return i;
453 	}
454 	return -1;
455 }
456 
457 static gboolean
cb_delayed_destroy(gpointer w)458 cb_delayed_destroy (gpointer w)
459 {
460 	gtk_widget_destroy (gtk_widget_get_toplevel (w));
461 	gtk_widget_destroy (w);
462 	g_object_unref (w);
463 	return FALSE;
464 }
465 
466 static void
kill_popup_menu(GtkWidget * widget,G_GNUC_UNUSED gpointer user)467 kill_popup_menu (GtkWidget *widget, G_GNUC_UNUSED gpointer user)
468 {
469 	/* gtk+ currently gets unhappy if we destroy here, see bug 725142 */
470 	g_idle_add (cb_delayed_destroy, widget);
471 }
472 
473 /**
474  * gnumeric_popup_menu:
475  * @menu: #GtkMenu
476  * @event: (nullable): #GdkEvent
477  *
478  * Bring up a popup and if @event is non-%NULL ensure that the popup is on the
479  * right screen.
480  **/
481 void
gnumeric_popup_menu(GtkMenu * menu,GdkEvent * event)482 gnumeric_popup_menu (GtkMenu *menu, GdkEvent *event)
483 {
484 	g_return_if_fail (menu != NULL);
485 	g_return_if_fail (GTK_IS_MENU (menu));
486 
487 	if (event)
488 		gtk_menu_set_screen (menu, gdk_event_get_screen (event));
489 
490 	g_object_ref_sink (menu);
491 	g_signal_connect (G_OBJECT (menu),
492 			  "hide",
493 			  G_CALLBACK (kill_popup_menu), NULL);
494 
495 	/* Do NOT pass the button used to create the menu.
496 	 * instead pass 0.  Otherwise bringing up a menu with
497 	 * the right button will disable clicking on the menu with the left.
498 	 */
499 	gtk_menu_popup (menu, NULL, NULL, NULL, NULL, 0,
500 			(event
501 			 ? gdk_event_get_time (event)
502 			 : gtk_get_current_event_time()));
503 }
504 
505 static void
gnumeric_tooltip_set_style(GtkWidget * widget)506 gnumeric_tooltip_set_style (GtkWidget *widget)
507 {
508 	gtk_style_context_add_class (gtk_widget_get_style_context (widget),
509 				     GTK_STYLE_CLASS_TOOLTIP);
510 	gtk_style_context_add_class (gtk_widget_get_style_context (widget),
511 				     "pseudo-tooltip");
512 	if (GTK_IS_CONTAINER (widget))
513 		gtk_container_forall (GTK_CONTAINER (widget),
514 				      (GtkCallback) (gnumeric_tooltip_set_style),
515 				      NULL);
516 }
517 
518 /**
519  * gnm_convert_to_tooltip:
520  * @ref_widget:
521  * @widget:
522  *
523  * Returns: (transfer none): @widget
524  **/
525 GtkWidget *
gnm_convert_to_tooltip(GtkWidget * ref_widget,GtkWidget * widget)526 gnm_convert_to_tooltip (GtkWidget *ref_widget, GtkWidget *widget)
527 {
528 	GtkWidget *tip, *frame;
529 	GdkScreen *screen = gtk_widget_get_screen (ref_widget);
530 
531 	tip = gtk_window_new (GTK_WINDOW_POPUP);
532 	gtk_window_set_type_hint (GTK_WINDOW (tip),
533 				  GDK_WINDOW_TYPE_HINT_TOOLTIP);
534 	gtk_window_set_resizable (GTK_WINDOW (tip), FALSE);
535 	gtk_window_set_gravity (GTK_WINDOW (tip), GDK_GRAVITY_NORTH_WEST);
536 	gtk_window_set_screen (GTK_WINDOW (tip), screen);
537 	gtk_widget_set_name (tip, "gtk-tooltip");
538 
539 	frame = gtk_frame_new (NULL);
540 	gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
541 	gtk_widget_show (frame);
542 	gtk_container_add (GTK_CONTAINER (frame), widget);
543 	gtk_container_add (GTK_CONTAINER (tip), frame);
544 
545 	gnumeric_tooltip_set_style (tip);
546 
547 	return widget;
548 }
549 
550 /**
551  * gnm_create_tooltip:
552  *
553  * Returns: (transfer full): the newly allocated #GtkWidget.
554  **/
555 GtkWidget *
gnm_create_tooltip(GtkWidget * ref_widget)556 gnm_create_tooltip (GtkWidget *ref_widget)
557 {
558 	return gnm_convert_to_tooltip (ref_widget, gtk_label_new (""));
559 }
560 
561 void
gnm_position_tooltip(GtkWidget * tip,int px,int py,gboolean horizontal)562 gnm_position_tooltip (GtkWidget *tip, int px, int py, gboolean horizontal)
563 {
564 	GtkRequisition req;
565 
566 	gtk_widget_get_preferred_size (tip, &req, NULL);
567 
568 	if (horizontal){
569 		px -= req.width / 2;
570 		py -= req.height + 20;
571 	} else {
572 		px -= req.width + 20;
573 		py -= req.height / 2;
574 	}
575 
576 	if (px < 0)
577 		px = 0;
578 	if (py < 0)
579 		py = 0;
580 
581 	gtk_window_move (GTK_WINDOW (gtk_widget_get_toplevel (tip)), px, py);
582 }
583 
584 /**
585  * gnm_gtk_builder_load:
586  * @cc: #GOCmdContext
587  * @uifile:
588  *
589  * Simple utility to open ui files
590  * Returns: (transfer full): the newly allocated #GtkBuilder.
591  **/
592 GtkBuilder *
gnm_gtk_builder_load(char const * uifile,char const * domain,GOCmdContext * cc)593 gnm_gtk_builder_load (char const *uifile, char const *domain, GOCmdContext *cc)
594 {
595 	GtkBuilder *gui;
596 	char *f;
597 
598 	if (strncmp (uifile, "res:", 4) == 0) {
599 		f = g_strconcat ("res:/org/gnumeric/gnumeric/",
600 				 uifile + 4,
601 				 NULL);
602 	} else if (g_path_is_absolute (uifile)) {
603 		f = g_strdup (uifile);
604 	} else {
605 		f = g_strconcat ("res:gnm:", uifile, NULL);
606 	}
607 
608 	gui = go_gtk_builder_load (f, domain, cc);
609 	g_free (f);
610 
611 	return gui;
612 }
613 
614 static void
popup_item_activate(GtkWidget * item,GnmPopupMenuElement const * elem)615 popup_item_activate (GtkWidget *item, GnmPopupMenuElement const *elem)
616 {
617 	GtkWidget *menu;
618 	GnmPopupMenuHandler handler;
619 	gpointer user_data;
620 
621 	// Go to top-level menu.  This shouldn't be that hard.
622 	menu = item;
623 	while (TRUE) {
624 		if (GTK_IS_MENU_ITEM (menu))
625 			menu = gtk_widget_get_parent (menu);
626 		else if (GTK_IS_MENU (menu)) {
627 			GtkWidget *a = gtk_menu_get_attach_widget (GTK_MENU (menu));
628 			if (a)
629 				menu = a;
630 			else
631 				break;
632 		} else
633 			break;
634 	}
635 	handler = g_object_get_data (G_OBJECT (menu), "handler");
636 	user_data = g_object_get_data (G_OBJECT (menu), "user-data");
637 	g_return_if_fail (handler != NULL);
638 
639 	handler (elem, user_data);
640 }
641 
642 /**
643  * gnm_create_popup_menu:
644  * @elements:
645  * @handler: (scope notified):
646  * @user_data: user data to pass to @handler.
647  * @notify: destroy notification for @user_data
648  * @display_filter:
649  * @sensitive_filter:
650  * @event:
651  **/
652 void
gnm_create_popup_menu(GnmPopupMenuElement const * elements,GnmPopupMenuHandler handler,gpointer user_data,GDestroyNotify notify,int display_filter,int sensitive_filter,GdkEvent * event)653 gnm_create_popup_menu (GnmPopupMenuElement const *elements,
654 		       GnmPopupMenuHandler handler,
655 		       gpointer user_data,
656 		       GDestroyNotify notify,
657 		       int display_filter, int sensitive_filter,
658 		       GdkEvent *event)
659 {
660 	char const *trans;
661 	GSList *menu_stack = NULL;
662 	GtkWidget *menu, *item;
663 
664 	menu = gtk_menu_new ();
665 	g_object_set_data (G_OBJECT (menu), "handler", (gpointer)handler);
666 	g_object_set_data_full (G_OBJECT (menu), "user-data", user_data, notify);
667 	for (; NULL != elements->name ; elements++) {
668 		char const * const name = elements->name;
669 		char const * const pix_name = elements->pixmap;
670 
671 		item = NULL;
672 
673 		if (elements->display_filter != 0 &&
674 		    !(elements->display_filter & display_filter)) {
675 			if (elements->allocated_name) {
676 				g_free (elements->allocated_name);
677 				*(gchar **)(&elements->allocated_name) = NULL;
678 			}
679 			continue;
680 		}
681 
682 		if (name != NULL && *name != '\0') {
683 			if (elements->allocated_name)
684 				trans = elements->allocated_name;
685 			else
686 				trans = _(name);
687 			item = gtk_image_menu_item_new_with_mnemonic (trans);
688 			if (elements->sensitive_filter != 0 &&
689 			    (elements->sensitive_filter & sensitive_filter))
690 				gtk_widget_set_sensitive (GTK_WIDGET (item), FALSE);
691 			if (pix_name != NULL) {
692 				GtkWidget *image = gtk_image_new_from_icon_name (pix_name,
693 										 GTK_ICON_SIZE_MENU);
694 				gtk_widget_show (image);
695 				gtk_image_menu_item_set_image (
696 					GTK_IMAGE_MENU_ITEM (item),
697 					image);
698 			}
699 			if (elements->allocated_name) {
700 				g_free (elements->allocated_name);
701 				*(gchar **)(&elements->allocated_name) = NULL;
702 			}
703 		} else if (elements->index >= 0) {
704 			/* separator */
705 			item = gtk_separator_menu_item_new ();
706 		}
707 
708 		if (elements->index > 0) {
709 			g_signal_connect (G_OBJECT (item),
710 					  "activate",
711 					  G_CALLBACK (popup_item_activate),
712 					  (gpointer)elements);
713 		}
714 		if (NULL != item) {
715 			gtk_widget_show (item);
716 			gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
717 		}
718 	      	if (elements->index < 0) {
719 			if (NULL != item) {
720 				menu_stack = g_slist_prepend (menu_stack, menu);
721 				menu = gtk_menu_new ();
722 				gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu);
723 			} else {
724 				menu = menu_stack->data;
725 				menu_stack = g_slist_remove (menu_stack, menu);
726 			}
727 		}
728 	}
729 	gnumeric_popup_menu (GTK_MENU (menu), event);
730 }
731 
732 void
gnm_init_help_button(GtkWidget * w,char const * lnk)733 gnm_init_help_button (GtkWidget *w, char const *lnk)
734 {
735 	go_gtk_help_button_init (w, gnm_sys_data_dir (), "gnumeric", lnk);
736 }
737 
738 char *
gnm_textbuffer_get_text(GtkTextBuffer * buf)739 gnm_textbuffer_get_text (GtkTextBuffer *buf)
740 {
741 	GtkTextIter    start, end;
742 
743 	g_return_val_if_fail (buf != NULL, NULL);
744 
745 	gtk_text_buffer_get_start_iter (buf, &start);
746 	gtk_text_buffer_get_end_iter (buf, &end);
747 	/* We are using slice rather than text so that the tags still match */
748 	return gtk_text_buffer_get_slice (buf, &start, &end, FALSE);
749 }
750 
751 char *
gnm_textview_get_text(GtkTextView * text_view)752 gnm_textview_get_text (GtkTextView *text_view)
753 {
754 	return gnm_textbuffer_get_text
755 		(gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view)));
756 }
757 
758 void
gnm_textview_set_text(GtkTextView * text_view,char const * txt)759 gnm_textview_set_text (GtkTextView *text_view, char const *txt)
760 {
761 	gtk_text_buffer_set_text (
762 		gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view)),
763 		txt, -1);
764 }
765 
766 void
gnm_load_pango_attributes_into_buffer(PangoAttrList * markup,GtkTextBuffer * buffer,gchar const * str)767 gnm_load_pango_attributes_into_buffer (PangoAttrList  *markup, GtkTextBuffer *buffer, gchar const *str)
768 {
769 	gchar *str_retrieved = NULL;
770 
771 	if (str == NULL) {
772 		GtkTextIter start, end;
773 		gtk_text_buffer_get_start_iter (buffer, &start);
774 		gtk_text_buffer_get_end_iter (buffer, &end);
775 		str = str_retrieved = gtk_text_buffer_get_slice
776 			(buffer, &start, &end, TRUE);
777 	}
778 
779 	go_load_pango_attributes_into_buffer (markup, buffer, str);
780 
781 	g_free (str_retrieved);
782 }
783 
784 #define gnmstoretexttagattrinpangoint(nameset, name, gnm_pango_attr_new)  \
785 	if (gnm_object_get_bool (tag, nameset)) {			  \
786 		int value;                                                \
787 		g_object_get (G_OBJECT (tag), name, &value, NULL);        \
788 		attr =  gnm_pango_attr_new (value);                       \
789 		attr->start_index = x;                                    \
790 		attr->end_index = y;                                      \
791 		pango_attr_list_change (list, attr);                      \
792 	}
793 
794 
795 static void
gnm_store_text_tag_attr_in_pango(PangoAttrList * list,GtkTextTag * tag,GtkTextIter * start,gchar const * text)796 gnm_store_text_tag_attr_in_pango (PangoAttrList *list, GtkTextTag *tag, GtkTextIter *start, gchar const *text)
797 {
798 	GtkTextIter end = *start;
799 	gint x, y;
800 	PangoAttribute * attr;
801 
802 	gtk_text_iter_forward_to_tag_toggle (&end, tag);
803 	x = g_utf8_offset_to_pointer (text, gtk_text_iter_get_offset (start)) - text;
804 	y = g_utf8_offset_to_pointer (text, gtk_text_iter_get_offset (&end)) - text;
805 
806 	if (gnm_object_get_bool (tag, "foreground-set")) {
807 		GdkRGBA *color = NULL;
808 		g_object_get (G_OBJECT (tag), "foreground-rgba", &color, NULL);
809 		if (color) {
810 			/* dividing 0 to 1 into 65536 equal length intervals */
811 			attr =  pango_attr_foreground_new
812 				((int)(CLAMP (color->red * 65536, 0., 65535.)),
813 				 (int)(CLAMP (color->green * 65536, 0., 65535.)),
814 				 (int)(CLAMP (color->blue * 65536, 0., 65535.)));
815 			gdk_rgba_free (color);
816 			attr->start_index = x;
817 			attr->end_index = y;
818 			pango_attr_list_change (list, attr);
819 		}
820 	}
821 
822 	gnmstoretexttagattrinpangoint ("style-set", "style", pango_attr_style_new)
823 	gnmstoretexttagattrinpangoint ("weight-set", "weight", pango_attr_weight_new)
824 	gnmstoretexttagattrinpangoint ("strikethrough-set", "strikethrough", pango_attr_strikethrough_new)
825 	gnmstoretexttagattrinpangoint ("underline-set", "underline", pango_attr_underline_new)
826 	gnmstoretexttagattrinpangoint ("rise-set", "rise", pango_attr_rise_new)
827 }
828 
829 #undef gnmstoretexttagattrinpangoint
830 
831 PangoAttrList *
gnm_get_pango_attributes_from_buffer(GtkTextBuffer * buffer)832 gnm_get_pango_attributes_from_buffer (GtkTextBuffer *buffer)
833 {
834 	PangoAttrList *list = pango_attr_list_new ();
835 	GtkTextIter start;
836 	gchar *text = gnm_textbuffer_get_text (buffer);
837 
838 	gtk_text_buffer_get_start_iter (buffer, &start);
839 
840 	while (!gtk_text_iter_is_end (&start)) {
841 		if (gtk_text_iter_begins_tag (&start, NULL)) {
842 			GSList *ptr, *l = gtk_text_iter_get_toggled_tags (&start, TRUE);
843 			for (ptr = l; ptr; ptr = ptr->next)
844 				gnm_store_text_tag_attr_in_pango (list, ptr->data, &start, text);
845 		}
846 		gtk_text_iter_forward_to_tag_toggle (&start, NULL);
847 	}
848 
849 	g_free (text);
850 
851 	return list;
852 }
853 
854 void
focus_on_entry(GtkEntry * entry)855 focus_on_entry (GtkEntry *entry)
856 {
857 	if (entry == NULL)
858 		return;
859 	gtk_widget_grab_focus (GTK_WIDGET(entry));
860 	gtk_editable_set_position (GTK_EDITABLE (entry), 0);
861 	gtk_editable_select_region (GTK_EDITABLE (entry), 0,
862 				    gtk_entry_get_text_length (entry));
863 }
864 
865 gboolean
entry_to_float_with_format_default(GtkEntry * entry,gnm_float * the_float,gboolean update,GOFormat const * format,gnm_float num)866 entry_to_float_with_format_default (GtkEntry *entry, gnm_float *the_float,
867 				    gboolean update,
868 				    GOFormat const *format, gnm_float num)
869 {
870 	char const *text = gtk_entry_get_text (entry);
871 	gboolean need_default = (text == NULL);
872 
873 	if (!need_default) {
874 		char *new_text = g_strdup (text);
875 		need_default = (0 ==  strlen (g_strstrip(new_text)));
876 		g_free (new_text);
877 	}
878 
879 	if (need_default && !update) {
880 		*the_float = num;
881 		return FALSE;
882 	}
883 
884 	if (need_default)
885 		float_to_entry (entry, num);
886 
887 	return entry_to_float_with_format (entry, the_float, update, format);
888 }
889 
890 gboolean
entry_to_float_with_format(GtkEntry * entry,gnm_float * the_float,gboolean update,GOFormat const * format)891 entry_to_float_with_format (GtkEntry *entry, gnm_float *the_float,
892 			    gboolean update, GOFormat const *format)
893 {
894 	GnmValue *value = format_match_number (gtk_entry_get_text (entry), format, NULL);
895 
896 	*the_float = 0.0;
897 	if (!value)
898 		return TRUE;
899 
900 	*the_float = value_get_as_float (value);
901 	if (update) {
902 		char *tmp = format_value (format, value, 16, NULL);
903 		gtk_entry_set_text (entry, tmp);
904 		g_free (tmp);
905 	}
906 
907 	value_release (value);
908 	return FALSE;
909 }
910 
911 /**
912  * entry_to_int:
913  * @entry:
914  * @the_int:
915  * @update:
916  *
917  * Retrieve an int from an entry field parsing all reasonable formats
918  *
919  **/
920 gboolean
entry_to_int(GtkEntry * entry,gint * the_int,gboolean update)921 entry_to_int (GtkEntry *entry, gint *the_int, gboolean update)
922 {
923 	GnmValue *value = format_match_number (gtk_entry_get_text (entry), NULL, NULL);
924 	gnm_float f;
925 
926 	*the_int = 0;
927 	if (!value)
928 		return TRUE;
929 
930 	f = value_get_as_float (value);
931 	if (f < INT_MIN || f > INT_MAX || f != (*the_int = (int)f)) {
932 		value_release (value);
933 		return TRUE;
934 	}
935 
936 	if (update) {
937 		char *tmp = format_value (NULL, value, 16, NULL);
938 		gtk_entry_set_text (entry, tmp);
939 		g_free (tmp);
940 	}
941 
942 	value_release (value);
943 	return FALSE;
944 }
945 
946 /**
947  * float_to_entry:
948  * @entry:
949  * @the_float:
950  *
951  **/
952 void
float_to_entry(GtkEntry * entry,gnm_float the_float)953 float_to_entry (GtkEntry *entry, gnm_float the_float)
954 {
955 	GnmValue *val = value_new_float (the_float);
956 	char *text = format_value (NULL, val, 16, NULL);
957 	value_release(val);
958 	if (text != NULL) {
959 		gtk_entry_set_text (entry, text);
960 		g_free (text);
961 	}
962 }
963 
964 /**
965  * int_to_entry:
966  * @entry:
967  * @the_int:
968  *
969  *
970   **/
971 void
int_to_entry(GtkEntry * entry,gint the_int)972 int_to_entry (GtkEntry *entry, gint the_int)
973 {
974 	GnmValue *val  = value_new_int (the_int);
975 	char *text = format_value (NULL, val, 16, NULL);
976 	value_release(val);
977 	if (text != NULL) {
978 		gtk_entry_set_text (entry, text);
979 		g_free (text);
980 	}
981 }
982 
983 static void
cb_focus_to_entry(GtkWidget * button,GtkWidget * entry)984 cb_focus_to_entry (GtkWidget *button, GtkWidget *entry)
985 {
986 	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
987 		gtk_widget_grab_focus (entry);
988 }
989 
990 static gboolean
cb_activate_button(GtkWidget * button)991 cb_activate_button (GtkWidget *button)
992 {
993 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
994 	return FALSE;
995 }
996 
997 void
gnm_link_button_and_entry(GtkWidget * button,GtkWidget * entry)998 gnm_link_button_and_entry (GtkWidget *button, GtkWidget *entry)
999 {
1000 	g_signal_connect (G_OBJECT (button),
1001 			  "clicked", G_CALLBACK (cb_focus_to_entry),
1002 			  entry);
1003 	g_signal_connect_swapped (G_OBJECT (entry),
1004 			  "focus_in_event",
1005 			  G_CALLBACK (cb_activate_button),
1006 			  button);
1007 }
1008 
1009 /* ------------------------------------------------------------------------- */
1010 
1011 void
gnm_widget_set_cursor(GtkWidget * w,GdkCursor * cursor)1012 gnm_widget_set_cursor (GtkWidget *w, GdkCursor *cursor)
1013 {
1014 	gdk_window_set_cursor (gtk_widget_get_window (w), cursor);
1015 }
1016 
1017 void
gnm_widget_set_cursor_type(GtkWidget * w,GdkCursorType ct)1018 gnm_widget_set_cursor_type (GtkWidget *w, GdkCursorType ct)
1019 {
1020 	GdkDisplay *display = gtk_widget_get_display (w);
1021 	GdkCursor *cursor = gdk_cursor_new_for_display (display, ct);
1022 	gnm_widget_set_cursor (w, cursor);
1023 	g_object_unref (cursor);
1024 }
1025 
1026 /* ------------------------------------------------------------------------- */
1027 
1028 /**
1029  * gnm_message_dialog_create:
1030  *
1031  * A convenience function to build HIG compliant message dialogs.
1032  *
1033  *   parent : transient parent, or %NULL for none.
1034  *   flags
1035  *   type : type of dialog
1036  *   primary_message : message displayed in bold
1037  *   secondary_message : message displayed below
1038  *
1039  * Returns: (transfer full): a GtkDialog, without buttons.
1040  **/
1041 
1042 GtkWidget *
gnm_message_dialog_create(GtkWindow * parent,GtkDialogFlags flags,GtkMessageType type,gchar const * primary_message,gchar const * secondary_message)1043 gnm_message_dialog_create (GtkWindow * parent,
1044 				GtkDialogFlags flags,
1045 				GtkMessageType type,
1046 				gchar const * primary_message,
1047 				gchar const * secondary_message)
1048 {
1049 	GtkWidget * dialog;
1050 	GtkWidget * label;
1051 	GtkWidget * hbox;
1052 	gchar *message;
1053 	const gchar *icon_name;
1054 	GtkWidget *image;
1055 	const char *title;
1056 
1057 	dialog = gtk_dialog_new_with_buttons ("", parent, flags, NULL, NULL);
1058 
1059 	switch (type) {
1060 	default:
1061 		g_warning ("Unknown GtkMessageType %d", type);
1062 	case GTK_MESSAGE_INFO:
1063 		icon_name = "dialog-information";
1064 		title = _("Information");
1065 		break;
1066 
1067 	case GTK_MESSAGE_QUESTION:
1068 		icon_name = "dialog-question";
1069 		title = _("Question");
1070 		break;
1071 
1072 	case GTK_MESSAGE_WARNING:
1073 		icon_name = "dialog-warning";
1074 		title = _("Warning");
1075 		break;
1076 
1077 	case GTK_MESSAGE_ERROR:
1078 		icon_name = "dialog-error";
1079 		title = _("Error");
1080 		break;
1081 	}
1082 
1083 	image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_DIALOG);
1084 	gtk_window_set_title (GTK_WINDOW (dialog), title);
1085 
1086 	if (primary_message) {
1087 		if (secondary_message) {
1088 			message = g_strdup_printf ("<b>%s</b>\n\n%s",
1089 						   primary_message,
1090 						   secondary_message);
1091 		} else {
1092 			message = g_strdup_printf ("<b>%s</b>",
1093 						   primary_message);
1094 		}
1095 	} else {
1096 		message = g_strdup_printf ("%s", secondary_message);
1097 	}
1098 	label = gtk_label_new (message);
1099 	g_free (message);
1100 
1101 	hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1102 	gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, TRUE, 0);
1103 	gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
1104 	gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), hbox, TRUE, TRUE, 0);
1105 
1106 	gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
1107 	gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1108 	gtk_misc_set_alignment (GTK_MISC (label), 0.0 , 0.0);
1109 	gtk_box_set_spacing (GTK_BOX (hbox), 12);
1110 	gtk_container_set_border_width (GTK_CONTAINER (hbox), 6);
1111 	gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 12);
1112 	gtk_container_set_border_width (GTK_CONTAINER (dialog), 6);
1113 	gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
1114 	gtk_widget_show_all (GTK_WIDGET (gtk_dialog_get_content_area (GTK_DIALOG (dialog))));
1115 
1116 	return dialog;
1117 }
1118 
1119 typedef struct {
1120 	GPtrArray *objects_signals;
1121 } GnmDialogDestroySignals;
1122 
1123 static void
cb_gnm_dialog_setup_destroy_handlers(G_GNUC_UNUSED GtkWidget * widget,GnmDialogDestroySignals * dd)1124 cb_gnm_dialog_setup_destroy_handlers (G_GNUC_UNUSED GtkWidget *widget,
1125 				      GnmDialogDestroySignals *dd)
1126 {
1127 	GPtrArray *os = dd->objects_signals;
1128 	int i;
1129 
1130 	for (i = 0; i < (int)os->len; i += 2) {
1131 		GObject *obj = g_ptr_array_index (os, i);
1132 		gulong s = GPOINTER_TO_SIZE (g_ptr_array_index (os, i + 1));
1133 		g_signal_handler_disconnect (obj, s);
1134 	}
1135 
1136 	g_ptr_array_free (os, TRUE);
1137 	memset (dd, 0, sizeof (*dd));
1138 	g_free (dd);
1139 }
1140 
1141 void
gnm_dialog_setup_destroy_handlers(GtkDialog * dialog,WBCGtk * wbcg,GnmDialogDestroyOptions what)1142 gnm_dialog_setup_destroy_handlers (GtkDialog *dialog,
1143 				   WBCGtk *wbcg,
1144 				   GnmDialogDestroyOptions what)
1145 {
1146 	GnmDialogDestroySignals *dd = g_new (GnmDialogDestroySignals, 1);
1147 	Workbook *wb = wb_control_get_workbook (GNM_WBC (wbcg));
1148 	Sheet *sheet = wb_control_cur_sheet (GNM_WBC (wbcg));
1149 	int N = workbook_sheet_count (wb), i;
1150 	GPtrArray *os = g_ptr_array_new ();
1151 
1152 	dd->objects_signals = os;
1153 
1154 	/* FIXME: Properly implement CURRENT_SHEET_REMOVED.  */
1155 	if (what & GNM_DIALOG_DESTROY_CURRENT_SHEET_REMOVED)
1156 		what |= GNM_DIALOG_DESTROY_SHEET_REMOVED;
1157 
1158 	if (what & GNM_DIALOG_DESTROY_SHEET_REMOVED) {
1159 		gulong s = g_signal_connect_swapped
1160 			(G_OBJECT (wb),
1161 			 "sheet_deleted",
1162 			 G_CALLBACK (gtk_widget_destroy),
1163 			 dialog);
1164 		g_ptr_array_add (os, wb);
1165 		g_ptr_array_add (os, GSIZE_TO_POINTER (s));
1166 	}
1167 
1168 	if (what & GNM_DIALOG_DESTROY_SHEET_ADDED) {
1169 		gulong s = g_signal_connect_swapped
1170 			(G_OBJECT (wb),
1171 			 "sheet_added",
1172 			 G_CALLBACK (gtk_widget_destroy),
1173 			 dialog);
1174 		g_ptr_array_add (os, wb);
1175 		g_ptr_array_add (os, GSIZE_TO_POINTER (s));
1176 	}
1177 
1178 	if (what & GNM_DIALOG_DESTROY_SHEETS_REORDERED) {
1179 		gulong s = g_signal_connect_swapped
1180 			(G_OBJECT (wb),
1181 			 "sheet_order_changed",
1182 			 G_CALLBACK (gtk_widget_destroy),
1183 			 dialog);
1184 		g_ptr_array_add (os, wb);
1185 		g_ptr_array_add (os, GSIZE_TO_POINTER (s));
1186 	}
1187 
1188 	for (i = 0; i < N; i++) {
1189 		Sheet *this_sheet = workbook_sheet_by_index (wb, i);
1190 		gboolean current = (sheet == this_sheet);
1191 
1192 		if ((what & GNM_DIALOG_DESTROY_SHEET_RENAMED) ||
1193 		    (current && (what & GNM_DIALOG_DESTROY_CURRENT_SHEET_RENAMED))) {
1194 			gulong s = g_signal_connect_swapped
1195 				(G_OBJECT (this_sheet),
1196 				 "notify::name",
1197 				 G_CALLBACK (gtk_widget_destroy),
1198 				 dialog);
1199 			g_ptr_array_add (os, this_sheet);
1200 			g_ptr_array_add (os, GSIZE_TO_POINTER (s));
1201 		}
1202 	}
1203 
1204 	g_signal_connect (G_OBJECT (dialog),
1205 			  "destroy",
1206 			  G_CALLBACK (cb_gnm_dialog_setup_destroy_handlers),
1207 			  dd);
1208 }
1209 
1210 
1211 void
gnm_canvas_get_position(GocCanvas * canvas,int * x,int * y,gint64 px,gint64 py)1212 gnm_canvas_get_position (GocCanvas *canvas, int *x, int *y, gint64 px, gint64 py)
1213 {
1214 	GtkWidget *cw = GTK_WIDGET (canvas);
1215 	GdkWindow *cbw = gtk_layout_get_bin_window (GTK_LAYOUT (cw));
1216 	int wx, wy;
1217 
1218 	gdk_window_get_origin (cbw, &wx, &wy);
1219 
1220 	/* we don't need to multiply px and py by the canvas pixels_per_unit
1221 	 * field since all the callers already do that */
1222 	px -= canvas->scroll_x1 * canvas->pixels_per_unit;
1223 	py -= canvas->scroll_y1 * canvas->pixels_per_unit;
1224 	/* let's take care of RTL sheets */
1225 	if (canvas->direction == GOC_DIRECTION_RTL)
1226 		px = goc_canvas_get_width (canvas) - px;
1227 
1228 	*x = px + wx;
1229 	*y = py + wy;
1230 }
1231 
1232 /*
1233  * Get the gdk position for canvas coordinates (x,y).  This is suitable
1234  * for tooltip windows.
1235  *
1236  * It is possible that this does not work right for very large coordinates
1237  * prior to gtk+ 2.18.  See the code and comments in gnm_canvas_get_position.
1238  */
1239 void
gnm_canvas_get_screen_position(GocCanvas * canvas,double x,double y,int * ix,int * iy)1240 gnm_canvas_get_screen_position (GocCanvas *canvas,
1241 				double x, double y,
1242 				int *ix, int *iy)
1243 {
1244 	GdkWindow *cbw = gtk_layout_get_bin_window (GTK_LAYOUT (canvas));
1245 	int wx, wy;
1246 
1247 	gdk_window_get_origin (cbw, &wx, &wy);
1248 	goc_canvas_c2w (canvas, x, y, ix, iy);
1249 	(*ix) += wx;
1250 	(*iy) += wy;
1251 }
1252 
1253 
1254 gboolean
gnm_check_for_plugins_missing(char const ** ids,GtkWindow * parent)1255 gnm_check_for_plugins_missing (char const **ids, GtkWindow *parent)
1256 {
1257 	for (; *ids != NULL; ids++) {
1258 		GOPlugin *pi = go_plugins_get_plugin_by_id (*ids);
1259 		if (pi == NULL) {
1260 			GOErrorInfo *error;
1261 			error = go_error_info_new_printf
1262 				(_("The plugin with id %s is required "
1263 				   "but cannot be found."), *ids);
1264 			gnm_go_error_info_dialog_show (parent,
1265 							 error);
1266 			return TRUE;
1267 		} else if (!go_plugin_is_active (pi)) {
1268 			GOErrorInfo *error;
1269 			error = go_error_info_new_printf
1270 				(_("The %s plugin is required "
1271 				   "but is not loaded."),
1272 				 go_plugin_get_name (pi));
1273 			gnm_go_error_info_dialog_show (parent,
1274 							 error);
1275 			return TRUE;
1276 		}
1277 	}
1278 	return FALSE;
1279 }
1280 
1281 
1282 void
gnm_cell_renderer_text_copy_background_to_cairo(GtkCellRendererText * crt,cairo_t * cr)1283 gnm_cell_renderer_text_copy_background_to_cairo (GtkCellRendererText *crt,
1284 						 cairo_t *cr)
1285 {
1286 	GdkRGBA *c = NULL;
1287 	g_object_get (crt, "background-rgba", &c, NULL);
1288 	gdk_cairo_set_source_rgba (cr, c);
1289 	gdk_rgba_free (c);
1290 }
1291 
1292 int
gnm_widget_measure_string(GtkWidget * w,const char * s)1293 gnm_widget_measure_string (GtkWidget *w, const char *s)
1294 {
1295 	GtkStyleContext *ctxt;
1296 	int len;
1297 	PangoFontDescription *desc;
1298 	GtkStateFlags state = GTK_STATE_FLAG_NORMAL;
1299 
1300 	ctxt = gtk_widget_get_style_context (w);
1301 
1302 	// As-of gtk+ 3.20 we have to set the context state to the state
1303 	// we are querying for.  This ought to work before gtk+ 3.20 too.
1304 	gtk_style_context_save (ctxt);
1305 	gtk_style_context_set_state (ctxt, state);
1306 	gtk_style_context_get (ctxt, state, "font", &desc, NULL);
1307 	gtk_style_context_restore (ctxt);
1308 
1309 	len = go_pango_measure_string
1310 		(gtk_widget_get_pango_context (w), desc, s);
1311 
1312 	pango_font_description_free (desc);
1313 
1314 	return len;
1315 }
1316 
1317 static const char *
gnm_ag_translate(const char * s,const char * ctxt)1318 gnm_ag_translate (const char *s, const char *ctxt)
1319 {
1320 	return ctxt
1321 		? g_dpgettext2 (NULL, ctxt, s)
1322 		: _(s);
1323 }
1324 
1325 void
gnm_action_group_add_actions(GtkActionGroup * group,GnmActionEntry const * actions,size_t n,gpointer user)1326 gnm_action_group_add_actions (GtkActionGroup *group,
1327 			      GnmActionEntry const *actions, size_t n,
1328 			      gpointer user)
1329 {
1330 	unsigned i;
1331 
1332 	for (i = 0; i < n; i++) {
1333 		GnmActionEntry const *entry = actions + i;
1334 		const char *name = entry->name;
1335 		const char *label =
1336 			gnm_ag_translate (entry->label, entry->label_context);
1337 		const char *tip =
1338 			gnm_ag_translate (entry->tooltip, NULL);
1339 		GtkAction *a;
1340 
1341 		if (entry->toggle) {
1342 			GtkToggleAction *ta =
1343 				gtk_toggle_action_new (name, label, tip, NULL);
1344 			gtk_toggle_action_set_active (ta, entry->is_active);
1345 			a = GTK_ACTION (ta);
1346 		} else {
1347 			a = gtk_action_new (name, label, tip, NULL);
1348 		}
1349 
1350 		g_object_set (a, "icon-name", entry->icon, NULL);
1351 
1352 		if (entry->callback) {
1353 			GClosure *closure =
1354 				g_cclosure_new (entry->callback, user, NULL);
1355 			g_signal_connect_closure (a, "activate", closure,
1356 						  FALSE);
1357 		}
1358 
1359 		gtk_action_group_add_action_with_accel (group,
1360 							a,
1361 							entry->accelerator);
1362 		g_object_unref (a);
1363 	}
1364 }
1365 
1366 void
gnm_action_group_add_action(GtkActionGroup * group,GtkAction * act)1367 gnm_action_group_add_action (GtkActionGroup *group, GtkAction *act)
1368 {
1369 	/*
1370 	 * See the docs for gtk_action_group_add_action as to why we don't
1371 	 * call just that.
1372 	 */
1373 	gtk_action_group_add_action_with_accel (group, act, NULL);
1374 }
1375 
1376 
1377 static int gnm_debug_css = -1;
1378 
1379 
1380 void
gnm_css_debug_color(const char * name,const GdkRGBA * color)1381 gnm_css_debug_color (const char *name,
1382 		     const GdkRGBA *color)
1383 {
1384 	if (gnm_debug_css < 0) gnm_debug_css = gnm_debug_flag ("css");
1385 
1386 	if (gnm_debug_css) {
1387 		char *ctxt = gdk_rgba_to_string (color);
1388 		g_printerr ("css %s = %s\n", name, ctxt);
1389 		g_free (ctxt);
1390 	}
1391 }
1392 
1393 void
gnm_css_debug_int(const char * name,int i)1394 gnm_css_debug_int (const char *name, int i)
1395 {
1396 	if (gnm_debug_css < 0) gnm_debug_css = gnm_debug_flag ("css");
1397 
1398 	if (gnm_debug_css)
1399 		g_printerr ("css %s = %d\n", name, i);
1400 }
1401 
1402 
1403 void
gnm_style_context_get_color(GtkStyleContext * context,GtkStateFlags state,GdkRGBA * color)1404 gnm_style_context_get_color (GtkStyleContext *context,
1405 			     GtkStateFlags state,
1406 			     GdkRGBA *color)
1407 {
1408 	// As-of gtk+ 3.20 we have to set the context state to the state
1409 	// we are querying for.  This ought to work before gtk+ 3.20 too.
1410 	gtk_style_context_save (context);
1411 	gtk_style_context_set_state (context, state);
1412 	gtk_style_context_get_color (context,
1413 				     gtk_style_context_get_state (context),
1414 				     color);
1415 	gtk_style_context_restore (context);
1416 }
1417 
1418 void
gnm_get_link_color(GtkWidget * widget,GdkRGBA * res)1419 gnm_get_link_color (GtkWidget *widget, GdkRGBA *res)
1420 {
1421 #if GTK_CHECK_VERSION(3,12,0)
1422 	GtkStyleContext *ctxt = gtk_widget_get_style_context (widget);
1423 	gnm_style_context_get_color (ctxt, GTK_STATE_FLAG_LINK, res);
1424 #else
1425 	(void)widget;
1426 	gdk_rgba_parse (res, "blue");
1427 #endif
1428 	gnm_css_debug_color ("link.color", res);
1429 }
1430 
1431 gboolean
gnm_theme_is_dark(GtkWidget * widget)1432 gnm_theme_is_dark (GtkWidget *widget)
1433 {
1434 	GtkStyleContext *context;
1435 	GdkRGBA fg_color;
1436 	double lum;
1437 	gboolean dark;
1438 
1439 	context = gtk_widget_get_style_context (widget);
1440 	gnm_style_context_get_color (context, GTK_STATE_FLAG_NORMAL, &fg_color);
1441 
1442 	// One of many possible versions.
1443 	lum = 0.299 * fg_color.red + 0.587 * fg_color.green + 0.114 * fg_color.blue;
1444 
1445 	// Theme is dark if fg is light.
1446 	dark = lum > 0.5;
1447 	gnm_css_debug_int ("theme.dark", dark);
1448 
1449 	return dark;
1450 }
1451 
1452 
1453 // ----------------------------------------------------------------------------
1454