Implement clarity preferences
[gtkpod:phantomjinx-gtkpod-plugin.git] / plugins / clarity / clarity_canvas.c
1 /*
2  |  Copyright (C) 2002-2011 Jorg Schuler <jcsjcs at users sourceforge net>
3  |                                             Paul Richardson <phantom_sf at users.sourceforge.net>
4  |  Part of the gtkpod project.
5  |
6  |  URL: http://www.gtkpod.org/
7  |  URL: http://gtkpod.sourceforge.net/
8  |
9  |  This program is free software; you can redistribute it and/or modify
10  |  it under the terms of the GNU General Public License as published by
11  |  the Free Software Foundation; either version 2 of the License, or
12  |  (at your option) any later version.
13  |
14  |  This program is distributed in the hope that it will be useful,
15  |  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  |  GNU General Public License for more details.
18  |
19  |  You should have received a copy of the GNU General Public License
20  |  along with this program; if not, write to the Free Software
21  |  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22  |
23  |  iTunes and iPod are trademarks of Apple
24  |
25  |  This product is not supported/written/published by Apple!
26  |
27  */
28 #include <clutter-gtk/clutter-gtk.h>
29 #include "libgtkpod/gp_itdb.h"
30 #include "plugin.h"
31 #include "clarity_cover.h"
32 #include "clarity_canvas.h"
33 #include "clarity_preview.h"
34 #include "clarity_utils.h"
35
36 G_DEFINE_TYPE( ClarityCanvas, clarity_canvas, GTK_TYPE_BOX);
37
38 #define CLARITY_CANVAS_GET_PRIVATE(obj) \
39   (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLARITY_TYPE_CANVAS, ClarityCanvasPrivate))
40
41 #define MAX_ANGLE                       70
42 #define COVER_SPACE                    50
43 #define FRONT_COVER_SPACE     150
44 #define MAX_SCALE                          1.4
45 #define VISIBLE_ITEMS                     8
46
47 struct _ClarityCanvasPrivate {
48
49     AlbumModel *model;
50
51     // clutter embed widget
52     GtkWidget *embed;
53
54     // clutter items
55     GList *covers;
56     ClutterActor *container;
57     ClutterTimeline *timeline;
58     ClutterAlpha *alpha;
59
60     gint curr_index;
61
62     gulong preview_signal;
63
64     gboolean loading_complete;
65 };
66
67 enum DIRECTION {
68     MOVE_LEFT = -1,
69     MOVE_RIGHT = 1
70 };
71
72 static void clarity_canvas_finalize(GObject *gobject) {
73     ClarityCanvasPrivate *priv = CLARITY_CANVAS(gobject)->priv;
74
75     //FIXME
76 //    g_list_free_full(priv->covers, clarity_cover_destroy);
77
78     if (G_IS_OBJECT(priv->alpha))
79         g_object_unref(priv->alpha);
80
81     if (G_IS_OBJECT(priv->timeline))
82         g_object_unref(priv->timeline);
83
84     if (GTK_IS_WIDGET(priv->embed))
85         gtk_widget_destroy(priv->embed);
86
87     /* call the parent class' finalize() method */
88     G_OBJECT_CLASS(clarity_canvas_parent_class)->finalize(gobject);
89 }
90
91 static void clarity_canvas_class_init(ClarityCanvasClass *klass) {
92     GObjectClass *gobject_class;
93
94     gobject_class = G_OBJECT_CLASS (klass);
95     gobject_class->finalize = clarity_canvas_finalize;
96
97     g_type_class_add_private(klass, sizeof(ClarityCanvasPrivate));
98 }
99
100 static gboolean _preview_cover_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
101     ClarityCanvas *ccanvas = CLARITY_CANVAS(widget);
102     ClarityCanvasPrivate *priv = ccanvas->priv;
103
104     if (!priv->model)
105         return TRUE;
106
107     AlbumItem *item = album_model_get_item(priv->model, priv->curr_index);
108
109     GtkWidget *dialog = clarity_preview_new(item);
110
111     /* Display the dialog */
112     gtk_widget_show_all(dialog);
113
114     return TRUE;
115 }
116
117 /**
118  * embed_widget_size_allocated_cb
119  *
120  * Ensures that when the embed gtk widget is resized or moved
121  * around the clutter animations are centred correctly.
122  *
123  * This finds the new dimensions of the stage each time and centres
124  * the group container accordingly.
125  *
126  */
127 void _embed_widget_size_allocated_cb(GtkWidget *widget,
128                       GtkAllocation *allocation,
129                       gpointer data) {
130     ClarityCanvasPrivate *priv = (ClarityCanvasPrivate *) data;
131     ClutterActor *stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(widget));
132
133     gint centreX = clutter_actor_get_width(stage) / 2;
134     gint centreY = clutter_actor_get_height(stage) / 2;
135     clutter_actor_set_position(priv->container, centreX, centreY);
136 }
137
138 static void clarity_canvas_init(ClarityCanvas *self) {
139     ClarityCanvasPrivate *priv;
140
141     self->priv = CLARITY_CANVAS_GET_PRIVATE (self);
142
143     priv = self->priv;
144
145     priv->container = clutter_group_new();
146     clutter_actor_set_reactive(priv->container, TRUE);
147     priv->preview_signal = g_signal_connect (self,
148                                 "button-press-event",
149                                 G_CALLBACK (_preview_cover_cb),
150                                 priv);
151
152     priv->embed = gtk_clutter_embed_new();
153     /*
154      * Minimum size before the scrollbars of the parent window
155      * are displayed.
156      */
157     gtk_widget_set_size_request(GTK_WIDGET(priv->embed), DEFAULT_IMG_SIZE * 4, DEFAULT_IMG_SIZE * 2.5);
158     /*
159      * Ensure that things are always centred when the embed
160      * widget is resized.
161      */
162     g_signal_connect(priv->embed, "size-allocate",
163                   G_CALLBACK(_embed_widget_size_allocated_cb), priv);
164
165     ClutterActor *stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->embed));
166     clutter_container_add_actor(CLUTTER_CONTAINER(stage), priv->container);
167
168     gtk_widget_show(priv->embed);
169
170     gtk_box_pack_start(GTK_BOX(self), priv->embed, TRUE, TRUE, 0);
171
172     priv->covers = NULL;
173     priv->timeline = clutter_timeline_new(1600);
174     priv->alpha = clutter_alpha_new_full(priv->timeline, CLUTTER_EASE_OUT_EXPO);
175     priv->curr_index = 0;
176     priv->loading_complete = FALSE;
177
178 }
179
180 GtkWidget *clarity_canvas_new() {
181     return g_object_new(CLARITY_TYPE_CANVAS, NULL);
182 }
183
184 /**
185  * coverart_get_background_display_color:
186  *
187  * Returns the background color of the clarity canvas.
188  *
189  * The return value is a hexstring in the form "rrggbbaa"
190  *
191  */
192 GdkRGBA *clarity_canvas_get_background_color(ClarityCanvas *self) {
193     g_return_val_if_fail(CLARITY_IS_CANVAS(self), NULL);
194
195     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
196
197     ClutterActor *stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->embed));
198
199     ClutterColor *ccolor;
200     ccolor = g_malloc(sizeof(ClutterColor));
201
202     clutter_stage_get_color(CLUTTER_STAGE(stage), ccolor);
203     g_return_val_if_fail(ccolor, NULL);
204
205     GdkRGBA *rgba;
206     rgba = g_malloc(sizeof(GdkRGBA));
207     rgba->red = ((gdouble) ccolor->red) / 255;
208     rgba->green = ((gdouble) ccolor->green) / 255;
209     rgba->blue = ((gdouble) ccolor->blue) / 255;
210     rgba->alpha = ((gdouble) ccolor->alpha) / 255;
211
212     return rgba;
213 }
214
215 void clarity_canvas_set_background(ClarityCanvas *self, const gchar *color_string) {
216     g_return_if_fail(self);
217     g_return_if_fail(color_string);
218
219     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
220
221     ClutterActor *stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->embed));
222
223     ClutterColor *ccolor;
224     ccolor = g_malloc(sizeof(ClutterColor));
225
226     clutter_color_from_string(ccolor, color_string);
227     clutter_stage_set_color(CLUTTER_STAGE(stage), ccolor);
228 }
229
230 void clarity_canvas_clear(ClarityCanvas *self) {
231     g_return_if_fail(self);
232     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
233
234     if (CLUTTER_IS_ACTOR(priv->container)) {
235         GList *iter = priv->covers;
236         while(iter) {
237             ClarityCover *ccover = iter->data;
238             // cover is not referenced anywhere else so it should be destroyed too
239             clutter_container_remove(CLUTTER_CONTAINER(priv->container), CLUTTER_ACTOR(ccover), NULL);
240             iter = iter->next;
241         }
242     }
243
244     priv->covers = NULL;
245     priv->model = NULL;
246     priv->curr_index = 0;
247 }
248
249 static void _calculate_index_angle_and_dir (gint dist_from_front, enum DIRECTION dir, gint *angle, ClutterRotateDirection *rotation_dir) {
250     /* The front item direction depends on the direction we came from */
251     if (dist_from_front == 0) {
252         *rotation_dir =  (dir == MOVE_RIGHT ? CLUTTER_ROTATE_CCW : CLUTTER_ROTATE_CW);
253         *angle = 0;
254     }
255
256     /* Item on the right */
257     else if (dist_from_front > 0) {
258         *rotation_dir = CLUTTER_ROTATE_CCW;
259         *angle = 360 - MAX_ANGLE;
260     }
261
262     /* Item on the left */
263     else if (dist_from_front < 0) {
264         *rotation_dir = CLUTTER_ROTATE_CW;
265         *angle = MAX_ANGLE;
266     }
267 }
268
269 static gint _calculate_index_distance (gint dist_from_front) {
270     gint dist = ((ABS(dist_from_front) - 1) * COVER_SPACE) + FRONT_COVER_SPACE;
271
272     if (dist_from_front == 0)
273         return 0;
274
275     return (dist_from_front > 0 ? dist : 0 - dist);
276 }
277
278 static float _calculate_index_scale(gint dist_from_front) {
279     if (dist_from_front == 0)
280         return MAX_SCALE;
281     else
282         return 1;
283 }
284
285 static gint _calculate_index_opacity (gint dist_from_front) {
286     return CLAMP ( 255 * (VISIBLE_ITEMS - ABS(dist_from_front)) / VISIBLE_ITEMS, 0, 255);
287 }
288
289 static void _display_clarity_cover(ClarityCover *ccover, gint index) {
290     ClutterTimeline  *timeline = clutter_timeline_new(1600 * 5);
291     ClutterAlpha *alpha = clutter_alpha_new_full (timeline, CLUTTER_EASE_OUT_EXPO);
292
293     gint opacity = _calculate_index_opacity(index);
294     clutter_actor_animate_with_alpha(CLUTTER_ACTOR(ccover), alpha, "opacity", opacity, NULL);
295     clutter_timeline_start (timeline);
296 }
297
298 static gboolean _set_loading_complete(gpointer data) {
299     ClarityCanvasPrivate *priv = (ClarityCanvasPrivate *) data;
300     priv->loading_complete = TRUE;
301     return TRUE;
302 }
303
304 static gboolean _create_cover_idle(gpointer data) {
305
306     AlbumItem *album_item = (AlbumItem *) data;
307     GObject *gobject = album_item->data;
308
309     g_return_val_if_fail(CLARITY_IS_CANVAS(gobject), FALSE);
310     ClarityCanvas *ccanvas = CLARITY_CANVAS(gobject);
311     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(ccanvas);
312
313     gint index = g_list_length(priv->covers);
314     ClarityCover *ccover = clarity_cover_new();
315     clutter_actor_set_opacity(CLUTTER_ACTOR(ccover), 0);
316     priv->covers = g_list_append(priv->covers, ccover);
317
318     clutter_container_add_actor(
319                             CLUTTER_CONTAINER(priv->container),
320                             CLUTTER_ACTOR(ccover));
321
322     clarity_cover_set_album_item(ccover, album_item);
323
324     //TEXT
325     //FIXME
326 //    temp.filename = filename;
327 //    temp.filetype = filetype;
328
329     //FIXME
330     // Confirm whether this does improve performance
331     if(index > 20)
332         return FALSE;
333
334     gint pos = _calculate_index_distance(index);
335     float scale = _calculate_index_scale(index);
336
337     gint angle;
338     ClutterRotateDirection rotation_dir;
339     _calculate_index_angle_and_dir(index, MOVE_LEFT, &angle, &rotation_dir);
340
341     clutter_actor_set_rotation(
342             CLUTTER_ACTOR(ccover),
343             CLUTTER_Y_AXIS,
344             angle,
345             clutter_actor_get_width(CLUTTER_ACTOR(ccover)) / 2,
346             0, 0);
347
348     clutter_actor_set_position(
349             CLUTTER_ACTOR(ccover),
350             pos - clutter_actor_get_width(CLUTTER_ACTOR(ccover)) / 2,
351             110 - clutter_actor_get_height(CLUTTER_ACTOR(ccover)));
352
353     clutter_actor_set_scale_full(
354             CLUTTER_ACTOR(ccover),
355             scale,
356             scale,
357             clutter_actor_get_width(CLUTTER_ACTOR(ccover)) / 2,
358             clutter_actor_get_height(CLUTTER_ACTOR(ccover)) / 2);
359
360     clutter_actor_lower_bottom(CLUTTER_ACTOR(ccover));
361
362     _display_clarity_cover(ccover, index);
363
364     return FALSE;
365 }
366
367 void _init_album_item(gpointer data, gpointer user_data) {
368     AlbumItem *item = (AlbumItem *) data;
369     ClarityCanvas *cc = CLARITY_CANVAS(user_data);
370
371     gdk_threads_enter();
372
373     Track *track = g_list_nth_data(item->tracks, 0);
374     item->albumart = _get_track_image(track);
375     item->data = cc;
376
377     g_idle_add_full(G_PRIORITY_LOW, _create_cover_idle, item, NULL);
378
379     gdk_threads_leave();
380 }
381
382 static gpointer _init_album_model_threaded(gpointer data) {
383     g_return_val_if_fail(CLARITY_IS_CANVAS(data), NULL);
384
385     ClarityCanvas *cc = CLARITY_CANVAS(data);
386     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(cc);
387     AlbumModel *model = priv->model;
388
389     album_model_foreach(model, _init_album_item, cc);
390
391     g_idle_add_full(G_PRIORITY_LOW, _set_loading_complete, priv, NULL);
392
393     return NULL;
394 }
395
396 void clarity_canvas_init_album_model(ClarityCanvas *self, AlbumModel *model) {
397     g_return_if_fail(self);
398     g_return_if_fail(model);
399
400     if (album_model_get_size(model) == 0)
401         return;
402
403     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
404     priv->model = model;
405     priv->loading_complete = FALSE;
406
407     g_thread_create_full(_init_album_model_threaded, self, /* user data  */
408     0, /* stack size */
409     FALSE, /* joinable   */
410     TRUE, /* bound      */
411     G_THREAD_PRIORITY_LOW, NULL);
412
413 }
414
415 static void _clear_rotation_behaviours(GList *covers) {
416     //Clear rotation behaviours
417     GList *iter = covers;
418     while (iter) {
419         ClarityCover *ccover = iter->data;
420         clarity_cover_clear_rotation_behaviour(ccover);
421         iter = iter->next;
422     }
423 }
424
425 static void _animate_indices(ClarityCanvasPrivate *priv, enum DIRECTION direction, gint increment) {
426
427     for (gint i = 0; i < g_list_length(priv->covers); ++i) {
428
429         ClarityCover *ccover = g_list_nth_data(priv->covers, i);
430
431         gint dist = i - priv->curr_index + (direction * increment);
432         gfloat depth = 1;
433         gint pos = 0;
434         gint opacity = 0;
435         gint angle = 0;
436         ClutterRotateDirection rotation_dir;
437
438         opacity = _calculate_index_opacity(dist);
439         depth = _calculate_index_scale(dist);
440         pos = _calculate_index_distance(dist);
441         _calculate_index_angle_and_dir(dist, direction, &angle, &rotation_dir);
442
443         /*Rotation*/
444         clarity_cover_set_rotation_behaviour(ccover, priv->alpha, angle, rotation_dir);
445
446         /* Opacity */
447         clutter_actor_animate_with_alpha (CLUTTER_ACTOR(ccover), priv->alpha,
448                         "opacity", opacity,
449                         NULL);
450
451         /* Position and scale */
452         clutter_actor_animate_with_alpha (CLUTTER_ACTOR(ccover), priv->alpha,
453                         "scale-x",          depth,
454                         "scale-y",          depth,
455                         "scale-center-x" ,  clutter_actor_get_width(CLUTTER_ACTOR(ccover)) / 2,
456                         "scale-center-y" ,  clutter_actor_get_height(CLUTTER_ACTOR(ccover)) / 2,
457                         "x", pos - clutter_actor_get_width(CLUTTER_ACTOR(ccover)) / 2 ,
458                         NULL);
459      }
460 }
461
462 static void _restore_z_order(ClarityCanvasPrivate *priv) {
463
464     if (g_list_length(priv->covers) == 0)
465         return;
466
467     GList *main_cover = g_list_nth(priv->covers, priv->curr_index);
468     g_return_if_fail(main_cover);
469
470     GList *iter = main_cover ->prev;
471     while(iter) {
472         ClarityCover *ccover = iter->data;
473         clutter_actor_lower_bottom(CLUTTER_ACTOR(ccover));
474         iter = iter->prev;
475     }
476
477     iter = main_cover->next;
478     while(iter) {
479         ClarityCover *ccover = iter->data;
480         clutter_actor_lower_bottom(CLUTTER_ACTOR(ccover));
481         iter = iter->next;
482     }
483 }
484
485 static void _move(ClarityCanvasPrivate *priv, enum DIRECTION direction, gint increment) {
486     /* Stop any animation */
487     clutter_timeline_stop(priv->timeline);
488
489     /* Clear all current rotation behaviours */
490     _clear_rotation_behaviours(priv->covers);
491
492     /* Animate to move left */
493     _animate_indices (priv, direction, increment);
494
495     /* Begin the animation */
496     clutter_timeline_start(priv->timeline);
497
498     priv->curr_index += ((direction * -1) * increment);
499
500 //        update_text     ();
501     _restore_z_order(priv);
502 }
503
504 void clarity_canvas_move_left(ClarityCanvas *self, gint increment) {
505     g_return_if_fail(self);
506     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
507
508     if(priv->curr_index == g_list_length(priv->covers) - 1)
509         return;
510
511     priv->loading_complete = FALSE;
512
513     _move(priv, MOVE_LEFT, increment);
514 }
515
516 void clarity_canvas_move_right(ClarityCanvas *self, gint increment) {
517     g_return_if_fail(self);
518     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
519
520     if(priv->curr_index == 0)
521         return;
522
523     priv->loading_complete = FALSE;
524
525     _move(priv, MOVE_RIGHT, increment);
526 }
527
528 gint clarity_canvas_get_current_index(ClarityCanvas *self) {
529     g_return_val_if_fail(self, 0);
530     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
531
532     return priv->curr_index;
533 }
534
535 gboolean clarity_canvas_is_loading(ClarityCanvas *self) {
536     g_return_val_if_fail(self, FALSE);
537     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
538     return !priv->loading_complete;
539 }
540