Arquivos
coverart-browser/coverart_coverflowview.py
2014-12-03 19:32:49 +00:00

437 linhas
15 KiB
Python

# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
#
# Copyright (C) 2012 - fossfreedom
# Copyright (C) 2012 - Agustin Carrasco
#
# 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.
#
# 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.
import json
import os
from xml.sax.saxutils import escape
from gi.repository import Gdk
from gi.repository import Gtk
from gi.repository import GLib
from gi.repository import GObject
from gi.repository import Gio
from coverart_browser_prefs import GSetting
from coverart_browser_prefs import webkit_support
from coverart_widgets import AbstractView
from coverart_widgets import PanedCollapsible
import rb
class FlowShowingPolicy(GObject.Object):
'''
Policy that mostly takes care of how and when things should be showed on
the view that makes use of the `AlbumsModel`.
'''
def __init__(self, flow_view):
super(FlowShowingPolicy, self).__init__()
self._flow_view = flow_view
self.counter = 0
self._has_initialised = False
def initialise(self, album_manager):
if self._has_initialised:
return
self._has_initialised = True
self._album_manager = album_manager
self._model = album_manager.model
class CoverFlowView(AbstractView):
__gtype_name__ = "CoverFlowView"
name = 'coverflowview'
# properties
flow_background = GObject.property(type=str, default='W')
flow_automatic = GObject.property(type=bool, default=False)
flow_scale = GObject.property(type=int, default=100)
flow_hide = GObject.property(type=bool, default=False)
flow_width = GObject.property(type=int, default=600)
flow_appearance = GObject.property(type=str, default='coverflow')
flow_max = GObject.property(type=int, default=100)
panedposition = PanedCollapsible.Paned.EXPAND
def __init__(self):
super(CoverFlowView, self).__init__()
self.show_policy = FlowShowingPolicy(self)
if webkit_support():
from gi.repository import WebKit
self.view = WebKit.WebView()
else:
self.view = None
self._last_album = None
self._has_initialised = False
self._filter_changed_inprogress = False
self._on_first_use = True
def _connect_properties(self):
gs = GSetting()
settings = gs.get_setting(gs.Path.PLUGIN)
settings.bind(gs.PluginKey.FLOW_APPEARANCE, self,
'flow_appearance', Gio.SettingsBindFlags.GET)
settings.bind(gs.PluginKey.FLOW_HIDE_CAPTION, self,
'flow_hide', Gio.SettingsBindFlags.GET)
settings.bind(gs.PluginKey.FLOW_SCALE, self,
'flow_scale', Gio.SettingsBindFlags.GET)
settings.bind(gs.PluginKey.FLOW_AUTOMATIC, self,
'flow_automatic', Gio.SettingsBindFlags.GET)
settings.bind(gs.PluginKey.FLOW_BACKGROUND_COLOUR, self,
'flow_background', Gio.SettingsBindFlags.GET)
settings.bind(gs.PluginKey.FLOW_WIDTH, self,
'flow_width', Gio.SettingsBindFlags.GET)
settings.bind(gs.PluginKey.FLOW_MAX, self,
'flow_max', Gio.SettingsBindFlags.GET)
def _connect_signals(self, source):
self.connect('notify::flow-background',
self.filter_changed)
self.connect('notify::flow-scale',
self.filter_changed)
self.connect('notify::flow-hide',
self.filter_changed)
self.connect('notify::flow-width',
self.filter_changed)
self.connect('notify::flow-appearance',
self.filter_changed)
self.connect('notify::flow-max',
self.filter_changed)
def filter_changed(self, *args):
# we can get several filter_changed calls per second
# lets simplify the processing & potential flickering when the
# call to this method has slowed stopped
self._filter_changed_event = True
if self._filter_changed_inprogress:
return
self._filter_changed_inprogress = True
def filter_events(*args):
if not self._filter_changed_event:
self._filter_changed()
self._filter_changed_inprogress = False
else:
self._filter_changed_event = False
return True
Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 250, filter_events, None)
def _filter_changed(self, *args):
path = rb.find_plugin_file(self.plugin, 'coverflow/index.html')
f = open(path)
string = f.read()
f.close()
if self.flow_background == 'W':
background_colour = 'white'
if len(self.album_manager.model.store) <= self.flow_max:
foreground_colour = 'white'
else:
foreground_colour = 'black'
else:
background_colour = 'black'
if len(self.album_manager.model.store) <= self.flow_max:
foreground_colour = 'black'
else:
foreground_colour = 'white'
string = string.replace('#BACKGROUND_COLOUR', background_colour)
string = string.replace('#FOREGROUND_COLOUR', foreground_colour)
string = string.replace('#FACTOR', str(float(self.flow_scale) / 100))
if self.flow_hide:
caption = ""
else:
caption = '<div class="globalCaption"></div>'
string = string.replace('#GLOBAL_CAPTION', caption)
addon = background_colour
if self.flow_appearance == 'flow-vert':
addon += " vertical"
elif self.flow_appearance == 'carousel':
addon += " carousel"
elif self.flow_appearance == 'roundabout':
addon += " roundabout"
string = string.replace('#ADDON', addon)
string = string.replace('#WIDTH', str(self.flow_width))
identifier = self.flow.get_identifier(self.last_album)
if not identifier:
identifier = "'start'"
else:
identifier = str(identifier)
string = string.replace('#START', identifier)
#TRANSLATORS: for example 'Number of covers limited to 150'
display_message = _("Number of covers limited to %d") % self.flow_max
string = string.replace('#MAXCOVERS',
'<p>' + display_message + '</p>')
items = self.flow.initialise(self.album_manager.model, self.flow_max)
string = string.replace('#ITEMS', items)
base = os.path.dirname(path) + "/"
Gdk.threads_enter()
print(string)
self.view.load_string(string, "text/html", "UTF-8", "file://" + base)
Gdk.threads_leave()
if self._on_first_use:
self._on_first_use = False
Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 250,
self.source.show_hide_pane, (self.last_album, PanedCollapsible.Paned.EXPAND))
def get_view_icon_name(self):
return "flowview.png"
def initialise(self, source):
if self._has_initialised:
return
self._has_initialised = True
super(CoverFlowView, self).initialise(source)
self.album_manager = source.album_manager
self.ext_menu_pos = 6
self._connect_properties()
self._connect_signals(source)
# lets check that all covers have finished loading before
# initialising the flowcontrol and other signals
if not self.album_manager.cover_man.has_finished_loading:
self.album_manager.cover_man.connect('load-finished', self._covers_loaded)
else:
self._covers_loaded()
def _covers_loaded(self, *args):
self.flow = FlowControl(self)
self.view.connect("notify::title", self.flow.receive_message_signal)
#self.album_manager.model.connect('album-updated', self.flow.update_album, self.view)
#self.album_manager.model.connect('visual-updated', self.flow.update_album, self.view)
self.album_manager.model.connect('album-updated', self.filter_changed)
self.album_manager.model.connect('visual-updated', self.filter_changed)
self.album_manager.model.connect('filter-changed', self.filter_changed)
self.filter_changed()
@property
def last_album(self):
return self._last_album
@last_album.setter
def last_album(self, new_album):
if self._last_album != new_album:
self._last_album = new_album
self.source.click_count = 0
self.selectionchanged_callback()
def item_rightclicked_callback(self, album):
self.last_album = album
self.popup.popup(self.source, 'popup_menu', 3, Gtk.get_current_event_time())
def item_clicked_callback(self, album):
'''
Callback called when the user clicks somewhere on the flow_view.
Along with source "show_hide_pane", takes care of showing/hiding the bottom
pane after a second click on a selected album.
'''
# to expand the entry view
if self.flow_automatic:
self.source.click_count += 1
self.last_album = album
if self.source.click_count == 1:
Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 250,
self.source.show_hide_pane, album)
def item_activated_callback(self, album):
'''
Callback called when the flow view is double clicked. It plays the selected album
'''
self.last_album = album
self.source.play_selected_album()
return True
def item_drop_callback(self, album, webpath):
'''
Callback called when something is dropped onto the flow view - hopefully a webpath
to a picture
'''
print("item_drop_callback %s" % webpath)
print("dropped on album %s" % album)
self.album_manager.cover_man.update_cover(album, uri=webpath)
def get_selected_objects(self):
if self.last_album:
return [self.last_album]
else:
return []
def select_and_scroll_to_path(self, path):
album = self.source.album_manager.model.get_from_path(path)
self.flow.scroll_to_album(album, self.view)
self.item_clicked_callback(album)
def switch_to_view(self, source, album):
self.initialise(source)
self.show_policy.initialise(source.album_manager)
self.last_album = album
self.scroll_to_album(self.last_album)
def grab_focus(self):
self.view.grab_focus()
def scroll_to_album(self, album):
self.flow.scroll_to_album(album, self.view)
class FlowControl(object):
def __init__(self, callback_view):
self.callback_view = callback_view
self.album_identifier = {}
def get_identifier(self, album):
index = -1
for row in self.album_identifier:
if self.album_identifier[row] == album:
index = row
break
if index == -1:
return None
else:
return row
def update_album(self, model, album_path, album_iter, webview):
album = model.get_from_path(album_path)
index = -1
for row in self.album_identifier:
if self.album_identifier[row] == album:
index = row
break
if index == -1:
return
obj = {}
obj['filename'] = album.cover.original
obj['title'] = album.artist
obj['caption'] = album.name
obj['identifier'] = str(index)
webview.execute_script("update_album('%s')" % json.dumps(obj))
def receive_message_signal(self, webview, param):
# this will be key to passing stuff back and forth - need
# to develop some-sort of message protocol to distinguish "events"
title = webview.get_title()
if (not title) or (title == '"clear"'):
return
args = json.loads(title)
try:
signal = args["signal"]
except:
print("unhandled: %s " % title)
return
if signal == 'clickactive':
self.callback_view.item_clicked_callback(self.album_identifier[int(args['param'][0])])
elif signal == 'rightclickactive':
self.callback_view.item_rightclicked_callback(
self.album_identifier[int(args['param'][0])])
elif signal == 'doubleclickactive':
self.callback_view.item_activated_callback(self.album_identifier[int(args['param'][0])])
elif signal == 'dropactive':
self.callback_view.item_drop_callback(self.album_identifier[int(args['param'][0])],
args['param'][1])
else:
print("unhandled signal: %s" % signal)
def scroll_to_album(self, album, webview):
for row in self.album_identifier:
if self.album_identifier[row] == album:
webview.execute_script("scroll_to_identifier('%s')" % str(row))
break
def initialise(self, model, max_covers):
album_col = model.columns['album']
index = 0
items = ""
self.album_identifier = {}
def html_elements(fullfilename, title, caption, identifier):
return '<div class="item"><img class="content" src="' + \
escape(fullfilename) + '" title="' + \
escape(title) + '" identifier="' + \
identifier + '"/> <div class="caption">' + \
escape(caption) + '</div> </div>'
for row in model.store:
cover = row[album_col].cover.original
cover = cover.replace(
'rhythmbox-missing-artwork.svg',
'rhythmbox-missing-artwork.png') # # need a white vs black when we change the background colour
self.album_identifier[index] = row[album_col]
items += html_elements(
fullfilename=cover,
caption=row[album_col].name,
title=row[album_col].artist,
identifier=str(index))
index += 1
if index == max_covers:
break
if index != 0:
# self.callback_view.last_album = self.album_identifier[0]
pass
else:
self.callback_view.last_album = None
return items