1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /* -*- encoding: utf8 -*- */
3 /*
4  * Copyright (C) 2012 Red Hat
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  *
19  * Written by:
20  *     Jasper St. Pierre <jstpierre@mecheye.net>
21  */
22 
23 #include "config.h"
24 
25 #include <glib-object.h>
26 #include <glib/gi18n.h>
27 #include <gtk/gtk.h>
28 
29 #include "gis-assistant.h"
30 
31 enum {
32   PROP_0,
33   PROP_TITLE,
34   PROP_LAST,
35 };
36 
37 enum {
38   PAGE_CHANGED,
39   LAST_SIGNAL
40 };
41 
42 static guint signals[LAST_SIGNAL];
43 static GParamSpec *obj_props[PROP_LAST];
44 
45 struct _GisAssistant
46 {
47   GtkBox     parent_instance;
48 
49   GtkWidget *forward;
50   GtkWidget *accept;
51   GtkWidget *skip;
52   GtkWidget *back;
53   GtkWidget *cancel;
54 
55   GtkWidget *main_layout;
56   GtkWidget *spinner;
57   GtkWidget *titlebar;
58   GtkWidget *title;
59   GtkWidget *stack;
60 
61   GList *pages;
62   GisPage *current_page;
63 };
64 
G_DEFINE_TYPE(GisAssistant,gis_assistant,GTK_TYPE_BOX)65 G_DEFINE_TYPE (GisAssistant, gis_assistant, GTK_TYPE_BOX)
66 
67 static void
68 visible_child_changed (GisAssistant *assistant)
69 {
70   g_signal_emit (assistant, signals[PAGE_CHANGED], 0);
71 }
72 
73 static void
widget_destroyed(GtkWidget * widget,GisAssistant * assistant)74 widget_destroyed (GtkWidget    *widget,
75                   GisAssistant *assistant)
76 {
77   GisPage *page = GIS_PAGE (widget);
78 
79   assistant->pages = g_list_remove (assistant->pages, page);
80   if (page == assistant->current_page)
81     assistant->current_page = NULL;
82 }
83 
84 static void
switch_to(GisAssistant * assistant,GisPage * page)85 switch_to (GisAssistant          *assistant,
86            GisPage               *page)
87 {
88   g_return_if_fail (page != NULL);
89 
90   gtk_stack_set_visible_child (GTK_STACK (assistant->stack), GTK_WIDGET (page));
91 }
92 
93 static inline gboolean
should_show_page(GisPage * page)94 should_show_page (GisPage *page)
95 {
96   return gtk_widget_get_visible (GTK_WIDGET (page));
97 }
98 
99 static GisPage *
find_next_page(GisAssistant * self,GisPage * page)100 find_next_page (GisAssistant *self,
101                 GisPage      *page)
102 {
103   GList *l = g_list_find (self->pages, page);
104 
105   g_return_val_if_fail (l != NULL, NULL);
106 
107   /* We need the next page */
108   l = l->next;
109 
110   for (; l != NULL; l = l->next)
111     {
112       GisPage *page = GIS_PAGE (l->data);
113 
114       if (should_show_page (page))
115         return page;
116     }
117 
118   return NULL;
119 }
120 
121 static void
switch_to_next_page(GisAssistant * assistant)122 switch_to_next_page (GisAssistant *assistant)
123 {
124   switch_to (assistant, find_next_page (assistant, assistant->current_page));
125 }
126 
127 static void
on_apply_done(GisPage * page,gboolean valid,gpointer user_data)128 on_apply_done (GisPage *page,
129                gboolean valid,
130                gpointer user_data)
131 {
132   GisAssistant *assistant = GIS_ASSISTANT (user_data);
133 
134   if (valid)
135     switch_to_next_page (assistant);
136 }
137 
138 void
gis_assistant_next_page(GisAssistant * assistant)139 gis_assistant_next_page (GisAssistant *assistant)
140 {
141   if (assistant->current_page)
142     gis_page_apply_begin (assistant->current_page, on_apply_done, assistant);
143   else
144     switch_to_next_page (assistant);
145 }
146 
147 static GisPage *
find_prev_page(GisAssistant * self,GisPage * page)148 find_prev_page (GisAssistant *self,
149                 GisPage      *page)
150 {
151   GList *l = g_list_find (self->pages, page);
152 
153   g_return_val_if_fail (l != NULL, NULL);
154 
155   /* We need the previous page */
156   l = l->prev;
157 
158   for (; l != NULL; l = l->prev)
159     {
160       GisPage *page = GIS_PAGE (l->data);
161 
162       if (should_show_page (page))
163         return page;
164     }
165 
166   return NULL;
167 }
168 
169 void
gis_assistant_previous_page(GisAssistant * assistant)170 gis_assistant_previous_page (GisAssistant *assistant)
171 {
172   g_return_if_fail (assistant->current_page != NULL);
173   switch_to (assistant, find_prev_page (assistant, assistant->current_page));
174 }
175 
176 static void
set_suggested_action_sensitive(GtkWidget * widget,gboolean sensitive)177 set_suggested_action_sensitive (GtkWidget *widget,
178                                 gboolean   sensitive)
179 {
180   gtk_widget_set_sensitive (widget, sensitive);
181 }
182 
183 static void
set_navigation_button(GisAssistant * assistant,GtkWidget * widget)184 set_navigation_button (GisAssistant *assistant,
185                        GtkWidget    *widget)
186 {
187   gtk_widget_set_visible (assistant->forward, (widget == assistant->forward));
188   gtk_widget_set_visible (assistant->accept, (widget == assistant->accept));
189   gtk_widget_set_visible (assistant->skip, (widget == assistant->skip));
190 }
191 
192 void
update_navigation_buttons(GisAssistant * assistant)193 update_navigation_buttons (GisAssistant *assistant)
194 {
195   GisPage *page = assistant->current_page;
196   GList *l;
197   gboolean is_last_page;
198 
199   if (page == NULL)
200     return;
201 
202   l = g_list_find (assistant->pages, page);
203 
204   is_last_page = (l->next == NULL);
205 
206   if (is_last_page)
207     {
208       gtk_widget_hide (assistant->back);
209       gtk_widget_hide (assistant->forward);
210       gtk_widget_hide (assistant->skip);
211       gtk_widget_hide (assistant->cancel);
212       gtk_widget_hide (assistant->accept);
213       /* FIXME: workaround for a GTK+ issue */
214       gtk_widget_queue_resize (assistant->titlebar);
215     }
216   else
217     {
218       gboolean is_first_page;
219       GtkWidget *next_widget;
220 
221       is_first_page = (l->prev == NULL);
222       gtk_widget_set_visible (assistant->back, !is_first_page);
223 
224       if (gis_page_get_needs_accept (page))
225         next_widget = assistant->accept;
226       else
227         next_widget = assistant->forward;
228 
229       if (gis_page_get_complete (page)) {
230         set_suggested_action_sensitive (next_widget, TRUE);
231         set_navigation_button (assistant, next_widget);
232       } else if (gis_page_get_skippable (page)) {
233         set_navigation_button (assistant, assistant->skip);
234       } else {
235         set_suggested_action_sensitive (next_widget, FALSE);
236         set_navigation_button (assistant, next_widget);
237       }
238 
239       if (gis_page_get_has_forward (page)) {
240         gtk_widget_hide (next_widget);
241       }
242     }
243 }
244 
245 static void
update_applying_state(GisAssistant * assistant)246 update_applying_state (GisAssistant *assistant)
247 {
248   gboolean applying = FALSE;
249   gboolean is_first_page = FALSE;
250 
251   if (assistant->current_page)
252     {
253       applying = gis_page_get_applying (assistant->current_page);
254       is_first_page = assistant->pages->data == assistant->current_page;
255     }
256   gtk_widget_set_sensitive (assistant->forward, !applying);
257   gtk_widget_set_visible (assistant->back, !applying && !is_first_page);
258   gtk_widget_set_visible (assistant->cancel, applying);
259   gtk_widget_set_visible (assistant->spinner, applying);
260 
261   if (applying)
262     gtk_spinner_start (GTK_SPINNER (assistant->spinner));
263   else
264     gtk_spinner_stop (GTK_SPINNER (assistant->spinner));
265 }
266 
267 static void
update_titlebar(GisAssistant * assistant)268 update_titlebar (GisAssistant *assistant)
269 {
270   gtk_label_set_label (GTK_LABEL (assistant->title),
271                        gis_assistant_get_title (assistant));
272 }
273 
274 static void
page_notify(GisPage * page,GParamSpec * pspec,GisAssistant * assistant)275 page_notify (GisPage      *page,
276              GParamSpec   *pspec,
277              GisAssistant *assistant)
278 {
279   if (page != assistant->current_page)
280     return;
281 
282   if (strcmp (pspec->name, "title") == 0)
283     {
284       g_object_notify_by_pspec (G_OBJECT (assistant), obj_props[PROP_TITLE]);
285       update_titlebar (assistant);
286     }
287   else if (strcmp (pspec->name, "applying") == 0)
288     {
289       update_applying_state (assistant);
290     }
291   else
292     {
293       update_navigation_buttons (assistant);
294     }
295 }
296 
297 void
gis_assistant_add_page(GisAssistant * assistant,GisPage * page)298 gis_assistant_add_page (GisAssistant *assistant,
299                         GisPage      *page)
300 {
301   GList *link;
302 
303   /* Page shouldn't already exist */
304   g_return_if_fail (!g_list_find (assistant->pages, page));
305 
306   assistant->pages = g_list_append (assistant->pages, page);
307   link = g_list_last (assistant->pages);
308   link = link->prev;
309 
310   g_signal_connect (page, "destroy", G_CALLBACK (widget_destroyed), assistant);
311   g_signal_connect (page, "notify", G_CALLBACK (page_notify), assistant);
312 
313   gtk_container_add (GTK_CONTAINER (assistant->stack), GTK_WIDGET (page));
314 
315   /* Update buttons if current page is now the second last page */
316   if (assistant->current_page && link &&
317       link->data == assistant->current_page)
318     update_navigation_buttons (assistant);
319 }
320 
321 GisPage *
gis_assistant_get_current_page(GisAssistant * assistant)322 gis_assistant_get_current_page (GisAssistant *assistant)
323 {
324   return assistant->current_page;
325 }
326 
327 GList *
gis_assistant_get_all_pages(GisAssistant * assistant)328 gis_assistant_get_all_pages (GisAssistant *assistant)
329 {
330   return assistant->pages;
331 }
332 
333 static void
go_forward(GtkWidget * button,GisAssistant * assistant)334 go_forward (GtkWidget    *button,
335             GisAssistant *assistant)
336 {
337   gis_assistant_next_page (assistant);
338 }
339 
340 static void
go_backward(GtkWidget * button,GisAssistant * assistant)341 go_backward (GtkWidget    *button,
342              GisAssistant *assistant)
343 {
344   gis_assistant_previous_page (assistant);
345 }
346 
347 static void
do_cancel(GtkWidget * button,GisAssistant * assistant)348 do_cancel (GtkWidget    *button,
349            GisAssistant *assistant)
350 {
351   if (assistant->current_page)
352     gis_page_apply_cancel (assistant->current_page);
353 }
354 
355 const gchar *
gis_assistant_get_title(GisAssistant * assistant)356 gis_assistant_get_title (GisAssistant *assistant)
357 {
358   if (assistant->current_page != NULL)
359     return gis_page_get_title (assistant->current_page);
360   else
361     return "";
362 }
363 
364 GtkWidget *
gis_assistant_get_titlebar(GisAssistant * assistant)365 gis_assistant_get_titlebar (GisAssistant *assistant)
366 {
367   return assistant->titlebar;
368 }
369 
370 static void
update_current_page(GisAssistant * assistant,GisPage * page)371 update_current_page (GisAssistant *assistant,
372                      GisPage      *page)
373 {
374   if (assistant->current_page == page)
375     return;
376 
377   assistant->current_page = page;
378   g_object_notify_by_pspec (G_OBJECT (assistant), obj_props[PROP_TITLE]);
379 
380   update_titlebar (assistant);
381   update_applying_state (assistant);
382   update_navigation_buttons (assistant);
383 
384   gtk_widget_grab_focus (assistant->forward);
385 
386   if (page)
387     gis_page_shown (page);
388 }
389 
390 static void
current_page_changed(GObject * gobject,GParamSpec * pspec,gpointer user_data)391 current_page_changed (GObject    *gobject,
392                       GParamSpec *pspec,
393                       gpointer    user_data)
394 {
395   GisAssistant *assistant = GIS_ASSISTANT (user_data);
396   GtkStack *stack = GTK_STACK (gobject);
397   GtkWidget *new_page = gtk_stack_get_visible_child (stack);
398 
399   update_current_page (assistant, GIS_PAGE (new_page));
400 }
401 
402 void
gis_assistant_locale_changed(GisAssistant * assistant)403 gis_assistant_locale_changed (GisAssistant *assistant)
404 {
405   GList *l;
406 
407   gtk_button_set_label (GTK_BUTTON (assistant->forward), _("_Next"));
408   gtk_button_set_label (GTK_BUTTON (assistant->accept), _("_Accept"));
409   gtk_button_set_label (GTK_BUTTON (assistant->skip), _("_Skip"));
410   gtk_button_set_label (GTK_BUTTON (assistant->back), _("_Previous"));
411   gtk_button_set_label (GTK_BUTTON (assistant->cancel), _("_Cancel"));
412 
413   for (l = assistant->pages; l != NULL; l = l->next)
414     gis_page_locale_changed (l->data);
415 
416   update_titlebar (assistant);
417 }
418 
419 gboolean
gis_assistant_save_data(GisAssistant * assistant,GError ** error)420 gis_assistant_save_data (GisAssistant  *assistant,
421                          GError       **error)
422 {
423   GList *l;
424 
425   for (l = assistant->pages; l != NULL; l = l->next)
426     {
427       if (!gis_page_save_data (l->data, error))
428         return FALSE;
429     }
430 
431   return TRUE;
432 }
433 
434 static void
gis_assistant_init(GisAssistant * assistant)435 gis_assistant_init (GisAssistant *assistant)
436 {
437   gtk_widget_init_template (GTK_WIDGET (assistant));
438 
439   g_signal_connect (assistant->stack, "notify::visible-child",
440                     G_CALLBACK (current_page_changed), assistant);
441 
442   g_signal_connect (assistant->forward, "clicked", G_CALLBACK (go_forward), assistant);
443   g_signal_connect (assistant->accept, "clicked", G_CALLBACK (go_forward), assistant);
444   g_signal_connect (assistant->skip, "clicked", G_CALLBACK (go_forward), assistant);
445 
446   g_signal_connect (assistant->back, "clicked", G_CALLBACK (go_backward), assistant);
447   g_signal_connect (assistant->cancel, "clicked", G_CALLBACK (do_cancel), assistant);
448 
449   gis_assistant_locale_changed (assistant);
450   update_applying_state (assistant);
451 }
452 
453 static void
gis_assistant_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)454 gis_assistant_get_property (GObject    *object,
455 			    guint       prop_id,
456 			    GValue     *value,
457 			    GParamSpec *pspec)
458 {
459   GisAssistant *assistant = GIS_ASSISTANT (object);
460 
461   switch (prop_id)
462     {
463     case PROP_TITLE:
464       g_value_set_string (value, gis_assistant_get_title (assistant));
465       break;
466 
467     default:
468       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
469       break;
470     }
471 }
472 
473 static void
gis_assistant_class_init(GisAssistantClass * klass)474 gis_assistant_class_init (GisAssistantClass *klass)
475 {
476   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
477 
478   gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-assistant.ui");
479 
480   gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAssistant, forward);
481   gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAssistant, accept);
482   gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAssistant, skip);
483   gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAssistant, back);
484   gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAssistant, cancel);
485 
486   gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAssistant, main_layout);
487   gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAssistant, spinner);
488   gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAssistant, titlebar);
489   gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAssistant, title);
490   gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAssistant, stack);
491 
492   gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (klass), visible_child_changed);
493 
494   gobject_class->get_property = gis_assistant_get_property;
495 
496   obj_props[PROP_TITLE] =
497     g_param_spec_string ("title",
498                          "", "",
499                          NULL,
500                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
501 
502 
503   /**
504    * GisAssistant::page-changed:
505    * @assistant: the #GisAssistant
506    *
507    * The ::page-changed signal is emitted when the visible page
508    * changed.
509    */
510   signals[PAGE_CHANGED] =
511     g_signal_new ("page-changed",
512                   G_TYPE_FROM_CLASS (gobject_class),
513                   G_SIGNAL_RUN_LAST,
514                   0,
515                   NULL, NULL, NULL,
516                   G_TYPE_NONE, 0);
517 
518   g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
519 }
520