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>
34 #include <X11/Xatom.h>
37 #include <gdk/gdkkeysyms.h>
42 #include "gui_support.h"
47 gint screen_width
, screen_height
;
49 static GdkAtom xa_cardinal
;
51 static GtkWidget
*current_dialog
= NULL
;
53 void gui_support_init()
55 xa_cardinal
= gdk_atom_intern("CARDINAL", FALSE
);
57 /* This call starts returning strange values after a while, so get
58 * the result here during init.
60 gdk_drawable_get_size(gdk_get_default_root_window(),
61 &screen_width
, &screen_height
);
64 /* Open a modal dialog box showing a message.
65 * The user can choose from a selection of buttons at the bottom.
66 * Returns -1 if the window is destroyed, or the number of the button
67 * if one is clicked (starting from zero).
69 * If a dialog is already open, returns -1 without waiting AND
70 * brings the current dialog to the front.
72 int get_choice(const char *title
,
74 int number_of_buttons
, ...)
77 GtkWidget
*button
= NULL
;
83 gtk_widget_hide(current_dialog
);
84 gtk_widget_show(current_dialog
);
88 current_dialog
= dialog
= gtk_message_dialog_new(NULL
,
93 gtk_window_set_position(GTK_WINDOW(dialog
), GTK_WIN_POS_CENTER
);
95 va_start(ap
, number_of_buttons
);
97 for (i
= 0; i
< number_of_buttons
; i
++)
98 button
= gtk_dialog_add_button(GTK_DIALOG(current_dialog
),
99 va_arg(ap
, char *), i
);
101 gtk_window_set_title(GTK_WINDOW(dialog
), title
);
102 gtk_window_set_position(GTK_WINDOW(dialog
), GTK_WIN_POS_CENTER
);
104 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), i
- 1);
108 retval
= gtk_dialog_run(GTK_DIALOG(dialog
));
109 if (retval
== GTK_RESPONSE_NONE
)
111 gtk_widget_destroy(dialog
);
113 current_dialog
= NULL
;
118 void info_message(const char *message
, ...)
124 g_return_if_fail(message
!= NULL
);
126 va_start(args
, message
);
127 s
= g_strdup_vprintf(message
, args
);
130 dialog
= gtk_message_dialog_new(NULL
,
135 gtk_window_set_position(GTK_WINDOW(dialog
), GTK_WIN_POS_CENTER
);
136 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), GTK_RESPONSE_OK
);
137 gtk_dialog_run(GTK_DIALOG(dialog
));
138 gtk_widget_destroy(dialog
);
143 /* Display a message in a window with "ROX-Filer" as title */
144 void report_error(const char *message
, ...)
150 g_return_if_fail(message
!= NULL
);
152 va_start(args
, message
);
153 s
= g_strdup_vprintf(message
, args
);
156 dialog
= gtk_message_dialog_new(NULL
,
161 gtk_window_set_position(GTK_WINDOW(dialog
), GTK_WIN_POS_CENTER
);
162 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), GTK_RESPONSE_OK
);
163 gtk_dialog_run(GTK_DIALOG(dialog
));
164 gtk_widget_destroy(dialog
);
169 void set_cardinal_property(GdkWindow
*window
, GdkAtom prop
, guint32 value
)
171 gdk_property_change(window
, prop
, xa_cardinal
, 32,
172 GDK_PROP_MODE_REPLACE
, (gchar
*) &value
, 1);
175 /* NB: Also used for pinned icons.
176 * TODO: Set the level here too.
178 void make_panel_window(GtkWidget
*widget
)
180 static gboolean need_init
= TRUE
;
181 static GdkAtom xa_state
, xa_atom
, xa_net_state
, xa_hints
, xa_win_hints
;
182 static GdkAtom state_list
[3];
183 GdkWindow
*window
= widget
->window
;
184 gint32 wm_hints_values
[] = {1, False
, 0, 0, 0, 0, 0, 0};
185 GdkAtom wm_protocols
[2];
187 g_return_if_fail(window
!= NULL
);
189 if (override_redirect
)
191 gdk_window_set_override_redirect(window
, TRUE
);
197 xa_win_hints
= gdk_atom_intern("_WIN_HINTS", FALSE
);
198 xa_state
= gdk_atom_intern("_WIN_STATE", FALSE
);
199 xa_atom
= gdk_atom_intern("ATOM", FALSE
);
200 xa_net_state
= gdk_atom_intern("_NET_WM_STATE", FALSE
);
201 xa_hints
= gdk_atom_intern("WM_HINTS", FALSE
);
203 /* Note: Starting with Gtk+-1.3.12, Gtk+ converts GdkAtoms
204 * to X atoms automatically when the type is ATOM.
206 state_list
[0] = gdk_atom_intern("_NET_WM_STATE_STICKY", FALSE
);
207 state_list
[1] = gdk_atom_intern("_NET_WM_STATE_SKIP_PAGER",
209 state_list
[2] = gdk_atom_intern("_NET_WM_STATE_SKIP_TASKBAR",
215 gdk_window_set_decorations(window
, 0);
216 gdk_window_set_functions(window
, 0);
218 /* Note: DON'T do gtk_window_stick(). Setting the state via
219 * gdk will override our other atoms (pager/taskbar).
222 /* Don't hide panel/pinboard windows initially (WIN_STATE_HIDDEN).
223 * Needed for IceWM - Christopher Arndt <chris.arndt@web.de>
225 set_cardinal_property(window
, xa_state
,
227 WIN_STATE_FIXED_POSITION
| WIN_STATE_ARRANGE_IGNORE
);
229 set_cardinal_property(window
, xa_win_hints
,
230 WIN_HINTS_SKIP_FOCUS
| WIN_HINTS_SKIP_WINLIST
|
231 WIN_HINTS_SKIP_TASKBAR
);
233 gdk_property_change(window
, xa_net_state
, xa_atom
, 32,
234 GDK_PROP_MODE_APPEND
, (guchar
*) state_list
, 3);
236 g_return_if_fail(window
!= NULL
);
238 gdk_property_change(window
, xa_hints
, xa_hints
, 32,
239 GDK_PROP_MODE_REPLACE
, (guchar
*) wm_hints_values
,
240 sizeof(wm_hints_values
) / sizeof(gint32
));
242 wm_protocols
[0] = gdk_atom_intern("WM_DELETE_WINDOW", FALSE
);
243 wm_protocols
[1] = gdk_atom_intern("_NET_WM_PING", FALSE
);
244 gdk_property_change(window
,
245 gdk_atom_intern("WM_PROTOCOLS", FALSE
), xa_atom
, 32,
246 GDK_PROP_MODE_REPLACE
, (guchar
*) wm_protocols
,
247 sizeof(wm_protocols
) / sizeof(GdkAtom
));
250 static gboolean
error_idle_cb(gpointer data
)
252 char **error
= (char **) data
;
254 report_error("%s", *error
);
258 if (--number_of_windows
== 0)
264 /* Display an error with "ROX-Filer" as title next time we are idle.
265 * If multiple errors are reported this way before the window is opened,
266 * all are displayed in a single window.
267 * If an error is reported while the error window is open, it is discarded.
269 void delayed_error(const char *error
, ...)
271 static char *delayed_error_data
= NULL
;
275 g_return_if_fail(error
!= NULL
);
277 old
= delayed_error_data
;
279 va_start(args
, error
);
280 new = g_strdup_vprintf(error
, args
);
285 delayed_error_data
= g_strconcat(old
,
293 delayed_error_data
= new;
294 gtk_idle_add(error_idle_cb
, &delayed_error_data
);
300 /* Load the file into memory. Return TRUE on success.
301 * Block is zero terminated (but this is not included in the length).
303 gboolean
load_file(const char *pathname
, char **data_out
, long *length_out
)
306 GError
*error
= NULL
;
308 if (!g_file_get_contents(pathname
, data_out
, &len
, &error
))
310 delayed_error("%s", error
? error
->message
: pathname
);
320 GtkWidget
*new_help_button(HelpFunc show_help
, gpointer data
)
324 b
= gtk_button_new();
325 gtk_button_set_relief(GTK_BUTTON(b
), GTK_RELIEF_NONE
);
326 icon
= gtk_image_new_from_pixbuf(im_help
->pixbuf
);
327 gtk_container_add(GTK_CONTAINER(b
), icon
);
328 g_signal_connect_swapped(b
, "clicked", G_CALLBACK(show_help
), data
);
330 GTK_WIDGET_UNSET_FLAGS(b
, GTK_CAN_FOCUS
);
335 /* Read file into memory. Call parse_line(guchar *line) for each line
336 * in the file. Callback returns NULL on success, or an error message
337 * if something went wrong. Only the first error is displayed to the user.
339 void parse_file(const char *path
, ParseFunc
*parse_line
)
343 gboolean seen_error
= FALSE
;
345 if (load_file(path
, &data
, &length
))
352 if (strncmp(data
, "<?xml ", 6) == 0)
354 delayed_error(_("Attempt to read an XML file as "
355 "a text file. File '%s' may be "
356 "corrupted."), path
);
360 while (line
&& *line
)
362 eol
= strchr(line
, '\n');
366 error
= parse_line(line
);
368 if (error
&& !seen_error
)
371 _("Error in '%s' file at line %d: "
373 "This may be due to upgrading from a previous version of "
374 "ROX-Filer. Open the Options window and click on Save.\n"
375 "Further errors will be ignored."),
391 /* Sets up a proxy window for DnD on the specified X window.
392 * Courtesy of Owen Taylor (taken from gmc).
394 gboolean
setup_xdnd_proxy(guint32 xid
, GdkWindow
*proxy_window
)
396 GdkAtom xdnd_proxy_atom
;
400 unsigned long nitems
, after
;
404 XGrabServer(GDK_DISPLAY());
406 xdnd_proxy_atom
= gdk_atom_intern("XdndProxy", FALSE
);
407 proxy_xid
= GDK_WINDOW_XWINDOW(proxy_window
);
411 gdk_error_trap_push();
413 /* Check if somebody else already owns drops on the root window */
415 XGetWindowProperty(GDK_DISPLAY(), xid
,
416 gdk_x11_atom_to_xatom(xdnd_proxy_atom
), 0,
417 1, False
, AnyPropertyType
,
418 &type
, &format
, &nitems
, &after
,
419 (guchar
**) &proxy_data
);
423 if (format
== 32 && nitems
== 1)
429 /* The property was set, now check if the window it points to exists
430 * and has a XdndProxy property pointing to itself.
436 XGetWindowProperty(GDK_DISPLAY(), proxy
,
437 gdk_x11_atom_to_xatom(xdnd_proxy_atom
),
438 0, 1, False
, AnyPropertyType
,
439 &type
, &format
, &nitems
, &after
,
440 (guchar
**) &proxy_data
);
442 gdk_error_code
= gdk_error_trap_pop();
443 gdk_error_trap_push();
445 if (!gdk_error_code
&& type
!= None
)
447 if (format
== 32 && nitems
== 1)
448 if (*proxy_data
!= proxy
)
454 proxy
= gdk_x11_atom_to_xatom(GDK_NONE
);
459 /* OK, we can set the property to point to us */
460 /* TODO: Use gdk call? */
462 XChangeProperty(GDK_DISPLAY(), xid
,
463 gdk_x11_atom_to_xatom(xdnd_proxy_atom
),
464 gdk_x11_atom_to_xatom(gdk_atom_intern("WINDOW",
467 (guchar
*) &proxy_xid
, 1);
470 gdk_error_trap_pop();
472 XUngrabServer(GDK_DISPLAY());
477 /* Mark our window as a valid proxy window with a XdndProxy
478 * property pointing recursively;
480 XChangeProperty(GDK_DISPLAY(), proxy_xid
,
481 gdk_x11_atom_to_xatom(xdnd_proxy_atom
),
482 gdk_x11_atom_to_xatom(gdk_atom_intern("WINDOW",
485 (guchar
*) &proxy_xid
, 1);
491 /* xid is the window (usually the root) which points to the proxy */
492 void release_xdnd_proxy(guint32 xid
)
494 GdkAtom xdnd_proxy_atom
;
496 xdnd_proxy_atom
= gdk_atom_intern("XdndProxy", FALSE
);
498 XDeleteProperty(GDK_DISPLAY(), xid
,
499 gdk_x11_atom_to_xatom(xdnd_proxy_atom
));
502 /* Looks for the proxy window to get root window clicks from the window
503 * manager. Taken from gmc. NULL if there is no proxy window.
505 GdkWindow
*find_click_proxy_window(void)
507 GdkAtom click_proxy_atom
;
510 unsigned long nitems
, after
;
513 GdkWindow
*proxy_gdk_window
;
515 XGrabServer(GDK_DISPLAY());
517 click_proxy_atom
= gdk_atom_intern("_WIN_DESKTOP_BUTTON_PROXY", FALSE
);
521 gdk_error_trap_push();
523 /* Check if the proxy window exists */
525 XGetWindowProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
526 gdk_x11_atom_to_xatom(click_proxy_atom
), 0,
527 1, False
, AnyPropertyType
,
528 &type
, &format
, &nitems
, &after
,
529 (guchar
**) &proxy_data
);
533 if (format
== 32 && nitems
== 1)
539 /* If the property was set, check if the window it points to exists
540 * and has a _WIN_DESKTOP_BUTTON_PROXY property pointing to itself.
546 XGetWindowProperty(GDK_DISPLAY(), proxy
,
547 gdk_x11_atom_to_xatom(click_proxy_atom
), 0,
548 1, False
, AnyPropertyType
,
549 &type
, &format
, &nitems
, &after
,
550 (guchar
**) &proxy_data
);
552 gdk_error_code
= gdk_error_trap_pop();
553 gdk_error_trap_push();
555 if (!gdk_error_code
&& type
!= None
)
557 if (format
== 32 && nitems
== 1)
558 if (*proxy_data
!= proxy
)
559 proxy
= gdk_x11_atom_to_xatom(GDK_NONE
);
564 proxy
= gdk_x11_atom_to_xatom(GDK_NONE
);
567 gdk_error_trap_pop();
569 XUngrabServer(GDK_DISPLAY());
573 proxy_gdk_window
= gdk_window_foreign_new(proxy
);
575 proxy_gdk_window
= NULL
;
577 return proxy_gdk_window
;
580 /* Returns the position of the pointer.
581 * TRUE if any modifier keys or mouse buttons are pressed.
583 gboolean
get_pointer_xy(int *x
, int *y
)
587 gdk_window_get_pointer(NULL
, x
, y
, &mask
);
592 #define DECOR_BORDER 32
594 /* Centre the window at these coords */
595 void centre_window(GdkWindow
*window
, int x
, int y
)
599 g_return_if_fail(window
!= NULL
);
601 gdk_drawable_get_size(window
, &w
, &h
);
606 gdk_window_move(window
,
607 CLAMP(x
, DECOR_BORDER
, screen_width
- w
- DECOR_BORDER
),
608 CLAMP(y
, DECOR_BORDER
, screen_height
- h
- DECOR_BORDER
));
611 static GtkWidget
*current_wink_widget
= NULL
;
612 static gint wink_timeout
= -1; /* Called when it's time to stop */
613 static gulong wink_destroy
; /* Called if the widget dies first */
615 static gboolean
end_wink(gpointer data
)
617 gtk_drag_unhighlight(current_wink_widget
);
619 g_signal_handler_disconnect(current_wink_widget
, wink_destroy
);
621 current_wink_widget
= NULL
;
626 static void cancel_wink(void)
628 gtk_timeout_remove(wink_timeout
);
632 static void wink_widget_died(gpointer data
)
634 current_wink_widget
= NULL
;
635 gtk_timeout_remove(wink_timeout
);
638 /* Draw a black box around this widget, briefly.
639 * Note: uses the drag highlighting code for now.
641 void wink_widget(GtkWidget
*widget
)
643 g_return_if_fail(widget
!= NULL
);
645 if (current_wink_widget
)
648 current_wink_widget
= widget
;
649 gtk_drag_highlight(current_wink_widget
);
651 wink_timeout
= gtk_timeout_add(300, (GtkFunction
) end_wink
, NULL
);
653 wink_destroy
= g_signal_connect_swapped(widget
, "destroy",
654 G_CALLBACK(wink_widget_died
), NULL
);
657 static gboolean
idle_destroy_cb(GtkWidget
*widget
)
659 gtk_widget_unref(widget
);
660 gtk_widget_destroy(widget
);
664 /* Destroy the widget in an idle callback */
665 void destroy_on_idle(GtkWidget
*widget
)
667 gtk_widget_ref(widget
);
668 gtk_idle_add((GtkFunction
) idle_destroy_cb
, widget
);
671 /* Spawn a child process (as spawn_full), and report errors.
674 gboolean
rox_spawn(const gchar
*dir
, const gchar
**argv
)
676 GError
*error
= NULL
;
678 if (!g_spawn_async_with_pipes(dir
, (gchar
**) argv
, NULL
,
679 G_SPAWN_DO_NOT_REAP_CHILD
|
681 NULL
, NULL
, /* Child setup fn */
682 NULL
, /* Child PID */
683 NULL
, NULL
, NULL
, /* Standard pipes */
686 delayed_error("%s", error
? error
->message
: "(null)");
695 GtkWidget
*button_new_mixed(const char *stock
, const char *message
)
697 GtkWidget
*button
, *align
, *image
, *hbox
, *label
;
699 button
= gtk_button_new();
700 label
= gtk_label_new_with_mnemonic(message
);
701 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), button
);
703 image
= gtk_image_new_from_stock(stock
, GTK_ICON_SIZE_BUTTON
);
704 hbox
= gtk_hbox_new(FALSE
, 2);
706 align
= gtk_alignment_new(0.5, 0.5, 0.0, 0.0);
708 gtk_box_pack_start(GTK_BOX(hbox
), image
, FALSE
, FALSE
, 0);
709 gtk_box_pack_end(GTK_BOX(hbox
), label
, FALSE
, FALSE
, 0);
711 gtk_container_add(GTK_CONTAINER(button
), align
);
712 gtk_container_add(GTK_CONTAINER(align
), hbox
);
713 gtk_widget_show_all(align
);
718 /* Highlight entry in red if 'error' is TRUE */
719 void entry_set_error(GtkWidget
*entry
, gboolean error
)
721 GdkColor red
= {0, 0xffff, 0, 0};
722 static gboolean need_init
= TRUE
;
723 static GdkColor normal
;
727 normal
= entry
->style
->text
[GTK_STATE_NORMAL
];
731 gtk_widget_modify_text(entry
, GTK_STATE_NORMAL
, error
? &red
: &normal
);
734 /* Change stacking position of higher to be just above lower */
735 void window_put_just_above(GdkWindow
*higher
, GdkWindow
*lower
)
737 if (override_redirect
&& lower
)
739 XWindowChanges restack
;
740 Window root
, parent
, w
;
744 gdk_error_trap_push();
746 restack
.stack_mode
= Above
;
749 parent
= GDK_WINDOW_XWINDOW(lower
);
754 XQueryTree(gdk_display
, w
,
755 &root
, &parent
, &children
, &nchildren
);
759 while (parent
!= root
);
763 parent
= GDK_WINDOW_XWINDOW(higher
);
768 XQueryTree(gdk_display
, w
,
769 &root
, &parent
, &children
, &nchildren
);
773 while (parent
!= root
);
775 XConfigureWindow(gdk_display
, w
,
776 CWSibling
| CWStackMode
, &restack
);
779 if (gdk_error_trap_pop())
780 g_warning("window_put_just_above()\n");
783 gdk_window_lower(higher
); /* To bottom of stack */
786 /* Copied from Gtk */
787 static GtkFixedChild
* fixed_get_child(GtkFixed
*fixed
, GtkWidget
*widget
)
791 children
= fixed
->children
;
794 GtkFixedChild
*child
;
796 child
= children
->data
;
797 children
= children
->next
;
799 if (child
->widget
== widget
)
806 /* Like gtk_fixed_move(), except not insanely slow */
807 void fixed_move_fast(GtkFixed
*fixed
, GtkWidget
*widget
, int x
, int y
)
809 GtkFixedChild
*child
;
811 child
= fixed_get_child(fixed
, widget
);
815 gtk_widget_freeze_child_notify(widget
);
818 gtk_widget_child_notify(widget
, "x");
821 gtk_widget_child_notify(widget
, "y");
823 gtk_widget_thaw_child_notify(widget
);
825 if (GTK_WIDGET_VISIBLE(widget
) && GTK_WIDGET_VISIBLE(fixed
))
827 int border_width
= GTK_CONTAINER(fixed
)->border_width
;
828 GtkAllocation child_allocation
;
829 GtkRequisition child_requisition
;
831 gtk_widget_get_child_requisition(child
->widget
,
833 child_allocation
.x
= child
->x
+ border_width
;
834 child_allocation
.y
= child
->y
+ border_width
;
836 child_allocation
.x
+= GTK_WIDGET(fixed
)->allocation
.x
;
837 child_allocation
.y
+= GTK_WIDGET(fixed
)->allocation
.y
;
839 child_allocation
.width
= child_requisition
.width
;
840 child_allocation
.height
= child_requisition
.height
;
841 gtk_widget_size_allocate(child
->widget
, &child_allocation
);