Support for track removal in Clarity
[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     ClutterActor *title_text;
60     ClutterActor *artist_text;
61
62     gint curr_index;
63
64     gulong preview_signal;
65
66     gboolean loading_complete;
67 };
68
69 enum DIRECTION {
70     MOVE_LEFT = -1,
71     MOVE_RIGHT = 1
72 };
73
74 static void clarity_canvas_finalize(GObject *gobject) {
75     ClarityCanvasPrivate *priv = CLARITY_CANVAS(gobject)->priv;
76
77     //FIXME
78 //    g_list_free_full(priv->covers, clarity_cover_destroy);
79
80     if (G_IS_OBJECT(priv->alpha))
81         g_object_unref(priv->alpha);
82
83     if (G_IS_OBJECT(priv->timeline))
84         g_object_unref(priv->timeline);
85
86     if (GTK_IS_WIDGET(priv->embed))
87         gtk_widget_destroy(priv->embed);
88
89     /* call the parent class' finalize() method */
90     G_OBJECT_CLASS(clarity_canvas_parent_class)->finalize(gobject);
91 }
92
93 static void clarity_canvas_class_init(ClarityCanvasClass *klass) {
94     GObjectClass *gobject_class;
95
96     gobject_class = G_OBJECT_CLASS (klass);
97     gobject_class->finalize = clarity_canvas_finalize;
98
99     g_type_class_add_private(klass, sizeof(ClarityCanvasPrivate));
100 }
101
102 static gboolean _preview_cover_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
103     ClarityCanvas *ccanvas = CLARITY_CANVAS(widget);
104     ClarityCanvasPrivate *priv = ccanvas->priv;
105
106     if (!priv->model)
107         return TRUE;
108
109     AlbumItem *item = album_model_get_item_with_index(priv->model, priv->curr_index);
110
111     GtkWidget *dialog = clarity_preview_new(item);
112
113     /* Display the dialog */
114     gtk_widget_show_all(dialog);
115
116     return TRUE;
117 }
118
119 /**
120  * embed_widget_size_allocated_cb
121  *
122  * Ensures that when the embed gtk widget is resized or moved
123  * around the clutter animations are centred correctly.
124  *
125  * This finds the new dimensions of the stage each time and centres
126  * the group container accordingly.
127  *
128  */
129 void _embed_widget_size_allocated_cb(GtkWidget *widget,
130                       GtkAllocation *allocation,
131                       gpointer data) {
132     ClarityCanvasPrivate *priv = (ClarityCanvasPrivate *) data;
133     ClutterActor *stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(widget));
134
135     gint centreX = clutter_actor_get_width(stage) / 2;
136     gint centreY = clutter_actor_get_height(stage) / 2;
137     clutter_actor_set_position(priv->container, centreX, centreY);
138 }
139
140 static void clarity_canvas_init(ClarityCanvas *self) {
141     ClarityCanvasPrivate *priv;
142
143     self->priv = CLARITY_CANVAS_GET_PRIVATE (self);
144
145     priv = self->priv;
146
147     priv->title_text = clutter_text_new();
148     clutter_text_set_font_name(CLUTTER_TEXT(priv->title_text), "Sans");
149
150     priv->artist_text = clutter_text_new();
151     clutter_text_set_font_name(CLUTTER_TEXT(priv->title_text), "Sans");
152
153     priv->container = clutter_group_new();
154     clutter_actor_set_reactive(priv->container, TRUE);
155     priv->preview_signal = g_signal_connect (self,
156                                 "button-press-event",
157                                 G_CALLBACK (_preview_cover_cb),
158                                 priv);
159     clutter_container_add(CLUTTER_CONTAINER(priv->container), priv->title_text, priv->artist_text, NULL);
160
161     priv->embed = gtk_clutter_embed_new();
162     /*
163      * Minimum size before the scrollbars of the parent window
164      * are displayed.
165      */
166     gtk_widget_set_size_request(GTK_WIDGET(priv->embed), DEFAULT_IMG_SIZE * 4, DEFAULT_IMG_SIZE * 2.5);
167     /*
168      * Ensure that things are always centred when the embed
169      * widget is resized.
170      */
171     g_signal_connect(priv->embed, "size-allocate",
172                   G_CALLBACK(_embed_widget_size_allocated_cb), priv);
173
174     ClutterActor *stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->embed));
175     clutter_container_add_actor(CLUTTER_CONTAINER(stage), priv->container);
176
177     gtk_widget_show(priv->embed);
178
179     gtk_box_pack_start(GTK_BOX(self), priv->embed, TRUE, TRUE, 0);
180
181     priv->covers = NULL;
182     priv->timeline = clutter_timeline_new(1600);
183     priv->alpha = clutter_alpha_new_full(priv->timeline, CLUTTER_EASE_OUT_EXPO);
184     priv->curr_index = 0;
185     priv->loading_complete = FALSE;
186
187 }
188
189 GtkWidget *clarity_canvas_new() {
190     return g_object_new(CLARITY_TYPE_CANVAS, NULL);
191 }
192
193 /**
194  * clarity_canvas_get_background_display_color:
195  *
196  * Returns the background color of the clarity canvas.
197  *
198  * The return value is a GdkRGBA
199  *
200  */
201 GdkRGBA *clarity_canvas_get_background_color(ClarityCanvas *self) {
202     g_return_val_if_fail(CLARITY_IS_CANVAS(self), NULL);
203
204     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
205
206     ClutterActor *stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->embed));
207
208     ClutterColor *ccolor;
209     ccolor = g_malloc(sizeof(ClutterColor));
210
211     clutter_stage_get_color(CLUTTER_STAGE(stage), ccolor);
212     g_return_val_if_fail(ccolor, NULL);
213
214     GdkRGBA *rgba;
215     rgba = g_malloc(sizeof(GdkRGBA));
216     rgba->red = ((gdouble) ccolor->red) / 255;
217     rgba->green = ((gdouble) ccolor->green) / 255;
218     rgba->blue = ((gdouble) ccolor->blue) / 255;
219     rgba->alpha = ((gdouble) ccolor->alpha) / 255;
220
221     return rgba;
222 }
223
224 /**
225  * clarity_canvas_get_text_color:
226  *
227  * Returns the text color of the clarity text.
228  *
229  * The return value is a GdkRGBA
230  *
231  */
232 GdkRGBA *clarity_canvas_get_text_color(ClarityCanvas *self) {
233     g_return_val_if_fail(CLARITY_IS_CANVAS(self), NULL);
234
235     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
236
237     ClutterColor *ccolor;
238     ccolor = g_malloc(sizeof(ClutterColor));
239
240     clutter_text_get_color(CLUTTER_TEXT(priv->title_text), ccolor);
241     g_return_val_if_fail(ccolor, NULL);
242
243     GdkRGBA *rgba;
244     rgba = g_malloc(sizeof(GdkRGBA));
245     rgba->red = ((gdouble) ccolor->red) / 255;
246     rgba->green = ((gdouble) ccolor->green) / 255;
247     rgba->blue = ((gdouble) ccolor->blue) / 255;
248     rgba->alpha = ((gdouble) ccolor->alpha) / 255;
249
250     return rgba;
251 }
252
253 void clarity_canvas_set_background_color(ClarityCanvas *self, const gchar *color_string) {
254     g_return_if_fail(self);
255     g_return_if_fail(color_string);
256
257     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
258
259     ClutterActor *stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->embed));
260
261     ClutterColor *ccolor;
262     ccolor = g_malloc(sizeof(ClutterColor));
263
264     clutter_color_from_string(ccolor, color_string);
265     clutter_stage_set_color(CLUTTER_STAGE(stage), ccolor);
266 }
267
268 void clarity_canvas_set_text_color(ClarityCanvas *self, const gchar *color_string) {
269     g_return_if_fail(self);
270     g_return_if_fail(color_string);
271
272     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
273
274     ClutterColor *ccolor;
275     ccolor = g_malloc(sizeof(ClutterColor));
276
277     clutter_color_from_string(ccolor, color_string);
278
279     clutter_text_set_color(CLUTTER_TEXT(priv->title_text), ccolor);
280     clutter_text_set_color(CLUTTER_TEXT(priv->artist_text), ccolor);
281 }
282
283 void clarity_canvas_clear(ClarityCanvas *self) {
284     g_return_if_fail(self);
285     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
286
287     if (CLUTTER_IS_ACTOR(priv->container)) {
288         GList *iter = priv->covers;
289         while(iter) {
290             ClarityCover *ccover = iter->data;
291             // cover is not referenced anywhere else so it should be destroyed too
292             clutter_container_remove(CLUTTER_CONTAINER(priv->container), CLUTTER_ACTOR(ccover), NULL);
293             iter = iter->next;
294         }
295     }
296
297     priv->covers = NULL;
298     priv->model = NULL;
299     priv->curr_index = 0;
300 }
301
302 static void _calculate_index_angle_and_dir (gint dist_from_front, enum DIRECTION dir, gint *angle, ClutterRotateDirection *rotation_dir) {
303     /* The front item direction depends on the direction we came from */
304     if (dist_from_front == 0) {
305         *rotation_dir =  (dir == MOVE_RIGHT ? CLUTTER_ROTATE_CCW : CLUTTER_ROTATE_CW);
306         *angle = 0;
307     }
308
309     /* Item on the right */
310     else if (dist_from_front > 0) {
311         *rotation_dir = CLUTTER_ROTATE_CCW;
312         *angle = 360 - MAX_ANGLE;
313     }
314
315     /* Item on the left */
316     else if (dist_from_front < 0) {
317         *rotation_dir = CLUTTER_ROTATE_CW;
318         *angle = MAX_ANGLE;
319     }
320 }
321
322 static gint _calculate_index_distance (gint dist_from_front) {
323     gint dist = ((ABS(dist_from_front) - 1) * COVER_SPACE) + FRONT_COVER_SPACE;
324
325     if (dist_from_front == 0)
326         return 0;
327
328     return (dist_from_front > 0 ? dist : 0 - dist);
329 }
330
331 static float _calculate_index_scale(gint dist_from_front) {
332     if (dist_from_front == 0)
333         return MAX_SCALE;
334     else
335         return 1;
336 }
337
338 static gint _calculate_index_opacity (gint dist_from_front) {
339     return CLAMP ( 255 * (VISIBLE_ITEMS - ABS(dist_from_front)) / VISIBLE_ITEMS, 0, 255);
340 }
341
342 static void _update_text(ClarityCanvasPrivate *priv) {
343     g_return_if_fail(priv);
344
345     if (g_list_length(priv->covers) == 0)
346             return;
347
348     ClarityCover *ccover = g_list_nth_data(priv->covers, priv->curr_index);
349
350     gchar *title = clarity_cover_get_title(ccover);
351     gchar *artist = clarity_cover_get_artist(ccover);
352
353     clutter_text_set_text(CLUTTER_TEXT(priv->title_text), title);
354     clutter_text_set_text(CLUTTER_TEXT(priv->artist_text), artist);
355
356     g_free(title);
357     g_free(artist);
358
359     clutter_actor_raise_top(priv->title_text);
360     clutter_actor_raise_top(priv->artist_text);
361
362     gfloat artistx = (clutter_actor_get_width(priv->artist_text) / 2) * -1;
363     gfloat artisty = ((clutter_actor_get_height(CLUTTER_ACTOR(ccover)) / 2) - 25) * -1;
364     clutter_actor_set_position(priv->artist_text, artistx, artisty);
365
366     gfloat titlex = (clutter_actor_get_width(priv->title_text) / 2) * -1;
367     gfloat titley = artisty - clutter_actor_get_height(priv->artist_text) - 2;
368     clutter_actor_set_position(priv->title_text, titlex, titley);
369 }
370
371 static void _display_clarity_cover(ClarityCover *ccover, gint index) {
372     ClutterTimeline  *timeline = clutter_timeline_new(1600 * 5);
373     ClutterAlpha *alpha = clutter_alpha_new_full (timeline, CLUTTER_EASE_OUT_EXPO);
374
375     gint opacity = _calculate_index_opacity(index);
376     clutter_actor_animate_with_alpha(CLUTTER_ACTOR(ccover), alpha, "opacity", opacity, NULL);
377     clutter_timeline_start (timeline);
378 }
379
380 static void _set_loading_complete(ClarityCanvasPrivate *priv, gboolean value) {
381     priv->loading_complete = value;
382
383     if (value) {
384         _update_text(priv);
385     }
386 }
387
388 static gboolean _create_cover_actors(ClarityCanvasPrivate *priv, AlbumItem *album_item, gint index) {
389     g_return_val_if_fail(priv, FALSE);
390
391     _set_loading_complete(priv, FALSE);
392
393     ClarityCover *ccover = clarity_cover_new();
394     clutter_actor_set_opacity(CLUTTER_ACTOR(ccover), 0);
395     priv->covers = g_list_insert(priv->covers, ccover, index);
396
397     clutter_container_add_actor(
398                             CLUTTER_CONTAINER(priv->container),
399                             CLUTTER_ACTOR(ccover));
400
401     clarity_cover_set_album_item(ccover, album_item);
402
403     gint pos = _calculate_index_distance(index);
404     clutter_actor_set_position(
405                 CLUTTER_ACTOR(ccover),
406                 pos - clutter_actor_get_width(CLUTTER_ACTOR(ccover)) / 2,
407                 110 - clutter_actor_get_height(CLUTTER_ACTOR(ccover)));
408
409     if((priv->curr_index + VISIBLE_ITEMS < index) ||
410             (priv->curr_index - VISIBLE_ITEMS > index)) {
411         _set_loading_complete(priv, TRUE);
412         return FALSE;
413     }
414
415     float scale = _calculate_index_scale(index);
416
417     gint angle;
418     ClutterRotateDirection rotation_dir;
419     _calculate_index_angle_and_dir(index, MOVE_LEFT, &angle, &rotation_dir);
420
421     clutter_actor_set_rotation(
422             CLUTTER_ACTOR(ccover),
423             CLUTTER_Y_AXIS,
424             angle,
425             clutter_actor_get_width(CLUTTER_ACTOR(ccover)) / 2,
426             0, 0);
427
428     clutter_actor_set_scale_full(
429             CLUTTER_ACTOR(ccover),
430             scale,
431             scale,
432             clutter_actor_get_width(CLUTTER_ACTOR(ccover)) / 2,
433             clutter_actor_get_height(CLUTTER_ACTOR(ccover)) / 2);
434
435     clutter_actor_lower_bottom(CLUTTER_ACTOR(ccover));
436
437     _display_clarity_cover(ccover, index);
438
439     _set_loading_complete(priv, TRUE);
440
441     return FALSE;
442 }
443
444 void _init_album_item(gpointer value, gint index, gpointer user_data) {
445     AlbumItem *item = (AlbumItem *) value;
446     ClarityCanvas *cc = CLARITY_CANVAS(user_data);
447     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(cc);
448
449     Track *track = g_list_nth_data(item->tracks, 0);
450     item->albumart = _get_track_image(track);
451     item->data = cc;
452
453     _create_cover_actors(priv, item, index);
454 }
455
456 void _destroy_cover(ClarityCanvas *cc, gint index) {
457     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(cc);
458
459     ClarityCover *ccover = (ClarityCover *) g_list_nth_data(priv->covers, index);
460     if (!ccover)
461         return;
462
463     priv->covers = g_list_remove(priv->covers, ccover);
464
465     clutter_container_remove_actor(
466                                CLUTTER_CONTAINER(priv->container),
467                                CLUTTER_ACTOR(ccover));
468
469     clarity_cover_destroy(CLUTTER_ACTOR(ccover));
470
471     return;
472 }
473
474 static gpointer _init_album_model(gpointer data) {
475     g_return_val_if_fail(CLARITY_IS_CANVAS(data), NULL);
476
477     ClarityCanvas *cc = CLARITY_CANVAS(data);
478     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(cc);
479     AlbumModel *model = priv->model;
480
481     album_model_foreach(model, _init_album_item, cc);
482
483     return NULL;
484 }
485
486 void clarity_canvas_init_album_model(ClarityCanvas *self, AlbumModel *model) {
487     g_return_if_fail(self);
488     g_return_if_fail(model);
489
490     if (album_model_get_size(model) == 0)
491         return;
492
493     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
494     priv->model = model;
495
496     _init_album_model(self);
497
498 }
499
500 static void _clear_rotation_behaviours(GList *covers) {
501     //Clear rotation behaviours
502     GList *iter = covers;
503     while (iter) {
504         ClarityCover *ccover = iter->data;
505         clarity_cover_clear_rotation_behaviour(ccover);
506         iter = iter->next;
507     }
508 }
509
510 static void _animate_indices(ClarityCanvasPrivate *priv, gint direction, gint increment) {
511
512     for (gint i = 0; i < g_list_length(priv->covers); ++i) {
513
514         ClarityCover *ccover = g_list_nth_data(priv->covers, i);
515
516         gint dist = i - priv->curr_index + (direction * increment);
517         gfloat depth = 1;
518         gint pos = 0;
519         gint opacity = 0;
520         gint angle = 0;
521         ClutterRotateDirection rotation_dir;
522
523         opacity = _calculate_index_opacity(dist);
524         depth = _calculate_index_scale(dist);
525         pos = _calculate_index_distance(dist);
526         _calculate_index_angle_and_dir(dist, direction, &angle, &rotation_dir);
527
528         /*Rotation*/
529         clarity_cover_set_rotation_behaviour(ccover, priv->alpha, angle, rotation_dir);
530
531         /* Opacity */
532         clutter_actor_animate_with_alpha (CLUTTER_ACTOR(ccover), priv->alpha,
533                         "opacity", opacity,
534                         NULL);
535
536         /* Position and scale */
537         clutter_actor_animate_with_alpha (CLUTTER_ACTOR(ccover), priv->alpha,
538                         "scale-x",          depth,
539                         "scale-y",          depth,
540                         "scale-center-x" ,  clutter_actor_get_width(CLUTTER_ACTOR(ccover)) / 2,
541                         "scale-center-y" ,  clutter_actor_get_height(CLUTTER_ACTOR(ccover)) / 2,
542                         "x", pos - clutter_actor_get_width(CLUTTER_ACTOR(ccover)) / 2,
543                         NULL);
544      }
545 }
546
547 static void _restore_z_order(ClarityCanvasPrivate *priv) {
548     g_return_if_fail(priv);
549
550     if (g_list_length(priv->covers) == 0)
551         return;
552
553     GList *main_cover = g_list_nth(priv->covers, priv->curr_index);
554     g_return_if_fail(main_cover);
555
556     GList *iter = main_cover ->prev;
557     while(iter) {
558         ClarityCover *ccover = iter->data;
559         clutter_actor_lower_bottom(CLUTTER_ACTOR(ccover));
560         iter = iter->prev;
561     }
562
563     iter = main_cover->next;
564     while(iter) {
565         ClarityCover *ccover = iter->data;
566         clutter_actor_lower_bottom(CLUTTER_ACTOR(ccover));
567         iter = iter->next;
568     }
569 }
570
571 static void _move(ClarityCanvasPrivate *priv, enum DIRECTION direction, gint increment) {
572
573     _set_loading_complete(priv, FALSE);
574
575     /* Stop any animation */
576     clutter_timeline_stop(priv->timeline);
577
578     /* Clear all current rotation behaviours */
579     _clear_rotation_behaviours(priv->covers);
580
581     /* Animate to move left */
582     _animate_indices (priv, direction, increment);
583
584     /* Begin the animation */
585     clutter_timeline_start(priv->timeline);
586
587     priv->curr_index += ((direction * -1) * increment);
588
589     _restore_z_order(priv);
590
591     _set_loading_complete(priv, TRUE);
592 }
593
594 void clarity_canvas_move_left(ClarityCanvas *self, gint increment) {
595     g_return_if_fail(self);
596     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
597
598     if(priv->curr_index == g_list_length(priv->covers) - 1)
599         return;
600
601     _move(priv, MOVE_LEFT, increment);
602 }
603
604 void clarity_canvas_move_right(ClarityCanvas *self, gint increment) {
605     g_return_if_fail(self);
606     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
607
608     if(priv->curr_index == 0)
609         return;
610
611     _move(priv, MOVE_RIGHT, increment);
612 }
613
614 gint clarity_canvas_get_current_index(ClarityCanvas *self) {
615     g_return_val_if_fail(self, 0);
616     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
617
618     return priv->curr_index;
619 }
620
621 gboolean clarity_canvas_is_loading(ClarityCanvas *self) {
622     g_return_val_if_fail(self, FALSE);
623     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
624     return !priv->loading_complete;
625 }
626
627 void clarity_canvas_add_album_item(ClarityCanvas *self, AlbumItem *item) {
628     g_return_if_fail(self);
629     g_return_if_fail(item);
630
631     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
632     gint index = album_model_get_index_with_album_item(priv->model, item);
633
634     _set_loading_complete(priv, FALSE);
635
636     _init_album_item(item, index, self);
637
638     _set_loading_complete(priv, TRUE);
639 }
640
641 void clarity_canvas_remove_album_item(ClarityCanvas *self, AlbumItem *item) {
642     g_return_if_fail(self);
643     g_return_if_fail(item);
644
645     ClarityCanvasPrivate *priv = CLARITY_CANVAS_GET_PRIVATE(self);
646     gint index = album_model_get_index_with_album_item(priv->model, item);
647
648     _set_loading_complete(priv, FALSE);
649
650     _destroy_cover(self, index);
651
652     _animate_indices(priv, 0, 0);
653
654     _set_loading_complete(priv, TRUE);
655 }