diff --git a/ArtDisplay/ArtDisplay.py b/ArtDisplay/ArtDisplay.py index 4004195..3ac292d 100755 --- a/ArtDisplay/ArtDisplay.py +++ b/ArtDisplay/ArtDisplay.py @@ -31,6 +31,7 @@ from gi.repository import GObject, Gtk, Gdk, GdkPixbuf, Gio, Peas from gi.repository import RB import gettext + gettext.install('rhythmbox', RB.locale_dir()) import urllib.request @@ -44,379 +45,372 @@ THROBBER = 'process-working' ASPECT_RATIO_MIN = 0.9 ASPECT_RATIO_MAX = 1.1 -def merge_pixbufs (old_pb, new_pb, reserve_pb, step, width, height, mode=GdkPixbuf.InterpType.BILINEAR): - if width <= 1 and height <= 1: - return None - if old_pb is None: - if new_pb is None: - return reserve_pb - else: - return new_pb.scale_simple (width, height, mode) - elif step == 0.0: - return old_pb.scale_simple (width, height, mode) - elif new_pb is None: - if reserve_pb is None: - return None - new_pb = reserve_pb - sw, sh = (float (width)) / new_pb.props.width, (float (height)) / new_pb.props.height - alpha = int (step * 255) - ret = old_pb.scale_simple (width, height, mode) - new_pb.composite (ret, 0, 0, width, height, 0, 0, sw, sh, mode, alpha) - return ret -def merge_with_background (pixbuf, bgcolor, pad_if_not_near_square): - - if pixbuf is None: - return pixbuf - has_alpha = pixbuf.get_has_alpha () - width, height = pixbuf.props.width, pixbuf.props.height - if pad_if_not_near_square and (height < width * ASPECT_RATIO_MIN or - height > width * ASPECT_RATIO_MAX): - rw, rh = max (width, height), max (width, height) - left, top = (rw - width) // 2, (rh - height) // 2 - else: - if not has_alpha: - return pixbuf - rw, rh, left, top = width, height, 0, 0 - ret = GdkPixbuf.Pixbuf.new (GdkPixbuf.Colorspace.RGB, False, 8, rw, rh) - ret.fill ((int(bgcolor.red * 255) << 24) | (int(bgcolor.green * 255) << 16) | (int(bgcolor.blue * 255) << 8) | int(bgcolor.alpha * 255)) - if has_alpha: - pixbuf.composite (ret, left, top, width, height, left, top, 1.0, 1.0, GdkPixbuf.InterpType.NEAREST, 255) - else: - pixbuf.copy_area (0, 0, width, height, ret, left, top) - return ret - -class FadingImage (Gtk.Misc): - __gsignals__ = { - 'get-max-size' : (GObject.SIGNAL_RUN_LAST, GObject.TYPE_INT, ()) - } - def __init__ (self, missing_image): - Gtk.Misc.__init__(self) - self.sc_id = self.connect('screen-changed', self.screen_changed) - self.sa_id = self.connect('size-allocate', self.size_allocate_cb) - self.resize_id, self.fade_id, self.anim_id = 0, 0, 0 - self.missing_image = missing_image - self.size = 100 - self.screen_changed (self, None) - self.old_pixbuf, self.new_pixbuf = None, None - self.merged_pixbuf, self.missing_pixbuf = None, None - self.fade_step = 0.0 - self.anim, self.anim_frames, self.anim_size = None, None, 0 - - def disconnect_handlers (self): - self.disconnect(self.sc_id) - self.disconnect(self.sa_id) - self.icon_theme.disconnect(self.tc_id) - for id in self.resize_id, self.fade_id, self.anim_id: - if id != 0: - GObject.source_remove (id) - - def screen_changed (self, widget, old_screen): - if old_screen: - self.icon_theme.disconnect (self.tc_id) - self.icon_theme = Gtk.IconTheme.get_for_screen (self.get_screen ()) - self.tc_id = self.icon_theme.connect ('changed', self.theme_changed) - self.theme_changed (self.icon_theme) - - def reload_anim_frames (self): - icon_info = self.icon_theme.lookup_icon (THROBBER, -1, 0) - size = icon_info.get_base_size () - icon = GdkPixbuf.Pixbuf.new_from_file (icon_info.get_filename ()) - self.anim_frames = [ # along, then down - icon.new_subpixbuf (x * size, y * size, size, size) - for y in range (int (icon.props.height / size)) - for x in range (int (icon.props.width / size))] - self.anim_size = size - - def theme_changed (self, icon_theme): - try: - self.reload_anim_frames () - except Exception as e: - warn ("Throbber animation not loaded: %s" % e, Warning) - self.reload_util_pixbufs () - - def reload_util_pixbufs (self): - if self.size <= 1: - return - try: - missing_pixbuf = self.icon_theme.load_icon (ART_MISSING_ICON, self.size, 0) - except: - try: - missing_pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size (self.missing_image, self.size, self.size) - except Exception as e: - warn ("Missing artwork icon not found: %s" % e, Warning) - return - - bgcolor = self.get_style_context().get_background_color(Gtk.StateType.NORMAL) - self.missing_pixbuf = merge_with_background (missing_pixbuf, bgcolor, False) - - def size_allocate_cb (self, widget, allocation): - if self.resize_id == 0: - self.resize_id = GObject.idle_add (self.after_resize) - - max_size = self.emit ('get-max-size') - self.size = min (self.get_allocated_width (), max_size) - - def after_resize (self): - self.reload_util_pixbufs () - self.merged_pixbuf = None - self.resize_id = 0 - self.queue_draw () - return False - - def do_get_request_mode(self): - return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH - - def do_get_preferred_width(self): - # maybe set minimum width here? - return (0, 0) - - def do_get_preferred_height_for_width(self, width): - max_size = self.emit ('get-max-size') - size = min(self.get_allocated_width(), max_size) - return (size, size) - - def do_draw (self, cr): - if not self.ensure_merged_pixbuf (): - return False - - if self.merged_pixbuf.props.width != self.size: - draw_pb = self.merged_pixbuf.scale_simple (self.size, self.size, GdkPixbuf.InterpType.NEAREST) - else: - draw_pb = self.merged_pixbuf - - # center the image if we're wider than we are tall - pad = (self.get_allocation().width - self.size) / 2 - - left = pad - right = pad + self.size - top = 0 - bottom = self.size - if right > left and bottom > top: - Gdk.cairo_set_source_pixbuf(cr, draw_pb, pad, 0) - cr.rectangle(left, top, right - left, bottom - top) - cr.fill() - - if self.anim: - x, y, w, h = self.anim_rect () - Gdk.cairo_set_source_pixbuf(cr, self.anim, max(0, x), max(0, y)) - cr.rectangle(max(0, x), max(0, y), w, h) - cr.fill() - - return False - - def anim_rect (self): - alloc_width = self.get_allocated_width() - alloc_height = self.get_allocated_height() - return ((alloc_width - self.anim_size) / 2, - (alloc_height - self.anim_size) / 2, - min (self.anim_size, alloc_width), - min (self.anim_size, alloc_height)) - - def ensure_merged_pixbuf (self): - if self.merged_pixbuf is None: - alloc_width = self.get_allocated_width() - alloc_height = self.get_allocated_height() - self.merged_pixbuf = merge_pixbufs (self.old_pixbuf, self.new_pixbuf, self.missing_pixbuf, self.fade_step, alloc_width, alloc_height) - return self.merged_pixbuf - - def render_overlay (self): - ret = self.ensure_merged_pixbuf () - if ret and self.anim: - if ret is self.missing_pixbuf: ret = ret.copy () - x, y, w, h = self.anim_rect () - self.anim.composite (ret, max (x, 0), max (y, 0), w, h, x, y, 1, 1, GdkPixbuf.InterpType.BILINEAR, 255) - return ret - - def fade_art (self, first_time): - self.fade_step += 1.0 / FADE_STEPS - if self.fade_step > 0.999: - self.old_pixbuf = None - self.fade_id = 0 - self.merged_pixbuf = None - if first_time: - self.fade_id = GObject.timeout_add ((FADE_TOTAL_TIME / FADE_STEPS), self.fade_art, False) - return False - self.queue_resize () - return (self.fade_step <= 0.999) - - def animation_advance (self, counter, first_time): - self.anim = self.anim_frames[counter[0]] - counter[0] = (counter[0] + 1) % len(self.anim_frames) - x, y, w, h = self.anim_rect () - self.queue_draw_area (max (x, 0), max (y, 0), w, h) - if first_time: - self.anim_id = GObject.timeout_add (int (1000 / THROBBER_RATE), self.animation_advance, counter, False) - return False - return True - - def set_current_art (self, pixbuf, working): - if self.props.visible and self.get_allocated_width() > 1: - self.old_pixbuf = self.render_overlay () - else: - self.old_pixbuf = None # don't fade - - bgcolor = self.get_style_context().get_background_color(Gtk.StateType.NORMAL) - self.new_pixbuf = merge_with_background (pixbuf, bgcolor, True) - self.merged_pixbuf = None - self.fade_step = 0.0 - self.anim = None - if self.fade_id != 0: - GObject.source_remove (self.fade_id) - self.fade_id = 0 - if self.old_pixbuf is not None: - self.fade_id = GObject.timeout_add (working and WORKING_DELAY or (FADE_TOTAL_TIME / FADE_STEPS), self.fade_art, working) - if working and self.anim_id == 0 and self.anim_frames: - self.anim_id = GObject.timeout_add (WORKING_DELAY, self.animation_advance, [0], True) - if not working and self.anim_id != 0: - GObject.source_remove (self.anim_id) - self.anim_id = 0 - self.queue_resize () - -GObject.type_register (FadingImage) +def merge_pixbufs(old_pb, new_pb, reserve_pb, step, width, height, mode=GdkPixbuf.InterpType.BILINEAR): + if width <= 1 and height <= 1: + return None + if old_pb is None: + if new_pb is None: + return reserve_pb + else: + return new_pb.scale_simple(width, height, mode) + elif step == 0.0: + return old_pb.scale_simple(width, height, mode) + elif new_pb is None: + if reserve_pb is None: + return None + new_pb = reserve_pb + sw, sh = (float(width)) / new_pb.props.width, (float(height)) / new_pb.props.height + alpha = int(step * 255) + ret = old_pb.scale_simple(width, height, mode) + new_pb.composite(ret, 0, 0, width, height, 0, 0, sw, sh, mode, alpha) + return ret -class ArtDisplayWidget (FadingImage): - - def __init__ (self, missing_image): - super (ArtDisplayWidget, self).__init__ (missing_image) - self.set_padding (0, 5) - self.qt_id = self.connect ('query-tooltip', self.query_tooltip) - self.props.has_tooltip = True - self.current_entry, self.working = None, False - self.current_pixbuf, self.current_uri = None, None - - def disconnect_handlers (self): - super (ArtDisplayWidget, self).disconnect_handlers () - - def query_tooltip (self, widget, x, y, keyboard_mode, tooltip): - if (self.tooltip_image, self.tooltip_text) != (None, None): - tooltip.set_text(self.tooltip_text) - tooltip.set_icon(self.tooltip_image) - return True - else: - return False - - def set (self, entry, pixbuf, uri, tooltip_image, tooltip_text, working): - self.current_entry = entry - self.current_pixbuf = pixbuf - self.current_uri = uri - self.set_current_art (pixbuf, working) - - self.tooltip_image = None - if not self.current_entry: - self.tooltip_text = None - elif working: - self.tooltip_text = _("Searching...") - elif (tooltip_image, tooltip_text) != (None, None): - self.tooltip_image = tooltip_image - self.tooltip_text = tooltip_text - else: - self.tooltip_text = None +def merge_with_background(pixbuf, bgcolor, pad_if_not_near_square): + if pixbuf is None: + return pixbuf + has_alpha = pixbuf.get_has_alpha() + width, height = pixbuf.props.width, pixbuf.props.height + if pad_if_not_near_square and (height < width * ASPECT_RATIO_MIN or + height > width * ASPECT_RATIO_MAX): + rw, rh = max(width, height), max(width, height) + left, top = (rw - width) // 2, (rh - height) // 2 + else: + if not has_alpha: + return pixbuf + rw, rh, left, top = width, height, 0, 0 + ret = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, False, 8, rw, rh) + ret.fill((int(bgcolor.red * 255) << 24) | (int(bgcolor.green * 255) << 16) | (int(bgcolor.blue * 255) << 8) | int( + bgcolor.alpha * 255)) + if has_alpha: + pixbuf.composite(ret, left, top, width, height, left, top, 1.0, 1.0, GdkPixbuf.InterpType.NEAREST, 255) + else: + pixbuf.copy_area(0, 0, width, height, ret, left, top) + return ret -GObject.type_register (ArtDisplayWidget) +class FadingImage(Gtk.Misc): + __gsignals__ = { + 'get-max-size': (GObject.SIGNAL_RUN_LAST, GObject.TYPE_INT, ()) + } + + def __init__(self, missing_image): + Gtk.Misc.__init__(self) + self.sc_id = self.connect('screen-changed', self.screen_changed) + self.sa_id = self.connect('size-allocate', self.size_allocate_cb) + self.resize_id, self.fade_id, self.anim_id = 0, 0, 0 + self.missing_image = missing_image + self.size = 100 + self.screen_changed(self, None) + self.old_pixbuf, self.new_pixbuf = None, None + self.merged_pixbuf, self.missing_pixbuf = None, None + self.fade_step = 0.0 + self.anim, self.anim_frames, self.anim_size = None, None, 0 + + def disconnect_handlers(self): + self.disconnect(self.sc_id) + self.disconnect(self.sa_id) + self.icon_theme.disconnect(self.tc_id) + for id in self.resize_id, self.fade_id, self.anim_id: + if id != 0: + GObject.source_remove(id) + + def screen_changed(self, widget, old_screen): + if old_screen: + self.icon_theme.disconnect(self.tc_id) + self.icon_theme = Gtk.IconTheme.get_for_screen(self.get_screen()) + self.tc_id = self.icon_theme.connect('changed', self.theme_changed) + self.theme_changed(self.icon_theme) + + def reload_anim_frames(self): + icon_info = self.icon_theme.lookup_icon(THROBBER, -1, 0) + size = icon_info.get_base_size() + icon = GdkPixbuf.Pixbuf.new_from_file(icon_info.get_filename()) + self.anim_frames = [ # along, then down + icon.new_subpixbuf(x * size, y * size, size, size) + for y in range(int(icon.props.height / size)) + for x in range(int(icon.props.width / size))] + self.anim_size = size + + def theme_changed(self, icon_theme): + try: + self.reload_anim_frames() + except Exception as e: + warn("Throbber animation not loaded: %s" % e, Warning) + self.reload_util_pixbufs() + + def reload_util_pixbufs(self): + if self.size <= 1: + return + try: + missing_pixbuf = self.icon_theme.load_icon(ART_MISSING_ICON, self.size, 0) + except: + try: + missing_pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(self.missing_image, self.size, self.size) + except Exception as e: + warn("Missing artwork icon not found: %s" % e, Warning) + return + + bgcolor = self.get_style_context().get_background_color(Gtk.StateType.NORMAL) + self.missing_pixbuf = merge_with_background(missing_pixbuf, bgcolor, False) + + def size_allocate_cb(self, widget, allocation): + if self.resize_id == 0: + self.resize_id = GObject.idle_add(self.after_resize) + + max_size = self.emit('get-max-size') + self.size = min(self.get_allocated_width(), max_size) + + def after_resize(self): + self.reload_util_pixbufs() + self.merged_pixbuf = None + self.resize_id = 0 + self.queue_draw() + return False + + def do_get_request_mode(self): + return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH + + def do_get_preferred_width(self): + # maybe set minimum width here? + return (0, 0) + + def do_get_preferred_height_for_width(self, width): + max_size = self.emit('get-max-size') + size = min(self.get_allocated_width(), max_size) + return (size, size) + + def do_draw(self, cr): + if not self.ensure_merged_pixbuf(): + return False + + if self.merged_pixbuf.props.width != self.size: + draw_pb = self.merged_pixbuf.scale_simple(self.size, self.size, GdkPixbuf.InterpType.NEAREST) + else: + draw_pb = self.merged_pixbuf + + # center the image if we're wider than we are tall + pad = (self.get_allocation().width - self.size) / 2 + + left = pad + right = pad + self.size + top = 0 + bottom = self.size + if right > left and bottom > top: + Gdk.cairo_set_source_pixbuf(cr, draw_pb, pad, 0) + cr.rectangle(left, top, right - left, bottom - top) + cr.fill() + + if self.anim: + x, y, w, h = self.anim_rect() + Gdk.cairo_set_source_pixbuf(cr, self.anim, max(0, x), max(0, y)) + cr.rectangle(max(0, x), max(0, y), w, h) + cr.fill() + + return False + + def anim_rect(self): + alloc_width = self.get_allocated_width() + alloc_height = self.get_allocated_height() + return ((alloc_width - self.anim_size) / 2, + (alloc_height - self.anim_size) / 2, + min(self.anim_size, alloc_width), + min(self.anim_size, alloc_height)) + + def ensure_merged_pixbuf(self): + if self.merged_pixbuf is None: + alloc_width = self.get_allocated_width() + alloc_height = self.get_allocated_height() + self.merged_pixbuf = merge_pixbufs(self.old_pixbuf, self.new_pixbuf, self.missing_pixbuf, self.fade_step, + alloc_width, alloc_height) + return self.merged_pixbuf + + def render_overlay(self): + ret = self.ensure_merged_pixbuf() + if ret and self.anim: + if ret is self.missing_pixbuf: ret = ret.copy() + x, y, w, h = self.anim_rect() + self.anim.composite(ret, max(x, 0), max(y, 0), w, h, x, y, 1, 1, GdkPixbuf.InterpType.BILINEAR, 255) + return ret + + def fade_art(self, first_time): + self.fade_step += 1.0 / FADE_STEPS + if self.fade_step > 0.999: + self.old_pixbuf = None + self.fade_id = 0 + self.merged_pixbuf = None + if first_time: + self.fade_id = GObject.timeout_add((FADE_TOTAL_TIME / FADE_STEPS), self.fade_art, False) + return False + self.queue_resize() + return (self.fade_step <= 0.999) + + def animation_advance(self, counter, first_time): + self.anim = self.anim_frames[counter[0]] + counter[0] = (counter[0] + 1) % len(self.anim_frames) + x, y, w, h = self.anim_rect() + self.queue_draw_area(max(x, 0), max(y, 0), w, h) + if first_time: + self.anim_id = GObject.timeout_add(int(1000 / THROBBER_RATE), self.animation_advance, counter, False) + return False + return True + + def set_current_art(self, pixbuf, working): + if self.props.visible and self.get_allocated_width() > 1: + self.old_pixbuf = self.render_overlay() + else: + self.old_pixbuf = None # don't fade + + bgcolor = self.get_style_context().get_background_color(Gtk.StateType.NORMAL) + self.new_pixbuf = merge_with_background(pixbuf, bgcolor, True) + self.merged_pixbuf = None + self.fade_step = 0.0 + self.anim = None + if self.fade_id != 0: + GObject.source_remove(self.fade_id) + self.fade_id = 0 + if self.old_pixbuf is not None: + self.fade_id = GObject.timeout_add(working and WORKING_DELAY or (FADE_TOTAL_TIME / FADE_STEPS), + self.fade_art, working) + if working and self.anim_id == 0 and self.anim_frames: + self.anim_id = GObject.timeout_add(WORKING_DELAY, self.animation_advance, [0], True) + if not working and self.anim_id != 0: + GObject.source_remove(self.anim_id) + self.anim_id = 0 + self.queue_resize() -class ArtDisplayPlugin (GObject.GObject, Peas.Activatable): - __gtype_name__ = 'ArtDisplayPlugin' - object = GObject.property(type=GObject.GObject) +GObject.type_register(FadingImage) - def __init__ (self): - GObject.GObject.__init__ (self) - def do_activate (self): - shell = self.object - sp = shell.props.shell_player - self.player_cb_ids = ( - sp.connect ('playing-song-changed', self.playing_entry_changed), - sp.connect ('playing-changed', self.playing_changed) - ) - self.art_store = RB.ExtDB(name="album-art") - self.art_widget = ArtDisplayWidget (rb.find_plugin_file (self, ART_MISSING_ICON + ".svg")) - self.art_widget.connect ('get-max-size', self.get_max_art_size) - self.art_widget.connect ('button-press-event', self.on_button_press) - self.art_container = Gtk.VBox () - self.art_container.pack_start (self.art_widget, True, True, 6) - shell.add_widget (self.art_container, RB.ShellUILocation.SIDEBAR, False, True) - self.current_entry, self.current_pixbuf = None, None - self.playing_entry_changed (sp, sp.get_playing_entry ()) +class ArtDisplayWidget(FadingImage): + def __init__(self, missing_image): + super(ArtDisplayWidget, self).__init__(missing_image) + self.set_padding(0, 5) + self.qt_id = self.connect('query-tooltip', self.query_tooltip) + self.props.has_tooltip = True + self.current_entry, self.working = None, False + self.current_pixbuf, self.current_uri = None, None - def do_deactivate (self): + def disconnect_handlers(self): + super(ArtDisplayWidget, self).disconnect_handlers() - shell = self.object - sp = shell.props.shell_player - for id in self.player_cb_ids: - sp.disconnect (id) - self.player_cb_ids = () + def query_tooltip(self, widget, x, y, keyboard_mode, tooltip): + if (self.tooltip_image, self.tooltip_text) != (None, None): + tooltip.set_text(self.tooltip_text) + tooltip.set_icon(self.tooltip_image) + return True + else: + return False - shell.remove_widget (self.art_container, RB.ShellUILocation.SIDEBAR) - self.art_widget.disconnect_handlers () - self.art_widget = None - self.art_container = None + def set(self, entry, pixbuf, uri, tooltip_image, tooltip_text, working): + self.current_entry = entry + self.current_pixbuf = pixbuf + self.current_uri = uri + self.set_current_art(pixbuf, working) - def playing_changed (self, sp, playing): - self.set_entry(sp.get_playing_entry ()) + self.tooltip_image = None + if not self.current_entry: + self.tooltip_text = None + elif working: + self.tooltip_text = _("Searching...") + elif (tooltip_image, tooltip_text) != (None, None): + self.tooltip_image = tooltip_image + self.tooltip_text = tooltip_text + else: + self.tooltip_text = None - def playing_entry_changed (self, sp, entry): - self.set_entry(entry) - def set_entry (self, entry): - if rb.entry_equal(entry, self.current_entry): - return +GObject.type_register(ArtDisplayWidget) - self.art_widget.set (entry, None, None, None, None, True) - self.art_container.show_all () - self.current_entry = entry - self.current_pixbuf = None - if entry is not None: - key = entry.create_ext_db_key (RB.RhythmDBPropType.ALBUM) - self.art_store.request(key, self.art_store_request_cb, entry) +class ArtDisplayPlugin(GObject.GObject, Peas.Activatable): + __gtype_name__ = 'ArtDisplayPlugin' + object = GObject.property(type=GObject.GObject) - def art_store_request_cb(self, *args): # key, filename, data, entry): - - # rhythmbox 3.2 breaks the API - - # since there isnt a way to say "we are using RB 3.2" we have to hack this... - # need to find the args filename(str), data (pixbuf) and entry (RB.RhythmDBEntry) manually - data = None - for arg in args: - if isinstance(arg, str): - filename = arg - if isinstance(arg, GdkPixbuf.Pixbuf): - data = arg - if isinstance(arg, RB.RhythmDBEntry): - entry = arg + def __init__(self): + GObject.GObject.__init__(self) - if rb.entry_equal(entry, self.current_entry) is False: - # track changed while we were searching - return + def do_activate(self): + shell = self.object + sp = shell.props.shell_player + self.player_cb_ids = ( + sp.connect('playing-song-changed', self.playing_entry_changed), + sp.connect('playing-changed', self.playing_changed) + ) + self.art_store = RB.ExtDB(name="album-art") + self.art_widget = ArtDisplayWidget(rb.find_plugin_file(self, ART_MISSING_ICON + ".svg")) + self.art_widget.connect('get-max-size', self.get_max_art_size) + self.art_widget.connect('button-press-event', self.on_button_press) + self.art_container = Gtk.VBox() + self.art_container.pack_start(self.art_widget, True, True, 6) + shell.add_widget(self.art_container, RB.ShellUILocation.SIDEBAR, False, True) + self.current_entry, self.current_pixbuf = None, None + self.playing_entry_changed(sp, sp.get_playing_entry()) - if isinstance(data, GdkPixbuf.Pixbuf): - self.current_pixbuf = data - uri = "file://" + urllib.request.pathname2url(filename) - self.art_widget.set (entry, self.current_pixbuf, uri, None, None, False) - else: - self.art_widget.set (entry, None, None, None, None, False) + def do_deactivate(self): - def get_max_art_size (self, widget): - # limit the art image to a third of the window height to prevent it from - # forcing the window to resize, obscuring everything else, and so on. - shell = self.object - (width, height) = shell.props.window.get_size() - return height / 3 + shell = self.object + sp = shell.props.shell_player + for id in self.player_cb_ids: + sp.disconnect(id) + self.player_cb_ids = () - def on_button_press (self, widget, event): - # on double clicks, open the cover image (if there is one) in the default - # image viewer + shell.remove_widget(self.art_container, RB.ShellUILocation.SIDEBAR) + self.art_widget.disconnect_handlers() + self.art_widget = None + self.art_container = None - doubleclick = Gdk.EventType._2BUTTON_PRESS - if event.type != doubleclick or event.button != 1: - return + def playing_changed(self, sp, playing): + self.set_entry(sp.get_playing_entry()) - if self.art_widget.current_uri is None: - return + def playing_entry_changed(self, sp, entry): + self.set_entry(entry) - f = Gio.file_new_for_uri(self.art_widget.current_uri) - shell = self.object - Gtk.show_uri(shell.props.window.get_screen(), f.get_uri(), event.time) + def set_entry(self, entry): + if rb.entry_equal(entry, self.current_entry): + return + + self.art_widget.set(entry, None, None, None, None, True) + self.art_container.show_all() + self.current_entry = entry + self.current_pixbuf = None + + if entry is not None: + key = entry.create_ext_db_key(RB.RhythmDBPropType.ALBUM) + self.art_store.request(key, self.art_store_request_cb, entry) + + def art_store_request_cb(self, key, filename, data, entry): + if rb.entry_equal(entry, self.current_entry) is False: + # track changed while we were searching + return + + if isinstance(data, GdkPixbuf.Pixbuf): + self.current_pixbuf = data + uri = "file://" + urllib.request.pathname2url(filename) + self.art_widget.set(entry, self.current_pixbuf, uri, None, None, False) + else: + self.art_widget.set(entry, None, None, None, None, False) + + def get_max_art_size(self, widget): + # limit the art image to a third of the window height to prevent it from + # forcing the window to resize, obscuring everything else, and so on. + shell = self.object + (width, height) = shell.props.window.get_size() + return height / 3 + + def on_button_press(self, widget, event): + # on double clicks, open the cover image (if there is one) in the default + # image viewer + + doubleclick = Gdk.EventType._2BUTTON_PRESS + if event.type != doubleclick or event.button != 1: + return + + if self.art_widget.current_uri is None: + return + + f = Gio.file_new_for_uri(self.art_widget.current_uri) + shell = self.object + Gtk.show_uri(shell.props.window.get_screen(), f.get_uri(), event.time)