2006-12-13 Jonathan Matthew <jonathan@kaolin.wh9.net>
[rhythmbox.git] / sources / rb-library-source.c
blobe1622b3a39050d8f514b1b15a1c05fc2de99f6ff
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * arch-tag: Implementation of local file source object
5 * Copyright (C) 2002 Jorn Baayen <jorn@nl.linux.org>
6 * Copyright (C) 2003,2004 Colin Walters <walters@verbum.org>
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.
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.
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 St, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include "config.h"
26 #include <string.h>
28 #include <gtk/gtk.h>
29 #include <glib/gi18n.h>
30 #include <glib-object.h>
32 #ifdef ENABLE_TRACK_TRANSFER
33 #include <profiles/gnome-media-profiles.h>
34 #include <profiles/audio-profile-choose.h>
35 #endif
37 #include "rhythmdb.h"
38 #include "rb-debug.h"
39 #include "rb-dialog.h"
40 #include "rb-glade-helpers.h"
41 #include "rb-file-helpers.h"
42 #include "rb-util.h"
43 #include "eel-gconf-extensions.h"
44 #include "rb-library-source.h"
45 #include "rb-removable-media-manager.h"
46 #include "rb-auto-playlist-source.h"
48 static void rb_library_source_class_init (RBLibrarySourceClass *klass);
49 static void rb_library_source_init (RBLibrarySource *source);
50 static GObject *rb_library_source_constructor (GType type,
51 guint n_construct_properties,
52 GObjectConstructParam *construct_properties);
53 static void rb_library_source_dispose (GObject *object);
54 static void rb_library_source_finalize (GObject *object);
56 /* RBSource implementations */
57 static gboolean impl_show_popup (RBSource *source);
58 static GtkWidget *impl_get_config_widget (RBSource *source, RBShellPreferences *prefs);
59 static char *impl_get_browser_key (RBSource *source);
60 static const char *impl_get_paned_key (RBBrowserSource *source);
61 static gboolean impl_receive_drag (RBSource *source, GtkSelectionData *data);
62 static gboolean impl_can_paste (RBSource *asource);
63 #ifdef ENABLE_TRACK_TRANSFER
64 static void impl_paste (RBSource *source, GList *entries);
65 #endif
66 static guint impl_want_uri (RBSource *source, const char *uri);
67 static gboolean impl_add_uri (RBSource *source, const char *uri, const char *title, const char *genre);
69 static void rb_library_source_ui_prefs_sync (RBLibrarySource *source);
70 static void rb_library_source_preferences_sync (RBLibrarySource *source);
72 static void rb_library_source_library_location_changed (GConfClient *client,
73 guint cnxn_id,
74 GConfEntry *entry,
75 RBLibrarySource *source);
76 #ifdef ENABLE_TRACK_TRANSFER
77 static void rb_library_source_layout_path_changed (GConfClient *client,
78 guint cnxn_id,
79 GConfEntry *entry,
80 RBLibrarySource *source);
81 static void rb_library_source_layout_filename_changed (GConfClient *client,
82 guint cnxn_id,
83 GConfEntry *entry,
84 RBLibrarySource *source);
85 static void rb_library_source_edit_profile_clicked_cb (GtkButton *button,
86 RBLibrarySource *source);
87 #endif
88 static void rb_library_source_ui_pref_changed (GConfClient *client,
89 guint cnxn_id,
90 GConfEntry *entry,
91 RBLibrarySource *source);
92 static gboolean rb_library_source_library_location_cb (GtkEntry *entry,
93 GdkEventFocus *event,
94 RBLibrarySource *source);
95 static void rb_library_source_watch_toggled_cb (GtkToggleButton *button,
96 RBLibrarySource *source);
97 static void rb_library_source_sync_child_sources (RBLibrarySource *source);
98 #ifdef ENABLE_TRACK_TRANSFER
99 static void rb_library_source_path_changed_cb (GtkComboBox *box,
100 RBLibrarySource *source);
101 static void rb_library_source_filename_changed_cb (GtkComboBox *box,
102 RBLibrarySource *source);
103 static void rb_library_source_format_changed_cb (GtkWidget *widget,
104 RBLibrarySource *source);
105 #endif
107 #define CONF_UI_LIBRARY_DIR CONF_PREFIX "/ui/library"
108 #define CONF_STATE_LIBRARY_DIR CONF_PREFIX "/state/library"
109 #define CONF_STATE_LIBRARY_SORTING CONF_PREFIX "/state/library/sorting"
110 #define CONF_STATE_PANED_POSITION CONF_PREFIX "/state/library/paned_position"
111 #define CONF_STATE_SHOW_BROWSER CONF_PREFIX "/state/library/show_browser"
113 #ifdef ENABLE_TRACK_TRANSFER
114 typedef struct {
115 char *title;
116 char *path;
117 } LibraryPathElement;
119 const LibraryPathElement library_layout_paths[] = {
120 {N_("Artist/Artist - Album"), "%aa/%aa - %at"},
121 {N_("Artist/Album"), "%aa/%at"},
122 {N_("Artist - Album"), "%aa - %at"},
123 {N_("Album"), "%at"},
124 {N_("Artist"), "%aa"},
126 const int num_library_layout_paths = G_N_ELEMENTS (library_layout_paths);
128 const LibraryPathElement library_layout_filenames[] = {
129 {N_("Number - Title"), "%tN - %tt"},
130 {N_("Artist - Title"), "%ta - %tt"},
131 {N_("Artist - Number - Title"), "%ta - %tN - %tt"},
132 {N_("Artist (Album) - Number - Title"), "%ta (%at) - %tN - %tt"},
133 {N_("Title"), "%tt"},
134 {N_("Number. Artist - Title"), "%tN. %ta - %tt"},
136 const int num_library_layout_filenames = G_N_ELEMENTS (library_layout_filenames);
137 #endif
139 struct RBLibrarySourcePrivate
141 RhythmDB *db;
143 gboolean loading_prefs;
144 RBShellPreferences *shell_prefs;
146 GtkWidget *config_widget;
148 GList *child_sources;
150 GtkWidget *library_location_entry;
151 GtkWidget *watch_library_check;
152 #ifdef ENABLE_TRACK_TRANSFER
153 GtkWidget *layout_path_menu;
154 GtkWidget *layout_filename_menu;
155 GtkWidget *preferred_format_menu;
156 GtkWidget *layout_example_label;
157 #endif
159 guint library_location_notify_id;
160 guint ui_dir_notify_id;
161 #ifdef ENABLE_TRACK_TRANSFER
162 guint layout_path_notify_id;
163 guint layout_filename_notify_id;
164 #endif
167 #define RB_LIBRARY_SOURCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_LIBRARY_SOURCE, RBLibrarySourcePrivate))
168 G_DEFINE_TYPE (RBLibrarySource, rb_library_source, RB_TYPE_BROWSER_SOURCE)
170 static void
171 rb_library_source_class_init (RBLibrarySourceClass *klass)
173 GObjectClass *object_class = G_OBJECT_CLASS (klass);
174 RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
175 RBBrowserSourceClass *browser_source_class = RB_BROWSER_SOURCE_CLASS (klass);
177 object_class->dispose = rb_library_source_dispose;
178 object_class->finalize = rb_library_source_finalize;
179 object_class->constructor = rb_library_source_constructor;
181 source_class->impl_show_popup = impl_show_popup;
182 source_class->impl_get_config_widget = impl_get_config_widget;
183 source_class->impl_get_browser_key = impl_get_browser_key;
184 source_class->impl_receive_drag = impl_receive_drag;
185 source_class->impl_can_copy = (RBSourceFeatureFunc) rb_true_function;
186 source_class->impl_can_paste = (RBSourceFeatureFunc) impl_can_paste;
187 #ifdef ENABLE_TRACK_TRANSFER
188 source_class->impl_paste = impl_paste;
189 #endif
190 source_class->impl_want_uri = impl_want_uri;
191 source_class->impl_add_uri = impl_add_uri;
193 browser_source_class->impl_get_paned_key = impl_get_paned_key;
194 browser_source_class->impl_has_drop_support = (RBBrowserSourceFeatureFunc) rb_true_function;
196 g_type_class_add_private (klass, sizeof (RBLibrarySourcePrivate));
198 #ifdef ENABLE_TRACK_TRANSFER
199 gnome_media_profiles_init (eel_gconf_client_get_global ());
200 #endif
203 static void
204 rb_library_source_init (RBLibrarySource *source)
206 source->priv = RB_LIBRARY_SOURCE_GET_PRIVATE (source);
209 static void
210 rb_library_source_dispose (GObject *object)
212 RBLibrarySource *source;
213 source = RB_LIBRARY_SOURCE (object);
215 if (source->priv->shell_prefs) {
216 g_object_unref (source->priv->shell_prefs);
217 source->priv->shell_prefs = NULL;
220 if (source->priv->db) {
221 g_object_unref (source->priv->db);
222 source->priv->db = NULL;
225 G_OBJECT_CLASS (rb_library_source_parent_class)->dispose (object);
228 static void
229 rb_library_source_finalize (GObject *object)
231 RBLibrarySource *source;
233 g_return_if_fail (object != NULL);
234 g_return_if_fail (RB_IS_LIBRARY_SOURCE (object));
236 source = RB_LIBRARY_SOURCE (object);
238 g_return_if_fail (source->priv != NULL);
240 rb_debug ("finalizing library source");
241 eel_gconf_notification_remove (source->priv->ui_dir_notify_id);
242 eel_gconf_notification_remove (source->priv->library_location_notify_id);
243 #ifdef ENABLE_TRACK_TRANSFER
244 eel_gconf_notification_remove (source->priv->layout_path_notify_id);
245 eel_gconf_notification_remove (source->priv->layout_filename_notify_id);
246 #endif
248 G_OBJECT_CLASS (rb_library_source_parent_class)->finalize (object);
251 static gboolean
252 add_child_sources_idle (RBLibrarySource *source)
254 GDK_THREADS_ENTER ();
255 rb_library_source_sync_child_sources (source);
256 GDK_THREADS_LEAVE ();
258 return FALSE;
261 static GObject *
262 rb_library_source_constructor (GType type,
263 guint n_construct_properties,
264 GObjectConstructParam *construct_properties)
266 RBLibrarySource *source;
267 RBShell *shell;
268 RBEntryView *songs;
270 source = RB_LIBRARY_SOURCE (G_OBJECT_CLASS (rb_library_source_parent_class)
271 ->constructor (type, n_construct_properties, construct_properties));
273 g_object_get (source, "shell", &shell, NULL);
274 g_object_get (shell, "db", &source->priv->db, NULL);
276 rb_library_source_ui_prefs_sync (source);
278 source->priv->library_location_notify_id =
279 eel_gconf_notification_add (CONF_LIBRARY_LOCATION,
280 (GConfClientNotifyFunc) rb_library_source_library_location_changed, source);
282 source->priv->ui_dir_notify_id =
283 eel_gconf_notification_add (CONF_UI_LIBRARY_DIR,
284 (GConfClientNotifyFunc) rb_library_source_ui_pref_changed, source);
286 songs = rb_source_get_entry_view (RB_SOURCE (source));
288 rb_entry_view_append_column (songs, RB_ENTRY_VIEW_COL_RATING, FALSE);
289 rb_entry_view_append_column (songs, RB_ENTRY_VIEW_COL_LAST_PLAYED, FALSE);
290 rb_entry_view_append_column (songs, RB_ENTRY_VIEW_COL_FIRST_SEEN, FALSE);
292 g_idle_add ((GSourceFunc)add_child_sources_idle, source);
294 g_object_unref (shell);
296 return G_OBJECT (source);
299 RBSource *
300 rb_library_source_new (RBShell *shell)
302 RBSource *source;
303 GdkPixbuf *icon;
304 gint size;
305 RhythmDBEntryType entry_type;
307 entry_type = RHYTHMDB_ENTRY_TYPE_SONG;
309 gtk_icon_size_lookup (GTK_ICON_SIZE_LARGE_TOOLBAR, &size, NULL);
310 icon = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
311 "stock_music-library",
312 size,
313 0, NULL);
314 source = RB_SOURCE (g_object_new (RB_TYPE_LIBRARY_SOURCE,
315 "name", _("Library"),
316 "entry-type", entry_type,
317 "sorting-key", CONF_STATE_LIBRARY_SORTING,
318 "shell", shell,
319 "icon", icon,
320 NULL));
321 if (icon != NULL) {
322 g_object_unref (icon);
325 rb_shell_register_entry_type_for_source (shell, source, entry_type);
327 return source;
330 #ifdef ENABLE_TRACK_TRANSFER
331 static void
332 rb_library_source_edit_profile_clicked_cb (GtkButton *button, RBLibrarySource *source)
334 GtkWidget *dialog;
336 dialog = gm_audio_profiles_edit_new (eel_gconf_client_get_global (),
337 GTK_WINDOW (source->priv->shell_prefs));
338 gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
339 gtk_widget_show_all (dialog);
340 gtk_dialog_run (GTK_DIALOG (dialog));
342 #endif
344 static void
345 rb_library_source_location_button_clicked_cb (GtkButton *button, RBLibrarySource *source)
347 GtkWidget *dialog;
349 dialog = rb_file_chooser_new (_("Choose Library Location"), GTK_WINDOW (source->priv->shell_prefs),
350 GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, FALSE);
351 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
352 char *uri;
353 char *path;
355 uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
356 if (uri == NULL) {
357 uri = gtk_file_chooser_get_current_folder_uri (GTK_FILE_CHOOSER (dialog));
360 path = gnome_vfs_format_uri_for_display (uri);
362 gtk_entry_set_text (GTK_ENTRY (source->priv->library_location_entry), path);
363 rb_library_source_library_location_cb (GTK_ENTRY (source->priv->library_location_entry),
364 NULL, source);
365 g_free (uri);
366 g_free (path);
369 gtk_widget_destroy (GTK_WIDGET (dialog));
372 static GtkWidget *
373 impl_get_config_widget (RBSource *asource, RBShellPreferences *prefs)
375 RBLibrarySource *source = RB_LIBRARY_SOURCE (asource);
376 GtkWidget *tmp;
377 GladeXML *xml;
378 #ifdef ENABLE_TRACK_TRANSFER
379 GtkWidget *label;
380 int i;
381 #endif
383 if (source->priv->config_widget)
384 return source->priv->config_widget;
386 g_object_ref (G_OBJECT (prefs));
387 source->priv->shell_prefs = prefs;
389 xml = rb_glade_xml_new ("library-prefs.glade", "library_vbox", source);
390 source->priv->config_widget =
391 glade_xml_get_widget (xml, "library_vbox");
393 rb_glade_boldify_label (xml, "library_location_label");
395 source->priv->library_location_entry = glade_xml_get_widget (xml, "library_location_entry");
396 tmp = glade_xml_get_widget (xml, "library_location_button");
397 g_signal_connect (G_OBJECT (tmp),
398 "clicked",
399 G_CALLBACK (rb_library_source_location_button_clicked_cb),
400 asource);
401 g_signal_connect (G_OBJECT (source->priv->library_location_entry),
402 "focus-out-event",
403 G_CALLBACK (rb_library_source_library_location_cb),
404 asource);
406 source->priv->watch_library_check = glade_xml_get_widget (xml, "watch_library_check");
407 g_signal_connect (G_OBJECT (source->priv->watch_library_check),
408 "toggled",
409 G_CALLBACK (rb_library_source_watch_toggled_cb),
410 asource);
412 #ifdef ENABLE_TRACK_TRANSFER
413 rb_glade_boldify_label (xml, "library_structure_label");
415 tmp = glade_xml_get_widget (xml, "layout_path_menu_box");
416 label = glade_xml_get_widget (xml, "layout_path_menu_label");
417 source->priv->layout_path_menu = gtk_combo_box_new_text ();
418 gtk_box_pack_start_defaults (GTK_BOX (tmp), source->priv->layout_path_menu);
419 gtk_label_set_mnemonic_widget (GTK_LABEL (label), source->priv->layout_path_menu);
420 g_signal_connect (G_OBJECT (source->priv->layout_path_menu),
421 "changed",
422 G_CALLBACK (rb_library_source_path_changed_cb),
423 asource);
424 for (i = 0; i < num_library_layout_paths; i++) {
425 gtk_combo_box_append_text (GTK_COMBO_BOX (source->priv->layout_path_menu),
426 _(library_layout_paths[i].title));
429 tmp = glade_xml_get_widget (xml, "layout_filename_menu_box");
430 label = glade_xml_get_widget (xml, "layout_filename_menu_label");
431 source->priv->layout_filename_menu = gtk_combo_box_new_text ();
432 gtk_box_pack_start_defaults (GTK_BOX (tmp), source->priv->layout_filename_menu);
433 gtk_label_set_mnemonic_widget (GTK_LABEL (label), source->priv->layout_filename_menu);
434 g_signal_connect (G_OBJECT (source->priv->layout_filename_menu),
435 "changed",
436 G_CALLBACK (rb_library_source_filename_changed_cb),
437 asource);
438 for (i = 0; i < num_library_layout_filenames; i++) {
439 gtk_combo_box_append_text (GTK_COMBO_BOX (source->priv->layout_filename_menu),
440 _(library_layout_filenames[i].title));
443 tmp = glade_xml_get_widget (xml, "edit_profile_button");
444 g_signal_connect (G_OBJECT (tmp),
445 "clicked",
446 G_CALLBACK (rb_library_source_edit_profile_clicked_cb),
447 asource);
449 tmp = glade_xml_get_widget (xml, "preferred_format_menu_box");
450 label = glade_xml_get_widget (xml, "preferred_format_menu_label");
451 source->priv->preferred_format_menu = gm_audio_profile_choose_new ();
452 gtk_box_pack_start_defaults (GTK_BOX (tmp), source->priv->preferred_format_menu);
453 gtk_label_set_mnemonic_widget (GTK_LABEL (label), source->priv->preferred_format_menu);
454 g_signal_connect (G_OBJECT (source->priv->preferred_format_menu),
455 "changed",
456 G_CALLBACK (rb_library_source_format_changed_cb),
457 asource);
459 source->priv->layout_example_label = glade_xml_get_widget (xml, "layout_example_label");
460 #else
461 tmp = glade_xml_get_widget (xml, "library_structure_vbox");
462 gtk_widget_set_no_show_all (tmp, TRUE);
463 gtk_widget_hide (tmp);
464 #endif
466 g_object_unref (G_OBJECT (xml));
468 rb_library_source_preferences_sync (source);
470 return source->priv->config_widget;
473 static void
474 rb_library_source_library_location_changed (GConfClient *client,
475 guint cnxn_id,
476 GConfEntry *entry,
477 RBLibrarySource *source)
479 if (source->priv->config_widget)
480 rb_library_source_preferences_sync (source);
482 rb_library_source_sync_child_sources (source);
485 static void
486 rb_library_source_ui_prefs_sync (RBLibrarySource *source)
488 if (source->priv->config_widget)
489 rb_library_source_preferences_sync (source);
492 static void
493 rb_library_source_ui_pref_changed (GConfClient *client,
494 guint cnxn_id,
495 GConfEntry *entry,
496 RBLibrarySource *source)
498 rb_debug ("ui pref changed");
499 rb_library_source_ui_prefs_sync (source);
502 static void
503 rb_library_source_preferences_sync (RBLibrarySource *source)
505 GSList *list;
506 #ifdef ENABLE_TRACK_TRANSFER
507 const char *str;
508 GConfClient *gconf_client;
509 #endif
511 rb_debug ("syncing pref dialog state");
513 /* library location */
514 list = eel_gconf_get_string_list (CONF_LIBRARY_LOCATION);
516 /* don't trigger the change notification */
517 g_signal_handlers_block_by_func (G_OBJECT (source->priv->library_location_entry),
518 G_CALLBACK (rb_library_source_library_location_cb),
519 source);
521 if (g_slist_length (list) == 1) {
522 char *path;
524 gtk_widget_set_sensitive (source->priv->library_location_entry, TRUE);
526 path = gnome_vfs_format_uri_for_display (list->data);
527 gtk_entry_set_text (GTK_ENTRY (source->priv->library_location_entry), path);
528 g_free (path);
529 } else if (g_slist_length (list) == 0) {
530 /* no library directories */
531 gtk_widget_set_sensitive (source->priv->library_location_entry, TRUE);
532 gtk_entry_set_text (GTK_ENTRY (source->priv->library_location_entry), "");
533 } else {
534 /* multiple library directories */
535 gtk_widget_set_sensitive (source->priv->library_location_entry, FALSE);
536 gtk_entry_set_text (GTK_ENTRY (source->priv->library_location_entry), _("Multiple locations set"));
539 g_signal_handlers_unblock_by_func (G_OBJECT (source->priv->library_location_entry),
540 G_CALLBACK (rb_library_source_library_location_cb),
541 source);
543 g_slist_foreach (list, (GFunc) g_free, NULL);
544 g_slist_free (list);
546 /* watch checkbox */
547 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (source->priv->watch_library_check),
548 eel_gconf_get_boolean (CONF_MONITOR_LIBRARY));
550 #ifdef ENABLE_TRACK_TRANSFER
551 /* preferred format */
552 str = eel_gconf_get_string (CONF_LIBRARY_PREFERRED_FORMAT);
553 if (str)
554 gm_audio_profile_choose_set_active (source->priv->preferred_format_menu, str);
556 source->priv->layout_path_notify_id =
557 eel_gconf_notification_add (CONF_LIBRARY_LAYOUT_PATH,
558 (GConfClientNotifyFunc) rb_library_source_layout_path_changed, source);
559 source->priv->layout_filename_notify_id =
560 eel_gconf_notification_add (CONF_LIBRARY_LAYOUT_FILENAME,
561 (GConfClientNotifyFunc) rb_library_source_layout_filename_changed, source);
563 gconf_client = eel_gconf_client_get_global ();
564 /* layout path */
565 rb_library_source_layout_path_changed (gconf_client, -1,
566 gconf_client_get_entry (gconf_client, CONF_LIBRARY_LAYOUT_PATH, NULL, TRUE, NULL),
567 source);
568 /* layout filename */
569 rb_library_source_layout_filename_changed (gconf_client, -1,
570 gconf_client_get_entry (gconf_client, CONF_LIBRARY_LAYOUT_FILENAME, NULL, TRUE, NULL),
571 source);
572 #endif
575 static gboolean
576 rb_library_source_library_location_cb (GtkEntry *entry,
577 GdkEventFocus *event,
578 RBLibrarySource *source)
580 GSList *list = NULL;
581 const char *path;
582 char *uri;
584 path = gtk_entry_get_text (entry);
585 uri = gnome_vfs_make_uri_from_input (path);
587 if (uri && uri[0])
588 list = g_slist_prepend (NULL, (gpointer)uri);
590 eel_gconf_set_string_list (CONF_LIBRARY_LOCATION, list);
592 g_free (uri);
593 if (list)
594 g_slist_free (list);
596 /* don't do the first-run druid if the user sets the library location */
597 if (list)
598 eel_gconf_set_boolean (CONF_FIRST_TIME, TRUE);
600 return FALSE;
603 static void
604 rb_library_source_watch_toggled_cb (GtkToggleButton *button, RBLibrarySource *source)
606 gboolean active;
608 active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (source->priv->watch_library_check));
609 eel_gconf_set_boolean (CONF_MONITOR_LIBRARY, active);
612 static char *
613 impl_get_browser_key (RBSource *source)
615 return g_strdup (CONF_STATE_SHOW_BROWSER);
618 static const char *
619 impl_get_paned_key (RBBrowserSource *status)
621 return CONF_STATE_PANED_POSITION;
624 static void
625 rb_library_source_add_location_entry_changed_cb (GtkEntry *entry,
626 GtkWidget *target)
628 gtk_widget_set_sensitive (target, g_utf8_strlen (gtk_entry_get_text (GTK_ENTRY (entry)), -1) > 0);
631 void
632 rb_library_source_add_location (RBLibrarySource *source, GtkWindow *win)
634 GladeXML *xml = rb_glade_xml_new ("uri.glade",
635 "open_uri_dialog_content",
636 source);
637 GtkWidget *content, *uri_widget, *open_button;
638 GtkWidget *dialog = gtk_dialog_new_with_buttons (_("Add Location"),
639 win,
640 GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR,
641 GTK_STOCK_CANCEL,
642 GTK_RESPONSE_CANCEL,
643 NULL);
645 g_return_if_fail (dialog != NULL);
647 open_button = gtk_dialog_add_button (GTK_DIALOG (dialog),
648 GTK_STOCK_OPEN,
649 GTK_RESPONSE_OK);
650 gtk_widget_set_sensitive (open_button, FALSE);
652 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
653 GTK_RESPONSE_OK);
654 gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
655 gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 2);
657 gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
659 content = glade_xml_get_widget (xml, "open_uri_dialog_content");
661 g_return_if_fail (content != NULL);
663 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
664 content, FALSE, FALSE, 0);
665 gtk_container_set_border_width (GTK_CONTAINER (content), 5);
667 uri_widget = glade_xml_get_widget (xml, "uri");
669 g_return_if_fail (uri_widget != NULL);
671 g_signal_connect_object (G_OBJECT (uri_widget),
672 "changed",
673 G_CALLBACK (rb_library_source_add_location_entry_changed_cb),
674 open_button, 0);
676 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
677 char *uri = gtk_editable_get_chars (GTK_EDITABLE (uri_widget), 0, -1);
678 if (uri != NULL) {
679 GnomeVFSURI *vfsuri = gnome_vfs_uri_new (uri);
680 if (vfsuri != NULL) {
681 rhythmdb_add_uri (source->priv->db, uri);
682 gnome_vfs_uri_unref (vfsuri);
683 } else {
684 rb_debug ("invalid uri: \"%s\"", uri);
689 gtk_widget_destroy (GTK_WIDGET (dialog));
693 static gboolean
694 impl_receive_drag (RBSource *asource, GtkSelectionData *data)
696 RBLibrarySource *source = RB_LIBRARY_SOURCE (asource);
697 GList *list, *i;
698 GList *entries = NULL;
699 gboolean is_id;
701 rb_debug ("parsing uri list");
702 list = rb_uri_list_parse ((const char *) data->data);
703 is_id = (data->type == gdk_atom_intern ("application/x-rhythmbox-entry", TRUE));
705 for (i = list; i != NULL; i = g_list_next (i)) {
706 if (i->data != NULL) {
707 char *uri = i->data;
708 RhythmDBEntry *entry;
710 entry = rhythmdb_entry_lookup_from_string (source->priv->db, uri, is_id);
712 if (entry == NULL) {
713 /* add to the library */
714 rhythmdb_add_uri (source->priv->db, uri);
715 } else {
716 /* add to list of entries to copy */
717 entries = g_list_prepend (entries, entry);
720 g_free (uri);
724 if (entries) {
725 entries = g_list_reverse (entries);
726 if (rb_source_can_paste (asource))
727 rb_source_paste (asource, entries);
728 g_list_free (entries);
731 g_list_free (list);
732 return TRUE;
735 static gboolean
736 impl_show_popup (RBSource *source)
738 _rb_source_show_popup (source, "/LibrarySourcePopup");
739 return TRUE;
742 #ifdef ENABLE_TRACK_TRANSFER
743 static void
744 rb_library_source_path_changed_cb (GtkComboBox *box, RBLibrarySource *source)
746 const char *path;
747 gint index;
749 index = gtk_combo_box_get_active (box);
750 path = (index >= 0) ? library_layout_paths[index].path : "";
751 eel_gconf_set_string (CONF_LIBRARY_LAYOUT_PATH, path);
754 static void
755 rb_library_source_filename_changed_cb (GtkComboBox *box, RBLibrarySource *source)
757 const char *filename;
758 gint index;
760 index = gtk_combo_box_get_active (box);
761 filename = (index >= 0) ? library_layout_filenames[index].path : "";
762 eel_gconf_set_string (CONF_LIBRARY_LAYOUT_FILENAME, filename);
765 static void
766 rb_library_source_format_changed_cb (GtkWidget *widget, RBLibrarySource *source)
768 GMAudioProfile *profile;
770 profile = gm_audio_profile_choose_get_active (widget);
771 eel_gconf_set_string (CONF_LIBRARY_PREFERRED_FORMAT, gm_audio_profile_get_id (profile));
775 * Perform magic on a path to make it safe.
777 * This will always replace '/' with ' ', and optionally make the file name
778 * shell-friendly. This involves removing [?*\ ] and replacing with '_'. Also
779 * any leading periods are removed so that the files don't end up being hidden.
781 static char *
782 sanitize_path (const char *str)
784 gchar *res = NULL;
785 gchar *s;
787 /* Skip leading periods, otherwise files disappear... */
788 while (*str == '.')
789 str++;
791 s = g_strdup(str);
792 /* Replace path seperators with a hyphen */
793 g_strdelimit (s, "/", '-');
794 if (eel_gconf_get_boolean (CONF_LIBRARY_STRIP_CHARS)) {
795 /* Replace separators with a hyphen */
796 g_strdelimit (s, "\\:|", '-');
797 /* Replace all other weird characters to whitespace */
798 g_strdelimit (s, "*?&!\'\"$()`>{}", ' ');
799 /* Replace all whitespace with underscores */
800 /* TODO: I'd like this to compress whitespace aswell */
801 g_strdelimit (s, "\t ", '_');
803 res = g_filename_from_utf8(s, -1, NULL, NULL, NULL);
804 g_free(s);
805 return res ? res : g_strdup(str);
809 * Parse a filename pattern and replace markers with values from a TrackDetails
810 * structure.
812 * Valid markers so far are:
813 * %at -- album title
814 * %aa -- album artist
815 * %aA -- album artist (lowercase)
816 * %as -- album artist sortname
817 * %aS -- album artist sortname (lowercase)
818 * %tn -- track number (i.e 8)
819 * %tN -- track number, zero padded (i.e 08)
820 * %tt -- track title
821 * %ta -- track artist
822 * %tA -- track artist (lowercase)
823 * %ts -- track artist sortname
824 * %tS -- track artist sortname (lowercase)
826 static char *
827 filepath_parse_pattern (const char *pattern,
828 RhythmDBEntry *entry)
830 /* p is the pattern iterator, i is a general purpose iterator */
831 const char *p;
832 char *temp;
833 GString *s;
835 if (pattern == NULL || pattern[0] == 0)
836 return g_strdup (" ");
838 s = g_string_new (NULL);
840 p = pattern;
841 while (*p) {
842 char *string = NULL;
844 /* If not a % marker, copy and continue */
845 if (*p != '%') {
846 g_string_append_c (s, *p++);
847 /* Explicit increment as we continue past the increment */
848 continue;
851 /* Is a % marker, go to next and see what to do */
852 switch (*++p) {
853 case '%':
855 * Literal %
857 g_string_append_c (s, '%');
858 break;
859 case 'a':
861 * Album tag
863 switch (*++p) {
864 case 't':
865 string = sanitize_path (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM));
866 break;
867 case 'T':
868 temp = sanitize_path (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM_FOLDED));
869 break;
870 case 'a':
871 string = sanitize_path (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST));
872 break;
873 case 'A':
874 string = sanitize_path (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST_FOLDED));
875 break;
876 /*case 's':
877 string = sanitize_path (album sort name);
878 break;
879 case 'S':
880 char *t = g_utf8_strdown (album sort name)
881 string = sanitize_path (t);
882 g_free (t);
883 break;*/
884 default:
885 string = g_strdup_printf ("%%a%c", *p);
888 break;
890 case 't':
892 * Track tag
894 switch (*++p) {
895 case 't':
896 string = sanitize_path (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE));
897 break;
898 case 'T':
899 string = sanitize_path (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE_FOLDED));
900 break;
901 case 'a':
902 string = sanitize_path (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST));
903 break;
904 case 'A':
905 string = sanitize_path (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST_FOLDED));
906 break;
907 /*case 's':
908 string = sanitize_path (artist sort name);
909 break;
910 case 'S':
911 char *t = g_utf8_strdown (artist sort name)
912 string = sanitize_path (t);
913 g_free (t);
914 break;*/
915 case 'n':
916 /* Track number */
917 string = g_strdup_printf ("%u", (guint)rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER));
918 break;
919 case 'N':
920 /* Track number, zero-padded */
921 string = g_strdup_printf ("%02u", (guint)rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER));
922 break;
923 default:
924 string = g_strdup_printf ("%%t%c", *p);
927 break;
929 default:
930 string = g_strdup_printf ("%%%c", *p);
933 if (string)
934 g_string_append (s, string);
935 g_free (string);
937 ++p;
940 temp = s->str;
941 g_string_free (s, FALSE);
942 return temp;
945 static void
946 layout_example_label_update (RBLibrarySource *source)
948 char *file_pattern;
949 char *path_pattern;
950 char *file_value;
951 char *path_value;
952 char *example;
953 char *format;
954 GMAudioProfile *profile;
955 RhythmDBEntryType entry_type;
956 RhythmDBEntry *sample_entry;
958 profile = gm_audio_profile_choose_get_active (source->priv->preferred_format_menu);
960 /* TODO: sucky. Replace with get-gconf-key-with-default mojo */
961 file_pattern = eel_gconf_get_string (CONF_LIBRARY_LAYOUT_FILENAME);
962 if (file_pattern == NULL) {
963 file_pattern = g_strdup (library_layout_filenames[0].path);
965 path_pattern = eel_gconf_get_string (CONF_LIBRARY_LAYOUT_PATH);
966 if (path_pattern == NULL) {
967 path_pattern = g_strdup (library_layout_paths[0].path);
970 g_object_get (source, "entry-type", &entry_type, NULL);
971 sample_entry = rhythmdb_entry_example_new (source->priv->db, entry_type, NULL);
972 g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, entry_type);
974 file_value = filepath_parse_pattern (file_pattern, sample_entry);
975 path_value = filepath_parse_pattern (path_pattern, sample_entry);
976 rhythmdb_entry_unref (sample_entry);
978 example = g_build_filename (G_DIR_SEPARATOR_S, path_value, file_value, NULL);
979 g_free (file_value);
980 g_free (file_pattern);
981 g_free (path_value);
982 g_free (path_pattern);
984 format = g_strconcat ("<small><i><b>Example Path:</b> ",
985 example,
986 ".",
987 profile ? gm_audio_profile_get_extension (profile) : "ogg",
988 "</i></small>", NULL);
989 g_free (example);
991 gtk_label_set_markup (GTK_LABEL (source->priv->layout_example_label), format);
992 g_free (format);
995 static void
996 rb_library_source_layout_path_changed (GConfClient *client,
997 guint cnxn_id,
998 GConfEntry *entry,
999 RBLibrarySource *source)
1001 char *value;
1002 int i = 0;
1004 g_return_if_fail (strcmp (entry->key, CONF_LIBRARY_LAYOUT_PATH) == 0);
1006 rb_debug ("layout path changed");
1008 if (entry->value == NULL) {
1009 value = g_strdup (library_layout_paths[0].path);
1010 } else if (entry->value->type == GCONF_VALUE_STRING) {
1011 value = g_strdup (gconf_value_get_string (entry->value));
1012 } else {
1013 return;
1016 while (library_layout_paths[i].path && strcmp (library_layout_paths[i].path, value) != 0) {
1017 i++;
1020 g_free (value);
1021 gtk_combo_box_set_active (GTK_COMBO_BOX (source->priv->layout_path_menu), i);
1023 layout_example_label_update (source);
1026 static void
1027 rb_library_source_layout_filename_changed (GConfClient *client,
1028 guint cnxn_id,
1029 GConfEntry *entry,
1030 RBLibrarySource *source)
1032 char *value;
1033 int i = 0;
1035 g_return_if_fail (strcmp (entry->key, CONF_LIBRARY_LAYOUT_FILENAME) == 0);
1037 rb_debug ("layout filename changed");
1039 if (entry->value == NULL) {
1040 value = g_strdup (library_layout_filenames[0].path);
1041 } else if (entry->value->type == GCONF_VALUE_STRING) {
1042 value = g_strdup (gconf_value_get_string (entry->value));
1043 } else {
1044 return;
1047 while (library_layout_filenames[i].path && strcmp (library_layout_filenames[i].path, value) != 0) {
1048 i++;
1051 g_free (value);
1052 gtk_combo_box_set_active (GTK_COMBO_BOX (source->priv->layout_filename_menu), i);
1054 layout_example_label_update (source);
1058 * Build the absolute filename for the specified track.
1060 * The base path is the extern variable 'base_path', the format to use
1061 * is the extern variable 'file_pattern'. Free the result when you
1062 * have finished with it.
1064 * Stolen from Sound-Juicer
1066 static char*
1067 build_filename (RBLibrarySource *source, RhythmDBEntry *entry)
1069 GnomeVFSURI *uri;
1070 GnomeVFSURI *new;
1071 char *realfile;
1072 char *realpath;
1073 char *filename;
1074 char *string;
1075 char *extension = NULL;
1076 GSList *list;
1077 const char *layout_path;
1078 const char *layout_filename;
1079 const char *preferred_format;
1081 list = eel_gconf_get_string_list (CONF_LIBRARY_LOCATION);
1082 layout_path = eel_gconf_get_string (CONF_LIBRARY_LAYOUT_PATH);
1083 layout_filename = eel_gconf_get_string (CONF_LIBRARY_LAYOUT_FILENAME);
1084 preferred_format = eel_gconf_get_string (CONF_LIBRARY_PREFERRED_FORMAT);
1086 if (list == NULL || layout_path == NULL || layout_filename == NULL || preferred_format == NULL) {
1087 /* emit warning */
1088 rb_debug ("Could not retrieve settings from GConf");
1089 g_slist_free (list);
1090 return NULL;
1093 uri = gnome_vfs_uri_new ((const char *)list->data);
1094 if (uri == NULL) {
1095 /* do something.. */
1096 g_slist_free (list);
1097 return NULL;
1100 g_slist_free (list);
1102 realpath = filepath_parse_pattern (layout_path, entry);
1103 new = gnome_vfs_uri_append_path (uri, realpath);
1104 gnome_vfs_uri_unref (uri);
1105 uri = new;
1106 g_free (realpath);
1108 if (g_str_has_prefix (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MIMETYPE), "audio/x-raw")) {
1109 GMAudioProfile *profile;
1110 profile = gm_audio_profile_lookup (preferred_format);
1111 if (profile)
1112 extension = g_strdup (gm_audio_profile_get_extension (profile));
1115 if (extension == NULL) {
1116 const char *uri;
1117 const char *loc;
1118 char *tmp;
1120 /* use the old extension. strip anything after a '?' for http/daap/etc */
1121 uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
1122 loc = g_utf8_strrchr (uri, -1, '.');
1123 if (loc == NULL)
1124 loc = g_utf8_strrchr (uri, -1, '/');
1125 if (loc == NULL)
1126 loc = uri;
1128 extension = g_strdup (loc + 1);
1130 tmp = g_utf8_strchr (extension, -1, '?');
1131 if (tmp)
1132 *tmp = '\0';
1135 realfile = filepath_parse_pattern (layout_filename, entry);
1136 if (extension) {
1137 filename = g_strdup_printf ("%s.%s", realfile, extension);
1138 g_free (realfile);
1139 } else {
1140 filename = realfile;
1143 new = gnome_vfs_uri_append_file_name (uri, filename);
1144 gnome_vfs_uri_unref (uri);
1145 uri = new;
1146 g_free (extension);
1147 g_free (filename);
1149 string = gnome_vfs_uri_to_string (uri, 0);
1150 gnome_vfs_uri_unref (uri);
1151 return string;
1153 #endif
1155 static gboolean
1156 impl_can_paste (RBSource *asource)
1158 #ifdef ENABLE_TRACK_TRANSFER
1159 GSList *list;
1160 gboolean can_paste = TRUE;
1162 list = eel_gconf_get_string_list (CONF_LIBRARY_LOCATION);
1163 can_paste = ((list != NULL) &&
1164 (eel_gconf_get_string (CONF_LIBRARY_LAYOUT_PATH) != NULL ) &&
1165 (eel_gconf_get_string (CONF_LIBRARY_LAYOUT_FILENAME) != NULL) &&
1166 (eel_gconf_get_string (CONF_LIBRARY_PREFERRED_FORMAT) != NULL));
1168 g_slist_free (list);
1169 return can_paste;
1170 #else
1171 return FALSE;
1172 #endif
1175 #ifdef ENABLE_TRACK_TRANSFER
1176 static void
1177 completed_cb (RhythmDBEntry *entry, const char *dest, RBLibrarySource *source)
1179 rhythmdb_add_uri (source->priv->db, dest);
1182 static void
1183 impl_paste (RBSource *asource, GList *entries)
1185 RBLibrarySource *source = RB_LIBRARY_SOURCE (asource);
1186 RBRemovableMediaManager *rm_mgr;
1187 GList *l;
1188 GSList *sl;
1189 RBShell *shell;
1190 RhythmDBEntryType source_entry_type;
1192 sl = eel_gconf_get_string_list (CONF_LIBRARY_LOCATION);
1193 if ((sl == NULL) ||
1194 (eel_gconf_get_string (CONF_LIBRARY_LAYOUT_PATH) == NULL )||
1195 (eel_gconf_get_string (CONF_LIBRARY_LAYOUT_FILENAME) == NULL) ||
1196 (eel_gconf_get_string (CONF_LIBRARY_PREFERRED_FORMAT) == NULL)) {
1197 g_slist_free (sl);
1198 g_warning ("RBLibrarySource impl_paste called when gconf keys unset");
1199 return;
1202 g_object_get (source,
1203 "shell", &shell,
1204 "entry-type", &source_entry_type,
1205 NULL);
1206 g_object_get (shell, "removable-media-manager", &rm_mgr, NULL);
1207 g_object_unref (shell);
1209 for (l = entries; l != NULL; l = g_list_next (l)) {
1210 RhythmDBEntry *entry = (RhythmDBEntry *)l->data;
1211 RhythmDBEntryType entry_type;
1212 RBSource *source_source;
1213 char *dest;
1215 rb_debug ("pasting entry %s", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
1217 entry_type = rhythmdb_entry_get_entry_type (entry);
1218 if (entry_type == source_entry_type)
1219 /* copying to ourselves would be silly */
1220 continue;
1222 /* see if the responsible source lets us copy */
1223 source_source = rb_shell_get_source_by_entry_type (shell, entry_type);
1224 if ((source_source != NULL) && !rb_source_can_copy (source_source))
1225 continue;
1227 dest = build_filename (source, entry);
1228 if (dest == NULL) {
1229 rb_debug ("could not create destination path for entry");
1230 continue;
1233 rb_removable_media_manager_queue_transfer (rm_mgr, entry,
1234 dest, NULL,
1235 (RBTranferCompleteCallback)completed_cb, source);
1237 g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, source_entry_type);
1239 g_object_unref (rm_mgr);
1241 #endif
1243 static guint
1244 impl_want_uri (RBSource *source, const char *uri)
1246 /* assume anything local, on smb, or on sftp is a song */
1247 if (rb_uri_is_local (uri) ||
1248 g_str_has_prefix (uri, "smb://") ||
1249 g_str_has_prefix (uri, "sftp://"))
1250 return 50;
1252 return 0;
1255 static gboolean
1256 impl_add_uri (RBSource *asource, const char *uri, const char *title, const char *genre)
1258 RBLibrarySource *source = RB_LIBRARY_SOURCE (asource);
1259 /* FIXME should be synchronous */
1260 rb_debug ("adding uri %s to library", uri);
1261 rhythmdb_add_uri (source->priv->db, uri);
1262 return TRUE;
1265 static void
1266 rb_library_source_add_child_source (const char *path, RBLibrarySource *library_source)
1268 RBSource *source;
1269 GPtrArray *query;
1270 RBShell *shell;
1271 GnomeVFSURI *uri;
1272 char *name;
1273 GdkPixbuf *icon;
1274 RhythmDBEntryType entry_type;
1275 char *sort_column;
1276 int sort_order;
1278 g_object_get (library_source,
1279 "shell", &shell,
1280 "entry-type", &entry_type,
1281 NULL);
1282 uri = gnome_vfs_uri_new (path);
1283 if (uri == NULL) {
1284 g_object_unref (shell);
1285 g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, entry_type);
1286 return;
1289 name = gnome_vfs_uri_extract_short_name (uri);
1290 gnome_vfs_uri_unref (uri);
1292 rb_entry_view_get_sorting_order (rb_source_get_entry_view (RB_SOURCE (library_source)),
1293 &sort_column, &sort_order);
1295 source = rb_auto_playlist_source_new (shell, name, FALSE);
1296 query = rhythmdb_query_parse (library_source->priv->db,
1297 RHYTHMDB_QUERY_PROP_EQUALS, RHYTHMDB_PROP_TYPE, entry_type,
1298 RHYTHMDB_QUERY_PROP_PREFIX, RHYTHMDB_PROP_LOCATION, path,
1299 RHYTHMDB_QUERY_END);
1300 rb_auto_playlist_source_set_query (RB_AUTO_PLAYLIST_SOURCE (source), query,
1301 RHYTHMDB_QUERY_MODEL_LIMIT_NONE, NULL,
1302 sort_column, sort_order);
1303 rhythmdb_query_free (query);
1304 g_free (sort_column);
1306 g_object_get (library_source, "icon", &icon, NULL);
1307 g_object_set (source, "icon", icon, NULL);
1308 if (icon != NULL) {
1309 g_object_unref (icon);
1312 rb_shell_append_source (shell, source, RB_SOURCE (library_source));
1313 library_source->priv->child_sources = g_list_prepend (library_source->priv->child_sources, source);
1315 g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, entry_type);
1316 g_object_unref (shell);
1317 g_free (name);
1320 static void
1321 rb_library_source_sync_child_sources (RBLibrarySource *source)
1323 GSList *list;
1325 list = eel_gconf_get_string_list (CONF_LIBRARY_LOCATION);
1327 /* FIXME: don't delete and re-create sources that are still there */
1328 g_list_foreach (source->priv->child_sources, (GFunc)rb_source_delete_thyself, NULL);
1329 g_list_free (source->priv->child_sources);
1330 source->priv->child_sources = NULL;
1332 if (g_slist_length (list) > 1)
1333 g_slist_foreach (list, (GFunc)rb_library_source_add_child_source, source);
1334 g_slist_foreach (list, (GFunc) g_free, NULL);
1335 g_slist_free (list);