1 /*
2  * * Copyright (C) 2009-2011 Ali <aliov@xfce.org>
3  * * Copyright (C) 2012-2017 Simon Steinbeiß <ochosi@xfce.org>
4  * * Copyright (C) 2012-2020 Sean Davis <bluesabre@xfce.org>
5  *
6  * Licensed under the GNU General Public License Version 2
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26 
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 
31 #include <unistd.h>
32 
33 #include <gtk/gtk.h>
34 #include <gdk/gdkkeysyms.h>
35 #include <glib.h>
36 #include <glib/gstdio.h>
37 #include <gio/gio.h>
38 
39 #include <libxfce4util/libxfce4util.h>
40 
41 #include "data/interfaces/playlist_ui.h"
42 #include "data/interfaces/save-playlist_ui.h"
43 
44 #include "src/common/parole-common.h"
45 
46 #include "src/dbus/parole-dbus.h"
47 
48 #include "src/misc/parole-file.h"
49 #include "src/misc/parole-filters.h"
50 #include "src/misc/parole-pl-parser.h"
51 
52 #include "src/parole-builder.h"
53 #include "src/parole-conf.h"
54 #include "src/parole-mediachooser.h"
55 #include "src/parole-open-location.h"
56 #include "src/parole-player.h"
57 #include "src/parole-utils.h"
58 
59 #include "src/parole-medialist.h"
60 
61 
62 #define PAROLE_AUTO_SAVED_PLAYLIST  "xfce4/parole/auto-saved-playlist.m3u"
63 
64 typedef struct {
65     GtkWidget *chooser;
66     GtkWidget *combo;
67     ParoleMediaList *list;
68     gboolean closing;
69 } ParolePlaylistSave;
70 
71 /* Playlist filetypes */
72 static struct {
73     ParolePlFormat format;
74     gchar * ext;
75 } playlist_format_map[] = {
76     { PAROLE_PL_FORMAT_UNKNOWN, ""      },
77     { PAROLE_PL_FORMAT_M3U,     ".m3u"  },
78     { PAROLE_PL_FORMAT_PLS,     ".pls"  },
79     { PAROLE_PL_FORMAT_ASX,     ".asx"  },
80     { PAROLE_PL_FORMAT_XSPF,    ".xspf" }
81 };
82 
83 static GtkTargetEntry target_entry[] = {
84     { "STRING",        0, 0 },
85     { "text/uri-list", 0, 1 },
86 };
87 
88 ParoleMediaList *media_list = NULL;
89 
90 static void     parole_media_list_dbus_class_init(ParoleMediaListClass *klass);
91 static void     parole_media_list_dbus_init(ParoleMediaList      *list);
92 
93 static GtkTreeRowReference *
94 parole_media_list_get_row_reference_from_iter(ParoleMediaList *list,
95                                                      GtkTreeIter *iter,
96                                                      gboolean select_path);
97 
98 static void     parole_media_list_select_path(ParoleMediaList *list,
99                                                      gboolean disc,
100                                                      GtkTreePath *path);
101 
102 /*
103  * Callbacks for GtkBuilder
104  */
105 void        parole_media_list_add_clicked_cb(GtkButton *button,
106                                                      ParoleMediaList *list);
107 
108 void        parole_media_list_remove_clicked_cb(GtkButton *button,
109                                                      ParoleMediaList *list);
110 
111 void        parole_media_list_clear_clicked_cb(GtkButton *button,
112                                                      ParoleMediaList *list);
113 
114 void        parole_media_list_move_up_clicked_cb(GtkButton *button,
115                                                      ParoleMediaList *list);
116 
117 void        parole_media_list_move_down_clicked_cb(GtkButton *button,
118                                                      ParoleMediaList *list);
119 
120 void        parole_media_list_row_activated_cb(GtkTreeView *view,
121                                                      GtkTreePath *path,
122                                                      GtkTreeViewColumn *col,
123                                                      ParoleMediaList *list);
124 
125 gboolean    parole_media_list_button_release_event(GtkWidget *widget,
126                                                      GdkEventButton *ev,
127                                                      ParoleMediaList *list);
128 
129 void        parole_media_list_drag_data_received_cb(GtkWidget *widget,
130                                                      GdkDragContext *drag_context,
131                                                      gint x,
132                                                      gint y,
133                                                      GtkSelectionData *data,
134                                                      guint info,
135                                                      guint drag_time,
136                                                      ParoleMediaList *list);
137 
138 gboolean    parole_media_list_key_press(GtkWidget *widget,
139                                                      GdkEventKey *ev,
140                                                      ParoleMediaList *list);
141 
142 void
143 parole_media_list_format_combo_changed_cb(GtkComboBox *combo,
144                                                      ParolePlaylistSave *data);
145 
146 void        parole_media_list_save_playlist_cb(GtkButton *button,
147                                                      ParolePlaylistSave *data);
148 
149 gboolean    parole_media_list_query_tooltip(GtkWidget *widget,
150                                                      gint x,
151                                                      gint y,
152                                                      gboolean keyboard_mode,
153                                                      GtkTooltip *tooltip,
154                                                      ParoleMediaList *list);
155 
156 GtkTreePath *parole_media_list_sorted_row_reference_get_path (ParoleMediaList     *list,
157                                                               GtkTreeRowReference *row);
158 
159 /*
160  * End of GtkBuilder callbacks
161  */
162 
163 struct ParoleMediaListPrivate {
164     DBusGConnection     *bus;
165     ParoleConf          *conf;
166     GtkWidget           *view, *disc_view;
167     GtkListStore        *store, *disc_store;
168     GtkTreeSelection    *sel, *disc_sel;
169     GtkTreeViewColumn   *col, *disc_col;
170 
171     GtkWidget *playlist_controls;
172 
173     GtkWidget *playlist_notebook;
174 
175     GtkWidget *remove_button;
176     GtkWidget *clear_button;
177 
178     GtkWidget *repeat_button;
179     GtkWidget *shuffle_button;
180 
181     char *history[3];
182 
183     guint entry_pos;
184 };
185 
186 enum {
187     MEDIA_ACTIVATED,
188     MEDIA_CURSOR_CHANGED,
189     URI_OPENED,
190     ISO_OPENED,
191     KEY_FWD_EVENT,
192     LAST_SIGNAL
193 };
194 
195 static guint signals[LAST_SIGNAL] = { 0 };
196 
G_DEFINE_TYPE_WITH_PRIVATE(ParoleMediaList,parole_media_list,GTK_TYPE_BOX)197 G_DEFINE_TYPE_WITH_PRIVATE(ParoleMediaList, parole_media_list, GTK_TYPE_BOX)
198 
199 static void
200 parole_media_list_set_widget_sensitive(ParoleMediaList *list, gboolean sensitive) {
201     gtk_widget_set_sensitive(GTK_WIDGET(list->priv->remove_button), sensitive);
202     gtk_widget_set_sensitive(GTK_WIDGET(list->priv->clear_button), sensitive);
203 }
204 
205 static gint
parole_media_list_get_current_page(ParoleMediaList * list)206 parole_media_list_get_current_page(ParoleMediaList *list) {
207     return gtk_notebook_get_current_page(GTK_NOTEBOOK(list->priv->playlist_notebook));
208 }
209 
210 static GtkTreeViewColumn *
parole_media_list_get_current_treeview_column(ParoleMediaList * list)211 parole_media_list_get_current_treeview_column(ParoleMediaList *list) {
212     if (parole_media_list_get_current_page(list) == 0) {
213         return list->priv->col;
214     }
215     return list->priv->disc_col;
216 }
217 
218 static GtkTreeModel *
parole_media_list_get_current_tree_model(ParoleMediaList * list)219 parole_media_list_get_current_tree_model(ParoleMediaList *list) {
220     if (parole_media_list_get_current_page(list) == 0) {
221         return GTK_TREE_MODEL(list->priv->store);
222     }
223     return GTK_TREE_MODEL(list->priv->disc_store);
224 }
225 
226 static GtkTreeModelSort *
parole_media_list_get_current_tree_model_sort(ParoleMediaList * list)227 parole_media_list_get_current_tree_model_sort(ParoleMediaList *list) {
228     if (parole_media_list_get_current_page(list) == 0)
229         return GTK_TREE_MODEL_SORT(gtk_tree_view_get_model(GTK_TREE_VIEW(list->priv->view)));
230     return GTK_TREE_MODEL_SORT(gtk_tree_view_get_model(GTK_TREE_VIEW(list->priv->disc_view)));
231 }
232 
233 static GtkTreeModelSort *
parole_media_list_get_tree_model_sort(ParoleMediaList * list,gboolean disc)234 parole_media_list_get_tree_model_sort(ParoleMediaList *list, gboolean disc) {
235     if (!disc)
236         return GTK_TREE_MODEL_SORT(gtk_tree_view_get_model(GTK_TREE_VIEW(list->priv->view)));
237     return GTK_TREE_MODEL_SORT(gtk_tree_view_get_model(GTK_TREE_VIEW(list->priv->disc_view)));
238 }
239 
240 static GtkListStore *
parole_media_list_get_current_list_store(ParoleMediaList * list)241 parole_media_list_get_current_list_store(ParoleMediaList *list) {
242     return GTK_LIST_STORE(gtk_tree_model_sort_get_model(parole_media_list_get_current_tree_model_sort(list)));
243 }
244 
245 static GtkListStore *
parole_media_list_get_list_store(ParoleMediaList * list,gboolean disc)246 parole_media_list_get_list_store(ParoleMediaList *list, gboolean disc) {
247     return GTK_LIST_STORE(gtk_tree_model_sort_get_model(parole_media_list_get_tree_model_sort(list, disc)));
248 }
249 
250 static void
parole_media_list_set_playlist_count(ParoleMediaList * list,gint n_items)251 parole_media_list_set_playlist_count(ParoleMediaList *list, gint n_items) {
252     gchar *title;
253 
254     /* Toggle sensitivity based on playlist count */
255     parole_media_list_set_widget_sensitive(list, n_items != 0);
256     gtk_widget_set_sensitive(list->priv->remove_button, n_items != 0);
257     gtk_widget_set_sensitive(list->priv->clear_button, n_items != 0);
258 
259     if (parole_media_list_get_current_page(list) == 0) {
260         title = g_strdup_printf(ngettext("Playlist (%i item)", "Playlist (%i items)", n_items), n_items);
261     } else {
262         title = g_strdup_printf(ngettext("Playlist (%i chapter)", "Playlist (%i chapters)", n_items), n_items);
263     }
264     gtk_tree_view_column_set_title(parole_media_list_get_current_treeview_column(list), title);
265 
266     /*
267      * Will emit the signal media_cursor_changed with FALSE because there is no any
268      * row remaining, so the player can disable click on the play button.
269      */
270     g_signal_emit(G_OBJECT(list), signals[MEDIA_CURSOR_CHANGED], 0, n_items != 0);
271 }
272 
273 gint
parole_media_list_get_playlist_count(ParoleMediaList * list)274 parole_media_list_get_playlist_count(ParoleMediaList *list) {
275     return gtk_tree_model_iter_n_children(parole_media_list_get_current_tree_model(list), NULL);
276 }
277 
278 /**
279  * parole_media_list_add:
280  * @ParoleMediaList: a #ParoleMediaList
281  * @file: a #ParoleFile
282  * @disc: TRUE if added to disc playlist
283  * @emit: TRUE to emit a play signal
284  * @select_row: TRUE to select the added row
285  *
286  * All the media items added to the media list view are added by
287  * this function, setting emit to TRUE will cause the player to
288  * start playing the added file.
289  **/
290 static void
parole_media_list_add(ParoleMediaList * list,ParoleFile * file,gboolean disc,gboolean emit,gboolean select_row)291 parole_media_list_add(ParoleMediaList *list, ParoleFile *file, gboolean disc, gboolean emit, gboolean select_row) {
292     GtkListStore *list_store;
293     GtkTreePath *path;
294     GtkTreeRowReference *row;
295     GtkTreeIter iter;
296     gint nch;
297 
298     /* Objects used for the remove-duplicates functionality. */
299     gchar *filename;
300     ParoleFile *row_file;
301     gboolean remove_duplicates;
302     g_object_get(G_OBJECT(list->priv->conf),
303                   "remove-duplicated", &remove_duplicates,
304                   NULL);
305 
306     /* Set the list_store variable based on with store we're viewing. */
307     list_store = parole_media_list_get_list_store(list, disc);
308 
309     /* Remove duplicates functionality. If the file being added is already in the
310      * playlist, remove it from its current position in the playlist before
311      * adding it again. */
312     if (!disc && remove_duplicates && gtk_tree_model_iter_n_children(GTK_TREE_MODEL(list_store), NULL) != 0) {
313         filename = g_strdup(parole_file_get_file_name(file));
314 
315         /* Check the first row */
316         if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(list_store), &iter)) {
317             gtk_tree_model_get(GTK_TREE_MODEL(list_store), &iter, DATA_COL, &row_file, -1);
318             if (g_strcmp0(filename, parole_file_get_file_name(row_file)) == 0) {
319                 gtk_list_store_remove(GTK_LIST_STORE(list_store), &iter);
320             }
321 
322             /* Check subsequent rows */
323             while (gtk_tree_model_iter_next(GTK_TREE_MODEL(list_store), &iter)) {
324                 gtk_tree_model_get(GTK_TREE_MODEL(list_store), &iter, DATA_COL, &row_file, -1);
325                 if (g_strcmp0(filename, parole_file_get_file_name(row_file)) == 0) {
326                     gtk_list_store_remove(GTK_LIST_STORE(list_store), &iter);
327                 }
328             }
329 
330             g_object_unref(row_file);
331         }
332     }
333 
334     /* Add the file to the playlist */
335     gtk_list_store_append(list_store, &iter);
336     gtk_list_store_set(list_store,
337                        &iter,
338                        NAME_COL, parole_file_get_display_name(file),
339                        DATA_COL, file,
340                        LENGTH_COL, parole_taglibc_get_media_length(file),
341                        STATE_COL, PAROLE_MEDIA_STATE_NONE,
342                        ENTRY_COL, list->priv->entry_pos,
343                        SORT_COL, list->priv->entry_pos,
344                        -1);
345 
346     list->priv->entry_pos++;
347 
348     if ( emit || select_row ) {
349         path = gtk_tree_model_get_path(GTK_TREE_MODEL(list_store), &iter);
350         row = gtk_tree_row_reference_new(GTK_TREE_MODEL(list_store), path);
351         if ( select_row )
352             parole_media_list_select_path(list, disc, path);
353         gtk_tree_path_free(path);
354         if ( emit )
355             g_signal_emit(G_OBJECT(list), signals[MEDIA_ACTIVATED], 0, row);
356         gtk_tree_row_reference_free(row);
357     }
358 
359     /*
360      * Unref it as the list store will have
361      * a reference anyway.
362      */
363     g_object_unref(file);
364 
365     /* Update the playlist count. */
366     nch = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(list_store), NULL);
367     parole_media_list_set_playlist_count(list, nch);
368 }
369 
370 /**
371  * parole_media_list_files_open:
372  * @ParoleMediaList: a #ParoleMediaList
373  * @files: a #GSList contains a list of #ParoleFile
374  * @disc: TRUE if files are opened to the disc playlist
375  * @emit: TRUE to emit a play signal
376  *
377  **/
378 static void
parole_media_list_files_open(ParoleMediaList * list,GSList * files,gboolean disc,gboolean emit)379 parole_media_list_files_open(ParoleMediaList *list, GSList *files, gboolean disc, gboolean emit) {
380     ParoleFile *file;
381     gboolean replace;
382     guint len;
383     guint i;
384 
385     g_object_get(G_OBJECT(list->priv->conf),
386                  "replace-playlist", &replace,
387                  NULL);
388 
389     len = g_slist_length(files);
390     TRACE("Adding %i files", len);
391 
392     if ( len != 0 ) {
393         if ( replace )
394             parole_media_list_clear_list(list);
395         file = g_slist_nth_data(files, 0);
396         parole_media_list_add(list, file, disc, emit, TRUE);
397     }
398 
399     for (i = 1; i < len; i++) {
400         file = g_slist_nth_data(files, i);
401         parole_media_list_add(list, file, disc, FALSE, FALSE);
402     }
403 }
404 
405 void
parole_media_list_add_cdda_tracks(ParoleMediaList * list,gint n_tracks)406 parole_media_list_add_cdda_tracks(ParoleMediaList *list, gint n_tracks) {
407     GSList *files = NULL;
408     ParoleFile *file;
409     int i;
410 
411     for (i = 0; i < n_tracks; i++) {
412         file = parole_file_new_cdda_track(i+1, g_strdup_printf(_("Track %i"), i+1));
413         files = g_slist_append(files, file);
414     }
415 
416     parole_media_list_files_open(list, files, FALSE, TRUE);
417 }
418 
419 void
parole_media_list_add_dvd_chapters(ParoleMediaList * list,gint n_chapters)420 parole_media_list_add_dvd_chapters(ParoleMediaList *list, gint n_chapters) {
421     GSList *files = NULL;
422     ParoleFile *file;
423     gint i;
424 
425     for (i = 0; i < n_chapters; i++) {
426         file = PAROLE_FILE(parole_file_new_dvd_chapter(i+1, g_strdup_printf(_("Chapter %i"), i+1)));
427         files = g_slist_append(files, file);
428     }
429 
430     // parole_media_list_clear_list (list);
431     parole_media_list_files_open(list, files, TRUE, TRUE);
432 }
433 
434 /* Callback to determine whether opened files should start playing immediately */
435 static void
parole_media_list_files_opened_cb(ParoleMediaChooser * chooser,GSList * files,ParoleMediaList * list)436 parole_media_list_files_opened_cb(ParoleMediaChooser *chooser, GSList *files, ParoleMediaList *list) {
437     gboolean play;
438 
439     g_object_get(G_OBJECT(list->priv->conf),
440                   "play-opened-files", &play,
441                   NULL);
442 
443     parole_media_list_files_open(list, files, FALSE, play);
444 }
445 
446 void
parole_media_list_open_uri(ParoleMediaList * list,const gchar * uri)447 parole_media_list_open_uri(ParoleMediaList *list, const gchar *uri) {
448     ParoleFile *file;
449 
450     if ( parole_is_uri_disc(uri) ) {
451         g_signal_emit(G_OBJECT(list), signals[URI_OPENED], 0, uri);
452     } else {
453         file = parole_file_new(uri);
454         parole_media_list_add(list, file, FALSE, TRUE, TRUE);
455     }
456 }
457 
458 static void
parole_media_list_location_opened_cb(ParoleOpenLocation * obj,const gchar * location,ParoleMediaList * list)459 parole_media_list_location_opened_cb(ParoleOpenLocation *obj, const gchar *location, ParoleMediaList *list) {
460     parole_media_list_open_uri(list, location);
461 }
462 
463 static void
parole_media_list_iso_opened_cb(ParoleMediaChooser * chooser,gchar * filename,ParoleMediaList * list)464 parole_media_list_iso_opened_cb(ParoleMediaChooser *chooser, gchar *filename, ParoleMediaList *list) {
465     gchar *uri;
466     uri = g_strdup_printf("dvd://%s", filename);
467     g_signal_emit(G_OBJECT(list), signals[ISO_OPENED], 0, uri);
468 }
469 
470 static void
parole_media_list_open_internal(ParoleMediaList * list)471 parole_media_list_open_internal(ParoleMediaList *list) {
472     ParoleMediaChooser *chooser;
473 
474     TRACE("start");
475 
476     chooser = parole_media_chooser_open_local(gtk_widget_get_toplevel(GTK_WIDGET(list)));
477 
478     g_signal_connect(G_OBJECT(chooser), "media_files_opened",
479                      G_CALLBACK(parole_media_list_files_opened_cb), list);
480 
481     g_signal_connect(G_OBJECT(chooser), "iso_opened",
482                      G_CALLBACK(parole_media_list_iso_opened_cb), list);
483 }
484 
485 static void
parole_media_list_open_location_internal(ParoleMediaList * list)486 parole_media_list_open_location_internal(ParoleMediaList *list) {
487     ParoleOpenLocation *location;
488 
489     location = parole_open_location(gtk_widget_get_toplevel(GTK_WIDGET(list)));
490 
491     g_signal_connect(G_OBJECT(location), "location-opened",
492                      G_CALLBACK(parole_media_list_location_opened_cb), list);
493 }
494 
495 /**
496  * parole_media_list_get_files:
497  * @list: a #ParoleMediaList
498  *
499  * Get a #GSList of all #ParoleFile media files currently displayed in the
500  * media list view
501  *
502  * Returns: a #GSList contains a list of #ParoleFile
503  *
504  **/
505 static GSList *
parole_media_list_get_files(ParoleMediaList * list)506 parole_media_list_get_files(ParoleMediaList *list) {
507     ParoleFile *file;
508     GtkTreeIter iter;
509     gboolean valid;
510     GSList *files_list = NULL;
511     GtkTreeModelSort *model;
512 
513     model = parole_media_list_get_current_tree_model_sort(list);
514 
515     for ( valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL(model), &iter);
516         valid;
517         valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter)) {
518         gtk_tree_model_get(GTK_TREE_MODEL(model), &iter,
519                             DATA_COL, &file,
520                             -1);
521 
522         files_list = g_slist_append(files_list, file);
523     }
524 
525     return files_list;
526 }
527 
528 /* Callback when an item is dragged on the playlist-widget */
parole_media_list_drag_data_received_cb(GtkWidget * widget,GdkDragContext * drag_context,gint x,gint y,GtkSelectionData * data,guint info,guint drag_time,ParoleMediaList * list)529 void    parole_media_list_drag_data_received_cb(GtkWidget *widget,
530                                                  GdkDragContext *drag_context,
531                                                  gint x,
532                                                  gint y,
533                                                  GtkSelectionData *data,
534                                                  guint info,
535                                                  guint drag_time,
536                                                  ParoleMediaList *list) {
537     gchar **uri_list;
538     gchar *path;
539     guint i;
540     guint added = 0;
541     gboolean play;
542 
543     parole_window_busy_cursor(gtk_widget_get_window(GTK_WIDGET(list)));
544 
545     g_object_get(G_OBJECT(list->priv->conf),
546                  "play-opened-files", &play,
547                  NULL);
548 
549     uri_list = g_uri_list_extract_uris((const gchar *)gtk_selection_data_get_data(data));
550 
551     for (i = 0; uri_list[i] != NULL; i++) {
552         path = g_filename_from_uri(uri_list[i], NULL, NULL);
553         added += parole_media_list_add_by_path(list, path, i == 0 ? play : FALSE);
554 
555         g_free(path);
556     }
557 
558     g_strfreev(uri_list);
559 
560     parole_window_normal_cursor(gtk_widget_get_window(GTK_WIDGET(list)));
561     gtk_drag_finish(drag_context, added == i ? TRUE : FALSE, FALSE, drag_time);
562 }
563 
parole_media_list_key_press(GtkWidget * widget,GdkEventKey * ev,ParoleMediaList * list)564 gboolean parole_media_list_key_press(GtkWidget *widget, GdkEventKey *ev, ParoleMediaList *list) {
565     GdkEvent *event;
566     switch ( ev->keyval ) {
567         case GDK_KEY_Delete:
568             parole_media_list_remove_clicked_cb(NULL, list);
569             return TRUE;
570             break;
571         case GDK_KEY_Right:
572         case GDK_KEY_Left:
573         case GDK_KEY_Page_Down:
574         case GDK_KEY_Page_Up:
575             event = gdk_event_copy ((GdkEvent *)ev);
576             g_signal_emit(G_OBJECT(list), signals[KEY_FWD_EVENT], 0, event);
577             return TRUE;
578             break;
579         default:
580             return FALSE;
581             break;
582     }
583 }
584 
585 /* Callback for the add button */
586 void
parole_media_list_add_clicked_cb(GtkButton * button,ParoleMediaList * list)587 parole_media_list_add_clicked_cb(GtkButton *button, ParoleMediaList *list) {
588     parole_media_list_open_internal(list);
589 }
590 
591 /* Callback for the clear button */
592 void
parole_media_list_clear_clicked_cb(GtkButton * button,ParoleMediaList * list)593 parole_media_list_clear_clicked_cb(GtkButton *button, ParoleMediaList *list) {
594     gchar *playlist_filename;
595     GFile *playlist_file;
596     parole_media_list_clear_list(list);
597     playlist_filename = xfce_resource_save_location(XFCE_RESOURCE_DATA,
598                                                          PAROLE_AUTO_SAVED_PLAYLIST,
599                                                          FALSE);
600     playlist_file = g_file_new_for_path(playlist_filename);
601     g_file_delete(playlist_file, NULL, NULL);
602     g_free(playlist_filename);
603 }
604 
605 /**
606  * parole_media_list_get_first_selected_row:
607  * @list: a #ParoleMediaList
608  *
609  * Gets the first selected row in the media list view.
610  *
611  * Returns: a #GtkTreeRowReference for the selected row, or NULL if no one is
612  *      currently selected.
613  *
614  **/
615 static GtkTreeRowReference *
parole_media_list_get_first_selected_row(ParoleMediaList * list)616 parole_media_list_get_first_selected_row(ParoleMediaList *list) {
617     GtkTreeModel *model;
618     GtkTreeRowReference *row = NULL;
619     GtkTreeIter iter;
620     GList *path_list = NULL;
621 
622     path_list = gtk_tree_selection_get_selected_rows(list->priv->sel, &model);
623 
624     if (g_list_length(path_list) > 0) {
625         GtkTreePath *path;
626         path = g_list_nth_data(path_list, 0);
627 
628         if (G_LIKELY(gtk_tree_model_get_iter(model, &iter, path) == TRUE)) {
629             row = parole_media_list_get_row_reference_from_iter(list, &iter, FALSE);
630         }
631     }
632 
633     g_list_foreach(path_list, (GFunc) (void (*)(void)) gtk_tree_path_free, NULL);
634     g_list_free(path_list);
635 
636     return row;
637 }
638 
639 /**
640  * parole_media_list_get_first_selected_file:
641  * @list: a #ParoleMediaList
642  *
643  * Get the first selected #ParoleFile media file in the media list view
644  *
645  * Returns: a #ParoleFile
646  **/
647 static ParoleFile *
parole_media_list_get_first_selected_file(ParoleMediaList * list)648 parole_media_list_get_first_selected_file(ParoleMediaList *list) {
649     ParoleFile *file = NULL;
650     GtkTreeRowReference *row;
651     GtkTreeIter iter;
652     GtkTreeModelSort *model;
653 
654     model = parole_media_list_get_current_tree_model_sort (list);
655 
656     row = parole_media_list_get_first_selected_row(list);
657 
658     if (gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, gtk_tree_row_reference_get_path(row))) {
659         gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, DATA_COL, &file, -1);
660     }
661 
662     return file;
663 }
664 
665 static void
parole_media_list_save_playlist_response_cb(GtkDialog * dialog,gint response_id,ParolePlaylistSave * data)666 parole_media_list_save_playlist_response_cb(GtkDialog *dialog, gint response_id, ParolePlaylistSave *data) {
667     gchar *filename = NULL;
668     gchar *dirname = NULL;
669 
670     if (response_id == GTK_RESPONSE_ACCEPT) {
671         ParolePlFormat format = PAROLE_PL_FORMAT_UNKNOWN;
672         GSList *list = NULL;
673         GtkTreeModel *model;
674         GtkTreeIter iter;
675 
676         filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(data->chooser));
677         dirname = g_path_get_dirname(filename);
678 
679         if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(data->combo), &iter)) {
680             model = gtk_combo_box_get_model(GTK_COMBO_BOX(data->combo));
681             gtk_tree_model_get(model, &iter, 2, &format, -1);
682         }
683 
684         if (g_access(dirname, W_OK) == -1) {
685             gchar *msg;
686             msg = g_strdup_printf("%s %s", dirname, _("Permission denied"));
687             parole_dialog_error(GTK_WINDOW(gtk_widget_get_toplevel(data->list->priv->view)),
688                                  _("Error saving playlist file"),
689                                  msg);
690             g_free(msg);
691             goto out;
692         }
693 
694         if ( format == PAROLE_PL_FORMAT_UNKNOWN ) {
695             format = parole_pl_parser_guess_format_from_extension(filename);
696             if ( format == PAROLE_PL_FORMAT_UNKNOWN ) {
697                 parole_dialog_info(GTK_WINDOW(gtk_widget_get_toplevel(data->list->priv->view)),
698                                     _("Unknown playlist format"),
699                                     _("Please choose a supported playlist format"));
700                 goto out;
701             }
702         }
703 
704         list = parole_media_list_get_files(data->list);
705 
706         parole_pl_parser_save_from_files(list, filename, format);
707         g_slist_free(list);
708     }
709     data->closing = TRUE;
710     gtk_widget_destroy(GTK_WIDGET(dialog));
711     g_free(data);
712 out:
713     g_free(filename);
714     g_free(dirname);
715 }
716 
717 /* Query to get the data to populate the tooltip */
parole_media_list_query_tooltip(GtkWidget * widget,gint x,gint y,gboolean keyboard_mode,GtkTooltip * tooltip,ParoleMediaList * list)718 gboolean    parole_media_list_query_tooltip(GtkWidget *widget,
719                                             gint x,
720                                             gint y,
721                                             gboolean keyboard_mode,
722                                             GtkTooltip *tooltip,
723                                             ParoleMediaList *list) {
724     GtkTreePath *path;
725     GtkTreeModelSort *model;
726 
727     model = parole_media_list_get_current_tree_model_sort(list);
728 
729     if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(list->priv->view), x, y, &path, NULL, NULL, NULL)) {
730         GtkTreeIter iter;
731 
732         if (path && gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path)) {
733             ParoleFile *file;
734             gchar *tip;
735             gchar *name;
736             gchar *len;
737 
738             gtk_tree_model_get(GTK_TREE_MODEL(model), &iter,
739                                DATA_COL, &file,
740                                NAME_COL, &name,
741                                LENGTH_COL, &len,
742                                -1);
743 
744             if (!len) {
745                 len = g_strdup(_("Unknown"));
746             }
747 
748             tip = g_strdup_printf("File: %s\nName: %s\nLength: %s",
749                                    parole_file_get_file_name(file),
750                                    name,
751                                    len);
752 
753             gtk_tooltip_set_text(tooltip, tip);
754             g_free(tip);
755             g_free(name);
756             g_free(len);
757             gtk_tree_path_free(path);
758 
759             return TRUE;
760         }
761     }
762 
763 
764     return FALSE;
765 }
766 
767 static void
parole_media_list_set_filter(ParolePlaylistSave * data,const gchar * extension)768 parole_media_list_set_filter(ParolePlaylistSave *data, const gchar *extension) {
769     GtkFileFilter *filter;
770     gchar *pattern;
771 
772     if (g_strcmp0(extension, "") == 0)
773         pattern = g_strdup("*.*");
774     else
775         pattern = g_strdup_printf("*.%s", extension);
776 
777     filter = gtk_file_filter_new();
778     gtk_file_filter_add_pattern (filter, pattern);
779     gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(data->chooser), filter);
780 
781     g_free(pattern);
782 }
783 
parole_media_list_format_combo_changed_cb(GtkComboBox * combo,ParolePlaylistSave * data)784 void parole_media_list_format_combo_changed_cb(GtkComboBox *combo, ParolePlaylistSave *data) {
785     GtkTreeIter iter;
786     GtkTreeModel *model;
787     ParolePlFormat format;
788     gchar *filename;
789     gchar *fbasename;
790     gchar *extension;
791 
792     // FIXME: replaces entered filename with Playlist.
793     filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(data->chooser));
794     if (filename)
795         fbasename = g_path_get_basename(filename);
796     else
797         fbasename = g_strconcat(_("Playlist"), ".m3u", NULL);
798     g_free(filename);
799 
800     if (gtk_combo_box_get_active_iter(combo, &iter)) {
801         model = gtk_combo_box_get_model(combo);
802         gtk_tree_model_get(model, &iter,
803             1, &extension,
804             2, &format,
805             -1);
806         if ( format != PAROLE_PL_FORMAT_UNKNOWN ) {
807             gchar *name, *new_name;
808             name = parole_get_name_without_extension(fbasename);
809             new_name = g_strdup_printf("%s%s", name, playlist_format_map[format].ext);
810             gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(data->chooser), new_name);
811             g_free(new_name);
812             g_free(name);
813         }
814         parole_media_list_set_filter(data, extension);
815     }
816     g_free(fbasename);
817 }
818 
819 /* Callback to save the current playlist */
parole_media_list_save_cb(GtkWidget * widget,ParoleMediaList * list)820 void parole_media_list_save_cb(GtkWidget *widget, ParoleMediaList *list) {
821     ParolePlaylistSave *data;
822     GtkWidget *chooser;
823     GtkListStore *store;
824     GtkBuilder *builder;
825     gchar *filename;
826     GtkTreeIter iter;
827     gchar *label;
828     GtkWidget *combo;
829 
830     data = g_new0(ParolePlaylistSave, 1);
831 
832     builder = parole_builder_new_from_string(save_playlist_ui, save_playlist_ui_length);
833     chooser = GTK_WIDGET(gtk_builder_get_object(builder, "filechooserdialog"));
834     store = GTK_LIST_STORE(gtk_builder_get_object(builder, "liststore"));
835     combo = GTK_WIDGET(gtk_builder_get_object(builder, "format_combo"));
836 
837     gtk_dialog_add_buttons(GTK_DIALOG(chooser),
838                            _("Cancel"), GTK_RESPONSE_CANCEL,
839                            _("Save"), GTK_RESPONSE_APPLY,
840                            NULL);
841     gtk_dialog_set_default_response(GTK_DIALOG(chooser), GTK_RESPONSE_APPLY);
842 
843     gtk_window_set_transient_for(GTK_WINDOW(chooser),
844                                   GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(list))));
845 
846     gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(chooser), TRUE);
847 
848     filename = g_strconcat(_("Playlist"), ".m3u", NULL);
849     gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(chooser), filename);
850     g_free(filename);
851 
852     gtk_list_store_append(store, &iter);
853     gtk_list_store_set(store,
854                         &iter,
855                         0, _("All files"),
856                         1, "",
857                         2, PAROLE_PL_FORMAT_UNKNOWN,
858                         -1);
859 
860     label = g_strdup_printf (_("M3U Playlist (%s)"), ".m3u");
861     gtk_list_store_append(store, &iter);
862     gtk_list_store_set(store,
863                         &iter,
864                         0, label,
865                         1, "m3u",
866                         2, PAROLE_PL_FORMAT_M3U,
867                         -1);
868     g_free (label);
869 
870     label = g_strdup_printf (_("PLS Playlist (%s)"), ".pls");
871     gtk_list_store_append(store, &iter);
872     gtk_list_store_set(store,
873                         &iter,
874                         0, label,
875                         1, "pls",
876                         2, PAROLE_PL_FORMAT_PLS,
877                         -1);
878     g_free (label);
879 
880     label = g_strdup_printf (_("Advanced Stream Redirector (%s)"), ".asx");
881     gtk_list_store_append(store, &iter);
882     gtk_list_store_set(store,
883                         &iter,
884                         0, label,
885                         1, "asx",
886                         2, PAROLE_PL_FORMAT_ASX,
887                         -1);
888     g_free (label);
889 
890     label = g_strdup_printf(_("Shareable Playlist (%s)"), ".xspf");
891     gtk_list_store_append(store, &iter);
892     gtk_list_store_set(store,
893                         &iter,
894                         0, label,
895                         1, "xspf",
896                         2, PAROLE_PL_FORMAT_XSPF,
897                         -1);
898     g_free (label);
899 
900     g_signal_connect(G_OBJECT(chooser), "response", G_CALLBACK(parole_media_list_save_playlist_response_cb), data);
901 
902     data->chooser = chooser;
903     data->combo = combo;
904     data->closing = FALSE;
905     data->list = list;
906 
907     gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 1);
908     parole_media_list_set_filter(data, "m3u");
909 
910     gtk_builder_connect_signals(builder, data);
911     gtk_widget_show_all(chooser);
912     g_object_unref(builder);
913 }
914 
915 /**
916  * parole_media_list_get_first_path:
917  * @model: a #GtkTreeModel
918  *
919  * Get the first path in the model, or NULL if the model is empty
920  *
921  * Returns: a #GtkTreePath
922  **/
923 static GtkTreePath *
parole_media_list_get_first_path(GtkTreeModel * model)924 parole_media_list_get_first_path(GtkTreeModel *model) {
925     GtkTreePath *path = NULL;
926     GtkTreeIter iter;
927 
928     if (gtk_tree_model_get_iter_first(model, &iter)) {
929         path = gtk_tree_model_get_path(GTK_TREE_MODEL(model), &iter);
930     }
931 
932     return path;
933 }
934 
935 /**
936  *
937  * parole_media_list_paths_to_row_list:
938  * @path_list: a #GList contains a list of #GtkTreePath
939  * @GtkTreeModel: a #GtkTreeModel that contains the paths
940  *
941  * Converts a list of #GtkTreePath to a list of #GtkTreeRowReference
942  *
943  * Returns: a #GList contains a list of #GtkTreeRowReference
944  *
945  *
946  **/
947 static GList *
parole_media_list_paths_to_row_list(GList * path_list,GtkTreeModel * model)948 parole_media_list_paths_to_row_list(GList *path_list, GtkTreeModel *model) {
949     GList *row_list = NULL;
950     guint len, i;
951 
952     len = g_list_length(path_list);
953 
954     for (i = 0; i < len; i++) {
955         GtkTreePath *path;
956         GtkTreeRowReference *row;
957 
958         path = g_list_nth_data(path_list, i);
959 
960         row = gtk_tree_row_reference_new(model, path);
961 
962         row_list = g_list_append(row_list, row);
963     }
964 
965     return row_list;
966 }
967 
968 /* Callback for the remove-from-playlist button */
969 void
parole_media_list_remove_clicked_cb(GtkButton * button,ParoleMediaList * list)970 parole_media_list_remove_clicked_cb(GtkButton *button, ParoleMediaList *list) {
971     GtkTreeModel *sort_model, *model;
972     GList *path_list = NULL;
973     GList *row_list = NULL;
974     GtkTreeIter iter;
975     gboolean row_selected = FALSE;
976     gint nch;
977     guint len, i;
978 
979     /* Get the GtkTreePath GList of all selected rows */
980     path_list = gtk_tree_selection_get_selected_rows(list->priv->sel, &sort_model);
981     model = gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(sort_model));
982 
983     /**
984      * Convert them to row references so when we remove one the others always points
985      * to the correct node.
986      **/
987     row_list = parole_media_list_paths_to_row_list(path_list, model);
988 
989     /**
990      * Select first path before the first path
991      * that we going to remove.
992      **/
993     if (g_list_length(path_list) != 0) {
994         GtkTreePath *path, *prev;
995 
996         /* Get first item */
997         path = g_list_nth_data(path_list, 0);
998 
999         /* copy it as we don't mess with the list*/
1000         prev = gtk_tree_path_copy(path);
1001 
1002         if (gtk_tree_path_prev(prev)) {
1003             parole_media_list_select_path(list, FALSE, prev);
1004             row_selected = TRUE;
1005         }
1006         gtk_tree_path_free(prev);
1007     }
1008 
1009     g_list_foreach(path_list, (GFunc) (void (*)(void)) gtk_tree_path_free, NULL);
1010     g_list_free(path_list);
1011 
1012     len = g_list_length(row_list);
1013 
1014     for (i = 0; i < len; i++) {
1015         GtkTreePath *path;
1016         GtkTreeRowReference *row;
1017         row = g_list_nth_data(row_list, i);
1018         path = parole_media_list_sorted_row_reference_get_path(list, row);
1019 
1020         if (G_LIKELY(gtk_tree_model_get_iter(model, &iter, path) == TRUE)) {
1021             gtk_list_store_remove(GTK_LIST_STORE(model),
1022                        &iter);
1023         }
1024     }
1025 
1026     g_list_foreach(row_list, (GFunc) (void (*)(void)) gtk_tree_row_reference_free, NULL);
1027     g_list_free(row_list);
1028 
1029     /*
1030      * Returns the number of children that iter has.
1031      * As a special case, if iter is NULL,
1032      * then the number of toplevel nodes is returned. Gtk API doc.
1033      */
1034     nch = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(model), NULL);
1035 
1036     /* No row was selected, then select the first one*/
1037     if (!row_selected && nch != 0) {
1038         GtkTreePath *path;
1039         path = parole_media_list_get_first_path(model);
1040         parole_media_list_select_path(list, FALSE, path);
1041         gtk_tree_path_free(path);
1042     }
1043 
1044     parole_media_list_set_playlist_count(list, nch);
1045 }
1046 
1047 static void
parole_media_list_tree_iter_swap(GtkTreeModel * tree_model,GtkTreeIter * first_iter,GtkTreeIter * second_iter,gboolean shuffled)1048 parole_media_list_tree_iter_swap (GtkTreeModel *tree_model, GtkTreeIter *first_iter, GtkTreeIter *second_iter, gboolean shuffled) {
1049     guint first_entry_pos, first_sort_pos, second_entry_pos, second_sort_pos;
1050 
1051     gtk_tree_model_get(GTK_TREE_MODEL(tree_model), first_iter,
1052                        ENTRY_COL, &first_entry_pos,
1053                        SORT_COL, &first_sort_pos,
1054                        -1);
1055 
1056     gtk_tree_model_get(GTK_TREE_MODEL(tree_model), second_iter,
1057                        ENTRY_COL, &second_entry_pos,
1058                        SORT_COL, &second_sort_pos,
1059                        -1);
1060 
1061     if (first_sort_pos == second_sort_pos) {
1062         second_sort_pos++;
1063     }
1064 
1065     if (shuffled) {
1066         gtk_list_store_set(GTK_LIST_STORE(tree_model), first_iter,
1067                            SORT_COL, second_sort_pos,
1068                            -1);
1069 
1070         gtk_list_store_set(GTK_LIST_STORE(tree_model), second_iter,
1071                            SORT_COL, first_sort_pos,
1072                            -1);
1073     } else {
1074         gtk_list_store_set(GTK_LIST_STORE(tree_model), first_iter,
1075                            ENTRY_COL, second_entry_pos,
1076                            SORT_COL, second_sort_pos,
1077                            -1);
1078 
1079         gtk_list_store_set(GTK_LIST_STORE(tree_model), second_iter,
1080                            ENTRY_COL, first_entry_pos,
1081                            SORT_COL, first_sort_pos,
1082                            -1);
1083     }
1084 }
1085 
tree_path_prev(GtkTreeModelSort * sort,GtkTreePath ** path,GtkTreePath ** realpath)1086 static gboolean tree_path_prev (GtkTreeModelSort *sort, GtkTreePath **path, GtkTreePath **realpath) {
1087     if (!gtk_tree_path_prev (*path)) {
1088         return FALSE;
1089     }
1090     *realpath = gtk_tree_model_sort_convert_path_to_child_path(sort, *path);
1091     if (*realpath == NULL)
1092         return FALSE;
1093     return TRUE;
1094 }
1095 
tree_path_next(GtkTreeModelSort * sort,GtkTreePath ** path,GtkTreePath ** realpath)1096 static gboolean tree_path_next (GtkTreeModelSort *sort, GtkTreePath **path, GtkTreePath **realpath) {
1097     gtk_tree_path_next (*path);
1098     *realpath = gtk_tree_model_sort_convert_path_to_child_path(sort, *path);
1099     if (*realpath == NULL)
1100         return FALSE;
1101     return TRUE;
1102 }
1103 
1104 void
parole_media_list_move_up_clicked_cb(GtkButton * button,ParoleMediaList * list)1105 parole_media_list_move_up_clicked_cb(GtkButton *button, ParoleMediaList *list) {
1106     GtkTreeModel *sort_model, *model;
1107     GList *path_list = NULL;
1108     GtkTreeIter current, iter;
1109     gboolean shuffled;
1110 
1111     /* Get the GtkTreePath GList of all selected rows */
1112     path_list = gtk_tree_selection_get_selected_rows(list->priv->sel, &sort_model);
1113     model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model));
1114     shuffled = gtk_toggle_tool_button_get_active (GTK_TOGGLE_TOOL_BUTTON (list->priv->shuffle_button));
1115 
1116     /**
1117      * Select first path before the first path
1118      * that we going to move.
1119      **/
1120     if (g_list_length(path_list) != 0) {
1121         GtkTreePath *sorted_path, *path, *sorted_prev, *prev;
1122         guint i;
1123 
1124         /* Get first item */
1125         sorted_path = g_list_nth_data(path_list, 0);
1126         path = gtk_tree_model_sort_convert_path_to_child_path(GTK_TREE_MODEL_SORT(sort_model), sorted_path);
1127         if (gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &current, path)) {
1128             /* copy it as we don't mess with the list*/
1129             sorted_prev = gtk_tree_path_copy(sorted_path);
1130 
1131             if (tree_path_prev(GTK_TREE_MODEL_SORT (sort_model), &sorted_prev, &prev)) {
1132                 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, prev)) {
1133                     /* Move each item about the previous path */
1134                     for (i=0; i < g_list_length(path_list); i++) {
1135                         sorted_path = g_list_nth_data(path_list, i);
1136                         path = gtk_tree_model_sort_convert_path_to_child_path(GTK_TREE_MODEL_SORT(sort_model), sorted_path);
1137 
1138                         if (gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &current, path))
1139                             parole_media_list_tree_iter_swap (GTK_TREE_MODEL (model), &iter, &current, shuffled);
1140                     }
1141                 }
1142                 gtk_tree_path_free(prev);
1143             }
1144 
1145             gtk_tree_path_free(sorted_prev);
1146         }
1147     }
1148 
1149     g_list_foreach(path_list, (GFunc) (void (*)(void)) gtk_tree_path_free, NULL);
1150     g_list_free(path_list);
1151 }
1152 
1153 void
parole_media_list_move_down_clicked_cb(GtkButton * button,ParoleMediaList * list)1154 parole_media_list_move_down_clicked_cb(GtkButton *button, ParoleMediaList *list) {
1155     GtkTreeModel *sort_model, *model;
1156     GList *path_list = NULL;
1157     GtkTreeIter current, iter;
1158     gboolean shuffled;
1159 
1160     /* Get the GtkTreePath GList of all selected rows */
1161     path_list = gtk_tree_selection_get_selected_rows(list->priv->sel, &sort_model);
1162     model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model));
1163     shuffled = gtk_toggle_tool_button_get_active (GTK_TOGGLE_TOOL_BUTTON (list->priv->shuffle_button));
1164 
1165     /* Reverse the list to repopulate in the right order */
1166     path_list = g_list_reverse(path_list);
1167 
1168     /**
1169      * Select first path before the first path
1170      * that we going to move.
1171      **/
1172     if (g_list_length(path_list) != 0) {
1173         GtkTreePath *sorted_path, *path, *sorted_next, *next;
1174         guint i;
1175 
1176         /* Get first item */
1177         sorted_path = g_list_nth_data(path_list, 0);
1178         path = gtk_tree_model_sort_convert_path_to_child_path(GTK_TREE_MODEL_SORT(sort_model), sorted_path);
1179         if (gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &current, path)) {
1180             /* copy it as we don't mess with the list*/
1181             sorted_next = gtk_tree_path_copy(sorted_path);
1182 
1183             if (tree_path_next(GTK_TREE_MODEL_SORT(sort_model), &sorted_next, &next)) {
1184                 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, next)) {
1185                     /* Move each item about the previous path */
1186                     for (i=0; i < g_list_length(path_list); i++) {
1187                         sorted_path = g_list_nth_data(path_list, i);
1188                         path = gtk_tree_model_sort_convert_path_to_child_path(GTK_TREE_MODEL_SORT(sort_model), sorted_path);
1189 
1190                         if (gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &current, path))
1191                             parole_media_list_tree_iter_swap (GTK_TREE_MODEL (model), &current, &iter, shuffled);
1192                     }
1193                 }
1194                 gtk_tree_path_free(next);
1195             }
1196 
1197             gtk_tree_path_free(sorted_next);
1198         }
1199     }
1200 
1201     g_list_foreach(path_list, (GFunc) (void (*)(void)) gtk_tree_path_free, NULL);
1202     g_list_free(path_list);
1203 }
1204 
1205 /**
1206  * parole_media_list_row_activated_cb:
1207  *
1208  *
1209  **/
1210 void
parole_media_list_row_activated_cb(GtkTreeView * view,GtkTreePath * path,GtkTreeViewColumn * col,ParoleMediaList * list)1211 parole_media_list_row_activated_cb(GtkTreeView *view,
1212                                    GtkTreePath *path,
1213                                    GtkTreeViewColumn *col,
1214                                    ParoleMediaList *list) {
1215     GtkTreeRowReference *row;
1216 
1217     if (gtk_notebook_get_current_page(GTK_NOTEBOOK(list->priv->playlist_notebook)) == 0)
1218         row = gtk_tree_row_reference_new(gtk_tree_view_get_model(GTK_TREE_VIEW(list->priv->view)), path);
1219     else
1220         row = gtk_tree_row_reference_new(gtk_tree_view_get_model(GTK_TREE_VIEW(list->priv->disc_view)), path);
1221 
1222     g_signal_emit(G_OBJECT(list), signals[MEDIA_ACTIVATED], 0, row);
1223 }
1224 
1225 static void
parole_media_list_selection_changed_cb(GtkTreeSelection * sel,ParoleMediaList * list)1226 parole_media_list_selection_changed_cb(GtkTreeSelection *sel, ParoleMediaList *list) {
1227     g_signal_emit(G_OBJECT(list), signals[MEDIA_CURSOR_CHANGED], 0,
1228                   gtk_tree_selection_count_selected_rows(sel) > 0);
1229 }
1230 
1231 static void
parole_media_list_open_folder(GtkWidget * menu)1232 parole_media_list_open_folder(GtkWidget *menu) {
1233     gchar *dirname;
1234 
1235     dirname =(gchar *) g_object_get_data(G_OBJECT(menu), "folder");
1236 
1237     if (dirname) {
1238         gchar *uri;
1239         uri = g_filename_to_uri(dirname, NULL, NULL);
1240         TRACE("Opening %s", dirname);
1241 #if GTK_CHECK_VERSION(3, 22, 0)
1242         gtk_show_uri_on_window(GTK_WINDOW(gtk_menu_get_attach_widget(GTK_MENU(menu))), uri, GDK_CURRENT_TIME, NULL);
1243 #else
1244         gtk_show_uri(gtk_widget_get_screen(menu),  uri, GDK_CURRENT_TIME, NULL);
1245 #endif
1246 
1247         g_free(uri);
1248     }
1249 }
1250 
1251 static void
parole_media_list_add_open_containing_folder(ParoleMediaList * list,GtkWidget * menu,gint x,gint y)1252 parole_media_list_add_open_containing_folder(ParoleMediaList *list, GtkWidget *menu, gint x, gint y) {
1253     GtkTreePath *path;
1254     GtkTreeModelSort *model;
1255 
1256     model = parole_media_list_get_current_tree_model_sort(list);
1257 
1258     if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(list->priv->view), x, y, &path, NULL, NULL, NULL)) {
1259         GtkTreeIter iter;
1260 
1261         if (path && gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path)) {
1262             ParoleFile *file;
1263             const gchar *filename;
1264             const gchar *uri;
1265 
1266             gtk_tree_model_get(GTK_TREE_MODEL(model), &iter,
1267                                DATA_COL, &file,
1268                                -1);
1269 
1270             filename = parole_file_get_file_name(file);
1271             uri = parole_file_get_uri(file);
1272 
1273             if (g_str_has_prefix(uri, "file:///")) {
1274                 GtkWidget *mi;
1275                 gchar *dirname;
1276 
1277                 dirname = g_path_get_dirname(filename);
1278 
1279                 /* Clear */
1280                 mi = gtk_menu_item_new_with_label(_("Open Containing Folder"));
1281                 gtk_widget_set_sensitive(mi, TRUE);
1282                 gtk_widget_show(mi);
1283                 g_signal_connect_swapped(mi, "activate",
1284                                             G_CALLBACK(parole_media_list_open_folder), menu);
1285 
1286                 g_object_set_data(G_OBJECT(menu), "folder", dirname);
1287 
1288                 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
1289 
1290 
1291                 mi = gtk_separator_menu_item_new();
1292                 gtk_widget_show(mi);
1293                 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
1294             }
1295 
1296             gtk_tree_path_free(path);
1297         }
1298     }
1299 }
1300 
1301 void
parole_media_list_set_playlist_view(ParoleMediaList * list,gint view)1302 parole_media_list_set_playlist_view(ParoleMediaList *list, gint view) {
1303     gtk_notebook_set_current_page(GTK_NOTEBOOK(list->priv->playlist_notebook), view);
1304     gtk_widget_set_sensitive(list->priv->playlist_controls, !view);
1305 }
1306 
1307 void
parole_media_list_clear_disc_list(ParoleMediaList * list)1308 parole_media_list_clear_disc_list(ParoleMediaList *list) {
1309     gtk_list_store_clear(GTK_LIST_STORE(list->priv->disc_store));
1310 }
1311 
1312 void
parole_media_list_clear_list(ParoleMediaList * list)1313 parole_media_list_clear_list(ParoleMediaList *list) {
1314     TRACE("CLEAR START");
1315     gtk_list_store_clear(parole_media_list_get_current_list_store(list));
1316     parole_media_list_set_playlist_count(list, 0);
1317     TRACE("CLEAR END");
1318 }
1319 
1320 static void
replace_list_activated_cb(GtkWidget * mi,ParoleConf * conf)1321 replace_list_activated_cb(GtkWidget *mi, ParoleConf *conf) {
1322     g_object_set(G_OBJECT(conf),
1323                  "replace-playlist", gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(mi)),
1324                  NULL);
1325 }
1326 
1327 static void
play_opened_files_activated_cb(GtkWidget * mi,ParoleConf * conf)1328 play_opened_files_activated_cb(GtkWidget *mi, ParoleConf *conf) {
1329     g_object_set(G_OBJECT(conf),
1330                  "play-opened-files", gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(mi)),
1331                  NULL);
1332 }
1333 
1334 static void
remember_playlist_activated_cb(GtkWidget * mi,ParoleConf * conf)1335 remember_playlist_activated_cb(GtkWidget *mi, ParoleConf *conf) {
1336     gchar *playlist_filename;
1337     GFile *playlist_file;
1338     g_object_set(G_OBJECT(conf),
1339                  "remember-playlist", gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(mi)),
1340                  NULL);
1341     if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(mi))) {
1342         playlist_filename = xfce_resource_save_location(XFCE_RESOURCE_DATA,
1343                                                          PAROLE_AUTO_SAVED_PLAYLIST,
1344                                                          FALSE);
1345         playlist_file = g_file_new_for_path(playlist_filename);
1346         g_file_delete(playlist_file, NULL, NULL);
1347         g_free(playlist_filename);
1348     }
1349 }
1350 
1351 static void
parole_media_list_destroy_menu(GtkWidget * menu)1352 parole_media_list_destroy_menu(GtkWidget *menu) {
1353     gchar *dirname;
1354 
1355     dirname =(gchar *) g_object_get_data(G_OBJECT(menu), "folder");
1356 
1357     if (dirname) {
1358         g_free(dirname);
1359     }
1360 
1361     gtk_widget_destroy(menu);
1362 }
1363 
1364 static void
parole_media_list_show_menu(ParoleMediaList * list,GdkEventButton * ev)1365 parole_media_list_show_menu(ParoleMediaList *list, GdkEventButton *ev) {
1366     gboolean val;
1367 #if GTK_CHECK_VERSION(3, 22, 0)
1368 #else
1369     guint button = ev->button;
1370     guint activate_time = ev->time;
1371 #endif
1372 
1373     GtkBuilder *builder;
1374 
1375     GtkMenu *menu;
1376     GtkMenuItem *clear;
1377     GtkCheckMenuItem *replace, *play_opened;
1378     GtkCheckMenuItem *remember;
1379 
1380     builder = parole_builder_new_from_string(playlist_ui, playlist_ui_length);
1381 
1382     menu = GTK_MENU(gtk_builder_get_object(builder, "playlist-menu"));
1383     replace = GTK_CHECK_MENU_ITEM(gtk_builder_get_object(builder, "menu-replace"));
1384     play_opened = GTK_CHECK_MENU_ITEM(gtk_builder_get_object(builder, "menu-play-opened"));
1385     remember = GTK_CHECK_MENU_ITEM(gtk_builder_get_object(builder, "menu-remember"));
1386     clear = GTK_MENU_ITEM(gtk_builder_get_object(builder, "menu-clear"));
1387 
1388     parole_media_list_add_open_containing_folder(list, GTK_WIDGET(menu), (gint)ev->x, (gint)ev->y);
1389 
1390     g_object_get(G_OBJECT(list->priv->conf),
1391                   "replace-playlist", &val,
1392                   NULL);
1393 
1394     gtk_check_menu_item_set_active(replace, val);
1395     g_signal_connect(replace, "activate",
1396                       G_CALLBACK(replace_list_activated_cb), list->priv->conf);
1397 
1398     g_object_get(G_OBJECT(list->priv->conf),
1399                   "play-opened-files", &val,
1400                   NULL);
1401     gtk_check_menu_item_set_active(play_opened, val);
1402     g_signal_connect(play_opened, "activate",
1403                       G_CALLBACK(play_opened_files_activated_cb), list->priv->conf);
1404 
1405     g_object_get(G_OBJECT(list->priv->conf),
1406                   "remember-playlist", &val,
1407                   NULL);
1408     gtk_check_menu_item_set_active(remember, val);
1409     g_signal_connect(remember, "activate",
1410                       G_CALLBACK(remember_playlist_activated_cb), list->priv->conf);
1411 
1412     g_signal_connect_swapped(clear, "activate",
1413                               G_CALLBACK(parole_media_list_clear_list), list);
1414 
1415     g_signal_connect_swapped(menu, "selection-done",
1416                               G_CALLBACK(parole_media_list_destroy_menu), menu);
1417 
1418 #if GTK_CHECK_VERSION(3, 22, 0)
1419     gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
1420 #else
1421     gtk_menu_popup(GTK_MENU(menu),
1422                     NULL, NULL,
1423                     NULL, NULL,
1424                     button, activate_time);
1425 #endif
1426 }
1427 
1428 gboolean
parole_media_list_button_release_event(GtkWidget * widget,GdkEventButton * ev,ParoleMediaList * list)1429 parole_media_list_button_release_event(GtkWidget *widget, GdkEventButton *ev, ParoleMediaList *list) {
1430     if ( ev->button == 3 ) {
1431         parole_media_list_show_menu(list, ev);
1432         return TRUE;
1433     }
1434 
1435     return FALSE;
1436 }
1437 
1438 static void
parole_media_list_select_path(ParoleMediaList * list,gboolean disc,GtkTreePath * path)1439 parole_media_list_select_path(ParoleMediaList *list, gboolean disc, GtkTreePath *path) {
1440     if (disc) {
1441         gtk_tree_selection_select_path(list->priv->disc_sel, path);
1442         gtk_tree_view_set_cursor(GTK_TREE_VIEW(list->priv->disc_view), path, NULL, FALSE);
1443     } else {
1444         gtk_tree_selection_select_path(list->priv->sel, path);
1445         gtk_tree_view_set_cursor(GTK_TREE_VIEW(list->priv->view), path, NULL, FALSE);
1446     }
1447 }
1448 
1449 static GtkTreeRowReference *
parole_media_list_get_row_reference_from_iter(ParoleMediaList * list,GtkTreeIter * iter,gboolean select_path)1450 parole_media_list_get_row_reference_from_iter(ParoleMediaList *list, GtkTreeIter *iter, gboolean select_path) {
1451     GtkTreePath *path;
1452     GtkTreeRowReference *row;
1453     GtkTreeModelSort *tree_model_sort;
1454 
1455     if (gtk_notebook_get_current_page(GTK_NOTEBOOK(list->priv->playlist_notebook)) == 0)
1456         tree_model_sort = GTK_TREE_MODEL_SORT(gtk_tree_view_get_model(GTK_TREE_VIEW(list->priv->view)));
1457     else
1458         tree_model_sort = GTK_TREE_MODEL_SORT(gtk_tree_view_get_model(GTK_TREE_VIEW(list->priv->disc_view)));
1459 
1460     path = gtk_tree_model_get_path(GTK_TREE_MODEL(tree_model_sort), iter);
1461     row = gtk_tree_row_reference_new(GTK_TREE_MODEL(tree_model_sort), path);
1462 
1463     if (select_path)
1464         parole_media_list_select_path(list, FALSE, path);
1465 
1466     gtk_tree_path_free(path);
1467 
1468     return row;
1469 }
1470 
1471 static void
parole_media_list_finalize(GObject * object)1472 parole_media_list_finalize(GObject *object) {
1473     ParoleMediaList *list;
1474 
1475     list = PAROLE_MEDIA_LIST(object);
1476 
1477     dbus_g_connection_unref(list->priv->bus);
1478 
1479     G_OBJECT_CLASS(parole_media_list_parent_class)->finalize(object);
1480 }
1481 
1482 static void
parole_media_list_class_init(ParoleMediaListClass * klass)1483 parole_media_list_class_init(ParoleMediaListClass *klass) {
1484     GObjectClass *object_class = G_OBJECT_CLASS(klass);
1485 
1486     object_class->finalize = parole_media_list_finalize;
1487 
1488     signals[MEDIA_ACTIVATED] =
1489         g_signal_new("media-activated",
1490                       PAROLE_TYPE_MEDIA_LIST,
1491                       G_SIGNAL_RUN_LAST,
1492                       G_STRUCT_OFFSET(ParoleMediaListClass, media_activated),
1493                       NULL, NULL,
1494                       g_cclosure_marshal_VOID__POINTER,
1495                       G_TYPE_NONE, 1, G_TYPE_POINTER);
1496 
1497     signals[MEDIA_CURSOR_CHANGED] =
1498         g_signal_new("media-cursor-changed",
1499                       PAROLE_TYPE_MEDIA_LIST,
1500                       G_SIGNAL_RUN_LAST,
1501                       G_STRUCT_OFFSET(ParoleMediaListClass, media_cursor_changed),
1502                       NULL, NULL,
1503                       g_cclosure_marshal_VOID__BOOLEAN,
1504                       G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
1505 
1506     signals[URI_OPENED] =
1507         g_signal_new("uri-opened",
1508                       PAROLE_TYPE_MEDIA_LIST,
1509                       G_SIGNAL_RUN_LAST,
1510                       G_STRUCT_OFFSET(ParoleMediaListClass, uri_opened),
1511                       NULL, NULL,
1512                       g_cclosure_marshal_VOID__STRING,
1513                       G_TYPE_NONE, 1, G_TYPE_STRING);
1514 
1515     signals[ISO_OPENED] =
1516         g_signal_new("iso-opened",
1517                       PAROLE_TYPE_MEDIA_LIST,
1518                       G_SIGNAL_RUN_LAST,
1519                       G_STRUCT_OFFSET(ParoleMediaListClass, iso_opened),
1520                       NULL, NULL,
1521                       g_cclosure_marshal_VOID__STRING,
1522                       G_TYPE_NONE, 1, G_TYPE_STRING);
1523 
1524     signals[KEY_FWD_EVENT] =
1525         g_signal_new("key-forward",
1526                       PAROLE_TYPE_MEDIA_LIST,
1527                       G_SIGNAL_RUN_LAST,
1528                       G_STRUCT_OFFSET(ParoleMediaListClass, key_fwd_event),
1529                       NULL, NULL,
1530                       g_cclosure_marshal_VOID__POINTER,
1531                       G_TYPE_NONE, 1, G_TYPE_POINTER);
1532 
1533     parole_media_list_dbus_class_init(klass);
1534 }
1535 
1536 static void
parole_media_list_playing_cell_data_func(GtkTreeViewColumn * column,GtkCellRenderer * renderer,GtkTreeModel * tree_model,GtkTreeIter * iter,GtkWidget * view)1537 parole_media_list_playing_cell_data_func(GtkTreeViewColumn *column,
1538                                          GtkCellRenderer *renderer,
1539                                          GtkTreeModel *tree_model,
1540                                          GtkTreeIter *iter,
1541                                          GtkWidget *view) {
1542     const char *name = NULL;
1543 
1544     gint state = 0;
1545     gtk_tree_model_get(tree_model, iter, STATE_COL, &state, -1);
1546 
1547     switch (state) {
1548     case PAROLE_MEDIA_STATE_NONE:
1549         name = NULL;
1550         break;
1551     case PAROLE_MEDIA_STATE_PAUSED:
1552         name = "media-playback-pause-symbolic";
1553         break;
1554     case PAROLE_MEDIA_STATE_PLAYING:
1555         name = "media-playback-start-symbolic";
1556         break;
1557     default:
1558         name = NULL;
1559         break;
1560     }
1561 
1562     g_object_set(renderer, "icon-name", name, NULL);
1563 }
1564 
1565 static void
parole_media_list_setup_view(ParoleMediaList * list)1566 parole_media_list_setup_view(ParoleMediaList *list) {
1567     GtkTreeSelection *sel, *disc_sel;
1568     GtkListStore *list_store, *disc_list_store;
1569     GtkTreeModel *list_sort, *disc_list_sort;
1570     GtkCellRenderer *renderer, *disc_renderer;
1571 
1572     list_store = gtk_list_store_new(COL_NUMBERS, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_OBJECT, G_TYPE_UINT, G_TYPE_UINT);
1573     disc_list_store = gtk_list_store_new(COL_NUMBERS, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_OBJECT, G_TYPE_UINT, G_TYPE_UINT);
1574 
1575     list_sort = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(list_store));
1576     disc_list_sort = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(disc_list_store));
1577 
1578     gtk_tree_view_set_model(GTK_TREE_VIEW(list->priv->view), GTK_TREE_MODEL(list_sort));
1579     gtk_tree_view_set_model(GTK_TREE_VIEW(list->priv->disc_view), GTK_TREE_MODEL(disc_list_sort));
1580 
1581     gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(list_sort), SORT_COL, GTK_SORT_ASCENDING);
1582     gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(disc_list_sort), SORT_COL, GTK_SORT_ASCENDING);
1583 
1584     list->priv->col = gtk_tree_view_column_new();
1585     list->priv->disc_col = gtk_tree_view_column_new();
1586 
1587     renderer = gtk_cell_renderer_pixbuf_new();
1588     disc_renderer = gtk_cell_renderer_pixbuf_new();
1589     g_object_set(renderer, "stock-size", GTK_ICON_SIZE_MENU, NULL);
1590     g_object_set(disc_renderer, "stock-size", GTK_ICON_SIZE_MENU, NULL);
1591 
1592     gtk_tree_view_column_pack_start(list->priv->col, renderer, FALSE);
1593     gtk_tree_view_column_pack_start(list->priv->disc_col, disc_renderer, FALSE);
1594     gtk_tree_view_column_set_cell_data_func(list->priv->col, renderer,
1595                              (GtkTreeCellDataFunc)
1596                              parole_media_list_playing_cell_data_func,
1597                              list->priv->view,
1598                              NULL);
1599     gtk_tree_view_column_set_cell_data_func(list->priv->disc_col, disc_renderer,
1600                              (GtkTreeCellDataFunc)
1601                              parole_media_list_playing_cell_data_func,
1602                              list->priv->disc_view,
1603                              NULL);
1604 
1605     /**
1606      * Name col
1607      *
1608      **/
1609     renderer = gtk_cell_renderer_text_new();
1610     disc_renderer = gtk_cell_renderer_text_new();
1611 
1612     gtk_tree_view_column_pack_start(list->priv->col, renderer, TRUE);
1613     gtk_tree_view_column_set_attributes(list->priv->col, renderer, "text", NAME_COL, NULL);
1614     g_object_set(renderer,
1615                   "ellipsize", PANGO_ELLIPSIZE_END,
1616                   NULL);
1617 
1618     gtk_tree_view_column_pack_start(list->priv->disc_col, disc_renderer, TRUE);
1619     gtk_tree_view_column_set_attributes(list->priv->disc_col, disc_renderer, "text", NAME_COL, NULL);
1620     g_object_set(disc_renderer,
1621                   "ellipsize", PANGO_ELLIPSIZE_END,
1622                   NULL);
1623 
1624     /* Make the name column the search target */
1625     gtk_tree_view_set_search_column(GTK_TREE_VIEW(list->priv->view), 1);
1626     gtk_tree_view_set_search_column(GTK_TREE_VIEW(list->priv->disc_view), 1);
1627 
1628     /**
1629      * Media length
1630      *
1631      **/
1632     renderer = gtk_cell_renderer_text_new();
1633     disc_renderer = gtk_cell_renderer_text_new();
1634 
1635     gtk_tree_view_column_pack_start(list->priv->col, renderer, FALSE);
1636     gtk_tree_view_column_pack_start(list->priv->disc_col, disc_renderer, FALSE);
1637     gtk_tree_view_column_set_attributes(list->priv->col, renderer, "text", LENGTH_COL, NULL);
1638     gtk_tree_view_column_set_attributes(list->priv->disc_col, disc_renderer, "text", LENGTH_COL, NULL);
1639 
1640     gtk_tree_view_append_column(GTK_TREE_VIEW(list->priv->view), list->priv->col);
1641     gtk_tree_view_append_column(GTK_TREE_VIEW(list->priv->disc_view), list->priv->disc_col);
1642 
1643     gtk_tree_view_column_set_title(list->priv->col,
1644             g_strdup_printf(ngettext("Playlist (%i item)", "Playlist (%i items)", 0), 0));
1645     gtk_tree_view_column_set_title(list->priv->disc_col,
1646             g_strdup_printf(ngettext("Playlist (%i chapter)", "Playlist (%i chapters)", 0), 0));
1647 
1648     gtk_drag_dest_set(list->priv->view, GTK_DEST_DEFAULT_ALL, target_entry, G_N_ELEMENTS(target_entry),
1649                        GDK_ACTION_COPY | GDK_ACTION_MOVE);
1650 
1651     list->priv->sel = sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(list->priv->view));
1652     list->priv->disc_sel = disc_sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(list->priv->disc_view));
1653     gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
1654 
1655     g_signal_connect(sel, "changed",
1656               G_CALLBACK(parole_media_list_selection_changed_cb), list);
1657     g_signal_connect(disc_sel, "changed",
1658               G_CALLBACK(parole_media_list_selection_changed_cb), list);
1659 
1660     list->priv->store = list_store;
1661     list->priv->disc_store = disc_list_store;
1662 }
1663 
1664 static void
parole_media_list_init(ParoleMediaList * list)1665 parole_media_list_init(ParoleMediaList *list) {
1666     GtkBuilder *builder;
1667     GtkWidget  *box;
1668 
1669     media_list = list;
1670     list->priv = parole_media_list_get_instance_private(list);
1671 
1672     list->priv->bus = parole_g_session_bus_get();
1673 
1674     list->priv->conf = parole_conf_new();
1675 
1676     builder = parole_builder_new_from_string(playlist_ui, playlist_ui_length);
1677 
1678     list->priv->playlist_controls = GTK_WIDGET(gtk_builder_get_object(builder, "playlist_controls"));
1679     list->priv->playlist_notebook = GTK_WIDGET(gtk_builder_get_object(builder, "playlist_notebook"));
1680 
1681     list->priv->view = GTK_WIDGET(gtk_builder_get_object(builder, "media-list"));
1682     list->priv->disc_view = GTK_WIDGET(gtk_builder_get_object(builder, "disc-list"));
1683 
1684     box = GTK_WIDGET(gtk_builder_get_object(builder, "playlist-box"));
1685 
1686     parole_media_list_setup_view(list);
1687 
1688     gtk_box_pack_start(GTK_BOX(list), box, TRUE, TRUE, 0);
1689 
1690     list->priv->remove_button = GTK_WIDGET(gtk_builder_get_object(builder, "remove-media"));
1691     list->priv->clear_button = GTK_WIDGET(gtk_builder_get_object(builder, "clear-media"));
1692     list->priv->repeat_button = GTK_WIDGET(gtk_builder_get_object(builder, "repeat-media"));
1693     list->priv->shuffle_button = GTK_WIDGET(gtk_builder_get_object(builder, "shuffle-media"));
1694 
1695     gtk_builder_connect_signals(builder, list);
1696 
1697     g_object_unref(builder);
1698 
1699     gtk_widget_show_all(GTK_WIDGET(list));
1700 
1701     parole_media_list_dbus_init(list);
1702 }
1703 
1704 GtkWidget *
parole_media_list_get(void)1705 parole_media_list_get(void) {
1706     static gpointer list = NULL;
1707 
1708     if ( G_LIKELY(list != NULL ) ) {
1709         g_object_ref(list);
1710     } else {
1711         list = g_object_new(PAROLE_TYPE_MEDIA_LIST, NULL);
1712         g_object_add_weak_pointer(list, &list);
1713     }
1714 
1715     return GTK_WIDGET (list);
1716 }
1717 
parole_media_list_load(ParoleMediaList * list)1718 void parole_media_list_load(ParoleMediaList *list) {
1719     gboolean    load_saved_list;
1720     gboolean    play;
1721     GSList     *fileslist = NULL;
1722 
1723     g_object_get(G_OBJECT(list->priv->conf),
1724                  "play-opened-files", &play,
1725                  "remember-playlist", &load_saved_list,
1726                  NULL);
1727 
1728     if ( load_saved_list ) {
1729         gchar *playlist_file;
1730 
1731         playlist_file = xfce_resource_save_location(XFCE_RESOURCE_DATA,
1732                                      PAROLE_AUTO_SAVED_PLAYLIST,
1733                                  FALSE);
1734         if ( playlist_file ) {
1735             fileslist = parole_pl_parser_parse_from_file_by_extension(playlist_file);
1736             g_free(playlist_file);
1737 
1738             parole_media_list_files_open(list, fileslist, FALSE, play);
1739             g_slist_free(fileslist);
1740         }
1741     }
1742 }
1743 
1744 gboolean
parole_media_list_add_by_path(ParoleMediaList * list,const gchar * path,gboolean emit)1745 parole_media_list_add_by_path(ParoleMediaList *list, const gchar *path, gboolean emit) {
1746     GSList *files_list = NULL;
1747     GtkFileFilter *filter;
1748     guint len;
1749     gboolean ret = FALSE;
1750     gchar *full_path;
1751 
1752     filter = parole_get_supported_media_filter();
1753     g_object_ref_sink(filter);
1754 
1755     if (g_path_is_absolute(path)) {
1756         full_path = g_strdup(path);
1757     } else {
1758         if (g_file_test(g_strjoin("/", g_get_current_dir(), g_strdup(path), NULL), G_FILE_TEST_EXISTS)) {
1759             full_path = g_strjoin("/", g_get_current_dir(), g_strdup(path), NULL);
1760         } else {
1761             full_path = g_strdup(path);
1762         }
1763     }
1764     TRACE("Path=%s", full_path);
1765 
1766     parole_get_media_files(filter, full_path, TRUE, &files_list);
1767 
1768     parole_media_list_files_open(list, files_list, FALSE, emit);
1769 
1770     len = g_slist_length(files_list);
1771     ret = len == 0 ? FALSE : TRUE;
1772 
1773     g_free(full_path);
1774 
1775     g_object_unref(filter);
1776     g_slist_free(files_list);
1777     return ret;
1778 }
1779 
1780 static gint
path_to_int(GtkTreePath * path)1781 path_to_int(GtkTreePath *path) {
1782     gchar *path_str = gtk_tree_path_to_string (path);
1783     gint   path_int = atoi(path_str);
1784     g_free(path_str);
1785     return path_int;
1786 }
1787 
1788 static GtkTreePath *
int_to_path(gint path_int)1789 int_to_path(gint path_int) {
1790     gchar *path_str;
1791     GtkTreePath *path;
1792 
1793     if (path_int < 0)
1794         return NULL;
1795 
1796     path_str = g_strdup_printf("%i", path_int);
1797     path = gtk_tree_path_new_from_string (path_str);
1798     g_free(path_str);
1799 
1800     return path;
1801 }
1802 
parole_media_list_get_next_row(ParoleMediaList * list,GtkTreeRowReference * row,gboolean repeat)1803 GtkTreeRowReference *parole_media_list_get_next_row(ParoleMediaList *list, GtkTreeRowReference *row, gboolean repeat) {
1804     GtkTreeRowReference *next = NULL;
1805     GtkTreePath *path, *new_path;
1806     GtkTreeModelSort *tree_model_sort;
1807     GtkTreeIter iter;
1808 
1809     g_return_val_if_fail(row != NULL, NULL);
1810 
1811     if ( !gtk_tree_row_reference_valid (row) )
1812         return NULL;
1813 
1814     path = gtk_tree_row_reference_get_path(row);
1815     new_path = int_to_path(path_to_int(path) + 1);
1816 
1817     if (gtk_notebook_get_current_page(GTK_NOTEBOOK(list->priv->playlist_notebook)) == 0)
1818         tree_model_sort = GTK_TREE_MODEL_SORT(gtk_tree_view_get_model(GTK_TREE_VIEW(list->priv->view)));
1819     else
1820         tree_model_sort = GTK_TREE_MODEL_SORT(gtk_tree_view_get_model(GTK_TREE_VIEW(list->priv->disc_view)));
1821 
1822     if (new_path && gtk_tree_model_get_iter(GTK_TREE_MODEL(tree_model_sort), &iter, new_path)) {
1823         next = gtk_tree_row_reference_new(GTK_TREE_MODEL(tree_model_sort), new_path);
1824     } else if ( repeat ) { /* Repeat playing ?*/
1825         if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(tree_model_sort), &iter)) {
1826             next = parole_media_list_get_row_reference_from_iter(list, &iter, TRUE);
1827         }
1828     }
1829 
1830     gtk_tree_path_free(new_path);
1831     gtk_tree_path_free(path);
1832 
1833     return next;
1834 }
1835 
parole_media_list_get_prev_row(ParoleMediaList * list,GtkTreeRowReference * row,gboolean repeat)1836 GtkTreeRowReference *parole_media_list_get_prev_row(ParoleMediaList *list, GtkTreeRowReference *row, gboolean repeat) {
1837     GtkTreeRowReference *prev = NULL;
1838     GtkTreePath *path, *new_path;
1839     GtkTreeModelSort *tree_model_sort;
1840     GtkTreeIter iter;
1841 
1842     g_return_val_if_fail(row != NULL, NULL);
1843 
1844     if ( !gtk_tree_row_reference_valid (row) )
1845         return NULL;
1846 
1847     path = gtk_tree_row_reference_get_path(row);
1848     new_path = int_to_path(path_to_int(path) - 1);
1849 
1850     if (gtk_notebook_get_current_page(GTK_NOTEBOOK(list->priv->playlist_notebook)) == 0)
1851         tree_model_sort = GTK_TREE_MODEL_SORT(gtk_tree_view_get_model(GTK_TREE_VIEW(list->priv->view)));
1852     else
1853         tree_model_sort = GTK_TREE_MODEL_SORT(gtk_tree_view_get_model(GTK_TREE_VIEW(list->priv->disc_view)));
1854 
1855     if (new_path && gtk_tree_model_get_iter(GTK_TREE_MODEL(tree_model_sort), &iter, new_path)) {
1856         prev = gtk_tree_row_reference_new(GTK_TREE_MODEL(tree_model_sort), new_path);
1857     } else if ( repeat ) { /* Repeat playing ?*/
1858         gtk_tree_path_free(new_path);
1859         new_path = int_to_path(gtk_tree_model_iter_n_children(GTK_TREE_MODEL(tree_model_sort), NULL) - 1);
1860         if (new_path && gtk_tree_model_get_iter(GTK_TREE_MODEL(tree_model_sort), &iter, new_path)) {
1861             prev = gtk_tree_row_reference_new(GTK_TREE_MODEL(tree_model_sort), new_path);
1862         }
1863     }
1864 
1865     gtk_tree_path_free(new_path);
1866     gtk_tree_path_free(path);
1867 
1868     return prev;
1869 }
1870 
parole_media_list_get_row_n(ParoleMediaList * list,gint wanted_row)1871 GtkTreeRowReference *parole_media_list_get_row_n(ParoleMediaList *list, gint wanted_row) {
1872     GtkTreeRowReference *row = NULL;
1873     GtkTreePath *path;
1874     GtkTreeIter iter;
1875     GtkTreeModelSort *model;
1876 
1877     if (wanted_row == -1)
1878         return NULL;
1879 
1880     model = parole_media_list_get_current_tree_model_sort(list);
1881 
1882     path = gtk_tree_path_new_from_string(g_strdup_printf("%i", wanted_row));
1883 
1884     if ( gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path))
1885         row = gtk_tree_row_reference_new(GTK_TREE_MODEL(model), path);
1886 
1887     gtk_tree_path_free(path);
1888 
1889     if ( !gtk_tree_row_reference_valid (row) )
1890         return NULL;
1891 
1892     return row;
1893 }
1894 
parole_media_list_is_selected_row(ParoleMediaList * list)1895 gboolean parole_media_list_is_selected_row(ParoleMediaList *list) {
1896     if (gtk_notebook_get_current_page(GTK_NOTEBOOK(list->priv->playlist_notebook)) == 0)
1897         return gtk_tree_selection_count_selected_rows (list->priv->sel) > 0;
1898     else
1899         return gtk_tree_selection_count_selected_rows (list->priv->disc_sel) > 0;
1900 }
1901 
parole_media_list_is_empty(ParoleMediaList * list)1902 gboolean parole_media_list_is_empty(ParoleMediaList *list) {
1903     GtkTreeIter iter;
1904     GtkTreeModelSort *model;
1905 
1906     model = parole_media_list_get_current_tree_model_sort(list);
1907 
1908     return !gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter);
1909 }
1910 
1911 /**
1912  * parole_media_list_get_first_row:
1913  * @list: a #ParoleMediaList
1914  *
1915  *
1916  * Returns: a #GtkTreeRowReference of the first row in the media list
1917  **/
parole_media_list_get_first_row(ParoleMediaList * list)1918 GtkTreeRowReference *parole_media_list_get_first_row(ParoleMediaList *list) {
1919     GtkTreeRowReference *row = NULL;
1920     GtkTreeIter iter;
1921     GtkTreeModelSort *model;
1922 
1923     model = parole_media_list_get_current_tree_model_sort(list);
1924 
1925     if ( gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter) )
1926         row = parole_media_list_get_row_reference_from_iter(list, &iter, TRUE);
1927 
1928     return row;
1929 }
1930 
1931 /**
1932  * parole_media_list_get_selected_row:
1933  * @list: a #ParoleMediaList
1934  *
1935  *
1936  * Returns: a #GtkTreeRowReference of the selected row
1937  **/
parole_media_list_get_selected_row(ParoleMediaList * list)1938 GtkTreeRowReference *parole_media_list_get_selected_row(ParoleMediaList *list) {
1939     return parole_media_list_get_first_selected_row (list);
1940 }
1941 
1942 /**
1943  * parole_media_list_get_selected_file:
1944  * @list: a #ParoleMediaList
1945  *
1946  *
1947  * Returns: a #ParoleFile of the selected row
1948  **/
parole_media_list_get_selected_file(ParoleMediaList * list)1949 ParoleFile *parole_media_list_get_selected_file(ParoleMediaList *list) {
1950     return parole_media_list_get_first_selected_file (list);
1951 }
1952 
parole_media_list_sorted_row_reference_get_path(ParoleMediaList * list,GtkTreeRowReference * row)1953 GtkTreePath *parole_media_list_sorted_row_reference_get_path(ParoleMediaList *list, GtkTreeRowReference *row) {
1954     GtkTreePath *sorted_path, *path;
1955     GtkTreeModelSort *sort;
1956 
1957     sort = parole_media_list_get_current_tree_model_sort (list);
1958     sorted_path = gtk_tree_row_reference_get_path(row);
1959 
1960     path = gtk_tree_model_sort_convert_path_to_child_path(sort, sorted_path);
1961     return path;
1962 }
1963 
parole_media_list_select_row(ParoleMediaList * list,GtkTreeRowReference * row)1964 void parole_media_list_select_row(ParoleMediaList *list, GtkTreeRowReference *row) {
1965     GtkTreePath *path;
1966 
1967     if (gtk_tree_row_reference_valid(row)) {
1968         path = gtk_tree_row_reference_get_path(row);
1969         parole_media_list_select_path(list,
1970             gtk_notebook_get_current_page(GTK_NOTEBOOK(list->priv->playlist_notebook)) == 1,
1971             path);
1972         gtk_tree_path_free(path);
1973     }
1974 }
1975 
parole_media_list_store_get_uint(ParoleMediaList * list,GtkTreeRowReference * row,guint col)1976 static guint parole_media_list_store_get_uint(ParoleMediaList *list, GtkTreeRowReference *row, guint col) {
1977     GtkTreeIter iter;
1978     GtkTreePath *path;
1979     guint val = 0;
1980     GtkTreeModelSort *model;
1981 
1982     model = parole_media_list_get_current_tree_model_sort(list);
1983 
1984     if (gtk_tree_row_reference_valid(row)) {
1985         path = parole_media_list_sorted_row_reference_get_path(list, row);
1986 
1987         if ( gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path) )
1988             gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, col, &val, -1);
1989 
1990         gtk_tree_path_free(path);
1991     }
1992 
1993     return val;
1994 }
1995 
1996 static void
parole_media_list_store_set_int(ParoleMediaList * list,GtkTreeRowReference * row,guint col,gint value)1997 parole_media_list_store_set_int(ParoleMediaList *list, GtkTreeRowReference *row, guint col, gint value) {
1998     GtkTreeIter iter;
1999     GtkTreePath *path;
2000     GtkListStore *model;
2001 
2002     model = parole_media_list_get_current_list_store(list);
2003 
2004     if ( gtk_tree_row_reference_valid(row) ) {
2005         path = parole_media_list_sorted_row_reference_get_path(list, row);
2006 
2007         if ( gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path) )
2008             gtk_list_store_set(model, &iter, col, value, -1);
2009 
2010         gtk_tree_path_free(path);
2011     }
2012 }
2013 
2014 static void
parole_media_list_store_set_str(ParoleMediaList * list,GtkTreeRowReference * row,guint col,const gchar * value)2015 parole_media_list_store_set_str(ParoleMediaList *list, GtkTreeRowReference *row, guint col, const gchar *value) {
2016     GtkTreeIter iter;
2017     GtkTreePath *path;
2018     GtkListStore *model;
2019 
2020     model = parole_media_list_get_current_list_store(list);
2021 
2022     if ( gtk_tree_row_reference_valid(row) ) {
2023         path = parole_media_list_sorted_row_reference_get_path(list, row);
2024 
2025         if (gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path))
2026             gtk_list_store_set(GTK_LIST_STORE(model), &iter, col, value, -1);
2027 
2028         gtk_tree_path_free(path);
2029     }
2030 }
2031 
parole_media_list_store_get_str(ParoleMediaList * list,GtkTreeRowReference * row,guint col)2032 static gchar* parole_media_list_store_get_str(ParoleMediaList *list, GtkTreeRowReference *row, guint col) {
2033     GtkTreeIter iter;
2034     GtkTreePath *path;
2035     gchar *str = NULL;
2036     GtkTreeModelSort *model;
2037 
2038     model = parole_media_list_get_current_tree_model_sort(list);
2039 
2040     if (gtk_tree_row_reference_valid(row)) {
2041         path = parole_media_list_sorted_row_reference_get_path(list, row);
2042 
2043         if (gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path))
2044             gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, col, &str, -1);
2045 
2046         gtk_tree_path_free(path);
2047     }
2048 
2049     return str;
2050 }
2051 
parole_media_list_set_row_playback_state(ParoleMediaList * list,GtkTreeRowReference * row,gint state)2052 void parole_media_list_set_row_playback_state(ParoleMediaList *list, GtkTreeRowReference *row, gint state) {
2053     parole_media_list_store_set_int(list, row, STATE_COL, state);
2054 }
2055 
parole_media_list_get_row_name(ParoleMediaList * list,GtkTreeRowReference * row)2056 gchar* parole_media_list_get_row_name(ParoleMediaList *list, GtkTreeRowReference *row) {
2057     return parole_media_list_store_get_str(list, row, NAME_COL);
2058 }
2059 
parole_media_list_set_row_name(ParoleMediaList * list,GtkTreeRowReference * row,const gchar * name)2060 void parole_media_list_set_row_name(ParoleMediaList *list, GtkTreeRowReference *row, const gchar *name) {
2061     parole_media_list_store_set_str(list, row, NAME_COL, name);
2062 }
2063 
parole_media_list_set_row_length(ParoleMediaList * list,GtkTreeRowReference * row,const gchar * len)2064 void parole_media_list_set_row_length(ParoleMediaList *list, GtkTreeRowReference *row, const gchar *len) {
2065     parole_media_list_store_set_str(list, row, LENGTH_COL, len);
2066 }
2067 
parole_media_list_get_row_entry_order(ParoleMediaList * list,GtkTreeRowReference * row)2068 guint parole_media_list_get_row_entry_order(ParoleMediaList *list, GtkTreeRowReference *row) {
2069     return parole_media_list_store_get_uint(list, row, ENTRY_COL);
2070 }
2071 
parole_media_list_open(ParoleMediaList * list)2072 void parole_media_list_open(ParoleMediaList *list) {
2073     parole_media_list_open_internal(list);
2074 }
2075 
parole_media_list_open_location(ParoleMediaList * list)2076 void parole_media_list_open_location(ParoleMediaList *list) {
2077     parole_media_list_open_location_internal(list);
2078 }
2079 
parole_media_list_add_files(ParoleMediaList * list,gchar ** filenames,gboolean enqueue)2080 gboolean parole_media_list_add_files(ParoleMediaList *list, gchar **filenames, gboolean enqueue) {
2081     guint i;
2082     guint added = 0;
2083 
2084     for (i = 0; filenames && filenames[i] != NULL; i++) {
2085         /*
2086          * File on disk?
2087          */
2088         if ( !enqueue && g_file_test(filenames[i], G_FILE_TEST_EXISTS ) ) {
2089             added += parole_media_list_add_by_path(list, filenames[i], i == 0 ? TRUE : FALSE);
2090         } else if ( g_file_test(filenames[i], G_FILE_TEST_IS_DIR) ) {
2091             added += parole_media_list_add_by_path(list, filenames[i], i == 0 ? TRUE : FALSE);
2092         } else {
2093             ParoleFile *file;
2094             TRACE("File=%s", filenames[i]);
2095             file = parole_file_new(filenames[i]);
2096             if (enqueue) {
2097                 parole_media_list_add(list, file, FALSE, FALSE, FALSE);
2098             } else {
2099                 parole_media_list_add(list, file, FALSE, i == 0 ? TRUE : FALSE, i == 0 ? TRUE : FALSE);
2100             }
2101             added++;
2102         }
2103     }
2104 
2105     return added > 0;
2106 }
2107 
parole_media_list_save_list(ParoleMediaList * list)2108 void parole_media_list_save_list(ParoleMediaList *list) {
2109     gboolean save;
2110 
2111     g_object_get(G_OBJECT(list->priv->conf),
2112                  "remember-playlist", &save,
2113                  NULL);
2114 
2115     if ( save ) {
2116         GSList *fileslist;
2117         gchar *history;
2118 
2119         history = xfce_resource_save_location(XFCE_RESOURCE_DATA, PAROLE_AUTO_SAVED_PLAYLIST , TRUE);
2120 
2121         if ( !history ) {
2122             g_warning("Failed to save playlist");
2123             return;
2124         }
2125 
2126         fileslist = parole_media_list_get_files(list);
2127         if ( g_slist_length(fileslist) > 0 ) {
2128             parole_pl_parser_save_from_files(fileslist, history, PAROLE_PL_FORMAT_M3U);
2129             g_slist_foreach(fileslist, (GFunc) (void (*)(void)) g_object_unref, NULL);
2130         } else {
2131             // If the playlist is empty, delete the list.
2132             if (remove(history) != 0)
2133                 g_warning("Failed to remove playlist");
2134             g_free(history);
2135         }
2136         g_slist_free(fileslist);
2137     }
2138 }
2139 
2140 static gboolean  parole_media_list_dbus_add_files(ParoleMediaList *list,
2141                                                     gchar **in_files, gboolean enqueue,
2142                                                     GError **error);
2143 
2144 #include "src/org.parole.media.list.h"
2145 
2146 /*
2147  * DBus server implementation
2148  */
2149 static void
parole_media_list_dbus_class_init(ParoleMediaListClass * klass)2150 parole_media_list_dbus_class_init(ParoleMediaListClass *klass) {
2151     dbus_g_object_type_install_info(G_TYPE_FROM_CLASS(klass),
2152                      &dbus_glib_parole_media_list_object_info);
2153 }
2154 
2155 static void
parole_media_list_dbus_init(ParoleMediaList * list)2156 parole_media_list_dbus_init(ParoleMediaList *list) {
2157     dbus_g_connection_register_g_object(list->priv->bus,
2158                      PAROLE_DBUS_PLAYLIST_PATH,
2159                      G_OBJECT(list));
2160 }
2161 
parole_media_list_dbus_add_files(ParoleMediaList * list,gchar ** in_files,gboolean enqueue,GError ** error)2162 static gboolean  parole_media_list_dbus_add_files(ParoleMediaList *list,
2163                                                   gchar **in_files, gboolean enqueue,
2164                                                   GError **error) {
2165     TRACE("Adding files for DBus request");
2166     gtk_window_present(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(list))));
2167     parole_media_list_add_files(list, in_files, enqueue);
2168 
2169     return TRUE;
2170 }
2171 
parole_media_list_grab_focus(ParoleMediaList * list)2172 void parole_media_list_grab_focus(ParoleMediaList *list) {
2173     if (gtk_widget_get_visible (list->priv->view) )
2174         gtk_widget_grab_focus(list->priv->view);
2175 }
2176 
2177 static void
repeat_action_state_changed(GSimpleAction * simple,GVariant * value,gpointer user_data)2178 repeat_action_state_changed(GSimpleAction *simple, GVariant *value, gpointer user_data) {
2179     gboolean active = g_simple_toggle_action_get_active(simple);
2180 
2181     if (gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(media_list->priv->repeat_button)) != active) {
2182         gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(media_list->priv->repeat_button), active);
2183     }
2184 }
2185 
2186 static void
repeat_toggled(GtkWidget * widget,GSimpleAction * simple)2187 repeat_toggled(GtkWidget *widget, GSimpleAction *simple) {
2188     gboolean active = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(media_list->priv->repeat_button));
2189     g_simple_toggle_action_set_active(simple, active);
2190 }
2191 
parole_media_list_connect_repeat_action(ParoleMediaList * list,GSimpleAction * simple)2192 void parole_media_list_connect_repeat_action(ParoleMediaList *list, GSimpleAction *simple) {
2193     /* Connect state-changed event to modify widget */
2194     g_signal_connect(G_OBJECT(simple), "notify::state", G_CALLBACK(repeat_action_state_changed), NULL);
2195     /* Connect changed event to modify action */
2196     g_signal_connect(G_OBJECT(list->priv->repeat_button), "clicked", G_CALLBACK(repeat_toggled), simple);
2197 }
2198 
2199 static void
shuffle_tree_model(GtkTreeModel * model)2200 shuffle_tree_model (GtkTreeModel *model) {
2201     GtkTreeIter iter;
2202     guint n_children = gtk_tree_model_iter_n_children(model, NULL);
2203     guint sort = 0;
2204     guint state = 0;
2205     GRand *grand = g_rand_new();
2206 
2207     if (gtk_tree_model_get_iter_first(model, &iter)) {
2208       do {
2209         gtk_tree_model_get(model, &iter, STATE_COL, &state, -1);
2210         if (state != PAROLE_MEDIA_STATE_NONE)
2211             sort = 0;
2212         else
2213             sort = g_rand_int_range(grand, 100, n_children * 100);
2214         gtk_list_store_set(GTK_LIST_STORE(model),
2215                            &iter,
2216                            SORT_COL, sort,
2217                            -1);
2218       } while (gtk_tree_model_iter_next(model, &iter));
2219     }
2220 
2221     g_rand_free (grand);
2222 }
2223 
2224 static void
parole_media_list_shuffle_tree_model(ParoleMediaList * list)2225 parole_media_list_shuffle_tree_model (ParoleMediaList *list) {
2226     GtkTreeModel *model = parole_media_list_get_current_tree_model (list);
2227     shuffle_tree_model(GTK_TREE_MODEL(model));
2228 }
2229 
2230 static void
unshuffle_tree_model(GtkTreeModel * model)2231 unshuffle_tree_model (GtkTreeModel *model) {
2232     GtkTreeIter iter;
2233     gint  order = 0;
2234 
2235     if (gtk_tree_model_get_iter_first(model, &iter)) {
2236       do {
2237         gtk_tree_model_get(GTK_TREE_MODEL(model), &iter,
2238                            ENTRY_COL, &order,
2239                            -1);
2240         gtk_list_store_set(GTK_LIST_STORE(model), &iter,
2241                            SORT_COL, order,
2242                            -1);
2243       } while (gtk_tree_model_iter_next(model, &iter));
2244   }
2245 }
2246 
2247 static void
parole_media_list_unshuffle_tree_model(ParoleMediaList * list)2248 parole_media_list_unshuffle_tree_model (ParoleMediaList *list) {
2249     GtkTreeModel *model = parole_media_list_get_current_tree_model (list);
2250     unshuffle_tree_model(GTK_TREE_MODEL(model));
2251 }
2252 
2253 static void
shuffle_action_state_changed(GSimpleAction * simple,GVariant * value,gpointer user_data)2254 shuffle_action_state_changed(GSimpleAction *simple, GVariant *value, gpointer user_data) {
2255     gboolean active = g_simple_toggle_action_get_active(simple);
2256 
2257     if (gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(media_list->priv->shuffle_button)) != active) {
2258         gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(media_list->priv->shuffle_button), active);
2259     }
2260 
2261     if (active) {
2262         parole_media_list_shuffle_tree_model(media_list);
2263     } else {
2264         parole_media_list_unshuffle_tree_model(media_list);
2265     }
2266 }
2267 
2268 static void
shuffle_toggled(GtkWidget * widget,GSimpleAction * simple)2269 shuffle_toggled(GtkWidget *widget, GSimpleAction *simple) {
2270     gboolean active = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(media_list->priv->shuffle_button));
2271     g_simple_toggle_action_set_active(simple, active);
2272 }
2273 
parole_media_list_connect_shuffle_action(ParoleMediaList * list,GSimpleAction * simple)2274 void parole_media_list_connect_shuffle_action(ParoleMediaList *list, GSimpleAction *simple) {
2275     /* Connect state-changed event to modify widget */
2276     g_signal_connect(G_OBJECT(simple), "notify::state", G_CALLBACK(shuffle_action_state_changed), NULL);
2277     /* Connect changed event to modify action */
2278     g_signal_connect(G_OBJECT(list->priv->shuffle_button), "clicked", G_CALLBACK(shuffle_toggled), simple);
2279 }
2280