From 8a88a330d9f76b29ac74fe6bc5523c92818d3ce1 Mon Sep 17 00:00:00 2001
From: Juan A. Suarez Romero <jasuarez@igalia.com>
Date: Tue, 17 May 2011 12:16:32 +0000
Subject: [PATCH] Add Grilo plugin

---
 configure.in                      |   11 +-
 src/plugins/grilo/Makefile.am     |   22 +
 src/plugins/grilo/grilo.plugin.in |    8 +
 src/plugins/grilo/grilo.ui        |  261 ++++++++
 src/plugins/grilo/totem-grilo.c   | 1178 +++++++++++++++++++++++++++++++++++++
 5 files changed, 1479 insertions(+), 1 deletions(-)
 create mode 100644 src/plugins/grilo/Makefile.am
 create mode 100644 src/plugins/grilo/grilo.plugin.in
 create mode 100644 src/plugins/grilo/grilo.ui
 create mode 100644 src/plugins/grilo/totem-grilo.c

diff --git a/configure.in b/configure.in
index 797c8fc..461376d 100644
--- a/configure.in
+++ b/configure.in
@@ -68,7 +68,7 @@ AC_SUBST(TOTEM_API_VERSION)
 AC_DEFINE_UNQUOTED(TOTEM_API_VERSION, ["$TOTEM_API_VERSION"], [Define to the Totem plugin API version])
 
 # The full list of plugins
-allowed_plugins="bemused brasero-disc-recorder chapters coherence_upnp dbus-service im-status gromit iplayer jamendo lirc media-player-keys ontop opensubtitles properties publish pythonconsole save-file sample-python sample-vala screensaver screenshot sidebar-test skipto thumbnail tracker youtube zeitgeist-dp"
+allowed_plugins="bemused brasero-disc-recorder chapters coherence_upnp dbus-service im-status gromit iplayer jamendo lirc media-player-keys ontop opensubtitles properties publish pythonconsole save-file sample-python sample-vala screensaver screenshot sidebar-test skipto thumbnail tracker youtube zeitgeist-dp grilo"
 
 PLUGINDIR='${libdir}/totem/plugins'
 AC_SUBST(PLUGINDIR)
@@ -537,6 +537,14 @@ for plugin in ${used_plugins}; do
 				fi
 			fi
 		;;
+		grilo)
+			PKG_CHECK_MODULES(LIBGRILO, grilo-0.1 >= 0.1.14,
+				[HAVE_LIBGRILO=yes], [HAVE_LIBGRILO=no])
+			if test "${HAVE_LIBGRILO}" != "yes" ; then
+				plugin_error_or_ignore "you need libgrilo >= 0.1.14 installed for the Grilo plugin"
+				add_plugin="0"
+			fi
+		;;
 	esac
 
 	# Add the specified plugin
@@ -803,6 +811,7 @@ src/plugins/jamendo/org.gnome.totem.plugins.jamendo.gschema.xml.in
 src/plugins/brasero-disc-recorder/Makefile
 src/plugins/chapters/Makefile
 src/plugins/zeitgeist-dp/Makefile
+src/plugins/grilo/Makefile
 src/backend/Makefile
 browser-plugin/Makefile
 data/Makefile
diff --git a/src/plugins/grilo/Makefile.am b/src/plugins/grilo/Makefile.am
new file mode 100644
index 0000000..ccbc07b
--- /dev/null
+++ b/src/plugins/grilo/Makefile.am
@@ -0,0 +1,22 @@
+include $(top_srcdir)/src/plugins/Makefile.plugins
+
+plugindir = $(PLUGINDIR)/grilo
+plugin_LTLIBRARIES = libgrilo.la
+
+plugin_in_files = grilo.plugin.in
+
+uidir = $(plugindir)
+ui_DATA = grilo.ui
+EXTRA_DIST += $(ui_DATA)
+
+libgrilo_la_SOURCES = totem-grilo.c
+libgrilo_la_LDFLAGS = $(plugin_ldflags)
+libgrilo_la_LIBADD =		\ 
+        $(plugin_libadd)	\
+        $(LIBGRILO_LIBS)
+libgrilo_la_CFLAGS =		\
+        $(plugin_cflags)        \
+        $(LIBGRILO_CFLAGS)
+
+-include $(top_srcdir)/git.mk
+
diff --git a/src/plugins/grilo/grilo.plugin.in b/src/plugins/grilo/grilo.plugin.in
new file mode 100644
index 0000000..d844418
--- /dev/null
+++ b/src/plugins/grilo/grilo.plugin.in
@@ -0,0 +1,8 @@
+[Plugin]
+Module=grilo
+IAge=1
+_Name=Grilo Browser
+_Description=A plugin to let you browse media content from various sources.
+Authors=Iago Toral Quiroga <itoral@igalia.com>, Juan A. Suarez Romero <jasuarez@igalia.com>
+Copyright=Copyright © 2011 Igalia S.L.
+Website=http://live.gnome.org/Grilo/
diff --git a/src/plugins/grilo/grilo.ui b/src/plugins/grilo/grilo.ui
new file mode 100644
index 0000000..ec29339
--- /dev/null
+++ b/src/plugins/grilo/grilo.ui
@@ -0,0 +1,261 @@
+<?xml version="1.0"?>
+<interface>
+  <!-- interface-requires gtk+ 2.12 -->
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkAdjustment" id="gw_adj_max_items">
+    <property name="value">300</property>
+    <property name="lower">1</property>
+    <property name="upper">10000</property>
+    <property name="step_increment">5</property>
+  </object>
+  <object class="GtkTable" id="gw_config">
+    <property name="visible">True</property>
+    <property name="n_columns">2</property>
+    <property name="column_spacing">6</property>
+    <property name="row_spacing">6</property>
+    <child>
+      <object class="GtkLabel" id="gw_lbl_max_items">
+        <property name="visible">True</property>
+        <property name="label" translatable="yes">Max items to retrieve:</property>
+      </object>
+    </child>
+    <child>
+      <object class="GtkSpinButton" id="gw_spin_max_items">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="invisible_char">&#x25CF;</property>
+        <property name="adjustment">gw_adj_max_items</property>
+        <signal name="value_changed" handler="gw_spin_max_items_value_changed_cb"/>
+      </object>
+      <packing>
+        <property name="left_attach">1</property>
+        <property name="right_attach">2</property>
+      </packing>
+    </child>
+  </object>
+  <object class="GtkListStore" id="gw_search_store_sources">
+    <columns>
+      <!-- column-name source -->
+      <column type="GObject"/>
+      <!-- column-name name -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
+  <object class="GtkListStore" id="gw_search_store_results">
+    <columns>
+      <!-- column-name source -->
+      <column type="GObject"/>
+      <!-- column-name content -->
+      <column type="GObject"/>
+      <!-- column-name thumbnail -->
+      <column type="GdkPixbuf"/>
+      <!-- column-name is_prethumbnail -->
+      <column type="gboolean"/>
+      <!-- column-name description -->
+      <column type="gchararray"/>
+      <!-- column-name duration -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
+  <object class="GtkTreeStore" id="gw_browse_store_results">
+    <columns>
+      <!-- column-name source -->
+      <column type="GObject"/>
+      <!-- column-name content -->
+      <column type="GObject"/>
+      <!-- column-name thumbnail -->
+      <column type="GdkPixbuf"/>
+      <!-- column-name is_prethumbnail -->
+      <column type="gboolean"/>
+      <!-- column-name description -->
+      <column type="gchararray"/>
+      <!-- column-name duration -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
+  <object class="GtkScrolledWindow" id="gw_browse_window">
+    <property name="visible">True</property>
+    <property name="can_focus">True</property>
+    <property name="hscrollbar_policy">automatic</property>
+    <property name="vscrollbar_policy">automatic</property>
+    <property name="shadow_type">in</property>
+    <child>
+      <object class="GtkTreeView" id="gw_browse">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="model">gw_browse_store_results</property>
+        <property name="headers_visible">False</property>
+        <property name="enable_search">False</property>
+        <property name="search_column">2</property>
+        <child>
+          <object class="GtkTreeViewColumn" id="gw_browse_col_pix">
+            <property name="title">column</property>
+            <child>
+              <object class="GtkCellRendererPixbuf" id="gw_browse_col_render_pix"/>
+              <attributes>
+                <attribute name="pixbuf">2</attribute>
+              </attributes>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkTreeViewColumn" id="gw_browse_col_desc">
+            <property name="min_width">6</property>
+            <property name="title">column</property>
+            <child>
+              <object class="GtkCellRendererText" id="gw_browse_col_render_desc"/>
+              <attributes>
+                <attribute name="markup">4</attribute>
+              </attributes>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkTreeViewColumn" id="gw_browse_col_dur">
+            <property name="title">column</property>
+            <child>
+              <object class="GtkCellRendererText" id="gw_browse_col_render_dur"/>
+              <attributes>
+                <attribute name="text">5</attribute>
+              </attributes>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </object>
+  <object class="GtkVBox" id="gw_search">
+    <property name="visible">True</property>
+    <property name="orientation">vertical</property>
+    <property name="spacing">6</property>
+    <child>
+      <object class="GtkComboBox" id="gw_search_select_source">
+        <property name="visible">True</property>
+        <property name="model">gw_search_store_sources</property>
+        <child>
+          <object class="GtkCellRendererText" id="gw_search_source_col_name"/>
+          <attributes>
+            <attribute name="text">1</attribute>
+          </attributes>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkEntry" id="gw_search_text">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="invisible_char">&#x25CF;</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="position">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkHBox" id="gw_search_buttons">
+        <property name="visible">True</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkButton" id="gw_search_search_button">
+            <property name="label" translatable="yes">Search</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+          </object>
+          <packing>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="gw_search_cancel_button">
+            <property name="label" translatable="yes">Cancel</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+          </object>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="position">2</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkScrolledWindow" id="gw_search_results_window">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="hscrollbar_policy">automatic</property>
+        <property name="vscrollbar_policy">automatic</property>
+        <property name="shadow_type">in</property>
+        <child>
+          <object class="GtkTreeView" id="gw_search_results">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="model">gw_search_store_results</property>
+            <property name="headers_visible">False</property>
+            <property name="headers_clickable">False</property>
+            <property name="search_column">2</property>
+            <child>
+              <object class="GtkTreeViewColumn" id="thumbnail">
+                <property name="title">thumbnail</property>
+                <child>
+                  <object class="GtkCellRendererPixbuf" id="gw_search_results_col_render_pix"/>
+                  <attributes>
+                    <attribute name="pixbuf">2</attribute>
+                  </attributes>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkTreeViewColumn" id="gw_search_results_col_desc">
+                <property name="title">title</property>
+                <child>
+                  <object class="GtkCellRendererText" id="gw_search_results_col_render_desc"/>
+                  <attributes>
+                    <attribute name="markup">4</attribute>
+                  </attributes>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkTreeViewColumn" id="duration">
+                <property name="sizing">autosize</property>
+                <property name="title">duration</property>
+                <property name="alignment">1</property>
+                <child>
+                  <object class="GtkCellRendererText" id="gw_search_results_col_render_dur">
+                    <property name="xalign">1</property>
+                    <property name="size_points">8</property>
+                  </object>
+                  <attributes>
+                    <attribute name="markup">5</attribute>
+                  </attributes>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="position">3</property>
+      </packing>
+    </child>
+  </object>
+  <object class="GtkAction" id="add-to-playlist">
+    <property name="label">Add to Playlist</property>
+    <property name="tooltip">add media to playlist</property>
+    <property name="stock_id">gtk-add</property>
+  </object>
+  <object class="GtkAction" id="copy-location">
+    <property name="label">Copy Location</property>
+    <property name="tooltip">copy media location</property>
+    <property name="stock_id">gtk-copy</property>
+  </object>
+</interface>
diff --git a/src/plugins/grilo/totem-grilo.c b/src/plugins/grilo/totem-grilo.c
new file mode 100644
index 0000000..560e47f
--- /dev/null
+++ b/src/plugins/grilo/totem-grilo.c
@@ -0,0 +1,1178 @@
+/*
+ * Copyright (C) 2010, 2011 Igalia S.L. <info@igalia.com>
+ *
+ * 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 <time.h>
+
+#include <glib.h>
+#include <glib-object.h>
+#include <glib/gi18n-lib.h>
+#include <grilo.h>
+
+#include <libpeas/peas-extension-base.h>
+#include <libpeas/peas-object-module.h>
+#include <libpeas/peas-activatable.h>
+#include <libpeas-gtk/peas-gtk-configurable.h>
+
+#include <totem-plugin.h>
+#include <totem-interface.h>
+#include <totem-dirs.h>
+#include <totem.h>
+
+#define TOTEM_TYPE_GRILO_PLUGIN	\
+  (totem_grilo_plugin_get_type ())
+#define TOTEM_GRILO_PLUGIN(o) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((o), TOTEM_TYPE_GRILO_PLUGIN, TotemGriloPlugin))
+#define TOTEM_GRILO_PLUGIN_CLASS(k) \
+  (G_TYPE_CHECK_CLASS_CAST((k), TOTEM_TYPE_GRILO_PLUGIN, TotemGriloPluginClass))
+#define TOTEM_IS_GRILO_PLUGIN(o) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((o), TOTEM_TYPE_GRILO_PLUGIN))
+#define TOTEM_IS_GRILO_PLUGIN_CLASS(k) \
+  (G_TYPE_CHECK_CLASS_TYPE ((k), TOTEM_TYPE_GRILO_PLUGIN))
+#define TOTEM_GRILO_PLUGIN_GET_CLASS(o) \
+  (G_TYPE_INSTANCE_GET_CLASS ((o), TOTEM_TYPE_GRILO_PLUGIN, TotemGriloPluginClass))
+
+#define GRILO_POPUP_MENU                                                \
+  "<ui>"								\
+  "<popup name=\"grilo-popup\">"					\
+  "<menuitem name=\"add-to-playlist\" action=\"add-to-playlist\"/>"	\
+  "<menuitem name=\"copy-location\" action=\"copy-location\"/>"		\
+  "</popup>"								\
+  "</ui>"
+
+#define BROWSE_FLAGS        (GRL_RESOLVE_FAST_ONLY | GRL_RESOLVE_IDLE_RELAY)
+#define MAX_ITEMS_DEFAULT   300
+#define THUMB_SIZE          32
+
+#define TOTEM_GRILO_CONFIG_FILE "totem-grilo.ini"
+
+#define YOUTUBE_KEY  "AI39si7Ix-bf9xRLx5PK1sjsG2FQjSHblunuaI8YvHdYbkV2BcgOemHE6Nzb9t6LaMsekofKGSwgAETzbo7OvL8L3vH4vrFaqw"
+#define VIMEO_KEY    "db924d3b7f1d5f3d6fdfeb4488ad01e2"
+#define VIMEO_SECRET "bef979d87f28ec47"
+
+enum { ICON_BOX = 0,
+       ICON_AUDIO,
+       ICON_VIDEO,
+       ICON_DEFAULT
+};
+
+typedef struct {
+  Totem *totem;
+
+  /* Current media selected in results*/
+  GrlMedia *selected_media;
+
+  /* Thumb cache to speed up loading */
+  gboolean updating_thumbnails;
+  GHashTable *cache_thumbnails;
+
+  /* Update in content in background */
+  GrlMediaSource *search_in_progress_source;
+  guint search_in_progress_id;
+
+  /* Browser widgets */
+  GtkWidget *browser;
+  GtkTreeModel *browser_model;
+
+  /* Search widgets */
+  GtkWidget *search_entry;
+  GtkTreeModel *search_results_model;
+  GtkTreeModel *search_sources_model;
+  GtkWidget *search_sources_list;
+  GtkWidget *search_results_list;
+  GtkWidget *search_search_button;
+  GtkWidget *search_cancel_button;
+
+  /* Popup */
+  GtkUIManager *ui_manager;
+  GtkActionGroup *action_group;
+
+  /* Configuration */
+  guint max_items;
+} TotemGriloPluginPrivate;
+
+TOTEM_PLUGIN_REGISTER_CONFIGURABLE (TOTEM_TYPE_GRILO_PLUGIN, TotemGriloPlugin, totem_grilo_plugin)
+
+typedef struct {
+  TotemGriloPlugin *totem_grilo;
+  GtkTreeIter *parent;
+} BrowseUserData;
+
+enum {
+  SEARCH_MODEL_SOURCES_SOURCE = 0,
+  SEARCH_MODEL_SOURCES_NAME,
+};
+
+enum {
+  MODEL_RESULTS_SOURCE = 0,
+  MODEL_RESULTS_CONTENT,
+  MODEL_RESULTS_THUMBNAIL,
+  MODEL_RESULTS_IS_PRETHUMBNAIL,
+  MODEL_RESULTS_DESCRIPTION,
+  MODEL_RESULTS_DURATION,
+};
+
+static guint max_items = MAX_ITEMS_DEFAULT;
+
+void gw_spin_max_items_value_changed_cb (GtkSpinButton *spin,
+                                         gpointer user_data);
+
+static void play (TotemGriloPlugin *self,
+                  GrlMediaSource *source,
+                  GrlMedia *media,
+                  gboolean resolve_url);
+
+static gchar *
+seconds_to_strtime (gint total_seconds)
+{
+  struct tm *time_tm;
+  gchar *time_str;
+
+  if (total_seconds > 3600) {
+    time_str = g_new (gchar, 9);
+    time_tm = gmtime ((const time_t *) (&total_seconds));
+    strftime (time_str, 9, "%H:%M:%S", time_tm);
+    return time_str;
+  }
+
+  if (total_seconds > 0) {
+    time_str = g_new (gchar, 6);
+    time_tm = gmtime ((const time_t *) (&total_seconds));
+    strftime (time_str, 6, "%M:%S", time_tm);
+    return time_str;
+  }
+
+  return NULL;
+}
+
+static gchar *
+get_description (GrlMedia *media)
+{
+  const gchar *author;
+
+  author = grl_data_get_string (GRL_DATA (media), GRL_METADATA_KEY_AUTHOR);
+  if (!author) {
+    author = grl_data_get_string (GRL_DATA (media), GRL_METADATA_KEY_ARTIST);
+  }
+
+  if (author) {
+    return g_markup_printf_escaped ("<b>%s</b>\n%s",
+                                    grl_media_get_title (media),
+                                    author);
+  } else {
+    return g_markup_printf_escaped ("<b>%s</b>",
+                                    grl_media_get_title (media));
+  }
+}
+
+static GList *
+browse_keys (void)
+{
+  static GList *browse_keys = NULL;
+
+  if (!browse_keys) {
+    browse_keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ARTIST,
+                                             GRL_METADATA_KEY_AUTHOR,
+                                             GRL_METADATA_KEY_DURATION,
+                                             GRL_METADATA_KEY_THUMBNAIL,
+                                             GRL_METADATA_KEY_URL,
+                                             GRL_METADATA_KEY_TITLE,
+                                             NULL);
+  }
+
+  return browse_keys;
+}
+
+static void
+clear_browser_branch (TotemGriloPlugin *self, GtkTreeIter *branch)
+{
+  GtkTreeIter iter;
+  while (gtk_tree_model_iter_children (self->priv->browser_model, &iter, branch)) {
+    gtk_tree_store_remove (GTK_TREE_STORE (self->priv->browser_model), &iter);
+  }
+}
+
+static GdkPixbuf *
+load_icon (gint icon_type)
+{
+  GdkScreen *screen;
+  GtkIconTheme *theme;
+
+  const gchar *icon_name[] = { GTK_STOCK_DIRECTORY,
+                               "gnome-mime-audio",
+                               "gnome-mime-video",
+                               GTK_STOCK_FILE };
+
+  static GdkPixbuf *pixbuf[G_N_ELEMENTS(icon_name)] = { NULL };
+
+  if (!pixbuf[icon_type]) {
+    screen = gdk_screen_get_default ();
+    theme = gtk_icon_theme_get_for_screen (screen);
+    pixbuf[icon_type] = gtk_icon_theme_load_icon (theme,
+                                                  icon_name[icon_type],
+                                                  THUMB_SIZE, 0, NULL);
+  }
+
+  if (pixbuf[icon_type]) {
+    return g_object_ref (pixbuf[icon_type]);
+  } else {
+    return NULL;
+  }
+}
+
+static GdkPixbuf *
+get_thumbnail (TotemGriloPlugin *self, GrlMedia *media, gboolean only_icon)
+{
+  const gchar *url_thumb;
+  GFile *file_uri;
+  GFileInputStream *fistream;
+  GdkPixbuf *thumbnail = NULL;
+
+  if (!only_icon) {
+    url_thumb = grl_media_get_thumbnail (media);
+    if (url_thumb) {
+      /* Check cache */
+      thumbnail = g_hash_table_lookup (self->priv->cache_thumbnails,
+                                       url_thumb);
+      if (!thumbnail) {
+        file_uri = g_file_new_for_uri (url_thumb);
+        fistream = g_file_read (file_uri, NULL, NULL);
+        if (fistream) {
+          thumbnail = gdk_pixbuf_new_from_stream_at_scale (G_INPUT_STREAM (fistream),
+                                                           THUMB_SIZE, THUMB_SIZE,
+                                                           TRUE, NULL, NULL);
+          g_object_unref (fistream);
+          /* Cache it */
+          if (thumbnail) {
+            g_hash_table_insert (self->priv->cache_thumbnails,
+                                 g_strdup (url_thumb),
+                                 thumbnail);
+          }
+        }
+        g_object_unref (file_uri);
+      }
+      g_object_ref (thumbnail);
+    }
+  }
+
+  if (!thumbnail) {
+    if (GRL_IS_MEDIA_BOX (media)) {
+      thumbnail = load_icon (ICON_BOX);
+    } else if (GRL_IS_MEDIA_AUDIO (media)) {
+      thumbnail = load_icon (ICON_AUDIO);
+    } else if (GRL_IS_MEDIA_VIDEO (media)) {
+      thumbnail = load_icon (ICON_VIDEO);
+    } else {
+      thumbnail = load_icon (ICON_DEFAULT);
+    }
+  }
+
+  return thumbnail;
+}
+
+static gboolean
+update_thumbnails_idle (TotemGriloPlugin *self)
+{
+  GtkTreePath *start_path;
+  GtkTreePath *end_path;
+  GrlMedia *media;
+  GdkPixbuf *thumbnail;
+  GtkTreeIter iter;
+  gboolean is_prethumbnail = FALSE;
+
+  if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (self->priv->search_results_list),
+                                       &start_path, &end_path)) {
+    for (;
+         gtk_tree_path_compare (start_path, end_path) <= 0;
+         gtk_tree_path_next (start_path)) {
+      if (!gtk_tree_model_get_iter (self->priv->search_results_model, &iter, start_path)) {
+        break;
+      }
+
+      gtk_tree_model_get (self->priv->search_results_model,
+                          &iter,
+                          MODEL_RESULTS_CONTENT, &media,
+                          MODEL_RESULTS_IS_PRETHUMBNAIL, &is_prethumbnail,
+                          -1);
+      if (is_prethumbnail) {
+        thumbnail = get_thumbnail (self, media, FALSE);
+        gtk_list_store_set (GTK_LIST_STORE (self->priv->search_results_model),
+                            &iter,
+                            MODEL_RESULTS_THUMBNAIL, thumbnail,
+                            MODEL_RESULTS_IS_PRETHUMBNAIL, FALSE,
+                            -1);
+        if (thumbnail) {
+          g_object_unref (thumbnail);
+        }
+      }
+
+      g_object_unref (media);
+
+      if (is_prethumbnail) {
+        break;
+      }
+    }
+    gtk_tree_path_free (start_path);
+    gtk_tree_path_free (end_path);
+  }
+  if (!is_prethumbnail) {
+    self->priv->updating_thumbnails = 0;
+  }
+  return is_prethumbnail;
+}
+
+static void
+update_search_thumbnails (TotemGriloPlugin *self)
+{
+  if (self->priv->updating_thumbnails) {
+    g_source_remove (self->priv->updating_thumbnails);
+  }
+
+  self->priv->updating_thumbnails = g_idle_add ((GSourceFunc) update_thumbnails_idle,
+                                                self);
+}
+
+static void
+show_sources (TotemGriloPlugin *self)
+{
+  GList *sources, *source;
+  GtkTreeIter iter;
+  GrlPluginRegistry *registry;
+  const gchar *name;
+  GdkPixbuf *icon;
+
+  registry = grl_plugin_registry_get_default ();
+  sources = grl_plugin_registry_get_sources_by_operations (registry,
+                                                           GRL_OP_BROWSE,
+                                                           FALSE);
+
+  for (source = sources; source ; source = g_list_next (source)) {
+    icon = load_icon (ICON_BOX);
+    name = grl_metadata_source_get_name (GRL_METADATA_SOURCE (source->data));
+    gtk_tree_store_append (GTK_TREE_STORE (self->priv->browser_model), &iter, NULL);
+    gtk_tree_store_set (GTK_TREE_STORE (self->priv->browser_model),
+			&iter,
+			MODEL_RESULTS_SOURCE, source->data,
+			MODEL_RESULTS_CONTENT, NULL,
+			MODEL_RESULTS_DESCRIPTION, name,
+			MODEL_RESULTS_THUMBNAIL, icon,
+                        MODEL_RESULTS_IS_PRETHUMBNAIL, FALSE,
+			-1);
+    if (icon) {
+      g_object_unref (icon);
+    }
+  }
+  g_list_free (sources);
+}
+
+static void
+browse_cb (GrlMediaSource *source,
+	   guint browse_id,
+	   GrlMedia *media,
+	   guint remaining,
+	   gpointer user_data,
+	   const GError *error)
+{
+  gchar *description;
+  gchar *pretty_duration;
+  GtkTreeIter iter;
+  GdkPixbuf *thumbnail;
+  BrowseUserData *bud;
+  TotemGriloPlugin *self;
+  GtkTreeIter *parent;
+
+  bud = (BrowseUserData *) user_data;
+  self = bud->totem_grilo;
+  parent = bud->parent;
+
+  if (error &&
+      !g_error_matches (error,
+                        GRL_CORE_ERROR,
+                        GRL_CORE_ERROR_OPERATION_CANCELLED)) {
+    g_critical ("Error: %s", error->message);
+  }
+
+  if (media) {
+    /* Filter images */
+    if (GRL_IS_MEDIA_IMAGE (media)) {
+      g_object_unref (media);
+    } else {
+      thumbnail = get_thumbnail (self, media, TRUE);
+      description = get_description (media);
+      pretty_duration = seconds_to_strtime (grl_media_get_duration (media));
+
+      gtk_tree_store_append (GTK_TREE_STORE (self->priv->browser_model), &iter, parent);
+      gtk_tree_store_set (GTK_TREE_STORE (self->priv->browser_model),
+                          &iter,
+                          MODEL_RESULTS_SOURCE, source,
+                          MODEL_RESULTS_CONTENT, media,
+                          MODEL_RESULTS_THUMBNAIL, thumbnail,
+                          MODEL_RESULTS_IS_PRETHUMBNAIL, TRUE,
+                          MODEL_RESULTS_DESCRIPTION, description,
+                          MODEL_RESULTS_DURATION, pretty_duration,
+                          -1);
+
+      g_object_unref (media);
+      if (thumbnail) {
+        g_object_unref (thumbnail);
+      }
+      g_free (description);
+      g_free (pretty_duration);
+
+      GtkTreePath *path =  gtk_tree_model_get_path (self->priv->browser_model, parent);
+      gtk_tree_view_expand_row (GTK_TREE_VIEW (self->priv->browser), path, FALSE);
+    }
+  }
+
+  if (remaining == 0) {
+    gtk_tree_iter_free (bud->parent);
+    g_object_unref (bud->totem_grilo);
+    g_slice_free (BrowseUserData, bud);
+  }
+}
+
+static void
+browse (TotemGriloPlugin *self,
+	GtkTreeIter *parent,
+	GrlMediaSource *source,
+	GrlMedia *container)
+{
+  if (source) {
+    clear_browser_branch (self, parent);
+    BrowseUserData *bud = g_slice_new (BrowseUserData);
+    bud->totem_grilo = g_object_ref (self);
+    bud->parent = gtk_tree_iter_copy (parent);
+    grl_media_source_browse (source,
+			     container,
+			     browse_keys (),
+			     0, max_items,
+			     BROWSE_FLAGS,
+			     browse_cb,
+			     bud);
+  } else {
+    show_sources (self);
+  }
+}
+
+static void
+resolve_url_cb (GrlMediaSource *source,
+                guint op_id,
+                GrlMedia *media,
+                gpointer user_data,
+                const GError *error)
+{
+  if (error) {
+    g_warning ("Failed to resolve URL for media: %s", error->message);
+    return;
+  }
+
+  play (TOTEM_GRILO_PLUGIN (user_data), source, media, FALSE);
+}
+
+static void
+play (TotemGriloPlugin *self,
+      GrlMediaSource *source,
+      GrlMedia *media,
+      gboolean resolve_url)
+{
+  const gchar *url;
+  const GList *slow_keys;
+  GList *url_keys;
+
+  url = grl_media_get_url (media);
+  if (url) {
+    totem_add_to_playlist_and_play (self->priv->totem, url,
+                                    grl_media_get_title (media),
+                                    TRUE);
+    return;
+  }
+
+  /* If url is a slow key, then we need to full resolve it */
+  if (resolve_url) {
+    slow_keys = grl_metadata_source_slow_keys (GRL_METADATA_SOURCE (source));
+    if (g_list_find ((GList *) slow_keys, GRL_METADATA_KEY_URL)) {
+      url_keys = grl_metadata_key_list_new (GRL_METADATA_KEY_URL, NULL);
+      grl_media_source_metadata (source, media, url_keys, 0, resolve_url_cb, self);
+      g_list_free (url_keys);
+      return;
+    }
+  }
+
+  g_warning ("Current element has no URL to play");
+}
+
+static void
+search_cb (GrlMediaSource *source,
+	   guint search_id,
+	   GrlMedia *media,
+	   guint remaining,
+	   gpointer user_data,
+	   const GError *error)
+{
+  gchar *description;
+  gchar *pretty_duration;
+  GtkTreeIter iter;
+  GdkPixbuf *thumbnail;
+  TotemGriloPlugin *self;
+
+  self = TOTEM_GRILO_PLUGIN (user_data);
+
+  if (error &&
+      !g_error_matches (error,
+                        GRL_CORE_ERROR,
+                        GRL_CORE_ERROR_OPERATION_CANCELLED)) {
+    g_critical ("Error: %s", error->message);
+  }
+
+  if (media) {
+    /* Filter images */
+    if (GRL_IS_MEDIA_IMAGE (media)) {
+      g_object_unref (media);
+      return;
+    }
+    thumbnail = get_thumbnail (self, media, TRUE);
+    description = get_description (media);
+    pretty_duration = seconds_to_strtime (grl_media_get_duration (media));
+
+    gtk_list_store_append (GTK_LIST_STORE (self->priv->search_results_model), &iter);
+    gtk_list_store_set (GTK_LIST_STORE (self->priv->search_results_model),
+			&iter,
+			MODEL_RESULTS_SOURCE, source,
+			MODEL_RESULTS_CONTENT, media,
+                        MODEL_RESULTS_THUMBNAIL, thumbnail,
+                        MODEL_RESULTS_IS_PRETHUMBNAIL, TRUE,
+                        MODEL_RESULTS_DESCRIPTION, description,
+                        MODEL_RESULTS_DURATION, pretty_duration,
+			-1);
+
+    g_object_unref (media);
+    if (thumbnail) {
+      g_object_unref (thumbnail);
+    }
+    g_free (description);
+    g_free (pretty_duration);
+  }
+
+  if (remaining == 0) {
+    self->priv->search_in_progress_source = NULL;
+    self->priv->search_in_progress_id = 0;
+    gtk_widget_set_sensitive (self->priv->search_cancel_button, FALSE);
+    gtk_widget_set_sensitive (self->priv->search_search_button, TRUE);
+    update_search_thumbnails (self);
+  }
+}
+
+static void
+search (TotemGriloPlugin *self, GrlMediaSource *source, const gchar *text)
+{
+  gtk_list_store_clear (GTK_LIST_STORE (self->priv->search_results_model));
+  g_hash_table_remove_all (self->priv->cache_thumbnails);
+  gtk_widget_set_sensitive (self->priv->search_search_button, FALSE);
+  gtk_widget_set_sensitive (self->priv->search_cancel_button, TRUE);
+  self->priv->search_in_progress_source = source;
+  self->priv->search_in_progress_id =
+    grl_media_source_search (source,
+                             text,
+                             browse_keys (),
+                             0, max_items,
+                             BROWSE_FLAGS,
+                             search_cb,
+                             self);
+  if (!self->priv->search_in_progress_id) {
+    search_cb (source, 0, NULL, 0, self, NULL);
+  }
+}
+
+static void
+search_button_clicked_cb (GtkButton *btn, gpointer user_data)
+{
+  GtkTreeIter iter;
+  GrlMediaSource *source = NULL;
+  const gchar *text;
+  TotemGriloPlugin *self = TOTEM_GRILO_PLUGIN (user_data);
+
+  if (gtk_widget_is_sensitive (self->priv->search_sources_list)) {
+    if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (self->priv->search_sources_list),
+                                       &iter)) {
+      gtk_tree_model_get (self->priv->search_sources_model, &iter,
+                          SEARCH_MODEL_SOURCES_SOURCE, &source,
+                          -1);
+    }
+  }
+
+  text = gtk_entry_get_text (GTK_ENTRY (self->priv->search_entry));
+  search (self, source, text);
+
+  if (source) {
+    g_object_unref (source);
+  }
+}
+
+static void
+cancel_button_clicked_cb (GtkButton *btn, gpointer user_data)
+{
+  TotemGriloPlugin *self = TOTEM_GRILO_PLUGIN (user_data);
+  grl_metadata_source_cancel (GRL_METADATA_SOURCE (self->priv->search_in_progress_source),
+                              self->priv->search_in_progress_id);
+  gtk_widget_set_sensitive (self->priv->search_cancel_button, FALSE);
+}
+
+static void
+browser_activated_cb (GtkTreeView *tree_view,
+		      GtkTreePath *path,
+		      GtkTreeViewColumn *column,
+		      gpointer user_data)
+{
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+  GrlMedia *content;
+  GrlMediaSource *source;
+  TotemGriloPlugin *self = TOTEM_GRILO_PLUGIN (user_data);
+
+  model = gtk_tree_view_get_model (tree_view);
+  gtk_tree_model_get_iter (model, &iter, path);
+  gtk_tree_model_get (model, &iter,
+		      MODEL_RESULTS_SOURCE, &source,
+		      MODEL_RESULTS_CONTENT, &content,
+		      -1);
+
+  if (content && !GRL_IS_MEDIA_BOX (content)) {
+    play (self, source, content, TRUE);
+    return;
+  }
+
+  browse (self, &iter, source, content);
+
+  if (source) {
+    g_object_unref (source);
+  }
+  if (content) {
+    g_object_unref (content);
+  }
+}
+
+static void
+search_activated_cb (GtkTreeView *tree_view,
+                     GtkTreePath *path,
+                     GtkTreeViewColumn *column,
+                     gpointer user_data)
+{
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+  GrlMediaSource *source;
+  GrlMedia *content;
+
+  model = gtk_tree_view_get_model (tree_view);
+  gtk_tree_model_get_iter (model, &iter, path);
+  gtk_tree_model_get (model, &iter,
+                      MODEL_RESULTS_SOURCE, &source,
+                      MODEL_RESULTS_CONTENT, &content,
+                      -1);
+
+  play (TOTEM_GRILO_PLUGIN (user_data), source, content, TRUE);
+
+  if (source) {
+    g_object_unref (source);
+  }
+
+  if (content) {
+    g_object_unref (content);
+  }
+}
+
+static void
+source_added_cb (GrlPluginRegistry *registry,
+		 GrlMediaSource *source,
+		 gpointer user_data)
+{
+  const gchar *name;
+  gchar *description;
+  GdkPixbuf *icon;
+  TotemGriloPlugin *self;
+  GtkTreeIter iter;
+  GrlSupportedOps ops;
+
+  self = TOTEM_GRILO_PLUGIN (user_data);
+  icon = load_icon (ICON_BOX);
+  name = grl_metadata_source_get_name (GRL_METADATA_SOURCE (source));
+  ops = grl_metadata_source_supported_operations (GRL_METADATA_SOURCE (source));
+  if (ops & GRL_OP_BROWSE) {
+    description = g_markup_printf_escaped ("<b>%s</b>", name);
+    gtk_tree_store_append (GTK_TREE_STORE (self->priv->browser_model), &iter, NULL);
+    gtk_tree_store_set (GTK_TREE_STORE (self->priv->browser_model),
+			&iter,
+			MODEL_RESULTS_SOURCE, source,
+			MODEL_RESULTS_CONTENT, NULL,
+			MODEL_RESULTS_DESCRIPTION, description,
+			MODEL_RESULTS_THUMBNAIL, icon,
+                        MODEL_RESULTS_IS_PRETHUMBNAIL, TRUE,
+			-1);
+    g_free (description);
+  }
+  if (ops & GRL_OP_SEARCH) {
+    gtk_list_store_append (GTK_LIST_STORE (self->priv->search_sources_model), &iter);
+    gtk_list_store_set (GTK_LIST_STORE (self->priv->search_sources_model),
+                        &iter,
+                        SEARCH_MODEL_SOURCES_SOURCE, source,
+                        SEARCH_MODEL_SOURCES_NAME, name,
+                        -1);
+    /* Select one of them */
+    if (gtk_combo_box_get_active (GTK_COMBO_BOX (self->priv->search_sources_list)) == -1) {
+      gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->search_sources_list), 0);
+      gtk_widget_set_sensitive (self->priv->search_search_button, TRUE);
+    }
+  }
+
+  if (icon) {
+    g_object_unref (icon);
+  }
+}
+
+static gboolean
+remove_browse_result (GtkTreeModel *model,
+                      GtkTreePath *path,
+                      GtkTreeIter *iter,
+                      gpointer user_data)
+{
+  GrlMediaSource *removed_source = GRL_MEDIA_SOURCE (user_data);
+  GrlMediaSource *model_source;
+  gboolean same_source;
+
+  gtk_tree_model_get (model, iter,
+                      MODEL_RESULTS_SOURCE, &model_source,
+                      -1);
+
+  same_source = (model_source == removed_source);
+
+  if (same_source) {
+    gtk_tree_store_remove (GTK_TREE_STORE (model), iter);
+  }
+
+  g_object_unref (model_source);
+
+  return same_source;
+}
+
+static gboolean
+remove_searchable_source (GtkTreeModel *model,
+                          GtkTreePath *path,
+                          GtkTreeIter *iter,
+                          gpointer user_data)
+{
+  GrlMediaSource *removed_source = GRL_MEDIA_SOURCE (user_data);
+  GrlMediaSource *model_source;
+  gboolean same_source;
+
+  gtk_tree_model_get (model, iter,
+                      MODEL_RESULTS_SOURCE, &model_source,
+                      -1);
+
+  same_source = (model_source == removed_source);
+
+  if (same_source) {
+    gtk_list_store_remove (GTK_LIST_STORE (model), iter);
+  }
+
+  g_object_unref (model_source);
+
+  return same_source;
+}
+
+static void
+source_removed_cb (GrlPluginRegistry *registry,
+                   GrlMediaSource *source,
+                   gpointer user_data)
+{
+  GrlSupportedOps ops;
+  GtkTreeIter iter;
+  GrlMediaSource *model_source;
+  TotemGriloPlugin *self = TOTEM_GRILO_PLUGIN (user_data);
+
+  ops = grl_metadata_source_supported_operations (GRL_METADATA_SOURCE (source));
+
+  /* Remove source and content from browse results */
+  if (ops & GRL_OP_BROWSE) {
+    gtk_tree_model_foreach (self->priv->browser_model,
+                            remove_browse_result,
+                            source);
+  }
+
+  /* If current search results belongs to removed source, clear the results. In
+     any case, remove the source from th elist of searchable sources */
+  if (ops & GRL_OP_SEARCH) {
+    if (gtk_tree_model_get_iter_first (self->priv->search_results_model, &iter)) {
+      gtk_tree_model_get (self->priv->search_results_model,
+                          &iter,
+                          MODEL_RESULTS_SOURCE, &model_source,
+                          -1);
+      if (model_source == source) {
+        gtk_list_store_clear (GTK_LIST_STORE (self->priv->search_results_model));
+      }
+    }
+
+    gtk_tree_model_foreach (self->priv->search_sources_model,
+                            remove_searchable_source,
+                            source);
+    /* Select one if the selected source was removed */
+    if (gtk_combo_box_get_active (GTK_COMBO_BOX (self->priv->search_sources_list)) == -1) {
+      gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->search_sources_list), 0);
+    }
+  }
+}
+
+static void
+load_grilo_plugins (TotemGriloPlugin *self)
+{
+  GrlPluginRegistry *registry;
+  GError *error = NULL;
+
+  registry = grl_plugin_registry_get_default ();
+
+  g_signal_connect (registry, "source-added",
+		    G_CALLBACK (source_added_cb), self);
+  g_signal_connect (registry, "source-removed",
+		    G_CALLBACK (source_removed_cb), self);
+
+  if (!grl_plugin_registry_load_all (registry, &error)) {
+    g_warning ("Failed to load grilo plugins: %s", error->message);
+    g_error_free (error);
+  }
+}
+
+static gboolean
+show_popup_menu (TotemGriloPlugin *self, GtkTreeView *view, GdkEventButton *event)
+{
+  GtkWidget *menu;
+  gint button = 0;
+  guint32 _time;
+  GtkAction *action;
+  GtkTreeSelection *sel;
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+  GrlMediaSource *source;
+  const gchar *url = NULL;
+
+  sel = gtk_tree_view_get_selection (view);
+  if (!gtk_tree_selection_get_selected (sel, &model, &iter)) {
+    return FALSE;
+  }
+
+  /* Get rid of previously selected media */
+  if (self->priv->selected_media) {
+    g_object_unref (self->priv->selected_media);
+  }
+
+  gtk_tree_model_get (model, &iter,
+                      MODEL_RESULTS_SOURCE, &source,
+                      MODEL_RESULTS_CONTENT, &(self->priv->selected_media),
+                      -1);
+
+  if (event) {
+    button = event->button;
+    _time = event->time;
+  } else {
+    _time = gtk_get_current_event_time ();
+  }
+
+  if (self->priv->selected_media) {
+    url = grl_media_get_url (self->priv->selected_media);
+  }
+
+  action = gtk_action_group_get_action (self->priv->action_group, "add-to-playlist");
+  gtk_action_set_sensitive (action, url != NULL);
+  action = gtk_action_group_get_action (self->priv->action_group, "copy-location");
+  gtk_action_set_sensitive (action, url != NULL);
+
+  menu = gtk_ui_manager_get_widget (self->priv->ui_manager, "/grilo-popup");
+  gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
+  gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
+		  button, _time);
+
+  if (source) {
+    g_object_unref (source);
+  }
+
+  return TRUE;
+}
+
+static gboolean
+popup_menu_cb (GtkTreeView *treeview, TotemGriloPlugin *self)
+{
+  return show_popup_menu (self, treeview, NULL);
+}
+
+static gboolean
+context_button_pressed_cb (GtkTreeView *treeview,
+                           GdkEventButton *event,
+                           TotemGriloPlugin *self)
+{
+  if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
+    return show_popup_menu (self, treeview, event);
+  }
+
+  return FALSE;
+}
+
+static void
+search_results_scroll_changed_cb (GtkRange *range,
+                                  gpointer user_data)
+{
+  update_search_thumbnails (TOTEM_GRILO_PLUGIN (user_data));
+}
+
+static void
+setup_sidebar_browse (TotemGriloPlugin *self,
+                      GtkBuilder *builder)
+{
+  self->priv->browser_model = g_object_ref (gtk_builder_get_object (builder, "gw_browse_store_results"));
+  self->priv->browser = g_object_ref (gtk_builder_get_object (builder, "gw_browse"));
+
+  g_signal_connect (self->priv->browser, "row-activated",
+                    G_CALLBACK (browser_activated_cb), self);
+  g_signal_connect (self->priv->browser, "popup-menu",
+                    G_CALLBACK (popup_menu_cb), self);
+  g_signal_connect (self->priv->browser,
+                    "button-press-event",
+                    G_CALLBACK (context_button_pressed_cb), self);
+
+  totem_add_sidebar_page (self->priv->totem, "grilo-browse", "Browse",
+                          g_object_ref (gtk_builder_get_object (builder, "gw_browse_window")));
+}
+
+static void
+setup_sidebar_search (TotemGriloPlugin *self,
+                       GtkBuilder *builder)
+{
+  self->priv->search_results_model = g_object_ref (gtk_builder_get_object (builder, "gw_search_store_results"));
+  self->priv->search_sources_model = g_object_ref (gtk_builder_get_object (builder, "gw_search_store_sources"));
+  self->priv->search_sources_list = g_object_ref (gtk_builder_get_object (builder, "gw_search_select_source"));
+  self->priv->search_results_list = g_object_ref (gtk_builder_get_object (builder, "gw_search_results"));
+  self->priv->search_search_button = g_object_ref (gtk_builder_get_object (builder, "gw_search_search_button"));
+  self->priv->search_cancel_button = g_object_ref (gtk_builder_get_object (builder, "gw_search_cancel_button"));
+  self->priv->search_entry = g_object_ref (gtk_builder_get_object (builder, "gw_search_text"));
+
+  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self->priv->search_sources_model),
+                                        SEARCH_MODEL_SOURCES_NAME,
+                                        GTK_SORT_ASCENDING);
+  gtk_widget_set_sensitive (self->priv->search_cancel_button, FALSE);
+  gtk_widget_set_sensitive (self->priv->search_search_button, FALSE);
+
+  g_signal_connect (self->priv->search_results_list,
+                    "row-activated",
+                    G_CALLBACK (search_activated_cb),
+                    self);
+  g_signal_connect (self->priv->search_results_list,
+                    "popup-menu",
+                    G_CALLBACK (popup_menu_cb), self);
+  g_signal_connect (self->priv->search_results_list,
+                    "button-press-event",
+                    G_CALLBACK (context_button_pressed_cb), self);
+  g_signal_connect (self->priv->search_search_button,
+                    "clicked",
+                    G_CALLBACK (search_button_clicked_cb),
+                    self);
+  g_signal_connect (self->priv->search_cancel_button,
+                    "clicked",
+                    G_CALLBACK (cancel_button_clicked_cb),
+                    self);
+
+  g_signal_connect (gtk_scrolled_window_get_vscrollbar (GTK_SCROLLED_WINDOW (gtk_builder_get_object (builder,
+                                                                                                     "gw_search_results_window"))),
+                    "value_changed",
+                    G_CALLBACK (search_results_scroll_changed_cb),
+                    self);
+
+  totem_add_sidebar_page (self->priv->totem, "grilo-search", "Search",
+                          g_object_ref (gtk_builder_get_object (builder, "gw_search")));
+}
+
+static void
+add_to_pls_cb (GtkAction *action, TotemGriloPlugin *self)
+{
+  totem_add_to_playlist_and_play (self->priv->totem,
+                                  grl_media_get_url (self->priv->selected_media),
+                                  grl_media_get_title (self->priv->selected_media),
+                                  TRUE);
+}
+
+static void
+copy_location_cb (GtkAction *action, TotemGriloPlugin *self)
+{
+  GtkClipboard *clip;
+  const gchar *url;
+
+  url = grl_media_get_url (self->priv->selected_media);
+  if (url) {
+    clip = gtk_clipboard_get_for_display (gdk_display_get_default (),
+                                          GDK_SELECTION_CLIPBOARD);
+    gtk_clipboard_set_text (clip, url, -1);
+    clip = gtk_clipboard_get_for_display (gdk_display_get_default (),
+                                          GDK_SELECTION_PRIMARY);
+    gtk_clipboard_set_text (clip, url, -1);
+  }
+}
+
+static void
+setup_menus (TotemGriloPlugin *self,
+             GtkBuilder *builder)
+{
+  GtkAction *action;
+  GError *error =NULL;
+
+  self->priv->ui_manager = gtk_ui_manager_new  ();
+  self->priv->action_group = gtk_action_group_new ("grilo-action-group");
+
+  action = GTK_ACTION (gtk_builder_get_object (builder, "add-to-playlist"));
+  g_signal_connect (action, "activate", G_CALLBACK (add_to_pls_cb), self);
+  gtk_action_group_add_action_with_accel (self->priv->action_group, action, NULL);
+
+  action = GTK_ACTION (gtk_builder_get_object (builder, "copy-location"));
+  g_signal_connect (action, "activate", G_CALLBACK (copy_location_cb), self);
+  gtk_action_group_add_action_with_accel (self->priv->action_group, action, NULL);
+
+  gtk_ui_manager_insert_action_group (self->priv->ui_manager, self->priv->action_group, 1);
+  gtk_ui_manager_add_ui_from_string (self->priv->ui_manager,
+				     GRILO_POPUP_MENU, -1, &error);
+  if (error) {
+    g_warning ("grilo-ui: Failed to create popup menu: %s", error->message);
+    g_error_free (error);
+    return;
+  }
+}
+
+static void
+setup_ui (TotemGriloPlugin *self,
+          GtkBuilder *builder)
+{
+  setup_sidebar_browse (self, builder);
+  setup_sidebar_search (self, builder);
+  setup_menus (self, builder);
+}
+
+static void
+setup_config (TotemGriloPlugin *self)
+{
+  GrlConfig *config;
+  gchar *config_file;
+  GrlPluginRegistry *registry = grl_plugin_registry_get_default ();
+
+  /* Setup Youtube plugin */
+  config = grl_config_new ("grl-youtube", NULL);
+  grl_config_set_api_key (config, YOUTUBE_KEY);
+  grl_plugin_registry_add_config (registry, config, NULL);
+
+  /* Setup Vimeo plugin */
+  config = grl_config_new ("grl-vimeo", NULL);
+  grl_config_set_api_key (config, VIMEO_KEY);
+  grl_config_set_api_secret (config, VIMEO_SECRET);
+  grl_plugin_registry_add_config (registry, config, NULL);
+
+  /* Setup other plugins */
+  config_file = g_build_path (G_DIR_SEPARATOR_S,
+                              g_get_user_config_dir (),
+                              g_get_prgname (),
+                              TOTEM_GRILO_CONFIG_FILE,
+                              NULL);
+
+  grl_plugin_registry_add_config_from_file (registry, config_file, NULL);
+  g_free (config_file);
+}
+
+static void
+impl_activate (PeasActivatable *plugin)
+{
+  GtkBuilder *builder;
+  GtkWindow *main_window;
+
+  TotemGriloPlugin *self = TOTEM_GRILO_PLUGIN (plugin);
+  TotemGriloPluginPrivate *priv = self->priv;
+  priv->totem = g_object_ref (g_object_get_data (G_OBJECT (plugin), "object"));
+  main_window = totem_get_main_window (priv->totem);
+  priv->cache_thumbnails = g_hash_table_new_full (g_str_hash,
+                                                  g_str_equal,
+                                                  g_free,
+                                                  g_object_unref);
+
+  builder = totem_plugin_load_interface ("grilo", "grilo.ui", TRUE, main_window, self);
+  g_object_unref (main_window);
+  setup_ui (self, builder);
+  grl_init (0, NULL);
+  setup_config (self);
+  load_grilo_plugins (self);
+}
+
+static void
+impl_deactivate (PeasActivatable *plugin)
+{
+  TotemGriloPlugin *self = TOTEM_GRILO_PLUGIN (plugin);
+  GList *sources;
+  GList *s;
+  GrlPluginRegistry *registry;
+
+  totem_remove_sidebar_page (self->priv->totem, "grilo-browse");
+  totem_remove_sidebar_page (self->priv->totem, "grilo-search");
+
+  registry = grl_plugin_registry_get_default ();
+  g_signal_handlers_disconnect_by_func (registry, source_added_cb, self);
+  g_signal_handlers_disconnect_by_func (registry, source_removed_cb, self);
+
+  /* Shutdown all sources */
+  sources  = grl_plugin_registry_get_sources (registry, FALSE);
+  for (s = sources; s; s = g_list_next (s)) {
+    grl_plugin_registry_unload (registry,
+                                grl_media_plugin_get_id (GRL_MEDIA_PLUGIN (s->data)),
+                                NULL);
+  }
+  g_list_free (sources);
+
+  /* Empty results */
+  gtk_tree_store_clear (GTK_TREE_STORE (self->priv->browser_model));
+  gtk_list_store_clear (GTK_LIST_STORE (self->priv->search_results_model));
+
+  g_object_unref (self->priv->totem);
+}
+
+static GtkWidget *
+impl_create_configure_widget (PeasGtkConfigurable *configurable)
+{
+  GtkBuilder *builder;
+  GtkWidget *config_widget;
+  GtkSpinButton *spin;
+
+  builder = totem_plugin_load_interface ("grilo", "grilo.ui", TRUE, NULL, configurable);
+  config_widget = g_object_ref (gtk_builder_get_object (builder, "gw_config"));
+  spin = GTK_SPIN_BUTTON (gtk_builder_get_object (builder, "gw_spin_max_items"));
+  gtk_spin_button_set_value (spin, max_items);
+
+  return config_widget;
+}
+
+void
+gw_spin_max_items_value_changed_cb (GtkSpinButton *spin,
+                                    gpointer user_data)
+{
+  max_items = (guint) gtk_spin_button_get_value (spin);
+}
-- 
1.7.4.1

