2 * Birthday application for Maemo.
3 * Copyright (C) 2010 Roman Moravcik
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.
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.
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
33 #include <glib/gi18n.h>
36 #include <hildon/hildon.h>
38 #include <libebook/e-book.h>
39 #include <libosso-abook/osso-abook.h>
41 #define _HL(str) dgettext("hildon-libs",str)
52 /* Application UI data struct */
53 typedef struct _BirthdayData BirthdayData;
54 struct _BirthdayData {
71 birthday_filered_view_visible_func (GtkTreeModel *model,
76 gchar *fullname = NULL, *ascii_searched_name = NULL, *ascii_fullname = NULL;
77 gboolean found = FALSE;
79 g_return_val_if_fail (data, FALSE);
80 priv = (BirthdayData *) data;
82 if (priv->searched_name == NULL) {
87 ascii_searched_name = g_ascii_strdown (priv->searched_name, strlen (priv->searched_name));
89 gtk_tree_model_get (model, iter, COLUMN_FULLNAME, &fullname, -1);
91 ascii_fullname = g_ascii_strdown (fullname, strlen (fullname));
95 if (g_strstr_len (ascii_fullname, strlen (ascii_fullname), ascii_searched_name) != NULL)
98 if (ascii_searched_name)
99 g_free (ascii_searched_name);
102 g_free (ascii_fullname);
111 sort_by_name_clicked (GtkButton *button,
116 g_return_if_fail (data);
117 priv = (BirthdayData *) data;
120 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->sorted),
121 COLUMN_FULLNAME, GTK_SORT_ASCENDING);
126 sort_by_date_clicked (GtkButton *button,
131 g_return_if_fail (data);
132 priv = (BirthdayData *) data;
135 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->sorted),
136 COLUMN_NEXT_BIRTHDAY, GTK_SORT_ASCENDING);
141 search_menu_clicked (GtkButton *button,
147 g_return_if_fail (data);
148 priv = (BirthdayData *) data;
150 /* show search bar */
151 gtk_widget_show (priv->search);
153 /* focus on search entry */
154 entry = g_object_get_data (G_OBJECT (priv->search), "entry");
155 gtk_entry_set_text (GTK_ENTRY (entry), "");
156 gtk_widget_grab_focus (GTK_WIDGET (entry));
158 /* refilter tree view */
159 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));
163 on_search_entry_changed (GtkEditable *editable,
168 g_return_if_fail (data);
169 priv = (BirthdayData *) data;
173 if (priv->searched_name)
174 g_free (priv->searched_name);
175 priv->searched_name = g_strdup (gtk_entry_get_text (GTK_ENTRY (editable)));
177 /* refilter tree view */
178 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));
182 gtk_widget_hide (priv->label);
185 gtk_widget_show (priv->view);
188 gtk_widget_show (priv->label);
189 gtk_label_set_text (GTK_LABEL (priv->label), _("No search results"));
192 gtk_widget_hide (priv->view);
195 /* ugly, but working way how to scroll to the first row */
196 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (priv->tree_view),
197 gtk_tree_path_new_from_string ("0"), NULL, FALSE, 0, 0);
201 on_search_close_clicked (GtkButton *button,
206 g_return_if_fail (data);
207 priv = (BirthdayData *) data;
209 /* hide search bar */
210 gtk_widget_hide (priv->search);
213 gtk_widget_hide (priv->label);
216 gtk_widget_show (priv->view);
218 /* clear searched name */
219 if (priv->searched_name)
220 g_free (priv->searched_name);
221 priv->searched_name = NULL;
223 /* refilter tree view */
224 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));
226 /* ugly, but working way how to scroll to the first row */
227 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (priv->tree_view),
228 gtk_tree_path_new_from_string ("0"), NULL, FALSE, 0, 0);
233 on_key_press_event (GtkWidget *widget,
239 g_return_val_if_fail (data, TRUE);
240 priv = (BirthdayData *) data;
242 if (priv->n_contacts == 0)
245 if ((event->keyval > GDK_space) && (event->keyval <= GDK_stricteq) && !GTK_WIDGET_VISIBLE (priv->search)) {
248 /* show search bar */
249 gtk_widget_show (priv->search);
251 /* focus on search entry */
252 entry = g_object_get_data (G_OBJECT (priv->search), "entry");
253 gtk_entry_set_text (GTK_ENTRY (entry), "");
254 gtk_widget_grab_focus (GTK_WIDGET (entry));
256 /* refilter tree view */
257 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));
264 calc_age (EContactDate *bdate, time_t current_date)
266 struct tm *current_date_tm;
270 current_date_tm = gmtime (¤t_date);
275 bday_tm.tm_mday = bdate->day;
276 bday_tm.tm_mon = bdate->month - 1;
277 bday_tm.tm_year = current_date_tm->tm_year;
278 bday_tm.tm_isdst = current_date_tm->tm_isdst;
280 if (mktime (&bday_tm) > current_date) {
281 age = (current_date_tm->tm_year + 1900) - bdate->year - 1;
283 age = (current_date_tm->tm_year + 1900) - bdate->year;
293 calc_next_bday (EContactDate *bdate, time_t current_date)
295 struct tm current_bday_tm, next_bday_tm;
296 struct tm *current_date_tm;
297 time_t current_bday, next_bday;
299 current_date_tm = gmtime (¤t_date);
301 current_bday_tm.tm_sec = 0;
302 current_bday_tm.tm_min = 0;
303 current_bday_tm.tm_hour = 0;
304 current_bday_tm.tm_mday = bdate->day;
305 current_bday_tm.tm_mon = bdate->month - 1;
306 current_bday_tm.tm_year = current_date_tm->tm_year;
307 current_bday_tm.tm_isdst = current_date_tm->tm_isdst;
308 current_bday = mktime (¤t_bday_tm);
310 if (current_date > current_bday) {
311 next_bday_tm.tm_sec = 0;
312 next_bday_tm.tm_min = 0;
313 next_bday_tm.tm_hour = 0;
314 next_bday_tm.tm_mday = bdate->day;
315 next_bday_tm.tm_mon = bdate->month - 1;
316 next_bday_tm.tm_year = current_date_tm->tm_year + 1;
317 next_bday_tm.tm_isdst = current_date_tm->tm_isdst;
318 next_bday = mktime (&next_bday_tm);
320 next_bday = current_bday;
323 return (next_bday - current_date) / 86400;
327 get_text_font_by_name (const gchar *name)
329 GtkSettings *settings;
332 settings = gtk_settings_get_default ();
333 style = gtk_rc_get_style_by_paths (settings, name, NULL, G_TYPE_NONE);
334 return pango_font_description_to_string (style->font_desc);
338 get_text_color_by_name (const gchar *name)
340 GtkSettings *settings;
344 settings = gtk_settings_get_default ();
345 style = gtk_rc_get_style_by_paths (settings, "GtkButton", "osso-logical-colors", G_OBJECT_TYPE(gtk_button_new()));
346 gtk_style_lookup_color (style, name, &color);
347 return gdk_color_to_string (&color);
350 static GtkListStore *
351 create_bday_liststore (BirthdayData *priv, GList *contacts)
356 gchar *text_font = NULL;
357 gchar *text_color = NULL;
358 guint n_contacts = 0;
360 struct tm *current_date_tm;
362 g_return_val_if_fail (priv, NULL);
364 text_font = get_text_font_by_name ("SmallSystemFont");
365 text_color = get_text_color_by_name ("SecondaryTextColor");
367 current_date = time (NULL);
369 /* set hour, minute, second to 0 */
370 current_date_tm = gmtime (¤t_date);
371 current_date_tm->tm_sec = 0;
372 current_date_tm->tm_min = 0;
373 current_date_tm->tm_hour = 0;
374 current_date = mktime (current_date_tm);
376 store = gtk_list_store_new(NUM_COLS,
377 GDK_TYPE_PIXBUF, /* COLUMN_AVATAR */
378 G_TYPE_STRING, /* COLUMN_DISPLAY */
379 G_TYPE_STRING, /* COLUMN_FULLNAME */
380 G_TYPE_INT); /* COLUMN_NEXT_BIRTHDAY */
382 for (contact = contacts; contact != NULL; contact = contact->next) {
383 EContactDate *bdate = NULL;
385 bdate = e_contact_get (E_CONTACT (contact->data), E_CONTACT_BIRTH_DATE);
387 EContactPhoto *photo = NULL;
388 GError *error = NULL;
389 GdkPixbuf *avatar = NULL;
390 gchar *fullname = NULL;
391 guint age = 0, next_birthday = 0;
392 gchar *display_column = NULL;
393 gchar *next_birthday_text = NULL;
394 struct tm birthday_tm;
395 gchar birthday_text[11];
397 photo = e_contact_get (E_CONTACT (contact->data), E_CONTACT_PHOTO);
399 if (photo->type == E_CONTACT_PHOTO_TYPE_INLINED) {
400 GdkPixbufLoader *loader;
402 loader = gdk_pixbuf_loader_new ();
403 if (gdk_pixbuf_loader_write (loader, (guchar *) photo->data.inlined.data, photo->data.inlined.length, NULL))
404 avatar = gdk_pixbuf_loader_get_pixbuf (loader);
407 gchar *avatar_filename = NULL;
409 avatar_filename = g_filename_from_uri (photo->data.uri, NULL, NULL);
410 if (avatar_filename) {
411 avatar = gdk_pixbuf_new_from_file (avatar_filename, &error);
412 g_free (avatar_filename);
417 gint height = gdk_pixbuf_get_height (avatar);
419 gint new_height = 48;
420 gint new_width = new_height * gdk_pixbuf_get_width (avatar) / height;
421 avatar = gdk_pixbuf_scale_simple (avatar, new_width, new_height, GDK_INTERP_BILINEAR);
424 e_contact_photo_free (photo);
427 avatar = gdk_pixbuf_new_from_file ("/usr/share/icons/hicolor/48x48/hildon/general_default_avatar.png", &error);
430 fullname = e_contact_get (E_CONTACT (contact->data), E_CONTACT_FULL_NAME);
432 fullname = e_contact_get (E_CONTACT (contact->data), E_CONTACT_GIVEN_NAME);
434 fullname = e_contact_get (E_CONTACT (contact->data), E_CONTACT_FAMILY_NAME);
436 fullname = e_contact_get (E_CONTACT (contact->data), E_CONTACT_NICKNAME);
438 fullname = e_contact_get (E_CONTACT (contact->data), E_CONTACT_ORG);
444 birthday_tm.tm_sec = 0;
445 birthday_tm.tm_min = 0;
446 birthday_tm.tm_hour = 0;
447 birthday_tm.tm_mday = bdate->day;
448 birthday_tm.tm_mon = bdate->month - 1;
449 birthday_tm.tm_year = bdate->year - 1900;
450 strftime (birthday_text, 11, _HL("wdgt_va_date"), &birthday_tm);
452 age = calc_age(bdate, current_date);
453 next_birthday = calc_next_bday(bdate, current_date);
455 if (next_birthday == 0)
456 next_birthday_text = g_strdup_printf(_("has birthday today"));
457 else if (next_birthday == 1)
458 next_birthday_text = g_strdup_printf(_("will have birthday tomorrow"));
460 next_birthday_text = g_strdup_printf(_("will have birthday in %d days"), next_birthday);
462 display_column = g_strdup_printf("%s <span font_desc=\"%s\" foreground=\"%s\"><sup>(%d)</sup>\n%s, %s</span>",
463 fullname, text_font, text_color, age, birthday_text, next_birthday_text);
465 gtk_list_store_append (store, &iter);
466 gtk_list_store_set (store, &iter,
467 COLUMN_AVATAR, avatar,
468 COLUMN_DISPLAY, display_column,
469 COLUMN_FULLNAME, fullname,
470 COLUMN_NEXT_BIRTHDAY, next_birthday,
475 g_free (display_column);
476 display_column = NULL;
482 if (next_birthday_text)
483 g_free (next_birthday_text);
484 next_birthday_text = NULL;
486 e_contact_date_free (bdate);
497 priv->n_contacts = n_contacts;
502 create_search_bar (BirthdayData *priv)
504 GtkWidget *entry, *button;
505 GtkEntryCompletion *completion;
507 g_return_if_fail (priv);
510 priv->search = gtk_hbox_new (FALSE, HILDON_MARGIN_DEFAULT);
513 entry = hildon_entry_new (HILDON_SIZE_FINGER_HEIGHT);
514 hildon_gtk_entry_set_input_mode (GTK_ENTRY (entry), HILDON_GTK_INPUT_MODE_FULL);
515 gtk_box_pack_start (GTK_BOX (priv->search), entry, TRUE, TRUE, 0);
517 completion = gtk_entry_completion_new ();
518 gtk_entry_completion_set_inline_completion (completion, TRUE);
519 gtk_entry_completion_set_popup_completion (completion, FALSE);
520 gtk_entry_set_completion (GTK_ENTRY (entry), completion);
523 button = GTK_WIDGET (gtk_tool_button_new (gtk_image_new_from_icon_name
524 ("general_close", (GtkIconSize) HILDON_ICON_PIXEL_SIZE_FINGER), "Clear"));
525 gtk_box_pack_end (GTK_BOX (priv->search), button, FALSE, TRUE, 0);
528 g_signal_connect (entry, "changed", G_CALLBACK (on_search_entry_changed), priv);
529 g_signal_connect (button, "clicked", G_CALLBACK (on_search_close_clicked), priv);
531 g_object_set_data (G_OBJECT (priv->search), "entry", entry);
535 create_main_menu (BirthdayData *priv)
538 GtkWidget *filter, *item;
540 g_return_if_fail (priv);
542 menu = HILDON_APP_MENU (hildon_app_menu_new ());
543 hildon_window_set_app_menu (HILDON_WINDOW (priv->window), menu);
545 filter = hildon_gtk_radio_button_new (HILDON_SIZE_FINGER_HEIGHT , NULL);
546 gtk_button_set_label (GTK_BUTTON (filter), _("Name"));
547 gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (filter), FALSE);
548 g_signal_connect_after (filter, "clicked", G_CALLBACK (sort_by_name_clicked), priv);
549 hildon_app_menu_add_filter (menu, GTK_BUTTON (filter));
551 filter = hildon_gtk_radio_button_new_from_widget (HILDON_SIZE_FINGER_HEIGHT , GTK_RADIO_BUTTON (filter));
552 gtk_button_set_label (GTK_BUTTON (filter), _("Date"));
553 gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (filter), FALSE);
554 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (filter), TRUE);
555 g_signal_connect_after (filter, "clicked", G_CALLBACK (sort_by_date_clicked), priv);
556 hildon_app_menu_add_filter (menu, GTK_BUTTON (filter));
558 item = hildon_gtk_button_new (HILDON_SIZE_AUTO);
559 gtk_button_set_label (GTK_BUTTON (item), _("Search"));
560 hildon_app_menu_append (menu, GTK_BUTTON (item));
561 g_signal_connect (item, "clicked", G_CALLBACK (search_menu_clicked), priv);
563 if (priv->n_contacts > 0)
564 gtk_widget_show_all (GTK_WIDGET (menu));
568 create_main_window (BirthdayData *priv, GtkListStore *store)
570 HildonProgram *program = NULL;
571 GtkWidget *main_vbox, *alignment, *pannable, *tree_view;
572 GtkTreeModel *filter;
573 GtkTreeViewColumn *column;
574 GtkCellRenderer *renderer;
576 g_return_if_fail (priv);
578 program = hildon_program_get_instance ();
579 g_set_application_name (_("Birthday"));
582 priv->window = hildon_stackable_window_new ();
583 hildon_program_add_window (program, HILDON_WINDOW (priv->window));
585 /* create main menu */
586 create_main_menu (priv);
589 alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
590 gtk_alignment_set_padding (GTK_ALIGNMENT (alignment),
591 HILDON_MARGIN_HALF, 0, HILDON_MARGIN_DEFAULT, HILDON_MARGIN_DEFAULT);
592 gtk_container_add (GTK_CONTAINER (priv->window), alignment);
595 main_vbox = gtk_vbox_new (FALSE, 0);
596 gtk_container_add (GTK_CONTAINER (alignment), main_vbox);
598 /* no_search_result label */
599 priv->label = gtk_label_new (_("No contacts with birthday"));
600 hildon_helper_set_logical_color (priv->label, GTK_RC_FG,
601 GTK_STATE_NORMAL, "SecondaryTextColor");
602 hildon_helper_set_logical_font (priv->label, "LargeSystemFont");
603 gtk_box_pack_start (GTK_BOX (main_vbox), priv->label, TRUE, TRUE, 0);
605 /* alignment for pannable area */
606 priv->view = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
607 gtk_alignment_set_padding (GTK_ALIGNMENT (priv->view),
608 0, 0, HILDON_MARGIN_DEFAULT, 0);
609 gtk_box_pack_start (GTK_BOX (main_vbox), priv->view, TRUE, TRUE, 0);
611 /* pannable for tree view */
612 pannable = hildon_pannable_area_new ();
613 g_object_set (G_OBJECT (pannable), "mov-mode", HILDON_MOVEMENT_MODE_VERT, NULL);
614 gtk_container_add (GTK_CONTAINER (priv->view), pannable);
616 /* sort list by next birthdays */
617 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
618 COLUMN_NEXT_BIRTHDAY, GTK_SORT_ASCENDING);
619 priv->sorted = GTK_TREE_MODEL (store);
622 filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (store), NULL);
623 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter),
624 birthday_filered_view_visible_func,
627 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter));
628 priv->filter = GTK_TREE_MODEL (filter);
631 priv->tree_view = hildon_gtk_tree_view_new_with_model (HILDON_UI_MODE_NORMAL, filter);
632 gtk_container_add (GTK_CONTAINER (pannable), priv->tree_view);
635 column = gtk_tree_view_column_new ();
636 gtk_tree_view_column_set_fixed_width (column, 709);
637 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
638 renderer = gtk_cell_renderer_text_new ();
639 gtk_tree_view_column_pack_start (column, renderer, TRUE);
640 gtk_tree_view_column_set_attributes (column, renderer,
641 "markup", COLUMN_DISPLAY,
643 g_object_set (G_OBJECT (renderer), "xpad", 10, NULL);
645 gtk_tree_view_append_column (GTK_TREE_VIEW (priv->tree_view), column);
648 column = gtk_tree_view_column_new ();
649 gtk_tree_view_column_set_fixed_width (column, 48);
650 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
651 renderer = gtk_cell_renderer_pixbuf_new ();
652 gtk_tree_view_column_pack_end (column, renderer, FALSE);
653 gtk_tree_view_column_set_attributes (column, renderer,
654 "pixbuf", COLUMN_AVATAR,
656 gtk_tree_view_append_column (GTK_TREE_VIEW (priv->tree_view), column);
659 create_search_bar(priv);
660 gtk_box_pack_end (GTK_BOX (main_vbox), priv->search, FALSE, FALSE, 0);
663 g_signal_connect (G_OBJECT (priv->window), "destroy", G_CALLBACK (gtk_main_quit), NULL);
664 g_signal_connect (G_OBJECT (priv->window), "key-press-event", G_CALLBACK (on_key_press_event), priv);
666 gtk_widget_show_all (GTK_WIDGET (priv->window));
667 gtk_widget_hide (GTK_WIDGET (priv->search));
669 if (priv->n_contacts > 0) {
670 gtk_widget_hide (GTK_WIDGET (priv->label));
671 gtk_widget_show (GTK_WIDGET (priv->view));
673 gtk_widget_show (GTK_WIDGET (priv->label));
674 gtk_widget_hide (GTK_WIDGET (priv->view));
679 get_all_contacts (EBook *ebook)
681 GError *error = NULL;
685 ebook = e_book_new_system_addressbook (&error);
687 g_warning ("Error opening system address book: %s", error->message);
688 g_error_free (error);
692 if (!e_book_open (ebook, TRUE, &error)) {
693 g_warning ("Error opening system address book: %s", error->message);
694 g_error_free (error);
698 query = e_book_query_any_field_contains ("");
700 if (!e_book_get_contacts (ebook, query, &contacts, &error)) {
701 g_warning ("Error getting contacts: %s", error->message);
702 g_error_free (error);
709 int main (int argc, char **argv)
712 osso_context_t *osso_context;
718 hildon_gtk_init (&argc, &argv);
720 /* create application data */
721 data = g_new0 (BirthdayData, 1);
722 data->searched_name = NULL;
724 data->n_contacts = 0;
726 /* initialize localization */
727 setlocale(LC_ALL, "");
728 bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
729 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
730 textdomain(GETTEXT_PACKAGE);
732 /* initialize osso */
733 osso_context = osso_initialize ("birthday", "0.1", TRUE, NULL);
734 if (osso_context == NULL) {
735 g_critical ("Error initializing osso");
740 if (!osso_abook_init (&argc, &argv, osso_context)) {
741 g_critical ("Error initializing libosso-abook");
745 contacts = get_all_contacts (ebook);
746 store = create_bday_liststore (data, contacts);
748 /* create main widow */
749 create_main_window (data, store);
754 osso_deinitialize (osso_context);