Arquivos
coverart-browser/coverart_entryview.py
T

1067 linhas
40 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.
from collections import OrderedDict
from os.path import expanduser
from gi.repository import RB
from gi.repository import Gtk
from gi.repository import GObject
from gi.repository import GdkPixbuf
from gi.repository import Gdk
from gi.repository import GLib
from coverart_rb3compat import Menu
from coverart_rb3compat import ActionGroup
from coverart_browser_prefs import GSetting
from coverart_browser_prefs import CoverLocale
from coverart_external_plugins import CreateExternalPluginMenu
from coverart_playlists import LastFMTrackPlaylist
from coverart_playlists import EchoNestPlaylist
from coverart_playlists import EchoNestGenrePlaylist
from coverart_utils import create_button_image
from coverart_external_plugins import ExternalPlugin
from stars import ReactiveStar
from coverart_search import CoverSearchPane
from coverart_widgets import PixbufButton
from coverart_widgets import PressButton
from coverart_window import CoverWindow
MIN_IMAGE_SIZE = 100
class EntryViewPane(object):
'''
encapulates all of the Track Pane objects
'''
def __init__(self, shell, plugin, source, entry_view_grid, viewmgr):
self.gs = GSetting()
self.entry_view_grid = entry_view_grid
self.shell = shell
self.viewmgr = viewmgr
self.plugin = plugin
self.source = source
# setup entry-view objects and widgets
self.stack = Gtk.Stack()
self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
self.stack.set_transition_duration(750)
# create entry views. Don't allow to reorder until the load is finished
self.entry_view_compact = CoverArtCompactEntryView(self.shell, self.source)
self.entry_view_full = CoverArtEntryView(self.shell, self.source)
self.entry_view = self.entry_view_compact
self.shell.props.library_source.get_entry_view().set_columns_clickable(
False)
self.entry_view_results = ResultsGrid()
self.entry_view_results.initialise(self.entry_view_grid, source)
self.stack.add_titled(self.entry_view_results, "notebook_tracks", _("Tracks"))
self.entry_view_grid.attach(self.stack, 0, 0, 3, 1)
def setup_source(self):
colour = self.viewmgr.get_selection_colour()
self.cover_search_pane = CoverSearchPane(self.plugin, colour)
self.stack.add_titled(self.cover_search_pane, "notebook_covers", _("Covers"))
# define entry-view toolbar
self.stars = ReactiveStar()
self.stars.set_rating(0)
self.stars.connect('changed', self.rating_changed_callback)
self.stars.props.valign = Gtk.Align.CENTER
self.entry_view_grid.attach(self.stars, 1, 1, 1, 1)
stack_switcher = Gtk.StackSwitcher()
stack_switcher.set_stack(self.stack)
whatsplayingbutton = PressButton()
whatsplayingbutton.set_image(create_button_image(self.plugin, "whatsplaying.png"))
whatsplayingbutton.connect('clicked', self.whatsplayingbutton_callback)
whatsplayingbutton.props.halign = Gtk.Align.START
leftgrid = Gtk.Grid()
leftgrid.attach(whatsplayingbutton, 0, 0, 1, 1)
leftgrid.attach(stack_switcher, 1, 0, 1, 1)
self.entry_view_grid.attach(leftgrid, 0, 1, 1, 1)
viewtoggle = PixbufButton()
viewtoggle.set_image(create_button_image(self.plugin, "entryview.png"))
self.viewtoggle_id = None
setting = self.gs.get_setting(self.gs.Path.PLUGIN)
viewtoggle.set_active(not setting[self.gs.PluginKey.ENTRY_VIEW_MODE])
self.entry_view_toggled(viewtoggle, True)
viewtoggle.connect('toggled', self.entry_view_toggled)
smallwindowbutton = PixbufButton()
smallwindowbutton.set_image(create_button_image(self.plugin, "view-restore.png"))
smallwindowbutton.connect('toggled', self.smallwindowbutton_callback)
self.smallwindowext = ExternalPlugin()
self.smallwindowext.appendattribute('plugin_name', 'smallwindow')
self.smallwindowext.appendattribute('action_group_name', 'small window actions')
self.smallwindowext.appendattribute('action_name', 'SmallWindow')
self.smallwindowext.appendattribute('action_type', 'app')
rightgrid = Gtk.Grid()
rightgrid.props.halign = Gtk.Align.END
rightgrid.attach(viewtoggle, 1, 0, 1, 1)
rightgrid.attach(smallwindowbutton, 2, 0, 1, 1)
self.entry_view_grid.attach_next_to(rightgrid, self.stars, Gtk.PositionType.RIGHT, 1, 1)
self.stack.set_visible_child(self.entry_view_results)
self.stack.connect('notify::visible-child-name', self.notebook_switch_page_callback)
self.entry_view_grid.show_all()
smallwindowbutton.set_visible(self.smallwindowext.is_activated())
def whatsplayingbutton_callback(self, widget):
self.entry_view_results.emit('whats-playing')
def smallwindowbutton_callback(self, widget):
if widget.get_active():
self.smallwindowext.activate(self.shell)
widget.emit('clicked')
def entry_view_toggled(self, widget, initialised=False):
print("DEBUG - entry_view_toggled")
if widget.get_active():
next_view = self.entry_view_full
show_coverart = False
if self.viewtoggle_id:
self.shell.props.window.disconnect(self.viewtoggle_id)
self.viewtoggle_id = None
else:
next_view = self.entry_view_compact
show_coverart = True
self.viewtoggle_id = self.shell.props.window.connect('check_resize', self.entry_view_results.window_resize)
setting = self.gs.get_setting(self.gs.Path.PLUGIN)
setting[self.gs.PluginKey.ENTRY_VIEW_MODE] = not widget.get_active()
self.entry_view_results.change_view(next_view, show_coverart)
self.entry_view = next_view
if not initialised:
self.source.update_with_selection()
def notebook_switch_page_callback(self, *args):
'''
Callback called when the notebook page gets switched. It initiates
the cover search when the cover search pane's page is selected.
'''
print("CoverArtBrowser DEBUG - notebook_switch_page_callback")
if self.stack.get_visible_child_name() == 'notebook_covers':
self.viewmgr.current_view.switch_to_coverpane(self.cover_search_pane)
else:
entries = self.entry_view.get_selected_entries()
if entries and len(entries) > 0:
self.entry_view_results.emit('update-cover', self.source, entries[0])
else:
selected = self.viewmgr.current_view.get_selected_objects()
tracks = selected[0].get_tracks()
self.entry_view_results.emit('update-cover', self.source, tracks[0].entry)
print("CoverArtBrowser DEBUG - end notebook_switch_page_callback")
def rating_changed_callback(self, widget):
'''
Callback called when the Rating stars is changed
'''
print("CoverArtBrowser DEBUG - rating_changed_callback")
rating = widget.get_rating()
for album in self.viewmgr.current_view.get_selected_objects():
album.rating = rating
print("CoverArtBrowser DEBUG - end rating_changed_callback")
def get_entry_view(self):
return self.entry_view
def update_cover(self, album_artist, manager):
if not self.stack.get_visible_child_name() == "notebook_covers":
return
self.cover_search_pane.clear()
self.cover_search(album_artist, manager)
def cover_search(self, album_artist, manager):
self.cover_search_pane.do_search(album_artist,
manager.cover_man.update_cover)
def update_selection(self, last_selected_album, click_count):
'''
Update the source view when an item gets selected.
'''
print("DEBUG - update_with_selection")
selected = self.viewmgr.current_view.get_selected_objects()
# clear the entry view
self.entry_view.clear()
cover_search_pane_visible = self.stack.get_visible_child_name() == "notebook_covers"
if not selected:
# clean cover tab if selected
if cover_search_pane_visible:
self.cover_search_pane.clear()
self.entry_view_results.emit('update-cover', self.source, None)
return last_selected_album, click_count
elif len(selected) == 1:
self.stars.set_rating(selected[0].rating)
if selected[0] is not last_selected_album:
# when the selection changes we've to take into account two
# things
if not click_count:
# we may be using the arrows, so if there is no mouse
# involved, we should change the last selected
last_selected_album = selected[0]
else:
# we may've doing a fast change after a valid second click,
# so it shouldn't be considered a double click
click_count -= 1
else:
self.stars.set_rating(0)
if len(selected) == 1:
self.source.artist_info.emit('selected',
selected[0].artist,
selected[0].name)
self.entry_view.set_sorting_order('track-number', Gtk.SortType.ASCENDING)
player = self.shell.props.shell_player
# add album tracks automatically to the playlist if current playsource is not ours
# or if it is ours, then add automatically when nothing is playing
autoplaylist = False
if player.get_playing_source() != self.source.playlist_source and player.get_playing_source() != self.source:
autoplaylist = True
elif not player.get_playing_entry():
autoplaylist = True
if autoplaylist:
# cleardown current playlist
for row in self.source.source_query_model:
self.source.source_query_model.remove_entry(row[0])
for album in selected:
# add the album to the entry_view
self.entry_view.add_album(album, autoplaylist)
if len(selected) > 0:
if autoplaylist:
self.source.props.query_model = self.source.source_query_model
player.set_playing_source(self.source)
def cover_update(*args):
print("emitting")
self.entry_view_results.emit('update-cover',
self.source,
selected[0].get_tracks()[0].entry)
# add a short delay to give the entry-pane time to expand etc.
Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 250, cover_update, None)
# update the cover search pane with the first selected album
if cover_search_pane_visible:
self.cover_search_pane.do_search(selected[0],
self.source.album_manager.cover_man.update_cover)
return last_selected_album, click_count
class ResultsGrid(Gtk.Grid):
# signals
__gsignals__ = {
'update-cover': (GObject.SIGNAL_RUN_LAST, None, (GObject.Object, RB.RhythmDBEntry)),
'whats-playing': (GObject.SIGNAL_RUN_LAST, None, ())
}
image_width = 0
def __init__(self, *args, **kwargs):
super(ResultsGrid, self).__init__(*args, **kwargs)
def initialise(self, entry_view_grid, source):
self.pixbuf = None
self.entry_view_grid = entry_view_grid
self.source = source
self.oldval = 0
self.stack = Gtk.Stack()
self.stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE)
self.stack.set_transition_duration(350)
self.image1 = Gtk.Image()
self.image1.props.hexpand = True
self.image1.props.vexpand = True
self.stack.add_named(self.image1, "image1")
self.image2 = Gtk.Image()
self.image2.props.hexpand = True
self.image2.props.vexpand = True
self.stack.add_named(self.image2, "image2")
self.frame = Gtk.Frame.new() # "", 0.5, 0.5, 1, False)
try:
# correct from Gtk 3.12 onwards
self.frame.set_margin_end(5)
except:
self.frame.set_margin_right(5)
self.update_cover(None, None, None)
self.scroll = Gtk.ScrolledWindow()
self.scroll.add(self.stack)
self.scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
self.scroll.set_resize_mode(Gtk.ResizeMode.QUEUE)
self.frame.add(self.scroll)
self._signal_connected = None
'''
when we hover over the coverart then a zoom icon is overlayed with the coverart
when we move the mouse pointer from the coverart then the zoom icon is hidden
this is achieved by a GtkOverlay - the stack doesnt normally have a mouse over event so
we need to understand when a mouse pointer enters or leaves the stack.
Also a peculiarity is that moving the pointer over the overlay zoom icon causes
enter and leave events ... so we need to monitor the mouse over for the icon as well
'''
self.overlay = Gtk.Overlay()
self.overlay.add(self.frame)
image = Gtk.Image.new_from_icon_name(Gtk.STOCK_ZOOM_FIT, Gtk.IconSize.SMALL_TOOLBAR)
self.cw_btn = Gtk.Button(label=None, image=image)
self.cw_btn.set_valign(Gtk.Align.END)
self.cw_btn.set_halign(Gtk.Align.CENTER)
self.cw_btn_state = False
self.hover = False
self.overlay.add_overlay(self.cw_btn)
self.attach(self.overlay, 6, 0, 1, 1)
self.stack.add_events(Gdk.EventMask.ENTER_NOTIFY_MASK)
self.stack.add_events(Gdk.EventMask.LEAVE_NOTIFY_MASK)
self.stack.add_events(Gdk.EventMask.POINTER_MOTION_MASK)
self.cw_btn.add_events(Gdk.EventMask.POINTER_MOTION_MASK)
self.stack.connect('enter-notify-event', self.stack_notify_event)
self.stack.connect('leave-notify-event', self.stack_notify_event)
self.stack.connect('motion-notify-event', self.motion_notify_event)
self.cw_btn.connect('clicked', self.cw_btn_clicked)
self.cw_btn.connect('motion-notify-event', self.motion_notify_event)
self.cw_btn.connect('show', self.cw_btn_show_event)
self.cw = None
self.hover_time_out = None
self.connect('update-cover', self.update_cover)
self.connect('whats-playing', self.display_whats_playing)
# lets fix the situation where some-themes background colour is incorrectly defined
# in these cases the background colour is black
context = self.get_style_context()
bg_colour = context.get_background_color(Gtk.StateFlags.NORMAL)
if bg_colour == Gdk.RGBA(0, 0, 0, 0):
color = context.get_color(Gtk.StateFlags.NORMAL)
self.override_background_color(Gtk.StateType.NORMAL, color)
'''
when a show, show_all is used lets make sure we set the icon visibility correctly
'''
def cw_btn_show_event(self, *args):
self.cw_btn.set_visible(self.hover)
'''
when mousing over the stack/icon we need to remember that we are hovering ... so the visibility
is set correctly - in the stack notify event
'''
def motion_notify_event(self, *args):
self.hover = True
def cw_btn_clicked(self, *args):
def cleanup(arg):
del self.cw
self.cw = None
if not self.cw:
home = expanduser("~")
self.cw = CoverWindow(self.source.plugin, self.cw_btn.get_toplevel(), home)
self.cw.connect('close-window', cleanup)
self.cw.show_all(' ', self.pixbuf)
'''
when entering and leaving the stack (also the icon) then we assume the icon is to be invisible
Use a short delay to allow the motion events to kick in - if we are still in the stack/icon
then the hover visibility will be changed
'''
def stack_notify_event(self, widget, event_crossing):
if self.hover_time_out:
GLib.source_remove(self.hover_time_out)
self.hover_time_out = None
def set_hover():
if self.pixbuf:
self.cw_btn.set_visible(self.hover)
else:
self.cw_btn.set_visible(False)
self.hover_time_out = None
return False
self.hover = False
self.hover_time_out = GLib.timeout_add(350, set_hover)
return False
def update_cover(self, widget, source, entry):
print('update_cover')
self.oldval = 0 # force a redraw
if entry:
print('entry')
album = source.album_manager.model.get_from_dbentry(entry)
self.pixbuf = GdkPixbuf.Pixbuf().new_from_file(album.cover.original)
self.window_resize(None)
self.frame.set_shadow_type(Gtk.ShadowType.NONE)
else:
print('no pixbuf')
self.pixbuf = None
self.frame.set_shadow_type(Gtk.ShadowType.ETCHED_OUT)
if self.stack.get_visible_child_name() == "image1":
self.image1.queue_draw()
else:
self.image2.queue_draw()
def display_whats_playing(self, *args):
'''
switch to the coverart_play_source
to do this we need to first expand the source tree to allow the select method to work
Unfortunately, rhythmbox api does not allow us to do this directly - there is only a toggle
method. Also - no direct access to the source tree-view.
Use a trick from alternative-toolbar to search for objects beneath other objects i.e.
tree-view is below the model
'''
def find(node, search_id, search_type):
if isinstance(node, Gtk.Buildable):
if search_type == 'by_id':
if Gtk.Buildable.get_name(node) == search_id:
return node
elif search_type == 'by_name':
if node.get_name() == search_id:
return node
if isinstance(node, Gtk.Container):
for child in node.get_children():
ret = find(child, search_id, search_type)
if ret:
return ret
return None
tree_view = find(self.source.shell.props.display_page_tree, "GtkTreeView", "by_name")
print (tree_view)
iter = Gtk.TreeIter()
self.source.shell.props.display_page_tree.props.model.find_page(self.source, iter)
path = self.source.shell.props.display_page_tree.props.model.get_path(iter)
if not tree_view.row_expanded(path):
tree_view.expand_row(path, False)
GLib.idle_add( self.source.shell.props.display_page_tree.select, self.source.playlist_source)
def window_resize(self, widget):
alloc = self.get_allocation()
if alloc.height < 10:
return
entry_grid_alloc = self.entry_view_grid.get_allocation()
if (alloc.width / 4) <= (MIN_IMAGE_SIZE + 30) or \
(entry_grid_alloc.height) <= (MIN_IMAGE_SIZE + 30):
self.overlay.set_visible(False)
return
else:
self.overlay.set_visible(True)
vbar = self.scroll.get_vscrollbar()
if vbar:
vbar.set_visible(False)
hbar = self.scroll.get_hscrollbar()
if hbar:
hbar.set_visible(False)
minval = min((alloc.width / 4), (alloc.height))
if self.oldval == minval:
return
self.oldval = minval
if minval < MIN_IMAGE_SIZE:
minval = MIN_IMAGE_SIZE
if self.pixbuf:
p = self.pixbuf.scale_simple(minval - 15, minval - 15, GdkPixbuf.InterpType.BILINEAR)
else:
p = None
if self.stack.get_visible_child_name() == "image1":
self.image2.set_from_pixbuf(p)
self.stack.set_visible_child_name("image2")
else:
self.image1.set_from_pixbuf(p)
self.stack.set_visible_child_name("image1")
def change_view(self, entry_view, show_coverart):
print("debug - change_view")
widget = self.get_child_at(0, 0)
if widget:
self.remove(widget)
if not show_coverart:
widget = self.get_child_at(6, 0)
if widget:
self.remove(widget)
entry_view.props.hexpand = True
entry_view.props.vexpand = True
self.attach(entry_view, 0, 0, 3, 1)
if show_coverart:
self.attach(self.overlay, 6, 0, 1, 1)
self.show_all()
self.cw_btn.set_visible(self.hover)
class BaseView(RB.EntryView):
def __init__(self, shell, source):
'''
Initializes the entryview.
'''
self.shell = shell
self.source = source
self.plugin = self.source.props.plugin
super(RB.EntryView, self).__init__(db=shell.props.db,
shell_player=shell.props.shell_player, is_drag_source=True,
visible_columns=[])
cl = CoverLocale()
cl.switch_locale(cl.Locale.RB)
self.display_columns()
cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
self.define_menu()
# connect signals to the shell to know when the playing state changes
self.shell.props.shell_player.connect('playing-song-changed',
self.playing_song_changed)
self.shell.props.shell_player.connect('playing-changed',
self.playing_changed)
self.actiongroup = ActionGroup(self.shell, 'coverentryplaylist_submenu')
self.external_plugins = None
self.source_query_model = self.source.source_query_model # RB.RhythmDBQueryModel.new_empty(self.shell.props.db)
self.qm = RB.RhythmDBQueryModel.new_empty(self.shell.props.db)
self.set_model(self.qm)
self.connect_library_signals()
self.echonest_similar_playlist = None
self.echonest_similar_genre_playlist = None
self.lastfm_similar_playlist = None
self.connect('selection-changed', self.selection_changed)
self.artists = ""
print("end constructor")
def __del__(self):
del self.action_group
del self.play_action
del self.queue_action
def connect_library_signals(self):
# connect the sort-order to the library source sort
library_view = self.shell.props.library_source.get_entry_view()
library_view.connect('notify::sort-order',
self._on_library_sorting_changed)
self._on_library_sorting_changed(library_view,
library_view.props.sort_order)
# connect to the sort-order property
self.connect('notify::sort-order', self._notify_sort_order,
library_view)
self.set_columns_clickable(False)
def display_playing_tracks(self, show_playing):
pass
def define_menu(self):
pass
def display_columns(self):
pass
def selection_changed(self, entry_view):
entries = entry_view.get_selected_entries()
if entries and len(entries) > 0:
self.source.entryviewpane.entry_view_results.emit('update-cover', self.source, entries[0])
def add_album(self, album, autoplaylist):
print("CoverArtBrowser DEBUG - add_album()")
tracks = album.get_tracks()
for track in tracks:
self.qm.add_entry(track.entry, -1)
if autoplaylist:
self.source.source_query_model.add_entry(track.entry, -1)
(_, playing) = self.shell.props.shell_player.get_playing()
self.playing_changed(self.shell.props.shell_player, playing)
artists = album.artists.split(', ')
if self.artists == "":
self.artists = artists
else:
self.artists = list(set(self.artists + artists))
print("CoverArtBrowser DEBUG - add_album()")
def clear(self):
print("CoverArtBrowser DEBUG - clear()")
for row in self.qm:
self.qm.remove_entry(row[0])
self.artists = ""
print("CoverArtBrowser DEBUG - clear()")
def do_entry_activated(self, entry):
print("CoverArtBrowser DEBUG - do_entry_activated()")
self.select_entry(entry)
self.play_track_menu_item_callback(entry)
print("CoverArtBrowser DEBUG - do_entry_activated()")
return True
def pre_popup_menu_callback(self, *args):
pass
def do_show_popup(self, over_entry):
if over_entry:
print("CoverArtBrowser DEBUG - do_show_popup()")
self.popup.popup(self.source,
'entryview_popup_menu', 0, Gtk.get_current_event_time())
return over_entry
def play_similar_artist_menu_item_callback(self, *args):
if not self.echonest_similar_playlist:
self.echonest_similar_playlist = \
EchoNestPlaylist(self.shell,
self.shell.props.queue_source)
selected = self.get_selected_entries()
entry = selected[0]
self.echonest_similar_playlist.start(entry, reinitialise=True)
def play_similar_genre_menu_item_callback(self, *args):
if not self.echonest_similar_genre_playlist:
self.echonest_similar_genre_playlist = \
EchoNestGenrePlaylist(self.shell,
self.shell.props.queue_source)
selected = self.get_selected_entries()
entry = selected[0]
self.echonest_similar_genre_playlist.start(entry, reinitialise=True)
def play_similar_track_menu_item_callback(self, *args):
if not self.lastfm_similar_playlist:
self.lastfm_similar_playlist = \
LastFMTrackPlaylist(self.shell,
self.shell.props.queue_source)
selected = self.get_selected_entries()
entry = selected[0]
self.lastfm_similar_playlist.start(entry, reinitialise=True)
def play_next_track_menu_item_callback(self, *args):
source = self.source_query_model
selected = self.get_selected_entries()
selected.reverse()
selected = sorted(selected,
key=lambda song: song.get_ulong(RB.RhythmDBPropType.TRACK_NUMBER))
entry = self.shell.props.shell_player.get_playing_entry()
if not entry:
return
index = 0
source = self.source_query_model
# the API doesnt provide a method to find the index position of the entry that is playing
# so lets reverse through the model calculating the position
while entry is not None:
index = index + 1
entry = source.get_previous_from_entry(entry)
for entry in selected:
source.add_entry(entry, index)
index = index + 1
def play_track_menu_item_callback(self, *args):
print("CoverArtBrowser DEBUG - play_track_menu_item_callback()")
for row in self.source_query_model:
self.source_query_model.remove_entry(row[0])
selected = self.get_selected_entries()
entry = selected[0]
if len(selected) == 1:
self.source_query_model.copy_contents(self.qm)
else:
self.add_tracks_to_source(self.source_query_model)
self.source.props.query_model = self.source_query_model
# library_view = self.shell.props.library_source.get_entry_view()
# library_view.set_sorting_order('track-number', Gtk.SortType.ASCENDING)
#self.set_sorting_order('track-number', Gtk.SortType.ASCENDING)
# Start the music
player = self.shell.props.shell_player
player.play_entry(entry, self.source)
print("CoverArtBrowser DEBUG - play_track_menu_item_callback()")
def queue_track_menu_item_callback(self, *args):
print("CoverArtBrowser DEBUG - queue_track_menu_item_callback()")
self.add_tracks_to_source(self.shell.props.queue_source)
def add_to_playing_menu_item_callback(self, *args):
print("CoverArtBrowser DEBUG - add_to_playing_menu_item_callback()")
self.add_tracks_to_source(None)
def add_tracks_to_source(self, source):
if source == None:
source = self.source_query_model
selected = self.get_selected_entries()
selected.reverse()
selected = sorted(selected,
key=lambda song: song.get_ulong(RB.RhythmDBPropType.TRACK_NUMBER))
for entry in selected:
source.add_entry(entry, -1)
print("CoverArtBrowser DEBUG - queue_track_menu_item_callback()")
def love_track(self, rating):
'''
utility function to set the rating for selected tracks
'''
selected = self.get_selected_entries()
for entry in selected:
self.shell.props.db.entry_set(entry, RB.RhythmDBPropType.RATING,
rating)
self.shell.props.db.commit()
def show_properties_menu_item_callback(self, *args):
print("CoverArtBrowser DEBUG - show_properties_menu_item_callback()")
info_dialog = RB.SongInfo(source=self.source, entry_view=self)
info_dialog.show_all()
print("CoverArtBrowser DEBUG - show_properties_menu_item_callback()")
def playing_song_changed(self, shell_player, entry):
print("CoverArtBrowser DEBUG - playing_song_changed()")
if entry is not None and self.get_entry_contained(entry):
self.set_state(RB.EntryViewState.PLAYING)
else:
self.set_state(RB.EntryViewState.NOT_PLAYING)
print("CoverArtBrowser DEBUG - playing_song_changed()")
def playing_changed(self, shell_player, playing):
print("CoverArtBrowser DEBUG - playing_changed()")
entry = shell_player.get_playing_entry()
if entry is not None and self.get_entry_contained(entry):
if playing:
self.set_state(RB.EntryViewState.PLAYING)
else:
self.set_state(RB.EntryViewState.PAUSED)
else:
self.set_state(RB.EntryViewState.NOT_PLAYING)
print("CoverArtBrowser DEBUG - playing_changed()")
def add_playlist_menu_item_callback(self, *args):
print("CoverArtBrowser DEBUG - add_playlist_menu_item_callback")
playlist_manager = self.shell.props.playlist_manager
playlist = playlist_manager.new_playlist(_('New Playlist'), False)
self.add_tracks_to_source(playlist)
def playlist_menu_item_callback(self, *args):
pass
def add_to_static_playlist_menu_item_callback(self, action, param, args):
print("CoverArtBrowser DEBUG - " + \
"add_to_static_playlist_menu_item_callback")
playlist = args['playlist']
self.add_tracks_to_source(playlist)
def _on_library_sorting_changed(self, view, _):
self._old_sort_order = self.props.sort_order
self.set_sorting_type(view.props.sort_order)
def _notify_sort_order(self, view, _, library_view):
if self.props.sort_order != self._old_sort_order:
self.resort_model()
# update library source's view direction
library_view.set_sorting_type(self.props.sort_order)
class CoverArtCompactEntryView(BaseView):
__hash__ = GObject.__hash__
def __init__(self, shell, source):
'''
Initializes the entryview.
'''
super(CoverArtCompactEntryView, self).__init__(shell, source)
def display_columns(self):
self.col_map = OrderedDict([
('track-number', RB.EntryViewColumn.TRACK_NUMBER),
('title', RB.EntryViewColumn.TITLE),
('artist', RB.EntryViewColumn.ARTIST),
('rating', RB.EntryViewColumn.RATING),
('duration', RB.EntryViewColumn.DURATION)
])
for entry in self.col_map:
visible = False if entry == 'artist' else True
self.append_column(self.col_map[entry], visible)
def add_album(self, album, autoplaylist):
super(CoverArtCompactEntryView, self).add_album(album, autoplaylist)
if len(self.artists) > 1:
self.get_column(RB.EntryViewColumn.ARTIST).set_visible(True)
else:
self.get_column(RB.EntryViewColumn.ARTIST).set_visible(False)
def define_menu(self):
popup = Menu(self.plugin, self.shell)
popup.load_from_file('N/A',
'ui/coverart_entryview_compact_pop_rb3.ui')
signals = {
'ev_compact_play_track_menu_item': self.play_track_menu_item_callback,
'ev_compact_play_next_track_menu_item': self.play_next_track_menu_item_callback,
'ev_compact_queue_track_menu_item': self.queue_track_menu_item_callback,
'ev_compact_add_to_playing_menu_item': self.add_to_playing_menu_item_callback,
'ev_compact_new_playlist': self.add_playlist_menu_item_callback,
'ev_compact_show_properties_menu_item': self.show_properties_menu_item_callback,
'ev_compact_similar_track_menu_item': self.play_similar_track_menu_item_callback,
'ev_compact_similar_artist_menu_item': self.play_similar_artist_menu_item_callback,
'ev_compact_similar_genre_menu_item': self.play_similar_genre_menu_item_callback}
popup.connect_signals(signals)
popup.connect('pre-popup', self.pre_popup_menu_callback)
self.popup = popup
def playlist_menu_item_callback(self, *args):
print("CoverArtBrowser DEBUG - playlist_menu_item_callback")
self.source.playlist_fillmenu(self.popup, 'ev_compact_playlist_sub_menu_item', 'ev_compact_playlist_section',
self.actiongroup, self.add_to_static_playlist_menu_item_callback)
def pre_popup_menu_callback(self, *args):
'''
Callback when the popup menu is about to be displayed
'''
state, sensitive = self.shell.props.shell_player.get_playing()
if not state:
sensitive = False
self.popup.set_sensitive('ev_compact_add_to_playing_menu_item', sensitive)
self.popup.set_sensitive('ev_compact_play_next_track_menu_item', sensitive)
if not self.external_plugins:
self.external_plugins = \
CreateExternalPluginMenu("ev_compact_entryview", 6, self.popup)
self.external_plugins.create_menu('entryview_compact_popup_menu')
self.playlist_menu_item_callback()
class CoverArtEntryView(BaseView):
__hash__ = GObject.__hash__
def __init__(self, shell, source):
'''
Initializes the entryview.
'''
super(CoverArtEntryView, self).__init__(shell, source)
def display_columns(self):
self.col_map = OrderedDict([
('track-number', RB.EntryViewColumn.TRACK_NUMBER),
('title', RB.EntryViewColumn.TITLE),
('genre', RB.EntryViewColumn.GENRE),
('artist', RB.EntryViewColumn.ARTIST),
('album', RB.EntryViewColumn.ALBUM),
('composer', RB.EntryViewColumn.COMPOSER),
('date', RB.EntryViewColumn.YEAR),
('duration', RB.EntryViewColumn.DURATION),
('bitrate', RB.EntryViewColumn.QUALITY),
('play-count', RB.EntryViewColumn.PLAY_COUNT),
('beats-per-minute', RB.EntryViewColumn.BPM),
('comment', RB.EntryViewColumn.COMMENT),
('location', RB.EntryViewColumn.LOCATION),
('rating', RB.EntryViewColumn.RATING),
('last-played', RB.EntryViewColumn.LAST_PLAYED),
('first-seen', RB.EntryViewColumn.FIRST_SEEN)
])
for entry in self.col_map:
visible = True if entry == 'title' else False
self.append_column(self.col_map[entry], visible)
# connect the visible-columns global setting to update our entryview
gs = GSetting()
rhythm_settings = gs.get_setting(gs.Path.RBSOURCE)
rhythm_settings.connect('changed::visible-columns',
self.on_visible_columns_changed)
self.on_visible_columns_changed(rhythm_settings, 'visible-columns')
def on_visible_columns_changed(self, settings, key):
print("CoverArtBrowser DEBUG - on_visible_columns_changed()")
# reset current columns
print("CoverArtBrowser DEBUG - end on_visible_columns_changed()")
for entry in self.col_map:
col = self.get_column(self.col_map[entry])
if entry in settings[key]:
col.set_visible(True)
else:
if entry != 'title':
col.set_visible(False)
print("CoverArtBrowser DEBUG - end on_visible_columns_changed()")
def define_menu(self):
popup = Menu(self.plugin, self.shell)
popup.load_from_file('N/A',
'ui/coverart_entryview_full_pop_rb3.ui')
signals = {
'ev_full_play_track_menu_item': self.play_track_menu_item_callback,
'ev_full_play_next_track_menu_item': self.play_next_track_menu_item_callback,
'ev_full_queue_track_menu_item': self.queue_track_menu_item_callback,
'ev_full_add_to_playing_menu_item': self.add_to_playing_menu_item_callback,
'ev_full_new_playlist': self.add_playlist_menu_item_callback,
'ev_full_show_properties_menu_item': self.show_properties_menu_item_callback,
'ev_full_similar_track_menu_item': self.play_similar_track_menu_item_callback,
'ev_full_similar_artist_menu_item': self.play_similar_artist_menu_item_callback,
'ev_full_similar_genre_menu_item': self.play_similar_genre_menu_item_callback}
popup.connect_signals(signals)
popup.connect('pre-popup', self.pre_popup_menu_callback)
self.popup = popup
def playlist_menu_item_callback(self, *args):
print("CoverArtBrowser DEBUG - playlist_menu_item_callback")
self.source.playlist_fillmenu(self.popup, 'ev_full_playlist_sub_menu_item', 'ev_full_playlist_section',
self.actiongroup, self.add_to_static_playlist_menu_item_callback)
def pre_popup_menu_callback(self, *args):
'''
Callback when the popup menu is about to be displayed
'''
state, sensitive = self.shell.props.shell_player.get_playing()
if not state:
sensitive = False
self.popup.set_sensitive('ev_full_add_to_playing_menu_item', sensitive)
self.popup.set_sensitive('ev_full_play_next_track_menu_item', sensitive)
if not self.external_plugins:
self.external_plugins = \
CreateExternalPluginMenu("ev_full_entryview", 6, self.popup)
self.external_plugins.create_menu('entryview_full_popup_menu')
self.playlist_menu_item_callback()
GObject.type_register(CoverArtEntryView)
GObject.type_register(CoverArtCompactEntryView)