summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJuan A. Suarez Romero <jasuarez@igalia.com>2011-06-20 18:19:46 +0000
committerBastien Nocera <hadess@hadess.net>2011-06-30 14:26:35 +0100
commit2c31c7bd3db1d3156c0c7ea18832b03a2920c591 (patch)
treed5feef9857329e5bededb8a485f4eb2d96c03e3d /src
parentbafff377e2207f92cdd33ebc6e770d9970e74ee1 (diff)
downloadtotem-2c31c7bd3db1d3156c0c7ea18832b03a2920c591.tar.gz
plugins: add Grilo plugin
https://bugzilla.gnome.org/show_bug.cgi?id=628648
Diffstat (limited to 'src')
-rw-r--r--src/plugins/grilo/Makefile.am27
-rw-r--r--src/plugins/grilo/grilo.plugin.in9
-rw-r--r--src/plugins/grilo/grilo.ui211
-rw-r--r--src/plugins/grilo/totem-grilo.c1417
-rw-r--r--src/plugins/grilo/totem-grilo.conf3
5 files changed, 1667 insertions, 0 deletions
diff --git a/src/plugins/grilo/Makefile.am b/src/plugins/grilo/Makefile.am
new file mode 100644
index 000000000..3df486a99
--- /dev/null
+++ b/src/plugins/grilo/Makefile.am
@@ -0,0 +1,27 @@
+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)
+
+confdir = $(CONFDIR)
+conf_DATA = totem-grilo.conf
+EXTRA_DIST += $(conf_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) \
+ -I $(top_srcdir)/src/backend
+
+-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 000000000..bbc674c8e
--- /dev/null
+++ b/src/plugins/grilo/grilo.plugin.in
@@ -0,0 +1,9 @@
+[Plugin]
+Module=grilo
+Builtin=true
+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 000000000..996e3477e
--- /dev/null
+++ b/src/plugins/grilo/grilo.ui
@@ -0,0 +1,211 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <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>
+ <object class="GtkAdjustment" id="gw_adj_max_items">
+ <property name="lower">1</property>
+ <property name="upper">10000</property>
+ <property name="value">300</property>
+ <property name="step_increment">5</property>
+ </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"/>
+ <!-- column-name page -->
+ <column type="gint"/>
+ <!-- column-name remaining -->
+ <column type="gint"/>
+ </columns>
+ </object>
+ <object class="GtkScrolledWindow" id="gw_browse_window">
+ <property name="visible">True</property>
+ <property name="can_focus">True</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 internal-child="selection">
+ <object class="GtkTreeSelection" id="treeview-selection1"/>
+ </child>
+ <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="can_focus">False</property>
+ <property name="border_width">6</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="gw_combo_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="gw_search_title">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Search</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="gw_search_select_source">
+ <property name="visible">True</property>
+ <property name="can_focus">False</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">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</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">●</property>
+ <property name="activates_default">True</property>
+ <property name="shadow_type">etched-out</property>
+ <property name="caps_lock_warning">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</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="shadow_type">in</property>
+ <child>
+ <object class="GtkIconView" id="gw_search_results_view">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="model">gw_search_store_results</property>
+ <child>
+ <object class="GtkCellRendererPixbuf" id="gw_search_result_thumbnail"/>
+ <attributes>
+ <attribute name="pixbuf">2</attribute>
+ </attributes>
+ </child>
+ <child>
+ <object class="GtkCellRendererText" id="gw_search_result_description">
+ <property name="ellipsize">end</property>
+ <property name="scale">0.75</property>
+ </object>
+ <attributes>
+ <attribute name="markup">4</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </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"/>
+ </columns>
+ </object>
+ <object class="GtkListStore" id="gw_search_store_sources">
+ <columns>
+ <!-- column-name source -->
+ <column type="GObject"/>
+ <!-- column-name name -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+</interface>
diff --git a/src/plugins/grilo/totem-grilo.c b/src/plugins/grilo/totem-grilo.c
new file mode 100644
index 000000000..59f81480f
--- /dev/null
+++ b/src/plugins/grilo/totem-grilo.c
@@ -0,0 +1,1417 @@
+/* -*- Mode: C; indent-tabs-mode: t -*- */
+
+/*
+ * 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>
+
+#include <video-utils.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 PAGE_SIZE 50
+#define THUMB_SEARCH_SIZE 128
+#define THUMB_BROWSE_SIZE 32
+#define SCROLL_GET_MORE_LIMIT 0.8
+
+#define TOTEM_GRILO_CONFIG_FILE "totem-grilo.conf"
+
+const gchar *BLACKLIST_SOURCES[] = { "grl-filesystem",
+ "grl-bookmarks",
+ "grl-shoutcast",
+ "grl-flickr",
+ NULL };
+
+typedef enum {
+ ICON_BOX = 0,
+ ICON_AUDIO,
+ ICON_VIDEO,
+ ICON_DEFAULT
+} IconType;
+
+typedef struct {
+ Totem *totem;
+
+ /* Current media selected in results*/
+ GrlMedia *selected_media;
+
+ /* Thumb cache to speed up loading: maps url strings to GdkPixbuf thumbnails */
+ GHashTable *cache_thumbnails;
+
+ /* Search related information */
+ GrlMediaSource *search_source;
+ guint search_id;
+ gint search_page;
+ gint search_remaining;
+ gchar *search_text;
+
+ /* 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_view;
+
+ /* Popup */
+ GtkUIManager *ui_manager;
+ GtkActionGroup *action_group;
+
+ /* Configuration */
+ guint max_items;
+} TotemGriloPluginPrivate;
+
+TOTEM_PLUGIN_REGISTER (TOTEM_TYPE_GRILO_PLUGIN, TotemGriloPlugin, totem_grilo_plugin)
+
+typedef struct {
+ TotemGriloPlugin *totem_grilo;
+ GtkTreeRowReference *ref_parent;
+} BrowseUserData;
+
+typedef struct {
+ TotemGriloPlugin *totem_grilo;
+ GrlMedia *media;
+ GFile *file;
+ GtkTreeRowReference *reference;
+ gint thumb_size;
+} SetThumbnailData;
+
+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,
+ MODEL_RESULTS_PAGE,
+ MODEL_RESULTS_REMAINING,
+};
+
+static void play (TotemGriloPlugin *self,
+ GrlMediaSource *source,
+ GrlMedia *media,
+ gboolean resolve_url);
+
+static gchar *
+get_description (GrlMedia *media)
+{
+ const gchar *author;
+
+ author = grl_data_get_string (GRL_DATA (media), GRL_METADATA_KEY_AUTHOR);
+ if (author == NULL) {
+ author = grl_data_get_string (GRL_DATA (media), GRL_METADATA_KEY_ARTIST);
+ }
+
+ if (author != NULL) {
+ 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 == NULL) {
+ 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 GList *
+search_keys (void)
+{
+ static GList *search_keys = NULL;
+
+ if (search_keys == NULL) {
+ search_keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ARTIST,
+ GRL_METADATA_KEY_AUTHOR,
+ GRL_METADATA_KEY_THUMBNAIL,
+ GRL_METADATA_KEY_URL,
+ GRL_METADATA_KEY_TITLE,
+ NULL);
+ }
+
+ return search_keys;
+}
+
+static GdkPixbuf *
+load_icon (TotemGriloPlugin *self, IconType icon_type, gint thumb_size)
+{
+ GdkScreen *screen;
+ GtkIconTheme *theme;
+
+ const gchar *icon_name[] = { GTK_STOCK_DIRECTORY,
+ "audio-x-generic",
+ "video-x-generic",
+ GTK_STOCK_FILE };
+
+ static GdkPixbuf *pixbuf[G_N_ELEMENTS(icon_name)] = { NULL };
+
+ if (pixbuf[icon_type] == NULL) {
+ screen = gtk_window_get_screen (totem_get_main_window (self->priv->totem));
+ 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] != NULL) {
+ return g_object_ref (pixbuf[icon_type]);
+ } else {
+ return NULL;
+ }
+}
+
+static GdkPixbuf *
+get_icon (TotemGriloPlugin *self, GrlMedia *media, gint thumb_size)
+{
+ if (GRL_IS_MEDIA_BOX (media)) {
+ return load_icon (self, ICON_BOX, thumb_size);
+ } else if (GRL_IS_MEDIA_AUDIO (media)) {
+ return load_icon (self, ICON_AUDIO, thumb_size);
+ } else if (GRL_IS_MEDIA_VIDEO (media)) {
+ return load_icon (self, ICON_VIDEO, thumb_size);
+ } else {
+ return load_icon (self, ICON_DEFAULT, thumb_size);
+ }
+}
+
+static void
+get_stream_thumbnail_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GFileInputStream *stream;
+ GdkPixbuf *thumbnail = NULL;
+ GtkTreeIter iter;
+ SetThumbnailData *thumb_data = (SetThumbnailData *) user_data;
+
+ stream = g_file_read_finish (thumb_data->file, res, NULL);
+ if (stream != NULL) {
+ thumbnail = gdk_pixbuf_new_from_stream_at_scale (G_INPUT_STREAM (stream),
+ thumb_data->thumb_size,
+ thumb_data->thumb_size,
+ TRUE, NULL, NULL);
+ g_object_unref (stream);
+ }
+
+ gtk_tree_model_get_iter (thumb_data->totem_grilo->priv->search_results_model,
+ &iter,
+ gtk_tree_row_reference_get_path (thumb_data->reference));
+
+ if (thumbnail) {
+ gtk_list_store_set (GTK_LIST_STORE (thumb_data->totem_grilo->priv->search_results_model),
+ &iter,
+ MODEL_RESULTS_THUMBNAIL, thumbnail,
+ -1);
+ /* Cache it */
+ g_hash_table_insert (thumb_data->totem_grilo->priv->cache_thumbnails,
+ g_file_get_uri (thumb_data->file),
+ thumbnail);
+ }
+
+ /* Free thumb data */
+ g_object_unref (thumb_data->totem_grilo);
+ g_object_unref (thumb_data->media);
+ g_object_unref (thumb_data->file);
+ gtk_tree_row_reference_free (thumb_data->reference);
+ g_slice_free (SetThumbnailData, thumb_data);
+}
+
+static void
+set_thumbnail_async (TotemGriloPlugin *self, GrlMedia *media, GtkTreePath *path, gint thumb_size)
+{
+ const gchar *url_thumb;
+ GFile *file_uri;
+ SetThumbnailData *thumb_data;
+ GdkPixbuf *thumbnail;
+ GtkTreeIter iter;
+
+ url_thumb = grl_media_get_thumbnail (media);
+ if (url_thumb != NULL) {
+ /* Check cache */
+ thumbnail = g_hash_table_lookup (self->priv->cache_thumbnails,
+ url_thumb);
+ if (thumbnail == NULL) {
+ /* Let's read the thumbnail stream and set the thumbnail */
+ file_uri = g_file_new_for_uri (url_thumb);
+ thumb_data = g_slice_new (SetThumbnailData);
+ thumb_data->totem_grilo = g_object_ref (self);
+ thumb_data->media = g_object_ref (media);
+ thumb_data->file = g_object_ref (file_uri);
+ thumb_data->reference = gtk_tree_row_reference_new (self->priv->search_results_model, path);
+ thumb_data->thumb_size = thumb_size;
+ g_file_read_async (file_uri, G_PRIORITY_DEFAULT, NULL,
+ get_stream_thumbnail_cb, thumb_data);
+ g_object_unref (file_uri);
+ } else {
+ /* Use cached thumbnail */
+ gtk_tree_model_get_iter (self->priv->search_results_model, &iter, path);
+ gtk_list_store_set (GTK_LIST_STORE (self->priv->search_results_model),
+ &iter,
+ MODEL_RESULTS_THUMBNAIL, thumbnail,
+ -1);
+ }
+ } else {
+ /* Keep the icon */
+ gtk_tree_model_get_iter (self->priv->search_results_model, &iter, path);
+ gtk_list_store_set (GTK_LIST_STORE (self->priv->search_results_model),
+ &iter,
+ MODEL_RESULTS_IS_PRETHUMBNAIL, FALSE,
+ -1);
+ }
+}
+
+static gboolean
+update_search_thumbnails_idle (TotemGriloPlugin *self)
+{
+ GtkTreePath *start_path;
+ GtkTreePath *end_path;
+ GrlMedia *media;
+ GtkTreeIter iter;
+ gboolean is_prethumbnail = FALSE;
+
+ if (gtk_icon_view_get_visible_range (GTK_ICON_VIEW (self->priv->search_results_view),
+ &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) == FALSE) {
+ 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) {
+ set_thumbnail_async (self, media, start_path, THUMB_SEARCH_SIZE);
+ gtk_list_store_set (GTK_LIST_STORE (self->priv->search_results_model),
+ &iter,
+ MODEL_RESULTS_IS_PRETHUMBNAIL, FALSE,
+ -1);
+ }
+
+ g_object_unref (media);
+ }
+ gtk_tree_path_free (start_path);
+ gtk_tree_path_free (end_path);
+ }
+
+ return FALSE;
+}
+
+static void
+update_search_thumbnails (TotemGriloPlugin *self)
+{
+ g_idle_add ((GSourceFunc) update_search_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 (self, ICON_BOX, THUMB_BROWSE_SIZE);
+ 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 != NULL) {
+ 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;
+ gint duration;
+ GtkTreeIter iter;
+ GdkPixbuf *thumbnail;
+ BrowseUserData *bud;
+ TotemGriloPlugin *self;
+ GtkTreeIter parent;
+ GtkTreePath *path;
+ GtkWindow *window;
+ gint remaining_expected;
+
+ bud = (BrowseUserData *) user_data;
+ self = bud->totem_grilo;
+
+ if (error != NULL &&
+ g_error_matches (error,
+ GRL_CORE_ERROR,
+ GRL_CORE_ERROR_OPERATION_CANCELLED) == FALSE) {
+ window = totem_get_main_window (self->priv->totem);
+ totem_interface_error (_("Browse Error"), error->message, window);
+ }
+
+ if (media != NULL) {
+ gtk_tree_model_get_iter (self->priv->browser_model, &parent, gtk_tree_row_reference_get_path (bud->ref_parent));
+ gtk_tree_model_get (self->priv->browser_model, &parent,
+ MODEL_RESULTS_REMAINING, &remaining_expected,
+ -1);
+ remaining_expected--;
+ gtk_tree_store_set (GTK_TREE_STORE (self->priv->browser_model), &parent,
+ MODEL_RESULTS_REMAINING, &remaining_expected,
+ -1);
+ /* Filter images */
+ if (GRL_IS_MEDIA_IMAGE (media) == FALSE) {
+ thumbnail = get_icon (self, media, THUMB_BROWSE_SIZE);
+ description = get_description (media);
+ duration = grl_media_get_duration (media);
+ if (duration > 0) {
+ pretty_duration = totem_time_to_string (duration * 1000);
+ } else {
+ pretty_duration = NULL;
+ }
+
+ 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);
+
+ if (thumbnail != NULL) {
+ g_object_unref (thumbnail);
+ }
+ g_free (description);
+ g_free (pretty_duration);
+
+ path = gtk_tree_model_get_path (self->priv->browser_model, &parent);
+ gtk_tree_view_expand_row (GTK_TREE_VIEW (self->priv->browser), path, FALSE);
+ gtk_tree_path_free (path);
+ }
+
+ g_object_unref (media);
+ }
+
+ if (remaining == 0) {
+ gtk_tree_row_reference_free (bud->ref_parent);
+ g_object_unref (bud->totem_grilo);
+ g_slice_free (BrowseUserData, bud);
+ }
+}
+
+static void
+browse (TotemGriloPlugin *self,
+ GtkTreePath *path,
+ GrlMediaSource *source,
+ GrlMedia *container,
+ gint page)
+{
+ if (source != NULL) {
+ BrowseUserData *bud = g_slice_new (BrowseUserData);
+ bud->totem_grilo = g_object_ref (self);
+ bud->ref_parent = gtk_tree_row_reference_new (self->priv->browser_model, path);
+ grl_media_source_browse (source,
+ container,
+ browse_keys (),
+ (page - 1) * PAGE_SIZE, PAGE_SIZE,
+ 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 != NULL) {
+ 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;
+
+ url = grl_media_get_url (media);
+ if (url != NULL) {
+ 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) {
+ const GList *slow_keys;
+ GList *url_keys;
+ slow_keys = grl_metadata_source_slow_keys (GRL_METADATA_SOURCE (source));
+ if (g_list_find ((GList *) slow_keys, GRL_METADATA_KEY_URL) != NULL) {
+ 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;
+ GtkTreeIter iter;
+ GdkPixbuf *thumbnail;
+ GtkWindow *window;
+ TotemGriloPlugin *self;
+
+ self = TOTEM_GRILO_PLUGIN (user_data);
+
+ if (error != NULL &&
+ g_error_matches (error,
+ GRL_CORE_ERROR,
+ GRL_CORE_ERROR_OPERATION_CANCELLED) == FALSE) {
+ window = totem_get_main_window (self->priv->totem);
+ totem_interface_error (_("Search Error"), error->message, window);
+ }
+
+ if (media != NULL) {
+ self->priv->search_remaining--;
+ /* Filter images */
+ if (GRL_IS_MEDIA_IMAGE (media) == FALSE) {
+ thumbnail = get_icon (self, media, THUMB_SEARCH_SIZE);
+ description = get_description (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,
+ -1);
+
+ if (thumbnail != NULL) {
+ g_object_unref (thumbnail);
+ }
+ g_free (description);
+ }
+
+ g_object_unref (media);
+ }
+
+ if (remaining == 0) {
+ self->priv->search_id = 0;
+ gtk_widget_set_sensitive (self->priv->search_entry, TRUE);
+ update_search_thumbnails (self);
+ }
+}
+
+static void
+search_more (TotemGriloPlugin *self)
+{
+ gtk_widget_set_sensitive (self->priv->search_entry, FALSE);
+ self->priv->search_page++;
+ self->priv->search_remaining = PAGE_SIZE;
+ self->priv->search_id = grl_media_source_search (self->priv->search_source,
+ self->priv->search_text,
+ search_keys (),
+ (self->priv->search_page - 1) * PAGE_SIZE,
+ PAGE_SIZE,
+ BROWSE_FLAGS,
+ search_cb,
+ self);
+ if (self->priv->search_id == 0) {
+ search_cb (self->priv->search_source, 0, NULL, 0, self, NULL);
+ }
+}
+
+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_entry, FALSE);
+ self->priv->search_source = source;
+ g_free (self->priv->search_text);
+ self->priv->search_text = g_strdup (text);
+ self->priv->search_page = 0;
+ search_more (self);
+}
+
+static void
+search_entry_changed_cb (GtkEntry *entry, TotemGriloPlugin *self)
+{
+ if (g_strcmp0 (gtk_entry_get_text (entry), "") == 0) {
+ g_object_set (G_OBJECT (entry),
+ "secondary-icon-name", "edit-find-symbolic",
+ "secondary-icon-activatable", FALSE,
+ "secondary-icon-sensitive", FALSE,
+ NULL);
+ } else {
+ g_object_set (G_OBJECT (entry),
+ "secondary-icon-name", "edit-clear-symbolic",
+ "secondary-icon-activatable", TRUE,
+ "secondary-icon-sensitive", TRUE,
+ NULL);
+ }
+}
+
+static void
+search_entry_activate_cb (GtkEntry *entry, TotemGriloPlugin *self)
+{
+ GtkTreeIter iter;
+ GrlMediaSource *source = NULL;
+ const gchar *text;
+
+ 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 != NULL) {
+ g_object_unref (source);
+ }
+}
+
+static void
+search_entry_clear_cb (GtkEntry *entry, TotemGriloPlugin *self)
+{
+ gtk_entry_set_text (entry, "");
+}
+
+static void
+browser_activated_cb (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ gpointer user_data)
+{
+ gint remaining;
+ gint page;
+ 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,
+ MODEL_RESULTS_PAGE, &page,
+ MODEL_RESULTS_REMAINING, &remaining,
+ -1);
+
+ if (content != NULL &&
+ GRL_IS_MEDIA_BOX (content) == FALSE) {
+ play (self, source, content, TRUE);
+ goto free_data;
+ }
+
+ if (gtk_tree_model_iter_has_child (model, &iter)) {
+ if (gtk_tree_view_row_expanded (tree_view, path)) {
+ gtk_tree_view_collapse_row (tree_view, path);
+ } else {
+ gtk_tree_view_expand_row (tree_view, path, FALSE);
+ }
+ goto free_data;
+ }
+
+ if (remaining == 0) {
+ gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
+ MODEL_RESULTS_PAGE, ++page,
+ MODEL_RESULTS_REMAINING, PAGE_SIZE,
+ -1);
+ browse (self, path, source, content, page);
+ }
+
+ free_data:
+ if (source != NULL) {
+ g_object_unref (source);
+ }
+ if (content != NULL) {
+ g_object_unref (content);
+ }
+}
+
+static void
+search_activated_cb (GtkIconView *icon_view,
+ GtkTreePath *path,
+ gpointer user_data)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GrlMediaSource *source;
+ GrlMedia *content;
+
+ model = gtk_icon_view_get_model (icon_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 != NULL) {
+ g_object_unref (source);
+ }
+
+ if (content != NULL) {
+ g_object_unref (content);
+ }
+}
+
+static gboolean
+source_is_blacklisted (GrlMediaSource *source)
+{
+ const gchar *id = grl_metadata_source_get_id (GRL_METADATA_SOURCE (source));
+ const gchar **s = BLACKLIST_SOURCES;
+
+ while (*s) {
+ if (g_strcmp0 (*s, id) == 0) {
+ return TRUE;
+ }
+ s++;
+ }
+
+ return FALSE;
+}
+
+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;
+
+ if (source_is_blacklisted (source)) {
+ grl_plugin_registry_unregister_source (registry,
+ GRL_MEDIA_PLUGIN (source),
+ NULL);
+ return;
+ }
+
+ self = TOTEM_GRILO_PLUGIN (user_data);
+ icon = load_icon (self, ICON_BOX, THUMB_BROWSE_SIZE);
+ 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_entry, TRUE);
+ }
+ }
+
+ if (icon != NULL) {
+ 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;
+ 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 the list of searchable sources */
+ if (ops & GRL_OP_SEARCH) {
+ if (self->priv->search_source == source) {
+ gtk_list_store_clear (GTK_LIST_STORE (self->priv->search_results_model));
+ self->priv->search_source = NULL;
+ }
+
+ 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) == FALSE) {
+ g_warning ("Failed to load grilo plugins: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+static gboolean
+show_popup_menu (TotemGriloPlugin *self, GtkWidget *view, GdkEventButton *event)
+{
+ GtkWidget *menu;
+ gint button = 0;
+ guint32 _time;
+ GtkAction *action;
+ GtkTreeSelection *sel_tree;
+ GList *sel_list = NULL;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GrlMediaSource *source;
+ const gchar *url = NULL;
+
+ if (view == self->priv->browser) {
+ /* Selection happened in browser view */
+ sel_tree = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
+
+ if (gtk_tree_selection_get_selected (sel_tree, &model, &iter) == FALSE) {
+ return FALSE;
+ }
+ } else {
+ /* Selection happened in search view */
+ sel_list = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (view));
+ if (!sel_list) {
+ return FALSE;
+ }
+
+ model = self->priv->search_results_model;
+
+ gtk_tree_model_get_iter (model,
+ &iter,
+ (GtkTreePath *) sel_list->data);
+
+ g_list_foreach (sel_list, (GFunc) gtk_tree_path_free, NULL);
+ g_list_free (sel_list);
+ }
+
+ /* Get rid of previously selected media */
+ if (self->priv->selected_media != NULL) {
+ 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 != NULL) {
+ button = event->button;
+ _time = event->time;
+ } else {
+ _time = gtk_get_current_event_time ();
+ }
+
+ if (self->priv->selected_media != NULL) {
+ 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 != NULL) {
+ g_object_unref (source);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+popup_menu_cb (GtkWidget *view, TotemGriloPlugin *self)
+{
+ return show_popup_menu (self, view, NULL);
+}
+
+static gboolean
+context_button_pressed_cb (GtkWidget *view,
+ GdkEventButton *event,
+ TotemGriloPlugin *self)
+{
+ if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
+ return show_popup_menu (self, view, event);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+adjustment_over_limit (GtkAdjustment *adjustment)
+{
+ if ((gtk_adjustment_get_value (adjustment) + gtk_adjustment_get_page_size (adjustment)) / gtk_adjustment_get_upper (adjustment) > SCROLL_GET_MORE_LIMIT) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+static void
+adjustment_value_changed_cb (GtkAdjustment *adjustment,
+ TotemGriloPlugin *self)
+{
+ update_search_thumbnails (self);
+
+ /* Do not get more results if search is in progress */
+ if (self->priv->search_id != 0) {
+ return;
+ }
+
+ /* Do not get more results if there are no more results to get :) */
+ if (self->priv->search_remaining > 0) {
+ return;
+ }
+
+ if (adjustment_over_limit (adjustment)) {
+ search_more (self);
+ }
+}
+
+static void
+adjustment_changed_cb (GtkAdjustment *adjustment,
+ TotemGriloPlugin *self)
+{
+ update_search_thumbnails (self);
+}
+
+static void
+get_more_browse_results_cb (GtkAdjustment *adjustment,
+ TotemGriloPlugin *self)
+{
+ GtkTreePath *start_path;
+ GtkTreePath *end_path;
+ GtkTreePath *parent_path;
+ GtkTreeIter iter;
+ GrlMediaSource *source;
+ GrlMedia *container;
+ gint page;
+ gint remaining;
+ gboolean stop_processing = FALSE;
+
+ if (adjustment_over_limit (adjustment) == FALSE) {
+ return;
+ }
+
+ if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (self->priv->browser),
+ &start_path, &end_path) == FALSE) {
+ return;
+ }
+
+ /* Start to check from last visible element, and check if its parent can get more elements */
+ while (gtk_tree_path_compare (start_path, end_path) <= 0 &&
+ stop_processing == FALSE) {
+ if (gtk_tree_path_get_depth (end_path) <= 1) {
+ goto continue_next;
+ }
+ parent_path = gtk_tree_path_copy (end_path);
+ if (gtk_tree_path_up (parent_path) == FALSE ||
+ gtk_tree_model_get_iter (self->priv->browser_model, &iter, parent_path) == FALSE) {
+ gtk_tree_path_free (parent_path);
+ goto continue_next;
+ }
+ gtk_tree_model_get (self->priv->browser_model,
+ &iter,
+ MODEL_RESULTS_SOURCE, &source,
+ MODEL_RESULTS_CONTENT, &container,
+ MODEL_RESULTS_PAGE, &page,
+ MODEL_RESULTS_REMAINING, &remaining,
+ -1);
+ /* Skip non-boxes (they can not be browsed) */
+ if (container != NULL &&
+ GRL_IS_MEDIA_BOX (container) == FALSE) {
+ goto free_elements;
+ }
+
+ /* In case of containers, check that more elements can be obtained */
+ if (remaining > 0) {
+ goto free_elements;
+ }
+
+ /* Continue browsing */
+ gtk_tree_store_set (GTK_TREE_STORE (self->priv->browser_model),
+ &iter,
+ MODEL_RESULTS_PAGE, ++page,
+ MODEL_RESULTS_REMAINING, PAGE_SIZE,
+ -1);
+ browse (self, parent_path, source, container, page);
+ stop_processing = TRUE;
+
+ free_elements:
+ if (source != NULL) {
+ g_object_unref (source);
+ }
+ if (container != NULL) {
+ g_object_unref (container);
+ }
+ if (parent_path) {
+ gtk_tree_path_free (parent_path);
+ }
+
+ continue_next:
+ stop_processing = stop_processing || (gtk_tree_path_prev (end_path) == FALSE);
+ }
+
+ gtk_tree_path_free (start_path);
+ gtk_tree_path_free (end_path);
+}
+
+static void
+setup_sidebar_browse (TotemGriloPlugin *self,
+ GtkBuilder *builder)
+{
+ self->priv->browser_model = GTK_TREE_MODEL (gtk_builder_get_object (builder, "gw_browse_store_results"));
+ self->priv->browser = GTK_WIDGET (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);
+ g_signal_connect (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self->priv->browser)),
+ "value_changed",
+ G_CALLBACK (get_more_browse_results_cb),
+ self);
+
+ totem_add_sidebar_page (self->priv->totem,
+ "grilo-browse", _("Browse"),
+ GTK_WIDGET (gtk_builder_get_object (builder, "gw_browse_window")));
+}
+
+static void
+setup_sidebar_search (TotemGriloPlugin *self,
+ GtkBuilder *builder)
+{
+ self->priv->search_results_model = GTK_TREE_MODEL (gtk_builder_get_object (builder, "gw_search_store_results"));
+ self->priv->search_sources_model = GTK_TREE_MODEL (gtk_builder_get_object (builder, "gw_search_store_sources"));
+ self->priv->search_sources_list = GTK_WIDGET (gtk_builder_get_object (builder, "gw_search_select_source"));
+ self->priv->search_results_view = GTK_WIDGET (gtk_builder_get_object (builder, "gw_search_results_view"));
+ self->priv->search_entry = GTK_WIDGET (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_entry, FALSE);
+
+ g_signal_connect (self->priv->search_results_view,
+ "item-activated",
+ G_CALLBACK (search_activated_cb),
+ self);
+ g_signal_connect (self->priv->search_results_view,
+ "popup-menu",
+ G_CALLBACK (popup_menu_cb), self);
+ g_signal_connect (self->priv->search_results_view,
+ "button-press-event",
+ G_CALLBACK (context_button_pressed_cb), self);
+ g_signal_connect (self->priv->search_entry,
+ "changed",
+ G_CALLBACK (search_entry_changed_cb), self);
+
+ g_signal_connect (self->priv->search_entry,
+ "activate",
+ G_CALLBACK (search_entry_activate_cb),
+ self);
+ g_signal_connect (self->priv->search_entry,
+ "icon-release",
+ G_CALLBACK (search_entry_clear_cb),
+ self);
+
+ g_object_set (G_OBJECT (self->priv->search_entry),
+ "secondary-icon-name", "edit-find-symbolic",
+ "secondary-icon-activatable", FALSE,
+ "secondary-icon-sensitive", FALSE,
+ NULL);
+
+ g_signal_connect (gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (gtk_builder_get_object (builder,
+ "gw_search_results_window"))),
+ "value_changed",
+ G_CALLBACK (adjustment_value_changed_cb),
+ self);
+
+ g_signal_connect (gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (gtk_builder_get_object (builder,
+ "gw_search_results_window"))),
+ "changed",
+ G_CALLBACK (adjustment_changed_cb),
+ self);
+
+ totem_add_sidebar_page (self->priv->totem,
+ "grilo-search", _("Search"),
+ GTK_WIDGET (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 != NULL) {
+ 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 != NULL) {
+ 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)
+{
+ gchar *config_file;
+ GrlPluginRegistry *registry = grl_plugin_registry_get_default ();
+
+ /* Setup system-wide plugins configuration */
+ config_file = g_build_path (G_DIR_SEPARATOR_S,
+ CONFDIR,
+ TOTEM_GRILO_CONFIG_FILE,
+ NULL);
+
+ if (g_file_test (config_file, G_FILE_TEST_EXISTS)) {
+ grl_plugin_registry_add_config_from_file (registry, config_file, NULL);
+ }
+ g_free (config_file);
+
+ /* Setup user-defined plugins configuration */
+ config_file = g_build_path (G_DIR_SEPARATOR_S,
+ g_get_user_config_dir (),
+ g_get_prgname (),
+ TOTEM_GRILO_CONFIG_FILE,
+ NULL);
+
+ if (g_file_test (config_file, G_FILE_TEST_EXISTS)) {
+ 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_unregister_source (registry,
+ 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);
+}
diff --git a/src/plugins/grilo/totem-grilo.conf b/src/plugins/grilo/totem-grilo.conf
new file mode 100644
index 000000000..c75ab3506
--- /dev/null
+++ b/src/plugins/grilo/totem-grilo.conf
@@ -0,0 +1,3 @@
+[grl-vimeo]
+api-key = db924d3b7f1d5f3d6fdfeb4488ad01e2
+api-secret = bef979d87f28ec47