Converted from Glade to GtkBuilder
[rox-filer.git] / ROX-Filer / src / gui_support.c
blobd2435763c9121ec28e883b949c14a969e6b3e34a
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"
46 #include "run.h"
48 gint screen_width, screen_height;
50 gint n_monitors;
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,
69 va_list args);
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)
75 gint mon;
77 screen_width = gdk_screen_get_width(screen);
78 screen_height = gdk_screen_get_height(screen);
80 if (monitor_adjacent)
81 g_free(monitor_adjacent);
83 monitor_width = monitor_height = G_MAXINT;
84 n_monitors = gdk_screen_get_n_monitors(screen);
85 if (monitor_geom)
86 g_free(monitor_geom);
87 monitor_geom = g_new(GdkRectangle, n_monitors ? n_monitors : 1);
89 if (n_monitors)
91 for (mon = 0; mon < n_monitors; ++mon)
93 gdk_screen_get_monitor_geometry(screen, mon,
94 &monitor_geom[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]);
106 else
108 n_monitors = 1;
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()
119 gpointer klass;
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",
125 FALSE);
126 xa__NET_NUMBER_OF_DESKTOPS = gdk_atom_intern("_NET_NUMBER_OF_DESKTOPS",
127 FALSE);
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,
149 const char *message,
150 int number_of_buttons, ...)
152 GtkWidget *dialog;
153 GtkWidget *button = NULL;
154 int i, retval;
155 va_list ap;
157 if (current_dialog)
159 gtk_widget_hide(current_dialog);
160 gtk_widget_show(current_dialog);
161 return -1;
164 current_dialog = dialog = gtk_message_dialog_new(NULL,
165 GTK_DIALOG_MODAL,
166 GTK_MESSAGE_QUESTION,
167 GTK_BUTTONS_NONE,
168 "%s", message);
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 *);
178 if (text)
179 button = button_new_mixed(stock, text);
180 else
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),
187 button, i);
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);
195 va_end(ap);
197 retval = gtk_dialog_run(GTK_DIALOG(dialog));
198 if (retval == GTK_RESPONSE_NONE)
199 retval = -1;
200 gtk_widget_destroy(dialog);
202 current_dialog = NULL;
204 return retval;
207 void info_message(const char *message, ...)
209 va_list args;
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, ...)
219 va_list args;
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)
235 GdkAtom actual_type;
236 gint actual_format, act_length;
237 guchar *d;
238 gulong *p;
239 int i;
240 gboolean ok;
242 /* Cardinals are format=32 so the length in bytes is 4 * number of
243 * cardinals */
244 ok=gdk_property_get(window, prop, xa_cardinal,
245 0, length*4, FALSE,
246 &actual_type, &actual_format,
247 &act_length, &d);
249 if(!ok)
250 return FALSE;
252 /* Check correct format */
253 if(actual_format!=32)
255 g_free(d);
256 return FALSE;
259 /* Actual data for cardinals returned as longs, which may be 64 bit */
260 if(act_length/sizeof(gulong)>length)
262 g_free(d);
263 return FALSE;
266 /* Copy data into return array */
267 p=(gulong *) d;
268 for(i=0; i<act_length/sizeof(gulong); i++)
269 data[i]=p[i];
270 g_free(d);
271 *actual_length=act_length/sizeof(gulong);
273 return ok;
276 int get_current_desktop(void)
278 gint act_len;
279 gulong current;
280 Window root=GDK_ROOT_WINDOW();
281 GdkWindow *gdk_root=gdk_window_foreign_new(root);
282 int desk=0;
284 if(get_cardinal_property(gdk_root, xa__NET_CURRENT_DESKTOP, 1,
285 &current, &act_len) && act_len==1)
286 desk=(int) current;
288 return desk;
291 int get_number_of_desktops(void)
293 gint act_len;
294 gulong num;
295 Window root=GDK_ROOT_WINDOW();
296 GdkWindow *gdk_root=gdk_window_foreign_new(root);
297 int desks=1;
299 if(get_cardinal_property(gdk_root, xa__NET_NUMBER_OF_DESKTOPS, 1,
300 &num, &act_len) && act_len==1)
301 desks=(int) num;
303 return desks;
306 /* Get the working area for the desktop, excluding things like the Gnome
307 * panels. */
308 void get_work_area(int *x, int *y, int *width, int *height)
310 gint act_len;
311 gulong *work_area;
312 Window root=GDK_ROOT_WINDOW();
313 GdkWindow *gdk_root=gdk_window_foreign_new(root);
314 int x0, y0, w0, h0;
315 int idesk, ndesk, nval;
317 idesk=get_current_desktop();
318 ndesk=get_number_of_desktops();
319 nval=4*ndesk;
320 work_area=g_new(gulong, nval);
322 if(get_cardinal_property(gdk_root, xa__NET_WORKAREA, nval,
323 work_area, &act_len) &&
324 act_len==nval)
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];
331 else
333 x0 = y0 = 0;
334 w0 = screen_width;
335 h0 = screen_height;
338 g_free(work_area);
340 if(x)
341 *x = x0;
342 if(y)
343 *y = y0;
344 if(width)
345 *width = w0;
346 if(height)
347 *height = h0;
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);
366 return;
369 if (need_init)
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);
376 need_init = 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,
387 WIN_STATE_STICKY |
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),
412 "accept_focus"))
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);
428 null_g_free(error);
430 one_less_window();
431 return FALSE;
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;
442 char *old, *new;
443 va_list args;
445 g_return_if_fail(error != NULL);
447 old = delayed_error_data;
449 va_start(args, error);
450 new = g_strdup_vprintf(error, args);
451 va_end(args);
453 if (old)
455 delayed_error_data = g_strconcat(old,
456 _("\n---\n"),
457 new, NULL);
458 g_free(old);
459 g_free(new);
461 else
463 delayed_error_data = new;
464 g_idle_add(error_idle_cb, &delayed_error_data);
466 number_of_windows++;
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)
475 gsize len;
476 GError *error = NULL;
478 if (!g_file_get_contents(pathname, data_out, &len, &error))
480 delayed_error("%s", error->message);
481 g_error_free(error);
482 return FALSE;
485 if (length_out)
486 *length_out = len;
487 return TRUE;
490 GtkWidget *new_help_button(HelpFunc show_help, gpointer data)
492 GtkWidget *b, *icon;
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);
503 return b;
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)
512 char *data;
513 long length;
514 gboolean seen_error = FALSE;
516 if (load_file(path, &data, &length))
518 char *eol;
519 const char *error;
520 char *line = data;
521 int line_number = 1;
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);
528 return;
531 while (line && *line)
533 eol = strchr(line, '\n');
534 if (eol)
535 *eol = '\0';
537 error = parse_line(line);
539 if (error && !seen_error)
541 delayed_error(
542 _("Error in '%s' file at line %d: "
543 "\n\"%s\"\n"
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."),
548 path,
549 line_number,
550 error);
551 seen_error = TRUE;
554 if (!eol)
555 break;
556 line = eol + 1;
557 line_number++;
559 g_free(data);
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)
568 unsigned int mask;
570 gdk_window_get_pointer(NULL, x, y, &mask);
572 return mask != 0;
575 int get_monitor_under_pointer(void)
577 int x, y;
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)
588 int w, h;
589 int m;
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);
597 x -= w / 2;
598 y -= h / 2;
600 gdk_window_move(window,
601 CLAMP(x, DECOR_BORDER + monitor_geom[m].x,
602 monitor_geom[m].x + monitor_geom[m].width
603 - w - DECOR_BORDER),
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,
610 va_list args)
612 GtkWidget *dialog;
613 gchar *s;
615 g_return_if_fail(message != NULL);
617 s = g_strdup_vprintf(message, args);
618 va_end(args);
620 dialog = gtk_message_dialog_new(NULL,
621 GTK_DIALOG_MODAL,
622 type,
623 GTK_BUTTONS_OK,
624 "%s", s);
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);
630 g_free(s);
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;
645 return FALSE;
648 static void cancel_wink(void)
650 g_source_remove(wink_timeout);
651 end_wink(NULL);
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)
668 cancel_wink();
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);
683 return FALSE;
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;
699 gint pid = 0;
701 if (!g_spawn_async_with_pipes(dir, (gchar **) argv, NULL,
702 G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_STDOUT_TO_DEV_NULL |
703 G_SPAWN_SEARCH_PATH,
704 NULL, NULL, /* Child setup fn */
705 &pid, /* Child PID */
706 NULL, NULL, NULL, /* Standard pipes */
707 &error))
709 delayed_error("%s", error ? error->message : "(null)");
710 g_error_free(error);
712 return 0;
715 return pid;
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);
737 return button;
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),
744 message);
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);
775 gdk_flush();
776 if (gdk_error_trap_pop())
777 g_warning("window_put_just_above()\n");
779 else
780 gdk_window_lower(higher); /* To bottom of stack */
783 /* Copied from Gtk */
784 static GtkFixedChild* fixed_get_child(GtkFixed *fixed, GtkWidget *widget)
786 GList *children;
788 children = fixed->children;
789 while (children)
791 GtkFixedChild *child;
793 child = children->data;
794 children = children->next;
796 if (child->widget == widget)
797 return child;
800 return NULL;
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);
810 g_assert(child);
812 gtk_widget_freeze_child_notify(widget);
814 child->x = x;
815 gtk_widget_child_notify(widget, "x");
817 child->y = y;
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,
829 &child_requisition);
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);
848 return FALSE;
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)
856 time(&tip_time);
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)
864 GtkWidget *label;
865 int x, y, py;
866 int w, h;
867 int m;
869 if (tip_timeout)
871 g_source_remove(tip_timeout);
872 tip_timeout = 0;
875 if (tip_widget)
877 gtk_widget_destroy(tip_widget);
878 tip_widget = NULL;
881 if (!text)
882 return;
884 /* Show the tip */
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);
904 x -= w / 2;
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)
915 y = py - h - 2;
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);
921 time(&tip_time);
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)
929 time_t now;
930 int delay;
932 g_return_if_fail(tip_timeout == 0);
934 time(&now);
935 delay = now - tip_time > 2 ? 1000 : 200;
937 g_object_ref(object);
938 tip_timeout = g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE,
939 delay,
940 (GSourceFunc) callback,
941 object,
942 g_object_unref);
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)
959 : NULL;
961 gtk_widget_modify_style(widget, rc_style);
964 /* Confirm the action with the user. If action is NULL, the text from stock
965 * is used.
967 gboolean confirm(const gchar *message, const gchar *stock, const gchar *action)
969 return get_choice(PROJECT, message, 2,
970 GTK_STOCK_CANCEL, NULL,
971 stock, action) == 1;
974 struct _Radios {
975 GList *widgets;
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
985 * change after that.
987 Radios *radios_new(void (*changed)(Radios *, gpointer data), gpointer data)
989 Radios *radios;
991 radios = g_new(Radios, 1);
993 radios->widgets = NULL;
994 radios->changed = changed;
995 radios->changed_data = data;
997 return radios;
1000 static void radios_free(GtkWidget *radio, Radios *radios)
1002 g_return_if_fail(radios != NULL);
1004 g_list_free(radios->widgets);
1005 g_free(radios);
1008 void radios_add(Radios *radios, const gchar *tip, gint value,
1009 const gchar *label, ...)
1011 GtkWidget *radio;
1012 GSList *group = NULL;
1013 gchar *s;
1014 va_list args;
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);
1021 va_end(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);
1032 if (tip)
1033 gtk_tooltips_set_tip(tooltips, radio, tip, NULL);
1034 if (!group)
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)
1056 GList *next;
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)
1073 GList *next;
1075 g_return_if_fail(radios != NULL);
1077 for (next = radios->widgets; next; next = next->next)
1079 GtkToggleButton *radio = GTK_TOGGLE_BUTTON(next->data);
1080 int radio_value;
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);
1088 return;
1092 g_warning("Value %d not in radio group!", value);
1095 gint radios_get_value(Radios *radios)
1097 GList *next;
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!");
1112 return -1;
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)
1122 GList *list = NULL;
1124 while (*uri_list)
1126 char *linebreak;
1127 int length;
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
1137 * 3.5) */
1138 if (!list && uri_list[0] != '#')
1139 list = g_list_append(list, g_strdup(uri_list));
1140 return 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;
1151 return list;
1154 typedef struct _SimpleImageClass SimpleImageClass;
1155 typedef struct _SimpleImage SimpleImage;
1157 struct _SimpleImageClass {
1158 GtkWidgetClass parent;
1161 struct _SimpleImage {
1162 GtkWidget widget;
1164 GdkPixbuf *pixbuf;
1165 int width, height;
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;
1200 int x;
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);
1212 return FALSE;
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;
1234 if (!type)
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 */
1246 simple_image_init,
1249 type = g_type_register_static(gtk_widget_get_type(),
1250 "SimpleImage", &info, 0);
1253 return type;
1256 GtkWidget *simple_image_new(GdkPixbuf *pixbuf)
1258 SimpleImage *image;
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)
1283 int m;
1285 adj->left = adj->right = adj->top = adj->bottom = FALSE;
1287 for (m = 0; m < n_monitors; ++m)
1289 if (m == monitor)
1290 continue;
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)
1298 adj->left = TRUE;
1300 else if (monitor_geom[m].x > monitor_geom[monitor].x)
1302 adj->right = TRUE;
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)
1312 adj->top = TRUE;
1314 else if (monitor_geom[m].y > monitor_geom[monitor].y)
1316 adj->bottom = TRUE;
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));
1326 XEvent xev;
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,
1341 state1);
1342 xev.xclient.data.l[2] = gdk_x11_atom_to_xatom_for_display(display,
1343 state2);
1344 xev.xclient.data.l[3] = 0;
1345 xev.xclient.data.l[4] = 0;
1347 XSendEvent(GDK_DISPLAY_XDISPLAY(display),
1348 GDK_WINDOW_XID(
1349 gdk_screen_get_root_window(
1350 gdk_drawable_get_screen(GDK_DRAWABLE(window)))),
1351 False,
1352 SubstructureRedirectMask | SubstructureNotifyMask,
1353 &xev);
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))
1362 return;
1364 if (gdk_window_is_visible(window))
1366 if (setting)
1368 rox_wmspec_change_state(FALSE, window,
1369 gdk_atom_intern("_NET_WM_STATE_ABOVE", FALSE),
1370 GDK_NONE);
1372 rox_wmspec_change_state(setting, window,
1373 gdk_atom_intern("_NET_WM_STATE_BELOW", FALSE),
1374 GDK_NONE);
1376 #if 0
1377 else
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);
1384 #endif
1386 #endif
1389 static void
1390 size_prepared_cb (GdkPixbufLoader *loader,
1391 int width,
1392 int height,
1393 gpointer data)
1395 struct {
1396 gint width;
1397 gint height;
1398 gboolean preserve_aspect_ratio;
1399 } *info = data;
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;
1408 } else {
1409 height = 0.5 + (double)height * (double)info->width / (double)width;
1410 width = info->width;
1412 } else {
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.
1441 GdkPixbuf *
1442 rox_pixbuf_new_from_file_at_scale (const char *filename,
1443 int width,
1444 int height,
1445 gboolean preserve_aspect_ratio,
1446 GError **error)
1449 GdkPixbufLoader *loader;
1450 GdkPixbuf *pixbuf;
1452 guchar buffer [4096];
1453 int length;
1454 FILE *f;
1455 struct {
1456 gint width;
1457 gint height;
1458 gboolean preserve_aspect_ratio;
1459 } info;
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");
1465 if (!f) {
1466 gchar *utf8_filename = g_filename_to_utf8 (filename, -1,
1467 NULL, NULL, NULL);
1468 g_set_error (error,
1469 G_FILE_ERROR,
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);
1475 return NULL;
1478 loader = gdk_pixbuf_loader_new ();
1480 info.width = width;
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);
1488 if (length > 0)
1489 if (!gdk_pixbuf_loader_write (loader, buffer, length, error)) {
1490 gdk_pixbuf_loader_close (loader, NULL);
1491 fclose (f);
1492 g_object_unref (loader);
1493 return NULL;
1497 fclose (f);
1499 if (!gdk_pixbuf_loader_close (loader, error)) {
1500 g_object_unref (loader);
1501 return NULL;
1504 pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
1506 if (!pixbuf) {
1507 gchar *utf8_filename = g_filename_to_utf8 (filename, -1,
1508 NULL, NULL, NULL);
1510 g_object_unref (loader);
1512 g_set_error (error,
1513 GDK_PIXBUF_ERROR,
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);
1518 return NULL;
1521 g_object_ref (pixbuf);
1523 g_object_unref (loader);
1525 return pixbuf;
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"
1558 "/bin/0launch";
1560 if (!available_in_path(argv[0]))
1562 if (access(uri_0launch, X_OK) == 0)
1563 argv[0] = uri_0launch;
1564 else
1566 const char *appname=g_object_get_data(button,
1567 "appname");
1569 if (appname)
1571 gchar *path=find_app(appname);
1572 if(path)
1574 run_by_path(path);
1575 g_free(path);
1576 return;
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"),
1584 uri);
1585 return;
1589 if (current_event_button() == 3)
1591 argv[1] = "-g";
1592 argv[2] = uri;
1594 else
1595 argv[1] = uri;
1597 rox_spawn(NULL, argv);
1600 static gint button3_button_pressed(GtkButton *button,
1601 GdkEventButton *event,
1602 gpointer date)
1604 if (event->button == 3)
1606 gtk_grab_add(GTK_WIDGET(button));
1607 gtk_button_pressed(button);
1609 return TRUE;
1612 return FALSE;
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);
1624 return TRUE;
1627 return FALSE;
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,
1639 * or not a click).
1641 gint current_event_button(void)
1643 GdkEventButton *bev;
1644 gint button = -1;
1646 bev = (GdkEventButton *) gtk_get_current_event();
1648 if (bev &&
1649 (bev->type == GDK_BUTTON_PRESS || bev->type == GDK_BUTTON_RELEASE))
1650 button = bev->button;
1652 gdk_event_free((GdkEvent *) bev);
1654 return button;
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;
1665 GdkPixbuf *dst;
1666 GdkColorspace colorspace;
1667 int width, height, src_rowstride, dst_rowstride, x, y;
1668 int n_channels, bps;
1669 int r, g, b;
1670 guchar *spixels, *dpixels, *src_pixels, *dst_pixels;
1671 gboolean has_alpha;
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) ||
1681 (bps != 8))
1682 goto error;
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);
1688 if (dst == NULL)
1689 goto error;
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;
1709 if (has_alpha)
1710 *dpixels++ = *spixels++;
1714 return dst;
1716 error:
1717 g_object_ref(src);
1718 return src;
1721 /* Load the Templates.ui file and build a component. */
1722 GtkBuilder *get_gtk_builder(gchar **ids)
1724 GError *error = NULL;
1725 char *path;
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);
1739 g_free(path);
1741 return builder;
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));