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"
48 gint screen_width
, screen_height
;
51 GdkRectangle
*monitor_geom
= NULL
;
52 gint monitor_width
, monitor_height
;
53 MonitorAdjacent
*monitor_adjacent
;
55 static GdkAtom xa_cardinal
;
56 GdkAtom xa__NET_WORKAREA
= GDK_NONE
;
57 GdkAtom xa__NET_WM_DESKTOP
= GDK_NONE
;
58 GdkAtom xa__NET_CURRENT_DESKTOP
= GDK_NONE
;
59 GdkAtom xa__NET_NUMBER_OF_DESKTOPS
= GDK_NONE
;
61 static GtkWidget
*current_dialog
= NULL
;
63 static GtkWidget
*tip_widget
= NULL
;
64 static time_t tip_time
= 0; /* Time tip widget last closed */
65 static gint tip_timeout
= 0; /* When primed */
67 /* Static prototypes */
68 static void run_error_info_dialog(GtkMessageType type
, const char *message
,
70 static GType
simple_image_get_type(void);
71 static void gui_get_monitor_adjacent(int monitor
, MonitorAdjacent
*adj
);
73 void gui_store_screen_geometry(GdkScreen
*screen
)
77 screen_width
= gdk_screen_get_width(screen
);
78 screen_height
= gdk_screen_get_height(screen
);
81 g_free(monitor_adjacent
);
83 monitor_width
= monitor_height
= G_MAXINT
;
84 n_monitors
= gdk_screen_get_n_monitors(screen
);
87 monitor_geom
= g_new(GdkRectangle
, n_monitors
? n_monitors
: 1);
91 for (mon
= 0; mon
< n_monitors
; ++mon
)
93 gdk_screen_get_monitor_geometry(screen
, mon
,
95 if (monitor_geom
[mon
].width
< monitor_width
)
96 monitor_width
= monitor_geom
[mon
].width
;
97 if (monitor_geom
[mon
].height
< monitor_height
)
98 monitor_height
= monitor_geom
[mon
].height
;
100 monitor_adjacent
= g_new(MonitorAdjacent
, n_monitors
);
101 for (mon
= 0; mon
< n_monitors
; ++mon
)
103 gui_get_monitor_adjacent(mon
, &monitor_adjacent
[mon
]);
109 monitor_geom
[0].x
= monitor_geom
[0].y
= 0;
110 monitor_width
= monitor_geom
[0].width
= screen_width
;
111 monitor_height
= monitor_geom
[0].height
= screen_height
;
112 monitor_adjacent
= g_new0(MonitorAdjacent
, 1);
117 void gui_support_init()
121 xa_cardinal
= gdk_atom_intern("CARDINAL", FALSE
);
122 xa__NET_WORKAREA
= gdk_atom_intern("_NET_WORKAREA", FALSE
);
123 xa__NET_WM_DESKTOP
= gdk_atom_intern("_NET_WM_DESKTOP", FALSE
);
124 xa__NET_CURRENT_DESKTOP
= gdk_atom_intern("_NET_CURRENT_DESKTOP",
126 xa__NET_NUMBER_OF_DESKTOPS
= gdk_atom_intern("_NET_NUMBER_OF_DESKTOPS",
129 gui_store_screen_geometry(gdk_screen_get_default());
131 /* Work around the scrollbar placement bug */
132 klass
= g_type_class_ref(gtk_scrolled_window_get_type());
133 ((GtkScrolledWindowClass
*) klass
)->scrollbar_spacing
= 0;
134 /* (don't unref, ever) */
137 /* Open a modal dialog box showing a message.
138 * The user can choose from a selection of buttons at the bottom.
139 * Returns -1 if the window is destroyed, or the number of the button
140 * if one is clicked (starting from zero).
142 * If a dialog is already open, returns -1 without waiting AND
143 * brings the current dialog to the front.
145 * Each button has two arguments, a GTK_STOCK icon and some text. If the
146 * text is NULL, the stock's text is used.
148 int get_choice(const char *title
,
150 int number_of_buttons
, ...)
153 GtkWidget
*button
= NULL
;
159 gtk_widget_hide(current_dialog
);
160 gtk_widget_show(current_dialog
);
164 current_dialog
= dialog
= gtk_message_dialog_new(NULL
,
166 GTK_MESSAGE_QUESTION
,
169 gtk_window_set_position(GTK_WINDOW(dialog
), GTK_WIN_POS_CENTER
);
171 va_start(ap
, number_of_buttons
);
173 for (i
= 0; i
< number_of_buttons
; i
++)
175 const char *stock
= va_arg(ap
, char *);
176 const char *text
= va_arg(ap
, char *);
179 button
= button_new_mixed(stock
, text
);
181 button
= gtk_button_new_from_stock(stock
);
183 GTK_WIDGET_SET_FLAGS(button
, GTK_CAN_DEFAULT
);
184 gtk_widget_show(button
);
186 gtk_dialog_add_action_widget(GTK_DIALOG(current_dialog
),
190 gtk_window_set_title(GTK_WINDOW(dialog
), title
);
191 gtk_window_set_position(GTK_WINDOW(dialog
), GTK_WIN_POS_CENTER
);
193 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), i
- 1);
197 retval
= gtk_dialog_run(GTK_DIALOG(dialog
));
198 if (retval
== GTK_RESPONSE_NONE
)
200 gtk_widget_destroy(dialog
);
202 current_dialog
= NULL
;
207 void info_message(const char *message
, ...)
211 va_start(args
, message
);
213 run_error_info_dialog(GTK_MESSAGE_INFO
, message
, args
);
216 /* Display a message in a window with "ROX-Filer" as title */
217 void report_error(const char *message
, ...)
221 va_start(args
, message
);
223 run_error_info_dialog(GTK_MESSAGE_ERROR
, message
, args
);
226 void set_cardinal_property(GdkWindow
*window
, GdkAtom prop
, gulong value
)
228 gdk_property_change(window
, prop
, xa_cardinal
, 32,
229 GDK_PROP_MODE_REPLACE
, (gchar
*) &value
, 1);
232 gboolean
get_cardinal_property(GdkWindow
*window
, GdkAtom prop
, gulong length
,
233 gulong
*data
, gint
*actual_length
)
236 gint actual_format
, act_length
;
242 /* Cardinals are format=32 so the length in bytes is 4 * number of
244 ok
=gdk_property_get(window
, prop
, xa_cardinal
,
246 &actual_type
, &actual_format
,
252 /* Check correct format */
253 if(actual_format
!=32)
259 /* Actual data for cardinals returned as longs, which may be 64 bit */
260 if(act_length
/sizeof(gulong
)>length
)
266 /* Copy data into return array */
268 for(i
=0; i
<act_length
/sizeof(gulong
); i
++)
271 *actual_length
=act_length
/sizeof(gulong
);
276 int get_current_desktop(void)
280 Window root
=GDK_ROOT_WINDOW();
281 GdkWindow
*gdk_root
=gdk_window_foreign_new(root
);
284 if(get_cardinal_property(gdk_root
, xa__NET_CURRENT_DESKTOP
, 1,
285 ¤t
, &act_len
) && act_len
==1)
291 int get_number_of_desktops(void)
295 Window root
=GDK_ROOT_WINDOW();
296 GdkWindow
*gdk_root
=gdk_window_foreign_new(root
);
299 if(get_cardinal_property(gdk_root
, xa__NET_NUMBER_OF_DESKTOPS
, 1,
300 &num
, &act_len
) && act_len
==1)
306 /* Get the working area for the desktop, excluding things like the Gnome
308 void get_work_area(int *x
, int *y
, int *width
, int *height
)
312 Window root
=GDK_ROOT_WINDOW();
313 GdkWindow
*gdk_root
=gdk_window_foreign_new(root
);
315 int idesk
, ndesk
, nval
;
317 idesk
=get_current_desktop();
318 ndesk
=get_number_of_desktops();
320 work_area
=g_new(gulong
, nval
);
322 if(get_cardinal_property(gdk_root
, xa__NET_WORKAREA
, nval
,
323 work_area
, &act_len
) &&
326 x0
= work_area
[idesk
*4+0];
327 y0
= work_area
[idesk
*4+1];
328 w0
= work_area
[idesk
*4+2];
329 h0
= work_area
[idesk
*4+3];
350 /* NB: Also used for pinned icons.
351 * TODO: Set the level here too.
353 void make_panel_window(GtkWidget
*widget
)
355 static gboolean need_init
= TRUE
;
356 static GdkAtom xa_state
, xa_atom
, xa_hints
, xa_win_hints
;
357 GdkWindow
*window
= widget
->window
;
358 long wm_hints_values
[] = {1, False
, 0, 0, 0, 0, 0, 0};
359 GdkAtom wm_protocols
[2];
361 g_return_if_fail(window
!= NULL
);
363 if (o_override_redirect
.int_value
)
365 gdk_window_set_override_redirect(window
, TRUE
);
371 xa_win_hints
= gdk_atom_intern("_WIN_HINTS", FALSE
);
372 xa_state
= gdk_atom_intern("_WIN_STATE", FALSE
);
373 xa_atom
= gdk_atom_intern("ATOM", FALSE
);
374 xa_hints
= gdk_atom_intern("WM_HINTS", FALSE
);
379 gdk_window_set_decorations(window
, 0);
380 gdk_window_set_functions(window
, 0);
381 gtk_window_set_resizable(GTK_WINDOW(widget
), FALSE
);
383 /* Don't hide panel/pinboard windows initially (WIN_STATE_HIDDEN).
384 * Needed for IceWM - Christopher Arndt <chris.arndt@web.de>
386 set_cardinal_property(window
, xa_state
,
388 WIN_STATE_FIXED_POSITION
| WIN_STATE_ARRANGE_IGNORE
);
390 set_cardinal_property(window
, xa_win_hints
,
391 WIN_HINTS_SKIP_FOCUS
| WIN_HINTS_SKIP_WINLIST
|
392 WIN_HINTS_SKIP_TASKBAR
);
394 /* Appear on all workspaces */
395 set_cardinal_property(window
, xa__NET_WM_DESKTOP
, 0xffffffff);
397 gdk_property_change(window
, xa_hints
, xa_hints
, 32,
398 GDK_PROP_MODE_REPLACE
, (guchar
*) wm_hints_values
,
399 sizeof(wm_hints_values
) / sizeof(long));
401 wm_protocols
[0] = gdk_atom_intern("WM_DELETE_WINDOW", FALSE
);
402 wm_protocols
[1] = gdk_atom_intern("_NET_WM_PING", FALSE
);
403 gdk_property_change(window
,
404 gdk_atom_intern("WM_PROTOCOLS", FALSE
), xa_atom
, 32,
405 GDK_PROP_MODE_REPLACE
, (guchar
*) wm_protocols
,
406 sizeof(wm_protocols
) / sizeof(GdkAtom
));
408 gdk_window_set_skip_taskbar_hint(window
, TRUE
);
409 gdk_window_set_skip_pager_hint(window
, TRUE
);
411 if (g_object_class_find_property(G_OBJECT_GET_CLASS(widget
),
414 GValue vfalse
= { 0, };
415 g_value_init(&vfalse
, G_TYPE_BOOLEAN
);
416 g_value_set_boolean(&vfalse
, FALSE
);
417 g_object_set_property(G_OBJECT(widget
),
418 "accept_focus", &vfalse
);
419 g_value_unset(&vfalse
);
423 static gboolean
error_idle_cb(gpointer data
)
425 char **error
= (char **) data
;
427 report_error("%s", *error
);
434 /* Display an error with "ROX-Filer" as title next time we are idle.
435 * If multiple errors are reported this way before the window is opened,
436 * all are displayed in a single window.
437 * If an error is reported while the error window is open, it is discarded.
439 void delayed_error(const char *error
, ...)
441 static char *delayed_error_data
= NULL
;
445 g_return_if_fail(error
!= NULL
);
447 old
= delayed_error_data
;
449 va_start(args
, error
);
450 new = g_strdup_vprintf(error
, args
);
455 delayed_error_data
= g_strconcat(old
,
463 delayed_error_data
= new;
464 g_idle_add(error_idle_cb
, &delayed_error_data
);
470 /* Load the file into memory. Return TRUE on success.
471 * Block is zero terminated (but this is not included in the length).
473 gboolean
load_file(const char *pathname
, char **data_out
, long *length_out
)
476 GError
*error
= NULL
;
478 if (!g_file_get_contents(pathname
, data_out
, &len
, &error
))
480 delayed_error("%s", error
->message
);
490 GtkWidget
*new_help_button(HelpFunc show_help
, gpointer data
)
494 b
= gtk_button_new();
495 gtk_button_set_relief(GTK_BUTTON(b
), GTK_RELIEF_NONE
);
496 icon
= gtk_image_new_from_stock(GTK_STOCK_HELP
,
497 GTK_ICON_SIZE_SMALL_TOOLBAR
);
498 gtk_container_add(GTK_CONTAINER(b
), icon
);
499 g_signal_connect_swapped(b
, "clicked", G_CALLBACK(show_help
), data
);
501 GTK_WIDGET_UNSET_FLAGS(b
, GTK_CAN_FOCUS
);
506 /* Read file into memory. Call parse_line(guchar *line) for each line
507 * in the file. Callback returns NULL on success, or an error message
508 * if something went wrong. Only the first error is displayed to the user.
510 void parse_file(const char *path
, ParseFunc
*parse_line
)
514 gboolean seen_error
= FALSE
;
516 if (load_file(path
, &data
, &length
))
523 if (strncmp(data
, "<?xml ", 6) == 0)
525 delayed_error(_("Attempt to read an XML file as "
526 "a text file. File '%s' may be "
527 "corrupted."), path
);
531 while (line
&& *line
)
533 eol
= strchr(line
, '\n');
537 error
= parse_line(line
);
539 if (error
&& !seen_error
)
542 _("Error in '%s' file at line %d: "
544 "This may be due to upgrading from a previous version of "
545 "ROX-Filer. Open the Options window and try changing something "
546 "and then changing it back (causing the file to be resaved).\n"
547 "Further errors will be ignored."),
563 /* Returns the position of the pointer.
564 * TRUE if any modifier keys or mouse buttons are pressed.
566 gboolean
get_pointer_xy(int *x
, int *y
)
570 gdk_window_get_pointer(NULL
, x
, y
, &mask
);
575 int get_monitor_under_pointer(void)
579 get_pointer_xy(&x
, &y
);
580 return gdk_screen_get_monitor_at_point(gdk_screen_get_default(), x
, y
);
583 #define DECOR_BORDER 32
585 /* Centre the window at these coords */
586 void centre_window(GdkWindow
*window
, int x
, int y
)
591 g_return_if_fail(window
!= NULL
);
593 m
= gdk_screen_get_monitor_at_point(gdk_screen_get_default(), x
, y
);
595 gdk_drawable_get_size(window
, &w
, &h
);
600 gdk_window_move(window
,
601 CLAMP(x
, DECOR_BORDER
+ monitor_geom
[m
].x
,
602 monitor_geom
[m
].x
+ monitor_geom
[m
].width
604 CLAMP(y
, DECOR_BORDER
+ monitor_geom
[m
].y
,
605 monitor_geom
[m
].y
+ monitor_geom
[m
].height
606 - h
- DECOR_BORDER
));
609 static void run_error_info_dialog(GtkMessageType type
, const char *message
,
615 g_return_if_fail(message
!= NULL
);
617 s
= g_strdup_vprintf(message
, args
);
620 dialog
= gtk_message_dialog_new(NULL
,
625 gtk_window_set_position(GTK_WINDOW(dialog
), GTK_WIN_POS_CENTER
);
626 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), GTK_RESPONSE_OK
);
627 gtk_dialog_run(GTK_DIALOG(dialog
));
628 gtk_widget_destroy(dialog
);
633 static GtkWidget
*current_wink_widget
= NULL
;
634 static gint wink_timeout
= -1; /* Called when it's time to stop */
635 static gulong wink_destroy
; /* Called if the widget dies first */
637 static gboolean
end_wink(gpointer data
)
639 gtk_drag_unhighlight(current_wink_widget
);
641 g_signal_handler_disconnect(current_wink_widget
, wink_destroy
);
643 current_wink_widget
= NULL
;
648 static void cancel_wink(void)
650 g_source_remove(wink_timeout
);
654 static void wink_widget_died(gpointer data
)
656 current_wink_widget
= NULL
;
657 g_source_remove(wink_timeout
);
660 /* Draw a black box around this widget, briefly.
661 * Note: uses the drag highlighting code for now.
663 void wink_widget(GtkWidget
*widget
)
665 g_return_if_fail(widget
!= NULL
);
667 if (current_wink_widget
)
670 current_wink_widget
= widget
;
671 gtk_drag_highlight(current_wink_widget
);
673 wink_timeout
= g_timeout_add(300, (GSourceFunc
) end_wink
, NULL
);
675 wink_destroy
= g_signal_connect_swapped(widget
, "destroy",
676 G_CALLBACK(wink_widget_died
), NULL
);
679 static gboolean
idle_destroy_cb(GtkWidget
*widget
)
681 gtk_widget_unref(widget
);
682 gtk_widget_destroy(widget
);
686 /* Destroy the widget in an idle callback */
687 void destroy_on_idle(GtkWidget
*widget
)
689 gtk_widget_ref(widget
);
690 g_idle_add((GSourceFunc
) idle_destroy_cb
, widget
);
693 /* Spawn a child process (as spawn_full), and report errors.
694 * Returns the child's PID on succes, or 0 on failure.
696 gint
rox_spawn(const gchar
*dir
, const gchar
**argv
)
698 GError
*error
= NULL
;
701 if (!g_spawn_async_with_pipes(dir
, (gchar
**) argv
, NULL
,
702 G_SPAWN_DO_NOT_REAP_CHILD
| G_SPAWN_STDOUT_TO_DEV_NULL
|
704 NULL
, NULL
, /* Child setup fn */
705 &pid
, /* Child PID */
706 NULL
, NULL
, NULL
, /* Standard pipes */
709 delayed_error("%s", error
? error
->message
: "(null)");
718 GtkWidget
*button_new_image_text(GtkWidget
*image
, const char *message
)
720 GtkWidget
*button
, *align
, *hbox
, *label
;
722 button
= gtk_button_new();
723 label
= gtk_label_new_with_mnemonic(message
);
724 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), button
);
726 hbox
= gtk_hbox_new(FALSE
, 2);
728 align
= gtk_alignment_new(0.5, 0.5, 0.0, 0.0);
730 gtk_box_pack_start(GTK_BOX(hbox
), image
, FALSE
, FALSE
, 0);
731 gtk_box_pack_end(GTK_BOX(hbox
), label
, FALSE
, FALSE
, 0);
733 gtk_container_add(GTK_CONTAINER(button
), align
);
734 gtk_container_add(GTK_CONTAINER(align
), hbox
);
735 gtk_widget_show_all(align
);
740 GtkWidget
*button_new_mixed(const char *stock
, const char *message
)
742 return button_new_image_text(gtk_image_new_from_stock(stock
,
743 GTK_ICON_SIZE_BUTTON
),
747 /* Highlight entry in red if 'error' is TRUE */
748 void entry_set_error(GtkWidget
*entry
, gboolean error
)
750 const GdkColor red
= {0, 0xffff, 0, 0};
751 const GdkColor white
= {0, 0xffff, 0xffff, 0xffff};
753 gtk_widget_modify_text(entry
, GTK_STATE_NORMAL
, error
? &red
: NULL
);
754 gtk_widget_modify_base(entry
, GTK_STATE_NORMAL
, error
? &white
: NULL
);
757 /* Change stacking position of higher to be just above lower.
758 * If lower is NULL, put higher at the bottom of the stack.
760 void window_put_just_above(GdkWindow
*higher
, GdkWindow
*lower
)
762 if (o_override_redirect
.int_value
&& lower
)
764 XWindowChanges restack
;
766 gdk_error_trap_push();
768 restack
.stack_mode
= Above
;
770 restack
.sibling
= GDK_WINDOW_XWINDOW(lower
);
772 XConfigureWindow(gdk_display
, GDK_WINDOW_XWINDOW(higher
),
773 CWSibling
| CWStackMode
, &restack
);
776 if (gdk_error_trap_pop())
777 g_warning("window_put_just_above()\n");
780 gdk_window_lower(higher
); /* To bottom of stack */
783 /* Copied from Gtk */
784 static GtkFixedChild
* fixed_get_child(GtkFixed
*fixed
, GtkWidget
*widget
)
788 children
= fixed
->children
;
791 GtkFixedChild
*child
;
793 child
= children
->data
;
794 children
= children
->next
;
796 if (child
->widget
== widget
)
803 /* Like gtk_fixed_move(), except not insanely slow */
804 void fixed_move_fast(GtkFixed
*fixed
, GtkWidget
*widget
, int x
, int y
)
806 GtkFixedChild
*child
;
808 child
= fixed_get_child(fixed
, widget
);
812 gtk_widget_freeze_child_notify(widget
);
815 gtk_widget_child_notify(widget
, "x");
818 gtk_widget_child_notify(widget
, "y");
820 gtk_widget_thaw_child_notify(widget
);
822 if (GTK_WIDGET_VISIBLE(widget
) && GTK_WIDGET_VISIBLE(fixed
))
824 int border_width
= GTK_CONTAINER(fixed
)->border_width
;
825 GtkAllocation child_allocation
;
826 GtkRequisition child_requisition
;
828 gtk_widget_get_child_requisition(child
->widget
,
830 child_allocation
.x
= child
->x
+ border_width
;
831 child_allocation
.y
= child
->y
+ border_width
;
833 child_allocation
.x
+= GTK_WIDGET(fixed
)->allocation
.x
;
834 child_allocation
.y
+= GTK_WIDGET(fixed
)->allocation
.y
;
836 child_allocation
.width
= child_requisition
.width
;
837 child_allocation
.height
= child_requisition
.height
;
838 gtk_widget_size_allocate(child
->widget
, &child_allocation
);
842 /* Draw the black border */
843 static gint
tooltip_draw(GtkWidget
*w
)
845 gdk_draw_rectangle(w
->window
, w
->style
->fg_gc
[w
->state
], FALSE
, 0, 0,
846 w
->allocation
.width
- 1, w
->allocation
.height
- 1);
851 /* When the tips window closed, record the time. If we try to open another
852 * tip soon, it will appear more quickly.
854 static void tooltip_destroyed(gpointer data
)
859 /* Display a tooltip-like widget near the pointer with 'text'. If 'text' is
860 * NULL, close any current tooltip.
862 void tooltip_show(guchar
*text
)
871 g_source_remove(tip_timeout
);
877 gtk_widget_destroy(tip_widget
);
885 tip_widget
= gtk_window_new(GTK_WINDOW_POPUP
);
886 gtk_widget_set_app_paintable(tip_widget
, TRUE
);
887 gtk_widget_set_name(tip_widget
, "gtk-tooltips");
889 g_signal_connect_swapped(tip_widget
, "expose_event",
890 G_CALLBACK(tooltip_draw
), tip_widget
);
892 label
= gtk_label_new(text
);
893 gtk_misc_set_padding(GTK_MISC(label
), 4, 2);
894 gtk_container_add(GTK_CONTAINER(tip_widget
), label
);
895 gtk_widget_show(label
);
896 gtk_widget_realize(tip_widget
);
898 w
= tip_widget
->allocation
.width
;
899 h
= tip_widget
->allocation
.height
;
900 gdk_window_get_pointer(NULL
, &x
, &py
, NULL
);
902 m
= gdk_screen_get_monitor_at_point(gdk_screen_get_default(), x
, py
);
905 y
= py
+ 12; /* I don't know the pointer height so I use a constant */
907 /* Now check for screen boundaries */
908 x
= CLAMP(x
, monitor_geom
[m
].x
,
909 monitor_geom
[m
].x
+ monitor_geom
[m
].width
- w
);
910 y
= CLAMP(y
, monitor_geom
[m
].y
,
911 monitor_geom
[m
].y
+ monitor_geom
[m
].height
- h
);
913 /* And again test if pointer is over the tooltip window */
914 if (py
>= y
&& py
<= y
+ h
)
916 gtk_window_move(GTK_WINDOW(tip_widget
), x
, y
);
917 gtk_widget_show(tip_widget
);
919 g_signal_connect_swapped(tip_widget
, "destroy",
920 G_CALLBACK(tooltip_destroyed
), NULL
);
924 /* Call callback(user_data) after a while, unless cancelled.
925 * Object is refd now and unref when cancelled / after callback called.
927 void tooltip_prime(GtkFunction callback
, GObject
*object
)
932 g_return_if_fail(tip_timeout
== 0);
935 delay
= now
- tip_time
> 2 ? 1000 : 200;
937 g_object_ref(object
);
938 tip_timeout
= g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE
,
940 (GSourceFunc
) callback
,
945 /* Like gtk_widget_modify_font, but copes with font_desc == NULL */
946 void widget_modify_font(GtkWidget
*widget
, PangoFontDescription
*font_desc
)
948 GtkRcStyle
*rc_style
;
950 g_return_if_fail(GTK_IS_WIDGET(widget
));
952 rc_style
= gtk_widget_get_modifier_style(widget
);
954 if (rc_style
->font_desc
)
955 pango_font_description_free(rc_style
->font_desc
);
957 rc_style
->font_desc
= font_desc
958 ? pango_font_description_copy(font_desc
)
961 gtk_widget_modify_style(widget
, rc_style
);
964 /* Confirm the action with the user. If action is NULL, the text from stock
967 gboolean
confirm(const gchar
*message
, const gchar
*stock
, const gchar
*action
)
969 return get_choice(PROJECT
, message
, 2,
970 GTK_STOCK_CANCEL
, NULL
,
977 void (*changed
)(Radios
*, gpointer data
);
978 gpointer changed_data
;
981 /* Create a new set of radio buttons.
982 * Use radios_add to add options, then radios_pack to put them into something.
983 * The radios object will self-destruct with the first widget it contains.
984 * changed(data) is called (if not NULL) when pack is called, and on any
987 Radios
*radios_new(void (*changed
)(Radios
*, gpointer data
), gpointer data
)
991 radios
= g_new(Radios
, 1);
993 radios
->widgets
= NULL
;
994 radios
->changed
= changed
;
995 radios
->changed_data
= data
;
1000 static void radios_free(GtkWidget
*radio
, Radios
*radios
)
1002 g_return_if_fail(radios
!= NULL
);
1004 g_list_free(radios
->widgets
);
1008 void radios_add(Radios
*radios
, const gchar
*tip
, gint value
,
1009 const gchar
*label
, ...)
1012 GSList
*group
= NULL
;
1016 g_return_if_fail(radios
!= NULL
);
1017 g_return_if_fail(label
!= NULL
);
1019 va_start(args
, label
);
1020 s
= g_strdup_vprintf(label
, args
);
1023 if (radios
->widgets
)
1025 GtkRadioButton
*first
= GTK_RADIO_BUTTON(radios
->widgets
->data
);
1026 group
= gtk_radio_button_get_group(first
);
1029 radio
= gtk_radio_button_new_with_label(group
, s
);
1030 gtk_label_set_line_wrap(GTK_LABEL(GTK_BIN(radio
)->child
), TRUE
);
1031 gtk_widget_show(radio
);
1033 gtk_tooltips_set_tip(tooltips
, radio
, tip
, NULL
);
1035 g_signal_connect(G_OBJECT(radio
), "destroy",
1036 G_CALLBACK(radios_free
), radios
);
1038 radios
->widgets
= g_list_prepend(radios
->widgets
, radio
);
1039 g_object_set_data(G_OBJECT(radio
), "rox-radios-value",
1040 GINT_TO_POINTER(value
));
1043 static void radio_toggled(GtkToggleButton
*button
, Radios
*radios
)
1045 g_return_if_fail(radios
!= NULL
);
1047 if (button
&& !gtk_toggle_button_get_active(button
))
1048 return; /* Stop double-notifies */
1050 if (radios
->changed
)
1051 radios
->changed(radios
, radios
->changed_data
);
1054 void radios_pack(Radios
*radios
, GtkBox
*box
)
1058 g_return_if_fail(radios
!= NULL
);
1060 for (next
= g_list_last(radios
->widgets
); next
; next
= next
->prev
)
1062 GtkWidget
*button
= GTK_WIDGET(next
->data
);
1064 gtk_box_pack_start(box
, button
, FALSE
, TRUE
, 0);
1065 g_signal_connect(button
, "toggled",
1066 G_CALLBACK(radio_toggled
), radios
);
1068 radio_toggled(NULL
, radios
);
1071 void radios_set_value(Radios
*radios
, gint value
)
1075 g_return_if_fail(radios
!= NULL
);
1077 for (next
= radios
->widgets
; next
; next
= next
->next
)
1079 GtkToggleButton
*radio
= GTK_TOGGLE_BUTTON(next
->data
);
1082 radio_value
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(radio
),
1083 "rox-radios-value"));
1085 if (radio_value
== value
)
1087 gtk_toggle_button_set_active(radio
, TRUE
);
1092 g_warning("Value %d not in radio group!", value
);
1095 gint
radios_get_value(Radios
*radios
)
1099 g_return_val_if_fail(radios
!= NULL
, -1);
1101 for (next
= radios
->widgets
; next
; next
= next
->next
)
1103 GtkToggleButton
*radio
= GTK_TOGGLE_BUTTON(next
->data
);
1105 if (gtk_toggle_button_get_active(radio
))
1106 return GPOINTER_TO_INT(g_object_get_data(
1107 G_OBJECT(radio
), "rox-radios-value"));
1110 g_warning("Nothing in the radio group is selected!");
1115 /* Convert a list of URIs as a string into a GList of EscapedPath URIs.
1116 * No unescaping is done.
1117 * Lines beginning with # are skipped.
1118 * The text block passed in is zero terminated (after the final CRLF)
1120 GList
*uri_list_to_glist(const char *uri_list
)
1129 linebreak
= strchr(uri_list
, 13);
1131 if (!linebreak
|| linebreak
[1] != 10)
1133 g_warning("uri_list_to_glist: %s",
1134 _("Incorrect or missing line "
1135 "break in text/uri-list data"));
1136 /* If this is the first, append it anyway (Firefox
1138 if (!list
&& uri_list
[0] != '#')
1139 list
= g_list_append(list
, g_strdup(uri_list
));
1143 length
= linebreak
- uri_list
;
1145 if (length
&& uri_list
[0] != '#')
1146 list
= g_list_append(list
, g_strndup(uri_list
, length
));
1148 uri_list
= linebreak
+ 2;
1154 typedef struct _SimpleImageClass SimpleImageClass
;
1155 typedef struct _SimpleImage SimpleImage
;
1157 struct _SimpleImageClass
{
1158 GtkWidgetClass parent
;
1161 struct _SimpleImage
{
1168 #define SIMPLE_IMAGE(obj) (GTK_CHECK_CAST((obj), \
1169 simple_image_get_type(), SimpleImage))
1171 static void simple_image_finialize(GObject
*object
)
1173 SimpleImage
*image
= SIMPLE_IMAGE(object
);
1175 g_object_unref(G_OBJECT(image
->pixbuf
));
1176 image
->pixbuf
= NULL
;
1179 static void simple_image_size_request(GtkWidget
*widget
,
1180 GtkRequisition
*requisition
)
1182 SimpleImage
*image
= (SimpleImage
*) widget
;
1184 requisition
->width
= image
->width
;
1185 requisition
->height
= image
->height
;
1188 /* Render a pixbuf without messing up the clipping */
1189 void render_pixbuf(GdkPixbuf
*pixbuf
, GdkDrawable
*target
, GdkGC
*gc
,
1190 int x
, int y
, int width
, int height
)
1192 gdk_draw_pixbuf(target
, gc
, pixbuf
, 0, 0, x
, y
, width
, height
,
1193 GDK_RGB_DITHER_NORMAL
, 0, 0);
1197 static gint
simple_image_expose(GtkWidget
*widget
, GdkEventExpose
*event
)
1199 SimpleImage
*image
= (SimpleImage
*) widget
;
1202 gdk_gc_set_clip_region(widget
->style
->black_gc
, event
->region
);
1204 x
= widget
->allocation
.x
+
1205 (widget
->allocation
.width
- image
->width
) / 2;
1207 render_pixbuf(image
->pixbuf
, widget
->window
, widget
->style
->black_gc
,
1208 x
, widget
->allocation
.y
,
1209 image
->width
, image
->height
);
1211 gdk_gc_set_clip_region(widget
->style
->black_gc
, NULL
);
1215 static void simple_image_class_init(gpointer gclass
, gpointer data
)
1217 GObjectClass
*object
= (GObjectClass
*) gclass
;
1218 GtkWidgetClass
*widget
= (GtkWidgetClass
*) gclass
;
1220 object
->finalize
= simple_image_finialize
;
1221 widget
->size_request
= simple_image_size_request
;
1222 widget
->expose_event
= simple_image_expose
;
1225 static void simple_image_init(GTypeInstance
*object
, gpointer gclass
)
1227 GTK_WIDGET_SET_FLAGS(object
, GTK_NO_WINDOW
);
1230 static GType
simple_image_get_type(void)
1232 static GType type
= 0;
1236 static const GTypeInfo info
=
1238 sizeof (SimpleImageClass
),
1239 NULL
, /* base_init */
1240 NULL
, /* base_finalise */
1241 simple_image_class_init
,
1242 NULL
, /* class_finalise */
1243 NULL
, /* class_data */
1244 sizeof(SimpleImage
),
1245 0, /* n_preallocs */
1249 type
= g_type_register_static(gtk_widget_get_type(),
1250 "SimpleImage", &info
, 0);
1256 GtkWidget
*simple_image_new(GdkPixbuf
*pixbuf
)
1260 g_return_val_if_fail(pixbuf
!= NULL
, NULL
);
1262 image
= g_object_new(simple_image_get_type(), NULL
);
1264 image
->pixbuf
= pixbuf
;
1265 g_object_ref(G_OBJECT(pixbuf
));
1267 image
->width
= gdk_pixbuf_get_width(pixbuf
);
1268 image
->height
= gdk_pixbuf_get_height(pixbuf
);
1270 return GTK_WIDGET(image
);
1273 /* Whether a line l1 long starting from n1 overlaps a line l2 from n2 */
1274 inline static gboolean
gui_ranges_overlap(int n1
, int l1
, int n2
, int l2
)
1276 return (n1
> n2
&& n1
< n2
+ l2
) ||
1277 (n1
+ l1
> n2
&& n1
+ l1
< n2
+ l2
) ||
1278 (n1
<= n2
&& n1
+ l1
>= n2
+ l2
);
1281 static void gui_get_monitor_adjacent(int monitor
, MonitorAdjacent
*adj
)
1285 adj
->left
= adj
->right
= adj
->top
= adj
->bottom
= FALSE
;
1287 for (m
= 0; m
< n_monitors
; ++m
)
1291 if (gui_ranges_overlap(monitor_geom
[m
].y
,
1292 monitor_geom
[m
].height
,
1293 monitor_geom
[monitor
].y
,
1294 monitor_geom
[monitor
].height
))
1296 if (monitor_geom
[m
].x
< monitor_geom
[monitor
].x
)
1300 else if (monitor_geom
[m
].x
> monitor_geom
[monitor
].x
)
1305 if (gui_ranges_overlap(monitor_geom
[m
].x
,
1306 monitor_geom
[m
].width
,
1307 monitor_geom
[monitor
].x
,
1308 monitor_geom
[monitor
].width
))
1310 if (monitor_geom
[m
].y
< monitor_geom
[monitor
].y
)
1314 else if (monitor_geom
[m
].y
> monitor_geom
[monitor
].y
)
1322 static void rox_wmspec_change_state(gboolean add
, GdkWindow
*window
,
1323 GdkAtom state1
, GdkAtom state2
)
1325 GdkDisplay
*display
= gdk_drawable_get_display(GDK_DRAWABLE(window
));
1328 #define _NET_WM_STATE_REMOVE 0 /* remove/unset property */
1329 #define _NET_WM_STATE_ADD 1 /* add/set property */
1330 #define _NET_WM_STATE_TOGGLE 2 /* toggle property */
1332 xev
.xclient
.type
= ClientMessage
;
1333 xev
.xclient
.serial
= 0;
1334 xev
.xclient
.send_event
= True
;
1335 xev
.xclient
.window
= GDK_WINDOW_XID(window
);
1336 xev
.xclient
.message_type
= gdk_x11_get_xatom_by_name_for_display(
1337 display
, "_NET_WM_STATE");
1338 xev
.xclient
.format
= 32;
1339 xev
.xclient
.data
.l
[0] = add
? _NET_WM_STATE_ADD
: _NET_WM_STATE_REMOVE
;
1340 xev
.xclient
.data
.l
[1] = gdk_x11_atom_to_xatom_for_display(display
,
1342 xev
.xclient
.data
.l
[2] = gdk_x11_atom_to_xatom_for_display(display
,
1344 xev
.xclient
.data
.l
[3] = 0;
1345 xev
.xclient
.data
.l
[4] = 0;
1347 XSendEvent(GDK_DISPLAY_XDISPLAY(display
),
1349 gdk_screen_get_root_window(
1350 gdk_drawable_get_screen(GDK_DRAWABLE(window
)))),
1352 SubstructureRedirectMask
| SubstructureNotifyMask
,
1356 /* Tell the window manager whether to keep this window below others. */
1357 void keep_below(GdkWindow
*window
, gboolean setting
)
1359 g_return_if_fail(GDK_IS_WINDOW(window
));
1361 if (GDK_WINDOW_DESTROYED(window
))
1364 if (gdk_window_is_visible(window
))
1368 rox_wmspec_change_state(FALSE
, window
,
1369 gdk_atom_intern("_NET_WM_STATE_ABOVE", FALSE
),
1372 rox_wmspec_change_state(setting
, window
,
1373 gdk_atom_intern("_NET_WM_STATE_BELOW", FALSE
),
1379 #if GTK_CHECK_VERSION(2,4,0)
1380 gdk_synthesize_window_state(window
,
1381 setting
? GDK_WINDOW_STATE_ABOVE
:
1382 GDK_WINDOW_STATE_BELOW
,
1383 setting
? GDK_WINDOW_STATE_BELOW
: 0);
1390 size_prepared_cb (GdkPixbufLoader
*loader
,
1398 gboolean preserve_aspect_ratio
;
1401 g_return_if_fail (width
> 0 && height
> 0);
1403 if(info
->preserve_aspect_ratio
) {
1404 if ((double)height
* (double)info
->width
>
1405 (double)width
* (double)info
->height
) {
1406 width
= 0.5 + (double)width
* (double)info
->height
/ (double)height
;
1407 height
= info
->height
;
1409 height
= 0.5 + (double)height
* (double)info
->width
/ (double)width
;
1410 width
= info
->width
;
1413 width
= info
->width
;
1414 height
= info
->height
;
1417 gdk_pixbuf_loader_set_size (loader
, width
, height
);
1421 * rox_pixbuf_new_from_file_at_scale:
1422 * @filename: Name of file to load.
1423 * @width: The width the image should have
1424 * @height: The height the image should have
1425 * @preserve_aspect_ratio: %TRUE to preserve the image's aspect ratio
1426 * @error: Return location for an error
1428 * Creates a new pixbuf by loading an image from a file. The file format is
1429 * detected automatically. If %NULL is returned, then @error will be set.
1430 * Possible errors are in the #GDK_PIXBUF_ERROR and #G_FILE_ERROR domains.
1431 * The image will be scaled to fit in the requested size, optionally preserving
1432 * the image's aspect ratio.
1434 * Return value: A newly-created pixbuf with a reference count of 1, or %NULL
1435 * if any of several error conditions occurred: the file could not be opened,
1436 * there was no loader for the file's format, there was not enough memory to
1437 * allocate the image buffer, or the image file contained invalid data.
1439 * Taken from GTK 2.6.
1442 rox_pixbuf_new_from_file_at_scale (const char *filename
,
1445 gboolean preserve_aspect_ratio
,
1449 GdkPixbufLoader
*loader
;
1452 guchar buffer
[4096];
1458 gboolean preserve_aspect_ratio
;
1461 g_return_val_if_fail (filename
!= NULL
, NULL
);
1462 g_return_val_if_fail (width
> 0 && height
> 0, NULL
);
1464 f
= fopen (filename
, "rb");
1466 gchar
*utf8_filename
= g_filename_to_utf8 (filename
, -1,
1470 g_file_error_from_errno (errno
),
1471 _("Failed to open file '%s': %s"),
1472 utf8_filename
? utf8_filename
: "???",
1473 g_strerror (errno
));
1474 g_free (utf8_filename
);
1478 loader
= gdk_pixbuf_loader_new ();
1481 info
.height
= height
;
1482 info
.preserve_aspect_ratio
= preserve_aspect_ratio
;
1484 g_signal_connect (loader
, "size-prepared", G_CALLBACK (size_prepared_cb
), &info
);
1486 while (!feof (f
) && !ferror (f
)) {
1487 length
= fread (buffer
, 1, sizeof (buffer
), f
);
1489 if (!gdk_pixbuf_loader_write (loader
, buffer
, length
, error
)) {
1490 gdk_pixbuf_loader_close (loader
, NULL
);
1492 g_object_unref (loader
);
1499 if (!gdk_pixbuf_loader_close (loader
, error
)) {
1500 g_object_unref (loader
);
1504 pixbuf
= gdk_pixbuf_loader_get_pixbuf (loader
);
1507 gchar
*utf8_filename
= g_filename_to_utf8 (filename
, -1,
1510 g_object_unref (loader
);
1514 GDK_PIXBUF_ERROR_FAILED
,
1515 _("Failed to load image '%s': reason not known, probably a corrupt image file"),
1516 utf8_filename
? utf8_filename
: "???");
1517 g_free (utf8_filename
);
1521 g_object_ref (pixbuf
);
1523 g_object_unref (loader
);
1528 /* Make the name bolder and larger.
1529 * scale_factor can be PANGO_SCALE_X_LARGE, etc.
1531 void make_heading(GtkWidget
*label
, double scale_factor
)
1533 PangoAttribute
*attr
;
1534 PangoAttrList
*list
;
1536 list
= pango_attr_list_new();
1538 attr
= pango_attr_weight_new(PANGO_WEIGHT_BOLD
);
1539 attr
->start_index
= 0;
1540 attr
->end_index
= -1;
1541 pango_attr_list_insert(list
, attr
);
1543 attr
= pango_attr_scale_new(scale_factor
);
1544 attr
->start_index
= 0;
1545 attr
->end_index
= -1;
1546 pango_attr_list_insert(list
, attr
);
1548 gtk_label_set_attributes(GTK_LABEL(label
), list
);
1551 /* Launch a program using 0launch.
1552 * If button-3 is used, open the GUI with -g.
1554 void launch_uri(GObject
*button
, const char *uri
)
1556 const char *argv
[] = {"0launch", NULL
, NULL
, NULL
};
1557 const char *uri_0launch
= "/uri/0install/zero-install.sourceforge.net"
1560 if (!available_in_path(argv
[0]))
1562 if (access(uri_0launch
, X_OK
) == 0)
1563 argv
[0] = uri_0launch
;
1566 const char *appname
=g_object_get_data(button
,
1571 gchar
*path
=find_app(appname
);
1580 delayed_error(_("This program (%s) cannot be run, "
1581 "as the 0launch command is not available. "
1582 "It can be downloaded from here:\n\n"
1583 "http://0install.net/injector.html"),
1589 if (current_event_button() == 3)
1597 rox_spawn(NULL
, argv
);
1600 static gint
button3_button_pressed(GtkButton
*button
,
1601 GdkEventButton
*event
,
1604 if (event
->button
== 3)
1606 gtk_grab_add(GTK_WIDGET(button
));
1607 gtk_button_pressed(button
);
1615 static gint
button3_button_released(GtkButton
*button
,
1616 GdkEventButton
*event
,
1617 FilerWindow
*filer_window
)
1619 if (event
->button
== 3)
1621 gtk_grab_remove(GTK_WIDGET(button
));
1622 gtk_button_released(button
);
1630 void allow_right_click(GtkWidget
*button
)
1632 g_signal_connect(button
, "button_press_event",
1633 G_CALLBACK(button3_button_pressed
), NULL
);
1634 g_signal_connect(button
, "button_release_event",
1635 G_CALLBACK(button3_button_released
), NULL
);
1638 /* Return mouse button used in the current event, or -1 if none (no event,
1641 gint
current_event_button(void)
1643 GdkEventButton
*bev
;
1646 bev
= (GdkEventButton
*) gtk_get_current_event();
1649 (bev
->type
== GDK_BUTTON_PRESS
|| bev
->type
== GDK_BUTTON_RELEASE
))
1650 button
= bev
->button
;
1652 gdk_event_free((GdkEvent
*) bev
);
1657 /* Create a new pixbuf by colourizing 'src' to 'color'. If the function fails,
1658 * 'src' will be returned (with an increased reference count, so it is safe to
1659 * g_object_unref() the return value whether the function fails or not).
1661 GdkPixbuf
*create_spotlight_pixbuf(GdkPixbuf
*src
, GdkColor
*color
)
1663 guchar opacity
= 192;
1664 guchar alpha
= 255 - opacity
;
1666 GdkColorspace colorspace
;
1667 int width
, height
, src_rowstride
, dst_rowstride
, x
, y
;
1668 int n_channels
, bps
;
1670 guchar
*spixels
, *dpixels
, *src_pixels
, *dst_pixels
;
1673 has_alpha
= gdk_pixbuf_get_has_alpha(src
);
1674 colorspace
= gdk_pixbuf_get_colorspace(src
);
1675 n_channels
= gdk_pixbuf_get_n_channels(src
);
1676 bps
= gdk_pixbuf_get_bits_per_sample(src
);
1678 if ((colorspace
!= GDK_COLORSPACE_RGB
) ||
1679 (!has_alpha
&& n_channels
!= 3) ||
1680 (has_alpha
&& n_channels
!= 4) ||
1684 width
= gdk_pixbuf_get_width(src
);
1685 height
= gdk_pixbuf_get_height(src
);
1687 dst
= gdk_pixbuf_new(colorspace
, has_alpha
, bps
, width
, height
);
1691 src_pixels
= gdk_pixbuf_get_pixels(src
);
1692 dst_pixels
= gdk_pixbuf_get_pixels(dst
);
1693 src_rowstride
= gdk_pixbuf_get_rowstride(src
);
1694 dst_rowstride
= gdk_pixbuf_get_rowstride(dst
);
1696 r
= opacity
* (color
->red
>> 8);
1697 g
= opacity
* (color
->green
>> 8);
1698 b
= opacity
* (color
->blue
>> 8);
1700 for (y
= 0; y
< height
; y
++)
1702 spixels
= src_pixels
+ y
* src_rowstride
;
1703 dpixels
= dst_pixels
+ y
* dst_rowstride
;
1704 for (x
= 0; x
< width
; x
++)
1706 *dpixels
++ = (*spixels
++ * alpha
+ r
) >> 8;
1707 *dpixels
++ = (*spixels
++ * alpha
+ g
) >> 8;
1708 *dpixels
++ = (*spixels
++ * alpha
+ b
) >> 8;
1710 *dpixels
++ = *spixels
++;
1721 /* Load the Templates.ui file and build a component. */
1722 GtkBuilder
*get_gtk_builder(gchar
**ids
)
1724 GError
*error
= NULL
;
1726 GtkBuilder
*builder
= NULL
;
1728 builder
= gtk_builder_new();
1729 gtk_builder_set_translation_domain(builder
, "ROX-Filer");
1731 path
= g_build_filename(app_dir
, "Templates.ui", NULL
);
1732 if (!gtk_builder_add_objects_from_file(builder
, path
, ids
, &error
))
1734 g_warning("Failed to load builder file %s: %s",
1735 path
, error
->message
);
1736 g_error_free(error
);
1744 void add_stock_to_menu_item(GtkWidget
*item
, const char *stock
)
1746 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
),
1747 gtk_image_new_from_stock(stock
, GTK_ICON_SIZE_MENU
));