totem r6219 - in trunk: . po src src/backend src/plugins/youtube



Author: pwithnall
Date: Wed Apr  1 18:04:30 2009
New Revision: 6219
URL: http://svn.gnome.org/viewvc/totem?rev=6219&view=rev

Log:
2009-04-01  Philip Withnall  <philip tecnocode co uk>

	* configure.in:
	* src/backend/bacon-video-widget-gst-0.10.c
	(bacon_video_widget_can_play_youtube_videos):
	* src/backend/bacon-video-widget-xine.c
	(bacon_video_widget_can_play_youtube_videos):
	* src/backend/bacon-video-widget.h:
	* src/plugins/youtube/Makefile.am:
	* src/plugins/youtube/totem-youtube.c
	(totem_youtube_plugin_class_init), (totem_youtube_plugin_init),
	(set_up_tree_view), (impl_activate), (impl_deactivate),
	(get_fmt_param), (progress_bar_pulse_cb), (set_progress_bar_text),
	(increment_progress_bar_fraction), (resolve_t_param_cb),
	(resolve_t_param), (thumbnail_loaded_cb), (thumbnail_opened_cb),
	(query_finished_cb), (query_progress_cb), (execute_query),
	(search_button_clicked_cb), (search_entry_activate_cb),
	(load_related_videos), (notebook_switch_page_cb),
	(open_in_web_browser_activate_cb), (value_changed_cb),
	(button_press_event_cb), (button_release_event_cb),
	(starting_video_cb):
	* src/plugins/youtube/youtube.py:
	* src/plugins/youtube/youtube.totem-plugin.in:
	* src/plugins/youtube/youtube.ui:
	* src/totem-cell-renderer-video.c
	(totem_cell_renderer_video_class_init),
	(totem_cell_renderer_video_init),
	(totem_cell_renderer_video_dispose),
	(totem_cell_renderer_video_finalize),
	(totem_cell_renderer_video_set_property), (get_size),
	(totem_cell_renderer_video_render):
	* src/totem-video-list.c (totem_video_list_set_property),
	(row_activated_cb): Ported the YouTube plugin to C, using the shiny
	new libgdata library.

2009-04-01  Philip Withnall  <philip tecnocode co uk>

	* POTFILES.in: Ported the YouTube plugin to C.



Added:
   trunk/src/plugins/youtube/totem-youtube.c
Removed:
   trunk/src/plugins/youtube/youtube.py
Modified:
   trunk/ChangeLog
   trunk/configure.in
   trunk/po/ChangeLog
   trunk/po/POTFILES.in
   trunk/src/backend/bacon-video-widget-gst-0.10.c
   trunk/src/backend/bacon-video-widget-xine.c
   trunk/src/backend/bacon-video-widget.h
   trunk/src/plugins/youtube/Makefile.am
   trunk/src/plugins/youtube/youtube.totem-plugin.in
   trunk/src/plugins/youtube/youtube.ui
   trunk/src/totem-cell-renderer-video.c
   trunk/src/totem-video-list.c

Modified: trunk/configure.in
==============================================================================
--- trunk/configure.in	(original)
+++ trunk/configure.in	Wed Apr  1 18:04:30 2009
@@ -608,6 +608,14 @@
 				add_plugin="0"
 			fi
 		;;
+		youtube)
+			PKG_CHECK_MODULES(LIBGDATA, libgdata,
+				[HAVE_LIBGDATA=yes], [HAVE_LIBGDATA=no])
+			if test "${HAVE_LIBGDATA}" != "yes" ; then
+				plugin_error_or_ignore "you need libgdata installed for the YouTube plugin"
+				add_plugin="0"
+			fi
+		;;
 	esac
 
 	# Add the specified plugin

Modified: trunk/po/POTFILES.in
==============================================================================
--- trunk/po/POTFILES.in	(original)
+++ trunk/po/POTFILES.in	Wed Apr  1 18:04:30 2009
@@ -82,7 +82,7 @@
 [type: gettext/ini]src/plugins/tracker/tracker.totem-plugin.in
 [type: gettext/ini]src/plugins/youtube/youtube.totem-plugin.in
 [type: gettext/glade]src/plugins/youtube/youtube.ui
-src/plugins/youtube/youtube.py
+src/plugins/youtube/totem-youtube.c
 browser-plugin/totem-plugin-viewer.c
 [type: gettext/ini]src/plugins/pythonconsole/pythonconsole.totem-plugin.in
 src/plugins/pythonconsole/pythonconsole.py

Modified: trunk/src/backend/bacon-video-widget-gst-0.10.c
==============================================================================
--- trunk/src/backend/bacon-video-widget-gst-0.10.c	(original)
+++ trunk/src/backend/bacon-video-widget-gst-0.10.c	Wed Apr  1 18:04:30 2009
@@ -5349,6 +5349,12 @@
   }
 }
 
+gboolean
+bacon_video_widget_can_play_youtube_videos (BaconVideoWidget *bvw)
+{
+  return gst_default_registry_check_feature_version ("souphttpsrc", 0, 10, 0);
+}
+
 /*
  * vim: sw=2 ts=8 cindent noai bs=2
  */

Modified: trunk/src/backend/bacon-video-widget-xine.c
==============================================================================
--- trunk/src/backend/bacon-video-widget-xine.c	(original)
+++ trunk/src/backend/bacon-video-widget-xine.c	Wed Apr  1 18:04:30 2009
@@ -4339,3 +4339,9 @@
 
 	return pixbuf;
 }
+
+gboolean
+bacon_video_widget_can_play_youtube_videos (BaconVideoWidget *bvw)
+{
+	return TRUE;
+}

Modified: trunk/src/backend/bacon-video-widget.h
==============================================================================
--- trunk/src/backend/bacon-video-widget.h	(original)
+++ trunk/src/backend/bacon-video-widget.h	Wed Apr  1 18:04:30 2009
@@ -307,6 +307,9 @@
 gboolean bacon_video_widget_has_next_track	 (BaconVideoWidget *bvw);
 gboolean bacon_video_widget_has_previous_track	 (BaconVideoWidget *bvw);
 
+/* YouTube functions */
+gboolean bacon_video_widget_can_play_youtube_videos (BaconVideoWidget *bvw);
+
 /* Screenshot functions */
 gboolean bacon_video_widget_can_get_frames       (BaconVideoWidget *bvw,
 						  GError **error);

Modified: trunk/src/plugins/youtube/Makefile.am
==============================================================================
--- trunk/src/plugins/youtube/Makefile.am	(original)
+++ trunk/src/plugins/youtube/Makefile.am	Wed Apr  1 18:04:30 2009
@@ -1,15 +1,45 @@
+modules_flags = -export_dynamic -avoid-version -module
+
 plugindir = $(PLUGINDIR)/youtube
+plugin_LTLIBRARIES = libyoutube.la
+
 uidir = $(plugindir)
-plugin_PYTHON = youtube.py
+ui_DATA = youtube.ui
 
 plugin_in_files = youtube.totem-plugin.in
 
 %.totem-plugin: %.totem-plugin.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
 
 plugin_DATA = $(plugin_in_files:.totem-plugin.in=.totem-plugin)
-ui_DATA = youtube.ui
 
-EXTRA_DIST = $(plugin_in_files) $(ui_DATA) youtube.py
+common_defines = \
+	-D_REENTRANT					\
+	-DDBUS_API_SUBJECT_TO_CHANGE			\
+	-DGNOMELOCALEDIR=\""$(datadir)/locale"\"	\
+	-DGCONF_PREFIX=\""/apps/totem"\"		\
+	-DDATADIR=\""$(datadir)"\"			\
+	-DLIBEXECDIR=\""$(libexecdir)"\"		\
+	-DBINDIR=\""$(bindir)"\"			\
+	-DTOTEM_PLUGIN_DIR=\""$(libdir)/totem/plugins"\"\
+	$(DISABLE_DEPRECATED)
 
-CLEANFILES = $(plugin_DATA)
+libyoutube_la_SOURCES = totem-youtube.c
+libyoutube_la_LDFLAGS = $(modules_flags)
+libyoutube_la_LIBADD = $(LIBGDATA_LIBS)
+libyoutube_la_CPPFLAGS = $(common_defines)
+
+libyoutube_la_CFLAGS = \
+	$(DEPENDENCY_CFLAGS)	\
+	$(LIBGDATA_CFLAGS)	\
+	$(WARN_CFLAGS)		\
+	$(DBUS_CFLAGS)		\
+	$(AM_CFLAGS)		\
+	-I$(top_srcdir)/	\
+	-I$(top_srcdir)/src	\
+	-I$(top_srcdir)/src/plugins
+
+EXTRA_DIST = $(plugin_in_files) $(ui_DATA)
+
+CLEANFILES = $(plugin_DATA) $(BUILT_SOURCES)
 DISTCLEANFILES = $(plugin_DATA)
+

Added: trunk/src/plugins/youtube/totem-youtube.c
==============================================================================
--- (empty file)
+++ trunk/src/plugins/youtube/totem-youtube.c	Wed Apr  1 18:04:30 2009
@@ -0,0 +1,798 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* 
+ * Copyright (C) 2009 Philip Withnall <philip tecnocode co uk>
+ *
+ * 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.
+ *
+ * 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 Street, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ * The Totem project hereby grant permission for non-GPL compatible GStreamer
+ * plugins to be used and distributed together with GStreamer and Totem. This
+ * permission are above and beyond the permissions granted by the GPL license
+ * Totem is covered by.
+ *
+ * See license_change file for details.
+ */
+
+#include <config.h>
+#include <glib.h>
+#include <glib-object.h>
+#include <glib/gi18n-lib.h>
+#include <gdata/gdata.h>
+
+#include "totem-plugin.h"
+#include "totem.h"
+#include "totem-video-list.h"
+#include "totem-interface.h"
+#include "backend/bacon-video-widget.h"
+
+/* Notebook pages */
+enum {
+	SEARCH_TREE_VIEW = 0,
+	RELATED_TREE_VIEW,
+	NUM_TREE_VIEWS
+};
+
+#define DEVELOPER_KEY	"AI39si5D82T7zgTGS9fmUQAZ7KO5EvKNN_Hf1yoEPf1bpVOTD0At-z7Ovgjupke6o0xdS4drF8SDLfjfmuIXLQQNdE3foPfIdg"
+#define CLIENT_ID	"ytapi-GNOME-Totem-444fubtt-1"
+#define MAX_RESULTS	10
+#define THUMBNAIL_WIDTH	180
+#define PULSE_INTERVAL	200
+
+#define TOTEM_TYPE_YOUTUBE_PLUGIN		(totem_youtube_plugin_get_type ())
+#define TOTEM_YOUTUBE_PLUGIN(o)			(G_TYPE_CHECK_INSTANCE_CAST ((o), TOTEM_TYPE_YOUTUBE_PLUGIN, TotemYouTubePlugin))
+#define TOTEM_YOUTUBE_PLUGIN_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST((k), TOTEM_TYPE_YOUTUBE_PLUGIN, TotemYouTubePluginClass))
+#define TOTEM_IS_YOUTUBE_PLUGIN(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), TOTEM_TYPE_YOUTUBE_PLUGIN))
+#define TOTEM_IS_YOUTUBE_PLUGIN_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), TOTEM_TYPE_YOUTUBE_PLUGIN))
+#define TOTEM_YOUTUBE_PLUGIN_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), TOTEM_TYPE_YOUTUBE_PLUGIN, TotemYouTubePluginClass))
+
+typedef struct {
+	TotemPlugin parent;
+	Totem *totem;
+	GDataYouTubeService *service;
+	BaconVideoWidget *bvw;
+
+	guint current_tree_view;
+	GDataQuery *query[NUM_TREE_VIEWS];
+	GCancellable *cancellable[NUM_TREE_VIEWS];
+	GRegex *regex;
+	gboolean button_down;
+	GDataYouTubeVideo *playing_video;
+
+	GtkEntry *search_entry;
+	GtkButton *search_button;
+	GtkProgressBar *progress_bar[NUM_TREE_VIEWS];
+	gfloat progress_bar_increment[NUM_TREE_VIEWS];
+	GtkNotebook *notebook;
+	GtkWidget *vbox;
+	GtkAdjustment *vadjust[NUM_TREE_VIEWS];
+	GtkListStore *list_store[NUM_TREE_VIEWS];
+	GtkTreeView *tree_view[NUM_TREE_VIEWS];
+} TotemYouTubePlugin;
+
+typedef struct {
+	TotemPluginClass parent_class;
+} TotemYouTubePluginClass;
+
+G_MODULE_EXPORT GType register_totem_plugin	(GTypeModule *module);
+GType totem_youtube_plugin_get_type		(void) G_GNUC_CONST;
+
+static gboolean impl_activate			(TotemPlugin *plugin, TotemObject *totem, GError **error);
+static void impl_deactivate			(TotemPlugin *plugin, TotemObject *totem);
+
+/* GtkBuilder callbacks */
+void notebook_switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page, guint page_num, TotemYouTubePlugin *self);
+void search_button_clicked_cb (GtkButton *button, TotemYouTubePlugin *self);
+void search_entry_activate_cb (GtkEntry *entry, TotemYouTubePlugin *self);
+gboolean button_press_event_cb (GtkWidget *widget, GdkEventButton *event, TotemYouTubePlugin *self);
+gboolean button_release_event_cb (GtkWidget *widget, GdkEventButton *event, TotemYouTubePlugin *self);
+void open_in_web_browser_activate_cb (GtkAction *action, TotemYouTubePlugin *self);
+void value_changed_cb (GtkAdjustment *adjustment, TotemYouTubePlugin *self);
+gboolean starting_video_cb (TotemVideoList *video_list, GtkTreeView *tree_view, GtkTreePath *path, TotemYouTubePlugin *self);
+
+TOTEM_PLUGIN_REGISTER (TotemYouTubePlugin, totem_youtube_plugin)
+
+static void
+totem_youtube_plugin_class_init (TotemYouTubePluginClass *klass)
+{
+	TotemPluginClass *plugin_class = TOTEM_PLUGIN_CLASS (klass);
+
+	plugin_class->activate = impl_activate;
+	plugin_class->deactivate = impl_deactivate;
+}
+
+static void
+totem_youtube_plugin_init (TotemYouTubePlugin *plugin)
+{
+	/* Nothing to see here; move along */
+}
+
+static void
+set_up_tree_view (TotemYouTubePlugin *self, GtkBuilder *builder, guint key)
+{
+	GtkUIManager *ui_manager;
+	GtkActionGroup *action_group;
+	GtkAction *action, *menu_item;
+	GtkWidget *vscroll, *tree_view;
+
+	/* Give the video lists a handle to Totem and connect their scrollbar signals */
+	if (key == SEARCH_TREE_VIEW) {
+		tree_view = GTK_WIDGET (gtk_builder_get_object (builder, "yt_treeview_search"));
+		vscroll = gtk_scrolled_window_get_vscrollbar (GTK_SCROLLED_WINDOW (gtk_builder_get_object (builder, "yt_scrolled_window_search")));
+		self->list_store[key] = GTK_LIST_STORE (gtk_builder_get_object (builder, "yt_list_store_search"));
+		self->tree_view[key] = GTK_TREE_VIEW (tree_view);
+		self->progress_bar[key] = GTK_PROGRESS_BAR (gtk_builder_get_object (builder, "yt_progress_bar_search"));
+	} else {
+		tree_view = GTK_WIDGET (gtk_builder_get_object (builder, "yt_treeview_related"));
+		vscroll = gtk_scrolled_window_get_vscrollbar (GTK_SCROLLED_WINDOW (gtk_builder_get_object (builder, "yt_scrolled_window_related")));
+		self->list_store[key] = GTK_LIST_STORE (gtk_builder_get_object (builder, "yt_list_store_related"));
+		self->tree_view[key] = GTK_TREE_VIEW (tree_view);
+		self->progress_bar[key] = GTK_PROGRESS_BAR (gtk_builder_get_object (builder, "yt_progress_bar_related"));
+	}
+	g_object_set (tree_view, "totem", self->totem, NULL);
+	g_signal_connect (vscroll, "button-press-event", G_CALLBACK (button_press_event_cb), self);
+	g_signal_connect (vscroll, "button-release-event", G_CALLBACK (button_release_event_cb), self);
+
+	/* Add the extra popup menu options. This is done here rather than in the UI file, because it's done for multiple treeviews;
+	 * if it were done in the UI file, the same action group would be used multiple times, which GTK+ doesn't like. */
+	ui_manager = totem_video_list_get_ui_manager (TOTEM_VIDEO_LIST (tree_view));
+	action_group = gtk_action_group_new ("youtube-action-group");
+	action = gtk_action_new ("open-in-web-browser", _("_Open in Web Browser"), _("Open the video in your web browser"), "gtk-jump-to");
+	gtk_action_group_add_action_with_accel (action_group, action, NULL);
+
+	gtk_ui_manager_insert_action_group (ui_manager, action_group, 1);
+	gtk_ui_manager_add_ui (ui_manager, gtk_ui_manager_new_merge_id (ui_manager),
+			       "/ui/totem-video-list-popup/",
+			       "open-in-web-browser",
+			       "open-in-web-browser",
+			       GTK_UI_MANAGER_MENUITEM,
+			       FALSE);
+
+	menu_item = gtk_ui_manager_get_action (ui_manager, "/ui/totem-video-list-popup/open-in-web-browser");
+	g_signal_connect (menu_item, "activate", G_CALLBACK (open_in_web_browser_activate_cb), self);
+
+	/* Connect to more scroll events */
+	self->vadjust[key] = gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (tree_view));
+	g_signal_connect (self->vadjust[key], "value-changed", G_CALLBACK (value_changed_cb), self);
+}
+
+static gboolean
+impl_activate (TotemPlugin *plugin, TotemObject *totem, GError **error)
+{
+	TotemYouTubePlugin *self = TOTEM_YOUTUBE_PLUGIN (plugin);
+	GtkWindow *main_window;
+	GtkBuilder *builder;
+	guint i;
+
+	self->totem = g_object_ref (totem);
+	self->bvw = BACON_VIDEO_WIDGET (totem_get_video_widget (totem));
+
+	/* Set up the interface */
+	main_window = totem_get_main_window (totem);
+	builder = totem_plugin_load_interface (plugin, "youtube.ui", TRUE, main_window, self);
+	g_object_unref (main_window);
+
+	self->search_entry = GTK_ENTRY (gtk_builder_get_object (builder, "yt_search_entry"));
+	self->search_button = GTK_BUTTON (gtk_builder_get_object (builder, "yt_search_button"));
+	self->notebook = GTK_NOTEBOOK (gtk_builder_get_object (builder, "yt_notebook"));
+
+	/* Set up the tree view pages */
+	for (i = 0; i < NUM_TREE_VIEWS; i++)
+		set_up_tree_view (self, builder, i);
+	self->current_tree_view = SEARCH_TREE_VIEW;
+
+	self->vbox = GTK_WIDGET (gtk_builder_get_object (builder, "yt_vbox"));
+	gtk_widget_show_all (self->vbox);
+
+	/* Add the sidebar page */
+	totem_add_sidebar_page (totem, "youtube", _("YouTube"), self->vbox);
+	g_object_unref (builder);
+
+	return TRUE;
+}
+
+static void
+impl_deactivate	(TotemPlugin *plugin, TotemObject *totem)
+{
+	guint i;
+	TotemYouTubePlugin *self = TOTEM_YOUTUBE_PLUGIN (plugin);
+
+	totem_remove_sidebar_page (self->totem, "youtube");
+
+	for (i = 0; i < NUM_TREE_VIEWS; i++) {
+		if (self->cancellable[i] != NULL) {
+			g_cancellable_cancel (self->cancellable[i]);
+			g_object_unref (self->cancellable[i]);
+		}
+		if (self->query[i] != NULL)
+			g_object_unref (self->query[i]);
+	}
+
+	if (self->playing_video != NULL)
+		g_object_unref (self->playing_video);
+	if (self->service != NULL)
+		g_object_unref (self->service);
+	g_object_unref (self->bvw);
+	g_object_unref (self->totem);
+	if (self->regex != NULL)
+		g_regex_unref (self->regex);
+}
+
+static const gchar *
+get_fmt_param (TotemYouTubePlugin *self)
+{
+	if (bacon_video_widget_get_connection_speed (self->bvw) >= 10)
+		return "&fmt=18";
+	return "";
+}
+
+typedef struct {
+	TotemYouTubePlugin *plugin;
+	guint tree_view;
+} ProgressBarData;
+
+static gboolean
+progress_bar_pulse_cb (ProgressBarData *data)
+{
+	TotemYouTubePlugin *self = data->plugin;
+
+	if (self->progress_bar_increment[data->tree_view] != 0.0) {
+		g_slice_free (ProgressBarData, data);
+		return FALSE; /* The first entry has been retrieved */
+	}
+
+	gtk_progress_bar_pulse (self->progress_bar[data->tree_view]);
+	return TRUE;
+}
+
+static void
+set_progress_bar_text (TotemYouTubePlugin *self, const gchar *text, guint tree_view)
+{
+	ProgressBarData *data;
+	GdkCursor *cursor;
+
+	/* Set the cursor to a watch */
+	cursor = gdk_cursor_new (GDK_WATCH);
+	gdk_window_set_cursor (gtk_widget_get_window (self->vbox), cursor);
+	gdk_cursor_unref (cursor);
+
+	/* Call the pulse method */
+	data = g_slice_new (ProgressBarData);
+	data->plugin = self;
+	data->tree_view = tree_view;
+
+	gtk_progress_bar_set_text (self->progress_bar[tree_view], text);
+	gtk_progress_bar_set_fraction (self->progress_bar[tree_view], 0.0);
+	self->progress_bar_increment[tree_view] = 0.0;
+	g_timeout_add (PULSE_INTERVAL, (GSourceFunc) progress_bar_pulse_cb, data);
+}
+
+static void
+increment_progress_bar_fraction (TotemYouTubePlugin *self, guint tree_view)
+{
+	gdouble new_value = MIN (gtk_progress_bar_get_fraction (self->progress_bar[tree_view]) + self->progress_bar_increment[tree_view], 1.0);
+
+	g_debug ("Incrementing progress bar by %f (new value: %f)", self->progress_bar_increment[tree_view], new_value);
+	gtk_progress_bar_set_fraction (self->progress_bar[tree_view], new_value);
+
+	/* Update the UI */
+	if (gtk_progress_bar_get_fraction (self->progress_bar[tree_view]) == 1.0) {
+		/* The entire search process (including loading thumbnails and t params) is finished, so update the progress bar */
+		gdk_window_set_cursor (gtk_widget_get_window (self->vbox), NULL);
+		gtk_progress_bar_set_text (self->progress_bar[tree_view], "");
+		gtk_progress_bar_set_fraction (self->progress_bar[tree_view], 0.0);
+	}
+}
+
+typedef struct {
+	TotemYouTubePlugin *plugin;
+	GDataEntry *entry;
+	GtkTreeIter iter;
+	guint tree_view;
+} TParamData;
+
+static void
+resolve_t_param_cb (GObject *source_object, GAsyncResult *result, TParamData *data)
+{
+	gchar *contents, *video_uri = NULL;
+	const gchar *video_id;
+	gsize length;
+	GMatchInfo *match_info;
+	GError *error = NULL;
+	TotemYouTubePlugin *self = data->plugin;
+
+	/* Finish loading the page */
+	if (g_file_load_contents_finish (G_FILE (source_object), result, &contents, &length, NULL, &error) == FALSE) {
+		GtkWindow *window;
+
+		/* Couldn't load the page contents; error */
+		window = totem_get_main_window (data->plugin->totem);
+		totem_interface_error (_("Error Looking Up Video URI"), error->message, window);
+		g_object_unref (window);
+		g_error_free (error);
+		goto free_data;
+	}
+
+	video_id = gdata_youtube_video_get_video_id (GDATA_YOUTUBE_VIDEO (data->entry));
+
+	/* Check for the t parameter, which is now in a JavaScript array on the video page */
+	g_regex_match (self->regex, contents, 0, &match_info);
+	if (g_match_info_matches (match_info) == TRUE) {
+		gchar *t_param;
+		const gchar *fmt_param;
+		GString *video_uri_string;
+
+		/* We have a match */
+		t_param = g_match_info_fetch (match_info, 1);
+		fmt_param = get_fmt_param (self);
+
+		video_uri_string = g_string_new ("http://www.youtube.com/get_video?video_id=";);
+		g_string_append_uri_escaped (video_uri_string, video_id, NULL, TRUE);
+		g_string_append (video_uri_string, "&t=");
+		g_string_append_uri_escaped (video_uri_string, t_param, NULL, TRUE);
+		g_string_append (video_uri_string, fmt_param);
+
+		video_uri = g_string_free (video_uri_string, FALSE);
+	} else {
+		GDataMediaContent *content;
+
+		/* We don't have a match, which is odd; fall back to the FLV URI as advertised by the YouTube API */
+		content = gdata_youtube_video_look_up_content (GDATA_YOUTUBE_VIDEO (data->entry), "application/x-shockwave-flash");
+		if (content != NULL) {
+			video_uri = g_strdup (content->uri);
+			g_debug ("Couldn't find the t param of entry %s; falling back to its FLV URI (\"%s\")", video_id, video_uri);
+		} else {
+			/* Cop out */
+			g_warning ("Couldn't find the t param of entry %s or its FLV URI.", video_uri);
+			video_uri = NULL;
+		}
+	}
+	g_match_info_free (match_info);
+	g_free (contents);
+
+	/* Update the tree view with the new MRL */
+	gtk_list_store_set (self->list_store[data->tree_view], &(data->iter), 2, video_uri, -1);
+	g_debug ("Updated list store with new video URI (\"%s\") for entry %s", video_uri, video_id);
+
+	g_free (video_uri);
+
+free_data:
+	/* Update the progress bar */
+	increment_progress_bar_fraction (self, data->tree_view);
+
+	g_object_unref (data->plugin);
+	g_object_unref (data->entry);
+	g_slice_free (TParamData, data);
+}
+
+static void
+resolve_t_param (TotemYouTubePlugin *self, GDataEntry *entry, GtkTreeIter *iter, guint tree_view)
+{
+	GDataLink *link;
+	GFile *video_page;
+	TParamData *data;
+
+	/* We have to get the t parameter from the actual HTML video page, since Google changed how their URIs work */
+	link = gdata_entry_lookup_link (entry, "alternate");
+	g_assert (link != NULL);
+
+	data = g_slice_new (TParamData);
+	data->plugin = g_object_ref (self);
+	data->entry = g_object_ref (entry);
+	data->iter = *iter;
+	data->tree_view = tree_view;
+
+	video_page = g_file_new_for_uri (link->href);
+	g_file_load_contents_async (video_page, self->cancellable[tree_view], (GAsyncReadyCallback) resolve_t_param_cb, data);
+	g_object_unref (video_page);
+}
+
+typedef struct {
+	TotemYouTubePlugin *plugin;
+	GtkTreeIter iter;
+	guint tree_view;
+} ThumbnailData;
+
+static void
+thumbnail_loaded_cb (GObject *source_object, GAsyncResult *result, ThumbnailData *data)
+{
+	GdkPixbuf *thumbnail;
+	GError *error = NULL;
+	TotemYouTubePlugin *self = data->plugin;
+
+	/* Finish loading the thumbnail */
+	thumbnail = gdk_pixbuf_new_from_stream_finish (result, &error);
+
+	if (thumbnail == NULL) {
+		GtkWindow *window;
+
+		/* Display an error message */
+		window = totem_get_main_window (data->plugin->totem);
+		totem_interface_error (_("Error Loading Video Thumbnail"), error->message, window);
+		g_object_unref (window);
+		g_error_free (error);
+		goto free_data;
+	}
+
+	g_debug ("Finished creating thumbnail from stream");
+
+	/* Update the tree view */
+	gtk_list_store_set (self->list_store[data->tree_view], &(data->iter), 0, thumbnail, -1);
+	g_debug ("Updated list store with new thumbnail");
+
+	g_object_unref (thumbnail);
+
+free_data:
+	/* Update the progress bar */
+	increment_progress_bar_fraction (self, data->tree_view);
+
+	g_object_unref (data->plugin);
+	g_slice_free (ThumbnailData, data);
+}
+
+static void
+thumbnail_opened_cb (GObject *source_object, GAsyncResult *result, ThumbnailData *data)
+{
+	GFile *thumbnail_file;
+	GFileInputStream *input_stream;
+	GError *error = NULL;
+	TotemYouTubePlugin *self = data->plugin;
+
+	/* Finish opening the thumbnail */
+	thumbnail_file = G_FILE (source_object);
+	input_stream = g_file_read_finish (thumbnail_file, result, &error);
+
+	if (input_stream == NULL) {
+		GtkWindow *window;
+
+		/* Display an error message */
+		window = totem_get_main_window (data->plugin->totem);
+		totem_interface_error (_("Error Loading Video Thumbnail"), error->message, window);
+		g_object_unref (window);
+		g_error_free (error);
+		return;
+	}
+
+	g_debug ("Creating thumbnail from stream");
+	gdk_pixbuf_new_from_stream_at_scale_async (G_INPUT_STREAM (input_stream), THUMBNAIL_WIDTH, -1, TRUE,
+						   self->cancellable[data->tree_view], (GAsyncReadyCallback) thumbnail_loaded_cb, data);
+	g_object_unref (input_stream);
+}
+
+typedef struct {
+	TotemYouTubePlugin *plugin;
+	guint tree_view;
+} QueryData;
+
+static void
+query_finished_cb (GObject *source_object, GAsyncResult *result, QueryData *data)
+{
+	GDataFeed *feed;
+	GError *error = NULL;
+	TotemYouTubePlugin *self = data->plugin;
+
+	g_debug ("Search finished!");
+
+	/* Unref cancellable */
+	g_object_unref (self->cancellable[data->tree_view]);
+	self->cancellable[data->tree_view] = NULL;
+
+	feed = gdata_service_query_finish (GDATA_SERVICE (self->service), result, &error);
+	if (feed == NULL) {
+		GtkWindow *window;
+
+		/* Error! */
+		window = totem_get_main_window (data->plugin->totem);
+		totem_interface_error (_("Error Searching for Videos"), error->message, window);
+		g_object_unref (window);
+		g_error_free (error);
+		goto free_data;
+	}
+
+	g_object_unref (feed);
+
+free_data:
+	g_object_unref (data->plugin);
+	g_slice_free (QueryData, data);
+}
+
+static void
+query_progress_cb (GDataEntry *entry, guint entry_key, guint entry_count, QueryData *data)
+{
+	GList *thumbnails;
+	GDataMediaThumbnail *thumbnail = NULL;
+	gint delta = G_MININT;
+	GtkTreeIter iter;
+	const gchar *title, *id;
+	GtkProgressBar *progress_bar;
+	TotemYouTubePlugin *self = data->plugin;
+
+	/* Check this query hasn't finished */
+	g_assert (self->cancellable[data->tree_view] != NULL);
+
+	/* Add the entry to the tree view */
+	title = gdata_youtube_video_get_title (GDATA_YOUTUBE_VIDEO (entry));
+	id = gdata_youtube_video_get_video_id (GDATA_YOUTUBE_VIDEO (entry));
+
+	gtk_list_store_append (self->list_store[data->tree_view], &iter);
+	gtk_list_store_set (self->list_store[data->tree_view], &iter,
+			    0, NULL, /* the thumbnail will be downloaded asynchronously and added to the tree view later */
+			    1, title,
+			    2, NULL, /* the video URI will be resolved asynchronously and added to the tree view later */
+			    3, entry,
+			    -1);
+	g_debug ("Added entry %s to tree view (title: \"%s\")", id, title);
+
+	/* Update the progress bar; we have three steps for each entry in the results: the entry, its thumbnail, and its t parameter */
+	g_assert (entry_count > 0);
+	progress_bar = self->progress_bar[data->tree_view];
+	self->progress_bar_increment[data->tree_view] = 1.0 / (entry_count * 3.0);
+	g_debug ("Setting progress_bar_increment to 1.0 / (%u * 3.0) = %f", entry_count, self->progress_bar_increment[data->tree_view]);
+	gtk_progress_bar_set_fraction (progress_bar, gtk_progress_bar_get_fraction (progress_bar) + self->progress_bar_increment[data->tree_view]);
+
+	/* Resolve the t parameter for the video, which is required before it can be played */
+	resolve_t_param (self, entry, &iter, data->tree_view);
+
+	/* Download the entry's thumbnail, ready for adding it to the tree view.
+	 * Find the thumbnail size which is closest to the wanted size (THUMBNAIL_WIDTH), so that we:
+	 * a) avoid fuzzy images due to scaling up, and
+	 * b) avoid downloading too much just to scale down by a factor of 10. */
+	thumbnails = gdata_youtube_video_get_thumbnails (GDATA_YOUTUBE_VIDEO (entry));
+	for (; thumbnails != NULL; thumbnails = thumbnails->next) {
+		gint new_delta;
+		GDataMediaThumbnail *current_thumb = (GDataMediaThumbnail*) thumbnails->data;
+
+		g_debug ("%u pixel wide thumbnail available for entry %s", current_thumb->width, id);
+
+		new_delta = current_thumb->width - THUMBNAIL_WIDTH;
+		if (delta == 0) {
+			break;
+		} else if ((delta == G_MININT) ||
+			   (delta < 0 && new_delta > delta) ||
+			   (delta > 0 && new_delta > 0 && new_delta < delta)) {
+			delta = new_delta;
+			thumbnail = current_thumb;
+			g_debug ("Choosing a %u pixel wide thumbnail (delta: %i) for entry %s", current_thumb->width, new_delta, id);
+		}
+	}
+
+	if (thumbnail != NULL) {
+		GFile *thumbnail_file;
+		ThumbnailData *t_data;
+
+		t_data = g_slice_new (ThumbnailData);
+		t_data->plugin = g_object_ref (self);
+		t_data->iter = iter;
+		t_data->tree_view = data->tree_view;
+
+		g_debug ("Starting thumbnail download for entry %s", id);
+		thumbnail_file = g_file_new_for_uri (thumbnail->uri);
+		g_file_read_async (thumbnail_file, G_PRIORITY_DEFAULT, self->cancellable[data->tree_view],
+				   (GAsyncReadyCallback) thumbnail_opened_cb, t_data);
+		g_object_unref (thumbnail_file);
+	}
+}
+
+static void
+execute_query (TotemYouTubePlugin *self, guint tree_view, gboolean clear_tree_view)
+{
+	QueryData *data;
+
+	/* Cancel previous searches on this tree view */
+	if (self->cancellable[tree_view] != NULL)
+		g_cancellable_cancel (self->cancellable[tree_view]);
+
+	/* Clear the tree views */
+	if (clear_tree_view == TRUE)
+		gtk_list_store_clear (self->list_store[tree_view]);
+
+	/* Do the query */
+	self->cancellable[tree_view] = g_cancellable_new ();
+
+	data = g_slice_new (QueryData);
+	data->plugin = g_object_ref (self);
+	data->tree_view = tree_view;
+
+	if (tree_view == SEARCH_TREE_VIEW) {
+		gdata_youtube_service_query_videos_async (self->service, self->query[tree_view], self->cancellable[tree_view],
+							  (GDataQueryProgressCallback) query_progress_cb, data,
+							  (GAsyncReadyCallback) query_finished_cb, data);
+	} else {
+		gdata_youtube_service_query_related_async (self->service, self->playing_video, self->query[tree_view], self->cancellable[tree_view],
+							   (GDataQueryProgressCallback) query_progress_cb, data,
+							   (GAsyncReadyCallback) query_finished_cb, data);
+	}
+}
+
+void
+search_button_clicked_cb (GtkButton *button, TotemYouTubePlugin *self)
+{
+	const gchar *search_terms;
+
+	search_terms = gtk_entry_get_text (self->search_entry);
+	g_debug ("Searching for \"%s\"", search_terms);
+
+	/* Focus the "Search" page */
+	gtk_notebook_set_current_page (self->notebook, SEARCH_TREE_VIEW);
+
+	/* Update the UI */
+	set_progress_bar_text (self, _("Fetching search resultsâ"), SEARCH_TREE_VIEW);
+
+	/* Clear details pertaining to related videos, since we're doing a new search */
+	gtk_list_store_clear (self->list_store[RELATED_TREE_VIEW]);
+	if (self->playing_video != NULL)
+		g_object_unref (self->playing_video);
+	self->playing_video = NULL;
+
+	/* If this is the first query, set up some stuff which we didn't do before to save memory */
+	if (self->query[SEARCH_TREE_VIEW] == NULL) {
+		/* If this is the first query, compile the regex used to resolve the t param. Doing this here rather than when
+		 * activating the plugin means we don't waste cycles if the plugin's never used. It also means we don't waste
+		 * cycles repeatedly creating new regexes for each video whose t param we resolve. */
+		self->regex = g_regex_new ("swfArgs.*\"t\": \"([^\"]+)\"", G_REGEX_OPTIMIZE, 0, NULL);
+		g_assert (self->regex != NULL);
+
+		/* Set up the GData service (needed for the tree views' queries) */
+		self->service = gdata_youtube_service_new (DEVELOPER_KEY, CLIENT_ID);
+
+		/* Set up the queries */
+		self->query[SEARCH_TREE_VIEW] = gdata_query_new_with_limits (NULL, 0, MAX_RESULTS);
+		self->query[RELATED_TREE_VIEW] = gdata_query_new_with_limits (NULL, 0, MAX_RESULTS);
+	}
+
+	/* Do the query */
+	gdata_query_set_q (self->query[SEARCH_TREE_VIEW], search_terms);
+	execute_query (self, SEARCH_TREE_VIEW, TRUE);
+}
+
+void
+search_entry_activate_cb (GtkEntry *entry, TotemYouTubePlugin *self)
+{
+	search_button_clicked_cb (self->search_button, self);
+}
+
+static void
+load_related_videos (TotemYouTubePlugin *self)
+{
+	g_assert (self->playing_video != NULL);
+	g_debug ("Loading related videos for %s", gdata_youtube_video_get_video_id (self->playing_video));
+
+	/* Update the UI */
+	set_progress_bar_text (self, _("Fetching related videosâ"), RELATED_TREE_VIEW);
+
+	/* Clear the existing results and do the query */
+	gtk_list_store_clear (self->list_store[RELATED_TREE_VIEW]);
+	execute_query (self, RELATED_TREE_VIEW, FALSE);
+}
+
+void
+notebook_switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page, guint page_num, TotemYouTubePlugin *self)
+{
+	/* Change the tree view */
+	self->current_tree_view = page_num;
+
+	/* If we're changing to the "Related Videos" tree view and have played a video, load
+	 * the related videos for that video; but only if the related tree view's empty first */
+	if (page_num == RELATED_TREE_VIEW && self->playing_video != NULL &&
+	    gtk_tree_model_iter_n_children (GTK_TREE_MODEL (self->list_store[RELATED_TREE_VIEW]), NULL) == 0) {
+		load_related_videos (self);
+	}
+}
+
+void
+open_in_web_browser_activate_cb (GtkAction *action, TotemYouTubePlugin *self)
+{
+	GtkTreeSelection *selection;
+	GtkTreeModel *model;
+	GList *paths, *path;
+
+	selection = gtk_tree_view_get_selection (self->tree_view[self->current_tree_view]);
+	paths = gtk_tree_selection_get_selected_rows (selection, &model);
+
+	for (path = paths; path != NULL; path = path->next) {
+		GtkTreeIter iter;
+		GDataYouTubeVideo *video;
+		GDataLink *link;
+		GError *error = NULL;
+
+		if (gtk_tree_model_get_iter (model, &iter, (GtkTreePath*) (path->data)) == FALSE)
+			continue;
+
+		/* Get the HTML page for the video; its <link rel="alternate" ... /> */
+		gtk_tree_model_get (model, &iter, 3, &video, -1);
+		link = gdata_entry_lookup_link (GDATA_ENTRY (video), "alternate");
+		g_object_unref (video);
+
+		/* Display the page */
+		if (gtk_show_uri (gtk_widget_get_screen (GTK_WIDGET (self->bvw)), link->href, GDK_CURRENT_TIME, &error) == FALSE) {
+			GtkWindow *window = totem_get_main_window (self->totem);
+			totem_interface_error (_("Error Opening Video in Web Browser"), error->message, window);
+			g_object_unref (window);
+			g_error_free (error);
+		}
+	}
+
+	g_list_foreach (paths, (GFunc) gtk_tree_path_free, NULL);
+	g_list_free (paths);
+}
+
+void
+value_changed_cb (GtkAdjustment *adjustment, TotemYouTubePlugin *self)
+{
+	if (self->button_down == FALSE &&
+	    gtk_tree_model_iter_n_children (GTK_TREE_MODEL (self->list_store[self->current_tree_view]), NULL) >= MAX_RESULTS &&
+	    (gtk_adjustment_get_value (adjustment) + gtk_adjustment_get_page_size (adjustment)) / gtk_adjustment_get_upper (adjustment) > 0.8) {
+		/* Only load more results if we're not already querying */
+		if (self->cancellable[self->current_tree_view] != NULL)
+			return;
+
+		set_progress_bar_text (self, _("Fetching more videosâ"), self->current_tree_view);
+		gdata_query_next_page (self->query[self->current_tree_view]);
+		execute_query (self, self->current_tree_view, FALSE);
+	}
+}
+
+gboolean
+button_press_event_cb (GtkWidget *widget, GdkEventButton *event, TotemYouTubePlugin *self)
+{
+	self->button_down = TRUE;
+	return FALSE;
+}
+
+gboolean
+button_release_event_cb (GtkWidget *widget, GdkEventButton *event, TotemYouTubePlugin *self)
+{
+	self->button_down = FALSE;
+	value_changed_cb (self->vadjust[self->current_tree_view], self);
+	return FALSE;
+}
+
+gboolean
+starting_video_cb (TotemVideoList *video_list, GtkTreeView *tree_view, GtkTreePath *path, TotemYouTubePlugin *self)
+{
+	GtkTreeIter iter;
+	GDataYouTubeVideo *video_entry;
+
+	if (bacon_video_widget_can_play_youtube_videos (self->bvw) == FALSE) {
+		gchar *title;
+		GtkWindow *main_window;
+
+		/* Display an error if the required GStreamer plugins aren't installed */
+		title = g_strdup_printf (_("Totem cannot play this type of media (%s) because you do not have the appropriate plugins to handle it."), _("YouTube"));
+		main_window = totem_get_main_window (self->totem);
+
+		totem_interface_error_with_link (title, _("Please install the necessary plugins and restart Totem to be able to play this media."),
+						 "http://projects.gnome.org/totem/#codecs";, _("More information about media plugins"),
+						 main_window, self->totem);
+
+		g_object_unref (main_window);
+		g_free (title);
+
+		return FALSE;
+	}
+
+	/* Store the current entry */
+	if (gtk_tree_model_get_iter (GTK_TREE_MODEL (self->list_store[self->current_tree_view]), &iter, path) == FALSE)
+		return FALSE;
+	gtk_tree_model_get (GTK_TREE_MODEL (self->list_store[self->current_tree_view]), &iter, 3, &video_entry, -1);
+
+	if (self->playing_video != NULL)
+		g_object_unref (self->playing_video);
+	self->playing_video = g_object_ref (video_entry);
+
+	/* If we're currently viewing the related videos page, load the new related videos */
+	if (self->current_tree_view == RELATED_TREE_VIEW)
+		load_related_videos (self);
+
+	return TRUE;
+}

Modified: trunk/src/plugins/youtube/youtube.totem-plugin.in
==============================================================================
--- trunk/src/plugins/youtube/youtube.totem-plugin.in	(original)
+++ trunk/src/plugins/youtube/youtube.totem-plugin.in	Wed Apr  1 18:04:30 2009
@@ -1,5 +1,4 @@
 [Totem Plugin]
-Loader=python
 Module=youtube
 IAge=1
 _Name=YouTube Browser

Modified: trunk/src/plugins/youtube/youtube.ui
==============================================================================
--- trunk/src/plugins/youtube/youtube.ui	(original)
+++ trunk/src/plugins/youtube/youtube.ui	Wed Apr  1 18:04:30 2009
@@ -2,33 +2,34 @@
 <!--*- mode: xml -*--><!DOCTYPE glade-interface
 	SYSTEM 'http://glade.gnome.org/glade-2.0.dtd'>
 <interface>
-<object class="GtkListStore" id="yt_liststore_search">
+<object class="GtkListStore" id="yt_list_store_search">
 	<columns>
 		<column type="GdkPixbuf"/><!--Thumbnail-->
 		<column type="gchararray"/><!--Title-->
 		<column type="gchararray"/><!--MRL-->
-		<column type="gchararray"/><!--YouTube ID-->
+		<column type="GObject"/><!--Video entry; TODO: should be GDataYouTubeVideo, see bug #576285-->
 	</columns>
 </object>
-<object class="GtkListStore" id="yt_liststore_related">
+<object class="GtkListStore" id="yt_list_store_related">
 	<columns>
 		<column type="GdkPixbuf"/><!--Thumbnail-->
 		<column type="gchararray"/><!--Title-->
 		<column type="gchararray"/><!--MRL-->
-		<column type="gchararray"/><!--YouTube ID-->
+		<column type="GObject"/><!--Video entry; TODO: should be GDataYouTubeVideo, see bug #576285-->
 	</columns>
 </object>
 
 <object class="GtkVBox" id="yt_vbox">
-	<property name="border_width">5</property>
-	<property name="visible">True</property>
+	<property name="border-width">5</property>
 	<property name="homogeneous">False</property>
 	<property name="spacing">6</property>
 	<child>
 		<object class="GtkHBox" id="yt_hbox">
 			<property name="spacing">4</property>
 			<child>
-				<object class="GtkEntry" id="yt_search_entry"/>
+				<object class="GtkEntry" id="yt_search_entry">
+					<signal name="activate" handler="search_entry_activate_cb"/>
+				</object>
 				<packing>
 					<property name="padding">0</property>
 					<property name="expand">True</property>
@@ -39,6 +40,7 @@
 				<object class="GtkButton" id="yt_search_button">
 					<property name="use-stock">True</property>
 					<property name="label">gtk-find</property>
+					<signal name="clicked" handler="search_button_clicked_cb"/>
 				</object>
 				<packing>
 					<property name="padding">0</property>
@@ -55,22 +57,49 @@
 	</child>
 	<child>
 		<object class="GtkNotebook" id="yt_notebook">
+			<signal name="switch-page" handler="notebook_switch_page_cb"/>
 			<child>
-				<object class="GtkScrolledWindow" id="yt_scrolled_window_search">
-					<property name="visible">True</property>
-					<property name="can_focus">True</property>
-					<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
-					<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
-					<property name="shadow_type">GTK_SHADOW_IN</property>
-					<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+				<object class="GtkVBox" id="yt_vbox_related">
 					<child>
-						<object class="TotemVideoList" id="yt_treeview_search">
-							<property name="headers-visible">False</property>
-							<property name="fixed-height-mode">False</property>
-							<property name="tooltip-column">1</property>
-							<property name="mrl-column">2</property>
+						<object class="GtkScrolledWindow" id="yt_scrolled_window_search">
+							<property name="hscrollbar-policy">GTK_POLICY_AUTOMATIC</property>
+							<property name="vscrollbar-policy">GTK_POLICY_AUTOMATIC</property>
+							<property name="shadow-type">GTK_SHADOW_IN</property>
+							<property name="window-placement">GTK_CORNER_TOP_LEFT</property>
+							<child>
+								<object class="TotemVideoList" id="yt_treeview_search">
+									<property name="headers-visible">False</property>
+									<property name="fixed-height-mode">False</property>
+									<property name="tooltip-column">1</property>
+									<property name="mrl-column">2</property>
+									<property name="model">yt_list_store_search</property>
+									<signal name="starting-video" handler="starting_video_cb"/>
+									<child>
+										<object class="GtkTreeViewColumn" id="yt_treeview_search_column">
+											<property name="title" translatable="yes">Videos</property>
+											<child>
+												<object class="TotemCellRendererVideo" id="yt_treeview_search_renderer">
+													<property name="use-placeholder">True</property>
+												</object>
+												<attributes>
+													<attribute name="thumbnail">0</attribute>
+													<attribute name="title">1</attribute>
+												</attributes>
+											</child>
+										</object>
+									</child>
+								</object>
+							</child>
 						</object>
 					</child>
+					<child>
+						<object class="GtkProgressBar" id="yt_progress_bar_search"/>
+						<packing>
+							<property name="padding">0</property>
+							<property name="expand">False</property>
+							<property name="fill">True</property>
+						</packing>
+					</child>
 				</object>
 			</child>
 			<child type="tab">
@@ -82,21 +111,47 @@
 				</packing>
 			</child>
 			<child>
-				<object class="GtkScrolledWindow" id="yt_scrolled_window_related">
-					<property name="visible">True</property>
-					<property name="can_focus">True</property>
-					<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
-					<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
-					<property name="shadow_type">GTK_SHADOW_IN</property>
-					<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+				<object class="GtkVBox" id="yt_vbox_related">
 					<child>
-						<object class="TotemVideoList" id="yt_treeview_related">
-							<property name="headers-visible">False</property>
-							<property name="fixed-height-mode">False</property>
-							<property name="tooltip-column">1</property>
-							<property name="mrl-column">2</property>
+						<object class="GtkScrolledWindow" id="yt_scrolled_window_related">
+							<property name="hscrollbar-policy">GTK_POLICY_AUTOMATIC</property>
+							<property name="vscrollbar-policy">GTK_POLICY_AUTOMATIC</property>
+							<property name="shadow-type">GTK_SHADOW_IN</property>
+							<property name="window-placement">GTK_CORNER_TOP_LEFT</property>
+							<child>
+								<object class="TotemVideoList" id="yt_treeview_related">
+									<property name="headers-visible">False</property>
+									<property name="fixed-height-mode">False</property>
+									<property name="tooltip-column">1</property>
+									<property name="mrl-column">2</property>
+									<property name="model">yt_list_store_related</property>
+									<signal name="starting-video" handler="starting_video_cb"/>
+									<child>
+										<object class="GtkTreeViewColumn" id="yt_treeview_related_column">
+											<property name="title" translatable="yes">Videos</property>
+											<child>
+												<object class="TotemCellRendererVideo" id="yt_treeview_related_renderer">
+													<property name="use-placeholder">True</property>
+												</object>
+												<attributes>
+													<attribute name="thumbnail">0</attribute>
+													<attribute name="title">1</attribute>
+												</attributes>
+											</child>
+										</object>
+									</child>
+								</object>
+							</child>
 						</object>
 					</child>
+					<child>
+						<object class="GtkProgressBar" id="yt_progress_bar_related"/>
+						<packing>
+							<property name="padding">0</property>
+							<property name="expand">False</property>
+							<property name="fill">True</property>
+						</packing>
+					</child>
 				</object>
 			</child>
 			<child type="tab">
@@ -114,14 +169,6 @@
 			<property name="fill">True</property>
 		</packing>
 	</child>
-	<child>
-		<object class="GtkProgressBar" id="yt_progress_bar"/>
-		<packing>
-			<property name="padding">0</property>
-			<property name="expand">False</property>
-			<property name="fill">True</property>
-		</packing>
-	</child>
 </object>
 
 </interface>

Modified: trunk/src/totem-cell-renderer-video.c
==============================================================================
--- trunk/src/totem-cell-renderer-video.c	(original)
+++ trunk/src/totem-cell-renderer-video.c	Wed Apr  1 18:04:30 2009
@@ -48,7 +48,6 @@
 #include "totem-private.h"
 
 struct _TotemCellRendererVideoPrivate {
-	gboolean dispose_has_run;
 	gchar *title;
 	GdkPixbuf *thumbnail;
 	PangoAlignment alignment;
@@ -67,6 +66,7 @@
 static void totem_cell_renderer_video_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
 static void totem_cell_renderer_video_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
 static void totem_cell_renderer_video_dispose (GObject *object);
+static void totem_cell_renderer_video_finalize (GObject *object);
 static void totem_cell_renderer_video_get_size (GtkCellRenderer *cell, GtkWidget *widget, GdkRectangle *cell_area, gint *x_offset, gint *y_offset, gint *width, gint *height);
 static void totem_cell_renderer_video_render (GtkCellRenderer *cell, GdkDrawable *window, GtkWidget *widget, GdkRectangle *background_area, GdkRectangle *cell_area, GdkRectangle *expose_area, GtkCellRendererState flags);
 
@@ -102,6 +102,7 @@
 	object_class->set_property = totem_cell_renderer_video_set_property;
 	object_class->get_property = totem_cell_renderer_video_get_property;
 	object_class->dispose = totem_cell_renderer_video_dispose;
+	object_class->finalize = totem_cell_renderer_video_finalize;
 	renderer_class->get_size = totem_cell_renderer_video_get_size;
 	renderer_class->render = totem_cell_renderer_video_render;
 
@@ -149,7 +150,6 @@
 totem_cell_renderer_video_init (TotemCellRendererVideo *self)
 {
 	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TOTEM_TYPE_CELL_RENDERER_VIDEO, TotemCellRendererVideoPrivate);
-	self->priv->dispose_has_run = FALSE;
 	self->priv->title = NULL;
 	self->priv->thumbnail = NULL;
 	self->priv->alignment = PANGO_ALIGN_CENTER;
@@ -164,20 +164,26 @@
 {
 	TotemCellRendererVideo *self = TOTEM_CELL_RENDERER_VIDEO (object);
 
-	/* Make sure we only run once */
-	if (self->priv->dispose_has_run)
-		return;
-	self->priv->dispose_has_run = TRUE;
-
-	g_free (self->priv->title);
 	if (self->priv->thumbnail != NULL)
 		g_object_unref (self->priv->thumbnail);
+	self->priv->thumbnail = NULL;
 
 	/* Chain up to the parent class */
 	G_OBJECT_CLASS (totem_cell_renderer_video_parent_class)->dispose (object);
 }
 
 static void
+totem_cell_renderer_video_finalize (GObject *object)
+{
+	TotemCellRendererVideo *self = TOTEM_CELL_RENDERER_VIDEO (object);
+
+	g_free (self->priv->title);
+
+	/* Chain up to the parent class */
+	G_OBJECT_CLASS (totem_cell_renderer_video_parent_class)->finalize (object);
+}
+
+static void
 totem_cell_renderer_video_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
 {
 	TotemCellRendererVideoPrivate *priv = TOTEM_CELL_RENDERER_VIDEO_GET_PRIVATE (object);
@@ -187,7 +193,7 @@
 		case PROP_THUMBNAIL:
 			if (priv->thumbnail != NULL)
 				g_object_unref (priv->thumbnail);
-			priv->thumbnail = (GdkPixbuf*) g_value_dup_object (value);
+			priv->thumbnail = GDK_PIXBUF (g_value_dup_object (value));
 			break;
 		case PROP_TITLE:
 			g_free (priv->title);
@@ -266,17 +272,16 @@
 	if (priv->thumbnail != NULL)
 		pango_font_description_set_weight (font_desc, PANGO_WEIGHT_BOLD);
 	context = gtk_widget_get_pango_context (widget);
-	metrics = pango_context_get_metrics (context,
-				font_desc,
-				pango_context_get_language (context));
+	metrics = pango_context_get_metrics (context, font_desc, pango_context_get_language (context));
 
 	if (cell_area)
 		title_width = cell_area->width;
-	else
+	else if (cell->width != -1)
 		title_width = cell->width;
+	else
+		title_width = pixbuf_width;
 
-	title_height = PANGO_PIXELS (pango_font_metrics_get_ascent (metrics) +
-				pango_font_metrics_get_descent (metrics));
+	title_height = PANGO_PIXELS (pango_font_metrics_get_ascent (metrics) + pango_font_metrics_get_descent (metrics));
 
 	pango_font_metrics_unref (metrics);
 	pango_font_description_free (font_desc);
@@ -302,10 +307,10 @@
 		draw_area->height = calc_height;
 
 		/*if (cell_area) {
-			g_message ("Cell area: X: %i, Y: %i, W: %i, H: %i", cell_area->x, cell_area->y, cell_area->width, cell_area->height);
-			g_message ("X-align: %f, Y-align: %f", cell->xalign, cell->yalign);
+			g_debug ("Cell area: X: %i, Y: %i, W: %i, H: %i", cell_area->x, cell_area->y, cell_area->width, cell_area->height);
+			g_debug ("X-align: %f, Y-align: %f", cell->xalign, cell->yalign);
 		}
-		g_message ("Draw area: X: %i, Y: %i, W: %i, H: %i", draw_area->x, draw_area->y, draw_area->width, draw_area->height);*/
+		g_debug ("Draw area: X: %i, Y: %i, W: %i, H: %i", draw_area->x, draw_area->y, draw_area->width, draw_area->height);*/
 
 		if (title_area) {
 			if (cell_area) {
@@ -322,7 +327,7 @@
 			else
 				title_area->y = draw_area->y;
 
-			/*g_message ("Title area: X: %i, Y: %i, W: %i, H: %i", title_area->x, title_area->y, title_area->width, title_area->height);*/
+			/*g_debug ("Title area: X: %i, Y: %i, W: %i, H: %i", title_area->x, title_area->y, title_area->width, title_area->height);*/
 		}
 
 		if (pixbuf_height > 0 && thumbnail_area) {
@@ -334,10 +339,10 @@
 			thumbnail_area->width = cell->xpad * 2 + pixbuf_width;
 			thumbnail_area->height = pixbuf_height;
 
-			/*g_message ("Thumbnail area: X: %i, Y: %i, W: %i, H: %i", thumbnail_area->x, thumbnail_area->y, thumbnail_area->width, thumbnail_area->height);*/
+			/*g_debug ("Thumbnail area: X: %i, Y: %i, W: %i, H: %i", thumbnail_area->x, thumbnail_area->y, thumbnail_area->width, thumbnail_area->height);*/
 		}
 
-		/*g_message ("---");*/
+		/*g_debug ("---");*/
 	}
 }
 
@@ -391,17 +396,14 @@
 	draw_area.height -= cell->ypad * 2;
 
 	if (!gdk_rectangle_intersect (cell_area, &draw_area, &draw_rect) ||
-				!gdk_rectangle_intersect (expose_area, &draw_rect, &draw_rect))
+	    !gdk_rectangle_intersect (expose_area, &draw_rect, &draw_rect))
 		return;
 
 	/* Sort out the thumbnail */
 	if (priv->thumbnail != NULL)
 		pixbuf = priv->thumbnail;
 	else if (priv->use_placeholder == TRUE)
-		pixbuf = gtk_widget_render_icon (widget,
-					GTK_STOCK_MISSING_IMAGE,
-					GTK_ICON_SIZE_DIALOG,
-					NULL);
+		pixbuf = gtk_widget_render_icon (widget, GTK_STOCK_MISSING_IMAGE, GTK_ICON_SIZE_DIALOG, NULL);
 	else
 		pixbuf = NULL;
 

Modified: trunk/src/totem-video-list.c
==============================================================================
--- trunk/src/totem-video-list.c	(original)
+++ trunk/src/totem-video-list.c	Wed Apr  1 18:04:30 2009
@@ -232,14 +232,17 @@
 	{
 		case PROP_TOOLTIP_COLUMN:
 			priv->tooltip_column = g_value_get_int (value);
+			g_object_notify (object, "tooltip-column");
 			break;
 		case PROP_MRL_COLUMN:
 			priv->mrl_column = g_value_get_int (value);
+			g_object_notify (object, "mrl-column");
 			break;
 		case PROP_TOTEM:
 			if (priv->totem != NULL)
 				g_object_unref (priv->totem);
 			priv->totem = (Totem*) g_value_dup_object (value);
+			g_object_notify (object, "totem");
 			break;
 		default:
 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -342,7 +345,8 @@
 				self->priv->tooltip_column, &display_name,
 				-1);
 
-	totem_add_to_playlist_and_play (self->priv->totem, mrl, display_name, FALSE);
+	if (mrl != NULL)
+		totem_add_to_playlist_and_play (self->priv->totem, mrl, display_name, FALSE);
 
 	g_free (mrl);
 	g_free (display_name);



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