Introspection fixes
[gnumeric.git] / src / gui-util.c
blobcfc502a6be258baa7c49b91d4ce6075138f7cc57
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.
342 * Returns: (transfer none): TRUE if dialog found, FALSE if not.
344 gpointer
345 gnm_dialog_raise_if_exists (WBCGtk *wbcg, char const *key)
347 KeyedDialogContext *ctxt;
349 g_return_val_if_fail (wbcg != NULL, NULL);
350 g_return_val_if_fail (key != NULL, NULL);
352 /* Ensure we only pop up one copy per workbook */
353 ctxt = g_object_get_data (G_OBJECT (wbcg), key);
354 if (ctxt && GTK_IS_WINDOW (ctxt->dialog)) {
355 gdk_window_raise (gtk_widget_get_window (ctxt->dialog));
356 return ctxt->dialog;
357 } else
358 return NULL;
361 static gboolean
362 cb_activate_default (GtkWindow *window)
364 GtkWidget *dw = gtk_window_get_default_widget (window);
366 * gtk_window_activate_default has a bad habit of trying
367 * to activate the focus widget.
369 return dw && gtk_widget_is_sensitive (dw) &&
370 gtk_window_activate_default (window);
375 * gnm_editable_enters:
376 * @window: dialog to affect.
377 * @editable: Editable to affect.
379 * Make the "activate" signal of an editable click the default dialog button.
381 * This is a literal copy of gnome_dialog_editable_enters, but not restricted
382 * to GnomeDialogs.
384 * Normally if there's an editable widget (such as #GtkEntry) in your
385 * dialog, pressing Enter will activate the editable rather than the
386 * default dialog button. However, in most cases, the user expects to
387 * type something in and then press enter to close the dialog. This
388 * function enables that behavior.
391 void
392 gnm_editable_enters (GtkWindow *window, GtkWidget *w)
394 g_return_if_fail (GTK_IS_WINDOW(window));
396 /* because I really do not feel like changing all the calls to this routine */
397 if (GNM_EXPR_ENTRY_IS (w))
398 w = GTK_WIDGET (gnm_expr_entry_get_entry (GNM_EXPR_ENTRY (w)));
400 g_signal_connect_swapped (G_OBJECT (w),
401 "activate",
402 G_CALLBACK (cb_activate_default), window);
406 * gnm_gtk_radio_group_get_selected:
407 * @radio_group: (element-type GtkRadioButton): list of radio buttons.
409 * Returns: the index of the selected radio button starting from list end.
412 gnm_gtk_radio_group_get_selected (GSList *radio_group)
414 GSList *l;
415 int i, c;
417 g_return_val_if_fail (radio_group != NULL, 0);
419 c = g_slist_length (radio_group);
421 for (i = 0, l = radio_group; l; l = l->next, i++){
422 GtkRadioButton *button = l->data;
424 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
425 return c - i - 1;
428 return 0;
433 gnm_gui_group_value (gpointer gui, char const * const group[])
435 int i;
436 for (i = 0; group[i]; i++) {
437 GtkWidget *w = go_gtk_builder_get_widget (gui, group[i]);
438 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w)))
439 return i;
441 return -1;
444 static gboolean
445 cb_delayed_destroy (gpointer w)
447 gtk_widget_destroy (gtk_widget_get_toplevel (w));
448 gtk_widget_destroy (w);
449 g_object_unref (w);
450 return FALSE;
453 static void
454 kill_popup_menu (GtkWidget *widget, G_GNUC_UNUSED gpointer user)
456 /* gtk+ currently gets unhappy if we destroy here, see bug 725142 */
457 g_idle_add (cb_delayed_destroy, widget);
461 * gnumeric_popup_menu:
462 * @menu: #GtkMenu
463 * @event: #GdkEvent optionally NULL
465 * Bring up a popup and if @event is non-NULL ensure that the popup is on the
466 * right screen.
468 void
469 gnumeric_popup_menu (GtkMenu *menu, GdkEvent *event)
471 g_return_if_fail (menu != NULL);
472 g_return_if_fail (GTK_IS_MENU (menu));
474 if (event)
475 gtk_menu_set_screen (menu, gdk_event_get_screen (event));
477 g_object_ref_sink (menu);
478 g_signal_connect (G_OBJECT (menu),
479 "hide",
480 G_CALLBACK (kill_popup_menu), NULL);
482 /* Do NOT pass the button used to create the menu.
483 * instead pass 0. Otherwise bringing up a menu with
484 * the right button will disable clicking on the menu with the left.
486 gtk_menu_popup (menu, NULL, NULL, NULL, NULL, 0,
487 (event
488 ? gdk_event_get_time (event)
489 : gtk_get_current_event_time()));
492 static void
493 gnumeric_tooltip_set_style (GtkWidget *widget)
495 gtk_style_context_add_class (gtk_widget_get_style_context (widget),
496 GTK_STYLE_CLASS_TOOLTIP);
497 gtk_style_context_add_class (gtk_widget_get_style_context (widget),
498 "pseudo-tooltip");
499 if (GTK_IS_CONTAINER (widget))
500 gtk_container_forall (GTK_CONTAINER (widget),
501 (GtkCallback) (gnumeric_tooltip_set_style),
502 NULL);
506 * gnm_convert_to_tooltip:
507 * @ref_widget:
508 * @widget:
510 * Returns: (transfer none): @widget
512 GtkWidget *
513 gnm_convert_to_tooltip (GtkWidget *ref_widget, GtkWidget *widget)
515 GtkWidget *tip, *frame;
516 GdkScreen *screen = gtk_widget_get_screen (ref_widget);
518 tip = gtk_window_new (GTK_WINDOW_POPUP);
519 gtk_window_set_type_hint (GTK_WINDOW (tip),
520 GDK_WINDOW_TYPE_HINT_TOOLTIP);
521 gtk_window_set_resizable (GTK_WINDOW (tip), FALSE);
522 gtk_window_set_gravity (GTK_WINDOW (tip), GDK_GRAVITY_NORTH_WEST);
523 gtk_window_set_screen (GTK_WINDOW (tip), screen);
524 gtk_widget_set_name (tip, "gtk-tooltip");
526 frame = gtk_frame_new (NULL);
527 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
528 gtk_widget_show (frame);
529 gtk_container_add (GTK_CONTAINER (frame), widget);
530 gtk_container_add (GTK_CONTAINER (tip), frame);
532 gnumeric_tooltip_set_style (tip);
534 return widget;
538 * gnm_create_tooltip:
540 * Returns: (transfer full): the newly allocated #GtkWidget.
542 GtkWidget *
543 gnm_create_tooltip (GtkWidget *ref_widget)
545 return gnm_convert_to_tooltip (ref_widget, gtk_label_new (""));
548 void
549 gnm_position_tooltip (GtkWidget *tip, int px, int py, gboolean horizontal)
551 GtkRequisition req;
553 gtk_widget_get_preferred_size (tip, &req, NULL);
555 if (horizontal){
556 px -= req.width / 2;
557 py -= req.height + 20;
558 } else {
559 px -= req.width + 20;
560 py -= req.height / 2;
563 if (px < 0)
564 px = 0;
565 if (py < 0)
566 py = 0;
568 gtk_window_move (GTK_WINDOW (gtk_widget_get_toplevel (tip)), px, py);
572 * gnm_gtk_builder_load:
573 * @cc: #GOCmdContext
574 * @uifile:
576 * Simple utility to open ui files
577 * Returns: (transfer full): the newly allocated #GtkBuilder.
579 GtkBuilder *
580 gnm_gtk_builder_load (char const *uifile, char const *domain, GOCmdContext *cc)
582 GtkBuilder *gui;
583 char *f;
585 if (strncmp (uifile, "res:", 4) == 0) {
586 f = g_strconcat ("res:/org/gnumeric/gnumeric/",
587 uifile + 4,
588 NULL);
589 } else if (g_path_is_absolute (uifile)) {
590 f = g_strdup (uifile);
591 } else {
592 f = g_strconcat ("res:gnm:", uifile, NULL);
595 gui = go_gtk_builder_load (f, domain, cc);
596 g_free (f);
598 return gui;
601 static void
602 popup_item_activate (GtkWidget *item, GnmPopupMenuElement const *elem)
604 GtkWidget *menu;
605 GnmPopupMenuHandler handler;
606 gpointer user_data;
608 // Go to top-level menu. This shouldn't be that hard.
609 menu = item;
610 while (TRUE) {
611 if (GTK_IS_MENU_ITEM (menu))
612 menu = gtk_widget_get_parent (menu);
613 else if (GTK_IS_MENU (menu)) {
614 GtkWidget *a = gtk_menu_get_attach_widget (GTK_MENU (menu));
615 if (a)
616 menu = a;
617 else
618 break;
619 } else
620 break;
622 handler = g_object_get_data (G_OBJECT (menu), "handler");
623 user_data = g_object_get_data (G_OBJECT (menu), "user-data");
624 g_return_if_fail (handler != NULL);
626 handler (elem, user_data);
630 * gnm_create_popup_menu:
631 * @elements:
632 * @handler: (scope notified):
633 * @user_data: user data to pass to @handler.
634 * @notify: destroy notification for @user_data
635 * @display_filter:
636 * @sensitive_filter:
637 * @event:
639 void
640 gnm_create_popup_menu (GnmPopupMenuElement const *elements,
641 GnmPopupMenuHandler handler,
642 gpointer user_data,
643 GDestroyNotify notify,
644 int display_filter, int sensitive_filter,
645 GdkEvent *event)
647 char const *trans;
648 GSList *menu_stack = NULL;
649 GtkWidget *menu, *item;
651 menu = gtk_menu_new ();
652 g_object_set_data (G_OBJECT (menu), "handler", (gpointer)handler);
653 g_object_set_data_full (G_OBJECT (menu), "user-data", user_data, notify);
654 for (; NULL != elements->name ; elements++) {
655 char const * const name = elements->name;
656 char const * const pix_name = elements->pixmap;
658 item = NULL;
660 if (elements->display_filter != 0 &&
661 !(elements->display_filter & display_filter)) {
662 if (elements->allocated_name) {
663 g_free (elements->allocated_name);
664 *(gchar **)(&elements->allocated_name) = NULL;
666 continue;
669 if (name != NULL && *name != '\0') {
670 if (elements->allocated_name)
671 trans = elements->allocated_name;
672 else
673 trans = _(name);
674 item = gtk_image_menu_item_new_with_mnemonic (trans);
675 if (elements->sensitive_filter != 0 &&
676 (elements->sensitive_filter & sensitive_filter))
677 gtk_widget_set_sensitive (GTK_WIDGET (item), FALSE);
678 if (pix_name != NULL) {
679 GtkWidget *image = gtk_image_new_from_icon_name (pix_name,
680 GTK_ICON_SIZE_MENU);
681 gtk_widget_show (image);
682 gtk_image_menu_item_set_image (
683 GTK_IMAGE_MENU_ITEM (item),
684 image);
686 if (elements->allocated_name) {
687 g_free (elements->allocated_name);
688 *(gchar **)(&elements->allocated_name) = NULL;
690 } else if (elements->index >= 0) {
691 /* separator */
692 item = gtk_menu_item_new ();
693 gtk_widget_set_sensitive (item, FALSE);
696 if (elements->index > 0) {
697 g_signal_connect (G_OBJECT (item),
698 "activate",
699 G_CALLBACK (popup_item_activate),
700 (gpointer)elements);
702 if (NULL != item) {
703 gtk_widget_show (item);
704 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
706 if (elements->index < 0) {
707 if (NULL != item) {
708 menu_stack = g_slist_prepend (menu_stack, menu);
709 menu = gtk_menu_new ();
710 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu);
711 } else {
712 menu = menu_stack->data;
713 menu_stack = g_slist_remove (menu_stack, menu);
717 gnumeric_popup_menu (GTK_MENU (menu), event);
720 void
721 gnm_init_help_button (GtkWidget *w, char const *lnk)
723 go_gtk_help_button_init (w, gnm_sys_data_dir (), "gnumeric", lnk);
726 char *
727 gnm_textbuffer_get_text (GtkTextBuffer *buf)
729 GtkTextIter start, end;
731 g_return_val_if_fail (buf != NULL, NULL);
733 gtk_text_buffer_get_start_iter (buf, &start);
734 gtk_text_buffer_get_end_iter (buf, &end);
735 /* We are using slice rather than text so that the tags still match */
736 return gtk_text_buffer_get_slice (buf, &start, &end, FALSE);
739 char *
740 gnm_textview_get_text (GtkTextView *text_view)
742 return gnm_textbuffer_get_text
743 (gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view)));
746 void
747 gnm_textview_set_text (GtkTextView *text_view, char const *txt)
749 gtk_text_buffer_set_text (
750 gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view)),
751 txt, -1);
754 void
755 gnm_load_pango_attributes_into_buffer (PangoAttrList *markup, GtkTextBuffer *buffer, gchar const *str)
757 gchar *str_retrieved = NULL;
759 if (str == NULL) {
760 GtkTextIter start, end;
761 gtk_text_buffer_get_start_iter (buffer, &start);
762 gtk_text_buffer_get_end_iter (buffer, &end);
763 str = str_retrieved = gtk_text_buffer_get_slice
764 (buffer, &start, &end, TRUE);
767 go_load_pango_attributes_into_buffer (markup, buffer, str);
769 g_free (str_retrieved);
772 #define gnmstoretexttagattrinpangoint(nameset, name, gnm_pango_attr_new) \
773 if (gnm_object_get_bool (tag, nameset)) { \
774 int value; \
775 g_object_get (G_OBJECT (tag), name, &value, NULL); \
776 attr = gnm_pango_attr_new (value); \
777 attr->start_index = x; \
778 attr->end_index = y; \
779 pango_attr_list_change (list, attr); \
783 static void
784 gnm_store_text_tag_attr_in_pango (PangoAttrList *list, GtkTextTag *tag, GtkTextIter *start, gchar const *text)
786 GtkTextIter end = *start;
787 gint x, y;
788 PangoAttribute * attr;
790 gtk_text_iter_forward_to_tag_toggle (&end, tag);
791 x = g_utf8_offset_to_pointer (text, gtk_text_iter_get_offset (start)) - text;
792 y = g_utf8_offset_to_pointer (text, gtk_text_iter_get_offset (&end)) - text;
794 if (gnm_object_get_bool (tag, "foreground-set")) {
795 GdkRGBA *color = NULL;
796 g_object_get (G_OBJECT (tag), "foreground-rgba", &color, NULL);
797 if (color) {
798 /* dividing 0 to 1 into 65536 equal length intervals */
799 attr = pango_attr_foreground_new
800 ((int)(CLAMP (color->red * 65536, 0., 65535.)),
801 (int)(CLAMP (color->green * 65536, 0., 65535.)),
802 (int)(CLAMP (color->blue * 65536, 0., 65535.)));
803 gdk_rgba_free (color);
804 attr->start_index = x;
805 attr->end_index = y;
806 pango_attr_list_change (list, attr);
810 gnmstoretexttagattrinpangoint ("style-set", "style", pango_attr_style_new)
811 gnmstoretexttagattrinpangoint ("weight-set", "weight", pango_attr_weight_new)
812 gnmstoretexttagattrinpangoint ("strikethrough-set", "strikethrough", pango_attr_strikethrough_new)
813 gnmstoretexttagattrinpangoint ("underline-set", "underline", pango_attr_underline_new)
814 gnmstoretexttagattrinpangoint ("rise-set", "rise", pango_attr_rise_new)
817 #undef gnmstoretexttagattrinpangoint
819 PangoAttrList *
820 gnm_get_pango_attributes_from_buffer (GtkTextBuffer *buffer)
822 PangoAttrList *list = pango_attr_list_new ();
823 GtkTextIter start;
824 gchar *text = gnm_textbuffer_get_text (buffer);
826 gtk_text_buffer_get_start_iter (buffer, &start);
828 while (!gtk_text_iter_is_end (&start)) {
829 if (gtk_text_iter_begins_tag (&start, NULL)) {
830 GSList *ptr, *l = gtk_text_iter_get_toggled_tags (&start, TRUE);
831 for (ptr = l; ptr; ptr = ptr->next)
832 gnm_store_text_tag_attr_in_pango (list, ptr->data, &start, text);
834 gtk_text_iter_forward_to_tag_toggle (&start, NULL);
837 g_free (text);
839 return list;
842 void
843 focus_on_entry (GtkEntry *entry)
845 if (entry == NULL)
846 return;
847 gtk_widget_grab_focus (GTK_WIDGET(entry));
848 gtk_editable_set_position (GTK_EDITABLE (entry), 0);
849 gtk_editable_select_region (GTK_EDITABLE (entry), 0,
850 gtk_entry_get_text_length (entry));
853 gboolean
854 entry_to_float_with_format_default (GtkEntry *entry, gnm_float *the_float,
855 gboolean update,
856 GOFormat const *format, gnm_float num)
858 char const *text = gtk_entry_get_text (entry);
859 gboolean need_default = (text == NULL);
861 if (!need_default) {
862 char *new_text = g_strdup (text);
863 need_default = (0 == strlen (g_strstrip(new_text)));
864 g_free (new_text);
867 if (need_default && !update) {
868 *the_float = num;
869 return FALSE;
872 if (need_default)
873 float_to_entry (entry, num);
875 return entry_to_float_with_format (entry, the_float, update, format);
878 gboolean
879 entry_to_float_with_format (GtkEntry *entry, gnm_float *the_float,
880 gboolean update, GOFormat const *format)
882 GnmValue *value = format_match_number (gtk_entry_get_text (entry), format, NULL);
884 *the_float = 0.0;
885 if (!value)
886 return TRUE;
888 *the_float = value_get_as_float (value);
889 if (update) {
890 char *tmp = format_value (format, value, 16, NULL);
891 gtk_entry_set_text (entry, tmp);
892 g_free (tmp);
895 value_release (value);
896 return FALSE;
900 * entry_to_int:
901 * @entry:
902 * @the_int:
903 * @update:
905 * Retrieve an int from an entry field parsing all reasonable formats
908 gboolean
909 entry_to_int (GtkEntry *entry, gint *the_int, gboolean update)
911 GnmValue *value = format_match_number (gtk_entry_get_text (entry), NULL, NULL);
912 gnm_float f;
914 *the_int = 0;
915 if (!value)
916 return TRUE;
918 f = value_get_as_float (value);
919 if (f < INT_MIN || f > INT_MAX || f != (*the_int = (int)f)) {
920 value_release (value);
921 return TRUE;
924 if (update) {
925 char *tmp = format_value (NULL, value, 16, NULL);
926 gtk_entry_set_text (entry, tmp);
927 g_free (tmp);
930 value_release (value);
931 return FALSE;
935 * float_to_entry:
936 * @entry:
937 * @the_float:
940 void
941 float_to_entry (GtkEntry *entry, gnm_float the_float)
943 GnmValue *val = value_new_float (the_float);
944 char *text = format_value (NULL, val, 16, NULL);
945 value_release(val);
946 if (text != NULL) {
947 gtk_entry_set_text (entry, text);
948 g_free (text);
953 * int_to_entry:
954 * @entry:
955 * @the_int:
959 void
960 int_to_entry (GtkEntry *entry, gint the_int)
962 GnmValue *val = value_new_int (the_int);
963 char *text = format_value (NULL, val, 16, NULL);
964 value_release(val);
965 if (text != NULL) {
966 gtk_entry_set_text (entry, text);
967 g_free (text);
971 static void
972 cb_focus_to_entry (GtkWidget *button, GtkWidget *entry)
974 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
975 gtk_widget_grab_focus (entry);
978 static gboolean
979 cb_activate_button (GtkWidget *button)
981 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
982 return FALSE;
985 void
986 gnm_link_button_and_entry (GtkWidget *button, GtkWidget *entry)
988 g_signal_connect (G_OBJECT (button),
989 "clicked", G_CALLBACK (cb_focus_to_entry),
990 entry);
991 g_signal_connect_swapped (G_OBJECT (entry),
992 "focus_in_event",
993 G_CALLBACK (cb_activate_button),
994 button);
997 /* ------------------------------------------------------------------------- */
999 void
1000 gnm_widget_set_cursor (GtkWidget *w, GdkCursor *cursor)
1002 gdk_window_set_cursor (gtk_widget_get_window (w), cursor);
1005 void
1006 gnm_widget_set_cursor_type (GtkWidget *w, GdkCursorType ct)
1008 GdkDisplay *display = gtk_widget_get_display (w);
1009 GdkCursor *cursor = gdk_cursor_new_for_display (display, ct);
1010 gnm_widget_set_cursor (w, cursor);
1011 g_object_unref (cursor);
1014 /* ------------------------------------------------------------------------- */
1017 * gnm_message_dialog_create:
1019 * A convenience fonction to build HIG compliant message dialogs.
1021 * parent : transient parent, or NULL for none.
1022 * flags
1023 * type : type of dialog
1024 * primary_message : message displayed in bold
1025 * secondary_message : message displayed below
1027 * Returns: (transfer full): a GtkDialog, without buttons.
1030 GtkWidget *
1031 gnm_message_dialog_create (GtkWindow * parent,
1032 GtkDialogFlags flags,
1033 GtkMessageType type,
1034 gchar const * primary_message,
1035 gchar const * secondary_message)
1037 GtkWidget * dialog;
1038 GtkWidget * label;
1039 GtkWidget * hbox;
1040 gchar *message;
1041 const gchar *icon_name;
1042 GtkWidget *image;
1043 const char *title;
1045 dialog = gtk_dialog_new_with_buttons ("", parent, flags, NULL, NULL);
1047 switch (type) {
1048 default:
1049 g_warning ("Unknown GtkMessageType %d", type);
1050 case GTK_MESSAGE_INFO:
1051 icon_name = "dialog-information";
1052 title = _("Information");
1053 break;
1055 case GTK_MESSAGE_QUESTION:
1056 icon_name = "dialog-question";
1057 title = _("Question");
1058 break;
1060 case GTK_MESSAGE_WARNING:
1061 icon_name = "dialog-warning";
1062 title = _("Warning");
1063 break;
1065 case GTK_MESSAGE_ERROR:
1066 icon_name = "dialog-error";
1067 title = _("Error");
1068 break;
1071 image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_DIALOG);
1072 gtk_window_set_title (GTK_WINDOW (dialog), title);
1074 if (primary_message) {
1075 if (secondary_message) {
1076 message = g_strdup_printf ("<b>%s</b>\n\n%s",
1077 primary_message,
1078 secondary_message);
1079 } else {
1080 message = g_strdup_printf ("<b>%s</b>",
1081 primary_message);
1083 } else {
1084 message = g_strdup_printf ("%s", secondary_message);
1086 label = gtk_label_new (message);
1087 g_free (message);
1089 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1090 gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, TRUE, 0);
1091 gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
1092 gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), hbox, TRUE, TRUE, 0);
1094 gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
1095 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1096 gtk_misc_set_alignment (GTK_MISC (label), 0.0 , 0.0);
1097 gtk_box_set_spacing (GTK_BOX (hbox), 12);
1098 gtk_container_set_border_width (GTK_CONTAINER (hbox), 6);
1099 gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 12);
1100 gtk_container_set_border_width (GTK_CONTAINER (dialog), 6);
1101 gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
1102 gtk_widget_show_all (GTK_WIDGET (gtk_dialog_get_content_area (GTK_DIALOG (dialog))));
1104 return dialog;
1107 typedef struct {
1108 GPtrArray *objects_signals;
1109 } GnmDialogDestroySignals;
1111 static void
1112 cb_gnm_dialog_setup_destroy_handlers (G_GNUC_UNUSED GtkWidget *widget,
1113 GnmDialogDestroySignals *dd)
1115 GPtrArray *os = dd->objects_signals;
1116 int i;
1118 for (i = 0; i < (int)os->len; i += 2) {
1119 GObject *obj = g_ptr_array_index (os, i);
1120 guint s = GPOINTER_TO_UINT (g_ptr_array_index (os, i + 1));
1121 g_signal_handler_disconnect (obj, s);
1124 g_ptr_array_free (os, TRUE);
1125 memset (dd, 0, sizeof (*dd));
1126 g_free (dd);
1129 void
1130 gnm_dialog_setup_destroy_handlers (GtkDialog *dialog,
1131 WBCGtk *wbcg,
1132 GnmDialogDestroyOptions what)
1134 GnmDialogDestroySignals *dd = g_new (GnmDialogDestroySignals, 1);
1135 Workbook *wb = wb_control_get_workbook (GNM_WBC (wbcg));
1136 Sheet *sheet = wb_control_cur_sheet (GNM_WBC (wbcg));
1137 int N = workbook_sheet_count (wb), i;
1138 GPtrArray *os = g_ptr_array_new ();
1140 dd->objects_signals = os;
1142 /* FIXME: Properly implement CURRENT_SHEET_REMOVED. */
1143 if (what & GNM_DIALOG_DESTROY_CURRENT_SHEET_REMOVED)
1144 what |= GNM_DIALOG_DESTROY_SHEET_REMOVED;
1146 if (what & GNM_DIALOG_DESTROY_SHEET_REMOVED) {
1147 guint s = g_signal_connect_swapped
1148 (G_OBJECT (wb),
1149 "sheet_deleted",
1150 G_CALLBACK (gtk_widget_destroy),
1151 dialog);
1152 g_ptr_array_add (os, wb);
1153 g_ptr_array_add (os, GUINT_TO_POINTER (s));
1156 if (what & GNM_DIALOG_DESTROY_SHEET_ADDED) {
1157 guint s = g_signal_connect_swapped
1158 (G_OBJECT (wb),
1159 "sheet_added",
1160 G_CALLBACK (gtk_widget_destroy),
1161 dialog);
1162 g_ptr_array_add (os, wb);
1163 g_ptr_array_add (os, GUINT_TO_POINTER (s));
1166 if (what & GNM_DIALOG_DESTROY_SHEETS_REORDERED) {
1167 guint s = g_signal_connect_swapped
1168 (G_OBJECT (wb),
1169 "sheet_order_changed",
1170 G_CALLBACK (gtk_widget_destroy),
1171 dialog);
1172 g_ptr_array_add (os, wb);
1173 g_ptr_array_add (os, GUINT_TO_POINTER (s));
1176 for (i = 0; i < N; i++) {
1177 Sheet *this_sheet = workbook_sheet_by_index (wb, i);
1178 gboolean current = (sheet == this_sheet);
1180 if ((what & GNM_DIALOG_DESTROY_SHEET_RENAMED) ||
1181 (current && (what & GNM_DIALOG_DESTROY_CURRENT_SHEET_RENAMED))) {
1182 guint s = g_signal_connect_swapped
1183 (G_OBJECT (this_sheet),
1184 "notify::name",
1185 G_CALLBACK (gtk_widget_destroy),
1186 dialog);
1187 g_ptr_array_add (os, this_sheet);
1188 g_ptr_array_add (os, GUINT_TO_POINTER (s));
1192 g_signal_connect (G_OBJECT (dialog),
1193 "destroy",
1194 G_CALLBACK (cb_gnm_dialog_setup_destroy_handlers),
1195 dd);
1199 void
1200 gnm_canvas_get_position (GocCanvas *canvas, int *x, int *y, gint64 px, gint64 py)
1202 GtkWidget *cw = GTK_WIDGET (canvas);
1203 GdkWindow *cbw = gtk_layout_get_bin_window (GTK_LAYOUT (cw));
1204 int wx, wy;
1206 gdk_window_get_origin (cbw, &wx, &wy);
1208 /* we don't need to multiply px and py by the canvas pixels_per_unit
1209 * field since all the callers already do that */
1210 px -= canvas->scroll_x1 * canvas->pixels_per_unit;
1211 py -= canvas->scroll_y1 * canvas->pixels_per_unit;
1212 /* let's take care of RTL sheets */
1213 if (canvas->direction == GOC_DIRECTION_RTL)
1214 px = goc_canvas_get_width (canvas) - px;
1216 *x = px + wx;
1217 *y = py + wy;
1221 * Get the gdk position for canvas coordinates (x,y). This is suitable
1222 * for tooltip windows.
1224 * It is possible that this does not work right for very large coordinates
1225 * prior to gtk+ 2.18. See the code and comments in gnm_canvas_get_position.
1227 void
1228 gnm_canvas_get_screen_position (GocCanvas *canvas,
1229 double x, double y,
1230 int *ix, int *iy)
1232 GdkWindow *cbw = gtk_layout_get_bin_window (GTK_LAYOUT (canvas));
1233 int wx, wy;
1235 gdk_window_get_origin (cbw, &wx, &wy);
1236 goc_canvas_c2w (canvas, x, y, ix, iy);
1237 (*ix) += wx;
1238 (*iy) += wy;
1242 gboolean
1243 gnm_check_for_plugins_missing (char const **ids, GtkWindow *parent)
1245 for (; *ids != NULL; ids++) {
1246 GOPlugin *pi = go_plugins_get_plugin_by_id (*ids);
1247 if (pi == NULL) {
1248 GOErrorInfo *error;
1249 error = go_error_info_new_printf
1250 (_("The plugin with id %s is required "
1251 "but cannot be found."), *ids);
1252 gnm_go_error_info_dialog_show (parent,
1253 error);
1254 return TRUE;
1255 } else if (!go_plugin_is_active (pi)) {
1256 GOErrorInfo *error;
1257 error = go_error_info_new_printf
1258 (_("The %s plugin is required "
1259 "but is not loaded."),
1260 go_plugin_get_name (pi));
1261 gnm_go_error_info_dialog_show (parent,
1262 error);
1263 return TRUE;
1266 return FALSE;
1270 void
1271 gnm_cell_renderer_text_copy_background_to_cairo (GtkCellRendererText *crt,
1272 cairo_t *cr)
1274 GdkRGBA *c = NULL;
1275 g_object_get (crt, "background-rgba", &c, NULL);
1276 gdk_cairo_set_source_rgba (cr, c);
1277 gdk_rgba_free (c);
1281 gnm_widget_measure_string (GtkWidget *w, const char *s)
1283 GtkStyleContext *ctxt;
1284 int len;
1285 PangoFontDescription *desc;
1286 GtkStateFlags state = GTK_STATE_FLAG_NORMAL;
1288 ctxt = gtk_widget_get_style_context (w);
1290 // As-of gtk+ 3.20 we have to set the context state to the state
1291 // we are querying for. This ought to work before gtk+ 3.20 too.
1292 gtk_style_context_save (ctxt);
1293 gtk_style_context_set_state (ctxt, state);
1294 gtk_style_context_get (ctxt, state, "font", &desc, NULL);
1295 gtk_style_context_restore (ctxt);
1297 len = go_pango_measure_string
1298 (gtk_widget_get_pango_context (w), desc, s);
1300 pango_font_description_free (desc);
1302 return len;
1305 static const char *
1306 gnm_ag_translate (const char *s, const char *ctxt)
1308 return ctxt
1309 ? g_dpgettext2 (NULL, ctxt, s)
1310 : _(s);
1313 void
1314 gnm_action_group_add_actions (GtkActionGroup *group,
1315 GnmActionEntry const *actions, size_t n,
1316 gpointer user)
1318 unsigned i;
1320 for (i = 0; i < n; i++) {
1321 GnmActionEntry const *entry = actions + i;
1322 const char *name = entry->name;
1323 const char *label =
1324 gnm_ag_translate (entry->label, entry->label_context);
1325 const char *tip =
1326 gnm_ag_translate (entry->tooltip, NULL);
1327 GtkAction *a;
1329 if (entry->toggle) {
1330 GtkToggleAction *ta =
1331 gtk_toggle_action_new (name, label, tip, NULL);
1332 gtk_toggle_action_set_active (ta, entry->is_active);
1333 a = GTK_ACTION (ta);
1334 } else {
1335 a = gtk_action_new (name, label, tip, NULL);
1338 g_object_set (a, "icon-name", entry->icon, NULL);
1340 if (entry->callback) {
1341 GClosure *closure =
1342 g_cclosure_new (entry->callback, user, NULL);
1343 g_signal_connect_closure (a, "activate", closure,
1344 FALSE);
1347 gtk_action_group_add_action_with_accel (group,
1349 entry->accelerator);
1350 g_object_unref (a);
1354 void
1355 gnm_action_group_add_action (GtkActionGroup *group, GtkAction *act)
1358 * See the docs for gtk_action_group_add_action as to why we don't
1359 * call just that.
1361 gtk_action_group_add_action_with_accel (group, act, NULL);
1364 void
1365 gnm_style_context_get_color (GtkStyleContext *context,
1366 GtkStateFlags state,
1367 GdkRGBA *color)
1369 // As-of gtk+ 3.20 we have to set the context state to the state
1370 // we are querying for. This ought to work before gtk+ 3.20 too.
1371 gtk_style_context_save (context);
1372 gtk_style_context_set_state (context, state);
1373 gtk_style_context_get_color (context,
1374 gtk_style_context_get_state (context),
1375 color);
1376 gtk_style_context_restore (context);
1379 void
1380 gnm_get_link_color (GtkWidget *widget, GdkRGBA *res)
1382 GtkStyleContext *ctxt = gtk_widget_get_style_context (widget);
1383 gnm_style_context_get_color (ctxt, GTK_STATE_FLAG_LINK, res);
1386 // ----------------------------------------------------------------------------