2 * gnumeric-util.c: Various GUI utility functions.
5 * Miguel de Icaza (miguel@gnu.org)
8 #include <gnumeric-config.h>
9 #include <glib/gi18n-lib.h>
11 #include <libgnumeric.h>
15 #include <parse-util.h>
17 #include <style-color.h>
19 #include <number-match.h>
20 #include <gnm-format.h>
21 #include <application.h>
23 #include <libgnumeric.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>
34 #define ERROR_INFO_MAX_LEVEL 9
35 #define ERROR_INFO_TAG_NAME "errorinfotag%i"
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
));
46 message
= g_strdup (_("Multiple errors\n"));
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
,
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);
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.
71 gnumeric_go_error_info_list_dialog_create (GSList
*errs
)
74 GtkWidget
*scrolled_window
;
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
)
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
);
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
127 "left_margin", i
* 12,
128 "right_margin", i
* 12,
129 "weight", ((i
< bf_lim
)
131 : PANGO_WEIGHT_NORMAL
),
135 for (l
= lf
; l
!= NULL
; l
= l
->next
) {
136 GOErrorInfo
*err
= l
->data
;
137 insert_error_info (text
, err
, 0);
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
);
150 * gnm_go_error_info_dialog_create:
152 * SHOULD BE IN GOFFICE
153 * Returns: (transfer full): the newly allocated dialog.
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
);
165 * gnm_go_error_info_dialog_show:
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:
178 * @errs: (element-type GOErrorInfo):
182 gnm_go_error_info_list_dialog_show (GtkWindow
*parent
,
185 GtkWidget
*dialog
= gnumeric_go_error_info_list_dialog_create (errs
);
186 go_gtk_dialog_run (GTK_DIALOG (dialog
), parent
);
195 } KeyedDialogContext
;
198 cb_free_keyed_dialog_context (KeyedDialogContext
*ctxt
)
205 * One of these causes a recursive call which will do nothing due to
208 g_object_set_data (G_OBJECT (ctxt
->wbcg
), ctxt
->key
, NULL
);
209 g_object_set_data (G_OBJECT (ctxt
->dialog
), "KeyedDialog", NULL
);
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.
221 g_object_set_data (G_OBJECT (dialog
), "state", NULL
);
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
));
235 #define SAVE_SIZES_SCREEN_KEY "geometry-hash"
238 cb_save_sizes (GtkWidget
*dialog
, const char *key
)
242 GdkScreen
*screen
= gtk_widget_get_screen (dialog
);
243 GHashTable
*h
= g_object_get_data (G_OBJECT (screen
),
244 SAVE_SIZES_SCREEN_KEY
);
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.
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
);
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
;
278 g_printerr ("Restoring %s to %dx%d at (%d,%d)\n",
279 key
, allocation
->width
, allocation
->height
,
280 allocation
->x
, allocation
->y
);
284 allocation
->x
, allocation
->y
);
285 gtk_window_set_default_size
287 allocation
->width
, allocation
->height
);
290 g_signal_connect (G_OBJECT (dialog
), "unrealize",
291 G_CALLBACK (cb_save_sizes
),
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.
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);
321 ctxt
->dialog
= GTK_WIDGET (dialog
);
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:
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
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
));
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
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.
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
),
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
)
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
)))
434 gnm_gui_group_value (gpointer gui
, char const * const group
[])
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
)))
446 cb_delayed_destroy (gpointer w
)
448 gtk_widget_destroy (gtk_widget_get_toplevel (w
));
449 gtk_widget_destroy (w
);
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:
464 * @event: (nullable): #GdkEvent
466 * Bring up a popup and if @event is non-%NULL ensure that the popup is on the
470 gnumeric_popup_menu (GtkMenu
*menu
, GdkEvent
*event
)
472 g_return_if_fail (menu
!= NULL
);
473 g_return_if_fail (GTK_IS_MENU (menu
));
476 gtk_menu_set_screen (menu
, gdk_event_get_screen (event
));
478 g_object_ref_sink (menu
);
479 g_signal_connect (G_OBJECT (menu
),
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,
489 ? gdk_event_get_time (event
)
490 : gtk_get_current_event_time()));
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
),
500 if (GTK_IS_CONTAINER (widget
))
501 gtk_container_forall (GTK_CONTAINER (widget
),
502 (GtkCallback
) (gnumeric_tooltip_set_style
),
507 * gnm_convert_to_tooltip:
511 * Returns: (transfer none): @widget
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
);
539 * gnm_create_tooltip:
541 * Returns: (transfer full): the newly allocated #GtkWidget.
544 gnm_create_tooltip (GtkWidget
*ref_widget
)
546 return gnm_convert_to_tooltip (ref_widget
, gtk_label_new (""));
550 gnm_position_tooltip (GtkWidget
*tip
, int px
, int py
, gboolean horizontal
)
554 gtk_widget_get_preferred_size (tip
, &req
, NULL
);
558 py
-= req
.height
+ 20;
560 px
-= req
.width
+ 20;
561 py
-= req
.height
/ 2;
569 gtk_window_move (GTK_WINDOW (gtk_widget_get_toplevel (tip
)), px
, py
);
573 * gnm_gtk_builder_load:
577 * Simple utility to open ui files
578 * Returns: (transfer full): the newly allocated #GtkBuilder.
581 gnm_gtk_builder_load (char const *uifile
, char const *domain
, GOCmdContext
*cc
)
586 if (strncmp (uifile
, "res:", 4) == 0) {
587 f
= g_strconcat ("res:/org/gnumeric/gnumeric/",
590 } else if (g_path_is_absolute (uifile
)) {
591 f
= g_strdup (uifile
);
593 f
= g_strconcat ("res:gnm:", uifile
, NULL
);
596 gui
= go_gtk_builder_load (f
, domain
, cc
);
603 popup_item_activate (GtkWidget
*item
, GnmPopupMenuElement
const *elem
)
606 GnmPopupMenuHandler handler
;
609 // Go to top-level menu. This shouldn't be that hard.
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
));
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:
633 * @handler: (scope notified):
634 * @user_data: user data to pass to @handler.
635 * @notify: destroy notification for @user_data
641 gnm_create_popup_menu (GnmPopupMenuElement
const *elements
,
642 GnmPopupMenuHandler handler
,
644 GDestroyNotify notify
,
645 int display_filter
, int sensitive_filter
,
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
;
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
;
670 if (name
!= NULL
&& *name
!= '\0') {
671 if (elements
->allocated_name
)
672 trans
= elements
->allocated_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
,
682 gtk_widget_show (image
);
683 gtk_image_menu_item_set_image (
684 GTK_IMAGE_MENU_ITEM (item
),
687 if (elements
->allocated_name
) {
688 g_free (elements
->allocated_name
);
689 *(gchar
**)(&elements
->allocated_name
) = NULL
;
691 } else if (elements
->index
>= 0) {
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
),
700 G_CALLBACK (popup_item_activate
),
704 gtk_widget_show (item
);
705 gtk_menu_shell_append (GTK_MENU_SHELL (menu
), item
);
707 if (elements
->index
< 0) {
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
);
713 menu
= menu_stack
->data
;
714 menu_stack
= g_slist_remove (menu_stack
, menu
);
718 gnumeric_popup_menu (GTK_MENU (menu
), event
);
722 gnm_init_help_button (GtkWidget
*w
, char const *lnk
)
724 go_gtk_help_button_init (w
, gnm_sys_data_dir (), "gnumeric", lnk
);
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
);
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
)));
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
)),
756 gnm_load_pango_attributes_into_buffer (PangoAttrList
*markup
, GtkTextBuffer
*buffer
, gchar
const *str
)
758 gchar
*str_retrieved
= 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)) { \
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); \
785 gnm_store_text_tag_attr_in_pango (PangoAttrList
*list
, GtkTextTag
*tag
, GtkTextIter
*start
, gchar
const *text
)
787 GtkTextIter end
= *start
;
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
);
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
;
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
821 gnm_get_pango_attributes_from_buffer (GtkTextBuffer
*buffer
)
823 PangoAttrList
*list
= pango_attr_list_new ();
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
);
844 focus_on_entry (GtkEntry
*entry
)
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
));
855 entry_to_float_with_format_default (GtkEntry
*entry
, gnm_float
*the_float
,
857 GOFormat
const *format
, gnm_float num
)
859 char const *text
= gtk_entry_get_text (entry
);
860 gboolean need_default
= (text
== NULL
);
863 char *new_text
= g_strdup (text
);
864 need_default
= (0 == strlen (g_strstrip(new_text
)));
868 if (need_default
&& !update
) {
874 float_to_entry (entry
, num
);
876 return entry_to_float_with_format (entry
, the_float
, update
, format
);
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
);
889 *the_float
= value_get_as_float (value
);
891 char *tmp
= format_value (format
, value
, 16, NULL
);
892 gtk_entry_set_text (entry
, tmp
);
896 value_release (value
);
906 * Retrieve an int from an entry field parsing all reasonable formats
910 entry_to_int (GtkEntry
*entry
, gint
*the_int
, gboolean update
)
912 GnmValue
*value
= format_match_number (gtk_entry_get_text (entry
), NULL
, NULL
);
919 f
= value_get_as_float (value
);
920 if (f
< INT_MIN
|| f
> INT_MAX
|| f
!= (*the_int
= (int)f
)) {
921 value_release (value
);
926 char *tmp
= format_value (NULL
, value
, 16, NULL
);
927 gtk_entry_set_text (entry
, tmp
);
931 value_release (value
);
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
);
948 gtk_entry_set_text (entry
, text
);
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
);
967 gtk_entry_set_text (entry
, text
);
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
);
980 cb_activate_button (GtkWidget
*button
)
982 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button
), TRUE
);
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
),
992 g_signal_connect_swapped (G_OBJECT (entry
),
994 G_CALLBACK (cb_activate_button
),
998 /* ------------------------------------------------------------------------- */
1001 gnm_widget_set_cursor (GtkWidget
*w
, GdkCursor
*cursor
)
1003 gdk_window_set_cursor (gtk_widget_get_window (w
), cursor
);
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.
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.
1032 gnm_message_dialog_create (GtkWindow
* parent
,
1033 GtkDialogFlags flags
,
1034 GtkMessageType type
,
1035 gchar
const * primary_message
,
1036 gchar
const * secondary_message
)
1042 const gchar
*icon_name
;
1046 dialog
= gtk_dialog_new_with_buttons ("", parent
, flags
, NULL
, NULL
);
1050 g_warning ("Unknown GtkMessageType %d", type
);
1051 case GTK_MESSAGE_INFO
:
1052 icon_name
= "dialog-information";
1053 title
= _("Information");
1056 case GTK_MESSAGE_QUESTION
:
1057 icon_name
= "dialog-question";
1058 title
= _("Question");
1061 case GTK_MESSAGE_WARNING
:
1062 icon_name
= "dialog-warning";
1063 title
= _("Warning");
1066 case GTK_MESSAGE_ERROR
:
1067 icon_name
= "dialog-error";
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",
1081 message
= g_strdup_printf ("<b>%s</b>",
1085 message
= g_strdup_printf ("%s", secondary_message
);
1087 label
= gtk_label_new (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
))));
1109 GPtrArray
*objects_signals
;
1110 } GnmDialogDestroySignals
;
1113 cb_gnm_dialog_setup_destroy_handlers (G_GNUC_UNUSED GtkWidget
*widget
,
1114 GnmDialogDestroySignals
*dd
)
1116 GPtrArray
*os
= dd
->objects_signals
;
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
));
1131 gnm_dialog_setup_destroy_handlers (GtkDialog
*dialog
,
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
1151 G_CALLBACK (gtk_widget_destroy
),
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
1161 G_CALLBACK (gtk_widget_destroy
),
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
1170 "sheet_order_changed",
1171 G_CALLBACK (gtk_widget_destroy
),
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
),
1186 G_CALLBACK (gtk_widget_destroy
),
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
),
1195 G_CALLBACK (cb_gnm_dialog_setup_destroy_handlers
),
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
));
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
;
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.
1229 gnm_canvas_get_screen_position (GocCanvas
*canvas
,
1233 GdkWindow
*cbw
= gtk_layout_get_bin_window (GTK_LAYOUT (canvas
));
1236 gdk_window_get_origin (cbw
, &wx
, &wy
);
1237 goc_canvas_c2w (canvas
, x
, y
, ix
, iy
);
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
);
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
,
1256 } else if (!go_plugin_is_active (pi
)) {
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
,
1272 gnm_cell_renderer_text_copy_background_to_cairo (GtkCellRendererText
*crt
,
1276 g_object_get (crt
, "background-rgba", &c
, NULL
);
1277 gdk_cairo_set_source_rgba (cr
, c
);
1282 gnm_widget_measure_string (GtkWidget
*w
, const char *s
)
1284 GtkStyleContext
*ctxt
;
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
);
1307 gnm_ag_translate (const char *s
, const char *ctxt
)
1310 ? g_dpgettext2 (NULL
, ctxt
, s
)
1315 gnm_action_group_add_actions (GtkActionGroup
*group
,
1316 GnmActionEntry
const *actions
, size_t n
,
1321 for (i
= 0; i
< n
; i
++) {
1322 GnmActionEntry
const *entry
= actions
+ i
;
1323 const char *name
= entry
->name
;
1325 gnm_ag_translate (entry
->label
, entry
->label_context
);
1327 gnm_ag_translate (entry
->tooltip
, NULL
);
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
);
1336 a
= gtk_action_new (name
, label
, tip
, NULL
);
1339 g_object_set (a
, "icon-name", entry
->icon
, NULL
);
1341 if (entry
->callback
) {
1343 g_cclosure_new (entry
->callback
, user
, NULL
);
1344 g_signal_connect_closure (a
, "activate", closure
,
1348 gtk_action_group_add_action_with_accel (group
,
1350 entry
->accelerator
);
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
1362 gtk_action_group_add_action_with_accel (group
, act
, NULL
);
1366 gnm_style_context_get_color (GtkStyleContext
*context
,
1367 GtkStateFlags state
,
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
),
1377 gtk_style_context_restore (context
);
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 // ----------------------------------------------------------------------------