Callers now responsible for adding stock items to extra items in icon_prepare_menu.
[rox-filer.git] / ROX-Filer / src / gui_support.c
blob708dde5875a64a138c6c6cdda9bf843b508b74cf
1 /*
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)
8 * any later version.
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
13 * more details.
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 */
22 #include "config.h"
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <sys/param.h>
28 #include <stdarg.h>
29 #include <errno.h>
30 #include <time.h>
32 #include <X11/Xlib.h>
33 #include <X11/Xatom.h>
34 #include <gdk/gdkx.h>
35 #include <gdk/gdk.h>
36 #include <gdk/gdkkeysyms.h>
38 #include "global.h"
40 #include "main.h"
41 #include "gui_support.h"
42 #include "support.h"
43 #include "pixmaps.h"
44 #include "choices.h"
45 #include "options.h"
47 gint screen_width, screen_height;
49 gint n_monitors;
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,
64 va_list args);
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)
70 gint mon;
72 screen_width = gdk_screen_get_width(screen);
73 screen_height = gdk_screen_get_height(screen);
75 if (monitor_adjacent)
76 g_free(monitor_adjacent);
78 monitor_width = monitor_height = G_MAXINT;
79 n_monitors = gdk_screen_get_n_monitors(screen);
80 if (monitor_geom)
81 g_free(monitor_geom);
82 monitor_geom = g_new(GdkRectangle, n_monitors ? n_monitors : 1);
84 if (n_monitors)
86 for (mon = 0; mon < n_monitors; ++mon)
88 gdk_screen_get_monitor_geometry(screen, mon,
89 &monitor_geom[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]);
101 else
103 n_monitors = 1;
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()
114 gpointer klass;
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,
138 const char *message,
139 int number_of_buttons, ...)
141 GtkWidget *dialog;
142 GtkWidget *button = NULL;
143 int i, retval;
144 va_list ap;
146 if (current_dialog)
148 gtk_widget_hide(current_dialog);
149 gtk_widget_show(current_dialog);
150 return -1;
153 current_dialog = dialog = gtk_message_dialog_new(NULL,
154 GTK_DIALOG_MODAL,
155 GTK_MESSAGE_QUESTION,
156 GTK_BUTTONS_NONE,
157 "%s", message);
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 *);
167 if (text)
168 button = button_new_mixed(stock, text);
169 else
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),
176 button, i);
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);
184 va_end(ap);
186 retval = gtk_dialog_run(GTK_DIALOG(dialog));
187 if (retval == GTK_RESPONSE_NONE)
188 retval = -1;
189 gtk_widget_destroy(dialog);
191 current_dialog = NULL;
193 return retval;
196 void info_message(const char *message, ...)
198 va_list args;
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, ...)
208 va_list args;
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);
238 return;
241 if (need_init)
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);
249 need_init = 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,
260 WIN_STATE_STICKY |
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),
285 "accept_focus"))
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);
301 null_g_free(error);
303 one_less_window();
304 return FALSE;
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;
315 char *old, *new;
316 va_list args;
318 g_return_if_fail(error != NULL);
320 old = delayed_error_data;
322 va_start(args, error);
323 new = g_strdup_vprintf(error, args);
324 va_end(args);
326 if (old)
328 delayed_error_data = g_strconcat(old,
329 _("\n---\n"),
330 new, NULL);
331 g_free(old);
332 g_free(new);
334 else
336 delayed_error_data = new;
337 g_idle_add(error_idle_cb, &delayed_error_data);
339 number_of_windows++;
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)
348 gsize len;
349 GError *error = NULL;
351 if (!g_file_get_contents(pathname, data_out, &len, &error))
353 delayed_error("%s", error->message);
354 g_error_free(error);
355 return FALSE;
358 if (length_out)
359 *length_out = len;
360 return TRUE;
363 GtkWidget *new_help_button(HelpFunc show_help, gpointer data)
365 GtkWidget *b, *icon;
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);
376 return b;
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)
385 char *data;
386 long length;
387 gboolean seen_error = FALSE;
389 if (load_file(path, &data, &length))
391 char *eol;
392 const char *error;
393 char *line = data;
394 int line_number = 1;
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);
401 return;
404 while (line && *line)
406 eol = strchr(line, '\n');
407 if (eol)
408 *eol = '\0';
410 error = parse_line(line);
412 if (error && !seen_error)
414 delayed_error(
415 _("Error in '%s' file at line %d: "
416 "\n\"%s\"\n"
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."),
421 path,
422 line_number,
423 error);
424 seen_error = TRUE;
427 if (!eol)
428 break;
429 line = eol + 1;
430 line_number++;
432 g_free(data);
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)
441 unsigned int mask;
443 gdk_window_get_pointer(NULL, x, y, &mask);
445 return mask != 0;
448 int get_monitor_under_pointer(void)
450 int x, y;
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)
461 int w, h;
462 int m;
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);
470 x -= w / 2;
471 y -= h / 2;
473 gdk_window_move(window,
474 CLAMP(x, DECOR_BORDER + monitor_geom[m].x,
475 monitor_geom[m].x + monitor_geom[m].width
476 - w - DECOR_BORDER),
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,
483 va_list args)
485 GtkWidget *dialog;
486 gchar *s;
488 g_return_if_fail(message != NULL);
490 s = g_strdup_vprintf(message, args);
491 va_end(args);
493 dialog = gtk_message_dialog_new(NULL,
494 GTK_DIALOG_MODAL,
495 type,
496 GTK_BUTTONS_OK,
497 "%s", s);
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);
503 g_free(s);
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;
518 return FALSE;
521 static void cancel_wink(void)
523 g_source_remove(wink_timeout);
524 end_wink(NULL);
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)
541 cancel_wink();
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);
556 return FALSE;
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;
572 gint pid = 0;
574 if (!g_spawn_async_with_pipes(dir, (gchar **) argv, NULL,
575 G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_STDOUT_TO_DEV_NULL |
576 G_SPAWN_SEARCH_PATH,
577 NULL, NULL, /* Child setup fn */
578 &pid, /* Child PID */
579 NULL, NULL, NULL, /* Standard pipes */
580 &error))
582 delayed_error("%s", error ? error->message : "(null)");
583 g_error_free(error);
585 return 0;
588 return pid;
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);
610 return button;
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),
617 message);
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);
648 gdk_flush();
649 if (gdk_error_trap_pop())
650 g_warning("window_put_just_above()\n");
652 else
653 gdk_window_lower(higher); /* To bottom of stack */
656 /* Copied from Gtk */
657 static GtkFixedChild* fixed_get_child(GtkFixed *fixed, GtkWidget *widget)
659 GList *children;
661 children = fixed->children;
662 while (children)
664 GtkFixedChild *child;
666 child = children->data;
667 children = children->next;
669 if (child->widget == widget)
670 return child;
673 return NULL;
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);
683 g_assert(child);
685 gtk_widget_freeze_child_notify(widget);
687 child->x = x;
688 gtk_widget_child_notify(widget, "x");
690 child->y = y;
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,
702 &child_requisition);
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);
721 return FALSE;
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)
729 time(&tip_time);
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)
737 GtkWidget *label;
738 int x, y, py;
739 int w, h;
740 int m;
742 if (tip_timeout)
744 g_source_remove(tip_timeout);
745 tip_timeout = 0;
748 if (tip_widget)
750 gtk_widget_destroy(tip_widget);
751 tip_widget = NULL;
754 if (!text)
755 return;
757 /* Show the tip */
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);
777 x -= w / 2;
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)
788 y = py - h - 2;
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);
794 time(&tip_time);
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)
802 time_t now;
803 int delay;
805 g_return_if_fail(tip_timeout == 0);
807 time(&now);
808 delay = now - tip_time > 2 ? 1000 : 200;
810 g_object_ref(object);
811 tip_timeout = g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE,
812 delay,
813 (GSourceFunc) callback,
814 object,
815 g_object_unref);
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)
832 : NULL;
834 gtk_widget_modify_style(widget, rc_style);
837 /* Confirm the action with the user. If action is NULL, the text from stock
838 * is used.
840 gboolean confirm(const gchar *message, const gchar *stock, const gchar *action)
842 return get_choice(PROJECT, message, 2,
843 GTK_STOCK_CANCEL, NULL,
844 stock, action) == 1;
847 struct _Radios {
848 GList *widgets;
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
858 * change after that.
860 Radios *radios_new(void (*changed)(Radios *, gpointer data), gpointer data)
862 Radios *radios;
864 radios = g_new(Radios, 1);
866 radios->widgets = NULL;
867 radios->changed = changed;
868 radios->changed_data = data;
870 return radios;
873 static void radios_free(GtkWidget *radio, Radios *radios)
875 g_return_if_fail(radios != NULL);
877 g_list_free(radios->widgets);
878 g_free(radios);
881 void radios_add(Radios *radios, const gchar *tip, gint value,
882 const gchar *label, ...)
884 GtkWidget *radio;
885 GSList *group = NULL;
886 gchar *s;
887 va_list args;
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);
894 va_end(args);
896 if (radios->widgets)
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);
905 if (tip)
906 gtk_tooltips_set_tip(tooltips, radio, tip, NULL);
907 if (!group)
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 */
923 if (radios->changed)
924 radios->changed(radios, radios->changed_data);
927 void radios_pack(Radios *radios, GtkBox *box)
929 GList *next;
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)
946 GList *next;
948 g_return_if_fail(radios != NULL);
950 for (next = radios->widgets; next; next = next->next)
952 GtkToggleButton *radio = GTK_TOGGLE_BUTTON(next->data);
953 int radio_value;
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);
961 return;
965 g_warning("Value %d not in radio group!", value);
968 gint radios_get_value(Radios *radios)
970 GList *next;
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!");
985 return -1;
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)
995 GList *list = NULL;
997 while (*uri_list)
999 char *linebreak;
1000 int length;
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"));
1009 return list;
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;
1020 return list;
1023 typedef struct _SimpleImageClass SimpleImageClass;
1024 typedef struct _SimpleImage SimpleImage;
1026 struct _SimpleImageClass {
1027 GtkWidgetClass parent;
1030 struct _SimpleImage {
1031 GtkWidget widget;
1033 GdkPixbuf *pixbuf;
1034 int width, height;
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;
1069 int x;
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);
1081 return FALSE;
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;
1103 if (!type)
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 */
1115 simple_image_init,
1118 type = g_type_register_static(gtk_widget_get_type(),
1119 "SimpleImage", &info, 0);
1122 return type;
1125 GtkWidget *simple_image_new(GdkPixbuf *pixbuf)
1127 SimpleImage *image;
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)
1152 int m;
1154 adj->left = adj->right = adj->top = adj->bottom = FALSE;
1156 for (m = 0; m < n_monitors; ++m)
1158 if (m == monitor)
1159 continue;
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)
1167 adj->left = TRUE;
1169 else if (monitor_geom[m].x > monitor_geom[monitor].x)
1171 adj->right = TRUE;
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)
1181 adj->top = TRUE;
1183 else if (monitor_geom[m].y > monitor_geom[monitor].y)
1185 adj->bottom = TRUE;
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));
1195 XEvent xev;
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,
1210 state1);
1211 xev.xclient.data.l[2] = gdk_x11_atom_to_xatom_for_display(display,
1212 state2);
1213 xev.xclient.data.l[3] = 0;
1214 xev.xclient.data.l[4] = 0;
1216 XSendEvent(GDK_DISPLAY_XDISPLAY(display),
1217 GDK_WINDOW_XID(
1218 gdk_screen_get_root_window(
1219 gdk_drawable_get_screen(GDK_DRAWABLE(window)))),
1220 False,
1221 SubstructureRedirectMask | SubstructureNotifyMask,
1222 &xev);
1225 void keep_below(GdkWindow *window, gboolean setting)
1227 g_return_if_fail(GDK_IS_WINDOW(window));
1229 if (GDK_WINDOW_DESTROYED(window))
1230 return;
1232 if (gdk_window_is_visible(window))
1234 if (setting)
1236 rox_wmspec_change_state(FALSE, window,
1237 gdk_atom_intern("_NET_WM_STATE_ABOVE", FALSE),
1238 GDK_NONE);
1240 rox_wmspec_change_state(setting, window,
1241 gdk_atom_intern("_NET_WM_STATE_BELOW", FALSE),
1242 GDK_NONE);
1244 #if 0
1245 else
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);
1252 #endif
1254 #endif
1257 static void
1258 size_prepared_cb (GdkPixbufLoader *loader,
1259 int width,
1260 int height,
1261 gpointer data)
1263 struct {
1264 gint width;
1265 gint height;
1266 gboolean preserve_aspect_ratio;
1267 } *info = data;
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;
1276 } else {
1277 height = 0.5 + (double)height * (double)info->width / (double)width;
1278 width = info->width;
1280 } else {
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.
1309 GdkPixbuf *
1310 rox_pixbuf_new_from_file_at_scale (const char *filename,
1311 int width,
1312 int height,
1313 gboolean preserve_aspect_ratio,
1314 GError **error)
1317 GdkPixbufLoader *loader;
1318 GdkPixbuf *pixbuf;
1320 guchar buffer [4096];
1321 int length;
1322 FILE *f;
1323 struct {
1324 gint width;
1325 gint height;
1326 gboolean preserve_aspect_ratio;
1327 } info;
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");
1333 if (!f) {
1334 gchar *utf8_filename = g_filename_to_utf8 (filename, -1,
1335 NULL, NULL, NULL);
1336 g_set_error (error,
1337 G_FILE_ERROR,
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);
1343 return NULL;
1346 loader = gdk_pixbuf_loader_new ();
1348 info.width = width;
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);
1356 if (length > 0)
1357 if (!gdk_pixbuf_loader_write (loader, buffer, length, error)) {
1358 gdk_pixbuf_loader_close (loader, NULL);
1359 fclose (f);
1360 g_object_unref (loader);
1361 return NULL;
1365 fclose (f);
1367 if (!gdk_pixbuf_loader_close (loader, error)) {
1368 g_object_unref (loader);
1369 return NULL;
1372 pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
1374 if (!pixbuf) {
1375 gchar *utf8_filename = g_filename_to_utf8 (filename, -1,
1376 NULL, NULL, NULL);
1378 g_object_unref (loader);
1380 g_set_error (error,
1381 GDK_PIXBUF_ERROR,
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);
1386 return NULL;
1389 g_object_ref (pixbuf);
1391 g_object_unref (loader);
1393 return pixbuf;
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"
1426 "/bin/0launch";
1428 if (!available_in_path(argv[0]))
1430 if (access(uri_0launch, X_OK) == 0)
1431 argv[0] = uri_0launch;
1432 else
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"),
1438 uri);
1439 return;
1443 if (current_event_button() == 3)
1445 argv[1] = "-g";
1446 argv[2] = uri;
1448 else
1449 argv[1] = uri;
1451 rox_spawn(NULL, argv);
1454 static gint button3_button_pressed(GtkButton *button,
1455 GdkEventButton *event,
1456 gpointer date)
1458 if (event->button == 3)
1460 gtk_grab_add(GTK_WIDGET(button));
1461 gtk_button_pressed(button);
1463 return TRUE;
1466 return FALSE;
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);
1478 return TRUE;
1481 return FALSE;
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,
1493 * or not a click).
1495 gint current_event_button(void)
1497 GdkEventButton *bev;
1498 gint button = -1;
1500 bev = (GdkEventButton *) gtk_get_current_event();
1502 if (bev &&
1503 (bev->type == GDK_BUTTON_PRESS || bev->type == GDK_BUTTON_RELEASE))
1504 button = bev->button;
1506 gdk_event_free((GdkEvent *) bev);
1508 return button;
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;
1519 GdkPixbuf *dst;
1520 GdkColorspace colorspace;
1521 int width, height, src_rowstride, dst_rowstride, x, y;
1522 int n_channels, bps;
1523 int r, g, b;
1524 guchar *spixels, *dpixels, *src_pixels, *dst_pixels;
1525 gboolean has_alpha;
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) ||
1535 (bps != 8))
1536 goto error;
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);
1542 if (dst == NULL)
1543 goto error;
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;
1563 if (has_alpha)
1564 *dpixels++ = *spixels++;
1568 return dst;
1570 error:
1571 g_object_ref(src);
1572 return src;
1575 /* Load the Templates.glade file and build a component.
1576 * Note that libglade caches the XML itself.
1578 GladeXML *get_glade_xml(const char *component)
1580 GladeXML *widgets;
1581 char *path;
1583 path = g_build_filename(app_dir, "Templates.glade", NULL);
1584 widgets = glade_xml_new(path, component, NULL);
1586 if (widgets == NULL)
1587 g_warning("Failed to load widget '%s' from '%s'",
1588 component, path);
1590 g_free(path);
1592 return widgets;
1595 void add_stock_to_menu_item(GtkWidget *item, const char *stock)
1597 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
1598 gtk_image_new_from_stock(stock, GTK_ICON_SIZE_MENU));