Fix typo: Similar Artist -> Similar Artists
[gmpc.git] / src / gmpc-mpddata-model-playlist.gob
blob666a7edd9b2d4f63dfaaba848b4f3697c9b859f6
1 /* Gnome Music Player Client (GMPC)
2  * Copyright (C) 2004-2012 Qball Cow <qball@gmpclient.org>
3  * Project homepage: http://gmpclient.org/
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 requires 2.0.0
22 %a{
23 #include "gmpc-mpddata-model.h" 
25 #include "gmpc-extras.h" 
27 %h{
28 #include "gmpc-mpddata-model-private.h" 
31 #define BLOCK_SIZE 200
32 #define LOG_DOMAIN "MpdData.Model.Playlist"
34 /**
35  * This is a Special version of Gmpc:MpdData:Model that is made to show the current playlist, and keep in sync with it.
36  * This is to replace the old playlist-lis backend.
37  * Use this model with Gmpc:MpdData:Treeview
38  */
40 class Gmpc:MpdData:Model:Playlist from Gmpc:MpdData:Model 
41         (interface Gtk:Tree:Model)
42                 (interface Gtk:Tree:Drag:Dest)
44     private GmpcConnection *conn = {NULL};
45     private unsigned int status_changed = 0;
46     private unsigned int connection_changed = 0;
47     private MpdObj *mi;
48     private long long playlist_id = {0};
49     private unsigned long total_time = {0};
50     private unsigned long loaded_songs = {0};
51     private int old_highlight = -1;
53                 private guint update_timeout = {0};
54                 private guint last_pos = {0};
56                 private gboolean update_callback(self)
57                 {
58                         static const unsigned int step_size = 100;
59                         unsigned int num_rows =  GMPC_MPDDATA_MODEL(self)->num_rows;
60                         if(self->_priv->loaded_songs <num_rows)
61                         {
62                                 unsigned int i = 0;
63                                 MpdData *data =  GMPC_MPDDATA_MODEL(self)->_priv->data;
64                                 data = mpd_data_get_first(data);
65                                 if(!data) return TRUE;
66                                 for(i=0; i < (self->_priv->last_pos);i++)
67                                 {
68                                         data = (MpdData *)mpd_data_get_next_real(data,FALSE);
69                                 }
72                                 for(i=0;i< step_size && data;i++)
73                                 {
74                                         if(data->song == NULL)
75                                         { 
76                                                 data->song = mpd_playlist_get_song_from_pos(self->_priv->mi,i+self->_priv->last_pos);
77                                                 if(data->song)
78                                                 {
79                                                         self->_priv->loaded_songs++;
80                                                         if(data->song->time>0)
81                                                         {
82                                                                 self->_priv->total_time += data->song->time;
83                                                                 self_total_playtime_changed(self, self->_priv->loaded_songs, self->_priv->total_time);
84                                                         }
85                                                 }
87                                         }
88                                         data = mpd_data_get_next_real(data, FALSE);
89                                 }
90                                 self->_priv->last_pos += step_size;
91                         }
92                         else 
93                         {
94                                 self->_priv->last_pos = 0;
95                         } 
96                         return TRUE;
97                 }
99 override (G:Object)
100         void
101         dispose (G:Object *obj)
102         {
103             gmpc_mpddata_model_set_mpd_data(GMPC_MPDDATA_MODEL(obj), NULL);
104         }
105 override (G:Object)
106         void 
107         finalize (G:Object *obj)
108         {
109             Self *self = GMPC_MPDDATA_MODEL_PLAYLIST(obj); 
110             if(self->_priv->status_changed > 0)
111             {
112                 g_signal_handler_disconnect(G_OBJECT(gmpcconn),self->_priv->status_changed);
113                 self->_priv->status_changed = 0;
114             }
115             if(self->_priv->connection_changed > 0)
116             {
117                 g_signal_handler_disconnect(G_OBJECT(gmpcconn),self->_priv->connection_changed);
118                 self->_priv->connection_changed = 0;
119             }
120             if(self->_priv->update_timeout > 0)
121             {
122                 g_source_remove(self->_priv->update_timeout);
123                 self->_priv->update_timeout  = 0;
124             }
125             PARENT_HANDLER(obj);
126         }
127     public
128         Gmpc:MpdData:Model:Playlist *new (Gmpc:Connection *conn (check type null), MpdObj *mi(check null))
129         {
130             Self *self = GET_NEW;
131             self->_priv->conn = conn;
132             self->_priv->status_changed = g_signal_connect_swapped(G_OBJECT(conn), "status_changed", G_CALLBACK(self_status_changed), self);
133             self->_priv->connection_changed = g_signal_connect_swapped(G_OBJECT(conn), "connection_changed", G_CALLBACK(self_connection_changed), self);
134             self->_priv->mi = mi;
135                                                 
136                                                 if(cfg_get_single_value_as_int_with_default(config, "playlist", "background-loading", FALSE))
137                                                 {
138                                                         self->_priv->update_timeout  = g_timeout_add_seconds(1, (GSourceFunc)(self_update_callback), self);
139                                                 }
140                                                         return self;
141         }
142     private
143     void
144     status_changed(self, MpdObj *mi, ChangedStatusType what, Gmpc:Connection *conn (check type))
145     {
146         if(what&MPD_CST_PLAYLIST)
147         {
148             MpdData *data  = NULL;
149             int new_length = mpd_playlist_get_playlist_length(mi);
150            
151             int old_length = GMPC_MPDDATA_MODEL(self)->num_rows; 
152             /* if it was empty just add everything */
153             if(new_length == 0)
154             {
155                 self->_priv->loaded_songs=0;
156                 self->_priv->total_time =0;
157                 self_total_playtime_changed(self, self->_priv->loaded_songs, self->_priv->total_time);
159                 
160                 gmpc_mpddata_model_set_mpd_data(GMPC_MPDDATA_MODEL(self), NULL);
163             }
164             else if(old_length == 0)
165             {
166                 int  i;
167                 for(i = new_length; i > 0; i--)
168                 {
169                     data= mpd_new_data_struct_append(data);
170                     data->type = MPD_DATA_TYPE_SONG;
171                     data->song = NULL;
172                 }
173                 gmpc_mpddata_model_set_mpd_data(GMPC_MPDDATA_MODEL(self), mpd_data_get_first(data));
174             }
175             else
176             {
177                 data = mpd_playlist_get_changes_posid(mi, self->_priv->playlist_id);
178                 /* if the new length is shorter then the old, remove rows at the end */
179                 if(new_length < old_length)
180                 {
181                     int i;
182                     MpdData_real *odata = (MpdData_real *)mpd_data_get_first(GMPC_MPDDATA_MODEL(self)->_priv->data);
183                     while(odata->next)odata = odata->next;
184                     /* data should be last */
186                     for(i=old_length-1;i>=new_length;i--)
187                     {
188                         GtkTreePath *path = gtk_tree_path_new(); 
189                         gtk_tree_path_append_index(path, i);
190                         gtk_tree_model_row_deleted(GTK_TREE_MODEL(self), path);
191                         gtk_tree_path_free(path);
193                         if(odata->song)
194                         {
195                             self->_priv->loaded_songs--;
196                             if(odata->song->time > 0)
197                             {
198                                 self->_priv->total_time -= odata->song->time;
199                                 self_total_playtime_changed(self, self->_priv->loaded_songs, self->_priv->total_time);
200                             }
201                         }
203                         odata = (MpdData_real *)mpd_data_delete_item((MpdData *)odata);
204                        GMPC_MPDDATA_MODEL(self)->num_rows--;
205                     }
206                 }
207                 /* if it's longer, append rows */
208                 else if ( new_length > old_length)
209                 {
210                     int i;
211                     MpdData_real *odata = (MpdData_real *)mpd_data_get_first(GMPC_MPDDATA_MODEL(self)->_priv->data);
212                     /* get the last element */
213                     while(odata->next) odata = odata->next;
214                     for(i=old_length; i< new_length;i++)
215                     {
216                        GtkTreePath *path = gtk_tree_path_new(); 
217                        GtkTreeIter iter;
218                        /* Append node */
219                        odata = (MpdData_real *)mpd_new_data_struct_append((MpdData *)odata);
220                        odata->type = MPD_DATA_TYPE_SONG;
221                        odata->song = NULL;
222                         /* Create iter */
223                        iter.stamp = GMPC_MPDDATA_MODEL(self)->_priv->stamp;
224                        iter.user_data = NULL; 
225                        iter.user_data2 =  GINT_TO_POINTER(GMPC_MPDDATA_MODEL(self)->num_rows);
226                        /* show the path */
227                        gtk_tree_path_append_index(path, i);
228                         /* insert it */
229                        gtk_tree_model_row_inserted(GTK_TREE_MODEL(self), path, &iter);
230                        gtk_tree_path_free(path);
231                        GMPC_MPDDATA_MODEL(self)->num_rows++;
233                     }
234                 }
235                 /* Now mark the changed rows */
236                 if(data)
237                 {
238                     int i=0;
239                     MpdData_real *list_iter =(MpdData_real *) mpd_data_get_first(GMPC_MPDDATA_MODEL(self)->_priv->data);
240                     MpdData_real *data_iter =(MpdData_real *) mpd_data_get_first(data);
241                     while(data_iter && list_iter)
242                     {
243                         GtkTreeIter iter;
244                         GtkTreePath *path = gtk_tree_path_new(); 
245                        /* get the right entry */
246                         for(;list_iter != NULL && i!= data_iter->song->pos;i++)
247                             list_iter = list_iter->next;
249                         if(list_iter)
250                         {
251                             if(list_iter->song)
252                             {
253                                 self->_priv->loaded_songs--;
254                                 if(list_iter->song->time > 0)
255                                 {
256                                     self->_priv->total_time -= list_iter->song->time;
257                                     self_total_playtime_changed(self, self->_priv->loaded_songs, self->_priv->total_time);
258                                 }
259                             }
261                             if(list_iter->song)
262                                 mpd_freeSong(list_iter->song);
263                             list_iter->song = NULL;
265                             iter.stamp = GMPC_MPDDATA_MODEL(self)->_priv->stamp;
266                             iter.user_data = NULL; 
267                             iter.user_data2 =  GINT_TO_POINTER(i);
268                             gtk_tree_path_append_index(path, i);
269                             gtk_tree_model_row_changed(GTK_TREE_MODEL(self), path, &iter);
272                         }
273                         gtk_tree_path_free(path);
274                         data_iter = data_iter->next;
275                     }
276                     mpd_data_free(data);
277                 }
278             }
279             if(GMPC_MPDDATA_MODEL(self)->num_rows != new_length)
280             {
281                 g_log(LOG_DOMAIN, G_LOG_LEVEL_WARNING, "Playlist out of sync: %i-%i \n",GMPC_MPDDATA_MODEL(self)->num_rows,new_length);
282             }
283             self->_priv->playlist_id = mpd_playlist_get_playlist_id(mi);
284         }
285         if(what&(MPD_CST_SONGID|MPD_CST_SONGPOS|MPD_CST_STATE))
286         {
287             GtkTreeIter iter;
288             GtkTreePath *path = NULL;
289             int i = mpd_player_get_current_song_pos(self->_priv->mi);
290             if(self->_priv->old_highlight >= 0)
291             {
292                 /* get the right entry */
293                 path = gtk_tree_path_new(); 
294                 iter.stamp = GMPC_MPDDATA_MODEL(self)->_priv->stamp;
295                 iter.user_data = NULL; 
296                 iter.user_data2 =  GINT_TO_POINTER(self->_priv->old_highlight);
297                 gtk_tree_path_append_index(path, self->_priv->old_highlight);
298                 gtk_tree_model_row_changed(GTK_TREE_MODEL(self), path, &iter);
299                 gtk_tree_path_free(path);
300                 self->_priv->old_highlight = -1;
301             }
302             if(i >= 0)
303             {
304                 /* Needs to be set before updating */
305                 self->_priv->old_highlight = i;
306                 /* get the right entry */
307                 path = gtk_tree_path_new(); 
308                 iter.stamp = GMPC_MPDDATA_MODEL(self)->_priv->stamp;
309                 iter.user_data = NULL; 
310                 iter.user_data2 =  GINT_TO_POINTER(i);
311                 gtk_tree_path_append_index(path, i);
312                 gtk_tree_model_row_changed(GTK_TREE_MODEL(self), path, &iter);
313                 self_current_song_changed(self, path, &iter);
314                 gtk_tree_path_free(path);
315             }
316         }
318     }
320     signal last NONE (POINTER, POINTER)
321     void
322     current_song_changed(self,  GtkTreePath *path,GtkTreeIter *iter)
323     {
325     }
326     
327     signal last NONE (LONG, LONG)
328     void
329     total_playtime_changed(self, unsigned long loaded_songs, unsigned long total_playtime)
330     {
331     }
333     private
334     void
335     connection_changed(self, MpdObj *mi, int connect, Gmpc:Connection *conn (check type))
336     {
337         if(connect == 0)
338         {
339             gmpc_mpddata_model_set_mpd_data(GMPC_MPDDATA_MODEL(self), NULL);
340             self->_priv->playlist_id = 0;
341             self->_priv->loaded_songs = 0;
342             self->_priv->total_time = 0;
343             self_total_playtime_changed(self, self->_priv->loaded_songs, self->_priv->total_time);
344         }
345     }
347     public
348     gboolean
349     is_current_song(Gtk:Tree:Model *model(check null type), GtkTreeIter *iter (check null))
350     {
351         Self *self = GMPC_MPDDATA_MODEL_PLAYLIST(model);
352         int n = GPOINTER_TO_INT(iter->user_data2);
353         if(n == self->_priv->old_highlight)
354             return TRUE;
355         return FALSE;
356     }
360     /**
361      * "override" the get_value method, because we need to fetch the value before it's available.
362      * So after we fetch it, let the Gmpc:MpdData:Model handle the hard work again.
363      */
364     interface Gtk:Tree:Model
365     private void 
366     get_value(Gtk:Tree:Model *model(check null type), GtkTreeIter *iter (check null), gint column (check >= 0 < MPDDATA_MODEL_N_COLUMNS), GValue *value (check null))
367     {
368         Self *self = GMPC_MPDDATA_MODEL_PLAYLIST(model);
369         MpdData_real *data = iter->user_data;   
370         int n = GPOINTER_TO_INT(iter->user_data2);
371         if(data == NULL) {
372             g_warning("data == NULL :: %i:: %i:: %i :: %i",n, GMPC_MPDDATA_MODEL(self)->num_rows,
373             GMPC_MPDDATA_MODEL(self)->_priv->stamp,
374             iter->stamp
376             );
378             g_value_init(value, GMPC_MPDDATA_MODEL(self)->types[column]);
379             return;
380         }
381         if(data->song == NULL)
382         {
383             int rs = n - n%BLOCK_SIZE;
384             int end = ((rs+(BLOCK_SIZE-1)) >= GMPC_MPDDATA_MODEL(self)->num_rows)? GMPC_MPDDATA_MODEL(self)->num_rows-1:rs+(BLOCK_SIZE-1);
386             MpdData_real *edit =(MpdData_real *) data;
387             MpdData_real *miter,*data2 = (MpdData_real *)mpd_playlist_get_song_from_pos_range(self->_priv->mi,rs, end); 
389             /* rewind to start block */
390             while(n>rs){ edit = edit->prev; rs++;}
392             data2 =(MpdData_real *) mpd_data_get_first((MpdData *)data2);
393             miter =data2;
394             while(data2){
395                 if(edit->song == NULL)
396                 {
397                     edit->song = data2->song;
398                     data2->song = NULL;
399                     self->_priv->loaded_songs++;
400                     if(edit->song->time>0)
401                     {
402                         self->_priv->total_time += edit->song->time;
403                     }
404                 }
405                 edit = edit->next;
406                 data2  = data2->next;
407             }
408             mpd_data_free((MpdData *)miter);
410             self_total_playtime_changed(self, self->_priv->loaded_songs, self->_priv->total_time);
411         }
412         /**
413          * If the fetch failed, return an empty value, otherwise we will get crashes
414          */
415         if(data->song == NULL)
416         {
417             g_value_init(value, GMPC_MPDDATA_MODEL(self)->types[column]);
418             g_log(LOG_DOMAIN, G_LOG_LEVEL_WARNING,"failed to get song entry\n");
419             return;
420         }
421         if(column == MPDDATA_MODEL_COL_ICON_ID)
422         {
423             if(n == mpd_player_get_current_song_pos(self->_priv->mi))
424             {
425                 g_value_init(value, GMPC_MPDDATA_MODEL(self)->types[column]);
426                 g_value_set_string(value, "gtk-media-play-ltr");
427                 return;
428             }
430         }
431         /**
432          * Call the parent function again
433          */
434         if(n < GMPC_MPDDATA_MODEL(self)->num_rows)
435             gmpc_mpddata_model_get_value(model, iter, column, value);
436     }
437     /**
438      * Moving interface 
439      */
440         interface Gtk:Tree:Drag:Dest
441             private gboolean row_drop_possible 
442                                         (Gtk:Tree:Drag:Dest *drag_dest,
443                                          Gtk:Tree:Path          *dest,
444                                          Gtk:Selection:Data *selection_data)
445                 {
446                         return TRUE;
447                 }
448                 interface Gtk:Tree:Drag:Dest
449                 private gboolean drag_data_received 
450                                 (Gtk:Tree:Drag:Dest *drag_dest, 
451                                  Gtk:Tree:Path *dest,
452                                  Gtk:Selection:Data *selection_data) 
453        {
454            GtkTreePath *path=NULL;
455            GtkTreeModel *model=NULL;
457            Self *self = NULL;
458            gint *ind = NULL, *ind2 = NULL;
459            if(dest == NULL || !gtk_tree_get_row_drag_data(selection_data, &model, &path))
460            {
461                return FALSE;
462            }
463            if(model != GTK_TREE_MODEL(drag_dest)) {
464                 GtkTreeIter iter;
465                 mpd_Song *song = NULL;
466                ind = gtk_tree_path_get_indices(dest);
467                if(gtk_tree_model_get_iter(model, &iter, path)) 
468                {
469                    gtk_tree_model_get(model, &iter,MPDDATA_MODEL_COL_MPDSONG, &song, -1);
470                    if(song && song->file) {
471                        int destp = ind[0];
472                        int length = mpd_playlist_get_playlist_length(connection); 
473                        mpd_playlist_add(connection,song->file);
474                        if(destp >= 0)
475                            mpd_playlist_move_pos(connection, length, destp);
476                    }
477                }
478                return FALSE;
479            }
480            if(GMPC_MPDDATA_MODEL(model)->num_rows < 2)
481            {
482                gtk_tree_path_free(path);
483                return FALSE;
484            }
485            self =  GMPC_MPDDATA_MODEL_PLAYLIST(model);
486            ind = gtk_tree_path_get_indices(dest);
487            ind2 = gtk_tree_path_get_indices(path);
488            if(ind && ind2)
489            {
490                int original = ind2[0];
491                int destination = ind[0];
492      /*         if(destination >0 && ind[1] != '\0') destination--; */
493                if(destination > original) destination--;
494                mpd_playlist_move_pos(self->_priv->mi,original,destination);
495            }
496            gtk_tree_path_free(path);
497            return TRUE;
498        } 
499        /** */
500        public 
501        void
502        trigger_total_playtime_signal(self)
503        {
504            self_total_playtime_changed(self, self->_priv->loaded_songs, self->_priv->total_time);
505        }
506        public
507        void
508        get_total_playtime(self, unsigned long *loaded_songs, unsigned long *total_time)
509        {
510             if(loaded_songs)
511                 (*loaded_songs) = self->_priv->loaded_songs;
512             if(total_time)
513                 (*total_time)= self->_priv->total_time;
514        }