GUI: Move .ui files from goffice resources to glib resources
[gnumeric.git] / src / gui-util.c
blobb28679a2cbca6c61dc9470d174eda872052cdf53
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * gnumeric-util.c: Various GUI utility functions.
5 * Author:
6 * Miguel de Icaza (miguel@gnu.org)
7 */
9 #include <gnumeric-config.h>
10 #include <glib/gi18n-lib.h>
11 #include "gnumeric.h"
12 #include "libgnumeric.h"
13 #include "gui-util.h"
15 #include "gutils.h"
16 #include "parse-util.h"
17 #include "style.h"
18 #include "style-color.h"
19 #include "value.h"
20 #include "number-match.h"
21 #include "gnm-format.h"
22 #include "application.h"
23 #include "workbook.h"
24 #include "libgnumeric.h"
25 #include "wbc-gtk.h"
26 #include "widgets/gnumeric-expr-entry.h"
28 #include <goffice/goffice.h>
29 #include <gtk/gtk.h>
30 #include <atk/atkrelation.h>
31 #include <atk/atkrelationset.h>
32 #include <gdk/gdkkeysyms.h>
34 #include <string.h>
36 #define ERROR_INFO_MAX_LEVEL 9
37 #define ERROR_INFO_TAG_NAME "errorinfotag%i"
39 static void
40 insert_error_info (GtkTextBuffer* text, GOErrorInfo *error, gint level)
42 gchar *message = (gchar *) go_error_info_peek_message (error);
43 GSList *details_list, *l;
44 GtkTextIter start, last;
45 gchar *tag_name = g_strdup_printf (ERROR_INFO_TAG_NAME,
46 MIN (level, ERROR_INFO_MAX_LEVEL));
47 if (message == NULL)
48 message = g_strdup (_("Multiple errors\n"));
49 else
50 message = g_strdup_printf ("%s\n", message);
51 gtk_text_buffer_get_bounds (text, &start, &last);
52 gtk_text_buffer_insert_with_tags_by_name (text, &last,
53 message, -1,
54 tag_name, NULL);
55 g_free (tag_name);
56 g_free (message);
57 details_list = go_error_info_peek_details (error);
58 for (l = details_list; l != NULL; l = l->next) {
59 GOErrorInfo *detail_error = l->data;
60 insert_error_info (text, detail_error, level + 1);
62 return;
65 /**
66 * gnumeric_go_error_info_list_dialog_create:
67 * @errs: (element-type GOErrorInfo):
69 * SHOULD BE IN GOFFICE
70 * Returns: (transfer full): the newly allocated dialog.
72 static GtkWidget *
73 gnumeric_go_error_info_list_dialog_create (GSList *errs)
75 GtkWidget *dialog;
76 GtkWidget *scrolled_window;
77 GtkTextView *view;
78 GtkTextBuffer *text;
79 GtkMessageType mtype;
80 gint bf_lim = 1;
81 gint i;
82 GdkScreen *screen;
83 GSList *l, *lf;
84 int severity = 0, this_severity;
85 gboolean message_null = TRUE;
87 for (l = errs; l != NULL; l = l->next) {
88 GOErrorInfo *err = l->data;
89 if (go_error_info_peek_message (err)!= NULL)
90 message_null = FALSE;
91 this_severity = go_error_info_peek_severity (err);
92 if (this_severity > severity)
93 severity = this_severity;
95 lf = g_slist_copy (errs);
96 lf = g_slist_reverse (lf);
98 if (message_null)
99 bf_lim++;
101 mtype = GTK_MESSAGE_ERROR;
102 if (severity < GO_ERROR)
103 mtype = GTK_MESSAGE_WARNING;
104 dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_DESTROY_WITH_PARENT,
105 mtype, GTK_BUTTONS_CLOSE, " ");
106 screen = gtk_widget_get_screen (dialog);
107 gtk_widget_set_size_request (dialog,
108 gdk_screen_get_width (screen) / 3,
109 gdk_screen_get_width (screen) / 4);
110 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
111 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
112 GTK_POLICY_AUTOMATIC,
113 GTK_POLICY_AUTOMATIC);
114 gtk_scrolled_window_set_shadow_type
115 (GTK_SCROLLED_WINDOW (scrolled_window),
116 GTK_SHADOW_ETCHED_IN);
117 view = GTK_TEXT_VIEW (gtk_text_view_new ());
118 gtk_text_view_set_wrap_mode (view, GTK_WRAP_WORD);
119 gtk_text_view_set_editable (view, FALSE);
120 gtk_text_view_set_cursor_visible (view, FALSE);
122 gtk_text_view_set_pixels_below_lines
123 (view, gtk_text_view_get_pixels_inside_wrap (view) + 3);
124 text = gtk_text_view_get_buffer (view);
125 for (i = ERROR_INFO_MAX_LEVEL; i-- > 0;) {
126 gchar *tag_name = g_strdup_printf (ERROR_INFO_TAG_NAME, i);
127 gtk_text_buffer_create_tag
128 (text, tag_name,
129 "left_margin", i * 12,
130 "right_margin", i * 12,
131 "weight", ((i < bf_lim)
132 ? PANGO_WEIGHT_BOLD
133 : PANGO_WEIGHT_NORMAL),
134 NULL);
135 g_free (tag_name);
137 for (l = lf; l != NULL; l = l->next) {
138 GOErrorInfo *err = l->data;
139 insert_error_info (text, err, 0);
141 g_slist_free (lf);
143 gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (view));
144 gtk_widget_show_all (GTK_WIDGET (scrolled_window));
145 gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), scrolled_window, TRUE, TRUE, 0);
147 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CLOSE);
148 return dialog;
152 * gnm_go_error_info_dialog_create:
154 * SHOULD BE IN GOFFICE
155 * Returns: (transfer full): the newly allocated dialog.
157 GtkWidget *
158 gnm_go_error_info_dialog_create (GOErrorInfo *error)
160 GSList *l = g_slist_append (NULL, error);
161 GtkWidget *w = gnumeric_go_error_info_list_dialog_create (l);
162 g_slist_free (l);
163 return w;
167 * gnm_go_error_info_dialog_show:
170 void
171 gnm_go_error_info_dialog_show (GtkWindow *parent, GOErrorInfo *error)
173 GtkWidget *dialog = gnm_go_error_info_dialog_create (error);
174 go_gtk_dialog_run (GTK_DIALOG (dialog), parent);
178 * gnm_go_error_info_list_dialog_show:
179 * @parent:
180 * @errs: (element-type GOErrorInfo):
183 void
184 gnm_go_error_info_list_dialog_show (GtkWindow *parent,
185 GSList *errs)
187 GtkWidget *dialog = gnumeric_go_error_info_list_dialog_create (errs);
188 go_gtk_dialog_run (GTK_DIALOG (dialog), parent);
192 typedef struct {
193 WBCGtk *wbcg;
194 GtkWidget *dialog;
195 char const *key;
196 gboolean freed;
197 } KeyedDialogContext;
199 static void
200 cb_free_keyed_dialog_context (KeyedDialogContext *ctxt)
202 if (ctxt->freed)
203 return;
204 ctxt->freed = TRUE;
207 * One of these causes a recursive call which will do nothing due to
208 * ->freed.
210 g_object_set_data (G_OBJECT (ctxt->wbcg), ctxt->key, NULL);
211 g_object_set_data (G_OBJECT (ctxt->dialog), "KeyedDialog", NULL);
212 g_free (ctxt);
215 static void
216 cb_keyed_dialog_destroy (GtkDialog *dialog)
219 * gtk-builder likes to hold refs on objects. That interferes
220 * with the way we handle finalization of dialogs' state.
221 * Trigger this now.
223 g_object_set_data (G_OBJECT (dialog), "state", NULL);
226 static gint
227 cb_keyed_dialog_keypress (GtkWidget *dialog, GdkEventKey *event,
228 G_GNUC_UNUSED gpointer user)
230 if (event->keyval == GDK_KEY_Escape) {
231 gtk_widget_destroy (GTK_WIDGET (dialog));
232 return TRUE;
234 return FALSE;
237 #define SAVE_SIZES_SCREEN_KEY "geometry-hash"
239 static void
240 cb_save_sizes (GtkWidget *dialog, const char *key)
242 GdkRectangle *r;
243 GtkAllocation da;
244 GdkScreen *screen = gtk_widget_get_screen (dialog);
245 GHashTable *h = g_object_get_data (G_OBJECT (screen),
246 SAVE_SIZES_SCREEN_KEY);
247 if (!h) {
248 h = g_hash_table_new_full (g_str_hash, g_str_equal,
249 (GDestroyNotify)g_free,
250 (GDestroyNotify)g_free);
252 * We hang this on the screen because pixel sizes make
253 * no sense across screens.
255 * ANYONE WHO CHANGES THIS CODE TO SAVE THESE SIZES ON EXIT
256 * AND RELOADS THEM ON STARTUP WILL GET TARRED AND FEATHERED.
257 * -- MW, 20071113
259 g_object_set_data_full (G_OBJECT (screen),
260 SAVE_SIZES_SCREEN_KEY, h,
261 (GDestroyNotify)g_hash_table_destroy);
264 gtk_widget_get_allocation (dialog, &da);
265 r = g_memdup (&da, sizeof (da));
266 gdk_window_get_position (gtk_widget_get_window (dialog), &r->x, &r->y);
267 g_hash_table_replace (h, g_strdup (key), r);
270 void
271 gnm_restore_window_geometry (GtkWindow *dialog, const char *key)
273 GtkWidget *top = gtk_widget_get_toplevel (GTK_WIDGET (dialog));
274 GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (dialog));
275 GHashTable *h = g_object_get_data (G_OBJECT (screen), SAVE_SIZES_SCREEN_KEY);
276 GdkRectangle *allocation = h ? g_hash_table_lookup (h, key) : NULL;
278 if (allocation) {
279 #if 0
280 g_printerr ("Restoring %s to %dx%d at (%d,%d)\n",
281 key, allocation->width, allocation->height,
282 allocation->x, allocation->y);
283 #endif
284 gtk_window_move
285 (GTK_WINDOW (top),
286 allocation->x, allocation->y);
287 gtk_window_set_default_size
288 (GTK_WINDOW (top),
289 allocation->width, allocation->height);
292 g_signal_connect (G_OBJECT (dialog), "unrealize",
293 G_CALLBACK (cb_save_sizes),
294 (gpointer)key);
298 * gnm_keyed_dialog:
299 * @wbcg: A WBCGtk
300 * @dialog: A transient window
301 * @key: A key to identify the dialog
303 * Make dialog a transient child of wbcg, attaching to wbcg object data to
304 * identify the dialog. The object data makes it possible to ensure that
305 * only one dialog of a kind can be displayed for a wbcg. Deallocation of
306 * the object data is managed here.
308 void
309 gnm_keyed_dialog (WBCGtk *wbcg, GtkWindow *dialog, char const *key)
311 KeyedDialogContext *ctxt;
313 g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
314 g_return_if_fail (GTK_IS_WINDOW (dialog));
315 g_return_if_fail (key != NULL);
317 wbcg_set_transient (wbcg, dialog);
319 go_dialog_guess_alternative_button_order (GTK_DIALOG (dialog));
321 ctxt = g_new (KeyedDialogContext, 1);
322 ctxt->wbcg = wbcg;
323 ctxt->dialog = GTK_WIDGET (dialog);
324 ctxt->key = key;
325 ctxt->freed = FALSE;
326 g_object_set_data_full (G_OBJECT (wbcg), key, ctxt,
327 (GDestroyNotify)cb_free_keyed_dialog_context);
328 g_object_set_data_full (G_OBJECT (dialog), "KeyedDialog", ctxt,
329 (GDestroyNotify)cb_free_keyed_dialog_context);
330 g_signal_connect (G_OBJECT (dialog), "key_press_event",
331 G_CALLBACK (cb_keyed_dialog_keypress), NULL);
332 g_signal_connect (G_OBJECT (dialog), "destroy",
333 G_CALLBACK (cb_keyed_dialog_destroy), NULL);
335 gnm_restore_window_geometry (dialog, key);
339 * gnm_dialog_raise_if_exists:
340 * @wbcg: A WBCGtk
341 * @key: A key to identify the dialog
343 * Raise the dialog identified by key if it is registered on the wbcg.
344 * Returns: (transfer none): TRUE if dialog found, FALSE if not.
346 gpointer
347 gnm_dialog_raise_if_exists (WBCGtk *wbcg, char const *key)
349 KeyedDialogContext *ctxt;
351 g_return_val_if_fail (wbcg != NULL, NULL);
352 g_return_val_if_fail (key != NULL, NULL);
354 /* Ensure we only pop up one copy per workbook */
355 ctxt = g_object_get_data (G_OBJECT (wbcg), key);
356 if (ctxt && GTK_IS_WINDOW (ctxt->dialog)) {
357 gdk_window_raise (gtk_widget_get_window (ctxt->dialog));
358 return ctxt->dialog;
359 } else
360 return NULL;
363 static gboolean
364 cb_activate_default (GtkWindow *window)
366 GtkWidget *dw = gtk_window_get_default_widget (window);
368 * gtk_window_activate_default has a bad habit of trying
369 * to activate the focus widget.
371 return dw && gtk_widget_is_sensitive (dw) &&
372 gtk_window_activate_default (window);
377 * gnm_editable_enters:
378 * @window: dialog to affect.
379 * @editable: Editable to affect.
381 * Make the "activate" signal of an editable click the default dialog button.
383 * This is a literal copy of gnome_dialog_editable_enters, but not restricted
384 * to GnomeDialogs.
386 * Normally if there's an editable widget (such as #GtkEntry) in your
387 * dialog, pressing Enter will activate the editable rather than the
388 * default dialog button. However, in most cases, the user expects to
389 * type something in and then press enter to close the dialog. This
390 * function enables that behavior.
393 void
394 gnm_editable_enters (GtkWindow *window, GtkWidget *w)
396 g_return_if_fail (GTK_IS_WINDOW(window));
398 /* because I really do not feel like changing all the calls to this routine */
399 if (GNM_EXPR_ENTRY_IS (w))
400 w = GTK_WIDGET (gnm_expr_entry_get_entry (GNM_EXPR_ENTRY (w)));
402 g_signal_connect_swapped (G_OBJECT (w),
403 "activate",
404 G_CALLBACK (cb_activate_default), window);
408 * gnm_gtk_radio_group_get_selected:
409 * @radio_group: (element-type GtkRadioButton): list of radio buttons.
411 * Returns: the index of the selected radio button starting from list end.
414 gnm_gtk_radio_group_get_selected (GSList *radio_group)
416 GSList *l;
417 int i, c;
419 g_return_val_if_fail (radio_group != NULL, 0);
421 c = g_slist_length (radio_group);
423 for (i = 0, l = radio_group; l; l = l->next, i++){
424 GtkRadioButton *button = l->data;
426 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
427 return c - i - 1;
430 return 0;
435 gnm_gui_group_value (gpointer gui, char const * const group[])
437 int i;
438 for (i = 0; group[i]; i++) {
439 GtkWidget *w = go_gtk_builder_get_widget (gui, group[i]);
440 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w)))
441 return i;
443 return -1;
446 static gboolean
447 cb_delayed_destroy (gpointer w)
449 gtk_widget_destroy (gtk_widget_get_toplevel (w));
450 gtk_widget_destroy (w);
451 g_object_unref (w);
452 return FALSE;
455 static void
456 kill_popup_menu (GtkWidget *widget, G_GNUC_UNUSED gpointer user)
458 /* gtk+ currently gets unhappy if we destroy here, see bug 725142 */
459 g_idle_add (cb_delayed_destroy, widget);
463 * gnumeric_popup_menu:
464 * @menu: #GtkMenu
465 * @event: #GdkEvent optionally NULL
467 * Bring up a popup and if @event is non-NULL ensure that the popup is on the
468 * right screen.
470 void
471 gnumeric_popup_menu (GtkMenu *menu, GdkEvent *event)
473 g_return_if_fail (menu != NULL);
474 g_return_if_fail (GTK_IS_MENU (menu));
476 if (event)
477 gtk_menu_set_screen (menu, gdk_event_get_screen (event));
479 g_object_ref_sink (menu);
480 g_signal_connect (G_OBJECT (menu),
481 "hide",
482 G_CALLBACK (kill_popup_menu), NULL);
484 /* Do NOT pass the button used to create the menu.
485 * instead pass 0. Otherwise bringing up a menu with
486 * the right button will disable clicking on the menu with the left.
488 gtk_menu_popup (menu, NULL, NULL, NULL, NULL, 0,
489 (event
490 ? gdk_event_get_time (event)
491 : gtk_get_current_event_time()));
494 static void
495 gnumeric_tooltip_set_style (GtkWidget *widget)
497 gtk_style_context_add_class (gtk_widget_get_style_context (widget),
498 GTK_STYLE_CLASS_TOOLTIP);
499 gtk_style_context_add_class (gtk_widget_get_style_context (widget),
500 "pseudo-tooltip");
501 if (GTK_IS_CONTAINER (widget))
502 gtk_container_forall (GTK_CONTAINER (widget),
503 (GtkCallback) (gnumeric_tooltip_set_style),
504 NULL);
508 * gnm_convert_to_tooltip:
509 * @ref_widget:
510 * @widget:
512 * Returns: (transfer none): @widget
514 GtkWidget *
515 gnm_convert_to_tooltip (GtkWidget *ref_widget, GtkWidget *widget)
517 GtkWidget *tip, *frame;
518 GdkScreen *screen = gtk_widget_get_screen (ref_widget);
520 tip = gtk_window_new (GTK_WINDOW_POPUP);
521 gtk_window_set_type_hint (GTK_WINDOW (tip),
522 GDK_WINDOW_TYPE_HINT_TOOLTIP);
523 gtk_window_set_resizable (GTK_WINDOW (tip), FALSE);
524 gtk_window_set_gravity (GTK_WINDOW (tip), GDK_GRAVITY_NORTH_WEST);
525 gtk_window_set_screen (GTK_WINDOW (tip), screen);
526 gtk_widget_set_name (tip, "gtk-tooltip");
528 frame = gtk_frame_new (NULL);
529 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
530 gtk_widget_show (frame);
531 gtk_container_add (GTK_CONTAINER (frame), widget);
532 gtk_container_add (GTK_CONTAINER (tip), frame);
534 gnumeric_tooltip_set_style (tip);
536 return widget;
540 * gnm_create_tooltip:
542 * Returns: (transfer full): the newly allocated #GtkWidget.
544 GtkWidget *
545 gnm_create_tooltip (GtkWidget *ref_widget)
547 return gnm_convert_to_tooltip (ref_widget, gtk_label_new (""));
550 void
551 gnm_position_tooltip (GtkWidget *tip, int px, int py, gboolean horizontal)
553 GtkRequisition req;
555 gtk_widget_get_preferred_size (tip, &req, NULL);
557 if (horizontal){
558 px -= req.width / 2;
559 py -= req.height + 20;
560 } else {
561 px -= req.width + 20;
562 py -= req.height / 2;
565 if (px < 0)
566 px = 0;
567 if (py < 0)
568 py = 0;
570 gtk_window_move (GTK_WINDOW (gtk_widget_get_toplevel (tip)), px, py);
574 * gnm_gtk_builder_load:
575 * @cc: #GOCmdContext
576 * @uifile:
578 * Simple utility to open ui files
579 * Returns: (transfer full): the newly allocated #GtkBuilder.
581 GtkBuilder *
582 gnm_gtk_builder_load (char const *uifile, char const *domain, GOCmdContext *cc)
584 GtkBuilder *gui;
585 char *f;
587 if (strncmp (uifile, "res:", 4) == 0) {
588 f = g_strconcat ("res:/org/gnumeric/gnumeric/",
589 uifile + 4,
590 NULL);
591 } else if (g_path_is_absolute (uifile)) {
592 f = g_strdup (uifile);
593 } else {
594 f = g_strconcat ("res:gnm:", uifile, NULL);
597 gui = go_gtk_builder_load (f, domain, cc);
598 g_free (f);
600 return gui;
603 static void
604 popup_item_activate (GtkWidget *item, gpointer *user_data)
606 GnmPopupMenuElement const *elem =
607 g_object_get_data (G_OBJECT (item), "descriptor");
608 GnmPopupMenuHandler handler =
609 g_object_get_data (G_OBJECT (item), "handler");
611 g_return_if_fail (elem != NULL);
612 g_return_if_fail (handler != NULL);
614 handler (elem, user_data);
618 * gnm_create_popup_menu:
619 * @elements:
620 * @handler: (scope async):
621 * @user_data: user data to pass to @handler.
622 * @display_filter:
623 * @sensitive_filter:
624 * @event:
626 void
627 gnm_create_popup_menu (GnmPopupMenuElement const *elements,
628 GnmPopupMenuHandler handler,
629 gpointer user_data,
630 int display_filter, int sensitive_filter,
631 GdkEvent *event)
633 char const *trans;
634 GSList *menu_stack = NULL;
635 GtkWidget *menu, *item;
637 menu = gtk_menu_new ();
638 for (; NULL != elements->name ; elements++) {
639 char const * const name = elements->name;
640 char const * const pix_name = elements->pixmap;
642 item = NULL;
644 if (elements->display_filter != 0 &&
645 !(elements->display_filter & display_filter)) {
646 if (elements->allocated_name) {
647 g_free (elements->allocated_name);
648 *(gchar **)(&elements->allocated_name) = NULL;
650 continue;
653 if (name != NULL && *name != '\0') {
654 if (elements->allocated_name)
655 trans = elements->allocated_name;
656 else
657 trans = _(name);
658 item = gtk_image_menu_item_new_with_mnemonic (trans);
659 if (elements->sensitive_filter != 0 &&
660 (elements->sensitive_filter & sensitive_filter))
661 gtk_widget_set_sensitive (GTK_WIDGET (item), FALSE);
662 if (pix_name != NULL) {
663 GtkWidget *image = gtk_image_new_from_icon_name (pix_name,
664 GTK_ICON_SIZE_MENU);
665 gtk_widget_show (image);
666 gtk_image_menu_item_set_image (
667 GTK_IMAGE_MENU_ITEM (item),
668 image);
670 if (elements->allocated_name) {
671 g_free (elements->allocated_name);
672 *(gchar **)(&elements->allocated_name) = NULL;
674 } else if (elements->index >= 0) {
675 /* separator */
676 item = gtk_menu_item_new ();
677 gtk_widget_set_sensitive (item, FALSE);
680 if (elements->index > 0) {
681 g_signal_connect (G_OBJECT (item),
682 "activate",
683 G_CALLBACK (&popup_item_activate), user_data);
684 g_object_set_data (
685 G_OBJECT (item), "descriptor", (gpointer)(elements));
686 g_object_set_data (
687 G_OBJECT (item), "handler", (gpointer)handler);
689 if (NULL != item) {
690 gtk_widget_show (item);
691 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
693 if (elements->index < 0) {
694 if (NULL != item) {
695 menu_stack = g_slist_prepend (menu_stack, menu);
696 menu = gtk_menu_new ();
697 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu);
698 } else {
699 menu = menu_stack->data;
700 menu_stack = g_slist_remove (menu_stack, menu);
704 gnumeric_popup_menu (GTK_MENU (menu), event);
707 void
708 gnm_init_help_button (GtkWidget *w, char const *lnk)
710 go_gtk_help_button_init (w, gnm_sys_data_dir (), "gnumeric", lnk);
713 char *
714 gnm_textbuffer_get_text (GtkTextBuffer *buf)
716 GtkTextIter start, end;
718 g_return_val_if_fail (buf != NULL, NULL);
720 gtk_text_buffer_get_start_iter (buf, &start);
721 gtk_text_buffer_get_end_iter (buf, &end);
722 /* We are using slice rather than text so that the tags still match */
723 return gtk_text_buffer_get_slice (buf, &start, &end, FALSE);
726 char *
727 gnm_textview_get_text (GtkTextView *text_view)
729 return gnm_textbuffer_get_text
730 (gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view)));
733 void
734 gnm_textview_set_text (GtkTextView *text_view, char const *txt)
736 gtk_text_buffer_set_text (
737 gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view)),
738 txt, -1);
741 void
742 gnm_load_pango_attributes_into_buffer (PangoAttrList *markup, GtkTextBuffer *buffer, gchar const *str)
744 gchar *str_retrieved = NULL;
746 if (str == NULL) {
747 GtkTextIter start, end;
748 gtk_text_buffer_get_start_iter (buffer, &start);
749 gtk_text_buffer_get_end_iter (buffer, &end);
750 str = str_retrieved = gtk_text_buffer_get_slice
751 (buffer, &start, &end, TRUE);
754 go_load_pango_attributes_into_buffer (markup, buffer, str);
756 g_free (str_retrieved);
759 #define gnmstoretexttagattrinpangoint(nameset, name, gnm_pango_attr_new) \
760 if (gnm_object_get_bool (tag, nameset)) { \
761 int value; \
762 g_object_get (G_OBJECT (tag), name, &value, NULL); \
763 attr = gnm_pango_attr_new (value); \
764 attr->start_index = x; \
765 attr->end_index = y; \
766 pango_attr_list_change (list, attr); \
770 static void
771 gnm_store_text_tag_attr_in_pango (PangoAttrList *list, GtkTextTag *tag, GtkTextIter *start, gchar const *text)
773 GtkTextIter end = *start;
774 gint x, y;
775 PangoAttribute * attr;
777 gtk_text_iter_forward_to_tag_toggle (&end, tag);
778 x = g_utf8_offset_to_pointer (text, gtk_text_iter_get_offset (start)) - text;
779 y = g_utf8_offset_to_pointer (text, gtk_text_iter_get_offset (&end)) - text;
781 if (gnm_object_get_bool (tag, "foreground-set")) {
782 GdkRGBA *color = NULL;
783 g_object_get (G_OBJECT (tag), "foreground-rgba", &color, NULL);
784 if (color) {
785 /* dividing 0 to 1 into 65536 equal length intervals */
786 attr = pango_attr_foreground_new
787 ((int)(CLAMP (color->red * 65536, 0., 65535.)),
788 (int)(CLAMP (color->green * 65536, 0., 65535.)),
789 (int)(CLAMP (color->blue * 65536, 0., 65535.)));
790 gdk_rgba_free (color);
791 attr->start_index = x;
792 attr->end_index = y;
793 pango_attr_list_change (list, attr);
797 gnmstoretexttagattrinpangoint ("style-set", "style", pango_attr_style_new)
798 gnmstoretexttagattrinpangoint ("weight-set", "weight", pango_attr_weight_new)
799 gnmstoretexttagattrinpangoint ("strikethrough-set", "strikethrough", pango_attr_strikethrough_new)
800 gnmstoretexttagattrinpangoint ("underline-set", "underline", pango_attr_underline_new)
801 gnmstoretexttagattrinpangoint ("rise-set", "rise", pango_attr_rise_new)
804 #undef gnmstoretexttagattrinpangoint
806 PangoAttrList *
807 gnm_get_pango_attributes_from_buffer (GtkTextBuffer *buffer)
809 PangoAttrList *list = pango_attr_list_new ();
810 GtkTextIter start;
811 gchar *text = gnm_textbuffer_get_text (buffer);
813 gtk_text_buffer_get_start_iter (buffer, &start);
815 while (!gtk_text_iter_is_end (&start)) {
816 if (gtk_text_iter_begins_tag (&start, NULL)) {
817 GSList *ptr, *l = gtk_text_iter_get_toggled_tags (&start, TRUE);
818 for (ptr = l; ptr; ptr = ptr->next)
819 gnm_store_text_tag_attr_in_pango (list, ptr->data, &start, text);
821 gtk_text_iter_forward_to_tag_toggle (&start, NULL);
824 g_free (text);
826 return list;
829 void
830 focus_on_entry (GtkEntry *entry)
832 if (entry == NULL)
833 return;
834 gtk_widget_grab_focus (GTK_WIDGET(entry));
835 gtk_editable_set_position (GTK_EDITABLE (entry), 0);
836 gtk_editable_select_region (GTK_EDITABLE (entry), 0,
837 gtk_entry_get_text_length (entry));
840 gboolean
841 entry_to_float_with_format_default (GtkEntry *entry, gnm_float *the_float,
842 gboolean update,
843 GOFormat const *format, gnm_float num)
845 char const *text = gtk_entry_get_text (entry);
846 gboolean need_default = (text == NULL);
848 if (!need_default) {
849 char *new_text = g_strdup (text);
850 need_default = (0 == strlen (g_strstrip(new_text)));
851 g_free (new_text);
854 if (need_default && !update) {
855 *the_float = num;
856 return FALSE;
859 if (need_default)
860 float_to_entry (entry, num);
862 return entry_to_float_with_format (entry, the_float, update, format);
865 gboolean
866 entry_to_float_with_format (GtkEntry *entry, gnm_float *the_float,
867 gboolean update, GOFormat const *format)
869 GnmValue *value = format_match_number (gtk_entry_get_text (entry), format, NULL);
871 *the_float = 0.0;
872 if (!value)
873 return TRUE;
875 *the_float = value_get_as_float (value);
876 if (update) {
877 char *tmp = format_value (format, value, 16, NULL);
878 gtk_entry_set_text (entry, tmp);
879 g_free (tmp);
882 value_release (value);
883 return FALSE;
887 * entry_to_int:
888 * @entry:
889 * @the_int:
890 * @update:
892 * Retrieve an int from an entry field parsing all reasonable formats
895 gboolean
896 entry_to_int (GtkEntry *entry, gint *the_int, gboolean update)
898 GnmValue *value = format_match_number (gtk_entry_get_text (entry), NULL, NULL);
899 gnm_float f;
901 *the_int = 0;
902 if (!value)
903 return TRUE;
905 f = value_get_as_float (value);
906 if (f < INT_MIN || f > INT_MAX || f != (*the_int = (int)f)) {
907 value_release (value);
908 return TRUE;
911 if (update) {
912 char *tmp = format_value (NULL, value, 16, NULL);
913 gtk_entry_set_text (entry, tmp);
914 g_free (tmp);
917 value_release (value);
918 return FALSE;
922 * float_to_entry:
923 * @entry:
924 * @the_float:
927 void
928 float_to_entry (GtkEntry *entry, gnm_float the_float)
930 GnmValue *val = value_new_float (the_float);
931 char *text = format_value (NULL, val, 16, NULL);
932 value_release(val);
933 if (text != NULL) {
934 gtk_entry_set_text (entry, text);
935 g_free (text);
940 * int_to_entry:
941 * @entry:
942 * @the_int:
946 void
947 int_to_entry (GtkEntry *entry, gint the_int)
949 GnmValue *val = value_new_int (the_int);
950 char *text = format_value (NULL, val, 16, NULL);
951 value_release(val);
952 if (text != NULL) {
953 gtk_entry_set_text (entry, text);
954 g_free (text);
958 static void
959 cb_focus_to_entry (GtkWidget *button, GtkWidget *entry)
961 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
962 gtk_widget_grab_focus (entry);
965 static gboolean
966 cb_activate_button (GtkWidget *button)
968 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
969 return FALSE;
972 void
973 gnm_link_button_and_entry (GtkWidget *button, GtkWidget *entry)
975 g_signal_connect (G_OBJECT (button),
976 "clicked", G_CALLBACK (cb_focus_to_entry),
977 entry);
978 g_signal_connect_swapped (G_OBJECT (entry),
979 "focus_in_event",
980 G_CALLBACK (cb_activate_button),
981 button);
984 /* ------------------------------------------------------------------------- */
986 void
987 gnm_widget_set_cursor (GtkWidget *w, GdkCursor *cursor)
989 gdk_window_set_cursor (gtk_widget_get_window (w), cursor);
992 void
993 gnm_widget_set_cursor_type (GtkWidget *w, GdkCursorType ct)
995 GdkDisplay *display = gtk_widget_get_display (w);
996 GdkCursor *cursor = gdk_cursor_new_for_display (display, ct);
997 gnm_widget_set_cursor (w, cursor);
998 g_object_unref (cursor);
1001 /* ------------------------------------------------------------------------- */
1004 * gnm_message_dialog_create:
1006 * A convenience fonction to build HIG compliant message dialogs.
1008 * parent : transient parent, or NULL for none.
1009 * flags
1010 * type : type of dialog
1011 * primary_message : message displayed in bold
1012 * secondary_message : message displayed below
1014 * Returns: (transfer full): a GtkDialog, without buttons.
1017 GtkWidget *
1018 gnm_message_dialog_create (GtkWindow * parent,
1019 GtkDialogFlags flags,
1020 GtkMessageType type,
1021 gchar const * primary_message,
1022 gchar const * secondary_message)
1024 GtkWidget * dialog;
1025 GtkWidget * label;
1026 GtkWidget * hbox;
1027 gchar *message;
1028 const gchar *icon_name;
1029 GtkWidget *image;
1030 const char *title;
1032 dialog = gtk_dialog_new_with_buttons ("", parent, flags, NULL, NULL);
1034 switch (type) {
1035 default:
1036 g_warning ("Unknown GtkMessageType %d", type);
1037 case GTK_MESSAGE_INFO:
1038 icon_name = "dialog-information";
1039 title = _("Information");
1040 break;
1042 case GTK_MESSAGE_QUESTION:
1043 icon_name = "dialog-question";
1044 title = _("Question");
1045 break;
1047 case GTK_MESSAGE_WARNING:
1048 icon_name = "dialog-warning";
1049 title = _("Warning");
1050 break;
1052 case GTK_MESSAGE_ERROR:
1053 icon_name = "dialog-error";
1054 title = _("Error");
1055 break;
1058 image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_DIALOG);
1059 gtk_window_set_title (GTK_WINDOW (dialog), title);
1061 if (primary_message) {
1062 if (secondary_message) {
1063 message = g_strdup_printf ("<b>%s</b>\n\n%s",
1064 primary_message,
1065 secondary_message);
1066 } else {
1067 message = g_strdup_printf ("<b>%s</b>",
1068 primary_message);
1070 } else {
1071 message = g_strdup_printf ("%s", secondary_message);
1073 label = gtk_label_new (message);
1074 g_free (message);
1076 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1077 gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, TRUE, 0);
1078 gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
1079 gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), hbox, TRUE, TRUE, 0);
1081 gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
1082 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1083 gtk_misc_set_alignment (GTK_MISC (label), 0.0 , 0.0);
1084 gtk_box_set_spacing (GTK_BOX (hbox), 12);
1085 gtk_container_set_border_width (GTK_CONTAINER (hbox), 6);
1086 gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 12);
1087 gtk_container_set_border_width (GTK_CONTAINER (dialog), 6);
1088 gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
1089 gtk_widget_show_all (GTK_WIDGET (gtk_dialog_get_content_area (GTK_DIALOG (dialog))));
1091 return dialog;
1094 typedef struct {
1095 GPtrArray *objects_signals;
1096 } GnmDialogDestroySignals;
1098 static void
1099 cb_gnm_dialog_setup_destroy_handlers (G_GNUC_UNUSED GtkWidget *widget,
1100 GnmDialogDestroySignals *dd)
1102 GPtrArray *os = dd->objects_signals;
1103 int i;
1105 for (i = 0; i < (int)os->len; i += 2) {
1106 GObject *obj = g_ptr_array_index (os, i);
1107 guint s = GPOINTER_TO_UINT (g_ptr_array_index (os, i + 1));
1108 g_signal_handler_disconnect (obj, s);
1111 g_ptr_array_free (os, TRUE);
1112 memset (dd, 0, sizeof (*dd));
1113 g_free (dd);
1116 void
1117 gnm_dialog_setup_destroy_handlers (GtkDialog *dialog,
1118 WBCGtk *wbcg,
1119 GnmDialogDestroyOptions what)
1121 GnmDialogDestroySignals *dd = g_new (GnmDialogDestroySignals, 1);
1122 Workbook *wb = wb_control_get_workbook (GNM_WBC (wbcg));
1123 Sheet *sheet = wb_control_cur_sheet (GNM_WBC (wbcg));
1124 int N = workbook_sheet_count (wb), i;
1125 GPtrArray *os = g_ptr_array_new ();
1127 dd->objects_signals = os;
1129 /* FIXME: Properly implement CURRENT_SHEET_REMOVED. */
1130 if (what & GNM_DIALOG_DESTROY_CURRENT_SHEET_REMOVED)
1131 what |= GNM_DIALOG_DESTROY_SHEET_REMOVED;
1133 if (what & GNM_DIALOG_DESTROY_SHEET_REMOVED) {
1134 guint s = g_signal_connect_swapped
1135 (G_OBJECT (wb),
1136 "sheet_deleted",
1137 G_CALLBACK (gtk_widget_destroy),
1138 dialog);
1139 g_ptr_array_add (os, wb);
1140 g_ptr_array_add (os, GUINT_TO_POINTER (s));
1143 if (what & GNM_DIALOG_DESTROY_SHEET_ADDED) {
1144 guint s = g_signal_connect_swapped
1145 (G_OBJECT (wb),
1146 "sheet_added",
1147 G_CALLBACK (gtk_widget_destroy),
1148 dialog);
1149 g_ptr_array_add (os, wb);
1150 g_ptr_array_add (os, GUINT_TO_POINTER (s));
1153 if (what & GNM_DIALOG_DESTROY_SHEETS_REORDERED) {
1154 guint s = g_signal_connect_swapped
1155 (G_OBJECT (wb),
1156 "sheet_order_changed",
1157 G_CALLBACK (gtk_widget_destroy),
1158 dialog);
1159 g_ptr_array_add (os, wb);
1160 g_ptr_array_add (os, GUINT_TO_POINTER (s));
1163 for (i = 0; i < N; i++) {
1164 Sheet *this_sheet = workbook_sheet_by_index (wb, i);
1165 gboolean current = (sheet == this_sheet);
1167 if ((what & GNM_DIALOG_DESTROY_SHEET_RENAMED) ||
1168 (current && (what & GNM_DIALOG_DESTROY_CURRENT_SHEET_RENAMED))) {
1169 guint s = g_signal_connect_swapped
1170 (G_OBJECT (this_sheet),
1171 "notify::name",
1172 G_CALLBACK (gtk_widget_destroy),
1173 dialog);
1174 g_ptr_array_add (os, this_sheet);
1175 g_ptr_array_add (os, GUINT_TO_POINTER (s));
1179 g_signal_connect (G_OBJECT (dialog),
1180 "destroy",
1181 G_CALLBACK (cb_gnm_dialog_setup_destroy_handlers),
1182 dd);
1186 void
1187 gnm_canvas_get_position (GocCanvas *canvas, int *x, int *y, gint64 px, gint64 py)
1189 GtkWidget *cw = GTK_WIDGET (canvas);
1190 GdkWindow *cbw = gtk_layout_get_bin_window (GTK_LAYOUT (cw));
1191 int wx, wy;
1193 gdk_window_get_origin (cbw, &wx, &wy);
1195 /* we don't need to multiply px and py by the canvas pixels_per_unit
1196 * field since all the callers already do that */
1197 px -= canvas->scroll_x1 * canvas->pixels_per_unit;
1198 py -= canvas->scroll_y1 * canvas->pixels_per_unit;
1199 /* let's take care of RTL sheets */
1200 if (canvas->direction == GOC_DIRECTION_RTL)
1201 px = goc_canvas_get_width (canvas) - px;
1203 *x = px + wx;
1204 *y = py + wy;
1208 * Get the gdk position for canvas coordinates (x,y). This is suitable
1209 * for tooltip windows.
1211 * It is possible that this does not work right for very large coordinates
1212 * prior to gtk+ 2.18. See the code and comments in gnm_canvas_get_position.
1214 void
1215 gnm_canvas_get_screen_position (GocCanvas *canvas,
1216 double x, double y,
1217 int *ix, int *iy)
1219 GdkWindow *cbw = gtk_layout_get_bin_window (GTK_LAYOUT (canvas));
1220 int wx, wy;
1222 gdk_window_get_origin (cbw, &wx, &wy);
1223 goc_canvas_c2w (canvas, x, y, ix, iy);
1224 (*ix) += wx;
1225 (*iy) += wy;
1229 gboolean
1230 gnm_check_for_plugins_missing (char const **ids, GtkWindow *parent)
1232 for (; *ids != NULL; ids++) {
1233 GOPlugin *pi = go_plugins_get_plugin_by_id (*ids);
1234 if (pi == NULL) {
1235 GOErrorInfo *error;
1236 error = go_error_info_new_printf
1237 (_("The plugin with id %s is required "
1238 "but cannot be found."), *ids);
1239 gnm_go_error_info_dialog_show (parent,
1240 error);
1241 return TRUE;
1242 } else if (!go_plugin_is_active (pi)) {
1243 GOErrorInfo *error;
1244 error = go_error_info_new_printf
1245 (_("The %s plugin is required "
1246 "but is not loaded."),
1247 go_plugin_get_name (pi));
1248 gnm_go_error_info_dialog_show (parent,
1249 error);
1250 return TRUE;
1253 return FALSE;
1257 void
1258 gnm_cell_renderer_text_copy_background_to_cairo (GtkCellRendererText *crt,
1259 cairo_t *cr)
1261 GdkRGBA *c = NULL;
1262 g_object_get (crt, "background-rgba", &c, NULL);
1263 gdk_cairo_set_source_rgba (cr, c);
1264 gdk_rgba_free (c);
1268 gnm_widget_measure_string (GtkWidget *w, const char *s)
1270 GtkStyleContext *ctxt;
1271 int len;
1272 PangoFontDescription *desc;
1273 GtkStateFlags state = GTK_STATE_FLAG_NORMAL;
1275 ctxt = gtk_widget_get_style_context (w);
1277 // As-of gtk+ 3.20 we have to set the context state to the state
1278 // we are querying for. This ought to work before gtk+ 3.20 too.
1279 gtk_style_context_save (ctxt);
1280 gtk_style_context_set_state (ctxt, state);
1281 gtk_style_context_get (ctxt, state, "font", &desc, NULL);
1282 gtk_style_context_restore (ctxt);
1284 len = go_pango_measure_string
1285 (gtk_widget_get_pango_context (w), desc, s);
1287 pango_font_description_free (desc);
1289 return len;
1292 static const char *
1293 gnm_ag_translate (const char *s, const char *ctxt)
1295 return ctxt
1296 ? g_dpgettext2 (NULL, ctxt, s)
1297 : _(s);
1300 void
1301 gnm_action_group_add_actions (GtkActionGroup *group,
1302 GnmActionEntry const *actions, size_t n,
1303 gpointer user)
1305 unsigned i;
1307 for (i = 0; i < n; i++) {
1308 GnmActionEntry const *entry = actions + i;
1309 const char *name = entry->name;
1310 const char *label =
1311 gnm_ag_translate (entry->label, entry->label_context);
1312 const char *tip =
1313 gnm_ag_translate (entry->tooltip, NULL);
1314 GtkAction *a;
1316 if (entry->toggle) {
1317 GtkToggleAction *ta =
1318 gtk_toggle_action_new (name, label, tip, NULL);
1319 gtk_toggle_action_set_active (ta, entry->is_active);
1320 a = GTK_ACTION (ta);
1321 } else {
1322 a = gtk_action_new (name, label, tip, NULL);
1325 g_object_set (a, "icon-name", entry->icon, NULL);
1327 if (entry->callback) {
1328 GClosure *closure =
1329 g_cclosure_new (entry->callback, user, NULL);
1330 g_signal_connect_closure (a, "activate", closure,
1331 FALSE);
1334 gtk_action_group_add_action_with_accel (group,
1336 entry->accelerator);
1337 g_object_unref (a);
1341 void
1342 gnm_action_group_add_action (GtkActionGroup *group, GtkAction *act)
1345 * See the docs for gtk_action_group_add_action as to why we don't
1346 * call just that.
1348 gtk_action_group_add_action_with_accel (group, act, NULL);
1351 void
1352 gnm_style_context_get_color (GtkStyleContext *context,
1353 GtkStateFlags state,
1354 GdkRGBA *color)
1356 // As-of gtk+ 3.20 we have to set the context state to the state
1357 // we are querying for. This ought to work before gtk+ 3.20 too.
1358 gtk_style_context_save (context);
1359 gtk_style_context_set_state (context, state);
1360 gtk_style_context_get_color (context,
1361 gtk_style_context_get_state (context),
1362 color);
1363 gtk_style_context_restore (context);
1366 void
1367 gnm_get_link_color (GtkWidget *widget, GdkRGBA *res)
1369 GtkStyleContext *ctxt = gtk_widget_get_style_context (widget);
1370 gnm_style_context_get_color (ctxt, GTK_STATE_FLAG_LINK, res);
1373 // ----------------------------------------------------------------------------