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