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 {
21 * private widget components
23 GtkWidget *epg_channels_vbox;
24 GtkWidget *epg_programs_vbox;
26 GHashTable *service_model_hash;
29 * guidegrid attributes
31 gboolean show_favorites;
32 gint current_start_channel_id;
34 GTimeVal *current_start_time;
35 GTimeVal *current_end_time;
37 guint selected_channel_index;
40 * GList of ProgramInfo for each Channel
42 GList *program_list[MAX_DISPLAY_CHANS];
47 GMythBackendInfo *backend_info;
52 static void mmyth_epg_grid_widget_class_init(MMythEpgGridWidgetClass *
54 static void mmyth_epg_grid_widget_init(MMythEpgGridWidget * object);
56 mmyth_epg_grid_widget_private_init(MMythEpgGridWidgetPrivate * private);
57 static void mmyth_epg_grid_widget_mount_services(MMythEpgGridWidget *
59 GTimeVal * start_time,
61 static void mmyth_epg_grid_widget_mount_header(MMythEpgGridWidget *
63 static void mmyth_epg_grid_widget_clicked(GtkWidget * widget,
64 GdkEventExpose * event,
66 static GtkWidget *create_event_box_lbl(gchar * str, int width,
67 const GdkColor * bg_color,
68 const GdkColor * fg_color);
71 mmyth_epg_grid_widget_fill_programinfos(MMythEpgGridWidgetPrivate *
74 mmyth_epg_grid_widget_fill_program_row_infos(MMythEpgGridWidgetPrivate *
75 private, unsigned int chanNum,
78 static gint mmyth_epg_grid_widget_signals[LAST_SIGNAL] = { 0 };
80 G_DEFINE_TYPE(MMythEpgGridWidget, mmyth_epg_grid_widget,
97 mmyth_epg_grid_widget_class_init(MMythEpgGridWidgetClass * klass)
99 g_type_class_add_private(klass, sizeof(MMythEpgGridWidgetPrivate));
101 mmyth_epg_grid_widget_signals[SELECTION_UPDATED_SIGNAL] =
102 g_signal_new("selection_updated", G_TYPE_FROM_CLASS(klass),
103 G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
104 g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1,
109 mmyth_epg_grid_widget_private_init(MMythEpgGridWidgetPrivate * private)
113 g_return_if_fail(private != NULL);
115 private->epg_channels_vbox = NULL;
116 private->epg_programs_vbox = NULL;
117 private->service_model_hash = NULL;
119 private->show_favorites = FALSE;
120 private->current_start_channel_id = -1;
123 * Selected the first diplayable channel initially
125 private->selected_channel_index = 0;
128 * TODO fix the current start/end time
130 private->current_start_time = g_new0(GTimeVal, 1);
131 private->current_start_time->tv_sec = time(&cur_time);
133 private->current_end_time = g_new0(GTimeVal, 1);
134 private->current_end_time->tv_sec = time(&cur_time) + 10800;
136 private->DISPLAY_CHANS = MAX_DISPLAY_CHANS;
138 private->backend_info =
139 gmyth_backend_info_new_full("localhost", "mythtv", "mythtv",
140 "mythconverg", 6543);
142 // TODO: Close the epg and unref it in dispose call
143 private->mmyth_epg = gmyth_epg_new();
144 if (!gmyth_epg_connect(private->mmyth_epg, private->backend_info)) {
145 g_warning("[%s] Could not connect mysql handler to db",
147 g_object_unref(private->mmyth_epg);
148 private->mmyth_epg = NULL;
153 mmyth_epg_grid_widget_init(MMythEpgGridWidget * mmyth_epg_grid_widget)
155 MMythEpgGridWidgetPrivate *private =
156 MMYTH_EPG_GRID_WIDGET_GET_PRIVATE(mmyth_epg_grid_widget);
159 * init private fields
161 mmyth_epg_grid_widget_private_init(private);
163 mmyth_epg_grid_widget->epg_view_model = NULL;
164 mmyth_epg_grid_widget->selected_grid_item = NULL;
166 GtkWidget *epg_event_box = GTK_WIDGET(mmyth_epg_grid_widget);
167 gtk_widget_modify_bg(epg_event_box, GTK_STATE_NORMAL, &main_bg_color);
168 gtk_widget_set_size_request(epg_event_box, 0, 125);
170 GtkWidget *epg_main_hbox = gtk_hbox_new(FALSE, 10);
171 gtk_container_set_border_width(GTK_CONTAINER(epg_main_hbox), 10);
173 gtk_container_add(GTK_CONTAINER(epg_event_box), epg_main_hbox);
178 GtkWidget *epg_channels_vbox = gtk_vbox_new(FALSE, 3);
179 private->epg_channels_vbox = epg_channels_vbox;
184 GtkWidget *epg_programs_vbox = gtk_vbox_new(FALSE, 3);
185 private->epg_programs_vbox = epg_programs_vbox;
190 gtk_box_pack_start(GTK_BOX(epg_main_hbox),
191 epg_channels_vbox, FALSE, FALSE, 0);
192 gtk_box_pack_start(GTK_BOX(epg_main_hbox),
193 epg_programs_vbox, FALSE, FALSE, 0);
196 * table header (first line)
198 mmyth_epg_grid_widget_mount_header(mmyth_epg_grid_widget);
204 * mount service programs with current time
206 mmyth_epg_grid_widget_mount_services(mmyth_epg_grid_widget,
207 private->current_start_time,
208 private->current_end_time);
212 mmyth_epg_grid_widget_new()
214 return GTK_WIDGET(gtk_type_new(mmyth_epg_grid_widget_get_type()));
218 mmyth_epg_grid_widget_mount_services(MMythEpgGridWidget *
219 mmyth_epg_grid_widget,
220 GTimeVal * start_time,
224 GList *channel_list = NULL;
225 GMythChannelInfo *channel_info;
228 MMythEpgGridWidgetPrivate *private =
229 MMYTH_EPG_GRID_WIDGET_GET_PRIVATE(mmyth_epg_grid_widget);
233 * FIXME shallow free or recursive?
235 if (mmyth_epg_grid_widget->epg_view_model != NULL) {
236 g_list_free(mmyth_epg_grid_widget->epg_view_model);
237 mmyth_epg_grid_widget->epg_view_model = NULL;
240 if (private->service_model_hash != NULL) {
241 g_hash_table_destroy(private->service_model_hash);
244 private->service_model_hash = g_hash_table_new(NULL, NULL);
247 * fill program infos from db
249 mmyth_epg_grid_widget_fill_programinfos(private);
251 channel_list = private->channel_list;
254 * for each channel get_programs()
256 for (chanid = 0; channel_list &&
257 chanid < private->DISPLAY_CHANS; chanid++) {
258 proglist = (GList *) private->program_list[chanid];
260 channel_info = (GMythChannelInfo *) channel_list->data;
261 channel_list = g_list_next(channel_list);
266 GString *name = NULL;
267 if (channel_info->channel_name)
268 name = g_string_new(channel_info->channel_name->str);
270 GdkColor title_bg_color;
271 title_bg_color.red = 5000;
272 title_bg_color.green = 9000;
273 title_bg_color.blue = 40000;
275 GdkColor title_fg_color;
276 title_fg_color.red = 60000;
277 title_fg_color.green = 60000;
278 title_fg_color.blue = 60000;
280 GtkWidget *event_box_channel =
281 create_event_box_lbl(name->str, 90,
285 gtk_box_pack_start(GTK_BOX(private->epg_channels_vbox),
286 event_box_channel, FALSE, FALSE, 0);
288 GtkWidget *epg_line_hbox = gtk_hbox_new(FALSE, 0);
292 bg_color.green = 30000;
293 bg_color.blue = 60000;
296 fg_color.red = 60000;
297 fg_color.green = 60000;
298 fg_color.blue = 60000;
303 GList *epg_grid_list = NULL;
305 GMythProgramInfo *proginfo;
307 for (; proglist; proglist = proglist->next) {
308 proginfo = (GMythProgramInfo *) proglist->data;
310 GString *content_name = proginfo->title;
312 GTimeVal *initial_time = g_new0(GTimeVal, 1);
313 GTimeVal *last_time = g_new0(GTimeVal, 1);
314 GTimeVal *duration = g_new0(GTimeVal, 1);
316 GTimeVal *schedule_start_time = proginfo->startts;
317 GTimeVal *schedule_end_time = proginfo->endts;
319 initial_time->tv_sec =
320 (schedule_start_time->tv_sec <
321 start_time->tv_sec) ? start_time->
322 tv_sec : schedule_start_time->tv_sec;
324 (schedule_end_time->tv_sec >
325 end_time->tv_sec) ? end_time->tv_sec : schedule_end_time->
327 duration->tv_sec = last_time->tv_sec - initial_time->tv_sec;
329 // Verify program time
331 g_debug("ServiceID: %d, ScheduleID: %d\n", service->id,
333 fprintf(stderr, "program time\nfrom = %d, to = %d\n",
334 schedule->validFrom, schedule->validTo);
339 * Convert it to local time representation.
341 if (localtime_r((time_t *) & schedule->validFrom, &loctime) ==
343 g_warning("localtime_r error in mmyth_epg_grid_widget!\n");
346 fprintf(stderr, asctime(&loctime));
348 if (localtime_r((time_t *) & schedule->validTo, &loctime) ==
350 g_warning("localtime_r error in mmyth_epg_grid_widget!\n");
353 fprintf(stderr, asctime(&loctime));
357 * fprintf(stderr, "duration = %d\n", duration);
359 double duration_hour =
360 duration->tv_sec / (double) 3600.0;
362 * fprintf(stderr, "duration_hour = %lf\n", duration_hour);
365 int size = PIXELS_HOUR * duration_hour;
371 * FIXME: UGLY WRONG HACK TO ALIGN PROGRAM TIME!!!
373 if (last_time->tv_sec % 3600 != 0) {
374 size -= PROGRAM_SEPARATION;
376 if (initial_time->tv_sec % 3600 != 0) {
377 size -= PROGRAM_SEPARATION;
380 pixel_count += size + PROGRAM_SEPARATION;
381 GtkWidget *event_box =
382 create_event_box_lbl(content_name->str,
385 gtk_widget_add_events(event_box,
386 GDK_BUTTON_PRESS_MASK |
387 GDK_BUTTON_RELEASE_MASK);
392 EpgGridItem *epg_grid_item = g_new0(EpgGridItem, 1);
393 epg_grid_item->proginfo = proginfo;
394 epg_grid_item->event_box = event_box;
395 epg_grid_item->object = mmyth_epg_grid_widget;
398 g_list_prepend(epg_grid_list, (gpointer) epg_grid_item);
400 gtk_box_pack_start(GTK_BOX(epg_line_hbox),
401 event_box, FALSE, FALSE,
404 g_signal_connect(G_OBJECT(event_box), "button-press-event",
405 G_CALLBACK(mmyth_epg_grid_widget_clicked),
406 (gpointer *) epg_grid_list);
409 printf("chaind = %d!!!!" chanid);
413 if (!epg_grid_list) {
415 * No programs for current channel
418 * FIXME: size HARDCODED
420 GtkWidget *event_box =
421 create_event_box_lbl("No program list available",
422 PIXELS_HOUR * 3, &bg_color,
424 gtk_widget_add_events(event_box,
425 GDK_BUTTON_PRESS_MASK |
426 GDK_BUTTON_RELEASE_MASK);
431 EpgGridItem *epg_grid_item = g_new0(EpgGridItem, 1);
432 epg_grid_item->proginfo = NULL;
433 epg_grid_item->event_box = event_box;
434 epg_grid_item->object = mmyth_epg_grid_widget;
437 g_list_prepend(epg_grid_list, (gpointer) epg_grid_item);
439 gtk_box_pack_start(GTK_BOX(epg_line_hbox),
440 event_box, FALSE, FALSE,
443 g_signal_connect(G_OBJECT(event_box), "button-press-event",
444 G_CALLBACK(mmyth_epg_grid_widget_clicked),
445 (gpointer *) epg_grid_list);
448 epg_grid_list = g_list_reverse(epg_grid_list);
449 mmyth_epg_grid_widget->epg_view_model =
450 g_list_append(mmyth_epg_grid_widget->epg_view_model,
453 gtk_box_pack_start(GTK_BOX(private->epg_programs_vbox),
454 epg_line_hbox, FALSE, FALSE, 0);
459 mmyth_epg_grid_widget_mount_header(MMythEpgGridWidget *
460 mmyth_epg_grid_widget)
462 MMythEpgGridWidgetPrivate *private =
463 MMYTH_EPG_GRID_WIDGET_GET_PRIVATE(mmyth_epg_grid_widget);
466 const gchar name_title[] = "Today";
467 GtkWidget *lbl_title = gtk_label_new(name_title);
469 gtk_misc_set_alignment(GTK_MISC(lbl_title), 0.0, 0.5);
471 gtk_box_pack_start(GTK_BOX(private->epg_channels_vbox),
472 lbl_title, FALSE, FALSE, 0);
477 GtkWidget *epg_programs_hours_hbox = gtk_hbox_new(TRUE, 0);
480 ((time_t *) & private->current_start_time->tv_sec,
482 g_warning("localtime_r error in mmyth_epg_grid_widget!\n");
486 if (hour_tm.tm_min > 30) {
488 } else if (hour_tm.tm_min > 0) {
493 strftime(hour1_str, 8, "%H:%M", &hour_tm);
494 GtkWidget *lbl_hour1 = gtk_label_new(hour1_str);
495 gtk_misc_set_alignment(GTK_MISC(lbl_hour1), 0.0, 0.5);
499 strftime(hour2_str, 8, "%H:%M", &hour_tm);
500 GtkWidget *lbl_hour2 = gtk_label_new(hour2_str);
501 gtk_misc_set_alignment(GTK_MISC(lbl_hour2), 0.0, 0.5);
505 strftime(hour3_str, 8, "%H:%M", &hour_tm);
506 GtkWidget *lbl_hour3 = gtk_label_new(hour3_str);
507 gtk_misc_set_alignment(GTK_MISC(lbl_hour3), 0.0, 0.5);
509 gtk_box_pack_start(GTK_BOX(epg_programs_hours_hbox),
510 lbl_hour1, TRUE, TRUE, 0);
511 gtk_box_pack_start(GTK_BOX(epg_programs_hours_hbox),
512 lbl_hour2, TRUE, TRUE, 0);
513 gtk_box_pack_start(GTK_BOX(epg_programs_hours_hbox),
514 lbl_hour3, TRUE, TRUE, 0);
516 gtk_box_pack_start(GTK_BOX(private->epg_programs_vbox),
517 epg_programs_hours_hbox, FALSE, FALSE, 0);
520 /******************************************************************************
521 * INTERNAL CALLBACKS FOR STATE CHANGE *
522 *****************************************************************************/
524 mmyth_epg_grid_widget_deselect_service(MMythEpgGridWidget *
525 mmyth_epg_grid_widget)
527 EpgGridItem *epg_grid_item;
532 if (mmyth_epg_grid_widget->selected_grid_item != NULL) {
534 (EpgGridItem *) mmyth_epg_grid_widget->selected_grid_item->
536 gtk_widget_set_state(GTK_WIDGET(epg_grid_item->event_box),
542 mmyth_epg_grid_widget_clicked(GtkWidget * widget,
543 GdkEventExpose * event, gpointer data)
545 g_return_if_fail(data != NULL);
547 GList *epg_grid_item_list = (GList *) data;
548 EpgGridItem *epg_grid_item =
549 (EpgGridItem *) epg_grid_item_list->data;
552 * update the selected service
554 mmyth_epg_grid_widget_update_service(epg_grid_item->object,
559 mmyth_epg_grid_widget_update_service(MMythEpgGridWidget * object,
560 GList * selected_grid_list)
562 g_return_if_fail(object != NULL);
563 g_return_if_fail(selected_grid_list != NULL);
565 EpgGridItem *epg_grid_item =
566 (EpgGridItem *) selected_grid_list->data;
568 mmyth_epg_grid_widget_deselect_service(epg_grid_item->object);
571 * updating current selected schedule_item and schedule_list
573 object->selected_grid_item = selected_grid_list;
576 * set state of the event box
578 gtk_widget_set_state(GTK_WIDGET(epg_grid_item->event_box),
581 * emit update signal for listeners
583 g_signal_emit(object,
584 mmyth_epg_grid_widget_signals[SELECTION_UPDATED_SIGNAL],
585 0, (gpointer) epg_grid_item);
589 create_event_box_lbl(gchar * str, int width, const GdkColor * bg_color,
590 const GdkColor * fg_color)
592 GtkWidget *event_box = gtk_event_box_new();
593 GtkWidget *lbl = gtk_label_new(str);
594 gtk_label_set_ellipsize(GTK_LABEL(lbl), PANGO_ELLIPSIZE_END);
596 gtk_widget_modify_bg(event_box, GTK_STATE_NORMAL, bg_color);
597 gtk_widget_modify_fg(lbl, GTK_STATE_NORMAL, fg_color);
600 * selected colors are const
602 GdkColor selected_bg_color;
603 selected_bg_color.red = 100;
604 selected_bg_color.green = 40000;
605 selected_bg_color.blue = 100;
607 GdkColor selected_fg_color;
608 selected_fg_color.red = 100;
609 selected_fg_color.green = 100;
610 selected_fg_color.blue = 100;
612 gtk_widget_modify_bg(event_box, GTK_STATE_SELECTED,
614 gtk_widget_modify_fg(lbl, GTK_STATE_SELECTED, &selected_fg_color);
616 gtk_misc_set_alignment(GTK_MISC(lbl), 0.0, 0.5);
617 gtk_container_add(GTK_CONTAINER(event_box), lbl);
618 gtk_widget_set_size_request(event_box, width, -1);
623 /******************************************************************************
625 *****************************************************************************/
628 * Callback for hardware keys
631 mmyth_epg_grid_widget_key_press(MMythEpgGridWidget * object,
632 GtkWidget * widget, GdkEventKey * event)
634 MMythEpgGridWidgetPrivate *private =
635 MMYTH_EPG_GRID_WIDGET_GET_PRIVATE(object);
637 EpgGridItem *epg_grid_item;
641 * List of selected_grid_item
643 GList *selected_view_model;
647 if (object->selected_grid_item == NULL) {
648 g_warning("No program selected");
652 epg_grid_item = (EpgGridItem *) object->selected_grid_item->data;
654 channel_index = private->selected_channel_index;
656 switch (event->keyval) {
658 selected_view_model =
659 g_list_nth(object->epg_view_model, channel_index - 1);
660 if (selected_view_model != NULL) {
661 private->selected_channel_index = channel_index - 1;
662 tmp = (GList *) selected_view_model->data;
664 * TODO: select a better centralized item currently is
665 * picking the 1st or last item
667 if (g_list_next(object->selected_grid_item) == NULL &&
668 g_list_previous(object->selected_grid_item) != NULL) {
670 * in this case the new selected will be the last
672 tmp = g_list_last(tmp);
676 * update the selected service
678 mmyth_epg_grid_widget_update_service(object, tmp);
682 selected_view_model =
683 g_list_nth(object->epg_view_model, channel_index + 1);
684 if (selected_view_model != NULL) {
685 private->selected_channel_index = channel_index + 1;
686 tmp = (GList *) selected_view_model->data;
688 * TODO: select a better centralized item currently is
689 * picking the 1st or last item
691 if (g_list_next(object->selected_grid_item) == NULL &&
692 g_list_previous(object->selected_grid_item) != NULL) {
694 * in this case the new selected will be the last
696 tmp = g_list_last(tmp);
700 * update the selected service
702 mmyth_epg_grid_widget_update_service(object, tmp);
706 tmp = g_list_previous(object->selected_grid_item);
709 * update the selected service
711 mmyth_epg_grid_widget_update_service(object, tmp);
715 tmp = g_list_next(object->selected_grid_item);
718 * update the selected service
720 mmyth_epg_grid_widget_update_service(object, tmp);
731 mmyth_epg_grid_widget_fill_programinfos(MMythEpgGridWidgetPrivate *
734 GList *channels_list = NULL;
737 if ((private->mmyth_epg != NULL) &&
738 (gmyth_epg_get_channel_list(private->mmyth_epg, &channels_list) <
740 private->channel_list = NULL;
744 private->channel_list = channels_list;
746 for (y = 0; y < private->DISPLAY_CHANS && channels_list; y++) {
747 GMythChannelInfo *channel_info =
748 (GMythChannelInfo *) channels_list->data;
750 mmyth_epg_grid_widget_fill_program_row_infos(private,
754 channels_list = g_list_next(channels_list);
759 mmyth_epg_grid_widget_fill_program_row_infos(MMythEpgGridWidgetPrivate *
760 private, guint chanNum,
763 gint res = gmyth_epg_get_program_list(private->mmyth_epg,
773 g_warning("[%s] Error while retrieving epg programs",