#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("localhost", "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__); } }