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 gi.repository import Gdk 00021 from gi.repository import Gtk 00022 from gi.repository import GLib 00023 from gi.repository import GObject 00024 from gi.repository import Gio 00025 from gi.repository import Pango 00026 from gi.repository import PangoCairo 00027 from gi.repository import GdkPixbuf 00028 00029 from coverart_widgets import EnhancedIconView 00030 from coverart_browser_prefs import GSetting 00031 from coverart_browser_prefs import CoverLocale 00032 from coverart_album import AlbumsModel 00033 from coverart_widgets import AbstractView 00034 from coverart_widgets import PanedCollapsible 00035 import coverart_rb3compat as rb3compat 00036 import rb 00037 import gettext 00038 00039 PLAY_SIZE_X = 30 00040 PLAY_SIZE_Y = 30 00041 00042 00043 class CellRendererThumb(Gtk.CellRendererPixbuf): 00044 markup = GObject.property(type=str, default="") 00045 00046 def __init__(self, font_description, cell_area_source): 00047 super(CellRendererThumb, self).__init__() 00048 self.font_description = font_description 00049 self.cell_area_source = cell_area_source 00050 ypad = 0 00051 00052 def do_render(self, cr, widget, 00053 background_area, 00054 cell_area, 00055 flags): 00056 00057 00058 x_offset = cell_area.x + 1 00059 y_offset = cell_area.y + 1 00060 wi = 0 00061 he = 0 00062 #IMAGE 00063 pixbuf = self.props.pixbuf.scale_simple(cell_area.width - 2, cell_area.height - 2, 00064 GdkPixbuf.InterpType.NEAREST) 00065 Gdk.cairo_set_source_pixbuf(cr, pixbuf, x_offset, y_offset) 00066 cr.paint() 00067 00068 alpha = 0.40 00069 00070 if ((flags & Gtk.CellRendererState.PRELIT) == Gtk.CellRendererState.PRELIT): 00071 alpha -= 0.15 00072 00073 if hasattr(Gtk.IconView, "get_cell_rect") and self.cell_area_source.hover_pixbuf: 00074 # this only works on Gtk+3.6 and later 00075 Gdk.cairo_set_source_pixbuf(cr, 00076 self.cell_area_source.hover_pixbuf, x_offset, y_offset) 00077 cr.paint() 00078 00079 #if((flags & Gtk.CellRendererState.SELECTED) == Gtk.CellRendererState.SELECTED or \ 00080 # (flags & Gtk.CellRendererState.FOCUSED) == Gtk.CellRendererState.FOCUSED): 00081 # alpha -= 0.15 00082 00083 00084 if not (self.cell_area_source.display_text and self.cell_area_source.display_text_pos == False): 00085 return 00086 00087 #PANGO LAYOUT 00088 layout_width = cell_area.width - 2 00089 pango_layout = PangoCairo.create_layout(cr) 00090 pango_layout.set_markup(self.markup, -1) 00091 pango_layout.set_alignment(Pango.Alignment.CENTER) 00092 pango_layout.set_font_description(self.font_description) 00093 pango_layout.set_width(int(layout_width * Pango.SCALE)) 00094 pango_layout.set_wrap(Pango.WrapMode.WORD_CHAR) 00095 wi, he = pango_layout.get_pixel_size() 00096 00097 rect_offset = y_offset + (int((2.0 * self.cell_area_source.cover_size) / 3.0)) 00098 rect_height = int(self.cell_area_source.cover_size / 3.0) 00099 was_to_large = False; 00100 if (he > rect_height): 00101 was_to_large = True 00102 pango_layout.set_ellipsize(Pango.EllipsizeMode.END) 00103 pango_layout.set_height(int((self.cell_area_source.cover_size / 3.0) * Pango.SCALE)) 00104 wi, he = pango_layout.get_pixel_size() 00105 00106 #RECTANGLE 00107 cr.set_source_rgba(0.0, 0.0, 0.0, alpha) 00108 cr.set_line_width(0) 00109 cr.rectangle(x_offset, 00110 rect_offset, 00111 cell_area.width - 1, 00112 rect_height - 1) 00113 cr.fill() 00114 00115 #DRAW FONT 00116 cr.set_source_rgba(1.0, 1.0, 1.0, 1.0) 00117 cr.move_to(x_offset, 00118 y_offset 00119 + 2.0 * self.cell_area_source.cover_size / 3.0 00120 + (((self.cell_area_source.cover_size / 3.0) - he) / 2.0) 00121 ) 00122 PangoCairo.show_layout(cr, pango_layout) 00123 00124 00125 class AlbumArtCellArea(Gtk.CellAreaBox): 00126 font_family = GObject.property(type=str, default="Sans") 00127 font_size = GObject.property(type=int, default=10) 00128 cover_size = GObject.property(type=int, default=0) 00129 display_text_pos = GObject.property(type=bool, default=False) 00130 display_text = GObject.property(type=bool, default=False) 00131 hover_pixbuf = GObject.property(type=object, default=None) 00132 00133 def __init__(self, ): 00134 super(AlbumArtCellArea, self).__init__() 00135 00136 self.font_description = Pango.FontDescription.new() 00137 self.font_description.set_family(self.font_family) 00138 self.font_description.set_size(int(self.font_size * Pango.SCALE)) 00139 00140 self._connect_properties() 00141 00142 #Add own cellrenderer 00143 renderer_thumb = CellRendererThumb(self.font_description, self) 00144 00145 self.pack_start(renderer_thumb, False, False, False) 00146 self.attribute_connect(renderer_thumb, "pixbuf", AlbumsModel.columns['pixbuf']) 00147 self.attribute_connect(renderer_thumb, "markup", AlbumsModel.columns['markup']) 00148 self.props.spacing = 2 00149 00150 def _connect_properties(self): 00151 gs = GSetting() 00152 setting = gs.get_setting(gs.Path.PLUGIN) 00153 00154 setting.bind(gs.PluginKey.COVER_SIZE, self, 'cover-size', 00155 Gio.SettingsBindFlags.GET) 00156 00157 setting.bind(gs.PluginKey.DISPLAY_TEXT_POS, self, 'display-text-pos', 00158 Gio.SettingsBindFlags.GET) 00159 00160 setting.bind(gs.PluginKey.DISPLAY_TEXT, self, 'display-text', 00161 Gio.SettingsBindFlags.GET) 00162 00163 00164 class AlbumShowingPolicy(GObject.Object): 00165 ''' 00166 Policy that mostly takes care of how and when things should be showed on 00167 the view that makes use of the `AlbumsModel`. 00168 ''' 00169 00170 def __init__(self, cover_view): 00171 super(AlbumShowingPolicy, self).__init__() 00172 00173 self._cover_view = cover_view # this will need to be reworked for all views 00174 self._visible_paths = None 00175 self._has_initialised = False 00176 00177 def initialise(self, album_manager): 00178 if self._has_initialised: 00179 return 00180 00181 self._album_manager = album_manager 00182 self._model = album_manager.model 00183 self._connect_signals() 00184 self._has_initialised = True 00185 00186 def _connect_signals(self): 00187 self._cover_view.props.vadjustment.connect('value-changed', 00188 self._viewport_changed) 00189 self._model.connect('album-updated', self._album_updated) 00190 self._model.connect('visual-updated', self._album_updated) 00191 00192 def _viewport_changed(self, *args): 00193 visible_range = self._cover_view.get_visible_range() 00194 00195 if visible_range: 00196 init, end = visible_range 00197 00198 # i have to use the tree iter instead of the path to iterate since 00199 # for some reason path.next doesn't work with the filtermodel 00200 tree_iter = self._model.store.get_iter(init) 00201 00202 self._visible_paths = [] 00203 00204 while init and init != end: 00205 self._visible_paths.append(init) 00206 00207 tree_iter = self._model.store.iter_next(tree_iter) 00208 init = self._model.store.get_path(tree_iter) 00209 00210 self._visible_paths.append(end) 00211 00212 def _album_updated(self, model, album_path, album_iter): 00213 # get the currently showing paths 00214 if not self._visible_paths: 00215 self._viewport_changed() 00216 00217 if (album_path and self._visible_paths) and album_path in self._visible_paths: 00218 # if our path is on the viewport, emit the signal to update it 00219 self._cover_view.queue_draw() 00220 00221 00222 class CoverIconView(EnhancedIconView, AbstractView): 00223 __gtype_name__ = "CoverIconView" 00224 00225 icon_spacing = GObject.property(type=int, default=0) 00226 icon_padding = GObject.property(type=int, default=0) 00227 icon_automatic = GObject.property(type=bool, default=True) 00228 00229 display_text_enabled = GObject.property(type=bool, default=False) 00230 display_text_pos = GObject.property(type=bool, default=False) 00231 name = 'coverview' 00232 panedposition = PanedCollapsible.Paned.COLLAPSE 00233 00234 __gsignals__ = { 00235 'update-toolbar': (GObject.SIGNAL_RUN_LAST, None, ()) 00236 } 00237 00238 00239 def __init__(self, *args, **kwargs): 00240 if not rb3compat.compare_pygobject_version("3.9"): 00241 super(CoverIconView, self).__init__(cell_area=AlbumArtCellArea(), *args, **kwargs) 00242 else: 00243 # this works in trusty but not in earlier versions - define in the super above 00244 super(CoverIconView, self).__init__(*args, **kwargs) 00245 self.props.cell_area = AlbumArtCellArea() 00246 00247 self.gs = GSetting() 00248 # custom text renderer 00249 self._text_renderer = None 00250 self.show_policy = AlbumShowingPolicy(self) 00251 self.view = self 00252 self._has_initialised = False 00253 self._last_path = None 00254 self._calc_motion_step = 0 00255 00256 def initialise(self, source): 00257 if self._has_initialised: 00258 return 00259 00260 self._has_initialised = True 00261 00262 self.view_name = "covers_view" 00263 super(CoverIconView, self).initialise(source) 00264 00265 self.shell = source.shell 00266 self.album_manager = source.album_manager 00267 00268 # setup iconview drag&drop support 00269 # first drag and drop on the coverart view to receive coverart 00270 self.enable_model_drag_dest([], Gdk.DragAction.COPY) 00271 self.drag_dest_add_image_targets() 00272 self.drag_dest_add_text_targets() 00273 self.connect('drag-drop', self.on_drag_drop) 00274 self.connect('drag-data-received', 00275 self.on_drag_data_received) 00276 self.source.paned.connect("expanded", self.bottom_expander_expanded_callback) 00277 00278 # lastly support drag-drop from coverart to devices/nautilus etc 00279 self.connect('drag-begin', self.on_drag_begin) 00280 self.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, 00281 [], Gdk.DragAction.COPY) 00282 #targets = Gtk.TargetList.new([Gtk.TargetEntry.new("application/x-rhythmbox-entry", 0, 0), 00283 # Gtk.TargetEntry.new("text/uri-list", 0, 1) ]) 00284 targets = Gtk.TargetList.new([Gtk.TargetEntry.new("text/uri-list", 0, 0)]) 00285 # N.B. values taken from rhythmbox v2.97 widgets/rb_entry_view.c 00286 targets.add_uri_targets(1) 00287 00288 self.drag_source_set_target_list(targets) 00289 self.connect("drag-data-get", self.on_drag_data_get) 00290 00291 # set the model to the view 00292 #self.set_pixbuf_column(AlbumsModel.columns['pixbuf']) 00293 self.set_model(self.album_manager.model.store) 00294 00295 # setup view to monitor mouse movements 00296 self.add_events(Gdk.EventMask.POINTER_MOTION_MASK) 00297 00298 self.hover_pixbufs = { 00299 'button_play': None, 00300 'button_play_hover': None, 00301 'button_playpause': None, 00302 'button_playpause_hover': None, 00303 'button_queue': None, 00304 'button_queue_hover': None, 00305 } 00306 00307 for pixbuf_type in self.hover_pixbufs: 00308 filename = 'img/' + pixbuf_type + '.png' 00309 filename = rb.find_plugin_file(self.plugin, filename) 00310 self.hover_pixbufs[pixbuf_type] = GdkPixbuf.Pixbuf.new_from_file_at_size(filename, 00311 PLAY_SIZE_X, PLAY_SIZE_Y) 00312 00313 self._connect_properties() 00314 self._connect_signals() 00315 00316 self._activate_markup() 00317 self.on_notify_icon_padding() 00318 self.on_notify_icon_spacing() 00319 00320 def _connect_properties(self): 00321 setting = self.gs.get_setting(self.gs.Path.PLUGIN) 00322 setting.bind( 00323 self.gs.PluginKey.ICON_SPACING, 00324 self, 00325 'icon_spacing', 00326 Gio.SettingsBindFlags.GET) 00327 setting.bind( 00328 self.gs.PluginKey.ICON_PADDING, 00329 self, 00330 'icon_padding', 00331 Gio.SettingsBindFlags.GET) 00332 00333 setting.bind(self.gs.PluginKey.DISPLAY_TEXT, self, 00334 'display_text_enabled', Gio.SettingsBindFlags.GET) 00335 00336 setting.bind(self.gs.PluginKey.ICON_AUTOMATIC, self, 00337 'icon_automatic', Gio.SettingsBindFlags.GET) 00338 00339 setting.bind(self.gs.PluginKey.DISPLAY_TEXT_POS, self, 00340 'display-text-pos', Gio.SettingsBindFlags.GET) 00341 00342 def _connect_signals(self): 00343 self.connect("item-clicked", self.item_clicked_callback) 00344 self.connect("selection-changed", self.selectionchanged_callback) 00345 self.connect("item-activated", self.item_activated_callback) 00346 self.connect('notify::icon-spacing', 00347 self.on_notify_icon_spacing) 00348 self.connect('notify::icon-padding', 00349 self.on_notify_icon_padding) 00350 self.connect('notify::display-text-enabled', 00351 self._activate_markup) 00352 self.connect('notify::display-text-pos', 00353 self._activate_markup) 00354 self.connect("motion-notify-event", self.on_pointer_motion) 00355 00356 def get_view_icon_name(self): 00357 return "iconview.png" 00358 00359 def resize_icon(self, cover_size): 00360 ''' 00361 Callback called when to resize the icon 00362 [common to all views] 00363 ''' 00364 self.set_item_width(cover_size) 00365 00366 def on_drag_drop(self, widget, context, x, y, time): 00367 ''' 00368 Callback called when a drag operation finishes over the cover view 00369 of the source. It decides if the dropped item can be processed as 00370 an image to use as a cover. 00371 ''' 00372 00373 # stop the propagation of the signal (deactivates superclass callback) 00374 widget.stop_emission_by_name('drag-drop') 00375 00376 # obtain the path of the icon over which the drag operation finished 00377 path, pos = widget.get_dest_item_at_pos(x, y) 00378 result = path is not None 00379 00380 if result: 00381 target = self.drag_dest_find_target(context, None) 00382 widget.drag_get_data(context, target, time) 00383 00384 return result 00385 00386 def on_drag_data_received(self, widget, drag_context, x, y, data, info, 00387 time): 00388 ''' 00389 Callback called when the drag source has prepared the data (pixbuf) 00390 for us to use. 00391 ''' 00392 00393 # stop the propagation of the signal (deactivates superclass callback) 00394 widget.stop_emission_by_name('drag-data-received') 00395 00396 # get the album and the info and ask the loader to update the cover 00397 path, pos = widget.get_dest_item_at_pos(x, y) 00398 album = widget.get_model()[path][2] 00399 00400 pixbuf = data.get_pixbuf() 00401 00402 if pixbuf: 00403 self.album_manager.cover_man.update_cover(album, pixbuf) 00404 else: 00405 uri = data.get_text() 00406 self.album_manager.cover_man.update_cover(album, uri=uri) 00407 00408 # call the context drag_finished to inform the source about it 00409 drag_context.finish(True, False, time) 00410 00411 00412 def on_drag_data_get(self, widget, drag_context, data, info, time): 00413 ''' 00414 Callback called when the drag destination (playlist) has 00415 requested what album (icon) has been dragged 00416 ''' 00417 00418 uris = [] 00419 for album in widget.get_selected_objects(): 00420 for track in album.get_tracks(): 00421 uris.append(track.location) 00422 00423 sel = data.set_uris(uris) 00424 # stop the propagation of the signal (deactivates superclass callback) 00425 widget.stop_emission_by_name('drag-data-get') 00426 00427 def on_drag_begin(self, widget, context): 00428 ''' 00429 Callback called when the drag-drop from coverview has started 00430 Changes the drag icon as appropriate 00431 ''' 00432 album_number = len(widget.get_selected_objects()) 00433 00434 if album_number == 1: 00435 item = Gtk.STOCK_DND 00436 else: 00437 item = Gtk.STOCK_DND_MULTIPLE 00438 00439 widget.drag_source_set_icon_stock(item) 00440 widget.stop_emission_by_name('drag-begin') 00441 00442 def _cover_play_hotspot(self, path, in_vacinity=False): 00443 if path: 00444 valid, rect = self.get_cell_rect(path, None) # rect of widget coords 00445 00446 cursor_x, cursor_y = self.get_pointer() # returns widget coords 00447 c_x = cursor_x - rect.x - (self.icon_padding / 2) - (self.icon_spacing / 2) 00448 c_y = cursor_y - rect.y - (self.icon_padding / 2) - (self.icon_spacing / 2) 00449 00450 sizing = (rect.width / 2) if in_vacinity else 0 00451 00452 if c_x < (PLAY_SIZE_X + sizing) and \ 00453 c_y < (PLAY_SIZE_Y + sizing) and \ 00454 c_x > 0 and \ 00455 c_y > 0: 00456 return True 00457 00458 return False 00459 00460 def on_pointer_motion(self, widget, event): 00461 self._current_mouse_x = event.x 00462 self._current_mouse_y = event.y 00463 00464 if self._calc_motion_step == 0: 00465 self._calc_motion_step = 1 00466 Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 100, 00467 self._calculate_hotspot) 00468 else: 00469 path = self.get_path_at_pos(self._current_mouse_x, 00470 self._current_mouse_y) 00471 00472 if not self._last_path or self._last_path != path: 00473 self._display_icon(None, self._last_path) 00474 00475 def _display_icon(self, icon, path): 00476 self.props.cell_area.hover_pixbuf = icon 00477 if path: 00478 valid, rect = self.get_cell_rect(path, None) 00479 self.props.window.invalidate_rect(rect, True) 00480 00481 self.queue_draw() 00482 00483 def _calculate_hotspot(self, *args): 00484 00485 path = self.get_path_at_pos(self._current_mouse_x, 00486 self._current_mouse_y) 00487 00488 # if the current path was not the same as the last path then 00489 # reset the counter 00490 if not self._last_path or self._last_path != path: 00491 self._display_icon(None, self._last_path) 00492 self._last_path = path 00493 self._calc_motion_step = 0 00494 return False 00495 00496 self._calc_motion_step = self._calc_motion_step + 1 00497 00498 # if havent yet reached the requisite number of steps then 00499 # let the thread roll to the next increment 00500 if self._calc_motion_step < 8: 00501 return True 00502 00503 if not self._cover_play_hotspot(path, in_vacinity = True): 00504 # we are not near the hot-spot so decrement the counter 00505 # hoping next time around we are near 00506 self._calc_motion_step = self._calc_motion_step - 1 00507 self._display_icon(None, self._last_path) 00508 return True 00509 00510 # from here on in, we are going to display a hotspot icon 00511 # so lets decide which one 00512 00513 (_, playing) = self.shell.props.shell_player.get_playing() 00514 00515 calc_path = -1 00516 if playing: 00517 entry = self.shell.props.shell_player.get_playing_entry() 00518 album = self.album_manager.model.get_from_dbentry(entry) 00519 calc_path = self.album_manager.model.get_path(album) 00520 00521 if playing and calc_path == path: 00522 icon = 'button_playpause' 00523 elif playing: 00524 icon = 'button_queue' 00525 else: 00526 icon = 'button_play' 00527 00528 # now we've got the icon - lets double check that we are 00529 # actually hovering exactly on the hotspot because the icon will visually change 00530 00531 exact_hotspot = self._cover_play_hotspot(path) 00532 if exact_hotspot: 00533 icon = icon + '_hover' 00534 00535 hover = self.hover_pixbufs[icon] 00536 00537 self._display_icon(hover, path) 00538 self._calc_motion_step = self._calc_motion_step - 1 00539 00540 return True 00541 00542 def item_clicked_callback(self, iconview, event, path): 00543 ''' 00544 Callback called when the user clicks somewhere on the cover_view. 00545 Along with source "show_hide_pane", takes care of showing/hiding the bottom 00546 pane after a second click on a selected album. 00547 ''' 00548 00549 # first test if we've clicked on the cover-play icon 00550 if self._cover_play_hotspot(path): 00551 (_, playing) = self.shell.props.shell_player.get_playing() 00552 00553 # first see if anything is playing... 00554 if playing: 00555 entry = self.shell.props.shell_player.get_playing_entry() 00556 album = self.album_manager.model.get_from_dbentry(entry) 00557 00558 # if the current playing entry corresponds to the album 00559 # we are hovering over then we are requesting to pause 00560 if self.album_manager.model.get_from_path(path) == album: 00561 self._last_path = path 00562 self.shell.props.shell_player.pause() 00563 self.on_pointer_motion(self, event) 00564 return 00565 00566 # this must be a new album so we are asking just 00567 # to play this new album ... just need a short interval 00568 # for the selection event to kick in first 00569 def delay(*args): 00570 if playing: # if we are playing then queue up the next album 00571 self.source.queue_selected_album(None, self.source.favourites) 00572 album = self.get_selected_objects()[0] 00573 cl = CoverLocale() 00574 cl.switch_locale(cl.Locale.LOCALE_DOMAIN) 00575 message = gettext.gettext('Album has added to list of playing albums') 00576 self.display_notification(album.name, 00577 message, 00578 album.cover.original) 00579 else: # otherwise just play it 00580 self._last_path = path 00581 self.source.play_selected_album(self.source.favourites) 00582 00583 icon = 'button_play_hover' 00584 self.props.cell_area.hover_pixbuf = \ 00585 self.hover_pixbufs[icon] 00586 00587 Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 250, 00588 delay, None) 00589 00590 return 00591 00592 # to expand the entry view 00593 ctrl = event.state & Gdk.ModifierType.CONTROL_MASK 00594 shift = event.state & Gdk.ModifierType.SHIFT_MASK 00595 00596 if self.icon_automatic: 00597 self.source.click_count += 1 if not ctrl and not shift else 0 00598 00599 if self.source.click_count == 1: 00600 album = self.album_manager.model.get_from_path(path) \ 00601 if path else None 00602 Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 250, 00603 self.source.show_hide_pane, album) 00604 00605 def item_activated_callback(self, iconview, path): 00606 ''' 00607 Callback called when the cover view is double clicked or space-bar 00608 is pressed. It plays the selected album 00609 ''' 00610 self.source.play_selected_album(self.source.favourites) 00611 00612 return True 00613 00614 def on_notify_icon_padding(self, *args): 00615 ''' 00616 Callback called when the icon-padding gsetting value is changed 00617 ''' 00618 self.set_item_padding(self.icon_padding) 00619 00620 def on_notify_icon_spacing(self, *args): 00621 ''' 00622 Callback called when the icon-spacing gsetting value is changed 00623 ''' 00624 self.set_row_spacing(self.icon_spacing) 00625 self.set_column_spacing(self.icon_spacing) 00626 00627 def _create_and_configure_renderer(self): 00628 #Add own cellrenderer 00629 self._text_renderer = Gtk.CellRendererText() 00630 00631 self._text_renderer.props.alignment = Pango.Alignment.CENTER 00632 self._text_renderer.props.wrap_mode = Pango.WrapMode.WORD 00633 self._text_renderer.props.xalign = 0.5 00634 self._text_renderer.props.yalign = 0 00635 self._text_renderer.props.width = \ 00636 self.album_manager.cover_man.cover_size 00637 self._text_renderer.props.wrap_width = \ 00638 self.album_manager.cover_man.cover_size 00639 00640 def _activate_markup(self, *args): 00641 ''' 00642 Utility method to activate/deactivate the markup text on the 00643 cover view. 00644 ''' 00645 if self.display_text_enabled and self.display_text_pos: 00646 if not self._text_renderer: 00647 # create and configure the custom cell renderer 00648 self._create_and_configure_renderer() 00649 00650 # set the renderer 00651 self.pack_end(self._text_renderer, False) 00652 self.add_attribute(self._text_renderer, 00653 'markup', AlbumsModel.columns['markup']) 00654 elif self._text_renderer: 00655 # remove the cell renderer 00656 self.props.cell_area.remove(self._text_renderer) 00657 00658 if self.display_text_enabled: 00659 self.set_tooltip_column(-1) # turnoff tooltips 00660 else: 00661 self.set_tooltip_column(AlbumsModel.columns['tooltip']) 00662 00663 def bottom_expander_expanded_callback(self, paned, expand): 00664 ''' 00665 Callback connected to expanded signal of the paned GtkExpander 00666 ''' 00667 if expand: 00668 # accommodate the viewport if there's an album selected 00669 if self.source.last_selected_album: 00670 def scroll_to_album(*args): 00671 # accommodate the viewport if there's an album selected 00672 path = self.album_manager.model.get_path( 00673 self.source.last_selected_album) 00674 00675 self.scroll_to_path(path, False, 0, 0) 00676 00677 return False 00678 00679 Gdk.threads_add_idle(GObject.PRIORITY_DEFAULT_IDLE, 00680 scroll_to_album, None) 00681 00682 00683 def switch_to_view(self, source, album): 00684 self.initialise(source) 00685 self.show_policy.initialise(source.album_manager) 00686 00687 self.scroll_to_album(album) 00688 00689 def grab_focus(self): 00690 super(EnhancedIconView, self).grab_focus()