Ejemplo n.º 1
0
 def __init__(self, view_type):
     """
         Init view
         @param view_type as ViewType
     """
     View.__init__(self, StorageType.COLLECTION,
                   view_type | ViewType.OVERLAY)
     self.__lyrics_timeout_id = None
     self.__downloads_running = 0
     self.__lyrics_text = ""
     self._empty_message = _("No track playing")
     self._empty_icon_name = "view-dual-symbolic"
     self.__lyrics_label = LyricsLabel()
     self.__lyrics_label.show()
     self.__lyrics_label.set_property("halign", Gtk.Align.CENTER)
     self.__banner = LyricsBannerWidget(self.view_type)
     self.__banner.show()
     self.__banner.connect("translate", self.__on_translate)
     self.add_widget(self.__lyrics_label, self.__banner)
     self.__lyrics_helper = LyricsHelper()
     self.__update_lyrics_style()
     self.__information_store = InformationStore()
     return [(App().window.container.widget, "notify::folded",
              "_on_container_folded"),
             (App().player, "current-changed", "_on_current_changed")]
Ejemplo n.º 2
0
 def _on_button_clicked(self, button):
     """
         Show file chooser
         @param button as Gtk.button
     """
     dialog = Gtk.FileChooserDialog()
     dialog.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
     dialog.add_buttons(Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
     dialog.set_transient_for(App().window)
     self.__close_popover()
     response = dialog.run()
     if response == Gtk.ResponseType.OK:
         try:
             f = Gio.File.new_for_path(dialog.get_filename())
             (status, data, tag) = f.load_contents()
             if not status:
                 raise
             if self.__album is not None:
                 App().art.save_album_artwork(data, self.__album.id)
             else:
                 InformationStore.uncache_artwork(self.__artist)
                 InformationStore.add_artist_artwork(self.__artist, data)
                 App().art.emit("artist-artwork-changed", self.__artist)
             self._streams = {}
         except Exception as e:
             Logger.error("ArtworkSearch::_on_button_clicked(): %s" % e)
     dialog.destroy()
Ejemplo n.º 3
0
 def __init__(self):
     """
         Init art downloader
     """
     self.__albums_queue = []
     self.__albums_history = []
     self.__in_albums_download = False
     self.__cache_artists_running = False
     InformationStore.init()
Ejemplo n.º 4
0
 def __init__(self, view_type, minimal=False):
     """
         Init artist infos
         @param view_type as ViewType
         @param minimal as bool
     """
     View.__init__(self, get_default_storage_type(), view_type)
     self.__information_store = InformationStore()
     self.__cancellable = Gio.Cancellable()
     self.__minimal = minimal
     self.__artist_name = ""
Ejemplo n.º 5
0
 def __init__(self, minimal=False):
     """
         Init artist infos
         @param follow_player as bool
     """
     BaseView.__init__(self)
     Gtk.Bin.__init__(self)
     self.__information_store = InformationStore()
     self.__information_store.connect("artist-info-changed",
                                      self.__on_artist_info_changed)
     self.__cancellable = Gio.Cancellable()
     self.__minimal = minimal
     self.__artist_name = ""
     self.connect("unmap", self.__on_unmap)
Ejemplo n.º 6
0
 def _on_reset_confirm(self, button):
     """
         Reset cover
         @param button as Gtk.Button
     """
     self.__infobar.hide()
     if self.__album is not None:
         App().art.remove_album_artwork(self.__album)
         App().art.clean_album_cache(self.__album)
         App().art.emit("album-artwork-changed", self.__album.id)
     else:
         InformationStore.uncache_artwork(self.__artist)
         InformationStore.add_artist_artwork(self.__artist, None)
         App().art.emit("artist-artwork-changed", self.__artist)
     self.__close_popover()
Ejemplo n.º 7
0
 def do_own_render(self, ctx, widget, cell_area, size):
     surface = None
     if self.rowid in self.__surfaces.keys():
         surface = self.__surfaces[self.rowid]
     if surface is None:
         path = InformationStore.get_artwork_path(self.artist, size,
                                                  self.__scale_factor)
         if path is not None:
             pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
                 path, size, size)
             surface = Gdk.cairo_surface_create_from_pixbuf(
                 pixbuf, self.__scale_factor, None)
             self.__surfaces[self.rowid] = surface
     if surface is None:
         surface = Gtk.IconTheme.get_default().load_surface(
             "avatar-default-symbolic", ArtSize.ARTIST_SMALL, 1,
             widget.get_window(), 0)
     ctx.translate(cell_area.x, cell_area.y)
     ctx.new_sub_path()
     radius = ArtSize.ARTIST_SMALL / 2
     ctx.arc(ArtSize.ARTIST_SMALL / 2, ArtSize.ARTIST_SMALL / 2, radius, 0,
             2 * pi)
     ctx.set_source_rgb(1, 1, 1)
     ctx.fill_preserve()
     ctx.set_line_width(2)
     ctx.set_source_rgba(0, 0, 0, 0.3)
     ctx.stroke_preserve()
     ctx.set_source_surface(surface, 0, 0)
     ctx.clip()
     ctx.paint()
Ejemplo n.º 8
0
 def __on_activate(self, flowbox, child):
     """
         Use pixbuf as cover
         Reset cache and use player object to announce cover change
     """
     try:
         data = self.__contents[child.get_child()]
         self.__close_popover()
         if self.__album is not None:
             App().art.save_album_artwork(data, self.__album.id)
         else:
             InformationStore.uncache_artwork(self.__artist)
             InformationStore.add_artist_artwork(self.__artist, data)
             App().art.emit("artist-artwork-changed", self.__artist)
         self._streams = {}
     except:
         self.__infobar_label.set_text(_("Reset artwork?"))
         self.__infobar.show()
         # GTK 3.20 https://bugzilla.gnome.org/show_bug.cgi?id=710888
         self.__infobar.queue_resize()
Ejemplo n.º 9
0
 def __get_artist_artwork_path_from_cache(self, artist, size):
     """
         Get artist artwork path
         @param artist as str
         @param size as int
         @return str
     """
     path = InformationStore.get_artwork_path(artist, size,
                                              self.get_scale_factor())
     if path is not None:
         return path
     return None
Ejemplo n.º 10
0
 def __get_artist_artwork(self, artist, width, height, scale_factor,
                          effect):
     """
         Set artwork for album id
         @param artist as str
         @param width as int
         @param height as int
         @param scale_factor as int
         @return GdkPixbuf.Pixbuf
     """
     path = InformationStore.get_artwork_path(artist, width, scale_factor)
     if path is not None:
         pixbuf = GdkPixbuf.Pixbuf.new_from_file(path)
         if effect & ArtHelperEffect.BLUR:
             pixbuf = self.__get_blur(pixbuf, width, height)
         return pixbuf
     return None
Ejemplo n.º 11
0
 def __cache_artists_artwork(self):
     """
         Cache artwork for all artists
     """
     # Then cache for lastfm/spotify/deezer/...
     for (artist_id, artist, sort) in App().artists.get([]):
         if not get_network_available() or\
                 InformationStore.artwork_exists(artist):
             continue
         artwork_set = False
         for (api, helper, unused) in InformationStore.WEBSERVICES:
             Logger.debug("Downloader::__cache_artists_info(): %s@%s" %
                          (artist, api))
             if helper is None:
                 continue
             try:
                 method = getattr(self, helper)
                 uri = method(artist)
                 if uri is not None:
                     (status,
                      data) = TaskHelper().load_uri_content_sync(uri, None)
                     if status:
                         InformationStore.add_artist_artwork(artist, data)
                         artwork_set = True
                         Logger.debug("""Downloader::
                                      __cache_artists_info(): %s""" % uri)
                     else:
                         InformationStore.add_artist_artwork(artist, None)
                     break
             except Exception as e:
                 Logger.error("Downloader::__cache_artists_info(): %s, %s" %
                              (e, artist))
                 InformationStore.add_artist_artwork(artist, None)
         if artwork_set:
             GLib.idle_add(App().art.emit, "artist-artwork-changed", artist)
     self.__cache_artists_running = False
Ejemplo n.º 12
0
class InformationView(View):
    """
        View with artist information
    """

    def __init__(self, view_type, minimal=False):
        """
            Init artist infos
            @param view_type as ViewType
            @param minimal as bool
        """
        View.__init__(self, get_default_storage_type(), view_type)
        self.__information_store = InformationStore()
        self.__cancellable = Gio.Cancellable()
        self.__minimal = minimal
        self.__artist_name = ""

    def populate(self, artist_id=None):
        """
            Show information for artists
            @param artist_id as int
        """
        builder = Gtk.Builder()
        builder.add_from_resource(
            "/org/gnome/Lollypop/ArtistInformation.ui")
        builder.connect_signals(self)
        self.__scrolled = builder.get_object("scrolled")
        widget = builder.get_object("widget")
        self.add(widget)
        self.__stack = builder.get_object("stack")
        self.__listbox = builder.get_object("listbox")
        self.__artist_label = builder.get_object("artist_label")
        title_label = builder.get_object("title_label")
        self.__artist_artwork = builder.get_object("artist_artwork")
        bio_eventbox = builder.get_object("bio_eventbox")
        artist_label_eventbox = builder.get_object("artist_label_eventbox")
        bio_eventbox.connect("realize", set_cursor_type)
        artist_label_eventbox.connect("realize", set_cursor_type)
        self.__gesture1 = GesturesHelper(
            bio_eventbox,
            primary_press_callback=self._on_info_label_press)
        self.__gesture2 = GesturesHelper(
            artist_label_eventbox,
            primary_press_callback=self._on_artist_label_press)
        self.__bio_label = builder.get_object("bio_label")
        if artist_id is None and App().player.current_track.id is not None:
            builder.get_object("header").show()
            if App().player.current_track.album.artist_ids[0] ==\
                    Type.COMPILATIONS:
                artist_id = App().player.current_track.artist_ids[0]
            else:
                artist_id = App().player.current_track.album.artist_ids[0]
            title_label.set_text(App().player.current_track.title)
        self.__artist_name = App().artists.get_name(artist_id)
        if self.__minimal:
            self.__bio_label.set_margin_start(MARGIN)
            self.__bio_label.set_margin_end(MARGIN)
            self.__bio_label.set_margin_top(MARGIN)
            self.__bio_label.set_margin_bottom(MARGIN)
            self.__artist_artwork.hide()
        else:
            self.__artist_artwork.set_margin_start(MARGIN_SMALL)
            builder.get_object("header").show()
            self.__artist_label.set_text(self.__artist_name)
            self.__artist_label.show()
            title_label.show()
            App().art_helper.set_artist_artwork(
                                    self.__artist_name,
                                    ArtSize.SMALL * 3,
                                    ArtSize.SMALL * 3,
                                    self.__artist_artwork.get_scale_factor(),
                                    ArtBehaviour.ROUNDED |
                                    ArtBehaviour.CROP_SQUARE |
                                    ArtBehaviour.CACHE,
                                    self.__on_artist_artwork)
            albums_view = AlbumsListView([], [],
                                         ViewType.SCROLLED)
            albums_view.set_size_request(300, -1)
            albums_view.show()
            albums_view.set_margin_start(5)
            albums_view.add_widget(albums_view.box)
            widget.attach(albums_view, 2, 1, 1, 2)
            albums = []
            storage_type = get_default_storage_type()
            for album_id in App().albums.get_ids([], [artist_id],
                                                 storage_type, True):
                albums.append(Album(album_id))
            if not albums:
                albums = [App().player.current_track.album]
            albums_view.populate(albums)
        content = self.__information_store.get_information(self.__artist_name,
                                                           ARTISTS_PATH)
        if content is None:
            self.__bio_label.set_text(_("Loading information"))
            from lollypop.information_downloader import InformationDownloader
            downloader = InformationDownloader()
            downloader.get_information(self.__artist_name,
                                       self.__on_artist_information,
                                       self.__artist_name)
        else:
            App().task_helper.run(self.__to_markup, content,
                                  callback=(self.__bio_label.set_markup,))

#######################
# PROTECTED           #
#######################
    def _on_unmap(self, widget):
        """
            Cancel operations
            @param widget as Gtk.Widget
        """
        self.__cancellable.cancel()

    def _on_previous_button_clicked(self, button):
        """
            Go back to main view
            @param button as Gtk.Button
        """
        self.__stack.set_visible_child_name("main")

    def _on_artist_label_press(self, x, y, event):
        """
            Go to artist view
            @param x as int
            @param y as int
            @param event as Gdk.Event
        """
        popover = self.get_ancestor(Gtk.Popover)
        if popover is not None:
            popover.popdown()
        if App().player.current_track.id is None:
            return
        GLib.idle_add(App().window.container.show_view,
                      [Type.ARTISTS],
                      App().player.current_track.album.artist_ids)

    def _on_info_label_press(self, x, y, event):
        """
            Show information cache (for edition)
            @param x as int
            @param y as int
            @param event as Gdk.Event
        """
        if get_network_available("WIKIPEDIA"):
            from lollypop.wikipedia import Wikipedia
            wikipedia = Wikipedia()
            self.__stack.set_visible_child_name("select")
            App().task_helper.run(wikipedia.get_search_list,
                                  self.__artist_name,
                                  callback=(self.__on_wikipedia_search_list,))

    def _on_row_activated(self, listbox, row):
        """
            Update artist information
            @param listbox as Gtk.ListBox
            @param row as Gtk.ListBoxRow
        """
        self.__stack.set_visible_child_name("main")
        from lollypop.wikipedia import Wikipedia
        wikipedia = Wikipedia()
        App().task_helper.run(wikipedia.get_content_for_page_id,
                              row.page_id, row.locale,
                              callback=(self.__on_wikipedia_get_content,))

#######################
# PRIVATE             #
#######################
    def __to_markup(self, data):
        """
            Transform message to markup
            @param data as bytes
            @return str
        """
        pango = ["large", "x-large", "xx-large"]
        start = ["^===*", "^==", "^="]
        end = ["===*$", "==$", "=$"]
        i = 0
        text = GLib.markup_escape_text(data.decode("utf-8"))
        while i < len(pango):
            text = re.sub(start[i], "<b><span size='%s'>" % pango[i],
                          text, flags=re.M)
            text = re.sub(end[i], "</span></b>", text, flags=re.M)
            i += 1
        return text

    def __on_wikipedia_search_list(self, items):
        """
            Populate view with items
            @param items as [(str, str)]
        """
        for item in items:
            row = ArtistRow(item)
            row.show()
            self.__listbox.add(row)

    def __on_wikipedia_get_content(self, content):
        """
            Update label and save to cache
            @param content as str
        """
        if content is not None:
            App().task_helper.run(self.__to_markup, content,
                                  callback=(self.__bio_label.set_markup,))
            self.__information_store.save_information(
                self.__artist_name, ARTISTS_PATH, content)

    def __on_artist_artwork(self, surface):
        """
            Finish widget initialisation
            @param surface as cairo.Surface
        """
        if surface is None:
            self.__artist_artwork.hide()
        else:
            self.__artist_artwork.set_from_surface(surface)
            del surface

    def __on_artist_information(self, content, artist_name):
        """
            Set label
            @param content as bytes
            @param artist_name as str
        """
        if artist_name != self.__artist_name:
            return
        if content is None:
            self.__bio_label.set_text(
                _("No information for %s") % self.__artist_name)
        else:
            App().task_helper.run(self.__to_markup, content,
                                  callback=(self.__bio_label.set_markup,))
            self.__information_store.save_information(self.__artist_name,
                                                      ARTISTS_PATH,
                                                      content)
Ejemplo n.º 13
0
class LyricsView(View, SignalsHelper):
    """
        Show lyrics for track
    """

    __FILTERS = ["[explicit]", "(explicit)"]

    # TODO add https://www.musixmatch.com support
    @signals_map
    def __init__(self, view_type):
        """
            Init view
            @param view_type as ViewType
        """
        View.__init__(self, StorageType.COLLECTION,
                      view_type | ViewType.OVERLAY)
        self.__lyrics_timeout_id = None
        self.__downloads_running = 0
        self.__lyrics_text = ""
        self._empty_message = _("No track playing")
        self._empty_icon_name = "view-dual-symbolic"
        self.__lyrics_label = LyricsLabel()
        self.__lyrics_label.show()
        self.__lyrics_label.set_property("halign", Gtk.Align.CENTER)
        self.__banner = LyricsBannerWidget(self.view_type)
        self.__banner.show()
        self.__banner.connect("translate", self.__on_translate)
        self.add_widget(self.__lyrics_label, self.__banner)
        self.__lyrics_helper = LyricsHelper()
        self.__update_lyrics_style()
        self.__information_store = InformationStore()
        return [(App().window.container.widget, "notify::folded",
                 "_on_container_folded"),
                (App().player, "current-changed", "_on_current_changed")]

    def populate(self, track):
        """
            Set lyrics
            @param track as Track
        """
        self.__banner.translate_button.set_sensitive(False)
        if track.id is None:
            self.__lyrics_label.set_text("")
            return
        self.__lyrics_label.set_text(_("Loading…"))
        lyrics = ""
        if isinstance(track, Track):
            self.__lyrics_helper.load(track)
            # First check synced lyrics
            if self.__lyrics_helper.available:
                if self.__lyrics_timeout_id is None:
                    self.__lyrics_timeout_id = GLib.timeout_add(
                        200, self.__show_sync_lyrics)
                return
            else:
                if self.__lyrics_timeout_id is not None:
                    GLib.source_remove(self.__lyrics_timeout_id)
                    self.__lyrics_timeout_id = None
                if track.storage_type & StorageType.COLLECTION:
                    from lollypop.tagreader import TagReader, Discoverer
                    tagreader = TagReader()
                    discoverer = Discoverer()
                    try:
                        info = discoverer.get_info(track.uri)
                    except:
                        info = None
                    if info is not None:
                        tags = info.get_tags()
                        lyrics = tagreader.get_lyrics(tags)
        if lyrics:
            self.__lyrics_label.set_text(lyrics)
            self.__lyrics_text = lyrics
        else:
            name = track.name + track.album.name + ",".join(track.artists)
            content = self.__information_store.get_information(
                name, LYRICS_PATH)
            if content:
                self.__lyrics_label.set_text(content.decode("utf-8"))
            elif not get_network_available():
                self.__lyrics_label.set_text(
                    _("Network unavailable or disabled in settings"))
            else:
                self.__lyrics_helper.get_lyrics_from_web(
                    track, self.__on_lyrics, False, track)

    @property
    def args(self):
        return None

##############
# PROTECTED  #
##############

    def __on_translate(self, banner, active):
        """
            Translate lyrics
            @param banner as LyricsBannerWidget
            @param active as bool
        """
        if active:
            App().task_helper.run(self.__get_blob,
                                  self.__lyrics_text,
                                  callback=(self.__lyrics_label.set_text, ))
        else:
            self.__lyrics_label.set_text(self.__lyrics_text)

    def _on_unmap(self, widget):
        """
            Connect player signal
            @param widget as Gtk.Widget
        """
        self.__lyrics_helper.cancel()
        View._on_unmap(self, widget)
        if self.__lyrics_timeout_id is not None:
            GLib.source_remove(self.__lyrics_timeout_id)
            self.__lyrics_timeout_id = None

    def _on_current_changed(self, player):
        """
            Update lyrics
            @param player as Player
        """
        self.populate(App().player.current_track)

    def _on_container_folded(self, leaflet, folded):
        """
            Handle libhandy folded status
            @param leaflet as Handy.Leaflet
            @param folded as Gparam
        """
        self.__update_lyrics_style()

############
# PRIVATE  #
############

    def __show_sync_lyrics(self):
        """
            Show sync lyrics for track
        """
        timestamp = App().player.position
        (previous, current, next) =\
            self.__lyrics_helper.get_lyrics_for_timestamp(timestamp + 200)
        lyrics = ""
        for line in previous[:-1]:
            if line.strip():
                escaped = GLib.markup_escape_text(line)
                lyrics += "<span alpha='20000'>%s</span>" % escaped + "\n"
        lyrics += "\n"
        for line in previous[-1:]:
            if line.strip():
                escaped = GLib.markup_escape_text(line)
                lyrics += "<span alpha='35000'>%s</span>" % escaped + "\n"
        for line in current:
            if line.strip():
                escaped = GLib.markup_escape_text(line)
                lyrics += "<span>%s</span>" % escaped + "\n"
        for line in next:
            if line.strip():
                escaped = GLib.markup_escape_text(line)
                lyrics += "<span alpha='35000'>%s</span>" % escaped + "\n"
        self.__lyrics_label.set_markup(lyrics, True)
        return True

    def __get_blob(self, text):
        """
            Translate text with current user locale
            @param text as str
        """
        try:
            locales = GLib.get_language_names()
            user_code = locales[0].split(".")[0]
            try:
                from textblob.blob import TextBlob
            except:
                return _("You need to install python3-textblob module")
            blob = TextBlob(text)
            return str(blob.translate(to=user_code))
        except Exception as e:
            Logger.error("LyricsView::__get_blob(): %s", e)
            return _("Can't translate this lyrics")

    def __update_lyrics_style(self):
        """
            Update lyrics style based on current view width
        """
        context = self.get_style_context()
        for cls in context.list_classes():
            context.remove_class(cls)
        context.add_class("lyrics")
        if App().window.folded:
            if self.__lyrics_helper.available:
                context.add_class("text-large")
            else:
                context.add_class("text-medium")
        else:
            if self.__lyrics_helper.available:
                context.add_class("text-x-large")
            else:
                context.add_class("text-large")

    def __on_lyrics(self, lyrics, filtered, track):
        """
            Set lyrics
            @param lyrics as str/None
            @param filtered as bool
            @param track as Track
        """
        if lyrics is None:
            self.__lyrics_label.set_text(_("Disabled in network settings"))
        elif lyrics == "":
            if filtered:
                self.__lyrics_label.set_text(_("No lyrics found ") + "😓")
            else:
                name = track.name.lower()
                for _filter in self.__FILTERS:
                    name = name.replace(_filter, "")
                track.set_name(name)
                self.__lyrics_helper.get_lyrics_from_web(
                    track, self.__on_lyrics, True, track)
        else:
            self.__lyrics_label.set_text(lyrics)
            self.__lyrics_text = lyrics
            name = track.name + track.album.name + ",".join(track.artists)
            self.__information_store.save_information(name, LYRICS_PATH,
                                                      lyrics.encode("utf-8"))
            self.__banner.translate_button.set_sensitive(True)
Ejemplo n.º 14
0
class InformationView(BaseView, Gtk.Bin):
    """
        View with artist information
    """
    def __init__(self, minimal=False):
        """
            Init artist infos
            @param follow_player as bool
        """
        BaseView.__init__(self)
        Gtk.Bin.__init__(self)
        self.__information_store = InformationStore()
        self.__information_store.connect("artist-info-changed",
                                         self.__on_artist_info_changed)
        self.__cancellable = Gio.Cancellable()
        self.__minimal = minimal
        self.__artist_name = ""
        self.connect("unmap", self.__on_unmap)

    def populate(self, artist_id=None):
        """
            Show information for artists
            @param artist_id as int
        """
        builder = Gtk.Builder()
        builder.add_from_resource("/org/gnome/Lollypop/ArtistInformation.ui")
        builder.connect_signals(self)
        self.__scrolled = builder.get_object("scrolled")
        widget = builder.get_object("widget")
        self.add(widget)
        self.__stack = builder.get_object("stack")
        self.__artist_label = builder.get_object("artist_label")
        self.__artist_label.connect("realize", on_realize)
        title_label = builder.get_object("title_label")
        self.__artist_artwork = builder.get_object("artist_artwork")
        eventbox = builder.get_object("eventbox")
        eventbox.connect("button-release-event",
                         self.__on_label_button_release_event)
        self.__information_label = builder.get_object("bio_label")
        if artist_id is None and App().player.current_track.id is not None:
            builder.get_object("header").show()
            builder.get_object("lyrics_button").show()
            builder.get_object("lyrics_button").connect(
                "clicked", self.__on_lyrics_button_clicked,
                App().player.current_track)
            artist_id = App().player.current_track.album.artist_ids[0]
            title_label.set_text(App().player.current_track.title)
        self.__artist_name = App().artists.get_name(artist_id)
        if self.__minimal:
            self.__information_label.set_margin_start(MARGIN)
            self.__information_label.set_margin_end(MARGIN)
            self.__information_label.set_margin_top(MARGIN)
            self.__information_label.set_margin_bottom(MARGIN)
            self.__artist_artwork.hide()
        else:
            self.__artist_artwork.set_margin_start(MARGIN_SMALL)
            builder.get_object("header").show()
            self.__artist_label.set_text(self.__artist_name)
            self.__artist_label.show()
            title_label.show()
            App().art_helper.set_artist_artwork(
                self.__artist_name, ArtSize.ARTIST_SMALL * 3,
                ArtSize.ARTIST_SMALL * 3,
                self.__artist_artwork.get_scale_factor(), ArtBehaviour.ROUNDED
                | ArtBehaviour.CROP_SQUARE | ArtBehaviour.CACHE,
                self.__on_artist_artwork)
            albums_view = AlbumsListView([], [], ViewType.POPOVER)
            albums_view.set_size_request(300, -1)
            albums_view.show()
            albums_view.set_margin_start(5)
            widget.attach(albums_view, 2, 1, 1, 2)
            albums = []
            for album_id in App().albums.get_ids([artist_id], []):
                albums.append(Album(album_id))
            if not albums:
                albums = [App().player.current_track.album]
            # Allows view to be shown without lag
            GLib.idle_add(albums_view.populate, albums)
        App().task_helper.run(self.__information_store.get_information,
                              self.__artist_name,
                              callback=(self.__set_information_content, True))

#######################
# PROTECTED           #
#######################

    def _on_label_realize(self, eventbox):
        """
            @param eventbox as Gtk.EventBox
        """
        try:
            eventbox.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.HAND2))
        except:
            Logger.warning(_("You are using a broken cursor theme!"))

#######################
# PRIVATE             #
#######################

    def __set_information_content(self, content, initial):
        """
            Set information
            @param content as bytes
            @param initial as bool => initial loading
        """
        if content:
            self.__information_label.set_markup(
                GLib.markup_escape_text(content.decode("utf-8")))
        elif initial:
            self.__information_label.set_text(_("Loading information"))
            self.__information_store.cache_artist_info(self.__artist_name)
        else:
            self.__information_label.set_text(
                _("No information for %s") % self.__artist_name)

    def __get_artist_artwork_path_from_cache(self, artist, size):
        """
            Get artist artwork path
            @param artist as str
            @param size as int
            @return str
        """
        path = InformationStore.get_artwork_path(artist, size,
                                                 self.get_scale_factor())
        if path is not None:
            return path
        return None

    def __on_lyrics_button_clicked(self, button, track):
        """
            Show lyrics
            @param button as Gtk.Button
            @param track as Track
        """
        popover = self.get_ancestor(Gtk.Popover)
        if popover is not None:
            popover.popdown()
        App().window.container.show_lyrics(track)

    def _on_artist_button_release_event(self, eventbox, event):
        """
            Go to artist view
            @param eventbox as Gtk.EventBox
            @param event as Gdk.Event
        """
        popover = self.get_ancestor(Gtk.Popover)
        if popover is not None:
            popover.popdown()
        if App().player.current_track.id is None:
            return
        GLib.idle_add(App().window.container.show_artist_view,
                      App().player.current_track.album.artist_ids)

    def __on_label_button_release_event(self, button, event):
        """
            Show information cache (for edition)
            @param button as Gtk.Button
            @param event as Gdk.Event
        """
        uri = "file://%s/%s.txt" % (App().art._INFO_PATH,
                                    escape(self.__artist_name))
        f = Gio.File.new_for_uri(uri)
        if not f.query_exists():
            f.replace_contents(b"", None, False, Gio.FileCreateFlags.NONE,
                               None)
        Gtk.show_uri_on_window(App().window, uri, Gdk.CURRENT_TIME)

    def __on_unmap(self, widget):
        """
            Cancel operations
            @param widget as Gtk.Widget
        """
        self.__cancellable.cancel()

    def __on_artist_artwork(self, surface):
        """
            Finish widget initialisation
            @param surface as cairo.Surface
        """
        if surface is None:
            self.__artist_artwork.hide()
        else:
            self.__artist_artwork.set_from_surface(surface)

    def __on_artist_info_changed(self, information_store, artist):
        """
            Update information
            @param information_store as InformationStore
            @param artist as str
        """
        if artist == self.__artist_name:
            App().task_helper.run(self.__information_store.get_information,
                                  self.__artist_name,
                                  callback=(self.__set_information_content,
                                            False))