diff -r cb885ee44618 -r 9f80dd75cd8a maemo-ui-old/src/mmyth_epg_grid_widget.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/maemo-ui-old/src/mmyth_epg_grid_widget.c Tue Jan 29 18:40:32 2008 +0000 @@ -0,0 +1,776 @@ +#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__); + } +}