[svn r350] Changed criteria to move to next program chain.
1 #include <gtk/gtksignal.h>
2 #include <gdk/gdkevents.h>
3 #include <gdk/gdkkeysyms.h>
5 #include "mmyth_uicommon.h"
6 #include "mmyth_epg_grid_widget.h"
8 #include <gmyth/gmyth_util.h>
9 #include <gmyth/gmyth_epg.h>
11 #define PIXELS_HOUR 105
12 #define PROGRAM_SEPARATION 2
15 SELECTION_UPDATED_SIGNAL,
19 struct _MMythEpgGridWidgetPrivate {
20 /* private widget components */
21 GtkWidget *epg_channels_vbox;
22 GtkWidget *epg_programs_vbox;
24 GHashTable *service_model_hash;
26 /* guidegrid attributes */
27 gboolean show_favorites;
28 gint current_start_channel_id;
30 GTimeVal* current_start_time;
31 GTimeVal* current_end_time;
33 guint selected_channel_index;
35 /* GList of ProgramInfo for each Channel */
36 GList * program_list[MAX_DISPLAY_CHANS];
41 GMythBackendInfo *backend_info;
46 static void mmyth_epg_grid_widget_class_init (MMythEpgGridWidgetClass *klass);
47 static void mmyth_epg_grid_widget_init (MMythEpgGridWidget *object);
48 static void mmyth_epg_grid_widget_private_init (MMythEpgGridWidgetPrivate *private);
49 static void mmyth_epg_grid_widget_mount_services (MMythEpgGridWidget *object,
50 GTimeVal* start_time, GTimeVal* end_time);
51 static void mmyth_epg_grid_widget_mount_header (MMythEpgGridWidget *object);
52 static void mmyth_epg_grid_widget_clicked (GtkWidget* widget,
53 GdkEventExpose *event,
55 static GtkWidget *create_event_box_lbl (gchar *str, int width,
56 const GdkColor *bg_color,
57 const GdkColor *fg_color);
59 static void mmyth_epg_grid_widget_fill_programinfos(MMythEpgGridWidgetPrivate *private);
60 static void mmyth_epg_grid_widget_fill_program_row_infos(
61 MMythEpgGridWidgetPrivate *private,
62 unsigned int chanNum, unsigned int row);
64 static gint mmyth_epg_grid_widget_signals[LAST_SIGNAL] = { 0 };
66 G_DEFINE_TYPE(MMythEpgGridWidget, mmyth_epg_grid_widget, GTK_TYPE_EVENT_BOX)
69 mmyth_epg_grid_widget_class_init (MMythEpgGridWidgetClass *klass)
71 g_type_class_add_private (klass, sizeof (MMythEpgGridWidgetPrivate));
73 mmyth_epg_grid_widget_signals[SELECTION_UPDATED_SIGNAL] = g_signal_new (
75 G_TYPE_FROM_CLASS(klass),
80 g_cclosure_marshal_VOID__POINTER,
87 mmyth_epg_grid_widget_private_init (MMythEpgGridWidgetPrivate *private)
91 g_return_if_fail(private != NULL);
93 private->epg_channels_vbox = NULL;
94 private->epg_programs_vbox = NULL;
95 private->service_model_hash = NULL;
97 private->show_favorites = FALSE;
98 private->current_start_channel_id = -1;
100 /* Selected the first diplayable channel initially */
101 private->selected_channel_index = 0;
103 /* TODO fix the current start/end time */
104 private->current_start_time = g_new0( GTimeVal, 1 );
105 private->current_start_time->tv_sec = time(&cur_time);
107 private->current_end_time = g_new0( GTimeVal, 1 );
108 private->current_end_time->tv_sec = time(&cur_time) + 10800;
110 private->DISPLAY_CHANS = MAX_DISPLAY_CHANS;
112 private->backend_info = gmyth_backend_info_new_full ( "localhost", "mythtv",
113 "mythtv", "mythconverg", 6543 );
115 // TODO: Close the epg and unref it in dispose call
116 private->mmyth_epg = gmyth_epg_new ();
117 if (!gmyth_epg_connect (private->mmyth_epg, private->backend_info)) {
118 g_warning ("[%s] Could not connect mysql handler to db", __FUNCTION__);
119 g_object_unref (private->mmyth_epg);
120 private->mmyth_epg = NULL;
125 mmyth_epg_grid_widget_init (MMythEpgGridWidget *mmyth_epg_grid_widget)
127 MMythEpgGridWidgetPrivate *private =
128 MMYTH_EPG_GRID_WIDGET_GET_PRIVATE(mmyth_epg_grid_widget);
130 /* init private fields */
131 mmyth_epg_grid_widget_private_init(private);
133 mmyth_epg_grid_widget->epg_view_model = NULL;
134 mmyth_epg_grid_widget->selected_grid_item = NULL;
136 GtkWidget *epg_event_box = GTK_WIDGET(mmyth_epg_grid_widget);
137 gtk_widget_modify_bg(epg_event_box, GTK_STATE_NORMAL, &main_bg_color);
138 gtk_widget_set_size_request (epg_event_box, 0, 125);
140 GtkWidget *epg_main_hbox = gtk_hbox_new (FALSE, 10);
141 gtk_container_set_border_width(GTK_CONTAINER (epg_main_hbox), 10);
143 gtk_container_add (GTK_CONTAINER (epg_event_box),
147 GtkWidget *epg_channels_vbox = gtk_vbox_new (FALSE, 3);
148 private->epg_channels_vbox = epg_channels_vbox;
151 GtkWidget *epg_programs_vbox = gtk_vbox_new (FALSE, 3);
152 private->epg_programs_vbox = epg_programs_vbox;
155 gtk_box_pack_start (GTK_BOX (epg_main_hbox),
156 epg_channels_vbox, FALSE, FALSE, 0);
157 gtk_box_pack_start (GTK_BOX (epg_main_hbox),
158 epg_programs_vbox, FALSE, FALSE, 0);
160 /* table header (first line) */
161 mmyth_epg_grid_widget_mount_header(mmyth_epg_grid_widget);
163 /* service programs */
164 /* mount service programs with current time */
165 mmyth_epg_grid_widget_mount_services(mmyth_epg_grid_widget,
166 private->current_start_time,
167 private->current_end_time);
171 mmyth_epg_grid_widget_new ()
173 return GTK_WIDGET ( gtk_type_new (mmyth_epg_grid_widget_get_type ()));
177 mmyth_epg_grid_widget_mount_services( MMythEpgGridWidget *mmyth_epg_grid_widget,
178 GTimeVal* start_time, GTimeVal* end_time )
181 GList *channel_list = NULL;
182 GMythChannelInfo *channel_info;
185 MMythEpgGridWidgetPrivate *private =
186 MMYTH_EPG_GRID_WIDGET_GET_PRIVATE(mmyth_epg_grid_widget);
189 /* FIXME shallow free or recursive? */
190 if(mmyth_epg_grid_widget->epg_view_model != NULL) {
191 g_list_free(mmyth_epg_grid_widget->epg_view_model);
192 mmyth_epg_grid_widget->epg_view_model = NULL;
195 if(private->service_model_hash != NULL) {
196 g_hash_table_destroy(private->service_model_hash);
199 private->service_model_hash = g_hash_table_new(NULL, NULL);
201 /* fill program infos from db */
202 mmyth_epg_grid_widget_fill_programinfos(private);
204 channel_list = private->channel_list;
206 /* for each channel get_programs() */
207 for (chanid=0; channel_list &&
208 chanid < private->DISPLAY_CHANS; chanid++) {
209 proglist = (GList *) private->program_list[chanid];
211 channel_info = (GMythChannelInfo *) channel_list->data;
212 channel_list = g_list_next(channel_list);
215 GString *name = NULL;
216 if (channel_info->channel_name)
217 name = g_string_new (channel_info->channel_name->str);
219 GdkColor title_bg_color;
220 title_bg_color.red = 5000;
221 title_bg_color.green = 9000;
222 title_bg_color.blue = 40000;
224 GdkColor title_fg_color;
225 title_fg_color.red = 60000;
226 title_fg_color.green = 60000;
227 title_fg_color.blue = 60000;
229 GtkWidget *event_box_channel = create_event_box_lbl(
234 gtk_box_pack_start (GTK_BOX (private->epg_channels_vbox),
235 event_box_channel, FALSE, FALSE, 0);
237 GtkWidget *epg_line_hbox = gtk_hbox_new (FALSE, 0);
241 bg_color.green = 30000;
242 bg_color.blue = 60000;
245 fg_color.red = 60000;
246 fg_color.green = 60000;
247 fg_color.blue = 60000;
249 /* Content parsing */
250 GList *epg_grid_list = NULL;
252 GMythProgramInfo *proginfo;
254 for (; proglist; proglist = proglist->next) {
255 proginfo = (GMythProgramInfo *) proglist->data;
257 GString *content_name = proginfo->title;
259 GTimeVal* initial_time = g_new0( GTimeVal, 1 );
260 GTimeVal* last_time = g_new0( GTimeVal, 1 );
261 GTimeVal* duration = g_new0( GTimeVal, 1 );
263 GTimeVal* schedule_start_time = proginfo->startts;
264 GTimeVal* schedule_end_time = proginfo->endts;
266 initial_time->tv_sec =
267 (schedule_start_time->tv_sec < start_time->tv_sec) ? start_time->tv_sec : schedule_start_time->tv_sec;
268 last_time->tv_sec = (schedule_end_time->tv_sec > end_time->tv_sec) ? end_time->tv_sec : schedule_end_time->tv_sec;
269 duration->tv_sec = last_time->tv_sec - initial_time->tv_sec;
271 // Verify program time
273 g_debug ("ServiceID: %d, ScheduleID: %d\n", service->id, schedule->id);
274 fprintf (stderr, "program time\nfrom = %d, to = %d\n",
275 schedule->validFrom, schedule->validTo);
279 /* Convert it to local time representation. */
280 if (localtime_r((time_t *)&schedule->validFrom, &loctime) == NULL) {
281 g_warning ("localtime_r error in mmyth_epg_grid_widget!\n");
284 fprintf (stderr, asctime (&loctime));
286 if (localtime_r((time_t *)&schedule->validTo, &loctime) == NULL) {
287 g_warning ("localtime_r error in mmyth_epg_grid_widget!\n");
290 fprintf (stderr, asctime (&loctime));
293 /* fprintf(stderr, "duration = %d\n", duration); */
294 double duration_hour = duration->tv_sec / (double) 3600.0;
295 /* fprintf(stderr, "duration_hour = %lf\n", duration_hour); */
297 int size = PIXELS_HOUR * duration_hour;
300 /* FIXME: UGLY WRONG HACK TO ALIGN PROGRAM TIME!!!*/
301 if( last_time->tv_sec % 3600 != 0 ) {
302 size -= PROGRAM_SEPARATION;
304 if ( initial_time->tv_sec % 3600 != 0 ) {
305 size -= PROGRAM_SEPARATION;
308 pixel_count += size + PROGRAM_SEPARATION;
309 GtkWidget *event_box = create_event_box_lbl(content_name->str,
312 gtk_widget_add_events(event_box,
313 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
315 /* create EpgGridItem */
316 EpgGridItem *epg_grid_item = g_new0(EpgGridItem, 1);
317 epg_grid_item->proginfo = proginfo;
318 epg_grid_item->event_box = event_box;
319 epg_grid_item->object = mmyth_epg_grid_widget;
321 epg_grid_list = g_list_prepend(epg_grid_list, (gpointer) epg_grid_item);
323 gtk_box_pack_start (GTK_BOX (epg_line_hbox),
324 event_box, FALSE, FALSE, PROGRAM_SEPARATION);
326 g_signal_connect (G_OBJECT (event_box), "button-press-event",
327 G_CALLBACK (mmyth_epg_grid_widget_clicked),
328 (gpointer*) epg_grid_list);
331 printf("chaind = %d!!!!" chanid);fflush(stdout);
335 /* No programs for current channel */
336 /* FIXME: size HARDCODED */
337 GtkWidget *event_box = create_event_box_lbl("No program list available",
338 PIXELS_HOUR * 3, &bg_color,
340 gtk_widget_add_events(event_box,
341 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
343 /* create EpgGridItem */
344 EpgGridItem *epg_grid_item = g_new0(EpgGridItem, 1);
345 epg_grid_item->proginfo = NULL;
346 epg_grid_item->event_box = event_box;
347 epg_grid_item->object = mmyth_epg_grid_widget;
349 epg_grid_list = g_list_prepend(epg_grid_list, (gpointer) epg_grid_item);
351 gtk_box_pack_start (GTK_BOX (epg_line_hbox),
352 event_box, FALSE, FALSE, PROGRAM_SEPARATION);
354 g_signal_connect (G_OBJECT (event_box), "button-press-event",
355 G_CALLBACK (mmyth_epg_grid_widget_clicked),
356 (gpointer*) epg_grid_list);
359 epg_grid_list = g_list_reverse(epg_grid_list);
360 mmyth_epg_grid_widget->epg_view_model =
361 g_list_append(mmyth_epg_grid_widget->epg_view_model, epg_grid_list);
363 gtk_box_pack_start (GTK_BOX (private->epg_programs_vbox),
364 epg_line_hbox, FALSE, FALSE, 0);
369 mmyth_epg_grid_widget_mount_header(MMythEpgGridWidget *mmyth_epg_grid_widget)
371 MMythEpgGridWidgetPrivate *private =
372 MMYTH_EPG_GRID_WIDGET_GET_PRIVATE(mmyth_epg_grid_widget);
375 const gchar name_title[] = "Today";
376 GtkWidget * lbl_title = gtk_label_new(name_title);
378 gtk_misc_set_alignment (GTK_MISC(lbl_title), 0.0, 0.5);
380 gtk_box_pack_start (GTK_BOX (private->epg_channels_vbox),
381 lbl_title, FALSE, FALSE, 0);
383 /* hours title line */
384 GtkWidget *epg_programs_hours_hbox = gtk_hbox_new (TRUE, 0);
386 if (localtime_r((time_t *)&private->current_start_time->tv_sec, &hour_tm) == NULL) {
387 g_warning ("localtime_r error in mmyth_epg_grid_widget!\n");
391 if (hour_tm.tm_min>30) {
393 } else if (hour_tm.tm_min>0) {
398 strftime(hour1_str, 8, "%H:%M", &hour_tm);
399 GtkWidget * lbl_hour1 = gtk_label_new(hour1_str);
400 gtk_misc_set_alignment (GTK_MISC(lbl_hour1), 0.0, 0.5);
404 strftime(hour2_str, 8, "%H:%M", &hour_tm);
405 GtkWidget * lbl_hour2 = gtk_label_new(hour2_str);
406 gtk_misc_set_alignment (GTK_MISC(lbl_hour2), 0.0, 0.5);
410 strftime(hour3_str, 8, "%H:%M", &hour_tm);
411 GtkWidget * lbl_hour3 = gtk_label_new(hour3_str);
412 gtk_misc_set_alignment (GTK_MISC(lbl_hour3), 0.0, 0.5);
414 gtk_box_pack_start (GTK_BOX (epg_programs_hours_hbox),
415 lbl_hour1, TRUE, TRUE, 0);
416 gtk_box_pack_start (GTK_BOX (epg_programs_hours_hbox),
417 lbl_hour2, TRUE, TRUE, 0);
418 gtk_box_pack_start (GTK_BOX (epg_programs_hours_hbox),
419 lbl_hour3, TRUE, TRUE, 0);
421 gtk_box_pack_start (GTK_BOX (private->epg_programs_vbox),
422 epg_programs_hours_hbox, FALSE, FALSE, 0);
425 /******************************************************************************
426 * INTERNAL CALLBACKS FOR STATE CHANGE *
427 *****************************************************************************/
429 mmyth_epg_grid_widget_deselect_service(MMythEpgGridWidget *mmyth_epg_grid_widget)
431 EpgGridItem *epg_grid_item;
434 if(mmyth_epg_grid_widget->selected_grid_item != NULL) {
436 (EpgGridItem*) mmyth_epg_grid_widget->selected_grid_item->data;
437 gtk_widget_set_state(GTK_WIDGET(epg_grid_item->event_box), GTK_STATE_NORMAL);
442 mmyth_epg_grid_widget_clicked (GtkWidget* widget,
443 GdkEventExpose *event, gpointer data)
445 g_return_if_fail(data != NULL);
447 GList *epg_grid_item_list = (GList *) data;
448 EpgGridItem *epg_grid_item = (EpgGridItem *) epg_grid_item_list->data;
450 /* update the selected service */
451 mmyth_epg_grid_widget_update_service( epg_grid_item->object, (GList*) data );
455 mmyth_epg_grid_widget_update_service(MMythEpgGridWidget * object,
456 GList *selected_grid_list)
458 g_return_if_fail(object != NULL);
459 g_return_if_fail(selected_grid_list != NULL);
461 EpgGridItem *epg_grid_item = (EpgGridItem *) selected_grid_list->data;
463 mmyth_epg_grid_widget_deselect_service(epg_grid_item->object);
465 /* updating current selected schedule_item and schedule_list*/
466 object->selected_grid_item = selected_grid_list;
468 /* set state of the event box */
469 gtk_widget_set_state(GTK_WIDGET(epg_grid_item->event_box), GTK_STATE_SELECTED);
470 /* emit update signal for listeners */
471 g_signal_emit(object,
472 mmyth_epg_grid_widget_signals[SELECTION_UPDATED_SIGNAL],
474 (gpointer) epg_grid_item);
478 create_event_box_lbl(gchar *str, int width, const GdkColor *bg_color,
479 const GdkColor *fg_color)
481 GtkWidget *event_box = gtk_event_box_new();
482 GtkWidget *lbl = gtk_label_new(str);
483 gtk_label_set_ellipsize(GTK_LABEL(lbl), PANGO_ELLIPSIZE_END);
485 gtk_widget_modify_bg(event_box, GTK_STATE_NORMAL, bg_color);
486 gtk_widget_modify_fg(lbl, GTK_STATE_NORMAL, fg_color);
488 /* selected colors are const*/
489 GdkColor selected_bg_color;
490 selected_bg_color.red = 100;
491 selected_bg_color.green = 40000;
492 selected_bg_color.blue = 100;
494 GdkColor selected_fg_color;
495 selected_fg_color.red = 100;
496 selected_fg_color.green = 100;
497 selected_fg_color.blue = 100;
499 gtk_widget_modify_bg(event_box, GTK_STATE_SELECTED, &selected_bg_color);
500 gtk_widget_modify_fg(lbl, GTK_STATE_SELECTED, &selected_fg_color);
502 gtk_misc_set_alignment (GTK_MISC(lbl), 0.0, 0.5);
503 gtk_container_add (GTK_CONTAINER (event_box),
505 gtk_widget_set_size_request(event_box, width, -1);
510 /******************************************************************************
512 *****************************************************************************/
514 /* Callback for hardware keys */
516 mmyth_epg_grid_widget_key_press (MMythEpgGridWidget * object,
517 GtkWidget * widget, GdkEventKey * event)
519 MMythEpgGridWidgetPrivate *private =
520 MMYTH_EPG_GRID_WIDGET_GET_PRIVATE(object);
522 EpgGridItem *epg_grid_item;
525 /* List of selected_grid_item */
526 GList *selected_view_model;
530 if(object->selected_grid_item == NULL) {
531 g_warning ("No program selected");
535 epg_grid_item = (EpgGridItem*) object->selected_grid_item->data;
537 channel_index = private->selected_channel_index;
539 switch (event->keyval) {
541 selected_view_model = g_list_nth( object->epg_view_model, channel_index - 1 );
542 if(selected_view_model != NULL) {
543 private->selected_channel_index = channel_index - 1;
544 tmp = (GList *) selected_view_model->data;
545 /* TODO: select a better centralized item
546 currently is picking the 1st or last item */
547 if(g_list_next(object->selected_grid_item) == NULL &&
548 g_list_previous(object->selected_grid_item) != NULL) {
549 /* in this case the new selected will be the last */
550 tmp = g_list_last(tmp);
553 /* update the selected service */
554 mmyth_epg_grid_widget_update_service( object, tmp );
558 selected_view_model = g_list_nth( object->epg_view_model, channel_index + 1 );
559 if(selected_view_model != NULL) {
560 private->selected_channel_index = channel_index + 1;
561 tmp = (GList *) selected_view_model->data;
562 /* TODO: select a better centralized item
563 currently is picking the 1st or last item */
564 if(g_list_next(object->selected_grid_item) == NULL &&
565 g_list_previous(object->selected_grid_item) != NULL) {
566 /* in this case the new selected will be the last */
567 tmp = g_list_last(tmp);
570 /* update the selected service */
571 mmyth_epg_grid_widget_update_service( object, tmp );
575 tmp = g_list_previous( object->selected_grid_item );
577 /* update the selected service */
578 mmyth_epg_grid_widget_update_service( object, tmp );
582 tmp = g_list_next( object->selected_grid_item );
584 /* update the selected service */
585 mmyth_epg_grid_widget_update_service( object, tmp );
596 mmyth_epg_grid_widget_fill_programinfos (MMythEpgGridWidgetPrivate *private)
598 GList *channels_list = NULL;
601 if ((private->mmyth_epg != NULL) &&
602 (gmyth_epg_get_channel_list (private->mmyth_epg, &channels_list) < 0 )) {
603 private->channel_list = NULL;
607 private->channel_list = channels_list;
609 for (y = 0; y < private->DISPLAY_CHANS && channels_list; y++) {
610 GMythChannelInfo *channel_info = (GMythChannelInfo *) channels_list->data;
612 mmyth_epg_grid_widget_fill_program_row_infos(
613 private, channel_info->channel_ID, y);
615 channels_list = g_list_next (channels_list);
620 mmyth_epg_grid_widget_fill_program_row_infos(MMythEpgGridWidgetPrivate *private,
621 guint chanNum, guint row)
623 gint res = gmyth_epg_get_program_list (private->mmyth_epg,
624 &(private->program_list[row]),
625 chanNum, private->current_start_time,
626 private->current_end_time);
629 g_warning ("[%s] Error while retrieving epg programs", __FUNCTION__);