r1992: Code tidying (Bernard Jungen).
[rox-filer.git] / ROX-Filer / src / gui_support.c
blob2038a5eae5d8170c97b7ffee8b9299fba63c61dd
1 /*
2 * $Id$
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)
10 * any later version.
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
15 * more details.
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 */
24 #include "config.h"
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <sys/param.h>
30 #include <stdarg.h>
31 #include <errno.h>
32 #include <time.h>
34 #include <X11/Xlib.h>
35 #include <X11/Xatom.h>
36 #include <gdk/gdkx.h>
37 #include <gdk/gdk.h>
38 #include <gdk/gdkkeysyms.h>
40 #include "global.h"
42 #include "main.h"
43 #include "gui_support.h"
44 #include "support.h"
45 #include "pixmaps.h"
46 #include "choices.h"
47 #include "options.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,
80 const char *message,
81 int number_of_buttons, ...)
83 GtkWidget *dialog;
84 GtkWidget *button = NULL;
85 int i, retval;
86 va_list ap;
88 if (current_dialog)
90 gtk_widget_hide(current_dialog);
91 gtk_widget_show(current_dialog);
92 return -1;
95 current_dialog = dialog = gtk_message_dialog_new(NULL,
96 GTK_DIALOG_MODAL,
97 GTK_MESSAGE_QUESTION,
98 GTK_BUTTONS_NONE,
99 "%s", message);
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);
113 va_end(ap);
115 retval = gtk_dialog_run(GTK_DIALOG(dialog));
116 if (retval == GTK_RESPONSE_NONE)
117 retval = -1;
118 gtk_widget_destroy(dialog);
120 current_dialog = NULL;
122 return retval;
125 void info_message(const char *message, ...)
127 GtkWidget *dialog;
128 va_list args;
129 gchar *s;
131 g_return_if_fail(message != NULL);
133 va_start(args, message);
134 s = g_strdup_vprintf(message, args);
135 va_end(args);
137 dialog = gtk_message_dialog_new(NULL,
138 GTK_DIALOG_MODAL,
139 GTK_MESSAGE_INFO,
140 GTK_BUTTONS_OK,
141 "%s", s);
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);
147 g_free(s);
150 /* Display a message in a window with "ROX-Filer" as title */
151 void report_error(const char *message, ...)
153 GtkWidget *dialog;
154 va_list args;
155 gchar *s;
157 g_return_if_fail(message != NULL);
159 va_start(args, message);
160 s = g_strdup_vprintf(message, args);
161 va_end(args);
163 dialog = gtk_message_dialog_new(NULL,
164 GTK_DIALOG_MODAL,
165 GTK_MESSAGE_ERROR,
166 GTK_BUTTONS_OK,
167 "%s", s);
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);
173 g_free(s);
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);
199 return;
202 if (need_init)
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",
215 FALSE);
216 state_list[2] = gdk_atom_intern("_NET_WM_STATE_SKIP_TASKBAR",
217 FALSE);
219 need_init = FALSE;
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,
233 WIN_STATE_STICKY |
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);
262 null_g_free(error);
264 one_less_window();
265 return FALSE;
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;
276 char *old, *new;
277 va_list args;
279 g_return_if_fail(error != NULL);
281 old = delayed_error_data;
283 va_start(args, error);
284 new = g_strdup_vprintf(error, args);
285 va_end(args);
287 if (old)
289 delayed_error_data = g_strconcat(old,
290 _("\n---\n"),
291 new, NULL);
292 g_free(old);
293 g_free(new);
295 else
297 delayed_error_data = new;
298 gtk_idle_add(error_idle_cb, &delayed_error_data);
300 number_of_windows++;
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)
309 gsize len;
310 GError *error = NULL;
312 if (!g_file_get_contents(pathname, data_out, &len, &error))
314 delayed_error("%s", error->message);
315 g_error_free(error);
316 return FALSE;
319 if (length_out)
320 *length_out = len;
321 return TRUE;
324 GtkWidget *new_help_button(HelpFunc show_help, gpointer data)
326 GtkWidget *b, *icon;
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);
337 return b;
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)
346 char *data;
347 long length;
348 gboolean seen_error = FALSE;
350 if (load_file(path, &data, &length))
352 char *eol;
353 const char *error;
354 char *line = data;
355 int line_number = 1;
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);
362 return;
365 while (line && *line)
367 eol = strchr(line, '\n');
368 if (eol)
369 *eol = '\0';
371 error = parse_line(line);
373 if (error && !seen_error)
375 delayed_error(
376 _("Error in '%s' file at line %d: "
377 "\n\"%s\"\n"
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."),
381 path,
382 line_number,
383 error);
384 seen_error = TRUE;
387 if (!eol)
388 break;
389 line = eol + 1;
390 line_number++;
392 g_free(data);
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;
402 Window proxy_xid;
403 Atom type;
404 int format;
405 unsigned long nitems, after;
406 Window *proxy_data;
407 Window proxy;
409 XGrabServer(GDK_DISPLAY());
411 xdnd_proxy_atom = gdk_atom_intern("XdndProxy", FALSE);
412 proxy_xid = GDK_WINDOW_XWINDOW(proxy_window);
413 type = None;
414 proxy = None;
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);
426 if (type != None)
428 if (format == 32 && nitems == 1)
429 proxy = *proxy_data;
431 XFree(proxy_data);
434 /* The property was set, now check if the window it points to exists
435 * and has a XdndProxy property pointing to itself.
437 if (proxy)
439 gint gdk_error_code;
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)
454 proxy = None;
456 XFree(proxy_data);
458 else
459 proxy = gdk_x11_atom_to_xatom(GDK_NONE);
462 if (!proxy)
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",
470 FALSE)),
471 32, PropModeReplace,
472 (guchar *) &proxy_xid, 1);
475 gdk_error_trap_pop();
477 XUngrabServer(GDK_DISPLAY());
478 gdk_flush();
480 if (!proxy)
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",
488 FALSE)),
489 32, PropModeReplace,
490 (guchar *) &proxy_xid, 1);
493 return !proxy;
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;
513 Atom type;
514 int format;
515 unsigned long nitems, after;
516 Window *proxy_data;
517 Window proxy;
518 GdkWindow *proxy_gdk_window;
520 XGrabServer(GDK_DISPLAY());
522 click_proxy_atom = gdk_atom_intern("_WIN_DESKTOP_BUTTON_PROXY", FALSE);
523 type = None;
524 proxy = None;
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);
536 if (type != None)
538 if (format == 32 && nitems == 1)
539 proxy = *proxy_data;
541 XFree(proxy_data);
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.
548 if (proxy)
550 gint gdk_error_code;
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);
566 XFree(proxy_data);
568 else
569 proxy = gdk_x11_atom_to_xatom(GDK_NONE);
572 gdk_error_trap_pop();
574 XUngrabServer(GDK_DISPLAY());
575 gdk_flush();
577 if (proxy)
578 proxy_gdk_window = gdk_window_foreign_new(proxy);
579 else
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)
590 unsigned int mask;
592 gdk_window_get_pointer(NULL, x, y, &mask);
594 return mask != 0;
597 #define DECOR_BORDER 32
599 /* Centre the window at these coords */
600 void centre_window(GdkWindow *window, int x, int y)
602 int w, h;
604 g_return_if_fail(window != NULL);
606 gdk_drawable_get_size(window, &w, &h);
608 x -= w / 2;
609 y -= h / 2;
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;
628 return FALSE;
631 static void cancel_wink(void)
633 gtk_timeout_remove(wink_timeout);
634 end_wink(NULL);
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)
651 cancel_wink();
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);
666 return FALSE;
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.
677 * TRUE on success.
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 |
685 G_SPAWN_SEARCH_PATH,
686 NULL, NULL, /* Child setup fn */
687 NULL, /* Child PID */
688 NULL, NULL, NULL, /* Standard pipes */
689 &error))
691 delayed_error("%s", error ? error->message : "(null)");
692 g_error_free(error);
694 return FALSE;
697 return TRUE;
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);
720 return button;
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;
730 if (need_init)
732 normal = entry->style->text[GTK_STATE_NORMAL];
733 need_init = FALSE;
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);
755 gdk_flush();
756 if (gdk_error_trap_pop())
757 g_warning("window_put_just_above()\n");
759 else
760 gdk_window_lower(higher); /* To bottom of stack */
763 /* Copied from Gtk */
764 static GtkFixedChild* fixed_get_child(GtkFixed *fixed, GtkWidget *widget)
766 GList *children;
768 children = fixed->children;
769 while (children)
771 GtkFixedChild *child;
773 child = children->data;
774 children = children->next;
776 if (child->widget == widget)
777 return child;
780 return NULL;
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);
790 g_assert(child);
792 gtk_widget_freeze_child_notify(widget);
794 child->x = x;
795 gtk_widget_child_notify(widget, "x");
797 child->y = y;
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,
809 &child_requisition);
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);
828 return FALSE;
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)
836 time(&tip_time);
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)
844 GtkWidget *label;
845 int x, y, py;
846 int w, h;
848 if (tip_timeout)
850 gtk_timeout_remove(tip_timeout);
851 tip_timeout = 0;
854 if (tip_widget)
856 gtk_widget_destroy(tip_widget);
857 tip_widget = NULL;
860 if (!text)
861 return;
863 /* Show the tip */
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);
881 x -= w / 2;
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)
890 y = py - h- 2;
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);
896 time(&tip_time);
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)
904 time_t now;
905 int delay;
907 g_return_if_fail(tip_timeout == 0);
909 time(&now);
910 delay = now - tip_time > 2 ? 1000 : 200;
912 g_object_ref(object);
913 tip_timeout = gtk_timeout_add_full(delay,
914 (GtkFunction) callback,
915 NULL,
916 object,
917 g_object_unref);
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)
934 : NULL;
936 gtk_widget_modify_style(widget, rc_style);