[rhythmbox] podcast: new podcast subscription dialog
- From: Jonathan Matthew <jmatthew src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [rhythmbox] podcast: new podcast subscription dialog
- Date: Sat, 26 May 2012 04:23:59 +0000 (UTC)
commit df4f01f0b1168011176aeb544f4f695b260648ae
Author: Jonathan Matthew <jonathan d14n org>
Date: Sat May 26 13:55:28 2012 +1000
podcast: new podcast subscription dialog
This uses the previously added podcast searches to find podcasts,
previews podcasts before subscription (including playback of episodes)
and makes importing feeds via OPML a bit easier too.
data/ui/Makefile.am | 1 +
data/ui/podcast-add-dialog.ui | 183 ++++++++
data/ui/rhythmbox-ui.xml | 1 -
po/POTFILES.in | 2 +
podcast/Makefile.am | 2 +
podcast/rb-podcast-add-dialog.c | 941 +++++++++++++++++++++++++++++++++++++++
podcast/rb-podcast-add-dialog.h | 73 +++
podcast/rb-podcast-manager.c | 16 -
podcast/rb-podcast-manager.h | 1 -
podcast/rb-podcast-source.c | 96 +++--
podcast/rb-podcast-source.h | 2 +
shell/rb-shell.c | 5 +-
12 files changed, 1275 insertions(+), 48 deletions(-)
---
diff --git a/data/ui/Makefile.am b/data/ui/Makefile.am
index 061e640..d0bd573 100644
--- a/data/ui/Makefile.am
+++ b/data/ui/Makefile.am
@@ -9,6 +9,7 @@ GTK_BUILDER_FILES = \
media-player-properties.ui \
playback-prefs.ui \
playlist-save.ui \
+ podcast-add-dialog.ui \
podcast-feed-properties.ui \
podcast-prefs.ui \
podcast-properties.ui \
diff --git a/data/ui/podcast-add-dialog.ui b/data/ui/podcast-add-dialog.ui
new file mode 100644
index 0000000..37caaf6
--- /dev/null
+++ b/data/ui/podcast-add-dialog.ui
@@ -0,0 +1,183 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <object class="GtkAction" id="subscribe">
+ <property name="label" translatable="yes">Subscribe</property>
+ </object>
+ <object class="GtkGrid" id="podcast-add-dialog">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkVPaned" id="paned">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkTreeView" id="feed-view">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="border_width">1</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeview-selection1"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">True</property>
+ </packing>
+ </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="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Search for podcasts in the iTunes Store and on Miroguide.com, or enter a podcast feed URL.
+Subscribe to podcasts to download new episodes as they are published.</property>
+ <property name="justify">center</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>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkGrid" id="grid1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkBox" id="search-entry-box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <placeholder/>
+ </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="GtkButton" id="close-button">
+ <property name="label" translatable="yes">Close</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">2</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="subscribe-button">
+ <property name="label" translatable="yes">Subscribe</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">1</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </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">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="info-bar-container">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <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 2aa169a..10896b2 100644
--- a/data/ui/rhythmbox-ui.xml
+++ b/data/ui/rhythmbox-ui.xml
@@ -17,7 +17,6 @@
<menuitem name="MusicPlaylistDeletePlaylistMenu" action="MusicPlaylistDeletePlaylist"/>
</menu>
<placeholder name="PluginPlaceholder" />
- <menuitem name="MusicNewPodcastMenu" action="MusicNewPodcast"/>
<separator/>
<menuitem name="MusicPropertiesMenu" action="MusicProperties"/>
<separator/>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 29b41c8..3132280 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -12,6 +12,7 @@ data/rhythmbox-device.desktop.in.in
[type: gettext/glade]data/ui/media-player-properties.ui
[type: gettext/glade]data/ui/playback-prefs.ui
[type: gettext/glade]data/ui/playlist-save.ui
+[type: gettext/glade]data/ui/podcast-add-dialog.ui
[type: gettext/glade]data/ui/podcast-feed-properties.ui
[type: gettext/glade]data/ui/podcast-prefs.ui
[type: gettext/glade]data/ui/podcast-properties.ui
@@ -137,6 +138,7 @@ plugins/visualizer/rb-visualizer-page.c
plugins/visualizer/rb-visualizer-plugin.c
[type: gettext/ini]plugins/visualizer/visualizer.plugin.in
podcast/rb-feed-podcast-properties-dialog.c
+podcast/rb-podcast-add-dialog.c
podcast/rb-podcast-main-source.c
podcast/rb-podcast-manager.c
podcast/rb-podcast-parse.c
diff --git a/podcast/Makefile.am b/podcast/Makefile.am
index 18cf01f..938fb60 100644
--- a/podcast/Makefile.am
+++ b/podcast/Makefile.am
@@ -19,6 +19,8 @@ librbpodcast_la_SOURCES = \
$(podcastinclude_HEADERS) \
rb-feed-podcast-properties-dialog.c \
rb-feed-podcast-properties-dialog.h \
+ rb-podcast-add-dialog.c \
+ rb-podcast-add-dialog.h \
rb-podcast-properties-dialog.c \
rb-podcast-properties-dialog.h \
rb-podcast-main-source.c \
diff --git a/podcast/rb-podcast-add-dialog.c b/podcast/rb-podcast-add-dialog.c
new file mode 100644
index 0000000..54417d2
--- /dev/null
+++ b/podcast/rb-podcast-add-dialog.c
@@ -0,0 +1,941 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 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-shell.h"
+#include "rb-shell-player.h"
+#include "rb-podcast-add-dialog.h"
+#include "rb-podcast-search.h"
+#include "rb-podcast-entry-types.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"
+
+static void rb_podcast_add_dialog_class_init (RBPodcastAddDialogClass *klass);
+static void rb_podcast_add_dialog_init (RBPodcastAddDialog *dialog);
+
+enum {
+ PROP_0,
+ PROP_PODCAST_MANAGER,
+ PROP_SHELL,
+};
+
+enum {
+ CLOSE,
+ CLOSED,
+ LAST_SIGNAL
+};
+
+enum {
+ FEED_COLUMN_TITLE = 0,
+ FEED_COLUMN_AUTHOR,
+ FEED_COLUMN_IMAGE,
+ FEED_COLUMN_IMAGE_FILE,
+ FEED_COLUMN_EPISODE_COUNT,
+ FEED_COLUMN_PARSED_FEED,
+ FEED_COLUMN_DATE,
+};
+
+struct RBPodcastAddDialogPrivate
+{
+ RBPodcastManager *podcast_mgr;
+ RhythmDB *db;
+ RBShell *shell;
+
+ GtkWidget *feed_view;
+ GtkListStore *feed_model;
+
+ GtkWidget *episode_view;
+
+ GtkWidget *text_entry;
+ GtkWidget *subscribe_button;
+ GtkWidget *info_bar;
+ GtkWidget *info_bar_message;
+
+ RBSearchEntry *search_entry;
+
+ gboolean paned_size_set;
+ gboolean have_selection;
+ gboolean clearing;
+ GtkTreeIter selected_feed;
+
+ int running_searches;
+ gboolean search_successful;
+};
+
+/* various prefixes that identify things we treat as feed URLs rather than search terms */
+static const char *podcast_uri_prefixes[] = {
+ "http://",
+ "https://",
+ "feed://",
+ "zcast://",
+ "zune://",
+ "itpc://",
+ "itms://",
+ "www.",
+};
+
+/* number of search results to request from each available search */
+#define PODCAST_SEARCH_LIMIT 25
+
+#define PODCAST_IMAGE_SIZE 50
+
+static guint signals[LAST_SIGNAL] = {0,};
+
+G_DEFINE_TYPE (RBPodcastAddDialog, rb_podcast_add_dialog, GTK_TYPE_VBOX);
+
+
+static gboolean
+remove_all_feeds_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, RBPodcastAddDialog *dialog)
+{
+ RBPodcastChannel *channel;
+ gtk_tree_model_get (model, iter, FEED_COLUMN_PARSED_FEED, &channel, -1);
+ rb_podcast_parse_channel_free (channel);
+ return FALSE;
+}
+
+static void
+remove_all_feeds (RBPodcastAddDialog *dialog)
+{
+ /* remove all feeds from the model and free associated data */
+ gtk_tree_model_foreach (GTK_TREE_MODEL (dialog->priv->feed_model),
+ (GtkTreeModelForeachFunc) remove_all_feeds_cb,
+ dialog);
+
+ dialog->priv->clearing = TRUE;
+ gtk_list_store_clear (dialog->priv->feed_model);
+ dialog->priv->clearing = FALSE;
+
+ dialog->priv->have_selection = FALSE;
+ gtk_widget_set_sensitive (dialog->priv->subscribe_button, FALSE);
+}
+
+static void
+add_posts_for_feed (RBPodcastAddDialog *dialog, RBPodcastChannel *channel)
+{
+ GList *l;
+
+ for (l = channel->posts; l != NULL; l = l->next) {
+ RBPodcastItem *item = (RBPodcastItem *) l->data;
+
+ rb_podcast_manager_add_post (dialog->priv->db,
+ TRUE,
+ channel->title ? channel->title : channel->url,
+ item->title,
+ channel->url,
+ (item->author ? item->author : channel->author),
+ item->url,
+ item->description,
+ (item->pub_date > 0 ? item->pub_date : channel->pub_date),
+ item->duration,
+ item->filesize);
+ }
+
+ rhythmdb_commit (dialog->priv->db);
+}
+
+static void
+image_file_read_cb (GObject *file, GAsyncResult *result, RBPodcastAddDialog *dialog)
+{
+ GFileInputStream *stream;
+ GdkPixbuf *pixbuf;
+ GError *error = NULL;
+
+ stream = g_file_read_finish (G_FILE (file), result, &error);
+ if (error != NULL) {
+ rb_debug ("podcast image read failed: %s", error->message);
+ g_clear_error (&error);
+ g_object_unref (dialog);
+ return;
+ }
+
+ pixbuf = gdk_pixbuf_new_from_stream_at_scale (G_INPUT_STREAM (stream), PODCAST_IMAGE_SIZE, PODCAST_IMAGE_SIZE, TRUE, NULL, &error);
+ if (error != NULL) {
+ rb_debug ("podcast image load failed: %s", error->message);
+ g_clear_error (&error);
+ } else {
+ GtkTreeIter iter;
+
+ if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (dialog->priv->feed_model), &iter)) {
+ do {
+ GFile *feedfile;
+ gtk_tree_model_get (GTK_TREE_MODEL (dialog->priv->feed_model), &iter,
+ FEED_COLUMN_IMAGE_FILE, &feedfile,
+ -1);
+ if (feedfile == G_FILE (file)) {
+ gtk_list_store_set (dialog->priv->feed_model,
+ &iter,
+ FEED_COLUMN_IMAGE, g_object_ref (pixbuf),
+ -1);
+ break;
+ }
+ } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (dialog->priv->feed_model), &iter));
+ }
+ g_object_unref (pixbuf);
+ }
+
+ g_object_unref (dialog);
+ g_object_unref (stream);
+}
+
+static void
+insert_search_result (RBPodcastAddDialog *dialog, RBPodcastChannel *channel, gboolean select)
+{
+ GtkTreeIter iter;
+ GFile *image_file;
+ int episodes;
+
+ if (channel->posts) {
+ episodes = g_list_length (channel->posts);
+ } else {
+ episodes = channel->num_posts;
+ }
+
+ /* if there's an image to load, fetch it */
+ if (channel->img) {
+ rb_debug ("fetching image %s", channel->img);
+ image_file = g_file_new_for_uri (channel->img);
+ } else {
+ image_file = NULL;
+ }
+
+ gtk_list_store_insert_with_values (dialog->priv->feed_model,
+ &iter,
+ G_MAXINT,
+ FEED_COLUMN_TITLE, channel->title,
+ FEED_COLUMN_AUTHOR, channel->author,
+ FEED_COLUMN_EPISODE_COUNT, episodes,
+ FEED_COLUMN_IMAGE, NULL,
+ FEED_COLUMN_IMAGE_FILE, image_file,
+ FEED_COLUMN_PARSED_FEED, channel,
+ -1);
+
+ if (image_file != NULL) {
+ g_file_read_async (image_file,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ (GAsyncReadyCallback) image_file_read_cb,
+ g_object_ref (dialog));
+ }
+
+ if (select) {
+ GtkTreeSelection *selection;
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->priv->feed_view));
+ gtk_tree_selection_select_iter (selection, &iter);
+ }
+}
+
+typedef struct {
+ RBPodcastAddDialog *dialog;
+ char *url;
+ RBPodcastChannel *channel;
+ gboolean existing;
+ gboolean single;
+ GError *error;
+} ParseThreadData;
+
+static gboolean
+parse_finished (ParseThreadData *data)
+{
+ if (data->error != NULL) {
+ gtk_label_set_label (GTK_LABEL (data->dialog->priv->info_bar_message),
+ _("Unable to load the feed. Check your network connection."));
+ gtk_widget_show (data->dialog->priv->info_bar);
+ } else {
+ gtk_widget_hide (data->dialog->priv->info_bar);
+ }
+
+ if (data->channel->is_opml) {
+ GList *l;
+ /* convert each item into its own channel */
+ for (l = data->channel->posts; l != NULL; l = l->next) {
+ RBPodcastChannel *channel;
+ RBPodcastItem *item;
+
+ item = l->data;
+ channel = g_new0 (RBPodcastChannel, 1);
+ channel->url = g_strdup (item->url);
+ channel->title = g_strdup (item->title);
+ /* none of the other fields get populated anyway */
+ insert_search_result (data->dialog, channel, FALSE);
+ }
+ rb_podcast_parse_channel_free (data->channel);
+ } else if (data->existing) {
+ /* find the row for the feed, replace the channel */
+ GtkTreeIter iter;
+ gboolean found = FALSE;
+
+ if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (data->dialog->priv->feed_model), &iter)) {
+ do {
+ RBPodcastChannel *channel;
+ gtk_tree_model_get (GTK_TREE_MODEL (data->dialog->priv->feed_model), &iter,
+ FEED_COLUMN_PARSED_FEED, &channel,
+ -1);
+ if (g_strcmp0 (channel->url, data->url) == 0) {
+ gtk_list_store_set (data->dialog->priv->feed_model,
+ &iter,
+ FEED_COLUMN_PARSED_FEED, data->channel,
+ -1);
+ found = TRUE;
+ rb_podcast_parse_channel_free (channel);
+ break;
+ }
+ } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (data->dialog->priv->feed_model), &iter));
+ }
+
+ /* if the row is selected, create entries for the channel contents */
+ if (data->dialog->priv->have_selection && found) {
+ GtkTreePath *a;
+ GtkTreePath *b;
+
+ a = gtk_tree_model_get_path (GTK_TREE_MODEL (data->dialog->priv->feed_model), &iter);
+ b = gtk_tree_model_get_path (GTK_TREE_MODEL (data->dialog->priv->feed_model), &data->dialog->priv->selected_feed);
+ if (gtk_tree_path_compare (a, b) == 0) {
+ add_posts_for_feed (data->dialog, data->channel);
+ }
+
+ gtk_tree_path_free (a);
+ gtk_tree_path_free (b);
+ } else {
+ rb_podcast_parse_channel_free (data->channel);
+ }
+ } else {
+ /* model owns data->channel now */
+ insert_search_result (data->dialog, data->channel, data->single);
+ }
+
+ g_object_unref (data->dialog);
+ g_clear_error (&data->error);
+ g_free (data->url);
+ g_free (data);
+ return FALSE;
+}
+
+static gpointer
+parse_thread (ParseThreadData *data)
+{
+ if (rb_podcast_parse_load_feed (data->channel, data->url, FALSE, &data->error) == FALSE) {
+ /* fake up a channel with just the url as the title, allowing the user
+ * to subscribe to the podcast anyway.
+ */
+ data->channel->url = g_strdup (data->url);
+ data->channel->title = g_strdup (data->url);
+ }
+
+ g_idle_add ((GSourceFunc) parse_finished, data);
+ return NULL;
+}
+
+static void
+parse_in_thread (RBPodcastAddDialog *dialog, const char *text, gboolean existing, gboolean single)
+{
+ ParseThreadData *data;
+ GError *error = NULL;
+
+ data = g_new0 (ParseThreadData, 1);
+ data->dialog = g_object_ref (dialog);
+ data->url = g_strdup (text);
+ data->channel = g_new0 (RBPodcastChannel, 1);
+ data->existing = existing;
+ data->single = single;
+
+ g_thread_create ((GThreadFunc) parse_thread, data, TRUE, &error);
+ if (error != NULL) {
+ /* ugh.. */
+ g_warning ("Unable to create podcast parsing thread: %s", error->message);
+ g_clear_error (&error);
+ }
+}
+
+static void
+podcast_search_result_cb (RBPodcastSearch *search, RBPodcastChannel *feed, RBPodcastAddDialog *dialog)
+{
+ rb_debug ("got result %s from podcast search %s", feed->url, G_OBJECT_TYPE_NAME (search));
+ insert_search_result (dialog, rb_podcast_parse_channel_copy (feed), FALSE);
+}
+
+static void
+podcast_search_finished_cb (RBPodcastSearch *search, gboolean successful, RBPodcastAddDialog *dialog)
+{
+ rb_debug ("podcast search %s finished", G_OBJECT_TYPE_NAME (search));
+ g_object_unref (search);
+
+ dialog->priv->search_successful |= successful;
+
+ dialog->priv->running_searches--;
+ if (dialog->priv->running_searches == 0) {
+ if (dialog->priv->search_successful == FALSE) {
+ gtk_label_set_label (GTK_LABEL (dialog->priv->info_bar_message),
+ _("Unable to search for podcasts. Check your network connection."));
+ gtk_widget_show (dialog->priv->info_bar);
+ }
+ }
+}
+
+static void
+search_cb (RBSearchEntry *entry, const char *text, RBPodcastAddDialog *dialog)
+{
+ GList *searches;
+ GList *s;
+ int i;
+
+ /* remove previous feeds */
+ remove_all_feeds (dialog);
+ rhythmdb_entry_delete_by_type (dialog->priv->db, RHYTHMDB_ENTRY_TYPE_PODCAST_SEARCH);
+ rhythmdb_commit (dialog->priv->db);
+
+ gtk_widget_hide (dialog->priv->info_bar);
+
+ if (text == NULL || text[0] == '\0') {
+ return;
+ }
+
+ /* if the entered text looks like a feed URL, parse it directly */
+ for (i = 0; i < G_N_ELEMENTS (podcast_uri_prefixes); i++) {
+ if (g_str_has_prefix (text, podcast_uri_prefixes[i])) {
+ parse_in_thread (dialog, text, FALSE, TRUE);
+ return;
+ }
+ }
+
+ /* not really sure about this one */
+ if (g_path_is_absolute (text)) {
+ parse_in_thread (dialog, text, FALSE, TRUE);
+ return;
+ }
+
+ /* otherwise, try podcast searches */
+ dialog->priv->search_successful = FALSE;
+ searches = rb_podcast_manager_get_searches (dialog->priv->podcast_mgr);
+ for (s = searches; s != NULL; s = s->next) {
+ RBPodcastSearch *search = s->data;
+
+ g_signal_connect_object (search, "result", G_CALLBACK (podcast_search_result_cb), dialog, 0);
+ g_signal_connect_object (search, "finished", G_CALLBACK (podcast_search_finished_cb), dialog, 0);
+ rb_podcast_search_start (search, text, PODCAST_SEARCH_LIMIT);
+ dialog->priv->running_searches++;
+ }
+}
+
+static void
+subscribe_selected_feed (RBPodcastAddDialog *dialog)
+{
+ RBPodcastChannel *channel;
+
+ g_assert (dialog->priv->have_selection);
+
+ rhythmdb_entry_delete_by_type (dialog->priv->db, RHYTHMDB_ENTRY_TYPE_PODCAST_SEARCH);
+ rhythmdb_commit (dialog->priv->db);
+
+ /* subscribe selected feed */
+ gtk_tree_model_get (GTK_TREE_MODEL (dialog->priv->feed_model),
+ &dialog->priv->selected_feed,
+ FEED_COLUMN_PARSED_FEED, &channel,
+ -1);
+ if (channel->posts != NULL) {
+ rb_podcast_manager_add_parsed_feed (dialog->priv->podcast_mgr, channel);
+ } else {
+ rb_podcast_manager_subscribe_feed (dialog->priv->podcast_mgr, channel->url, TRUE);
+ }
+}
+
+static void
+subscribe_clicked_cb (GtkButton *button, RBPodcastAddDialog *dialog)
+{
+ if (dialog->priv->have_selection == FALSE) {
+ rb_debug ("no selection");
+ return;
+ }
+
+ subscribe_selected_feed (dialog);
+
+ dialog->priv->clearing = TRUE;
+ gtk_list_store_remove (GTK_LIST_STORE (dialog->priv->feed_model), &dialog->priv->selected_feed);
+ dialog->priv->clearing = FALSE;
+
+ gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->priv->feed_view)));
+}
+
+static void
+close_clicked_cb (GtkButton *button, RBPodcastAddDialog *dialog)
+{
+ g_signal_emit (dialog, signals[CLOSED], 0);
+}
+
+static void
+feed_activated_cb (GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *column, RBPodcastAddDialog *dialog)
+{
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (dialog->priv->feed_model), &dialog->priv->selected_feed, path);
+ dialog->priv->have_selection = TRUE;
+
+ subscribe_selected_feed (dialog);
+
+ dialog->priv->have_selection = FALSE;
+
+ g_signal_emit (dialog, signals[CLOSED], 0);
+}
+
+static void
+feed_selection_changed_cb (GtkTreeSelection *selection, RBPodcastAddDialog *dialog)
+{
+ GtkTreeModel *model;
+
+ if (dialog->priv->clearing)
+ return;
+
+ dialog->priv->have_selection =
+ gtk_tree_selection_get_selected (selection, &model, &dialog->priv->selected_feed);
+ gtk_widget_set_sensitive (dialog->priv->subscribe_button, dialog->priv->have_selection);
+
+ rhythmdb_entry_delete_by_type (dialog->priv->db, RHYTHMDB_ENTRY_TYPE_PODCAST_SEARCH);
+ rhythmdb_commit (dialog->priv->db);
+
+ if (dialog->priv->have_selection) {
+ RBPodcastChannel *channel = NULL;
+
+ gtk_tree_model_get (model,
+ &dialog->priv->selected_feed,
+ FEED_COLUMN_PARSED_FEED, &channel,
+ -1);
+
+ if (channel->posts == NULL) {
+ rb_debug ("parsing feed %s to get posts", channel->url);
+ parse_in_thread (dialog, channel->url, TRUE, FALSE);
+ } else {
+ add_posts_for_feed (dialog, channel);
+ }
+ }
+}
+
+static void
+episode_count_column_cell_data_func (GtkTreeViewColumn *column,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ GtkTreeIter parent;
+ if (gtk_tree_model_iter_parent (model, &parent, iter)) {
+ g_object_set (renderer, "visible", FALSE, NULL);
+ } else {
+ int count;
+ char *text;
+ gtk_tree_model_get (model, iter, FEED_COLUMN_EPISODE_COUNT, &count, -1);
+ text = g_strdup_printf ("%d", count);
+ g_object_set (renderer, "visible", TRUE, "text", text, NULL);
+ g_free (text);
+ }
+}
+
+static void
+podcast_post_date_cell_data_func (GtkTreeViewColumn *column,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ RhythmDBEntry *entry;
+ gulong value;
+ char *str;
+
+ gtk_tree_model_get (tree_model, iter, 0, &entry, -1);
+
+ value = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_POST_TIME);
+ if (value == 0) {
+ str = g_strdup (_("Unknown"));
+ } else {
+ str = rb_utf_friendly_time (value);
+ }
+
+ g_object_set (G_OBJECT (renderer), "text", str, NULL);
+ g_free (str);
+
+ rhythmdb_entry_unref (entry);
+}
+
+static gint
+podcast_post_date_sort_func (RhythmDBEntry *a,
+ RhythmDBEntry *b,
+ RhythmDBQueryModel *model)
+{
+ gulong a_val, b_val;
+ gint ret;
+
+ a_val = rhythmdb_entry_get_ulong (a, RHYTHMDB_PROP_POST_TIME);
+ b_val = rhythmdb_entry_get_ulong (b, RHYTHMDB_PROP_POST_TIME);
+
+ if (a_val != b_val)
+ ret = (a_val > b_val) ? 1 : -1;
+ else
+ ret = rhythmdb_query_model_title_sort_func (a, b, model);
+
+ return ret;
+}
+
+static void
+episodes_sort_changed_cb (GObject *object, GParamSpec *pspec, RBPodcastAddDialog *dialog)
+{
+ rb_entry_view_resort_model (RB_ENTRY_VIEW (object));
+}
+
+static void
+impl_close (RBPodcastAddDialog *dialog)
+{
+ g_signal_emit (dialog, signals[CLOSED], 0);
+}
+
+static gboolean
+set_paned_position (GtkWidget *paned)
+{
+ gtk_paned_set_position (GTK_PANED (paned), gtk_widget_get_allocated_height (paned) / 2);
+ g_object_unref (paned);
+ return FALSE;
+}
+
+static void
+paned_size_allocate_cb (GtkWidget *widget, GdkRectangle *allocation, RBPodcastAddDialog *dialog)
+{
+ if (dialog->priv->paned_size_set == FALSE) {
+ dialog->priv->paned_size_set = TRUE;
+ g_idle_add ((GSourceFunc) set_paned_position, g_object_ref (widget));
+ }
+}
+
+static void
+episode_entry_activated_cb (RBEntryView *entry_view, RhythmDBEntry *entry, RBPodcastAddDialog *dialog)
+{
+ rb_debug ("search result podcast 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
+impl_constructed (GObject *object)
+{
+ RBPodcastAddDialog *dialog;
+ GtkBuilder *builder;
+ GtkWidget *widget;
+ GtkWidget *paned;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *renderer;
+ RBEntryView *episodes;
+ RBShellPlayer *shell_player;
+ RhythmDBQuery *query;
+ RhythmDBQueryModel *query_model;
+ const char *episode_strings[3];
+
+ RB_CHAIN_GOBJECT_METHOD (rb_podcast_add_dialog_parent_class, constructed, object);
+ dialog = RB_PODCAST_ADD_DIALOG (object);
+
+ g_object_get (dialog->priv->podcast_mgr, "db", &dialog->priv->db, NULL);
+
+ builder = rb_builder_load ("podcast-add-dialog.ui", NULL);
+
+ dialog->priv->info_bar_message = gtk_label_new ("");
+ dialog->priv->info_bar = gtk_info_bar_new ();
+ gtk_container_add (GTK_CONTAINER (gtk_info_bar_get_content_area (GTK_INFO_BAR (dialog->priv->info_bar))),
+ dialog->priv->info_bar_message);
+ gtk_widget_set_no_show_all (dialog->priv->info_bar, TRUE);
+ gtk_box_pack_start (GTK_BOX (gtk_builder_get_object (builder, "info-bar-container")), dialog->priv->info_bar, TRUE, TRUE, 0);
+ gtk_widget_show (dialog->priv->info_bar_message);
+
+ dialog->priv->subscribe_button = GTK_WIDGET (gtk_builder_get_object (builder, "subscribe-button"));
+ g_signal_connect_object (dialog->priv->subscribe_button, "clicked", G_CALLBACK (subscribe_clicked_cb), dialog, 0);
+ gtk_widget_set_sensitive (dialog->priv->subscribe_button, FALSE);
+
+ dialog->priv->feed_view = GTK_WIDGET (gtk_builder_get_object (builder, "feed-view"));
+ g_signal_connect (dialog->priv->feed_view, "row-activated", G_CALLBACK (feed_activated_cb), dialog);
+ g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->priv->feed_view)),
+ "changed",
+ G_CALLBACK (feed_selection_changed_cb),
+ dialog);
+
+
+ dialog->priv->search_entry = rb_search_entry_new (FALSE);
+ gtk_widget_set_size_request (GTK_WIDGET (dialog->priv->search_entry), 400, -1);
+ g_object_set (dialog->priv->search_entry,"explicit-mode", TRUE, NULL);
+ g_signal_connect (dialog->priv->search_entry, "search", G_CALLBACK (search_cb), dialog);
+ g_signal_connect (dialog->priv->search_entry, "activate", G_CALLBACK (search_cb), dialog);
+ gtk_container_add (GTK_CONTAINER (gtk_builder_get_object (builder, "search-entry-box")),
+ GTK_WIDGET (dialog->priv->search_entry));
+
+ g_signal_connect (gtk_builder_get_object (builder, "close-button"),
+ "clicked",
+ G_CALLBACK (close_clicked_cb),
+ dialog);
+
+ dialog->priv->feed_model = gtk_list_store_new (7,
+ G_TYPE_STRING, /* name */
+ G_TYPE_STRING, /* author */
+ GDK_TYPE_PIXBUF, /* image */
+ G_TYPE_FILE, /* image file */
+ G_TYPE_INT, /* episode count */
+ G_TYPE_POINTER, /* RBPodcastChannel */
+ G_TYPE_ULONG); /* date */
+ gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->priv->feed_view), GTK_TREE_MODEL (dialog->priv->feed_model));
+
+ column = gtk_tree_view_column_new_with_attributes (_("Title"), gtk_cell_renderer_pixbuf_new (), "pixbuf", FEED_COLUMN_IMAGE, NULL);
+ renderer = gtk_cell_renderer_text_new ();
+ g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_tree_view_column_pack_start (column, renderer, TRUE);
+ gtk_tree_view_column_set_attributes (column, renderer, "text", FEED_COLUMN_TITLE, NULL);
+
+ gtk_tree_view_column_set_expand (column, TRUE);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->priv->feed_view), column);
+
+ renderer = gtk_cell_renderer_text_new ();
+ g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ column = gtk_tree_view_column_new_with_attributes (_("Author"), renderer, "text", FEED_COLUMN_AUTHOR, NULL);
+ gtk_tree_view_column_set_expand (column, TRUE);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->priv->feed_view), column);
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes (_("Episodes"), renderer, NULL);
+ gtk_tree_view_column_set_cell_data_func (column, renderer, episode_count_column_cell_data_func, NULL, NULL);
+ episode_strings[0] = "0000";
+ episode_strings[1] = _("Episodes");
+ episode_strings[2] = NULL;
+ rb_set_tree_view_column_fixed_width (dialog->priv->feed_view, column, renderer, episode_strings, 6);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->priv->feed_view), column);
+
+ widget = GTK_WIDGET (gtk_builder_get_object (builder, "podcast-add-dialog"));
+ gtk_box_pack_start (GTK_BOX (dialog), widget, TRUE, TRUE, 12); /* 12? */
+
+ gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (dialog->priv->feed_view), TRUE);
+
+ /* set up episode view */
+ g_object_get (dialog->priv->shell, "shell-player", &shell_player, NULL);
+ episodes = rb_entry_view_new (dialog->priv->db, G_OBJECT (shell_player), TRUE, FALSE);
+ g_object_unref (shell_player);
+
+ g_signal_connect (episodes, "entry-activated", G_CALLBACK (episode_entry_activated_cb), dialog);
+
+ /* date column */
+ column = gtk_tree_view_column_new ();
+ renderer = gtk_cell_renderer_text_new();
+
+ gtk_tree_view_column_pack_start (column, renderer, TRUE);
+
+ gtk_tree_view_column_set_clickable (column, TRUE);
+ gtk_tree_view_column_set_resizable (column, TRUE);
+ gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
+ {
+ const char *sample_strings[3];
+ sample_strings[0] = _("Date");
+ sample_strings[1] = rb_entry_view_get_time_date_column_sample ();
+ sample_strings[2] = NULL;
+ rb_entry_view_set_fixed_column_width (episodes, column, renderer, sample_strings);
+ }
+
+ gtk_tree_view_column_set_cell_data_func (column, renderer,
+ (GtkTreeCellDataFunc) podcast_post_date_cell_data_func,
+ dialog, NULL);
+
+ rb_entry_view_append_column_custom (episodes, column,
+ _("Date"), "Date",
+ (GCompareDataFunc) podcast_post_date_sort_func,
+ 0, NULL);
+ rb_entry_view_append_column (episodes, RB_ENTRY_VIEW_COL_TITLE, TRUE);
+ rb_entry_view_append_column (episodes, RB_ENTRY_VIEW_COL_DURATION, TRUE);
+ rb_entry_view_set_sorting_order (RB_ENTRY_VIEW (episodes), "Date", GTK_SORT_DESCENDING);
+ g_signal_connect (episodes,
+ "notify::sort-order",
+ G_CALLBACK (episodes_sort_changed_cb),
+ dialog);
+
+ query = rhythmdb_query_parse (dialog->priv->db,
+ RHYTHMDB_QUERY_PROP_EQUALS,
+ RHYTHMDB_PROP_TYPE,
+ RHYTHMDB_ENTRY_TYPE_PODCAST_SEARCH,
+ RHYTHMDB_QUERY_END);
+ query_model = rhythmdb_query_model_new_empty (dialog->priv->db);
+ rb_entry_view_set_model (episodes, query_model);
+
+ rhythmdb_do_full_query_async_parsed (dialog->priv->db, RHYTHMDB_QUERY_RESULTS (query_model), query);
+ rhythmdb_query_free (query);
+
+ g_object_unref (query_model);
+
+ paned = GTK_WIDGET (gtk_builder_get_object (builder, "paned"));
+ g_signal_connect (paned, "size-allocate", G_CALLBACK (paned_size_allocate_cb), dialog);
+ gtk_paned_pack2 (GTK_PANED (paned),
+ GTK_WIDGET (episodes),
+ TRUE,
+ FALSE);
+
+ gtk_widget_show_all (GTK_WIDGET (dialog));
+ g_object_unref (builder);
+}
+
+static void
+impl_dispose (GObject *object)
+{
+ RBPodcastAddDialog *dialog = RB_PODCAST_ADD_DIALOG (object);
+
+ if (dialog->priv->podcast_mgr != NULL) {
+ g_object_unref (dialog->priv->podcast_mgr);
+ dialog->priv->podcast_mgr = NULL;
+ }
+ if (dialog->priv->db != NULL) {
+ g_object_unref (dialog->priv->db);
+ dialog->priv->db = NULL;
+ }
+
+ G_OBJECT_CLASS (rb_podcast_add_dialog_parent_class)->dispose (object);
+}
+
+static void
+impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+ RBPodcastAddDialog *dialog = RB_PODCAST_ADD_DIALOG (object);
+
+ switch (prop_id) {
+ case PROP_PODCAST_MANAGER:
+ dialog->priv->podcast_mgr = g_value_dup_object (value);
+ break;
+ case PROP_SHELL:
+ dialog->priv->shell = g_value_dup_object (value);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static void
+impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+ RBPodcastAddDialog *dialog = RB_PODCAST_ADD_DIALOG (object);
+
+ switch (prop_id) {
+ case PROP_PODCAST_MANAGER:
+ g_value_set_object (value, dialog->priv->podcast_mgr);
+ break;
+ case PROP_SHELL:
+ g_value_set_object (value, dialog->priv->shell);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static void
+rb_podcast_add_dialog_init (RBPodcastAddDialog *dialog)
+{
+ dialog->priv = G_TYPE_INSTANCE_GET_PRIVATE (dialog,
+ RB_TYPE_PODCAST_ADD_DIALOG,
+ RBPodcastAddDialogPrivate);
+}
+
+static void
+rb_podcast_add_dialog_class_init (RBPodcastAddDialogClass *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_PODCAST_MANAGER,
+ g_param_spec_object ("podcast-manager",
+ "podcast-manager",
+ "RBPodcastManager instance",
+ RB_TYPE_PODCAST_MANAGER,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ 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));
+
+ signals[CLOSE] = g_signal_new ("close",
+ RB_TYPE_PODCAST_ADD_DIALOG,
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (RBPodcastAddDialogClass, close),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+ signals[CLOSED] = g_signal_new ("closed",
+ RB_TYPE_PODCAST_ADD_DIALOG,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (RBPodcastAddDialogClass, closed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ g_type_class_add_private (object_class, sizeof (RBPodcastAddDialogPrivate));
+
+ gtk_binding_entry_add_signal (gtk_binding_set_by_class (klass),
+ GDK_KEY_Escape,
+ 0,
+ "close",
+ 0);
+}
+
+void
+rb_podcast_add_dialog_reset (RBPodcastAddDialog *dialog, const char *text, gboolean load)
+{
+ remove_all_feeds (dialog);
+ rhythmdb_entry_delete_by_type (dialog->priv->db, RHYTHMDB_ENTRY_TYPE_PODCAST_SEARCH);
+ rhythmdb_commit (dialog->priv->db);
+
+ rb_search_entry_set_text (dialog->priv->search_entry, text);
+
+ if (load) {
+ search_cb (dialog->priv->search_entry, text, dialog);
+ } else {
+ rb_search_entry_grab_focus (dialog->priv->search_entry);
+ }
+}
+
+GtkWidget *
+rb_podcast_add_dialog_new (RBShell *shell, RBPodcastManager *podcast_mgr)
+{
+ return GTK_WIDGET (g_object_new (RB_TYPE_PODCAST_ADD_DIALOG,
+ "shell", shell,
+ "podcast-manager", podcast_mgr,
+ NULL));
+}
+
diff --git a/podcast/rb-podcast-add-dialog.h b/podcast/rb-podcast-add-dialog.h
new file mode 100644
index 0000000..2c06ffc
--- /dev/null
+++ b/podcast/rb-podcast-add-dialog.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2010 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_PODCAST_ADD_DIALOG_H
+#define RB_PODCAST_ADD_DIALOG_H
+
+#include <gtk/gtk.h>
+
+#include "rb-podcast-manager.h"
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_PODCAST_ADD_DIALOG (rb_podcast_add_dialog_get_type ())
+#define RB_PODCAST_ADD_DIALOG(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_PODCAST_ADD_DIALOG, RBPodcastAddDialog))
+#define RB_PODCAST_ADD_DIALOG_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_PODCAST_ADD_DIALOG, RBPodcastAddDialogClass))
+#define RB_IS_PODCAST_ADD_DIALOG(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_PODCAST_ADD_DIALOG))
+#define RB_IS_PODCAST_ADD_DIALOG_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_PODCAST_ADD_DIALOG))
+#define RB_PODCAST_ADD_DIALOG_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_PODCAST_ADD_DIALOG, RBPodcastAddDialogClass))
+
+typedef struct _RBPodcastAddDialog RBPodcastAddDialog;
+typedef struct _RBPodcastAddDialogClass RBPodcastAddDialogClass;
+
+typedef struct RBPodcastAddDialogPrivate RBPodcastAddDialogPrivate;
+
+struct _RBPodcastAddDialog
+{
+ GtkHBox parent;
+
+ RBPodcastAddDialogPrivate *priv;
+};
+
+struct _RBPodcastAddDialogClass
+{
+ GtkHBoxClass parent;
+
+ /* signals */
+ void (*close) (RBPodcastAddDialog *dialog);
+ void (*closed) (RBPodcastAddDialog *dialog);
+};
+
+GType rb_podcast_add_dialog_get_type (void);
+
+GtkWidget * rb_podcast_add_dialog_new (RBShell *shell, RBPodcastManager *podcast_mgr);
+
+void rb_podcast_add_dialog_reset (RBPodcastAddDialog *dialog, const char *text, gboolean load);
+
+G_END_DECLS
+
+#endif /* RB_PODCAST_ADD_DIALOG_H */
diff --git a/podcast/rb-podcast-manager.c b/podcast/rb-podcast-manager.c
index 55758c2..3d5068b 100644
--- a/podcast/rb-podcast-manager.c
+++ b/podcast/rb-podcast-manager.c
@@ -61,7 +61,6 @@ enum
enum
{
- STATUS_CHANGED,
START_DOWNLOAD,
FINISH_DOWNLOAD,
PROCESS_ERROR,
@@ -194,18 +193,6 @@ rb_podcast_manager_class_init (RBPodcastManagerClass *klass)
RHYTHMDB_TYPE,
G_PARAM_READWRITE));
- rb_podcast_manager_signals[STATUS_CHANGED] =
- g_signal_new ("status_changed",
- G_OBJECT_CLASS_TYPE (object_class),
- G_SIGNAL_RUN_LAST,
- G_STRUCT_OFFSET (RBPodcastManagerClass, status_changed),
- NULL, NULL,
- rb_marshal_VOID__BOXED_ULONG,
- G_TYPE_NONE,
- 2,
- RHYTHMDB_TYPE_ENTRY,
- G_TYPE_ULONG);
-
rb_podcast_manager_signals[START_DOWNLOAD] =
g_signal_new ("start_download",
G_OBJECT_CLASS_TYPE (object_class),
@@ -1500,9 +1487,6 @@ download_progress (RBPodcastManagerInfo *data, guint64 downloaded, guint64 total
rhythmdb_commit (data->pd->priv->db);
- g_signal_emit (data->pd, rb_podcast_manager_signals[STATUS_CHANGED],
- 0, data->entry, local_progress);
-
GDK_THREADS_LEAVE ();
data->progress = local_progress;
diff --git a/podcast/rb-podcast-manager.h b/podcast/rb-podcast-manager.h
index 6d0f3a0..bdde140 100644
--- a/podcast/rb-podcast-manager.h
+++ b/podcast/rb-podcast-manager.h
@@ -57,7 +57,6 @@ typedef struct
GObjectClass parent_class;
/* signals */
- void (*status_changed) (RBPodcastManager* pd, RhythmDBEntry *entry, glong value);
void (*start_download) (RBPodcastManager* pd, RhythmDBEntry *entry);
void (*finish_download) (RBPodcastManager* pd, RhythmDBEntry *entry);
void (*feed_updates_available) (RBPodcastManager* pd, RhythmDBEntry *entry);
diff --git a/podcast/rb-podcast-source.c b/podcast/rb-podcast-source.c
index 4622b1b..279fcd0 100644
--- a/podcast/rb-podcast-source.c
+++ b/podcast/rb-podcast-source.c
@@ -40,6 +40,7 @@
#include <glib.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
+#include <libsoup/soup.h>
#include "rb-podcast-source.h"
#include "rb-podcast-settings.h"
@@ -54,7 +55,6 @@
#include "rb-util.h"
#include "rb-file-helpers.h"
#include "rb-dialog.h"
-#include "rb-uri-dialog.h"
#include "rb-podcast-properties-dialog.h"
#include "rb-feed-podcast-properties-dialog.h"
#include "rb-playlist-manager.h"
@@ -64,6 +64,7 @@
#include "rb-cut-and-paste-code.h"
#include "rb-source-search-basic.h"
#include "rb-cell-renderer-pixbuf.h"
+#include "rb-podcast-add-dialog.h"
#include "rb-source-toolbar.h"
static void podcast_cmd_new_podcast (GtkAction *action,
@@ -88,6 +89,9 @@ struct _RBPodcastSourcePrivate
guint prefs_notify_id;
GtkWidget *paned;
+ GtkWidget *add_dialog;
+ GtkAction *add_action;
+ RBSourceToolbar *toolbar;
RhythmDBPropertyModel *feed_model;
RBPropertyView *feeds;
@@ -107,8 +111,8 @@ struct _RBPodcastSourcePrivate
static GtkActionEntry rb_podcast_source_actions [] =
{
- { "MusicNewPodcast", RB_STOCK_PODCAST_NEW, N_("_New Podcast Feed..."), "<control>P",
- N_("Subscribe to a new Podcast Feed"),
+ { "MusicNewPodcast", RB_STOCK_PODCAST_NEW, N_("_New Podcast Feed..."), NULL,
+ N_("Subscribe to a new podcast feed"),
G_CALLBACK (podcast_cmd_new_podcast) },
{ "PodcastSrcDownloadPost", NULL, N_("Download _Episode"), NULL,
N_("Download Podcast Episode"),
@@ -340,35 +344,67 @@ posts_view_drag_data_received_cb (GtkWidget *widget,
}
static void
-podcast_location_added_cb (RBURIDialog *dialog,
- const char *location,
- RBPodcastSource *source)
+podcast_add_dialog_closed_cb (RBPodcastAddDialog *dialog, RBPodcastSource *source)
{
- rb_podcast_manager_subscribe_feed (source->priv->podcast_mgr,
- location,
- FALSE);
+ rb_podcast_source_do_query (source);
+ gtk_widget_hide (source->priv->add_dialog);
+ gtk_widget_show (GTK_WIDGET (source->priv->toolbar));
+ gtk_widget_show (source->priv->paned);
}
static void
-podcast_add_response_cb (GtkDialog *dialog, int response, gpointer meh)
+yank_clipboard_url (GtkClipboard *clipboard, const char *text, RBPodcastSource *source)
{
- gtk_widget_destroy (GTK_WIDGET (dialog));
+ SoupURI *uri;
+
+ if (text == NULL) {
+ return;
+ }
+
+ uri = soup_uri_new (text);
+ if (SOUP_URI_VALID_FOR_HTTP (uri)) {
+ rb_podcast_add_dialog_reset (RB_PODCAST_ADD_DIALOG (source->priv->add_dialog), text, FALSE);
+ }
+
+ if (uri != NULL) {
+ soup_uri_free (uri);
+ }
}
static void
podcast_cmd_new_podcast (GtkAction *action, RBPodcastSource *source)
{
- GtkWidget *dialog;
+ RhythmDBQueryModel *query_model;
- dialog = rb_uri_dialog_new (_("New Podcast Feed"), _("URL of podcast feed:"));
- g_signal_connect_object (dialog,
- "location-added",
- G_CALLBACK (podcast_location_added_cb),
- source, 0);
- g_signal_connect (dialog, "response", G_CALLBACK (podcast_add_response_cb), NULL);
- gtk_widget_show_all (dialog);
+ rb_podcast_add_dialog_reset (RB_PODCAST_ADD_DIALOG (source->priv->add_dialog), NULL, FALSE);
+
+ /* if we can get a url from the clipboard, populate the dialog with that,
+ * since there's a good chance that's what the user wants to do anyway.
+ */
+ gtk_clipboard_request_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD),
+ (GtkClipboardTextReceivedFunc) yank_clipboard_url,
+ source);
+ gtk_clipboard_request_text (gtk_clipboard_get (GDK_SELECTION_PRIMARY),
+ (GtkClipboardTextReceivedFunc) yank_clipboard_url,
+ source);
+
+ query_model = rhythmdb_query_model_new_empty (source->priv->db);
+ rb_entry_view_set_model (source->priv->posts, query_model);
+ g_object_set (source, "query-model", query_model, NULL);
+ g_object_unref (query_model);
+
+ gtk_widget_hide (source->priv->paned);
+ gtk_widget_hide (GTK_WIDGET (source->priv->toolbar));
+ gtk_widget_show (source->priv->add_dialog);
}
+void
+rb_podcast_source_add_feed (RBPodcastSource *source, const char *text)
+{
+ gtk_action_activate (source->priv->add_action);
+
+ rb_podcast_add_dialog_reset (RB_PODCAST_ADD_DIALOG (source->priv->add_dialog), text, TRUE);
+}
static void
podcast_cmd_download_post (GtkAction *action, RBPodcastSource *source)
@@ -1283,7 +1319,6 @@ impl_constructed (GObject *object)
int position;
GtkUIManager *ui_manager;
GtkWidget *grid;
- RBSourceToolbar *toolbar;
RB_CHAIN_GOBJECT_METHOD (rb_podcast_source_parent_class, constructed, object);
source = RB_PODCAST_SOURCE (object);
@@ -1305,11 +1340,11 @@ impl_constructed (GObject *object)
rb_podcast_source_actions,
G_N_ELEMENTS (rb_podcast_source_actions));
- action = gtk_action_group_get_action (source->priv->action_group,
- "MusicNewPodcast");
+ source->priv->add_action = gtk_action_group_get_action (source->priv->action_group,
+ "MusicNewPodcast");
/* Translators: this is the toolbar button label
for New Podcast Feed action. */
- g_object_set (action, "short-label", C_("Podcast", "Add"), NULL);
+ g_object_set (source->priv->add_action, "short-label", C_("Podcast", "Add"), NULL);
action = gtk_action_group_get_action (source->priv->action_group,
"PodcastFeedUpdate");
@@ -1334,7 +1369,6 @@ impl_constructed (GObject *object)
source->priv->paned = gtk_paned_new (GTK_ORIENTATION_VERTICAL);
- g_object_unref (shell);
/* set up posts view */
source->priv->posts = rb_entry_view_new (source->priv->db,
@@ -1535,8 +1569,8 @@ impl_constructed (GObject *object)
GDK_ACTION_COPY | GDK_ACTION_MOVE);
/* set up toolbar */
- toolbar = rb_source_toolbar_new (RB_SOURCE (source), ui_manager);
- rb_source_toolbar_add_search_entry (toolbar, "/PodcastSourceSearchMenu", NULL);
+ source->priv->toolbar = rb_source_toolbar_new (RB_SOURCE (source), ui_manager);
+ rb_source_toolbar_add_search_entry (source->priv->toolbar, "/PodcastSourceSearchMenu", NULL);
/* pack the feed and post views into the source */
gtk_paned_pack1 (GTK_PANED (source->priv->paned),
@@ -1548,12 +1582,19 @@ impl_constructed (GObject *object)
gtk_widget_set_margin_top (GTK_WIDGET (grid), 6);
gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
- gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (toolbar), 0, 0, 1, 1);
+ gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (source->priv->toolbar), 0, 0, 1, 1);
gtk_grid_attach (GTK_GRID (grid), source->priv->paned, 0, 1, 1, 1);
gtk_container_add (GTK_CONTAINER (source), grid);
+ /* podcast add dialog */
+ source->priv->add_dialog = rb_podcast_add_dialog_new (shell, source->priv->podcast_mgr);
+ gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (source->priv->add_dialog), 0, 2, 1, 1);
+ gtk_widget_set_no_show_all (source->priv->add_dialog, TRUE);
+ g_signal_connect_object (source->priv->add_dialog, "closed", G_CALLBACK (podcast_add_dialog_closed_cb), source, 0);
+
gtk_widget_show_all (GTK_WIDGET (source));
+ gtk_widget_hide (source->priv->add_dialog);
g_object_get (source, "settings", &settings, NULL);
@@ -1569,6 +1610,7 @@ impl_constructed (GObject *object)
g_object_unref (settings);
g_object_unref (ui_manager);
+ g_object_unref (shell);
rb_podcast_source_do_query (source);
}
diff --git a/podcast/rb-podcast-source.h b/podcast/rb-podcast-source.h
index bea1f1f..d363731 100644
--- a/podcast/rb-podcast-source.h
+++ b/podcast/rb-podcast-source.h
@@ -66,6 +66,8 @@ RBSource *rb_podcast_source_new (RBShell *shell,
const char *name,
const char *icon_name);
+void rb_podcast_source_add_feed (RBPodcastSource *source, const char *url);
+
G_END_DECLS
#endif /* __RB_PODCAST_SOURCE_H */
diff --git a/shell/rb-shell.c b/shell/rb-shell.c
index 347daf4..b7c1939 100644
--- a/shell/rb-shell.c
+++ b/shell/rb-shell.c
@@ -3616,11 +3616,10 @@ rb_shell_load_uri (RBShell *shell,
{
RhythmDBEntry *entry;
- /* If the URI points to a Podcast, pass it on to
- * the Podcast source */
+ /* If the URI points to a Podcast, pass it on to the Podcast source */
if (rb_uri_could_be_podcast (uri, NULL)) {
- rb_podcast_manager_subscribe_feed (shell->priv->podcast_manager, uri, FALSE);
rb_shell_select_page (shell, RB_DISPLAY_PAGE (shell->priv->podcast_source));
+ rb_podcast_source_add_feed (shell->priv->podcast_source, uri);
return TRUE;
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]