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