예제 #1
0
class AlbumBannerWidget(BannerWidget, SignalsHelper):
    """
        Banner for album
    """
    @signals_map
    def __init__(self, album, storage_type, view_type):
        """
            Init cover widget
            @param album
            @param storage_type as int
            @param view_type as ViewType
        """
        BannerWidget.__init__(self, view_type)
        self.__album = album
        self.__storage_type = storage_type
        builder = Gtk.Builder()
        builder.add_from_resource("/org/gnome/Lollypop/AlbumBannerWidget.ui")
        builder.connect_signals(self)
        self.__labels = builder.get_object("labels")
        self.__title_label = builder.get_object("title_label")
        self.__title_label.connect("query-tooltip", on_query_tooltip)
        self.__artist_label = builder.get_object("artist_label")
        self.__artist_label.connect("query-tooltip", on_query_tooltip)
        self.__year_label = builder.get_object("year_label")
        self.__duration_label = builder.get_object("duration_label")
        self.__play_button = builder.get_object("play_button")
        self.__add_button = builder.get_object("add_button")
        self.__menu_button = builder.get_object("menu_button")
        self.__widget = builder.get_object("widget")
        if view_type & ViewType.OVERLAY:
            style = "banner-button"
        else:
            style = "menu-button"
        self.__play_button.get_style_context().add_class(style)
        self.__add_button.get_style_context().add_class(style)
        self.__menu_button.get_style_context().add_class(style)
        self.__cover_widget = CoverWidget(album, view_type)
        self.__cover_widget.show()
        self.__title_label.set_label(album.name)
        if view_type & ViewType.ALBUM:
            self.__artist_label.show()
            self.__artist_label.set_label(", ".join(album.artists))
        else:
            self.__title_label.set_opacity(0.8)
            self.__year_label.set_opacity(0.7)
            self.__duration_label.set_opacity(0.6)
            self.__widget.get_style_context().add_class("album-banner")
        if album.year is not None:
            self.__year_label.set_label(str(album.year))
            self.__year_label.show()
        human_duration = get_human_duration(album.duration)
        self.__duration_label.set_text(human_duration)
        artist_eventbox = builder.get_object("artist_eventbox")
        artist_eventbox.connect("realize", set_cursor_type)
        self.__gesture1 = GesturesHelper(
            artist_eventbox, primary_press_callback=self._on_artist_press)
        year_eventbox = builder.get_object("year_eventbox")
        year_eventbox.connect("realize", set_cursor_type)
        self.__gesture2 = GesturesHelper(
            year_eventbox, primary_press_callback=self._on_year_press)
        self.__widget.attach(self.__cover_widget, 0, 0, 1, 3)
        self.__bottom_box = builder.get_object("bottom_box")
        self.__loved_widget = LovedWidget(album, Gtk.IconSize.INVALID)
        self.__loved_widget.show()
        self.__bottom_box.pack_start(self.__loved_widget, 0, False, False)
        self.__rating_widget = RatingWidget(album, Gtk.IconSize.INVALID)
        self.__rating_widget.show()
        self.__bottom_box.pack_start(self.__rating_widget, 0, True, True)
        if view_type & ViewType.OVERLAY:
            self._overlay.add_overlay(self.__widget)
            self._overlay.set_overlay_pass_through(self.__widget, True)
        else:
            self.add(self.__widget)
        self.__update_add_button()
        self.__set_internal_size()
        return [(App().window.container.widget, "notify::folded",
                 "_on_container_folded"),
                (App().art, "album-artwork-changed",
                 "_on_album_artwork_changed"),
                (App().player, "playback-added", "_on_playback_changed"),
                (App().player, "playback-updated", "_on_playback_changed"),
                (App().player, "playback-removed", "_on_playback_changed")]

    def update_for_width(self, width):
        """
            Update banner internals for width, call this before showing banner
            @param width as int
        """
        BannerWidget.update_for_width(self, width)
        self.__set_artwork()

    def set_selected(self, selected):
        """
            Mark widget as selected
            @param selected as bool
        """
        if selected:
            self.__widget.set_state_flags(Gtk.StateFlags.SELECTED, True)
        else:
            self.__widget.set_state_flags(Gtk.StateFlags.NORMAL, True)

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

    def _handle_width_allocate(self, allocation):
        """
            Update artwork
            @param allocation as Gtk.Allocation
        """
        if BannerWidget._handle_width_allocate(self, allocation):
            self.__set_artwork()

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

    def _on_menu_button_clicked(self, button):
        """
            Show album menu
            @param button as Gtk.Button
        """
        from lollypop.widgets_menu import MenuBuilder
        from lollypop.menu_objects import AlbumMenu
        from lollypop.menu_artwork import AlbumArtworkMenu
        menu = AlbumMenu(self.__album, self.__storage_type, self.view_type)
        menu_widget = MenuBuilder(menu)
        menu_widget.show()
        if App().window.folded:
            menu_ext = AlbumArtworkMenu(self.__album, self.view_type, True)
            menu_ext.connect("hidden", self.__close_artwork_menu)
            menu_ext.show()
            menu_widget.add_widget(menu_ext, -2)
        popup_widget(menu_widget, button, None, None, button)

    def _on_play_button_clicked(self, button):
        """
            Play album
           @param button as Gtk.Button
        """
        App().player.play_album(self.__album.clone(False))

    def _on_add_button_clicked(self, button):
        """
            Add/Remove album
           @param button as Gtk.Button
        """
        if self.__album.id in App().player.album_ids:
            App().player.remove_album_by_id(self.__album.id)
        else:
            App().player.add_album(self.__album.clone(False))

    def _on_album_artwork_changed(self, art, album_id):
        """
            Update cover for album_id
            @param art as Art
            @param album_id as int
        """
        if album_id == self.__album.id:
            self.__set_artwork()

    def _on_playback_changed(self, *ignore):
        """
            Update add button
        """
        self.__update_add_button()

    def _on_year_press(self, x, y, event):
        """
            Show year view
            @param x as int
            @param y as int
            @param event as Gdk.EventButton
        """
        App().window.container.show_view([Type.YEARS], [self.__album.year])

    def _on_artist_press(self, x, y, event):
        """
            Show artist view
            @param x as int
            @param y as int
            @param event as Gdk.EventButton
        """
        App().window.container.show_view([Type.ARTISTS],
                                         self.__album.artist_ids)

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

    def __close_artwork_menu(self, action, variant):
        if App().window.folded:
            App().window.container.go_back()
        else:
            self.__artwork_popup.destroy()

    def __set_artwork(self):
        """
            Set artwork on banner
        """
        if self._artwork is not None and\
                self.view_type & ViewType.ALBUM and\
                App().animations:
            App().art_helper.set_album_artwork(
                self.__album,
                # +100 to prevent resize lag
                self.width + 100,
                self.height,
                self._artwork.get_scale_factor(),
                ArtBehaviour.BLUR_HARD | ArtBehaviour.DARKER,
                self._on_artwork)
        if self.width < ArtSize.BANNER * 3:
            if self.__cover_widget.get_opacity() == 1:
                self.__cover_widget.set_opacity(0.1)
                self.__widget.remove(self.__labels)
                self.__widget.attach(self.__labels, 0, 0, 2, 2)
        elif self.__cover_widget.get_opacity() != 1:
            self.__cover_widget.set_opacity(1)
            self.__widget.remove(self.__labels)
            self.__widget.attach(self.__labels, 1, 0, 1, 2)

    def __update_add_button(self):
        """
            Set image as +/-
        """
        if self.__album.id in App().player.album_ids:
            self.__add_button.get_image().set_from_icon_name(
                "list-remove-symbolic", Gtk.IconSize.BUTTON)
        else:
            self.__add_button.get_image().set_from_icon_name(
                "list-add-symbolic", Gtk.IconSize.BUTTON)

    def __set_internal_size(self):
        """
            Set content size based on available width
        """
        # Text size
        for label in [
                self.__title_label, self.__artist_label, self.__year_label,
                self.__duration_label
        ]:
            context = label.get_style_context()
            for c in context.list_classes():
                context.remove_class(c)

        if App().window.folded:
            art_size = ArtSize.MEDIUM
            icon_size = Gtk.IconSize.BUTTON
            cls_title = "text-medium"
            cls_others = "text-medium"
        else:
            art_size = ArtSize.BANNER
            icon_size = Gtk.IconSize.LARGE_TOOLBAR
            cls_title = "text-x-large"
            cls_others = "text-large"
        self.__title_label.get_style_context().add_class(cls_title)
        self.__artist_label.get_style_context().add_class(cls_title)
        self.__year_label.get_style_context().add_class(cls_others)
        self.__duration_label.get_style_context().add_class(cls_others)

        self.__rating_widget.set_icon_size(icon_size)
        self.__loved_widget.set_icon_size(icon_size)
        self.__cover_widget.set_art_size(art_size)
예제 #2
0
class AlbumBannerWidget(BannerWidget, SignalsHelper):
    """
        Banner for album
    """

    @signals_map
    def __init__(self, album, storage_type, view_type):
        """
            Init cover widget
            @param album
            @param storage_type as int
            @param view_type as ViewType
        """
        BannerWidget.__init__(self, view_type)
        self.__album = album
        self.__storage_type = storage_type
        self.__widget = None
        return [
                (App().window.container.widget, "notify::folded",
                 "_on_container_folded"),
                (App().art, "album-artwork-changed",
                 "_on_album_artwork_changed"),
                (App().player, "playback-added", "_on_playback_changed"),
                (App().player, "playback-updated", "_on_playback_changed"),
                (App().player, "playback-setted", "_on_playback_changed"),
                (App().player, "playback-removed", "_on_playback_changed")
        ]

    def populate(self):
        """
            Populate the view
        """
        if self.__widget is not None:
            return
        self.__widget = Gtk.Grid.new()
        self.__widget.set_margin_start(MARGIN)
        self.__widget.set_margin_end(MARGIN_MEDIUM)
        self.__widget.set_margin_top(MARGIN_SMALL)
        self.__widget.set_margin_bottom(MARGIN_SMALL)
        self.__widget.set_row_spacing(MARGIN_SMALL)
        self.__widget.set_column_spacing(MARGIN_MEDIUM)
        self.__top_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        self.__middle_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        self.__bottom_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        self.__labels_box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        self.__top_box.set_vexpand(True)
        self.__middle_box.set_halign(Gtk.Align.END)
        self.__middle_box.set_valign(Gtk.Align.CENTER)
        self.__middle_box.set_hexpand(True)
        self.__bottom_box.set_vexpand(True)
        self.__year_eventbox = Gtk.EventBox.new()
        self.__year_eventbox.set_hexpand(True)
        self.__year_eventbox.set_halign(Gtk.Align.END)
        self.__year_label = Gtk.Label.new()
        self.__year_label.get_style_context().add_class("dim-label")
        self.__year_eventbox.add(self.__year_label)
        self.__year_eventbox.connect("realize", set_cursor_type)
        self.__gesture_year = GesturesHelper(
            self.__year_eventbox, primary_press_callback=self._on_year_press)
        self.__play_button = Gtk.Button.new_from_icon_name(
            "media-playback-start-symbolic", Gtk.IconSize.BUTTON)
        self.__add_button = Gtk.Button.new_from_icon_name(
            "list-add-symbolic", Gtk.IconSize.BUTTON)
        self.__menu_button = Gtk.Button.new_from_icon_name(
            "view-more-symbolic", Gtk.IconSize.BUTTON)
        self.__play_button.connect("clicked", self.__on_play_button_clicked)
        self.__add_button.connect("clicked", self.__on_add_button_clicked)
        self.__menu_button.connect("clicked", self.__on_menu_button_clicked)
        self.__title_label = Gtk.Label.new()
        self.__title_label.set_valign(Gtk.Align.END)
        self.__title_label.set_vexpand(True)
        self.__title_label.connect("query-tooltip", on_query_tooltip)
        self.__title_label.set_ellipsize(Pango.EllipsizeMode.END)
        self.__title_label.set_halign(Gtk.Align.START)
        self.__title_label.set_xalign(0)
        self.__artist_label = Gtk.Label.new()
        self.__artist_eventbox = Gtk.EventBox.new()
        self.__artist_eventbox.set_valign(Gtk.Align.START)
        self.__artist_eventbox.add(self.__artist_label)
        self.__artist_eventbox.connect("realize", set_cursor_type)
        self.__artist_label.connect("query-tooltip", on_query_tooltip)
        self.__artist_label.set_ellipsize(Pango.EllipsizeMode.END)
        self.__artist_label.set_halign(Gtk.Align.START)
        self.__artist_label.set_xalign(0)
        self.__gesture_artist = GesturesHelper(
            self.__artist_eventbox,
            primary_press_callback=self._on_artist_press)
        self.__duration_label = Gtk.Label.new()
        self.__duration_label.get_style_context().add_class("dim-label")
        self.__top_box.pack_end(self.__year_eventbox, False, True, 0)
        self.__middle_box.add(self.__play_button)
        self.__middle_box.add(self.__add_button)
        self.__middle_box.add(self.__menu_button)
        self.__middle_box.get_style_context().add_class("linked")
        self.__bottom_box.pack_end(self.__duration_label, False, True, 0)
        self.__labels_box.add(self.__title_label)
        self.__labels_box.add(self.__artist_eventbox)
        self.__widget.attach(self.__top_box, 2, 0, 1, 1)
        self.__widget.attach(self.__middle_box, 2, 1, 1, 1)
        self.__widget.attach(self.__bottom_box, 1, 2, 2, 1)
        self.__widget.attach(self.__labels_box, 1, 0, 1, 2)
        self.__widget.show_all()
        if self.view_type & ViewType.OVERLAY:
            style = "banner-button"
        else:
            style = "menu-button"
        self.__play_button.get_style_context().add_class(style)
        self.__add_button.get_style_context().add_class(style)
        self.__menu_button.get_style_context().add_class(style)
        self.__title_label.set_label(self.__album.name)
        if self.view_type & ViewType.ALBUM:
            self.__cover_widget = EditCoverWidget(self.__album, self.view_type)
            self.__artist_label.get_style_context().add_class("dim-label")
            self.__artist_label.set_label(", ".join(self.__album.artists))
        else:
            self.__artist_label.hide()
            self.__cover_widget = CoverWidget(self.__album, self.view_type)
            self.__cover_widget.set_margin_top(MARGIN_MEDIUM)
            self.__cover_widget.set_margin_bottom(MARGIN_MEDIUM)
            self.__cover_widget.connect("populated",
                                        self.__on_cover_populated)
        self.__cover_widget.show()
        if self.__album.year is not None:
            self.__year_label.set_label(str(self.__album.year))
            self.__year_label.show()
        human_duration = get_human_duration(self.__album.duration)
        self.__duration_label.set_text(human_duration)
        self.__widget.attach(self.__cover_widget, 0, 0, 1, 3)
        self.__loved_widget = LovedWidget(self.__album, Gtk.IconSize.INVALID)
        self.__loved_widget.show()
        self.__bottom_box.pack_start(self.__loved_widget, 0, False, False)
        self.__rating_widget = RatingWidget(self.__album, Gtk.IconSize.INVALID)
        self.__rating_widget.show()
        self.__bottom_box.pack_start(self.__rating_widget, 0, True, True)
        if self.view_type & ViewType.OVERLAY:
            self._overlay.add_overlay(self.__widget)
            self._overlay.set_overlay_pass_through(self.__widget, True)
        else:
            self.add(self.__widget)
        self.__update_add_button()
        self.__set_internal_size()

    def update_for_width(self, width):
        """
            Update banner internals for width, call this before showing banner
            @param width as int
        """
        BannerWidget.update_for_width(self, width)
        self.__set_artwork()

    def set_selected(self, selected):
        """
            Mark widget as selected
            @param selected as bool
        """
        if selected:
            self.__widget.set_state_flags(Gtk.StateFlags.SELECTED, True)
        else:
            self.__widget.set_state_flags(Gtk.StateFlags.NORMAL, True)

#######################
# PROTECTED           #
#######################
    def _handle_width_allocate(self, allocation):
        """
            Update artwork
            @param allocation as Gtk.Allocation
        """
        if BannerWidget._handle_width_allocate(self, allocation):
            self.__set_artwork()

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

    def _on_album_artwork_changed(self, art, album_id):
        """
            Update cover for album_id
            @param art as Art
            @param album_id as int
        """
        if album_id == self.__album.id:
            self.__set_artwork()

    def _on_playback_changed(self, *ignore):
        """
            Update add button
        """
        self.__update_add_button()

    def _on_year_press(self, x, y, event):
        """
            Show year view
            @param x as int
            @param y as int
            @param event as Gdk.EventButton
        """
        App().window.container.show_view([Type.YEARS], [self.__album.year])

    def _on_artist_press(self, x, y, event):
        """
            Show artist view
            @param x as int
            @param y as int
            @param event as Gdk.EventButton
        """
        App().window.container.show_view([Type.ARTISTS],
                                         self.__album.artist_ids)

#######################
# PRIVATE             #
#######################
    def __close_artwork_menu(self, action, variant):
        if App().window.folded:
            App().window.container.go_back()
        else:
            self.__artwork_popup.destroy()

    def __set_artwork(self):
        """
            Set artwork on banner
        """
        if self._artwork is not None and\
                self.view_type & ViewType.ALBUM and\
                App().animations:
            App().art_helper.set_album_artwork(
                            self.__album,
                            # +100 to prevent resize lag
                            self.width + 100,
                            self.height,
                            self._artwork.get_scale_factor(),
                            ArtBehaviour.BLUR_HARD |
                            ArtBehaviour.DARKER,
                            self._on_artwork)
        if self.width < ArtSize.BANNER * 3:
            if self.__widget.get_child_at(2, 0) == self.__top_box:
                self.__widget.remove(self.__labels_box)
                self.__widget.remove(self.__top_box)
                self.__widget.attach(self.__top_box, 1, 1, 1, 1)
                self.__widget.attach(self.__labels_box, 1, 0, 2, 1)
        elif self.__widget.get_child_at(1, 0) == self.__labels_box:
            self.__widget.remove(self.__labels_box)
            self.__widget.remove(self.__top_box)
            self.__widget.attach(self.__top_box, 2, 0, 1, 1)
            self.__widget.attach(self.__labels_box, 1, 0, 1, 2)

    def __update_add_button(self):
        """
            Set image as +/-
        """
        if self.__album.id in App().player.album_ids:
            self.__add_button.get_image().set_from_icon_name(
                "list-remove-symbolic", Gtk.IconSize.BUTTON)
        else:
            self.__add_button.get_image().set_from_icon_name(
                "list-add-symbolic", Gtk.IconSize.BUTTON)

    def __set_internal_size(self):
        """
            Set content size based on available width
        """
        classes = ["text-medium", "text-large", "text-x-large"]
        # Text size
        for label in [self.__title_label,
                      self.__artist_label,
                      self.__year_label,
                      self.__duration_label]:
            context = label.get_style_context()
            for c in classes:
                context.remove_class(c)

        if App().window.folded:
            art_size = ArtSize.MEDIUM
            icon_size = Gtk.IconSize.BUTTON
            cls_title = "text-medium"
            cls_others = "text-medium"
        elif not self.view_type & ViewType.OVERLAY:
            art_size = ArtSize.BANNER
            icon_size = Gtk.IconSize.LARGE_TOOLBAR
            cls_title = "text-large"
            cls_others = "text-large"
        else:
            art_size = ArtSize.BANNER
            icon_size = Gtk.IconSize.LARGE_TOOLBAR
            cls_title = "text-x-large"
            cls_others = "text-large"
        self.__title_label.get_style_context().add_class(cls_title)
        self.__artist_label.get_style_context().add_class(cls_title)
        self.__year_label.get_style_context().add_class(cls_others)
        self.__duration_label.get_style_context().add_class(cls_others)

        self.__rating_widget.set_icon_size(icon_size)
        self.__loved_widget.set_icon_size(icon_size)
        self.__cover_widget.set_art_size(art_size)

    def __on_cover_populated(self, widget):
        """
            Pass signal
            @param widget as Gtk.Widget
        """
        emit_signal(self, "populated")

    def __on_menu_button_clicked(self, button):
        """
            Show album menu
            @param button as Gtk.Button
        """
        from lollypop.widgets_menu import MenuBuilder
        from lollypop.menu_objects import AlbumMenu
        from lollypop.menu_artwork import AlbumArtworkMenu
        menu = AlbumMenu(self.__album,
                         self.__storage_type,
                         self.view_type)
        menu_widget = MenuBuilder(menu)
        menu_widget.show()
        if App().window.folded:
            menu_ext = AlbumArtworkMenu(self.__album, self.view_type, True)
            menu_ext.connect("hidden", self.__close_artwork_menu)
            menu_ext.show()
            menu_widget.add_widget(menu_ext, -2)
        popup_widget(menu_widget, button, None, None, button)

    def __on_play_button_clicked(self, button):
        """
            Play album
           @param button as Gtk.Button
        """
        App().player.play_album(self.__album.clone(False))

    def __on_add_button_clicked(self, button):
        """
            Add/Remove album
           @param button as Gtk.Button
        """
        if self.__album.id in App().player.album_ids:
            App().player.remove_album_by_id(self.__album.id)
        else:
            App().player.add_album(self.__album.clone(False))
예제 #3
0
    def __init__(self, track):
        """
            Init widget
            @param track as Track
        """
        Gtk.Grid.__init__(self)
        self.set_margin_top(MARGIN_SMALL)
        self.set_row_spacing(MARGIN_SMALL)
        self.set_orientation(Gtk.Orientation.VERTICAL)

        if track.year is not None:
            year_label = Gtk.Label.new()
            year_label.set_text(str(track.year))
            dt = GLib.DateTime.new_from_unix_local(track.timestamp)
            year_label.set_tooltip_text(dt.format(_("%Y-%m-%d")))
            year_label.set_margin_end(5)
            year_label.get_style_context().add_class("dim-label")
            year_label.set_property("halign", Gtk.Align.START)
            year_label.set_property("hexpand", True)
            year_label.show()

        hgrid = Gtk.Grid()
        rating = RatingWidget(track)
        rating.set_property("halign", Gtk.Align.START)
        if App().window.folded:
            rating.set_icon_size(Gtk.IconSize.LARGE_TOOLBAR)
        rating.show()

        loved = LovedWidget(track)
        loved.set_property("halign", Gtk.Align.START)
        if App().window.folded:
            loved.set_icon_size(Gtk.IconSize.LARGE_TOOLBAR)
        loved.show()

        if track.year is not None:
            hgrid.add(year_label)
        hgrid.add(loved)
        hgrid.add(rating)
        hgrid.show()

        if not track.storage_type & StorageType.COLLECTION:
            try:
                escaped = GLib.uri_escape_string(track.uri, None, True)
                uri = load(open("%s/web_%s" % (CACHE_PATH, escaped), "rb"))
            except:
                uri = ""
            edit = Gtk.Entry()
            edit.set_placeholder_text(_("YouTube page address"))
            edit.set_margin_top(MARGIN_SMALL)
            edit.set_margin_start(MARGIN_SMALL)
            edit.set_margin_end(MARGIN_SMALL)
            edit.set_margin_bottom(MARGIN_SMALL)
            edit.set_property("hexpand", True)
            edit.set_text(uri)
            edit.connect("changed", self.__on_edit_changed, track)
            edit.show()
            self.add(edit)

        separator = Gtk.Separator.new(Gtk.Orientation.HORIZONTAL)
        separator.show()
        self.add(separator)
        self.add(hgrid)
class AlbumBannerWidget(Gtk.Bin):
    """
        Banner for album
    """

    def __init__(self, album, view_type=ViewType.DEFAULT):
        """
            Init cover widget
            @param album
            @param view_type as ViewType
        """
        Gtk.Bin.__init__(self)
        self.__view_type = view_type
        self.__height = None
        self.__width = 0
        self.__cloud_image = None
        self.__allocation_timeout_id = None
        self.__album = album
        self.set_property("valign", Gtk.Align.START)
        builder = Gtk.Builder()
        builder.add_from_resource("/org/gnome/Lollypop/AlbumBannerWidget.ui")
        builder.connect_signals(self)
        self.__title_label = builder.get_object("name_label")
        self.__title_label.connect("query-tooltip", on_query_tooltip)
        self.__year_label = builder.get_object("year_label")
        self.__duration_label = builder.get_object("duration_label")
        self.__menu_button = builder.get_object("menu_button")
        self.__cover_widget = CoverWidget(album, view_type)
        self.__cover_widget.set_vexpand(True)
        self.__cover_widget.show()
        album_name = GLib.markup_escape_text(album.name)
        markup = "<b>%s</b>" % album_name
        if view_type & ViewType.ALBUM:
            artist_name = GLib.markup_escape_text(", ".join(album.artists))
            if view_type & ViewType.SMALL:
                markup += "\n<span alpha='40000'>%s</span>" % artist_name
            else:
                markup += "\n<span size='x-small' alpha='40000'>%s</span>" %\
                                                                  artist_name
        self.__title_label.set_markup(markup)
        if album.year is not None:
            self.__year_label.set_text(str(album.year))
        else:
            self.__year_label.hide()
        year_eventbox = builder.get_object("year_eventbox")
        year_eventbox.connect("realize", on_realize)
        year_eventbox.connect("button-release-event",
                              self.__on_year_button_release_event)
        duration = App().albums.get_duration(self.__album.id,
                                             self.__album.genre_ids)
        self.__duration_label.set_text(get_human_duration(duration))
        self.__artwork = builder.get_object("artwork")
        self.__grid = builder.get_object("grid")
        self.__widget = builder.get_object("widget")
        if view_type & ViewType.ALBUM:
            self.__menu_button.get_style_context().add_class(
                "black-transparent")
            self.get_style_context().add_class("black")
            self.__artwork.get_style_context().add_class("black")
            self.connect("size-allocate", self.__on_size_allocate)
            self.connect("destroy", self.__on_destroy)
            self.__art_signal_id = App().art.connect(
                                               "album-artwork-changed",
                                               self.__on_album_artwork_changed)
        else:
            self.__grid.get_style_context().add_class("banner-frame")
        self.__grid.attach(self.__cover_widget, 0, 0, 1, 3)
        self.__rating_grid = builder.get_object("rating_grid")
        if album.mtime <= 0:
            self.__cloud_image = Gtk.Image.new()
            self.__cloud_image.show()
            self.__cloud_image.set_margin_start(MARGIN)
            self.__rating_grid.attach(self.__cloud_image, 1, 0, 1, 1)
        self.__rating_widget = RatingWidget(album, Gtk.IconSize.INVALID)
        self.__rating_widget.set_property("halign", Gtk.Align.START)
        self.__rating_widget.set_property("valign", Gtk.Align.CENTER)
        self.__rating_widget.show()
        self.__rating_grid.attach(self.__rating_widget, 2, 0, 1, 1)
        self.__loved_widget = LovedWidget(album, Gtk.IconSize.INVALID)
        self.__loved_widget.set_margin_start(10)
        self.__loved_widget.set_property("halign", Gtk.Align.START)
        self.__loved_widget.set_property("valign", Gtk.Align.CENTER)
        self.__loved_widget.show()
        self.__rating_grid.attach(self.__loved_widget, 3, 0, 1, 1)
        self.add(self.__widget)
        self.__cover_widget.set_margin_start(MARGIN)
        self.__year_label.set_margin_end(MARGIN)
        self.__duration_label.set_margin_start(MARGIN)
        self.__rating_grid.set_margin_end(MARGIN)
        self.set_view_type(view_type)

    def set_view_type(self, view_type):
        """
            Update widget internals for view_type
            @param view_type as ViewType
        """
        self.__view_type = view_type
        art_size = 0
        if view_type & ViewType.SMALL:
            art_size = ArtSize.LARGE
            style = "menu-button"
            icon_size = Gtk.IconSize.BUTTON
            self.__title_label.get_style_context().add_class(
                "text-large")
            self.__year_label.get_style_context().add_class(
                "text-large")
        elif view_type & ViewType.MEDIUM:
            art_size = ArtSize.BANNER
            style = "menu-button-48"
            icon_size = Gtk.IconSize.LARGE_TOOLBAR
            self.__title_label.get_style_context().add_class(
                "text-x-large")
            self.__year_label.get_style_context().add_class(
                "text-large")
        else:
            art_size = ArtSize.BANNER
            style = "menu-button-48"
            icon_size = Gtk.IconSize.LARGE_TOOLBAR
            self.__title_label.get_style_context().add_class(
                "text-xx-large")
            self.__year_label.get_style_context().add_class(
                "text-x-large")
        self.__rating_widget.set_icon_size(icon_size)
        self.__loved_widget.set_icon_size(icon_size)
        if self.__cloud_image is not None:
            self.__cloud_image.set_from_icon_name("goa-panel-symbolic",
                                                  icon_size)
        self.__cover_widget.set_artwork(art_size)
        menu_button_style_context = self.__menu_button.get_style_context()
        menu_button_style_context.remove_class("menu-button-48")
        menu_button_style_context.remove_class("menu-button")
        menu_button_style_context.add_class(style)
        self.__menu_button.get_image().set_from_icon_name(
                                                   "view-more-symbolic",
                                                   icon_size)
        self.set_height(self.height)

    def set_height(self, height):
        """
            Set height
            @param height as int
        """
        if height < self.default_height:
            self.__height = height
            # Make grid cover artwork
            # No idea why...
            self.__grid.set_size_request(-1, height + 1)
            self.__cover_widget.hide()
            self.__duration_label.hide()
            self.__rating_grid.hide()
            self.__year_label.set_vexpand(True)
            self.__set_text_height(True)
        else:
            self.__height = None
            # Make grid cover artwork
            # No idea why...
            self.__grid.set_size_request(-1, height + 1)
            self.__cover_widget.show()
            self.__duration_label.show()
            self.__rating_grid.show()
            self.__year_label.set_vexpand(False)
            self.__set_text_height(False)

    def do_get_preferred_width(self):
        """
            Force preferred width
        """
        return (0, 0)

    def do_get_preferred_height(self):
        """
            Force preferred height
        """
        return (self.height, self.height)

    def set_selected(self, selected):
        """
            Mark widget as selected
            @param selected as bool
        """
        if selected:
            self.__grid.set_state_flags(Gtk.StateFlags.SELECTED, True)
        else:
            self.__grid.set_state_flags(Gtk.StateFlags.NORMAL, True)

    @property
    def height(self):
        """
            Get height
            @return int
        """
        return self.__height if self.__height is not None\
            else self.default_height

    @property
    def default_height(self):
        """
            Get default height
        """
        if self.__view_type & ViewType.SMALL:
            return ArtSize.LARGE + 40
        elif self.__view_type & ViewType.MEDIUM:
            return ArtSize.BANNER + 40
        else:
            return ArtSize.BANNER + 40

#######################
# PROTECTED           #
#######################
    def _on_menu_button_clicked(self, button):
        """
            Show album menu
            @param button as Gtk.Button
        """
        from lollypop.menu_objects import AlbumMenu
        menu = AlbumMenu(self.__album, self.__view_type)
        popover = Gtk.Popover.new_from_model(button, menu)
        popover.popup()

#######################
# PRIVATE             #
#######################
    def __set_text_height(self, collapsed):
        """
            Set text height
            @param collapsed as bool
        """
        title_context = self.__title_label.get_style_context()
        year_context = self.__year_label.get_style_context()
        for c in title_context.list_classes():
            title_context.remove_class(c)
        for c in year_context.list_classes():
            title_context.remove_class(c)
        if self.__view_type & ViewType.SMALL:
            if not collapsed:
                self.__title_label.get_style_context().add_class("text-large")
        # Collapsed not implemented for MEDIUM
        elif self.__view_type & ViewType.MEDIUM:
            self.__title_label.get_style_context().add_class(
                "text-x-large")
            self.__year_label.get_style_context().add_class(
                "text-large")
        elif collapsed:
            self.__title_label.get_style_context().add_class(
                "text-x-large")
            self.__year_label.get_style_context().add_class(
                "text-large")
        else:
            self.__title_label.get_style_context().add_class(
                "text-xx-large")
            self.__year_label.get_style_context().add_class(
                "text-x-large")

    def __handle_size_allocate(self, allocation):
        """
            Change box max/min children
            @param allocation as Gtk.Allocation
        """
        self.__allocation_timeout_id = None
        if allocation.width == 1 or self.__width == allocation.width:
            return
        self.__width = allocation.width
        App().art_helper.set_album_artwork(
                self.__album,
                # +100 to prevent resize lag
                allocation.width + 100,
                self.default_height,
                self.__artwork.get_scale_factor(),
                ArtBehaviour.BLUR_HARD |
                ArtBehaviour.DARKER,
                self.__on_album_artwork)

    def __on_destroy(self, widget):
        """
            Disconnect signal
            @param widget as Gtk.Widget
        """
        if self.__art_signal_id is not None:
            App().art.disconnect(self.__art_signal_id)

    def __on_album_artwork_changed(self, art, album_id):
        """
            Update cover for album_id
            @param art as Art
            @param album_id as int
        """
        if album_id == self.__album.id:
            App().art_helper.set_album_artwork(
                            self.__album,
                            # +100 to prevent resize lag
                            self.get_allocated_width() + 100,
                            self.default_height,
                            self.__artwork.get_scale_factor(),
                            ArtBehaviour.BLUR_HARD |
                            ArtBehaviour.DARKER,
                            self.__on_album_artwork)

    def __on_album_artwork(self, surface):
        """
            Set album artwork
            @param surface as str
        """
        if surface is not None:
            self.__artwork.set_from_surface(surface)

    def __on_year_button_release_event(self, widget, event):
        """
            Show year view
            @param widget as Gtk.Widget
            @param event as Gdk.event
        """
        App().window.emit("can-go-back-changed", True)
        App().window.emit("show-can-go-back", True)
        App().window.container.show_view([Type.YEARS], [self.__album.year])

    def __on_size_allocate(self, widget, allocation):
        """
            Delayed handling
            @param widget as Gtk.Widget
            @param allocation as Gtk.Allocation
        """
        if self.__allocation_timeout_id is not None:
            GLib.source_remove(self.__allocation_timeout_id)
        self.__allocation_timeout_id = GLib.idle_add(
            self.__handle_size_allocate, allocation)