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