imported version 0.1-2
[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 GtkWidget *tree_view;
42
43 enum
44 {
45         COLUMN_AVATAR = 0,
46         COLUMN_DISPLAY,
47         COLUMN_FULLNAME,
48         COLUMN_NEXT_BIRTHDAY,
49         NUM_COLS
50 };
51
52 static void
53 sort_by_name_clicked (GtkButton *button,
54                       GtkLabel *label)
55 {
56         GtkTreeModel *model;
57
58         model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view));
59         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
60                                               COLUMN_FULLNAME, GTK_SORT_ASCENDING);
61 }
62
63 static void
64 sort_by_date_clicked (GtkButton *button,
65                       GtkLabel *label)
66 {
67         GtkTreeModel *model;
68
69         model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view));
70         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
71                                               COLUMN_NEXT_BIRTHDAY, GTK_SORT_ASCENDING);
72
73 }
74
75 static void
76 on_search_close_clicked (GtkButton *button,
77                          gpointer data)
78 {
79         GtkWidget *search = GTK_WIDGET (data);
80
81         /* hide search bar */
82         gtk_widget_hide (search);
83
84         /* FIXME: refilter treeview */
85 }
86
87
88 static gboolean
89 on_key_press_event (GtkWidget *widget,
90                     GdkEventKey *event,
91                     gpointer data)
92 {
93         GtkWidget *search = GTK_WIDGET (data);
94
95         if ((event->keyval > GDK_space) && (event->keyval <= GDK_stricteq) && !GTK_WIDGET_VISIBLE (search)) {
96                 /* show search bar */
97                 gtk_widget_show (search);
98
99                 /* FIXME: focus on entry */
100         }
101
102         return FALSE;
103 }
104
105 static unsigned int
106 calc_age (EContactDate *bdate)
107 {
108         struct tm tm_bday;
109         struct tm *tm_age;
110         time_t t_age;
111         int age = 0;
112
113         tm_bday.tm_sec = 0;
114         tm_bday.tm_min = 0;
115         tm_bday.tm_hour = 0;
116         tm_bday.tm_mday = bdate->day;
117         tm_bday.tm_mon = bdate->month - 1;
118         tm_bday.tm_year = bdate->year - 1900;
119
120         t_age = time (NULL) - mktime (&tm_bday);
121         tm_age = gmtime (&t_age);
122         age = tm_age->tm_year - 70;
123
124         if (age < 0)
125                 age = 0;
126
127         return age;
128 }
129
130 static unsigned int
131 calc_next_bday (EContactDate *bdate)
132 {
133         struct tm tm_current_bday, tm_next_bday;
134         struct tm *tm_current_date;
135         time_t t_current_date, t_current_bday, t_next_bday;
136
137         t_current_date = time (NULL);
138         tm_current_date = localtime (&t_current_date);
139
140         tm_current_bday.tm_sec = 0;
141         tm_current_bday.tm_min = 0;
142         tm_current_bday.tm_hour = 0;
143         tm_current_bday.tm_mday = bdate->day;
144         tm_current_bday.tm_mon = bdate->month - 1;
145         tm_current_bday.tm_year = tm_current_date->tm_year;
146         t_current_bday = mktime (&tm_current_bday);
147
148         if (t_current_date > t_current_bday) {
149                 tm_next_bday.tm_sec = 0;
150                 tm_next_bday.tm_min = 0;
151                 tm_next_bday.tm_hour = 0;
152                 tm_next_bday.tm_mday = bdate->day;
153                 tm_next_bday.tm_mon = bdate->month - 1;
154                 tm_next_bday.tm_year = tm_current_date->tm_year + 1;
155                 t_next_bday = mktime (&tm_next_bday);
156         } else {
157                 t_next_bday = t_current_bday;
158         }
159
160         return (t_next_bday - t_current_date) / 86400;
161 }
162
163 static gchar *
164 get_text_font_by_name (const gchar *name)
165 {
166         GtkSettings *settings;
167         GtkStyle *style;
168
169         settings = gtk_settings_get_default ();
170         style = gtk_rc_get_style_by_paths (settings, name, NULL, G_TYPE_NONE);
171         return pango_font_description_to_string (style->font_desc);
172 }
173
174 static gchar *
175 get_text_color_by_name (const gchar *name)
176 {
177         GtkSettings *settings;
178         GtkStyle *style;
179         GdkColor color;
180
181         settings = gtk_settings_get_default ();
182         style = gtk_rc_get_style_by_paths (settings, "GtkButton", "osso-logical-colors", G_OBJECT_TYPE(gtk_button_new()));
183         gtk_style_lookup_color (style, name, &color);
184         return gdk_color_to_string (&color);
185 }
186
187 static GtkListStore *
188 create_bday_liststore (GList *contacts)
189 {
190         GtkListStore *store;
191         GtkTreeIter iter;
192         GList *contact;
193         gchar *text_font = NULL;
194         gchar *text_color = NULL;
195
196         text_font = get_text_font_by_name ("SmallSystemFont");
197         text_color = get_text_color_by_name ("SecondaryTextColor");
198
199         store = gtk_list_store_new(NUM_COLS,
200                                    GDK_TYPE_PIXBUF,     /* COLUMN_AVATAR */
201                                    G_TYPE_STRING,       /* COLUMN_DISPLAY */
202                                    G_TYPE_STRING,       /* COLUMN_FULLNAME */
203                                    G_TYPE_INT);         /* COLUMN_NEXT_BIRTHDAY */
204
205         for (contact = contacts; contact != NULL; contact = contact->next) {
206                 EContactDate *bdate = NULL;
207
208                 bdate = e_contact_get (E_CONTACT (contact->data), E_CONTACT_BIRTH_DATE);
209                 if (bdate) {
210                         EContactPhoto *photo = NULL;
211                         GError *error = NULL;
212                         GdkPixbuf *avatar = NULL;
213                         gchar *fullname = NULL;
214                         guint age = 0, next_birthday = 0;
215                         gchar *display_column = NULL;
216                         gchar *next_birthday_text = NULL;
217
218                         photo = e_contact_get (E_CONTACT (contact->data), E_CONTACT_PHOTO);
219                         if (photo) {
220                                 if (photo->type == E_CONTACT_PHOTO_TYPE_INLINED) {
221                                         GdkPixbufLoader *loader;
222
223                                         loader = gdk_pixbuf_loader_new ();
224                                         if (gdk_pixbuf_loader_write (loader, (guchar *) photo->data.inlined.data, photo->data.inlined.length, NULL))
225                                                 avatar = gdk_pixbuf_loader_get_pixbuf (loader);
226
227                                 } else {
228                                         gchar *avatar_filename = NULL;
229
230                                         avatar_filename = g_filename_from_uri (photo->data.uri, NULL, NULL);
231                                         if (avatar_filename) {
232                                                 avatar = gdk_pixbuf_new_from_file (avatar_filename, &error);
233                                                 g_free (avatar_filename);
234                                         }
235                                 }
236
237                                 if (avatar) {
238                                         gint height = gdk_pixbuf_get_height (avatar);
239                                         if (height != 48) {
240                                                 gint new_height = 48;
241                                                 gint new_width = new_height * gdk_pixbuf_get_width (avatar) / height;
242                                                 avatar = gdk_pixbuf_scale_simple (avatar, new_width, new_height, GDK_INTERP_BILINEAR);
243                                         }
244                                 }
245                                 e_contact_photo_free (photo);
246                                 photo = NULL;
247                         } else {
248                                 avatar = gdk_pixbuf_new_from_file ("/usr/share/icons/hicolor/48x48/hildon/general_default_avatar.png", &error);
249                         }
250
251                         fullname = e_contact_get (E_CONTACT (contact->data), E_CONTACT_FULL_NAME);
252                         if (!fullname) {
253                                 fullname = e_contact_get (E_CONTACT (contact->data), E_CONTACT_GIVEN_NAME);
254                                 if (!fullname) {
255                                         fullname = e_contact_get (E_CONTACT (contact->data), E_CONTACT_FAMILY_NAME);
256                                         if (!fullname) {
257                                                 fullname = e_contact_get (E_CONTACT (contact->data), E_CONTACT_NICKNAME);
258                                                 if (!fullname) {
259                                                         fullname = e_contact_get (E_CONTACT (contact->data), E_CONTACT_ORG);
260                                                 }
261                                         }
262                                 }
263                         }
264
265                         age = calc_age(bdate);
266                         next_birthday = calc_next_bday(bdate);
267                         next_birthday_text = g_strdup_printf(ngettext ("next birthday in %d day", "next birthday in %d days", next_birthday), next_birthday);
268                         display_column = g_strdup_printf("%s <span font_desc=\"%s\" foreground=\"%s\"><sup>(%d)</sup>\n%02d.%02d.%04d, %s</span>",
269                                                          fullname, text_font, text_color, age, bdate->day, bdate->month, bdate->year, next_birthday_text);
270
271                         gtk_list_store_append (store, &iter);
272                         gtk_list_store_set (store, &iter,
273                                             COLUMN_AVATAR, avatar,
274                                             COLUMN_DISPLAY, display_column,
275                                             COLUMN_FULLNAME, fullname,
276                                             COLUMN_NEXT_BIRTHDAY, next_birthday,
277                                             -1);
278
279                         if (display_column)
280                                 g_free (display_column);
281                         display_column = NULL;
282
283                         if (fullname)
284                                 g_free (fullname);
285                         fullname = NULL;
286
287                         if (next_birthday_text)
288                                 g_free (next_birthday_text);
289                         next_birthday_text = NULL;
290
291                         e_contact_date_free (bdate);
292                 }
293                 bdate = NULL;
294         }
295
296         if (text_font)
297                 g_free (text_font);
298
299         if (text_color)
300                 g_free (text_color);
301
302         return store;
303 }
304
305 static GtkWidget *
306 create_search_bar ()
307 {
308         GtkWidget *hbox, *entry, *button;
309         GtkEntryCompletion *completion;
310
311         /* search hbox */
312         hbox = gtk_hbox_new (FALSE, HILDON_MARGIN_DEFAULT);
313
314         /* search entry */
315         entry = hildon_entry_new (HILDON_SIZE_FINGER_HEIGHT);
316         hildon_gtk_entry_set_input_mode (GTK_ENTRY (entry), HILDON_GTK_INPUT_MODE_FULL);
317         gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
318
319         completion = gtk_entry_completion_new ();
320         gtk_entry_completion_set_inline_completion (completion, TRUE);
321         gtk_entry_completion_set_popup_completion (completion, FALSE);
322         gtk_entry_set_completion (GTK_ENTRY (entry), completion);
323
324         /* clear button */
325         button = GTK_WIDGET (gtk_tool_button_new (gtk_image_new_from_icon_name
326                              ("general_close", (GtkIconSize) HILDON_ICON_PIXEL_SIZE_FINGER), "Clear"));
327         gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, TRUE, 0);
328
329         /* search signals */
330 //      g_signal_connect (entry, "changed", G_CALLBACK (onSearchEntryChanged), this);
331         g_signal_connect (button, "clicked", G_CALLBACK (on_search_close_clicked), hbox);
332
333         return hbox;
334 }
335
336 static void
337 create_main_menu (GtkWidget *window)
338 {
339         HildonAppMenu *menu;
340         GtkWidget *filter;
341
342         menu = HILDON_APP_MENU (hildon_app_menu_new ());
343         hildon_window_set_app_menu (HILDON_WINDOW (window), menu);
344
345         filter = hildon_gtk_radio_button_new (HILDON_SIZE_FINGER_HEIGHT , NULL);
346         gtk_button_set_label (GTK_BUTTON (filter), _("Name"));
347         gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (filter), FALSE);
348         g_signal_connect_after (filter, "clicked", G_CALLBACK (sort_by_name_clicked), NULL);
349         hildon_app_menu_add_filter (menu, GTK_BUTTON (filter));
350
351         filter = hildon_gtk_radio_button_new_from_widget (HILDON_SIZE_FINGER_HEIGHT , GTK_RADIO_BUTTON (filter));
352         gtk_button_set_label (GTK_BUTTON (filter), _("Date"));
353         gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (filter), FALSE);
354         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (filter), TRUE);
355         g_signal_connect_after (filter, "clicked", G_CALLBACK (sort_by_date_clicked), NULL);
356         hildon_app_menu_add_filter (menu, GTK_BUTTON (filter));
357
358         gtk_widget_show_all (GTK_WIDGET (menu));
359 }
360
361 static GtkWidget *
362 create_main_window (GtkListStore *store)
363 {
364         HildonProgram *program = NULL;
365         GtkWidget *window, *main_vbox, *alignment, *label, *pannable, *search;
366         GtkTreeViewColumn *column;
367         GtkCellRenderer *renderer;
368
369         program = hildon_program_get_instance ();
370         g_set_application_name (_("Birthday"));
371
372         /* main window */
373         window = hildon_stackable_window_new ();
374         hildon_program_add_window (program, HILDON_WINDOW (window));
375
376         /* aligment */
377         alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
378         gtk_alignment_set_padding (GTK_ALIGNMENT (alignment),
379                                    HILDON_MARGIN_HALF, 0, HILDON_MARGIN_DEFAULT, HILDON_MARGIN_DEFAULT);
380         gtk_container_add (GTK_CONTAINER (window), alignment);
381
382         /* main vbox */
383         main_vbox = gtk_vbox_new (FALSE, 0);
384         gtk_container_add (GTK_CONTAINER (alignment), main_vbox);
385
386         /* no_search_result label */
387         label = gtk_label_new (_("No contacts with set birthdate"));
388         hildon_helper_set_logical_color (label, GTK_RC_FG,
389                                          GTK_STATE_NORMAL, "SecondaryTextColor");
390         hildon_helper_set_logical_font (label, "LargeSystemFont");
391         gtk_box_pack_start (GTK_BOX (main_vbox), label, TRUE, TRUE, 0);
392
393         /* alignment for pannable area */
394         alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
395         gtk_alignment_set_padding (GTK_ALIGNMENT (alignment),
396                                    0, 0, HILDON_MARGIN_DEFAULT, 0);
397         gtk_box_pack_start (GTK_BOX (main_vbox), alignment, TRUE, TRUE, 0);
398
399         /* pannable for tree view */
400         pannable = hildon_pannable_area_new ();
401         g_object_set (G_OBJECT (pannable), "mov-mode", HILDON_MOVEMENT_MODE_VERT, NULL);
402         gtk_container_add (GTK_CONTAINER (alignment), pannable);
403
404         /* tree view */
405         tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
406         gtk_container_add (GTK_CONTAINER (pannable), tree_view);
407         /* display column */
408         column = gtk_tree_view_column_new ();
409         gtk_tree_view_column_set_fixed_width (column, 709);
410         gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
411         renderer = gtk_cell_renderer_text_new ();
412         gtk_tree_view_column_pack_start (column, renderer, TRUE);
413         gtk_tree_view_column_set_attributes (column, renderer,
414                                              "markup", COLUMN_DISPLAY,
415                                              NULL);
416         g_object_set (G_OBJECT (renderer), "xpad", 10, NULL);
417
418         gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
419
420         /* avatar column */
421         column = gtk_tree_view_column_new ();
422         gtk_tree_view_column_set_fixed_width (column, 48);
423         gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
424         renderer = gtk_cell_renderer_pixbuf_new ();
425         gtk_tree_view_column_pack_end (column, renderer, FALSE);
426         gtk_tree_view_column_set_attributes (column, renderer,
427                                              "pixbuf", COLUMN_AVATAR,
428                                              NULL);
429         gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
430
431         /* search bar */
432         search = create_search_bar();
433         gtk_box_pack_end (GTK_BOX (main_vbox), search, FALSE, FALSE, 0);
434
435         /* window signals */
436         g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL);
437 //      g_signal_connect (G_OBJECT (window), "key-press-event", G_CALLBACK (on_key_press_event), search);
438
439         gtk_widget_show_all (GTK_WIDGET (window));
440         gtk_widget_hide (GTK_WIDGET (label));
441         gtk_widget_hide (GTK_WIDGET (search));
442         return window;
443 }
444
445 int main (int argc, char **argv)
446 {
447         osso_context_t *osso_context;
448         EBook *ebook;
449         EBookQuery *query;
450         GError *error = NULL;
451         GtkWidget *window;
452         GtkListStore *store;
453         GList *contacts;
454
455         hildon_gtk_init (&argc, &argv);
456
457         /* initialize localization */
458         setlocale(LC_ALL, "");
459         bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
460         bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
461         textdomain(GETTEXT_PACKAGE);
462
463         /* initialize osso */
464         osso_context = osso_initialize ("birthday", "0.1", TRUE, NULL);
465         if (osso_context == NULL) {
466                 g_critical ("Error initializing osso");
467                 return 1;
468         }
469
470         /* init abook */
471         if (!osso_abook_init (&argc, &argv, osso_context)) {
472                 g_critical ("Error initializing libosso-abook");
473                 goto exit;
474         }
475
476         ebook = e_book_new_system_addressbook (&error);
477         if (!ebook) {
478                 g_warning ("Error opening system address book: %s", error->message);
479                 g_error_free (error);
480                 goto exit;
481         }
482
483         if (!e_book_open (ebook, TRUE, &error)) {
484                 g_warning ("Error opening system address book: %s", error->message);
485                 g_error_free (error);
486                 goto exit;
487         }
488
489         query = e_book_query_any_field_contains ("");
490
491         if (!e_book_get_contacts (ebook, query, &contacts, &error)) {
492                 g_warning ("Error getting contacts: %s", error->message);
493                 g_error_free (error);
494                 goto exit;
495         }
496
497         store = create_bday_liststore (contacts);
498         window = create_main_window (store);
499         g_object_unref (G_OBJECT (store));
500         create_main_menu (window);
501
502         /* sort list by next birthdays */
503         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
504                                               COLUMN_NEXT_BIRTHDAY, GTK_SORT_ASCENDING);
505
506         gtk_main ();
507
508 exit:
509         osso_deinitialize (osso_context);
510         return 0;
511 }