1 /*
2  * * Copyright (C) 2006-2011 Anders Brander <anders@brander.dk>,
3  * * Anders Kvist <akv@lnxbx.dk> and Klaus Post <klauspost@gmail.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  */
19 
20 #include <glib.h>
21 #include <glib/gstdio.h>
22 #include <gtk/gtk.h>
23 #include <config.h>
24 #ifndef WIN32
25 #include <gconf/gconf-client.h>
26 #endif
27 #include "application.h"
28 #include "conf_interface.h"
29 #include "gtk-interface.h"
30 #include "filename.h"
31 #include "gtk-helper.h"
32 #include "rs-preview-widget.h"
33 #include <gettext.h>
34 #include <lcms.h>
35 
36 struct _RS_CONFBOX
37 {
38 	GtkWidget *widget;
39 	GtkListStore *model;
40 	const gchar *conf_key;
41 	gpointer user_data;
42 	void (*callback)(gpointer active, gpointer user_data);
43 };
44 
45 static void gui_confbox_changed(GtkComboBox *filetype_combo, gpointer callback_data);
46 static gboolean gui_confbox_deleted(GtkWidget *widget, GdkEvent *event, gpointer callback_data);
47 static gboolean gui_confbox_select_value(RS_CONFBOX *confbox, gchar *value);
48 static inline guint8 convert_color_channel (guint8 src, guint8 alpha);
49 static gboolean label_new_with_mouseover_cb(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data);
50 
51 static gboolean rs_block_keyboard = FALSE;
52 
53 enum {
54 	COMBO_CONF_ID = 0,
55 	COMBO_TEXT,
56 	COMBO_PTR,
57 	COMBO_ROWS,
58 };
59 
60 static void
gui_confbox_changed(GtkComboBox * combo,gpointer callback_data)61 gui_confbox_changed(GtkComboBox *combo, gpointer callback_data)
62 {
63 	RS_CONFBOX *confbox = (RS_CONFBOX *) callback_data;
64 	GtkTreeIter iter;
65 	GtkTreeModel *model;
66 	gchar *conf_id;
67 	gpointer ptr;
68 
69 	gtk_combo_box_get_active_iter(GTK_COMBO_BOX(confbox->widget), &iter);
70 	model = gtk_combo_box_get_model(GTK_COMBO_BOX(confbox->widget));
71 	gtk_tree_model_get(model, &iter,
72 		COMBO_CONF_ID, &conf_id,
73 		COMBO_PTR, &ptr,
74 		-1);
75 	rs_conf_set_string(confbox->conf_key, conf_id);
76 
77 	if (confbox->callback)
78 		confbox->callback(ptr, confbox->user_data);
79 	return;
80 }
81 
82 static gboolean
gui_confbox_deleted(GtkWidget * widget,GdkEvent * event,gpointer callback_data)83 gui_confbox_deleted(GtkWidget *widget, GdkEvent *event, gpointer callback_data)
84 {
85 	RS_CONFBOX *confbox = (RS_CONFBOX *) callback_data;
86 	gui_confbox_destroy(confbox);
87 
88 	return(TRUE);
89 }
90 
91 gpointer
gui_confbox_get_active(RS_CONFBOX * confbox)92 gui_confbox_get_active(RS_CONFBOX *confbox)
93 {
94 	GtkTreeIter iter;
95 	GtkTreeModel *model;
96 	gpointer ptr;
97 
98 	if(gtk_combo_box_get_active_iter(GTK_COMBO_BOX(confbox->widget), &iter))
99 	{
100 		model = gtk_combo_box_get_model(GTK_COMBO_BOX(confbox->widget));
101 		gtk_tree_model_get(model, &iter,
102 			COMBO_PTR, &ptr,
103 			-1);
104 		return(ptr);
105 	}
106 	else
107 		return(NULL);
108 }
109 
110 void
gui_confbox_add_entry(RS_CONFBOX * confbox,const gchar * conf_id,const gchar * text,gpointer * user_data)111 gui_confbox_add_entry(RS_CONFBOX *confbox, const gchar *conf_id, const gchar *text, gpointer *user_data)
112 {
113 	GtkTreeIter iter;
114 	gtk_list_store_append (confbox->model, &iter);
115 	gtk_list_store_set (confbox->model, &iter,
116 		COMBO_CONF_ID, conf_id,
117 		COMBO_TEXT, text,
118 		COMBO_PTR, user_data,
119 		-1);
120 
121 	return;
122 }
123 
124 gboolean
gui_confbox_select_value(RS_CONFBOX * confbox,gchar * value)125 gui_confbox_select_value(RS_CONFBOX *confbox, gchar *value)
126 {
127 	gchar *conf_id;
128 	GtkTreeModel *model;
129 	GtkTreePath *path;
130 	GtkTreeIter iter;
131 	gboolean found = FALSE;
132 
133 	model = gtk_combo_box_get_model(GTK_COMBO_BOX(confbox->widget));
134 	path = gtk_tree_path_new_first();
135 	gtk_tree_model_get_iter(model, &iter, path);
136 	gtk_tree_path_free(path);
137 
138 	do {
139 		gtk_tree_model_get(model, &iter, COMBO_CONF_ID, &conf_id, -1);
140 		if (g_str_equal(conf_id, value))
141 		{
142 			gtk_combo_box_set_active_iter(GTK_COMBO_BOX(confbox->widget), &iter);
143 			found = TRUE;
144 			break;
145 		}
146 	} while(gtk_tree_model_iter_next (model, &iter));
147 
148 	return found;
149 }
150 
151 void
gui_confbox_load_conf(RS_CONFBOX * confbox,gchar * default_value)152 gui_confbox_load_conf(RS_CONFBOX *confbox, gchar *default_value)
153 {
154 	gchar *value;
155 
156 	value = rs_conf_get_string(confbox->conf_key);
157 	if (value)
158 	{
159 		if (!gui_confbox_select_value(confbox, value))
160 			gui_confbox_select_value(confbox, default_value);
161 		g_free(value);
162 	}
163 	else
164 		gui_confbox_select_value(confbox, default_value);
165 	return;
166 }
167 
168 void
gui_confbox_set_callback(RS_CONFBOX * confbox,gpointer user_data,void (* callback)(gpointer active,gpointer user_data))169 gui_confbox_set_callback(RS_CONFBOX *confbox, gpointer user_data, void (*callback)(gpointer active, gpointer user_data))
170 {
171 	confbox->user_data = user_data;
172 	confbox->callback = callback;
173 	return;
174 }
175 
176 RS_CONFBOX *
gui_confbox_new(const gchar * conf_key)177 gui_confbox_new(const gchar *conf_key)
178 {
179 	RS_CONFBOX *confbox;
180 	GtkCellRenderer *renderer;
181 
182 	confbox = g_new(RS_CONFBOX, 1);
183 
184 	confbox->widget = gtk_combo_box_new();
185 	confbox->model = gtk_list_store_new(COMBO_ROWS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
186 	gtk_combo_box_set_model (GTK_COMBO_BOX(confbox->widget), GTK_TREE_MODEL (confbox->model));
187 	renderer = gtk_cell_renderer_text_new ();
188 	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (confbox->widget), renderer, TRUE);
189 	gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (confbox->widget), renderer,
190 		"text", COMBO_TEXT, NULL);
191 	confbox->conf_key = conf_key;
192 	g_signal_connect ((gpointer) confbox->widget, "changed", G_CALLBACK (gui_confbox_changed), confbox);
193 	g_signal_connect ((gpointer) confbox->widget, "delete_event", G_CALLBACK(gui_confbox_deleted), confbox);
194 	confbox->user_data = NULL;
195 	confbox->callback = NULL;
196 
197 	return(confbox);
198 }
199 
200 void
gui_confbox_destroy(RS_CONFBOX * confbox)201 gui_confbox_destroy(RS_CONFBOX *confbox)
202 {
203 	gtk_widget_destroy(confbox->widget);
204 	g_free(confbox);
205 
206 	return;
207 }
208 
209 GtkWidget *
gui_confbox_get_widget(RS_CONFBOX * confbox)210 gui_confbox_get_widget(RS_CONFBOX *confbox)
211 {
212 	return(confbox->widget);
213 }
214 
215 RS_CONFBOX *
gui_confbox_filetype_new(const gchar * conf_key)216 gui_confbox_filetype_new(const gchar *conf_key)
217 {
218 	GType *savers;
219 	guint n_savers = 0, i;
220 	RS_CONFBOX *confbox;
221 
222 	confbox = gui_confbox_new(conf_key);
223 
224 	savers = g_type_children (RS_TYPE_OUTPUT, &n_savers);
225 	for (i = 0; i < n_savers; i++)
226 	{
227 		RSOutputClass *klass;
228 		gchar *name = g_strdup(g_type_name(savers[i])); /* FIXME: Stop leaking! */
229 		GType type = g_type_from_name(name);
230 		klass = g_type_class_ref(savers[i]);
231 		gui_confbox_add_entry(confbox, name, klass->display_name, GINT_TO_POINTER(type));
232 		g_type_class_unref(klass);
233 	}
234 	g_free(savers);
235 
236 	gui_confbox_load_conf(confbox, "RSJpegfile");
237 
238 	return confbox;
239 }
240 
241 void
checkbox_set_conf(GtkToggleButton * togglebutton,gpointer user_data)242 checkbox_set_conf(GtkToggleButton *togglebutton, gpointer user_data)
243 {
244 	const gchar *path = user_data;
245 	rs_conf_set_boolean(path, togglebutton->active);
246 	return;
247 }
248 
249 GtkWidget *
checkbox_from_conf(const gchar * conf,gchar * label,gboolean default_value)250 checkbox_from_conf(const gchar *conf, gchar *label, gboolean default_value)
251 {
252 	gboolean check = default_value;
253 	GtkWidget *checkbox;
254 	rs_conf_get_boolean(conf, &check);
255 	checkbox = gtk_check_button_new_with_label(label);
256 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
257 		check);
258 	g_signal_connect ((gpointer) checkbox, "toggled",
259 		G_CALLBACK (checkbox_set_conf), (gpointer) conf);
260 	return(checkbox);
261 }
262 
gui_tooltip_no_window(GtkWidget * widget,gchar * tip_tip,gchar * tip_private)263 GtkWidget *gui_tooltip_no_window(GtkWidget *widget, gchar *tip_tip, gchar *tip_private)
264 {
265 	GtkWidget *e;
266 	GtkTooltips *tip;
267 
268 	tip = gtk_tooltips_new();
269 	e = gtk_event_box_new();
270 	gtk_tooltips_set_tip(tip, e, tip_tip, tip_private);
271 	gtk_widget_show(widget);
272 	gtk_container_add(GTK_CONTAINER(e), widget);
273 
274 	return e;
275 }
276 
gui_tooltip_window(GtkWidget * widget,gchar * tip_tip,gchar * tip_private)277 void gui_tooltip_window(GtkWidget *widget, gchar *tip_tip, gchar *tip_private)
278 {
279 	GtkTooltips *tip;
280 
281 	tip = gtk_tooltips_new();
282 	gtk_tooltips_set_tip(tip, widget, tip_tip, tip_private);
283 	gtk_widget_show(widget);
284 
285 	return;
286 }
287 
288 void
gui_batch_directory_entry_changed(GtkEntry * entry,gpointer user_data)289 gui_batch_directory_entry_changed(GtkEntry *entry, gpointer user_data)
290 {
291 	rs_conf_set_string(CONF_BATCH_DIRECTORY, gtk_entry_get_text(entry));
292 	return;
293 }
294 
295 void
gui_batch_filename_entry_changed(GtkComboBox * combobox,gpointer user_data)296 gui_batch_filename_entry_changed(GtkComboBox *combobox, gpointer user_data)
297 {
298 	rs_conf_set_string(CONF_BATCH_FILENAME, gtk_combo_box_get_active_text(combobox));
299 	return;
300 }
301 
302 void
gui_batch_filetype_combobox_changed(gpointer active,gpointer user_data)303 gui_batch_filetype_combobox_changed(gpointer active, gpointer user_data)
304 {
305 	return;
306 }
307 
gui_set_block_keyboard(gboolean block_keyboard)308 void gui_set_block_keyboard(gboolean block_keyboard)
309 {
310 	rs_block_keyboard = block_keyboard;
311 }
312 
313 static GdkEventKey*
replace_key_events(const GdkEventKey * in)314 replace_key_events(const GdkEventKey *in)
315 {
316 	GdkEventKey *out = g_memdup(in, sizeof(GdkEventKey));
317 
318 	static guint zero_keyval = 0;
319 	static guint one_keyval = 0;
320 	static guint zero_hardware = 0;
321 	static guint one_hardware = 0;
322 	if (!one_keyval)
323 	{
324 		GdkKeymapKey *keys;
325 		gint n_keys;
326 		zero_keyval = gdk_keyval_from_name("0");
327 		if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(), zero_keyval, &keys, &n_keys))
328 		{
329 			zero_hardware = keys[0].keycode;
330 			g_free(keys);
331 		}
332 		one_keyval = gdk_keyval_from_name("1");
333 		if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(), one_keyval, &keys, &n_keys))
334 		{
335 			one_hardware = keys[0].keycode;
336 			g_free(keys);
337 		}
338 	}
339 
340 	/* Replace 'Num-*' with '*' */
341 	if (in->keyval == 65450)
342 	{
343 		/* TODO: Find some way to figure that out, since state is often different */
344 	}
345 
346 	/* Replace Numpad 1,2,3  with ordinary numbers */
347 	if (in->keyval >= 65457 && in->keyval <= 65459)
348 	{
349 		out->keyval = one_keyval+(in->keyval-65457);
350 		out->hardware_keycode = one_hardware+(in->keyval-65457);
351 	}
352 
353 	/* Replace Numpad 0  with ordinary numbers */
354 	if (in->keyval == 65456)
355 	{
356 		out->keyval = zero_keyval;
357 		out->hardware_keycode = zero_hardware;
358 	}
359 	return out;
360 }
361 
362 /* copied verbatim from Gimp: app/widgets/gimpdock.c */
363 gboolean
window_key_press_event(GtkWidget * widget,GdkEventKey * event)364 window_key_press_event (GtkWidget   *widget,
365                            GdkEventKey *event)
366 {
367   GtkWindow *window  = GTK_WINDOW (widget);
368   GtkWidget *focus   = gtk_window_get_focus (window);
369   gboolean   handled = FALSE;
370 
371   /* we're overriding the GtkWindow implementation here to give
372    * the focus widget precedence over unmodified accelerators
373    * before the accelerator activation scheme.
374    */
375 
376   /* Eat the event, if we are told so */
377   if (! handled && event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK))
378     handled = gtk_window_activate_key (window, event);
379 
380   /* control/alt accelerators get all key events first */
381   if (! handled && rs_block_keyboard)
382     handled = TRUE;
383 
384   /* invoke text widgets  */
385   if (! handled && G_UNLIKELY (GTK_IS_EDITABLE (focus) || GTK_IS_TEXT_VIEW (focus)))
386     handled = gtk_window_propagate_key_event (window, event);
387 
388 	GdkEventKey *new_event = replace_key_events(event);
389 
390   /* invoke focus widget handlers */
391   if (! handled)
392     handled = gtk_window_propagate_key_event (window, new_event);
393 
394   /* invoke non-(control/alt) accelerators */
395   if (! handled && ! (new_event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)))
396     handled = gtk_window_activate_key (window, new_event);
397 
398   /* chain up, bypassing gtk_window_key_press(), to invoke binding set */
399   if (! handled)
400     handled = GTK_WIDGET_CLASS (g_type_class_peek (g_type_parent (GTK_TYPE_WINDOW)))->key_press_event (widget, new_event);
401 
402 	g_free(new_event);
403   return handled;
404 }
405 
406 /**
407  * Function to help gtk_menu_popup(), positiones the popup menu below a widget
408  * Should be used like this: gtk_menu_popup(GTK_MENU(menu), NULL, NULL, pos_menu_below_widget, widget, 0, GDK_CURRENT_TIME);
409  */
410 void
pos_menu_below_widget(GtkMenu * menu,gint * x,gint * y,gboolean * push_in,gpointer user_data)411 pos_menu_below_widget (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer user_data)
412 {
413 	GtkWidget *widget = GTK_WIDGET (user_data);
414     gint origin_x, origin_y;
415 
416 	gdk_window_get_origin (widget->window, &origin_x, &origin_y);
417 
418 	*x = origin_x + widget->allocation.x;
419 	*y = origin_y + widget->allocation.y + widget->allocation.height;
420 	*push_in = TRUE;
421 	return;
422 }
423 
424 /**
425  * Put a GtkFrame around a widget
426  * @param widget The widget to frame
427  * @param title Title for the frame
428  * @param shadowtype A GtkShadowType
429  * @return A new GtkFrame
430  */
431 GtkWidget *
gui_framed(GtkWidget * widget,const gchar * title,GtkShadowType shadowtype)432 gui_framed(GtkWidget *widget, const gchar *title, GtkShadowType shadowtype)
433 {
434 	GtkWidget *frame;
435 
436 	frame = gtk_frame_new(title);
437 	gtk_frame_set_shadow_type(GTK_FRAME(frame), shadowtype);
438 	gtk_container_add (GTK_CONTAINER (frame), widget);
439 
440 	return(frame);
441 }
442 
443 GtkWidget *
gui_aligned(GtkWidget * widget,const gfloat xalign,const gfloat yalign,const gfloat xscale,const gfloat yscale)444 gui_aligned(GtkWidget *widget, const gfloat xalign, const gfloat yalign, const gfloat xscale, const gfloat yscale)
445 {
446 	GtkWidget *alignment;
447 
448 	g_assert(GTK_IS_WIDGET(widget));
449 
450 	alignment = gtk_alignment_new(xalign, yalign, xscale, yscale);
451 	gtk_container_add (GTK_CONTAINER (alignment), widget);
452 
453 	return alignment;
454 }
455 
456 /**
457  * Build and show a popup-menu
458  * @param widget A widget to pop up below or NULL to pop upat mouse pointer
459  * @param user_data Pointer to pass to callback
460  * @param ... Pairs of gchar labels and callbaks, terminated by -1
461  * @return The newly created menu
462  */
463 GtkWidget *
gui_menu_popup(GtkWidget * widget,gpointer user_data,...)464 gui_menu_popup(GtkWidget *widget, gpointer user_data, ...)
465 {
466 	va_list ap;
467 	GCallback cb;
468 	gchar *label;
469 	GtkWidget *item, *menu = gtk_menu_new();
470 	gint n = 0;
471 
472 	va_start(ap, user_data);
473 
474 	/* Loop through arguments, abort on -1 */
475 	while (1)
476 	{
477 		label = va_arg(ap, gchar *);
478 		if (GPOINTER_TO_INT(label) == -1) break;
479 		cb = va_arg(ap, GCallback);
480 		if (GPOINTER_TO_INT(cb) == -1) break;
481 
482 		item = gtk_menu_item_new_with_label (label);
483 		gtk_widget_show (item);
484 		gtk_menu_attach (GTK_MENU (menu), item, 0, 1, n, n+1); n++;
485 		g_signal_connect (item, "activate", cb, user_data);
486 	}
487 
488 	va_end(ap);
489 
490 	if (widget)
491 		gtk_menu_popup(GTK_MENU(menu), NULL, NULL, pos_menu_below_widget, widget, 0, GDK_CURRENT_TIME);
492 	else
493 		gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0, GDK_CURRENT_TIME);
494 
495 	return (menu);
496 }
497 
498 void
gui_select_theme(RS_THEME theme)499 gui_select_theme(RS_THEME theme)
500 {
501 	static RS_THEME current_theme = STANDARD_GTK_THEME;
502 	static gchar **default_rc_files = NULL;
503 	static GStaticMutex lock = G_STATIC_MUTEX_INIT;
504 	GtkSettings *settings;
505 
506 	g_static_mutex_lock(&lock);
507 
508 	/* Copy default RC files */
509 	if (!default_rc_files)
510 	{
511 		gchar **def;
512 		gint i;
513 		def = gtk_rc_get_default_files();
514 		for(i=0;def[i];i++); /* Count */
515 		default_rc_files = g_new0(gchar *, i+1);
516 		for(i=0;def[i];i++) /* Copy */
517 			default_rc_files[i] = g_strdup(def[i]);
518 	}
519 
520 	/* Change theme if needed */
521 	if (theme != current_theme)
522 	{
523 		settings = gtk_settings_get_default();
524 		switch (theme)
525 		{
526 			case STANDARD_GTK_THEME:
527 				gtk_rc_set_default_files(default_rc_files);
528 				break;
529 			case RAWSTUDIO_THEME:
530 				gtk_rc_add_default_file(PACKAGE_DATA_DIR G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "rawstudio.gtkrc");
531 				break;
532 		}
533 		current_theme = theme;
534 		/* Reread everything */
535 		if (settings)
536 			gtk_rc_reparse_all_for_settings(settings, TRUE);
537 	}
538 
539 	g_static_mutex_unlock(&lock);
540 }
541 
542 /**
543  * http://davyd.ucc.asn.au/projects/gtk-utils/cairo_convert_to_pixbuf.html
544  */
545 static inline guint8
convert_color_channel(guint8 src,guint8 alpha)546 convert_color_channel (guint8 src, guint8 alpha)
547 {
548 	return alpha ? ((src << 8) - src) / alpha : 0;
549 }
550 
551 /**
552  * cairo_convert_to_pixbuf:
553  * Converts from a Cairo image surface to a GdkPixbuf. Why does GTK+ not
554  * implement this?
555  * http://davyd.ucc.asn.au/projects/gtk-utils/cairo_convert_to_pixbuf.html
556  */
557 GdkPixbuf *
cairo_convert_to_pixbuf(cairo_surface_t * surface)558 cairo_convert_to_pixbuf (cairo_surface_t *surface)
559 {
560 	GdkPixbuf *pixbuf;
561 	int width, height;
562 	int srcstride, dststride;
563 	guchar *srcpixels, *dstpixels;
564 	guchar *srcpixel, *dstpixel;
565 	int n_channels;
566 	int x, y;
567 
568 	switch (cairo_image_surface_get_format (surface))
569 	{
570 		case CAIRO_FORMAT_ARGB32:
571 		case CAIRO_FORMAT_RGB24:
572 			break;
573 
574 		default:
575 			g_critical ("This Cairo surface format not supported");
576 			return NULL;
577 			break;
578 	}
579 
580 	width = cairo_image_surface_get_width (surface);
581 	height = cairo_image_surface_get_height (surface);
582 	srcstride = cairo_image_surface_get_stride (surface);
583 	srcpixels = cairo_image_surface_get_data (surface);
584 
585 	pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, (cairo_image_surface_get_format (surface) == CAIRO_FORMAT_ARGB32), 8,
586 			width, height);
587 	dststride = gdk_pixbuf_get_rowstride (pixbuf);
588 	dstpixels = gdk_pixbuf_get_pixels (pixbuf);
589 	n_channels = gdk_pixbuf_get_n_channels (pixbuf);
590 
591 	for (y = 0; y < height; y++)
592 	{
593 		for (x = 0; x < width; x++)
594 		{
595 			srcpixel = srcpixels + y * srcstride + x * 4;
596 			dstpixel = dstpixels + y * dststride + x * n_channels;
597 
598 			dstpixel[0] = convert_color_channel (srcpixel[2],
599 							     srcpixel[3]);
600 			dstpixel[1] = convert_color_channel (srcpixel[1],
601 							     srcpixel[3]);
602 			dstpixel[2] = convert_color_channel (srcpixel[0],
603 							     srcpixel[3]);
604 			dstpixel[3] = srcpixel[3];
605 		}
606 	}
607 
608 	return pixbuf;
609 }
610 
611 /**
612  * Creates a new GtkButton widget.
613  * @param stock_id A stock id registered with GTK+
614  * @param label The text to show besides the icon
615  * @return a new GtkButton
616  */
617 GtkWidget *
gui_button_new_from_stock_with_label(const gchar * stock_id,const gchar * label)618 gui_button_new_from_stock_with_label(const gchar *stock_id, const gchar *label)
619 {
620 	GtkWidget *button;
621 	GtkWidget *stock;
622 
623 	g_assert(stock_id);
624 	g_assert(label);
625 
626 	stock = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_BUTTON);
627 	button = gtk_button_new_with_label(label);
628 	gtk_button_set_image(GTK_BUTTON(button), stock);
629 
630 	return button;
631 }
632 
633 static gboolean
label_new_with_mouseover_cb(GtkWidget * widget,GdkEventCrossing * event,gpointer user_data)634 label_new_with_mouseover_cb(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
635 {
636 	/* Do some mangling to get the GtkLabel */
637 	GtkLabel *label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(widget)));
638 	const gchar *key;
639 
640 	/* Get the relevant key - if any */
641 	switch (event->type)
642 	{
643 		case GDK_ENTER_NOTIFY:
644 			key = "rs-mouseover-enter";
645 			gtk_widget_set_state(widget, GTK_STATE_PRELIGHT);
646 			break;
647 		case GDK_LEAVE_NOTIFY:
648 			key = "rs-mouseover-leave";
649 			gtk_widget_set_state(widget, GTK_STATE_NORMAL);
650 			break;
651 		default:
652 			key = NULL;
653 			break;
654 	}
655 
656 	/* Set new text */
657 	if (key)
658 		gtk_label_set_text(label, g_object_get_data(G_OBJECT(label), key));
659 
660 	/* Propagate this event, otherwise tooltip may not be shown */
661 	return FALSE;
662 }
663 
664 /**
665  * This will create a new GtkLabel that can alternate text when the pointer is
666  * hovering above it.
667  * @param normal_text The text to display when pointer is not hovering above
668  * @param hover_text The text to display when pointer is hovering above the label
669  * @return A new GtkLabel
670  */
671 GtkWidget *
gui_label_new_with_mouseover(const gchar * normal_text,const gchar * hover_text)672 gui_label_new_with_mouseover(const gchar *normal_text, const gchar *hover_text)
673 {
674 	GtkWidget *eventbox;
675 	GtkWidget *label;
676 	gint max_width;
677 
678 	g_assert(normal_text != NULL);
679 	g_assert(hover_text != NULL);
680 
681 	label = gtk_label_new(normal_text);
682 
683 	/* Align right */
684 	gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
685 
686 	/* Calculate the maximum amount of characters displayed to avoid flickering */
687 	max_width = MAX(g_utf8_strlen(normal_text, -1), g_utf8_strlen(hover_text, -1));
688 	gtk_label_set_width_chars(GTK_LABEL(label), max_width);
689 
690 	/* Keep these in memory - AND free them with the GtkLabel */
691 	g_object_set_data_full(G_OBJECT(label), "rs-mouseover-leave", g_strdup(normal_text), g_free);
692 	g_object_set_data_full(G_OBJECT(label), "rs-mouseover-enter", g_strdup(hover_text), g_free);
693 
694 	/* Use an event box, since GtkLabel has no window of its own */
695 	eventbox = gtk_event_box_new();
696 
697 	/* Listen for enter/leave events */
698 	gtk_widget_set_events(eventbox, GDK_ENTER_NOTIFY_MASK|GDK_LEAVE_NOTIFY_MASK);
699 	g_signal_connect(eventbox, "enter-notify-event", G_CALLBACK(label_new_with_mouseover_cb), NULL);
700 	g_signal_connect(eventbox, "leave-notify-event", G_CALLBACK(label_new_with_mouseover_cb), NULL);
701 
702 	gtk_container_add(GTK_CONTAINER(eventbox), label);
703 
704 	return eventbox;
705 }
706 
707 void
gui_box_toggle_callback(GtkExpander * expander,gchar * key)708 gui_box_toggle_callback(GtkExpander *expander, gchar *key)
709 {
710 #ifndef WIN32
711 	GConfClient *client = gconf_client_get_default();
712 	gboolean expanded = gtk_expander_get_expanded(expander);
713 
714 	/* Save state to gconf */
715 	gconf_client_set_bool(client, key, expanded, NULL);
716 #endif
717 }
718 #ifndef WIN32
719 void
gui_box_notify(GConfClient * client,guint cnxn_id,GConfEntry * entry,gpointer user_data)720 gui_box_notify(GConfClient *client, guint cnxn_id, GConfEntry *entry, gpointer user_data)
721 {
722 	GtkExpander *expander = GTK_EXPANDER(user_data);
723 
724 	if (entry->value)
725 	{
726 		gboolean expanded = gconf_value_get_bool(entry->value);
727 		gtk_expander_set_expanded(expander, expanded);
728 	}
729 }
730 #endif
731 
732 GtkWidget *
gui_box(const gchar * title,GtkWidget * in,gchar * key,gboolean default_expanded)733 gui_box(const gchar *title, GtkWidget *in, gchar *key, gboolean default_expanded)
734 {
735 	GtkWidget *expander, *label;
736 	gboolean expanded;
737 
738 	rs_conf_get_boolean_with_default(key, &expanded, default_expanded);
739 
740 	expander = gtk_expander_new(NULL);
741 
742 	if (key)
743 	{
744 #ifndef WIN32
745 		GConfClient *client = gconf_client_get_default();
746 		gchar *name = g_build_filename("/apps", PACKAGE, key, NULL);
747 		g_signal_connect_after(expander, "activate", G_CALLBACK(gui_box_toggle_callback), name);
748 		gconf_client_notify_add(client, name, gui_box_notify, expander, NULL, NULL);
749 #endif
750 	}
751 	gtk_expander_set_expanded(GTK_EXPANDER(expander), expanded);
752 
753 	label = gtk_label_new(title);
754 	gtk_expander_set_label_widget (GTK_EXPANDER (expander), label);
755 	gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
756 	gtk_container_add (GTK_CONTAINER (expander), in);
757 	return expander;
758 }
759