2 * ROX-Filer, filer for the ROX desktop project
3 * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17 * Place, Suite 330, Boston, MA 02111-1307 USA
20 /* gui_support.c - general (GUI) support routines */
27 #include <sys/param.h>
33 #include <X11/Xatom.h>
36 #include <gdk/gdkkeysyms.h>
41 #include "gui_support.h"
47 gint screen_width
, screen_height
;
50 GdkRectangle
*monitor_geom
= NULL
;
51 gint monitor_width
, monitor_height
;
52 MonitorAdjacent
*monitor_adjacent
;
54 static GdkAtom xa_cardinal
;
56 static GtkWidget
*current_dialog
= NULL
;
58 static GtkWidget
*tip_widget
= NULL
;
59 static time_t tip_time
= 0; /* Time tip widget last closed */
60 static gint tip_timeout
= 0; /* When primed */
62 /* Static prototypes */
63 static void run_error_info_dialog(GtkMessageType type
, const char *message
,
65 static GType
simple_image_get_type(void);
66 static void gui_get_monitor_adjacent(int monitor
, MonitorAdjacent
*adj
);
68 void gui_store_screen_geometry(GdkScreen
*screen
)
72 screen_width
= gdk_screen_get_width(screen
);
73 screen_height
= gdk_screen_get_height(screen
);
76 g_free(monitor_adjacent
);
78 monitor_width
= monitor_height
= G_MAXINT
;
79 n_monitors
= gdk_screen_get_n_monitors(screen
);
82 monitor_geom
= g_new(GdkRectangle
, n_monitors
? n_monitors
: 1);
86 for (mon
= 0; mon
< n_monitors
; ++mon
)
88 gdk_screen_get_monitor_geometry(screen
, mon
,
90 if (monitor_geom
[mon
].width
< monitor_width
)
91 monitor_width
= monitor_geom
[mon
].width
;
92 if (monitor_geom
[mon
].height
< monitor_height
)
93 monitor_height
= monitor_geom
[mon
].height
;
95 monitor_adjacent
= g_new(MonitorAdjacent
, n_monitors
);
96 for (mon
= 0; mon
< n_monitors
; ++mon
)
98 gui_get_monitor_adjacent(mon
, &monitor_adjacent
[mon
]);
104 monitor_geom
[0].x
= monitor_geom
[0].y
= 0;
105 monitor_width
= monitor_geom
[0].width
= screen_width
;
106 monitor_height
= monitor_geom
[0].height
= screen_height
;
107 monitor_adjacent
= g_new0(MonitorAdjacent
, 1);
112 void gui_support_init()
116 xa_cardinal
= gdk_atom_intern("CARDINAL", FALSE
);
118 gui_store_screen_geometry(gdk_screen_get_default());
120 /* Work around the scrollbar placement bug */
121 klass
= g_type_class_ref(gtk_scrolled_window_get_type());
122 ((GtkScrolledWindowClass
*) klass
)->scrollbar_spacing
= 0;
123 /* (don't unref, ever) */
126 /* Open a modal dialog box showing a message.
127 * The user can choose from a selection of buttons at the bottom.
128 * Returns -1 if the window is destroyed, or the number of the button
129 * if one is clicked (starting from zero).
131 * If a dialog is already open, returns -1 without waiting AND
132 * brings the current dialog to the front.
134 * Each button has two arguments, a GTK_STOCK icon and some text. If the
135 * text is NULL, the stock's text is used.
137 int get_choice(const char *title
,
139 int number_of_buttons
, ...)
142 GtkWidget
*button
= NULL
;
148 gtk_widget_hide(current_dialog
);
149 gtk_widget_show(current_dialog
);
153 current_dialog
= dialog
= gtk_message_dialog_new(NULL
,
155 GTK_MESSAGE_QUESTION
,
158 gtk_window_set_position(GTK_WINDOW(dialog
), GTK_WIN_POS_CENTER
);
160 va_start(ap
, number_of_buttons
);
162 for (i
= 0; i
< number_of_buttons
; i
++)
164 const char *stock
= va_arg(ap
, char *);
165 const char *text
= va_arg(ap
, char *);
168 button
= button_new_mixed(stock
, text
);
170 button
= gtk_button_new_from_stock(stock
);
172 GTK_WIDGET_SET_FLAGS(button
, GTK_CAN_DEFAULT
);
173 gtk_widget_show(button
);
175 gtk_dialog_add_action_widget(GTK_DIALOG(current_dialog
),
179 gtk_window_set_title(GTK_WINDOW(dialog
), title
);
180 gtk_window_set_position(GTK_WINDOW(dialog
), GTK_WIN_POS_CENTER
);
182 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), i
- 1);
186 retval
= gtk_dialog_run(GTK_DIALOG(dialog
));
187 if (retval
== GTK_RESPONSE_NONE
)
189 gtk_widget_destroy(dialog
);
191 current_dialog
= NULL
;
196 void info_message(const char *message
, ...)
200 va_start(args
, message
);
202 run_error_info_dialog(GTK_MESSAGE_INFO
, message
, args
);
205 /* Display a message in a window with "ROX-Filer" as title */
206 void report_error(const char *message
, ...)
210 va_start(args
, message
);
212 run_error_info_dialog(GTK_MESSAGE_ERROR
, message
, args
);
215 void set_cardinal_property(GdkWindow
*window
, GdkAtom prop
, gulong value
)
217 gdk_property_change(window
, prop
, xa_cardinal
, 32,
218 GDK_PROP_MODE_REPLACE
, (gchar
*) &value
, 1);
221 /* NB: Also used for pinned icons.
222 * TODO: Set the level here too.
224 void make_panel_window(GtkWidget
*widget
)
226 static gboolean need_init
= TRUE
;
227 static GdkAtom xa_state
, xa_atom
, xa_hints
, xa_win_hints
;
228 static GdkAtom xa_NET_WM_DESKTOP
;
229 GdkWindow
*window
= widget
->window
;
230 long wm_hints_values
[] = {1, False
, 0, 0, 0, 0, 0, 0};
231 GdkAtom wm_protocols
[2];
233 g_return_if_fail(window
!= NULL
);
235 if (o_override_redirect
.int_value
)
237 gdk_window_set_override_redirect(window
, TRUE
);
243 xa_win_hints
= gdk_atom_intern("_WIN_HINTS", FALSE
);
244 xa_state
= gdk_atom_intern("_WIN_STATE", FALSE
);
245 xa_atom
= gdk_atom_intern("ATOM", FALSE
);
246 xa_hints
= gdk_atom_intern("WM_HINTS", FALSE
);
247 xa_NET_WM_DESKTOP
= gdk_atom_intern("_NET_WM_DESKTOP", FALSE
);
252 gdk_window_set_decorations(window
, 0);
253 gdk_window_set_functions(window
, 0);
254 gtk_window_set_resizable(GTK_WINDOW(widget
), FALSE
);
256 /* Don't hide panel/pinboard windows initially (WIN_STATE_HIDDEN).
257 * Needed for IceWM - Christopher Arndt <chris.arndt@web.de>
259 set_cardinal_property(window
, xa_state
,
261 WIN_STATE_FIXED_POSITION
| WIN_STATE_ARRANGE_IGNORE
);
263 set_cardinal_property(window
, xa_win_hints
,
264 WIN_HINTS_SKIP_FOCUS
| WIN_HINTS_SKIP_WINLIST
|
265 WIN_HINTS_SKIP_TASKBAR
);
267 /* Appear on all workspaces */
268 set_cardinal_property(window
, xa_NET_WM_DESKTOP
, 0xffffffff);
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(long));
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
));
281 gdk_window_set_skip_taskbar_hint(window
, TRUE
);
282 gdk_window_set_skip_pager_hint(window
, TRUE
);
284 if (g_object_class_find_property(G_OBJECT_GET_CLASS(widget
),
287 GValue vfalse
= { 0, };
288 g_value_init(&vfalse
, G_TYPE_BOOLEAN
);
289 g_value_set_boolean(&vfalse
, FALSE
);
290 g_object_set_property(G_OBJECT(widget
),
291 "accept_focus", &vfalse
);
292 g_value_unset(&vfalse
);
296 static gboolean
error_idle_cb(gpointer data
)
298 char **error
= (char **) data
;
300 report_error("%s", *error
);
307 /* Display an error with "ROX-Filer" as title next time we are idle.
308 * If multiple errors are reported this way before the window is opened,
309 * all are displayed in a single window.
310 * If an error is reported while the error window is open, it is discarded.
312 void delayed_error(const char *error
, ...)
314 static char *delayed_error_data
= NULL
;
318 g_return_if_fail(error
!= NULL
);
320 old
= delayed_error_data
;
322 va_start(args
, error
);
323 new = g_strdup_vprintf(error
, args
);
328 delayed_error_data
= g_strconcat(old
,
336 delayed_error_data
= new;
337 g_idle_add(error_idle_cb
, &delayed_error_data
);
343 /* Load the file into memory. Return TRUE on success.
344 * Block is zero terminated (but this is not included in the length).
346 gboolean
load_file(const char *pathname
, char **data_out
, long *length_out
)
349 GError
*error
= NULL
;
351 if (!g_file_get_contents(pathname
, data_out
, &len
, &error
))
353 delayed_error("%s", error
->message
);
363 GtkWidget
*new_help_button(HelpFunc show_help
, gpointer data
)
367 b
= gtk_button_new();
368 gtk_button_set_relief(GTK_BUTTON(b
), GTK_RELIEF_NONE
);
369 icon
= gtk_image_new_from_stock(GTK_STOCK_HELP
,
370 GTK_ICON_SIZE_SMALL_TOOLBAR
);
371 gtk_container_add(GTK_CONTAINER(b
), icon
);
372 g_signal_connect_swapped(b
, "clicked", G_CALLBACK(show_help
), data
);
374 GTK_WIDGET_UNSET_FLAGS(b
, GTK_CAN_FOCUS
);
379 /* Read file into memory. Call parse_line(guchar *line) for each line
380 * in the file. Callback returns NULL on success, or an error message
381 * if something went wrong. Only the first error is displayed to the user.
383 void parse_file(const char *path
, ParseFunc
*parse_line
)
387 gboolean seen_error
= FALSE
;
389 if (load_file(path
, &data
, &length
))
396 if (strncmp(data
, "<?xml ", 6) == 0)
398 delayed_error(_("Attempt to read an XML file as "
399 "a text file. File '%s' may be "
400 "corrupted."), path
);
404 while (line
&& *line
)
406 eol
= strchr(line
, '\n');
410 error
= parse_line(line
);
412 if (error
&& !seen_error
)
415 _("Error in '%s' file at line %d: "
417 "This may be due to upgrading from a previous version of "
418 "ROX-Filer. Open the Options window and try changing something "
419 "and then changing it back (causing the file to be resaved).\n"
420 "Further errors will be ignored."),
436 /* Returns the position of the pointer.
437 * TRUE if any modifier keys or mouse buttons are pressed.
439 gboolean
get_pointer_xy(int *x
, int *y
)
443 gdk_window_get_pointer(NULL
, x
, y
, &mask
);
448 int get_monitor_under_pointer(void)
452 get_pointer_xy(&x
, &y
);
453 return gdk_screen_get_monitor_at_point(gdk_screen_get_default(), x
, y
);
456 #define DECOR_BORDER 32
458 /* Centre the window at these coords */
459 void centre_window(GdkWindow
*window
, int x
, int y
)
464 g_return_if_fail(window
!= NULL
);
466 m
= gdk_screen_get_monitor_at_point(gdk_screen_get_default(), x
, y
);
468 gdk_drawable_get_size(window
, &w
, &h
);
473 gdk_window_move(window
,
474 CLAMP(x
, DECOR_BORDER
+ monitor_geom
[m
].x
,
475 monitor_geom
[m
].x
+ monitor_geom
[m
].width
477 CLAMP(y
, DECOR_BORDER
+ monitor_geom
[m
].y
,
478 monitor_geom
[m
].y
+ monitor_geom
[m
].height
479 - h
- DECOR_BORDER
));
482 static void run_error_info_dialog(GtkMessageType type
, const char *message
,
488 g_return_if_fail(message
!= NULL
);
490 s
= g_strdup_vprintf(message
, args
);
493 dialog
= gtk_message_dialog_new(NULL
,
498 gtk_window_set_position(GTK_WINDOW(dialog
), GTK_WIN_POS_CENTER
);
499 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), GTK_RESPONSE_OK
);
500 gtk_dialog_run(GTK_DIALOG(dialog
));
501 gtk_widget_destroy(dialog
);
506 static GtkWidget
*current_wink_widget
= NULL
;
507 static gint wink_timeout
= -1; /* Called when it's time to stop */
508 static gulong wink_destroy
; /* Called if the widget dies first */
510 static gboolean
end_wink(gpointer data
)
512 gtk_drag_unhighlight(current_wink_widget
);
514 g_signal_handler_disconnect(current_wink_widget
, wink_destroy
);
516 current_wink_widget
= NULL
;
521 static void cancel_wink(void)
523 g_source_remove(wink_timeout
);
527 static void wink_widget_died(gpointer data
)
529 current_wink_widget
= NULL
;
530 g_source_remove(wink_timeout
);
533 /* Draw a black box around this widget, briefly.
534 * Note: uses the drag highlighting code for now.
536 void wink_widget(GtkWidget
*widget
)
538 g_return_if_fail(widget
!= NULL
);
540 if (current_wink_widget
)
543 current_wink_widget
= widget
;
544 gtk_drag_highlight(current_wink_widget
);
546 wink_timeout
= g_timeout_add(300, (GSourceFunc
) end_wink
, NULL
);
548 wink_destroy
= g_signal_connect_swapped(widget
, "destroy",
549 G_CALLBACK(wink_widget_died
), NULL
);
552 static gboolean
idle_destroy_cb(GtkWidget
*widget
)
554 gtk_widget_unref(widget
);
555 gtk_widget_destroy(widget
);
559 /* Destroy the widget in an idle callback */
560 void destroy_on_idle(GtkWidget
*widget
)
562 gtk_widget_ref(widget
);
563 g_idle_add((GSourceFunc
) idle_destroy_cb
, widget
);
566 /* Spawn a child process (as spawn_full), and report errors.
567 * Returns the child's PID on succes, or 0 on failure.
569 gint
rox_spawn(const gchar
*dir
, const gchar
**argv
)
571 GError
*error
= NULL
;
574 if (!g_spawn_async_with_pipes(dir
, (gchar
**) argv
, NULL
,
575 G_SPAWN_DO_NOT_REAP_CHILD
| G_SPAWN_STDOUT_TO_DEV_NULL
|
577 NULL
, NULL
, /* Child setup fn */
578 &pid
, /* Child PID */
579 NULL
, NULL
, NULL
, /* Standard pipes */
582 delayed_error("%s", error
? error
->message
: "(null)");
591 GtkWidget
*button_new_image_text(GtkWidget
*image
, const char *message
)
593 GtkWidget
*button
, *align
, *hbox
, *label
;
595 button
= gtk_button_new();
596 label
= gtk_label_new_with_mnemonic(message
);
597 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), button
);
599 hbox
= gtk_hbox_new(FALSE
, 2);
601 align
= gtk_alignment_new(0.5, 0.5, 0.0, 0.0);
603 gtk_box_pack_start(GTK_BOX(hbox
), image
, FALSE
, FALSE
, 0);
604 gtk_box_pack_end(GTK_BOX(hbox
), label
, FALSE
, FALSE
, 0);
606 gtk_container_add(GTK_CONTAINER(button
), align
);
607 gtk_container_add(GTK_CONTAINER(align
), hbox
);
608 gtk_widget_show_all(align
);
613 GtkWidget
*button_new_mixed(const char *stock
, const char *message
)
615 return button_new_image_text(gtk_image_new_from_stock(stock
,
616 GTK_ICON_SIZE_BUTTON
),
620 /* Highlight entry in red if 'error' is TRUE */
621 void entry_set_error(GtkWidget
*entry
, gboolean error
)
623 const GdkColor red
= {0, 0xffff, 0, 0};
624 const GdkColor white
= {0, 0xffff, 0xffff, 0xffff};
626 gtk_widget_modify_text(entry
, GTK_STATE_NORMAL
, error
? &red
: NULL
);
627 gtk_widget_modify_base(entry
, GTK_STATE_NORMAL
, error
? &white
: NULL
);
630 /* Change stacking position of higher to be just above lower.
631 * If lower is NULL, put higher at the bottom of the stack.
633 void window_put_just_above(GdkWindow
*higher
, GdkWindow
*lower
)
635 if (o_override_redirect
.int_value
&& lower
)
637 XWindowChanges restack
;
639 gdk_error_trap_push();
641 restack
.stack_mode
= Above
;
643 restack
.sibling
= GDK_WINDOW_XWINDOW(lower
);
645 XConfigureWindow(gdk_display
, GDK_WINDOW_XWINDOW(higher
),
646 CWSibling
| CWStackMode
, &restack
);
649 if (gdk_error_trap_pop())
650 g_warning("window_put_just_above()\n");
653 gdk_window_lower(higher
); /* To bottom of stack */
656 /* Copied from Gtk */
657 static GtkFixedChild
* fixed_get_child(GtkFixed
*fixed
, GtkWidget
*widget
)
661 children
= fixed
->children
;
664 GtkFixedChild
*child
;
666 child
= children
->data
;
667 children
= children
->next
;
669 if (child
->widget
== widget
)
676 /* Like gtk_fixed_move(), except not insanely slow */
677 void fixed_move_fast(GtkFixed
*fixed
, GtkWidget
*widget
, int x
, int y
)
679 GtkFixedChild
*child
;
681 child
= fixed_get_child(fixed
, widget
);
685 gtk_widget_freeze_child_notify(widget
);
688 gtk_widget_child_notify(widget
, "x");
691 gtk_widget_child_notify(widget
, "y");
693 gtk_widget_thaw_child_notify(widget
);
695 if (GTK_WIDGET_VISIBLE(widget
) && GTK_WIDGET_VISIBLE(fixed
))
697 int border_width
= GTK_CONTAINER(fixed
)->border_width
;
698 GtkAllocation child_allocation
;
699 GtkRequisition child_requisition
;
701 gtk_widget_get_child_requisition(child
->widget
,
703 child_allocation
.x
= child
->x
+ border_width
;
704 child_allocation
.y
= child
->y
+ border_width
;
706 child_allocation
.x
+= GTK_WIDGET(fixed
)->allocation
.x
;
707 child_allocation
.y
+= GTK_WIDGET(fixed
)->allocation
.y
;
709 child_allocation
.width
= child_requisition
.width
;
710 child_allocation
.height
= child_requisition
.height
;
711 gtk_widget_size_allocate(child
->widget
, &child_allocation
);
715 /* Draw the black border */
716 static gint
tooltip_draw(GtkWidget
*w
)
718 gdk_draw_rectangle(w
->window
, w
->style
->fg_gc
[w
->state
], FALSE
, 0, 0,
719 w
->allocation
.width
- 1, w
->allocation
.height
- 1);
724 /* When the tips window closed, record the time. If we try to open another
725 * tip soon, it will appear more quickly.
727 static void tooltip_destroyed(gpointer data
)
732 /* Display a tooltip-like widget near the pointer with 'text'. If 'text' is
733 * NULL, close any current tooltip.
735 void tooltip_show(guchar
*text
)
744 g_source_remove(tip_timeout
);
750 gtk_widget_destroy(tip_widget
);
758 tip_widget
= gtk_window_new(GTK_WINDOW_POPUP
);
759 gtk_widget_set_app_paintable(tip_widget
, TRUE
);
760 gtk_widget_set_name(tip_widget
, "gtk-tooltips");
762 g_signal_connect_swapped(tip_widget
, "expose_event",
763 G_CALLBACK(tooltip_draw
), tip_widget
);
765 label
= gtk_label_new(text
);
766 gtk_misc_set_padding(GTK_MISC(label
), 4, 2);
767 gtk_container_add(GTK_CONTAINER(tip_widget
), label
);
768 gtk_widget_show(label
);
769 gtk_widget_realize(tip_widget
);
771 w
= tip_widget
->allocation
.width
;
772 h
= tip_widget
->allocation
.height
;
773 gdk_window_get_pointer(NULL
, &x
, &py
, NULL
);
775 m
= gdk_screen_get_monitor_at_point(gdk_screen_get_default(), x
, py
);
778 y
= py
+ 12; /* I don't know the pointer height so I use a constant */
780 /* Now check for screen boundaries */
781 x
= CLAMP(x
, monitor_geom
[m
].x
,
782 monitor_geom
[m
].x
+ monitor_geom
[m
].width
- w
);
783 y
= CLAMP(y
, monitor_geom
[m
].y
,
784 monitor_geom
[m
].y
+ monitor_geom
[m
].height
- h
);
786 /* And again test if pointer is over the tooltip window */
787 if (py
>= y
&& py
<= y
+ h
)
789 gtk_window_move(GTK_WINDOW(tip_widget
), x
, y
);
790 gtk_widget_show(tip_widget
);
792 g_signal_connect_swapped(tip_widget
, "destroy",
793 G_CALLBACK(tooltip_destroyed
), NULL
);
797 /* Call callback(user_data) after a while, unless cancelled.
798 * Object is refd now and unref when cancelled / after callback called.
800 void tooltip_prime(GtkFunction callback
, GObject
*object
)
805 g_return_if_fail(tip_timeout
== 0);
808 delay
= now
- tip_time
> 2 ? 1000 : 200;
810 g_object_ref(object
);
811 tip_timeout
= g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE
,
813 (GSourceFunc
) callback
,
818 /* Like gtk_widget_modify_font, but copes with font_desc == NULL */
819 void widget_modify_font(GtkWidget
*widget
, PangoFontDescription
*font_desc
)
821 GtkRcStyle
*rc_style
;
823 g_return_if_fail(GTK_IS_WIDGET(widget
));
825 rc_style
= gtk_widget_get_modifier_style(widget
);
827 if (rc_style
->font_desc
)
828 pango_font_description_free(rc_style
->font_desc
);
830 rc_style
->font_desc
= font_desc
831 ? pango_font_description_copy(font_desc
)
834 gtk_widget_modify_style(widget
, rc_style
);
837 /* Confirm the action with the user. If action is NULL, the text from stock
840 gboolean
confirm(const gchar
*message
, const gchar
*stock
, const gchar
*action
)
842 return get_choice(PROJECT
, message
, 2,
843 GTK_STOCK_CANCEL
, NULL
,
850 void (*changed
)(Radios
*, gpointer data
);
851 gpointer changed_data
;
854 /* Create a new set of radio buttons.
855 * Use radios_add to add options, then radios_pack to put them into something.
856 * The radios object will self-destruct with the first widget it contains.
857 * changed(data) is called (if not NULL) when pack is called, and on any
860 Radios
*radios_new(void (*changed
)(Radios
*, gpointer data
), gpointer data
)
864 radios
= g_new(Radios
, 1);
866 radios
->widgets
= NULL
;
867 radios
->changed
= changed
;
868 radios
->changed_data
= data
;
873 static void radios_free(GtkWidget
*radio
, Radios
*radios
)
875 g_return_if_fail(radios
!= NULL
);
877 g_list_free(radios
->widgets
);
881 void radios_add(Radios
*radios
, const gchar
*tip
, gint value
,
882 const gchar
*label
, ...)
885 GSList
*group
= NULL
;
889 g_return_if_fail(radios
!= NULL
);
890 g_return_if_fail(label
!= NULL
);
892 va_start(args
, label
);
893 s
= g_strdup_vprintf(label
, args
);
898 GtkRadioButton
*first
= GTK_RADIO_BUTTON(radios
->widgets
->data
);
899 group
= gtk_radio_button_get_group(first
);
902 radio
= gtk_radio_button_new_with_label(group
, s
);
903 gtk_label_set_line_wrap(GTK_LABEL(GTK_BIN(radio
)->child
), TRUE
);
904 gtk_widget_show(radio
);
906 gtk_tooltips_set_tip(tooltips
, radio
, tip
, NULL
);
908 g_signal_connect(G_OBJECT(radio
), "destroy",
909 G_CALLBACK(radios_free
), radios
);
911 radios
->widgets
= g_list_prepend(radios
->widgets
, radio
);
912 g_object_set_data(G_OBJECT(radio
), "rox-radios-value",
913 GINT_TO_POINTER(value
));
916 static void radio_toggled(GtkToggleButton
*button
, Radios
*radios
)
918 g_return_if_fail(radios
!= NULL
);
920 if (button
&& !gtk_toggle_button_get_active(button
))
921 return; /* Stop double-notifies */
924 radios
->changed(radios
, radios
->changed_data
);
927 void radios_pack(Radios
*radios
, GtkBox
*box
)
931 g_return_if_fail(radios
!= NULL
);
933 for (next
= g_list_last(radios
->widgets
); next
; next
= next
->prev
)
935 GtkWidget
*button
= GTK_WIDGET(next
->data
);
937 gtk_box_pack_start(box
, button
, FALSE
, TRUE
, 0);
938 g_signal_connect(button
, "toggled",
939 G_CALLBACK(radio_toggled
), radios
);
941 radio_toggled(NULL
, radios
);
944 void radios_set_value(Radios
*radios
, gint value
)
948 g_return_if_fail(radios
!= NULL
);
950 for (next
= radios
->widgets
; next
; next
= next
->next
)
952 GtkToggleButton
*radio
= GTK_TOGGLE_BUTTON(next
->data
);
955 radio_value
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(radio
),
956 "rox-radios-value"));
958 if (radio_value
== value
)
960 gtk_toggle_button_set_active(radio
, TRUE
);
965 g_warning("Value %d not in radio group!", value
);
968 gint
radios_get_value(Radios
*radios
)
972 g_return_val_if_fail(radios
!= NULL
, -1);
974 for (next
= radios
->widgets
; next
; next
= next
->next
)
976 GtkToggleButton
*radio
= GTK_TOGGLE_BUTTON(next
->data
);
978 if (gtk_toggle_button_get_active(radio
))
979 return GPOINTER_TO_INT(g_object_get_data(
980 G_OBJECT(radio
), "rox-radios-value"));
983 g_warning("Nothing in the radio group is selected!");
988 /* Convert a list of URIs as a string into a GList of EscapedPath URIs.
989 * No unescaping is done.
990 * Lines beginning with # are skipped.
991 * The text block passed in is zero terminated (after the final CRLF)
993 GList
*uri_list_to_glist(const char *uri_list
)
1002 linebreak
= strchr(uri_list
, 13);
1004 if (!linebreak
|| linebreak
[1] != 10)
1006 delayed_error("uri_list_to_glist: %s",
1007 _("Incorrect or missing line "
1008 "break in text/uri-list data"));
1012 length
= linebreak
- uri_list
;
1014 if (length
&& uri_list
[0] != '#')
1015 list
= g_list_append(list
, g_strndup(uri_list
, length
));
1017 uri_list
= linebreak
+ 2;
1023 typedef struct _SimpleImageClass SimpleImageClass
;
1024 typedef struct _SimpleImage SimpleImage
;
1026 struct _SimpleImageClass
{
1027 GtkWidgetClass parent
;
1030 struct _SimpleImage
{
1037 #define SIMPLE_IMAGE(obj) (GTK_CHECK_CAST((obj), \
1038 simple_image_get_type(), SimpleImage))
1040 static void simple_image_finialize(GObject
*object
)
1042 SimpleImage
*image
= SIMPLE_IMAGE(object
);
1044 g_object_unref(G_OBJECT(image
->pixbuf
));
1045 image
->pixbuf
= NULL
;
1048 static void simple_image_size_request(GtkWidget
*widget
,
1049 GtkRequisition
*requisition
)
1051 SimpleImage
*image
= (SimpleImage
*) widget
;
1053 requisition
->width
= image
->width
;
1054 requisition
->height
= image
->height
;
1057 /* Render a pixbuf without messing up the clipping */
1058 void render_pixbuf(GdkPixbuf
*pixbuf
, GdkDrawable
*target
, GdkGC
*gc
,
1059 int x
, int y
, int width
, int height
)
1061 gdk_draw_pixbuf(target
, gc
, pixbuf
, 0, 0, x
, y
, width
, height
,
1062 GDK_RGB_DITHER_NORMAL
, 0, 0);
1066 static gint
simple_image_expose(GtkWidget
*widget
, GdkEventExpose
*event
)
1068 SimpleImage
*image
= (SimpleImage
*) widget
;
1071 gdk_gc_set_clip_region(widget
->style
->black_gc
, event
->region
);
1073 x
= widget
->allocation
.x
+
1074 (widget
->allocation
.width
- image
->width
) / 2;
1076 render_pixbuf(image
->pixbuf
, widget
->window
, widget
->style
->black_gc
,
1077 x
, widget
->allocation
.y
,
1078 image
->width
, image
->height
);
1080 gdk_gc_set_clip_region(widget
->style
->black_gc
, NULL
);
1084 static void simple_image_class_init(gpointer gclass
, gpointer data
)
1086 GObjectClass
*object
= (GObjectClass
*) gclass
;
1087 GtkWidgetClass
*widget
= (GtkWidgetClass
*) gclass
;
1089 object
->finalize
= simple_image_finialize
;
1090 widget
->size_request
= simple_image_size_request
;
1091 widget
->expose_event
= simple_image_expose
;
1094 static void simple_image_init(GTypeInstance
*object
, gpointer gclass
)
1096 GTK_WIDGET_SET_FLAGS(object
, GTK_NO_WINDOW
);
1099 static GType
simple_image_get_type(void)
1101 static GType type
= 0;
1105 static const GTypeInfo info
=
1107 sizeof (SimpleImageClass
),
1108 NULL
, /* base_init */
1109 NULL
, /* base_finalise */
1110 simple_image_class_init
,
1111 NULL
, /* class_finalise */
1112 NULL
, /* class_data */
1113 sizeof(SimpleImage
),
1114 0, /* n_preallocs */
1118 type
= g_type_register_static(gtk_widget_get_type(),
1119 "SimpleImage", &info
, 0);
1125 GtkWidget
*simple_image_new(GdkPixbuf
*pixbuf
)
1129 g_return_val_if_fail(pixbuf
!= NULL
, NULL
);
1131 image
= g_object_new(simple_image_get_type(), NULL
);
1133 image
->pixbuf
= pixbuf
;
1134 g_object_ref(G_OBJECT(pixbuf
));
1136 image
->width
= gdk_pixbuf_get_width(pixbuf
);
1137 image
->height
= gdk_pixbuf_get_height(pixbuf
);
1139 return GTK_WIDGET(image
);
1142 /* Whether a line l1 long starting from n1 overlaps a line l2 from n2 */
1143 inline static gboolean
gui_ranges_overlap(int n1
, int l1
, int n2
, int l2
)
1145 return (n1
> n2
&& n1
< n2
+ l2
) ||
1146 (n1
+ l1
> n2
&& n1
+ l1
< n2
+ l2
) ||
1147 (n1
<= n2
&& n1
+ l1
>= n2
+ l2
);
1150 static void gui_get_monitor_adjacent(int monitor
, MonitorAdjacent
*adj
)
1154 adj
->left
= adj
->right
= adj
->top
= adj
->bottom
= FALSE
;
1156 for (m
= 0; m
< n_monitors
; ++m
)
1160 if (gui_ranges_overlap(monitor_geom
[m
].y
,
1161 monitor_geom
[m
].height
,
1162 monitor_geom
[monitor
].y
,
1163 monitor_geom
[monitor
].height
))
1165 if (monitor_geom
[m
].x
< monitor_geom
[monitor
].x
)
1169 else if (monitor_geom
[m
].x
> monitor_geom
[monitor
].x
)
1174 if (gui_ranges_overlap(monitor_geom
[m
].x
,
1175 monitor_geom
[m
].width
,
1176 monitor_geom
[monitor
].x
,
1177 monitor_geom
[monitor
].width
))
1179 if (monitor_geom
[m
].y
< monitor_geom
[monitor
].y
)
1183 else if (monitor_geom
[m
].y
> monitor_geom
[monitor
].y
)
1191 static void rox_wmspec_change_state(gboolean add
, GdkWindow
*window
,
1192 GdkAtom state1
, GdkAtom state2
)
1194 GdkDisplay
*display
= gdk_drawable_get_display(GDK_DRAWABLE(window
));
1197 #define _NET_WM_STATE_REMOVE 0 /* remove/unset property */
1198 #define _NET_WM_STATE_ADD 1 /* add/set property */
1199 #define _NET_WM_STATE_TOGGLE 2 /* toggle property */
1201 xev
.xclient
.type
= ClientMessage
;
1202 xev
.xclient
.serial
= 0;
1203 xev
.xclient
.send_event
= True
;
1204 xev
.xclient
.window
= GDK_WINDOW_XID(window
);
1205 xev
.xclient
.message_type
= gdk_x11_get_xatom_by_name_for_display(
1206 display
, "_NET_WM_STATE");
1207 xev
.xclient
.format
= 32;
1208 xev
.xclient
.data
.l
[0] = add
? _NET_WM_STATE_ADD
: _NET_WM_STATE_REMOVE
;
1209 xev
.xclient
.data
.l
[1] = gdk_x11_atom_to_xatom_for_display(display
,
1211 xev
.xclient
.data
.l
[2] = gdk_x11_atom_to_xatom_for_display(display
,
1213 xev
.xclient
.data
.l
[3] = 0;
1214 xev
.xclient
.data
.l
[4] = 0;
1216 XSendEvent(GDK_DISPLAY_XDISPLAY(display
),
1218 gdk_screen_get_root_window(
1219 gdk_drawable_get_screen(GDK_DRAWABLE(window
)))),
1221 SubstructureRedirectMask
| SubstructureNotifyMask
,
1225 void keep_below(GdkWindow
*window
, gboolean setting
)
1227 g_return_if_fail(GDK_IS_WINDOW(window
));
1229 if (GDK_WINDOW_DESTROYED(window
))
1232 if (gdk_window_is_visible(window
))
1236 rox_wmspec_change_state(FALSE
, window
,
1237 gdk_atom_intern("_NET_WM_STATE_ABOVE", FALSE
),
1240 rox_wmspec_change_state(setting
, window
,
1241 gdk_atom_intern("_NET_WM_STATE_BELOW", FALSE
),
1247 #if GTK_CHECK_VERSION(2,4,0)
1248 gdk_synthesize_window_state(window
,
1249 setting
? GDK_WINDOW_STATE_ABOVE
:
1250 GDK_WINDOW_STATE_BELOW
,
1251 setting
? GDK_WINDOW_STATE_BELOW
: 0);
1258 size_prepared_cb (GdkPixbufLoader
*loader
,
1266 gboolean preserve_aspect_ratio
;
1269 g_return_if_fail (width
> 0 && height
> 0);
1271 if(info
->preserve_aspect_ratio
) {
1272 if ((double)height
* (double)info
->width
>
1273 (double)width
* (double)info
->height
) {
1274 width
= 0.5 + (double)width
* (double)info
->height
/ (double)height
;
1275 height
= info
->height
;
1277 height
= 0.5 + (double)height
* (double)info
->width
/ (double)width
;
1278 width
= info
->width
;
1281 width
= info
->width
;
1282 height
= info
->height
;
1285 gdk_pixbuf_loader_set_size (loader
, width
, height
);
1289 * rox_pixbuf_new_from_file_at_scale:
1290 * @filename: Name of file to load.
1291 * @width: The width the image should have
1292 * @height: The height the image should have
1293 * @preserve_aspect_ratio: %TRUE to preserve the image's aspect ratio
1294 * @error: Return location for an error
1296 * Creates a new pixbuf by loading an image from a file. The file format is
1297 * detected automatically. If %NULL is returned, then @error will be set.
1298 * Possible errors are in the #GDK_PIXBUF_ERROR and #G_FILE_ERROR domains.
1299 * The image will be scaled to fit in the requested size, optionally preserving
1300 * the image's aspect ratio.
1302 * Return value: A newly-created pixbuf with a reference count of 1, or %NULL
1303 * if any of several error conditions occurred: the file could not be opened,
1304 * there was no loader for the file's format, there was not enough memory to
1305 * allocate the image buffer, or the image file contained invalid data.
1307 * Taken from GTK 2.6.
1310 rox_pixbuf_new_from_file_at_scale (const char *filename
,
1313 gboolean preserve_aspect_ratio
,
1317 GdkPixbufLoader
*loader
;
1320 guchar buffer
[4096];
1326 gboolean preserve_aspect_ratio
;
1329 g_return_val_if_fail (filename
!= NULL
, NULL
);
1330 g_return_val_if_fail (width
> 0 && height
> 0, NULL
);
1332 f
= fopen (filename
, "rb");
1334 gchar
*utf8_filename
= g_filename_to_utf8 (filename
, -1,
1338 g_file_error_from_errno (errno
),
1339 _("Failed to open file '%s': %s"),
1340 utf8_filename
? utf8_filename
: "???",
1341 g_strerror (errno
));
1342 g_free (utf8_filename
);
1346 loader
= gdk_pixbuf_loader_new ();
1349 info
.height
= height
;
1350 info
.preserve_aspect_ratio
= preserve_aspect_ratio
;
1352 g_signal_connect (loader
, "size-prepared", G_CALLBACK (size_prepared_cb
), &info
);
1354 while (!feof (f
) && !ferror (f
)) {
1355 length
= fread (buffer
, 1, sizeof (buffer
), f
);
1357 if (!gdk_pixbuf_loader_write (loader
, buffer
, length
, error
)) {
1358 gdk_pixbuf_loader_close (loader
, NULL
);
1360 g_object_unref (loader
);
1367 if (!gdk_pixbuf_loader_close (loader
, error
)) {
1368 g_object_unref (loader
);
1372 pixbuf
= gdk_pixbuf_loader_get_pixbuf (loader
);
1375 gchar
*utf8_filename
= g_filename_to_utf8 (filename
, -1,
1378 g_object_unref (loader
);
1382 GDK_PIXBUF_ERROR_FAILED
,
1383 _("Failed to load image '%s': reason not known, probably a corrupt image file"),
1384 utf8_filename
? utf8_filename
: "???");
1385 g_free (utf8_filename
);
1389 g_object_ref (pixbuf
);
1391 g_object_unref (loader
);
1396 /* Make the name bolder and larger.
1397 * scale_factor can be PANGO_SCALE_X_LARGE, etc.
1399 void make_heading(GtkWidget
*label
, double scale_factor
)
1401 PangoAttribute
*attr
;
1402 PangoAttrList
*list
;
1404 list
= pango_attr_list_new();
1406 attr
= pango_attr_weight_new(PANGO_WEIGHT_BOLD
);
1407 attr
->start_index
= 0;
1408 attr
->end_index
= -1;
1409 pango_attr_list_insert(list
, attr
);
1411 attr
= pango_attr_scale_new(scale_factor
);
1412 attr
->start_index
= 0;
1413 attr
->end_index
= -1;
1414 pango_attr_list_insert(list
, attr
);
1416 gtk_label_set_attributes(GTK_LABEL(label
), list
);
1419 /* Launch a program using 0launch.
1420 * If button-3 is used, open the GUI with -g.
1422 void launch_uri(const char *uri
)
1424 const char *argv
[] = {"0launch", NULL
, NULL
, NULL
};
1425 const char *uri_0launch
= "/uri/0install/zero-install.sourceforge.net"
1428 if (!available_in_path(argv
[0]))
1430 if (access(uri_0launch
, X_OK
) == 0)
1431 argv
[0] = uri_0launch
;
1434 delayed_error(_("This program (%s) cannot be run, "
1435 "as the 0launch command is not available. "
1436 "It can be downloaded from here:\n\n"
1437 "http://0install.net/injector.html"),
1443 if (current_event_button() == 3)
1451 rox_spawn(NULL
, argv
);
1454 static gint
button3_button_pressed(GtkButton
*button
,
1455 GdkEventButton
*event
,
1458 if (event
->button
== 3)
1460 gtk_grab_add(GTK_WIDGET(button
));
1461 gtk_button_pressed(button
);
1469 static gint
button3_button_released(GtkButton
*button
,
1470 GdkEventButton
*event
,
1471 FilerWindow
*filer_window
)
1473 if (event
->button
== 3)
1475 gtk_grab_remove(GTK_WIDGET(button
));
1476 gtk_button_released(button
);
1484 void allow_right_click(GtkWidget
*button
)
1486 g_signal_connect(button
, "button_press_event",
1487 G_CALLBACK(button3_button_pressed
), NULL
);
1488 g_signal_connect(button
, "button_release_event",
1489 G_CALLBACK(button3_button_released
), NULL
);
1492 /* Return mouse button used in the current event, or -1 if none (no event,
1495 gint
current_event_button(void)
1497 GdkEventButton
*bev
;
1500 bev
= (GdkEventButton
*) gtk_get_current_event();
1503 (bev
->type
== GDK_BUTTON_PRESS
|| bev
->type
== GDK_BUTTON_RELEASE
))
1504 button
= bev
->button
;
1506 gdk_event_free((GdkEvent
*) bev
);
1511 /* Create a new pixbuf by colourizing 'src' to 'color'. If the function fails,
1512 * 'src' will be returned (with an increased reference count, so it is safe to
1513 * g_object_unref() the return value whether the function fails or not).
1515 GdkPixbuf
*create_spotlight_pixbuf(GdkPixbuf
*src
, GdkColor
*color
)
1517 guchar opacity
= 192;
1518 guchar alpha
= 255 - opacity
;
1520 GdkColorspace colorspace
;
1521 int width
, height
, src_rowstride
, dst_rowstride
, x
, y
;
1522 int n_channels
, bps
;
1524 guchar
*spixels
, *dpixels
, *src_pixels
, *dst_pixels
;
1527 has_alpha
= gdk_pixbuf_get_has_alpha(src
);
1528 colorspace
= gdk_pixbuf_get_colorspace(src
);
1529 n_channels
= gdk_pixbuf_get_n_channels(src
);
1530 bps
= gdk_pixbuf_get_bits_per_sample(src
);
1532 if ((colorspace
!= GDK_COLORSPACE_RGB
) ||
1533 (!has_alpha
&& n_channels
!= 3) ||
1534 (has_alpha
&& n_channels
!= 4) ||
1538 width
= gdk_pixbuf_get_width(src
);
1539 height
= gdk_pixbuf_get_height(src
);
1541 dst
= gdk_pixbuf_new(colorspace
, has_alpha
, bps
, width
, height
);
1545 src_pixels
= gdk_pixbuf_get_pixels(src
);
1546 dst_pixels
= gdk_pixbuf_get_pixels(dst
);
1547 src_rowstride
= gdk_pixbuf_get_rowstride(src
);
1548 dst_rowstride
= gdk_pixbuf_get_rowstride(dst
);
1550 r
= opacity
* (color
->red
>> 8);
1551 g
= opacity
* (color
->green
>> 8);
1552 b
= opacity
* (color
->blue
>> 8);
1554 for (y
= 0; y
< height
; y
++)
1556 spixels
= src_pixels
+ y
* src_rowstride
;
1557 dpixels
= dst_pixels
+ y
* dst_rowstride
;
1558 for (x
= 0; x
< width
; x
++)
1560 *dpixels
++ = (*spixels
++ * alpha
+ r
) >> 8;
1561 *dpixels
++ = (*spixels
++ * alpha
+ g
) >> 8;
1562 *dpixels
++ = (*spixels
++ * alpha
+ b
) >> 8;
1564 *dpixels
++ = *spixels
++;