WIP
[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 "libgtkpod/fileselection.h"
31 #include "libgtkpod/misc.h"
32 #include "plugin.h"
33 #include "clarity_cover.h"
34 #include "clarity_canvas.h"
35 #include "clarity_preview.h"
36 #include "clarity_utils.h"
37 #include "clarity_context_menu.h"
38
39 G_DEFINE_TYPE( ClarityCanvas, clarity_canvas, GTK_TYPE_BOX);
40
41 #define CLARITY_CANVAS_GET_PRIVATE(obj) \
42   (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLARITY_TYPE_CANVAS, ClarityCanvasPrivate))
43
44 #define MAX_ANGLE                       70
45 #define COVER_SPACE                    50
46 #define FRONT_COVER_SPACE     150
47 #define MAX_SCALE                          1.4
48 #define VISIBLE_ITEMS                     8
49 #define FLOOR                              110
50
51 struct _ClarityCanvasPrivate {
52
53     AlbumModel *model;
54
55     // clutter embed widget
56     GtkWidget *embed;
57
58     // clutter items
59     GList *covers;
60     ClutterActor *container;
61     ClutterActor *title_text;
62     ClutterActor *artist_text;
63
64     gint curr_index;
65
66     gulong preview_signal;
67
68     gboolean blocked;
69 };
70
71 enum DIRECTION {
72     MOVE_LEFT = -1,
73     MOVE_RIGHT = 1
74 };
75
76 static void clarity_canvas_finalize(GObject *gobject) {
77     ClarityCanvasPrivate *priv = CLARITY_CANVAS(gobject)->priv;
78
79     //FIXME
80 //    g_list_free_full(priv->covers, clarity_cover_destroy);
81
82     if (GTK_IS_WIDGET(priv->embed))
83         gtk_widget_destroy(priv->embed);
84
85     /* call the parent class' finalize() method */
86     G_OBJECT_CLASS(clarity_canvas_parent_class)->finalize(gobject);
87 }
88
89 static void clarity_canvas_class_init(ClarityCanvasClass *klass) {
90     GObjectClass *gobject_class;
91
92     gobject_class = G_OBJECT_CLASS (klass);
93     gobject_class->finalize = clarity_canvas_finalize;
94
95     g_type_class_add_private(klass, sizeof(ClarityCanvasPrivate));
96 }
97
98 static void _update_text(ClarityCanvasPrivate *priv) {
99     g_return_if_fail(priv);
100
101     if (g_list_length(priv->covers) == 0)
102             return;
103
104     ClarityCover *ccover = g_list_nth_data(priv->covers, priv->curr_index);
105
106     gchar *title = clarity_cover_get_title(ccover);
107     gchar *artist = clarity_cover_get_artist(ccover);
108
109     clutter_text_set_text(CLUTTER_TEXT(priv->title_text), title);
110     clutter_text_set_text(CLUTTER_TEXT(priv->artist_text), artist);
111
112     g_free(title);
113     g_free(artist);
114
115     clutter_actor_set_child_above_sibling(priv->container, priv->title_text, NULL);
116     clutter_actor_set_child_above_sibling(priv->container, priv->artist_text, NULL);
117
118     gfloat artistx = (clutter_actor_get_width(priv->artist_text) / 2) * -1;
119     gfloat artisty = FLOOR - (clarity_cover_get_artwork_height(ccover) * MAX_SCALE);
120     clutter_actor_set_position(priv->artist_text, artistx, artisty);
121
122     gfloat titlex = (clutter_actor_get_width(priv->title_text) / 2) * -1;
123     gfloat titley = artisty - clutter_actor_get_height(priv->artist_text) - 2;
124     clutter_actor_set_position(priv->title_text, titlex, titley);
125 }
126
127 void clarity_canvas_block_change(ClarityCanvas *self, gboolean value) {
128     g_return_if_fail(self);
129
130     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
131     priv->blocked = value;
132
133     if (!value) {
134         _update_text(priv);
135     }
136 }
137
138 gboolean clarity_canvas_is_blocked(ClarityCanvas *self) {
139     g_return_val_if_fail(self, TRUE);
140     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
141     return priv->blocked;
142 }
143
144 static void _preview_cover(ClarityCanvasPrivate *priv) {
145     if (!priv->model)
146         return;
147
148     AlbumItem *item = album_model_get_item_with_index(priv->model, priv->curr_index);
149
150     GtkWidget *dialog = clarity_preview_new(item);
151
152     /* Display the dialog */
153     gtk_widget_show_all(dialog);
154 }
155
156 /**
157  * on_main_cover_image_clicked_cb:
158  *
159  * Call handler used for displaying the tracks associated with
160  * the main displayed album cover.
161  *
162  * @ClarityCanvas
163  * @event: event object used to determine the event type
164  * @data: any data needed by the function (not required)
165  *
166  */
167 static gint _on_main_cover_image_clicked_cb(GtkWidget *widget, GdkEvent *event, gpointer data) {
168     ClarityCanvas *self = CLARITY_CANVAS(widget);
169     ClarityCanvasPrivate *priv = self->priv;
170     guint mbutton;
171
172     if (event->type != GDK_BUTTON_PRESS)
173             return FALSE;
174
175     mbutton = event->button.button;
176
177     if ((mbutton == 1) && (event->button.state & GDK_SHIFT_MASK)) {
178         clarity_canvas_block_change(self, TRUE);
179
180         AlbumItem *item = album_model_get_item_with_index(priv->model, priv->curr_index);
181         if (item) {
182             gtkpod_set_displayed_tracks(item->tracks);
183         }
184
185         clarity_canvas_block_change(self, FALSE);
186     }
187     else if (mbutton == 1) {
188         _preview_cover(priv);
189     }
190     else if ((mbutton == 3) && (event->button.state & GDK_SHIFT_MASK)) {
191         /* Right mouse button clicked and shift pressed.
192          * Go straight to edit details window
193          */
194         AlbumItem *item = album_model_get_item_with_index(priv->model, priv->curr_index);
195         GList *tracks = item->tracks;
196         gtkpod_edit_details(tracks);
197     }
198     else if (mbutton == 3) {
199         /* Right mouse button clicked on its own so display
200          * popup menu
201          */
202         clarity_context_menu_init(self);
203     }
204
205     return FALSE;
206 }
207
208 /**
209  * embed_widget_size_allocated_cb
210  *
211  * Ensures that when the embed gtk widget is resized or moved
212  * around the clutter animations are centred correctly.
213  *
214  * This finds the new dimensions of the stage each time and centres
215  * the group container accordingly.
216  *
217  */
218 void _embed_widget_size_allocated_cb(GtkWidget *widget,
219                       GtkAllocation *allocation,
220                       gpointer data) {
221     ClarityCanvasPrivate *priv = (ClarityCanvasPrivate *) data;
222     ClutterActor *stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(widget));
223
224     gint centreX = clutter_actor_get_width(stage) / 2;
225     gint centreY = clutter_actor_get_height(stage) / 2;
226     clutter_actor_set_position(priv->container, centreX, centreY);
227 }
228
229 static void clarity_canvas_init(ClarityCanvas *self) {
230     ClarityCanvasPrivate *priv;
231
232     self->priv = CLARITY_CANVAS_GET_PRIVATE (self);
233
234     priv = self->priv;
235
236     priv->title_text = clutter_text_new();
237     clutter_text_set_font_name(CLUTTER_TEXT(priv->title_text), "Sans");
238
239     priv->artist_text = clutter_text_new();
240     clutter_text_set_font_name(CLUTTER_TEXT(priv->title_text), "Sans");
241
242     priv->container = clutter_actor_new();
243     clutter_actor_set_reactive(priv->container, TRUE);
244     priv->preview_signal = g_signal_connect (self,
245                                 "button-press-event",
246                                 G_CALLBACK (_on_main_cover_image_clicked_cb),
247                                 priv);
248     clutter_actor_add_child(priv->container, priv->title_text);
249     clutter_actor_add_child(priv->container, priv->artist_text);
250
251     priv->embed = gtk_clutter_embed_new();
252     /*
253      * Minimum size before the scrollbars of the parent window
254      * are displayed.
255      */
256     gtk_widget_set_size_request(GTK_WIDGET(priv->embed), DEFAULT_IMG_SIZE * 4, DEFAULT_IMG_SIZE * 2.5);
257     /*
258      * Ensure that things are always centred when the embed
259      * widget is resized.
260      */
261     g_signal_connect(priv->embed, "size-allocate",
262                   G_CALLBACK(_embed_widget_size_allocated_cb), priv);
263
264     ClutterActor *stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->embed));
265     clutter_actor_add_child(stage, priv->container);
266
267     gtk_widget_show(priv->embed);
268
269     gtk_box_pack_start(GTK_BOX(self), priv->embed, TRUE, TRUE, 0);
270
271     priv->covers = NULL;
272     priv->curr_index = 0;
273     priv->blocked = FALSE;
274
275 }
276
277 GtkWidget *clarity_canvas_new() {
278     return g_object_new(CLARITY_TYPE_CANVAS, NULL);
279 }
280
281 /**
282  * clarity_canvas_get_background_display_color:
283  *
284  * Returns the background color of the clarity canvas.
285  *
286  * The return value is a GdkRGBA
287  *
288  */
289 GdkRGBA *clarity_canvas_get_background_color(ClarityCanvas *self) {
290     g_return_val_if_fail(CLARITY_IS_CANVAS(self), NULL);
291
292     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
293
294     ClutterActor *stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->embed));
295
296     ClutterColor *ccolor;
297     ccolor = g_malloc(sizeof(ClutterColor));
298
299     clutter_actor_get_background_color(stage, ccolor);
300     g_return_val_if_fail(ccolor, NULL);
301
302     GdkRGBA *rgba;
303     rgba = g_malloc(sizeof(GdkRGBA));
304     rgba->red = ((gdouble) ccolor->red) / 255;
305     rgba->green = ((gdouble) ccolor->green) / 255;
306     rgba->blue = ((gdouble) ccolor->blue) / 255;
307     rgba->alpha = ((gdouble) ccolor->alpha) / 255;
308
309     return rgba;
310 }
311
312 /**
313  * clarity_canvas_get_text_color:
314  *
315  * Returns the text color of the clarity text.
316  *
317  * The return value is a GdkRGBA
318  *
319  */
320 GdkRGBA *clarity_canvas_get_text_color(ClarityCanvas *self) {
321     g_return_val_if_fail(CLARITY_IS_CANVAS(self), NULL);
322
323     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
324
325     ClutterColor *ccolor;
326     ccolor = g_malloc(sizeof(ClutterColor));
327
328     clutter_text_get_color(CLUTTER_TEXT(priv->title_text), ccolor);
329     g_return_val_if_fail(ccolor, NULL);
330
331     GdkRGBA *rgba;
332     rgba = g_malloc(sizeof(GdkRGBA));
333     rgba->red = ((gdouble) ccolor->red) / 255;
334     rgba->green = ((gdouble) ccolor->green) / 255;
335     rgba->blue = ((gdouble) ccolor->blue) / 255;
336     rgba->alpha = ((gdouble) ccolor->alpha) / 255;
337
338     return rgba;
339 }
340
341 void clarity_canvas_set_background_color(ClarityCanvas *self, const gchar *color_string) {
342     g_return_if_fail(self);
343     g_return_if_fail(color_string);
344
345     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
346
347     ClutterActor *stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->embed));
348
349     ClutterColor *ccolor;
350     ccolor = g_malloc(sizeof(ClutterColor));
351
352     clutter_color_from_string(ccolor, color_string);
353     clutter_actor_set_background_color(stage, ccolor);
354 }
355
356 void clarity_canvas_set_text_color(ClarityCanvas *self, const gchar *color_string) {
357     g_return_if_fail(self);
358     g_return_if_fail(color_string);
359
360     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
361
362     ClutterColor *ccolor;
363     ccolor = g_malloc(sizeof(ClutterColor));
364
365     clutter_color_from_string(ccolor, color_string);
366
367     clutter_text_set_color(CLUTTER_TEXT(priv->title_text), ccolor);
368     clutter_text_set_color(CLUTTER_TEXT(priv->artist_text), ccolor);
369 }
370
371 void clarity_canvas_clear(ClarityCanvas *self) {
372     g_return_if_fail(self);
373     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
374
375     if (CLUTTER_IS_ACTOR(priv->container)) {
376         GList *iter = priv->covers;
377         while(iter) {
378             ClarityCover *ccover = iter->data;
379             // cover is not referenced anywhere else so it should be destroyed too
380             clutter_actor_remove_child(priv->container, CLUTTER_ACTOR(ccover));
381             iter = iter->next;
382         }
383
384         if (CLUTTER_IS_ACTOR(priv->artist_text))
385             clutter_text_set_text(CLUTTER_TEXT(priv->artist_text), "");
386
387         if (CLUTTER_IS_ACTOR(priv->title_text))
388             clutter_text_set_text(CLUTTER_TEXT(priv->title_text), "");
389     }
390
391     priv->covers = NULL;
392     priv->model = NULL;
393     priv->curr_index = 0;
394 }
395
396 static void _calculate_index_angle_and_dir (gint dist_from_front, enum DIRECTION dir, gint *angle, ClutterRotateDirection *rotation_dir) {
397     /* The front item direction depends on the direction we came from */
398     if (dist_from_front == 0) {
399         *rotation_dir =  (dir == MOVE_RIGHT ? CLUTTER_ROTATE_CCW : CLUTTER_ROTATE_CW);
400         *angle = 0;
401     }
402
403     /* Item on the right */
404     else if (dist_from_front > 0) {
405         *rotation_dir = CLUTTER_ROTATE_CCW;
406         *angle = 360 - MAX_ANGLE;
407     }
408
409     /* Item on the left */
410     else if (dist_from_front < 0) {
411         *rotation_dir = CLUTTER_ROTATE_CW;
412         *angle = MAX_ANGLE;
413     }
414 }
415
416 static gint _calculate_index_distance (gint dist_from_front) {
417     gint dist = ((ABS(dist_from_front) - 1) * COVER_SPACE) + FRONT_COVER_SPACE;
418
419     if (dist_from_front == 0)
420         return 0;
421
422     return (dist_from_front > 0 ? dist : 0 - dist);
423 }
424
425 static float _calculate_index_scale(gint dist_from_front) {
426     if (dist_from_front == 0)
427         return MAX_SCALE;
428     else
429         return 1;
430 }
431
432 static gint _calculate_index_opacity (gint dist_from_front) {
433     return CLAMP ( 255 * (VISIBLE_ITEMS - ABS(dist_from_front)) / VISIBLE_ITEMS, 0, 255);
434 }
435
436 static void _display_clarity_cover(ClarityCover *ccover, gint index) {
437     clutter_actor_save_easing_state (CLUTTER_ACTOR(ccover));
438     clutter_actor_set_easing_mode (CLUTTER_ACTOR(ccover), CLUTTER_EASE_OUT_EXPO);
439     clutter_actor_set_easing_duration (CLUTTER_ACTOR(ccover), 1600);
440
441     gint opacity = _calculate_index_opacity(index);
442     clutter_actor_set_opacity (CLUTTER_ACTOR(ccover), opacity);
443 }
444
445 static void _set_cover_position(ClarityCover *ccover, gint index) {
446     gint pos = _calculate_index_distance(index);
447     clutter_actor_set_position(
448                     CLUTTER_ACTOR(ccover),
449                     pos - clarity_cover_get_artwork_width(ccover) / 2,
450                     FLOOR - clarity_cover_get_artwork_height(ccover));
451 }
452
453 static gboolean _create_cover_actors(ClarityCanvasPrivate *priv, AlbumItem *album_item, gint index) {
454     g_return_val_if_fail(priv, FALSE);
455
456     ClarityCover *ccover = clarity_cover_new();
457
458     clutter_actor_set_opacity(CLUTTER_ACTOR(ccover), 0);
459     priv->covers = g_list_insert(priv->covers, ccover, index);
460
461     clutter_actor_add_child(
462                             priv->container,
463                             CLUTTER_ACTOR(ccover));
464
465     clarity_cover_set_album_item(ccover, album_item);
466
467     _set_cover_position(ccover, index);
468
469     if((priv->curr_index + VISIBLE_ITEMS < index) ||
470             (priv->curr_index - VISIBLE_ITEMS > index)) {
471         return FALSE;
472     }
473
474     float scale = _calculate_index_scale(index);
475
476     gint angle;
477     ClutterRotateDirection rotation_dir;
478     _calculate_index_angle_and_dir(index, MOVE_LEFT, &angle, &rotation_dir);
479
480     clutter_actor_set_pivot_point(CLUTTER_ACTOR(ccover), 0.5f, 0.5f);
481     clutter_actor_set_rotation_angle(CLUTTER_ACTOR(ccover), CLUTTER_Y_AXIS, angle);
482     clutter_actor_set_scale(CLUTTER_ACTOR(ccover), scale, scale);
483
484     clutter_actor_set_child_below_sibling(priv->container, CLUTTER_ACTOR(ccover), NULL);
485
486     _display_clarity_cover(ccover, index);
487
488     return FALSE;
489 }
490
491 void _init_album_item(gpointer value, gint index, gpointer user_data) {
492     AlbumItem *item = (AlbumItem *) value;
493     ClarityCanvas *self = CLARITY_CANVAS(user_data);
494     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
495
496     album_model_init_coverart(priv->model, item);
497
498     clarity_canvas_block_change(self, TRUE);
499     _create_cover_actors(priv, item, index);
500     clarity_canvas_block_change(self, FALSE);
501 }
502
503 void _destroy_cover(ClarityCanvas *cc, gint index) {
504     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(cc);
505
506     ClarityCover *ccover = (ClarityCover *) g_list_nth_data(priv->covers, index);
507     if (!ccover)
508         return;
509
510     priv->covers = g_list_remove(priv->covers, ccover);
511
512     clutter_actor_remove_child(
513                                priv->container,
514                                CLUTTER_ACTOR(ccover));
515
516     clarity_cover_destroy(CLUTTER_ACTOR(ccover));
517
518     return;
519 }
520
521 static gpointer _init_album_model(gpointer data) {
522     g_return_val_if_fail(CLARITY_IS_CANVAS(data), NULL);
523
524     ClarityCanvas *cc = CLARITY_CANVAS(data);
525     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(cc);
526     AlbumModel *model = priv->model;
527
528     album_model_foreach(model, _init_album_item, cc);
529
530     return NULL;
531 }
532
533 static gboolean _init_album_model_idle(gpointer data) {
534     g_return_val_if_fail(CLARITY_IS_CANVAS(data), FALSE);
535
536     ClarityCanvas *self = CLARITY_CANVAS(data);
537
538     _init_album_model(self);
539
540     return FALSE;
541 }
542
543 void clarity_canvas_init_album_model(ClarityCanvas *self, AlbumModel *model) {
544     g_return_if_fail(self);
545     g_return_if_fail(model);
546
547     if (album_model_get_size(model) == 0)
548         return;
549
550     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
551     priv->model = model;
552
553     /*
554      * Necessary to avoid generating cogl errors in the following use case:
555      * 1) Load gtkpod with clarity plugin window docked in the gui but
556      *      obscured by another plugin window.
557      * 2) Select a playlist.
558      * 3) Select the relevant toggle button to bring the clarity window to
559      *      the front and visible.
560      *
561      * This function gets called during the realized signal callback so using
562      * g_idle_add lets the realized callback finish and the window display
563      * before loading the cogl textures.
564      */
565     g_idle_add(_init_album_model_idle, self);
566 }
567
568 static void _animate_indices(ClarityCanvasPrivate *priv, gint direction, gint increment) {
569
570     for (gint i = 0; i < g_list_length(priv->covers); ++i) {
571         ClarityCover *ccover = g_list_nth_data(priv->covers, i);
572
573         gint dist = i - priv->curr_index + (direction * increment);
574         gfloat scale = 1;
575         gint pos = 0;
576         gint opacity = 0;
577         gint angle = 0;
578         double current_rotation;
579         ClutterRotateDirection rotation_dir;
580
581         opacity = _calculate_index_opacity(dist);
582         scale = _calculate_index_scale(dist);
583         pos = _calculate_index_distance(dist);
584         _calculate_index_angle_and_dir(dist, direction, &angle, &rotation_dir);
585         gfloat w = clarity_cover_get_artwork_width(ccover);
586         gfloat h = clarity_cover_get_artwork_height(ccover);
587
588         clutter_actor_save_easing_state (CLUTTER_ACTOR(ccover));
589         clutter_actor_set_easing_mode (CLUTTER_ACTOR(ccover), CLUTTER_LINEAR);
590         clutter_actor_set_easing_duration (CLUTTER_ACTOR(ccover), 1600);
591
592         /* Position and scale */
593         clutter_actor_set_scale(CLUTTER_ACTOR(ccover), scale, scale);
594 //        clutter_actor_set_position(CLUTTER_ACTOR(ccover), pos - (w / 2), FLOOR - h);
595
596         /*Rotation*/
597         current_rotation = clutter_actor_get_rotation_angle(CLUTTER_ACTOR(ccover), CLUTTER_Y_AXIS);
598         if(current_rotation < 0)
599             current_rotation += 360;
600         else if(current_rotation > 360)
601             current_rotation -= 360;
602
603         clutter_actor_set_pivot_point(CLUTTER_ACTOR(ccover), 0.5f, 0.5f);
604
605         if(current_rotation != angle) {
606             g_warning("Angle %d", angle);
607             clutter_actor_set_rotation_angle(CLUTTER_ACTOR(ccover), CLUTTER_Y_AXIS, angle);
608         }
609
610         /* Opacity */
611         clutter_actor_set_opacity (CLUTTER_ACTOR(ccover), opacity);
612      }
613 }
614
615 static void _restore_z_order(ClarityCanvasPrivate *priv) {
616     g_return_if_fail(priv);
617
618     if (g_list_length(priv->covers) == 0)
619         return;
620
621     GList *main_cover = g_list_nth(priv->covers, priv->curr_index);
622     g_return_if_fail(main_cover);
623
624     GList *iter = main_cover ->prev;
625     while(iter) {
626         ClarityCover *ccover = iter->data;
627         clutter_actor_set_child_below_sibling(priv->container, CLUTTER_ACTOR(ccover), NULL);
628         iter = iter->prev;
629     }
630
631     iter = main_cover->next;
632     while(iter) {
633         ClarityCover *ccover = iter->data;
634         clutter_actor_set_child_below_sibling(priv->container, CLUTTER_ACTOR(ccover), NULL);
635         iter = iter->next;
636     }
637 }
638
639 static void _move(ClarityCanvasPrivate *priv, enum DIRECTION direction, gint increment) {
640
641     /* Stop any animation */
642     for (gint i = 0; i < g_list_length(priv->covers); ++i) {
643         ClarityCover *ccover = g_list_nth_data(priv->covers, i);
644         clutter_actor_remove_all_transitions(CLUTTER_ACTOR(ccover));
645     }
646
647     /* Animate to move left */
648     _animate_indices (priv, direction, increment);
649
650     priv->curr_index += ((direction * -1) * increment);
651
652     _restore_z_order(priv);
653 }
654
655 void clarity_canvas_move_left(ClarityCanvas *self, gint increment) {
656     g_return_if_fail(self);
657     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
658
659     if(priv->curr_index == g_list_length(priv->covers) - 1)
660         return;
661
662     clarity_canvas_block_change(self, TRUE);
663     _move(priv, MOVE_LEFT, increment);
664     clarity_canvas_block_change(self, FALSE);
665 }
666
667 void clarity_canvas_move_right(ClarityCanvas *self, gint increment) {
668     g_return_if_fail(self);
669     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
670
671     if(priv->curr_index == 0)
672         return;
673
674     clarity_canvas_block_change(self, TRUE);
675     _move(priv, MOVE_RIGHT, increment);
676     clarity_canvas_block_change(self, FALSE);
677 }
678
679 gint clarity_canvas_get_current_index(ClarityCanvas *self) {
680     g_return_val_if_fail(self, 0);
681     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
682
683     return priv->curr_index;
684 }
685
686 AlbumItem *clarity_canvas_get_current_album_item(ClarityCanvas *self) {
687     g_return_val_if_fail(self, NULL);
688     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
689
690     if (!priv->model)
691         return NULL;
692
693     return album_model_get_item_with_index(priv->model, priv->curr_index);
694 }
695
696 void clarity_canvas_add_album_item(ClarityCanvas *self, AlbumItem *item) {
697     g_return_if_fail(self);
698     g_return_if_fail(item);
699
700     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
701     gint index = album_model_get_index_with_album_item(priv->model, item);
702
703     clarity_canvas_block_change(self, TRUE);
704
705     _init_album_item(item, index, self);
706
707     _animate_indices(priv, 0, 0);
708
709     clarity_canvas_block_change(self, FALSE);
710 }
711
712 void clarity_canvas_remove_album_item(ClarityCanvas *self, AlbumItem *item) {
713     g_return_if_fail(self);
714     g_return_if_fail(item);
715
716     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
717     gint index = album_model_get_index_with_album_item(priv->model, item);
718
719     clarity_canvas_block_change(self, TRUE);
720
721     _destroy_cover(self, index);
722
723     _animate_indices(priv, 0, 0);
724
725     clarity_canvas_block_change(self, FALSE);
726 }
727
728 void clarity_canvas_update(ClarityCanvas *self, AlbumItem *item) {
729     g_return_if_fail(self);
730
731     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
732
733     gint index = album_model_get_index_with_album_item(priv->model, item);
734
735     clarity_canvas_block_change(self, TRUE);
736
737     album_model_init_coverart(priv->model, item);
738
739     ClarityCover *ccover = (ClarityCover *) g_list_nth_data(priv->covers, index);
740     if (!ccover)
741         return;
742
743     clarity_cover_set_album_item(ccover, item);
744
745     _set_cover_position(ccover, index);
746
747     _animate_indices(priv, 0, 0);
748
749     clarity_canvas_block_change(self, FALSE);
750 }
751
752 static void _set_cover_from_file(ClarityCanvas *self) {
753     g_return_if_fail(self);
754
755     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
756
757     gchar *filename = fileselection_get_cover_filename();
758
759     if (filename) {
760         AlbumItem *item = album_model_get_item_with_index(priv->model, priv->curr_index);
761         clarity_util_update_coverart(item->tracks, filename);
762     }
763
764     g_free(filename);
765 }
766
767 void on_clarity_set_cover_menuitem_activate(GtkMenuItem *mi, gpointer data) {
768     g_return_if_fail(CLARITY_IS_CANVAS(data));
769
770     _set_cover_from_file(CLARITY_CANVAS(data));
771 }