Gtk UI: New podcast directory UI

This commit is contained in:
Thomas Perl 2014-10-22 21:23:06 +02:00
parent f6e7d05dd2
commit c022d9e453
21 changed files with 615 additions and 702 deletions

View File

Before

Width:  |  Height:  |  Size: 916 B

After

Width:  |  Height:  |  Size: 916 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 410 B

After

Width:  |  Height:  |  Size: 410 B

View File

Before

Width:  |  Height:  |  Size: 743 B

After

Width:  |  Height:  |  Size: 743 B

View File

Before

Width:  |  Height:  |  Size: 908 B

After

Width:  |  Height:  |  Size: 908 B

View File

Before

Width:  |  Height:  |  Size: 441 B

After

Width:  |  Height:  |  Size: 441 B

View File

@ -1,326 +1,233 @@
<?xml version="1.0" encoding="utf-8"?>
<!--*- mode: xml -*-->
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<!-- interface-requires gtk+ 2.16 -->
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkDialog" id="gPodderPodcastDirectory">
<property name="visible">True</property>
<property name="has_separator">False</property>
<property name="visible">False</property>
<property name="can_focus">False</property>
<property name="border_width">6</property>
<property name="title" translatable="yes">Find new podcasts</property>
<property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="default_width">600</property>
<property name="default_height">400</property>
<property name="destroy_with_parent">False</property>
<property name="skip_taskbar_hint">False</property>
<property name="skip_pager_hint">False</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
<property name="focus_on_map">True</property>
<property name="urgency_hint">False</property>
<property name="border-width">6</property>
<signal handler="on_gPodderPodcastDirectory_destroy" name="destroy"/>
<property name="type_hint">dialog</property>
<child internal-child="vbox">
<object class="GtkVBox" id="vboxOPML">
<object class="GtkVBox" id="vb_directory">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="can_focus">False</property>
<property name="spacing">6</property>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="hboxBottomButtons">
<property name="visible">True</property>
<property name="layout_style">GTK_BUTTONBOX_END</property>
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="btnSelectAll">
<property name="label" translatable="yes">Select All</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Select All</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="focus_on_click">True</property>
<signal handler="on_btnSelectAll_clicked" name="clicked"/>
<signal name="clicked" handler="on_btnSelectAll_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btnSelectNone">
<property name="label" translatable="yes">Select None</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Select None</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="focus_on_click">True</property>
<signal handler="on_btnSelectNone_clicked" name="clicked"/>
<signal name="clicked" handler="on_btnSelectNone_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btnCancel">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_default">True</property>
<property name="has_default">True</property>
<property name="can_focus">True</property>
<property name="has_focus">True</property>
<property name="label">gtk-cancel</property>
<property name="can_default">True</property>
<property name="has_default">True</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
<property name="focus_on_click">True</property>
<signal handler="on_btnCancel_clicked" name="clicked"/>
<signal name="clicked" handler="on_btnCancel_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btnOK">
<property name="label">gtk-add</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-add</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
<property name="focus_on_click">True</property>
<signal handler="on_btnOK_clicked" name="clicked"/>
<signal name="clicked" handler="on_btnOK_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">GTK_PACK_END</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkNotebook" id="notebookChannelAdder">
<object class="GtkHPaned" id="hpaned">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="show_tabs">True</property>
<property name="show_border">True</property>
<property name="tab_pos">GTK_POS_TOP</property>
<property name="scrollable">False</property>
<property name="enable_popup">False</property>
<child>
<object class="GtkVBox" id="vboxOpmlImport">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<child>
<object class="GtkHBox" id="hboxOpmlUrlEntry">
<property name="border_width">5</property>
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkEntry" id="entryURL">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="max_length">0</property>
<property name="has_frame">True</property>
<property name="invisible_char">●</property>
<property name="activates_default">False</property>
<signal handler="on_btnDownloadOpml_clicked" name="activate"/>
<signal handler="on_entryURL_changed" name="changed"/>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btnDownloadOpml">
<property name="label" translatable="yes">Download</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">True</property>
<signal handler="on_btnDownloadOpml_clicked" name="clicked"/>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow5">
<property name="border_width">5</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="shadow_type">GTK_SHADOW_IN</property>
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
<child>
<object class="GtkTreeView" id="treeviewChannelChooser">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="headers_visible">False</property>
<property name="rules_hint">False</property>
<property name="reorderable">False</property>
<property name="enable_search">True</property>
<property name="fixed_height_mode">False</property>
<property name="hover_selection">False</property>
<property name="hover_expand">False</property>
</object>
</child>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
</object>
<packing>
<property name="tab_expand">False</property>
<property name="tab_fill">True</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel" id="label138">
<property name="visible">True</property>
<property name="label" translatable="yes">_OPML/Search</property>
<property name="use_underline">True</property>
<property name="use_markup">False</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
</object>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow9">
<property name="border_width">5</property>
<object class="GtkScrolledWindow" id="sw_providers">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="shadow_type">GTK_SHADOW_IN</property>
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
<property name="hscrollbar_policy">never</property>
<property name="vscrollbar_policy">automatic</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="treeviewTopPodcastsChooser">
<object class="GtkTreeView" id="tv_providers">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="headers_visible">False</property>
<property name="rules_hint">False</property>
<property name="reorderable">False</property>
<property name="enable_search">True</property>
<property name="fixed_height_mode">False</property>
<property name="hover_selection">False</property>
<property name="hover_expand">False</property>
<property name="enable_search">False</property>
<signal name="row-activated" handler="on_tv_providers_row_activated" swapped="no"/>
<signal name="cursor-changed" handler="on_tv_providers_cursor_changed" swapped="no"/>
</object>
</child>
</object>
<packing>
<property name="tab_expand">False</property>
<property name="tab_fill">True</property>
<property name="resize">False</property>
<property name="shrink">True</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel" id="label139">
<property name="visible">True</property>
<property name="label" translatable="yes">Top _podcasts</property>
<property name="use_underline">True</property>
<property name="use_markup">False</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
</object>
</child>
<child>
<object class="GtkVBox" id="vbox46">
<object class="GtkVBox" id="vb_podcasts">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkHBox" id="hbox43">
<property name="border_width">5</property>
<object class="GtkHBox" id="hb_text_entry">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkEntry" id="entryYoutubeSearch">
<object class="GtkLabel" id="lb_search">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="max_length">0</property>
<property name="has_frame">True</property>
<property name="invisible_char">●</property>
<property name="activates_default">False</property>
<signal handler="on_entryYoutubeSearch_key_press_event" name="key_press_event"/>
<property name="can_focus">False</property>
<property name="label" translatable="yes">label</property>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btnSearchYouTube">
<object class="GtkEntry" id="en_query">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="true">Search</property>
<property name="use_underline">True</property>
<property name="focus_on_click">True</property>
<signal handler="on_btnSearchYouTube_clicked" name="clicked"/>
<property name="invisible_char">•</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<signal name="activate" handler="on_bt_search_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="bt_search">
<property name="label" translatable="yes">...</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_bt_search_clicked" swapped="no"/>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow10">
<property name="border_width">5</property>
<object class="GtkScrolledWindow" id="sw_tagcloud">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="shadow_type">GTK_SHADOW_IN</property>
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<child>
<object class="GtkTreeView" id="treeviewYouTubeChooser">
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="sw_podcasts">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
<property name="vscrollbar_policy">automatic</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="tv_podcasts">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="headers_visible">False</property>
<property name="rules_hint">False</property>
<property name="reorderable">False</property>
<property name="enable_search">True</property>
<property name="fixed_height_mode">False</property>
<property name="hover_selection">False</property>
<property name="hover_expand">False</property>
</object>
</child>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="tab_expand">False</property>
<property name="tab_fill">True</property>
<property name="resize">True</property>
<property name="shrink">True</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel" id="label140">
<property name="visible">True</property>
<property name="label" translatable="yes">_YouTube</property>
<property name="use_underline">True</property>
<property name="use_markup">False</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
</object>
</child>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>

208
src/gpodder/directory.py Normal file
View File

@ -0,0 +1,208 @@
# -*- coding: utf-8 -*-
#
# gPodder - A media aggregator and podcast client
# Copyright (c) 2005-2014 Thomas Perl and the gPodder Team
#
# gPodder 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 3 of the License, or
# (at your option) any later version.
#
# gPodder 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, see <http://www.gnu.org/licenses/>.
#
#
# gpodder.directory - Podcast directory and search providers
# Thomas Perl <thp@gpodder.org>; 2014-10-22
#
import gpodder
_ = gpodder.gettext
import urllib
import json
import os
from gpodder import opml
class DirectoryEntry(object):
def __init__(self, title, url, image=None, subscribers=-1, description=None):
self.title = title
self.url = url
self.image = image
self.subscribers = subscribers
self.description = description
class DirectoryTag(object):
def __init__(self, tag, weight):
self.tag = tag
self.weight = weight
class Provider(object):
PROVIDER_SEARCH, PROVIDER_URL, PROVIDER_FILE, PROVIDER_TAGCLOUD, PROVIDER_STATIC = range(5)
def __init__(self):
self.name = ''
self.kind = self.PROVIDER_SEARCH
self.icon = None
def on_search(self, query):
# Should return a list of DirectoryEntry objects
raise NotImplemented()
def on_url(self, url):
# Should return a list of DirectoryEntry objects
raise NotImplemented()
def on_file(self, filename):
# Should return a list of DirectoryEntry objects
raise NotImplemented()
def on_tag(self, tag):
# Should return a list of DirectoryEntry objects
raise NotImplemented()
def on_static(self):
# Should return a list of DirectoryEntry objects
raise NotImplemented()
def get_tags(self):
# Should return a list of DirectoryTag objects
raise NotImplemented()
def directory_entry_from_opml(url):
return [DirectoryEntry(d['title'], d['url'], description=d['description']) for d in opml.Importer(url).items]
def directory_entry_from_mygpo_json(url):
return [DirectoryEntry(d['title'], d['url'], d['logo_url'], d['subscribers'], d['description'])
for d in json.load(urllib.urlopen(url))]
class GPodderNetSearchProvider(Provider):
def __init__(self):
self.name = _('gpodder.net search')
self.kind = Provider.PROVIDER_SEARCH
self.icon = 'directory-gpodder.png'
def on_search(self, query):
return directory_entry_from_mygpo_json('http://gpodder.net/search.json?q=' + urllib.quote(query))
class OpmlWebImportProvider(Provider):
def __init__(self):
self.name = _('OPML from web')
self.kind = Provider.PROVIDER_URL
self.icon = 'directory-opml.png'
def on_url(self, url):
return directory_entry_from_opml(url)
class OpmlFileImportProvider(Provider):
def __init__(self):
self.name = _('OPML file')
self.kind = Provider.PROVIDER_FILE
self.icon = 'directory-opml.png'
def on_file(self, filename):
return directory_entry_from_opml(filename)
class GPodderRecommendationsProvider(Provider):
def __init__(self):
self.name = _('Getting started')
self.kind = Provider.PROVIDER_STATIC
self.icon = 'directory-examples.png'
def on_static(self):
return directory_entry_from_opml('http://gpodder.org/directory.opml')
class GPodderNetToplistProvider(Provider):
def __init__(self):
self.name = _('gpodder.net Top 50')
self.kind = Provider.PROVIDER_STATIC
self.icon = 'directory-toplist.png'
def on_static(self):
return directory_entry_from_mygpo_json('http://gpodder.net/toplist/50.json')
class GPodderNetTagsProvider(Provider):
def __init__(self):
self.name = _('gpodder.net Tags')
self.kind = Provider.PROVIDER_TAGCLOUD
self.icon = 'directory-tags.png'
def on_tag(self, tag):
return directory_entry_from_mygpo_json('http://gpodder.net/api/2/tag/%s/50.json' % urllib.quote(tag))
def get_tags(self):
return [DirectoryTag(d['tag'], d['usage']) for d in json.load(urllib.urlopen('http://gpodder.net/api/2/tags/40.json'))]
class YouTubeSearchProvider(Provider):
def __init__(self):
self.name = _('YouTube search')
self.kind = Provider.PROVIDER_SEARCH
self.icon = 'directory-youtube.png'
def on_search(self, query):
url = 'http://gdata.youtube.com/feeds/api/videos?alt=json&q=%s' % urllib.quote(query)
data = json.load(urllib.urlopen(url))
result = []
seen_users = set()
for entry in data['feed']['entry']:
user = os.path.basename(entry['author'][0]['uri']['$t'])
title = entry['title']['$t']
url = 'http://www.youtube.com/rss/user/%s/videos.rss' % user
if user not in seen_users:
result.append(DirectoryEntry(user, url))
seen_users.add(user)
return result
class SoundcloudSearchProvider(Provider):
def __init__(self):
self.name = _('Soundcloud search')
self.kind = Provider.PROVIDER_SEARCH
self.icon = 'directory-soundcloud.png'
def on_search(self, query):
# XXX: This cross-import of the plugin here is bad, but it
# works for now (no proper plugin architecture...)
from gpodder.plugins.soundcloud import search_for_user
return [DirectoryEntry(entry['username'], entry['permalink_url']) for entry in search_for_user(query)]
class FixedOpmlFileProvider(Provider):
def __init__(self, filename):
self.name = _('Imported OPML file')
self.kind = Provider.PROVIDER_STATIC
self.icon = 'directory-opml.png'
self.filename = filename
def on_static(self):
return directory_entry_from_opml(self.filename)
PROVIDERS = [
GPodderRecommendationsProvider,
None,
GPodderNetSearchProvider,
GPodderNetToplistProvider,
#GPodderNetTagsProvider,
None,
OpmlWebImportProvider,
#OpmlFileImportProvider,
None,
YouTubeSearchProvider,
SoundcloudSearchProvider,
]

View File

@ -17,184 +17,289 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#
# gpodder.gtkui.desktop.podcastdirectory - Podcast directory Gtk UI
# Thomas Perl <thp@gpodder.org>; 2014-10-22
#
import gtk
import pango
import urllib
import os.path
import cgi
import os
import gpodder
_ = gpodder.gettext
from gpodder import util
from gpodder import opml
from gpodder import youtube
from gpodder import my
import logging
logger = logging.getLogger(__name__)
from gpodder.gtkui.opml import OpmlListModel
from gpodder import util
from gpodder import directory
from gpodder.gtkui.interface.common import BuilderWidget
from gpodder.gtkui.interface.progress import ProgressIndicator
from gpodder.gtkui.interface.tagcloud import TagCloud
class DirectoryPodcastsModel(gtk.ListStore):
C_SELECTED, C_MARKUP, C_TITLE, C_URL = range(4)
def __init__(self, callback_can_subscribe):
gtk.ListStore.__init__(self, bool, str, str, str)
self.callback_can_subscribe = callback_can_subscribe
def load(self, directory_entries):
self.clear()
for entry in directory_entries:
if entry.subscribers != -1:
self.append((False, '%s (%d)\n<small>%s</small>' % (cgi.escape(entry.title),
entry.subscribers, cgi.escape(entry.url)), entry.title, entry.url))
else:
self.append((False, '%s\n<small>%s</small>' % (cgi.escape(entry.title),
cgi.escape(entry.url)), entry.title, entry.url))
self.callback_can_subscribe(len(self.get_selected_podcasts()) > 0)
def toggle(self, path):
self[path][self.C_SELECTED] = not self[path][self.C_SELECTED]
self.callback_can_subscribe(len(self.get_selected_podcasts()) > 0)
def set_selection_to(self, selected):
for row in self:
row[self.C_SELECTED] = selected
self.callback_can_subscribe(len(self.get_selected_podcasts()) > 0)
def get_selected_podcasts(self):
return [(row[self.C_TITLE], row[self.C_URL]) for row in self if row[self.C_SELECTED]]
class DirectoryProvidersModel(gtk.ListStore):
C_WEIGHT, C_TEXT, C_ICON, C_PROVIDER = range(4)
SEPARATOR = (pango.WEIGHT_NORMAL, '', None, None)
def __init__(self, providers):
gtk.ListStore.__init__(self, int, str, gtk.gdk.Pixbuf, object)
for provider in providers:
self.add_provider(provider() if provider else None)
def add_provider(self, provider):
if provider is None:
self.append(self.SEPARATOR)
else:
try:
pixbuf = gtk.gdk.pixbuf_new_from_file(os.path.join(gpodder.images_folder, provider.icon)) if provider.icon else None
except Exception as e:
logger.warn('Could not load icon: %s (%s)', provider.icon or '-', e)
pixbuf = None
self.append((pango.WEIGHT_NORMAL, provider.name, pixbuf, provider))
def is_row_separator(self, model, it):
return self.get_value(it, self.C_PROVIDER) is None
class gPodderPodcastDirectory(BuilderWidget):
def new(self):
if hasattr(self, 'custom_title'):
self.gPodderPodcastDirectory.set_title(self.custom_title)
if hasattr(self, 'hide_url_entry'):
self.hboxOpmlUrlEntry.hide_all()
new_parent = self.notebookChannelAdder.get_parent()
new_parent.remove(self.notebookChannelAdder)
self.vboxOpmlImport.reparent(new_parent)
self.main_window.set_title(self.custom_title)
if not hasattr(self, 'add_podcast_list'):
self.add_podcast_list = None
self.setup_treeview(self.treeviewChannelChooser)
self.setup_treeview(self.treeviewTopPodcastsChooser)
self.setup_treeview(self.treeviewYouTubeChooser)
self.providers_model = DirectoryProvidersModel(directory.PROVIDERS)
self.podcasts_model = DirectoryPodcastsModel(self.on_can_subscribe_changed)
self.current_provider = None
self.podcasts_progress_indicator = None
self.notebookChannelAdder.connect('switch-page', lambda a, b, c: self.on_change_tab(c))
self.setup_providers_treeview()
self.setup_podcasts_treeview()
self.setup_tag_cloud()
def on_entryURL_changed(self, entry):
url = entry.get_text()
if self.is_search_term(url):
self.btnDownloadOpml.set_label(_('Search'))
selection = self.tv_providers.get_selection()
selection.select_path((0,))
self.on_tv_providers_row_activated(self.tv_providers, (0,), None)
self.main_window.show()
def download_opml_file(self, filename):
self.providers_model.add_provider(directory.FixedOpmlFileProvider(filename))
self.tv_providers.set_cursor(len(self.providers_model)-1)
def setup_podcasts_treeview(self):
column = gtk.TreeViewColumn('')
cell = gtk.CellRendererToggle()
column.pack_start(cell, False)
cell.set_activatable(True)
column.add_attribute(cell, 'active', DirectoryPodcastsModel.C_SELECTED)
cell.connect('toggled', lambda cell, path: self.podcasts_model.toggle(path))
self.tv_podcasts.append_column(column)
column = gtk.TreeViewColumn('')
cell = gtk.CellRendererText()
cell.set_property('ellipsize', pango.ELLIPSIZE_END)
column.pack_start(cell)
column.add_attribute(cell, 'markup', DirectoryPodcastsModel.C_MARKUP)
self.tv_podcasts.append_column(column)
self.tv_podcasts.set_model(self.podcasts_model)
self.podcasts_model.append((False, 'a', 'b', 'c'))
def setup_providers_treeview(self):
column = gtk.TreeViewColumn('')
cell = gtk.CellRendererPixbuf()
column.pack_start(cell, False)
column.add_attribute(cell, 'pixbuf', DirectoryProvidersModel.C_ICON)
cell = gtk.CellRendererText()
#cell.set_property('ellipsize', pango.ELLIPSIZE_END)
column.pack_start(cell)
column.add_attribute(cell, 'text', DirectoryProvidersModel.C_TEXT)
column.add_attribute(cell, 'weight', DirectoryProvidersModel.C_WEIGHT)
self.tv_providers.append_column(column)
self.tv_providers.set_row_separator_func(self.providers_model.is_row_separator)
self.tv_providers.set_model(self.providers_model)
def setup_tag_cloud(self):
self.tag_cloud = TagCloud()
self.tag_cloud.set_size_request(-1, 130)
self.tag_cloud.show_all()
self.sw_tagcloud.add(self.tag_cloud)
self.tag_cloud.connect('selected', self.on_tag_selected)
def on_tag_selected(self, tag_cloud, tag):
self.obtain_podcasts_with(lambda: self.current_provider.on_tag(tag))
def on_tv_providers_row_activated(self, treeview, path, column):
it = self.providers_model.get_iter(path)
for row in self.providers_model:
row[DirectoryProvidersModel.C_WEIGHT] = pango.WEIGHT_NORMAL
if it:
self.providers_model.set_value(it, DirectoryProvidersModel.C_WEIGHT, pango.WEIGHT_BOLD)
provider = self.providers_model.get_value(it, DirectoryProvidersModel.C_PROVIDER)
self.use_provider(provider)
def use_provider(self, provider):
self.podcasts_model.clear()
self.current_provider = provider
if provider.kind == directory.Provider.PROVIDER_SEARCH:
self.lb_search.set_text('Search:')
self.bt_search.set_label('Search')
elif provider.kind == directory.Provider.PROVIDER_URL:
self.lb_search.set_text('URL:')
self.bt_search.set_label('Download')
elif provider.kind == directory.Provider.PROVIDER_FILE:
self.lb_search.set_text('Filename:')
self.bt_search.set_label('Open')
elif provider.kind == directory.Provider.PROVIDER_TAGCLOUD:
self.tag_cloud.clear_tags()
@util.run_in_background
def load_tags():
try:
tags = [(t.tag, t.weight) for t in provider.get_tags()]
except Exception as e:
logger.warn('Got exception while loading tags: %s', e)
tags = []
@util.idle_add
def update_ui():
self.tag_cloud.set_tags(tags)
elif provider.kind == directory.Provider.PROVIDER_STATIC:
self.obtain_podcasts_with(provider.on_static)
if provider.kind in (directory.Provider.PROVIDER_SEARCH,
directory.Provider.PROVIDER_URL,
directory.Provider.PROVIDER_FILE):
self.en_query.set_text('')
self.hb_text_entry.show()
util.idle_add(self.en_query.grab_focus)
else:
self.btnDownloadOpml.set_label(_('Download'))
self.hb_text_entry.hide()
def setup_treeview(self, tv):
togglecell = gtk.CellRendererToggle()
togglecell.set_property( 'activatable', True)
togglecell.connect( 'toggled', self.callback_edited)
togglecolumn = gtk.TreeViewColumn( '', togglecell, active=OpmlListModel.C_SELECTED)
togglecolumn.set_min_width(40)
self.sw_tagcloud.set_visible(provider.kind == directory.Provider.PROVIDER_TAGCLOUD)
titlecell = gtk.CellRendererText()
titlecell.set_property('ellipsize', pango.ELLIPSIZE_END)
titlecolumn = gtk.TreeViewColumn(_('Podcast'), titlecell, markup=OpmlListModel.C_DESCRIPTION_MARKUP)
for itemcolumn in (togglecolumn, titlecolumn):
tv.append_column(itemcolumn)
def on_tv_providers_cursor_changed(self, treeview):
path, column = treeview.get_cursor()
self.on_tv_providers_row_activated(treeview, path, column)
def callback_edited( self, cell, path):
model = self.get_treeview().get_model()
model[path][OpmlListModel.C_SELECTED] = not model[path][OpmlListModel.C_SELECTED]
self.btnOK.set_sensitive(bool(len(self.get_selected_channels())))
def obtain_podcasts_with(self, callback):
if self.podcasts_progress_indicator is not None:
self.podcasts_progress_indicator.on_finished()
def get_selected_channels(self, tab=None):
channels = []
self.podcasts_progress_indicator = ProgressIndicator(_('Loading podcasts'),
_('Please wait while the podcast list is downloaded'),
parent=self.main_window)
model = self.get_treeview(tab).get_model()
if model is not None:
for row in model:
if row[OpmlListModel.C_SELECTED]:
channels.append((row[OpmlListModel.C_TITLE],
row[OpmlListModel.C_URL]))
original_provider = self.current_provider
return channels
self.en_query.set_sensitive(False)
self.bt_search.set_sensitive(False)
self.tag_cloud.set_sensitive(False)
self.podcasts_model.clear()
def on_change_tab(self, tab):
self.btnOK.set_sensitive( bool(len(self.get_selected_channels(tab))))
@util.run_in_background
def download_data():
try:
podcasts = callback()
except Exception as e:
logger.warn('Got exception while loading podcasts: %s', e)
podcasts = []
def thread_finished(self, model, tab=0):
if tab == 1:
tv = self.treeviewTopPodcastsChooser
elif tab == 2:
tv = self.treeviewYouTubeChooser
self.entryYoutubeSearch.set_sensitive(True)
self.btnSearchYouTube.set_sensitive(True)
self.btnOK.set_sensitive(False)
else:
tv = self.treeviewChannelChooser
self.btnDownloadOpml.set_sensitive(True)
self.entryURL.set_sensitive(True)
@util.idle_add
def update_ui():
if self.podcasts_progress_indicator is not None:
self.podcasts_progress_indicator.on_finished()
self.podcasts_progress_indicator = None
tv.set_model(model)
tv.set_sensitive(True)
if original_provider != self.current_provider:
logger.warn('Ignoring update from old thread')
return
def is_search_term(self, url):
return ('://' not in url and not os.path.exists(url))
self.podcasts_model.load(podcasts or [])
self.en_query.set_sensitive(True)
self.bt_search.set_sensitive(True)
self.tag_cloud.set_sensitive(True)
self.en_query.grab_focus()
def thread_func(self, tab=0):
if tab == 1:
model = OpmlListModel(opml.Importer(my.TOPLIST_OPML))
if len(model) == 0:
self.notification(_('The specified URL does not provide any valid OPML podcast items.'), _('No feeds found'))
elif tab == 2:
model = OpmlListModel(youtube.find_youtube_channels(self.entryYoutubeSearch.get_text()))
if len(model) == 0:
self.notification(_('There are no YouTube channels that would match this query.'), _('No channels found'))
else:
url = self.entryURL.get_text()
if self.is_search_term(url):
url = 'http://gpodder.net/search.opml?q=' + urllib.quote(url)
model = OpmlListModel(opml.Importer(url))
if len(model) == 0:
self.notification(_('The specified URL does not provide any valid OPML podcast items.'), _('No feeds found'))
def on_bt_search_clicked(self, widget):
if self.current_provider is None:
return
util.idle_add(self.thread_finished, model, tab)
def download_opml_file(self, url):
self.entryURL.set_text(url)
self.btnDownloadOpml.set_sensitive(False)
self.entryURL.set_sensitive(False)
self.btnOK.set_sensitive(False)
self.treeviewChannelChooser.set_sensitive(False)
util.run_in_background(self.thread_func)
util.run_in_background(lambda: self.thread_func(1))
query = self.en_query.get_text()
def select_all( self, value ):
enabled = False
model = self.get_treeview().get_model()
if model is not None:
for row in model:
row[OpmlListModel.C_SELECTED] = value
if value:
enabled = True
self.btnOK.set_sensitive(enabled)
@self.obtain_podcasts_with
def load_data():
if self.current_provider.kind == directory.Provider.PROVIDER_SEARCH:
return self.current_provider.on_search(query)
elif self.current_provider.kind == directory.Provider.PROVIDER_URL:
return self.current_provider.on_url(query)
elif self.current_provider.kind == directory.Provider.PROVIDER_FILE:
return self.current_provider.on_file(query)
def on_gPodderPodcastDirectory_destroy(self, widget, *args):
pass
def on_btnDownloadOpml_clicked(self, widget, *args):
self.download_opml_file(self.entryURL.get_text())
def on_btnSearchYouTube_clicked(self, widget, *args):
self.entryYoutubeSearch.set_sensitive(False)
self.treeviewYouTubeChooser.set_sensitive(False)
self.btnSearchYouTube.set_sensitive(False)
util.run_in_background(lambda: self.thread_func(2))
def on_can_subscribe_changed(self, can_subscribe):
self.btnOK.set_sensitive(can_subscribe)
def on_btnSelectAll_clicked(self, widget, *args):
self.select_all(True)
self.podcasts_model.set_selection_to(True)
def on_btnSelectNone_clicked(self, widget, *args):
self.select_all(False)
self.podcasts_model.set_selection_to(False)
def on_btnOK_clicked(self, widget, *args):
channels = self.get_selected_channels()
self.gPodderPodcastDirectory.destroy()
urls = self.podcasts_model.get_selected_podcasts()
self.main_window.destroy()
# add channels that have been selected
if self.add_podcast_list is not None:
self.add_podcast_list(channels)
self.add_podcast_list(urls)
def on_btnCancel_clicked(self, widget, *args):
self.gPodderPodcastDirectory.destroy()
def on_entryYoutubeSearch_key_press_event(self, widget, event):
if event.keyval == gtk.keysyms.Return:
self.on_btnSearchYouTube_clicked(widget)
def get_treeview(self, tab=None):
if tab is None:
tab = self.notebookChannelAdder.get_current_page()
if tab == 0:
return self.treeviewChannelChooser
elif tab == 1:
return self.treeviewTopPodcastsChooser
else:
return self.treeviewYouTubeChooser
self.main_window.destroy()

View File

@ -1,80 +1,69 @@
# -*- coding: utf-8 -*-
#
# gPodder - A media aggregator and podcast client
# Copyright (c) 2005-2014 Thomas Perl and the gPodder Team
#
# gPodder 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 3 of the License, or
# (at your option) any later version.
#
# gPodder 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, see <http://www.gnu.org/licenses/>.
#
import gtk
import gobject
import cgi
tags = (
('Electronica', 5),
('Reggae', 5),
('Electro', 20),
('Detroit Techno', 4),
('Funk', 14),
('Jazz', 4),
('Minimal', 20),
('Soulful Drum and Bass', 6),
('Dub', 7),
('Drum and Bass', 23),
('Deep Techno', 7),
('Deephouse', 27),
('Soulful', 9),
('Minimal Techno', 30),
('Downtempo', 17),
('House', 29),
('Dubstep', 14),
('Techno', 32),
('Electrotech', 8),
('Techhouse', 28),
('Disco', 15),
('Downbeat', 28),
('Electrohouse', 14),
('Hiphop', 25),
('Trance', 6),
('Freestyle', 14),
('Funky House', 3),
('Minimal House', 4),
('Nu Jazz', 11),
('Chill-Out', 6),
('Breaks', 10),
('UK Garage', 4),
('Soul', 10),
('Progressive House', 3),
('Lounge', 6),
)
class TagCloud(gtk.Layout):
__gsignals__ = {
'selected': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
(gobject.TYPE_STRING,))
}
def __init__(self, tags, min_size=20, max_size=36):
def __init__(self, min_size=20, max_size=36):
self.__gobject_init__()
gtk.Layout.__init__(self)
self._tags = tags
self._min_weight = 0
self._max_weight = 0
self._min_size = min_size
self._max_size = max_size
self._min_weight = min(weight for tag, weight in self._tags)
self._max_weight = max(weight for tag, weight in self._tags)
self._size = 0, 0
self._alloc_id = self.connect('size-allocate', self._on_size_allocate)
self._init_tags()
self._in_relayout = False
def clear_tags(self):
for child in self.get_children():
self.remove(child)
def set_tags(self, tags):
tags = list(tags)
self._min_weight = min(weight for tag, weight in tags)
self._max_weight = max(weight for tag, weight in tags)
for tag, weight in tags:
label = gtk.Label()
markup = '<span size="%d">%s</span>' % (1000*self._scale(weight), cgi.escape(tag))
label.set_markup(markup)
button = gtk.ToolButton(label)
button.connect('clicked', lambda b, t: self.emit('selected', t), tag)
self.put(button, 1, 1)
button.show_all()
self.relayout()
def _on_size_allocate(self, widget, allocation):
self._size = (allocation.width, allocation.height)
if not self._in_relayout:
self.relayout()
def _init_tags(self):
for tag, weight in self._tags:
label = gtk.Label()
markup = '<span size="%d">%s</span>' % (1000*self._scale(weight), cgi.escape(tag))
label.set_markup(markup)
button = gtk.ToolButton(label)
button.connect('clicked', lambda b: self.emit('selected', tag))
self.put(button, 1, 1)
def _scale(self, weight):
weight_range = float(self._max_weight-self._min_weight)
ratio = float(weight-self._min_weight)/weight_range
@ -110,32 +99,5 @@ class TagCloud(gtk.Layout):
self._in_relayout = False
return False
gobject.idle_add(unrelayout)
gobject.type_register(TagCloud)
if __name__ == '__main__':
l = TagCloud(tags)
try:
import hildon
w = hildon.StackableWindow()
sw = hildon.PannableArea()
except:
w = gtk.Window()
w.set_default_size(600, 300)
sw = gtk.ScrolledWindow()
sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
w.set_title('Tag cloud Demo')
w.add(sw)
sw.add(l)
def on_tag_selected(cloud, tag):
print 'tag selected:', tag
l.connect('selected', on_tag_selected)
w.show_all()
w.connect('destroy', gtk.main_quit)
gtk.main()

View File

@ -3123,9 +3123,9 @@ class gPodder(BuilderWidget, dbus.service.Object):
dlg.destroy()
def on_itemImportChannels_activate(self, widget, *args):
dir = gPodderPodcastDirectory(self.main_window, _config=self.config, \
self._podcast_directory = gPodderPodcastDirectory(self.main_window,
_config=self.config,
add_podcast_list=self.add_podcast_list)
util.idle_add(dir.download_opml_file, my.EXAMPLES_OPML)
def on_homepage_activate(self, widget, *args):
util.open_website(gpodder.__url__)

View File

@ -1,44 +0,0 @@
# -*- coding: utf-8 -*-
#
# gPodder - A media aggregator and podcast client
# Copyright (c) 2005-2014 Thomas Perl and the gPodder Team
#
# gPodder 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 3 of the License, or
# (at your option) any later version.
#
# gPodder 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, see <http://www.gnu.org/licenses/>.
#
#
# gpodder.gtkui.opml - Module for displaying OPML feeds (2009-08-13)
#
import gtk
import cgi
import urllib
class OpmlListModel(gtk.ListStore):
C_SELECTED, C_TITLE, C_DESCRIPTION_MARKUP, C_URL = range(4)
def __init__(self, importer):
gtk.ListStore.__init__(self, bool, str, str, str)
for channel in importer.items:
self.append([False, channel['title'],
self._format_channel(channel), channel['url']])
def _format_channel(self, channel):
title = cgi.escape(urllib.unquote_plus(channel['title']))
description = cgi.escape(channel['description'])
return '<b>%s</b>\n%s' % (title, description)

View File

@ -72,8 +72,6 @@ from mygpoclient import public
from mygpoclient import util as mygpoutil
EXAMPLES_OPML = 'http://gpodder.org/directory.opml'
TOPLIST_OPML = 'http://gpodder.org/toplist.opml'
EPISODE_ACTIONS_BATCH_SIZE=100
# Database model classes

View File

@ -40,6 +40,7 @@ import time
import re
import email
import urllib
# gPodder's consumer key for the Soundcloud API
@ -227,3 +228,7 @@ class SoundcloudFavFeed(SoundcloudFeed):
# Register our URL handlers
model.register_custom_handler(SoundcloudFeed)
model.register_custom_handler(SoundcloudFavFeed)
def search_for_user(query):
json_url = 'http://api.soundcloud.com/users.json?q=%s&consumer_key=%s' % (urllib.quote(query), CONSUMER_KEY)
return json.load(util.urlopen(json_url))

View File

@ -219,29 +219,3 @@ def get_real_cover(url):
return None
return for_each_feed_pattern(return_user_cover, url, None)
def find_youtube_channels(string):
url = 'http://gdata.youtube.com/feeds/api/videos?alt=json&q=%s' % urllib.quote(string, '')
data = json.load(util.urlopen(url))
class FakeImporter(object):
def __init__(self):
self.items = []
result = FakeImporter()
seen_users = set()
for entry in data['feed']['entry']:
user = os.path.basename(entry['author'][0]['uri']['$t'])
title = entry['title']['$t']
url = 'http://www.youtube.com/rss/user/%s/videos.rss' % user
if user not in seen_users:
result.items.append({
'title': user,
'url': url,
'description': title
})
seen_users.add(user)
return result

View File

@ -1 +0,0 @@
gPodder "tres" draft implementation / prototype code

View File

@ -1,201 +0,0 @@
import gtk
import gobject
import pango
import tagcloud
import json
w = gtk.Dialog()
w.set_title('Discover new podcasts')
w.set_default_size(650, 450)
tv = gtk.TreeView()
tv.set_headers_visible(False)
tv.set_size_request(160, -1)
class OpmlEdit(object): pass
class Search(object): pass
class OpmlFixed(object): pass
class TagCloud(object): pass
search_providers = (
('gpodder.net', 'search_gpodder.png', Search),
('YouTube', 'search_youtube.png', Search),
('SoundCloud', 'search_soundcloud.png', Search),
('Miro Guide', 'search_miro.png', Search),
)
directory_providers = (
('Toplist', 'directory_toplist.png', OpmlFixed),
('Examples', 'directory_example.png', OpmlFixed),
('Tag cloud', 'directory_tags.png', TagCloud),
)
SEPARATOR = (True, pango.WEIGHT_NORMAL, '', None, None)
C_SEPARATOR, C_WEIGHT, C_TEXT, C_ICON, C_PROVIDER = range(5)
store = gtk.ListStore(bool, int, str, gtk.gdk.Pixbuf, object)
opml_pixbuf = gtk.gdk.pixbuf_new_from_file('directory_opml.png')
store.append((False, pango.WEIGHT_NORMAL, 'OPML', opml_pixbuf, OpmlEdit))
store.append(SEPARATOR)
for name, icon, provider in search_providers:
pixbuf = gtk.gdk.pixbuf_new_from_file(icon)
store.append((False, pango.WEIGHT_NORMAL, name, pixbuf, provider))
store.append(SEPARATOR)
for name, icon, provider in directory_providers:
pixbuf = gtk.gdk.pixbuf_new_from_file(icon)
store.append((False, pango.WEIGHT_NORMAL, name, pixbuf, provider))
store.append(SEPARATOR)
for i in range(1, 5):
store.append((False, pango.WEIGHT_NORMAL, 'Bookmark %d' % i, None, None))
tv.set_model(store)
def is_row_separator(model, iter):
return model.get_value(iter, C_SEPARATOR)
tv.set_row_separator_func(is_row_separator)
column = gtk.TreeViewColumn('')
cell = gtk.CellRendererPixbuf()
column.pack_start(cell, False)
column.add_attribute(cell, 'pixbuf', C_ICON)
cell = gtk.CellRendererText()
column.pack_start(cell)
column.add_attribute(cell, 'text', C_TEXT)
column.add_attribute(cell, 'weight', C_WEIGHT)
tv.append_column(column)
def on_row_activated(treeview, path, column):
model = treeview.get_model()
iter = model.get_iter(path)
for row in model:
row[C_WEIGHT] = pango.WEIGHT_NORMAL
if iter:
model.set_value(iter, C_WEIGHT, pango.WEIGHT_BOLD)
provider = model.get_value(iter, C_PROVIDER)
use_provider(provider)
def on_cursor_changed(treeview):
path, column = treeview.get_cursor()
on_row_activated(treeview, path, column)
tv.connect('row-activated', on_row_activated)
tv.connect('cursor-changed', on_cursor_changed)
sw = gtk.ScrolledWindow()
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
sw.set_shadow_type(gtk.SHADOW_IN)
sw.add(tv)
sidebar = gtk.VBox()
sidebar.set_spacing(6)
sidebar.pack_start(sw, True, True)
sidebar.pack_start(gtk.Button('Add bookmark'), False, False)
vb = gtk.VBox()
vb.set_spacing(6)
title_label = gtk.Label('Title')
title_label.set_alignment(0, 0)
vb.pack_start(title_label, False, False)
search_hbox = gtk.HBox()
search_hbox.set_spacing(6)
search_label = gtk.Label('')
search_hbox.pack_start(search_label, False, False)
search_entry = gtk.Entry()
search_hbox.pack_start(search_entry, True, True)
search_button = gtk.Button('')
search_hbox.pack_start(search_button, False, False)
vb.pack_start(search_hbox, False, False)
tagcloud_sw = gtk.ScrolledWindow()
tagcloud_sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
tagcloud_sw.set_shadow_type(gtk.SHADOW_IN)
podcast_tags = json.loads("""
[
{"tag": "Technology",
"usage": 530 },
{"tag": "Society & Culture",
"usage": 420 },
{"tag": "Arts",
"usage": 400},
{"tag": "News & Politics",
"usage": 320}
]
""")
tagcloudw = tagcloud.TagCloud(list((x['tag'], x['usage']) for x in podcast_tags), 10, 14)
tagcloud_sw.set_size_request(-1, 130)
tagcloud_sw.add(tagcloudw)
vb.pack_start(tagcloud_sw, False, False)
podcasts_sw = gtk.ScrolledWindow()
podcasts_sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
podcasts_sw.set_shadow_type(gtk.SHADOW_IN)
podcasts_tv = gtk.TreeView()
podcasts_sw.add(podcasts_tv)
vb.pack_start(podcasts_sw, True, True)
hb = gtk.HBox()
hb.set_spacing(12)
hb.set_border_width(12)
hb.pack_start(sidebar, False, True)
hb.pack_start(vb, True, True)
w.vbox.add(hb)
w.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
w.add_button('Subscribe', gtk.RESPONSE_OK)
w.set_response_sensitive(gtk.RESPONSE_OK, False)
def use_provider(provider):
if provider == OpmlEdit:
search_label.set_text('URL:')
search_button.set_label('Download')
else:
search_label.set_text('Search:')
search_button.set_label('Search')
if provider in (OpmlEdit, Search):
title_label.hide()
search_hbox.show()
search_entry.set_text('')
def later():
search_entry.grab_focus()
return False
gobject.idle_add(later)
elif provider == TagCloud:
title_label.hide()
search_hbox.hide()
else:
if provider == OpmlFixed:
title_label.set_text('Example stuff')
elif provider == TagCloud:
title_label.set_text('Tag cloud')
title_label.show()
search_hbox.hide()
tagcloud_sw.set_visible(provider == TagCloud)
print 'using provider:', provider
#w.connect('destroy', gtk.main_quit)
w.show_all()
on_row_activated(tv, (0,), None)
w.run()
#gtk.main()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 973 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 766 B