#include #include #include #include "mmyth_uicommon.h" #include "mmyth_epg_grid_widget.h" #include #include #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; GTimeVal* current_start_time; GTimeVal* 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; GMythBackendInfo *backend_info; 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, GTimeVal* start_time, GTimeVal* 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 = g_new0( GTimeVal, 1 ); private->current_start_time->tv_sec = time(&cur_time); private->current_end_time = g_new0( GTimeVal, 1 ); private->current_end_time->tv_sec = time(&cur_time) + 10800; private->DISPLAY_CHANS = MAX_DISPLAY_CHANS; private->backend_info = gmyth_backend_info_new_full ( "192.168.1.109", "mythtv", "mythtv", "mythconverg", 6543 ); // TODO: Close the epg and unref it in dispose call private->mmyth_epg = gmyth_epg_new (); if (!gmyth_epg_connect (private->mmyth_epg, private->backend_info)) { 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, GTimeVal* start_time, GTimeVal* 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; GTimeVal* initial_time = g_new0( GTimeVal, 1 ); GTimeVal* last_time = g_new0( GTimeVal, 1 ); GTimeVal* duration = g_new0( GTimeVal, 1 ); GTimeVal* schedule_start_time = proginfo->startts; GTimeVal* schedule_end_time = proginfo->endts; initial_time->tv_sec = (schedule_start_time->tv_sec < start_time->tv_sec) ? start_time->tv_sec : schedule_start_time->tv_sec; last_time->tv_sec = (schedule_end_time->tv_sec > end_time->tv_sec) ? end_time->tv_sec : schedule_end_time->tv_sec; duration->tv_sec = last_time->tv_sec - initial_time->tv_sec; // 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->tv_sec / (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->tv_sec % 3600 != 0 ) { size -= PROGRAM_SEPARATION; } if ( initial_time->tv_sec % 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_new0(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_new0(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->tv_sec, &hour_tm) == NULL) { g_warning ("localtime_r error in mmyth_epg_grid_widget!\n"); return; } 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__); } }