4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2002, the ROX-Filer team.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 * Place, Suite 330, Boston, MA 02111-1307 USA
22 /* gui_support.c - general (GUI) support routines */
29 #include <sys/param.h>
35 #include <X11/Xatom.h>
38 #include <gdk/gdkkeysyms.h>
43 #include "gui_support.h"
49 /* XXX: RandR will break this! */
50 gint screen_width
, screen_height
;
52 static GdkAtom xa_cardinal
;
54 static GtkWidget
*current_dialog
= NULL
;
56 static GtkWidget
*tip_widget
= NULL
;
57 static time_t tip_time
= 0; /* Time tip widget last closed */
58 static gint tip_timeout
= 0; /* When primed */
60 void gui_support_init()
62 xa_cardinal
= gdk_atom_intern("CARDINAL", FALSE
);
64 /* This call starts returning strange values after a while, so get
65 * the result here during init.
67 gdk_drawable_get_size(gdk_get_default_root_window(),
68 &screen_width
, &screen_height
);
71 /* Open a modal dialog box showing a message.
72 * The user can choose from a selection of buttons at the bottom.
73 * Returns -1 if the window is destroyed, or the number of the button
74 * if one is clicked (starting from zero).
76 * If a dialog is already open, returns -1 without waiting AND
77 * brings the current dialog to the front.
79 int get_choice(const char *title
,
81 int number_of_buttons
, ...)
84 GtkWidget
*button
= NULL
;
90 gtk_widget_hide(current_dialog
);
91 gtk_widget_show(current_dialog
);
95 current_dialog
= dialog
= gtk_message_dialog_new(NULL
,
100 gtk_window_set_position(GTK_WINDOW(dialog
), GTK_WIN_POS_CENTER
);
102 va_start(ap
, number_of_buttons
);
104 for (i
= 0; i
< number_of_buttons
; i
++)
105 button
= gtk_dialog_add_button(GTK_DIALOG(current_dialog
),
106 va_arg(ap
, char *), i
);
108 gtk_window_set_title(GTK_WINDOW(dialog
), title
);
109 gtk_window_set_position(GTK_WINDOW(dialog
), GTK_WIN_POS_CENTER
);
111 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), i
- 1);
115 retval
= gtk_dialog_run(GTK_DIALOG(dialog
));
116 if (retval
== GTK_RESPONSE_NONE
)
118 gtk_widget_destroy(dialog
);
120 current_dialog
= NULL
;
125 void info_message(const char *message
, ...)
131 g_return_if_fail(message
!= NULL
);
133 va_start(args
, message
);
134 s
= g_strdup_vprintf(message
, args
);
137 dialog
= gtk_message_dialog_new(NULL
,
142 gtk_window_set_position(GTK_WINDOW(dialog
), GTK_WIN_POS_CENTER
);
143 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), GTK_RESPONSE_OK
);
144 gtk_dialog_run(GTK_DIALOG(dialog
));
145 gtk_widget_destroy(dialog
);
150 /* Display a message in a window with "ROX-Filer" as title */
151 void report_error(const char *message
, ...)
157 g_return_if_fail(message
!= NULL
);
159 va_start(args
, message
);
160 s
= g_strdup_vprintf(message
, args
);
163 dialog
= gtk_message_dialog_new(NULL
,
168 gtk_window_set_position(GTK_WINDOW(dialog
), GTK_WIN_POS_CENTER
);
169 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), GTK_RESPONSE_OK
);
170 gtk_dialog_run(GTK_DIALOG(dialog
));
171 gtk_widget_destroy(dialog
);
176 void set_cardinal_property(GdkWindow
*window
, GdkAtom prop
, guint32 value
)
178 gdk_property_change(window
, prop
, xa_cardinal
, 32,
179 GDK_PROP_MODE_REPLACE
, (gchar
*) &value
, 1);
182 /* NB: Also used for pinned icons.
183 * TODO: Set the level here too.
185 void make_panel_window(GtkWidget
*widget
)
187 static gboolean need_init
= TRUE
;
188 static GdkAtom xa_state
, xa_atom
, xa_net_state
, xa_hints
, xa_win_hints
;
189 static GdkAtom state_list
[3];
190 GdkWindow
*window
= widget
->window
;
191 gint32 wm_hints_values
[] = {1, False
, 0, 0, 0, 0, 0, 0};
192 GdkAtom wm_protocols
[2];
194 g_return_if_fail(window
!= NULL
);
196 if (o_override_redirect
.int_value
)
198 gdk_window_set_override_redirect(window
, TRUE
);
204 xa_win_hints
= gdk_atom_intern("_WIN_HINTS", FALSE
);
205 xa_state
= gdk_atom_intern("_WIN_STATE", FALSE
);
206 xa_atom
= gdk_atom_intern("ATOM", FALSE
);
207 xa_net_state
= gdk_atom_intern("_NET_WM_STATE", FALSE
);
208 xa_hints
= gdk_atom_intern("WM_HINTS", FALSE
);
210 /* Note: Starting with Gtk+-1.3.12, Gtk+ converts GdkAtoms
211 * to X atoms automatically when the type is ATOM.
213 state_list
[0] = gdk_atom_intern("_NET_WM_STATE_STICKY", FALSE
);
214 state_list
[1] = gdk_atom_intern("_NET_WM_STATE_SKIP_PAGER",
216 state_list
[2] = gdk_atom_intern("_NET_WM_STATE_SKIP_TASKBAR",
222 gdk_window_set_decorations(window
, 0);
223 gdk_window_set_functions(window
, 0);
225 /* Note: DON'T do gtk_window_stick(). Setting the state via
226 * gdk will override our other atoms (pager/taskbar).
229 /* Don't hide panel/pinboard windows initially (WIN_STATE_HIDDEN).
230 * Needed for IceWM - Christopher Arndt <chris.arndt@web.de>
232 set_cardinal_property(window
, xa_state
,
234 WIN_STATE_FIXED_POSITION
| WIN_STATE_ARRANGE_IGNORE
);
236 set_cardinal_property(window
, xa_win_hints
,
237 WIN_HINTS_SKIP_FOCUS
| WIN_HINTS_SKIP_WINLIST
|
238 WIN_HINTS_SKIP_TASKBAR
);
240 gdk_property_change(window
, xa_net_state
, xa_atom
, 32,
241 GDK_PROP_MODE_APPEND
, (guchar
*) state_list
, 3);
243 g_return_if_fail(window
!= NULL
);
245 gdk_property_change(window
, xa_hints
, xa_hints
, 32,
246 GDK_PROP_MODE_REPLACE
, (guchar
*) wm_hints_values
,
247 sizeof(wm_hints_values
) / sizeof(gint32
));
249 wm_protocols
[0] = gdk_atom_intern("WM_DELETE_WINDOW", FALSE
);
250 wm_protocols
[1] = gdk_atom_intern("_NET_WM_PING", FALSE
);
251 gdk_property_change(window
,
252 gdk_atom_intern("WM_PROTOCOLS", FALSE
), xa_atom
, 32,
253 GDK_PROP_MODE_REPLACE
, (guchar
*) wm_protocols
,
254 sizeof(wm_protocols
) / sizeof(GdkAtom
));
257 static gboolean
error_idle_cb(gpointer data
)
259 char **error
= (char **) data
;
261 report_error("%s", *error
);
268 /* Display an error with "ROX-Filer" as title next time we are idle.
269 * If multiple errors are reported this way before the window is opened,
270 * all are displayed in a single window.
271 * If an error is reported while the error window is open, it is discarded.
273 void delayed_error(const char *error
, ...)
275 static char *delayed_error_data
= NULL
;
279 g_return_if_fail(error
!= NULL
);
281 old
= delayed_error_data
;
283 va_start(args
, error
);
284 new = g_strdup_vprintf(error
, args
);
289 delayed_error_data
= g_strconcat(old
,
297 delayed_error_data
= new;
298 gtk_idle_add(error_idle_cb
, &delayed_error_data
);
304 /* Load the file into memory. Return TRUE on success.
305 * Block is zero terminated (but this is not included in the length).
307 gboolean
load_file(const char *pathname
, char **data_out
, long *length_out
)
310 GError
*error
= NULL
;
312 if (!g_file_get_contents(pathname
, data_out
, &len
, &error
))
314 delayed_error("%s", error
->message
);
324 GtkWidget
*new_help_button(HelpFunc show_help
, gpointer data
)
328 b
= gtk_button_new();
329 gtk_button_set_relief(GTK_BUTTON(b
), GTK_RELIEF_NONE
);
330 icon
= gtk_image_new_from_stock(GTK_STOCK_DIALOG_INFO
,
331 GTK_ICON_SIZE_SMALL_TOOLBAR
);
332 gtk_container_add(GTK_CONTAINER(b
), icon
);
333 g_signal_connect_swapped(b
, "clicked", G_CALLBACK(show_help
), data
);
335 GTK_WIDGET_UNSET_FLAGS(b
, GTK_CAN_FOCUS
);
340 /* Read file into memory. Call parse_line(guchar *line) for each line
341 * in the file. Callback returns NULL on success, or an error message
342 * if something went wrong. Only the first error is displayed to the user.
344 void parse_file(const char *path
, ParseFunc
*parse_line
)
348 gboolean seen_error
= FALSE
;
350 if (load_file(path
, &data
, &length
))
357 if (strncmp(data
, "<?xml ", 6) == 0)
359 delayed_error(_("Attempt to read an XML file as "
360 "a text file. File '%s' may be "
361 "corrupted."), path
);
365 while (line
&& *line
)
367 eol
= strchr(line
, '\n');
371 error
= parse_line(line
);
373 if (error
&& !seen_error
)
376 _("Error in '%s' file at line %d: "
378 "This may be due to upgrading from a previous version of "
379 "ROX-Filer. Open the Options window and click on Save.\n"
380 "Further errors will be ignored."),
396 /* Sets up a proxy window for DnD on the specified X window.
397 * Courtesy of Owen Taylor (taken from gmc).
399 gboolean
setup_xdnd_proxy(guint32 xid
, GdkWindow
*proxy_window
)
401 GdkAtom xdnd_proxy_atom
;
405 unsigned long nitems
, after
;
409 XGrabServer(GDK_DISPLAY());
411 xdnd_proxy_atom
= gdk_atom_intern("XdndProxy", FALSE
);
412 proxy_xid
= GDK_WINDOW_XWINDOW(proxy_window
);
416 gdk_error_trap_push();
418 /* Check if somebody else already owns drops on the root window */
420 XGetWindowProperty(GDK_DISPLAY(), xid
,
421 gdk_x11_atom_to_xatom(xdnd_proxy_atom
), 0,
422 1, False
, AnyPropertyType
,
423 &type
, &format
, &nitems
, &after
,
424 (guchar
**) &proxy_data
);
428 if (format
== 32 && nitems
== 1)
434 /* The property was set, now check if the window it points to exists
435 * and has a XdndProxy property pointing to itself.
441 XGetWindowProperty(GDK_DISPLAY(), proxy
,
442 gdk_x11_atom_to_xatom(xdnd_proxy_atom
),
443 0, 1, False
, AnyPropertyType
,
444 &type
, &format
, &nitems
, &after
,
445 (guchar
**) &proxy_data
);
447 gdk_error_code
= gdk_error_trap_pop();
448 gdk_error_trap_push();
450 if (!gdk_error_code
&& type
!= None
)
452 if (format
== 32 && nitems
== 1)
453 if (*proxy_data
!= proxy
)
459 proxy
= gdk_x11_atom_to_xatom(GDK_NONE
);
464 /* OK, we can set the property to point to us */
465 /* TODO: Use gdk call? */
467 XChangeProperty(GDK_DISPLAY(), xid
,
468 gdk_x11_atom_to_xatom(xdnd_proxy_atom
),
469 gdk_x11_atom_to_xatom(gdk_atom_intern("WINDOW",
472 (guchar
*) &proxy_xid
, 1);
475 gdk_error_trap_pop();
477 XUngrabServer(GDK_DISPLAY());
482 /* Mark our window as a valid proxy window with a XdndProxy
483 * property pointing recursively;
485 XChangeProperty(GDK_DISPLAY(), proxy_xid
,
486 gdk_x11_atom_to_xatom(xdnd_proxy_atom
),
487 gdk_x11_atom_to_xatom(gdk_atom_intern("WINDOW",
490 (guchar
*) &proxy_xid
, 1);
496 /* xid is the window (usually the root) which points to the proxy */
497 void release_xdnd_proxy(guint32 xid
)
499 GdkAtom xdnd_proxy_atom
;
501 xdnd_proxy_atom
= gdk_atom_intern("XdndProxy", FALSE
);
503 XDeleteProperty(GDK_DISPLAY(), xid
,
504 gdk_x11_atom_to_xatom(xdnd_proxy_atom
));
507 /* Looks for the proxy window to get root window clicks from the window
508 * manager. Taken from gmc. NULL if there is no proxy window.
510 GdkWindow
*find_click_proxy_window(void)
512 GdkAtom click_proxy_atom
;
515 unsigned long nitems
, after
;
518 GdkWindow
*proxy_gdk_window
;
520 XGrabServer(GDK_DISPLAY());
522 click_proxy_atom
= gdk_atom_intern("_WIN_DESKTOP_BUTTON_PROXY", FALSE
);
526 gdk_error_trap_push();
528 /* Check if the proxy window exists */
530 XGetWindowProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
531 gdk_x11_atom_to_xatom(click_proxy_atom
), 0,
532 1, False
, AnyPropertyType
,
533 &type
, &format
, &nitems
, &after
,
534 (guchar
**) &proxy_data
);
538 if (format
== 32 && nitems
== 1)
544 /* If the property was set, check if the window it points to exists
545 * and has a _WIN_DESKTOP_BUTTON_PROXY property pointing to itself.
551 XGetWindowProperty(GDK_DISPLAY(), proxy
,
552 gdk_x11_atom_to_xatom(click_proxy_atom
), 0,
553 1, False
, AnyPropertyType
,
554 &type
, &format
, &nitems
, &after
,
555 (guchar
**) &proxy_data
);
557 gdk_error_code
= gdk_error_trap_pop();
558 gdk_error_trap_push();
560 if (!gdk_error_code
&& type
!= None
)
562 if (format
== 32 && nitems
== 1)
563 if (*proxy_data
!= proxy
)
564 proxy
= gdk_x11_atom_to_xatom(GDK_NONE
);
569 proxy
= gdk_x11_atom_to_xatom(GDK_NONE
);
572 gdk_error_trap_pop();
574 XUngrabServer(GDK_DISPLAY());
578 proxy_gdk_window
= gdk_window_foreign_new(proxy
);
580 proxy_gdk_window
= NULL
;
582 return proxy_gdk_window
;
585 /* Returns the position of the pointer.
586 * TRUE if any modifier keys or mouse buttons are pressed.
588 gboolean
get_pointer_xy(int *x
, int *y
)
592 gdk_window_get_pointer(NULL
, x
, y
, &mask
);
597 #define DECOR_BORDER 32
599 /* Centre the window at these coords */
600 void centre_window(GdkWindow
*window
, int x
, int y
)
604 g_return_if_fail(window
!= NULL
);
606 gdk_drawable_get_size(window
, &w
, &h
);
611 gdk_window_move(window
,
612 CLAMP(x
, DECOR_BORDER
, screen_width
- w
- DECOR_BORDER
),
613 CLAMP(y
, DECOR_BORDER
, screen_height
- h
- DECOR_BORDER
));
616 static GtkWidget
*current_wink_widget
= NULL
;
617 static gint wink_timeout
= -1; /* Called when it's time to stop */
618 static gulong wink_destroy
; /* Called if the widget dies first */
620 static gboolean
end_wink(gpointer data
)
622 gtk_drag_unhighlight(current_wink_widget
);
624 g_signal_handler_disconnect(current_wink_widget
, wink_destroy
);
626 current_wink_widget
= NULL
;
631 static void cancel_wink(void)
633 gtk_timeout_remove(wink_timeout
);
637 static void wink_widget_died(gpointer data
)
639 current_wink_widget
= NULL
;
640 gtk_timeout_remove(wink_timeout
);
643 /* Draw a black box around this widget, briefly.
644 * Note: uses the drag highlighting code for now.
646 void wink_widget(GtkWidget
*widget
)
648 g_return_if_fail(widget
!= NULL
);
650 if (current_wink_widget
)
653 current_wink_widget
= widget
;
654 gtk_drag_highlight(current_wink_widget
);
656 wink_timeout
= gtk_timeout_add(300, (GtkFunction
) end_wink
, NULL
);
658 wink_destroy
= g_signal_connect_swapped(widget
, "destroy",
659 G_CALLBACK(wink_widget_died
), NULL
);
662 static gboolean
idle_destroy_cb(GtkWidget
*widget
)
664 gtk_widget_unref(widget
);
665 gtk_widget_destroy(widget
);
669 /* Destroy the widget in an idle callback */
670 void destroy_on_idle(GtkWidget
*widget
)
672 gtk_widget_ref(widget
);
673 gtk_idle_add((GtkFunction
) idle_destroy_cb
, widget
);
676 /* Spawn a child process (as spawn_full), and report errors.
679 gboolean
rox_spawn(const gchar
*dir
, const gchar
**argv
)
681 GError
*error
= NULL
;
683 if (!g_spawn_async_with_pipes(dir
, (gchar
**) argv
, NULL
,
684 G_SPAWN_DO_NOT_REAP_CHILD
|
686 NULL
, NULL
, /* Child setup fn */
687 NULL
, /* Child PID */
688 NULL
, NULL
, NULL
, /* Standard pipes */
691 delayed_error("%s", error
? error
->message
: "(null)");
700 GtkWidget
*button_new_mixed(const char *stock
, const char *message
)
702 GtkWidget
*button
, *align
, *image
, *hbox
, *label
;
704 button
= gtk_button_new();
705 label
= gtk_label_new_with_mnemonic(message
);
706 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), button
);
708 image
= gtk_image_new_from_stock(stock
, GTK_ICON_SIZE_BUTTON
);
709 hbox
= gtk_hbox_new(FALSE
, 2);
711 align
= gtk_alignment_new(0.5, 0.5, 0.0, 0.0);
713 gtk_box_pack_start(GTK_BOX(hbox
), image
, FALSE
, FALSE
, 0);
714 gtk_box_pack_end(GTK_BOX(hbox
), label
, FALSE
, FALSE
, 0);
716 gtk_container_add(GTK_CONTAINER(button
), align
);
717 gtk_container_add(GTK_CONTAINER(align
), hbox
);
718 gtk_widget_show_all(align
);
723 /* Highlight entry in red if 'error' is TRUE */
724 void entry_set_error(GtkWidget
*entry
, gboolean error
)
726 GdkColor red
= {0, 0xffff, 0, 0};
727 static gboolean need_init
= TRUE
;
728 static GdkColor normal
;
732 normal
= entry
->style
->text
[GTK_STATE_NORMAL
];
736 gtk_widget_modify_text(entry
, GTK_STATE_NORMAL
, error
? &red
: &normal
);
739 /* Change stacking position of higher to be just above lower */
740 void window_put_just_above(GdkWindow
*higher
, GdkWindow
*lower
)
742 if (o_override_redirect
.int_value
&& lower
)
744 XWindowChanges restack
;
746 gdk_error_trap_push();
748 restack
.stack_mode
= Above
;
750 restack
.sibling
= GDK_WINDOW_XWINDOW(lower
);
752 XConfigureWindow(gdk_display
, GDK_WINDOW_XWINDOW(higher
),
753 CWSibling
| CWStackMode
, &restack
);
756 if (gdk_error_trap_pop())
757 g_warning("window_put_just_above()\n");
760 gdk_window_lower(higher
); /* To bottom of stack */
763 /* Copied from Gtk */
764 static GtkFixedChild
* fixed_get_child(GtkFixed
*fixed
, GtkWidget
*widget
)
768 children
= fixed
->children
;
771 GtkFixedChild
*child
;
773 child
= children
->data
;
774 children
= children
->next
;
776 if (child
->widget
== widget
)
783 /* Like gtk_fixed_move(), except not insanely slow */
784 void fixed_move_fast(GtkFixed
*fixed
, GtkWidget
*widget
, int x
, int y
)
786 GtkFixedChild
*child
;
788 child
= fixed_get_child(fixed
, widget
);
792 gtk_widget_freeze_child_notify(widget
);
795 gtk_widget_child_notify(widget
, "x");
798 gtk_widget_child_notify(widget
, "y");
800 gtk_widget_thaw_child_notify(widget
);
802 if (GTK_WIDGET_VISIBLE(widget
) && GTK_WIDGET_VISIBLE(fixed
))
804 int border_width
= GTK_CONTAINER(fixed
)->border_width
;
805 GtkAllocation child_allocation
;
806 GtkRequisition child_requisition
;
808 gtk_widget_get_child_requisition(child
->widget
,
810 child_allocation
.x
= child
->x
+ border_width
;
811 child_allocation
.y
= child
->y
+ border_width
;
813 child_allocation
.x
+= GTK_WIDGET(fixed
)->allocation
.x
;
814 child_allocation
.y
+= GTK_WIDGET(fixed
)->allocation
.y
;
816 child_allocation
.width
= child_requisition
.width
;
817 child_allocation
.height
= child_requisition
.height
;
818 gtk_widget_size_allocate(child
->widget
, &child_allocation
);
822 /* Draw the black border */
823 static gint
tooltip_draw(GtkWidget
*w
)
825 gdk_draw_rectangle(w
->window
, w
->style
->fg_gc
[w
->state
], FALSE
, 0, 0,
826 w
->allocation
.width
- 1, w
->allocation
.height
- 1);
831 /* When the tips window closed, record the time. If we try to open another
832 * tip soon, it will appear more quickly.
834 static void tooltip_destroyed(gpointer data
)
839 /* Display a tooltip-like widget near the pointer with 'text'. If 'text' is
840 * NULL, close any current tooltip.
842 void tooltip_show(guchar
*text
)
850 gtk_timeout_remove(tip_timeout
);
856 gtk_widget_destroy(tip_widget
);
864 tip_widget
= gtk_window_new(GTK_WINDOW_POPUP
);
865 gtk_widget_set_app_paintable(tip_widget
, TRUE
);
866 gtk_widget_set_name(tip_widget
, "gtk-tooltips");
868 g_signal_connect_swapped(tip_widget
, "expose_event",
869 G_CALLBACK(tooltip_draw
), tip_widget
);
871 label
= gtk_label_new(text
);
872 gtk_misc_set_padding(GTK_MISC(label
), 4, 2);
873 gtk_container_add(GTK_CONTAINER(tip_widget
), label
);
874 gtk_widget_show(label
);
875 gtk_widget_realize(tip_widget
);
877 w
= tip_widget
->allocation
.width
;
878 h
= tip_widget
->allocation
.height
;
879 gdk_window_get_pointer(NULL
, &x
, &py
, NULL
);
882 y
= py
+ 12; /* I don't know the pointer height so I use a constant */
884 /* Now check for screen boundaries */
885 x
= CLAMP(x
, 0, screen_width
- w
);
886 y
= CLAMP(y
, 0, screen_height
- h
);
888 /* And again test if pointer is over the tooltip window */
889 if (py
>= y
&& py
<= y
+ h
)
891 gtk_window_move(GTK_WINDOW(tip_widget
), x
, y
);
892 gtk_widget_show(tip_widget
);
894 g_signal_connect_swapped(tip_widget
, "destroy",
895 G_CALLBACK(tooltip_destroyed
), NULL
);
899 /* Call callback(user_data) after a while, unless cancelled.
900 * Object is refd now and unref when cancelled / after callback called.
902 void tooltip_prime(GtkFunction callback
, GObject
*object
)
907 g_return_if_fail(tip_timeout
== 0);
910 delay
= now
- tip_time
> 2 ? 1000 : 200;
912 g_object_ref(object
);
913 tip_timeout
= gtk_timeout_add_full(delay
,
914 (GtkFunction
) callback
,
920 /* Like gtk_widget_modify_font, but copes with font_desc == NULL */
921 void widget_modify_font(GtkWidget
*widget
, PangoFontDescription
*font_desc
)
923 GtkRcStyle
*rc_style
;
925 g_return_if_fail(GTK_IS_WIDGET(widget
));
927 rc_style
= gtk_widget_get_modifier_style(widget
);
929 if (rc_style
->font_desc
)
930 pango_font_description_free(rc_style
->font_desc
);
932 rc_style
->font_desc
= font_desc
933 ? pango_font_description_copy(font_desc
)
936 gtk_widget_modify_style(widget
, rc_style
);