1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2013 Intel Corporation
4  *
5  * This library is free software: you can redistribute it and/or modify it
6  * under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation.
8  *
9  * This library is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12  * for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this library. If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors: Tristan Van Berkom <tristanvb@openismus.com>
18  */
19 
20 #include <libebook/libebook.h>
21 
22 #include "cursor-example.h"
23 #include "cursor-navigator.h"
24 #include "cursor-search.h"
25 #include "cursor-data.h"
26 #include "cursor-slot.h"
27 
28 #define N_SLOTS         10
29 
30 #define INITIAL_TIMEOUT 600
31 #define TICK_TIMEOUT    100
32 
33 #define d(x)
34 
35 typedef enum _TimeoutActivity TimeoutActivity;
36 
37 /* GObjectClass */
38 static void            cursor_example_dispose                 (GObject            *object);
39 
40 /* UI Callbacks */
41 static gboolean        cursor_example_up_button_press         (CursorExample      *example,
42 							       GdkEvent           *event,
43 							       GtkButton          *button);
44 static gboolean        cursor_example_up_button_release       (CursorExample      *example,
45 							       GdkEvent           *event,
46 							       GtkButton          *button);
47 static gboolean        cursor_example_down_button_press       (CursorExample      *example,
48 							       GdkEvent           *event,
49 							       GtkButton          *button);
50 static gboolean        cursor_example_down_button_release     (CursorExample      *example,
51 							       GdkEvent           *event,
52 							       GtkButton          *button);
53 static void            cursor_example_navigator_changed       (CursorExample      *example,
54 							       CursorNavigator    *navigator);
55 static void            cursor_example_sexp_changed            (CursorExample      *example,
56 							       GParamSpec         *pspec,
57 							       CursorSearch       *search);
58 
59 /* EDS Callbacks */
60 static void            cursor_example_refresh                 (EBookClientCursor  *cursor,
61 							       CursorExample      *example);
62 static void            cursor_example_alphabet_changed        (EBookClientCursor  *cursor,
63 							       GParamSpec         *pspec,
64 							       CursorExample      *example);
65 static void            cursor_example_status_changed          (EBookClientCursor  *cursor,
66 							       GParamSpec         *spec,
67 							       CursorExample      *example);
68 
69 /* Utilities */
70 static void            cursor_example_load_alphabet           (CursorExample      *example);
71 static gboolean        cursor_example_move_cursor             (CursorExample      *example,
72 							       EBookCursorOrigin   origin,
73 							       gint                count);
74 static gboolean        cursor_example_load_page               (CursorExample      *example,
75 							       gboolean           *full_results);
76 static void            cursor_example_update_status           (CursorExample      *example);
77 static void            cursor_example_update_current_index    (CursorExample      *example,
78 							       EContact           *contact);
79 static void            cursor_example_ensure_timeout          (CursorExample      *example,
80 							       TimeoutActivity     activity);
81 static void            cursor_example_cancel_timeout          (CursorExample      *example);
82 
83 enum _TimeoutActivity {
84 	TIMEOUT_NONE = 0,
85 	TIMEOUT_UP_INITIAL,
86 	TIMEOUT_UP_TICK,
87 	TIMEOUT_DOWN_INITIAL,
88 	TIMEOUT_DOWN_TICK,
89 };
90 
91 struct _CursorExamplePrivate {
92 	/* Screen widgets */
93 	GtkWidget *browse_up_button;
94 	GtkWidget *browse_down_button;
95 	GtkWidget *progressbar;
96 	GtkWidget *alphabet_label;
97 	GtkWidget *slots[N_SLOTS];
98 	CursorNavigator *navigator;
99 
100 	/* EDS Resources */
101 	EBookClient          *client;
102 	EBookClientCursor    *cursor;
103 
104 	/* Manage the automatic scrolling with button pressed */
105 	guint                 timeout_id;
106 	TimeoutActivity       activity;
107 };
108 
109 G_DEFINE_TYPE_WITH_PRIVATE (CursorExample, cursor_example, GTK_TYPE_WINDOW);
110 
111 /************************************************************************
112  *                          GObjectClass                                *
113  ************************************************************************/
114 static void
cursor_example_class_init(CursorExampleClass * klass)115 cursor_example_class_init (CursorExampleClass *klass)
116 {
117 	GObjectClass *object_class;
118 	GtkWidgetClass *widget_class;
119 	gint i;
120 
121 	object_class = G_OBJECT_CLASS (klass);
122 	object_class->dispose = cursor_example_dispose;
123 
124 	/* Bind to template */
125 	widget_class = GTK_WIDGET_CLASS (klass);
126 	gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/evolution/cursor-example/cursor-example.ui");
127 	gtk_widget_class_bind_template_child_private (widget_class, CursorExample, navigator);
128 	gtk_widget_class_bind_template_child_private (widget_class, CursorExample, browse_up_button);
129 	gtk_widget_class_bind_template_child_private (widget_class, CursorExample, browse_down_button);
130 	gtk_widget_class_bind_template_child_private (widget_class, CursorExample, alphabet_label);
131 	gtk_widget_class_bind_template_child_private (widget_class, CursorExample, progressbar);
132 
133 	for (i = 0; i < N_SLOTS; i++) {
134 		gchar *name = g_strdup_printf ("contact_slot_%d", i + 1);
135 
136 		gtk_widget_class_bind_template_child_full (widget_class, name, FALSE, 0);
137 		g_free (name);
138 	}
139 
140 	gtk_widget_class_bind_template_callback (widget_class, cursor_example_navigator_changed);
141 	gtk_widget_class_bind_template_callback (widget_class, cursor_example_up_button_press);
142 	gtk_widget_class_bind_template_callback (widget_class, cursor_example_up_button_release);
143 	gtk_widget_class_bind_template_callback (widget_class, cursor_example_down_button_press);
144 	gtk_widget_class_bind_template_callback (widget_class, cursor_example_down_button_release);
145 	gtk_widget_class_bind_template_callback (widget_class, cursor_example_sexp_changed);
146 }
147 
148 static void
cursor_example_init(CursorExample * example)149 cursor_example_init (CursorExample *example)
150 {
151 	CursorExamplePrivate *priv;
152 	gint i;
153 
154 	example->priv = priv =
155 		cursor_example_get_instance_private (example);
156 
157 	g_type_ensure (CURSOR_TYPE_NAVIGATOR);
158 	g_type_ensure (CURSOR_TYPE_SEARCH);
159 
160 	gtk_widget_init_template (GTK_WIDGET (example));
161 
162 	for (i = 0; i < N_SLOTS; i++) {
163 
164 		gchar *name = g_strdup_printf ("contact_slot_%d", i + 1);
165 		priv->slots[i] = (GtkWidget *) gtk_widget_get_template_child (GTK_WIDGET (example),
166 									     CURSOR_TYPE_EXAMPLE,
167 									     name);
168 		g_free (name);
169 	}
170 }
171 
172 static void
cursor_example_dispose(GObject * object)173 cursor_example_dispose (GObject *object)
174 {
175 	CursorExample        *example = CURSOR_EXAMPLE (object);
176 	CursorExamplePrivate *priv = example->priv;
177 
178 	cursor_example_cancel_timeout (example);
179 
180 	g_clear_object (&priv->client);
181 	g_clear_object (&priv->cursor);
182 
183 	G_OBJECT_CLASS (cursor_example_parent_class)->dispose (object);
184 }
185 
186 /************************************************************************
187  *                           UI Callbacks                               *
188  ************************************************************************/
189 static gboolean
cursor_example_up_button_press(CursorExample * example,GdkEvent * event,GtkButton * button)190 cursor_example_up_button_press (CursorExample *example,
191                                 GdkEvent *event,
192                                 GtkButton *button)
193 {
194 	d (g_print ("Browse up press\n"));
195 
196 	/* Move the cursor backwards by 1 and then refresh the page */
197 	if (cursor_example_move_cursor (example, E_BOOK_CURSOR_ORIGIN_CURRENT, 0 - 1))
198 		cursor_example_load_page (example, NULL);
199 
200 	cursor_example_ensure_timeout (example, TIMEOUT_UP_INITIAL);
201 
202 	return FALSE;
203 }
204 
205 static gboolean
cursor_example_up_button_release(CursorExample * example,GdkEvent * event,GtkButton * button)206 cursor_example_up_button_release (CursorExample *example,
207                                   GdkEvent *event,
208                                   GtkButton *button)
209 {
210 	d (g_print ("Browse up release\n"));
211 
212 	cursor_example_cancel_timeout (example);
213 
214 	return FALSE;
215 }
216 
217 static gboolean
cursor_example_down_button_press(CursorExample * example,GdkEvent * event,GtkButton * button)218 cursor_example_down_button_press (CursorExample *example,
219                                   GdkEvent *event,
220                                   GtkButton *button)
221 {
222 	d (g_print ("Browse down press\n"));
223 
224 	/* Move the cursor forward by 1 and then refresh the page */
225 	if (cursor_example_move_cursor (example, E_BOOK_CURSOR_ORIGIN_CURRENT, 1))
226 		cursor_example_load_page (example, NULL);
227 
228 	cursor_example_ensure_timeout (example, TIMEOUT_DOWN_INITIAL);
229 
230 	return FALSE;
231 }
232 
233 static gboolean
cursor_example_down_button_release(CursorExample * example,GdkEvent * event,GtkButton * button)234 cursor_example_down_button_release (CursorExample *example,
235                                     GdkEvent *event,
236                                     GtkButton *button)
237 {
238 	d (g_print ("Browse down released\n"));
239 
240 	cursor_example_cancel_timeout (example);
241 
242 	return FALSE;
243 }
244 
245 static void
cursor_example_navigator_changed(CursorExample * example,CursorNavigator * navigator)246 cursor_example_navigator_changed (CursorExample *example,
247                                   CursorNavigator *navigator)
248 {
249 	CursorExamplePrivate *priv = example->priv;
250 	GError               *error = NULL;
251 	gint                  index;
252 	gboolean              full_results = FALSE;
253 
254 	index = cursor_navigator_get_index (priv->navigator);
255 
256 	d (g_print ("Alphabet index changed to: %d\n", index));
257 
258 	/* Move to this index */
259 	if (!e_book_client_cursor_set_alphabetic_index_sync (priv->cursor, index, NULL, &error)) {
260 
261 		if (g_error_matches (error,
262 				     E_CLIENT_ERROR,
263 				     E_CLIENT_ERROR_OUT_OF_SYNC)) {
264 
265 			/* Just ignore the error.
266 			 *
267 			 * The addressbook locale has recently changed, very
268 			 * soon we will receive an alphabet change notification
269 			 * where we will reset the cursor position and reload
270 			 * the alphabet.
271 			 */
272 			d (g_print ("Cursor was temporarily out of sync while setting the alphabetic target\n"));
273 		} else
274 			g_warning ("Failed to move the cursor: %s", error->message);
275 
276 		g_clear_error (&error);
277 	}
278 
279 	/* And load one page full of results starting with this index */
280 	if (!cursor_example_load_page (example, &full_results))
281 		return;
282 
283 	/* If we hit the end of the results (less than a full page) then load the last page of results */
284 	if (!full_results) {
285 		if (cursor_example_move_cursor (example,
286 						E_BOOK_CURSOR_ORIGIN_END,
287 						0 - (N_SLOTS + 1))) {
288 			cursor_example_load_page (example, NULL);
289 		}
290 	}
291 }
292 
293 static void
cursor_example_sexp_changed(CursorExample * example,GParamSpec * pspec,CursorSearch * search)294 cursor_example_sexp_changed (CursorExample *example,
295                              GParamSpec *pspec,
296                              CursorSearch *search)
297 {
298 	CursorExamplePrivate *priv = example->priv;
299 	gboolean              full_results = FALSE;
300 	GError               *error = NULL;
301 	const gchar          *sexp;
302 
303 	sexp = cursor_search_get_sexp (search);
304 
305 	d (g_print ("Search expression changed to: '%s'\n", sexp));
306 
307 	/* Set the search expression */
308 	if (!e_book_client_cursor_set_sexp_sync (priv->cursor, sexp, NULL, &error)) {
309 		g_warning ("Failed to move the cursor: %s", error->message);
310 		g_clear_error (&error);
311 	}
312 
313 	/* And load one page full of results */
314 	if (!cursor_example_load_page (example, &full_results))
315 		return;
316 
317 	/* If we hit the end of the results (less than a full page) then load the last page of results */
318 	if (!full_results)
319 		if (cursor_example_move_cursor (example,
320 						E_BOOK_CURSOR_ORIGIN_END,
321 						0 - (N_SLOTS + 1))) {
322 			cursor_example_load_page (example, NULL);
323 	}
324 }
325 
326 /************************************************************************
327  *                           EDS Callbacks                              *
328  ************************************************************************/
329 static void
cursor_example_refresh(EBookClientCursor * cursor,CursorExample * example)330 cursor_example_refresh (EBookClientCursor *cursor,
331                         CursorExample *example)
332 {
333 	d (g_print ("Cursor refreshed\n"));
334 
335 	/* Refresh the page */
336 	if (cursor_example_load_page (example, NULL))
337 		cursor_example_update_status (example);
338 }
339 
340 static void
cursor_example_alphabet_changed(EBookClientCursor * cursor,GParamSpec * spec,CursorExample * example)341 cursor_example_alphabet_changed (EBookClientCursor *cursor,
342                                  GParamSpec *spec,
343                                  CursorExample *example)
344 {
345 	d (g_print ("Alphabet Changed\n"));
346 
347 	cursor_example_load_alphabet (example);
348 
349 	/* Get the first page of contacts in the addressbook */
350 	if (cursor_example_move_cursor (example, E_BOOK_CURSOR_ORIGIN_BEGIN, 0))
351 		cursor_example_load_page (example, NULL);
352 }
353 
354 static void
cursor_example_status_changed(EBookClientCursor * cursor,GParamSpec * spec,CursorExample * example)355 cursor_example_status_changed (EBookClientCursor *cursor,
356                                GParamSpec *spec,
357                                CursorExample *example)
358 {
359 	d (g_print ("Status changed\n"));
360 
361 	cursor_example_update_status (example);
362 }
363 
364 /************************************************************************
365  *                             Utilities                                *
366  ************************************************************************/
367 static void
cursor_example_load_alphabet(CursorExample * example)368 cursor_example_load_alphabet (CursorExample *example)
369 {
370 	CursorExamplePrivate *priv = example->priv;
371 	const gchar *const   *alphabet;
372 
373 	/* Update the alphabet on the navigator */
374 	alphabet = e_book_client_cursor_get_alphabet (priv->cursor, NULL, NULL, NULL, NULL);
375 	cursor_navigator_set_alphabet (priv->navigator, alphabet);
376 
377 	/* Reset navigator to the beginning */
378 	g_signal_handlers_block_by_func (priv->navigator, cursor_example_navigator_changed, example);
379 	cursor_navigator_set_index (priv->navigator, 0);
380 	g_signal_handlers_unblock_by_func (priv->navigator, cursor_example_navigator_changed, example);
381 }
382 
383 static gboolean
cursor_example_move_cursor(CursorExample * example,EBookCursorOrigin origin,gint count)384 cursor_example_move_cursor (CursorExample *example,
385                             EBookCursorOrigin origin,
386                             gint count)
387 {
388 	CursorExamplePrivate *priv = example->priv;
389 	GError               *error = NULL;
390 	gint                  n_results;
391 
392 	n_results = e_book_client_cursor_step_sync (
393 		priv->cursor,
394 		E_BOOK_CURSOR_STEP_MOVE,
395 		origin,
396 		count,
397 		NULL, /* Result list */
398 		NULL, /* GCancellable */
399 		&error);
400 
401 	if (n_results < 0) {
402 
403 		if (g_error_matches (error,
404 				     E_CLIENT_ERROR,
405 				     E_CLIENT_ERROR_OUT_OF_SYNC)) {
406 
407 			/* The addressbook has very recently been modified,
408 			 * very soon we will receive a "refresh" signal and
409 			 * automatically reload the current page position.
410 			 */
411 			d (g_print ("Cursor was temporarily out of sync while moving\n"));
412 
413 		} else if (g_error_matches (error,
414 					    E_CLIENT_ERROR,
415 					    E_CLIENT_ERROR_QUERY_REFUSED)) {
416 
417 			d (g_print ("End of list was reached\n"));
418 
419 		} else
420 			g_warning ("Failed to move the cursor: %s", error->message);
421 
422 		g_clear_error (&error);
423 
424 	}
425 
426 	return n_results >= 0;
427 }
428 
429 /* Loads a page at the current cursor position, returns
430  * FALSE if there was an error.
431  */
432 static gboolean
cursor_example_load_page(CursorExample * example,gboolean * full_results)433 cursor_example_load_page (CursorExample *example,
434                           gboolean *full_results)
435 {
436 	CursorExamplePrivate *priv = example->priv;
437 	GError               *error = NULL;
438 	GSList               *results = NULL;
439 	gint                  n_results;
440 
441 	/* Fetch N_SLOTS contacts after the current cursor position,
442 	 * without modifying the current cursor position
443 	 */
444 	n_results = e_book_client_cursor_step_sync (
445 		priv->cursor,
446 		E_BOOK_CURSOR_STEP_FETCH,
447 		E_BOOK_CURSOR_ORIGIN_CURRENT,
448 		N_SLOTS,
449 		&results,
450 		NULL, /* GCancellable */
451 		&error);
452 
453 	if (n_results < 0) {
454 		if (g_error_matches (error,
455 				     E_CLIENT_ERROR,
456 				     E_CLIENT_ERROR_OUT_OF_SYNC)) {
457 
458 			/* The addressbook has very recently been modified,
459 			 * very soon we will receive a "refresh" signal and
460 			 * automatically reload the current page position.
461 			 */
462 			d (g_print ("Cursor was temporarily out of sync while loading page\n"));
463 
464 		} else if (g_error_matches (error,
465 					    E_CLIENT_ERROR,
466 					    E_CLIENT_ERROR_QUERY_REFUSED)) {
467 
468 			d (g_print ("End of list was reached\n"));
469 
470 		} else
471 			g_warning ("Failed to move the cursor: %s", error->message);
472 
473 		g_clear_error (&error);
474 
475 	} else {
476 		/* Display the results */
477 		EContact             *contact;
478 		gint                  i;
479 
480 		/* Fill the page with results for the current cursor position
481 		 */
482 		for (i = 0; i < N_SLOTS; i++) {
483 			contact = g_slist_nth_data (results, i);
484 
485 			/* For the first contact, give some visual feedback about where we
486 			 * are in the list, which alphabet character we're browsing right now.
487 			 */
488 			if (i == 0 && contact)
489 				cursor_example_update_current_index (example, contact);
490 
491 			cursor_slot_set_from_contact (CURSOR_SLOT (priv->slots[i]), contact);
492 		}
493 	}
494 
495 	if (full_results)
496 		*full_results = (n_results == N_SLOTS);
497 
498 	g_slist_free_full (results, (GDestroyNotify) g_object_unref);
499 
500 	return n_results >= 0;
501 }
502 
503 static void
cursor_example_update_status(CursorExample * example)504 cursor_example_update_status (CursorExample *example)
505 {
506 	CursorExamplePrivate *priv = example->priv;
507 	gint                  total, position;
508 	gchar                *txt;
509 	gboolean              up_sensitive;
510 	gboolean              down_sensitive;
511 	gdouble               fraction;
512 
513 	total = e_book_client_cursor_get_total (priv->cursor);
514 	position = e_book_client_cursor_get_position (priv->cursor);
515 
516 	/* Set the label showing the cursor position and total contacts */
517 	txt = g_strdup_printf ("Position %d / Total %d", position, total);
518 	gtk_progress_bar_set_text (GTK_PROGRESS_BAR (priv->progressbar), txt);
519 	g_free (txt);
520 
521 	/* Give visual feedback on how far we are into the contact list */
522 	fraction = position * 1.0F / (total - N_SLOTS);
523 	gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->progressbar), fraction);
524 
525 	/* Update sensitivity of buttons */
526 	if (total <= N_SLOTS) {
527 		/* If the amount of contacts is less than the amount of visual slots,
528 		 * then we cannot browse up and down
529 		 */
530 		up_sensitive = FALSE;
531 		down_sensitive = FALSE;
532 	} else {
533 		/* The cursor is always pointing directly before
534 		 * the first contact visible in the view, so if the
535 		 * cursor is passed the first contact we can rewind.
536 		 */
537 		up_sensitive = position > 0;
538 
539 		/* If more than N_SLOTS contacts remain, then
540 		 * we can still scroll down */
541 		down_sensitive = position < total - N_SLOTS;
542 	}
543 
544 	gtk_widget_set_sensitive (priv->browse_up_button, up_sensitive);
545 	gtk_widget_set_sensitive (priv->browse_down_button, down_sensitive);
546 }
547 
548 /* This is called when refreshing the window contents with
549  * the first contact shown in the window.
550  */
551 static void
cursor_example_update_current_index(CursorExample * example,EContact * contact)552 cursor_example_update_current_index (CursorExample *example,
553                                      EContact *contact)
554 {
555 	CursorExamplePrivate *priv = example->priv;
556 	const gchar *const   *labels;
557 	gint                  index;
558 
559 	/* Fetch the alphabetic index for this contact */
560 	index = e_book_client_cursor_get_contact_alphabetic_index (priv->cursor, contact);
561 
562 	/* Refresh the current alphabet index indicator.
563 	 *
564 	 * The index returned by e_book_client_cursor_get_contact_alphabetic_index() is
565 	 * a valid position into the array returned by e_book_client_cursor_get_alphabet().
566 	 */
567 	labels = e_book_client_cursor_get_alphabet (priv->cursor, NULL, NULL, NULL, NULL);
568 	gtk_label_set_text (GTK_LABEL (priv->alphabet_label), labels[index]);
569 
570 	/* Update the current scroll position (and avoid reacting to the value change)
571 	 */
572 	if (contact) {
573 		g_signal_handlers_block_by_func (priv->navigator, cursor_example_navigator_changed, example);
574 		cursor_navigator_set_index (priv->navigator, index);
575 		g_signal_handlers_unblock_by_func (priv->navigator, cursor_example_navigator_changed, example);
576 	}
577 }
578 
579 static gboolean
cursor_example_timeout(CursorExample * example)580 cursor_example_timeout (CursorExample *example)
581 {
582 	CursorExamplePrivate *priv = example->priv;
583 	gboolean can_move;
584 
585 	switch (priv->activity) {
586 	case TIMEOUT_NONE:
587 		break;
588 
589 	case TIMEOUT_UP_INITIAL:
590 	case TIMEOUT_UP_TICK:
591 
592 		/* Move the cursor backwards by 1 and then refresh the page */
593 		if (cursor_example_move_cursor (example, E_BOOK_CURSOR_ORIGIN_CURRENT, 0 - 1)) {
594 			cursor_example_load_page (example, NULL);
595 			cursor_example_ensure_timeout (example, TIMEOUT_UP_TICK);
596 		} else
597 			cursor_example_cancel_timeout (example);
598 
599 		break;
600 
601 	case TIMEOUT_DOWN_INITIAL:
602 	case TIMEOUT_DOWN_TICK:
603 
604 		/* Avoid scrolling past the end of the list - N_SLOTS */
605 		can_move = (e_book_client_cursor_get_position (priv->cursor) <
606 			    e_book_client_cursor_get_total (priv->cursor) - N_SLOTS);
607 
608 		/* Move the cursor forwards by 1 and then refresh the page */
609 		if (can_move &&
610 		    cursor_example_move_cursor (example, E_BOOK_CURSOR_ORIGIN_CURRENT, 1)) {
611 			cursor_example_load_page (example, NULL);
612 			cursor_example_ensure_timeout (example, TIMEOUT_DOWN_TICK);
613 		} else
614 			cursor_example_cancel_timeout (example);
615 
616 		break;
617 	}
618 
619 	return FALSE;
620 }
621 
622 static void
cursor_example_ensure_timeout(CursorExample * example,TimeoutActivity activity)623 cursor_example_ensure_timeout (CursorExample *example,
624                                TimeoutActivity activity)
625 {
626 	CursorExamplePrivate *priv = example->priv;
627 	guint                 timeout = 0;
628 
629 	cursor_example_cancel_timeout (example);
630 
631 	if (activity == TIMEOUT_UP_INITIAL ||
632 	    activity == TIMEOUT_DOWN_INITIAL)
633 		timeout = INITIAL_TIMEOUT;
634 	else
635 		timeout = TICK_TIMEOUT;
636 
637 	priv->activity = activity;
638 
639 	priv->timeout_id =
640 		g_timeout_add (
641 			timeout,
642 			(GSourceFunc) cursor_example_timeout,
643 			example);
644 }
645 
646 static void
cursor_example_cancel_timeout(CursorExample * example)647 cursor_example_cancel_timeout (CursorExample *example)
648 {
649 	CursorExamplePrivate *priv = example->priv;
650 
651 	if (priv->timeout_id) {
652 		g_source_remove (priv->timeout_id);
653 		priv->timeout_id = 0;
654 	}
655 }
656 
657 /************************************************************************
658  *                                API                                   *
659  ************************************************************************/
660 GtkWidget *
cursor_example_new(const gchar * vcard_path)661 cursor_example_new (const gchar *vcard_path)
662 {
663   CursorExample *example;
664   CursorExamplePrivate *priv;
665 
666   example = g_object_new (CURSOR_TYPE_EXAMPLE, NULL);
667   priv = example->priv;
668 
669   priv->client = cursor_load_data (vcard_path, &priv->cursor);
670 
671   cursor_example_load_alphabet (example);
672 
673   /* Load the first page of results */
674   cursor_example_load_page (example, NULL);
675   cursor_example_update_status (example);
676 
677   g_signal_connect (priv->cursor, "refresh",
678 		    G_CALLBACK (cursor_example_refresh), example);
679   g_signal_connect (priv->cursor, "notify::alphabet",
680 		    G_CALLBACK (cursor_example_alphabet_changed), example);
681   g_signal_connect (priv->cursor, "notify::total",
682 		    G_CALLBACK (cursor_example_status_changed), example);
683   g_signal_connect (priv->cursor, "notify::position",
684 		    G_CALLBACK (cursor_example_status_changed), example);
685 
686   g_message ("Cursor example started in locale: %s",
687 	     e_book_client_get_locale (priv->client));
688 
689   return (GtkWidget *) example;
690 }
691