r3933: Type changes for 64-bit compatibility (Tony Houghton).
[rox-filer.git] / ROX-Filer / src / gui_support.c
bloba36c79775e93c85b207e129c93c7a9fee300d0df
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2005, the ROX-Filer team.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 * Place, Suite 330, Boston, MA 02111-1307 USA
22 /* gui_support.c - general (GUI) support routines */
24 #include "config.h"
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <sys/param.h>
30 #include <stdarg.h>
31 #include <errno.h>
32 #include <time.h>
34 #include <X11/Xlib.h>
35 #include <X11/Xatom.h>
36 #include <gdk/gdkx.h>
37 #include <gdk/gdk.h>
38 #include <gdk/gdkkeysyms.h>
40 #include "global.h"
42 #include "main.h"
43 #include "gui_support.h"
44 #include "support.h"
45 #include "pixmaps.h"
46 #include "choices.h"
47 #include "options.h"
49 gint screen_width, screen_height;
51 gint n_monitors;
52 GdkRectangle *monitor_geom = NULL;
53 gint monitor_width, monitor_height;
54 MonitorAdjacent *monitor_adjacent;
56 static GdkAtom xa_cardinal;
58 static GtkWidget *current_dialog = NULL;
60 static GtkWidget *tip_widget = NULL;
61 static time_t tip_time = 0; /* Time tip widget last closed */
62 static gint tip_timeout = 0; /* When primed */
64 /* Static prototypes */
65 static void run_error_info_dialog(GtkMessageType type, const char *message,
66 va_list args);
67 static GType simple_image_get_type(void);
68 static void gui_get_monitor_adjacent(int monitor, MonitorAdjacent *adj);
70 void gui_store_screen_geometry(GdkScreen *screen)
72 gint mon;
74 screen_width = gdk_screen_get_width(screen);
75 screen_height = gdk_screen_get_height(screen);
77 if (monitor_adjacent)
78 g_free(monitor_adjacent);
80 monitor_width = monitor_height = G_MAXINT;
81 n_monitors = gdk_screen_get_n_monitors(screen);
82 if (monitor_geom)
83 g_free(monitor_geom);
84 monitor_geom = g_new(GdkRectangle, n_monitors ? n_monitors : 1);
86 if (n_monitors)
88 for (mon = 0; mon < n_monitors; ++mon)
90 gdk_screen_get_monitor_geometry(screen, mon,
91 &monitor_geom[mon]);
92 if (monitor_geom[mon].width < monitor_width)
93 monitor_width = monitor_geom[mon].width;
94 if (monitor_geom[mon].height < monitor_height)
95 monitor_height = monitor_geom[mon].height;
97 monitor_adjacent = g_new(MonitorAdjacent, n_monitors);
98 for (mon = 0; mon < n_monitors; ++mon)
100 gui_get_monitor_adjacent(mon, &monitor_adjacent[mon]);
103 else
105 n_monitors = 1;
106 monitor_geom[0].x = monitor_geom[0].y = 0;
107 monitor_width = monitor_geom[0].width = screen_width;
108 monitor_height = monitor_geom[0].height = screen_height;
109 monitor_adjacent = g_new0(MonitorAdjacent, 1);
114 void gui_support_init()
116 gpointer klass;
118 xa_cardinal = gdk_atom_intern("CARDINAL", FALSE);
120 gui_store_screen_geometry(gdk_screen_get_default());
122 /* Work around the scrollbar placement bug */
123 klass = g_type_class_ref(gtk_scrolled_window_get_type());
124 ((GtkScrolledWindowClass *) klass)->scrollbar_spacing = 0;
125 /* (don't unref, ever) */
128 /* Open a modal dialog box showing a message.
129 * The user can choose from a selection of buttons at the bottom.
130 * Returns -1 if the window is destroyed, or the number of the button
131 * if one is clicked (starting from zero).
133 * If a dialog is already open, returns -1 without waiting AND
134 * brings the current dialog to the front.
136 * Each button has two arguments, a GTK_STOCK icon and some text. If the
137 * text is NULL, the stock's text is used.
139 int get_choice(const char *title,
140 const char *message,
141 int number_of_buttons, ...)
143 GtkWidget *dialog;
144 GtkWidget *button = NULL;
145 int i, retval;
146 va_list ap;
148 if (current_dialog)
150 gtk_widget_hide(current_dialog);
151 gtk_widget_show(current_dialog);
152 return -1;
155 current_dialog = dialog = gtk_message_dialog_new(NULL,
156 GTK_DIALOG_MODAL,
157 GTK_MESSAGE_QUESTION,
158 GTK_BUTTONS_NONE,
159 "%s", message);
160 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
162 va_start(ap, number_of_buttons);
164 for (i = 0; i < number_of_buttons; i++)
166 const char *stock = va_arg(ap, char *);
167 const char *text = va_arg(ap, char *);
169 if (text)
170 button = button_new_mixed(stock, text);
171 else
172 button = gtk_button_new_from_stock(stock);
174 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
175 gtk_widget_show(button);
177 gtk_dialog_add_action_widget(GTK_DIALOG(current_dialog),
178 button, i);
181 gtk_window_set_title(GTK_WINDOW(dialog), title);
182 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
184 gtk_dialog_set_default_response(GTK_DIALOG(dialog), i - 1);
186 va_end(ap);
188 retval = gtk_dialog_run(GTK_DIALOG(dialog));
189 if (retval == GTK_RESPONSE_NONE)
190 retval = -1;
191 gtk_widget_destroy(dialog);
193 current_dialog = NULL;
195 return retval;
198 void info_message(const char *message, ...)
200 va_list args;
202 va_start(args, message);
204 run_error_info_dialog(GTK_MESSAGE_INFO, message, args);
207 /* Display a message in a window with "ROX-Filer" as title */
208 void report_error(const char *message, ...)
210 va_list args;
212 va_start(args, message);
214 run_error_info_dialog(GTK_MESSAGE_ERROR, message, args);
217 void set_cardinal_property(GdkWindow *window, GdkAtom prop, gulong value)
219 gdk_property_change(window, prop, xa_cardinal, 32,
220 GDK_PROP_MODE_REPLACE, (gchar *) &value, 1);
223 /* NB: Also used for pinned icons.
224 * TODO: Set the level here too.
226 void make_panel_window(GtkWidget *widget)
228 static gboolean need_init = TRUE;
229 static GdkAtom xa_state, xa_atom, xa_hints, xa_win_hints;
230 static GdkAtom xa_NET_WM_DESKTOP;
231 GdkWindow *window = widget->window;
232 long wm_hints_values[] = {1, False, 0, 0, 0, 0, 0, 0};
233 GdkAtom wm_protocols[2];
235 g_return_if_fail(window != NULL);
237 if (o_override_redirect.int_value)
239 gdk_window_set_override_redirect(window, TRUE);
240 return;
243 if (need_init)
245 xa_win_hints = gdk_atom_intern("_WIN_HINTS", FALSE);
246 xa_state = gdk_atom_intern("_WIN_STATE", FALSE);
247 xa_atom = gdk_atom_intern("ATOM", FALSE);
248 xa_hints = gdk_atom_intern("WM_HINTS", FALSE);
249 xa_NET_WM_DESKTOP = gdk_atom_intern("_NET_WM_DESKTOP", FALSE);
251 need_init = FALSE;
254 gdk_window_set_decorations(window, 0);
255 gdk_window_set_functions(window, 0);
256 gtk_window_set_resizable(GTK_WINDOW(widget), FALSE);
258 /* Don't hide panel/pinboard windows initially (WIN_STATE_HIDDEN).
259 * Needed for IceWM - Christopher Arndt <chris.arndt@web.de>
261 set_cardinal_property(window, xa_state,
262 WIN_STATE_STICKY |
263 WIN_STATE_FIXED_POSITION | WIN_STATE_ARRANGE_IGNORE);
265 set_cardinal_property(window, xa_win_hints,
266 WIN_HINTS_SKIP_FOCUS | WIN_HINTS_SKIP_WINLIST |
267 WIN_HINTS_SKIP_TASKBAR);
269 /* Appear on all workspaces */
270 set_cardinal_property(window, xa_NET_WM_DESKTOP, 0xffffffff);
272 gdk_property_change(window, xa_hints, xa_hints, 32,
273 GDK_PROP_MODE_REPLACE, (guchar *) wm_hints_values,
274 sizeof(wm_hints_values) / sizeof(long));
276 wm_protocols[0] = gdk_atom_intern("WM_DELETE_WINDOW", FALSE);
277 wm_protocols[1] = gdk_atom_intern("_NET_WM_PING", FALSE);
278 gdk_property_change(window,
279 gdk_atom_intern("WM_PROTOCOLS", FALSE), xa_atom, 32,
280 GDK_PROP_MODE_REPLACE, (guchar *) wm_protocols,
281 sizeof(wm_protocols) / sizeof(GdkAtom));
283 gdk_window_set_skip_taskbar_hint(window, TRUE);
284 gdk_window_set_skip_pager_hint(window, TRUE);
286 if (g_object_class_find_property(G_OBJECT_GET_CLASS(widget),
287 "accept_focus"))
289 GValue vfalse = { 0, };
290 g_value_init(&vfalse, G_TYPE_BOOLEAN);
291 g_value_set_boolean(&vfalse, FALSE);
292 g_object_set_property(G_OBJECT(widget),
293 "accept_focus", &vfalse);
294 g_value_unset(&vfalse);
298 static gboolean error_idle_cb(gpointer data)
300 char **error = (char **) data;
302 report_error("%s", *error);
303 null_g_free(error);
305 one_less_window();
306 return FALSE;
309 /* Display an error with "ROX-Filer" as title next time we are idle.
310 * If multiple errors are reported this way before the window is opened,
311 * all are displayed in a single window.
312 * If an error is reported while the error window is open, it is discarded.
314 void delayed_error(const char *error, ...)
316 static char *delayed_error_data = NULL;
317 char *old, *new;
318 va_list args;
320 g_return_if_fail(error != NULL);
322 old = delayed_error_data;
324 va_start(args, error);
325 new = g_strdup_vprintf(error, args);
326 va_end(args);
328 if (old)
330 delayed_error_data = g_strconcat(old,
331 _("\n---\n"),
332 new, NULL);
333 g_free(old);
334 g_free(new);
336 else
338 delayed_error_data = new;
339 g_idle_add(error_idle_cb, &delayed_error_data);
341 number_of_windows++;
345 /* Load the file into memory. Return TRUE on success.
346 * Block is zero terminated (but this is not included in the length).
348 gboolean load_file(const char *pathname, char **data_out, long *length_out)
350 gsize len;
351 GError *error = NULL;
353 if (!g_file_get_contents(pathname, data_out, &len, &error))
355 delayed_error("%s", error->message);
356 g_error_free(error);
357 return FALSE;
360 if (length_out)
361 *length_out = len;
362 return TRUE;
365 GtkWidget *new_help_button(HelpFunc show_help, gpointer data)
367 GtkWidget *b, *icon;
369 b = gtk_button_new();
370 gtk_button_set_relief(GTK_BUTTON(b), GTK_RELIEF_NONE);
371 icon = gtk_image_new_from_stock(GTK_STOCK_HELP,
372 GTK_ICON_SIZE_SMALL_TOOLBAR);
373 gtk_container_add(GTK_CONTAINER(b), icon);
374 g_signal_connect_swapped(b, "clicked", G_CALLBACK(show_help), data);
376 GTK_WIDGET_UNSET_FLAGS(b, GTK_CAN_FOCUS);
378 return b;
381 /* Read file into memory. Call parse_line(guchar *line) for each line
382 * in the file. Callback returns NULL on success, or an error message
383 * if something went wrong. Only the first error is displayed to the user.
385 void parse_file(const char *path, ParseFunc *parse_line)
387 char *data;
388 long length;
389 gboolean seen_error = FALSE;
391 if (load_file(path, &data, &length))
393 char *eol;
394 const char *error;
395 char *line = data;
396 int line_number = 1;
398 if (strncmp(data, "<?xml ", 6) == 0)
400 delayed_error(_("Attempt to read an XML file as "
401 "a text file. File '%s' may be "
402 "corrupted."), path);
403 return;
406 while (line && *line)
408 eol = strchr(line, '\n');
409 if (eol)
410 *eol = '\0';
412 error = parse_line(line);
414 if (error && !seen_error)
416 delayed_error(
417 _("Error in '%s' file at line %d: "
418 "\n\"%s\"\n"
419 "This may be due to upgrading from a previous version of "
420 "ROX-Filer. Open the Options window and click on Save.\n"
421 "Further errors will be ignored."),
422 path,
423 line_number,
424 error);
425 seen_error = TRUE;
428 if (!eol)
429 break;
430 line = eol + 1;
431 line_number++;
433 g_free(data);
437 /* Returns the position of the pointer.
438 * TRUE if any modifier keys or mouse buttons are pressed.
440 gboolean get_pointer_xy(int *x, int *y)
442 unsigned int mask;
444 gdk_window_get_pointer(NULL, x, y, &mask);
446 return mask != 0;
449 #define DECOR_BORDER 32
451 /* Centre the window at these coords */
452 void centre_window(GdkWindow *window, int x, int y)
454 int w, h;
455 int m;
457 g_return_if_fail(window != NULL);
459 m = gdk_screen_get_monitor_at_point(gdk_screen_get_default(), x, y);
461 gdk_drawable_get_size(window, &w, &h);
463 x -= w / 2;
464 y -= h / 2;
466 gdk_window_move(window,
467 CLAMP(x, DECOR_BORDER + monitor_geom[m].x,
468 monitor_geom[m].x + monitor_geom[m].width
469 - w - DECOR_BORDER),
470 CLAMP(y, DECOR_BORDER + monitor_geom[m].y,
471 monitor_geom[m].y + monitor_geom[m].height
472 - h - DECOR_BORDER));
475 static void run_error_info_dialog(GtkMessageType type, const char *message,
476 va_list args)
478 GtkWidget *dialog;
479 gchar *s;
481 g_return_if_fail(message != NULL);
483 s = g_strdup_vprintf(message, args);
484 va_end(args);
486 dialog = gtk_message_dialog_new(NULL,
487 GTK_DIALOG_MODAL,
488 type,
489 GTK_BUTTONS_OK,
490 "%s", s);
491 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
492 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
493 gtk_dialog_run(GTK_DIALOG(dialog));
494 gtk_widget_destroy(dialog);
496 g_free(s);
499 static GtkWidget *current_wink_widget = NULL;
500 static gint wink_timeout = -1; /* Called when it's time to stop */
501 static gulong wink_destroy; /* Called if the widget dies first */
503 static gboolean end_wink(gpointer data)
505 gtk_drag_unhighlight(current_wink_widget);
507 g_signal_handler_disconnect(current_wink_widget, wink_destroy);
509 current_wink_widget = NULL;
511 return FALSE;
514 static void cancel_wink(void)
516 g_source_remove(wink_timeout);
517 end_wink(NULL);
520 static void wink_widget_died(gpointer data)
522 current_wink_widget = NULL;
523 g_source_remove(wink_timeout);
526 /* Draw a black box around this widget, briefly.
527 * Note: uses the drag highlighting code for now.
529 void wink_widget(GtkWidget *widget)
531 g_return_if_fail(widget != NULL);
533 if (current_wink_widget)
534 cancel_wink();
536 current_wink_widget = widget;
537 gtk_drag_highlight(current_wink_widget);
539 wink_timeout = g_timeout_add(300, (GSourceFunc) end_wink, NULL);
541 wink_destroy = g_signal_connect_swapped(widget, "destroy",
542 G_CALLBACK(wink_widget_died), NULL);
545 static gboolean idle_destroy_cb(GtkWidget *widget)
547 gtk_widget_unref(widget);
548 gtk_widget_destroy(widget);
549 return FALSE;
552 /* Destroy the widget in an idle callback */
553 void destroy_on_idle(GtkWidget *widget)
555 gtk_widget_ref(widget);
556 g_idle_add((GSourceFunc) idle_destroy_cb, widget);
559 /* Spawn a child process (as spawn_full), and report errors.
560 * Returns the child's PID on succes, or 0 on failure.
562 gint rox_spawn(const gchar *dir, const gchar **argv)
564 GError *error = NULL;
565 gint pid = 0;
567 if (!g_spawn_async_with_pipes(dir, (gchar **) argv, NULL,
568 G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_STDOUT_TO_DEV_NULL |
569 G_SPAWN_SEARCH_PATH,
570 NULL, NULL, /* Child setup fn */
571 &pid, /* Child PID */
572 NULL, NULL, NULL, /* Standard pipes */
573 &error))
575 delayed_error("%s", error ? error->message : "(null)");
576 g_error_free(error);
578 return 0;
581 return pid;
584 GtkWidget *button_new_image_text(GtkWidget *image, const char *message)
586 GtkWidget *button, *align, *hbox, *label;
588 button = gtk_button_new();
589 label = gtk_label_new_with_mnemonic(message);
590 gtk_label_set_mnemonic_widget(GTK_LABEL(label), button);
592 hbox = gtk_hbox_new(FALSE, 2);
594 align = gtk_alignment_new(0.5, 0.5, 0.0, 0.0);
596 gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
597 gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 0);
599 gtk_container_add(GTK_CONTAINER(button), align);
600 gtk_container_add(GTK_CONTAINER(align), hbox);
601 gtk_widget_show_all(align);
603 return button;
606 GtkWidget *button_new_mixed(const char *stock, const char *message)
608 return button_new_image_text(gtk_image_new_from_stock(stock,
609 GTK_ICON_SIZE_BUTTON),
610 message);
613 /* Highlight entry in red if 'error' is TRUE */
614 void entry_set_error(GtkWidget *entry, gboolean error)
616 const GdkColor red = {0, 0xffff, 0, 0};
617 const GdkColor white = {0, 0xffff, 0xffff, 0xffff};
619 gtk_widget_modify_text(entry, GTK_STATE_NORMAL, error ? &red : NULL);
620 gtk_widget_modify_base(entry, GTK_STATE_NORMAL, error ? &white : NULL);
623 /* Change stacking position of higher to be just above lower.
624 * If lower is NULL, put higher at the bottom of the stack.
626 void window_put_just_above(GdkWindow *higher, GdkWindow *lower)
628 if (o_override_redirect.int_value && lower)
630 XWindowChanges restack;
632 gdk_error_trap_push();
634 restack.stack_mode = Above;
636 restack.sibling = GDK_WINDOW_XWINDOW(lower);
638 XConfigureWindow(gdk_display, GDK_WINDOW_XWINDOW(higher),
639 CWSibling | CWStackMode, &restack);
641 gdk_flush();
642 if (gdk_error_trap_pop())
643 g_warning("window_put_just_above()\n");
645 else
646 gdk_window_lower(higher); /* To bottom of stack */
649 /* Copied from Gtk */
650 static GtkFixedChild* fixed_get_child(GtkFixed *fixed, GtkWidget *widget)
652 GList *children;
654 children = fixed->children;
655 while (children)
657 GtkFixedChild *child;
659 child = children->data;
660 children = children->next;
662 if (child->widget == widget)
663 return child;
666 return NULL;
669 /* Like gtk_fixed_move(), except not insanely slow */
670 void fixed_move_fast(GtkFixed *fixed, GtkWidget *widget, int x, int y)
672 GtkFixedChild *child;
674 child = fixed_get_child(fixed, widget);
676 g_assert(child);
678 gtk_widget_freeze_child_notify(widget);
680 child->x = x;
681 gtk_widget_child_notify(widget, "x");
683 child->y = y;
684 gtk_widget_child_notify(widget, "y");
686 gtk_widget_thaw_child_notify(widget);
688 if (GTK_WIDGET_VISIBLE(widget) && GTK_WIDGET_VISIBLE(fixed))
690 int border_width = GTK_CONTAINER(fixed)->border_width;
691 GtkAllocation child_allocation;
692 GtkRequisition child_requisition;
694 gtk_widget_get_child_requisition(child->widget,
695 &child_requisition);
696 child_allocation.x = child->x + border_width;
697 child_allocation.y = child->y + border_width;
699 child_allocation.x += GTK_WIDGET(fixed)->allocation.x;
700 child_allocation.y += GTK_WIDGET(fixed)->allocation.y;
702 child_allocation.width = child_requisition.width;
703 child_allocation.height = child_requisition.height;
704 gtk_widget_size_allocate(child->widget, &child_allocation);
708 /* Draw the black border */
709 static gint tooltip_draw(GtkWidget *w)
711 gdk_draw_rectangle(w->window, w->style->fg_gc[w->state], FALSE, 0, 0,
712 w->allocation.width - 1, w->allocation.height - 1);
714 return FALSE;
717 /* When the tips window closed, record the time. If we try to open another
718 * tip soon, it will appear more quickly.
720 static void tooltip_destroyed(gpointer data)
722 time(&tip_time);
725 /* Display a tooltip-like widget near the pointer with 'text'. If 'text' is
726 * NULL, close any current tooltip.
728 void tooltip_show(guchar *text)
730 GtkWidget *label;
731 int x, y, py;
732 int w, h;
733 int m;
735 if (tip_timeout)
737 g_source_remove(tip_timeout);
738 tip_timeout = 0;
741 if (tip_widget)
743 gtk_widget_destroy(tip_widget);
744 tip_widget = NULL;
747 if (!text)
748 return;
750 /* Show the tip */
751 tip_widget = gtk_window_new(GTK_WINDOW_POPUP);
752 gtk_widget_set_app_paintable(tip_widget, TRUE);
753 gtk_widget_set_name(tip_widget, "gtk-tooltips");
755 g_signal_connect_swapped(tip_widget, "expose_event",
756 G_CALLBACK(tooltip_draw), tip_widget);
758 label = gtk_label_new(text);
759 gtk_misc_set_padding(GTK_MISC(label), 4, 2);
760 gtk_container_add(GTK_CONTAINER(tip_widget), label);
761 gtk_widget_show(label);
762 gtk_widget_realize(tip_widget);
764 w = tip_widget->allocation.width;
765 h = tip_widget->allocation.height;
766 gdk_window_get_pointer(NULL, &x, &py, NULL);
768 m = gdk_screen_get_monitor_at_point(gdk_screen_get_default(), x, py);
770 x -= w / 2;
771 y = py + 12; /* I don't know the pointer height so I use a constant */
773 /* Now check for screen boundaries */
774 x = CLAMP(x, monitor_geom[m].x,
775 monitor_geom[m].x + monitor_geom[m].width - w);
776 y = CLAMP(y, monitor_geom[m].y,
777 monitor_geom[m].y + monitor_geom[m].height - h);
779 /* And again test if pointer is over the tooltip window */
780 if (py >= y && py <= y + h)
781 y = py - h - 2;
782 gtk_window_move(GTK_WINDOW(tip_widget), x, y);
783 gtk_widget_show(tip_widget);
785 g_signal_connect_swapped(tip_widget, "destroy",
786 G_CALLBACK(tooltip_destroyed), NULL);
787 time(&tip_time);
790 /* Call callback(user_data) after a while, unless cancelled.
791 * Object is refd now and unref when cancelled / after callback called.
793 void tooltip_prime(GtkFunction callback, GObject *object)
795 time_t now;
796 int delay;
798 g_return_if_fail(tip_timeout == 0);
800 time(&now);
801 delay = now - tip_time > 2 ? 1000 : 200;
803 g_object_ref(object);
804 tip_timeout = g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE,
805 delay,
806 (GSourceFunc) callback,
807 object,
808 g_object_unref);
811 /* Like gtk_widget_modify_font, but copes with font_desc == NULL */
812 void widget_modify_font(GtkWidget *widget, PangoFontDescription *font_desc)
814 GtkRcStyle *rc_style;
816 g_return_if_fail(GTK_IS_WIDGET(widget));
818 rc_style = gtk_widget_get_modifier_style(widget);
820 if (rc_style->font_desc)
821 pango_font_description_free(rc_style->font_desc);
823 rc_style->font_desc = font_desc
824 ? pango_font_description_copy(font_desc)
825 : NULL;
827 gtk_widget_modify_style(widget, rc_style);
830 /* Confirm the action with the user. If action is NULL, the text from stock
831 * is used.
833 gboolean confirm(const gchar *message, const gchar *stock, const gchar *action)
835 return get_choice(PROJECT, message, 2,
836 GTK_STOCK_CANCEL, NULL,
837 stock, action) == 1;
840 struct _Radios {
841 GList *widgets;
843 void (*changed)(gpointer data);
844 gpointer changed_data;
847 /* Create a new set of radio buttons.
848 * Use radios_add to add options, then radios_pack to put them into something.
849 * The radios object will self-destruct with the first widget it contains.
850 * changed(data) is called (if not NULL) when pack is called, and on any
851 * change after that.
853 Radios *radios_new(void (*changed)(gpointer data), gpointer data)
855 Radios *radios;
857 radios = g_new(Radios, 1);
859 radios->widgets = NULL;
860 radios->changed = changed;
861 radios->changed_data = data;
863 return radios;
866 static void radios_free(GtkWidget *radio, Radios *radios)
868 g_return_if_fail(radios != NULL);
870 g_list_free(radios->widgets);
871 g_free(radios);
874 void radios_add(Radios *radios, const gchar *tip, gint value,
875 const gchar *label, ...)
877 GtkWidget *radio;
878 GSList *group = NULL;
879 gchar *s;
880 va_list args;
882 g_return_if_fail(radios != NULL);
883 g_return_if_fail(label != NULL);
885 va_start(args, label);
886 s = g_strdup_vprintf(label, args);
887 va_end(args);
889 if (radios->widgets)
891 GtkRadioButton *first = GTK_RADIO_BUTTON(radios->widgets->data);
892 group = gtk_radio_button_get_group(first);
895 radio = gtk_radio_button_new_with_label(group, s);
896 gtk_label_set_line_wrap(GTK_LABEL(GTK_BIN(radio)->child), TRUE);
897 gtk_widget_show(radio);
898 if (tip)
899 gtk_tooltips_set_tip(tooltips, radio, tip, NULL);
900 if (!group)
901 g_signal_connect(G_OBJECT(radio), "destroy",
902 G_CALLBACK(radios_free), radios);
904 radios->widgets = g_list_prepend(radios->widgets, radio);
905 g_object_set_data(G_OBJECT(radio), "rox-radios-value",
906 GINT_TO_POINTER(value));
909 static void radio_toggled(GtkToggleButton *button, Radios *radios)
911 g_return_if_fail(radios != NULL);
913 if (radios->changed)
914 radios->changed(radios->changed_data);
917 void radios_pack(Radios *radios, GtkBox *box)
919 GList *next;
921 g_return_if_fail(radios != NULL);
923 for (next = g_list_last(radios->widgets); next; next = next->prev)
925 GtkWidget *button = GTK_WIDGET(next->data);
927 gtk_box_pack_start(box, button, FALSE, TRUE, 0);
928 g_signal_connect(button, "toggled",
929 G_CALLBACK(radio_toggled), radios);
931 radio_toggled(NULL, radios);
934 void radios_set_value(Radios *radios, gint value)
936 GList *next;
938 g_return_if_fail(radios != NULL);
940 for (next = radios->widgets; next; next = next->next)
942 GtkToggleButton *radio = GTK_TOGGLE_BUTTON(next->data);
943 int radio_value;
945 radio_value = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(radio),
946 "rox-radios-value"));
948 if (radio_value == value)
950 gtk_toggle_button_set_active(radio, TRUE);
951 return;
955 g_warning("Value %d not in radio group!", value);
958 gint radios_get_value(Radios *radios)
960 GList *next;
962 g_return_val_if_fail(radios != NULL, -1);
964 for (next = radios->widgets; next; next = next->next)
966 GtkToggleButton *radio = GTK_TOGGLE_BUTTON(next->data);
968 if (gtk_toggle_button_get_active(radio))
969 return GPOINTER_TO_INT(g_object_get_data(
970 G_OBJECT(radio), "rox-radios-value"));
973 g_warning("Nothing in the radio group is selected!");
975 return -1;
978 /* Convert a list of URIs as a string into a GList of EscapedPath URIs.
979 * No unescaping is done.
980 * Lines beginning with # are skipped.
981 * The text block passed in is zero terminated (after the final CRLF)
983 GList *uri_list_to_glist(const char *uri_list)
985 GList *list = NULL;
987 while (*uri_list)
989 char *linebreak;
990 int length;
992 linebreak = strchr(uri_list, 13);
994 if (!linebreak || linebreak[1] != 10)
996 delayed_error("uri_list_to_glist: %s",
997 _("Incorrect or missing line "
998 "break in text/uri-list data"));
999 return list;
1002 length = linebreak - uri_list;
1004 if (length && uri_list[0] != '#')
1005 list = g_list_append(list, g_strndup(uri_list, length));
1007 uri_list = linebreak + 2;
1010 return list;
1013 typedef struct _SimpleImageClass SimpleImageClass;
1014 typedef struct _SimpleImage SimpleImage;
1016 struct _SimpleImageClass {
1017 GtkWidgetClass parent;
1020 struct _SimpleImage {
1021 GtkWidget widget;
1023 GdkPixbuf *pixbuf;
1024 int width, height;
1027 #define SIMPLE_IMAGE(obj) (GTK_CHECK_CAST((obj), \
1028 simple_image_get_type(), SimpleImage))
1030 static void simple_image_finialize(GObject *object)
1032 SimpleImage *image = SIMPLE_IMAGE(object);
1034 g_object_unref(G_OBJECT(image->pixbuf));
1035 image->pixbuf = NULL;
1038 static void simple_image_size_request(GtkWidget *widget,
1039 GtkRequisition *requisition)
1041 SimpleImage *image = (SimpleImage *) widget;
1043 requisition->width = image->width;
1044 requisition->height = image->height;
1047 /* Render a pixbuf without messing up the clipping */
1048 void render_pixbuf(GdkPixbuf *pixbuf, GdkDrawable *target, GdkGC *gc,
1049 int x, int y, int width, int height)
1051 gdk_draw_pixbuf(target, gc, pixbuf, 0, 0, x, y, width, height,
1052 GDK_RGB_DITHER_NORMAL, 0, 0);
1056 static gint simple_image_expose(GtkWidget *widget, GdkEventExpose *event)
1058 SimpleImage *image = (SimpleImage *) widget;
1059 int x;
1061 gdk_gc_set_clip_region(widget->style->black_gc, event->region);
1063 x = widget->allocation.x +
1064 (widget->allocation.width - image->width) / 2;
1066 render_pixbuf(image->pixbuf, widget->window, widget->style->black_gc,
1067 x, widget->allocation.y,
1068 image->width, image->height);
1070 gdk_gc_set_clip_region(widget->style->black_gc, NULL);
1071 return FALSE;
1074 static void simple_image_class_init(gpointer gclass, gpointer data)
1076 GObjectClass *object = (GObjectClass *) gclass;
1077 GtkWidgetClass *widget = (GtkWidgetClass *) gclass;
1079 object->finalize = simple_image_finialize;
1080 widget->size_request = simple_image_size_request;
1081 widget->expose_event = simple_image_expose;
1084 static void simple_image_init(GTypeInstance *object, gpointer gclass)
1086 GTK_WIDGET_SET_FLAGS(object, GTK_NO_WINDOW);
1089 static GType simple_image_get_type(void)
1091 static GType type = 0;
1093 if (!type)
1095 static const GTypeInfo info =
1097 sizeof (SimpleImageClass),
1098 NULL, /* base_init */
1099 NULL, /* base_finalise */
1100 simple_image_class_init,
1101 NULL, /* class_finalise */
1102 NULL, /* class_data */
1103 sizeof(SimpleImage),
1104 0, /* n_preallocs */
1105 simple_image_init,
1108 type = g_type_register_static(gtk_widget_get_type(),
1109 "SimpleImage", &info, 0);
1112 return type;
1115 GtkWidget *simple_image_new(GdkPixbuf *pixbuf)
1117 SimpleImage *image;
1119 g_return_val_if_fail(pixbuf != NULL, NULL);
1121 image = g_object_new(simple_image_get_type(), NULL);
1123 image->pixbuf = pixbuf;
1124 g_object_ref(G_OBJECT(pixbuf));
1126 image->width = gdk_pixbuf_get_width(pixbuf);
1127 image->height = gdk_pixbuf_get_height(pixbuf);
1129 return GTK_WIDGET(image);
1132 /* Whether a line l1 long starting from n1 overlaps a line l2 from n2 */
1133 inline static gboolean gui_ranges_overlap(int n1, int l1, int n2, int l2)
1135 return (n1 > n2 && n1 < n2 + l2) ||
1136 (n1 + l1 > n2 && n1 + l1 < n2 + l2) ||
1137 (n1 <= n2 && n1 + l1 >= n2 + l2);
1140 static void gui_get_monitor_adjacent(int monitor, MonitorAdjacent *adj)
1142 int m;
1144 adj->left = adj->right = adj->top = adj->bottom = FALSE;
1146 for (m = 0; m < n_monitors; ++m)
1148 if (m == monitor)
1149 continue;
1150 if (gui_ranges_overlap(monitor_geom[m].y,
1151 monitor_geom[m].height,
1152 monitor_geom[monitor].y,
1153 monitor_geom[monitor].height))
1155 if (monitor_geom[m].x < monitor_geom[monitor].x)
1157 adj->left = TRUE;
1159 else if (monitor_geom[m].x > monitor_geom[monitor].x)
1161 adj->right = TRUE;
1164 if (gui_ranges_overlap(monitor_geom[m].x,
1165 monitor_geom[m].width,
1166 monitor_geom[monitor].x,
1167 monitor_geom[monitor].width))
1169 if (monitor_geom[m].y < monitor_geom[monitor].y)
1171 adj->top = TRUE;
1173 else if (monitor_geom[m].y > monitor_geom[monitor].y)
1175 adj->bottom = TRUE;
1181 static void rox_wmspec_change_state(gboolean add, GdkWindow *window,
1182 GdkAtom state1, GdkAtom state2)
1184 GdkDisplay *display = gdk_drawable_get_display(GDK_DRAWABLE(window));
1185 XEvent xev;
1187 #define _NET_WM_STATE_REMOVE 0 /* remove/unset property */
1188 #define _NET_WM_STATE_ADD 1 /* add/set property */
1189 #define _NET_WM_STATE_TOGGLE 2 /* toggle property */
1191 xev.xclient.type = ClientMessage;
1192 xev.xclient.serial = 0;
1193 xev.xclient.send_event = True;
1194 xev.xclient.window = GDK_WINDOW_XID(window);
1195 xev.xclient.message_type = gdk_x11_get_xatom_by_name_for_display(
1196 display, "_NET_WM_STATE");
1197 xev.xclient.format = 32;
1198 xev.xclient.data.l[0] = add ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
1199 xev.xclient.data.l[1] = gdk_x11_atom_to_xatom_for_display(display,
1200 state1);
1201 xev.xclient.data.l[2] = gdk_x11_atom_to_xatom_for_display(display,
1202 state2);
1203 xev.xclient.data.l[3] = 0;
1204 xev.xclient.data.l[4] = 0;
1206 XSendEvent(GDK_DISPLAY_XDISPLAY(display),
1207 GDK_WINDOW_XID(
1208 gdk_screen_get_root_window(
1209 gdk_drawable_get_screen(GDK_DRAWABLE(window)))),
1210 False,
1211 SubstructureRedirectMask | SubstructureNotifyMask,
1212 &xev);
1215 void keep_below(GdkWindow *window, gboolean setting)
1217 g_return_if_fail(GDK_IS_WINDOW(window));
1219 if (GDK_WINDOW_DESTROYED(window))
1220 return;
1222 if (gdk_window_is_visible(window))
1224 if (setting)
1226 rox_wmspec_change_state(FALSE, window,
1227 gdk_atom_intern("_NET_WM_STATE_ABOVE", FALSE),
1228 GDK_NONE);
1230 rox_wmspec_change_state(setting, window,
1231 gdk_atom_intern("_NET_WM_STATE_BELOW", FALSE),
1232 GDK_NONE);
1234 #if 0
1235 else
1237 #if GTK_CHECK_VERSION(2,4,0)
1238 gdk_synthesize_window_state(window,
1239 setting ? GDK_WINDOW_STATE_ABOVE :
1240 GDK_WINDOW_STATE_BELOW,
1241 setting ? GDK_WINDOW_STATE_BELOW : 0);
1242 #endif
1244 #endif
1247 static void
1248 size_prepared_cb (GdkPixbufLoader *loader,
1249 int width,
1250 int height,
1251 gpointer data)
1253 struct {
1254 gint width;
1255 gint height;
1256 gboolean preserve_aspect_ratio;
1257 } *info = data;
1259 g_return_if_fail (width > 0 && height > 0);
1261 if(info->preserve_aspect_ratio) {
1262 if ((double)height * (double)info->width >
1263 (double)width * (double)info->height) {
1264 width = 0.5 + (double)width * (double)info->height / (double)height;
1265 height = info->height;
1266 } else {
1267 height = 0.5 + (double)height * (double)info->width / (double)width;
1268 width = info->width;
1270 } else {
1271 width = info->width;
1272 height = info->height;
1275 gdk_pixbuf_loader_set_size (loader, width, height);
1279 * rox_pixbuf_new_from_file_at_scale:
1280 * @filename: Name of file to load.
1281 * @width: The width the image should have
1282 * @height: The height the image should have
1283 * @preserve_aspect_ratio: %TRUE to preserve the image's aspect ratio
1284 * @error: Return location for an error
1286 * Creates a new pixbuf by loading an image from a file. The file format is
1287 * detected automatically. If %NULL is returned, then @error will be set.
1288 * Possible errors are in the #GDK_PIXBUF_ERROR and #G_FILE_ERROR domains.
1289 * The image will be scaled to fit in the requested size, optionally preserving
1290 * the image's aspect ratio.
1292 * Return value: A newly-created pixbuf with a reference count of 1, or %NULL
1293 * if any of several error conditions occurred: the file could not be opened,
1294 * there was no loader for the file's format, there was not enough memory to
1295 * allocate the image buffer, or the image file contained invalid data.
1297 * Taken from GTK 2.6.
1299 GdkPixbuf *
1300 rox_pixbuf_new_from_file_at_scale (const char *filename,
1301 int width,
1302 int height,
1303 gboolean preserve_aspect_ratio,
1304 GError **error)
1307 GdkPixbufLoader *loader;
1308 GdkPixbuf *pixbuf;
1310 guchar buffer [4096];
1311 int length;
1312 FILE *f;
1313 struct {
1314 gint width;
1315 gint height;
1316 gboolean preserve_aspect_ratio;
1317 } info;
1319 g_return_val_if_fail (filename != NULL, NULL);
1320 g_return_val_if_fail (width > 0 && height > 0, NULL);
1322 f = fopen (filename, "rb");
1323 if (!f) {
1324 gchar *utf8_filename = g_filename_to_utf8 (filename, -1,
1325 NULL, NULL, NULL);
1326 g_set_error (error,
1327 G_FILE_ERROR,
1328 g_file_error_from_errno (errno),
1329 _("Failed to open file '%s': %s"),
1330 utf8_filename ? utf8_filename : "???",
1331 g_strerror (errno));
1332 g_free (utf8_filename);
1333 return NULL;
1336 loader = gdk_pixbuf_loader_new ();
1338 info.width = width;
1339 info.height = height;
1340 info.preserve_aspect_ratio = preserve_aspect_ratio;
1342 g_signal_connect (loader, "size-prepared", G_CALLBACK (size_prepared_cb), &info);
1344 while (!feof (f) && !ferror (f)) {
1345 length = fread (buffer, 1, sizeof (buffer), f);
1346 if (length > 0)
1347 if (!gdk_pixbuf_loader_write (loader, buffer, length, error)) {
1348 gdk_pixbuf_loader_close (loader, NULL);
1349 fclose (f);
1350 g_object_unref (loader);
1351 return NULL;
1355 fclose (f);
1357 if (!gdk_pixbuf_loader_close (loader, error)) {
1358 g_object_unref (loader);
1359 return NULL;
1362 pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
1364 if (!pixbuf) {
1365 gchar *utf8_filename = g_filename_to_utf8 (filename, -1,
1366 NULL, NULL, NULL);
1368 g_object_unref (loader);
1370 g_set_error (error,
1371 GDK_PIXBUF_ERROR,
1372 GDK_PIXBUF_ERROR_FAILED,
1373 _("Failed to load image '%s': reason not known, probably a corrupt image file"),
1374 utf8_filename ? utf8_filename : "???");
1375 g_free (utf8_filename);
1376 return NULL;
1379 g_object_ref (pixbuf);
1381 g_object_unref (loader);
1383 return pixbuf;