r3423: Added Xinerama support (Tony Houghton).
[rox-filer.git] / ROX-Filer / src / gui_support.c
blob07345e8d461649280bf59577244d29ff27cb0610
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2003, 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 gint screen_width, screen_height;
51 gint n_monitors;
52 GdkRectangle *monitor_geom = NULL;
53 gint monitor_width, monitor_height;
55 static GdkAtom xa_cardinal;
57 static GtkWidget *current_dialog = NULL;
59 static GtkWidget *tip_widget = NULL;
60 static time_t tip_time = 0; /* Time tip widget last closed */
61 static gint tip_timeout = 0; /* When primed */
63 /* Static prototypes */
64 static void run_error_info_dialog(GtkMessageType type, const char *message,
65 va_list args);
66 static GType simple_image_get_type(void);
68 void gui_store_screen_geometry(GdkScreen *screen)
70 gint mon;
72 monitor_width = monitor_height = G_MAXINT;
73 n_monitors = gdk_screen_get_n_monitors(screen);
74 if (monitor_geom)
75 g_free(monitor_geom);
76 monitor_geom = g_new(GdkRectangle, n_monitors);
77 for (mon = 0; mon < n_monitors; ++mon)
79 gdk_screen_get_monitor_geometry(screen, mon,
80 &monitor_geom[mon]);
81 if (monitor_geom[mon].width < monitor_width)
82 monitor_width = monitor_geom[mon].width;
83 if (monitor_geom[mon].height < monitor_height)
84 monitor_height = monitor_geom[mon].height;
89 void gui_support_init()
91 gpointer klass;
93 xa_cardinal = gdk_atom_intern("CARDINAL", FALSE);
95 /* This call starts returning strange values after a while, so get
96 * the result here during init.
98 gdk_drawable_get_size(gdk_get_default_root_window(),
99 &screen_width, &screen_height);
100 gui_store_screen_geometry(gdk_screen_get_default());
102 /* Work around the scrollbar placement bug */
103 klass = g_type_class_ref(gtk_scrolled_window_get_type());
104 ((GtkScrolledWindowClass *) klass)->scrollbar_spacing = 0;
105 /* (don't unref, ever) */
108 /* Open a modal dialog box showing a message.
109 * The user can choose from a selection of buttons at the bottom.
110 * Returns -1 if the window is destroyed, or the number of the button
111 * if one is clicked (starting from zero).
113 * If a dialog is already open, returns -1 without waiting AND
114 * brings the current dialog to the front.
116 * Each button has two arguments, a GTK_STOCK icon and some text. If the
117 * text is NULL, the stock's text is used.
119 int get_choice(const char *title,
120 const char *message,
121 int number_of_buttons, ...)
123 GtkWidget *dialog;
124 GtkWidget *button = NULL;
125 int i, retval;
126 va_list ap;
128 if (current_dialog)
130 gtk_widget_hide(current_dialog);
131 gtk_widget_show(current_dialog);
132 return -1;
135 current_dialog = dialog = gtk_message_dialog_new(NULL,
136 GTK_DIALOG_MODAL,
137 GTK_MESSAGE_QUESTION,
138 GTK_BUTTONS_NONE,
139 "%s", message);
140 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
142 va_start(ap, number_of_buttons);
144 for (i = 0; i < number_of_buttons; i++)
146 const char *stock = va_arg(ap, char *);
147 const char *text = va_arg(ap, char *);
149 if (text)
150 button = button_new_mixed(stock, text);
151 else
152 button = gtk_button_new_from_stock(stock);
154 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
155 gtk_widget_show(button);
157 gtk_dialog_add_action_widget(GTK_DIALOG(current_dialog),
158 button, i);
161 gtk_window_set_title(GTK_WINDOW(dialog), title);
162 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
164 gtk_dialog_set_default_response(GTK_DIALOG(dialog), i - 1);
166 va_end(ap);
168 retval = gtk_dialog_run(GTK_DIALOG(dialog));
169 if (retval == GTK_RESPONSE_NONE)
170 retval = -1;
171 gtk_widget_destroy(dialog);
173 current_dialog = NULL;
175 return retval;
178 void info_message(const char *message, ...)
180 va_list args;
182 va_start(args, message);
184 run_error_info_dialog(GTK_MESSAGE_INFO, message, args);
187 /* Display a message in a window with "ROX-Filer" as title */
188 void report_error(const char *message, ...)
190 va_list args;
192 va_start(args, message);
194 run_error_info_dialog(GTK_MESSAGE_ERROR, message, args);
197 void set_cardinal_property(GdkWindow *window, GdkAtom prop, guint32 value)
199 gdk_property_change(window, prop, xa_cardinal, 32,
200 GDK_PROP_MODE_REPLACE, (gchar *) &value, 1);
203 /* NB: Also used for pinned icons.
204 * TODO: Set the level here too.
206 void make_panel_window(GtkWidget *widget)
208 static gboolean need_init = TRUE;
209 static GdkAtom xa_state, xa_atom, xa_net_state, xa_hints, xa_win_hints;
210 static GdkAtom xa_NET_WM_DESKTOP;
211 static GdkAtom state_list[3];
212 GdkWindow *window = widget->window;
213 gint32 wm_hints_values[] = {1, False, 0, 0, 0, 0, 0, 0};
214 GdkAtom wm_protocols[2];
216 g_return_if_fail(window != NULL);
218 if (o_override_redirect.int_value)
220 gdk_window_set_override_redirect(window, TRUE);
221 return;
224 if (need_init)
226 xa_win_hints = gdk_atom_intern("_WIN_HINTS", FALSE);
227 xa_state = gdk_atom_intern("_WIN_STATE", FALSE);
228 xa_atom = gdk_atom_intern("ATOM", FALSE);
229 xa_net_state = gdk_atom_intern("_NET_WM_STATE", FALSE);
230 xa_hints = gdk_atom_intern("WM_HINTS", FALSE);
231 xa_NET_WM_DESKTOP = gdk_atom_intern("_NET_WM_DESKTOP", FALSE);
233 /* Note: Starting with Gtk+-1.3.12, Gtk+ converts GdkAtoms
234 * to X atoms automatically when the type is ATOM.
236 state_list[0] = gdk_atom_intern("_NET_WM_STATE_STICKY", FALSE);
237 state_list[1] = gdk_atom_intern("_NET_WM_STATE_SKIP_PAGER",
238 FALSE);
239 state_list[2] = gdk_atom_intern("_NET_WM_STATE_SKIP_TASKBAR",
240 FALSE);
242 need_init = FALSE;
245 gdk_window_set_decorations(window, 0);
246 gdk_window_set_functions(window, 0);
247 gtk_window_set_resizable(GTK_WINDOW(widget), FALSE);
249 /* Note: DON'T do gtk_window_stick(). Setting the state via
250 * gdk will override our other atoms (pager/taskbar).
253 /* Don't hide panel/pinboard windows initially (WIN_STATE_HIDDEN).
254 * Needed for IceWM - Christopher Arndt <chris.arndt@web.de>
256 set_cardinal_property(window, xa_state,
257 WIN_STATE_STICKY |
258 WIN_STATE_FIXED_POSITION | WIN_STATE_ARRANGE_IGNORE);
260 set_cardinal_property(window, xa_win_hints,
261 WIN_HINTS_SKIP_FOCUS | WIN_HINTS_SKIP_WINLIST |
262 WIN_HINTS_SKIP_TASKBAR);
264 /* Appear on all workspaces */
265 set_cardinal_property(window, xa_NET_WM_DESKTOP, 0xffffffff);
267 gdk_property_change(window, xa_net_state, xa_atom, 32,
268 GDK_PROP_MODE_APPEND, (guchar *) state_list, 3);
270 gdk_property_change(window, xa_hints, xa_hints, 32,
271 GDK_PROP_MODE_REPLACE, (guchar *) wm_hints_values,
272 sizeof(wm_hints_values) / sizeof(gint32));
274 wm_protocols[0] = gdk_atom_intern("WM_DELETE_WINDOW", FALSE);
275 wm_protocols[1] = gdk_atom_intern("_NET_WM_PING", FALSE);
276 gdk_property_change(window,
277 gdk_atom_intern("WM_PROTOCOLS", FALSE), xa_atom, 32,
278 GDK_PROP_MODE_REPLACE, (guchar *) wm_protocols,
279 sizeof(wm_protocols) / sizeof(GdkAtom));
282 static gboolean error_idle_cb(gpointer data)
284 char **error = (char **) data;
286 report_error("%s", *error);
287 null_g_free(error);
289 one_less_window();
290 return FALSE;
293 /* Display an error with "ROX-Filer" as title next time we are idle.
294 * If multiple errors are reported this way before the window is opened,
295 * all are displayed in a single window.
296 * If an error is reported while the error window is open, it is discarded.
298 void delayed_error(const char *error, ...)
300 static char *delayed_error_data = NULL;
301 char *old, *new;
302 va_list args;
304 g_return_if_fail(error != NULL);
306 old = delayed_error_data;
308 va_start(args, error);
309 new = g_strdup_vprintf(error, args);
310 va_end(args);
312 if (old)
314 delayed_error_data = g_strconcat(old,
315 _("\n---\n"),
316 new, NULL);
317 g_free(old);
318 g_free(new);
320 else
322 delayed_error_data = new;
323 gtk_idle_add(error_idle_cb, &delayed_error_data);
325 number_of_windows++;
329 /* Load the file into memory. Return TRUE on success.
330 * Block is zero terminated (but this is not included in the length).
332 gboolean load_file(const char *pathname, char **data_out, long *length_out)
334 gsize len;
335 GError *error = NULL;
337 if (!g_file_get_contents(pathname, data_out, &len, &error))
339 delayed_error("%s", error->message);
340 g_error_free(error);
341 return FALSE;
344 if (length_out)
345 *length_out = len;
346 return TRUE;
349 GtkWidget *new_help_button(HelpFunc show_help, gpointer data)
351 GtkWidget *b, *icon;
353 b = gtk_button_new();
354 gtk_button_set_relief(GTK_BUTTON(b), GTK_RELIEF_NONE);
355 icon = gtk_image_new_from_stock(GTK_STOCK_DIALOG_INFO,
356 GTK_ICON_SIZE_SMALL_TOOLBAR);
357 gtk_container_add(GTK_CONTAINER(b), icon);
358 g_signal_connect_swapped(b, "clicked", G_CALLBACK(show_help), data);
360 GTK_WIDGET_UNSET_FLAGS(b, GTK_CAN_FOCUS);
362 return b;
365 /* Read file into memory. Call parse_line(guchar *line) for each line
366 * in the file. Callback returns NULL on success, or an error message
367 * if something went wrong. Only the first error is displayed to the user.
369 void parse_file(const char *path, ParseFunc *parse_line)
371 char *data;
372 long length;
373 gboolean seen_error = FALSE;
375 if (load_file(path, &data, &length))
377 char *eol;
378 const char *error;
379 char *line = data;
380 int line_number = 1;
382 if (strncmp(data, "<?xml ", 6) == 0)
384 delayed_error(_("Attempt to read an XML file as "
385 "a text file. File '%s' may be "
386 "corrupted."), path);
387 return;
390 while (line && *line)
392 eol = strchr(line, '\n');
393 if (eol)
394 *eol = '\0';
396 error = parse_line(line);
398 if (error && !seen_error)
400 delayed_error(
401 _("Error in '%s' file at line %d: "
402 "\n\"%s\"\n"
403 "This may be due to upgrading from a previous version of "
404 "ROX-Filer. Open the Options window and click on Save.\n"
405 "Further errors will be ignored."),
406 path,
407 line_number,
408 error);
409 seen_error = TRUE;
412 if (!eol)
413 break;
414 line = eol + 1;
415 line_number++;
417 g_free(data);
421 /* Returns the position of the pointer.
422 * TRUE if any modifier keys or mouse buttons are pressed.
424 gboolean get_pointer_xy(int *x, int *y)
426 unsigned int mask;
428 gdk_window_get_pointer(NULL, x, y, &mask);
430 return mask != 0;
433 #define DECOR_BORDER 32
435 /* Centre the window at these coords */
436 void centre_window(GdkWindow *window, int x, int y)
438 int w, h;
440 g_return_if_fail(window != NULL);
442 gdk_drawable_get_size(window, &w, &h);
444 x -= w / 2;
445 y -= h / 2;
447 gdk_window_move(window,
448 CLAMP(x, DECOR_BORDER, screen_width - w - DECOR_BORDER),
449 CLAMP(y, DECOR_BORDER, screen_height - h - DECOR_BORDER));
452 static void run_error_info_dialog(GtkMessageType type, const char *message,
453 va_list args)
455 GtkWidget *dialog;
456 gchar *s;
458 g_return_if_fail(message != NULL);
460 s = g_strdup_vprintf(message, args);
461 va_end(args);
463 dialog = gtk_message_dialog_new(NULL,
464 GTK_DIALOG_MODAL,
465 type,
466 GTK_BUTTONS_OK,
467 "%s", s);
468 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
469 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
470 gtk_dialog_run(GTK_DIALOG(dialog));
471 gtk_widget_destroy(dialog);
473 g_free(s);
476 static GtkWidget *current_wink_widget = NULL;
477 static gint wink_timeout = -1; /* Called when it's time to stop */
478 static gulong wink_destroy; /* Called if the widget dies first */
480 static gboolean end_wink(gpointer data)
482 gtk_drag_unhighlight(current_wink_widget);
484 g_signal_handler_disconnect(current_wink_widget, wink_destroy);
486 current_wink_widget = NULL;
488 return FALSE;
491 static void cancel_wink(void)
493 gtk_timeout_remove(wink_timeout);
494 end_wink(NULL);
497 static void wink_widget_died(gpointer data)
499 current_wink_widget = NULL;
500 gtk_timeout_remove(wink_timeout);
503 /* Draw a black box around this widget, briefly.
504 * Note: uses the drag highlighting code for now.
506 void wink_widget(GtkWidget *widget)
508 g_return_if_fail(widget != NULL);
510 if (current_wink_widget)
511 cancel_wink();
513 current_wink_widget = widget;
514 gtk_drag_highlight(current_wink_widget);
516 wink_timeout = gtk_timeout_add(300, (GtkFunction) end_wink, NULL);
518 wink_destroy = g_signal_connect_swapped(widget, "destroy",
519 G_CALLBACK(wink_widget_died), NULL);
522 static gboolean idle_destroy_cb(GtkWidget *widget)
524 gtk_widget_unref(widget);
525 gtk_widget_destroy(widget);
526 return FALSE;
529 /* Destroy the widget in an idle callback */
530 void destroy_on_idle(GtkWidget *widget)
532 gtk_widget_ref(widget);
533 gtk_idle_add((GtkFunction) idle_destroy_cb, widget);
536 /* Spawn a child process (as spawn_full), and report errors.
537 * Returns the child's PID on succes, or 0 on failure.
539 gint rox_spawn(const gchar *dir, const gchar **argv)
541 GError *error = NULL;
542 gint pid = 0;
544 if (!g_spawn_async_with_pipes(dir, (gchar **) argv, NULL,
545 G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_STDOUT_TO_DEV_NULL |
546 G_SPAWN_SEARCH_PATH,
547 NULL, NULL, /* Child setup fn */
548 &pid, /* Child PID */
549 NULL, NULL, NULL, /* Standard pipes */
550 &error))
552 delayed_error("%s", error ? error->message : "(null)");
553 g_error_free(error);
555 return 0;
558 return pid;
561 GtkWidget *button_new_image_text(GtkWidget *image, const char *message)
563 GtkWidget *button, *align, *hbox, *label;
565 button = gtk_button_new();
566 label = gtk_label_new_with_mnemonic(message);
567 gtk_label_set_mnemonic_widget(GTK_LABEL(label), button);
569 hbox = gtk_hbox_new(FALSE, 2);
571 align = gtk_alignment_new(0.5, 0.5, 0.0, 0.0);
573 gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
574 gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 0);
576 gtk_container_add(GTK_CONTAINER(button), align);
577 gtk_container_add(GTK_CONTAINER(align), hbox);
578 gtk_widget_show_all(align);
580 return button;
583 GtkWidget *button_new_mixed(const char *stock, const char *message)
585 return button_new_image_text(gtk_image_new_from_stock(stock,
586 GTK_ICON_SIZE_BUTTON),
587 message);
590 /* Highlight entry in red if 'error' is TRUE */
591 void entry_set_error(GtkWidget *entry, gboolean error)
593 GdkColor red = {0, 0xffff, 0, 0};
594 static gboolean need_init = TRUE;
595 static GdkColor normal;
597 if (need_init)
599 normal = entry->style->text[GTK_STATE_NORMAL];
600 need_init = FALSE;
603 gtk_widget_modify_text(entry, GTK_STATE_NORMAL, error ? &red : &normal);
606 /* Change stacking position of higher to be just above lower.
607 * If lower is NULL, put higher at the bottom of the stack.
609 void window_put_just_above(GdkWindow *higher, GdkWindow *lower)
611 if (o_override_redirect.int_value && lower)
613 XWindowChanges restack;
615 gdk_error_trap_push();
617 restack.stack_mode = Above;
619 restack.sibling = GDK_WINDOW_XWINDOW(lower);
621 XConfigureWindow(gdk_display, GDK_WINDOW_XWINDOW(higher),
622 CWSibling | CWStackMode, &restack);
624 gdk_flush();
625 if (gdk_error_trap_pop())
626 g_warning("window_put_just_above()\n");
628 else
629 gdk_window_lower(higher); /* To bottom of stack */
632 /* Copied from Gtk */
633 static GtkFixedChild* fixed_get_child(GtkFixed *fixed, GtkWidget *widget)
635 GList *children;
637 children = fixed->children;
638 while (children)
640 GtkFixedChild *child;
642 child = children->data;
643 children = children->next;
645 if (child->widget == widget)
646 return child;
649 return NULL;
652 /* Like gtk_fixed_move(), except not insanely slow */
653 void fixed_move_fast(GtkFixed *fixed, GtkWidget *widget, int x, int y)
655 GtkFixedChild *child;
657 child = fixed_get_child(fixed, widget);
659 g_assert(child);
661 gtk_widget_freeze_child_notify(widget);
663 child->x = x;
664 gtk_widget_child_notify(widget, "x");
666 child->y = y;
667 gtk_widget_child_notify(widget, "y");
669 gtk_widget_thaw_child_notify(widget);
671 if (GTK_WIDGET_VISIBLE(widget) && GTK_WIDGET_VISIBLE(fixed))
673 int border_width = GTK_CONTAINER(fixed)->border_width;
674 GtkAllocation child_allocation;
675 GtkRequisition child_requisition;
677 gtk_widget_get_child_requisition(child->widget,
678 &child_requisition);
679 child_allocation.x = child->x + border_width;
680 child_allocation.y = child->y + border_width;
682 child_allocation.x += GTK_WIDGET(fixed)->allocation.x;
683 child_allocation.y += GTK_WIDGET(fixed)->allocation.y;
685 child_allocation.width = child_requisition.width;
686 child_allocation.height = child_requisition.height;
687 gtk_widget_size_allocate(child->widget, &child_allocation);
691 /* Draw the black border */
692 static gint tooltip_draw(GtkWidget *w)
694 gdk_draw_rectangle(w->window, w->style->fg_gc[w->state], FALSE, 0, 0,
695 w->allocation.width - 1, w->allocation.height - 1);
697 return FALSE;
700 /* When the tips window closed, record the time. If we try to open another
701 * tip soon, it will appear more quickly.
703 static void tooltip_destroyed(gpointer data)
705 time(&tip_time);
708 /* Display a tooltip-like widget near the pointer with 'text'. If 'text' is
709 * NULL, close any current tooltip.
711 void tooltip_show(guchar *text)
713 GtkWidget *label;
714 int x, y, py;
715 int w, h;
717 if (tip_timeout)
719 gtk_timeout_remove(tip_timeout);
720 tip_timeout = 0;
723 if (tip_widget)
725 gtk_widget_destroy(tip_widget);
726 tip_widget = NULL;
729 if (!text)
730 return;
732 /* Show the tip */
733 tip_widget = gtk_window_new(GTK_WINDOW_POPUP);
734 gtk_widget_set_app_paintable(tip_widget, TRUE);
735 gtk_widget_set_name(tip_widget, "gtk-tooltips");
737 g_signal_connect_swapped(tip_widget, "expose_event",
738 G_CALLBACK(tooltip_draw), tip_widget);
740 label = gtk_label_new(text);
741 gtk_misc_set_padding(GTK_MISC(label), 4, 2);
742 gtk_container_add(GTK_CONTAINER(tip_widget), label);
743 gtk_widget_show(label);
744 gtk_widget_realize(tip_widget);
746 w = tip_widget->allocation.width;
747 h = tip_widget->allocation.height;
748 gdk_window_get_pointer(NULL, &x, &py, NULL);
750 x -= w / 2;
751 y = py + 12; /* I don't know the pointer height so I use a constant */
753 /* Now check for screen boundaries */
754 x = CLAMP(x, 0, screen_width - w);
755 y = CLAMP(y, 0, screen_height - h);
757 /* And again test if pointer is over the tooltip window */
758 if (py >= y && py <= y + h)
759 y = py - h- 2;
760 gtk_window_move(GTK_WINDOW(tip_widget), x, y);
761 gtk_widget_show(tip_widget);
763 g_signal_connect_swapped(tip_widget, "destroy",
764 G_CALLBACK(tooltip_destroyed), NULL);
765 time(&tip_time);
768 /* Call callback(user_data) after a while, unless cancelled.
769 * Object is refd now and unref when cancelled / after callback called.
771 void tooltip_prime(GtkFunction callback, GObject *object)
773 time_t now;
774 int delay;
776 g_return_if_fail(tip_timeout == 0);
778 time(&now);
779 delay = now - tip_time > 2 ? 1000 : 200;
781 g_object_ref(object);
782 tip_timeout = gtk_timeout_add_full(delay,
783 (GtkFunction) callback,
784 NULL,
785 object,
786 g_object_unref);
789 /* Like gtk_widget_modify_font, but copes with font_desc == NULL */
790 void widget_modify_font(GtkWidget *widget, PangoFontDescription *font_desc)
792 GtkRcStyle *rc_style;
794 g_return_if_fail(GTK_IS_WIDGET(widget));
796 rc_style = gtk_widget_get_modifier_style(widget);
798 if (rc_style->font_desc)
799 pango_font_description_free(rc_style->font_desc);
801 rc_style->font_desc = font_desc
802 ? pango_font_description_copy(font_desc)
803 : NULL;
805 gtk_widget_modify_style(widget, rc_style);
808 /* Confirm the action with the user. If action is NULL, the text from stock
809 * is used.
811 gboolean confirm(const gchar *message, const gchar *stock, const gchar *action)
813 return get_choice(PROJECT, message, 2,
814 GTK_STOCK_CANCEL, NULL,
815 stock, action) == 1;
818 struct _Radios {
819 GList *widgets;
821 void (*changed)(gpointer data);
822 gpointer changed_data;
825 /* Create a new set of radio buttons.
826 * Use radios_add to add options, then radios_pack to put them into something.
827 * The radios object will self-destruct with the first widget it contains.
828 * changed(data) is called (if not NULL) when pack is called, and on any
829 * change after that.
831 Radios *radios_new(void (*changed)(gpointer data), gpointer data)
833 Radios *radios;
835 radios = g_new(Radios, 1);
837 radios->widgets = NULL;
838 radios->changed = changed;
839 radios->changed_data = data;
841 return radios;
844 static void radios_free(GtkWidget *radio, Radios *radios)
846 g_return_if_fail(radios != NULL);
848 g_list_free(radios->widgets);
849 g_free(radios);
852 void radios_add(Radios *radios, const gchar *tip, gint value,
853 const gchar *label, ...)
855 GtkWidget *radio;
856 GSList *group = NULL;
857 gchar *s;
858 va_list args;
860 g_return_if_fail(radios != NULL);
861 g_return_if_fail(label != NULL);
863 va_start(args, label);
864 s = g_strdup_vprintf(label, args);
865 va_end(args);
867 if (radios->widgets)
869 GtkRadioButton *first = GTK_RADIO_BUTTON(radios->widgets->data);
870 group = gtk_radio_button_get_group(first);
873 radio = gtk_radio_button_new_with_label(group, s);
874 gtk_label_set_line_wrap(GTK_LABEL(GTK_BIN(radio)->child), TRUE);
875 gtk_widget_show(radio);
876 if (tip)
877 gtk_tooltips_set_tip(tooltips, radio, tip, NULL);
878 if (!group)
879 g_signal_connect(G_OBJECT(radio), "destroy",
880 G_CALLBACK(radios_free), radios);
882 radios->widgets = g_list_prepend(radios->widgets, radio);
883 g_object_set_data(G_OBJECT(radio), "rox-radios-value",
884 GINT_TO_POINTER(value));
887 static void radio_toggled(GtkToggleButton *button, Radios *radios)
889 g_return_if_fail(radios != NULL);
891 if (radios->changed)
892 radios->changed(radios->changed_data);
895 void radios_pack(Radios *radios, GtkBox *box)
897 GList *next;
899 g_return_if_fail(radios != NULL);
901 for (next = g_list_last(radios->widgets); next; next = next->prev)
903 GtkWidget *button = GTK_WIDGET(next->data);
905 gtk_box_pack_start(box, button, FALSE, TRUE, 0);
906 g_signal_connect(button, "toggled",
907 G_CALLBACK(radio_toggled), radios);
909 radio_toggled(NULL, radios);
912 void radios_set_value(Radios *radios, gint value)
914 GList *next;
916 g_return_if_fail(radios != NULL);
918 for (next = radios->widgets; next; next = next->next)
920 GtkToggleButton *radio = GTK_TOGGLE_BUTTON(next->data);
921 int radio_value;
923 radio_value = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(radio),
924 "rox-radios-value"));
926 if (radio_value == value)
928 gtk_toggle_button_set_active(radio, TRUE);
929 return;
933 g_warning("Value %d not in radio group!", value);
936 gint radios_get_value(Radios *radios)
938 GList *next;
940 g_return_val_if_fail(radios != NULL, -1);
942 for (next = radios->widgets; next; next = next->next)
944 GtkToggleButton *radio = GTK_TOGGLE_BUTTON(next->data);
946 if (gtk_toggle_button_get_active(radio))
947 return GPOINTER_TO_INT(g_object_get_data(
948 G_OBJECT(radio), "rox-radios-value"));
951 g_warning("Nothing in the radio group is selected!");
953 return -1;
956 /* Convert a list of URIs as a string into a GList of URIs.
957 * Lines beginning with # are skipped.
958 * The text block passed in is zero terminated (after the final CRLF)
960 GList *uri_list_to_glist(const char *uri_list)
962 GList *list = NULL;
964 while (*uri_list)
966 char *linebreak;
967 char *uri;
968 int length;
970 linebreak = strchr(uri_list, 13);
972 if (!linebreak || linebreak[1] != 10)
974 delayed_error("uri_list_to_glist: %s",
975 _("Incorrect or missing line "
976 "break in text/uri-list data"));
977 return list;
980 length = linebreak - uri_list;
982 if (length && uri_list[0] != '#')
984 char *tmp;
985 tmp = g_strndup(uri_list, length);
986 uri = unescape_uri(tmp);
987 g_free(tmp);
988 list = g_list_append(list, uri);
991 uri_list = linebreak + 2;
994 return list;
997 typedef struct _SimpleImageClass SimpleImageClass;
998 typedef struct _SimpleImage SimpleImage;
1000 struct _SimpleImageClass {
1001 GtkWidgetClass parent;
1004 struct _SimpleImage {
1005 GtkWidget widget;
1007 GdkPixbuf *pixbuf;
1008 int width, height;
1011 #define SIMPLE_IMAGE(obj) (GTK_CHECK_CAST((obj), \
1012 simple_image_get_type(), SimpleImage))
1014 static void simple_image_finialize(GObject *object)
1016 SimpleImage *image = SIMPLE_IMAGE(object);
1018 g_object_unref(G_OBJECT(image->pixbuf));
1019 image->pixbuf = NULL;
1022 static void simple_image_size_request(GtkWidget *widget,
1023 GtkRequisition *requisition)
1025 SimpleImage *image = (SimpleImage *) widget;
1027 requisition->width = image->width;
1028 requisition->height = image->height;
1031 /* Render a pixbuf without messing up the clipping */
1032 void render_pixbuf(GdkPixbuf *pixbuf, GdkDrawable *target, GdkGC *gc,
1033 int x, int y, int width, int height)
1035 #if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION > 1
1036 gdk_draw_pixbuf(target, gc, pixbuf, 0, 0, x, y, width, height,
1037 GDK_RGB_DITHER_NORMAL, 0, 0);
1039 #else
1040 static gboolean warned = FALSE;
1041 if (gtk_minor_version == 0)
1042 GDK_DRAWABLE_GET_CLASS(target)->_draw_pixbuf(target, gc, pixbuf,
1043 0, 0, x, y, width, height,
1044 GDK_RGB_DITHER_NORMAL, 0, 0);
1045 else if (!warned)
1047 delayed_error(_("Pinboard icons cannot be drawn because "
1048 "ROX-Filer was compiled against GTK+-2.0 "
1049 "but is running with GTK+-2.2. Please "
1050 "recompile it."));
1051 warned = TRUE;
1053 #endif
1056 static gint simple_image_expose(GtkWidget *widget, GdkEventExpose *event)
1058 SimpleImage *image = (SimpleImage *) widget;
1059 int x;
1061 gdk_gc_set_clip_region(widget->style->black_gc, event->region);
1063 x = widget->allocation.x +
1064 (widget->allocation.width - image->width) / 2;
1066 render_pixbuf(image->pixbuf, widget->window, widget->style->black_gc,
1067 x, widget->allocation.y,
1068 image->width, image->height);
1070 gdk_gc_set_clip_region(widget->style->black_gc, NULL);
1071 return FALSE;
1074 static void simple_image_class_init(gpointer gclass, gpointer data)
1076 GObjectClass *object = (GObjectClass *) gclass;
1077 GtkWidgetClass *widget = (GtkWidgetClass *) gclass;
1079 object->finalize = simple_image_finialize;
1080 widget->size_request = simple_image_size_request;
1081 widget->expose_event = simple_image_expose;
1084 static void simple_image_init(GTypeInstance *object, gpointer gclass)
1086 GTK_WIDGET_SET_FLAGS(object, GTK_NO_WINDOW);
1089 static GType simple_image_get_type(void)
1091 static GType type = 0;
1093 if (!type)
1095 static const GTypeInfo info =
1097 sizeof (SimpleImageClass),
1098 NULL, /* base_init */
1099 NULL, /* base_finalise */
1100 simple_image_class_init,
1101 NULL, /* class_finalise */
1102 NULL, /* class_data */
1103 sizeof(SimpleImage),
1104 0, /* n_preallocs */
1105 simple_image_init,
1108 type = g_type_register_static(gtk_widget_get_type(),
1109 "SimpleImage", &info, 0);
1112 return type;
1115 GtkWidget *simple_image_new(GdkPixbuf *pixbuf)
1117 SimpleImage *image;
1119 g_return_val_if_fail(pixbuf != NULL, NULL);
1121 image = g_object_new(simple_image_get_type(), NULL);
1123 image->pixbuf = pixbuf;
1124 g_object_ref(G_OBJECT(pixbuf));
1126 image->width = gdk_pixbuf_get_width(pixbuf);
1127 image->height = gdk_pixbuf_get_height(pixbuf);
1129 return GTK_WIDGET(image);