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)
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>
35 #include <X11/Xatom.h>
38 #include <gdk/gdkkeysyms.h>
43 #include "gui_support.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 /* Static prototypes */
61 static void run_error_info_dialog(GtkMessageType type
, const char *message
,
63 static GType
simple_image_get_type(void);
65 void gui_support_init()
69 xa_cardinal
= gdk_atom_intern("CARDINAL", FALSE
);
71 /* This call starts returning strange values after a while, so get
72 * the result here during init.
74 gdk_drawable_get_size(gdk_get_default_root_window(),
75 &screen_width
, &screen_height
);
77 /* Work around the scrollbar placement bug */
78 klass
= g_type_class_ref(gtk_scrolled_window_get_type());
79 ((GtkScrolledWindowClass
*) klass
)->scrollbar_spacing
= 0;
80 /* (don't unref, ever) */
83 /* Open a modal dialog box showing a message.
84 * The user can choose from a selection of buttons at the bottom.
85 * Returns -1 if the window is destroyed, or the number of the button
86 * if one is clicked (starting from zero).
88 * If a dialog is already open, returns -1 without waiting AND
89 * brings the current dialog to the front.
91 * Each button has two arguments, a GTK_STOCK icon and some text. If the
92 * text is NULL, the stock's text is used.
94 int get_choice(const char *title
,
96 int number_of_buttons
, ...)
99 GtkWidget
*button
= NULL
;
105 gtk_widget_hide(current_dialog
);
106 gtk_widget_show(current_dialog
);
110 current_dialog
= dialog
= gtk_message_dialog_new(NULL
,
112 GTK_MESSAGE_QUESTION
,
115 gtk_window_set_position(GTK_WINDOW(dialog
), GTK_WIN_POS_CENTER
);
117 va_start(ap
, number_of_buttons
);
119 for (i
= 0; i
< number_of_buttons
; i
++)
121 const char *stock
= va_arg(ap
, char *);
122 const char *text
= va_arg(ap
, char *);
125 button
= button_new_mixed(stock
, text
);
127 button
= gtk_button_new_from_stock(stock
);
129 GTK_WIDGET_SET_FLAGS(button
, GTK_CAN_DEFAULT
);
130 gtk_widget_show(button
);
132 gtk_dialog_add_action_widget(GTK_DIALOG(current_dialog
),
136 gtk_window_set_title(GTK_WINDOW(dialog
), title
);
137 gtk_window_set_position(GTK_WINDOW(dialog
), GTK_WIN_POS_CENTER
);
139 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), i
- 1);
143 retval
= gtk_dialog_run(GTK_DIALOG(dialog
));
144 if (retval
== GTK_RESPONSE_NONE
)
146 gtk_widget_destroy(dialog
);
148 current_dialog
= NULL
;
153 void info_message(const char *message
, ...)
157 va_start(args
, message
);
159 run_error_info_dialog(GTK_MESSAGE_INFO
, message
, args
);
162 /* Display a message in a window with "ROX-Filer" as title */
163 void report_error(const char *message
, ...)
167 va_start(args
, message
);
169 run_error_info_dialog(GTK_MESSAGE_ERROR
, message
, args
);
172 void set_cardinal_property(GdkWindow
*window
, GdkAtom prop
, guint32 value
)
174 gdk_property_change(window
, prop
, xa_cardinal
, 32,
175 GDK_PROP_MODE_REPLACE
, (gchar
*) &value
, 1);
178 /* NB: Also used for pinned icons.
179 * TODO: Set the level here too.
181 void make_panel_window(GtkWidget
*widget
)
183 static gboolean need_init
= TRUE
;
184 static GdkAtom xa_state
, xa_atom
, xa_net_state
, xa_hints
, xa_win_hints
;
185 static GdkAtom xa_NET_WM_DESKTOP
;
186 static GdkAtom state_list
[3];
187 GdkWindow
*window
= widget
->window
;
188 gint32 wm_hints_values
[] = {1, False
, 0, 0, 0, 0, 0, 0};
189 GdkAtom wm_protocols
[2];
191 g_return_if_fail(window
!= NULL
);
193 if (o_override_redirect
.int_value
)
195 gdk_window_set_override_redirect(window
, TRUE
);
201 xa_win_hints
= gdk_atom_intern("_WIN_HINTS", FALSE
);
202 xa_state
= gdk_atom_intern("_WIN_STATE", FALSE
);
203 xa_atom
= gdk_atom_intern("ATOM", FALSE
);
204 xa_net_state
= gdk_atom_intern("_NET_WM_STATE", FALSE
);
205 xa_hints
= gdk_atom_intern("WM_HINTS", FALSE
);
206 xa_NET_WM_DESKTOP
= gdk_atom_intern("_NET_WM_DESKTOP", FALSE
);
208 /* Note: Starting with Gtk+-1.3.12, Gtk+ converts GdkAtoms
209 * to X atoms automatically when the type is ATOM.
211 state_list
[0] = gdk_atom_intern("_NET_WM_STATE_STICKY", FALSE
);
212 state_list
[1] = gdk_atom_intern("_NET_WM_STATE_SKIP_PAGER",
214 state_list
[2] = gdk_atom_intern("_NET_WM_STATE_SKIP_TASKBAR",
220 gdk_window_set_decorations(window
, 0);
221 gdk_window_set_functions(window
, 0);
222 gtk_window_set_resizable(GTK_WINDOW(widget
), FALSE
);
224 /* Note: DON'T do gtk_window_stick(). Setting the state via
225 * gdk will override our other atoms (pager/taskbar).
228 /* Don't hide panel/pinboard windows initially (WIN_STATE_HIDDEN).
229 * Needed for IceWM - Christopher Arndt <chris.arndt@web.de>
231 set_cardinal_property(window
, xa_state
,
233 WIN_STATE_FIXED_POSITION
| WIN_STATE_ARRANGE_IGNORE
);
235 set_cardinal_property(window
, xa_win_hints
,
236 WIN_HINTS_SKIP_FOCUS
| WIN_HINTS_SKIP_WINLIST
|
237 WIN_HINTS_SKIP_TASKBAR
);
239 /* Appear on all workspaces */
240 set_cardinal_property(window
, xa_NET_WM_DESKTOP
, 0xffffffff);
242 gdk_property_change(window
, xa_net_state
, xa_atom
, 32,
243 GDK_PROP_MODE_APPEND
, (guchar
*) state_list
, 3);
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
);
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
;
279 g_return_if_fail(error
!= NULL
);
281 old
= delayed_error_data
;
283 va_start(args
, error
);
284 new = g_strdup_vprintf(error
, args
);
289 delayed_error_data
= g_strconcat(old
,
297 delayed_error_data
= new;
298 gtk_idle_add(error_idle_cb
, &delayed_error_data
);
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
)
310 GError
*error
= NULL
;
312 if (!g_file_get_contents(pathname
, data_out
, &len
, &error
))
314 delayed_error("%s", error
->message
);
324 GtkWidget
*new_help_button(HelpFunc show_help
, gpointer data
)
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
);
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
)
348 gboolean seen_error
= FALSE
;
350 if (load_file(path
, &data
, &length
))
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
);
365 while (line
&& *line
)
367 eol
= strchr(line
, '\n');
371 error
= parse_line(line
);
373 if (error
&& !seen_error
)
376 _("Error in '%s' file at line %d: "
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."),
396 /* Returns the position of the pointer.
397 * TRUE if any modifier keys or mouse buttons are pressed.
399 gboolean
get_pointer_xy(int *x
, int *y
)
403 gdk_window_get_pointer(NULL
, x
, y
, &mask
);
408 #define DECOR_BORDER 32
410 /* Centre the window at these coords */
411 void centre_window(GdkWindow
*window
, int x
, int y
)
415 g_return_if_fail(window
!= NULL
);
417 gdk_drawable_get_size(window
, &w
, &h
);
422 gdk_window_move(window
,
423 CLAMP(x
, DECOR_BORDER
, screen_width
- w
- DECOR_BORDER
),
424 CLAMP(y
, DECOR_BORDER
, screen_height
- h
- DECOR_BORDER
));
427 static void run_error_info_dialog(GtkMessageType type
, const char *message
,
433 g_return_if_fail(message
!= NULL
);
435 s
= g_strdup_vprintf(message
, args
);
438 dialog
= gtk_message_dialog_new(NULL
,
443 gtk_window_set_position(GTK_WINDOW(dialog
), GTK_WIN_POS_CENTER
);
444 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), GTK_RESPONSE_OK
);
445 gtk_dialog_run(GTK_DIALOG(dialog
));
446 gtk_widget_destroy(dialog
);
451 static GtkWidget
*current_wink_widget
= NULL
;
452 static gint wink_timeout
= -1; /* Called when it's time to stop */
453 static gulong wink_destroy
; /* Called if the widget dies first */
455 static gboolean
end_wink(gpointer data
)
457 gtk_drag_unhighlight(current_wink_widget
);
459 g_signal_handler_disconnect(current_wink_widget
, wink_destroy
);
461 current_wink_widget
= NULL
;
466 static void cancel_wink(void)
468 gtk_timeout_remove(wink_timeout
);
472 static void wink_widget_died(gpointer data
)
474 current_wink_widget
= NULL
;
475 gtk_timeout_remove(wink_timeout
);
478 /* Draw a black box around this widget, briefly.
479 * Note: uses the drag highlighting code for now.
481 void wink_widget(GtkWidget
*widget
)
483 g_return_if_fail(widget
!= NULL
);
485 if (current_wink_widget
)
488 current_wink_widget
= widget
;
489 gtk_drag_highlight(current_wink_widget
);
491 wink_timeout
= gtk_timeout_add(300, (GtkFunction
) end_wink
, NULL
);
493 wink_destroy
= g_signal_connect_swapped(widget
, "destroy",
494 G_CALLBACK(wink_widget_died
), NULL
);
497 static gboolean
idle_destroy_cb(GtkWidget
*widget
)
499 gtk_widget_unref(widget
);
500 gtk_widget_destroy(widget
);
504 /* Destroy the widget in an idle callback */
505 void destroy_on_idle(GtkWidget
*widget
)
507 gtk_widget_ref(widget
);
508 gtk_idle_add((GtkFunction
) idle_destroy_cb
, widget
);
511 /* Spawn a child process (as spawn_full), and report errors.
512 * Returns the child's PID on succes, or 0 on failure.
514 gint
rox_spawn(const gchar
*dir
, const gchar
**argv
)
516 GError
*error
= NULL
;
519 if (!g_spawn_async_with_pipes(dir
, (gchar
**) argv
, NULL
,
520 G_SPAWN_DO_NOT_REAP_CHILD
| G_SPAWN_STDOUT_TO_DEV_NULL
|
522 NULL
, NULL
, /* Child setup fn */
523 &pid
, /* Child PID */
524 NULL
, NULL
, NULL
, /* Standard pipes */
527 delayed_error("%s", error
? error
->message
: "(null)");
536 GtkWidget
*button_new_image_text(GtkWidget
*image
, const char *message
)
538 GtkWidget
*button
, *align
, *hbox
, *label
;
540 button
= gtk_button_new();
541 label
= gtk_label_new_with_mnemonic(message
);
542 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), button
);
544 hbox
= gtk_hbox_new(FALSE
, 2);
546 align
= gtk_alignment_new(0.5, 0.5, 0.0, 0.0);
548 gtk_box_pack_start(GTK_BOX(hbox
), image
, FALSE
, FALSE
, 0);
549 gtk_box_pack_end(GTK_BOX(hbox
), label
, FALSE
, FALSE
, 0);
551 gtk_container_add(GTK_CONTAINER(button
), align
);
552 gtk_container_add(GTK_CONTAINER(align
), hbox
);
553 gtk_widget_show_all(align
);
558 GtkWidget
*button_new_mixed(const char *stock
, const char *message
)
560 return button_new_image_text(gtk_image_new_from_stock(stock
,
561 GTK_ICON_SIZE_BUTTON
),
565 /* Highlight entry in red if 'error' is TRUE */
566 void entry_set_error(GtkWidget
*entry
, gboolean error
)
568 GdkColor red
= {0, 0xffff, 0, 0};
569 static gboolean need_init
= TRUE
;
570 static GdkColor normal
;
574 normal
= entry
->style
->text
[GTK_STATE_NORMAL
];
578 gtk_widget_modify_text(entry
, GTK_STATE_NORMAL
, error
? &red
: &normal
);
581 /* Change stacking position of higher to be just above lower.
582 * If lower is NULL, put higher at the bottom of the stack.
584 void window_put_just_above(GdkWindow
*higher
, GdkWindow
*lower
)
586 if (o_override_redirect
.int_value
&& lower
)
588 XWindowChanges restack
;
590 gdk_error_trap_push();
592 restack
.stack_mode
= Above
;
594 restack
.sibling
= GDK_WINDOW_XWINDOW(lower
);
596 XConfigureWindow(gdk_display
, GDK_WINDOW_XWINDOW(higher
),
597 CWSibling
| CWStackMode
, &restack
);
600 if (gdk_error_trap_pop())
601 g_warning("window_put_just_above()\n");
604 gdk_window_lower(higher
); /* To bottom of stack */
607 /* Copied from Gtk */
608 static GtkFixedChild
* fixed_get_child(GtkFixed
*fixed
, GtkWidget
*widget
)
612 children
= fixed
->children
;
615 GtkFixedChild
*child
;
617 child
= children
->data
;
618 children
= children
->next
;
620 if (child
->widget
== widget
)
627 /* Like gtk_fixed_move(), except not insanely slow */
628 void fixed_move_fast(GtkFixed
*fixed
, GtkWidget
*widget
, int x
, int y
)
630 GtkFixedChild
*child
;
632 child
= fixed_get_child(fixed
, widget
);
636 gtk_widget_freeze_child_notify(widget
);
639 gtk_widget_child_notify(widget
, "x");
642 gtk_widget_child_notify(widget
, "y");
644 gtk_widget_thaw_child_notify(widget
);
646 if (GTK_WIDGET_VISIBLE(widget
) && GTK_WIDGET_VISIBLE(fixed
))
648 int border_width
= GTK_CONTAINER(fixed
)->border_width
;
649 GtkAllocation child_allocation
;
650 GtkRequisition child_requisition
;
652 gtk_widget_get_child_requisition(child
->widget
,
654 child_allocation
.x
= child
->x
+ border_width
;
655 child_allocation
.y
= child
->y
+ border_width
;
657 child_allocation
.x
+= GTK_WIDGET(fixed
)->allocation
.x
;
658 child_allocation
.y
+= GTK_WIDGET(fixed
)->allocation
.y
;
660 child_allocation
.width
= child_requisition
.width
;
661 child_allocation
.height
= child_requisition
.height
;
662 gtk_widget_size_allocate(child
->widget
, &child_allocation
);
666 /* Draw the black border */
667 static gint
tooltip_draw(GtkWidget
*w
)
669 gdk_draw_rectangle(w
->window
, w
->style
->fg_gc
[w
->state
], FALSE
, 0, 0,
670 w
->allocation
.width
- 1, w
->allocation
.height
- 1);
675 /* When the tips window closed, record the time. If we try to open another
676 * tip soon, it will appear more quickly.
678 static void tooltip_destroyed(gpointer data
)
683 /* Display a tooltip-like widget near the pointer with 'text'. If 'text' is
684 * NULL, close any current tooltip.
686 void tooltip_show(guchar
*text
)
694 gtk_timeout_remove(tip_timeout
);
700 gtk_widget_destroy(tip_widget
);
708 tip_widget
= gtk_window_new(GTK_WINDOW_POPUP
);
709 gtk_widget_set_app_paintable(tip_widget
, TRUE
);
710 gtk_widget_set_name(tip_widget
, "gtk-tooltips");
712 g_signal_connect_swapped(tip_widget
, "expose_event",
713 G_CALLBACK(tooltip_draw
), tip_widget
);
715 label
= gtk_label_new(text
);
716 gtk_misc_set_padding(GTK_MISC(label
), 4, 2);
717 gtk_container_add(GTK_CONTAINER(tip_widget
), label
);
718 gtk_widget_show(label
);
719 gtk_widget_realize(tip_widget
);
721 w
= tip_widget
->allocation
.width
;
722 h
= tip_widget
->allocation
.height
;
723 gdk_window_get_pointer(NULL
, &x
, &py
, NULL
);
726 y
= py
+ 12; /* I don't know the pointer height so I use a constant */
728 /* Now check for screen boundaries */
729 x
= CLAMP(x
, 0, screen_width
- w
);
730 y
= CLAMP(y
, 0, screen_height
- h
);
732 /* And again test if pointer is over the tooltip window */
733 if (py
>= y
&& py
<= y
+ h
)
735 gtk_window_move(GTK_WINDOW(tip_widget
), x
, y
);
736 gtk_widget_show(tip_widget
);
738 g_signal_connect_swapped(tip_widget
, "destroy",
739 G_CALLBACK(tooltip_destroyed
), NULL
);
743 /* Call callback(user_data) after a while, unless cancelled.
744 * Object is refd now and unref when cancelled / after callback called.
746 void tooltip_prime(GtkFunction callback
, GObject
*object
)
751 g_return_if_fail(tip_timeout
== 0);
754 delay
= now
- tip_time
> 2 ? 1000 : 200;
756 g_object_ref(object
);
757 tip_timeout
= gtk_timeout_add_full(delay
,
758 (GtkFunction
) callback
,
764 /* Like gtk_widget_modify_font, but copes with font_desc == NULL */
765 void widget_modify_font(GtkWidget
*widget
, PangoFontDescription
*font_desc
)
767 GtkRcStyle
*rc_style
;
769 g_return_if_fail(GTK_IS_WIDGET(widget
));
771 rc_style
= gtk_widget_get_modifier_style(widget
);
773 if (rc_style
->font_desc
)
774 pango_font_description_free(rc_style
->font_desc
);
776 rc_style
->font_desc
= font_desc
777 ? pango_font_description_copy(font_desc
)
780 gtk_widget_modify_style(widget
, rc_style
);
783 /* Confirm the action with the user. If action is NULL, the text from stock
786 gboolean
confirm(const gchar
*message
, const gchar
*stock
, const gchar
*action
)
788 return get_choice(PROJECT
, message
, 2,
789 GTK_STOCK_CANCEL
, NULL
,
796 void (*changed
)(gpointer data
);
797 gpointer changed_data
;
800 /* Create a new set of radio buttons.
801 * Use radios_add to add options, then radios_pack to put them into something.
802 * The radios object will self-destruct with the first widget it contains.
803 * changed(data) is called (if not NULL) when pack is called, and on any
806 Radios
*radios_new(void (*changed
)(gpointer data
), gpointer data
)
810 radios
= g_new(Radios
, 1);
812 radios
->widgets
= NULL
;
813 radios
->changed
= changed
;
814 radios
->changed_data
= data
;
819 static void radios_free(GtkWidget
*radio
, Radios
*radios
)
821 g_return_if_fail(radios
!= NULL
);
823 g_list_free(radios
->widgets
);
827 void radios_add(Radios
*radios
, const gchar
*tip
, gint value
,
828 const gchar
*label
, ...)
831 GSList
*group
= NULL
;
835 g_return_if_fail(radios
!= NULL
);
836 g_return_if_fail(label
!= NULL
);
838 va_start(args
, label
);
839 s
= g_strdup_vprintf(label
, args
);
844 GtkRadioButton
*first
= GTK_RADIO_BUTTON(radios
->widgets
->data
);
845 group
= gtk_radio_button_get_group(first
);
848 radio
= gtk_radio_button_new_with_label(group
, s
);
849 gtk_label_set_line_wrap(GTK_LABEL(GTK_BIN(radio
)->child
), TRUE
);
850 gtk_widget_show(radio
);
852 gtk_tooltips_set_tip(tooltips
, radio
, tip
, NULL
);
854 g_signal_connect(G_OBJECT(radio
), "destroy",
855 G_CALLBACK(radios_free
), radios
);
857 radios
->widgets
= g_list_prepend(radios
->widgets
, radio
);
858 g_object_set_data(G_OBJECT(radio
), "rox-radios-value",
859 GINT_TO_POINTER(value
));
862 static void radio_toggled(GtkToggleButton
*button
, Radios
*radios
)
864 g_return_if_fail(radios
!= NULL
);
867 radios
->changed(radios
->changed_data
);
870 void radios_pack(Radios
*radios
, GtkBox
*box
)
874 g_return_if_fail(radios
!= NULL
);
876 for (next
= g_list_last(radios
->widgets
); next
; next
= next
->prev
)
878 GtkWidget
*button
= GTK_WIDGET(next
->data
);
880 gtk_box_pack_start(box
, button
, FALSE
, TRUE
, 0);
881 g_signal_connect(button
, "toggled",
882 G_CALLBACK(radio_toggled
), radios
);
884 radio_toggled(NULL
, radios
);
887 void radios_set_value(Radios
*radios
, gint value
)
891 g_return_if_fail(radios
!= NULL
);
893 for (next
= radios
->widgets
; next
; next
= next
->next
)
895 GtkToggleButton
*radio
= GTK_TOGGLE_BUTTON(next
->data
);
898 radio_value
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(radio
),
899 "rox-radios-value"));
901 if (radio_value
== value
)
903 gtk_toggle_button_set_active(radio
, TRUE
);
908 g_warning("Value %d not in radio group!", value
);
911 gint
radios_get_value(Radios
*radios
)
915 g_return_val_if_fail(radios
!= NULL
, -1);
917 for (next
= radios
->widgets
; next
; next
= next
->next
)
919 GtkToggleButton
*radio
= GTK_TOGGLE_BUTTON(next
->data
);
921 if (gtk_toggle_button_get_active(radio
))
922 return GPOINTER_TO_INT(g_object_get_data(
923 G_OBJECT(radio
), "rox-radios-value"));
926 g_warning("Nothing in the radio group is selected!");
931 /* Convert a list of URIs into a list of strings.
932 * Lines beginning with # are skipped.
933 * The text block passed in is zero terminated (after the final CRLF)
935 GList
*uri_list_to_glist(const char *uri_list
)
945 linebreak
= strchr(uri_list
, 13);
947 if (!linebreak
|| linebreak
[1] != 10)
949 delayed_error("uri_list_to_glist: %s",
950 _("Incorrect or missing line "
951 "break in text/uri-list data"));
955 length
= linebreak
- uri_list
;
957 if (length
&& uri_list
[0] != '#')
960 tmp
= g_strndup(uri_list
, length
);
961 uri
=unescape_uri(tmp
);
963 list
= g_list_append(list
, uri
);
966 uri_list
= linebreak
+ 2;
972 typedef struct _SimpleImageClass SimpleImageClass
;
973 typedef struct _SimpleImage SimpleImage
;
975 struct _SimpleImageClass
{
976 GtkWidgetClass parent
;
979 struct _SimpleImage
{
986 #define SIMPLE_IMAGE(obj) (GTK_CHECK_CAST((obj), \
987 simple_image_get_type(), SimpleImage))
989 static void simple_image_finialize(GObject
*object
)
991 SimpleImage
*image
= SIMPLE_IMAGE(object
);
993 g_object_unref(G_OBJECT(image
->pixbuf
));
994 image
->pixbuf
= NULL
;
997 static void simple_image_size_request(GtkWidget
*widget
,
998 GtkRequisition
*requisition
)
1000 SimpleImage
*image
= (SimpleImage
*) widget
;
1002 requisition
->width
= image
->width
;
1003 requisition
->height
= image
->height
;
1006 /* Render a pixbuf without messing up the clipping */
1007 void render_pixbuf(GdkPixbuf
*pixbuf
, GdkDrawable
*target
, GdkGC
*gc
,
1008 int x
, int y
, int width
, int height
)
1010 #if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION > 1
1011 gdk_draw_pixbuf(target
, gc
, pixbuf
, 0, 0, x
, y
, width
, height
,
1012 GDK_RGB_DITHER_NORMAL
, 0, 0);
1015 static gboolean warned
= FALSE
;
1016 if (gtk_minor_version
== 0)
1017 GDK_DRAWABLE_GET_CLASS(target
)->_draw_pixbuf(target
, gc
, pixbuf
,
1018 0, 0, x
, y
, width
, height
,
1019 GDK_RGB_DITHER_NORMAL
, 0, 0);
1022 delayed_error(_("Pinboard icons cannot be drawn because "
1023 "ROX-Filer was compiled against GTK+-2.0 "
1024 "but is running with GTK+-2.2. Please "
1031 static gint
simple_image_expose(GtkWidget
*widget
, GdkEventExpose
*event
)
1033 SimpleImage
*image
= (SimpleImage
*) widget
;
1036 gdk_gc_set_clip_region(widget
->style
->black_gc
, event
->region
);
1038 x
= widget
->allocation
.x
+
1039 (widget
->allocation
.width
- image
->width
) / 2;
1041 render_pixbuf(image
->pixbuf
, widget
->window
, widget
->style
->black_gc
,
1042 x
, widget
->allocation
.y
,
1043 image
->width
, image
->height
);
1045 gdk_gc_set_clip_region(widget
->style
->black_gc
, NULL
);
1049 static void simple_image_class_init(gpointer gclass
, gpointer data
)
1051 GObjectClass
*object
= (GObjectClass
*) gclass
;
1052 GtkWidgetClass
*widget
= (GtkWidgetClass
*) gclass
;
1054 object
->finalize
= simple_image_finialize
;
1055 widget
->size_request
= simple_image_size_request
;
1056 widget
->expose_event
= simple_image_expose
;
1059 static void simple_image_init(GTypeInstance
*object
, gpointer gclass
)
1061 GTK_WIDGET_SET_FLAGS(object
, GTK_NO_WINDOW
);
1064 static GType
simple_image_get_type(void)
1066 static GType type
= 0;
1070 static const GTypeInfo info
=
1072 sizeof (SimpleImageClass
),
1073 NULL
, /* base_init */
1074 NULL
, /* base_finalise */
1075 simple_image_class_init
,
1076 NULL
, /* class_finalise */
1077 NULL
, /* class_data */
1078 sizeof(SimpleImage
),
1079 0, /* n_preallocs */
1083 type
= g_type_register_static(gtk_widget_get_type(),
1084 "SimpleImage", &info
, 0);
1090 GtkWidget
*simple_image_new(GdkPixbuf
*pixbuf
)
1094 g_return_val_if_fail(pixbuf
!= NULL
, NULL
);
1096 image
= g_object_new(simple_image_get_type(), NULL
);
1098 image
->pixbuf
= pixbuf
;
1099 g_object_ref(G_OBJECT(pixbuf
));
1101 image
->width
= gdk_pixbuf_get_width(pixbuf
);
1102 image
->height
= gdk_pixbuf_get_height(pixbuf
);
1104 return GTK_WIDGET(image
);