307 lines
12 KiB
Python
307 lines
12 KiB
Python
# -*- coding: utf-8 -*-
|
|
#
|
|
# gPodder - A media aggregator and podcast client
|
|
# Copyright (c) 2005-2018 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.desktop.podcastdirectory - Podcast directory Gtk UI
|
|
# Thomas Perl <thp@gpodder.org>; 2014-10-22
|
|
#
|
|
|
|
|
|
import cgi
|
|
import logging
|
|
import os
|
|
|
|
from gi.repository import GdkPixbuf, Gtk, Pango
|
|
|
|
import gpodder
|
|
from gpodder import directory, util
|
|
from gpodder.gtkui.interface.common import BuilderWidget
|
|
from gpodder.gtkui.interface.progress import ProgressIndicator
|
|
from gpodder.gtkui.interface.tagcloud import TagCloud
|
|
|
|
_ = gpodder.gettext
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class DirectoryPodcastsModel(Gtk.ListStore):
|
|
C_SELECTED, C_MARKUP, C_TITLE, C_URL = list(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 = list(range(4))
|
|
|
|
SEPARATOR = (Pango.Weight.NORMAL, '', None, None)
|
|
|
|
def __init__(self, providers):
|
|
Gtk.ListStore.__init__(self, int, str, GdkPixbuf.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 = GdkPixbuf.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.main_window.set_title(self.custom_title)
|
|
|
|
if not hasattr(self, 'add_podcast_list'):
|
|
self.add_podcast_list = None
|
|
|
|
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.setup_providers_treeview()
|
|
self.setup_podcasts_treeview()
|
|
self.setup_tag_cloud()
|
|
|
|
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)
|
|
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.EllipsizeMode.END)
|
|
column.pack_start(cell, True)
|
|
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.EllipsizeMode.END)
|
|
column.pack_start(cell, True)
|
|
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.hb_text_entry.hide()
|
|
|
|
if provider.kind == directory.Provider.PROVIDER_TAGCLOUD:
|
|
self.sw_tagcloud.show()
|
|
else:
|
|
self.sw_tagcloud.hide()
|
|
|
|
def on_tv_providers_cursor_changed(self, treeview):
|
|
path, column = treeview.get_cursor()
|
|
if path is not None:
|
|
self.on_tv_providers_row_activated(treeview, path, column)
|
|
|
|
def obtain_podcasts_with(self, callback):
|
|
if self.podcasts_progress_indicator is not None:
|
|
self.podcasts_progress_indicator.on_finished()
|
|
|
|
self.podcasts_progress_indicator = ProgressIndicator(_('Loading podcasts'),
|
|
_('Please wait while the podcast list is downloaded'),
|
|
parent=self.main_window)
|
|
|
|
original_provider = self.current_provider
|
|
|
|
self.en_query.set_sensitive(False)
|
|
self.bt_search.set_sensitive(False)
|
|
self.tag_cloud.set_sensitive(False)
|
|
self.podcasts_model.clear()
|
|
|
|
@util.run_in_background
|
|
def download_data():
|
|
try:
|
|
podcasts = callback()
|
|
except Exception as e:
|
|
logger.warn('Got exception while loading podcasts: %s', e)
|
|
podcasts = []
|
|
|
|
@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
|
|
|
|
if original_provider == self.current_provider:
|
|
self.podcasts_model.load(podcasts or [])
|
|
else:
|
|
logger.warn('Ignoring update from old thread')
|
|
|
|
self.en_query.set_sensitive(True)
|
|
self.bt_search.set_sensitive(True)
|
|
self.tag_cloud.set_sensitive(True)
|
|
if self.en_query.get_realized():
|
|
self.en_query.grab_focus()
|
|
|
|
def on_bt_search_clicked(self, widget):
|
|
if self.current_provider is None:
|
|
return
|
|
|
|
query = self.en_query.get_text()
|
|
|
|
@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_can_subscribe_changed(self, can_subscribe):
|
|
self.btnOK.set_sensitive(can_subscribe)
|
|
|
|
def on_btnSelectAll_clicked(self, widget, *args):
|
|
self.podcasts_model.set_selection_to(True)
|
|
|
|
def on_btnSelectNone_clicked(self, widget, *args):
|
|
self.podcasts_model.set_selection_to(False)
|
|
|
|
def on_btnOK_clicked(self, widget, *args):
|
|
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(urls)
|
|
|
|
def on_btnCancel_clicked(self, widget, *args):
|
|
self.main_window.destroy()
|