diff -r 000000000000 -r 084148043ccf maemo-ui/src/mmyth_epg_grid_widget.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/maemo-ui/src/mmyth_epg_grid_widget.c Thu Sep 28 22:04:16 2006 +0100 @@ -0,0 +1,622 @@ +#include +#include +#include + +#include "mmyth_uicommon.h" +#include "mmyth_epg_grid_widget.h" + +#include "gmyth_util.h" +#include "gmyth_epg.h" + +#define PIXELS_HOUR 105 +#define PROGRAM_SEPARATION 2 + +enum { + SELECTION_UPDATED_SIGNAL, + LAST_SIGNAL +}; + +struct _MMythEpgGridWidgetPrivate { + /* private widget components */ + GtkWidget *epg_channels_vbox; + GtkWidget *epg_programs_vbox; + + GHashTable *service_model_hash; + + /* guidegrid attributes */ + gboolean show_favorites; + gint current_start_channel_id; + + time_t current_start_time; + time_t current_end_time; + + guint selected_channel_index; + + /* GList of ProgramInfo for each Channel */ + GList * program_list[MAX_DISPLAY_CHANS]; + GList * channel_list; + + GMythEPG *mmyth_epg; + + gint DISPLAY_CHANS; +}; + +static void mmyth_epg_grid_widget_class_init (MMythEpgGridWidgetClass *klass); +static void mmyth_epg_grid_widget_init (MMythEpgGridWidget *object); +static void mmyth_epg_grid_widget_private_init (MMythEpgGridWidgetPrivate *private); +static void mmyth_epg_grid_widget_mount_services (MMythEpgGridWidget *object, + int start_time, int end_time); +static void mmyth_epg_grid_widget_mount_header (MMythEpgGridWidget *object); +static void mmyth_epg_grid_widget_clicked (GtkWidget* widget, + GdkEventExpose *event, + gpointer data); +static GtkWidget *create_event_box_lbl (gchar *str, int width, + const GdkColor *bg_color, + const GdkColor *fg_color); + +static void mmyth_epg_grid_widget_fill_programinfos(MMythEpgGridWidgetPrivate *private); +static void mmyth_epg_grid_widget_fill_program_row_infos( + MMythEpgGridWidgetPrivate *private, + unsigned int chanNum, unsigned int row); + +static gint mmyth_epg_grid_widget_signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE(MMythEpgGridWidget, mmyth_epg_grid_widget, GTK_TYPE_EVENT_BOX) + +static void +mmyth_epg_grid_widget_class_init (MMythEpgGridWidgetClass *klass) +{ + g_type_class_add_private (klass, sizeof (MMythEpgGridWidgetPrivate)); + + mmyth_epg_grid_widget_signals[SELECTION_UPDATED_SIGNAL] = g_signal_new ( + "selection_updated", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); +} + +static void +mmyth_epg_grid_widget_private_init (MMythEpgGridWidgetPrivate *private) +{ + time_t cur_time; + + g_return_if_fail(private != NULL); + + private->epg_channels_vbox = NULL; + private->epg_programs_vbox = NULL; + private->service_model_hash = NULL; + + private->show_favorites = FALSE; + private->current_start_channel_id = -1; + + /* Selected the first diplayable channel initially */ + private->selected_channel_index = 0; + + /* TODO fix the current start/end time */ + private->current_start_time = time(&cur_time); + private->current_end_time = time(&cur_time) + 10800; + + private->DISPLAY_CHANS = MAX_DISPLAY_CHANS; + + // TODO: Close the epg and unref it in dispose call + private->mmyth_epg = gmyth_epg_new (); + if (!gmyth_epg_connect (private->mmyth_epg)) { + g_warning ("[%s] Could not connect mysql handler to db", __FUNCTION__); + g_object_unref (private->mmyth_epg); + private->mmyth_epg = NULL; + } +} + +static void +mmyth_epg_grid_widget_init (MMythEpgGridWidget *mmyth_epg_grid_widget) +{ + MMythEpgGridWidgetPrivate *private = + MMYTH_EPG_GRID_WIDGET_GET_PRIVATE(mmyth_epg_grid_widget); + + /* init private fields */ + mmyth_epg_grid_widget_private_init(private); + + mmyth_epg_grid_widget->epg_view_model = NULL; + mmyth_epg_grid_widget->selected_grid_item = NULL; + + GtkWidget *epg_event_box = GTK_WIDGET(mmyth_epg_grid_widget); + gtk_widget_modify_bg(epg_event_box, GTK_STATE_NORMAL, &main_bg_color); + gtk_widget_set_size_request (epg_event_box, 0, 125); + + GtkWidget *epg_main_hbox = gtk_hbox_new (FALSE, 10); + gtk_container_set_border_width(GTK_CONTAINER (epg_main_hbox), 10); + + gtk_container_add (GTK_CONTAINER (epg_event_box), + epg_main_hbox); + + /* channels vbox */ + GtkWidget *epg_channels_vbox = gtk_vbox_new (FALSE, 3); + private->epg_channels_vbox = epg_channels_vbox; + + /* programs vbox */ + GtkWidget *epg_programs_vbox = gtk_vbox_new (FALSE, 3); + private->epg_programs_vbox = epg_programs_vbox; + + /* packing start */ + gtk_box_pack_start (GTK_BOX (epg_main_hbox), + epg_channels_vbox, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (epg_main_hbox), + epg_programs_vbox, FALSE, FALSE, 0); + + /* table header (first line) */ + mmyth_epg_grid_widget_mount_header(mmyth_epg_grid_widget); + + /* service programs */ + /* mount service programs with current time */ + mmyth_epg_grid_widget_mount_services(mmyth_epg_grid_widget, + private->current_start_time, + private->current_end_time); +} + +GtkWidget* +mmyth_epg_grid_widget_new () +{ + return GTK_WIDGET ( gtk_type_new (mmyth_epg_grid_widget_get_type ())); +} + +static void +mmyth_epg_grid_widget_mount_services(MMythEpgGridWidget *mmyth_epg_grid_widget, + int start_time, int end_time) +{ + GList *proglist; + GList *channel_list = NULL; + GMythChannelInfo *channel_info; + + int chanid; + MMythEpgGridWidgetPrivate *private = + MMYTH_EPG_GRID_WIDGET_GET_PRIVATE(mmyth_epg_grid_widget); + + // update view_model + /* FIXME shallow free or recursive? */ + if(mmyth_epg_grid_widget->epg_view_model != NULL) { + g_list_free(mmyth_epg_grid_widget->epg_view_model); + mmyth_epg_grid_widget->epg_view_model = NULL; + } + + if(private->service_model_hash != NULL) { + g_hash_table_destroy(private->service_model_hash); + } + + private->service_model_hash = g_hash_table_new(NULL, NULL); + + /* fill program infos from db */ + mmyth_epg_grid_widget_fill_programinfos(private); + + channel_list = private->channel_list; + + /* for each channel get_programs() */ + for (chanid=0; channel_list && + chanid < private->DISPLAY_CHANS; chanid++) { + proglist = (GList *) private->program_list[chanid]; + + channel_info = (GMythChannelInfo *) channel_list->data; + channel_list = g_list_next(channel_list); + + /* Service Title*/ + GString *name = NULL; + if (channel_info->channel_name) + name = g_string_new (channel_info->channel_name->str); + + GdkColor title_bg_color; + title_bg_color.red = 5000; + title_bg_color.green = 9000; + title_bg_color.blue = 40000; + + GdkColor title_fg_color; + title_fg_color.red = 60000; + title_fg_color.green = 60000; + title_fg_color.blue = 60000; + + GtkWidget *event_box_channel = create_event_box_lbl( + name->str, 90, + &title_bg_color, + &title_fg_color); + + gtk_box_pack_start (GTK_BOX (private->epg_channels_vbox), + event_box_channel, FALSE, FALSE, 0); + + GtkWidget *epg_line_hbox = gtk_hbox_new (FALSE, 0); + + GdkColor bg_color; + bg_color.red = 5000; + bg_color.green = 30000; + bg_color.blue = 60000; + + GdkColor fg_color; + fg_color.red = 60000; + fg_color.green = 60000; + fg_color.blue = 60000; + + /* Content parsing */ + GList *epg_grid_list = NULL; + + GMythProgramInfo *proginfo; + int pixel_count = 0; + for (; proglist; proglist = proglist->next) { + proginfo = (GMythProgramInfo *) proglist->data; + + GString *content_name = proginfo->title; + + int initial_time, last_time, duration; + + int schedule_start_time = proginfo->startts; + int schedule_end_time = proginfo->endts; + + initial_time = + (schedule_start_time < start_time) ? start_time : schedule_start_time; + last_time = (schedule_end_time > end_time) ? end_time : schedule_end_time; + duration = last_time - initial_time; + + // Verify program time + #if 0 + g_debug ("ServiceID: %d, ScheduleID: %d\n", service->id, schedule->id); + fprintf (stderr, "program time\nfrom = %d, to = %d\n", + schedule->validFrom, schedule->validTo); + + struct tm loctime; + + /* Convert it to local time representation. */ + if (localtime_r((time_t *)&schedule->validFrom, &loctime) == NULL) { + g_warning ("localtime_r error in mmyth_epg_grid_widget!\n"); + return NULL; + } + fprintf (stderr, asctime (&loctime)); + + if (localtime_r((time_t *)&schedule->validTo, &loctime) == NULL) { + g_warning ("localtime_r error in mmyth_epg_grid_widget!\n"); + return NULL; + } + fprintf (stderr, asctime (&loctime)); + #endif + + /* fprintf(stderr, "duration = %d\n", duration); */ + double duration_hour = duration / (double) 3600.0; + /* fprintf(stderr, "duration_hour = %lf\n", duration_hour); */ + + int size = PIXELS_HOUR * duration_hour; + + /* complete hour */ + /* FIXME: UGLY WRONG HACK TO ALIGN PROGRAM TIME!!!*/ + if(last_time%3600 != 0) { + size -= PROGRAM_SEPARATION; + } + if(initial_time%3600 != 0) { + size -= PROGRAM_SEPARATION; + } + + pixel_count += size + PROGRAM_SEPARATION; + GtkWidget *event_box = create_event_box_lbl(content_name->str, + size, &bg_color, + &fg_color); + gtk_widget_add_events(event_box, + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK); + + /* create EpgGridItem */ + EpgGridItem *epg_grid_item = g_new(EpgGridItem, 1); + epg_grid_item->proginfo = proginfo; + epg_grid_item->event_box = event_box; + epg_grid_item->object = mmyth_epg_grid_widget; + + epg_grid_list = g_list_prepend(epg_grid_list, (gpointer) epg_grid_item); + + gtk_box_pack_start (GTK_BOX (epg_line_hbox), + event_box, FALSE, FALSE, PROGRAM_SEPARATION); + + g_signal_connect (G_OBJECT (event_box), "button-press-event", + G_CALLBACK (mmyth_epg_grid_widget_clicked), + (gpointer*) epg_grid_list); + } +#if 0 + printf("chaind = %d!!!!" chanid);fflush(stdout); +#endif + + if(!epg_grid_list) { + /* No programs for current channel */ + /* FIXME: size HARDCODED */ + GtkWidget *event_box = create_event_box_lbl("No program list available", + PIXELS_HOUR * 3, &bg_color, + &fg_color); + gtk_widget_add_events(event_box, + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK); + + /* create EpgGridItem */ + EpgGridItem *epg_grid_item = g_new(EpgGridItem, 1); + epg_grid_item->proginfo = NULL; + epg_grid_item->event_box = event_box; + epg_grid_item->object = mmyth_epg_grid_widget; + + epg_grid_list = g_list_prepend(epg_grid_list, (gpointer) epg_grid_item); + + gtk_box_pack_start (GTK_BOX (epg_line_hbox), + event_box, FALSE, FALSE, PROGRAM_SEPARATION); + + g_signal_connect (G_OBJECT (event_box), "button-press-event", + G_CALLBACK (mmyth_epg_grid_widget_clicked), + (gpointer*) epg_grid_list); + } + + epg_grid_list = g_list_reverse(epg_grid_list); + mmyth_epg_grid_widget->epg_view_model = + g_list_append(mmyth_epg_grid_widget->epg_view_model, epg_grid_list); + + gtk_box_pack_start (GTK_BOX (private->epg_programs_vbox), + epg_line_hbox, FALSE, FALSE, 0); + } +} + +static void +mmyth_epg_grid_widget_mount_header(MMythEpgGridWidget *mmyth_epg_grid_widget) +{ + MMythEpgGridWidgetPrivate *private = + MMYTH_EPG_GRID_WIDGET_GET_PRIVATE(mmyth_epg_grid_widget); + + struct tm hour_tm; + const gchar name_title[] = "Today"; + GtkWidget * lbl_title = gtk_label_new(name_title); + + gtk_misc_set_alignment (GTK_MISC(lbl_title), 0.0, 0.5); + + gtk_box_pack_start (GTK_BOX (private->epg_channels_vbox), + lbl_title, FALSE, FALSE, 0); + + /* hours title line */ + GtkWidget *epg_programs_hours_hbox = gtk_hbox_new (TRUE, 0); + + if (localtime_r((time_t *)&private->current_start_time, &hour_tm) == NULL) { + g_warning ("localtime_r error in mmyth_epg_grid_widget!\n"); + return NULL; + } + + if (hour_tm.tm_min>30) { + hour_tm.tm_min = 30; + } else if (hour_tm.tm_min>0) { + hour_tm.tm_min = 0; + } + + gchar hour1_str[10]; + strftime(hour1_str, 8, "%H:%M", &hour_tm); + GtkWidget * lbl_hour1 = gtk_label_new(hour1_str); + gtk_misc_set_alignment (GTK_MISC(lbl_hour1), 0.0, 0.5); + + hour_tm.tm_hour++; + gchar hour2_str[10]; + strftime(hour2_str, 8, "%H:%M", &hour_tm); + GtkWidget * lbl_hour2 = gtk_label_new(hour2_str); + gtk_misc_set_alignment (GTK_MISC(lbl_hour2), 0.0, 0.5); + + hour_tm.tm_hour++; + gchar hour3_str[10]; + strftime(hour3_str, 8, "%H:%M", &hour_tm); + GtkWidget * lbl_hour3 = gtk_label_new(hour3_str); + gtk_misc_set_alignment (GTK_MISC(lbl_hour3), 0.0, 0.5); + + gtk_box_pack_start (GTK_BOX (epg_programs_hours_hbox), + lbl_hour1, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (epg_programs_hours_hbox), + lbl_hour2, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (epg_programs_hours_hbox), + lbl_hour3, TRUE, TRUE, 0); + + gtk_box_pack_start (GTK_BOX (private->epg_programs_vbox), + epg_programs_hours_hbox, FALSE, FALSE, 0); +} + +/****************************************************************************** + * INTERNAL CALLBACKS FOR STATE CHANGE * + *****************************************************************************/ +static void +mmyth_epg_grid_widget_deselect_service(MMythEpgGridWidget *mmyth_epg_grid_widget) +{ + EpgGridItem *epg_grid_item; + + /* deselect*/ + if(mmyth_epg_grid_widget->selected_grid_item != NULL) { + epg_grid_item = + (EpgGridItem*) mmyth_epg_grid_widget->selected_grid_item->data; + gtk_widget_set_state(GTK_WIDGET(epg_grid_item->event_box), GTK_STATE_NORMAL); + } +} + +static void +mmyth_epg_grid_widget_clicked (GtkWidget* widget, + GdkEventExpose *event, gpointer data) +{ + g_return_if_fail(data != NULL); + + GList *epg_grid_item_list = (GList *) data; + EpgGridItem *epg_grid_item = (EpgGridItem *) epg_grid_item_list->data; + + /* update the selected service */ + mmyth_epg_grid_widget_update_service( epg_grid_item->object, (GList*) data ); +} + +void +mmyth_epg_grid_widget_update_service(MMythEpgGridWidget * object, + GList *selected_grid_list) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(selected_grid_list != NULL); + + EpgGridItem *epg_grid_item = (EpgGridItem *) selected_grid_list->data; + + mmyth_epg_grid_widget_deselect_service(epg_grid_item->object); + + /* updating current selected schedule_item and schedule_list*/ + object->selected_grid_item = selected_grid_list; + + /* set state of the event box */ + gtk_widget_set_state(GTK_WIDGET(epg_grid_item->event_box), GTK_STATE_SELECTED); + /* emit update signal for listeners */ + g_signal_emit(object, + mmyth_epg_grid_widget_signals[SELECTION_UPDATED_SIGNAL], + 0, + (gpointer) epg_grid_item); +} + +static GtkWidget * +create_event_box_lbl(gchar *str, int width, const GdkColor *bg_color, + const GdkColor *fg_color) +{ + GtkWidget *event_box = gtk_event_box_new(); + GtkWidget *lbl = gtk_label_new(str); + gtk_label_set_ellipsize(GTK_LABEL(lbl), PANGO_ELLIPSIZE_END); + + gtk_widget_modify_bg(event_box, GTK_STATE_NORMAL, bg_color); + gtk_widget_modify_fg(lbl, GTK_STATE_NORMAL, fg_color); + + /* selected colors are const*/ + GdkColor selected_bg_color; + selected_bg_color.red = 100; + selected_bg_color.green = 40000; + selected_bg_color.blue = 100; + + GdkColor selected_fg_color; + selected_fg_color.red = 100; + selected_fg_color.green = 100; + selected_fg_color.blue = 100; + + gtk_widget_modify_bg(event_box, GTK_STATE_SELECTED, &selected_bg_color); + gtk_widget_modify_fg(lbl, GTK_STATE_SELECTED, &selected_fg_color); + + gtk_misc_set_alignment (GTK_MISC(lbl), 0.0, 0.5); + gtk_container_add (GTK_CONTAINER (event_box), + lbl); + gtk_widget_set_size_request(event_box, width, -1); + + return event_box; +} + +/****************************************************************************** + * METHODS * + *****************************************************************************/ + +/* Callback for hardware keys */ +gboolean +mmyth_epg_grid_widget_key_press (MMythEpgGridWidget * object, + GtkWidget * widget, GdkEventKey * event) +{ + MMythEpgGridWidgetPrivate *private = + MMYTH_EPG_GRID_WIDGET_GET_PRIVATE(object); + + EpgGridItem *epg_grid_item; + GList *tmp; + + /* List of selected_grid_item */ + GList *selected_view_model; + + gint channel_index; + + if(object->selected_grid_item == NULL) { + g_warning ("No program selected"); + return FALSE; + } + + epg_grid_item = (EpgGridItem*) object->selected_grid_item->data; + + channel_index = private->selected_channel_index; + + switch (event->keyval) { + case GDK_Up: + selected_view_model = g_list_nth( object->epg_view_model, channel_index - 1 ); + if(selected_view_model != NULL) { + private->selected_channel_index = channel_index - 1; + tmp = (GList *) selected_view_model->data; + /* TODO: select a better centralized item + currently is picking the 1st or last item */ + if(g_list_next(object->selected_grid_item) == NULL && + g_list_previous(object->selected_grid_item) != NULL) { + /* in this case the new selected will be the last */ + tmp = g_list_last(tmp); + } + + /* update the selected service */ + mmyth_epg_grid_widget_update_service( object, tmp ); + } + return TRUE; + case GDK_Down: + selected_view_model = g_list_nth( object->epg_view_model, channel_index + 1 ); + if(selected_view_model != NULL) { + private->selected_channel_index = channel_index + 1; + tmp = (GList *) selected_view_model->data; + /* TODO: select a better centralized item + currently is picking the 1st or last item */ + if(g_list_next(object->selected_grid_item) == NULL && + g_list_previous(object->selected_grid_item) != NULL) { + /* in this case the new selected will be the last */ + tmp = g_list_last(tmp); + } + + /* update the selected service */ + mmyth_epg_grid_widget_update_service( object, tmp ); + } + return TRUE; + case GDK_Left: + tmp = g_list_previous( object->selected_grid_item ); + if(tmp != NULL) { + /* update the selected service */ + mmyth_epg_grid_widget_update_service( object, tmp ); + } + return TRUE; + case GDK_Right: + tmp = g_list_next( object->selected_grid_item ); + if(tmp != NULL) { + /* update the selected service */ + mmyth_epg_grid_widget_update_service( object, tmp ); + } + return TRUE; + default: + return TRUE; + } + + return FALSE; +} + +static void +mmyth_epg_grid_widget_fill_programinfos (MMythEpgGridWidgetPrivate *private) +{ + GList *channels_list = NULL; + int y; + + if ((private->mmyth_epg != NULL) && + (gmyth_epg_get_channel_list (private->mmyth_epg, &channels_list) < 0 )) { + private->channel_list = NULL; + return; + } + + private->channel_list = channels_list; + + for (y = 0; y < private->DISPLAY_CHANS && channels_list; y++) { + GMythChannelInfo *channel_info = (GMythChannelInfo *) channels_list->data; + + mmyth_epg_grid_widget_fill_program_row_infos( + private, channel_info->channel_ID, y); + + channels_list = g_list_next (channels_list); + } +} + +static void +mmyth_epg_grid_widget_fill_program_row_infos(MMythEpgGridWidgetPrivate *private, + guint chanNum, guint row) +{ + gint res = gmyth_epg_get_program_list (private->mmyth_epg, + &(private->program_list[row]), + chanNum, private->current_start_time, + private->current_end_time); + + if (res < 0) { + g_warning ("[%s] Error while retrieving epg programs", __FUNCTION__); + } +} +