1 /*
2  *  Copyright (C) 2005 Marc Pavot <marc.pavot@gmail.com>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2, or (at your option)
7  *  any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  *
18  */
19 
20 #include "widgets/ario-playlist.h"
21 #include <gtk/gtk.h>
22 #include <gdk/gdkkeysyms.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include <ctype.h>
26 #include <glib/gi18n.h>
27 
28 #include "ario-util.h"
29 #include "ario-debug.h"
30 #include "lib/ario-conf.h"
31 #include "preferences/ario-preferences.h"
32 #include "servers/ario-server.h"
33 #include "shell/ario-shell-songinfos.h"
34 #include "sources/ario-source-manager.h"
35 #include "widgets/ario-dnd-tree.h"
36 
37 typedef struct ArioPlaylistColumn ArioPlaylistColumn;
38 
39 static void ario_playlist_finalize (GObject *object);
40 static void ario_playlist_changed_cb (ArioServer *server,
41                                       ArioPlaylist *playlist);
42 static void ario_playlist_connectivity_changed_cb (ArioServer *server,
43                                                    ArioPlaylist *playlist);
44 static void ario_playlist_song_changed_cb (ArioServer *server,
45                                            ArioPlaylist *playlist);
46 static void ario_playlist_state_changed_cb (ArioServer *server,
47                                             ArioPlaylist *playlist);
48 static void ario_playlist_sort_changed_cb (GtkTreeSortable *treesortable,
49                                            ArioPlaylist *playlist);
50 static void ario_playlist_rows_reordered_cb (GtkTreeModel *tree_model,
51                                              GtkTreePath *path,
52                                              GtkTreeIter *iter,
53                                              gpointer arg3,
54                                              ArioPlaylist *playlist);
55 static void ario_playlist_drag_leave_cb (GtkWidget *widget,
56                                          GdkDragContext *context,
57                                          gint x, gint y,
58                                          GtkSelectionData *data,
59                                          guint info,
60                                          guint time,
61                                          gpointer user_data);
62 static void ario_playlist_drag_data_get_cb (GtkWidget * widget,
63                                             GdkDragContext * context,
64                                             GtkSelectionData * selection_data,
65                                             guint info, guint time, gpointer data);
66 static gboolean ario_playlist_drag_drop_cb (GtkWidget * widget,
67                                             gint x, gint y,
68                                             guint time,
69                                             ArioPlaylist *playlist);
70 static void ario_playlist_cmd_clear (GSimpleAction *action,
71                                      GVariant *parameter,
72                                      gpointer data);
73 static void ario_playlist_cmd_shuffle (GSimpleAction *action,
74                                        GVariant *parameter,
75                                        gpointer data);
76 static void ario_playlist_cmd_remove (GSimpleAction *action,
77                                       GVariant *parameter,
78                                       gpointer data);
79 static void ario_playlist_cmd_crop (GSimpleAction *action,
80                                     GVariant *parameter,
81                                     gpointer data);
82 static void ario_playlist_search (ArioPlaylist *playlist,
83                                   const char* text);
84 static void ario_playlist_cmd_search (GSimpleAction *action,
85                                       GVariant *parameter,
86                                       gpointer data);
87 static void ario_playlist_cmd_songs_properties (GSimpleAction *action,
88                                                 GVariant *parameter,
89                                                 gpointer data);
90 static void ario_playlist_cmd_goto_playing_song (GSimpleAction *action,
91                                                  GVariant *parameter,
92                                                  gpointer data);
93 static void ario_playlist_cmd_save (GSimpleAction *action,
94                                     GVariant *parameter,
95                                     gpointer data);
96 static gboolean ario_playlist_view_key_press_cb (GtkWidget *widget,
97                                                  GdkEventKey *event,
98                                                  ArioPlaylist *playlist);
99 static void ario_playlist_activate_selected ();
100 static void ario_playlist_column_visible_changed_cb (guint notification_id,
101                                                      ArioPlaylistColumn *ario_column);
102 static void ario_playlist_popup_menu_cb (ArioDndTree* tree,
103                                          ArioPlaylist *playlist);
104 static void ario_playlist_activate_cb (ArioDndTree* tree,
105                                        ArioPlaylist *playlist);
106 
107 static ArioPlaylist *instance = NULL;
108 
109 struct ArioPlaylistPrivate
110 {
111         GtkWidget *tree;
112         GtkListStore *model;
113         GtkTreeSelection *selection;
114         GtkTreeModelFilter *filter;
115 
116         GtkWidget *search_hbox;
117         GtkWidget *search_entry;
118         gboolean in_search;
119         const gchar *search_text;
120         gulong dnd_handler;
121 
122         gint64 playlist_id;
123         int playlist_length;
124         gint pos;
125 
126         GdkPixbuf *play_pixbuf;
127 
128         GtkWidget *menu;
129 };
130 
131 static const GActionEntry widget_actions[] = {
132         { "playlist-clear", ario_playlist_cmd_clear},
133         { "playlist-shuffle", ario_playlist_cmd_shuffle},
134         { "playlist-crop", ario_playlist_cmd_crop},
135         { "playlist-search", ario_playlist_cmd_search},
136         { "playlist-remove", ario_playlist_cmd_remove},
137         { "playlist-save", ario_playlist_cmd_save},
138         { "playlist-goto", ario_playlist_cmd_goto_playing_song},
139         { "playlist-properties", ario_playlist_cmd_songs_properties},
140 };
141 
142 /* Object properties */
143 enum
144 {
145         PROP_0,
146 };
147 
148 /* Treeview columns */
149 enum
150 {
151         PIXBUF_COLUMN,
152         TRACK_COLUMN,
153         TITLE_COLUMN,
154         ARTIST_COLUMN,
155         ALBUM_COLUMN,
156         DURATION_COLUMN,
157         FILE_COLUMN,
158         GENRE_COLUMN,
159         DATE_COLUMN,
160         DISC_COLUMN,
161         ID_COLUMN,
162         TIME_COLUMN,
163         N_COLUMN
164 };
165 
166 /*
167  * ArioPlaylistColumn is used to initialise a column in the
168  * playlist treeview and defines various column properties
169  */
170 struct ArioPlaylistColumn {
171         /* Column number */
172         const int columnnb;
173 
174         /* Identification of column size preference in Ario
175          * configuration system
176          */
177         const gchar *pref_size;
178 
179         /* Default size if it is not set in preferences */
180         const int default_size;
181 
182         /* Identification of column order preference in Ario
183          * configuration system
184          */
185         const gchar *pref_order;
186 
187         /* Default order if it is not set in preferences */
188         const int default_order;
189 
190         /* Identification of column visibility preference in Ario
191          * configuration system
192          */
193         const gchar *pref_is_visible;
194 
195         /* Default visibility if it is not set in preferences */
196         const gboolean default_is_visible;
197 
198         /* Whether the column is a pixbuf column or a text column */
199         const gboolean is_pixbuf;
200 
201         /* Whether the column is resizable or not */
202         const gboolean is_resizable;
203 
204         /* Whether the column is sortable or not */
205         const gboolean is_sortable;
206 
207         /* Pointer to the column once it is initialized */
208         GtkTreeViewColumn *column;
209 };
210 
211 /* Definition of all columns with the preperties */
212 static ArioPlaylistColumn all_columns []  = {
213         { PIXBUF_COLUMN, NULL, 20, PREF_PIXBUF_COLUMN_ORDER, PREF_PIXBUF_COLUMN_ORDER_DEFAULT, NULL, TRUE, TRUE, FALSE, FALSE, NULL },
214         { TRACK_COLUMN, PREF_TRACK_COLUMN_SIZE, PREF_TRACK_COLUMN_SIZE_DEFAULT, PREF_TRACK_COLUMN_ORDER, PREF_TRACK_COLUMN_ORDER_DEFAULT, PREF_TRACK_COLUMN_VISIBLE, PREF_TRACK_COLUMN_VISIBLE_DEFAULT, FALSE, TRUE, TRUE, NULL },
215         { TITLE_COLUMN, PREF_TITLE_COLUMN_SIZE, PREF_TITLE_COLUMN_SIZE_DEFAULT, PREF_TITLE_COLUMN_ORDER, PREF_TITLE_COLUMN_ORDER_DEFAULT, PREF_TITLE_COLUMN_VISIBLE, PREF_TITLE_COLUMN_VISIBLE_DEFAULT, FALSE, TRUE, TRUE, NULL },
216         { ARTIST_COLUMN, PREF_ARTIST_COLUMN_SIZE, PREF_ARTIST_COLUMN_SIZE_DEFAULT, PREF_ARTIST_COLUMN_ORDER, PREF_ARTIST_COLUMN_ORDER_DEFAULT, PREF_ARTIST_COLUMN_VISIBLE, PREF_ARTIST_COLUMN_VISIBLE_DEFAULT, FALSE, TRUE, TRUE, NULL },
217         { ALBUM_COLUMN, PREF_ALBUM_COLUMN_SIZE, PREF_ALBUM_COLUMN_SIZE_DEFAULT, PREF_ALBUM_COLUMN_ORDER, PREF_ALBUM_COLUMN_ORDER_DEFAULT, PREF_ALBUM_COLUMN_VISIBLE, PREF_ALBUM_COLUMN_VISIBLE_DEFAULT, FALSE, TRUE, TRUE, NULL },
218         { DURATION_COLUMN, PREF_DURATION_COLUMN_SIZE, PREF_DURATION_COLUMN_SIZE_DEFAULT, PREF_DURATION_COLUMN_ORDER, PREF_DURATION_COLUMN_ORDER_DEFAULT, PREF_DURATION_COLUMN_VISIBLE, PREF_DURATION_COLUMN_VISIBLE_DEFAULT, FALSE, TRUE, TRUE, NULL },
219         { FILE_COLUMN, PREF_FILE_COLUMN_SIZE, PREF_FILE_COLUMN_SIZE_DEFAULT, PREF_FILE_COLUMN_ORDER, PREF_FILE_COLUMN_ORDER_DEFAULT, PREF_FILE_COLUMN_VISIBLE, PREF_FILE_COLUMN_VISIBLE_DEFAULT, FALSE, TRUE, TRUE, NULL },
220         { GENRE_COLUMN, PREF_GENRE_COLUMN_SIZE, PREF_GENRE_COLUMN_SIZE_DEFAULT, PREF_GENRE_COLUMN_ORDER, PREF_GENRE_COLUMN_ORDER_DEFAULT, PREF_GENRE_COLUMN_VISIBLE, PREF_GENRE_COLUMN_VISIBLE_DEFAULT, FALSE, TRUE, TRUE, NULL },
221         { DATE_COLUMN, PREF_DATE_COLUMN_SIZE, PREF_DATE_COLUMN_SIZE_DEFAULT, PREF_DATE_COLUMN_ORDER, PREF_DATE_COLUMN_ORDER_DEFAULT, PREF_DATE_COLUMN_VISIBLE, PREF_DATE_COLUMN_VISIBLE_DEFAULT, FALSE, TRUE, TRUE, NULL },
222         { DISC_COLUMN, PREF_DISC_COLUMN_SIZE, PREF_DISC_COLUMN_SIZE_DEFAULT, PREF_DISC_COLUMN_ORDER, PREF_DISC_COLUMN_ORDER_DEFAULT, PREF_DISC_COLUMN_VISIBLE, PREF_DISC_COLUMN_VISIBLE_DEFAULT, FALSE, TRUE, TRUE, NULL },
223         { -1, NULL, 0, NULL, 0, NULL, FALSE, FALSE, FALSE, FALSE, NULL },
224         { -1, NULL, 0, NULL, 0, NULL, FALSE, FALSE, FALSE, FALSE, NULL },
225         { -1, NULL, 0, NULL, 0, NULL, FALSE, FALSE, FALSE, FALSE, NULL }
226 };
227 
228 /* Targets for drag & drop from other widgets */
229 static const GtkTargetEntry targets  [] = {
230         { "text/internal-list", 0, 10},
231         { "text/songs-list", 0, 20 },
232         { "text/radios-list", 0, 30 },
233         { "text/directory", 0, 40 },
234         { "text/criterias-list", 0, 50 },
235 };
236 
237 /* Targets for internal drag & drop */
238 static const GtkTargetEntry internal_targets  [] = {
239         { "text/internal-list", 0, 10 },
240 };
241 
242 #define ARIO_PLAYLIST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TYPE_ARIO_PLAYLIST, ArioPlaylistPrivate))
G_DEFINE_TYPE(ArioPlaylist,ario_playlist,ARIO_TYPE_SOURCE)243 G_DEFINE_TYPE (ArioPlaylist, ario_playlist, ARIO_TYPE_SOURCE)
244 
245 static gchar *
246 ario_playlist_get_id (ArioSource *source)
247 {
248         return "playlist";
249 }
250 
251 static gchar *
ario_playlist_get_name(ArioSource * source)252 ario_playlist_get_name (ArioSource *source)
253 {
254         return _("Playlist");
255 }
256 
257 static gchar *
ario_playlist_get_icon(ArioSource * source)258 ario_playlist_get_icon (ArioSource *source)
259 {
260         return "";
261 }
262 
263 static void
ario_playlist_class_init(ArioPlaylistClass * klass)264 ario_playlist_class_init (ArioPlaylistClass *klass)
265 {
266         ARIO_LOG_FUNCTION_START;
267         GObjectClass *object_class = G_OBJECT_CLASS (klass);
268         ArioSourceClass *source_class = ARIO_SOURCE_CLASS (klass);
269 
270         /* Virtual methods */
271         object_class->finalize = ario_playlist_finalize;
272 
273         /* Virtual ArioSource methods */
274         source_class->get_id = ario_playlist_get_id;
275         source_class->get_name = ario_playlist_get_name;
276         source_class->get_icon = ario_playlist_get_icon;
277 
278         /* Private attributes */
279         g_type_class_add_private (klass, sizeof (ArioPlaylistPrivate));
280 }
281 
282 static void
ario_playlist_append_column(ArioPlaylistColumn * ario_column,const gchar * column_name)283 ario_playlist_append_column (ArioPlaylistColumn *ario_column,
284                              const gchar *column_name)
285 {
286         ARIO_LOG_FUNCTION_START;
287         GtkTreeViewColumn *column;
288         GtkCellRenderer *renderer;
289 
290         /* Create the appropriate renderer */
291         if (ario_column->is_pixbuf) {
292                 renderer = gtk_cell_renderer_pixbuf_new ();
293                 column = gtk_tree_view_column_new_with_attributes (column_name,
294                                                                    renderer,
295                                                                    "pixbuf", ario_column->columnnb,
296                                                                    NULL);
297         } else {
298                 renderer = gtk_cell_renderer_text_new ();
299                 column = gtk_tree_view_column_new_with_attributes (column_name,
300                                                                    renderer,
301                                                                    "text", ario_column->columnnb,
302                                                                    NULL);
303         }
304 
305         /* Set column size */
306         gtk_tree_view_column_set_resizable (column, ario_column->is_resizable);
307         gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
308         if (ario_column->pref_size)
309                 gtk_tree_view_column_set_fixed_width (column, ario_conf_get_integer (ario_column->pref_size, ario_column->default_size));
310         else
311                 gtk_tree_view_column_set_fixed_width (column, ario_column->default_size);
312 
313         /* Set column sortation */
314         if (ario_column->is_sortable) {
315                 gtk_tree_view_column_set_sort_indicator (column, TRUE);
316                 gtk_tree_view_column_set_sort_column_id (column, ario_column->columnnb);
317         }
318 
319         /* All columns are reorderable */
320         gtk_tree_view_column_set_reorderable (column, TRUE);
321 
322         /* Column visibility */
323         if (ario_column->pref_is_visible)
324                 gtk_tree_view_column_set_visible (column, ario_conf_get_integer (ario_column->pref_is_visible, ario_column->default_is_visible));
325         else
326                 gtk_tree_view_column_set_visible (column, ario_column->default_is_visible);
327 
328         /* Add column to the treeview */
329         gtk_tree_view_append_column (GTK_TREE_VIEW (instance->priv->tree), column);
330         ario_column->column = column;
331 
332         /* Notification if user changes column visibility in preferences */
333         if (ario_column->pref_is_visible)
334                 ario_conf_notification_add (ario_column->pref_is_visible,
335                                             (ArioNotifyFunc) ario_playlist_column_visible_changed_cb,
336                                             ario_column);
337 }
338 
339 static void
ario_playlist_reorder_columns(void)340 ario_playlist_reorder_columns (void)
341 {
342         ARIO_LOG_FUNCTION_START;
343         GtkTreeViewColumn *orders[N_COLUMN] = {NULL};
344         GtkTreeViewColumn *current, *prev = NULL;
345         int i, order;
346 
347         /* Get an ordered list of column in orders[] thanks to the preferences */
348         for (i = 0; all_columns[i].columnnb != -1; ++i)
349         {
350                 order = ario_conf_get_integer (all_columns[i].pref_order, all_columns[i].default_order);
351                 if (order < N_COLUMN)
352                         orders[order] = all_columns[i].column;
353         }
354 
355         /* Move columns in the order computed in orders[] */
356         for (i = 0; i < N_COLUMN; ++i) {
357                 current = orders[i];
358                 if (current)
359                         gtk_tree_view_move_column_after (GTK_TREE_VIEW (instance->priv->tree), current, prev);
360                 prev = current;
361         }
362 
363         /* Resize the last visible column */
364         for (i = N_COLUMN - 1; i >= 0; --i) {
365                 if (!orders[i])
366                         continue;
367                 if (gtk_tree_view_column_get_visible (orders[i])) {
368                         gtk_tree_view_column_set_fixed_width (orders[i], ario_util_min (gtk_tree_view_column_get_fixed_width (orders[i]), 50));
369                         break;
370                 }
371         }
372 }
373 
374 static gint
ario_playlist_no_sort(G_GNUC_UNUSED GtkTreeModel * model,G_GNUC_UNUSED GtkTreeIter * a,G_GNUC_UNUSED GtkTreeIter * b,G_GNUC_UNUSED gpointer user_data)375 ario_playlist_no_sort (G_GNUC_UNUSED GtkTreeModel *model,
376                        G_GNUC_UNUSED GtkTreeIter *a,
377                        G_GNUC_UNUSED GtkTreeIter *b,
378                        G_GNUC_UNUSED gpointer user_data)
379 {
380         /* Default sort always return 0 */
381         return 0;
382 }
383 
384 static gboolean
ario_playlist_filter_func(GtkTreeModel * model,GtkTreeIter * iter,ArioPlaylist * playlist)385 ario_playlist_filter_func (GtkTreeModel *model,
386                            GtkTreeIter  *iter,
387                            ArioPlaylist *playlist)
388 {
389         ARIO_LOG_FUNCTION_START;
390         gchar *title, *artist, *album, *genre;
391         gboolean visible = TRUE, filter;
392         int i;
393         gchar **cmp_str;
394 
395         /* There is no filter if the search box is empty */
396         if (!playlist->priv->search_text || *playlist->priv->search_text == '\0')
397                 return TRUE;
398 
399         /* Split on spaces to have multiple filters */
400         cmp_str = g_strsplit (playlist->priv->search_text, " ", -1);
401         if (!cmp_str)
402                 return TRUE;
403 
404         /* Get data needed to filter */
405         gtk_tree_model_get (model, iter,
406                             TITLE_COLUMN, &title,
407                             ARTIST_COLUMN, &artist,
408                             ALBUM_COLUMN, &album,
409                             GENRE_COLUMN, &genre,
410                             -1);
411 
412         /* Loop on every filter */
413         for (i = 0; cmp_str[i] && visible; ++i) {
414                 if (g_utf8_collate (cmp_str[i], ""))
415                 {
416                         /* By default the row doesn't match the filter */
417                         filter = FALSE;
418 
419                         /* The row match the filter if one of the visible column contains
420                          * the filter */
421                         if (title
422                             && ario_conf_get_boolean (PREF_TITLE_COLUMN_VISIBLE, PREF_TITLE_COLUMN_VISIBLE_DEFAULT)
423                             && ario_util_stristr (title, cmp_str[i])) {
424                                 filter = TRUE;
425                         } else if (artist
426                                    && ario_conf_get_boolean (PREF_ARTIST_COLUMN_VISIBLE, PREF_ARTIST_COLUMN_VISIBLE_DEFAULT)
427                                    && ario_util_stristr (artist, cmp_str[i])) {
428                                 filter = TRUE;
429                         } else if (album
430                                    && ario_conf_get_boolean (PREF_ALBUM_COLUMN_VISIBLE, PREF_ALBUM_COLUMN_VISIBLE_DEFAULT)
431                                    && ario_util_stristr (album, cmp_str[i])) {
432                                 filter = TRUE;
433                         } else if (genre
434                                    && ario_conf_get_boolean (PREF_GENRE_COLUMN_VISIBLE, PREF_GENRE_COLUMN_VISIBLE_DEFAULT)
435                                    && ario_util_stristr (genre, cmp_str[i])) {
436                                 filter = TRUE;
437                         }
438 
439                         /* The row must match all the filters to be shown */
440                         visible &= filter;
441                 }
442         }
443 
444         g_strfreev (cmp_str);
445         g_free (title);
446         g_free (artist);
447         g_free (album);
448         g_free (genre);
449 
450         return visible;
451 }
452 
453 static void
ario_playlist_search_close(GtkButton * button,ArioPlaylist * playlist)454 ario_playlist_search_close (GtkButton *button,
455                             ArioPlaylist *playlist)
456 {
457         ARIO_LOG_FUNCTION_START;
458         /* Hide the search box */
459         gtk_entry_set_text (GTK_ENTRY (playlist->priv->search_entry), "");
460         gtk_widget_hide (playlist->priv->search_hbox);
461         gtk_tree_view_set_model (GTK_TREE_VIEW (playlist->priv->tree),
462                                  GTK_TREE_MODEL (playlist->priv->model));
463         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW (playlist->priv->tree), TRUE);
464         playlist->priv->in_search = FALSE;
465 
466         /* Stop handling drag & drop differently */
467         if (playlist->priv->dnd_handler) {
468                 g_signal_handler_disconnect (playlist->priv->tree,
469                                              playlist->priv->dnd_handler);
470                 playlist->priv->dnd_handler = 0;
471         }
472 }
473 
474 static void
ario_playlist_search_entry_changed(GtkEntry * entry,ArioPlaylist * playlist)475 ario_playlist_search_entry_changed (GtkEntry *entry,
476                                     ArioPlaylist *playlist)
477 {
478         ARIO_LOG_FUNCTION_START;
479         const gchar *cmp = gtk_entry_get_text (GTK_ENTRY (playlist->priv->search_entry));
480         playlist->priv->search_text = cmp;
481 
482         if (!cmp || *cmp == '\0') {
483                 /* Close search window if text box is empty */
484                 ario_playlist_search_close (NULL, playlist);
485                 gtk_widget_grab_focus (playlist->priv->tree);
486         } else {
487                 /* Refilter all rows if filter has changed */
488                 gtk_tree_model_filter_refilter (playlist->priv->filter);
489         }
490 }
491 
492 static gboolean
ario_playlist_search_entry_key_press_cb(GtkWidget * widget,GdkEventKey * event,ArioPlaylist * playlist)493 ario_playlist_search_entry_key_press_cb (GtkWidget *widget,
494                                          GdkEventKey *event,
495                                          ArioPlaylist *playlist)
496 {
497         ARIO_LOG_FUNCTION_START;
498         /* Escape key closes the search box */
499         if (event->keyval == GDK_KEY_Escape) {
500                 ario_playlist_search_close (NULL, playlist);
501                 return TRUE;
502         }
503 
504         return FALSE;
505 }
506 
507 static void
ario_playlist_init(ArioPlaylist * playlist)508 ario_playlist_init (ArioPlaylist *playlist)
509 {
510         ARIO_LOG_FUNCTION_START;
511         int i;
512         const gchar *column_names []  = { " ", _("Track"), _("Title"), _("Artist"), _("Album"), _("Duration"), _("File"), _("Genre"), _("Date"), _("Disc") };
513         GtkWidget *image, *close_button, *vbox;
514         GtkScrolledWindow *scrolled_window;
515         GtkBuilder *builder;
516         GMenuModel *menu;
517 
518         /* Attributes initialization */
519         instance = playlist;
520         playlist->priv = ARIO_PLAYLIST_GET_PRIVATE (playlist);
521         playlist->priv->playlist_id = -1;
522         playlist->priv->pos = -1;
523         playlist->priv->playlist_length = 0;
524         playlist->priv->play_pixbuf = gdk_pixbuf_new_from_file (PIXMAP_PATH "play.png", NULL);
525 
526         /* Create main vbox */
527         vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
528 
529         /* Create scrolled window */
530         scrolled_window = GTK_SCROLLED_WINDOW (gtk_scrolled_window_new (NULL, NULL));
531         gtk_scrolled_window_set_policy (scrolled_window, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
532         gtk_scrolled_window_set_shadow_type (scrolled_window, GTK_SHADOW_IN);
533 
534         /* Create the drag & drop tree */
535         playlist->priv->tree = ario_dnd_tree_new (internal_targets,
536                                                   G_N_ELEMENTS (internal_targets),
537                                                   FALSE);
538         gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (playlist->priv->tree), TRUE);
539 
540         /* Append all columns to treeview */
541         for (i = 0; all_columns[i].columnnb != -1; ++i)
542                 ario_playlist_append_column (&all_columns[i], column_names[i]);
543 
544         /* Reorder columns */
545         ario_playlist_reorder_columns ();
546 
547         /* Create tree model */
548         playlist->priv->model = gtk_list_store_new (N_COLUMN,
549                                                     GDK_TYPE_PIXBUF,
550                                                     G_TYPE_STRING,
551                                                     G_TYPE_STRING,
552                                                     G_TYPE_STRING,
553                                                     G_TYPE_STRING,
554                                                     G_TYPE_STRING,
555                                                     G_TYPE_STRING,
556                                                     G_TYPE_STRING,
557                                                     G_TYPE_STRING,
558                                                     G_TYPE_STRING,
559                                                     G_TYPE_INT,
560                                                     G_TYPE_INT);
561 
562         /* Create the filter used when the search box is activated */
563         playlist->priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (GTK_TREE_MODEL (playlist->priv->model), NULL));
564         gtk_tree_model_filter_set_visible_func (playlist->priv->filter,
565                                                 (GtkTreeModelFilterVisibleFunc) ario_playlist_filter_func,
566                                                 playlist,
567                                                 NULL);
568 
569         /* Set various treeview properties */
570         gtk_tree_view_set_model (GTK_TREE_VIEW (playlist->priv->tree),
571                                  GTK_TREE_MODEL (playlist->priv->model));
572         gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (playlist->priv->model),
573                                                  ario_playlist_no_sort,
574                                                  NULL, NULL);
575         gtk_tree_view_set_enable_search (GTK_TREE_VIEW (playlist->priv->tree), FALSE);
576         playlist->priv->selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (playlist->priv->tree));
577         gtk_tree_selection_set_mode (playlist->priv->selection,
578                                      GTK_SELECTION_MULTIPLE);
579 
580         /* Connect signal to detect clicks on columns header for reordering */
581         g_signal_connect (playlist->priv->model,
582                           "sort-column-changed",
583                           G_CALLBACK (ario_playlist_sort_changed_cb),
584                           playlist);
585 
586         /* Set playlist as drag & drop destination */
587         gtk_tree_view_enable_model_drag_dest (GTK_TREE_VIEW (playlist->priv->tree),
588                                               targets, G_N_ELEMENTS (targets),
589                                               GDK_ACTION_MOVE | GDK_ACTION_COPY);
590 
591         /* Add the tree in the scrolled window */
592         gtk_container_add (GTK_CONTAINER (scrolled_window), playlist->priv->tree);
593 
594         /* Create menu */
595         builder = gtk_builder_new_from_file (UI_PATH "ario-playlist-menu.ui");
596         menu = G_MENU_MODEL (gtk_builder_get_object (builder, "menu"));
597         playlist->priv->menu = gtk_menu_new_from_model (menu);
598         gtk_menu_attach_to_widget  (GTK_MENU (playlist->priv->menu),
599                                     GTK_WIDGET (playlist),
600                                     NULL);
601 
602         /* Connect various signals of the tree */
603         g_signal_connect (playlist->priv->tree,
604                           "key_press_event",
605                           G_CALLBACK (ario_playlist_view_key_press_cb),
606                           playlist);
607 
608         g_signal_connect (playlist->priv->tree,
609                           "drag_data_received",
610                           G_CALLBACK (ario_playlist_drag_leave_cb),
611                           playlist);
612 
613         g_signal_connect (playlist->priv->tree,
614                           "drag_data_get",
615                           G_CALLBACK (ario_playlist_drag_data_get_cb),
616                           playlist);
617 
618         g_signal_connect (playlist->priv->tree,
619                           "popup",
620                           G_CALLBACK (ario_playlist_popup_menu_cb),
621                           playlist);
622 
623         g_signal_connect (playlist->priv->tree,
624                           "activate",
625                           G_CALLBACK (ario_playlist_activate_cb),
626                           playlist);
627 
628         /* Creation of search box */
629         playlist->priv->search_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
630 
631         image = gtk_image_new_from_icon_name ("window-close",
632                                               GTK_ICON_SIZE_MENU);
633         close_button = gtk_button_new ();
634         gtk_button_set_relief (GTK_BUTTON (close_button), GTK_RELIEF_NONE);
635         gtk_container_add (GTK_CONTAINER (close_button), image);
636         g_signal_connect (close_button,
637                           "clicked",
638                           G_CALLBACK (ario_playlist_search_close),
639                           playlist);
640 
641         gtk_box_pack_start (GTK_BOX (playlist->priv->search_hbox),
642                             close_button,
643                             FALSE, FALSE, 0);
644 
645         gtk_box_pack_start (GTK_BOX (playlist->priv->search_hbox),
646                             gtk_label_new (_("Filter:")),
647                             FALSE, FALSE, 0);
648         playlist->priv->search_entry = gtk_entry_new ();
649         gtk_box_pack_start (GTK_BOX (playlist->priv->search_hbox),
650                             playlist->priv->search_entry,
651                             FALSE, FALSE, 0);
652         g_signal_connect (playlist->priv->search_entry,
653                           "changed",
654                           G_CALLBACK (ario_playlist_search_entry_changed),
655                           playlist);
656         g_signal_connect (playlist->priv->search_entry,
657                           "key_press_event",
658                           G_CALLBACK (ario_playlist_search_entry_key_press_cb),
659                           playlist);
660 
661         gtk_widget_show_all (playlist->priv->search_hbox);
662         gtk_widget_set_no_show_all (playlist->priv->search_hbox, TRUE);
663         gtk_widget_hide (playlist->priv->search_hbox);
664 
665         /* Add scrolled window to playlist */
666         gtk_box_pack_start (GTK_BOX (vbox),
667                             GTK_WIDGET (scrolled_window),
668                             TRUE, TRUE, 0);
669 
670         /* Add search box to playlist */
671         gtk_box_pack_start (GTK_BOX (vbox),
672                             playlist->priv->search_hbox,
673                             FALSE, FALSE, 0);
674 
675         gtk_box_pack_start (GTK_BOX (playlist),
676                             vbox,
677                             TRUE, TRUE, 0);
678 }
679 
680 void
ario_playlist_shutdown(void)681 ario_playlist_shutdown (void)
682 {
683         ARIO_LOG_FUNCTION_START;
684         int width;
685         int orders[N_COLUMN];
686         GtkTreeViewColumn *column;
687         int i, j = 1;
688         GList *columns, *tmp;
689 
690         /* Get ordered list of columns in orders[] */
691         columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (instance->priv->tree));
692         for (tmp = columns; tmp; tmp = g_list_next (tmp)) {
693                 column = tmp->data;
694                 for (i = 0; i < N_COLUMN; ++i) {
695                         if (all_columns[i].column == column)
696                                 orders[i] = j;
697                 }
698                 ++j;
699         }
700         g_list_free (columns);
701 
702         /* Save preferences */
703         for (i = 0; all_columns[i].columnnb != -1; ++i) {
704                 /* Save column size */
705                 width = gtk_tree_view_column_get_width (all_columns[i].column);
706                 if (width > 10 && all_columns[i].pref_size)
707                         ario_conf_set_integer (all_columns[i].pref_size,
708                                                width);
709                 /* Save column order */
710                 ario_conf_set_integer (all_columns[i].pref_order, orders[all_columns[i].columnnb]);
711         }
712 }
713 
714 static void
ario_playlist_finalize(GObject * object)715 ario_playlist_finalize (GObject *object)
716 {
717         ARIO_LOG_FUNCTION_START;
718         ArioPlaylist *playlist;
719         g_return_if_fail (object != NULL);
720         g_return_if_fail (IS_ARIO_PLAYLIST (object));
721 
722         playlist = ARIO_PLAYLIST (object);
723 
724         g_return_if_fail (playlist->priv != NULL);
725         g_object_unref (playlist->priv->play_pixbuf);
726 
727         G_OBJECT_CLASS (ario_playlist_parent_class)->finalize (object);
728 }
729 
730 /**
731  * This method removes the 'playing' pixbuf from the previous played
732  * song and adds it to the new song
733  */
734 static void
ario_playlist_sync_song(void)735 ario_playlist_sync_song (void)
736 {
737         ARIO_LOG_FUNCTION_START;
738         int state = ario_server_get_current_state ();
739         ArioServerSong *song = ario_server_get_current_song ();
740         GtkTreePath *path;
741         GtkTreeIter iter;
742 
743         /* If we are still playing and the song has not changed we don't do anything */
744         if (song
745             && instance->priv->pos == song->pos
746             && (state == ARIO_STATE_PLAY
747                 || state == ARIO_STATE_PAUSE))
748                 return;
749 
750         /* If there is no song playing and it was already the case before, we don't
751          * do anything */
752         if ((instance->priv->pos == -1
753              && (!song
754                  || state == ARIO_STATE_UNKNOWN
755                  || state == ARIO_STATE_STOP)))
756                 return;
757 
758         /* Remove the 'playing' icon from previous song */
759         if (instance->priv->pos >= 0) {
760                 path = gtk_tree_path_new_from_indices (instance->priv->pos, -1);
761                 if (gtk_tree_model_get_iter (GTK_TREE_MODEL (instance->priv->model), &iter, path)) {
762                         gtk_list_store_set (instance->priv->model, &iter,
763                                             PIXBUF_COLUMN, NULL,
764                                             -1);
765                         instance->priv->pos = -1;
766                 }
767                 gtk_tree_path_free (path);
768         }
769 
770         /* Add 'playing' icon to new song */
771         if (song
772             && state != ARIO_STATE_UNKNOWN
773             && state != ARIO_STATE_STOP) {
774                 path = gtk_tree_path_new_from_indices (song->pos, -1);
775                 if (gtk_tree_model_get_iter (GTK_TREE_MODEL (instance->priv->model), &iter, path)) {
776                         gtk_list_store_set (instance->priv->model, &iter,
777                                             PIXBUF_COLUMN, instance->priv->play_pixbuf,
778                                             -1);
779                         instance->priv->pos = song->pos;
780                 }
781                 gtk_tree_path_free (path);
782         }
783 }
784 
785 static void
ario_playlist_changed_cb(ArioServer * server,ArioPlaylist * playlist)786 ario_playlist_changed_cb (ArioServer *server,
787                           ArioPlaylist *playlist)
788 {
789         ARIO_LOG_FUNCTION_START;
790         gint old_length;
791         GtkTreeIter iter;
792         gchar track[ARIO_MAX_TRACK_SIZE];
793         gchar time[ARIO_MAX_TIME_SIZE];
794         gchar *title;
795         GSList *songs, *tmp;
796         ArioServerSong *song;
797         gboolean need_set;
798         GtkTreePath *path;
799 
800         /* Clear the playlist if ario is not connected to the server */
801         if (!ario_server_is_connected ()) {
802                 playlist->priv->playlist_length = 0;
803                 playlist->priv->playlist_id = -1;
804                 gtk_list_store_clear (playlist->priv->model);
805                 return;
806         }
807 
808         /* Get changes on server */
809         songs = ario_server_get_playlist_changes (playlist->priv->playlist_id);
810         playlist->priv->playlist_id = ario_server_get_current_playlist_id ();
811 
812         old_length = playlist->priv->playlist_length;
813 
814         /* For each change in playlist */
815         for (tmp = songs; tmp; tmp = g_slist_next (tmp)) {
816                 song = tmp->data;
817                 need_set = FALSE;
818                 /* Decide whether to update or to add */
819                 if (song->pos < old_length) {
820                         /* Update */
821                         path = gtk_tree_path_new_from_indices (song->pos, -1);
822                         if (gtk_tree_model_get_iter (GTK_TREE_MODEL (playlist->priv->model), &iter, path)) {
823                                 need_set = TRUE;
824                         }
825                         gtk_tree_path_free (path);
826                 } else {
827                         /* Add */
828                         gtk_list_store_append (playlist->priv->model, &iter);
829                         need_set = TRUE;
830                 }
831                 if (need_set) {
832                         /* Set appropriate data in playlist row */
833                         ario_util_format_time_buf (song->time, time, ARIO_MAX_TIME_SIZE);
834                         ario_util_format_track_buf (song->track, track, ARIO_MAX_TRACK_SIZE);
835                         title = ario_util_format_title (song);
836                         gtk_list_store_set (playlist->priv->model, &iter,
837                                             TRACK_COLUMN, track,
838                                             TITLE_COLUMN, title,
839                                             ARTIST_COLUMN, song->artist,
840                                             ALBUM_COLUMN, song->album ? song->album : ARIO_SERVER_UNKNOWN,
841                                             DURATION_COLUMN, time,
842                                             FILE_COLUMN, song->file,
843                                             GENRE_COLUMN, song->genre,
844                                             DATE_COLUMN, song->date,
845                                             ID_COLUMN, song->id,
846                                             TIME_COLUMN, song->time,
847                                             DISC_COLUMN, song->disc,
848                                             -1);
849                 }
850         }
851 
852         g_slist_foreach (songs, (GFunc) ario_server_free_song, NULL);
853         g_slist_free (songs);
854 
855         playlist->priv->playlist_length = ario_server_get_current_playlist_length ();
856 
857         /* Remove rows at the end of playlist if playlist size has decreased */
858         if (playlist->priv->playlist_length < old_length) {
859                 path = gtk_tree_path_new_from_indices (playlist->priv->playlist_length, -1);
860 
861                 if (gtk_tree_model_get_iter (GTK_TREE_MODEL (playlist->priv->model), &iter, path)) {
862                         while (gtk_list_store_remove (playlist->priv->model, &iter)) { }
863                 }
864 
865                 gtk_tree_path_free (path);
866         }
867 
868         /* Synchronize 'playing' pixbuf in playlist */
869         ario_playlist_sync_song ();
870 }
871 
872 static void
ario_playlist_connectivity_changed_cb(ArioServer * server,ArioPlaylist * playlist)873 ario_playlist_connectivity_changed_cb (ArioServer *server,
874                                        ArioPlaylist *playlist)
875 {
876         ARIO_LOG_FUNCTION_START;
877         if (!ario_server_is_connected ())
878                 ario_playlist_changed_cb (server, playlist);
879 }
880 
881 static void
ario_playlist_song_changed_cb(ArioServer * server,ArioPlaylist * playlist)882 ario_playlist_song_changed_cb (ArioServer *server,
883                                ArioPlaylist *playlist)
884 {
885         ARIO_LOG_FUNCTION_START;
886         ario_playlist_sync_song ();
887 
888         /* Autoscroll in playlist on song chang if option is activated */
889         if (ario_conf_get_boolean (PREF_PLAYLIST_AUTOSCROLL, PREF_PLAYLIST_AUTOSCROLL_DEFAULT))
890                 ario_playlist_cmd_goto_playing_song (NULL, NULL, playlist);
891 }
892 
893 static void
ario_playlist_state_changed_cb(ArioServer * server,ArioPlaylist * playlist)894 ario_playlist_state_changed_cb (ArioServer *server,
895                                 ArioPlaylist *playlist)
896 {
897         ARIO_LOG_FUNCTION_START;
898         static gboolean first_run = TRUE;
899 
900         /* Synchronise song information */
901         ario_playlist_sync_song ();
902 
903         /* Set focus to playlist at first start */
904         if (first_run
905             && ario_server_is_connected()) {
906                 gtk_widget_grab_focus (playlist->priv->tree);
907                 first_run = FALSE;
908         }
909 }
910 
911 static void
ario_playlist_sort_changed_cb(GtkTreeSortable * treesortable,ArioPlaylist * playlist)912 ario_playlist_sort_changed_cb (GtkTreeSortable *treesortable,
913                                ArioPlaylist *playlist)
914 {
915         ARIO_LOG_FUNCTION_START;
916         g_signal_connect (playlist->priv->model,
917                           "rows-reordered",
918                           G_CALLBACK (ario_playlist_rows_reordered_cb),
919                           playlist);
920 }
921 
922 static gboolean
ario_playlist_rows_reordered_foreach(GtkTreeModel * model,GtkTreePath * p,GtkTreeIter * iter,GSList ** ids)923 ario_playlist_rows_reordered_foreach (GtkTreeModel *model,
924                                       GtkTreePath *p,
925                                       GtkTreeIter *iter,
926                                       GSList **ids)
927 {
928         gint *id;
929 
930         id = g_malloc (sizeof (gint));
931         gtk_tree_model_get (model, iter, ID_COLUMN, id, -1);
932 
933         *ids = g_slist_append (*ids, id);
934 
935         return FALSE;
936 }
937 
938 static void
ario_playlist_rows_reordered_cb(GtkTreeModel * tree_model,GtkTreePath * path,GtkTreeIter * iter,gpointer arg3,ArioPlaylist * playlist)939 ario_playlist_rows_reordered_cb (GtkTreeModel *tree_model,
940                                  GtkTreePath *path,
941                                  GtkTreeIter *iter,
942                                  gpointer arg3,
943                                  ArioPlaylist *playlist)
944 {
945         ARIO_LOG_FUNCTION_START;
946         GSList *tmp, *ids = NULL;
947         int i = 0;
948 
949         g_signal_handlers_disconnect_by_func (G_OBJECT (playlist->priv->model),
950                                               G_CALLBACK (ario_playlist_rows_reordered_cb),
951                                               playlist);
952 
953         /* Get the list of songs ids in the playlist */
954         gtk_tree_model_foreach (GTK_TREE_MODEL (playlist->priv->model),
955                                 (GtkTreeModelForeachFunc) ario_playlist_rows_reordered_foreach,
956                                 &ids);
957 
958         /* Move songs on server according to the order in the playlist */
959         for (tmp = ids; tmp; tmp = g_slist_next (tmp)) {
960                 ario_server_queue_moveid (*((gint *)tmp->data), i);
961                 ++i;
962         }
963 
964         /* Force a full synchronization of playlist in next update */
965         playlist->priv->playlist_id = -1;
966 
967         /* Commit songs moves */
968         ario_server_queue_commit ();
969 
970         g_slist_foreach (ids, (GFunc) g_free, NULL);
971         g_slist_free (ids);
972 
973         g_signal_handlers_block_by_func (playlist->priv->model,
974                                          G_CALLBACK (ario_playlist_sort_changed_cb),
975                                          playlist);
976         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (playlist->priv->model),
977                                               GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
978                                               GTK_SORT_ASCENDING);
979         g_signal_handlers_unblock_by_func (playlist->priv->model,
980                                            G_CALLBACK (ario_playlist_sort_changed_cb),
981                                            playlist);
982 }
983 
984 GtkWidget *
ario_playlist_new(void)985 ario_playlist_new (void)
986 {
987         ARIO_LOG_FUNCTION_START;
988         ArioServer *server = ario_server_get_instance ();
989 
990         g_return_val_if_fail (instance == NULL, NULL);
991 
992         instance = g_object_new (TYPE_ARIO_PLAYLIST,
993                                  NULL);
994 
995         g_return_val_if_fail (instance->priv != NULL, NULL);
996 
997         /* Connect signals to remain synchronized with music server */
998         g_signal_connect_object (server,
999                                  "playlist_changed",
1000                                  G_CALLBACK (ario_playlist_changed_cb),
1001                                  instance, 0);
1002         g_signal_connect_object (server,
1003                                  "song_changed",
1004                                  G_CALLBACK (ario_playlist_song_changed_cb),
1005                                  instance, 0);
1006         g_signal_connect_object (server,
1007                                  "state_changed",
1008                                  G_CALLBACK (ario_playlist_state_changed_cb),
1009                                  instance, 0);
1010         g_signal_connect_object (server,
1011                                  "connectivity_changed",
1012                                  G_CALLBACK (ario_playlist_connectivity_changed_cb),
1013                                  instance, 0);
1014 
1015         /* Add contextual menu actions */
1016         g_action_map_add_action_entries (G_ACTION_MAP (g_application_get_default ()),
1017                                          widget_actions,
1018                                          G_N_ELEMENTS (widget_actions),
1019                                          instance);
1020 
1021         return GTK_WIDGET (instance);
1022 }
1023 
1024 static int
ario_playlist_get_indice(GtkTreePath * path)1025 ario_playlist_get_indice (GtkTreePath *path)
1026 {
1027         ARIO_LOG_FUNCTION_START;
1028         GtkTreePath *parent_path = NULL;
1029         int *indices = NULL;
1030         int indice = -1;
1031 
1032         /* Get indice from a path depending on if search is activated or not */
1033         if (instance->priv->in_search) {
1034                 parent_path = gtk_tree_model_filter_convert_path_to_child_path (GTK_TREE_MODEL_FILTER (instance->priv->filter), path);
1035                 if (parent_path)
1036                         indices = gtk_tree_path_get_indices (parent_path);
1037         } else {
1038                 indices = gtk_tree_path_get_indices (path);
1039         }
1040 
1041         if (indices)
1042                 indice = indices[0];
1043 
1044         if (parent_path)
1045                 gtk_tree_path_free (parent_path);
1046 
1047         return indice;
1048 }
1049 
1050 static void
ario_playlist_activate_selected(void)1051 ario_playlist_activate_selected (void)
1052 {
1053         ARIO_LOG_FUNCTION_START;
1054         GList *paths;
1055         GtkTreePath *path = NULL;
1056         GtkTreeModel *model = GTK_TREE_MODEL (instance->priv->model);
1057 
1058         /* Start playing a song when a row is activated */
1059         paths = gtk_tree_selection_get_selected_rows (instance->priv->selection, &model);
1060         if (paths)
1061                 path = paths->data;
1062         if (path)
1063                 ario_server_do_play_pos (ario_playlist_get_indice (path));
1064 
1065         g_list_foreach (paths, (GFunc) gtk_tree_path_free, NULL);
1066         g_list_free (paths);
1067 }
1068 
1069 static void
ario_playlist_move_rows(const int x,const int y)1070 ario_playlist_move_rows (const int x, const int y)
1071 {
1072         ARIO_LOG_FUNCTION_START;
1073         GtkTreePath *path = NULL;
1074         GtkTreeViewDropPosition drop_pos;
1075         gint pos;
1076         gint *indice;
1077         GList *list;
1078         int offset = 0;
1079         GtkTreePath *path_to_select;
1080         GtkTreeModel *model = GTK_TREE_MODEL (instance->priv->model);
1081 
1082         /* Get drop location */
1083         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (instance->priv->tree), x, y, &path, &drop_pos);
1084 
1085         if (path == NULL) {
1086                 pos = instance->priv->playlist_length;
1087         } else {
1088                 indice = gtk_tree_path_get_indices (path);
1089                 pos = indice[0];
1090 
1091                 /* Adjust position acording to drop after */
1092                 if ((drop_pos == GTK_TREE_VIEW_DROP_AFTER
1093                      || drop_pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER)
1094                     && pos < instance->priv->playlist_length)
1095                         ++pos;
1096 
1097                 gtk_tree_path_free (path);
1098         }
1099 
1100         /* Get all selected rows */
1101         list = gtk_tree_selection_get_selected_rows (instance->priv->selection, &model);
1102         if (!list)
1103                 return;
1104 
1105         /* Unselect all rows */
1106         gtk_tree_selection_unselect_all (instance->priv->selection);
1107 
1108         /* For each selected row (starting from the end) */
1109         for (list = g_list_last (list); list; list = g_list_previous (list)) {
1110                 /* Get start pos */
1111                 indice = gtk_tree_path_get_indices ((GtkTreePath *) list->data);
1112 
1113                 /* Compensate */
1114                 if (pos > indice[0])
1115                         --pos;
1116 
1117                 /* Move the song */
1118                 ario_server_queue_move (indice[0] + offset, pos);
1119 
1120                 /* Adjust offset to take the move into account */
1121                 if (pos < indice[0])
1122                         ++offset;
1123 
1124                 path_to_select = gtk_tree_path_new_from_indices (pos, -1);
1125                 gtk_tree_selection_select_path (instance->priv->selection, path_to_select);
1126                 gtk_tree_path_free (path_to_select);
1127         }
1128 
1129         g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
1130         g_list_free (list);
1131 
1132         /* Commit queue moves */
1133         ario_server_queue_commit ();
1134 }
1135 
1136 static void
ario_playlist_copy_rows(const int x,const int y)1137 ario_playlist_copy_rows (const int x, const int y)
1138 {
1139         ARIO_LOG_FUNCTION_START;
1140         GtkTreePath *path = NULL;
1141         GtkTreeViewDropPosition drop_pos;
1142         gint pos;
1143         gint *indice;
1144         GList *list;
1145         GSList *songs = NULL;
1146         GtkTreeModel *model = GTK_TREE_MODEL (instance->priv->model);
1147         GtkTreeIter iter;
1148         gchar *filename;
1149 
1150         /* Get drop location */
1151         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (instance->priv->tree), x, y, &path, &drop_pos);
1152         if (path == NULL) {
1153                 pos = instance->priv->playlist_length;
1154         } else {
1155                 indice = gtk_tree_path_get_indices (path);
1156                 pos = indice[0];
1157 
1158                 /* Adjust position acording to drop after */
1159                 if ((drop_pos == GTK_TREE_VIEW_DROP_AFTER
1160                      || drop_pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER)
1161                     && pos < instance->priv->playlist_length)
1162                         ++pos;
1163 
1164                 gtk_tree_path_free (path);
1165         }
1166 
1167         /* Get all selected rows */
1168         list = gtk_tree_selection_get_selected_rows (instance->priv->selection, &model);
1169 
1170         /* Unselect all rows */
1171         gtk_tree_selection_unselect_all (instance->priv->selection);
1172 
1173         /* For each selected row (starting from the end) */
1174         for (; list; list = g_list_next (list)) {
1175                 /* Get start pos */
1176                 gtk_tree_model_get_iter (model, &iter, (GtkTreePath *) list->data);
1177                 gtk_tree_model_get (model, &iter, FILE_COLUMN, &filename, -1);
1178                 songs = g_slist_append (songs, filename);
1179         }
1180         /* Insert songs in playlist */
1181         ario_server_insert_at (songs, pos - 1);
1182 
1183         g_slist_foreach (songs, (GFunc) g_free, NULL);
1184         g_slist_free (songs);
1185 
1186         g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
1187         g_list_free (list);
1188 }
1189 
1190 static gint
ario_playlist_get_drop_position(const int x,const int y)1191 ario_playlist_get_drop_position (const int x,
1192                                  const int y)
1193 {
1194         ARIO_LOG_FUNCTION_START;
1195         GtkTreeViewDropPosition pos;
1196         gint *indice;
1197         GtkTreePath *path = NULL;
1198         gint drop = 1;
1199 
1200         if (x < 0 || y < 0)
1201                 return -1;
1202 
1203         /* Get drop location */
1204         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (instance->priv->tree), x, y, &path, &pos);
1205 
1206         if (!path)
1207                 return -1;
1208 
1209         /* Grab drop localtion */
1210         indice = gtk_tree_path_get_indices (path);
1211         drop = indice[0];
1212         /* Adjust position */
1213         if ((pos == GTK_TREE_VIEW_DROP_BEFORE
1214              || pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)
1215             && drop > 0)
1216                 --drop;
1217         gtk_tree_path_free (path);
1218 
1219         return drop;
1220 }
1221 
1222 static void
ario_playlist_drop_songs(const int x,const int y,GtkSelectionData * data)1223 ario_playlist_drop_songs (const int x, const int y,
1224                           GtkSelectionData *data)
1225 {
1226         ARIO_LOG_FUNCTION_START;
1227         gchar **songs;
1228         GSList *filenames = NULL;
1229         int i;
1230 
1231         songs = g_strsplit ((const gchar *) gtk_selection_data_get_data (data), "\n", 0);
1232 
1233         /* Get a list of filenames */
1234         for (i=0; songs[i]!=NULL && g_utf8_collate (songs[i], ""); ++i)
1235                 filenames = g_slist_append (filenames, songs[i]);
1236 
1237         /* Add songs to the playlist */
1238         ario_server_playlist_add_songs (filenames,
1239                                         ario_playlist_get_drop_position (x, y),
1240                                         PLAYLIST_ADD);
1241 
1242         g_strfreev (songs);
1243         g_slist_free (filenames);
1244 }
1245 
1246 static void
ario_playlist_drop_dir(const int x,const int y,GtkSelectionData * data)1247 ario_playlist_drop_dir (const int x, const int y,
1248                         GtkSelectionData *data)
1249 {
1250         ARIO_LOG_FUNCTION_START;
1251         const gchar *dir = (const gchar *) gtk_selection_data_get_data (data);
1252 
1253         /* Add a whole directory to the playlist */
1254         ario_server_playlist_add_dir (dir,
1255                                       ario_playlist_get_drop_position (x, y),
1256                                       PLAYLIST_ADD);
1257 }
1258 
1259 static void
ario_playlist_drop_criterias(const int x,const int y,GtkSelectionData * data)1260 ario_playlist_drop_criterias (const int x, const int y,
1261                               GtkSelectionData *data)
1262 {
1263         ARIO_LOG_FUNCTION_START;
1264         gchar **criterias_str;
1265         ArioServerCriteria *criteria;
1266         ArioServerAtomicCriteria *atomic_criteria;
1267         int i = 0, j;
1268         int nb;
1269         GSList *criterias = NULL;
1270 
1271         /* Get a list of criteria from drag data */
1272         criterias_str = g_strsplit ((const gchar *) gtk_selection_data_get_data (data), "\n", 0);
1273         while (criterias_str[i] && *criterias_str[i] != '\0') {
1274                 nb = atoi (criterias_str[i]);
1275                 criteria = NULL;
1276 
1277                 for (j=0; j<nb; ++j) {
1278                         atomic_criteria = (ArioServerAtomicCriteria *) g_malloc0 (sizeof (ArioServerAtomicCriteria));
1279                         atomic_criteria->tag = atoi (criterias_str[i+2*j+1]);
1280                         atomic_criteria->value = g_strdup (criterias_str[i+2*j+2]);
1281                         criteria = g_slist_append (criteria, atomic_criteria);
1282                 }
1283                 i += 2*nb + 1;
1284 
1285                 criterias = g_slist_append (criterias, criteria);
1286         }
1287         g_strfreev (criterias_str);
1288 
1289         /* Add all songs matching the list of criteria to the playlist */
1290         ario_server_playlist_add_criterias (criterias,
1291                                             ario_playlist_get_drop_position (x, y),
1292                                             PLAYLIST_ADD, -1);
1293 
1294         g_slist_foreach (criterias, (GFunc) ario_server_criteria_free, NULL);
1295         g_slist_free (criterias);
1296 }
1297 
1298 static void
ario_playlist_drag_leave_cb(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * data,guint info,guint time,gpointer user_data)1299 ario_playlist_drag_leave_cb (GtkWidget *widget,
1300                              GdkDragContext *context,
1301                              gint x, gint y,
1302                              GtkSelectionData *data,
1303                              guint info,
1304                              guint time,
1305                              gpointer user_data)
1306 {
1307         ARIO_LOG_FUNCTION_START;
1308 
1309         /* Call the appropriate functions depending on data type */
1310         if (gtk_selection_data_get_data_type (data) == gdk_atom_intern ("text/internal-list", TRUE)) {
1311 #if GTK_CHECK_VERSION(2, 22, 0)
1312                 if (gdk_drag_context_get_selected_action (context) & GDK_ACTION_COPY)
1313 #else
1314                         if (context->action & GDK_ACTION_COPY)
1315 #endif
1316                                 ario_playlist_copy_rows (x, y);
1317                         else
1318                                 ario_playlist_move_rows (x, y);
1319         } else if (gtk_selection_data_get_data_type (data) == gdk_atom_intern ("text/songs-list", TRUE)) {
1320                 ario_playlist_drop_songs (x, y, data);
1321         } else if (gtk_selection_data_get_data_type (data) == gdk_atom_intern ("text/radios-list", TRUE)) {
1322                 ario_playlist_drop_songs (x, y, data);
1323         } else if (gtk_selection_data_get_data_type (data) == gdk_atom_intern ("text/directory", TRUE)) {
1324                 ario_playlist_drop_dir (x, y, data);
1325         } else if (gtk_selection_data_get_data_type (data) == gdk_atom_intern ("text/criterias-list", TRUE)) {
1326                 ario_playlist_drop_criterias (x, y, data);
1327         }
1328 
1329         /* Finish the drag */
1330         gtk_drag_finish (context, TRUE, FALSE, time);
1331 }
1332 
1333 static void
ario_playlist_drag_data_get_cb(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint info,guint time,gpointer data)1334 ario_playlist_drag_data_get_cb (GtkWidget * widget,
1335                                 GdkDragContext * context,
1336                                 GtkSelectionData * selection_data,
1337                                 guint info, guint time, gpointer data)
1338 {
1339         ARIO_LOG_FUNCTION_START;
1340 
1341         g_return_if_fail (selection_data != NULL);
1342 
1343         gtk_selection_data_set (selection_data, gtk_selection_data_get_target (selection_data), 8,
1344                                 NULL, 0);
1345 }
1346 
1347 static gboolean
ario_playlist_drag_drop_cb(GtkWidget * widget,gint x,gint y,guint time,ArioPlaylist * playlist)1348 ario_playlist_drag_drop_cb (GtkWidget * widget,
1349                             gint x, gint y,
1350                             guint time,
1351                             ArioPlaylist *playlist)
1352 {
1353         ARIO_LOG_FUNCTION_START;
1354 
1355         /* Drag and drop is deactivated when search is activated */
1356         if (instance->priv->in_search)
1357                 g_signal_stop_emission_by_name (widget, "drag_drop");
1358 
1359         return FALSE;
1360 }
1361 
1362 static void
ario_playlist_popup_menu_cb(ArioDndTree * tree,ArioPlaylist * playlist)1363 ario_playlist_popup_menu_cb (ArioDndTree* tree,
1364                              ArioPlaylist *playlist)
1365 {
1366         ARIO_LOG_FUNCTION_START;
1367         /* Show popup menu */
1368         gtk_menu_popup_at_pointer (GTK_MENU (playlist->priv->menu), NULL);
1369 }
1370 
1371 static void
ario_playlist_activate_cb(ArioDndTree * tree,ArioPlaylist * playlist)1372 ario_playlist_activate_cb (ArioDndTree* tree,
1373                            ArioPlaylist *playlist)
1374 {
1375         ARIO_LOG_FUNCTION_START;
1376         ario_playlist_activate_selected ();
1377 }
1378 
1379 static void
ario_playlist_cmd_clear(GSimpleAction * action,GVariant * parameter,gpointer data)1380 ario_playlist_cmd_clear (GSimpleAction *action,
1381                          GVariant *parameter,
1382                          gpointer data)
1383 {
1384         ARIO_LOG_FUNCTION_START;
1385         /* Clear playlist */
1386         ario_server_clear ();
1387 }
1388 
1389 static void
ario_playlist_cmd_shuffle(GSimpleAction * action,GVariant * parameter,gpointer data)1390 ario_playlist_cmd_shuffle (GSimpleAction *action,
1391                            GVariant *parameter,
1392                            gpointer data)
1393 {
1394         ARIO_LOG_FUNCTION_START;
1395         /* Shuffle playlist */
1396         ario_server_shuffle ();
1397 }
1398 
1399 static void
ario_playlist_selection_remove_foreach(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,guint * deleted)1400 ario_playlist_selection_remove_foreach (GtkTreeModel *model,
1401                                         GtkTreePath *path,
1402                                         GtkTreeIter *iter,
1403                                         guint *deleted)
1404 {
1405         ARIO_LOG_FUNCTION_START;
1406         gint indice = ario_playlist_get_indice (path);
1407 
1408         if (indice >= 0) {
1409                 /* Remove song from playlist on server */
1410                 ario_server_queue_delete_pos (indice - *deleted);
1411                 ++(*deleted);
1412         }
1413 }
1414 
1415 static void
ario_playlist_remove(void)1416 ario_playlist_remove (void)
1417 {
1418         ARIO_LOG_FUNCTION_START;
1419         guint deleted = 0;
1420 
1421         /* Delete every selected song */
1422         gtk_tree_selection_selected_foreach (instance->priv->selection,
1423                                              (GtkTreeSelectionForeachFunc) ario_playlist_selection_remove_foreach,
1424                                              &deleted);
1425 
1426         /* Commit song deletions */
1427         ario_server_queue_commit ();
1428 
1429         /* Unselect all rows */
1430         gtk_tree_selection_unselect_all (instance->priv->selection);
1431 }
1432 
1433 static void
ario_playlist_cmd_remove(GSimpleAction * action,GVariant * parameter,gpointer data)1434 ario_playlist_cmd_remove (GSimpleAction *action,
1435                           GVariant *parameter,
1436                           gpointer data)
1437 {
1438         ARIO_LOG_FUNCTION_START;
1439         ario_playlist_remove ();
1440 }
1441 
1442 typedef struct ArioPlaylistCropData
1443 {
1444         gint kept;
1445         gint deleted;
1446 } ArioPlaylistCropData;
1447 
1448 static void
ario_playlist_selection_crop_foreach(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,ArioPlaylistCropData * data)1449 ario_playlist_selection_crop_foreach (GtkTreeModel *model,
1450                                       GtkTreePath *path,
1451                                       GtkTreeIter *iter,
1452                                       ArioPlaylistCropData *data)
1453 {
1454         ARIO_LOG_FUNCTION_START;
1455         gint indice = ario_playlist_get_indice (path);
1456 
1457         if (indice >= 0) {
1458                 /* Remove all rows between the last kept row and the current row */
1459                 while (data->deleted + data->kept < indice) {
1460                         ario_server_queue_delete_pos (data->kept);
1461                         ++data->deleted;
1462                 }
1463 
1464                 /* Keep the current row */
1465                 ++data->kept;
1466         }
1467 }
1468 
1469 static void
ario_playlist_cmd_crop(GSimpleAction * action,GVariant * parameter,gpointer data)1470 ario_playlist_cmd_crop (GSimpleAction *action,
1471                         GVariant *parameter,
1472                         gpointer data)
1473 {
1474         ARIO_LOG_FUNCTION_START;
1475         ArioPlaylistCropData crop_data;
1476 
1477         crop_data.kept = 0;
1478         crop_data.deleted = 0;
1479 
1480         /* Call ario_playlist_selection_crop_foreach, for each selected row */
1481         gtk_tree_selection_selected_foreach (instance->priv->selection,
1482                                              (GtkTreeSelectionForeachFunc) ario_playlist_selection_crop_foreach,
1483                                              &crop_data);
1484 
1485         /* Delete all songs after the last selected one */
1486         while (crop_data.deleted + crop_data.kept < instance->priv->playlist_length) {
1487                 ario_server_queue_delete_pos (crop_data.kept);
1488                 ++crop_data.deleted;
1489         }
1490 
1491         /* Commit song deletions */
1492         ario_server_queue_commit ();
1493 
1494         /* Unselect all rows */
1495         gtk_tree_selection_unselect_all (instance->priv->selection);
1496 }
1497 
1498 static void
ario_playlist_search(ArioPlaylist * playlist,const char * text)1499 ario_playlist_search (ArioPlaylist *playlist,
1500                       const char* text)
1501 {
1502         ARIO_LOG_FUNCTION_START;
1503         if (!playlist->priv->in_search) {
1504                 /* Show search box */
1505                 gtk_widget_show (playlist->priv->search_hbox);
1506                 gtk_widget_grab_focus (playlist->priv->search_entry);
1507                 gtk_entry_set_text (GTK_ENTRY (playlist->priv->search_entry), text);
1508                 gtk_editable_set_position (GTK_EDITABLE (playlist->priv->search_entry), -1);
1509                 gtk_tree_view_set_model (GTK_TREE_VIEW (playlist->priv->tree),
1510                                          GTK_TREE_MODEL (playlist->priv->filter));
1511                 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW (playlist->priv->tree), FALSE);
1512                 playlist->priv->in_search = TRUE;
1513 
1514                 /* Handles drag & drop differently while filters are activated */
1515                 playlist->priv->dnd_handler = g_signal_connect (playlist->priv->tree,
1516                                                                 "drag_drop",
1517                                                                 G_CALLBACK (ario_playlist_drag_drop_cb),
1518                                                                 playlist);
1519         }
1520 }
1521 
1522 static void
ario_playlist_cmd_search(GSimpleAction * action,GVariant * parameter,gpointer data)1523 ario_playlist_cmd_search (GSimpleAction *action,
1524                           GVariant *parameter,
1525                           gpointer data)
1526 {
1527         ARIO_LOG_FUNCTION_START;
1528         ArioPlaylist *playlist = ARIO_PLAYLIST (data);
1529         ario_playlist_search (playlist, "");
1530         gtk_tree_model_filter_refilter (playlist->priv->filter);
1531 }
1532 
1533 static void
ario_playlist_selection_properties_foreach(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer userdata)1534 ario_playlist_selection_properties_foreach (GtkTreeModel *model,
1535                                             GtkTreePath *path,
1536                                             GtkTreeIter *iter,
1537                                             gpointer userdata)
1538 {
1539         ARIO_LOG_FUNCTION_START;
1540         GSList **paths = (GSList **) userdata;
1541         gchar *val = NULL;
1542 
1543         gtk_tree_model_get (model, iter, FILE_COLUMN, &val, -1);
1544 
1545         *paths = g_slist_append (*paths, val);
1546 }
1547 
1548 static void
ario_playlist_cmd_songs_properties(GSimpleAction * action,GVariant * parameter,gpointer data)1549 ario_playlist_cmd_songs_properties (GSimpleAction *action,
1550                                     GVariant *parameter,
1551                                     gpointer data)
1552 {
1553         ARIO_LOG_FUNCTION_START;
1554         ArioPlaylist *playlist = ARIO_PLAYLIST (data);
1555         GSList *paths = NULL;
1556         GtkWidget *songinfos;
1557 
1558         /* Get a list of selected song paths */
1559         gtk_tree_selection_selected_foreach (playlist->priv->selection,
1560                                              ario_playlist_selection_properties_foreach,
1561                                              &paths);
1562 
1563         /* Show the songinfos dialog for all selected songs */
1564         if (paths) {
1565                 songinfos = ario_shell_songinfos_new (paths);
1566                 if (songinfos)
1567                         gtk_widget_show_all (songinfos);
1568 
1569                 g_slist_foreach (paths, (GFunc) g_free, NULL);
1570                 g_slist_free (paths);
1571         }
1572 }
1573 
1574 static gboolean
ario_playlist_cmd_goto_playing_song_foreach(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,ArioPlaylist * playlist)1575 ario_playlist_cmd_goto_playing_song_foreach (GtkTreeModel *model,
1576                                              GtkTreePath *path,
1577                                              GtkTreeIter *iter,
1578                                              ArioPlaylist *playlist)
1579 {
1580         int song_id;
1581 
1582         gtk_tree_model_get (model, iter, ID_COLUMN, &song_id, -1);
1583 
1584         /* The song is the currently playing song */
1585         if (song_id == ario_server_get_current_song_id ()) {
1586                 /* Scroll to current song */
1587                 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (playlist->priv->tree),
1588                                               path,
1589                                               NULL,
1590                                               TRUE,
1591                                               0.5, 0);
1592                 /* Select current song */
1593                 gtk_tree_view_set_cursor (GTK_TREE_VIEW (playlist->priv->tree),
1594                                           path,
1595                                           NULL, FALSE);
1596                 return TRUE;
1597         }
1598 
1599         return FALSE;
1600 }
1601 
1602 static void
ario_playlist_cmd_goto_playing_song(GSimpleAction * action,GVariant * parameter,gpointer data)1603 ario_playlist_cmd_goto_playing_song (GSimpleAction *action,
1604                                      GVariant *parameter,
1605                                      gpointer data)
1606 {
1607         ARIO_LOG_FUNCTION_START;
1608         ArioPlaylist *playlist = ARIO_PLAYLIST (data);
1609 
1610         /* Call ario_playlist_cmd_goto_playing_song_foreach for each song in playlist */
1611         if (instance->priv->in_search) {
1612                 gtk_tree_model_foreach (GTK_TREE_MODEL (playlist->priv->filter),
1613                                         (GtkTreeModelForeachFunc) ario_playlist_cmd_goto_playing_song_foreach,
1614                                         playlist);
1615         } else {
1616                 gtk_tree_model_foreach (GTK_TREE_MODEL (playlist->priv->model),
1617                                         (GtkTreeModelForeachFunc) ario_playlist_cmd_goto_playing_song_foreach,
1618                                         playlist);
1619         }
1620 
1621         /* Also go to the playing song in the souces */
1622         ario_source_manager_goto_playling_song ();
1623 }
1624 
1625 void
ario_playlist_cmd_save(GSimpleAction * action,GVariant * parameter,gpointer data)1626 ario_playlist_cmd_save (GSimpleAction *action,
1627                         GVariant *parameter,
1628                         gpointer data)
1629 {
1630         ARIO_LOG_FUNCTION_START;
1631         GtkWidget *dialog;
1632         GtkWidget *hbox;
1633         GtkWidget *label, *entry;
1634         gint retval = GTK_RESPONSE_CANCEL;
1635         gchar *name;
1636 
1637         /* Create the widgets */
1638         dialog = gtk_dialog_new_with_buttons (_("Save playlist"),
1639                                               NULL,
1640                                               GTK_DIALOG_DESTROY_WITH_PARENT,
1641                                               _("_Cancel"),
1642                                               GTK_RESPONSE_CANCEL,
1643                                               _("_OK"),
1644                                               GTK_RESPONSE_OK,
1645                                               NULL);
1646         gtk_dialog_set_default_response (GTK_DIALOG (dialog),
1647                                          GTK_RESPONSE_OK);
1648         label = gtk_label_new (_("Playlist name :"));
1649         entry = gtk_entry_new ();
1650         hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5);
1651 
1652         gtk_box_pack_start (GTK_BOX (hbox),
1653                             label,
1654                             TRUE, TRUE,
1655                             0);
1656         gtk_box_pack_start (GTK_BOX (hbox),
1657                             entry,
1658                             TRUE, TRUE,
1659                             0);
1660         gtk_container_set_border_width (GTK_CONTAINER (hbox), 10);
1661         gtk_box_set_spacing (GTK_BOX (hbox), 4);
1662         gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
1663                            hbox);
1664         gtk_widget_show_all (dialog);
1665 
1666         /* Launch dialog */
1667         retval = gtk_dialog_run (GTK_DIALOG(dialog));
1668         if (retval != GTK_RESPONSE_OK) {
1669                 gtk_widget_destroy (dialog);
1670                 return;
1671         }
1672 
1673         /* Get playlist name */
1674         name = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry)));
1675         gtk_widget_destroy (dialog);
1676 
1677         /* Save playlist */
1678         if (ario_server_save_playlist (name)) {
1679                 /* Playlist exists: ask for a confirmation before replacement */
1680                 dialog = gtk_message_dialog_new (NULL,
1681                                                  GTK_DIALOG_MODAL,
1682                                                  GTK_MESSAGE_QUESTION,
1683                                                  GTK_BUTTONS_YES_NO,
1684                                                  _("Playlist already exists. Do you want to overwrite it?"));
1685 
1686                 retval = gtk_dialog_run (GTK_DIALOG (dialog));
1687                 gtk_widget_destroy (dialog);
1688                 if (retval == GTK_RESPONSE_YES) {
1689                         ario_server_delete_playlist (name);
1690                         ario_server_save_playlist (name);
1691                 }
1692         }
1693         g_free (name);
1694 }
1695 
1696 static gboolean
ario_playlist_view_key_press_cb(GtkWidget * widget,GdkEventKey * event,ArioPlaylist * playlist)1697 ario_playlist_view_key_press_cb (GtkWidget *widget,
1698                                  GdkEventKey *event,
1699                                  ArioPlaylist *playlist)
1700 {
1701         ARIO_LOG_FUNCTION_START;
1702 
1703         if (event->keyval == GDK_KEY_Delete) {
1704                 /* Del key removes songs from playlist */
1705                 ario_playlist_remove ();
1706         } else if (event->keyval == GDK_KEY_Return) {
1707                 /* Enter key activate a song */
1708                 ario_playlist_activate_selected ();
1709                 return TRUE;
1710         } else if (event->string
1711                    && event->length > 0
1712                    && event->keyval != GDK_KEY_Escape
1713                    && !(event->state & GDK_CONTROL_MASK)) {
1714                 /* Other keys start the search in playlist */
1715                 ario_playlist_search (playlist, event->string);
1716         }
1717 
1718         return FALSE;
1719 }
1720 
1721 static void
ario_playlist_column_visible_changed_cb(guint notification_id,ArioPlaylistColumn * ario_column)1722 ario_playlist_column_visible_changed_cb (guint notification_id,
1723                                          ArioPlaylistColumn *ario_column)
1724 {
1725         ARIO_LOG_FUNCTION_START;
1726         /* Called when user changes column visibility in preferences */
1727         gtk_tree_view_column_set_visible (ario_column->column,
1728                                           ario_conf_get_integer (ario_column->pref_is_visible, ario_column->default_is_visible));
1729 }
1730 
1731 static gboolean
ario_playlist_get_total_time_foreach(GtkTreeModel * model,GtkTreePath * p,GtkTreeIter * iter,int * total_time)1732 ario_playlist_get_total_time_foreach (GtkTreeModel *model,
1733                                       GtkTreePath *p,
1734                                       GtkTreeIter *iter,
1735                                       int *total_time)
1736 {
1737         gint time;
1738 
1739         gtk_tree_model_get (model, iter, TIME_COLUMN, &time, -1);
1740         *total_time += time;
1741 
1742         return FALSE;
1743 }
1744 
1745 gint
ario_playlist_get_total_time(void)1746 ario_playlist_get_total_time (void)
1747 {
1748         ARIO_LOG_FUNCTION_START;
1749         int total_time = 0;
1750 
1751         /* Compute total time by adding all song times */
1752         gtk_tree_model_foreach (GTK_TREE_MODEL (instance->priv->model),
1753                                 (GtkTreeModelForeachFunc) ario_playlist_get_total_time_foreach,
1754                                 &total_time);
1755 
1756         return total_time;
1757 }
1758 
1759