[rhythmbox] add a new dialog for importing music into the library



commit 35f2586589deb7f095f74f81d3a948b098b24de2
Author: Jonathan Matthew <jonathan d14n org>
Date:   Sun Sep 23 17:21:16 2012 +1000

    add a new dialog for importing music into the library
    
    This replaces the previous menu items that just opened a file
    chooser window allowing selection of a file or folder to import.
    The new dialog scans all the files under a selected location and
    presents them all in a track list, allowing individual tracks
    to be added or copied into the library, or played without being
    added at all.
    
    In the future this will be extended to allow copying from media
    players and network shares (daap or upnp; smb etc. are already
    covered).
    
    On initial startup (with no library locations set), the contents
    of the default music directory are imported, and if that doesn't
    add anything to the library, the import dialog is shown.

 data/ui/import-dialog.ui    |  201 +++++++++++
 data/ui/rhythmbox-ui.xml    |    8 +-
 po/POTFILES.in              |    2 +
 shell/rb-shell.c            |  126 +------
 sources/rb-library-source.c |  112 ++++++-
 sources/rb-library-source.h |    2 +
 widgets/Makefile.am         |    6 +-
 widgets/rb-import-dialog.c  |  829 +++++++++++++++++++++++++++++++++++++++++++
 widgets/rb-import-dialog.h  |   73 ++++
 9 files changed, 1233 insertions(+), 126 deletions(-)
---
diff --git a/data/ui/import-dialog.ui b/data/ui/import-dialog.ui
new file mode 100644
index 0000000..0f1bf58
--- /dev/null
+++ b/data/ui/import-dialog.ui
@@ -0,0 +1,201 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.0 -->
+  <object class="GtkGrid" id="import-dialog">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="margin_top">6</property>
+    <property name="row_spacing">6</property>
+    <child>
+      <object class="GtkGrid" id="grid4">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="halign">center</property>
+        <property name="column_spacing">6</property>
+        <child>
+          <object class="GtkFileChooserButton" id="file-chooser-button">
+            <property name="width_request">300</property>
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="hexpand">True</property>
+            <property name="orientation">vertical</property>
+            <property name="action">select-folder</property>
+            <property name="local_only">False</property>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">0</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="add-button">
+            <property name="label" translatable="yes">Add Tracks</property>
+            <property name="use_action_appearance">False</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="tooltip_text" translatable="yes">Add tracks to the library</property>
+            <property name="use_action_appearance">False</property>
+          </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="top_attach">0</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="copy-button">
+            <property name="label" translatable="yes">Copy Tracks</property>
+            <property name="use_action_appearance">False</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="tooltip_text" translatable="yes">Copy tracks to the library location</property>
+            <property name="use_action_appearance">False</property>
+          </object>
+          <packing>
+            <property name="left_attach">2</property>
+            <property name="top_attach">0</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="close-button">
+            <property name="label" translatable="yes">Close</property>
+            <property name="use_action_appearance">False</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="use_action_appearance">False</property>
+          </object>
+          <packing>
+            <property name="left_attach">4</property>
+            <property name="top_attach">0</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="remove-button">
+            <property name="label" translatable="yes">Remove Tracks</property>
+            <property name="use_action_appearance">False</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="tooltip_text" translatable="yes">Remove the selected tracks</property>
+            <property name="use_action_appearance">False</property>
+          </object>
+          <packing>
+            <property name="left_attach">3</property>
+            <property name="top_attach">0</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">1</property>
+        <property name="width">1</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel" id="instructions">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="label" translatable="yes">Select a location containing music to add to your library.</property>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">0</property>
+        <property name="width">1</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkGrid" id="entry-view-container">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="hexpand">True</property>
+        <property name="vexpand">True</property>
+        <child>
+          <placeholder/>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">3</property>
+        <property name="width">1</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkGrid" id="info-bar-container">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <child>
+          <placeholder/>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">2</property>
+        <property name="width">1</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+  </object>
+</interface>
diff --git a/data/ui/rhythmbox-ui.xml b/data/ui/rhythmbox-ui.xml
index 10896b2..b2d73ce 100644
--- a/data/ui/rhythmbox-ui.xml
+++ b/data/ui/rhythmbox-ui.xml
@@ -1,8 +1,7 @@
 <ui>
   <menubar name="MenuBar">
     <menu name="MusicMenu" action="Music">
-      <menuitem name="MusicImportFileMenu" action="MusicImportFile"/>
-      <menuitem name="MusicImportFolderMenu" action="MusicImportFolder"/>
+      <menuitem name="MusicImport" action="MusicAdd"/>
       <separator/>
       <menuitem name="MusicCheckDevices" action="MusicCheckDevices"/>
       <separator/>
@@ -158,7 +157,7 @@
   <toolbar name="LibrarySourceToolBar">
     <toolitem name="Browse" action="ViewBrowser"/>
     <toolitem name="ViewAll" action="ViewAll"/>
-    <toolitem name="LibraryImport" action="MusicImportFolder"/>
+    <toolitem name="LibraryAdd" action="MusicAdd"/>
     <placeholder name="PluginPlaceholder"/>
   </toolbar>
 
@@ -251,8 +250,7 @@
   </popup>
 
   <popup name="LibrarySourcePopup">
-    <menuitem name="LibrarySrcPopupAddFile" action="MusicImportFile"/>
-    <menuitem name="LibrarySrcPopupAddFolder" action="MusicImportFolder"/>
+    <menuitem name="LibraryPopupAdd" action="MusicAdd"/>
     <separator/>
     <placeholder name="PluginPlaceholder"/>
   </popup>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 252ba73..f47a274 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -8,6 +8,7 @@ data/rhythmbox.desktop.in.in
 data/rhythmbox-device.desktop.in.in
 [type: gettext/glade]data/ui/create-playlist.ui
 [type: gettext/glade]data/ui/general-prefs.ui
+[type: gettext/glade]data/ui/import-dialog.ui
 [type: gettext/glade]data/ui/library-prefs.ui
 [type: gettext/glade]data/ui/media-player-properties.ui
 [type: gettext/glade]data/ui/playback-prefs.ui
@@ -180,6 +181,7 @@ widgets/rb-dialog.c
 widgets/rb-entry-view.c
 widgets/rb-fading-image.c
 widgets/rb-header.c
+widgets/rb-import-dialog.c
 widgets/rb-library-browser.c
 widgets/rb-property-view.c
 widgets/rb-query-creator.c
diff --git a/shell/rb-shell.c b/shell/rb-shell.c
index 55d43cf..9a88e28 100644
--- a/shell/rb-shell.c
+++ b/shell/rb-shell.c
@@ -168,10 +168,7 @@ static void rb_shell_cmd_preferences (GtkAction *action,
 		                      RBShell *shell);
 static void rb_shell_cmd_plugins (GtkAction *action,
 		                  RBShell *shell);
-static void rb_shell_cmd_add_folder_to_library (GtkAction *action,
-						RBShell *shell);
-static void rb_shell_cmd_add_file_to_library (GtkAction *action,
-					      RBShell *shell);
+static void rb_shell_cmd_add_music (GtkAction *action, RBShell *shell);
 
 static void rb_shell_cmd_current_song (GtkAction *action,
 				       RBShell *shell);
@@ -283,7 +280,6 @@ struct _RBShellPrivate
 
 	guint async_state_save_id;
 	guint save_playlist_id;
-	guint save_db_id;
 
 	gboolean shutting_down;
 	gboolean load_complete;
@@ -344,12 +340,9 @@ static GtkActionEntry rb_shell_actions [] =
 	{ "Tools", NULL, N_("_Tools") },
 	{ "Help", NULL, N_("_Help") },
 
-	{ "MusicImportFolder", GTK_STOCK_OPEN, N_("_Import Folder..."), "<control>O",
-	  N_("Choose folder to be added to the Library"),
-	  G_CALLBACK (rb_shell_cmd_add_folder_to_library) },
-	{ "MusicImportFile", GTK_STOCK_FILE, N_("Import _File..."), NULL,
-	  N_("Choose file to be added to the Library"),
-	  G_CALLBACK (rb_shell_cmd_add_file_to_library) },
+	{ "MusicAdd", GTK_STOCK_OPEN, N_("Add Music..."), "<control>O",
+	  N_("Add music to the library"),
+	  G_CALLBACK (rb_shell_cmd_add_music) },
 	{ "HelpAbout", GTK_STOCK_ABOUT, N_("_About"), NULL,
 	  N_("Show information about Rhythmbox"),
 	  G_CALLBACK (rb_shell_cmd_about) },
@@ -1802,16 +1795,6 @@ rb_shell_sync_state (RBShell *shell)
 }
 
 static gboolean
-idle_save_rhythmdb (RBShell *shell)
-{
-	rhythmdb_save (shell->priv->db);
-
-	shell->priv->save_db_id = 0;
-
-	return FALSE;
-}
-
-static gboolean
 idle_save_playlist_manager (RBShell *shell)
 {
 	GDK_THREADS_ENTER ();
@@ -1871,11 +1854,6 @@ rb_shell_finalize (GObject *object)
 		shell->priv->save_playlist_id = 0;
 	}
 
-	if (shell->priv->save_db_id > 0) {
-		g_source_remove (shell->priv->save_db_id);
-		shell->priv->save_db_id = 0;
-	}
-
 	if (shell->priv->queue_sidebar != NULL) {
 		g_object_unref (shell->priv->queue_sidebar);
 	}
@@ -2084,8 +2062,8 @@ rb_shell_constructed (GObject *object)
 					     rb_shell_n_toggle_entries,
 					     shell);
 
-	/* Translators: this is the short label for the 'import folder' action */
-	gtk_action_set_short_label (gtk_action_group_get_action (shell->priv->actiongroup, "MusicImportFolder"), C_("Library", "Import"));
+	/* Translators: this is the short label for the 'add music' action */
+	gtk_action_set_short_label (gtk_action_group_get_action (shell->priv->actiongroup, "MusicAdd"), C_("Library", "Import"));
 	/* Translators: this is the short label for the 'view all tracks' action */
 	gtk_action_set_short_label (gtk_action_group_get_action (shell->priv->actiongroup, "ViewAll"), _("Show All"));
 
@@ -2883,96 +2861,10 @@ rb_shell_cmd_plugins (GtkAction *action,
 }
 
 static void
-add_to_library_response_cb (GtkDialog *dialog,
-			    int response_id,
-			    RBShell *shell)
+rb_shell_cmd_add_music (GtkAction *action, RBShell *shell)
 {
-
-	char *current_dir = NULL;
-	GSList *uri_list = NULL, *uris = NULL;
-	GSettings *library_settings;
-
-	if (response_id != GTK_RESPONSE_ACCEPT) {
-		gtk_widget_destroy (GTK_WIDGET (dialog));
-		return;
-	}
-
-	library_settings = g_settings_new ("org.gnome.rhythmbox.library");
-	current_dir = gtk_file_chooser_get_current_folder_uri (GTK_FILE_CHOOSER (dialog));
-	g_settings_set_string (library_settings, "add-dir", current_dir);
-	g_object_unref (library_settings);
-
-	uri_list = gtk_file_chooser_get_uris (GTK_FILE_CHOOSER (dialog));
-	if (uri_list == NULL) {
-		uri_list = g_slist_prepend (uri_list, g_strdup (current_dir));
-	}
-
-	for (uris = uri_list; uris; uris = uris->next) {
-		rb_shell_load_uri (shell, (char *)uris->data, FALSE, NULL);
-		g_free (uris->data);
-	}
-	g_slist_free (uri_list);
-	g_free (current_dir);
-	gtk_widget_destroy (GTK_WIDGET (dialog));
-
-	if (shell->priv->save_db_id > 0) {
-		g_source_remove (shell->priv->save_db_id);
-	}
-	shell->priv->save_db_id = g_timeout_add_seconds (10, (GSourceFunc) idle_save_rhythmdb, shell);
-}
-
-static void
-set_current_folder_uri (RBShell *shell, GtkWidget *dialog)
-{
-	GSettings *settings;
-	char *dir;
-
-	settings = g_settings_new ("org.gnome.rhythmbox.library");
-	dir = g_settings_get_string (settings, "add-dir");
-	if (dir && dir[0] != '\0') {
-		gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dialog),
-							 dir);
-	}
-	g_free (dir);
-	g_object_unref (settings);
-}
-
-static void
-rb_shell_cmd_add_folder_to_library (GtkAction *action,
-				    RBShell *shell)
-{
-	GtkWidget *dialog;
-
-	dialog = rb_file_chooser_new (_("Import Folder into Library"),
-			              GTK_WINDOW (shell->priv->window),
-				      GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
-				      FALSE);
-	gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dialog), TRUE);
-	set_current_folder_uri (shell, dialog);
-
-	g_signal_connect_object (G_OBJECT (dialog),
-				 "response",
-				 G_CALLBACK (add_to_library_response_cb),
-				 shell, 0);
-}
-
-static void
-rb_shell_cmd_add_file_to_library (GtkAction *action,
-				  RBShell *shell)
-{
-	GtkWidget *dialog;
-
-	dialog = rb_file_chooser_new (_("Import File into Library"),
-			              GTK_WINDOW (shell->priv->window),
-				      GTK_FILE_CHOOSER_ACTION_OPEN,
-				      FALSE);
-	gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dialog), TRUE);
-	set_current_folder_uri (shell, dialog);
-
-	g_signal_connect_object (G_OBJECT (dialog),
-				 "response",
-				 G_CALLBACK (add_to_library_response_cb),
-				 shell, 0);
+	rb_shell_select_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source));
+	rb_library_source_show_import_dialog (shell->priv->library_source);
 }
 
 static gboolean
diff --git a/sources/rb-library-source.c b/sources/rb-library-source.c
index 13dddbf..061b4bd 100644
--- a/sources/rb-library-source.c
+++ b/sources/rb-library-source.c
@@ -68,6 +68,10 @@
 #include "rb-missing-plugins.h"
 #include "rb-gst-media-types.h"
 #include "rb-object-property-editor.h"
+#include "rb-import-dialog.h"
+
+#define SOURCE_PAGE		0
+#define IMPORT_DIALOG_PAGE	1
 
 static void rb_library_source_class_init (RBLibrarySourceClass *klass);
 static void rb_library_source_init (RBLibrarySource *source);
@@ -90,6 +94,7 @@ static void impl_add_uri (RBSource *source,
 			  RBSourceAddCallback callback,
 			  gpointer data,
 			  GDestroyNotify destroy_data);
+static void impl_pack_content (RBBrowserSource *source, GtkWidget *content);
 
 static void library_settings_changed_cb (GSettings *settings, const char *key, RBLibrarySource *source);
 static void encoding_settings_changed_cb (GSettings *settings, const char *key, RBLibrarySource *source);
@@ -141,10 +146,11 @@ struct RBLibrarySourcePrivate
 {
 	RhythmDB *db;
 
-	gboolean loading_prefs;
 	RBShellPreferences *shell_prefs;
 
+	GtkWidget *notebook;
 	GtkWidget *config_widget;
+	GtkWidget *import_dialog;
 
 	GList *child_sources;
 
@@ -167,6 +173,7 @@ struct RBLibrarySourcePrivate
 	gulong profile_changed_id;
 	gboolean custom_settings_exists;
 	gboolean profile_init;
+	gboolean do_initial_import;
 
 	GSettings *settings;
 	GSettings *db_settings;
@@ -200,6 +207,7 @@ rb_library_source_class_init (RBLibrarySourceClass *klass)
 	source_class->impl_add_uri = impl_add_uri;
 
 	browser_source_class->has_drop_support = (RBBrowserSourceFeatureFunc) rb_true_function;
+	browser_source_class->pack_content = impl_pack_content;
 
 	g_type_class_add_private (klass, sizeof (RBLibrarySourcePrivate));
 }
@@ -275,10 +283,42 @@ rb_library_source_finalize (GObject *object)
 }
 
 static void
+initial_import_job_complete_cb (RhythmDBImportJob *job, int total, RBLibrarySource *source)
+{
+	if (rhythmdb_import_job_get_imported (job) == 0) {
+		rb_library_source_show_import_dialog (source);
+	}
+}
+
+static void
 db_load_complete_cb (RhythmDB *db, RBLibrarySource *source)
 {
+	RhythmDBImportJob *job;
+
 	/* once the database is loaded, we can run the query to populate the library source */
 	g_object_set (source, "populate", TRUE, NULL);
+
+	if (source->priv->do_initial_import) {
+		const char *music_dir;
+		char *music_dir_uri;
+		
+		music_dir = rb_music_dir ();
+		music_dir_uri = g_filename_to_uri (music_dir, NULL, NULL);
+
+		/* create the music dir if it doesn't exist */
+		if (g_file_test (music_dir, G_FILE_TEST_EXISTS) == FALSE) {
+			g_mkdir_with_parents (music_dir, 0700);
+		}
+
+		/* import anything that's already in there */
+		job = maybe_create_import_job (source);
+		rhythmdb_import_job_add_uri (job, music_dir_uri);
+
+		/* if this doesn't import anything, show the import dialog */
+		g_signal_connect (job, "complete", G_CALLBACK (initial_import_job_complete_cb), source);
+
+		g_free (music_dir_uri);
+	}
 }
 
 static void
@@ -289,12 +329,21 @@ rb_library_source_constructed (GObject *object)
 	RBEntryView *songs;
 	char **locations;
 
-	RB_CHAIN_GOBJECT_METHOD (rb_library_source_parent_class, constructed, object);
 	source = RB_LIBRARY_SOURCE (object);
+	source->priv->notebook = gtk_notebook_new ();
+	gtk_notebook_set_show_tabs (GTK_NOTEBOOK (source->priv->notebook), FALSE);
+	gtk_notebook_set_show_border (GTK_NOTEBOOK (source->priv->notebook), FALSE);
+
+	RB_CHAIN_GOBJECT_METHOD (rb_library_source_parent_class, constructed, object);
 
 	g_object_get (source, "shell", &shell, NULL);
 	g_object_get (shell, "db", &source->priv->db, NULL);
 
+	gtk_container_add (GTK_CONTAINER (source), source->priv->notebook);
+
+	gtk_notebook_set_current_page (GTK_NOTEBOOK (source->priv->notebook), 0);
+	gtk_widget_show_all (source->priv->notebook);
+
 	source->priv->settings = g_settings_new ("org.gnome.rhythmbox.library");
 	g_signal_connect_object (source->priv->settings, "changed", G_CALLBACK (library_settings_changed_cb), source, 0);
 
@@ -314,10 +363,13 @@ rb_library_source_constructed (GObject *object)
 		music_dir_uri = g_filename_to_uri (rb_music_dir (), NULL, NULL);
 		if (music_dir_uri != NULL) {
 			const char *set_locations[2];
+
 			set_locations[0] = music_dir_uri;
 			set_locations[1] = NULL;
 			g_settings_set_strv (source->priv->db_settings, "locations", set_locations);
 
+			source->priv->do_initial_import = TRUE;
+
 			g_free (music_dir_uri);
 		}
 	}
@@ -376,6 +428,14 @@ rb_library_source_new (RBShell *shell)
 }
 
 static void
+impl_pack_content (RBBrowserSource *bsource, GtkWidget *content)
+{
+	RBLibrarySource *source = RB_LIBRARY_SOURCE (bsource);
+	gtk_notebook_append_page (GTK_NOTEBOOK (source->priv->notebook), content, NULL);
+	gtk_widget_show_all (content);
+}
+
+static void
 location_response_cb (GtkDialog *dialog, int response, RBLibrarySource *source)
 {
 	char *uri;
@@ -1875,5 +1935,53 @@ impl_get_status (RBDisplayPage *source, char **text, char **progress_text, float
 	if (lsource->priv->import_jobs != NULL) {
 		RhythmDBImportJob *job = RHYTHMDB_IMPORT_JOB (lsource->priv->import_jobs->data);
 		_rb_source_set_import_status (RB_SOURCE (source), job, progress_text, progress);
+	} else if (gtk_notebook_get_current_page (GTK_NOTEBOOK (lsource->priv->notebook)) == IMPORT_DIALOG_PAGE) {
+		g_free (*text);
+		g_object_get (lsource->priv->import_dialog, "status", text, NULL);
+	}
+}
+
+static void
+import_dialog_closed_cb (RBImportDialog *dialog, RBLibrarySource *source)
+{
+	gtk_notebook_set_current_page (GTK_NOTEBOOK (source->priv->notebook), 0);
+	rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
+}
+
+static void
+import_dialog_status_notify_cb (GObject *dialog, GParamSpec *pspec, RBLibrarySource *source)
+{
+	rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
+}
+
+void
+rb_library_source_show_import_dialog (RBLibrarySource *source)
+{
+	if (source->priv->import_dialog == NULL) {
+		RBShell *shell;
+
+		g_object_get (source, "shell", &shell, NULL);
+		source->priv->import_dialog = rb_import_dialog_new (shell);
+		g_object_unref (shell);
+
+		g_signal_connect (source->priv->import_dialog,
+				  "closed",
+				  G_CALLBACK (import_dialog_closed_cb),
+				  source);
+		g_signal_connect (source->priv->import_dialog,
+				  "notify::status",
+				  G_CALLBACK (import_dialog_status_notify_cb),
+				  source);
+
+		gtk_widget_show_all (GTK_WIDGET (source->priv->import_dialog));
+		gtk_notebook_append_page (GTK_NOTEBOOK (source->priv->notebook),
+					  source->priv->import_dialog,
+					  NULL);
+	}
+
+	if (gtk_notebook_get_current_page (GTK_NOTEBOOK (source->priv->notebook)) != IMPORT_DIALOG_PAGE) {
+		rb_import_dialog_reset (RB_IMPORT_DIALOG (source->priv->import_dialog));
+		gtk_notebook_set_current_page (GTK_NOTEBOOK (source->priv->notebook), IMPORT_DIALOG_PAGE);
+		rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
 	}
 }
diff --git a/sources/rb-library-source.h b/sources/rb-library-source.h
index 149175f..649d966 100644
--- a/sources/rb-library-source.h
+++ b/sources/rb-library-source.h
@@ -64,6 +64,8 @@ GType		rb_library_source_get_type		(void);
 
 RBSource *      rb_library_source_new			(RBShell *shell);
 
+void		rb_library_source_show_import_dialog	(RBLibrarySource *source);
+
 G_END_DECLS
 
 #endif /* __RB_LIBRARY_SOURCE_H */
diff --git a/widgets/Makefile.am b/widgets/Makefile.am
index e46096f..07e8fbd 100644
--- a/widgets/Makefile.am
+++ b/widgets/Makefile.am
@@ -15,7 +15,8 @@ widgetinclude_HEADERS =					\
 	rb-source-toolbar.h				\
 	rb-uri-dialog.h					\
 	rb-fading-image.h				\
-	rb-object-property-editor.h
+	rb-object-property-editor.h			\
+	rb-import-dialog.h
 
 librbwidgets_la_SOURCES =				\
 	$(widgetinclude_HEADERS)			\
@@ -48,7 +49,8 @@ librbwidgets_la_SOURCES =				\
 	eggwrapbox-enums.h				\
 	rb-source-toolbar.c				\
 	rb-fading-image.c				\
-	rb-object-property-editor.c
+	rb-object-property-editor.c			\
+	rb-import-dialog.c
 
 INCLUDES =						\
 	-DGNOMELOCALEDIR=\""$(datadir)/locale"\"        \
diff --git a/widgets/rb-import-dialog.c b/widgets/rb-import-dialog.c
new file mode 100644
index 0000000..9fe7f98
--- /dev/null
+++ b/widgets/rb-import-dialog.c
@@ -0,0 +1,829 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2012 Jonathan Matthew <jonathan d14n org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "rb-import-dialog.h"
+#include "rb-shell-player.h"
+#include "rb-builder-helpers.h"
+#include "rb-debug.h"
+#include "rb-util.h"
+#include "rb-cut-and-paste-code.h"
+#include "rb-search-entry.h"
+#include "rhythmdb-entry-type.h"
+#include "rb-device-source.h"
+#include "rb-file-helpers.h"
+
+/* normal entries */
+typedef struct _RhythmDBEntryType RBImportDialogEntryType;
+typedef struct _RhythmDBEntryTypeClass RBImportDialogEntryTypeClass;
+
+static void rb_import_dialog_entry_type_class_init (RBImportDialogEntryTypeClass *klass);
+static void rb_import_dialog_entry_type_init (RBImportDialogEntryType *etype);
+GType rb_import_dialog_entry_type_get_type (void);
+
+G_DEFINE_TYPE (RBImportDialogEntryType, rb_import_dialog_entry_type, RHYTHMDB_TYPE_ENTRY_TYPE);
+
+/* ignored files */
+typedef struct _RhythmDBEntryType RBImportDialogIgnoreType;
+typedef struct _RhythmDBEntryTypeClass RBImportDialogIgnoreTypeClass;
+
+static void rb_import_dialog_ignore_type_class_init (RBImportDialogIgnoreTypeClass *klass);
+static void rb_import_dialog_ignore_type_init (RBImportDialogIgnoreType *etype);
+GType rb_import_dialog_ignore_type_get_type (void);
+
+G_DEFINE_TYPE (RBImportDialogIgnoreType, rb_import_dialog_ignore_type, RHYTHMDB_TYPE_ENTRY_TYPE);
+
+
+static void rb_import_dialog_class_init (RBImportDialogClass *klass);
+static void rb_import_dialog_init (RBImportDialog *dialog);
+
+enum {
+	PROP_0,
+	PROP_SHELL,
+	PROP_STATUS
+};
+
+enum {
+	CLOSE,
+	CLOSED,
+	LAST_SIGNAL
+};
+
+struct RBImportDialogPrivate
+{
+	RhythmDB *db;
+	RBShell *shell;
+	RBShellPlayer *shell_player;
+
+	RhythmDBQueryModel *query_model;
+	RBEntryView *entry_view;
+
+	GtkWidget *info_bar;
+	GtkWidget *info_bar_container;
+	GtkWidget *import_progress;
+	GtkWidget *file_chooser;
+	GtkWidget *add_button;
+	GtkWidget *copy_button;
+	GtkWidget *remove_button;
+
+	RhythmDBEntryType *entry_type;
+	RhythmDBEntryType *ignore_type;
+	RhythmDBImportJob *import_job;
+	int entry_count;
+	gboolean can_copy;
+	gboolean can_add;
+
+	GList *add_entry_list;
+	guint add_entries_id;
+	guint added_entries_id;
+
+	char *current_uri;
+	guint pulse_id;
+};
+
+static guint signals[LAST_SIGNAL] = {0,};
+
+G_DEFINE_TYPE (RBImportDialog, rb_import_dialog, GTK_TYPE_GRID);
+
+static void
+rb_import_dialog_entry_type_class_init (RBImportDialogEntryTypeClass *klass)
+{
+}
+
+static void
+rb_import_dialog_entry_type_init (RBImportDialogEntryType *etype)
+{
+}
+
+static void
+rb_import_dialog_ignore_type_class_init (RBImportDialogIgnoreTypeClass *klass)
+{
+}
+
+static void
+rb_import_dialog_ignore_type_init (RBImportDialogIgnoreType *etype)
+{
+}
+
+
+static void
+sort_changed_cb (GObject *object, GParamSpec *pspec, RBImportDialog *dialog)
+{
+	rb_entry_view_resort_model (RB_ENTRY_VIEW (object));
+}
+
+static void
+impl_close (RBImportDialog *dialog)
+{
+	g_signal_emit (dialog, signals[CLOSED], 0);
+}
+
+static void
+entry_activated_cb (RBEntryView *entry_view, RhythmDBEntry *entry, RBImportDialog *dialog)
+{
+	rb_debug ("import dialog entry %s activated", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
+	rb_shell_load_uri (dialog->priv->shell,
+			   rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
+			   TRUE,
+			   NULL);
+}
+
+static void
+clear_info_bar (RBImportDialog *dialog)
+{
+	if (dialog->priv->info_bar != NULL) {
+		gtk_container_remove (GTK_CONTAINER (dialog->priv->info_bar_container), dialog->priv->info_bar);
+		dialog->priv->info_bar = NULL;
+	}
+}
+
+static gboolean
+collect_entries (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, GList **list)
+{
+	RhythmDBEntry *entry;
+
+	entry = rhythmdb_query_model_iter_to_entry (RHYTHMDB_QUERY_MODEL (model), iter);
+	*list = g_list_prepend (*list, rhythmdb_entry_ref (entry));
+	return FALSE;
+}
+
+static GList *
+get_entries (RBImportDialog *dialog)
+{
+	/* selection if non-empty, all entries otherwise */
+	if (rb_entry_view_have_selection (dialog->priv->entry_view)) {
+		return rb_entry_view_get_selected_entries (dialog->priv->entry_view);
+	} else {
+		GList *l = NULL;
+
+		gtk_tree_model_foreach (GTK_TREE_MODEL (dialog->priv->query_model),
+					(GtkTreeModelForeachFunc) collect_entries,
+					&l);
+		return g_list_reverse (l);
+	}
+}
+
+static gboolean
+add_entries_done (RBImportDialog *dialog)
+{
+	/* if we added all the tracks, close the dialog */
+	if (dialog->priv->entry_count == 0) {
+		g_signal_emit (dialog, signals[CLOSED], 0);
+	}
+
+	dialog->priv->added_entries_id = 0;
+	return FALSE;
+}
+
+static gboolean
+add_entries (RBImportDialog *dialog)
+{
+	int i;
+	GValue new_type = {0,};
+
+	g_value_init (&new_type, G_TYPE_OBJECT);
+	g_value_set_object (&new_type, RHYTHMDB_ENTRY_TYPE_SONG);
+
+	for (i = 0; i < 1000; i++) {
+		RhythmDBEntry *entry;
+
+		entry = dialog->priv->add_entry_list->data;
+		dialog->priv->add_entry_list = g_list_delete_link (dialog->priv->add_entry_list, dialog->priv->add_entry_list);
+
+		rhythmdb_entry_set (dialog->priv->db, entry, RHYTHMDB_PROP_TYPE, &new_type);
+		rhythmdb_entry_unref (entry);
+
+		if (dialog->priv->add_entry_list == NULL)
+			break;
+	}
+	
+	rhythmdb_commit (dialog->priv->db);
+
+	if (dialog->priv->add_entry_list == NULL) {
+		dialog->priv->add_entries_id = 0;
+
+		dialog->priv->added_entries_id = g_idle_add ((GSourceFunc) add_entries_done, dialog);
+
+		return FALSE;
+	} else {
+		return TRUE;
+	}
+}
+
+static void
+add_clicked_cb (GtkButton *button, RBImportDialog *dialog)
+{
+	GList *entries;
+
+	entries = get_entries (dialog);
+	dialog->priv->add_entry_list = g_list_concat (dialog->priv->add_entry_list, entries);
+
+	if (dialog->priv->add_entries_id == 0) {
+		dialog->priv->add_entries_id = g_idle_add ((GSourceFunc) add_entries, dialog);
+	}
+}
+
+static void
+copy_track_done_cb (RBTrackTransferBatch *batch,
+		    RhythmDBEntry *entry,
+		    const char *dest,
+		    guint64 dest_size,
+		    const char *mediatype,
+		    GError *error,
+		    RBImportDialog *dialog)
+{
+	int total;
+	int done;
+
+	g_object_get (batch, "total-entries", &total, "done-entries", &done, NULL);
+	gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (dialog->priv->import_progress),
+				       (float)done / total);
+
+	rhythmdb_entry_delete (dialog->priv->db, entry);
+	rhythmdb_commit (dialog->priv->db);
+}
+
+static void
+copy_complete_cb (RBTrackTransferBatch *batch, RBImportDialog *dialog)
+{
+	clear_info_bar (dialog);
+
+	/* if we copied everything, close the dialog */
+	if (dialog->priv->entry_count == 0) {
+		g_signal_emit (dialog, signals[CLOSED], 0);
+	}
+}
+
+static void
+copy_clicked_cb (GtkButton *button, RBImportDialog *dialog)
+{
+	RBSource *library_source;
+	RBTrackTransferBatch *batch;
+	GList *entries;
+	GtkWidget *content;
+	GtkWidget *label;
+       
+	g_object_get (dialog->priv->shell, "library-source", &library_source, NULL);
+
+	entries = get_entries (dialog);
+	batch = rb_source_paste (library_source, entries);
+	g_list_free_full (entries, (GDestroyNotify) rhythmdb_entry_unref);
+	g_object_unref (library_source);
+
+	/* delete source entries as they finish being copied */
+	g_signal_connect (batch, "track-done", G_CALLBACK (copy_track_done_cb), dialog);
+	g_signal_connect (batch, "complete", G_CALLBACK (copy_complete_cb), dialog);
+
+	/* set up info bar for copy progress */
+	clear_info_bar (dialog);
+
+	dialog->priv->info_bar = gtk_info_bar_new ();
+	g_object_set (dialog->priv->info_bar, "hexpand", TRUE, NULL);
+	gtk_info_bar_set_message_type (GTK_INFO_BAR (dialog->priv->info_bar), GTK_MESSAGE_INFO);
+	content = gtk_info_bar_get_content_area (GTK_INFO_BAR (dialog->priv->info_bar));
+
+	label = gtk_label_new (_("Copying..."));
+	gtk_container_add (GTK_CONTAINER (content), label);
+
+	dialog->priv->import_progress = gtk_progress_bar_new ();
+	content = gtk_info_bar_get_action_area (GTK_INFO_BAR (dialog->priv->info_bar));
+	gtk_container_add (GTK_CONTAINER (content), dialog->priv->import_progress);
+
+	gtk_container_add (GTK_CONTAINER (dialog->priv->info_bar_container), dialog->priv->info_bar);
+	gtk_widget_show_all (dialog->priv->info_bar);
+}
+
+static void
+remove_clicked_cb (GtkButton *button, RBImportDialog *dialog)
+{
+	GList *entries;
+	GList *l;
+
+	entries = rb_entry_view_get_selected_entries (dialog->priv->entry_view);
+	for (l = entries; l != NULL; l = l->next) {
+		rhythmdb_entry_delete (dialog->priv->db, l->data);
+	}
+	rhythmdb_commit (dialog->priv->db);
+	g_list_free_full (entries, (GDestroyNotify) rhythmdb_entry_unref);
+}
+
+static void
+close_clicked_cb (GtkButton *button, RBImportDialog *dialog)
+{
+	g_signal_emit (dialog, signals[CLOSED], 0);
+}
+
+static void
+import_status_changed_cb (RhythmDBImportJob *job, int total, int imported, RBImportDialog *dialog)
+{
+	if (dialog->priv->pulse_id == 0) {
+		gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (dialog->priv->import_progress),
+					       (float)imported / total);
+	}
+}
+
+static void
+import_scan_complete_cb (RhythmDBImportJob *job, int total, RBImportDialog *dialog)
+{
+	if (dialog->priv->pulse_id != 0) {
+		g_source_remove (dialog->priv->pulse_id);
+		dialog->priv->pulse_id = 0;
+	}
+
+	gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (dialog->priv->import_progress), 0.0);
+}
+
+static void
+import_complete_cb (RhythmDBImportJob *job, int total, RBImportDialog *dialog)
+{
+	g_object_unref (job);
+	dialog->priv->import_job = NULL;
+
+	clear_info_bar (dialog);
+}
+
+static gboolean
+pulse_cb (RBImportDialog *dialog)
+{
+	gtk_progress_bar_pulse (GTK_PROGRESS_BAR (dialog->priv->import_progress));
+	return TRUE;
+}
+
+static void
+start_scanning (RBImportDialog *dialog)
+{
+	rb_debug ("starting %s\n", dialog->priv->current_uri);
+	dialog->priv->import_job = rhythmdb_import_job_new (dialog->priv->db,
+							    dialog->priv->entry_type,
+							    dialog->priv->ignore_type,
+							    dialog->priv->ignore_type);
+	g_signal_connect (dialog->priv->import_job, "status-changed", G_CALLBACK (import_status_changed_cb), dialog);
+	g_signal_connect (dialog->priv->import_job, "scan-complete", G_CALLBACK (import_scan_complete_cb), dialog);
+	g_signal_connect (dialog->priv->import_job, "complete", G_CALLBACK (import_complete_cb), dialog);
+	rhythmdb_import_job_add_uri (dialog->priv->import_job, dialog->priv->current_uri);
+	rhythmdb_import_job_start (dialog->priv->import_job);
+
+	dialog->priv->pulse_id = g_timeout_add (100, (GSourceFunc) pulse_cb, dialog);
+}
+
+static void
+start_deferred_scan (RhythmDBImportJob *job, int total, RBImportDialog *dialog)
+{
+	rb_debug ("previous import job finished");
+	start_scanning (dialog);
+}
+
+static void
+info_bar_response_cb (GtkInfoBar *bar, gint response, RBImportDialog *dialog)
+{
+	RBSource *source;
+	const char *uri;
+
+	g_signal_emit (dialog, signals[CLOSED], 0);
+	uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog->priv->file_chooser));
+	source = rb_shell_guess_source_for_uri (dialog->priv->shell, uri);
+	rb_shell_activate_source (dialog->priv->shell, source, FALSE, NULL);
+}
+
+
+static void
+current_folder_changed_cb (GtkFileChooser *chooser, RBImportDialog *dialog)
+{
+	GSettings *settings;
+	RBSource *source;
+	GtkWidget *label;
+	GtkWidget *content;
+	const char *uri;
+	char **locations;
+	int i;
+	
+	uri = gtk_file_chooser_get_uri (chooser);
+	if (g_strcmp0 (uri, dialog->priv->current_uri) == 0)
+		return;
+	g_free (dialog->priv->current_uri);
+	dialog->priv->current_uri = g_strdup (uri);
+
+	if (dialog->priv->import_job != NULL) {
+		g_signal_handlers_disconnect_by_func (dialog->priv->import_job, G_CALLBACK (import_status_changed_cb), dialog);
+		g_signal_handlers_disconnect_by_func (dialog->priv->import_job, G_CALLBACK (import_scan_complete_cb), dialog);
+		g_signal_handlers_disconnect_by_func (dialog->priv->import_job, G_CALLBACK (import_complete_cb), dialog);
+		rhythmdb_import_job_cancel (dialog->priv->import_job);
+	}
+	if (dialog->priv->pulse_id != 0) {
+		g_source_remove (dialog->priv->pulse_id);
+		dialog->priv->pulse_id = 0;
+	}
+
+	rhythmdb_entry_delete_by_type (dialog->priv->db, dialog->priv->entry_type);
+	rhythmdb_entry_delete_by_type (dialog->priv->db, dialog->priv->ignore_type);
+	rhythmdb_commit (dialog->priv->db);
+
+	clear_info_bar (dialog);
+
+	/* disable copy if the selected location is already inside the library */
+	settings = g_settings_new ("org.gnome.rhythmbox.rhythmdb");
+	locations = g_settings_get_strv (settings, "locations");
+	dialog->priv->can_copy = TRUE;
+	for (i = 0; locations[i] != NULL; i++) {
+		if (g_str_has_prefix (uri, locations[i])) {
+			dialog->priv->can_copy = FALSE;
+			break;
+		}
+	}
+	g_strfreev (locations);
+	g_object_unref (settings);
+
+	dialog->priv->can_add = TRUE;
+
+	source = rb_shell_guess_source_for_uri (dialog->priv->shell, uri);
+	if (source != NULL) {
+		if (RB_IS_DEVICE_SOURCE (source)) {
+			char *msg;
+			char *name;
+
+			dialog->priv->info_bar = gtk_info_bar_new ();
+			g_object_set (dialog->priv->info_bar, "hexpand", TRUE, NULL);
+
+			g_object_get (source, "name", &name, NULL);
+
+			/* this isn't a terribly helpful message. */
+			msg = g_strdup_printf (_("The location you have selected is on the device %s."), name);
+			label = gtk_label_new (msg);
+			g_free (msg);
+			content = gtk_info_bar_get_content_area (GTK_INFO_BAR (dialog->priv->info_bar));
+			gtk_container_add (GTK_CONTAINER (content), label);
+
+			msg = g_strdup_printf (_("Show %s"), name);
+			gtk_info_bar_add_button (GTK_INFO_BAR (dialog->priv->info_bar), msg, GTK_RESPONSE_ACCEPT);
+			g_free (msg);
+
+			g_signal_connect (dialog->priv->info_bar, "response", G_CALLBACK (info_bar_response_cb), dialog);
+
+			gtk_widget_show_all (dialog->priv->info_bar);
+			gtk_container_add (GTK_CONTAINER (dialog->priv->info_bar_container), dialog->priv->info_bar);
+			return;
+		}
+	}
+
+	dialog->priv->info_bar = gtk_info_bar_new ();
+	g_object_set (dialog->priv->info_bar, "hexpand", TRUE, NULL);
+	gtk_info_bar_set_message_type (GTK_INFO_BAR (dialog->priv->info_bar), GTK_MESSAGE_INFO);
+	content = gtk_info_bar_get_content_area (GTK_INFO_BAR (dialog->priv->info_bar));
+
+	label = gtk_label_new (_("Scanning..."));
+	gtk_container_add (GTK_CONTAINER (content), label);
+
+	dialog->priv->import_progress = gtk_progress_bar_new ();
+	content = gtk_info_bar_get_action_area (GTK_INFO_BAR (dialog->priv->info_bar));
+	gtk_container_add (GTK_CONTAINER (content), dialog->priv->import_progress);
+
+	gtk_container_add (GTK_CONTAINER (dialog->priv->info_bar_container), dialog->priv->info_bar);
+	gtk_widget_show_all (dialog->priv->info_bar);
+
+	if (dialog->priv->import_job != NULL) {
+		/* wait for the previous job to finish up */
+		rb_debug ("need to wait for previous import job to finish");
+		g_signal_connect (dialog->priv->import_job, "complete", G_CALLBACK (start_deferred_scan), dialog);
+	} else {
+		start_scanning (dialog);
+	}
+}
+
+static void
+entry_inserted_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, RBImportDialog *dialog)
+{
+	if (dialog->priv->entry_count == 0) {
+		gtk_widget_set_sensitive (dialog->priv->add_button, dialog->priv->can_add);
+		gtk_widget_set_sensitive (dialog->priv->copy_button, dialog->priv->can_copy);
+	}
+
+	dialog->priv->entry_count++;
+	g_object_notify (G_OBJECT (dialog), "status");
+}
+
+static void
+entry_deleted_cb (GtkTreeModel *model, RhythmDBEntry *entry, RBImportDialog *dialog)
+{
+	dialog->priv->entry_count--;
+	if (dialog->priv->entry_count == 0) {
+		gtk_widget_set_sensitive (dialog->priv->add_button, FALSE);
+		gtk_widget_set_sensitive (dialog->priv->copy_button, FALSE);
+	}
+
+	g_object_notify (G_OBJECT (dialog), "status");
+}
+
+static void
+have_selection_changed_cb (RBEntryView *view, gboolean have_selection, RBImportDialog *dialog)
+{
+	gtk_widget_set_sensitive (dialog->priv->remove_button, have_selection);
+}
+
+static void
+impl_constructed (GObject *object)
+{
+	RBImportDialog *dialog;
+	RhythmDBQuery *query;
+	GtkBuilder *builder;
+	GSettings *settings;
+	char **locations;
+
+	RB_CHAIN_GOBJECT_METHOD (rb_import_dialog_parent_class, constructed, object);
+	dialog = RB_IMPORT_DIALOG (object);
+
+	g_object_get (dialog->priv->shell,
+		      "db", &dialog->priv->db,
+		      "shell-player", &dialog->priv->shell_player,
+		      NULL);
+
+	/* create entry types */
+	dialog->priv->entry_type = g_object_new (rb_import_dialog_entry_type_get_type (),
+						 "db", dialog->priv->db,
+						 "name", "import-dialog",
+						 NULL);
+	dialog->priv->ignore_type = g_object_new (rb_import_dialog_ignore_type_get_type (),
+						  "db", dialog->priv->db,
+						  "name", "import-dialog-ignore",
+						  NULL);
+	rhythmdb_register_entry_type (dialog->priv->db, dialog->priv->entry_type);
+	rhythmdb_register_entry_type (dialog->priv->db, dialog->priv->ignore_type);
+
+
+	builder = rb_builder_load ("import-dialog.ui", NULL);
+
+	dialog->priv->add_button = GTK_WIDGET (gtk_builder_get_object (builder, "add-button"));
+	g_signal_connect_object (dialog->priv->add_button, "clicked", G_CALLBACK (add_clicked_cb), dialog, 0);
+	gtk_widget_set_sensitive (dialog->priv->add_button, FALSE);
+	
+	dialog->priv->copy_button = GTK_WIDGET (gtk_builder_get_object (builder, "copy-button"));
+	g_signal_connect_object (dialog->priv->copy_button, "clicked", G_CALLBACK (copy_clicked_cb), dialog, 0);
+	gtk_widget_set_sensitive (dialog->priv->copy_button, FALSE);
+
+	dialog->priv->remove_button = GTK_WIDGET (gtk_builder_get_object (builder, "remove-button"));
+	g_signal_connect_object (dialog->priv->remove_button, "clicked", G_CALLBACK (remove_clicked_cb), dialog, 0);
+	gtk_widget_set_sensitive (dialog->priv->remove_button, FALSE);
+
+	g_signal_connect (gtk_builder_get_object (builder, "close-button"),
+			  "clicked",
+			  G_CALLBACK (close_clicked_cb),
+			  dialog);
+
+	dialog->priv->file_chooser = GTK_WIDGET (gtk_builder_get_object (builder, "file-chooser-button"));
+	/* select the first library location, since the default may be
+	 * the user's home dir or / or something that will take forever to scan.
+	 */
+	settings = g_settings_new ("org.gnome.rhythmbox.rhythmdb");
+	locations = g_settings_get_strv (settings, "locations");
+	if (locations[0] != NULL) {
+		dialog->priv->current_uri = g_strdup (locations[0]);
+	} else {
+		dialog->priv->current_uri = g_filename_to_uri (rb_music_dir (), NULL, NULL);
+	}
+	gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dialog->priv->file_chooser),
+						 dialog->priv->current_uri);
+	g_strfreev (locations);
+	g_object_unref (settings);
+
+	g_signal_connect_object (dialog->priv->file_chooser, "current-folder-changed", G_CALLBACK (current_folder_changed_cb), dialog, 0);
+
+	/* not sure why we have to set this, it should be the default */
+	gtk_widget_set_vexpand (gtk_widget_get_parent (dialog->priv->file_chooser), FALSE);
+
+	dialog->priv->info_bar_container = GTK_WIDGET (gtk_builder_get_object (builder, "info-bar-container"));
+
+	/* set up entry view */
+	dialog->priv->entry_view = rb_entry_view_new (dialog->priv->db, G_OBJECT (dialog->priv->shell_player), TRUE, FALSE);
+
+	g_signal_connect (dialog->priv->entry_view, "entry-activated", G_CALLBACK (entry_activated_cb), dialog);
+	g_signal_connect (dialog->priv->entry_view, "have-selection-changed", G_CALLBACK (have_selection_changed_cb), dialog);
+
+	rb_entry_view_append_column (dialog->priv->entry_view, RB_ENTRY_VIEW_COL_TRACK_NUMBER, FALSE);
+	rb_entry_view_append_column (dialog->priv->entry_view, RB_ENTRY_VIEW_COL_TITLE, TRUE);
+	rb_entry_view_append_column (dialog->priv->entry_view, RB_ENTRY_VIEW_COL_GENRE, FALSE);
+	rb_entry_view_append_column (dialog->priv->entry_view, RB_ENTRY_VIEW_COL_ARTIST, FALSE);
+	rb_entry_view_append_column (dialog->priv->entry_view, RB_ENTRY_VIEW_COL_ALBUM, FALSE);
+	rb_entry_view_append_column (dialog->priv->entry_view, RB_ENTRY_VIEW_COL_YEAR, FALSE);
+	rb_entry_view_append_column (dialog->priv->entry_view, RB_ENTRY_VIEW_COL_DURATION, FALSE);
+ 	rb_entry_view_append_column (dialog->priv->entry_view, RB_ENTRY_VIEW_COL_QUALITY, FALSE);
+	rb_entry_view_append_column (dialog->priv->entry_view, RB_ENTRY_VIEW_COL_PLAY_COUNT, FALSE);
+	rb_entry_view_append_column (dialog->priv->entry_view, RB_ENTRY_VIEW_COL_BPM, FALSE);
+	rb_entry_view_append_column (dialog->priv->entry_view, RB_ENTRY_VIEW_COL_COMMENT, FALSE);
+	rb_entry_view_append_column (dialog->priv->entry_view, RB_ENTRY_VIEW_COL_LOCATION, FALSE);
+
+	settings = g_settings_new ("org.gnome.rhythmbox.sources");
+	g_settings_bind (settings, "visible-columns", dialog->priv->entry_view, "visible-columns", G_SETTINGS_BIND_DEFAULT);
+	g_object_unref (settings);
+
+	g_signal_connect (dialog->priv->entry_view,
+			  "notify::sort-order",
+			  G_CALLBACK (sort_changed_cb),
+			  dialog);
+	rb_entry_view_set_sorting_order (dialog->priv->entry_view, "Album", GTK_SORT_ASCENDING);
+
+	gtk_container_add (GTK_CONTAINER (gtk_builder_get_object (builder, "entry-view-container")),
+			   GTK_WIDGET (dialog->priv->entry_view));
+
+	dialog->priv->query_model = rhythmdb_query_model_new_empty (dialog->priv->db);
+	rb_entry_view_set_model (dialog->priv->entry_view, dialog->priv->query_model);
+	query = rhythmdb_query_parse (dialog->priv->db,
+				      RHYTHMDB_QUERY_PROP_EQUALS, RHYTHMDB_PROP_TYPE, dialog->priv->entry_type,
+				      RHYTHMDB_QUERY_END);
+	rhythmdb_do_full_query_async_parsed (dialog->priv->db, RHYTHMDB_QUERY_RESULTS (dialog->priv->query_model), query);
+	rhythmdb_query_free (query);
+
+	g_signal_connect (dialog->priv->query_model, "post-entry-delete", G_CALLBACK (entry_deleted_cb), dialog);
+	g_signal_connect (dialog->priv->query_model, "row-inserted", G_CALLBACK (entry_inserted_cb), dialog);
+
+	gtk_container_add (GTK_CONTAINER (dialog), GTK_WIDGET (gtk_builder_get_object (builder, "import-dialog")));
+
+	gtk_widget_show_all (GTK_WIDGET (dialog));
+	g_object_unref (builder);
+}
+
+static void
+impl_dispose (GObject *object)
+{
+	RBImportDialog *dialog = RB_IMPORT_DIALOG (object);
+
+	if (dialog->priv->pulse_id) {
+		g_source_remove (dialog->priv->pulse_id);
+		dialog->priv->pulse_id = 0;
+	}
+	if (dialog->priv->add_entries_id) {
+		g_source_remove (dialog->priv->add_entries_id);
+		dialog->priv->add_entries_id = 0;
+	}
+	if (dialog->priv->added_entries_id) {
+		g_source_remove (dialog->priv->added_entries_id);
+		dialog->priv->added_entries_id = 0;
+	}
+
+	if (dialog->priv->query_model != NULL) {
+		g_object_unref (dialog->priv->query_model);
+		dialog->priv->query_model = NULL;
+	}
+	if (dialog->priv->shell != NULL) {
+		g_object_unref (dialog->priv->shell);
+		dialog->priv->shell = NULL;
+	}
+	if (dialog->priv->shell_player != NULL) {
+		g_object_unref (dialog->priv->shell_player);
+		dialog->priv->shell_player = NULL;
+	}
+	if (dialog->priv->db != NULL) {
+		g_object_unref (dialog->priv->db);
+		dialog->priv->db = NULL;
+	}
+
+	G_OBJECT_CLASS (rb_import_dialog_parent_class)->dispose (object);
+}
+
+static void
+impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	RBImportDialog *dialog = RB_IMPORT_DIALOG (object);
+
+	switch (prop_id) {
+	case PROP_SHELL:
+		dialog->priv->shell = g_value_dup_object (value);
+		break;
+	default:
+		g_assert_not_reached ();
+		break;
+	}
+}
+
+static char *
+get_status (RBImportDialog *dialog)
+{
+	if (dialog->priv->query_model == NULL)
+		return NULL;
+
+	return rhythmdb_query_model_compute_status_normal (dialog->priv->query_model, "%d song", "%d songs");
+}
+
+static void
+impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	RBImportDialog *dialog = RB_IMPORT_DIALOG (object);
+
+	switch (prop_id) {
+	case PROP_SHELL:
+		g_value_set_object (value, dialog->priv->shell);
+		break;
+	case PROP_STATUS:
+		g_value_take_string (value, get_status (dialog));
+		break;
+	default:
+		g_assert_not_reached ();
+		break;
+	}
+}
+
+static void
+rb_import_dialog_init (RBImportDialog *dialog)
+{
+	dialog->priv = G_TYPE_INSTANCE_GET_PRIVATE (dialog,
+						    RB_TYPE_IMPORT_DIALOG,
+						    RBImportDialogPrivate);
+}
+
+static void
+rb_import_dialog_class_init (RBImportDialogClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->constructed = impl_constructed;
+	object_class->dispose = impl_dispose;
+	object_class->set_property = impl_set_property;
+	object_class->get_property = impl_get_property;
+
+	klass->close = impl_close;
+
+	g_object_class_install_property (object_class,
+					 PROP_SHELL,
+					 g_param_spec_object ("shell",
+							      "shell",
+							      "RBShell instance",
+							      RB_TYPE_SHELL,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	g_object_class_install_property (object_class,
+					 PROP_STATUS,
+					 g_param_spec_string ("status",
+							      "status",
+							      "status text",
+							      NULL,
+							      G_PARAM_READABLE));
+
+	signals[CLOSE] = g_signal_new ("close",
+				       RB_TYPE_IMPORT_DIALOG,
+				       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+				       G_STRUCT_OFFSET (RBImportDialogClass, close),
+				       NULL, NULL,
+				       g_cclosure_marshal_VOID__VOID,
+				       G_TYPE_NONE,
+				       0);
+	signals[CLOSED] = g_signal_new ("closed",
+					RB_TYPE_IMPORT_DIALOG,
+					G_SIGNAL_RUN_LAST,
+					G_STRUCT_OFFSET (RBImportDialogClass, closed),
+					NULL, NULL,
+					g_cclosure_marshal_VOID__VOID,
+					G_TYPE_NONE,
+					0);
+
+	g_type_class_add_private (object_class, sizeof (RBImportDialogPrivate));
+
+	gtk_binding_entry_add_signal (gtk_binding_set_by_class (klass),
+				      GDK_KEY_Escape,
+				      0,
+				      "close",
+				      0);
+}
+
+void
+rb_import_dialog_reset (RBImportDialog *dialog)
+{
+	g_free (dialog->priv->current_uri);
+	dialog->priv->current_uri = NULL;
+
+	current_folder_changed_cb (GTK_FILE_CHOOSER (dialog->priv->file_chooser), dialog);
+}
+
+GtkWidget *
+rb_import_dialog_new (RBShell *shell)
+{
+	return GTK_WIDGET (g_object_new (RB_TYPE_IMPORT_DIALOG,
+					 "shell", shell,
+					 "orientation", GTK_ORIENTATION_VERTICAL,
+					 NULL));
+}
diff --git a/widgets/rb-import-dialog.h b/widgets/rb-import-dialog.h
new file mode 100644
index 0000000..e4a6ce2
--- /dev/null
+++ b/widgets/rb-import-dialog.h
@@ -0,0 +1,73 @@
+/*
+ *  Copyright (C) 2012 Jonathan Matthew  <jonathan d14n org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#ifndef RB_IMPORT_DIALOG_H
+#define RB_IMPORT_DIALOG_H
+
+#include <gtk/gtk.h>
+
+#include <shell/rb-shell.h>
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_IMPORT_DIALOG         (rb_import_dialog_get_type ())
+#define RB_IMPORT_DIALOG(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_IMPORT_DIALOG, RBImportDialog))
+#define RB_IMPORT_DIALOG_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_IMPORT_DIALOG, RBImportDialogClass))
+#define RB_IS_IMPORT_DIALOG(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_IMPORT_DIALOG))
+#define RB_IS_IMPORT_DIALOG_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_IMPORT_DIALOG))
+#define RB_IMPORT_DIALOG_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_IMPORT_DIALOG, RBImportDialogClass))
+
+typedef struct _RBImportDialog RBImportDialog;
+typedef struct _RBImportDialogClass RBImportDialogClass;
+
+typedef struct RBImportDialogPrivate RBImportDialogPrivate;
+
+struct _RBImportDialog
+{
+	GtkGrid parent;
+
+	RBImportDialogPrivate *priv;
+};
+
+struct _RBImportDialogClass
+{
+	GtkGridClass parent;
+
+	/* signals */
+	void	(*close)	(RBImportDialog *dialog);
+	void	(*closed)	(RBImportDialog *dialog);
+};
+
+GType		rb_import_dialog_get_type		(void);
+
+GtkWidget *     rb_import_dialog_new			(RBShell *shell);
+
+void		rb_import_dialog_reset			(RBImportDialog *dialog);
+
+G_END_DECLS
+
+#endif /* RB_IMPORT_DIALOG_H */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]