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>
50 /* Application UI data struct */
51 typedef struct _BirthdayData BirthdayData;
52 struct _BirthdayData {
66 birthday_filered_view_visible_func (GtkTreeModel *model,
71 gchar *fullname = NULL, *ascii_searched_name = NULL, *ascii_fullname = NULL;
72 gboolean found = FALSE;
74 g_return_val_if_fail (data, FALSE);
75 priv = (BirthdayData *) data;
77 if (priv->searched_name == NULL) {
82 ascii_searched_name = g_ascii_strdown (priv->searched_name, strlen (priv->searched_name));
84 gtk_tree_model_get (model, iter, COLUMN_FULLNAME, &fullname, -1);
86 ascii_fullname = g_ascii_strdown (fullname, strlen (fullname));
90 if (g_strstr_len (ascii_fullname, strlen (ascii_fullname), ascii_searched_name) != NULL)
93 if (ascii_searched_name)
94 g_free (ascii_searched_name);
97 g_free (ascii_fullname);
106 sort_by_name_clicked (GtkButton *button,
111 g_return_if_fail (data);
112 priv = (BirthdayData *) data;
115 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->sorted),
116 COLUMN_FULLNAME, GTK_SORT_ASCENDING);
121 sort_by_date_clicked (GtkButton *button,
126 g_return_if_fail (data);
127 priv = (BirthdayData *) data;
130 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->sorted),
131 COLUMN_NEXT_BIRTHDAY, GTK_SORT_ASCENDING);
136 on_search_entry_changed (GtkEditable *editable,
142 g_return_if_fail (data);
143 priv = (BirthdayData *) data;
147 if (priv->searched_name)
148 g_free (priv->searched_name);
149 priv->searched_name = g_strdup (gtk_entry_get_text (GTK_ENTRY (editable)));
151 /* refilter tree view */
152 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));
156 gtk_widget_hide (priv->label);
159 gtk_widget_show (priv->view);
162 gtk_widget_show (priv->label);
163 gtk_label_set_text (GTK_LABEL (priv->label), _("No search results"));
166 gtk_widget_hide (priv->view);
169 pannable = g_object_get_data (G_OBJECT (priv->view), "pannable");
170 hildon_pannable_area_jump_to (HILDON_PANNABLE_AREA (pannable), 0, 0);
174 on_search_close_clicked (GtkButton *button,
180 g_return_if_fail (data);
181 priv = (BirthdayData *) data;
183 /* hide search bar */
184 gtk_widget_hide (priv->search);
187 gtk_widget_hide (priv->label);
190 gtk_widget_show (priv->view);
192 /* clear searched name */
193 if (priv->searched_name)
194 g_free (priv->searched_name);
195 priv->searched_name = NULL;
197 /* refilter tree view */
198 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));
200 pannable = g_object_get_data (G_OBJECT (priv->view), "pannable");
201 hildon_pannable_area_jump_to (HILDON_PANNABLE_AREA (pannable), 0, 0);
206 on_key_press_event (GtkWidget *widget,
212 g_return_val_if_fail (data, TRUE);
213 priv = (BirthdayData *) data;
215 if ((event->keyval > GDK_space) && (event->keyval <= GDK_stricteq) && !GTK_WIDGET_VISIBLE (priv->search)) {
218 /* show search bar */
219 gtk_widget_show (priv->search);
221 /* focus on search entry */
222 entry = g_object_get_data (G_OBJECT (priv->search), "entry");
223 gtk_entry_set_text (GTK_ENTRY (entry), "");
224 gtk_widget_grab_focus (GTK_WIDGET (entry));
226 /* refilter tree view */
227 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));
234 calc_age (EContactDate *bdate)
244 tm_bday.tm_mday = bdate->day;
245 tm_bday.tm_mon = bdate->month - 1;
246 tm_bday.tm_year = bdate->year - 1900;
248 t_age = time (NULL) - mktime (&tm_bday);
249 tm_age = gmtime (&t_age);
250 age = tm_age->tm_year - 70;
259 calc_next_bday (EContactDate *bdate)
261 struct tm tm_current_bday, tm_next_bday;
262 struct tm *tm_current_date;
263 time_t t_current_date, t_current_bday, t_next_bday;
265 t_current_date = time (NULL);
266 tm_current_date = localtime (&t_current_date);
268 tm_current_bday.tm_sec = 0;
269 tm_current_bday.tm_min = 0;
270 tm_current_bday.tm_hour = 0;
271 tm_current_bday.tm_mday = bdate->day;
272 tm_current_bday.tm_mon = bdate->month - 1;
273 tm_current_bday.tm_year = tm_current_date->tm_year;
274 t_current_bday = mktime (&tm_current_bday);
276 if (t_current_date > t_current_bday) {
277 tm_next_bday.tm_sec = 0;
278 tm_next_bday.tm_min = 0;
279 tm_next_bday.tm_hour = 0;
280 tm_next_bday.tm_mday = bdate->day;
281 tm_next_bday.tm_mon = bdate->month - 1;
282 tm_next_bday.tm_year = tm_current_date->tm_year + 1;
283 t_next_bday = mktime (&tm_next_bday);
285 t_next_bday = t_current_bday;
288 return (t_next_bday - t_current_date) / 86400;
292 get_text_font_by_name (const gchar *name)
294 GtkSettings *settings;
297 settings = gtk_settings_get_default ();
298 style = gtk_rc_get_style_by_paths (settings, name, NULL, G_TYPE_NONE);
299 return pango_font_description_to_string (style->font_desc);
303 get_text_color_by_name (const gchar *name)
305 GtkSettings *settings;
309 settings = gtk_settings_get_default ();
310 style = gtk_rc_get_style_by_paths (settings, "GtkButton", "osso-logical-colors", G_OBJECT_TYPE(gtk_button_new()));
311 gtk_style_lookup_color (style, name, &color);
312 return gdk_color_to_string (&color);
315 static GtkListStore *
316 create_bday_liststore (GList *contacts)
321 gchar *text_font = NULL;
322 gchar *text_color = NULL;
324 text_font = get_text_font_by_name ("SmallSystemFont");
325 text_color = get_text_color_by_name ("SecondaryTextColor");
327 store = gtk_list_store_new(NUM_COLS,
328 GDK_TYPE_PIXBUF, /* COLUMN_AVATAR */
329 G_TYPE_STRING, /* COLUMN_DISPLAY */
330 G_TYPE_STRING, /* COLUMN_FULLNAME */
331 G_TYPE_INT); /* COLUMN_NEXT_BIRTHDAY */
333 for (contact = contacts; contact != NULL; contact = contact->next) {
334 EContactDate *bdate = NULL;
336 bdate = e_contact_get (E_CONTACT (contact->data), E_CONTACT_BIRTH_DATE);
338 EContactPhoto *photo = NULL;
339 GError *error = NULL;
340 GdkPixbuf *avatar = NULL;
341 gchar *fullname = NULL;
342 guint age = 0, next_birthday = 0;
343 gchar *display_column = NULL;
344 gchar *next_birthday_text = NULL;
346 photo = e_contact_get (E_CONTACT (contact->data), E_CONTACT_PHOTO);
348 if (photo->type == E_CONTACT_PHOTO_TYPE_INLINED) {
349 GdkPixbufLoader *loader;
351 loader = gdk_pixbuf_loader_new ();
352 if (gdk_pixbuf_loader_write (loader, (guchar *) photo->data.inlined.data, photo->data.inlined.length, NULL))
353 avatar = gdk_pixbuf_loader_get_pixbuf (loader);
356 gchar *avatar_filename = NULL;
358 avatar_filename = g_filename_from_uri (photo->data.uri, NULL, NULL);
359 if (avatar_filename) {
360 avatar = gdk_pixbuf_new_from_file (avatar_filename, &error);
361 g_free (avatar_filename);
366 gint height = gdk_pixbuf_get_height (avatar);
368 gint new_height = 48;
369 gint new_width = new_height * gdk_pixbuf_get_width (avatar) / height;
370 avatar = gdk_pixbuf_scale_simple (avatar, new_width, new_height, GDK_INTERP_BILINEAR);
373 e_contact_photo_free (photo);
376 avatar = gdk_pixbuf_new_from_file ("/usr/share/icons/hicolor/48x48/hildon/general_default_avatar.png", &error);
379 fullname = e_contact_get (E_CONTACT (contact->data), E_CONTACT_FULL_NAME);
381 fullname = e_contact_get (E_CONTACT (contact->data), E_CONTACT_GIVEN_NAME);
383 fullname = e_contact_get (E_CONTACT (contact->data), E_CONTACT_FAMILY_NAME);
385 fullname = e_contact_get (E_CONTACT (contact->data), E_CONTACT_NICKNAME);
387 fullname = e_contact_get (E_CONTACT (contact->data), E_CONTACT_ORG);
393 age = calc_age(bdate);
394 next_birthday = calc_next_bday(bdate);
395 next_birthday_text = g_strdup_printf(ngettext ("next birthday in %d day", "next birthday in %d days", next_birthday), next_birthday);
396 display_column = g_strdup_printf("%s <span font_desc=\"%s\" foreground=\"%s\"><sup>(%d)</sup>\n%02d.%02d.%04d, %s</span>",
397 fullname, text_font, text_color, age, bdate->day, bdate->month, bdate->year, next_birthday_text);
399 gtk_list_store_append (store, &iter);
400 gtk_list_store_set (store, &iter,
401 COLUMN_AVATAR, avatar,
402 COLUMN_DISPLAY, display_column,
403 COLUMN_FULLNAME, fullname,
404 COLUMN_NEXT_BIRTHDAY, next_birthday,
408 g_free (display_column);
409 display_column = NULL;
415 if (next_birthday_text)
416 g_free (next_birthday_text);
417 next_birthday_text = NULL;
419 e_contact_date_free (bdate);
434 create_search_bar (BirthdayData *priv)
436 GtkWidget *entry, *button;
437 GtkEntryCompletion *completion;
439 g_return_if_fail (priv);
442 priv->search = gtk_hbox_new (FALSE, HILDON_MARGIN_DEFAULT);
445 entry = hildon_entry_new (HILDON_SIZE_FINGER_HEIGHT);
446 hildon_gtk_entry_set_input_mode (GTK_ENTRY (entry), HILDON_GTK_INPUT_MODE_FULL);
447 gtk_box_pack_start (GTK_BOX (priv->search), entry, TRUE, TRUE, 0);
449 completion = gtk_entry_completion_new ();
450 gtk_entry_completion_set_inline_completion (completion, TRUE);
451 gtk_entry_completion_set_popup_completion (completion, FALSE);
452 gtk_entry_set_completion (GTK_ENTRY (entry), completion);
455 button = GTK_WIDGET (gtk_tool_button_new (gtk_image_new_from_icon_name
456 ("general_close", (GtkIconSize) HILDON_ICON_PIXEL_SIZE_FINGER), "Clear"));
457 gtk_box_pack_end (GTK_BOX (priv->search), button, FALSE, TRUE, 0);
460 g_signal_connect (entry, "changed", G_CALLBACK (on_search_entry_changed), priv);
461 g_signal_connect (button, "clicked", G_CALLBACK (on_search_close_clicked), priv);
463 g_object_set_data (G_OBJECT (priv->search), "entry", entry);
467 create_main_menu (BirthdayData *priv)
472 g_return_if_fail (priv);
474 menu = HILDON_APP_MENU (hildon_app_menu_new ());
475 hildon_window_set_app_menu (HILDON_WINDOW (priv->window), menu);
477 filter = hildon_gtk_radio_button_new (HILDON_SIZE_FINGER_HEIGHT , NULL);
478 gtk_button_set_label (GTK_BUTTON (filter), _("Name"));
479 gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (filter), FALSE);
480 g_signal_connect_after (filter, "clicked", G_CALLBACK (sort_by_name_clicked), priv);
481 hildon_app_menu_add_filter (menu, GTK_BUTTON (filter));
483 filter = hildon_gtk_radio_button_new_from_widget (HILDON_SIZE_FINGER_HEIGHT , GTK_RADIO_BUTTON (filter));
484 gtk_button_set_label (GTK_BUTTON (filter), _("Date"));
485 gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (filter), FALSE);
486 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (filter), TRUE);
487 g_signal_connect_after (filter, "clicked", G_CALLBACK (sort_by_date_clicked), priv);
488 hildon_app_menu_add_filter (menu, GTK_BUTTON (filter));
490 gtk_widget_show_all (GTK_WIDGET (menu));
494 create_main_window (BirthdayData *priv, GtkListStore *store)
496 HildonProgram *program = NULL;
497 GtkWidget *main_vbox, *alignment, *pannable, *tree_view;
498 GtkTreeModel *filter;
499 GtkTreeViewColumn *column;
500 GtkCellRenderer *renderer;
502 g_return_if_fail (priv);
504 program = hildon_program_get_instance ();
505 g_set_application_name (_("Birthday"));
508 priv->window = hildon_stackable_window_new ();
509 hildon_program_add_window (program, HILDON_WINDOW (priv->window));
511 /* create main menu */
512 create_main_menu (priv);
515 alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
516 gtk_alignment_set_padding (GTK_ALIGNMENT (alignment),
517 HILDON_MARGIN_HALF, 0, HILDON_MARGIN_DEFAULT, HILDON_MARGIN_DEFAULT);
518 gtk_container_add (GTK_CONTAINER (priv->window), alignment);
521 main_vbox = gtk_vbox_new (FALSE, 0);
522 gtk_container_add (GTK_CONTAINER (alignment), main_vbox);
524 /* no_search_result label */
525 priv->label = gtk_label_new (_("No contacts with set birthdate"));
526 hildon_helper_set_logical_color (priv->label, GTK_RC_FG,
527 GTK_STATE_NORMAL, "SecondaryTextColor");
528 hildon_helper_set_logical_font (priv->label, "LargeSystemFont");
529 gtk_box_pack_start (GTK_BOX (main_vbox), priv->label, TRUE, TRUE, 0);
531 /* alignment for pannable area */
532 priv->view = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
533 gtk_alignment_set_padding (GTK_ALIGNMENT (priv->view),
534 0, 0, HILDON_MARGIN_DEFAULT, 0);
535 gtk_box_pack_start (GTK_BOX (main_vbox), priv->view, TRUE, TRUE, 0);
537 /* pannable for tree view */
538 pannable = hildon_pannable_area_new ();
539 g_object_set (G_OBJECT (pannable), "mov-mode", HILDON_MOVEMENT_MODE_VERT, NULL);
540 g_object_set_data (G_OBJECT (priv->view), "pannable", pannable);
541 gtk_container_add (GTK_CONTAINER (priv->view), pannable);
543 /* sort list by next birthdays */
544 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
545 COLUMN_NEXT_BIRTHDAY, GTK_SORT_ASCENDING);
546 priv->sorted = GTK_TREE_MODEL (store);
549 filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (store), NULL);
550 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter),
551 birthday_filered_view_visible_func,
554 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter));
555 priv->filter = GTK_TREE_MODEL (filter);
558 tree_view = hildon_gtk_tree_view_new_with_model (HILDON_UI_MODE_NORMAL, filter);
559 gtk_container_add (GTK_CONTAINER (pannable), tree_view);
562 column = gtk_tree_view_column_new ();
563 gtk_tree_view_column_set_fixed_width (column, 709);
564 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
565 renderer = gtk_cell_renderer_text_new ();
566 gtk_tree_view_column_pack_start (column, renderer, TRUE);
567 gtk_tree_view_column_set_attributes (column, renderer,
568 "markup", COLUMN_DISPLAY,
570 g_object_set (G_OBJECT (renderer), "xpad", 10, NULL);
572 gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
575 column = gtk_tree_view_column_new ();
576 gtk_tree_view_column_set_fixed_width (column, 48);
577 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
578 renderer = gtk_cell_renderer_pixbuf_new ();
579 gtk_tree_view_column_pack_end (column, renderer, FALSE);
580 gtk_tree_view_column_set_attributes (column, renderer,
581 "pixbuf", COLUMN_AVATAR,
583 gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
586 create_search_bar(priv);
587 gtk_box_pack_end (GTK_BOX (main_vbox), priv->search, FALSE, FALSE, 0);
590 g_signal_connect (G_OBJECT (priv->window), "destroy", G_CALLBACK (gtk_main_quit), NULL);
591 g_signal_connect (G_OBJECT (priv->window), "key-press-event", G_CALLBACK (on_key_press_event), priv);
593 gtk_widget_show_all (GTK_WIDGET (priv->window));
594 gtk_widget_hide (GTK_WIDGET (priv->label));
595 gtk_widget_hide (GTK_WIDGET (priv->search));
598 int main (int argc, char **argv)
601 osso_context_t *osso_context;
604 GError *error = NULL;
609 hildon_gtk_init (&argc, &argv);
611 /* create application data */
612 data = g_new0 (BirthdayData, 1);
613 data->searched_name = NULL;
616 /* initialize localization */
617 setlocale(LC_ALL, "");
618 bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
619 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
620 textdomain(GETTEXT_PACKAGE);
622 /* initialize osso */
623 osso_context = osso_initialize ("birthday", "0.1", TRUE, NULL);
624 if (osso_context == NULL) {
625 g_critical ("Error initializing osso");
630 if (!osso_abook_init (&argc, &argv, osso_context)) {
631 g_critical ("Error initializing libosso-abook");
635 ebook = e_book_new_system_addressbook (&error);
637 g_warning ("Error opening system address book: %s", error->message);
638 g_error_free (error);
642 if (!e_book_open (ebook, TRUE, &error)) {
643 g_warning ("Error opening system address book: %s", error->message);
644 g_error_free (error);
648 query = e_book_query_any_field_contains ("");
650 if (!e_book_get_contacts (ebook, query, &contacts, &error)) {
651 g_warning ("Error getting contacts: %s", error->message);
652 g_error_free (error);
656 store = create_bday_liststore (contacts);
657 create_main_window (data, store);
662 osso_deinitialize (osso_context);