Initial Import
[profile/ivi/clutter-toys.git] / attic / youhaa / src / yh-youtube-browser.c
1
2 #ifdef HAVE_CONFIG_H
3 # include <config.h>
4 #endif
5
6 #include "yh-youtube-browser.h"
7 #include "yh-youtube.h"
8 #include "yh-theme.h"
9
10 #include <clutter/clutter.h>
11 #include <clutter-gst/clutter-gst.h>
12 #include <string.h>
13
14 enum
15 {
16   PROP_0,
17   
18   PROP_MODEL,
19   PROP_YOUTUBE,
20 };
21
22 struct _YHYoutubeBrowserPrivate {
23   ClutterModel     *model;
24   YHYoutube        *youtube;
25   ClutterModelIter *iter;
26   
27   ClutterActor     *group;
28   
29   ClutterActor     *frame;
30   ClutterActor     *related;
31   ClutterActor     *related_label;
32   ClutterActor     *prev;
33   ClutterActor     *next;
34   ClutterActor     *thumb;
35   ClutterActor     *title;
36   ClutterActor     *author;
37   ClutterActor     *rating;
38   ClutterActor     *description;
39   
40   GList            *thumb_handles;
41   GList            *thumbs;
42   GList            *current_thumb;
43   ClutterTimeline  *timeline_in;
44   ClutterTimeline  *timeline_out;
45   guint             fade_timeout;
46   ClutterEffectTemplate *template;
47   gboolean          loading;
48 };
49
50 G_DEFINE_TYPE (YHYoutubeBrowser, yh_youtube_browser, CLUTTER_TYPE_ACTOR)
51
52 #define YOUTUBE_BROWSER_PRIVATE(obj) \
53         (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
54         YH_TYPE_YOUTUBE_BROWSER, \
55         YHYoutubeBrowserPrivate))
56
57 enum
58 {
59   RELATED,
60
61   LAST_SIGNAL
62 };
63
64 static guint signals[LAST_SIGNAL] = { 0, };
65
66 static void
67 video_out_complete_cb (ClutterActor *actor, YHYoutubeBrowser *browser)
68 {
69   clutter_actor_destroy (actor);
70 }
71
72 static gboolean
73 true_function ()
74 {
75   return TRUE;
76 }
77
78 static gboolean
79 video_bg_button_press_cb (ClutterActor       *actor,
80                           ClutterButtonEvent *event,
81                           YHYoutubeBrowser   *browser)
82 {
83   YHYoutubeBrowserPrivate *priv = browser->priv;
84
85   clutter_effect_fade (priv->template,
86                        actor,
87                        0x00,
88                        (ClutterEffectCompleteFunc)video_out_complete_cb,
89                        browser);
90   
91   /* Disconnect handler and just block events until we're faded out */
92   g_signal_handlers_disconnect_by_func (actor,
93                                         video_bg_button_press_cb,
94                                         browser);
95   g_signal_connect (actor, "button-press-event",
96                     G_CALLBACK (true_function), NULL);
97   
98   return TRUE;
99 }
100
101 static void
102 video_in_complete_cb (ClutterActor *actor, YHYoutubeBrowser *browser)
103 {
104   g_signal_handlers_disconnect_by_func (actor,
105                                         true_function,
106                                         browser);
107   g_signal_connect (actor, "button-press-event",
108                     G_CALLBACK (video_bg_button_press_cb), browser);
109 }
110
111 static void
112 video_error_cb (ClutterMedia *media, GError *error)
113 {
114   g_warning ("Error from video: %s", error->message);
115 }
116
117 static void
118 video_buffer_notify_cb (ClutterMedia *media,
119                         GParamSpec *pspec,
120                         YHYoutubeBrowser *browser)
121 {
122   if ((clutter_media_get_buffer_percent (media) == 100) &&
123       (!clutter_media_get_playing (media)))
124     clutter_media_set_playing (media, TRUE);
125 }
126
127 static void
128 link_cb (YHYoutube *youtube, const gchar *url, YHYoutubeBrowser *browser)
129 {
130   ClutterUnit width, height;
131   ClutterActor *group, *rect, *player, *player_bg;
132   
133   YHYoutubeBrowserPrivate *priv = browser->priv;
134
135   priv->loading = FALSE;
136   
137   clutter_actor_get_sizeu (priv->frame, &width, &height);
138   
139   group = clutter_group_new ();
140   
141   rect = clutter_rectangle_new_with_color (&stage_color);
142   clutter_actor_set_opacity (rect, 128);
143   clutter_actor_set_size (rect, CLUTTER_STAGE_WIDTH(), CLUTTER_STAGE_HEIGHT());
144   
145   player_bg = clutter_rectangle_new_with_color (&frame_color);
146   clutter_actor_set_sizeu (player_bg,
147                            (width*4)/5 + UFRAME,
148                            (height*4)/5 + UFRAME);
149   
150   player = clutter_gst_video_texture_new ();
151   g_signal_connect (player, "error", G_CALLBACK (video_error_cb), browser);
152   g_signal_connect (player, "notify::buffer-percent",
153                     G_CALLBACK (video_buffer_notify_cb), browser);
154
155   g_object_set (G_OBJECT (player),
156                 "sync-size", FALSE,
157                 "uri", url,
158                 "playing", FALSE,
159                 NULL);
160   
161   clutter_actor_set_sizeu (player,
162                            (width*4)/5,
163                            (height*4)/5);
164   
165   clutter_container_add (CLUTTER_CONTAINER (group),
166                          rect, player_bg, player, NULL);
167   clutter_actor_show_all (group);
168   
169   clutter_container_add_actor (CLUTTER_CONTAINER (priv->group), group);
170   clutter_actor_set_position (group, 0, 0);
171   
172   clutter_actor_set_anchor_point_from_gravity (player,
173                                                CLUTTER_GRAVITY_CENTER);
174   clutter_actor_set_anchor_point_from_gravity (player_bg,
175                                                CLUTTER_GRAVITY_CENTER);
176   clutter_actor_set_positionu (player, width/2, height/2);
177   clutter_actor_set_positionu (player_bg, width/2, height/2);
178   
179   clutter_actor_set_opacity (group, 0x00);
180   clutter_effect_fade (priv->template,
181                        group,
182                        0xFF,
183                        (ClutterEffectCompleteFunc)video_in_complete_cb,
184                        browser);
185   clutter_actor_set_reactive (group, TRUE);
186   g_signal_connect (group, "button-press-event",
187                     G_CALLBACK (true_function), browser);
188 }
189
190 static gboolean
191 thumb_button_press_cb (ClutterActor       *actor,
192                        ClutterButtonEvent *event,
193                        YHYoutubeBrowser   *browser)
194 {
195   gchar **uris;
196   
197   YHYoutubeBrowserPrivate *priv = browser->priv;
198
199   if (priv->loading)
200     return TRUE;
201
202   clutter_model_iter_get (priv->iter, YH_YOUTUBE_COL_URIS, &uris, -1);
203   
204   if (!uris)
205     return TRUE;
206   
207   if (!uris[0])
208     {
209       g_strfreev (uris);
210       return TRUE;
211     }
212
213   /* We assume the http link comes first
214    * (which it does, but should probably check)
215    */
216   yh_youtube_get_http_link (priv->youtube, uris[0]);
217   
218   g_strfreev (uris);
219   priv->loading = TRUE;
220   
221   return TRUE;
222 }
223
224 static void
225 in_complete_cb (ClutterActor *actor, YHYoutubeBrowser *browser)
226 {
227   YHYoutubeBrowserPrivate *priv = browser->priv;
228   
229   priv->timeline_in = NULL;
230 }
231
232 static void
233 out_complete_cb (ClutterActor *actor, YHYoutubeBrowser *browser)
234 {
235   YHYoutubeBrowserPrivate *priv = browser->priv;
236   
237   priv->timeline_out = NULL;
238 }
239
240 static gboolean
241 crossfade_timeout (YHYoutubeBrowser *browser)
242 {
243   YHYoutubeBrowserPrivate *priv = browser->priv;
244   
245   if (!priv->current_thumb)
246     return FALSE;
247   
248   if (priv->current_thumb->next ||
249       (priv->current_thumb != priv->thumbs))
250     {
251       ClutterActor *in, *out;
252       
253       out = CLUTTER_ACTOR (priv->current_thumb->data);
254       
255       priv->current_thumb = priv->current_thumb->next ?
256         priv->current_thumb->next : priv->thumbs;
257       
258       in = CLUTTER_ACTOR (priv->current_thumb->data);
259       
260       /* Cross-fade effect */
261       priv->timeline_out = clutter_effect_fade (priv->template,
262                                                 out,
263                                                 0x00,
264                                                 (ClutterEffectCompleteFunc)
265                                                  out_complete_cb,
266                                                 browser);
267       priv->timeline_in = clutter_effect_fade (priv->template,
268                                                in,
269                                                0xFF,
270                                                (ClutterEffectCompleteFunc)
271                                                 in_complete_cb,
272                                                browser);
273     }
274   
275   return TRUE;
276 }
277
278 static void
279 complete_cb (YHYoutube *youtube, void *handle, YHYoutubeBrowser *browser)
280 {
281   GList *l;
282   YHYoutubeBrowserPrivate *priv = browser->priv;
283   
284   if ((l = g_list_find (priv->thumb_handles, handle)))
285     priv->thumb_handles = g_list_delete_link (priv->thumb_handles, l);
286 }
287
288 static void
289 thumbnail_cb (YHYoutube *youtube, GdkPixbuf *pixbuf, YHYoutubeBrowser *browser)
290 {
291   ClutterActor *thumb;
292   YHYoutubeBrowserPrivate *priv = browser->priv;
293   
294   if (!pixbuf)
295     return;
296   
297   thumb = clutter_texture_new_from_pixbuf (pixbuf);
298   if (!thumb)
299     return;
300   
301   priv->thumbs = g_list_append (priv->thumbs, thumb);
302   
303   clutter_container_add_actor (CLUTTER_CONTAINER (priv->group), thumb);
304   clutter_actor_set_positionu (thumb,
305                                clutter_actor_get_xu (priv->prev),
306                                UBORDER/2);
307   clutter_actor_set_sizeu (thumb,
308                            clutter_actor_get_widthu (priv->frame)/2 -
309                            (UBORDER*3)/2,
310                            (clutter_actor_get_heightu (priv->frame)*3)/4 -
311                            UBORDER);
312   clutter_actor_show (thumb);
313   clutter_actor_set_opacity (thumb, 0x00);
314   
315   clutter_actor_set_reactive (thumb, TRUE);
316   g_signal_connect (thumb, "button-press-event",
317                     G_CALLBACK (thumb_button_press_cb), browser);
318
319   if (!priv->current_thumb)
320     {
321       /* Fade in */
322       priv->current_thumb = priv->thumbs;
323       priv->timeline_in = clutter_effect_fade (priv->template,
324                                                thumb,
325                                                0xFF,
326                                                (ClutterEffectCompleteFunc)
327                                                 in_complete_cb,
328                                                browser);
329       priv->fade_timeout = g_timeout_add_seconds (5,
330                                                   (GSourceFunc)crossfade_timeout,
331                                                   browser);
332     }
333 }
334
335 static void
336 free_thumbs (YHYoutubeBrowser *self)
337 {
338   YHYoutubeBrowserPrivate *priv = self->priv;
339   
340   if (priv->fade_timeout)
341     {
342       g_source_remove (priv->fade_timeout);
343       priv->fade_timeout = 0;
344     }
345   
346   if (priv->timeline_in)
347     {
348       clutter_timeline_pause (priv->timeline_in);
349       g_object_unref (priv->timeline_in);
350       priv->timeline_in = NULL;
351     }
352
353   if (priv->timeline_out)
354     {
355       clutter_timeline_pause (priv->timeline_out);
356       g_object_unref (priv->timeline_out);
357       priv->timeline_out = NULL;
358     }
359   
360   while (priv->thumb_handles)
361     {
362       yh_youtube_cancel (priv->youtube, priv->thumb_handles->data);
363       priv->thumb_handles = g_list_delete_link (priv->thumb_handles,
364                                                 priv->thumb_handles);
365     }
366
367   while (priv->thumbs)
368     {
369       clutter_actor_destroy (CLUTTER_ACTOR (priv->thumbs->data));
370       priv->thumbs = g_list_delete_link (priv->thumbs, priv->thumbs);
371     }
372   
373   priv->current_thumb = NULL;
374 }
375
376 static void
377 fill_details (YHYoutubeBrowser *self)
378 {
379   gchar *title, *author, *description, **thumbs;
380   ClutterModelIter *next_iter;
381   gdouble rating;
382   guint row;
383   
384   YHYoutubeBrowserPrivate *priv = self->priv;
385   
386   free_thumbs (self);
387   if (!priv->iter)
388     return;
389   
390   clutter_model_iter_get (priv->iter,
391                           YH_YOUTUBE_COL_TITLE, &title,
392                           YH_YOUTUBE_COL_AUTHOR, &author,
393                           YH_YOUTUBE_COL_DESCRIPTION, &description,
394                           YH_YOUTUBE_COL_RATING, &rating,
395                           YH_YOUTUBE_COL_THUMBS, &thumbs,
396                           -1);
397   
398   clutter_label_set_text (CLUTTER_LABEL (priv->title), title);
399   clutter_label_set_text (CLUTTER_LABEL (priv->author), author);
400   clutter_label_set_text (CLUTTER_LABEL (priv->description), description);
401   
402   switch ((gint)(rating + 0.5))
403     {
404     case 1 :
405       clutter_label_set_text (CLUTTER_LABEL (priv->rating), "★••••");
406       break;
407     case 2 :
408       clutter_label_set_text (CLUTTER_LABEL (priv->rating), "★★•••");
409       break;
410     case 3 :
411       clutter_label_set_text (CLUTTER_LABEL (priv->rating), "★★★••");
412       break;
413     case 4 :
414       clutter_label_set_text (CLUTTER_LABEL (priv->rating), "★★★★•");
415       break;
416     case 5 :
417       clutter_label_set_text (CLUTTER_LABEL (priv->rating), "★★★★★");
418       break;
419     default :
420       clutter_label_set_text (CLUTTER_LABEL (priv->rating), "No rating");
421     }
422   
423   if (clutter_model_iter_is_first (priv->iter))
424     {
425       clutter_actor_set_opacity (priv->prev, 128);
426       clutter_actor_set_reactive (priv->prev, FALSE);
427     }
428   else
429     {
430       clutter_actor_set_opacity (priv->prev, 255);
431       clutter_actor_set_reactive (priv->prev, TRUE);
432     }
433
434   row = clutter_model_iter_get_row (priv->iter);
435   next_iter = clutter_model_get_iter_at_row (priv->model, row + 1);
436   if (!next_iter)
437     {
438       clutter_actor_set_opacity (priv->next, 128);
439       clutter_actor_set_reactive (priv->next, FALSE);
440     }
441   else
442     {
443       clutter_actor_set_opacity (priv->next, 255);
444       clutter_actor_set_reactive (priv->next, TRUE);
445       g_object_unref (next_iter);
446     }
447   
448   if (thumbs)
449     {
450       gint i;
451       for (i = 0; thumbs[i]; i++)
452         {
453           priv->thumb_handles = g_list_append (priv->thumb_handles,
454                                         yh_youtube_get_thumb (priv->youtube,
455                                                               thumbs[i]));
456         }
457       g_strfreev (thumbs);
458     }
459 }
460
461 static void
462 yh_youtube_browser_get_property (GObject *object, guint property_id,
463                                  GValue *value, GParamSpec *pspec)
464 {
465   YHYoutubeBrowser *self = YH_YOUTUBE_BROWSER (object);
466   
467   switch (property_id)
468     {
469     case PROP_MODEL :
470       g_value_set_object (value, self->priv->model);
471       break;
472     
473     case PROP_YOUTUBE :
474       g_value_set_object (value, self->priv->youtube);
475       break;
476     
477     default:
478       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
479     }
480 }
481
482 static void
483 yh_youtube_browser_set_property (GObject *object, guint property_id,
484                                  const GValue *value, GParamSpec *pspec)
485 {
486   YHYoutubeBrowser *self = YH_YOUTUBE_BROWSER (object);
487   
488   switch (property_id)
489     {
490     case PROP_MODEL :
491       self->priv->model = CLUTTER_MODEL (g_value_dup_object (value));
492       self->priv->iter = clutter_model_get_first_iter (self->priv->model);
493       if (self->priv->youtube)
494         fill_details (self);
495       break;
496     
497     case PROP_YOUTUBE :
498       self->priv->youtube = YH_YOUTUBE (g_value_dup_object (value));
499       g_signal_connect (self->priv->youtube, "complete",
500                         G_CALLBACK (complete_cb), self);
501       g_signal_connect (self->priv->youtube, "thumbnail",
502                         G_CALLBACK (thumbnail_cb), self);
503       g_signal_connect (self->priv->youtube, "link",
504                         G_CALLBACK (link_cb), self);
505       if (self->priv->model)
506         fill_details (self);
507       break;
508     
509     default:
510       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
511     }
512 }
513
514 static void
515 yh_youtube_browser_dispose (GObject *object)
516 {
517   YHYoutubeBrowser *browser = YH_YOUTUBE_BROWSER (object);
518   YHYoutubeBrowserPrivate *priv = browser->priv;
519   
520   free_thumbs (browser);
521   
522   if (priv->iter)
523     {
524       g_object_unref (priv->iter);
525       priv->iter = NULL;
526     }
527   
528   if (priv->model)
529     {
530       g_object_unref (priv->model);
531       priv->model = NULL;
532     }
533   
534   if (priv->template)
535     {
536       g_object_unref (priv->template);
537       priv->template = NULL;
538     }
539   
540   if (priv->group)
541     {
542       clutter_actor_unparent (priv->group);
543       priv->group = NULL;
544     }
545   
546   if (priv->youtube)
547     {
548       g_signal_handlers_disconnect_by_func (priv->youtube,
549                                             complete_cb,
550                                             browser);
551       g_signal_handlers_disconnect_by_func (priv->youtube,
552                                             thumbnail_cb,
553                                             browser);
554       g_signal_handlers_disconnect_by_func (priv->youtube,
555                                             link_cb,
556                                             browser);
557       g_object_unref (priv->youtube);
558       priv->youtube = NULL;
559     }
560   
561   if (G_OBJECT_CLASS (yh_youtube_browser_parent_class)->dispose)
562     G_OBJECT_CLASS (yh_youtube_browser_parent_class)->dispose (object);
563 }
564
565 static void
566 yh_youtube_browser_paint (ClutterActor *actor)
567 {
568   YHYoutubeBrowserPrivate *priv = YH_YOUTUBE_BROWSER (actor)->priv;
569   
570   clutter_actor_paint (priv->group);
571 }
572
573 static void
574 yh_youtube_browser_pick (ClutterActor *actor, const ClutterColor *color)
575 {
576   yh_youtube_browser_paint (actor);
577 }
578
579 static void
580 yh_youtube_browser_request_coords (ClutterActor *actor, ClutterActorBox *box)
581 {
582   ClutterUnit width, height;
583   
584   YHYoutubeBrowserPrivate *priv = YH_YOUTUBE_BROWSER (actor)->priv;
585   
586   width = box->x2 - box->x1;
587   height = box->y2 - box->y1;
588   
589   clutter_actor_set_sizeu (priv->frame, width, height);
590   
591   clutter_actor_set_widthu (priv->title, width/2 - (UBORDER*3)/2);
592   clutter_actor_set_widthu (priv->author, width/2 - (UBORDER*3)/2);
593   clutter_actor_set_widthu (priv->rating, width/2 - (UBORDER*3)/2);
594   clutter_actor_set_widthu (priv->description,
595                            width/2 - (UBORDER*3)/2);
596   clutter_actor_set_clipu (priv->description, 0, 0,
597                            clutter_actor_get_widthu (priv->description),
598                            height - clutter_actor_get_yu (priv->description) -
599                            UBORDER/2);
600   
601   clutter_actor_set_positionu (priv->related,
602                                width/2 + UBORDER/2,
603                                (height*3)/4 - UBORDER/2);
604   clutter_actor_set_sizeu (priv->related, width/2 - (UBORDER*3)/2,
605                            height/10);
606   clutter_actor_set_anchor_point_from_gravity (priv->related_label,
607                                                CLUTTER_GRAVITY_CENTER);
608   clutter_actor_set_positionu (priv->related_label,
609                                clutter_actor_get_xu (priv->related) +
610                                clutter_actor_get_widthu (priv->related)/2,
611                                clutter_actor_get_yu (priv->related) +
612                                clutter_actor_get_heightu (priv->related)/2);
613   
614   clutter_actor_set_positionu (priv->prev,
615                                width/2 + UBORDER/2,
616                                (height*3)/4 +
617                                clutter_actor_get_heightu (priv->related));
618   clutter_actor_set_sizeu (priv->prev, width/4 - UBORDER, height/4 - UBORDER/2 -
619                            clutter_actor_get_heightu (priv->related));
620
621   clutter_actor_set_positionu (priv->next,
622                        clutter_actor_get_xu (priv->prev) +
623                        clutter_actor_get_widthu (priv->prev) + UBORDER/2,
624                        (height*3)/4 +
625                        clutter_actor_get_heightu (priv->related));
626   clutter_actor_set_sizeu (priv->next, width/4 - UBORDER, height/4 - UBORDER/2 -
627                            clutter_actor_get_heightu (priv->related));
628
629   CLUTTER_ACTOR_CLASS (yh_youtube_browser_parent_class)->
630     request_coords (actor, box);
631 }
632
633 static void
634 yh_youtube_browser_class_init (YHYoutubeBrowserClass *klass)
635 {
636   GObjectClass *object_class = G_OBJECT_CLASS (klass);
637   ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
638
639   g_type_class_add_private (klass, sizeof (YHYoutubeBrowserPrivate));
640
641   object_class->get_property = yh_youtube_browser_get_property;
642   object_class->set_property = yh_youtube_browser_set_property;
643   object_class->dispose = yh_youtube_browser_dispose;
644   
645   actor_class->paint = yh_youtube_browser_paint;
646   actor_class->pick = yh_youtube_browser_pick;
647   actor_class->request_coords = yh_youtube_browser_request_coords;
648
649   g_object_class_install_property (object_class,
650                                    PROP_MODEL,
651                                    g_param_spec_object ("model",
652                                                         "Model",
653                                                         "ClutterModel",
654                                                         CLUTTER_TYPE_MODEL,
655                                                         G_PARAM_READWRITE |
656                                                         G_PARAM_CONSTRUCT_ONLY));
657
658   g_object_class_install_property (object_class,
659                                    PROP_YOUTUBE,
660                                    g_param_spec_object ("youtube",
661                                                         "YHYoutube",
662                                                         "Youtube data provider",
663                                                         YH_TYPE_YOUTUBE,
664                                                         G_PARAM_READWRITE |
665                                                         G_PARAM_CONSTRUCT_ONLY));
666
667   signals[RELATED] =
668     g_signal_new ("related",
669                   G_TYPE_FROM_CLASS (klass),
670                   G_SIGNAL_RUN_LAST,
671                   G_STRUCT_OFFSET (YHYoutubeBrowserClass, related),
672                   NULL, NULL,
673                   g_cclosure_marshal_VOID__OBJECT,
674                   G_TYPE_NONE, 1, CLUTTER_TYPE_MODEL_ITER);
675 }
676
677 static gboolean
678 prev_pressed_cb (ClutterActor     *actor,
679                  ClutterEvent     *event,
680                  YHYoutubeBrowser *self)
681 {
682   self->priv->iter = clutter_model_iter_prev (self->priv->iter);
683   fill_details (self);
684   return TRUE;
685 }
686
687 static gboolean
688 next_pressed_cb (ClutterActor     *actor,
689                  ClutterEvent     *event,
690                  YHYoutubeBrowser *self)
691 {
692   self->priv->iter = clutter_model_iter_next (self->priv->iter);
693   fill_details (self);
694   return TRUE;
695 }
696
697 static gboolean
698 related_pressed_cb (ClutterActor     *actor,
699                     ClutterEvent     *event,
700                     YHYoutubeBrowser *self)
701 {
702   g_signal_emit (self, signals[RELATED], 0, self->priv->iter);
703   return TRUE;
704 }
705
706 static void
707 yh_youtube_browser_init (YHYoutubeBrowser *self)
708 {
709   GdkPixbuf *pixbuf;
710   
711   GError *error = NULL;
712   YHYoutubeBrowserPrivate *priv = self->priv = YOUTUBE_BROWSER_PRIVATE (self);
713   
714   priv->group = clutter_group_new ();
715   clutter_actor_set_parent (priv->group, CLUTTER_ACTOR (self));
716   
717   /* Frame */
718   priv->frame = clutter_rectangle_new_with_color (&entry_color);
719   clutter_rectangle_set_border_width (CLUTTER_RECTANGLE (priv->frame), 6);
720   clutter_rectangle_set_border_color (CLUTTER_RECTANGLE (priv->frame),
721                                       &frame_color);
722   
723   /* Related videos button */
724   priv->related = clutter_rectangle_new_with_color (&bg_color);
725   clutter_rectangle_set_border_width (CLUTTER_RECTANGLE (priv->related), FRAME);
726   clutter_rectangle_set_border_color (CLUTTER_RECTANGLE (priv->related),
727                                       &frame_color);
728   priv->related_label = clutter_label_new_full (font, "Related videos",
729                                                 &entry_color);
730   clutter_actor_set_reactive (CLUTTER_ACTOR (priv->related), TRUE);
731   g_signal_connect (priv->related, "button-press-event",
732                     G_CALLBACK (related_pressed_cb), self);
733   
734   /* Previous arrow */
735   pixbuf = gdk_pixbuf_new_from_file_at_size (PKGDATADIR "/go-previous.svg",
736                                              CLUTTER_STAGE_WIDTH () / 5,
737                                              CLUTTER_STAGE_HEIGHT () / 4,
738                                              &error);
739   if (!pixbuf)
740     {
741       g_warning ("Error loading pixbuf: %s", error->message);
742       g_error_free (error);
743       error = NULL;
744     }
745   
746   priv->prev = clutter_texture_new_from_pixbuf (pixbuf);
747   g_signal_connect (priv->prev, "button-press-event",
748                     G_CALLBACK (prev_pressed_cb), self);
749   
750   /* Next arrow */
751   pixbuf = gdk_pixbuf_new_from_file_at_size (PKGDATADIR "/go-next.svg",
752                                              CLUTTER_STAGE_WIDTH () / 5,
753                                              CLUTTER_STAGE_HEIGHT () / 4,
754                                              &error);
755   if (!pixbuf)
756     {
757       g_warning ("Error loading pixbuf: %s", error->message);
758       g_error_free (error);
759     }
760   
761   priv->next = clutter_texture_new_from_pixbuf (pixbuf);
762   g_signal_connect (priv->next, "button-press-event",
763                     G_CALLBACK (next_pressed_cb), self);
764   
765   /* Title */
766   priv->title = clutter_label_new_full (font, "", &text_color);
767   clutter_label_set_ellipsize (CLUTTER_LABEL (priv->title),
768                                PANGO_ELLIPSIZE_END);
769   
770   /* Author */
771   priv->author = clutter_label_new_full (small_font, "", &text_color);
772   clutter_label_set_ellipsize (CLUTTER_LABEL (priv->author),
773                                PANGO_ELLIPSIZE_END);
774
775   /* Rating */
776   priv->rating = clutter_label_new_full (small_font, "", &text_color);
777   clutter_label_set_ellipsize (CLUTTER_LABEL (priv->rating),
778                                PANGO_ELLIPSIZE_END);
779
780   /* Description */
781   priv->description = clutter_label_new_full (small_font, "", &text_color);
782   clutter_label_set_line_wrap (CLUTTER_LABEL (priv->description), TRUE);
783   
784   /* Add widgets to group, they'll be sized (mostly) by request-coords */
785   clutter_container_add (CLUTTER_CONTAINER (priv->group),
786                          priv->frame,
787                          priv->related,
788                          priv->related_label,
789                          priv->prev,
790                          priv->next,
791                          priv->title,
792                          priv->author,
793                          priv->rating,
794                          priv->description,
795                          NULL);
796   clutter_actor_set_position (priv->title, BORDER, BORDER/2);
797   clutter_actor_set_position (priv->author,
798                               BORDER,
799                               clutter_actor_get_y (priv->title) +
800                               clutter_actor_get_height (priv->title) +
801                               BORDER/2);
802   clutter_actor_set_position (priv->rating,
803                               BORDER,
804                               clutter_actor_get_y (priv->author) +
805                               clutter_actor_get_height (priv->author) +
806                               BORDER/2);
807   clutter_actor_set_position (priv->description,
808                               BORDER,
809                               clutter_actor_get_y (priv->rating) +
810                               clutter_actor_get_height (priv->rating) +
811                               BORDER/2);
812   
813   clutter_actor_show_all (priv->group);
814   
815   /* Create template for cycling preview image cross-fades */
816   priv->template = clutter_effect_template_new (
817                    clutter_timeline_new_for_duration (750),
818                    CLUTTER_ALPHA_RAMP_INC);
819 }
820
821 ClutterActor *
822 yh_youtube_browser_new (ClutterModel *model, YHYoutube *youtube)
823 {
824   return CLUTTER_ACTOR (g_object_new (YH_TYPE_YOUTUBE_BROWSER,
825                                       "model", model,
826                                       "youtube", youtube, NULL));
827 }
828