libyui-gtk  2.42.2
 All Classes
ygtkwizard.c
1 /********************************************************************
2  * YaST2-GTK - http://en.opensuse.org/YaST2-GTK *
3  ********************************************************************/
4 
5 /* YGtkWizard widget */
6 // check the header file for information about this widget
7 
8 /*
9  Textdomain "gtk"
10  */
11 
12 #include <yui/Libyui_config.h>
13 #include "ygtkwizard.h"
14 #include <atk/atk.h>
15 #include <gtk/gtk.h>
16 #include <gdk/gdkkeysyms.h>
17 #include <string.h>
18 #include "ygtkhtmlwrap.h"
19 #include "ygtksteps.h"
20 #include "ygtklinklabel.h"
21 #define YGI18N_C
22 #include "YGi18n.h"
23 
24 // YGUtils bridge
25 extern char *ygutils_mapKBAccel (const char *src);
26 extern void ygutils_setWidgetFont (GtkWidget *widget, PangoStyle style,
27  PangoWeight weight, double scale);
28 extern void ygutils_setPaneRelPosition (GtkWidget *paned, gdouble rel);
29 extern const char *ygutils_mapStockIcon (const char *label);
30 extern const char *ygutils_setStockIcon (GtkWidget *button, const char *label,
31  const char *fallbackIcon);
32 extern GdkPixbuf *ygutils_setOpacity (const GdkPixbuf *src, int opacity, gboolean alpha);
33 extern void ygdialog_setTitle (const gchar *title, gboolean sticky);
34 extern gchar *ygutils_headerize_help (const char *help_text, gboolean *cut);
35 
36 //** YGtkHelpDialog
37 
38 G_DEFINE_TYPE (YGtkHelpDialog, ygtk_help_dialog, GTK_TYPE_WINDOW)
39 
40 // callbacks
41 static void ygtk_help_dialog_find_next (YGtkHelpDialog *dialog)
42 {
43  const gchar *text = gtk_entry_get_text (GTK_ENTRY (dialog->search_entry));
44  ygtk_html_wrap_search_next (dialog->help_text, text);
45 }
46 
47 static void search_entry_changed_cb (GtkEditable *editable, YGtkHelpDialog *dialog)
48 {
49  static GdkColor red = { 0, 255 << 8, 102 << 8, 102 << 8 };
50  static GdkColor white = { 0, 0xffff, 0xffff, 0xffff };
51  static GdkColor yellow = { 0, 0xf7f7, 0xf7f7, 0xbdbd };
52  static GdkColor black = { 0, 0, 0, 0 };
53 
54  GtkWidget *widget = GTK_WIDGET (editable);
55  GtkEntry *entry = GTK_ENTRY (editable);
56  const gchar *text = gtk_entry_get_text (entry);
57  gboolean found = ygtk_html_wrap_search (dialog->help_text, text);
58 
59  if (found && *text) {
60  gtk_widget_modify_base (widget, GTK_STATE_NORMAL, &yellow);
61  gtk_widget_modify_text (widget, GTK_STATE_NORMAL, &black);
62  }
63  else if (found) { // revert
64  gtk_widget_modify_base (widget, GTK_STATE_NORMAL, NULL);
65  gtk_widget_modify_text (widget, GTK_STATE_NORMAL, NULL);
66  }
67  else {
68  gtk_widget_modify_base (widget, GTK_STATE_NORMAL, &red);
69  gtk_widget_modify_text (widget, GTK_STATE_NORMAL, &white);
70  gtk_widget_error_bell (widget);
71  }
72 
73  gboolean showIcon = *text; // show clear icon if text
74  if (showIcon != gtk_entry_get_icon_activatable (entry, GTK_ENTRY_ICON_SECONDARY)) {
75  gtk_entry_set_icon_activatable (entry,
76  GTK_ENTRY_ICON_SECONDARY, showIcon);
77  gtk_entry_set_icon_from_stock (entry,
78  GTK_ENTRY_ICON_SECONDARY, showIcon ? GTK_STOCK_CLEAR : NULL);
79  if (showIcon)
80  gtk_entry_set_icon_tooltip_text (entry,
81  GTK_ENTRY_ICON_SECONDARY, _("Clear"));
82  }
83 }
84 
85 static void search_entry_icon_press_cb (GtkEntry *entry, GtkEntryIconPosition pos,
86  GdkEvent *event, YGtkHelpDialog *dialog)
87 {
88  if (pos == GTK_ENTRY_ICON_PRIMARY)
89  gtk_editable_select_region (GTK_EDITABLE (entry), 0, -1);
90  else
91  gtk_entry_set_text (entry, "");
92  gtk_widget_grab_focus (GTK_WIDGET (entry));
93 }
94 
95 static void search_entry_activated_cb (GtkEntry *entry, YGtkHelpDialog *dialog)
96 { ygtk_help_dialog_find_next (dialog); }
97 
98 static void close_button_clicked_cb (GtkButton *button, YGtkHelpDialog *dialog)
99 { gtk_widget_hide (GTK_WIDGET (dialog)); }
100 
101 static void ygtk_help_dialog_init (YGtkHelpDialog *dialog)
102 {
103  gtk_container_set_border_width (GTK_CONTAINER (dialog), 6);
104  gtk_window_set_type_hint (GTK_WINDOW (dialog), GDK_WINDOW_TYPE_HINT_DIALOG);
105  gtk_window_set_title (GTK_WINDOW (dialog), _("Help"));
106  GdkPixbuf *icon = gtk_widget_render_icon (
107  GTK_WIDGET (dialog), GTK_STOCK_HELP, GTK_ICON_SIZE_MENU, NULL);
108  gtk_window_set_icon (GTK_WINDOW (dialog), icon);
109  g_object_unref (G_OBJECT (icon));
110  gtk_window_set_default_size (GTK_WINDOW (dialog), 500, 450);
111 
112  // help text
113  dialog->help_box = gtk_scrolled_window_new (NULL, NULL);
114  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (dialog->help_box),
115  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
116  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (dialog->help_box),
117  GTK_SHADOW_IN);
118  dialog->help_text = ygtk_html_wrap_new();
119  gtk_container_add (GTK_CONTAINER (dialog->help_box), dialog->help_text);
120 
121 #if 0 // show a nice background image
122  GtkIconTheme *theme = gtk_icon_theme_get_default();
123  GtkIconInfo *info = gtk_icon_theme_lookup_icon (theme, HELP_IMG_BG, 192, 0);
124  if (info) {
125  GdkPixbuf *pixbuf = gtk_icon_info_load_icon (info, NULL);
126  if (pixbuf) {
127  const gchar *filename = gtk_icon_info_get_filename (info);
128  GdkPixbuf *transparent = ygutils_setOpacity (pixbuf, 60, FALSE);
129  ygtk_html_wrap_set_background (dialog->help_text, transparent, filename);
130  g_object_unref (pixbuf);
131  g_object_unref (transparent);
132  }
133  gtk_icon_info_free (info);
134  }
135 #endif
136 
137  // bottom part (search entry + close button)
138  dialog->search_entry = gtk_entry_new();
139  gtk_widget_set_size_request (dialog->search_entry, 140, -1);
140  gtk_entry_set_icon_from_stock (GTK_ENTRY (dialog->search_entry),
141  GTK_ENTRY_ICON_PRIMARY, GTK_STOCK_FIND);
142  gtk_entry_set_icon_activatable (GTK_ENTRY (dialog->search_entry),
143  GTK_ENTRY_ICON_PRIMARY, TRUE);
144  g_signal_connect (G_OBJECT (dialog->search_entry), "icon-press",
145  G_CALLBACK (search_entry_icon_press_cb), dialog);
146  g_signal_connect (G_OBJECT (dialog->search_entry), "changed",
147  G_CALLBACK (search_entry_changed_cb), dialog);
148  g_signal_connect (G_OBJECT (dialog->search_entry), "activate",
149  G_CALLBACK (search_entry_activated_cb), dialog);
150 
151  dialog->close_button = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
152  gtk_widget_set_can_default(dialog->close_button, TRUE);
153 
154  GtkWidget *close_box = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
155  gtk_container_add (GTK_CONTAINER (close_box), dialog->close_button);
156 
157  char *label_str = ygutils_mapKBAccel (_("&Find:"));
158  GtkWidget *bottom_box, *label = gtk_label_new_with_mnemonic (label_str);
159  g_free (label_str);
160  gtk_misc_set_alignment (GTK_MISC (label), 0, .5);
161  gtk_label_set_mnemonic_widget (GTK_LABEL (label), dialog->search_entry);
162 
163  bottom_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
164  gtk_box_set_homogeneous (GTK_BOX (bottom_box), FALSE);
165 
166  gtk_box_pack_start (GTK_BOX (bottom_box), label, FALSE, FALSE, 0);
167  gtk_box_pack_start (GTK_BOX (bottom_box), dialog->search_entry, FALSE, FALSE, 0);
168  gtk_box_pack_end (GTK_BOX (bottom_box), close_box, FALSE, FALSE, 0);
169 
170 #ifdef SET_HELP_HISTORY
171  dialog->history_combo = gtk_combo_box_new_text();
172  GList *cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (dialog->history_combo));
173  g_object_set (G_OBJECT (cells->data), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
174  g_list_free (cells);
175 #endif
176 
177  // glue it
178  dialog->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
179  gtk_box_set_homogeneous (GTK_BOX (dialog->vbox), FALSE);
180 
181 #ifdef SET_HELP_HISTORY
182  GtkWidget *hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
183  gtk_box_set_homogeneous (GTK_BOX (bottom_box), FALSE);
184 
185  gtk_box_pack_start (GTK_BOX (hbox), gtk_image_new_from_stock (GTK_STOCK_HELP, GTK_ICON_SIZE_BUTTON), FALSE, TRUE, 0);
186  gtk_box_pack_start (GTK_BOX (hbox), dialog->history_combo, TRUE, TRUE, 0);
187  gtk_box_pack_start (GTK_BOX (dialog->vbox), hbox, FALSE, TRUE, 0);
188 #endif
189  gtk_box_pack_start (GTK_BOX (dialog->vbox), dialog->help_box, TRUE, TRUE, 0);
190  gtk_box_pack_start (GTK_BOX (dialog->vbox), bottom_box, FALSE, TRUE, 0);
191  gtk_container_add (GTK_CONTAINER (dialog), dialog->vbox);
192  gtk_widget_show_all (dialog->vbox);
193 
194  g_signal_connect (G_OBJECT (dialog->close_button), "clicked",
195  G_CALLBACK (close_button_clicked_cb), dialog);
196  g_signal_connect (G_OBJECT (dialog), "delete-event",
197  G_CALLBACK (gtk_widget_hide_on_delete), NULL);
198 }
199 
200 static void ygtk_help_dialog_realize (GtkWidget *widget)
201 {
202  GTK_WIDGET_CLASS (ygtk_help_dialog_parent_class)->realize (widget);
203  YGtkHelpDialog *dialog = YGTK_HELP_DIALOG (widget);
204 
205  // set close as default widget
206  gtk_widget_grab_default (dialog->close_button);
207 }
208 
209 static void ygtk_help_dialog_close (YGtkHelpDialog *dialog)
210 { gtk_widget_hide (GTK_WIDGET (dialog)); }
211 
212 GtkWidget *ygtk_help_dialog_new (GtkWindow *parent)
213 {
214  GtkWidget *dialog = g_object_new (YGTK_TYPE_HELP_DIALOG, NULL);
215  if (parent)
216  gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
217  return dialog;
218 }
219 
220 void ygtk_help_dialog_set_text (YGtkHelpDialog *dialog, const gchar *text)
221 {
222  gtk_editable_delete_text (GTK_EDITABLE (dialog->search_entry), 0, -1);
223  ygtk_html_wrap_set_text (dialog->help_text, text, FALSE);
224  ygtk_html_wrap_scroll (dialog->help_text, TRUE);
225 }
226 
227 static void ygtk_help_dialog_class_init (YGtkHelpDialogClass *klass)
228 {
229  klass->find_next = ygtk_help_dialog_find_next;
230  klass->close = ygtk_help_dialog_close;
231 
232  GtkWidgetClass* widget_class = GTK_WIDGET_CLASS (klass);
233  widget_class->realize = ygtk_help_dialog_realize;
234 
235  // key bindings (F3 for next word, Esc to close the window)
236  g_signal_new ("find_next", G_TYPE_FROM_CLASS (G_OBJECT_CLASS (klass)),
237  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
238  G_STRUCT_OFFSET (YGtkHelpDialogClass, find_next),
239  NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
240  g_signal_new ("close", G_TYPE_FROM_CLASS (G_OBJECT_CLASS (klass)),
241  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
242  G_STRUCT_OFFSET (YGtkHelpDialogClass, close),
243  NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
244 
245  GtkBindingSet *binding_set = gtk_binding_set_by_class (klass);
246  gtk_binding_entry_add_signal (binding_set, GDK_KEY_F3, 0, "find_next", 0);
247  gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0, "close", 0);
248 }
249 
250 #ifdef SET_HELP_HISTORY
251 typedef struct TitleTextPair {
252  gchar *title, *text;
253 } TitleTextPair;
254 #endif
255 
256 YGtkHelpText *ygtk_help_text_new (void)
257 { return g_new0 (YGtkHelpText, 1); }
258 
259 void ygtk_help_text_destroy (YGtkHelpText *help)
260 {
261 #ifdef SET_HELP_HISTORY
262  if (help->history) {
263  GList *i;
264  for (i = help->history; i; i = i->next) {
265  TitleTextPair *pair = i->data;
266  g_free (pair->title);
267  g_free (pair->text);
268  g_free (pair);
269  }
270  g_list_free (help->history);
271  help->history = 0;
272  }
273 #else
274  if (help->text) {
275  g_free (help->text);
276  help->text = 0;
277  }
278 #endif
279  if (help->dialog) {
280  gtk_widget_destroy (help->dialog);
281  help->dialog = 0;
282  }
283 }
284 
285 #ifdef SET_HELP_HISTORY
286 static gint compare_links (gconstpointer pa, gconstpointer pb)
287 {
288  const TitleTextPair *a = pa, *b = pb;
289  return strcmp (a->text, b->text);
290 }
291 #endif
292 
293 void ygtk_help_text_set (YGtkHelpText *help, const gchar *title, const gchar *text)
294 {
295  if (!*text) return;
296 #ifdef SET_HELP_HISTORY
297  TitleTextPair *pair = g_new (TitleTextPair, 1);
298  if (title)
299  pair->title = g_strdup (title);
300  else {
301  gboolean in_tag = FALSE;
302  GString *str = g_string_new ("");
303  const gchar *i;
304  for (i = text; *i; i++) {
305  if (*i == '<')
306  in_tag = TRUE;
307  else if (*i == '>')
308  in_tag = FALSE;
309  else if (*i == '\n') {
310  if (str->len)
311  break;
312  }
313  else if (!in_tag)
314  str = g_string_append_c (str, *i);
315  }
316  pair->title = g_string_free (str, FALSE);
317  }
318  pair->text = g_strdup (text);
319 
320  GList *i = g_list_find_custom (help->history, pair, (GCompareFunc) compare_links);
321  if (i) {
322  TitleTextPair *p = i->data;
323  g_free (p->text);
324  g_free (p->title);
325  g_free (p);
326  help->history = g_list_delete_link (help->history, i);
327  }
328  help->history = g_list_prepend (help->history, pair);
329 #else
330  if (help->text)
331  g_free (help->text);
332  help->text = g_strdup (text);
333 #endif
334  if (help->dialog)
335  ygtk_help_text_sync (help, NULL);
336 }
337 
338 const gchar *ygtk_help_text_get (YGtkHelpText *help, gint n)
339 {
340 #ifdef SET_HELP_HISTORY
341  TitleTextPair *pair = g_list_nth_data (help->history, n);
342  if (pair)
343  return pair->text;
344  return NULL;
345 #else
346  return help->text;
347 #endif
348 }
349 
350 #ifdef SET_HELP_HISTORY
351 static void history_changed_cb (GtkComboBox *combo, YGtkHelpText *text)
352 {
353  YGtkHelpDialog *dialog = YGTK_HELP_DIALOG (text->dialog);
354  gint active = gtk_combo_box_get_active (GTK_COMBO_BOX (dialog->history_combo));
355  ygtk_help_dialog_set_text (dialog, ygtk_help_text_get (text, active));
356 }
357 #endif
358 
359 void ygtk_help_text_sync (YGtkHelpText *help, GtkWidget *widget)
360 {
361  YGtkHelpDialog *dialog;
362  if (!help->dialog) {
363  if (!widget)
364  return;
365 #ifdef SET_HELP_HISTORY
366  dialog = YGTK_HELP_DIALOG (widget);
367  g_signal_connect (G_OBJECT (dialog->history_combo), "changed",
368  G_CALLBACK (history_changed_cb), help);
369 #endif
370  help->dialog = widget;
371  }
372  dialog = YGTK_HELP_DIALOG (help->dialog);
373  ygtk_help_dialog_set_text (dialog, ygtk_help_text_get (help, 0));
374 
375 #ifdef SET_HELP_HISTORY
376  g_signal_handlers_block_by_func (dialog->history_combo, history_changed_cb, help);
377  GtkListStore *store = GTK_LIST_STORE (gtk_combo_box_get_model (
378  GTK_COMBO_BOX (dialog->history_combo)));
379  gtk_list_store_clear (store);
380  GList *i;
381  for (i = help->history; i; i = i->next) {
382  TitleTextPair *pair = i->data;
383  gtk_combo_box_append_text (GTK_COMBO_BOX (dialog->history_combo), pair->title);
384  }
385  gtk_combo_box_set_active (GTK_COMBO_BOX (dialog->history_combo), 0);
386  g_signal_handlers_unblock_by_func (dialog->history_combo, history_changed_cb, help);
387 #endif
388 }
389 
390 //** Header
391 
392 typedef struct _YGtkWizardHeader
393 {
394  GtkEventBox box;
395  // members:
396  GtkWidget *title, *description, *icon, *description_more;
397  gint press_x, press_y;
399 
401 {
402  GtkEventBoxClass parent_class;
403  // signals:
404  void (*more_clicked) (YGtkWizardHeader *header);
406 
407 static guint more_clicked_signal;
408 
409 #define YGTK_TYPE_WIZARD_HEADER (ygtk_wizard_header_get_type ())
410 #define YGTK_WIZARD_HEADER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
411  YGTK_TYPE_WIZARD_HEADER, YGtkWizardHeader))
412 #define YGTK_WIZARD_HEADER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), \
413  YGTK_TYPE_WIZARD_HEADER, YGtkWizardHeaderClass))
414 #define YGTK_IS_WIZARD_HEADER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
415  YGTK_TYPE_WIZARD_HEADER))
416 #define YGTK_IS_WIZARD_HEADER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \
417  YGTK_TYPE_WIZARD_HEADER))
418 #define YGTK_WIZARD_HEADER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), \
419  YGTK_TYPE_WIZARD_HEADER, YGtkWizardHeaderClass))
420 
421 static GtkWidget *ygtk_wizard_header_new (void);
422 static GType ygtk_wizard_header_get_type (void) G_GNUC_CONST;
423 
424 G_DEFINE_TYPE (YGtkWizardHeader, ygtk_wizard_header, GTK_TYPE_EVENT_BOX)
425 
426 static void description_link_clicked_cb (YGtkLinkLabel *label, YGtkWizardHeader *header)
427 {
428  g_signal_emit (header, more_clicked_signal, 0, NULL);
429 }
430 
431 static void ygtk_wizard_header_init (YGtkWizardHeader *header)
432 {
433  GdkColor white = { 0, 0xffff, 0xffff, 0xffff };
434  gtk_widget_modify_bg (GTK_WIDGET (header), GTK_STATE_NORMAL, &white);
435 
436  header->title = gtk_label_new ("");
437  gtk_label_set_ellipsize (GTK_LABEL (header->title), PANGO_ELLIPSIZE_END);
438  gtk_misc_set_alignment (GTK_MISC (header->title), 0, 0.5);
439  ygutils_setWidgetFont (header->title, PANGO_STYLE_NORMAL, PANGO_WEIGHT_BOLD,
440  PANGO_SCALE_X_LARGE);
441  GdkColor black = { 0, 0, 0, 0 }; // set text to black cause of some style colors
442  gtk_widget_modify_fg (header->title, GTK_STATE_NORMAL, &black);
443 
444  header->description = ygtk_link_label_new ("", _("more"));
445  g_signal_connect (G_OBJECT (header->description), "link-clicked",
446  G_CALLBACK (description_link_clicked_cb), header);
447  gtk_widget_modify_fg (header->description, GTK_STATE_NORMAL, &black);
448 
449  header->icon = gtk_image_new();
450 
451  GtkWidget *text_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
452  gtk_box_set_homogeneous (GTK_BOX (text_box), FALSE);
453 
454  gtk_box_pack_start (GTK_BOX (text_box), header->title, TRUE, TRUE, 0);
455  gtk_box_pack_start (GTK_BOX (text_box), header->description, FALSE, TRUE, 0);
456 
457  GtkWidget *title_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
458  gtk_box_set_homogeneous (GTK_BOX (title_box), FALSE);
459 
460  gtk_box_pack_start (GTK_BOX (title_box), header->icon, FALSE, TRUE, 4);
461  gtk_box_pack_start (GTK_BOX (title_box), text_box, TRUE, TRUE, 0);
462  gtk_container_set_border_width (GTK_CONTAINER (title_box), 6);
463 
464  GtkWidget *box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
465  gtk_box_set_homogeneous (GTK_BOX (box), FALSE);
466 
467  gtk_box_pack_start (GTK_BOX (box), title_box, TRUE, TRUE, 0);
468  gtk_box_pack_start (GTK_BOX (box), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), FALSE, TRUE, 0);
469  gtk_widget_show_all (box);
470  gtk_container_add (GTK_CONTAINER (header), box);
471 }
472 
473 static gboolean ygtk_wizard_header_button_press_event (GtkWidget *widget, GdkEventButton *event)
474 {
475  if (event->button == 1) {
476  GdkCursor *cursor = gdk_cursor_new (GDK_FLEUR);
477  gdk_window_set_cursor (event->window, cursor);
478  g_object_unref (cursor);
479 
480  YGtkWizardHeader *header = YGTK_WIZARD_HEADER (widget);
481  header->press_x = event->x;
482  header->press_y = event->y;
483  }
484  return TRUE;
485 }
486 
487 static gboolean ygtk_wizard_header_button_release_event (GtkWidget *widget, GdkEventButton *event)
488 {
489  if (event->button == 1)
490  gdk_window_set_cursor (event->window, NULL);
491  return TRUE;
492 }
493 
494 static gboolean ygtk_wizard_header_motion_notify_event (GtkWidget *widget, GdkEventMotion *event)
495 {
496  if (event->state & GDK_BUTTON1_MASK) {
497  YGtkWizardHeader *header = YGTK_WIZARD_HEADER (widget);
498  gint root_x, root_y, pointer_x, pointer_y;
499  gdk_window_get_root_origin (event->window, &root_x, &root_y);
500 
501  GdkDisplay *display = gdk_window_get_display (event->window);
502  GdkDeviceManager *device_manager = gdk_display_get_device_manager (display);
503  GdkDevice *pointer = gdk_device_manager_get_client_pointer (device_manager);
504  gdk_window_get_device_position (event->window, pointer, &pointer_x, &pointer_y, NULL);
505 
506  gint x = pointer_x + root_x - header->press_x;
507  gint y = pointer_y + root_y - header->press_y;
508 
509  GtkWidget *top_window = gtk_widget_get_toplevel (widget);
510  gtk_window_move (GTK_WINDOW (top_window), x, y);
511  }
512  return TRUE;
513 }
514 
515 GtkWidget *ygtk_wizard_header_new()
516 { return g_object_new (YGTK_TYPE_WIZARD_HEADER, NULL); }
517 
518 static void ygtk_wizard_header_class_init (YGtkWizardHeaderClass *klass)
519 {
520  ygtk_wizard_header_parent_class = g_type_class_peek_parent (klass);
521 
522  GtkWidgetClass* widget_class = GTK_WIDGET_CLASS (klass);
523  widget_class->button_press_event = ygtk_wizard_header_button_press_event;
524  widget_class->button_release_event = ygtk_wizard_header_button_release_event;
525  widget_class->motion_notify_event = ygtk_wizard_header_motion_notify_event;
526 
527  more_clicked_signal = g_signal_new ("more-clicked",
528  G_TYPE_FROM_CLASS (G_OBJECT_CLASS (klass)), G_SIGNAL_RUN_LAST,
529  G_STRUCT_OFFSET (YGtkWizardHeaderClass, more_clicked), NULL, NULL,
530  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
531 }
532 
533 static void ygtk_wizard_header_set_title (YGtkWizardHeader *header, const gchar *text)
534 { gtk_label_set_text (GTK_LABEL (header->title), text); }
535 
536 static const gchar *ygtk_wizard_header_get_title (YGtkWizardHeader *header)
537 { return gtk_label_get_text (GTK_LABEL (header->title)); }
538 
539 static void ygtk_wizard_header_set_description (YGtkWizardHeader *header, const gchar *text)
540 {
541  gboolean cut = FALSE;
542  gchar *desc = ygutils_headerize_help (text, &cut);
543  ygtk_link_label_set_text (YGTK_LINK_LABEL (header->description), desc, NULL, cut);
544  g_free (desc);
545 }
546 
547 static void ygtk_wizard_header_set_icon (YGtkWizardHeader *header, GdkPixbuf *pixbuf)
548 { gtk_image_set_from_pixbuf (GTK_IMAGE (header->icon), pixbuf); }
549 
550 //** YGtkWizard
551 
552 // callbacks
553 static void destroy_tree_path (gpointer data)
554 {
555  GtkTreePath *path = data;
556  gtk_tree_path_free (path);
557 }
558 
559 // signals
560 static guint action_triggered_signal;
561 
562 static void ygtk_marshal_VOID__POINTER_INT (GClosure *closure,
563  GValue *return_value, guint n_param_values, const GValue *param_values,
564  gpointer invocation_hint, gpointer marshal_data)
565 {
566  typedef void (*GMarshalFunc_VOID__POINTER_INT) (gpointer data1, gpointer arg_1,
567  gint arg_2, gpointer data2);
568  register GMarshalFunc_VOID__POINTER_INT callback;
569  register GCClosure *cc = (GCClosure*) closure;
570  register gpointer data1, data2;
571 
572  g_return_if_fail (n_param_values == 3);
573 
574  if (G_CCLOSURE_SWAP_DATA (closure)) {
575  data1 = closure->data;
576  data2 = g_value_peek_pointer (param_values + 0);
577  }
578  else {
579  data1 = g_value_peek_pointer (param_values + 0);
580  data2 = closure->data;
581  }
582  callback = (GMarshalFunc_VOID__POINTER_INT)
583  (marshal_data ? marshal_data : cc->callback);
584 
585  callback (data1, g_value_get_pointer (param_values + 1),
586  g_value_get_int (param_values + 2), data2);
587 }
588 
589 static void button_clicked_cb (GtkButton *button, YGtkWizard *wizard)
590 {
591  gpointer id;
592  id = g_object_get_data (G_OBJECT (button), "ptr-id");
593  if (id)
594  g_signal_emit (wizard, action_triggered_signal, 0, id, G_TYPE_POINTER);
595  id = g_object_get_data (G_OBJECT (button), "str-id");
596  if (id)
597  g_signal_emit (wizard, action_triggered_signal, 0, id, G_TYPE_STRING);
598 }
599 
600 static GtkWidget *button_new (YGtkWizard *wizard)
601 {
602  GtkWidget *button = gtk_button_new_with_mnemonic ("");
603  gtk_widget_set_can_default(button, TRUE);
604  g_signal_connect (G_OBJECT (button), "clicked",
605  G_CALLBACK (button_clicked_cb), wizard);
606  return button;
607 }
608 
609 static GtkWidget *create_help_button()
610 {
611  GtkWidget *button, *image;
612  button = gtk_toggle_button_new();
613  gtk_button_set_label (GTK_BUTTON (button), _("Help"));
614  gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
615  image = gtk_image_new_from_stock (GTK_STOCK_HELP, GTK_ICON_SIZE_BUTTON);
616  gtk_button_set_image (GTK_BUTTON (button), image);
617  return button;
618 }
619 
620 static void ygtk_wizard_popup_help (YGtkWizard *wizard);
621 static void help_button_toggled_cb (GtkToggleButton *button, YGtkWizard *wizard)
622 {
623  if (gtk_toggle_button_get_active (button))
624  ygtk_wizard_popup_help (wizard);
625  else if (wizard->m_help->dialog)
626  gtk_widget_hide (wizard->m_help->dialog);
627 }
628 static void help_button_silent_set_active (YGtkWizard *wizard, gboolean active)
629 {
630  if (!wizard->help_button) return; // unmap may be issued at destroy
631  GtkToggleButton *button = GTK_TOGGLE_BUTTON (wizard->help_button);
632  g_signal_handlers_block_by_func (button,
633  (gpointer) help_button_toggled_cb, wizard);
634  gtk_toggle_button_set_active (button, active);
635  g_signal_handlers_unblock_by_func (button,
636  (gpointer) help_button_toggled_cb, wizard);
637 }
638 static void help_dialog_unmap_cb (GtkWidget *dialog, YGtkWizard *wizard)
639 { help_button_silent_set_active (wizard, FALSE); }
640 
641 static void ygtk_wizard_popup_help (YGtkWizard *wizard)
642 {
643  if (!wizard->m_help->dialog) {
644  GtkWindow *window = (GtkWindow *) gtk_widget_get_ancestor (
645  GTK_WIDGET (wizard), GTK_TYPE_WINDOW);
646  GtkWidget *dialog = ygtk_help_dialog_new (window);
647  g_signal_connect (G_OBJECT (dialog), "unmap",
648  G_CALLBACK (help_dialog_unmap_cb), wizard);
649  ygtk_help_text_sync (wizard->m_help, dialog);
650  }
651  help_button_silent_set_active (wizard, TRUE);
652  gtk_window_present (GTK_WINDOW (wizard->m_help->dialog));
653 }
654 
655 static void more_clicked_cb (YGtkWizardHeader *header, YGtkWizard *wizard)
656 { ygtk_wizard_popup_help (wizard); }
657 
658 /* We must dishonor the size group if the space doesn't afford it. */
659 
660 static void buttons_size_allocate_cb (GtkWidget *box, GtkAllocation *alloc,
661  GtkSizeGroup *group)
662 {
663  GSList *buttons = gtk_size_group_get_widgets (group), *i;
664  int max_width = 0, total = 0;
665  for (i = buttons; i; i = i->next) {
666  if (!gtk_widget_get_visible (i->data))
667  continue;
668  GtkRequisition req;
669  gtk_widget_get_preferred_size ((GtkWidget *) i->data, &req, NULL);
670  max_width = MAX (max_width, req.width);
671  total++;
672  }
673  int spacing = gtk_box_get_spacing (GTK_BOX (box));
674  int width = max_width*total + (total ? spacing*(total-1) : 0);
675  GtkSizeGroupMode new_mode = width > alloc->width ?
676  GTK_SIZE_GROUP_VERTICAL : GTK_SIZE_GROUP_BOTH;
677  if (gtk_size_group_get_mode (group) != new_mode)
678  gtk_size_group_set_mode (group, new_mode);
679 }
680 
681 G_DEFINE_TYPE (YGtkWizard, ygtk_wizard, GTK_TYPE_VBOX)
682 
683 static void ygtk_wizard_init (YGtkWizard *wizard)
684 {
685  wizard->menu_ids = g_hash_table_new_full (g_str_hash, g_str_equal,
686  g_free, NULL);
687  wizard->tree_ids = g_hash_table_new_full (g_str_hash, g_str_equal,
688  g_free, destroy_tree_path);
689  wizard->steps_ids = g_hash_table_new_full (g_str_hash, g_str_equal,
690  g_free, NULL);
691 
692  //** Title
693  wizard->m_title = ygtk_wizard_header_new();
694  g_signal_connect (G_OBJECT (wizard->m_title), "more-clicked",
695  G_CALLBACK (more_clicked_cb), wizard);
696  gtk_widget_show_all (wizard->m_title);
697 
698  //** Adding the bottom buttons
699  wizard->next_button = button_new (wizard);
700  wizard->back_button = button_new (wizard);
701  wizard->abort_button = button_new (wizard);
702  wizard->release_notes_button = button_new (wizard);
703  wizard->help_button = create_help_button();
704  g_signal_connect (G_OBJECT (wizard->help_button), "toggled",
705  G_CALLBACK (help_button_toggled_cb), wizard);
706 
707  wizard->m_buttons = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
708  gtk_button_box_set_layout (GTK_BUTTON_BOX (wizard->m_buttons), GTK_BUTTONBOX_END);
709  gtk_box_set_spacing (GTK_BOX (wizard->m_buttons), 6);
710  gtk_widget_show (wizard->m_buttons);
711  gtk_box_pack_start (GTK_BOX (wizard->m_buttons), wizard->help_button, FALSE, TRUE, 0);
712  gtk_box_pack_start (GTK_BOX (wizard->m_buttons), wizard->release_notes_button,
713  FALSE, TRUE, 0);
714  gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (wizard->m_buttons), wizard->help_button, TRUE);
715  gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (wizard->m_buttons), wizard->release_notes_button, TRUE);
716 
717  gtk_box_pack_end (GTK_BOX (wizard->m_buttons), wizard->abort_button, FALSE, TRUE, 0);
718  gtk_box_pack_end (GTK_BOX (wizard->m_buttons), wizard->back_button, FALSE, TRUE, 0);
719  gtk_box_pack_end (GTK_BOX (wizard->m_buttons), wizard->next_button, FALSE, TRUE, 0);
720 
721  // make buttons all having the same size
722  GtkSizeGroup *buttons_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH);
723  gtk_size_group_add_widget (buttons_group, wizard->help_button);
724  gtk_size_group_add_widget (buttons_group, wizard->release_notes_button);
725  gtk_size_group_add_widget (buttons_group, wizard->next_button);
726  gtk_size_group_add_widget (buttons_group, wizard->back_button);
727  gtk_size_group_add_widget (buttons_group, wizard->abort_button);
728  g_object_unref (G_OBJECT (buttons_group));
729  gtk_widget_set_size_request (wizard->m_buttons, 0, -1);
730  g_signal_connect_after (G_OBJECT (wizard->m_buttons), "size-allocate",
731  G_CALLBACK (buttons_size_allocate_cb), buttons_group);
732  wizard->m_default_button = NULL;
733 
734  //** The menu and the navigation widgets will be created when requested.
735  // space for them
736  wizard->m_menu_box = gtk_event_box_new();
737  wizard->m_info_box = gtk_event_box_new();
738  wizard->m_status_box = gtk_event_box_new();
739 
740  wizard->m_pane = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
741  gtk_widget_show (wizard->m_pane);
742 
743  wizard->m_contents_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
744  gtk_box_set_homogeneous (GTK_BOX (wizard->m_contents_box), FALSE);
745 
746  gtk_box_pack_start (GTK_BOX (wizard->m_contents_box), wizard->m_info_box, FALSE, TRUE, 0);
747  gtk_box_pack_start (GTK_BOX (wizard->m_contents_box), wizard->m_pane, TRUE, TRUE, 0);
748  gtk_widget_show (wizard->m_contents_box);
749 
750  GtkWidget *vbox;
751  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
752  gtk_box_set_homogeneous (GTK_BOX (vbox), FALSE);
753 
754  gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); // content's border
755  gtk_box_pack_start (GTK_BOX (vbox), wizard->m_contents_box, TRUE, TRUE, 0);
756 #if 0
757  GtkWidget *hsep = gtk_hseparator_new();
758  gtk_box_pack_start (GTK_BOX (vbox), hsep, FALSE, TRUE, 0);
759  gtk_widget_show (hsep);
760 #endif
761  gtk_box_pack_start (GTK_BOX (vbox), wizard->m_buttons, FALSE, TRUE, 0);
762  gtk_widget_show (vbox);
763 
764  gtk_box_pack_start (GTK_BOX (wizard), wizard->m_menu_box, FALSE, TRUE, 0);
765  gtk_box_pack_start (GTK_BOX (wizard), wizard->m_title, FALSE, TRUE, 0);
766  gtk_box_pack_start (GTK_BOX (wizard), vbox, TRUE, TRUE, 0);
767  gtk_box_pack_start (GTK_BOX (wizard), wizard->m_status_box, FALSE, TRUE, 0);
768 }
769 
770 static void ygtk_wizard_realize (GtkWidget *widget)
771 {
772  GTK_WIDGET_CLASS (ygtk_wizard_parent_class)->realize (widget);
773  YGtkWizard *wizard = YGTK_WIZARD (widget);
774  if (wizard->m_default_button) {
775  GtkWidget *window = gtk_widget_get_toplevel (widget);
776  if (GTK_IS_WINDOW (window))
777  if (!gtk_window_get_default_widget (GTK_WINDOW (window)))
778  gtk_widget_grab_default (wizard->m_default_button);
779  }
780 }
781 
782 static void ygtk_wizard_map (GtkWidget *widget)
783 {
784  GTK_WIDGET_CLASS (ygtk_wizard_parent_class)->map (widget);
785  // since wizards can swap the window, we need to update the title on map
786  YGtkWizard *wizard = YGTK_WIZARD (widget);
787  YGtkWizardHeader *header = YGTK_WIZARD_HEADER (wizard->m_title);
788  const gchar *title = gtk_label_get_text (GTK_LABEL (header->title));
789  ygdialog_setTitle (title, FALSE);
790 }
791 
792 static gboolean clear_hash_cb (gpointer key, gpointer value, gpointer data)
793 { return TRUE; }
794 static void clear_hash (GHashTable *hash_table)
795 {
796  g_hash_table_foreach_remove (hash_table, clear_hash_cb, NULL);
797 }
798 static void destroy_hash (GHashTable **hash, gboolean is_tree)
799 {
800  if (*hash)
801  g_hash_table_destroy (*hash);
802  *hash = NULL;
803 }
804 
805 static void ygtk_wizard_finalize (GObject *object)
806 {
807  YGtkWizard *wizard = YGTK_WIZARD (object);
808  wizard->help_button = NULL; // dialog unmap will try to access this
809  destroy_hash (&wizard->menu_ids, FALSE);
810  destroy_hash (&wizard->tree_ids, TRUE);
811  destroy_hash (&wizard->steps_ids, FALSE);
812  if (wizard->m_help) {
813  ygtk_help_text_destroy (wizard->m_help);
814  wizard->m_help = NULL;
815  }
816  G_OBJECT_CLASS (ygtk_wizard_parent_class)->finalize (object);
817 }
818 
819 GtkWidget *ygtk_wizard_new (void)
820 { return g_object_new (YGTK_TYPE_WIZARD, NULL); }
821 
822 static void selected_menu_item_cb (GtkMenuItem *item, const char *id)
823 {
824  YGtkWizard *wizard = g_object_get_data (G_OBJECT (item), "wizard");
825  g_signal_emit (wizard, action_triggered_signal, 0, id, G_TYPE_STRING);
826 }
827 
828 static void tree_item_selected_cb (GtkTreeView *tree_view, YGtkWizard *wizard)
829 {
830  const gchar *id = ygtk_wizard_get_tree_selection (wizard);
831  if (id)
832  g_signal_emit (wizard, action_triggered_signal, 0, id, G_TYPE_STRING);
833 }
834 
835 void ygtk_wizard_set_child (YGtkWizard *wizard, GtkWidget *child)
836 {
837  if (wizard->m_child)
838  gtk_container_remove (GTK_CONTAINER (wizard->m_pane), wizard->m_child);
839  wizard->m_child = child;
840  if (child)
841  gtk_paned_pack2 (GTK_PANED (wizard->m_pane), child, TRUE, TRUE);
842 }
843 
844 void ygtk_wizard_set_information_widget (YGtkWizard *wizard, GtkWidget *widget)
845 {
846  GtkWidget *hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
847  gtk_box_set_homogeneous (GTK_BOX (hbox), FALSE);
848 
849  GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
850  gtk_box_pack_start (GTK_BOX (hbox), widget, TRUE, TRUE, 0);
851  gtk_box_pack_start (GTK_BOX (hbox), sep, FALSE, TRUE, 0);
852  gtk_container_add (GTK_CONTAINER (wizard->m_info_box), hbox);
853  gtk_widget_show_all (wizard->m_info_box);
854 }
855 
856 void ygtk_wizard_set_control_widget (YGtkWizard *wizard, GtkWidget *widget)
857 {
858  gtk_paned_pack1 (GTK_PANED (wizard->m_pane), widget, FALSE, TRUE);
859 }
860 
861 void ygtk_wizard_enable_steps (YGtkWizard *wizard)
862 {
863  g_return_if_fail (wizard->steps == NULL);
864  wizard->steps = ygtk_steps_new();
865  ygtk_wizard_set_information_widget (wizard, wizard->steps);
866 }
867 
868 void ygtk_wizard_enable_tree (YGtkWizard *wizard)
869 {
870  g_return_if_fail (wizard->tree_view == NULL);
871 
872  wizard->tree_view = gtk_tree_view_new_with_model
873  (GTK_TREE_MODEL (gtk_tree_store_new (1, G_TYPE_STRING)));
874  GtkTreeView *view = GTK_TREE_VIEW (wizard->tree_view);
875  gtk_tree_view_insert_column_with_attributes (view,
876  0, "", gtk_cell_renderer_text_new(), "text", 0, NULL);
877  gtk_tree_view_set_headers_visible (view, FALSE);
878  gtk_tree_selection_set_mode (gtk_tree_view_get_selection (view), GTK_SELECTION_BROWSE);
879  g_signal_connect (G_OBJECT (wizard->tree_view), "cursor-changed",
880  G_CALLBACK (tree_item_selected_cb), wizard);
881  // start by assuming it will be list, and set expanders when a tree is built
882  gtk_tree_view_set_show_expanders (view, FALSE);
883 
884  GtkWidget *scroll = gtk_scrolled_window_new (NULL, NULL);
885  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll),
886  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
887  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroll),
888  GTK_SHADOW_IN);
889 
890  gtk_container_add (GTK_CONTAINER (scroll), wizard->tree_view);
891  gtk_widget_show_all (scroll);
892 
893  ygtk_wizard_set_control_widget (wizard, scroll);
894  ygutils_setPaneRelPosition (wizard->m_pane, .30);
895 }
896 
897 #define ENABLE_WIDGET(enable, widget) \
898  (enable ? gtk_widget_show (widget) : gtk_widget_hide (widget))
899 #define ENABLE_WIDGET_STR(str, widget) \
900  (str && str[0] ? gtk_widget_show (widget) : gtk_widget_hide (widget))
901 
902 /* Commands */
903 
904 void ygtk_wizard_set_help_text (YGtkWizard *wizard, const gchar *text)
905 {
906  if (!wizard->m_help)
907  wizard->m_help = ygtk_help_text_new();
908  const gchar *title = ygtk_wizard_header_get_title (YGTK_WIZARD_HEADER (wizard->m_title));
909  ygtk_help_text_set (wizard->m_help, title, text);
910 /* helpful for building out test.cc
911  fprintf (stderr, "Help text:\n%s\n", text); */
912  ygtk_wizard_header_set_description (YGTK_WIZARD_HEADER (wizard->m_title), text);
913  ENABLE_WIDGET_STR (text, wizard->help_button);
914 }
915 
916 gboolean ygtk_wizard_add_tree_item (YGtkWizard *wizard, const char *parent_id,
917  const char *text, const char *id)
918 {
919  GtkTreeView *view = GTK_TREE_VIEW (wizard->tree_view);
920  GtkTreeModel *model = gtk_tree_view_get_model (view);
921  GtkTreeIter iter;
922 
923  if (!parent_id || !*parent_id)
924  gtk_tree_store_append (GTK_TREE_STORE (model), &iter, NULL);
925  else {
926  GtkTreePath *path = g_hash_table_lookup (wizard->tree_ids, parent_id);
927  if (path == NULL)
928  return FALSE;
929  gtk_tree_view_set_show_expanders (view, TRUE); // has children
930  GtkTreeIter parent_iter;
931  gtk_tree_model_get_iter (model, &parent_iter, path);
932  gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &parent_iter);
933  }
934 
935  gtk_tree_store_set (GTK_TREE_STORE (model), &iter, 0, text, -1);
936  g_hash_table_insert (wizard->tree_ids, g_strdup (id),
937  gtk_tree_model_get_path (model, &iter));
938  return TRUE;
939 }
940 
941 void ygtk_wizard_clear_tree (YGtkWizard *wizard)
942 {
943  GtkTreeView *tree = GTK_TREE_VIEW (wizard->tree_view);
944  gtk_tree_store_clear (GTK_TREE_STORE (gtk_tree_view_get_model (tree)));
945  clear_hash (wizard->tree_ids);
946 }
947 
948 gboolean ygtk_wizard_select_tree_item (YGtkWizard *wizard, const char *id)
949 {
950  GtkTreePath *path = g_hash_table_lookup (wizard->tree_ids, id);
951  if (path == NULL)
952  return FALSE;
953 
954  g_signal_handlers_block_by_func (wizard->tree_view,
955  (gpointer) tree_item_selected_cb, wizard);
956 
957  GtkWidget *widget = wizard->tree_view;
958  gtk_tree_view_expand_to_path (GTK_TREE_VIEW (widget), path);
959  gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget), path,
960  NULL, FALSE);
961  gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (widget), path, NULL,
962  TRUE, 0.5, 0.5);
963 
964  g_signal_handlers_unblock_by_func (wizard->tree_view,
965  (gpointer) tree_item_selected_cb, wizard);
966  return TRUE;
967 }
968 
969 void ygtk_wizard_set_header_text (YGtkWizard *wizard, const char *text)
970 {
971  if (*text)
972  ygtk_wizard_header_set_title (YGTK_WIZARD_HEADER (wizard->m_title), text);
973 }
974 
975 gboolean ygtk_wizard_set_header_icon (YGtkWizard *wizard, const char *icon)
976 {
977  GError *error = 0;
978  GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file (icon, &error);
979  if (!pixbuf)
980  return FALSE;
981  ygtk_wizard_header_set_icon (YGTK_WIZARD_HEADER (wizard->m_title), pixbuf);
982  g_object_unref (G_OBJECT (pixbuf));
983  return TRUE;
984 }
985 
986 void ygtk_wizard_set_button_label (YGtkWizard *wizard, GtkWidget *button,
987  const char *_label, const char *stock)
988 {
989  const char *label = _label ? _label : "";
990  gtk_button_set_label (GTK_BUTTON (button), label);
991  ENABLE_WIDGET_STR (label, button);
992  if (button == wizard->abort_button)
993  stock = GTK_STOCK_CANCEL;
994  else if (button == wizard->release_notes_button)
995  stock = GTK_STOCK_EDIT;
996 
997  const char *_stock = ygutils_setStockIcon (button, label, stock);
998  g_object_set_data (G_OBJECT (button), "icon-fallback", _stock ? 0 : GINT_TO_POINTER (1));
999 }
1000 
1001 void ygtk_wizard_set_button_str_id (YGtkWizard *wizard, GtkWidget *button, const char *id)
1002 {
1003  g_object_set_data_full (G_OBJECT (button), "str-id", g_strdup (id), g_free);
1004 }
1005 
1006 void ygtk_wizard_set_button_ptr_id (YGtkWizard *wizard, GtkWidget *button, gpointer id)
1007 {
1008  g_object_set_data (G_OBJECT (button), "ptr-id", id);
1009 }
1010 
1011 void ygtk_wizard_set_default_button (YGtkWizard *wizard, GtkWidget *button)
1012 { wizard->m_default_button = button; }
1013 
1014 void ygtk_wizard_enable_button (YGtkWizard *wizard, GtkWidget *button, gboolean enable)
1015 {
1016  gtk_widget_set_sensitive (button, enable);
1017 }
1018 
1019 void ygtk_wizard_set_extra_button (YGtkWizard *wizard, GtkWidget *widget)
1020 {
1021  gtk_box_pack_start (GTK_BOX (wizard->m_buttons), widget, FALSE, TRUE, 0);
1022 }
1023 
1024 void ygtk_wizard_add_menu (YGtkWizard *wizard, const char *text,
1025  const char *id)
1026 {
1027  if (!wizard->menu) {
1028  wizard->menu = gtk_menu_bar_new();
1029  ygtk_wizard_set_custom_menubar (wizard, wizard->menu, TRUE);
1030  gtk_widget_show (wizard->menu);
1031  }
1032 
1033  GtkWidget *entry = gtk_menu_item_new_with_mnemonic (text);
1034  GtkWidget *submenu = gtk_menu_new();
1035  gtk_menu_item_set_submenu (GTK_MENU_ITEM (entry), submenu);
1036  gtk_menu_shell_append (GTK_MENU_SHELL (wizard->menu), entry);
1037  gtk_widget_show_all (entry);
1038 
1039  g_hash_table_insert (wizard->menu_ids, g_strdup (id), submenu);
1040 }
1041 
1042 gboolean ygtk_wizard_add_menu_entry (YGtkWizard *wizard, const char *parent_id,
1043  const char *text, const char *id)
1044 {
1045  GtkWidget *parent = g_hash_table_lookup (wizard->menu_ids, parent_id);
1046  if (!parent)
1047  return FALSE;
1048 
1049  GtkWidget *entry;
1050  const char *icon = ygutils_mapStockIcon (text);
1051  if (icon) {
1052  GtkWidget *image = gtk_image_new_from_stock (icon, GTK_ICON_SIZE_MENU);
1053  entry = gtk_image_menu_item_new_with_mnemonic (text);
1054  gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (entry), image);
1055  }
1056  else
1057  entry = gtk_menu_item_new_with_mnemonic (text);
1058  gtk_menu_shell_append (GTK_MENU_SHELL (parent), entry);
1059  gtk_widget_show (entry);
1060 
1061  // we need to get YGtkWizard to send signal
1062  g_object_set_data (G_OBJECT (entry), "wizard", wizard);
1063  g_signal_connect_data (G_OBJECT (entry), "activate",
1064  G_CALLBACK (selected_menu_item_cb), g_strdup (id),
1065  (GClosureNotify) g_free, 0);
1066  return TRUE;
1067 }
1068 
1069 gboolean ygtk_wizard_add_sub_menu (YGtkWizard *wizard, const char *parent_id,
1070  const char *text, const char *id)
1071 {
1072  GtkWidget *parent = g_hash_table_lookup (wizard->menu_ids, parent_id);
1073  if (!parent)
1074  return FALSE;
1075 
1076  GtkWidget *entry = gtk_menu_item_new_with_mnemonic (text);
1077  GtkWidget *submenu = gtk_menu_new();
1078  gtk_menu_item_set_submenu (GTK_MENU_ITEM (entry), submenu);
1079  gtk_menu_shell_append (GTK_MENU_SHELL (parent), entry);
1080  gtk_widget_show_all (entry);
1081 
1082  g_hash_table_insert (wizard->menu_ids, g_strdup (id), submenu);
1083  return TRUE;
1084 }
1085 
1086 gboolean ygtk_wizard_add_menu_separator (YGtkWizard *wizard, const char *parent_id)
1087 {
1088  GtkWidget *parent = g_hash_table_lookup (wizard->menu_ids, parent_id);
1089  if (!parent)
1090  return FALSE;
1091 
1092  GtkWidget *separator = gtk_separator_menu_item_new();
1093  gtk_menu_shell_append (GTK_MENU_SHELL (parent), separator);
1094  gtk_widget_show (separator);
1095  return TRUE;
1096 }
1097 
1098 void ygtk_wizard_clear_menu (YGtkWizard *wizard)
1099 {
1100  if (!wizard->menu)
1101  return;
1102  clear_hash (wizard->menu_ids);
1103  GList *children = gtk_container_get_children (GTK_CONTAINER (wizard->menu)), *i;
1104  for (i = children; i; i = i->next) {
1105  GtkWidget *child = (GtkWidget *) i->data;
1106  gtk_container_remove (GTK_CONTAINER (wizard->menu), child);
1107  }
1108 }
1109 
1110 void ygtk_wizard_set_custom_menubar (YGtkWizard *wizard, GtkWidget *menu_bar, gboolean hide_header)
1111 {
1112  gtk_container_add (GTK_CONTAINER (wizard->m_menu_box), menu_bar);
1113  gtk_widget_show (wizard->m_menu_box);
1114  // we probably want to hide the title, so the menu is more visible
1115  if (hide_header)
1116  gtk_widget_hide (wizard->m_title);
1117 }
1118 
1119 void ygtk_wizard_set_status_bar (YGtkWizard *wizard, GtkWidget *status_bar)
1120 {
1121  gtk_container_add (GTK_CONTAINER (wizard->m_status_box), status_bar);
1122  gtk_widget_show (wizard->m_status_box);
1123 }
1124 
1125 void ygtk_wizard_add_step_header (YGtkWizard *wizard, const char *text)
1126 {
1127  g_return_if_fail (wizard->steps != NULL);
1128  ygtk_steps_append_heading (YGTK_STEPS (wizard->steps), text);
1129 }
1130 
1131 void ygtk_wizard_add_step (YGtkWizard *wizard, const char *text, const char *id)
1132 {
1133  g_return_if_fail (wizard->steps != NULL);
1134  YGtkSteps *steps = YGTK_STEPS (wizard->steps);
1135 
1136  // append may be called for the same step a few times to mean that we
1137  // should consider it several steps, but present it only once
1138  gint step_nb, last_n = ygtk_steps_total (steps)-1;
1139  const gchar *last = ygtk_steps_get_nth_label (steps, last_n);
1140  if (last && !strcmp (last, text))
1141  step_nb = last_n;
1142  else
1143  step_nb = ygtk_steps_append (steps, text);
1144  g_hash_table_insert (wizard->steps_ids, g_strdup (id), GINT_TO_POINTER (step_nb));
1145 }
1146 
1147 gboolean ygtk_wizard_set_current_step (YGtkWizard *wizard, const char *id)
1148 {
1149  if (*id) {
1150 #if 0
1151  gpointer step_nb = g_hash_table_lookup (wizard->steps_ids, id);
1152  if (!step_nb)
1153  return FALSE;
1154  ygtk_steps_set_current (YGTK_STEPS (wizard->steps), GPOINTER_TO_INT (step_nb));
1155 #else
1156  // can't use ordinary lookup because it returns '0' if not found
1157  // which is a valid step_nb
1158  gpointer step_nb, foo;
1159  if (!g_hash_table_lookup_extended (wizard->steps_ids, id, &foo, &step_nb))
1160  return FALSE;
1161  ygtk_steps_set_current (YGTK_STEPS (wizard->steps), GPOINTER_TO_INT (step_nb));
1162 #endif
1163  }
1164  else
1165  ygtk_steps_set_current (YGTK_STEPS (wizard->steps), -1);
1166  return TRUE;
1167 }
1168 
1169 void ygtk_wizard_clear_steps (YGtkWizard *wizard)
1170 {
1171  ygtk_steps_clear (YGTK_STEPS (wizard->steps));
1172  clear_hash (wizard->steps_ids);
1173 }
1174 
1175 static const gchar *found_key;
1176 static void hash_lookup_tree_path_value (gpointer _key, gpointer _value,
1177  gpointer user_data)
1178 {
1179  gchar *key = _key;
1180  GtkTreePath *value = _value;
1181  GtkTreePath *needle = user_data;
1182 
1183  if (gtk_tree_path_compare (value, needle) == 0)
1184  found_key = key;
1185 }
1186 
1187 const gchar *ygtk_wizard_get_tree_selection (YGtkWizard *wizard)
1188 {
1189  GtkTreePath *path;
1190  gtk_tree_view_get_cursor (GTK_TREE_VIEW (wizard->tree_view), &path, NULL);
1191  if (path == NULL) return NULL;
1192 
1193  /* A more elegant solution would be using a crossed hash table, but there
1194  is none out of box, so we'll just iterate the hash table. */
1195  found_key = 0;
1196  g_hash_table_foreach (wizard->tree_ids, hash_lookup_tree_path_value, path);
1197 
1198  gtk_tree_path_free (path);
1199  return found_key;
1200 }
1201 
1202 static void ygtk_wizard_class_init (YGtkWizardClass *klass)
1203 {
1204  ygtk_wizard_parent_class = g_type_class_peek_parent (klass);
1205 
1206  GtkWidgetClass* widget_class = GTK_WIDGET_CLASS (klass);
1207  widget_class->realize = ygtk_wizard_realize;
1208  widget_class->map = ygtk_wizard_map;
1209 
1210  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
1211  gobject_class->finalize = ygtk_wizard_finalize;
1212 
1213  action_triggered_signal = g_signal_new ("action-triggered",
1214  G_TYPE_FROM_CLASS (G_OBJECT_CLASS (klass)), G_SIGNAL_RUN_LAST,
1215  G_STRUCT_OFFSET (YGtkWizardClass, action_triggered),
1216  NULL, NULL, ygtk_marshal_VOID__POINTER_INT, G_TYPE_NONE,
1217  2, G_TYPE_POINTER, G_TYPE_INT);
1218 
1219  // on F1, popup the help box
1220  klass->popup_help = ygtk_wizard_popup_help;
1221  g_signal_new ("popup_help", G_TYPE_FROM_CLASS (G_OBJECT_CLASS (klass)),
1222  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1223  G_STRUCT_OFFSET (YGtkWizardClass, popup_help),
1224  NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
1225 
1226  GtkBindingSet *binding_set = gtk_binding_set_by_class (klass);
1227  gtk_binding_entry_add_signal (binding_set, GDK_KEY_F1, 0, "popup_help", 0);
1228 }
1229