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