r898: Applied Bernard Jungen's latest patch:
[rox-filer.git] / ROX-Filer / src / gui_support.c
blob975720a7ee2f19e028bd7943548d5224db9dcdc8
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2001, 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>
33 #include <X11/Xlib.h>
34 #include <X11/Xatom.h>
35 #include <gdk/gdkx.h>
36 #include <gdk/gdk.h>
37 #include "collection.h"
39 #include "global.h"
41 #include "main.h"
42 #include "gui_support.h"
43 #include "support.h"
44 #include "pixmaps.h"
46 GdkFont *item_font = NULL;
47 GdkFont *fixed_font = NULL;
48 GtkStyle *fixed_style = NULL;
49 gint fixed_width;
50 GdkColor red = {0, 0xffff, 0, 0};
51 GdkGC *red_gc = NULL; /* Not automatically initialised */
52 gint screen_width, screen_height;
54 static GdkAtom xa_cardinal;
56 static GtkWidget *current_dialog = NULL;
58 void gui_support_init()
60 GtkWidget *tmp;
62 fixed_font = gdk_font_load("fixed");
64 /* Create a window and get its font rather than using
65 * the default style (allows customisation via .gtkrc)
67 tmp = gtk_window_new(GTK_WINDOW_TOPLEVEL);
68 gtk_widget_realize(tmp);
69 #ifdef GTK2
70 item_font = gtk_style_get_font(gtk_widget_get_style(tmp));
71 #else
72 item_font = gtk_widget_get_style(tmp)->font;
73 #endif
74 gdk_font_ref(item_font);
75 gtk_widget_destroy(tmp);
77 fixed_style = gtk_style_copy(gtk_widget_get_default_style());
78 #ifdef GTK2
79 gtk_style_set_font(fixed_style, fixed_font);
80 #else
81 fixed_style->font = fixed_font;
82 #endif
84 fixed_width = gdk_string_width(fixed_font, "m");
86 xa_cardinal = gdk_atom_intern("CARDINAL", FALSE);
88 gdk_color_alloc(gtk_widget_get_default_colormap(), &red);
90 /* This call starts returning strange values after a while, so get
91 * the result here during init.
93 gdk_window_get_size(GDK_ROOT_PARENT(), &screen_width, &screen_height);
96 static void choice_clicked(GtkWidget *widget, gpointer number)
98 int *choice_return;
100 choice_return = gtk_object_get_data(GTK_OBJECT(widget),
101 "choice_return");
103 if (choice_return)
104 *choice_return = GPOINTER_TO_INT(number);
107 /* Open a modal dialog box showing a message.
108 * The user can choose from a selection of buttons at the bottom.
109 * Returns -1 if the window is destroyed, or the number of the button
110 * if one is clicked (starting from zero).
112 * If a dialog is already open, returns -1 without waiting AND
113 * brings the current dialog to the front.
115 int get_choice(char *title,
116 char *message,
117 int number_of_buttons, ...)
119 GtkWidget *dialog;
120 GtkWidget *vbox, *action_area, *separator;
121 GtkWidget *text, *text_container;
122 GtkWidget *button = NULL, *default_button;
123 int i, retval;
124 va_list ap;
125 int choice_return;
127 if (current_dialog)
129 gtk_widget_hide(current_dialog);
130 gtk_widget_show(current_dialog);
131 return -1;
134 current_dialog = dialog = gtk_window_new(GTK_WINDOW_DIALOG);
135 GTK_WIDGET_UNSET_FLAGS(dialog, GTK_CAN_FOCUS);
136 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
137 gtk_window_set_title(GTK_WINDOW(dialog), title);
138 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
139 gtk_container_set_border_width(GTK_CONTAINER(dialog), 2);
141 vbox = gtk_vbox_new(FALSE, 0);
142 gtk_container_add(GTK_CONTAINER(dialog), vbox);
144 action_area = gtk_hbox_new(TRUE, 5);
145 gtk_container_set_border_width(GTK_CONTAINER(action_area), 2);
146 gtk_box_pack_end(GTK_BOX(vbox), action_area, FALSE, TRUE, 0);
148 separator = gtk_hseparator_new ();
149 gtk_box_pack_end(GTK_BOX(vbox), separator, FALSE, TRUE, 2);
151 text = gtk_label_new(message);
152 gtk_label_set_line_wrap(GTK_LABEL(text), TRUE);
153 text_container = gtk_event_box_new();
154 gtk_container_set_border_width(GTK_CONTAINER(text_container), 40);
155 gtk_container_add(GTK_CONTAINER(text_container), text);
157 gtk_box_pack_start(GTK_BOX(vbox),
158 text_container,
159 TRUE, TRUE, 0);
161 va_start(ap, number_of_buttons);
163 default_button = NULL;
164 for (i = 0; i < number_of_buttons; i++)
166 button = gtk_button_new_with_label(va_arg(ap, char *));
167 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
168 gtk_misc_set_padding(GTK_MISC(GTK_BIN(button)->child), 16, 2);
169 gtk_object_set_data(GTK_OBJECT(button), "choice_return",
170 &choice_return);
171 gtk_box_pack_start(GTK_BOX(action_area), button, TRUE, TRUE, 0);
172 gtk_signal_connect(GTK_OBJECT(button), "clicked",
173 GTK_SIGNAL_FUNC(choice_clicked), GINT_TO_POINTER(i));
174 if (!default_button)
175 default_button = button;
178 gtk_window_set_focus(GTK_WINDOW(dialog), default_button);
179 gtk_window_set_default(GTK_WINDOW(dialog), default_button);
180 gtk_container_set_focus_child(GTK_CONTAINER(action_area),
181 default_button);
183 va_end(ap);
185 gtk_object_set_data(GTK_OBJECT(dialog), "choice_return",
186 &choice_return);
187 gtk_signal_connect(GTK_OBJECT(dialog), "destroy",
188 GTK_SIGNAL_FUNC(choice_clicked), GINT_TO_POINTER(-1));
190 choice_return = -2;
192 gtk_widget_show_all(dialog);
194 while (choice_return == -2)
195 g_main_iteration(TRUE);
197 retval = choice_return;
199 if (retval != -1)
200 gtk_widget_destroy(dialog);
202 current_dialog = NULL;
204 return retval;
207 /* Display a message in a window */
208 void report_error(char *title, char *message, ...)
210 va_list args;
211 gchar *s;
213 g_return_if_fail(message != NULL);
215 va_start(args, message);
216 s = g_strdup_vprintf(message, args);
217 va_end(args);
219 if (!title)
220 title = _("Error");
222 get_choice(title, s, 1, _("OK"));
223 g_free(s);
226 /* Display a message in a window with "ROX-Filer" as title */
227 void report_rox_error(char *message, ...)
229 va_list args;
230 gchar *s;
232 g_return_if_fail(message != NULL);
234 va_start(args, message);
235 s = g_strdup_vprintf(message, args);
236 va_end(args);
238 report_error(PROJECT, "%s", s);
239 g_free(s);
242 void set_cardinal_property(GdkWindow *window, GdkAtom prop, guint32 value)
244 gdk_property_change(window, prop, xa_cardinal, 32,
245 GDK_PROP_MODE_REPLACE, (gchar *) &value, 1);
248 /* NB: Also used for pinned icons.
249 * TODO: Set the level here too.
251 void make_panel_window(GdkWindow *window)
253 static gboolean need_init = TRUE;
254 static GdkAtom xa_state, xa_atom, xa_net_state;
255 static GdkAtom state_list[3];
257 if (override_redirect)
259 gdk_window_lower(window);
260 gdk_window_set_override_redirect(window, TRUE);
261 return;
264 if (need_init)
266 xa_state = gdk_atom_intern("_WIN_STATE", FALSE);
267 xa_atom = gdk_atom_intern("ATOM", FALSE);
268 xa_net_state = gdk_atom_intern("_NET_WM_STATE", FALSE);
270 state_list[0] = gdk_atom_intern("_NET_WM_STATE_STICKY", FALSE);
271 state_list[1] = gdk_atom_intern("_NET_WM_STATE_SKIP_PAGER",
272 FALSE);
273 state_list[2] = gdk_atom_intern("_NET_WM_STATE_SKIP_TASKBAR",
274 FALSE);
276 need_init = FALSE;
279 gdk_window_set_decorations(window, 0);
280 gdk_window_set_functions(window, 0);
282 /* Don't hide panel/pinboard windows initially (WIN_STATE_HIDDEN).
283 * Needed for IceWM - Christopher Arndt <chris.arndt@web.de>
285 set_cardinal_property(window, xa_state,
286 WIN_STATE_STICKY |
287 WIN_STATE_FIXED_POSITION | WIN_STATE_ARRANGE_IGNORE);
289 gdk_property_change(window, xa_net_state, xa_atom, 32,
290 GDK_PROP_MODE_APPEND, (guchar *) state_list, 3);
293 gint hide_dialog_event(GtkWidget *widget, GdkEvent *event, gpointer window)
295 gtk_widget_hide((GtkWidget *) window);
297 return TRUE;
300 static gboolean error_idle_cb(gpointer data)
302 char **error = (char **) data;
304 report_error(error[0], error[1]);
305 g_free(error[0]);
306 g_free(error[1]);
307 error[0] = error[1] = NULL;
309 if (--number_of_windows == 0)
310 gtk_main_quit();
312 return FALSE;
315 /* Display an error next time we are idle */
316 void delayed_error(char *title, char *error, ...)
318 static char *delayed_error_data[2] = {NULL, NULL};
319 gboolean already_open;
320 va_list args;
322 g_return_if_fail(error != NULL);
324 already_open = delayed_error_data[1] != NULL;
326 g_free(delayed_error_data[0]);
327 g_free(delayed_error_data[1]);
329 delayed_error_data[0] = g_strdup(title);
330 va_start(args, error);
331 delayed_error_data[1] = g_strdup_vprintf(error, args);
332 va_end(args);
334 if (already_open)
335 return;
337 gtk_idle_add(error_idle_cb, delayed_error_data);
339 number_of_windows++;
342 /* Display an error with "ROX-Filer" as title next time we are idle */
343 void delayed_rox_error(char *error, ...)
345 va_list args;
346 gchar *s;
348 g_return_if_fail(error != NULL);
350 va_start(args, error);
351 s = g_strdup_vprintf(error, args);
352 va_end(args);
354 delayed_error(PROJECT, "%s", s);
355 g_free(s);
358 /* Load the file into memory. Return TRUE on success.
359 * Block is zero terminated (but this is not included in the length).
361 gboolean load_file(char *pathname, char **data_out, long *length_out)
363 FILE *file;
364 long length;
365 char *buffer;
366 gboolean retval = FALSE;
368 file = fopen(pathname, "r");
370 if (!file)
372 delayed_rox_error("open(%s): %s", pathname, g_strerror(errno));
373 return FALSE;
376 fseek(file, 0, SEEK_END);
377 length = ftell(file);
379 buffer = malloc(length + 1);
380 if (buffer)
382 fseek(file, 0, SEEK_SET);
383 fread(buffer, 1, length, file);
385 if (ferror(file))
387 delayed_rox_error(_("Error reading file %s: %s"),
388 pathname, g_strerror(errno));
389 g_free(buffer);
391 else
393 *data_out = buffer;
394 *length_out = length;
395 buffer[length] = '\0';
396 retval = TRUE;
399 else
400 delayed_rox_error(_("Can't allocate memory for buffer to "
401 "load this file"));
403 fclose(file);
405 return retval;
408 GtkWidget *new_help_button(HelpFunc show_help, gpointer data)
410 GtkWidget *b, *icon;
412 b = gtk_button_new();
413 gtk_button_set_relief(GTK_BUTTON(b), GTK_RELIEF_NONE);
414 icon = gtk_pixmap_new(im_help->pixmap, im_help->mask);
415 gtk_container_add(GTK_CONTAINER(b), icon);
416 gtk_signal_connect_object(GTK_OBJECT(b), "clicked",
417 GTK_SIGNAL_FUNC(show_help), data);
419 GTK_WIDGET_UNSET_FLAGS(b, GTK_CAN_FOCUS);
421 return b;
424 /* Read file into memory. Call parse_line(guchar *line) for each line
425 * in the file. Callback returns NULL on success, or an error message
426 * if something went wrong. Only the first error is displayed to the user.
428 void parse_file(char *path, ParseFunc *parse_line)
430 char *data;
431 long length;
432 gboolean seen_error = FALSE;
434 if (load_file(path, &data, &length))
436 char *eol, *error;
437 char *line = data;
438 int line_number = 1;
440 while (line && *line)
442 eol = strchr(line, '\n');
443 if (eol)
444 *eol = '\0';
446 error = parse_line(line);
448 if (error && !seen_error)
450 delayed_rox_error(
451 _("Error in '%s' file at line %d: "
452 "\n\"%s\"\n"
453 "This may be due to upgrading from a previous version of "
454 "ROX-Filer. Open the Options window and click on Save.\n"
455 "Further errors will be ignored."),
456 path,
457 line_number,
458 error);
459 seen_error = TRUE;
462 if (!eol)
463 break;
464 line = eol + 1;
465 line_number++;
467 g_free(data);
471 /* Sets up a proxy window for DnD on the specified X window.
472 * Courtesy of Owen Taylor (taken from gmc).
474 gboolean setup_xdnd_proxy(guint32 xid, GdkWindow *proxy_window)
476 GdkAtom xdnd_proxy_atom;
477 Window proxy_xid;
478 Atom type;
479 int format;
480 unsigned long nitems, after;
481 Window *proxy_data;
482 Window proxy;
483 #ifndef GTK2
484 guint32 old_warnings;
485 #endif
487 XGrabServer(GDK_DISPLAY());
489 xdnd_proxy_atom = gdk_atom_intern("XdndProxy", FALSE);
490 proxy_xid = GDK_WINDOW_XWINDOW(proxy_window);
491 type = None;
492 proxy = None;
494 #ifdef GTK2
495 gdk_error_trap_push();
496 #else
497 old_warnings = gdk_error_warnings;
498 gdk_error_code = 0;
499 gdk_error_warnings = 0;
500 #endif
502 /* Check if somebody else already owns drops on the root window */
504 XGetWindowProperty(GDK_DISPLAY(), xid,
505 gdk_x11_atom_to_xatom(xdnd_proxy_atom), 0,
506 1, False, AnyPropertyType,
507 &type, &format, &nitems, &after,
508 (guchar **) &proxy_data);
510 if (type != None)
512 if (format == 32 && nitems == 1)
513 proxy = *proxy_data;
515 XFree(proxy_data);
518 /* The property was set, now check if the window it points to exists
519 * and has a XdndProxy property pointing to itself.
521 if (proxy)
523 #ifdef GTK2
524 gint gdk_error_code;
525 #endif
527 XGetWindowProperty(GDK_DISPLAY(), proxy,
528 gdk_x11_atom_to_xatom(xdnd_proxy_atom),
529 0, 1, False, AnyPropertyType,
530 &type, &format, &nitems, &after,
531 (guchar **) &proxy_data);
533 #ifdef GTK2
534 gdk_error_code = gdk_error_trap_pop();
535 gdk_error_trap_push();
536 #endif
538 if (!gdk_error_code && type != None)
540 if (format == 32 && nitems == 1)
541 if (*proxy_data != proxy)
542 proxy = None;
544 XFree(proxy_data);
546 else
547 proxy = gdk_x11_atom_to_xatom(GDK_NONE);
550 if (!proxy)
552 /* OK, we can set the property to point to us */
553 /* TODO: Use gdk call? */
555 XChangeProperty(GDK_DISPLAY(), xid,
556 gdk_x11_atom_to_xatom(xdnd_proxy_atom),
557 gdk_x11_atom_to_xatom(gdk_atom_intern("WINDOW",
558 FALSE)),
559 32, PropModeReplace,
560 (guchar *) &proxy_xid, 1);
563 #ifdef GTK2
564 gdk_error_trap_pop();
565 #else
566 gdk_error_code = 0;
567 gdk_error_warnings = old_warnings;
568 #endif
570 XUngrabServer(GDK_DISPLAY());
571 gdk_flush();
573 if (!proxy)
575 /* Mark our window as a valid proxy window with a XdndProxy
576 * property pointing recursively;
578 XChangeProperty(GDK_DISPLAY(), proxy_xid,
579 gdk_x11_atom_to_xatom(xdnd_proxy_atom),
580 gdk_x11_atom_to_xatom(gdk_atom_intern("WINDOW",
581 FALSE)),
582 32, PropModeReplace,
583 (guchar *) &proxy_xid, 1);
586 return !proxy;
589 /* xid is the window (usually the root) which points to the proxy */
590 void release_xdnd_proxy(guint32 xid)
592 GdkAtom xdnd_proxy_atom;
594 xdnd_proxy_atom = gdk_atom_intern("XdndProxy", FALSE);
596 XDeleteProperty(GDK_DISPLAY(), xid,
597 gdk_x11_atom_to_xatom(xdnd_proxy_atom));
600 /* Looks for the proxy window to get root window clicks from the window
601 * manager. Taken from gmc. NULL if there is no proxy window.
603 GdkWindow *find_click_proxy_window(void)
605 GdkAtom click_proxy_atom;
606 Atom type;
607 int format;
608 unsigned long nitems, after;
609 Window *proxy_data;
610 Window proxy;
611 GdkWindow *proxy_gdk_window;
612 #ifndef GTK2
613 guint32 old_warnings;
614 #endif
616 XGrabServer(GDK_DISPLAY());
618 click_proxy_atom = gdk_atom_intern("_WIN_DESKTOP_BUTTON_PROXY", FALSE);
619 type = None;
620 proxy = None;
622 #ifdef GTK2
623 gdk_error_trap_push();
624 #else
625 old_warnings = gdk_error_warnings;
626 gdk_error_code = 0;
627 gdk_error_warnings = 0;
628 #endif
630 /* Check if the proxy window exists */
632 XGetWindowProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
633 gdk_x11_atom_to_xatom(click_proxy_atom), 0,
634 1, False, AnyPropertyType,
635 &type, &format, &nitems, &after,
636 (guchar **) &proxy_data);
638 if (type != None)
640 if (format == 32 && nitems == 1)
641 proxy = *proxy_data;
643 XFree(proxy_data);
646 /* If the property was set, check if the window it points to exists
647 * and has a _WIN_DESKTOP_BUTTON_PROXY property pointing to itself.
650 if (proxy)
652 #ifdef GTK2
653 gint gdk_error_code;
654 #endif
655 XGetWindowProperty(GDK_DISPLAY(), proxy,
656 gdk_x11_atom_to_xatom(click_proxy_atom), 0,
657 1, False, AnyPropertyType,
658 &type, &format, &nitems, &after,
659 (guchar **) &proxy_data);
661 #ifdef GTK2
662 gdk_error_code = gdk_error_trap_pop();
663 gdk_error_trap_push();
664 #endif
666 if (!gdk_error_code && type != None)
668 if (format == 32 && nitems == 1)
669 if (*proxy_data != proxy)
670 proxy = gdk_x11_atom_to_xatom(GDK_NONE);
672 XFree(proxy_data);
674 else
675 proxy = gdk_x11_atom_to_xatom(GDK_NONE);
678 #ifdef GTK2
679 gdk_error_trap_pop();
680 #else
681 gdk_error_code = 0;
682 gdk_error_warnings = old_warnings;
683 #endif
685 XUngrabServer(GDK_DISPLAY());
686 gdk_flush();
688 if (proxy)
689 proxy_gdk_window = gdk_window_foreign_new(proxy);
690 else
691 proxy_gdk_window = NULL;
693 return proxy_gdk_window;
696 /* Returns the position of the pointer.
697 * TRUE if any modifier keys or mouse buttons are pressed.
699 gboolean get_pointer_xy(int *x, int *y)
701 unsigned int mask;
703 gdk_window_get_pointer(NULL, x, y, &mask);
705 return mask != 0;
708 #define DECOR_BORDER 32
710 /* Centre the window at these coords */
711 void centre_window(GdkWindow *window, int x, int y)
713 int w, h;
715 g_return_if_fail(window != NULL);
717 gdk_window_get_size(window, &w, &h);
719 x -= w / 2;
720 y -= h / 2;
722 gdk_window_move(window,
723 CLAMP(x, DECOR_BORDER, screen_width - w - DECOR_BORDER),
724 CLAMP(y, DECOR_BORDER, screen_height - h - DECOR_BORDER));
727 /* Non-NULL => selector window is open */
728 static GtkColorSelectionDialog *current_csel_box = NULL;
730 static void get_new_colour(GtkWidget *ok, GtkWidget *button)
732 GtkWidget *csel;
733 gdouble c[4];
734 GdkColor colour;
736 g_return_if_fail(current_csel_box != NULL);
738 csel = current_csel_box->colorsel;
740 gtk_color_selection_get_color(GTK_COLOR_SELECTION(csel), c);
741 colour.red = c[0] * 0xffff;
742 colour.green = c[1] * 0xffff;
743 colour.blue = c[2] * 0xffff;
745 button_patch_set_colour(button, &colour);
747 gtk_widget_destroy(GTK_WIDGET(current_csel_box));
750 static void open_coloursel(GtkWidget *ok, GtkWidget *button)
752 GtkColorSelectionDialog *csel;
753 GtkWidget *dialog, *patch;
754 gdouble c[4];
756 if (current_csel_box)
757 gtk_widget_destroy(GTK_WIDGET(current_csel_box));
759 dialog = gtk_color_selection_dialog_new(NULL);
760 csel = GTK_COLOR_SELECTION_DIALOG(dialog);
761 current_csel_box = csel;
763 gtk_signal_connect_object(GTK_OBJECT(dialog), "destroy",
764 GTK_SIGNAL_FUNC(set_to_null),
765 (GtkObject *) &current_csel_box);
766 gtk_widget_hide(csel->help_button);
767 gtk_signal_connect_object(GTK_OBJECT(csel->cancel_button), "clicked",
768 GTK_SIGNAL_FUNC(gtk_widget_destroy),
769 GTK_OBJECT(dialog));
770 gtk_signal_connect(GTK_OBJECT(csel->ok_button), "clicked",
771 GTK_SIGNAL_FUNC(get_new_colour), button);
773 patch = GTK_BIN(button)->child;
775 c[0] = ((gdouble) patch->style->bg[GTK_STATE_NORMAL].red) / 0xffff;
776 c[1] = ((gdouble) patch->style->bg[GTK_STATE_NORMAL].green) / 0xffff;
777 c[2] = ((gdouble) patch->style->bg[GTK_STATE_NORMAL].blue) / 0xffff;
778 gtk_color_selection_set_color(GTK_COLOR_SELECTION(csel->colorsel), c);
780 gtk_widget_show(dialog);
783 /* Turns a button (with no child) into a colour patch button.
784 * When clicked, the button will open the colour selector window
785 * and let the user pick a colour. Afterwards, the buttons changes
786 * colour to the one chosen.
788 void make_colour_patch(GtkWidget *button)
790 GtkWidget *da;
792 da = gtk_drawing_area_new();
793 gtk_drawing_area_size(GTK_DRAWING_AREA(da), 64, 12);
794 gtk_container_add(GTK_CONTAINER(button), da);
795 gtk_signal_connect(GTK_OBJECT(button), "clicked",
796 GTK_SIGNAL_FUNC(open_coloursel), button);
799 /* Returns a pointer to the style's background colour */
800 GdkColor *button_patch_get_colour(GtkWidget *button)
802 return &(GTK_BIN(button)->child)->style->bg[GTK_STATE_NORMAL];
805 void button_patch_set_colour(GtkWidget *button, GdkColor *colour)
807 GtkStyle *style;
808 GtkWidget *patch;
810 patch = GTK_BIN(button)->child;
812 style = gtk_style_copy(GTK_WIDGET(patch)->style);
813 style->bg[GTK_STATE_NORMAL].red = colour->red;
814 style->bg[GTK_STATE_NORMAL].green = colour->green;
815 style->bg[GTK_STATE_NORMAL].blue = colour->blue;
816 gtk_widget_set_style(patch, style);
817 gtk_style_unref(style);
819 if (GTK_WIDGET_REALIZED(patch))
820 gdk_window_clear(patch->window);
823 static GtkWidget *current_wink_widget = NULL;
824 static gint wink_timeout = -1; /* Called when it's time to stop */
825 static gint wink_destroy; /* Called if the widget dies first */
827 static gboolean end_wink(gpointer data)
829 gtk_drag_unhighlight(current_wink_widget);
831 gtk_signal_disconnect(GTK_OBJECT(current_wink_widget), wink_destroy);
833 current_wink_widget = NULL;
835 return FALSE;
838 static void cancel_wink(void)
840 gtk_timeout_remove(wink_timeout);
841 end_wink(NULL);
844 static void wink_widget_died(gpointer data)
846 current_wink_widget = NULL;
847 gtk_timeout_remove(wink_timeout);
850 /* Draw a black box around this widget, briefly.
851 * Note: uses the drag highlighting code for now.
853 void wink_widget(GtkWidget *widget)
855 g_return_if_fail(widget != NULL);
857 if (current_wink_widget)
858 cancel_wink();
860 current_wink_widget = widget;
861 gtk_drag_highlight(current_wink_widget);
863 wink_timeout = gtk_timeout_add(300, (GtkFunction) end_wink, NULL);
865 wink_destroy = gtk_signal_connect_object(GTK_OBJECT(widget), "destroy",
866 GTK_SIGNAL_FUNC(wink_widget_died), NULL);
869 static gboolean idle_destroy_cb(GtkWidget *widget)
871 gtk_widget_unref(widget);
872 gtk_widget_destroy(widget);
873 return FALSE;
876 /* Destroy the widget in an idle callback */
877 void destroy_on_idle(GtkWidget *widget)
879 gtk_widget_ref(widget);
880 gtk_idle_add((GtkFunction) idle_destroy_cb, widget);