Update Spanish translation
[gnumeric.git] / src / gui-util.c
blob4e7c4a4c56da5e10184c83ccfbec0f4171ad3659
1 /*
2 * gnumeric-util.c: Various GUI utility functions.
4 * Author:
5 * Miguel de Icaza (miguel@gnu.org)
6 */
8 #include <gnumeric-config.h>
9 #include <glib/gi18n-lib.h>
10 #include <gnumeric.h>
11 #include <libgnumeric.h>
12 #include <gui-util.h>
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>
27 #include <goffice/goffice.h>
28 #include <atk/atkrelation.h>
29 #include <atk/atkrelationset.h>
30 #include <gdk/gdkkeysyms.h>
32 #include <string.h>
34 #define ERROR_INFO_MAX_LEVEL 9
35 #define ERROR_INFO_TAG_NAME "errorinfotag%i"
37 static void
38 insert_error_info (GtkTextBuffer* text, GOErrorInfo *error, gint level)
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);
60 return;
63 /**
64 * gnumeric_go_error_info_list_dialog_create:
65 * @errs: (element-type GOErrorInfo):
67 * SHOULD BE IN GOFFICE
68 * Returns: (transfer full): the newly allocated dialog.
70 static GtkWidget *
71 gnumeric_go_error_info_list_dialog_create (GSList *errs)
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;
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;
93 lf = g_slist_copy (errs);
94 lf = g_slist_reverse (lf);
96 if (message_null)
97 bf_lim++;
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);
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);
135 for (l = lf; l != NULL; l = l->next) {
136 GOErrorInfo *err = l->data;
137 insert_error_info (text, err, 0);
139 g_slist_free (lf);
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);
145 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CLOSE);
146 return dialog;
150 * gnm_go_error_info_dialog_create:
152 * SHOULD BE IN GOFFICE
153 * Returns: (transfer full): the newly allocated dialog.
155 GtkWidget *
156 gnm_go_error_info_dialog_create (GOErrorInfo *error)
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;
165 * gnm_go_error_info_dialog_show:
168 void
169 gnm_go_error_info_dialog_show (GtkWindow *parent, GOErrorInfo *error)
171 GtkWidget *dialog = gnm_go_error_info_dialog_create (error);
172 go_gtk_dialog_run (GTK_DIALOG (dialog), parent);
176 * gnm_go_error_info_list_dialog_show:
177 * @parent:
178 * @errs: (element-type GOErrorInfo):
181 void
182 gnm_go_error_info_list_dialog_show (GtkWindow *parent,
183 GSList *errs)
185 GtkWidget *dialog = gnumeric_go_error_info_list_dialog_create (errs);
186 go_gtk_dialog_run (GTK_DIALOG (dialog), parent);
190 typedef struct {
191 WBCGtk *wbcg;
192 GtkWidget *dialog;
193 char const *key;
194 gboolean freed;
195 } KeyedDialogContext;
197 static void
198 cb_free_keyed_dialog_context (KeyedDialogContext *ctxt)
200 if (ctxt->freed)
201 return;
202 ctxt->freed = TRUE;
205 * One of these causes a recursive call which will do nothing due to
206 * ->freed.
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);
213 static void
214 cb_keyed_dialog_destroy (GtkDialog *dialog)
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.
221 g_object_set_data (G_OBJECT (dialog), "state", NULL);
224 static gint
225 cb_keyed_dialog_keypress (GtkWidget *dialog, GdkEventKey *event,
226 G_GNUC_UNUSED gpointer user)
228 if (event->keyval == GDK_KEY_Escape) {
229 gtk_widget_destroy (GTK_WIDGET (dialog));
230 return TRUE;
232 return FALSE;
235 #define SAVE_SIZES_SCREEN_KEY "geometry-hash"
237 static void
238 cb_save_sizes (GtkWidget *dialog, const char *key)
240 GdkRectangle *r;
241 GtkAllocation da;
242 GdkScreen *screen = gtk_widget_get_screen (dialog);
243 GHashTable *h = g_object_get_data (G_OBJECT (screen),
244 SAVE_SIZES_SCREEN_KEY);
245 if (!h) {
246 h = g_hash_table_new_full (g_str_hash, g_str_equal,
247 (GDestroyNotify)g_free,
248 (GDestroyNotify)g_free);
250 * We hang this on the screen because pixel sizes make
251 * no sense across screens.
253 * ANYONE WHO CHANGES THIS CODE TO SAVE THESE SIZES ON EXIT
254 * AND RELOADS THEM ON STARTUP WILL GET TARRED AND FEATHERED.
255 * -- MW, 20071113
257 g_object_set_data_full (G_OBJECT (screen),
258 SAVE_SIZES_SCREEN_KEY, h,
259 (GDestroyNotify)g_hash_table_destroy);
262 gtk_widget_get_allocation (dialog, &da);
263 r = g_memdup (&da, sizeof (da));
264 gdk_window_get_position (gtk_widget_get_window (dialog), &r->x, &r->y);
265 g_hash_table_replace (h, g_strdup (key), r);
268 void
269 gnm_restore_window_geometry (GtkWindow *dialog, const char *key)
271 GtkWidget *top = gtk_widget_get_toplevel (GTK_WIDGET (dialog));
272 GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (dialog));
273 GHashTable *h = g_object_get_data (G_OBJECT (screen), SAVE_SIZES_SCREEN_KEY);
274 GdkRectangle *allocation = h ? g_hash_table_lookup (h, key) : NULL;
276 if (allocation) {
277 #if 0
278 g_printerr ("Restoring %s to %dx%d at (%d,%d)\n",
279 key, allocation->width, allocation->height,
280 allocation->x, allocation->y);
281 #endif
282 gtk_window_move
283 (GTK_WINDOW (top),
284 allocation->x, allocation->y);
285 gtk_window_set_default_size
286 (GTK_WINDOW (top),
287 allocation->width, allocation->height);
290 g_signal_connect (G_OBJECT (dialog), "unrealize",
291 G_CALLBACK (cb_save_sizes),
292 (gpointer)key);
296 * gnm_keyed_dialog:
297 * @wbcg: A WBCGtk
298 * @dialog: A transient window
299 * @key: A key to identify the dialog
301 * Make dialog a transient child of wbcg, attaching to wbcg object data to
302 * identify the dialog. The object data makes it possible to ensure that
303 * only one dialog of a kind can be displayed for a wbcg. Deallocation of
304 * the object data is managed here.
306 void
307 gnm_keyed_dialog (WBCGtk *wbcg, GtkWindow *dialog, char const *key)
309 KeyedDialogContext *ctxt;
311 g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
312 g_return_if_fail (GTK_IS_WINDOW (dialog));
313 g_return_if_fail (key != NULL);
315 wbcg_set_transient (wbcg, dialog);
317 go_dialog_guess_alternative_button_order (GTK_DIALOG (dialog));
319 ctxt = g_new (KeyedDialogContext, 1);
320 ctxt->wbcg = wbcg;
321 ctxt->dialog = GTK_WIDGET (dialog);
322 ctxt->key = key;
323 ctxt->freed = FALSE;
324 g_object_set_data_full (G_OBJECT (wbcg), key, ctxt,
325 (GDestroyNotify)cb_free_keyed_dialog_context);
326 g_object_set_data_full (G_OBJECT (dialog), "KeyedDialog", ctxt,
327 (GDestroyNotify)cb_free_keyed_dialog_context);
328 g_signal_connect (G_OBJECT (dialog), "key_press_event",
329 G_CALLBACK (cb_keyed_dialog_keypress), NULL);
330 g_signal_connect (G_OBJECT (dialog), "destroy",
331 G_CALLBACK (cb_keyed_dialog_destroy), NULL);
333 gnm_restore_window_geometry (dialog, key);
337 * gnm_dialog_raise_if_exists:
338 * @wbcg: A WBCGtk
339 * @key: A key to identify the dialog
341 * Raise the dialog identified by key if it is registered on the wbcg.
343 * Returns: (transfer none) (type GtkDialog) (nullable): existing dialog
345 gpointer
346 gnm_dialog_raise_if_exists (WBCGtk *wbcg, char const *key)
348 KeyedDialogContext *ctxt;
350 g_return_val_if_fail (wbcg != NULL, NULL);
351 g_return_val_if_fail (key != NULL, NULL);
353 /* Ensure we only pop up one copy per workbook */
354 ctxt = g_object_get_data (G_OBJECT (wbcg), key);
355 if (ctxt && GTK_IS_WINDOW (ctxt->dialog)) {
356 gdk_window_raise (gtk_widget_get_window (ctxt->dialog));
357 return ctxt->dialog;
358 } else
359 return NULL;
362 static gboolean
363 cb_activate_default (GtkWindow *window)
365 GtkWidget *dw = gtk_window_get_default_widget (window);
367 * gtk_window_activate_default has a bad habit of trying
368 * to activate the focus widget.
370 return dw && gtk_widget_is_sensitive (dw) &&
371 gtk_window_activate_default (window);
376 * gnm_editable_enters:
377 * @window: dialog to affect.
378 * @editable: Editable to affect.
380 * Make the "activate" signal of an editable click the default dialog button.
382 * This is a literal copy of gnome_dialog_editable_enters, but not restricted
383 * to GnomeDialogs.
385 * Normally if there's an editable widget (such as #GtkEntry) in your
386 * dialog, pressing Enter will activate the editable rather than the
387 * default dialog button. However, in most cases, the user expects to
388 * type something in and then press enter to close the dialog. This
389 * function enables that behavior.
392 void
393 gnm_editable_enters (GtkWindow *window, GtkWidget *w)
395 g_return_if_fail (GTK_IS_WINDOW(window));
397 /* because I really do not feel like changing all the calls to this routine */
398 if (GNM_EXPR_ENTRY_IS (w))
399 w = GTK_WIDGET (gnm_expr_entry_get_entry (GNM_EXPR_ENTRY (w)));
401 g_signal_connect_swapped (G_OBJECT (w),
402 "activate",
403 G_CALLBACK (cb_activate_default), window);
407 * gnm_gtk_radio_group_get_selected:
408 * @radio_group: (element-type GtkRadioButton): list of radio buttons.
410 * Returns: the index of the selected radio button starting from list end.
413 gnm_gtk_radio_group_get_selected (GSList *radio_group)
415 GSList *l;
416 int i, c;
418 g_return_val_if_fail (radio_group != NULL, 0);
420 c = g_slist_length (radio_group);
422 for (i = 0, l = radio_group; l; l = l->next, i++){
423 GtkRadioButton *button = l->data;
425 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
426 return c - i - 1;
429 return 0;
434 gnm_gui_group_value (gpointer gui, char const * const group[])
436 int i;
437 for (i = 0; group[i]; i++) {
438 GtkWidget *w = go_gtk_builder_get_widget (gui, group[i]);
439 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w)))
440 return i;
442 return -1;
445 static gboolean
446 cb_delayed_destroy (gpointer w)
448 gtk_widget_destroy (gtk_widget_get_toplevel (w));
449 gtk_widget_destroy (w);
450 g_object_unref (w);
451 return FALSE;
454 static void
455 kill_popup_menu (GtkWidget *widget, G_GNUC_UNUSED gpointer user)
457 /* gtk+ currently gets unhappy if we destroy here, see bug 725142 */
458 g_idle_add (cb_delayed_destroy, widget);
462 * gnumeric_popup_menu:
463 * @menu: #GtkMenu
464 * @event: (nullable): #GdkEvent
466 * Bring up a popup and if @event is non-%NULL ensure that the popup is on the
467 * right screen.
469 void
470 gnumeric_popup_menu (GtkMenu *menu, GdkEvent *event)
472 g_return_if_fail (menu != NULL);
473 g_return_if_fail (GTK_IS_MENU (menu));
475 if (event)
476 gtk_menu_set_screen (menu, gdk_event_get_screen (event));
478 g_object_ref_sink (menu);
479 g_signal_connect (G_OBJECT (menu),
480 "hide",
481 G_CALLBACK (kill_popup_menu), NULL);
483 /* Do NOT pass the button used to create the menu.
484 * instead pass 0. Otherwise bringing up a menu with
485 * the right button will disable clicking on the menu with the left.
487 gtk_menu_popup (menu, NULL, NULL, NULL, NULL, 0,
488 (event
489 ? gdk_event_get_time (event)
490 : gtk_get_current_event_time()));
493 static void
494 gnumeric_tooltip_set_style (GtkWidget *widget)
496 gtk_style_context_add_class (gtk_widget_get_style_context (widget),
497 GTK_STYLE_CLASS_TOOLTIP);
498 gtk_style_context_add_class (gtk_widget_get_style_context (widget),
499 "pseudo-tooltip");
500 if (GTK_IS_CONTAINER (widget))
501 gtk_container_forall (GTK_CONTAINER (widget),
502 (GtkCallback) (gnumeric_tooltip_set_style),
503 NULL);
507 * gnm_convert_to_tooltip:
508 * @ref_widget:
509 * @widget:
511 * Returns: (transfer none): @widget
513 GtkWidget *
514 gnm_convert_to_tooltip (GtkWidget *ref_widget, GtkWidget *widget)
516 GtkWidget *tip, *frame;
517 GdkScreen *screen = gtk_widget_get_screen (ref_widget);
519 tip = gtk_window_new (GTK_WINDOW_POPUP);
520 gtk_window_set_type_hint (GTK_WINDOW (tip),
521 GDK_WINDOW_TYPE_HINT_TOOLTIP);
522 gtk_window_set_resizable (GTK_WINDOW (tip), FALSE);
523 gtk_window_set_gravity (GTK_WINDOW (tip), GDK_GRAVITY_NORTH_WEST);
524 gtk_window_set_screen (GTK_WINDOW (tip), screen);
525 gtk_widget_set_name (tip, "gtk-tooltip");
527 frame = gtk_frame_new (NULL);
528 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
529 gtk_widget_show (frame);
530 gtk_container_add (GTK_CONTAINER (frame), widget);
531 gtk_container_add (GTK_CONTAINER (tip), frame);
533 gnumeric_tooltip_set_style (tip);
535 return widget;
539 * gnm_create_tooltip:
541 * Returns: (transfer full): the newly allocated #GtkWidget.
543 GtkWidget *
544 gnm_create_tooltip (GtkWidget *ref_widget)
546 return gnm_convert_to_tooltip (ref_widget, gtk_label_new (""));
549 void
550 gnm_position_tooltip (GtkWidget *tip, int px, int py, gboolean horizontal)
552 GtkRequisition req;
554 gtk_widget_get_preferred_size (tip, &req, NULL);
556 if (horizontal){
557 px -= req.width / 2;
558 py -= req.height + 20;
559 } else {
560 px -= req.width + 20;
561 py -= req.height / 2;
564 if (px < 0)
565 px = 0;
566 if (py < 0)
567 py = 0;
569 gtk_window_move (GTK_WINDOW (gtk_widget_get_toplevel (tip)), px, py);
573 * gnm_gtk_builder_load:
574 * @cc: #GOCmdContext
575 * @uifile:
577 * Simple utility to open ui files
578 * Returns: (transfer full): the newly allocated #GtkBuilder.
580 GtkBuilder *
581 gnm_gtk_builder_load (char const *uifile, char const *domain, GOCmdContext *cc)
583 GtkBuilder *gui;
584 char *f;
586 if (strncmp (uifile, "res:", 4) == 0) {
587 f = g_strconcat ("res:/org/gnumeric/gnumeric/",
588 uifile + 4,
589 NULL);
590 } else if (g_path_is_absolute (uifile)) {
591 f = g_strdup (uifile);
592 } else {
593 f = g_strconcat ("res:gnm:", uifile, NULL);
596 gui = go_gtk_builder_load (f, domain, cc);
597 g_free (f);
599 return gui;
602 static void
603 popup_item_activate (GtkWidget *item, GnmPopupMenuElement const *elem)
605 GtkWidget *menu;
606 GnmPopupMenuHandler handler;
607 gpointer user_data;
609 // Go to top-level menu. This shouldn't be that hard.
610 menu = item;
611 while (TRUE) {
612 if (GTK_IS_MENU_ITEM (menu))
613 menu = gtk_widget_get_parent (menu);
614 else if (GTK_IS_MENU (menu)) {
615 GtkWidget *a = gtk_menu_get_attach_widget (GTK_MENU (menu));
616 if (a)
617 menu = a;
618 else
619 break;
620 } else
621 break;
623 handler = g_object_get_data (G_OBJECT (menu), "handler");
624 user_data = g_object_get_data (G_OBJECT (menu), "user-data");
625 g_return_if_fail (handler != NULL);
627 handler (elem, user_data);
631 * gnm_create_popup_menu:
632 * @elements:
633 * @handler: (scope notified):
634 * @user_data: user data to pass to @handler.
635 * @notify: destroy notification for @user_data
636 * @display_filter:
637 * @sensitive_filter:
638 * @event:
640 void
641 gnm_create_popup_menu (GnmPopupMenuElement const *elements,
642 GnmPopupMenuHandler handler,
643 gpointer user_data,
644 GDestroyNotify notify,
645 int display_filter, int sensitive_filter,
646 GdkEvent *event)
648 char const *trans;
649 GSList *menu_stack = NULL;
650 GtkWidget *menu, *item;
652 menu = gtk_menu_new ();
653 g_object_set_data (G_OBJECT (menu), "handler", (gpointer)handler);
654 g_object_set_data_full (G_OBJECT (menu), "user-data", user_data, notify);
655 for (; NULL != elements->name ; elements++) {
656 char const * const name = elements->name;
657 char const * const pix_name = elements->pixmap;
659 item = NULL;
661 if (elements->display_filter != 0 &&
662 !(elements->display_filter & display_filter)) {
663 if (elements->allocated_name) {
664 g_free (elements->allocated_name);
665 *(gchar **)(&elements->allocated_name) = NULL;
667 continue;
670 if (name != NULL && *name != '\0') {
671 if (elements->allocated_name)
672 trans = elements->allocated_name;
673 else
674 trans = _(name);
675 item = gtk_image_menu_item_new_with_mnemonic (trans);
676 if (elements->sensitive_filter != 0 &&
677 (elements->sensitive_filter & sensitive_filter))
678 gtk_widget_set_sensitive (GTK_WIDGET (item), FALSE);
679 if (pix_name != NULL) {
680 GtkWidget *image = gtk_image_new_from_icon_name (pix_name,
681 GTK_ICON_SIZE_MENU);
682 gtk_widget_show (image);
683 gtk_image_menu_item_set_image (
684 GTK_IMAGE_MENU_ITEM (item),
685 image);
687 if (elements->allocated_name) {
688 g_free (elements->allocated_name);
689 *(gchar **)(&elements->allocated_name) = NULL;
691 } else if (elements->index >= 0) {
692 /* separator */
693 item = gtk_menu_item_new ();
694 gtk_widget_set_sensitive (item, FALSE);
697 if (elements->index > 0) {
698 g_signal_connect (G_OBJECT (item),
699 "activate",
700 G_CALLBACK (popup_item_activate),
701 (gpointer)elements);
703 if (NULL != item) {
704 gtk_widget_show (item);
705 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
707 if (elements->index < 0) {
708 if (NULL != item) {
709 menu_stack = g_slist_prepend (menu_stack, menu);
710 menu = gtk_menu_new ();
711 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu);
712 } else {
713 menu = menu_stack->data;
714 menu_stack = g_slist_remove (menu_stack, menu);
718 gnumeric_popup_menu (GTK_MENU (menu), event);
721 void
722 gnm_init_help_button (GtkWidget *w, char const *lnk)
724 go_gtk_help_button_init (w, gnm_sys_data_dir (), "gnumeric", lnk);
727 char *
728 gnm_textbuffer_get_text (GtkTextBuffer *buf)
730 GtkTextIter start, end;
732 g_return_val_if_fail (buf != NULL, NULL);
734 gtk_text_buffer_get_start_iter (buf, &start);
735 gtk_text_buffer_get_end_iter (buf, &end);
736 /* We are using slice rather than text so that the tags still match */
737 return gtk_text_buffer_get_slice (buf, &start, &end, FALSE);
740 char *
741 gnm_textview_get_text (GtkTextView *text_view)
743 return gnm_textbuffer_get_text
744 (gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view)));
747 void
748 gnm_textview_set_text (GtkTextView *text_view, char const *txt)
750 gtk_text_buffer_set_text (
751 gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view)),
752 txt, -1);
755 void
756 gnm_load_pango_attributes_into_buffer (PangoAttrList *markup, GtkTextBuffer *buffer, gchar const *str)
758 gchar *str_retrieved = NULL;
760 if (str == NULL) {
761 GtkTextIter start, end;
762 gtk_text_buffer_get_start_iter (buffer, &start);
763 gtk_text_buffer_get_end_iter (buffer, &end);
764 str = str_retrieved = gtk_text_buffer_get_slice
765 (buffer, &start, &end, TRUE);
768 go_load_pango_attributes_into_buffer (markup, buffer, str);
770 g_free (str_retrieved);
773 #define gnmstoretexttagattrinpangoint(nameset, name, gnm_pango_attr_new) \
774 if (gnm_object_get_bool (tag, nameset)) { \
775 int value; \
776 g_object_get (G_OBJECT (tag), name, &value, NULL); \
777 attr = gnm_pango_attr_new (value); \
778 attr->start_index = x; \
779 attr->end_index = y; \
780 pango_attr_list_change (list, attr); \
784 static void
785 gnm_store_text_tag_attr_in_pango (PangoAttrList *list, GtkTextTag *tag, GtkTextIter *start, gchar const *text)
787 GtkTextIter end = *start;
788 gint x, y;
789 PangoAttribute * attr;
791 gtk_text_iter_forward_to_tag_toggle (&end, tag);
792 x = g_utf8_offset_to_pointer (text, gtk_text_iter_get_offset (start)) - text;
793 y = g_utf8_offset_to_pointer (text, gtk_text_iter_get_offset (&end)) - text;
795 if (gnm_object_get_bool (tag, "foreground-set")) {
796 GdkRGBA *color = NULL;
797 g_object_get (G_OBJECT (tag), "foreground-rgba", &color, NULL);
798 if (color) {
799 /* dividing 0 to 1 into 65536 equal length intervals */
800 attr = pango_attr_foreground_new
801 ((int)(CLAMP (color->red * 65536, 0., 65535.)),
802 (int)(CLAMP (color->green * 65536, 0., 65535.)),
803 (int)(CLAMP (color->blue * 65536, 0., 65535.)));
804 gdk_rgba_free (color);
805 attr->start_index = x;
806 attr->end_index = y;
807 pango_attr_list_change (list, attr);
811 gnmstoretexttagattrinpangoint ("style-set", "style", pango_attr_style_new)
812 gnmstoretexttagattrinpangoint ("weight-set", "weight", pango_attr_weight_new)
813 gnmstoretexttagattrinpangoint ("strikethrough-set", "strikethrough", pango_attr_strikethrough_new)
814 gnmstoretexttagattrinpangoint ("underline-set", "underline", pango_attr_underline_new)
815 gnmstoretexttagattrinpangoint ("rise-set", "rise", pango_attr_rise_new)
818 #undef gnmstoretexttagattrinpangoint
820 PangoAttrList *
821 gnm_get_pango_attributes_from_buffer (GtkTextBuffer *buffer)
823 PangoAttrList *list = pango_attr_list_new ();
824 GtkTextIter start;
825 gchar *text = gnm_textbuffer_get_text (buffer);
827 gtk_text_buffer_get_start_iter (buffer, &start);
829 while (!gtk_text_iter_is_end (&start)) {
830 if (gtk_text_iter_begins_tag (&start, NULL)) {
831 GSList *ptr, *l = gtk_text_iter_get_toggled_tags (&start, TRUE);
832 for (ptr = l; ptr; ptr = ptr->next)
833 gnm_store_text_tag_attr_in_pango (list, ptr->data, &start, text);
835 gtk_text_iter_forward_to_tag_toggle (&start, NULL);
838 g_free (text);
840 return list;
843 void
844 focus_on_entry (GtkEntry *entry)
846 if (entry == NULL)
847 return;
848 gtk_widget_grab_focus (GTK_WIDGET(entry));
849 gtk_editable_set_position (GTK_EDITABLE (entry), 0);
850 gtk_editable_select_region (GTK_EDITABLE (entry), 0,
851 gtk_entry_get_text_length (entry));
854 gboolean
855 entry_to_float_with_format_default (GtkEntry *entry, gnm_float *the_float,
856 gboolean update,
857 GOFormat const *format, gnm_float num)
859 char const *text = gtk_entry_get_text (entry);
860 gboolean need_default = (text == NULL);
862 if (!need_default) {
863 char *new_text = g_strdup (text);
864 need_default = (0 == strlen (g_strstrip(new_text)));
865 g_free (new_text);
868 if (need_default && !update) {
869 *the_float = num;
870 return FALSE;
873 if (need_default)
874 float_to_entry (entry, num);
876 return entry_to_float_with_format (entry, the_float, update, format);
879 gboolean
880 entry_to_float_with_format (GtkEntry *entry, gnm_float *the_float,
881 gboolean update, GOFormat const *format)
883 GnmValue *value = format_match_number (gtk_entry_get_text (entry), format, NULL);
885 *the_float = 0.0;
886 if (!value)
887 return TRUE;
889 *the_float = value_get_as_float (value);
890 if (update) {
891 char *tmp = format_value (format, value, 16, NULL);
892 gtk_entry_set_text (entry, tmp);
893 g_free (tmp);
896 value_release (value);
897 return FALSE;
901 * entry_to_int:
902 * @entry:
903 * @the_int:
904 * @update:
906 * Retrieve an int from an entry field parsing all reasonable formats
909 gboolean
910 entry_to_int (GtkEntry *entry, gint *the_int, gboolean update)
912 GnmValue *value = format_match_number (gtk_entry_get_text (entry), NULL, NULL);
913 gnm_float f;
915 *the_int = 0;
916 if (!value)
917 return TRUE;
919 f = value_get_as_float (value);
920 if (f < INT_MIN || f > INT_MAX || f != (*the_int = (int)f)) {
921 value_release (value);
922 return TRUE;
925 if (update) {
926 char *tmp = format_value (NULL, value, 16, NULL);
927 gtk_entry_set_text (entry, tmp);
928 g_free (tmp);
931 value_release (value);
932 return FALSE;
936 * float_to_entry:
937 * @entry:
938 * @the_float:
941 void
942 float_to_entry (GtkEntry *entry, gnm_float the_float)
944 GnmValue *val = value_new_float (the_float);
945 char *text = format_value (NULL, val, 16, NULL);
946 value_release(val);
947 if (text != NULL) {
948 gtk_entry_set_text (entry, text);
949 g_free (text);
954 * int_to_entry:
955 * @entry:
956 * @the_int:
960 void
961 int_to_entry (GtkEntry *entry, gint the_int)
963 GnmValue *val = value_new_int (the_int);
964 char *text = format_value (NULL, val, 16, NULL);
965 value_release(val);
966 if (text != NULL) {
967 gtk_entry_set_text (entry, text);
968 g_free (text);
972 static void
973 cb_focus_to_entry (GtkWidget *button, GtkWidget *entry)
975 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
976 gtk_widget_grab_focus (entry);
979 static gboolean
980 cb_activate_button (GtkWidget *button)
982 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
983 return FALSE;
986 void
987 gnm_link_button_and_entry (GtkWidget *button, GtkWidget *entry)
989 g_signal_connect (G_OBJECT (button),
990 "clicked", G_CALLBACK (cb_focus_to_entry),
991 entry);
992 g_signal_connect_swapped (G_OBJECT (entry),
993 "focus_in_event",
994 G_CALLBACK (cb_activate_button),
995 button);
998 /* ------------------------------------------------------------------------- */
1000 void
1001 gnm_widget_set_cursor (GtkWidget *w, GdkCursor *cursor)
1003 gdk_window_set_cursor (gtk_widget_get_window (w), cursor);
1006 void
1007 gnm_widget_set_cursor_type (GtkWidget *w, GdkCursorType ct)
1009 GdkDisplay *display = gtk_widget_get_display (w);
1010 GdkCursor *cursor = gdk_cursor_new_for_display (display, ct);
1011 gnm_widget_set_cursor (w, cursor);
1012 g_object_unref (cursor);
1015 /* ------------------------------------------------------------------------- */
1018 * gnm_message_dialog_create:
1020 * A convenience fonction to build HIG compliant message dialogs.
1022 * parent : transient parent, or NULL for none.
1023 * flags
1024 * type : type of dialog
1025 * primary_message : message displayed in bold
1026 * secondary_message : message displayed below
1028 * Returns: (transfer full): a GtkDialog, without buttons.
1031 GtkWidget *
1032 gnm_message_dialog_create (GtkWindow * parent,
1033 GtkDialogFlags flags,
1034 GtkMessageType type,
1035 gchar const * primary_message,
1036 gchar const * secondary_message)
1038 GtkWidget * dialog;
1039 GtkWidget * label;
1040 GtkWidget * hbox;
1041 gchar *message;
1042 const gchar *icon_name;
1043 GtkWidget *image;
1044 const char *title;
1046 dialog = gtk_dialog_new_with_buttons ("", parent, flags, NULL, NULL);
1048 switch (type) {
1049 default:
1050 g_warning ("Unknown GtkMessageType %d", type);
1051 case GTK_MESSAGE_INFO:
1052 icon_name = "dialog-information";
1053 title = _("Information");
1054 break;
1056 case GTK_MESSAGE_QUESTION:
1057 icon_name = "dialog-question";
1058 title = _("Question");
1059 break;
1061 case GTK_MESSAGE_WARNING:
1062 icon_name = "dialog-warning";
1063 title = _("Warning");
1064 break;
1066 case GTK_MESSAGE_ERROR:
1067 icon_name = "dialog-error";
1068 title = _("Error");
1069 break;
1072 image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_DIALOG);
1073 gtk_window_set_title (GTK_WINDOW (dialog), title);
1075 if (primary_message) {
1076 if (secondary_message) {
1077 message = g_strdup_printf ("<b>%s</b>\n\n%s",
1078 primary_message,
1079 secondary_message);
1080 } else {
1081 message = g_strdup_printf ("<b>%s</b>",
1082 primary_message);
1084 } else {
1085 message = g_strdup_printf ("%s", secondary_message);
1087 label = gtk_label_new (message);
1088 g_free (message);
1090 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1091 gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, TRUE, 0);
1092 gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
1093 gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), hbox, TRUE, TRUE, 0);
1095 gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
1096 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1097 gtk_misc_set_alignment (GTK_MISC (label), 0.0 , 0.0);
1098 gtk_box_set_spacing (GTK_BOX (hbox), 12);
1099 gtk_container_set_border_width (GTK_CONTAINER (hbox), 6);
1100 gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 12);
1101 gtk_container_set_border_width (GTK_CONTAINER (dialog), 6);
1102 gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
1103 gtk_widget_show_all (GTK_WIDGET (gtk_dialog_get_content_area (GTK_DIALOG (dialog))));
1105 return dialog;
1108 typedef struct {
1109 GPtrArray *objects_signals;
1110 } GnmDialogDestroySignals;
1112 static void
1113 cb_gnm_dialog_setup_destroy_handlers (G_GNUC_UNUSED GtkWidget *widget,
1114 GnmDialogDestroySignals *dd)
1116 GPtrArray *os = dd->objects_signals;
1117 int i;
1119 for (i = 0; i < (int)os->len; i += 2) {
1120 GObject *obj = g_ptr_array_index (os, i);
1121 guint s = GPOINTER_TO_UINT (g_ptr_array_index (os, i + 1));
1122 g_signal_handler_disconnect (obj, s);
1125 g_ptr_array_free (os, TRUE);
1126 memset (dd, 0, sizeof (*dd));
1127 g_free (dd);
1130 void
1131 gnm_dialog_setup_destroy_handlers (GtkDialog *dialog,
1132 WBCGtk *wbcg,
1133 GnmDialogDestroyOptions what)
1135 GnmDialogDestroySignals *dd = g_new (GnmDialogDestroySignals, 1);
1136 Workbook *wb = wb_control_get_workbook (GNM_WBC (wbcg));
1137 Sheet *sheet = wb_control_cur_sheet (GNM_WBC (wbcg));
1138 int N = workbook_sheet_count (wb), i;
1139 GPtrArray *os = g_ptr_array_new ();
1141 dd->objects_signals = os;
1143 /* FIXME: Properly implement CURRENT_SHEET_REMOVED. */
1144 if (what & GNM_DIALOG_DESTROY_CURRENT_SHEET_REMOVED)
1145 what |= GNM_DIALOG_DESTROY_SHEET_REMOVED;
1147 if (what & GNM_DIALOG_DESTROY_SHEET_REMOVED) {
1148 guint s = g_signal_connect_swapped
1149 (G_OBJECT (wb),
1150 "sheet_deleted",
1151 G_CALLBACK (gtk_widget_destroy),
1152 dialog);
1153 g_ptr_array_add (os, wb);
1154 g_ptr_array_add (os, GUINT_TO_POINTER (s));
1157 if (what & GNM_DIALOG_DESTROY_SHEET_ADDED) {
1158 guint s = g_signal_connect_swapped
1159 (G_OBJECT (wb),
1160 "sheet_added",
1161 G_CALLBACK (gtk_widget_destroy),
1162 dialog);
1163 g_ptr_array_add (os, wb);
1164 g_ptr_array_add (os, GUINT_TO_POINTER (s));
1167 if (what & GNM_DIALOG_DESTROY_SHEETS_REORDERED) {
1168 guint s = g_signal_connect_swapped
1169 (G_OBJECT (wb),
1170 "sheet_order_changed",
1171 G_CALLBACK (gtk_widget_destroy),
1172 dialog);
1173 g_ptr_array_add (os, wb);
1174 g_ptr_array_add (os, GUINT_TO_POINTER (s));
1177 for (i = 0; i < N; i++) {
1178 Sheet *this_sheet = workbook_sheet_by_index (wb, i);
1179 gboolean current = (sheet == this_sheet);
1181 if ((what & GNM_DIALOG_DESTROY_SHEET_RENAMED) ||
1182 (current && (what & GNM_DIALOG_DESTROY_CURRENT_SHEET_RENAMED))) {
1183 guint s = g_signal_connect_swapped
1184 (G_OBJECT (this_sheet),
1185 "notify::name",
1186 G_CALLBACK (gtk_widget_destroy),
1187 dialog);
1188 g_ptr_array_add (os, this_sheet);
1189 g_ptr_array_add (os, GUINT_TO_POINTER (s));
1193 g_signal_connect (G_OBJECT (dialog),
1194 "destroy",
1195 G_CALLBACK (cb_gnm_dialog_setup_destroy_handlers),
1196 dd);
1200 void
1201 gnm_canvas_get_position (GocCanvas *canvas, int *x, int *y, gint64 px, gint64 py)
1203 GtkWidget *cw = GTK_WIDGET (canvas);
1204 GdkWindow *cbw = gtk_layout_get_bin_window (GTK_LAYOUT (cw));
1205 int wx, wy;
1207 gdk_window_get_origin (cbw, &wx, &wy);
1209 /* we don't need to multiply px and py by the canvas pixels_per_unit
1210 * field since all the callers already do that */
1211 px -= canvas->scroll_x1 * canvas->pixels_per_unit;
1212 py -= canvas->scroll_y1 * canvas->pixels_per_unit;
1213 /* let's take care of RTL sheets */
1214 if (canvas->direction == GOC_DIRECTION_RTL)
1215 px = goc_canvas_get_width (canvas) - px;
1217 *x = px + wx;
1218 *y = py + wy;
1222 * Get the gdk position for canvas coordinates (x,y). This is suitable
1223 * for tooltip windows.
1225 * It is possible that this does not work right for very large coordinates
1226 * prior to gtk+ 2.18. See the code and comments in gnm_canvas_get_position.
1228 void
1229 gnm_canvas_get_screen_position (GocCanvas *canvas,
1230 double x, double y,
1231 int *ix, int *iy)
1233 GdkWindow *cbw = gtk_layout_get_bin_window (GTK_LAYOUT (canvas));
1234 int wx, wy;
1236 gdk_window_get_origin (cbw, &wx, &wy);
1237 goc_canvas_c2w (canvas, x, y, ix, iy);
1238 (*ix) += wx;
1239 (*iy) += wy;
1243 gboolean
1244 gnm_check_for_plugins_missing (char const **ids, GtkWindow *parent)
1246 for (; *ids != NULL; ids++) {
1247 GOPlugin *pi = go_plugins_get_plugin_by_id (*ids);
1248 if (pi == NULL) {
1249 GOErrorInfo *error;
1250 error = go_error_info_new_printf
1251 (_("The plugin with id %s is required "
1252 "but cannot be found."), *ids);
1253 gnm_go_error_info_dialog_show (parent,
1254 error);
1255 return TRUE;
1256 } else if (!go_plugin_is_active (pi)) {
1257 GOErrorInfo *error;
1258 error = go_error_info_new_printf
1259 (_("The %s plugin is required "
1260 "but is not loaded."),
1261 go_plugin_get_name (pi));
1262 gnm_go_error_info_dialog_show (parent,
1263 error);
1264 return TRUE;
1267 return FALSE;
1271 void
1272 gnm_cell_renderer_text_copy_background_to_cairo (GtkCellRendererText *crt,
1273 cairo_t *cr)
1275 GdkRGBA *c = NULL;
1276 g_object_get (crt, "background-rgba", &c, NULL);
1277 gdk_cairo_set_source_rgba (cr, c);
1278 gdk_rgba_free (c);
1282 gnm_widget_measure_string (GtkWidget *w, const char *s)
1284 GtkStyleContext *ctxt;
1285 int len;
1286 PangoFontDescription *desc;
1287 GtkStateFlags state = GTK_STATE_FLAG_NORMAL;
1289 ctxt = gtk_widget_get_style_context (w);
1291 // As-of gtk+ 3.20 we have to set the context state to the state
1292 // we are querying for. This ought to work before gtk+ 3.20 too.
1293 gtk_style_context_save (ctxt);
1294 gtk_style_context_set_state (ctxt, state);
1295 gtk_style_context_get (ctxt, state, "font", &desc, NULL);
1296 gtk_style_context_restore (ctxt);
1298 len = go_pango_measure_string
1299 (gtk_widget_get_pango_context (w), desc, s);
1301 pango_font_description_free (desc);
1303 return len;
1306 static const char *
1307 gnm_ag_translate (const char *s, const char *ctxt)
1309 return ctxt
1310 ? g_dpgettext2 (NULL, ctxt, s)
1311 : _(s);
1314 void
1315 gnm_action_group_add_actions (GtkActionGroup *group,
1316 GnmActionEntry const *actions, size_t n,
1317 gpointer user)
1319 unsigned i;
1321 for (i = 0; i < n; i++) {
1322 GnmActionEntry const *entry = actions + i;
1323 const char *name = entry->name;
1324 const char *label =
1325 gnm_ag_translate (entry->label, entry->label_context);
1326 const char *tip =
1327 gnm_ag_translate (entry->tooltip, NULL);
1328 GtkAction *a;
1330 if (entry->toggle) {
1331 GtkToggleAction *ta =
1332 gtk_toggle_action_new (name, label, tip, NULL);
1333 gtk_toggle_action_set_active (ta, entry->is_active);
1334 a = GTK_ACTION (ta);
1335 } else {
1336 a = gtk_action_new (name, label, tip, NULL);
1339 g_object_set (a, "icon-name", entry->icon, NULL);
1341 if (entry->callback) {
1342 GClosure *closure =
1343 g_cclosure_new (entry->callback, user, NULL);
1344 g_signal_connect_closure (a, "activate", closure,
1345 FALSE);
1348 gtk_action_group_add_action_with_accel (group,
1350 entry->accelerator);
1351 g_object_unref (a);
1355 void
1356 gnm_action_group_add_action (GtkActionGroup *group, GtkAction *act)
1359 * See the docs for gtk_action_group_add_action as to why we don't
1360 * call just that.
1362 gtk_action_group_add_action_with_accel (group, act, NULL);
1365 void
1366 gnm_style_context_get_color (GtkStyleContext *context,
1367 GtkStateFlags state,
1368 GdkRGBA *color)
1370 // As-of gtk+ 3.20 we have to set the context state to the state
1371 // we are querying for. This ought to work before gtk+ 3.20 too.
1372 gtk_style_context_save (context);
1373 gtk_style_context_set_state (context, state);
1374 gtk_style_context_get_color (context,
1375 gtk_style_context_get_state (context),
1376 color);
1377 gtk_style_context_restore (context);
1380 void
1381 gnm_get_link_color (GtkWidget *widget, GdkRGBA *res)
1383 GtkStyleContext *ctxt = gtk_widget_get_style_context (widget);
1384 gnm_style_context_get_color (ctxt, GTK_STATE_FLAG_LINK, res);
1387 // ----------------------------------------------------------------------------