[rhythmbox] add soundcloud plugin
- From: Jonathan Matthew <jmatthew src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [rhythmbox] add soundcloud plugin
- Date: Sat, 8 Nov 2014 11:21:06 +0000 (UTC)
commit a3924db0840cc1035d37db3547ef7cfa91720bcc
Author: Jonathan Matthew <jonathan d14n org>
Date: Sat Nov 8 21:20:32 2014 +1000
add soundcloud plugin
configure.ac | 1 +
plugins/Makefile.am | 1 +
plugins/soundcloud/Makefile.am | 16 +
plugins/soundcloud/powered-by-soundcloud.png | Bin 0 -> 4519 bytes
plugins/soundcloud/soundcloud-logo.png | Bin 0 -> 406 bytes
plugins/soundcloud/soundcloud.plugin.in | 10 +
plugins/soundcloud/soundcloud.py | 502 ++++++++++++++++++++++++++
plugins/soundcloud/soundcloud.ui | 106 ++++++
po/POTFILES.in | 3 +
9 files changed, 639 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 449a04c..4dafa8a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -802,6 +802,7 @@ plugins/rbzeitgeist/Makefile
plugins/notification/Makefile
plugins/visualizer/Makefile
plugins/grilo/Makefile
+plugins/soundcloud/Makefile
sample-plugins/Makefile
sample-plugins/sample/Makefile
sample-plugins/sample-python/Makefile
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 3a9ea03..94de29c 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -21,6 +21,7 @@ SUBDIRS += \
rbzeitgeist \
replaygain \
sendto \
+ soundcloud \
rb
if WITH_WEBKIT
diff --git a/plugins/soundcloud/Makefile.am b/plugins/soundcloud/Makefile.am
new file mode 100644
index 0000000..8de1b04
--- /dev/null
+++ b/plugins/soundcloud/Makefile.am
@@ -0,0 +1,16 @@
+plugindir = $(PLUGINDIR)/soundcloud
+plugindatadir = $(PLUGINDATADIR)/soundcloud
+plugin_PYTHON = soundcloud.py
+
+plugin_in_files = soundcloud.plugin.in
+%.plugin: %.plugin.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE)
$(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugin_DATA = $(plugin_in_files:.plugin.in=.plugin)
+
+gtkbuilderdir = $(plugindatadir)
+gtkbuilder_DATA = soundcloud.ui powered-by-soundcloud.png soundcloud-logo.png
+
+EXTRA_DIST = $(plugin_in_files) $(gtkbuilder_DATA)
+
+CLEANFILES = $(plugin_DATA)
+DISTCLEANFILES = $(plugin_DATA)
diff --git a/plugins/soundcloud/powered-by-soundcloud.png b/plugins/soundcloud/powered-by-soundcloud.png
new file mode 100644
index 0000000..a0f5b61
Binary files /dev/null and b/plugins/soundcloud/powered-by-soundcloud.png differ
diff --git a/plugins/soundcloud/soundcloud-logo.png b/plugins/soundcloud/soundcloud-logo.png
new file mode 100644
index 0000000..b43aaf3
Binary files /dev/null and b/plugins/soundcloud/soundcloud-logo.png differ
diff --git a/plugins/soundcloud/soundcloud.plugin.in b/plugins/soundcloud/soundcloud.plugin.in
new file mode 100644
index 0000000..d565817
--- /dev/null
+++ b/plugins/soundcloud/soundcloud.plugin.in
@@ -0,0 +1,10 @@
+[Plugin]
+Loader=python3
+Module=soundcloud
+IAge=2
+_Name=SoundCloud
+_Description=Browse and play sounds from SoundCloud®
+Authors=Jonathan Matthew
+Copyright=Copyright © 2014 Jonathan Matthew
+Website=http://www.rhythmbox.org/
+Depends=rb
diff --git a/plugins/soundcloud/soundcloud.py b/plugins/soundcloud/soundcloud.py
new file mode 100644
index 0000000..a68a2c6
--- /dev/null
+++ b/plugins/soundcloud/soundcloud.py
@@ -0,0 +1,502 @@
+# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-
+#
+# Copyright (C) 2014 Jonathan Matthew
+#
+# 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, or (at your option)
+# any later version.
+#
+# The Rhythmbox authors hereby grant permission for non-GPL compatible
+# GStreamer plugins to be used and distributed together with GStreamer
+# and Rhythmbox. This permission is above and beyond the permissions granted
+# by the GPL license by which Rhythmbox is covered. If you modify this code
+# you may extend this exception to your version of the code, but you are not
+# obligated to do so. If you do not wish to do so, delete this exception
+# statement from your version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from gi.repository import Gtk, Gdk, GObject, Gio, GLib, Peas
+from gi.repository import RB
+import rb
+import urllib.parse
+import json
+from datetime import datetime
+
+import gettext
+gettext.install('rhythmbox', RB.locale_dir())
+
+# rhythmbox app registered with soundcloud with the account notverysmart gmail com
+CLIENT_ID = 'e4ef6572c2baf401db2f64b4e0eae9ce'
+
+class SoundCloudEntryType(RB.RhythmDBEntryType):
+ def __init__(self):
+ RB.RhythmDBEntryType.__init__(self, name='soundcloud')
+
+ def do_get_playback_uri(self, entry):
+ uri = entry.get_string(RB.RhythmDBPropType.MOUNTPOINT)
+ return uri + "?client_id=" + CLIENT_ID
+
+ def do_can_sync_metadata(self, entry):
+ return False
+
+
+class SoundCloudPlugin(GObject.Object, Peas.Activatable):
+ __gtype_name = 'SoundCloudPlugin'
+ object = GObject.property(type=GObject.GObject)
+
+ def __init__(self):
+ GObject.Object.__init__(self)
+
+ def do_activate(self):
+ shell = self.object
+
+ db = shell.props.db
+
+ self.entry_type = SoundCloudEntryType()
+ db.register_entry_type(self.entry_type)
+
+ logofile = rb.find_plugin_file(self, "soundcloud-logo.png")
+ icon = Gio.FileIcon.new(Gio.File.new_for_path(logofile))
+
+ model = RB.RhythmDBQueryModel.new_empty(db)
+ self.source = GObject.new (SoundCloudSource,
+ shell=shell,
+ name=_("SoundCloud"),
+ plugin=self,
+ query_model=model,
+ entry_type=self.entry_type,
+ icon=icon)
+ self.source.setup()
+ group = RB.DisplayPageGroup.get_by_id ("shared")
+ shell.append_display_page(self.source, group)
+
+ def do_deactivate(self):
+ self.source.delete_thyself()
+ self.source = None
+
+class SoundCloudSource(RB.StreamingSource):
+ def __init__(self, **kwargs):
+ super(SoundCloudSource, self).__init__(kwargs)
+ self.loader = None
+
+ self.search_count = 1
+ self.search_types = {
+ 'tracks': {
+ 'label': _("Search tracks"),
+ 'placeholder': _("Search tracks on SoundCloud"),
+ 'title': "", # container view is hidden
+ 'endpoint': '/tracks.json',
+ 'containers': False
+ },
+ 'sets': {
+ 'label': _("Search sets"),
+ 'placeholder': _("Search sets on SoundCloud"),
+ 'title': _("SoundCloud Sets"),
+ 'endpoint': '/playlists.json',
+ 'containers': True
+ },
+ 'users': {
+ 'label': _("Search users"),
+ 'placeholder': _("Search users on SoundCloud"),
+ 'title': _("SoundCloud Users"),
+ 'endpoint': '/users.json',
+ 'containers': True
+ },
+ 'groups': {
+ 'label': _("Search groups"),
+ 'placeholder': _("Search groups on SoundCloud"),
+ 'title': _("SoundCloud Groups"),
+ 'endpoint': '/groups.json',
+ 'containers': True
+ },
+ }
+
+ self.container_types = {
+ 'user': {
+ 'attributes': ['username', 'kind', 'uri', 'permalink_url', 'avatar_url',
'description'],
+ 'tracks-url': '/tracks.json',
+ 'tracks-type': 'plain',
+ },
+ 'playlist': {
+ 'attributes': ['title', 'kind', 'uri', 'permalink_url', 'artwork_url',
'description'],
+ 'tracks-url': '.json',
+ 'tracks-type': 'playlist',
+ },
+ 'group': {
+ 'attributes': ['name', 'kind', 'uri', 'permalink_url', 'artwork_url',
'description'],
+ 'tracks-url': '/tracks.json',
+ 'tracks-type': 'plain',
+ }
+ }
+
+ def hide_entry_cb(self, entry):
+ shell = self.props.shell
+ shell.props.db.entry_set(entry, RB.RhythmDBPropType.HIDDEN, True)
+
+ def new_model(self):
+ shell = self.props.shell
+ plugin = self.props.plugin
+ db = shell.props.db
+
+ self.search_count = self.search_count + 1
+ q = GLib.PtrArray()
+ db.query_append_params(q, RB.RhythmDBQueryType.EQUALS, RB.RhythmDBPropType.TYPE,
plugin.entry_type)
+ db.query_append_params(q, RB.RhythmDBQueryType.EQUALS, RB.RhythmDBPropType.LAST_SEEN,
self.search_count)
+ model = RB.RhythmDBQueryModel.new_empty(db)
+
+ db.do_full_query_async_parsed(model, q)
+ self.props.query_model = model
+ self.songs.set_model(model)
+
+ def add_track(self, db, entry_type, item):
+ if not item['streamable']:
+ return
+
+ uri = item['permalink_url']
+ entry = db.entry_lookup_by_location(uri)
+ if entry:
+ db.entry_set(entry, RB.RhythmDBPropType.LAST_SEEN, self.search_count)
+ else:
+ entry = RB.RhythmDBEntry.new(db, entry_type, item['permalink_url'])
+ db.entry_set(entry, RB.RhythmDBPropType.MOUNTPOINT, item['stream_url'])
+ db.entry_set(entry, RB.RhythmDBPropType.ARTIST, item['user']['username'])
+ db.entry_set(entry, RB.RhythmDBPropType.TITLE, item['title'])
+ db.entry_set(entry, RB.RhythmDBPropType.LAST_SEEN, self.search_count)
+ if item['genre'] is not None:
+ db.entry_set(entry, RB.RhythmDBPropType.GENRE, item['genre'])
+ db.entry_set(entry, RB.RhythmDBPropType.DURATION, item['duration']/1000)
+ if item['bpm'] is not None:
+ db.entry_set(entry, RB.RhythmDBPropType.BEATS_PER_MINUTE, item['bpm'])
+
+ if item['description'] is not None:
+ db.entry_set(entry, RB.RhythmDBPropType.COMMENT, item['description'])
+
+ if item['artwork_url'] is not None:
+ db.entry_set(entry, RB.RhythmDBPropType.MB_ALBUMID, item['artwork_url'])
+
+ if item['release_year'] is not None:
+ date = GLib.Date.new_dmy(item.get('release_day', 0),
item.get('release_month', 0), item['release_year'])
+ db.entry_set(entry, RB.RhythmDBPropType.DATE, date.get_julian())
+
+ if item['created_at'] is not None:
+ try:
+ dt = datetime.strptime(item['created_at'], '%Y/%m/%d %H:%M:%S %z')
+ db.entry_set(entry, RB.RhythmDBPropType.FIRST_SEEN,
int(dt.timestamp()))
+ except Exception as e:
+ print(str(e))
+
+ db.commit()
+
+ def add_container(self, item):
+ k = item['kind']
+ if k not in self.container_types:
+ return
+
+ ct = self.container_types[k]
+ self.containers.append([item.get(i) for i in ct['attributes']])
+
+ def search_tracks_api_cb(self, data):
+ if data is None:
+ return
+
+ shell = self.props.shell
+ db = shell.props.db
+ entry_type = self.props.entry_type
+
+ data = data.decode('utf-8')
+ stuff = json.loads(data)
+ for item in stuff:
+ self.add_track(db, entry_type, item)
+
+ def search_containers_api_cb(self, data):
+ if data is None:
+ return
+
+ entry_type = self.props.entry_type
+
+ data = data.decode('utf-8')
+ stuff = json.loads(data)
+ for item in stuff:
+ self.add_container(item)
+
+ def resolve_api_cb(self, data):
+ if data is None:
+ return
+
+ data = data.decode('utf-8')
+ stuff = json.loads(data)
+
+ if stuff['kind'] == 'track':
+ shell = self.props.shell
+ db = shell.props.db
+ self.add_track(db, self.props.entry_type, stuff)
+ else:
+ self.add_container(stuff)
+ # select, etc. too?
+
+ def playlist_api_cb(self, data):
+ if data is None:
+ return
+
+ shell = self.props.shell
+ db = shell.props.db
+
+ data = data.decode('utf-8')
+ stuff = json.loads(data)
+ for t in stuff['tracks']:
+ self.add_track(db, self.props.entry_type, t)
+
+ def cancel_request(self):
+ if self.loader:
+ self.loader.cancel()
+ self.loader = None
+
+ def search_popup_cb(self, widget):
+ self.search_popup.popup(None, None, None, None, 3, Gtk.get_current_event_time())
+
+ def search_type_action_cb(self, action, parameter):
+ print(parameter.get_string() + " selected")
+ self.search_type = parameter.get_string()
+
+ if self.search_entry.searching():
+ self.do_search()
+
+ st = self.search_types[self.search_type]
+ self.search_entry.set_placeholder(st['placeholder'])
+
+ def search_entry_cb(self, widget, term):
+ self.search_text = term
+ self.do_search()
+
+ def do_search(self):
+ self.cancel_request()
+
+ base = 'https://api.soundcloud.com'
+ self.new_model()
+ self.containers.clear()
+ term = self.search_text
+
+ if term.startswith('https://soundcloud.com/') or term.startswith("http://soundcloud.com/"):
+ # ignore the selected search type and try to resolve whatever the url is
+ print("resolving " + term)
+ self.scrolled.hide()
+ url = base + '/resolve.json?url=' + term + '&client_id=' + CLIENT_ID
+ self.loader = rb.Loader()
+ self.loader.get_url(url, self.resolve_api_cb)
+ return
+
+ if self.search_type not in self.search_types:
+ print("not sure how to search for " + self.search_type)
+ return
+
+ print("searching for " + self.search_type + " matching " + term)
+ st = self.search_types[self.search_type]
+ self.container_view.get_column(0).set_title(st['title'])
+
+ url = base + st['endpoint'] + '?q=' + urllib.parse.quote(term) + '&client_id=' + CLIENT_ID
+ self.loader = rb.Loader()
+ if st['containers']:
+ self.scrolled.show()
+ self.loader.get_url(url, self.search_containers_api_cb)
+ else:
+ self.scrolled.hide()
+ self.loader.get_url(url, self.search_tracks_api_cb)
+
+
+ def selection_changed_cb(self, selection):
+ self.new_model()
+ self.cancel_request()
+ self.build_sc_menu()
+
+ (model, aiter) = selection.get_selected()
+ if aiter is None:
+ return
+
+ [itemtype, url] = model.get(aiter, 1, 2)
+ if itemtype not in self.container_types:
+ return
+
+ print("loading %s %s" % (itemtype, url))
+ ct = self.container_types[itemtype]
+ trackurl = url + ct['tracks-url'] + '?client_id=' + CLIENT_ID
+
+ self.loader = rb.Loader()
+ if ct['tracks-type'] == 'playlist':
+ self.loader.get_url(trackurl, self.playlist_api_cb)
+ else:
+ self.loader.get_url(trackurl, self.search_tracks_api_cb)
+
+ def sort_order_changed_cb(self, obj, pspec):
+ obj.resort_model()
+
+ def songs_selection_changed_cb(self, songs):
+ self.build_sc_menu()
+
+ def playing_entry_changed_cb(self, player, entry):
+ self.build_sc_menu()
+ if not entry:
+ return
+ if entry.get_entry_type() != self.props.entry_type:
+ return
+
+ au = entry.get_string(RB.RhythmDBPropType.MB_ALBUMID)
+ if au:
+ key = RB.ExtDBKey.create_storage("title", entry.get_string(RB.RhythmDBPropType.TITLE))
+ key.add_field("artist", entry.get_string(RB.RhythmDBPropType.ARTIST))
+ self.art_store.store_uri(key, RB.ExtDBSourceType.EMBEDDED, au)
+
+ def open_uri_action_cb(self, action, param):
+ shell = self.props.shell
+ window = shell.props.window
+ screen = window.get_screen()
+
+ uri = param.get_string()
+ Gtk.show_uri(screen, uri, Gdk.CURRENT_TIME)
+
+ def build_sc_menu(self):
+ menu = {}
+
+ # playing track
+ shell = self.props.shell
+ player = shell.props.shell_player
+ entry = player.get_playing_entry()
+ if entry is not None and entry.get_entry_type() == self.props.entry_type:
+ url = entry.get_string(RB.RhythmDBPropType.LOCATION)
+ menu[url] = _("View '%(title)s' on SoundCloud") % {'title':
entry.get_string(RB.RhythmDBPropType.TITLE) }
+ # artist too?
+
+
+ # selected track
+ if self.songs.have_selection():
+ entry = self.songs.get_selected_entries()[0]
+ url = entry.get_string(RB.RhythmDBPropType.LOCATION)
+ menu[url] = _("View '%(title)s' on SoundCloud") % {'title':
entry.get_string(RB.RhythmDBPropType.TITLE) }
+ # artist too?
+
+ # selected container
+ selection = self.container_view.get_selection()
+ (model, aiter) = selection.get_selected()
+ if aiter is not None:
+ [name, url] = model.get(aiter, 0, 3)
+ menu[url] = _("View '%(container)s' on SoundCloud") % {'container': name}
+
+ if len(menu) == 0:
+ self.sc_button.set_menu_model(None)
+ self.sc_button.set_sensitive(False)
+ return None
+
+ m = Gio.Menu()
+ for u in menu:
+ i = Gio.MenuItem()
+ i.set_label(menu[u])
+ i.set_action_and_target_value("win.soundcloud-open-uri", GLib.Variant.new_string(u))
+ m.append_item(i)
+ self.sc_button.set_menu_model(m)
+ self.sc_button.set_sensitive(True)
+
+ def setup(self):
+ shell = self.props.shell
+
+ builder = Gtk.Builder()
+ builder.add_from_file(rb.find_plugin_file(self.props.plugin, "soundcloud.ui"))
+
+ self.scrolled = builder.get_object("container-scrolled")
+ self.scrolled.set_no_show_all(True)
+ self.scrolled.hide()
+
+ self.search_entry = RB.SearchEntry()
+ self.search_entry.props.explicit_mode = True
+
+ action = Gio.SimpleAction.new("soundcloud-search-type", GLib.VariantType.new('s'))
+ action.connect("activate", self.search_type_action_cb)
+ shell.props.window.add_action(action)
+
+ m = Gio.Menu()
+ for st in sorted(self.search_types):
+ i = Gio.MenuItem()
+ i.set_label(self.search_types[st]['label'])
+ i.set_action_and_target_value("win.soundcloud-search-type",
GLib.Variant.new_string(st))
+ m.append_item(i)
+
+ self.search_popup = Gtk.Menu.new_from_model(m)
+
+ action.activate(GLib.Variant.new_string("tracks"))
+
+ grid = builder.get_object("soundcloud-source")
+
+ self.search_entry.connect("search", self.search_entry_cb)
+ self.search_entry.connect("activate", self.search_entry_cb)
+ self.search_entry.connect("show-popup", self.search_popup_cb)
+ self.search_entry.set_size_request(400, -1)
+ builder.get_object("search-box").pack_start(self.search_entry, False, True, 0)
+
+
+
+ self.search_popup.attach_to_widget(self.search_entry)
+
+ self.containers = builder.get_object("container-store")
+ self.container_view = builder.get_object("containers")
+ self.container_view.set_model(self.containers)
+
+ action = Gio.SimpleAction.new("soundcloud-open-uri", GLib.VariantType.new('s'))
+ action.connect("activate", self.open_uri_action_cb)
+ shell.props.window.add_action(action)
+
+ r = Gtk.CellRendererText()
+ c = Gtk.TreeViewColumn("", r, text=0)
+ self.container_view.append_column(c)
+
+ self.container_view.get_selection().connect('changed', self.selection_changed_cb)
+
+ self.songs = RB.EntryView(db=shell.props.db,
+ shell_player=shell.props.shell_player,
+ is_drag_source=True,
+ is_drag_dest=False)
+ self.songs.append_column(RB.EntryViewColumn.TITLE, True)
+ self.songs.append_column(RB.EntryViewColumn.ARTIST, True)
+ self.songs.append_column(RB.EntryViewColumn.DURATION, True)
+ self.songs.append_column(RB.EntryViewColumn.YEAR, False)
+ self.songs.append_column(RB.EntryViewColumn.GENRE, False)
+ self.songs.append_column(RB.EntryViewColumn.BPM, False)
+ self.songs.append_column(RB.EntryViewColumn.FIRST_SEEN, False)
+ self.songs.set_model(self.props.query_model)
+ self.songs.connect("notify::sort-order", self.sort_order_changed_cb)
+ self.songs.connect("selection-changed", self.songs_selection_changed_cb)
+
+ paned = builder.get_object("paned")
+ paned.pack2(self.songs)
+
+ self.bind_settings(self.songs, paned, None, True)
+
+ self.sc_button = Gtk.MenuButton()
+ self.sc_button.set_relief(Gtk.ReliefStyle.NONE)
+ img = Gtk.Image.new_from_file(rb.find_plugin_file(self.props.plugin,
"powered-by-soundcloud.png"))
+ self.sc_button.add(img)
+ box = builder.get_object("soundcloud-button-box")
+ box.pack_start(self.sc_button, True, True, 0)
+
+ self.build_sc_menu()
+
+ self.pack_start(grid, expand=True, fill=True, padding=0)
+ grid.show_all()
+
+ self.art_store = RB.ExtDB(name="album-art")
+ player = shell.props.shell_player
+ player.connect('playing-song-changed', self.playing_entry_changed_cb)
+
+ def do_get_entry_view(self):
+ return self.songs
+
+ def do_get_playback_status(self, text, progress):
+ return self.get_progress()
+
+GObject.type_register(SoundCloudSource)
diff --git a/plugins/soundcloud/soundcloud.ui b/plugins/soundcloud/soundcloud.ui
new file mode 100644
index 0000000..7a62e11
--- /dev/null
+++ b/plugins/soundcloud/soundcloud.ui
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+ <requires lib="gtk+" version="3.6"/>
+ <object class="GtkListStore" id="container-store">
+ <columns>
+ <!-- column-name name -->
+ <column type="gchararray"/>
+ <!-- column-name type -->
+ <column type="gchararray"/>
+ <!-- column-name url -->
+ <column type="gchararray"/>
+ <!-- column-name permalink -->
+ <column type="gchararray"/>
+ <!-- column-name image -->
+ <column type="gchararray"/>
+ <!-- column-name description -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkGrid" id="soundcloud-source">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_top">12</property>
+ <property name="vexpand">True</property>
+ <property name="row_spacing">12</property>
+ <child>
+ <object class="GtkPaned" id="paned">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkScrolledWindow" id="container-scrolled">
+ <property name="width_request">150</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="containers">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeview-selection1"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_right">6</property>
+ <child>
+ <object class="GtkBox" id="search-box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="soundcloud-button-box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ </object>
+</interface>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 99db7e3..27f3911 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -150,6 +150,9 @@ plugins/replaygain/player.py
[type: gettext/glade]plugins/replaygain/replaygain-prefs.ui
[type: gettext/ini]plugins/sendto/sendto.plugin.in
plugins/sendto/sendto.py
+[type: gettext/ini]plugins/soundcloud/soundcloud.plugin.in
+plugins/soundcloud/soundcloud.py
+[type: gettext/glade]plugins/soundcloud/soundcloud.ui
plugins/visualizer/rb-visualizer-fullscreen.c
plugins/visualizer/rb-visualizer-menu.c
plugins/visualizer/rb-visualizer-page.c
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]