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