r1325: Allow COMPOUND_TEXT target for pinboard and panels (allows pasting into
[rox-filer.git] / ROX-Filer / src / gui_support.c
blob9aec72f57d52f128cfdcd0522a7a77e339f3bee0
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>
33 #include <X11/Xlib.h>
34 #include <X11/Xatom.h>
35 #include <gdk/gdkx.h>
36 #include <gdk/gdk.h>
37 #include <gdk/gdkkeysyms.h>
39 #include "global.h"
41 #include "main.h"
42 #include "gui_support.h"
43 #include "support.h"
44 #include "pixmaps.h"
45 #include "choices.h"
47 GdkGC *red_gc = NULL; /* Not automatically initialised */
48 GdkColor red = {0, 0xffff, 0, 0};
49 gint screen_width, screen_height;
51 static GdkAtom xa_cardinal;
53 static GtkWidget *current_dialog = NULL;
55 void gui_support_init()
57 gdk_color_alloc(gtk_widget_get_default_colormap(), &red); /* XXX */
59 xa_cardinal = gdk_atom_intern("CARDINAL", FALSE);
61 /* This call starts returning strange values after a while, so get
62 * the result here during init.
64 gdk_window_get_size(GDK_ROOT_PARENT(), &screen_width, &screen_height);
67 /* Open a modal dialog box showing a message.
68 * The user can choose from a selection of buttons at the bottom.
69 * Returns -1 if the window is destroyed, or the number of the button
70 * if one is clicked (starting from zero).
72 * If a dialog is already open, returns -1 without waiting AND
73 * brings the current dialog to the front.
75 int get_choice(const char *title,
76 const char *message,
77 int number_of_buttons, ...)
79 GtkWidget *dialog;
80 GtkWidget *button = NULL;
81 int i, retval;
82 va_list ap;
84 if (current_dialog)
86 gtk_widget_hide(current_dialog);
87 gtk_widget_show(current_dialog);
88 return -1;
91 current_dialog = dialog = gtk_message_dialog_new(NULL,
92 GTK_DIALOG_MODAL,
93 GTK_MESSAGE_QUESTION,
94 GTK_BUTTONS_NONE,
95 "%s", message);
96 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
98 va_start(ap, number_of_buttons);
100 for (i = 0; i < number_of_buttons; i++)
101 button = gtk_dialog_add_button(GTK_DIALOG(current_dialog),
102 va_arg(ap, char *), i);
104 gtk_window_set_title(GTK_WINDOW(dialog), title);
105 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
107 gtk_dialog_set_default_response(GTK_DIALOG(dialog), i - 1);
109 va_end(ap);
111 retval = gtk_dialog_run(GTK_DIALOG(dialog));
112 if (retval == GTK_RESPONSE_NONE)
113 retval = -1;
114 gtk_widget_destroy(dialog);
116 current_dialog = NULL;
118 return retval;
121 /* Display a message in a window with "ROX-Filer" as title */
122 void report_error(const char *message, ...)
124 va_list args;
125 gchar *s;
127 g_return_if_fail(message != NULL);
129 va_start(args, message);
130 s = g_strdup_vprintf(message, args);
131 va_end(args);
134 GtkWidget *dialog;
135 dialog = gtk_message_dialog_new(NULL,
136 GTK_DIALOG_MODAL,
137 GTK_MESSAGE_ERROR,
138 GTK_BUTTONS_OK,
139 "%s", s);
140 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
141 gtk_dialog_set_default_response(GTK_DIALOG(dialog),
142 GTK_RESPONSE_OK);
143 gtk_dialog_run(GTK_DIALOG(dialog));
144 gtk_widget_destroy(dialog);
147 g_free(s);
150 void set_cardinal_property(GdkWindow *window, GdkAtom prop, guint32 value)
152 gdk_property_change(window, prop, xa_cardinal, 32,
153 GDK_PROP_MODE_REPLACE, (gchar *) &value, 1);
156 /* NB: Also used for pinned icons.
157 * TODO: Set the level here too.
159 void make_panel_window(GtkWidget *widget)
161 static gboolean need_init = TRUE;
162 static GdkAtom xa_state, xa_atom, xa_net_state, xa_hints, xa_win_hints;
163 static GdkAtom state_list[3];
164 GdkWindow *window = widget->window;
165 gint32 values[2];
168 g_return_if_fail(window != NULL);
170 if (override_redirect)
172 gdk_window_lower(window);
173 gdk_window_set_override_redirect(window, TRUE);
174 return;
177 if (need_init)
179 xa_win_hints = gdk_atom_intern("_WIN_HINTS", FALSE);
180 xa_state = gdk_atom_intern("_WIN_STATE", FALSE);
181 xa_atom = gdk_atom_intern("ATOM", FALSE);
182 xa_net_state = gdk_atom_intern("_NET_WM_STATE", FALSE);
183 xa_hints = gdk_atom_intern("WM_HINTS", FALSE);
185 /* Note: Starting with Gtk+-1.3.12, Gtk+ converts GdkAtoms
186 * to X atoms automatically when the type is ATOM.
188 state_list[0] = gdk_atom_intern("_NET_WM_STATE_STICKY", FALSE);
189 state_list[1] = gdk_atom_intern("_NET_WM_STATE_SKIP_PAGER",
190 FALSE);
191 state_list[2] = gdk_atom_intern("_NET_WM_STATE_SKIP_TASKBAR",
192 FALSE);
194 need_init = FALSE;
197 gdk_window_set_decorations(window, 0);
198 gdk_window_set_functions(window, 0);
200 /* Note: DON'T do gtk_window_stick(). Setting the state via
201 * gdk will override our other atoms (pager/taskbar).
204 /* Don't hide panel/pinboard windows initially (WIN_STATE_HIDDEN).
205 * Needed for IceWM - Christopher Arndt <chris.arndt@web.de>
207 set_cardinal_property(window, xa_state,
208 WIN_STATE_STICKY |
209 WIN_STATE_FIXED_POSITION | WIN_STATE_ARRANGE_IGNORE);
211 set_cardinal_property(window, xa_win_hints,
212 WIN_HINTS_SKIP_FOCUS | WIN_HINTS_SKIP_WINLIST |
213 WIN_HINTS_SKIP_TASKBAR);
215 gdk_property_change(window, xa_net_state, xa_atom, 32,
216 GDK_PROP_MODE_APPEND, (guchar *) state_list, 3);
218 g_return_if_fail(window != NULL);
220 values[0] = 1; /* InputHint */
221 values[1] = False;
223 gdk_property_change(window, xa_hints, xa_hints, 32,
224 GDK_PROP_MODE_REPLACE, (guchar *) values, 2);
227 gint hide_dialog_event(GtkWidget *widget, GdkEvent *event, gpointer window)
229 gtk_widget_hide((GtkWidget *) window);
231 return TRUE;
234 static gboolean error_idle_cb(gpointer data)
236 char **error = (char **) data;
238 report_error("%s", *error);
239 g_free(*error);
240 *error = NULL;
242 if (--number_of_windows == 0)
243 gtk_main_quit();
245 return FALSE;
248 /* Display an error with "ROX-Filer" as title next time we are idle.
249 * If multiple errors are reported this way before the window is opened,
250 * all are displayed in a single window.
251 * If an error is reported while the error window is open, it is discarded.
253 void delayed_error(const char *error, ...)
255 static char *delayed_error_data = NULL;
256 char *old, *new;
257 va_list args;
259 g_return_if_fail(error != NULL);
261 old = delayed_error_data;
263 va_start(args, error);
264 new = g_strdup_vprintf(error, args);
265 va_end(args);
267 if (old)
269 delayed_error_data = g_strconcat(old,
270 _("\n---\n"),
271 new, NULL);
272 g_free(old);
273 g_free(new);
275 else
277 delayed_error_data = new;
278 gtk_idle_add(error_idle_cb, &delayed_error_data);
280 number_of_windows++;
284 /* Load the file into memory. Return TRUE on success.
285 * Block is zero terminated (but this is not included in the length).
287 gboolean load_file(const char *pathname, char **data_out, long *length_out)
289 gsize len;
290 GError *error;
292 if (!g_file_get_contents(pathname, data_out, &len, &error))
294 delayed_error("%s", error ? error->message : pathname);
295 g_error_free(error);
296 return FALSE;
299 if (length_out)
300 *length_out = len;
301 return TRUE;
304 GtkWidget *new_help_button(HelpFunc show_help, gpointer data)
306 GtkWidget *b, *icon;
308 b = gtk_button_new();
309 gtk_button_set_relief(GTK_BUTTON(b), GTK_RELIEF_NONE);
310 icon = gtk_image_new_from_pixmap(im_help->pixmap, im_help->mask);
311 gtk_container_add(GTK_CONTAINER(b), icon);
312 g_signal_connect_swapped(b, "clicked", G_CALLBACK(show_help), data);
314 GTK_WIDGET_UNSET_FLAGS(b, GTK_CAN_FOCUS);
316 return b;
319 /* Read file into memory. Call parse_line(guchar *line) for each line
320 * in the file. Callback returns NULL on success, or an error message
321 * if something went wrong. Only the first error is displayed to the user.
323 void parse_file(const char *path, ParseFunc *parse_line)
325 char *data;
326 long length;
327 gboolean seen_error = FALSE;
329 if (load_file(path, &data, &length))
331 char *eol;
332 const char *error;
333 char *line = data;
334 int line_number = 1;
336 if (strncmp(data, "<?xml ", 6) == 0)
338 delayed_error(_("Attempt to read an XML file as "
339 "a text file. File '%s' may be "
340 "corrupted."), path);
341 return;
344 while (line && *line)
346 eol = strchr(line, '\n');
347 if (eol)
348 *eol = '\0';
350 error = parse_line(line);
352 if (error && !seen_error)
354 delayed_error(
355 _("Error in '%s' file at line %d: "
356 "\n\"%s\"\n"
357 "This may be due to upgrading from a previous version of "
358 "ROX-Filer. Open the Options window and click on Save.\n"
359 "Further errors will be ignored."),
360 path,
361 line_number,
362 error);
363 seen_error = TRUE;
366 if (!eol)
367 break;
368 line = eol + 1;
369 line_number++;
371 g_free(data);
375 /* Sets up a proxy window for DnD on the specified X window.
376 * Courtesy of Owen Taylor (taken from gmc).
378 gboolean setup_xdnd_proxy(guint32 xid, GdkWindow *proxy_window)
380 GdkAtom xdnd_proxy_atom;
381 Window proxy_xid;
382 Atom type;
383 int format;
384 unsigned long nitems, after;
385 Window *proxy_data;
386 Window proxy;
388 XGrabServer(GDK_DISPLAY());
390 xdnd_proxy_atom = gdk_atom_intern("XdndProxy", FALSE);
391 proxy_xid = GDK_WINDOW_XWINDOW(proxy_window);
392 type = None;
393 proxy = None;
395 gdk_error_trap_push();
397 /* Check if somebody else already owns drops on the root window */
399 XGetWindowProperty(GDK_DISPLAY(), xid,
400 gdk_x11_atom_to_xatom(xdnd_proxy_atom), 0,
401 1, False, AnyPropertyType,
402 &type, &format, &nitems, &after,
403 (guchar **) &proxy_data);
405 if (type != None)
407 if (format == 32 && nitems == 1)
408 proxy = *proxy_data;
410 XFree(proxy_data);
413 /* The property was set, now check if the window it points to exists
414 * and has a XdndProxy property pointing to itself.
416 if (proxy)
418 gint gdk_error_code;
420 XGetWindowProperty(GDK_DISPLAY(), proxy,
421 gdk_x11_atom_to_xatom(xdnd_proxy_atom),
422 0, 1, False, AnyPropertyType,
423 &type, &format, &nitems, &after,
424 (guchar **) &proxy_data);
426 gdk_error_code = gdk_error_trap_pop();
427 gdk_error_trap_push();
429 if (!gdk_error_code && type != None)
431 if (format == 32 && nitems == 1)
432 if (*proxy_data != proxy)
433 proxy = None;
435 XFree(proxy_data);
437 else
438 proxy = gdk_x11_atom_to_xatom(GDK_NONE);
441 if (!proxy)
443 /* OK, we can set the property to point to us */
444 /* TODO: Use gdk call? */
446 XChangeProperty(GDK_DISPLAY(), xid,
447 gdk_x11_atom_to_xatom(xdnd_proxy_atom),
448 gdk_x11_atom_to_xatom(gdk_atom_intern("WINDOW",
449 FALSE)),
450 32, PropModeReplace,
451 (guchar *) &proxy_xid, 1);
454 gdk_error_trap_pop();
456 XUngrabServer(GDK_DISPLAY());
457 gdk_flush();
459 if (!proxy)
461 /* Mark our window as a valid proxy window with a XdndProxy
462 * property pointing recursively;
464 XChangeProperty(GDK_DISPLAY(), proxy_xid,
465 gdk_x11_atom_to_xatom(xdnd_proxy_atom),
466 gdk_x11_atom_to_xatom(gdk_atom_intern("WINDOW",
467 FALSE)),
468 32, PropModeReplace,
469 (guchar *) &proxy_xid, 1);
472 return !proxy;
475 /* xid is the window (usually the root) which points to the proxy */
476 void release_xdnd_proxy(guint32 xid)
478 GdkAtom xdnd_proxy_atom;
480 xdnd_proxy_atom = gdk_atom_intern("XdndProxy", FALSE);
482 XDeleteProperty(GDK_DISPLAY(), xid,
483 gdk_x11_atom_to_xatom(xdnd_proxy_atom));
486 /* Looks for the proxy window to get root window clicks from the window
487 * manager. Taken from gmc. NULL if there is no proxy window.
489 GdkWindow *find_click_proxy_window(void)
491 GdkAtom click_proxy_atom;
492 Atom type;
493 int format;
494 unsigned long nitems, after;
495 Window *proxy_data;
496 Window proxy;
497 GdkWindow *proxy_gdk_window;
499 XGrabServer(GDK_DISPLAY());
501 click_proxy_atom = gdk_atom_intern("_WIN_DESKTOP_BUTTON_PROXY", FALSE);
502 type = None;
503 proxy = None;
505 gdk_error_trap_push();
507 /* Check if the proxy window exists */
509 XGetWindowProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
510 gdk_x11_atom_to_xatom(click_proxy_atom), 0,
511 1, False, AnyPropertyType,
512 &type, &format, &nitems, &after,
513 (guchar **) &proxy_data);
515 if (type != None)
517 if (format == 32 && nitems == 1)
518 proxy = *proxy_data;
520 XFree(proxy_data);
523 /* If the property was set, check if the window it points to exists
524 * and has a _WIN_DESKTOP_BUTTON_PROXY property pointing to itself.
527 if (proxy)
529 gint gdk_error_code;
530 XGetWindowProperty(GDK_DISPLAY(), proxy,
531 gdk_x11_atom_to_xatom(click_proxy_atom), 0,
532 1, False, AnyPropertyType,
533 &type, &format, &nitems, &after,
534 (guchar **) &proxy_data);
536 gdk_error_code = gdk_error_trap_pop();
537 gdk_error_trap_push();
539 if (!gdk_error_code && type != None)
541 if (format == 32 && nitems == 1)
542 if (*proxy_data != proxy)
543 proxy = gdk_x11_atom_to_xatom(GDK_NONE);
545 XFree(proxy_data);
547 else
548 proxy = gdk_x11_atom_to_xatom(GDK_NONE);
551 gdk_error_trap_pop();
553 XUngrabServer(GDK_DISPLAY());
554 gdk_flush();
556 if (proxy)
557 proxy_gdk_window = gdk_window_foreign_new(proxy);
558 else
559 proxy_gdk_window = NULL;
561 return proxy_gdk_window;
564 /* Returns the position of the pointer.
565 * TRUE if any modifier keys or mouse buttons are pressed.
567 gboolean get_pointer_xy(int *x, int *y)
569 unsigned int mask;
571 gdk_window_get_pointer(NULL, x, y, &mask);
573 return mask != 0;
576 #define DECOR_BORDER 32
578 /* Centre the window at these coords */
579 void centre_window(GdkWindow *window, int x, int y)
581 int w, h;
583 g_return_if_fail(window != NULL);
585 gdk_window_get_size(window, &w, &h);
587 x -= w / 2;
588 y -= h / 2;
590 gdk_window_move(window,
591 CLAMP(x, DECOR_BORDER, screen_width - w - DECOR_BORDER),
592 CLAMP(y, DECOR_BORDER, screen_height - h - DECOR_BORDER));
595 static GtkWidget *current_wink_widget = NULL;
596 static gint wink_timeout = -1; /* Called when it's time to stop */
597 static gulong wink_destroy; /* Called if the widget dies first */
599 static gboolean end_wink(gpointer data)
601 gtk_drag_unhighlight(current_wink_widget);
603 g_signal_handler_disconnect(current_wink_widget, wink_destroy);
605 current_wink_widget = NULL;
607 return FALSE;
610 static void cancel_wink(void)
612 gtk_timeout_remove(wink_timeout);
613 end_wink(NULL);
616 static void wink_widget_died(gpointer data)
618 current_wink_widget = NULL;
619 gtk_timeout_remove(wink_timeout);
622 /* Draw a black box around this widget, briefly.
623 * Note: uses the drag highlighting code for now.
625 void wink_widget(GtkWidget *widget)
627 g_return_if_fail(widget != NULL);
629 if (current_wink_widget)
630 cancel_wink();
632 current_wink_widget = widget;
633 gtk_drag_highlight(current_wink_widget);
635 wink_timeout = gtk_timeout_add(300, (GtkFunction) end_wink, NULL);
637 wink_destroy = g_signal_connect_swapped(widget, "destroy",
638 G_CALLBACK(wink_widget_died), NULL);
641 static gboolean idle_destroy_cb(GtkWidget *widget)
643 gtk_widget_unref(widget);
644 gtk_widget_destroy(widget);
645 return FALSE;
648 /* Destroy the widget in an idle callback */
649 void destroy_on_idle(GtkWidget *widget)
651 gtk_widget_ref(widget);
652 gtk_idle_add((GtkFunction) idle_destroy_cb, widget);
655 /* Spawn a child process (as spawn_full), and report errors.
656 * TRUE on success.
658 gboolean rox_spawn(const gchar *dir, const gchar **argv)
660 GError *error = NULL;
662 if (!g_spawn_async_with_pipes(dir, (gchar **) argv, NULL,
663 G_SPAWN_DO_NOT_REAP_CHILD |
664 G_SPAWN_SEARCH_PATH,
665 NULL, NULL, /* Child setup fn */
666 NULL, /* Child PID */
667 NULL, NULL, NULL, /* Standard pipes */
668 &error))
670 delayed_error("%s", error ? error->message : "(null)");
671 g_error_free(error);
673 return FALSE;
676 return TRUE;
679 /* Called before gtk_init(). Override default styles with our defaults,
680 * and override them with user choices, if any.
682 void add_default_styles(void)
684 gchar *rc_file;
686 rc_file = g_strconcat(app_dir, "/Styles", NULL);
687 gtk_rc_add_default_file(rc_file);
688 g_free(rc_file);
690 rc_file = choices_find_path_load("Styles", PROJECT);
691 if (rc_file)
693 gtk_rc_add_default_file(rc_file);
694 g_free(rc_file);
698 GtkWidget *button_new_mixed(const char *stock, const char *message)
700 GtkWidget *button, *align, *image, *hbox, *label;
702 button = gtk_button_new();
703 label = gtk_label_new_with_mnemonic(message);
704 gtk_label_set_mnemonic_widget(GTK_LABEL(label), button);
706 image = gtk_image_new_from_stock(stock, GTK_ICON_SIZE_BUTTON);
707 hbox = gtk_hbox_new(FALSE, 2);
709 align = gtk_alignment_new(0.5, 0.5, 0.0, 0.0);
711 gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
712 gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 0);
714 gtk_container_add(GTK_CONTAINER(button), align);
715 gtk_container_add(GTK_CONTAINER(align), hbox);
716 gtk_widget_show_all(align);
718 return button;