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), ¤t, 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), ¤t, path))
1139 parole_media_list_tree_iter_swap (GTK_TREE_MODEL (model), &iter, ¤t, 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), ¤t, 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), ¤t, path))
1191 parole_media_list_tree_iter_swap (GTK_TREE_MODEL (model), ¤t, &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