2006-08-04 James Livingston <doclivingston@gmail.com>
[rhythmbox.git] / sources / rb-library-source.c
blob6ef97c09af6dcfa8c6078c18c3b367191b8b4ef6
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 rb_library_source_sync_child_sources (source);
256 return FALSE;
259 static GObject *
260 rb_library_source_constructor (GType type,
261 guint n_construct_properties,
262 GObjectConstructParam *construct_properties)
264 RBLibrarySource *source;
265 RBShell *shell;
266 RBEntryView *songs;
268 source = RB_LIBRARY_SOURCE (G_OBJECT_CLASS (rb_library_source_parent_class)
269 ->constructor (type, n_construct_properties, construct_properties));
271 g_object_get (source, "shell", &shell, NULL);
272 g_object_get (shell, "db", &source->priv->db, NULL);
274 rb_library_source_ui_prefs_sync (source);
276 source->priv->library_location_notify_id =
277 eel_gconf_notification_add (CONF_LIBRARY_LOCATION,
278 (GConfClientNotifyFunc) rb_library_source_library_location_changed, source);
280 source->priv->ui_dir_notify_id =
281 eel_gconf_notification_add (CONF_UI_LIBRARY_DIR,
282 (GConfClientNotifyFunc) rb_library_source_ui_pref_changed, source);
284 songs = rb_source_get_entry_view (RB_SOURCE (source));
286 rb_entry_view_append_column (songs, RB_ENTRY_VIEW_COL_RATING, FALSE);
287 rb_entry_view_append_column (songs, RB_ENTRY_VIEW_COL_LAST_PLAYED, FALSE);
288 rb_entry_view_append_column (songs, RB_ENTRY_VIEW_COL_FIRST_SEEN, FALSE);
290 g_idle_add ((GSourceFunc)add_child_sources_idle, source);
292 g_object_unref (shell);
294 return G_OBJECT (source);
297 RBSource *
298 rb_library_source_new (RBShell *shell)
300 RBSource *source;
301 GdkPixbuf *icon;
302 gint size;
304 gtk_icon_size_lookup (GTK_ICON_SIZE_LARGE_TOOLBAR, &size, NULL);
305 icon = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
306 "stock_music-library",
307 size,
308 0, NULL);
309 source = RB_SOURCE (g_object_new (RB_TYPE_LIBRARY_SOURCE,
310 "name", _("Library"),
311 "entry-type", RHYTHMDB_ENTRY_TYPE_SONG,
312 "sorting-key", CONF_STATE_LIBRARY_SORTING,
313 "shell", shell,
314 "icon", icon,
315 NULL));
316 if (icon != NULL) {
317 g_object_unref (icon);
320 rb_shell_register_entry_type_for_source (shell, source,
321 RHYTHMDB_ENTRY_TYPE_SONG);
323 return source;
326 #ifdef ENABLE_TRACK_TRANSFER
327 static void
328 rb_library_source_edit_profile_clicked_cb (GtkButton *button, RBLibrarySource *source)
330 GtkWidget *dialog;
332 dialog = gm_audio_profiles_edit_new (eel_gconf_client_get_global (),
333 GTK_WINDOW (source->priv->shell_prefs));
334 gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
335 gtk_widget_show_all (dialog);
336 gtk_dialog_run (GTK_DIALOG (dialog));
338 #endif
340 static void
341 rb_library_source_location_button_clicked_cb (GtkButton *button, RBLibrarySource *source)
343 GtkWidget *dialog;
345 dialog = rb_file_chooser_new (_("Choose Library Location"), GTK_WINDOW (source->priv->shell_prefs),
346 GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, FALSE);
347 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
348 char *uri;
349 char *path;
351 uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
352 if (uri == NULL) {
353 uri = gtk_file_chooser_get_current_folder_uri (GTK_FILE_CHOOSER (dialog));
356 path = gnome_vfs_format_uri_for_display (uri);
358 gtk_entry_set_text (GTK_ENTRY (source->priv->library_location_entry), path);
359 rb_library_source_library_location_cb (GTK_ENTRY (source->priv->library_location_entry),
360 NULL, source);
361 g_free (uri);
362 g_free (path);
365 gtk_widget_destroy (GTK_WIDGET (dialog));
368 static GtkWidget *
369 impl_get_config_widget (RBSource *asource, RBShellPreferences *prefs)
371 RBLibrarySource *source = RB_LIBRARY_SOURCE (asource);
372 GtkWidget *tmp;
373 GladeXML *xml;
374 #ifdef ENABLE_TRACK_TRANSFER
375 GtkWidget *label;
376 int i;
377 #endif
379 if (source->priv->config_widget)
380 return source->priv->config_widget;
382 g_object_ref (G_OBJECT (prefs));
383 source->priv->shell_prefs = prefs;
385 xml = rb_glade_xml_new ("library-prefs.glade", "library_vbox", source);
386 source->priv->config_widget =
387 glade_xml_get_widget (xml, "library_vbox");
389 rb_glade_boldify_label (xml, "library_location_label");
391 source->priv->library_location_entry = glade_xml_get_widget (xml, "library_location_entry");
392 tmp = glade_xml_get_widget (xml, "library_location_button");
393 g_signal_connect (G_OBJECT (tmp),
394 "clicked",
395 G_CALLBACK (rb_library_source_location_button_clicked_cb),
396 asource);
397 g_signal_connect (G_OBJECT (source->priv->library_location_entry),
398 "focus-out-event",
399 G_CALLBACK (rb_library_source_library_location_cb),
400 asource);
402 source->priv->watch_library_check = glade_xml_get_widget (xml, "watch_library_check");
403 g_signal_connect (G_OBJECT (source->priv->watch_library_check),
404 "toggled",
405 G_CALLBACK (rb_library_source_watch_toggled_cb),
406 asource);
408 #ifdef ENABLE_TRACK_TRANSFER
409 rb_glade_boldify_label (xml, "library_structure_label");
411 tmp = glade_xml_get_widget (xml, "layout_path_menu_box");
412 label = glade_xml_get_widget (xml, "layout_path_menu_label");
413 source->priv->layout_path_menu = gtk_combo_box_new_text ();
414 gtk_box_pack_start_defaults (GTK_BOX (tmp), source->priv->layout_path_menu);
415 gtk_label_set_mnemonic_widget (GTK_LABEL (label), source->priv->layout_path_menu);
416 g_signal_connect (G_OBJECT (source->priv->layout_path_menu),
417 "changed",
418 G_CALLBACK (rb_library_source_path_changed_cb),
419 asource);
420 for (i = 0; i < num_library_layout_paths; i++) {
421 gtk_combo_box_append_text (GTK_COMBO_BOX (source->priv->layout_path_menu),
422 _(library_layout_paths[i].title));
425 tmp = glade_xml_get_widget (xml, "layout_filename_menu_box");
426 label = glade_xml_get_widget (xml, "layout_filename_menu_label");
427 source->priv->layout_filename_menu = gtk_combo_box_new_text ();
428 gtk_box_pack_start_defaults (GTK_BOX (tmp), source->priv->layout_filename_menu);
429 gtk_label_set_mnemonic_widget (GTK_LABEL (label), source->priv->layout_filename_menu);
430 g_signal_connect (G_OBJECT (source->priv->layout_filename_menu),
431 "changed",
432 G_CALLBACK (rb_library_source_filename_changed_cb),
433 asource);
434 for (i = 0; i < num_library_layout_filenames; i++) {
435 gtk_combo_box_append_text (GTK_COMBO_BOX (source->priv->layout_filename_menu),
436 _(library_layout_filenames[i].title));
439 tmp = glade_xml_get_widget (xml, "edit_profile_button");
440 g_signal_connect (G_OBJECT (tmp),
441 "clicked",
442 G_CALLBACK (rb_library_source_edit_profile_clicked_cb),
443 asource);
445 tmp = glade_xml_get_widget (xml, "preferred_format_menu_box");
446 label = glade_xml_get_widget (xml, "preferred_format_menu_label");
447 source->priv->preferred_format_menu = gm_audio_profile_choose_new ();
448 gtk_box_pack_start_defaults (GTK_BOX (tmp), source->priv->preferred_format_menu);
449 gtk_label_set_mnemonic_widget (GTK_LABEL (label), source->priv->preferred_format_menu);
450 g_signal_connect (G_OBJECT (source->priv->preferred_format_menu),
451 "changed",
452 G_CALLBACK (rb_library_source_format_changed_cb),
453 asource);
455 source->priv->layout_example_label = glade_xml_get_widget (xml, "layout_example_label");
456 #else
457 tmp = glade_xml_get_widget (xml, "library_structure_vbox");
458 gtk_widget_set_no_show_all (tmp, TRUE);
459 gtk_widget_hide (tmp);
460 #endif
462 g_object_unref (G_OBJECT (xml));
464 rb_library_source_preferences_sync (source);
466 return source->priv->config_widget;
469 static void
470 rb_library_source_library_location_changed (GConfClient *client,
471 guint cnxn_id,
472 GConfEntry *entry,
473 RBLibrarySource *source)
475 if (source->priv->config_widget)
476 rb_library_source_preferences_sync (source);
478 rb_library_source_sync_child_sources (source);
481 static void
482 rb_library_source_ui_prefs_sync (RBLibrarySource *source)
484 if (source->priv->config_widget)
485 rb_library_source_preferences_sync (source);
488 static void
489 rb_library_source_ui_pref_changed (GConfClient *client,
490 guint cnxn_id,
491 GConfEntry *entry,
492 RBLibrarySource *source)
494 rb_debug ("ui pref changed");
495 rb_library_source_ui_prefs_sync (source);
498 static void
499 rb_library_source_preferences_sync (RBLibrarySource *source)
501 GSList *list;
502 #ifdef ENABLE_TRACK_TRANSFER
503 const char *str;
504 GConfClient *gconf_client;
505 #endif
507 rb_debug ("syncing pref dialog state");
509 /* library location */
510 list = eel_gconf_get_string_list (CONF_LIBRARY_LOCATION);
512 /* don't trigger the change notification */
513 g_signal_handlers_block_by_func (G_OBJECT (source->priv->library_location_entry),
514 G_CALLBACK (rb_library_source_library_location_cb),
515 source);
517 if (g_slist_length (list) == 1) {
518 char *path;
520 gtk_widget_set_sensitive (source->priv->library_location_entry, TRUE);
522 path = gnome_vfs_format_uri_for_display (list->data);
523 gtk_entry_set_text (GTK_ENTRY (source->priv->library_location_entry), path);
524 g_free (path);
525 } else if (g_slist_length (list) == 0) {
526 /* no library directories */
527 gtk_widget_set_sensitive (source->priv->library_location_entry, TRUE);
528 gtk_entry_set_text (GTK_ENTRY (source->priv->library_location_entry), "");
529 } else {
530 /* multiple library directories */
531 gtk_widget_set_sensitive (source->priv->library_location_entry, FALSE);
532 gtk_entry_set_text (GTK_ENTRY (source->priv->library_location_entry), _("Multiple locations set"));
535 g_signal_handlers_unblock_by_func (G_OBJECT (source->priv->library_location_entry),
536 G_CALLBACK (rb_library_source_library_location_cb),
537 source);
539 g_slist_foreach (list, (GFunc) g_free, NULL);
540 g_slist_free (list);
542 /* watch checkbox */
543 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (source->priv->watch_library_check),
544 eel_gconf_get_boolean (CONF_MONITOR_LIBRARY));
546 #ifdef ENABLE_TRACK_TRANSFER
547 /* preferred format */
548 str = eel_gconf_get_string (CONF_LIBRARY_PREFERRED_FORMAT);
549 if (str)
550 gm_audio_profile_choose_set_active (source->priv->preferred_format_menu, str);
552 source->priv->layout_path_notify_id =
553 eel_gconf_notification_add (CONF_LIBRARY_LAYOUT_PATH,
554 (GConfClientNotifyFunc) rb_library_source_layout_path_changed, source);
555 source->priv->layout_filename_notify_id =
556 eel_gconf_notification_add (CONF_LIBRARY_LAYOUT_FILENAME,
557 (GConfClientNotifyFunc) rb_library_source_layout_filename_changed, source);
559 gconf_client = eel_gconf_client_get_global ();
560 /* layout path */
561 rb_library_source_layout_path_changed (gconf_client, -1,
562 gconf_client_get_entry (gconf_client, CONF_LIBRARY_LAYOUT_PATH, NULL, TRUE, NULL),
563 source);
564 /* layout filename */
565 rb_library_source_layout_filename_changed (gconf_client, -1,
566 gconf_client_get_entry (gconf_client, CONF_LIBRARY_LAYOUT_FILENAME, NULL, TRUE, NULL),
567 source);
568 #endif
571 static gboolean
572 rb_library_source_library_location_cb (GtkEntry *entry,
573 GdkEventFocus *event,
574 RBLibrarySource *source)
576 GSList *list = NULL;
577 const char *path;
578 char *uri;
580 path = gtk_entry_get_text (entry);
581 uri = gnome_vfs_make_uri_from_input (path);
583 if (uri && uri[0])
584 list = g_slist_prepend (NULL, (gpointer)uri);
586 eel_gconf_set_string_list (CONF_LIBRARY_LOCATION, list);
588 g_free (uri);
589 if (list)
590 g_slist_free (list);
592 /* don't do the first-run druid if the user sets the library location */
593 if (list)
594 eel_gconf_set_boolean (CONF_FIRST_TIME, TRUE);
596 return FALSE;
599 static void
600 rb_library_source_watch_toggled_cb (GtkToggleButton *button, RBLibrarySource *source)
602 gboolean active;
604 active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (source->priv->watch_library_check));
605 eel_gconf_set_boolean (CONF_MONITOR_LIBRARY, active);
608 static char *
609 impl_get_browser_key (RBSource *source)
611 return g_strdup (CONF_STATE_SHOW_BROWSER);
614 static const char *
615 impl_get_paned_key (RBBrowserSource *status)
617 return CONF_STATE_PANED_POSITION;
620 static void
621 rb_library_source_add_location_entry_changed_cb (GtkEntry *entry,
622 GtkWidget *target)
624 gtk_widget_set_sensitive (target, g_utf8_strlen (gtk_entry_get_text (GTK_ENTRY (entry)), -1) > 0);
627 void
628 rb_library_source_add_location (RBLibrarySource *source, GtkWindow *win)
630 GladeXML *xml = rb_glade_xml_new ("uri.glade",
631 "open_uri_dialog_content",
632 source);
633 GtkWidget *content, *uri_widget, *open_button;
634 GtkWidget *dialog = gtk_dialog_new_with_buttons (_("Add Location"),
635 win,
636 GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR,
637 GTK_STOCK_CANCEL,
638 GTK_RESPONSE_CANCEL,
639 NULL);
641 g_return_if_fail (dialog != NULL);
643 open_button = gtk_dialog_add_button (GTK_DIALOG (dialog),
644 GTK_STOCK_OPEN,
645 GTK_RESPONSE_OK);
646 gtk_widget_set_sensitive (open_button, FALSE);
648 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
649 GTK_RESPONSE_OK);
650 gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
651 gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 2);
653 gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
655 content = glade_xml_get_widget (xml, "open_uri_dialog_content");
657 g_return_if_fail (content != NULL);
659 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
660 content, FALSE, FALSE, 0);
661 gtk_container_set_border_width (GTK_CONTAINER (content), 5);
663 uri_widget = glade_xml_get_widget (xml, "uri");
665 g_return_if_fail (uri_widget != NULL);
667 g_signal_connect_object (G_OBJECT (uri_widget),
668 "changed",
669 G_CALLBACK (rb_library_source_add_location_entry_changed_cb),
670 open_button, 0);
672 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
673 char *uri = gtk_editable_get_chars (GTK_EDITABLE (uri_widget), 0, -1);
674 if (uri != NULL) {
675 GnomeVFSURI *vfsuri = gnome_vfs_uri_new (uri);
676 if (vfsuri != NULL) {
677 rhythmdb_add_uri (source->priv->db, uri);
678 gnome_vfs_uri_unref (vfsuri);
679 } else {
680 rb_debug ("invalid uri: \"%s\"", uri);
685 gtk_widget_destroy (GTK_WIDGET (dialog));
689 static gboolean
690 impl_receive_drag (RBSource *asource, GtkSelectionData *data)
692 RBLibrarySource *source = RB_LIBRARY_SOURCE (asource);
693 GList *list, *i;
694 GList *entries = NULL;
696 rb_debug ("parsing uri list");
697 list = rb_uri_list_parse ((const char *) data->data);
699 for (i = list; i != NULL; i = g_list_next (i)) {
700 if (i->data != NULL) {
701 char *uri = i->data;
702 RhythmDBEntry *entry;
704 entry = rhythmdb_entry_lookup_by_location (source->priv->db, uri);
706 if (entry == NULL) {
707 /* add to the library */
708 rhythmdb_add_uri (source->priv->db, uri);
709 } else {
710 /* add to list of entries to copy */
711 entries = g_list_prepend (entries, entry);
714 g_free (uri);
718 if (entries) {
719 entries = g_list_reverse (entries);
720 if (rb_source_can_paste (asource))
721 rb_source_paste (asource, entries);
722 g_list_free (entries);
725 g_list_free (list);
726 return TRUE;
729 static gboolean
730 impl_show_popup (RBSource *source)
732 _rb_source_show_popup (source, "/LibrarySourcePopup");
733 return TRUE;
736 #ifdef ENABLE_TRACK_TRANSFER
737 static void
738 rb_library_source_path_changed_cb (GtkComboBox *box, RBLibrarySource *source)
740 const char *path;
741 gint index;
743 index = gtk_combo_box_get_active (box);
744 path = (index >= 0) ? library_layout_paths[index].path : "";
745 eel_gconf_set_string (CONF_LIBRARY_LAYOUT_PATH, path);
748 static void
749 rb_library_source_filename_changed_cb (GtkComboBox *box, RBLibrarySource *source)
751 const char *filename;
752 gint index;
754 index = gtk_combo_box_get_active (box);
755 filename = (index >= 0) ? library_layout_filenames[index].path : "";
756 eel_gconf_set_string (CONF_LIBRARY_LAYOUT_FILENAME, filename);
759 static void
760 rb_library_source_format_changed_cb (GtkWidget *widget, RBLibrarySource *source)
762 GMAudioProfile *profile;
764 profile = gm_audio_profile_choose_get_active (widget);
765 eel_gconf_set_string (CONF_LIBRARY_PREFERRED_FORMAT, gm_audio_profile_get_id (profile));
769 * Perform magic on a path to make it safe.
771 * This will always replace '/' with ' ', and optionally make the file name
772 * shell-friendly. This involves removing [?*\ ] and replacing with '_'. Also
773 * any leading periods are removed so that the files don't end up being hidden.
775 static char *
776 sanitize_path (const char *str)
778 gchar *res = NULL;
779 gchar *s;
781 /* Skip leading periods, otherwise files disappear... */
782 while (*str == '.')
783 str++;
785 s = g_strdup(str);
786 /* Replace path seperators with a hyphen */
787 g_strdelimit (s, "/", '-');
788 if (eel_gconf_get_boolean (CONF_LIBRARY_STRIP_CHARS)) {
789 /* Replace separators with a hyphen */
790 g_strdelimit (s, "\\:|", '-');
791 /* Replace all other weird characters to whitespace */
792 g_strdelimit (s, "*?&!\'\"$()`>{}", ' ');
793 /* Replace all whitespace with underscores */
794 /* TODO: I'd like this to compress whitespace aswell */
795 g_strdelimit (s, "\t ", '_');
797 res = g_filename_from_utf8(s, -1, NULL, NULL, NULL);
798 g_free(s);
799 return res ? res : g_strdup(str);
803 * Parse a filename pattern and replace markers with values from a TrackDetails
804 * structure.
806 * Valid markers so far are:
807 * %at -- album title
808 * %aa -- album artist
809 * %aA -- album artist (lowercase)
810 * %as -- album artist sortname
811 * %aS -- album artist sortname (lowercase)
812 * %tn -- track number (i.e 8)
813 * %tN -- track number, zero padded (i.e 08)
814 * %tt -- track title
815 * %ta -- track artist
816 * %tA -- track artist (lowercase)
817 * %ts -- track artist sortname
818 * %tS -- track artist sortname (lowercase)
820 static char *
821 filepath_parse_pattern (const char *pattern,
822 RhythmDBEntry *entry)
824 /* p is the pattern iterator, i is a general purpose iterator */
825 const char *p;
826 char *temp;
827 GString *s;
829 if (pattern == NULL || pattern[0] == 0)
830 return g_strdup (" ");
832 s = g_string_new (NULL);
834 p = pattern;
835 while (*p) {
836 char *string = NULL;
838 /* If not a % marker, copy and continue */
839 if (*p != '%') {
840 g_string_append_c (s, *p++);
841 /* Explicit increment as we continue past the increment */
842 continue;
845 /* Is a % marker, go to next and see what to do */
846 switch (*++p) {
847 case '%':
849 * Literal %
851 g_string_append_c (s, '%');
852 break;
853 case 'a':
855 * Album tag
857 switch (*++p) {
858 case 't':
859 string = sanitize_path (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM));
860 break;
861 case 'T':
862 temp = sanitize_path (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM_FOLDED));
863 break;
864 case 'a':
865 string = sanitize_path (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST));
866 break;
867 case 'A':
868 string = sanitize_path (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST_FOLDED));
869 break;
870 /*case 's':
871 string = sanitize_path (album sort name);
872 break;
873 case 'S':
874 char *t = g_utf8_strdown (album sort name)
875 string = sanitize_path (t);
876 g_free (t);
877 break;*/
878 default:
879 string = g_strdup_printf ("%%a%c", *p);
882 break;
884 case 't':
886 * Track tag
888 switch (*++p) {
889 case 't':
890 string = sanitize_path (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE));
891 break;
892 case 'T':
893 string = sanitize_path (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE_FOLDED));
894 break;
895 case 'a':
896 string = sanitize_path (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST));
897 break;
898 case 'A':
899 string = sanitize_path (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST_FOLDED));
900 break;
901 /*case 's':
902 string = sanitize_path (artist sort name);
903 break;
904 case 'S':
905 char *t = g_utf8_strdown (artist sort name)
906 string = sanitize_path (t);
907 g_free (t);
908 break;*/
909 case 'n':
910 /* Track number */
911 string = g_strdup_printf ("%u", (guint)rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER));
912 break;
913 case 'N':
914 /* Track number, zero-padded */
915 string = g_strdup_printf ("%02u", (guint)rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER));
916 break;
917 default:
918 string = g_strdup_printf ("%%t%c", *p);
921 break;
923 default:
924 string = g_strdup_printf ("%%%c", *p);
927 if (string)
928 g_string_append (s, string);
929 g_free (string);
931 ++p;
934 temp = s->str;
935 g_string_free (s, FALSE);
936 return temp;
939 static void
940 layout_example_label_update (RBLibrarySource *source)
942 char *file_pattern;
943 char *path_pattern;
944 char *file_value;
945 char *path_value;
946 char *example;
947 char *format;
948 GMAudioProfile *profile;
949 RhythmDBEntry *sample_entry;
951 profile = gm_audio_profile_choose_get_active (source->priv->preferred_format_menu);
953 /* TODO: sucky. Replace with get-gconf-key-with-default mojo */
954 file_pattern = eel_gconf_get_string (CONF_LIBRARY_LAYOUT_FILENAME);
955 if (file_pattern == NULL) {
956 file_pattern = g_strdup (library_layout_filenames[0].path);
958 path_pattern = eel_gconf_get_string (CONF_LIBRARY_LAYOUT_PATH);
959 if (path_pattern == NULL) {
960 path_pattern = g_strdup (library_layout_paths[0].path);
963 sample_entry = rhythmdb_entry_example_new (source->priv->db, RHYTHMDB_ENTRY_TYPE_SONG, NULL);
964 file_value = filepath_parse_pattern (file_pattern, sample_entry);
965 path_value = filepath_parse_pattern (path_pattern, sample_entry);
966 rhythmdb_entry_unref (sample_entry);
968 example = g_build_filename (G_DIR_SEPARATOR_S, path_value, file_value, NULL);
969 g_free (file_value);
970 g_free (file_pattern);
971 g_free (path_value);
972 g_free (path_pattern);
974 format = g_strconcat ("<small><i><b>Example Path:</b> ",
975 example,
976 ".",
977 profile ? gm_audio_profile_get_extension (profile) : "ogg",
978 "</i></small>", NULL);
979 g_free (example);
981 gtk_label_set_markup (GTK_LABEL (source->priv->layout_example_label), format);
982 g_free (format);
985 static void
986 rb_library_source_layout_path_changed (GConfClient *client,
987 guint cnxn_id,
988 GConfEntry *entry,
989 RBLibrarySource *source)
991 char *value;
992 int i = 0;
994 g_return_if_fail (strcmp (entry->key, CONF_LIBRARY_LAYOUT_PATH) == 0);
996 rb_debug ("layout path changed");
998 if (entry->value == NULL) {
999 value = g_strdup (library_layout_paths[0].path);
1000 } else if (entry->value->type == GCONF_VALUE_STRING) {
1001 value = g_strdup (gconf_value_get_string (entry->value));
1002 } else {
1003 return;
1006 while (library_layout_paths[i].path && strcmp (library_layout_paths[i].path, value) != 0) {
1007 i++;
1010 g_free (value);
1011 gtk_combo_box_set_active (GTK_COMBO_BOX (source->priv->layout_path_menu), i);
1013 layout_example_label_update (source);
1016 static void
1017 rb_library_source_layout_filename_changed (GConfClient *client,
1018 guint cnxn_id,
1019 GConfEntry *entry,
1020 RBLibrarySource *source)
1022 char *value;
1023 int i = 0;
1025 g_return_if_fail (strcmp (entry->key, CONF_LIBRARY_LAYOUT_FILENAME) == 0);
1027 rb_debug ("layout filename changed");
1029 if (entry->value == NULL) {
1030 value = g_strdup (library_layout_filenames[0].path);
1031 } else if (entry->value->type == GCONF_VALUE_STRING) {
1032 value = g_strdup (gconf_value_get_string (entry->value));
1033 } else {
1034 return;
1037 while (library_layout_filenames[i].path && strcmp (library_layout_filenames[i].path, value) != 0) {
1038 i++;
1041 g_free (value);
1042 gtk_combo_box_set_active (GTK_COMBO_BOX (source->priv->layout_filename_menu), i);
1044 layout_example_label_update (source);
1048 * Build the absolute filename for the specified track.
1050 * The base path is the extern variable 'base_path', the format to use
1051 * is the extern variable 'file_pattern'. Free the result when you
1052 * have finished with it.
1054 * Stolen from Sound-Juicer
1056 static char*
1057 build_filename (RBLibrarySource *source, RhythmDBEntry *entry)
1059 GnomeVFSURI *uri;
1060 GnomeVFSURI *new;
1061 char *realfile;
1062 char *realpath;
1063 char *filename;
1064 char *string;
1065 char *extension = NULL;
1066 GSList *list;
1067 const char *layout_path;
1068 const char *layout_filename;
1069 const char *preferred_format;
1071 list = eel_gconf_get_string_list (CONF_LIBRARY_LOCATION);
1072 layout_path = eel_gconf_get_string (CONF_LIBRARY_LAYOUT_PATH);
1073 layout_filename = eel_gconf_get_string (CONF_LIBRARY_LAYOUT_FILENAME);
1074 preferred_format = eel_gconf_get_string (CONF_LIBRARY_PREFERRED_FORMAT);
1076 if (list == NULL || layout_path == NULL || layout_filename == NULL || preferred_format == NULL) {
1077 /* emit warning */
1078 rb_debug ("Could not retrieve settings from GConf");
1079 g_slist_free (list);
1080 return NULL;
1083 uri = gnome_vfs_uri_new ((const char *)list->data);
1084 g_slist_free (list);
1086 realpath = filepath_parse_pattern (layout_path, entry);
1087 new = gnome_vfs_uri_append_path (uri, realpath);
1088 gnome_vfs_uri_unref (uri);
1089 uri = new;
1090 g_free (realpath);
1092 if (g_str_has_prefix (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MIMETYPE), "audio/x-raw")) {
1093 GMAudioProfile *profile;
1094 profile = gm_audio_profile_lookup (preferred_format);
1095 if (profile)
1096 extension = g_strdup (gm_audio_profile_get_extension (profile));
1099 if (extension == NULL) {
1100 char *tmp;
1102 /* use the old extension. strip anything after a '?' for http/daap/etc */
1103 extension = g_strdup (g_utf8_strrchr (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION), -1, '.') + 1);
1105 tmp = g_utf8_strchr (extension, -1, '?');
1106 if (tmp)
1107 *tmp = '\0';
1110 realfile = filepath_parse_pattern (layout_filename, entry);
1111 if (extension) {
1112 filename = g_strdup_printf ("%s.%s", realfile, extension);
1113 g_free (realfile);
1114 } else {
1115 filename = realfile;
1118 new = gnome_vfs_uri_append_file_name (uri, filename);
1119 gnome_vfs_uri_unref (uri);
1120 uri = new;
1121 g_free (extension);
1122 g_free (filename);
1124 string = gnome_vfs_uri_to_string (uri, 0);
1125 gnome_vfs_uri_unref (uri);
1126 return string;
1128 #endif
1130 static gboolean
1131 impl_can_paste (RBSource *asource)
1133 #ifdef ENABLE_TRACK_TRANSFER
1134 GSList *list;
1135 gboolean can_paste = TRUE;
1137 list = eel_gconf_get_string_list (CONF_LIBRARY_LOCATION);
1138 can_paste = ((list != NULL) &&
1139 (eel_gconf_get_string (CONF_LIBRARY_LAYOUT_PATH) != NULL ) &&
1140 (eel_gconf_get_string (CONF_LIBRARY_LAYOUT_FILENAME) != NULL) &&
1141 (eel_gconf_get_string (CONF_LIBRARY_PREFERRED_FORMAT) != NULL));
1143 g_slist_free (list);
1144 return can_paste;
1145 #else
1146 return FALSE;
1147 #endif
1150 #ifdef ENABLE_TRACK_TRANSFER
1151 static void
1152 completed_cb (RhythmDBEntry *entry, const char *dest, RBLibrarySource *source)
1154 rhythmdb_add_uri (source->priv->db, dest);
1157 static void
1158 impl_paste (RBSource *asource, GList *entries)
1160 RBLibrarySource *source = RB_LIBRARY_SOURCE (asource);
1161 RBRemovableMediaManager *rm_mgr;
1162 GList *l;
1163 GSList *sl;
1164 RBShell *shell;
1166 sl = eel_gconf_get_string_list (CONF_LIBRARY_LOCATION);
1167 if ((sl == NULL) ||
1168 (eel_gconf_get_string (CONF_LIBRARY_LAYOUT_PATH) == NULL )||
1169 (eel_gconf_get_string (CONF_LIBRARY_LAYOUT_FILENAME) == NULL) ||
1170 (eel_gconf_get_string (CONF_LIBRARY_PREFERRED_FORMAT) == NULL)) {
1171 g_slist_free (sl);
1172 g_warning ("RBLibrarySource impl_paste called when gconf keys unset");
1173 return;
1176 g_object_get (source, "shell", &shell, NULL);
1177 g_object_get (shell, "removable-media-manager", &rm_mgr, NULL);
1178 g_object_unref (shell);
1180 for (l = entries; l != NULL; l = g_list_next (l)) {
1181 RhythmDBEntry *entry = (RhythmDBEntry *)l->data;
1182 RhythmDBEntryType entry_type;
1183 char *dest;
1185 rb_debug ("pasting entry %s", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
1187 entry_type = rhythmdb_entry_get_entry_type (entry);
1188 if (entry_type == RHYTHMDB_ENTRY_TYPE_SONG)
1189 /* copying to ourselves would be silly */
1190 continue;
1192 /* see if the responsible source lets us copy */
1193 if (!rb_source_can_copy (rb_shell_get_source_by_entry_type (shell, entry_type)))
1194 continue;
1196 dest = build_filename (source, entry);
1197 if (dest == NULL) {
1198 rb_debug ("could not create destination path for entry");
1199 continue;
1202 rb_removable_media_manager_queue_transfer (rm_mgr, entry,
1203 dest, NULL,
1204 (RBTranferCompleteCallback)completed_cb, source);
1207 g_object_unref (rm_mgr);
1209 #endif
1211 static guint
1212 impl_want_uri (RBSource *source, const char *uri)
1214 /* assume anything local or on smb is a song */
1215 if (rb_uri_is_local (uri) ||
1216 g_str_has_prefix (uri, "smb://"))
1217 return 50;
1219 return 0;
1222 static gboolean
1223 impl_add_uri (RBSource *asource, const char *uri, const char *title, const char *genre)
1225 RBLibrarySource *source = RB_LIBRARY_SOURCE (asource);
1226 /* FIXME should be synchronous */
1227 rb_debug ("adding uri %s to library", uri);
1228 rhythmdb_add_uri (source->priv->db, uri);
1229 return TRUE;
1232 static void
1233 rb_library_source_add_child_source (const char *path, RBLibrarySource *library_source)
1235 RBSource *source;
1236 GPtrArray *query;
1237 RBShell *shell;
1238 GnomeVFSURI *uri;
1239 char *name;
1240 GdkPixbuf *icon;
1242 g_object_get (library_source, "shell", &shell, NULL);
1243 uri = gnome_vfs_uri_new (path);
1244 name = gnome_vfs_uri_extract_short_name (uri);
1245 gnome_vfs_uri_unref (uri);
1247 source = rb_auto_playlist_source_new (shell, name, FALSE);
1248 query = rhythmdb_query_parse (library_source->priv->db,
1249 RHYTHMDB_QUERY_PROP_EQUALS, RHYTHMDB_PROP_TYPE, RHYTHMDB_ENTRY_TYPE_SONG,
1250 RHYTHMDB_QUERY_PROP_PREFIX, RHYTHMDB_PROP_LOCATION, path,
1251 RHYTHMDB_QUERY_END);
1252 rb_auto_playlist_source_set_query (RB_AUTO_PLAYLIST_SOURCE (source), query,
1253 RHYTHMDB_QUERY_MODEL_LIMIT_NONE, NULL,
1254 NULL, 0);
1255 rhythmdb_query_free (query);
1257 g_object_get (library_source, "icon", &icon, NULL);
1258 g_object_set (source, "icon", icon, NULL);
1259 if (icon != NULL) {
1260 g_object_unref (icon);
1263 rb_shell_append_source (shell, source, RB_SOURCE (library_source));
1264 library_source->priv->child_sources = g_list_prepend (library_source->priv->child_sources, source);
1266 g_object_unref (shell);
1267 g_free (name);
1270 static void
1271 rb_library_source_sync_child_sources (RBLibrarySource *source)
1273 GSList *list;
1275 list = eel_gconf_get_string_list (CONF_LIBRARY_LOCATION);
1277 /* FIXME: don't delete and re-create sources that are still there */
1278 g_list_foreach (source->priv->child_sources, (GFunc)rb_source_delete_thyself, NULL);
1279 g_list_free (source->priv->child_sources);
1280 source->priv->child_sources = NULL;
1282 if (g_slist_length (list) > 1)
1283 g_slist_foreach (list, (GFunc)rb_library_source_add_child_source, source);
1284 g_slist_foreach (list, (GFunc) g_free, NULL);
1285 g_slist_free (list);