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