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