CoverArt Browser
v2.0
Browse your cover-art albums in Rhythmbox
|
00001 # -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*- 00002 # 00003 # Copyright (C) 2012 - fossfreedom 00004 # Copyright (C) 2012 - Agustin Carrasco 00005 # 00006 # This program is free software; you can redistribute it and/or modify 00007 # it under the terms of the GNU General Public License as published by 00008 # the Free Software Foundation; either version 2, or (at your option) 00009 # any later version. 00010 # 00011 # This program is distributed in the hope that it will be useful, 00012 # but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 # GNU General Public License for more details. 00015 # 00016 # You should have received a copy of the GNU General Public License 00017 # along with this program; if not, write to the Free Software 00018 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 00019 00020 from collections import OrderedDict 00021 00022 from gi.repository import RB 00023 from gi.repository import Gtk 00024 from gi.repository import GObject 00025 from gi.repository import GdkPixbuf 00026 from gi.repository import Gdk 00027 from gi.repository import GLib 00028 00029 from coverart_rb3compat import Menu 00030 from coverart_rb3compat import ActionGroup 00031 from coverart_browser_prefs import GSetting 00032 from coverart_browser_prefs import CoverLocale 00033 from coverart_external_plugins import CreateExternalPluginMenu 00034 from coverart_playlists import LastFMTrackPlaylist 00035 from coverart_playlists import EchoNestPlaylist 00036 from coverart_playlists import EchoNestGenrePlaylist 00037 from coverart_utils import create_button_image 00038 from coverart_external_plugins import ExternalPlugin 00039 from stars import ReactiveStar 00040 from coverart_search import CoverSearchPane 00041 from coverart_widgets import PixbufButton 00042 00043 00044 MIN_IMAGE_SIZE = 100 00045 00046 00047 class EntryViewPane(object): 00048 ''' 00049 encapulates all of the Track Pane objects 00050 ''' 00051 00052 def __init__(self, shell, plugin, source, entry_view_grid, viewmgr): 00053 self.gs = GSetting() 00054 00055 self.entry_view_grid = entry_view_grid 00056 self.shell = shell 00057 self.viewmgr = viewmgr 00058 self.plugin = plugin 00059 self.source = source 00060 00061 # setup entry-view objects and widgets 00062 self.stack = Gtk.Stack() 00063 self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT) 00064 self.stack.set_transition_duration(750) 00065 00066 # create entry views. Don't allow to reorder until the load is finished 00067 self.entry_view_compact = CoverArtCompactEntryView(self.shell, self.source) 00068 self.entry_view_full = CoverArtEntryView(self.shell, self.source) 00069 self.entry_view = self.entry_view_compact 00070 self.shell.props.library_source.get_entry_view().set_columns_clickable( 00071 False) 00072 00073 self.entry_view_results = ResultsGrid() 00074 self.entry_view_results.initialise() 00075 00076 self.stack.add_titled(self.entry_view_results, "notebook_tracks", _("Tracks")) 00077 self.entry_view_grid.attach(self.stack, 0, 0, 3, 1) 00078 00079 def setup_source(self): 00080 00081 colour = self.viewmgr.get_selection_colour() 00082 self.cover_search_pane = CoverSearchPane(self.plugin, colour) 00083 00084 self.stack.add_titled(self.cover_search_pane, "notebook_covers", _("Covers")) 00085 00086 # define entry-view toolbar 00087 self.stars = ReactiveStar() 00088 self.stars.set_rating(0) 00089 self.stars.connect('changed', self.rating_changed_callback) 00090 self.stars.props.valign = Gtk.Align.CENTER 00091 self.entry_view_grid.attach(self.stars, 1, 1, 1, 1) 00092 stack_switcher = Gtk.StackSwitcher() 00093 stack_switcher.set_stack(self.stack) 00094 self.entry_view_grid.attach(stack_switcher, 0, 1, 1, 1) 00095 viewtoggle = PixbufButton() 00096 viewtoggle.set_image(create_button_image(self.plugin, "entryview.png")) 00097 self.viewtoggle_id = None 00098 00099 setting = self.gs.get_setting(self.gs.Path.PLUGIN) 00100 viewtoggle.set_active(not setting[self.gs.PluginKey.ENTRY_VIEW_MODE]) 00101 self.entry_view_toggled(viewtoggle, True) 00102 viewtoggle.connect('toggled', self.entry_view_toggled) 00103 00104 smallwindowbutton = PixbufButton() 00105 smallwindowbutton.set_image(create_button_image(self.plugin, "view-restore.png")) 00106 smallwindowbutton.connect('toggled', self.smallwindowbutton_callback) 00107 00108 self.smallwindowext = ExternalPlugin() 00109 self.smallwindowext.appendattribute('plugin_name', 'smallwindow') 00110 self.smallwindowext.appendattribute('action_group_name', 'small window actions') 00111 self.smallwindowext.appendattribute('action_name', 'SmallWindow') 00112 self.smallwindowext.appendattribute('action_type', 'app') 00113 00114 whatsplayingtoggle = PixbufButton() 00115 whatsplayingtoggle.set_image(create_button_image(self.plugin, "whatsplaying.png")) 00116 whatsplayingtoggle.connect('toggled', self.whatsplayingtoggle_callback) 00117 00118 rightgrid = Gtk.Grid() 00119 rightgrid.props.halign = Gtk.Align.END 00120 00121 #rightgrid.attach(whatsplayingtoggle, 0, 0, 1, 1) 00122 rightgrid.attach(viewtoggle, 1, 0, 1, 1) 00123 rightgrid.attach(smallwindowbutton, 2, 0, 1, 1) 00124 00125 self.entry_view_grid.attach_next_to(rightgrid, self.stars, Gtk.PositionType.RIGHT, 1, 1) 00126 self.stack.set_visible_child(self.entry_view_results) 00127 self.stack.connect('notify::visible-child-name', self.notebook_switch_page_callback) 00128 00129 self.entry_view_grid.show_all() 00130 smallwindowbutton.set_visible(self.smallwindowext.is_activated()) 00131 00132 def whatsplayingtoggle_callback(self, widget): 00133 self.entry_view_results.emit('whats-playing', widget.get_active()) 00134 00135 00136 def smallwindowbutton_callback(self, widget): 00137 if widget.get_active(): 00138 self.smallwindowext.activate(self.shell) 00139 widget.emit('clicked') 00140 00141 def entry_view_toggled(self, widget, initialised=False): 00142 print("DEBUG - entry_view_toggled") 00143 if widget.get_active(): 00144 next_view = self.entry_view_full 00145 show_coverart = False 00146 if self.viewtoggle_id: 00147 self.shell.props.window.disconnect(self.viewtoggle_id) 00148 self.viewtoggle_id = None 00149 else: 00150 next_view = self.entry_view_compact 00151 show_coverart = True 00152 self.viewtoggle_id = self.shell.props.window.connect('check_resize', self.entry_view_results.window_resize) 00153 00154 setting = self.gs.get_setting(self.gs.Path.PLUGIN) 00155 setting[self.gs.PluginKey.ENTRY_VIEW_MODE] = not widget.get_active() 00156 00157 self.entry_view_results.change_view(next_view, show_coverart) 00158 self.entry_view = next_view 00159 if not initialised: 00160 self.source.update_with_selection() 00161 00162 def notebook_switch_page_callback(self, *args): 00163 ''' 00164 Callback called when the notebook page gets switched. It initiates 00165 the cover search when the cover search pane's page is selected. 00166 ''' 00167 print("CoverArtBrowser DEBUG - notebook_switch_page_callback") 00168 00169 if self.stack.get_visible_child_name() == 'notebook_covers': 00170 self.viewmgr.current_view.switch_to_coverpane(self.cover_search_pane) 00171 else: 00172 entries = self.entry_view.get_selected_entries() 00173 if entries and len(entries) > 0: 00174 self.entry_view_results.emit('update-cover', self.source, entries[0]) 00175 else: 00176 selected = self.viewmgr.current_view.get_selected_objects() 00177 tracks = selected[0].get_tracks() 00178 self.entry_view_results.emit('update-cover', self.source, tracks[0].entry) 00179 00180 print("CoverArtBrowser DEBUG - end notebook_switch_page_callback") 00181 00182 def rating_changed_callback(self, widget): 00183 ''' 00184 Callback called when the Rating stars is changed 00185 ''' 00186 print("CoverArtBrowser DEBUG - rating_changed_callback") 00187 00188 rating = widget.get_rating() 00189 00190 for album in self.viewmgr.current_view.get_selected_objects(): 00191 album.rating = rating 00192 00193 print("CoverArtBrowser DEBUG - end rating_changed_callback") 00194 00195 def get_entry_view(self): 00196 return self.entry_view 00197 00198 def update_cover(self, album_artist, manager): 00199 if not self.stack.get_visible_child_name() == "notebook_covers": 00200 return 00201 00202 self.cover_search_pane.clear() 00203 self.cover_search(album_artist, manager) 00204 00205 def cover_search(self, album_artist, manager): 00206 self.cover_search_pane.do_search(album_artist, 00207 manager.cover_man.update_cover) 00208 00209 def update_selection(self, last_selected_album, click_count): 00210 ''' 00211 Update the source view when an item gets selected. 00212 ''' 00213 print("DEBUG - update_with_selection") 00214 selected = self.viewmgr.current_view.get_selected_objects() 00215 00216 # clear the entry view 00217 self.entry_view.clear() 00218 00219 cover_search_pane_visible = self.stack.get_visible_child_name() == "notebook_covers" 00220 00221 if not selected: 00222 # clean cover tab if selected 00223 if cover_search_pane_visible: 00224 self.cover_search_pane.clear() 00225 00226 self.entry_view_results.emit('update-cover', self.source, None) 00227 return last_selected_album, click_count 00228 elif len(selected) == 1: 00229 self.stars.set_rating(selected[0].rating) 00230 00231 if selected[0] is not last_selected_album: 00232 # when the selection changes we've to take into account two 00233 # things 00234 if not click_count: 00235 # we may be using the arrows, so if there is no mouse 00236 # involved, we should change the last selected 00237 last_selected_album = selected[0] 00238 else: 00239 # we may've doing a fast change after a valid second click, 00240 # so it shouldn't be considered a double click 00241 click_count -= 1 00242 else: 00243 self.stars.set_rating(0) 00244 00245 if len(selected) == 1: 00246 self.source.artist_info.emit('selected', 00247 selected[0].artist, 00248 selected[0].name) 00249 00250 self.entry_view.set_sorting_order('track-number', Gtk.SortType.ASCENDING) 00251 00252 for album in selected: 00253 # add the album to the entry_view 00254 self.entry_view.add_album(album) 00255 00256 if len(selected) > 0: 00257 00258 def cover_update(*args): 00259 print ("emitting") 00260 self.entry_view_results.emit('update-cover', 00261 self.source, 00262 selected[0].get_tracks()[0].entry) 00263 00264 # add a short delay to give the entry-pane time to expand etc. 00265 Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 250, cover_update, None) 00266 00267 # update the cover search pane with the first selected album 00268 if cover_search_pane_visible: 00269 self.cover_search_pane.do_search(selected[0], 00270 self.source.album_manager.cover_man.update_cover) 00271 00272 return last_selected_album, click_count 00273 00274 00275 class ResultsGrid(Gtk.Grid): 00276 # signals 00277 __gsignals__ = { 00278 'update-cover': (GObject.SIGNAL_RUN_LAST, None, (GObject.Object, RB.RhythmDBEntry)), 00279 'whats-playing': (GObject.SIGNAL_RUN_LAST, None, (bool,)) 00280 } 00281 image_width = 0 00282 00283 def __init__(self, *args, **kwargs): 00284 super(ResultsGrid, self).__init__(*args, **kwargs) 00285 00286 def initialise(self): 00287 self.pixbuf = None 00288 00289 self.oldval = 0 00290 self.stack = Gtk.Stack() 00291 self.stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) 00292 self.stack.set_transition_duration(350) 00293 00294 self.image1 = Gtk.Image() 00295 self.image1.props.hexpand = True 00296 self.image1.props.vexpand = True 00297 self.stack.add_named(self.image1, "image1") 00298 00299 self.image2 = Gtk.Image() 00300 self.image2.props.hexpand = True 00301 self.image2.props.vexpand = True 00302 self.stack.add_named(self.image2, "image2") 00303 00304 self.frame = Gtk.AspectFrame.new("", 0.5, 0.5, 1, False) 00305 self.update_cover(None, None, None) 00306 scroll = Gtk.ScrolledWindow() 00307 scroll.add_with_viewport(self.stack) 00308 scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER) 00309 scroll.set_resize_mode(Gtk.ResizeMode.QUEUE) 00310 00311 self.frame.add(scroll) 00312 self._signal_connected = None 00313 00314 self.attach(self.frame, 6, 0, 1, 1) 00315 self.connect('update-cover', self.update_cover) 00316 self.connect('whats-playing', self.display_whats_playing) 00317 00318 #lets fix the situation where some-themes background colour is incorrectly defined 00319 #in these cases the background colour is black 00320 context = self.get_style_context() 00321 bg_colour = context.get_background_color(Gtk.StateFlags.NORMAL) 00322 if bg_colour == Gdk.RGBA(0, 0, 0, 0): 00323 color = context.get_color(Gtk.StateFlags.NORMAL) 00324 self.override_background_color(Gtk.StateType.NORMAL, color) 00325 00326 def update_cover(self, widget, source, entry): 00327 00328 print ('update_cover') 00329 self.oldval = 0 # force a redraw 00330 if entry: 00331 print ('entry') 00332 album = source.album_manager.model.get_from_dbentry(entry) 00333 self.pixbuf = GdkPixbuf.Pixbuf().new_from_file(album.cover.original) 00334 self.window_resize(None) 00335 self.frame.set_shadow_type(Gtk.ShadowType.NONE) 00336 else: 00337 print ('no pixbuf') 00338 self.pixbuf = None 00339 self.frame.set_shadow_type(Gtk.ShadowType.ETCHED_OUT) 00340 00341 if self.stack.get_visible_child_name() == "image1": 00342 self.image1.queue_draw() 00343 else: 00344 self.image2.queue_draw() 00345 00346 def display_whats_playing(self, show_playing): 00347 view = self.get_child_at(0, 0) 00348 00349 view.display_playing_tracks(show_playing) 00350 00351 def window_resize(self, widget): 00352 alloc = self.get_allocation() 00353 if alloc.height < 10: 00354 print ('less than') 00355 return 00356 00357 if (alloc.width / 3) <= (MIN_IMAGE_SIZE + 30) or \ 00358 (alloc.height) <= (MIN_IMAGE_SIZE + 30): 00359 self.frame.props.visible = False 00360 else: 00361 self.frame.props.visible = True 00362 00363 framealloc = self.frame.get_allocation() 00364 minval = min(framealloc.width - 30, framealloc.height - 30) 00365 if self.oldval == minval: 00366 print (self.oldval) 00367 return 00368 print("resizing") 00369 print (self.pixbuf) 00370 print (minval) 00371 self.oldval = minval 00372 if self.pixbuf: 00373 p = self.pixbuf.scale_simple(minval, minval, GdkPixbuf.InterpType.BILINEAR) 00374 else: 00375 p = None 00376 00377 if self.stack.get_visible_child_name() == "image1": 00378 self.image2.set_from_pixbuf(p) 00379 self.stack.set_visible_child_name("image2") 00380 else: 00381 self.image1.set_from_pixbuf(p) 00382 self.stack.set_visible_child_name("image1") 00383 00384 def change_view(self, entry_view, show_coverart): 00385 print("debug - change_view") 00386 widget = self.get_child_at(0, 0) 00387 if widget: 00388 self.remove(widget) 00389 00390 if not show_coverart: 00391 widget = self.get_child_at(6, 0) 00392 if widget: 00393 self.remove(widget) 00394 00395 entry_view.props.hexpand = True 00396 entry_view.props.vexpand = True 00397 self.attach(entry_view, 0, 0, 3, 1) 00398 00399 if show_coverart: 00400 self.attach(self.frame, 6, 0, 1, 1) 00401 00402 self.show_all() 00403 00404 00405 class BaseView(RB.EntryView): 00406 00407 def __init__(self, shell, source): 00408 ''' 00409 Initializes the entryview. 00410 ''' 00411 self.shell = shell 00412 self.source = source 00413 self.plugin = self.source.props.plugin 00414 00415 super(RB.EntryView, self).__init__(db=shell.props.db, 00416 shell_player=shell.props.shell_player, is_drag_source=True, 00417 visible_columns=[]) 00418 00419 cl = CoverLocale() 00420 cl.switch_locale(cl.Locale.RB) 00421 00422 self.display_columns() 00423 00424 cl.switch_locale(cl.Locale.LOCALE_DOMAIN) 00425 00426 self.define_menu() 00427 00428 # connect signals to the shell to know when the playing state changes 00429 self.shell.props.shell_player.connect('playing-song-changed', 00430 self.playing_song_changed) 00431 self.shell.props.shell_player.connect('playing-changed', 00432 self.playing_changed) 00433 00434 self.actiongroup = ActionGroup(self.shell, 'coverentryplaylist_submenu') 00435 00436 self.external_plugins = None 00437 00438 self.source_query_model = self.source.source_query_model # RB.RhythmDBQueryModel.new_empty(self.shell.props.db) 00439 self.qm = RB.RhythmDBQueryModel.new_empty(self.shell.props.db) 00440 self.set_model(self.qm) 00441 00442 self.connect_library_signals() 00443 self.echonest_similar_playlist = None 00444 self.echonest_similar_genre_playlist = None 00445 self.lastfm_similar_playlist = None 00446 00447 self.connect('selection-changed', self.selection_changed) 00448 00449 self.artists = "" 00450 00451 print ("end constructor") 00452 00453 def __del__(self): 00454 del self.action_group 00455 del self.play_action 00456 del self.queue_action 00457 00458 def connect_library_signals(self): 00459 # connect the sort-order to the library source sort 00460 library_view = self.shell.props.library_source.get_entry_view() 00461 library_view.connect('notify::sort-order', 00462 self._on_library_sorting_changed) 00463 self._on_library_sorting_changed(library_view, 00464 library_view.props.sort_order) 00465 00466 # connect to the sort-order property 00467 self.connect('notify::sort-order', self._notify_sort_order, 00468 library_view) 00469 00470 self.set_columns_clickable(False) 00471 00472 00473 def display_playing_tracks(self, show_playing): 00474 pass 00475 00476 def define_menu(self): 00477 pass 00478 00479 def display_columns(self): 00480 pass 00481 00482 def selection_changed(self, entry_view): 00483 entries = entry_view.get_selected_entries() 00484 if entries and len(entries) > 0: 00485 self.source.entryviewpane.entry_view_results.emit('update-cover', self.source, entries[0]) 00486 00487 def add_album(self, album): 00488 print("CoverArtBrowser DEBUG - add_album()") 00489 tracks = album.get_tracks() 00490 00491 for track in tracks: 00492 self.qm.add_entry(track.entry, -1) 00493 00494 (_, playing) = self.shell.props.shell_player.get_playing() 00495 self.playing_changed(self.shell.props.shell_player, playing) 00496 00497 artists = album.artists.split(', ') 00498 if self.artists == "": 00499 self.artists = artists 00500 else: 00501 self.artists = list(set(self.artists + artists)) 00502 00503 print("CoverArtBrowser DEBUG - add_album()") 00504 00505 def clear(self): 00506 print("CoverArtBrowser DEBUG - clear()") 00507 00508 for row in self.qm: 00509 self.qm.remove_entry(row[0]) 00510 00511 self.artists = "" 00512 00513 print("CoverArtBrowser DEBUG - clear()") 00514 00515 def do_entry_activated(self, entry): 00516 print("CoverArtBrowser DEBUG - do_entry_activated()") 00517 self.select_entry(entry) 00518 self.play_track_menu_item_callback(entry) 00519 print("CoverArtBrowser DEBUG - do_entry_activated()") 00520 return True 00521 00522 def pre_popup_menu_callback(self, *args): 00523 pass 00524 00525 def do_show_popup(self, over_entry): 00526 if over_entry: 00527 print("CoverArtBrowser DEBUG - do_show_popup()") 00528 00529 self.popup.popup(self.source, 00530 'entryview_popup_menu', 0, Gtk.get_current_event_time()) 00531 00532 return over_entry 00533 00534 def play_similar_artist_menu_item_callback(self, *args): 00535 if not self.echonest_similar_playlist: 00536 self.echonest_similar_playlist = \ 00537 EchoNestPlaylist(self.shell, 00538 self.shell.props.queue_source) 00539 00540 selected = self.get_selected_entries() 00541 entry = selected[0] 00542 self.echonest_similar_playlist.start(entry, reinitialise=True) 00543 00544 def play_similar_genre_menu_item_callback(self, *args): 00545 if not self.echonest_similar_genre_playlist: 00546 self.echonest_similar_genre_playlist = \ 00547 EchoNestGenrePlaylist(self.shell, 00548 self.shell.props.queue_source) 00549 00550 selected = self.get_selected_entries() 00551 entry = selected[0] 00552 self.echonest_similar_genre_playlist.start(entry, reinitialise=True) 00553 00554 def play_similar_track_menu_item_callback(self, *args): 00555 if not self.lastfm_similar_playlist: 00556 self.lastfm_similar_playlist = \ 00557 LastFMTrackPlaylist(self.shell, 00558 self.shell.props.queue_source) 00559 00560 selected = self.get_selected_entries() 00561 entry = selected[0] 00562 self.lastfm_similar_playlist.start(entry, reinitialise=True) 00563 00564 00565 def play_track_menu_item_callback(self, *args): 00566 print("CoverArtBrowser DEBUG - play_track_menu_item_callback()") 00567 00568 for row in self.source_query_model: 00569 self.source_query_model.remove_entry(row[0]) 00570 00571 selected = self.get_selected_entries() 00572 entry = selected[0] 00573 00574 if len(selected) == 1: 00575 self.source_query_model.copy_contents(self.qm) 00576 else: 00577 self.add_tracks_to_source(self.source_query_model) 00578 00579 self.source.props.query_model = self.source_query_model 00580 00581 #library_view = self.shell.props.library_source.get_entry_view() 00582 #library_view.set_sorting_order('track-number', Gtk.SortType.ASCENDING) 00583 #self.set_sorting_order('track-number', Gtk.SortType.ASCENDING) 00584 00585 # Start the music 00586 player = self.shell.props.shell_player 00587 player.play_entry(entry, self.source) 00588 00589 print("CoverArtBrowser DEBUG - play_track_menu_item_callback()") 00590 00591 def queue_track_menu_item_callback(self, *args): 00592 print("CoverArtBrowser DEBUG - queue_track_menu_item_callback()") 00593 00594 self.add_tracks_to_source(self.shell.props.queue_source) 00595 00596 def add_to_playing_menu_item_callback(self, *args): 00597 print("CoverArtBrowser DEBUG - add_to_playing_menu_item_callback()") 00598 self.add_tracks_to_source(None) 00599 00600 def add_tracks_to_source(self, source): 00601 00602 if source == None: 00603 source = self.source_query_model 00604 00605 selected = self.get_selected_entries() 00606 selected.reverse() 00607 00608 selected = sorted(selected, 00609 key=lambda song: song.get_ulong(RB.RhythmDBPropType.TRACK_NUMBER)) 00610 00611 for entry in selected: 00612 source.add_entry(entry, -1) 00613 00614 print("CoverArtBrowser DEBUG - queue_track_menu_item_callback()") 00615 00616 def love_track(self, rating): 00617 ''' 00618 utility function to set the rating for selected tracks 00619 ''' 00620 selected = self.get_selected_entries() 00621 00622 for entry in selected: 00623 self.shell.props.db.entry_set(entry, RB.RhythmDBPropType.RATING, 00624 rating) 00625 00626 self.shell.props.db.commit() 00627 00628 def show_properties_menu_item_callback(self, *args): 00629 print("CoverArtBrowser DEBUG - show_properties_menu_item_callback()") 00630 00631 info_dialog = RB.SongInfo(source=self.source, entry_view=self) 00632 info_dialog.show_all() 00633 00634 print("CoverArtBrowser DEBUG - show_properties_menu_item_callback()") 00635 00636 def playing_song_changed(self, shell_player, entry): 00637 print("CoverArtBrowser DEBUG - playing_song_changed()") 00638 00639 if entry is not None and self.get_entry_contained(entry): 00640 self.set_state(RB.EntryViewState.PLAYING) 00641 else: 00642 self.set_state(RB.EntryViewState.NOT_PLAYING) 00643 00644 print("CoverArtBrowser DEBUG - playing_song_changed()") 00645 00646 def playing_changed(self, shell_player, playing): 00647 print("CoverArtBrowser DEBUG - playing_changed()") 00648 entry = shell_player.get_playing_entry() 00649 00650 if entry is not None and self.get_entry_contained(entry): 00651 if playing: 00652 self.set_state(RB.EntryViewState.PLAYING) 00653 else: 00654 self.set_state(RB.EntryViewState.PAUSED) 00655 else: 00656 self.set_state(RB.EntryViewState.NOT_PLAYING) 00657 00658 print("CoverArtBrowser DEBUG - playing_changed()") 00659 00660 def add_playlist_menu_item_callback(self, *args): 00661 print("CoverArtBrowser DEBUG - add_playlist_menu_item_callback") 00662 playlist_manager = self.shell.props.playlist_manager 00663 playlist = playlist_manager.new_playlist(_('New Playlist'), False) 00664 00665 self.add_tracks_to_source(playlist) 00666 00667 def playlist_menu_item_callback(self, *args): 00668 pass 00669 00670 def add_to_static_playlist_menu_item_callback(self, action, param, args): 00671 print("CoverArtBrowser DEBUG - " + \ 00672 "add_to_static_playlist_menu_item_callback") 00673 00674 playlist = args['playlist'] 00675 self.add_tracks_to_source(playlist) 00676 00677 def _on_library_sorting_changed(self, view, _): 00678 self._old_sort_order = self.props.sort_order 00679 00680 self.set_sorting_type(view.props.sort_order) 00681 00682 def _notify_sort_order(self, view, _, library_view): 00683 if self.props.sort_order != self._old_sort_order: 00684 self.resort_model() 00685 00686 # update library source's view direction 00687 library_view.set_sorting_type(self.props.sort_order) 00688 00689 00690 class CoverArtCompactEntryView(BaseView): 00691 __hash__ = GObject.__hash__ 00692 00693 def __init__(self, shell, source): 00694 ''' 00695 Initializes the entryview. 00696 ''' 00697 super(CoverArtCompactEntryView, self).__init__(shell, source) 00698 00699 def display_columns(self): 00700 00701 self.col_map = OrderedDict([ 00702 ('track-number', RB.EntryViewColumn.TRACK_NUMBER), 00703 ('title', RB.EntryViewColumn.TITLE), 00704 ('artist', RB.EntryViewColumn.ARTIST), 00705 ('rating', RB.EntryViewColumn.RATING), 00706 ('duration', RB.EntryViewColumn.DURATION) 00707 ]) 00708 00709 for entry in self.col_map: 00710 visible = False if entry == 'artist' else True 00711 self.append_column(self.col_map[entry], visible) 00712 00713 def add_album(self, album): 00714 super(CoverArtCompactEntryView, self).add_album(album) 00715 00716 if len(self.artists) > 1: 00717 self.get_column(RB.EntryViewColumn.ARTIST).set_visible(True) 00718 else: 00719 self.get_column(RB.EntryViewColumn.ARTIST).set_visible(False) 00720 00721 def define_menu(self): 00722 popup = Menu(self.plugin, self.shell) 00723 popup.load_from_file('N/A', 00724 'ui/coverart_entryview_compact_pop_rb3.ui') 00725 signals = { 00726 'ev_compact_play_track_menu_item': self.play_track_menu_item_callback, 00727 'ev_compact_queue_track_menu_item': self.queue_track_menu_item_callback, 00728 'ev_compact_add_to_playing_menu_item': self.add_to_playing_menu_item_callback, 00729 'ev_compact_new_playlist': self.add_playlist_menu_item_callback, 00730 'ev_compact_show_properties_menu_item': self.show_properties_menu_item_callback, 00731 'ev_compact_similar_track_menu_item': self.play_similar_track_menu_item_callback, 00732 'ev_compact_similar_artist_menu_item': self.play_similar_artist_menu_item_callback, 00733 'ev_compact_similar_genre_menu_item': self.play_similar_genre_menu_item_callback} 00734 00735 popup.connect_signals(signals) 00736 popup.connect('pre-popup', self.pre_popup_menu_callback) 00737 self.popup = popup 00738 00739 def playlist_menu_item_callback(self, *args): 00740 print("CoverArtBrowser DEBUG - playlist_menu_item_callback") 00741 00742 self.source.playlist_fillmenu(self.popup, 'ev_compact_playlist_sub_menu_item', 'ev_compact_playlist_section', 00743 self.actiongroup, self.add_to_static_playlist_menu_item_callback) 00744 00745 def pre_popup_menu_callback(self, *args): 00746 ''' 00747 Callback when the popup menu is about to be displayed 00748 ''' 00749 00750 state, sensitive = self.shell.props.shell_player.get_playing() 00751 if not state: 00752 sensitive = False 00753 00754 self.popup.set_sensitive('ev_compact_add_to_playing_menu_item', sensitive) 00755 00756 if not self.external_plugins: 00757 self.external_plugins = \ 00758 CreateExternalPluginMenu("ev_compact_entryview", 5, self.popup) 00759 self.external_plugins.create_menu('entryview_compact_popup_menu') 00760 00761 self.playlist_menu_item_callback() 00762 00763 00764 class CoverArtEntryView(BaseView): 00765 __hash__ = GObject.__hash__ 00766 00767 def __init__(self, shell, source): 00768 ''' 00769 Initializes the entryview. 00770 ''' 00771 super(CoverArtEntryView, self).__init__(shell, source) 00772 00773 def display_columns(self): 00774 00775 self.col_map = OrderedDict([ 00776 ('track-number', RB.EntryViewColumn.TRACK_NUMBER), 00777 ('title', RB.EntryViewColumn.TITLE), 00778 ('genre', RB.EntryViewColumn.GENRE), 00779 ('artist', RB.EntryViewColumn.ARTIST), 00780 ('album', RB.EntryViewColumn.ALBUM), 00781 ('composer', RB.EntryViewColumn.COMPOSER), 00782 ('date', RB.EntryViewColumn.YEAR), 00783 ('duration', RB.EntryViewColumn.DURATION), 00784 ('bitrate', RB.EntryViewColumn.QUALITY), 00785 ('play-count', RB.EntryViewColumn.PLAY_COUNT), 00786 ('beats-per-minute', RB.EntryViewColumn.BPM), 00787 ('comment', RB.EntryViewColumn.COMMENT), 00788 ('location', RB.EntryViewColumn.LOCATION), 00789 ('rating', RB.EntryViewColumn.RATING), 00790 ('last-played', RB.EntryViewColumn.LAST_PLAYED), 00791 ('first-seen', RB.EntryViewColumn.FIRST_SEEN) 00792 ]) 00793 00794 for entry in self.col_map: 00795 visible = True if entry == 'title' else False 00796 self.append_column(self.col_map[entry], visible) 00797 00798 # connect the visible-columns global setting to update our entryview 00799 gs = GSetting() 00800 rhythm_settings = gs.get_setting(gs.Path.RBSOURCE) 00801 rhythm_settings.connect('changed::visible-columns', 00802 self.on_visible_columns_changed) 00803 self.on_visible_columns_changed(rhythm_settings, 'visible-columns') 00804 00805 def on_visible_columns_changed(self, settings, key): 00806 print("CoverArtBrowser DEBUG - on_visible_columns_changed()") 00807 # reset current columns 00808 print("CoverArtBrowser DEBUG - end on_visible_columns_changed()") 00809 for entry in self.col_map: 00810 col = self.get_column(self.col_map[entry]) 00811 if entry in settings[key]: 00812 col.set_visible(True) 00813 else: 00814 if entry != 'title': 00815 col.set_visible(False) 00816 00817 print("CoverArtBrowser DEBUG - end on_visible_columns_changed()") 00818 00819 def define_menu(self): 00820 popup = Menu(self.plugin, self.shell) 00821 popup.load_from_file('N/A', 00822 'ui/coverart_entryview_full_pop_rb3.ui') 00823 signals = { 00824 'ev_full_play_track_menu_item': self.play_track_menu_item_callback, 00825 'ev_full_queue_track_menu_item': self.queue_track_menu_item_callback, 00826 'ev_full_add_to_playing_menu_item': self.add_to_playing_menu_item_callback, 00827 'ev_full_new_playlist': self.add_playlist_menu_item_callback, 00828 'ev_full_show_properties_menu_item': self.show_properties_menu_item_callback, 00829 'ev_full_similar_track_menu_item': self.play_similar_track_menu_item_callback, 00830 'ev_full_similar_artist_menu_item': self.play_similar_artist_menu_item_callback, 00831 'ev_full_similar_genre_menu_item': self.play_similar_genre_menu_item_callback} 00832 00833 popup.connect_signals(signals) 00834 popup.connect('pre-popup', self.pre_popup_menu_callback) 00835 self.popup = popup 00836 00837 def playlist_menu_item_callback(self, *args): 00838 print("CoverArtBrowser DEBUG - playlist_menu_item_callback") 00839 00840 self.source.playlist_fillmenu(self.popup, 'ev_full_playlist_sub_menu_item', 'ev_full_playlist_section', 00841 self.actiongroup, self.add_to_static_playlist_menu_item_callback) 00842 00843 def pre_popup_menu_callback(self, *args): 00844 ''' 00845 Callback when the popup menu is about to be displayed 00846 ''' 00847 00848 state, sensitive = self.shell.props.shell_player.get_playing() 00849 if not state: 00850 sensitive = False 00851 00852 self.popup.set_sensitive('ev_full_add_to_playing_menu_item', sensitive) 00853 00854 if not self.external_plugins: 00855 self.external_plugins = \ 00856 CreateExternalPluginMenu("ev_full_entryview", 5, self.popup) 00857 self.external_plugins.create_menu('entryview_full_popup_menu') 00858 00859 self.playlist_menu_item_callback() 00860 00861 00862 GObject.type_register(CoverArtEntryView) 00863 GObject.type_register(CoverArtCompactEntryView) 00864