Include IMs in contact detail pop-up.
[birthday] / src / birthday.c
1 /*
2  *  Birthday application for Maemo.
3  *  Copyright (C) 2010 Roman Moravcik
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <time.h>
29
30 #include <libosso.h>
31
32 #include <glib.h>
33 #include <glib/gi18n.h>
34
35 #include <gtk/gtk.h>
36 #include <hildon/hildon.h>
37
38 #include <libebook/e-book.h>
39 #include <libosso-abook/osso-abook.h>
40
41 #include <mce/dbus-names.h>
42 #include <mce/mode-names.h>
43
44 #define MCE_MATCH_RULE "type='signal',interface='" MCE_SIGNAL_IF "',member='" MCE_DEVICE_ORIENTATION_SIG "'"
45
46 #define _HL(str) dgettext("hildon-libs",str)
47
48 enum
49 {
50         COLUMN_AVATAR = 0,
51         COLUMN_DISPLAY,
52         COLUMN_FULLNAME,
53         COLUMN_NEXT_BIRTHDAY,
54         COLUMN_ABOOK_CONTACT,
55         NUM_COLS
56 };
57
58 /* Application UI data struct */
59 typedef struct _BirthdayData BirthdayData;
60 struct _BirthdayData {
61         GtkWidget *window;
62         GtkWidget *label;
63         GtkWidget *view;
64         GtkWidget *search;
65
66         GtkTreeViewColumn *display_column;
67
68         GtkWidget *tree_view;
69         GtkTreeModel *sorted;
70         GtkTreeModel *filter;
71
72         gchar *searched_name;
73         gboolean found;
74
75         guint n_contacts;
76 };
77
78 static void set_portrait_mode (BirthdayData *priv, gboolean enable);
79
80 static gboolean
81 is_portrait_mode (osso_context_t *osso_context)
82 {
83         osso_rpc_t ret;
84         gboolean result = FALSE;
85
86         if (osso_rpc_run_system (osso_context, MCE_SERVICE, MCE_REQUEST_PATH,
87                                  MCE_REQUEST_IF, MCE_DEVICE_ORIENTATION_GET,
88                                  &ret, DBUS_TYPE_INVALID) == OSSO_OK) {
89
90                 if (strcmp (ret.value.s, MCE_ORIENTATION_PORTRAIT) == 0) {
91                         result = TRUE;
92                 }
93                 osso_rpc_free_val (&ret);
94         } else {
95                 g_critical ("ERROR: Call do DBus failed\n");
96         }
97         return result;
98 }
99
100 static DBusHandlerResult
101 dbus_handle_mce_message (DBusConnection *connection,
102                          DBusMessage *message,
103                          gpointer data)
104 {
105         DBusMessageIter iter;
106         const gchar *orientation = NULL;
107         BirthdayData *priv;
108
109         g_return_val_if_fail (data, DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
110         priv = (BirthdayData *) data;
111
112         if (dbus_message_is_signal (message, MCE_SIGNAL_IF, MCE_DEVICE_ORIENTATION_SIG)) {
113                 if (dbus_message_iter_init (message, &iter)) {
114                         dbus_message_iter_get_basic(&iter, &orientation);
115                         if (orientation) {
116                                 if (!strcmp (orientation, MCE_ORIENTATION_PORTRAIT))
117                                         set_portrait_mode (priv, TRUE);
118                                 else
119                                         set_portrait_mode (priv, FALSE);
120                         }
121                 }
122         }
123         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
124 }
125
126 static gboolean
127 birthday_filered_view_visible_func (GtkTreeModel *model,
128                                     GtkTreeIter *iter,
129                                     gpointer data)
130 {
131         BirthdayData *priv;
132         gchar *fullname = NULL, *ascii_searched_name = NULL, *ascii_fullname = NULL;
133         gboolean found = FALSE;
134
135         g_return_val_if_fail (data, FALSE);
136         priv = (BirthdayData *) data;
137
138         if (priv->searched_name == NULL) {
139                 priv->found = TRUE;
140                 return TRUE;
141         }
142
143         ascii_searched_name = g_ascii_strdown (priv->searched_name, strlen (priv->searched_name));
144
145         gtk_tree_model_get (model, iter, COLUMN_FULLNAME, &fullname, -1);
146         if (fullname) {
147                 ascii_fullname = g_ascii_strdown (fullname,  strlen (fullname));
148                 g_free (fullname);
149         }
150
151         if (g_strstr_len (ascii_fullname, strlen (ascii_fullname), ascii_searched_name) != NULL)
152                 found = TRUE;
153
154         if (ascii_searched_name)
155                 g_free (ascii_searched_name);
156
157         if (ascii_fullname)
158                 g_free (ascii_fullname);
159
160         if (found)
161                 priv->found = TRUE;
162
163         return found;
164 }
165
166 static void
167 sort_by_name_clicked (GtkButton *button,
168                       gpointer data)
169 {
170         BirthdayData *priv;
171
172         g_return_if_fail (data);
173         priv = (BirthdayData *) data;
174
175         if (priv->sorted) {
176                 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->sorted),
177                                                       COLUMN_FULLNAME, GTK_SORT_ASCENDING);
178         }
179 }
180
181 static void
182 sort_by_date_clicked (GtkButton *button,
183                       gpointer data)
184 {
185         BirthdayData *priv;
186
187         g_return_if_fail (data);
188         priv = (BirthdayData *) data;
189
190         if (priv->sorted) {
191                 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->sorted),
192                                                       COLUMN_NEXT_BIRTHDAY, GTK_SORT_ASCENDING);
193         }
194 }
195
196 static void
197 search_menu_clicked (GtkButton *button,
198                      gpointer data)
199 {
200         BirthdayData *priv;
201         GtkWidget *entry;
202
203         g_return_if_fail (data);
204         priv = (BirthdayData *) data;
205
206         /* show search bar */
207         gtk_widget_show (priv->search);
208
209         /* focus on search entry */
210         entry  = g_object_get_data (G_OBJECT (priv->search), "entry");
211         gtk_entry_set_text (GTK_ENTRY (entry), "");
212         gtk_widget_grab_focus (GTK_WIDGET (entry));
213
214         /* refilter tree view */
215         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));
216 }
217
218 static void
219 on_search_entry_changed (GtkEditable *editable,
220                          gpointer data)
221 {
222         GtkTreeSelection *selection;
223         BirthdayData *priv;
224
225         g_return_if_fail (data);
226         priv = (BirthdayData *) data;
227
228         priv->found = FALSE;
229
230         if (priv->searched_name)
231                 g_free (priv->searched_name);
232         priv->searched_name = g_strdup (gtk_entry_get_text (GTK_ENTRY (editable)));
233
234         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->tree_view));
235
236         /* ugly hack, set back mode to selection none to not generate "changed"
237            signal during re-filtering  */
238         gtk_tree_selection_set_mode (selection, GTK_SELECTION_NONE);
239
240         /* refilter tree view */
241         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));
242
243         if (priv->found) {
244                 /* hide label */
245                 gtk_widget_hide (priv->label);
246
247                 /* show tree view */
248                 gtk_widget_show (priv->view);
249         } else {
250                 /* hide label */
251                 gtk_widget_show (priv->label);
252                 gtk_label_set_text (GTK_LABEL (priv->label), _("No search results"));
253
254                 /* show tree view */
255                 gtk_widget_hide (priv->view);
256         }
257
258         /* ugly, but working way how to scroll to the first row */
259         gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (priv->tree_view),
260                                       gtk_tree_path_new_from_string ("0"), NULL, FALSE, 0, 0);
261
262         /* ugly hack, set back mode to single selection */
263         gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
264
265         /* unselect selected rows */
266         gtk_tree_selection_unselect_all (selection);
267 }
268
269 static void
270 on_search_close_clicked (GtkButton *button,
271                          gpointer data)
272 {
273         GtkTreeSelection *selection;
274         BirthdayData *priv;
275
276         g_return_if_fail (data);
277         priv = (BirthdayData *) data;
278
279         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->tree_view));
280
281         /* ugly hack, set back mode to selection none to not generate "changed"
282            signal during re-filtering  */
283         gtk_tree_selection_set_mode (selection, GTK_SELECTION_NONE);
284
285         /* hide search bar */
286         gtk_widget_hide (priv->search);
287
288         /* hide label */
289         gtk_widget_hide (priv->label);
290
291         /* show tree view */
292         gtk_widget_show (priv->view);
293
294         /* clear searched name */
295         if (priv->searched_name)
296                 g_free (priv->searched_name);
297         priv->searched_name = NULL;
298
299         /* refilter tree view */
300         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));
301
302         /* ugly, but working way how to scroll to the first row */
303         gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (priv->tree_view),
304                                       gtk_tree_path_new_from_string ("0"), NULL, FALSE, 0, 0);
305
306         /* ugly hack, set back mode to single selection */
307         gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
308
309         /* unselect selected rows */
310         gtk_tree_selection_unselect_all (selection);
311 }
312
313
314 static gboolean
315 on_key_press_event (GtkWidget *widget,
316                     GdkEventKey *event,
317                     gpointer data)
318 {
319         BirthdayData *priv;
320
321         g_return_val_if_fail (data, TRUE);
322         priv = (BirthdayData *) data;
323
324         if (priv->n_contacts == 0)
325                 return FALSE;
326
327         if ((event->keyval > GDK_space) && (event->keyval <= GDK_stricteq) && !GTK_WIDGET_VISIBLE (priv->search)) {
328                 GtkWidget *entry;
329
330                 /* show search bar */
331                 gtk_widget_show (priv->search);
332
333                 /* focus on search entry */
334                 entry  = g_object_get_data (G_OBJECT (priv->search), "entry");
335                 gtk_entry_set_text (GTK_ENTRY (entry), "");
336                 gtk_widget_grab_focus (GTK_WIDGET (entry));
337
338                 /* refilter tree view */
339                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));
340         }
341
342         return FALSE;
343 }
344
345 static void
346 on_selection_changed (GtkTreeSelection *selection,
347                       gpointer data)
348 {
349         BirthdayData *priv;
350         GtkTreeModel *model;
351         GtkTreeIter iter;
352
353         g_return_if_fail (data);
354         priv = (BirthdayData *) data;
355
356         if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
357                 OssoABookContact *abook_contact = NULL;
358
359                 /* unselect selected rows */
360                 gtk_tree_selection_unselect_all (selection);
361
362                 gtk_tree_model_get (model, &iter, COLUMN_ABOOK_CONTACT, &abook_contact, -1);
363
364                 if (abook_contact) {
365                         GtkWidget *starter, *dialog;
366                         OssoABookContactDetailStore *store;
367                         OssoABookContactAction actions[9] = {OSSO_ABOOK_CONTACT_ACTION_TEL,
368                                                              OSSO_ABOOK_CONTACT_ACTION_SMS,
369                                                              OSSO_ABOOK_CONTACT_ACTION_CHATTO,
370                                                              OSSO_ABOOK_CONTACT_ACTION_VOIPTO,
371                                                              OSSO_ABOOK_CONTACT_ACTION_VOIPTO_AUDIO,
372                                                              OSSO_ABOOK_CONTACT_ACTION_VOIPTO_VIDEO,
373                                                              OSSO_ABOOK_CONTACT_ACTION_MAILTO,
374                                                              OSSO_ABOOK_CONTACT_ACTION_CREATE_ACCOUNT,
375                                                              OSSO_ABOOK_CONTACT_ACTION_BIND};
376
377                         store = osso_abook_contact_detail_store_new (abook_contact,
378                                                                      OSSO_ABOOK_CONTACT_DETAIL_EMAIL |
379                                                                      OSSO_ABOOK_CONTACT_DETAIL_PHONE |
380                                                                      OSSO_ABOOK_CONTACT_DETAIL_IM_VOICE |
381                                                                      OSSO_ABOOK_CONTACT_DETAIL_IM_VIDEO |
382                                                                      OSSO_ABOOK_CONTACT_DETAIL_IM_CHAT |
383                                                                      OSSO_ABOOK_CONTACT_DETAIL_OTHERS |
384                                                                      OSSO_ABOOK_CONTACT_DETAIL_SMS);
385
386                         starter = osso_abook_touch_contact_starter_new_with_store (store,
387                                                                                    (OssoABookContactAction *) &actions,
388                                                                                    sizeof (actions));
389
390                         dialog = osso_abook_touch_contact_starter_dialog_new (GTK_WINDOW (priv->window),
391                                                                               OSSO_ABOOK_TOUCH_CONTACT_STARTER (starter));
392                         gtk_widget_show_all (dialog);
393                         gtk_dialog_run (GTK_DIALOG (dialog));
394                 }
395         }
396 }
397
398 static unsigned int
399 calc_age (EContactDate *bdate, time_t current_date)
400 {
401         struct tm *current_date_tm;
402         struct tm bday_tm;
403         int age = 0;
404
405         current_date_tm = gmtime (&current_date);
406
407         bday_tm.tm_sec = 0;
408         bday_tm.tm_min = 0;
409         bday_tm.tm_hour = 0;
410         bday_tm.tm_mday = bdate->day;
411         bday_tm.tm_mon = bdate->month - 1;
412         bday_tm.tm_year = current_date_tm->tm_year;
413         bday_tm.tm_isdst = current_date_tm->tm_isdst;
414
415         if (mktime (&bday_tm) > current_date) {
416                 age = (current_date_tm->tm_year + 1900) - bdate->year - 1;
417         } else {
418                 age = (current_date_tm->tm_year + 1900) - bdate->year;
419         }
420
421         if (age < 0)
422                 age = 0;
423
424         return age;
425 }
426
427 static unsigned int
428 calc_next_bday (EContactDate *bdate, time_t current_date)
429 {
430         struct tm current_bday_tm, next_bday_tm;
431         struct tm *current_date_tm;
432         time_t current_bday, next_bday;
433
434         current_date_tm = gmtime (&current_date);
435
436         current_bday_tm.tm_sec = 0;
437         current_bday_tm.tm_min = 0;
438         current_bday_tm.tm_hour = 0;
439         current_bday_tm.tm_mday = bdate->day;
440         current_bday_tm.tm_mon = bdate->month - 1;
441         current_bday_tm.tm_year = current_date_tm->tm_year;
442         current_bday_tm.tm_isdst = current_date_tm->tm_isdst;
443         current_bday = mktime (&current_bday_tm);
444
445         if (current_date > current_bday) {
446                 next_bday_tm.tm_sec = 0;
447                 next_bday_tm.tm_min = 0;
448                 next_bday_tm.tm_hour = 0;
449                 next_bday_tm.tm_mday = bdate->day;
450                 next_bday_tm.tm_mon = bdate->month - 1;
451                 next_bday_tm.tm_year = current_date_tm->tm_year + 1;
452                 next_bday_tm.tm_isdst = current_date_tm->tm_isdst;
453                 next_bday = mktime (&next_bday_tm);
454         } else {
455                 next_bday = current_bday;
456         }
457
458         return (next_bday - current_date) / 86400;
459 }
460
461 static gchar *
462 get_text_font_by_name (const gchar *name)
463 {
464         GtkSettings *settings;
465         GtkStyle *style;
466
467         settings = gtk_settings_get_default ();
468         style = gtk_rc_get_style_by_paths (settings, name, NULL, G_TYPE_NONE);
469         return pango_font_description_to_string (style->font_desc);
470 }
471
472 static gchar *
473 get_text_color_by_name (const gchar *name)
474 {
475         GtkSettings *settings;
476         GtkStyle *style;
477         GdkColor color;
478
479         settings = gtk_settings_get_default ();
480         style = gtk_rc_get_style_by_paths (settings, "GtkButton", "osso-logical-colors", G_OBJECT_TYPE(gtk_button_new()));
481         gtk_style_lookup_color (style, name, &color);
482         return gdk_color_to_string (&color);
483 }
484
485 static void
486 set_portrait_mode (BirthdayData *priv,
487                    gboolean enable)
488 {
489         g_return_if_fail (priv);
490
491         if (enable) {
492                 gtk_tree_view_column_set_fixed_width (priv->display_column, 389);
493                 hildon_gtk_window_set_portrait_flags (GTK_WINDOW (priv->window),
494                                                       HILDON_PORTRAIT_MODE_REQUEST);
495         } else {
496                 gtk_tree_view_column_set_fixed_width (priv->display_column, 709);
497                 hildon_gtk_window_set_portrait_flags (GTK_WINDOW (priv->window),
498                                                       ~HILDON_PORTRAIT_MODE_REQUEST);
499         }
500 }
501
502 static GtkListStore *
503 create_bday_liststore (BirthdayData *priv, GList *contacts)
504 {
505         GtkListStore *store;
506         GtkTreeIter iter;
507         GList *contact;
508         gchar *text_font = NULL;
509         gchar *text_color = NULL;
510         guint n_contacts = 0;
511         time_t current_date;
512         struct tm *current_date_tm;
513
514         g_return_val_if_fail (priv, NULL);
515
516         text_font = get_text_font_by_name ("SmallSystemFont");
517         text_color = get_text_color_by_name ("SecondaryTextColor");
518
519         current_date = time (NULL);
520
521         /* set hour, minute, second to 0 */
522         current_date_tm = gmtime (&current_date);
523         current_date_tm->tm_sec = 0;
524         current_date_tm->tm_min = 0;
525         current_date_tm->tm_hour = 0;
526         current_date = mktime (current_date_tm);
527
528         store = gtk_list_store_new(NUM_COLS,
529                                    GDK_TYPE_PIXBUF,     /* COLUMN_AVATAR */
530                                    G_TYPE_STRING,       /* COLUMN_DISPLAY */
531                                    G_TYPE_STRING,       /* COLUMN_FULLNAME */
532                                    G_TYPE_INT,          /* COLUMN_NEXT_BIRTHDAY */
533                                    G_TYPE_POINTER);     /* COLUMN_ABOOK_CONTACT */
534
535         for (contact = contacts; contact != NULL; contact = contact->next) {
536                 EContactDate *bdate = NULL;
537
538                 bdate = e_contact_get (E_CONTACT (contact->data), E_CONTACT_BIRTH_DATE);
539                 if (bdate) {
540                         EContactPhoto *photo = NULL;
541                         GError *error = NULL;
542                         GdkPixbuf *avatar = NULL;
543                         gchar *fullname = NULL;
544                         guint age = 0, next_birthday = 0;
545                         gchar *display_column = NULL;
546                         gchar *next_birthday_text = NULL;
547                         struct tm birthday_tm;
548                         gchar birthday_text[11];
549                         OssoABookContact *abook_contact;
550
551                         photo = e_contact_get (E_CONTACT (contact->data), E_CONTACT_PHOTO);
552                         if (photo) {
553                                 if (photo->type == E_CONTACT_PHOTO_TYPE_INLINED) {
554                                         GdkPixbufLoader *loader;
555
556                                         loader = gdk_pixbuf_loader_new ();
557                                         if (gdk_pixbuf_loader_write (loader, (guchar *) photo->data.inlined.data, photo->data.inlined.length, NULL))
558                                                 avatar = gdk_pixbuf_loader_get_pixbuf (loader);
559
560                                 } else {
561                                         gchar *avatar_filename = NULL;
562
563                                         avatar_filename = g_filename_from_uri (photo->data.uri, NULL, NULL);
564                                         if (avatar_filename) {
565                                                 avatar = gdk_pixbuf_new_from_file (avatar_filename, &error);
566                                                 g_free (avatar_filename);
567                                         }
568                                 }
569
570                                 if (avatar) {
571                                         gint height = gdk_pixbuf_get_height (avatar);
572                                         if (height != 48) {
573                                                 gint new_height = 48;
574                                                 gint new_width = new_height * gdk_pixbuf_get_width (avatar) / height;
575                                                 avatar = gdk_pixbuf_scale_simple (avatar, new_width, new_height, GDK_INTERP_BILINEAR);
576                                         }
577                                 }
578                                 e_contact_photo_free (photo);
579                                 photo = NULL;
580                         } else {
581                                 avatar = gdk_pixbuf_new_from_file ("/usr/share/icons/hicolor/48x48/hildon/general_default_avatar.png", &error);
582                         }
583
584                         fullname = e_contact_get (E_CONTACT (contact->data), E_CONTACT_FULL_NAME);
585                         if (!fullname) {
586                                 fullname = e_contact_get (E_CONTACT (contact->data), E_CONTACT_GIVEN_NAME);
587                                 if (!fullname) {
588                                         fullname = e_contact_get (E_CONTACT (contact->data), E_CONTACT_FAMILY_NAME);
589                                         if (!fullname) {
590                                                 fullname = e_contact_get (E_CONTACT (contact->data), E_CONTACT_NICKNAME);
591                                                 if (!fullname) {
592                                                         fullname = e_contact_get (E_CONTACT (contact->data), E_CONTACT_ORG);
593                                                 }
594                                         }
595                                 }
596                         }
597
598                         birthday_tm.tm_sec = 0;
599                         birthday_tm.tm_min = 0;
600                         birthday_tm.tm_hour = 0;
601                         birthday_tm.tm_mday = bdate->day;
602                         birthday_tm.tm_mon = bdate->month - 1;
603                         birthday_tm.tm_year = bdate->year - 1900;
604                         strftime (birthday_text, 11, _HL("wdgt_va_date"), &birthday_tm);
605
606                         age = calc_age(bdate, current_date);
607                         next_birthday = calc_next_bday(bdate, current_date);
608
609                         if (next_birthday == 0)
610                                 next_birthday_text = g_strdup_printf (_("has birthday today"));
611                         else
612                                 next_birthday_text = g_strdup_printf (ngettext ("will have birthday tomorrow",
613                                                                                 "will have birthday in %d days", next_birthday),
614                                                                       next_birthday);
615
616                         display_column = g_strdup_printf ("%s <span font_desc=\"%s\" foreground=\"%s\"><sup>(%d)</sup>\n%s, %s</span>",
617                                                          fullname, text_font, text_color, age, birthday_text, next_birthday_text);
618
619                         abook_contact = osso_abook_contact_new_from_template (E_CONTACT (contact->data));
620
621                         gtk_list_store_append (store, &iter);
622                         gtk_list_store_set (store, &iter,
623                                             COLUMN_AVATAR, avatar,
624                                             COLUMN_DISPLAY, display_column,
625                                             COLUMN_FULLNAME, fullname,
626                                             COLUMN_NEXT_BIRTHDAY, next_birthday,
627                                             COLUMN_ABOOK_CONTACT, abook_contact,
628                                             -1);
629                         n_contacts++;
630
631                         if (display_column)
632                                 g_free (display_column);
633                         display_column = NULL;
634
635                         if (fullname)
636                                 g_free (fullname);
637                         fullname = NULL;
638
639                         if (next_birthday_text)
640                                 g_free (next_birthday_text);
641                         next_birthday_text = NULL;
642
643                         e_contact_date_free (bdate);
644                 }
645                 bdate = NULL;
646         }
647
648         if (text_font)
649                 g_free (text_font);
650
651         if (text_color)
652                 g_free (text_color);
653
654         priv->n_contacts = n_contacts;
655         return store;
656 }
657
658 static void
659 create_search_bar (BirthdayData *priv)
660 {
661         GtkWidget *entry, *button;
662         GtkEntryCompletion *completion;
663
664         g_return_if_fail (priv);
665
666         /* search hbox */
667         priv->search = gtk_hbox_new (FALSE, HILDON_MARGIN_DEFAULT);
668
669         /* search entry */
670         entry = hildon_entry_new (HILDON_SIZE_FINGER_HEIGHT);
671         hildon_gtk_entry_set_input_mode (GTK_ENTRY (entry), HILDON_GTK_INPUT_MODE_FULL);
672         gtk_box_pack_start (GTK_BOX (priv->search), entry, TRUE, TRUE, 0);
673
674         completion = gtk_entry_completion_new ();
675         gtk_entry_completion_set_inline_completion (completion, TRUE);
676         gtk_entry_completion_set_popup_completion (completion, FALSE);
677         gtk_entry_set_completion (GTK_ENTRY (entry), completion);
678
679         /* clear button */
680         button = GTK_WIDGET (gtk_tool_button_new (gtk_image_new_from_icon_name
681                              ("general_close", (GtkIconSize) HILDON_ICON_PIXEL_SIZE_FINGER), "Clear"));
682         gtk_box_pack_end (GTK_BOX (priv->search), button, FALSE, TRUE, 0);
683
684         /* search signals */
685         g_signal_connect (entry, "changed", G_CALLBACK (on_search_entry_changed), priv);
686         g_signal_connect (button, "clicked", G_CALLBACK (on_search_close_clicked), priv);
687
688         g_object_set_data (G_OBJECT (priv->search), "entry", entry);
689 }
690
691 static void
692 create_main_menu (BirthdayData *priv)
693 {
694         HildonAppMenu *menu;
695         GtkWidget *filter, *item;
696
697         g_return_if_fail (priv);
698
699         menu = HILDON_APP_MENU (hildon_app_menu_new ());
700         hildon_window_set_app_menu (HILDON_WINDOW (priv->window), menu);
701
702         filter = hildon_gtk_radio_button_new (HILDON_SIZE_FINGER_HEIGHT , NULL);
703         gtk_button_set_label (GTK_BUTTON (filter), _("Name"));
704         gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (filter), FALSE);
705         g_signal_connect_after (filter, "clicked", G_CALLBACK (sort_by_name_clicked), priv);
706         hildon_app_menu_add_filter (menu, GTK_BUTTON (filter));
707
708         filter = hildon_gtk_radio_button_new_from_widget (HILDON_SIZE_FINGER_HEIGHT , GTK_RADIO_BUTTON (filter));
709         gtk_button_set_label (GTK_BUTTON (filter), _("Date"));
710         gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (filter), FALSE);
711         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (filter), TRUE);
712         g_signal_connect_after (filter, "clicked", G_CALLBACK (sort_by_date_clicked), priv);
713         hildon_app_menu_add_filter (menu, GTK_BUTTON (filter));
714
715         item = hildon_gtk_button_new (HILDON_SIZE_AUTO);
716         gtk_button_set_label (GTK_BUTTON (item), _("Search"));
717         hildon_app_menu_append (menu, GTK_BUTTON (item));
718         g_signal_connect (item, "clicked", G_CALLBACK (search_menu_clicked), priv);
719
720         if (priv->n_contacts > 0)
721                 gtk_widget_show_all (GTK_WIDGET (menu));
722 }
723
724 static void
725 create_main_window (BirthdayData *priv, GtkListStore *store)
726 {
727         HildonProgram *program = NULL;
728         GtkWidget *main_vbox, *alignment, *pannable, *tree_view;
729         GtkTreeModel *filter;
730         GtkTreeViewColumn *column;
731         GtkCellRenderer *renderer;
732         GtkTreeSelection *selection;
733
734         g_return_if_fail (priv);
735
736         program = hildon_program_get_instance ();
737         g_set_application_name (_("Birthday"));
738
739         /* main window */
740         priv->window = hildon_stackable_window_new ();
741         hildon_program_add_window (program, HILDON_WINDOW (priv->window));
742
743         /* create main menu */
744         create_main_menu (priv);
745
746         /* aligment */
747         alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
748         gtk_alignment_set_padding (GTK_ALIGNMENT (alignment),
749                                    HILDON_MARGIN_HALF, 0, HILDON_MARGIN_DEFAULT, HILDON_MARGIN_DEFAULT);
750         gtk_container_add (GTK_CONTAINER (priv->window), alignment);
751
752         /* main vbox */
753         main_vbox = gtk_vbox_new (FALSE, 0);
754         gtk_container_add (GTK_CONTAINER (alignment), main_vbox);
755
756         /* no_search_result label */
757         priv->label = gtk_label_new (_("No contacts with birthday"));
758         hildon_helper_set_logical_color (priv->label, GTK_RC_FG,
759                                          GTK_STATE_NORMAL, "SecondaryTextColor");
760         hildon_helper_set_logical_font (priv->label, "LargeSystemFont");
761         gtk_box_pack_start (GTK_BOX (main_vbox), priv->label, TRUE, TRUE, 0);
762
763         /* alignment for pannable area */
764         priv->view = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
765         gtk_alignment_set_padding (GTK_ALIGNMENT (priv->view),
766                                    0, 0, HILDON_MARGIN_DEFAULT, 0);
767         gtk_box_pack_start (GTK_BOX (main_vbox), priv->view, TRUE, TRUE, 0);
768
769         /* pannable for tree view */
770         pannable = hildon_pannable_area_new ();
771         g_object_set (G_OBJECT (pannable), "mov-mode", HILDON_MOVEMENT_MODE_VERT, NULL);
772         gtk_container_add (GTK_CONTAINER (priv->view), pannable);
773
774         /* sort list by next birthdays */
775         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
776                                               COLUMN_NEXT_BIRTHDAY, GTK_SORT_ASCENDING);
777         priv->sorted = GTK_TREE_MODEL (store);
778
779         /* filtered view */
780         filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (store), NULL);
781         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter),
782                                                 birthday_filered_view_visible_func,
783                                                 priv,
784                                                 NULL);
785         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter));
786         priv->filter = GTK_TREE_MODEL (filter);
787
788         /* tree view */
789         priv->tree_view = hildon_gtk_tree_view_new_with_model (HILDON_UI_MODE_EDIT, filter);
790         gtk_container_add (GTK_CONTAINER (pannable), priv->tree_view);
791
792         /* display column */
793         column = gtk_tree_view_column_new ();
794         gtk_tree_view_column_set_fixed_width (column, 709);
795         gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
796         renderer = gtk_cell_renderer_text_new ();
797         gtk_tree_view_column_pack_start (column, renderer, TRUE);
798         gtk_tree_view_column_set_attributes (column, renderer,
799                                              "markup", COLUMN_DISPLAY,
800                                              NULL);
801         g_object_set (G_OBJECT (renderer), "xpad", 10, NULL);
802         gtk_tree_view_append_column (GTK_TREE_VIEW (priv->tree_view), column);
803         priv->display_column = column;
804
805         /* avatar column */
806         column = gtk_tree_view_column_new ();
807         gtk_tree_view_column_set_fixed_width (column, 48);
808         gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
809         renderer = gtk_cell_renderer_pixbuf_new ();
810         gtk_tree_view_column_pack_end (column, renderer, FALSE);
811         gtk_tree_view_column_set_attributes (column, renderer,
812                                              "pixbuf", COLUMN_AVATAR,
813                                              NULL);
814         gtk_tree_view_append_column (GTK_TREE_VIEW (priv->tree_view), column);
815
816         /* search bar */
817         create_search_bar(priv);
818         gtk_box_pack_end (GTK_BOX (main_vbox), priv->search, FALSE, FALSE, 0);
819
820         gtk_widget_show_all (GTK_WIDGET (priv->window));
821         gtk_widget_hide (GTK_WIDGET (priv->search));
822
823         if (priv->n_contacts > 0) {
824                 gtk_widget_hide (GTK_WIDGET (priv->label));
825                 gtk_widget_show (GTK_WIDGET (priv->view));
826         } else {
827                 gtk_widget_show (GTK_WIDGET (priv->label));
828                 gtk_widget_hide (GTK_WIDGET (priv->view));
829         }
830
831         /* enable portrait mode support */
832         hildon_gtk_window_set_portrait_flags (GTK_WINDOW (priv->window),
833                                               HILDON_PORTRAIT_MODE_SUPPORT);
834
835         /* tree view signals */
836         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->tree_view));
837         gtk_tree_selection_unselect_all (selection);
838         g_signal_connect (selection, "changed", G_CALLBACK (on_selection_changed), priv);
839
840         /* window signals */
841         g_signal_connect (G_OBJECT (priv->window), "destroy", G_CALLBACK (gtk_main_quit), NULL);
842         g_signal_connect (G_OBJECT (priv->window), "key-press-event", G_CALLBACK (on_key_press_event), priv);
843 }
844
845 static GList *
846 get_all_contacts (EBook *ebook)
847 {
848         GError *error = NULL;
849         EBookQuery *query;
850         GList *contacts;
851
852         ebook = e_book_new_system_addressbook (&error);
853         if (!ebook) {
854                 g_warning ("Error opening system address book: %s", error->message);
855                 g_error_free (error);
856                 return NULL;
857         }
858
859         if (!e_book_open (ebook, TRUE, &error)) {
860                 g_warning ("Error opening system address book: %s", error->message);
861                 g_error_free (error);
862                 return NULL;
863         }
864
865         query = e_book_query_any_field_contains ("");
866
867         if (!e_book_get_contacts (ebook, query, &contacts, &error)) {
868                 g_warning ("Error getting contacts: %s", error->message);
869                 g_error_free (error);
870                 return NULL;
871         }
872
873         return contacts;
874 }
875
876 int main (int argc, char **argv)
877 {
878         BirthdayData *data;
879         osso_context_t *osso_context;
880         EBook *ebook;
881         GtkWidget *window;
882         GtkListStore *store;
883         GList *contacts;
884
885         hildon_gtk_init (&argc, &argv);
886
887         /* create application data */
888         data = g_new0 (BirthdayData, 1);
889         data->searched_name = NULL;
890         data->found = TRUE;
891         data->n_contacts = 0;
892
893         /* initialize localization */
894         setlocale(LC_ALL, "");
895         bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
896         bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
897         textdomain(GETTEXT_PACKAGE);
898
899         /* initialize osso */
900         osso_context = osso_initialize ("birthday", "0.1", TRUE, NULL);
901         if (osso_context == NULL) {
902                 g_critical ("Error initializing osso");
903                 return 1;
904         }
905
906         /* init abook */
907         if (!osso_abook_init (&argc, &argv, osso_context)) {
908                 g_critical ("Error initializing libosso-abook");
909                 goto exit;
910         }
911
912         contacts = get_all_contacts (ebook);
913         store = create_bday_liststore (data, contacts);
914
915         /* create main widow */
916         create_main_window (data, store);
917
918         /* get the system dbus connection */
919         DBusConnection *dbus_connection = osso_get_sys_dbus_connection (osso_context);
920
921         /* add the callback, which should be called, once the device is rotated */
922         dbus_bus_add_match (dbus_connection, MCE_MATCH_RULE, NULL);
923         dbus_connection_add_filter (dbus_connection, dbus_handle_mce_message, data, NULL);
924
925         /* check if device is not already in portrait orientation */
926         if (is_portrait_mode (osso_context))
927                 set_portrait_mode (data, TRUE);
928
929         gtk_main ();
930
931 exit:
932         osso_deinitialize (osso_context);
933         return 0;
934 }