albumwidget.py 9.52 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
# Copyright (c) 2016 The GNOME Music Developers
#
# GNOME Music is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# GNOME Music is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with GNOME Music; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# The GNOME Music authors hereby grant permission for non-GPL compatible
# GStreamer plugins to be used and distributed together with GStreamer
# and GNOME Music.  This permission is above and beyond the permissions
# granted by the GPL license by which GNOME Music is covered.  If you
# modify this code, you may extend this exception to your version of the
# code, but you are not obligated to do so.  If you do not wish to do so,
# delete this exception statement from your version.

25
from gettext import ngettext
Marinus Schraal's avatar
Marinus Schraal committed
26
from gi.repository import GdkPixbuf, GLib, GObject, Gtk
27

28
from gnomemusic import log
Marinus Schraal's avatar
Marinus Schraal committed
29
from gnomemusic.albumartcache import Art, ArtImage
30
from gnomemusic.grilo import grilo
31
from gnomemusic.player import PlayerPlaylist
32 33
from gnomemusic.widgets.disclistboxwidget import DiscBox
from gnomemusic.widgets.disclistboxwidget import DiscListBox  # noqa: F401
34
from gnomemusic.widgets.songwidget import SongWidget
35 36 37
import gnomemusic.utils as utils


38
@Gtk.Template(resource_path='/org/gnome/Music/ui/AlbumWidget.ui')
39 40 41 42 43 44 45
class AlbumWidget(Gtk.EventBox):
    """Album widget.

    The album widget consists of an image with the album art
    on the left and a list of songs on the right.
    """

46 47 48 49 50 51 52 53 54 55 56
    __gtype_name__ = 'AlbumWidget'

    _artist_label = Gtk.Template.Child()
    _composer_label = Gtk.Template.Child()
    _composer_info_label = Gtk.Template.Child()
    _cover = Gtk.Template.Child()
    _disc_listbox = Gtk.Template.Child()
    _released_info_label = Gtk.Template.Child()
    _running_info_label = Gtk.Template.Child()
    _title_label = Gtk.Template.Child()

57 58
    selection_mode = GObject.Property(type=bool, default=False)

59 60 61 62 63 64
    _duration = 0

    def __repr__(self):
        return '<AlbumWidget>'

    @log
65
    def __init__(self, player, parent_view, header_bar, selection_toolbar):
66 67 68 69
        """Initialize the AlbumWidget.

        :param player: The player object
        :param parent_view: The view this widget is part of
70
        :param header_bar: The header bar object
71
        :param selection_toolbar: The selection toolbar object
72
        """
73
        super().__init__()
74

75
        self._songs = []
76

77
        self._parent_view = parent_view
78 79 80 81 82
        self._player = player
        self._iter_to_clean = None

        self._create_model()
        self._album = None
83
        self._header_bar = header_bar
84
        self._selection_toolbar = selection_toolbar
85

86 87 88
        self.bind_property(
            'selection-mode', self._disc_listbox, 'selection-mode',
            GObject.BindingFlags.BIDIRECTIONAL)
89

90 91 92 93 94
        self.bind_property(
            'selection-mode', self._header_bar, 'selection-mode',
            GObject.BindingFlags.BIDIRECTIONAL |
            GObject.BindingFlags.SYNC_CREATE)

95
        self.show_all()
96 97 98 99

    @log
    def _create_model(self):
        """Create the ListStore model for this widget."""
100
        self._model = Gtk.ListStore(
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
            GObject.TYPE_STRING,  # title
            GObject.TYPE_STRING,
            GObject.TYPE_STRING,
            GObject.TYPE_STRING,
            GdkPixbuf.Pixbuf,    # icon
            GObject.TYPE_OBJECT,  # song object
            GObject.TYPE_BOOLEAN,  # item selected
            GObject.TYPE_STRING,
            GObject.TYPE_BOOLEAN,
            GObject.TYPE_INT,  # icon shown
            GObject.TYPE_BOOLEAN,
            GObject.TYPE_INT
        )

    @log
116
    def update(self, item):
117 118 119 120
        """Update the album widget.

        :param item: The grilo media item
        """
121
        # reset view
122
        self._songs = []
123 124 125 126
        self._create_model()
        for widget in self._disc_listbox.get_children():
            self._disc_listbox.remove(widget)

127
        self._duration = 0
Marinus Schraal's avatar
Marinus Schraal committed
128
        art = ArtImage(Art.Size.LARGE, item)
129
        art.image = self._cover
130

131
        self._album = utils.get_album_title(item)
132 133
        artist = utils.get_artist_name(item)

134
        self._title_label.props.label = self._album
135 136 137 138
        self._title_label.props.tooltip_text = self._album

        self._artist_label.props.label = artist
        self._artist_label.props.tooltip_text = artist
139 140 141 142

        year = utils.get_media_year(item)
        if not year:
            year = '----'
143
        self._released_info_label.props.label = year
144 145 146

        self._set_composer_label(item)

147
        self._player.connect('song-changed', self._update_model)
148

149 150 151 152 153 154 155 156 157 158 159
        # If an album is playing, restore it.
        if self._player.playing_playlist(
                PlayerPlaylist.Type.ALBUM, self._album):
            length = len(self._player.get_songs())
            for i, song in enumerate(self._player.get_songs()):
                self.add_item(None, None, song[0], length - (i + 1))
            self.add_item(None, None, None, 0)
            self._update_model(self._player)
        else:
            GLib.idle_add(grilo.populate_album_songs, item, self.add_item)

160 161 162 163 164 165
    @log
    def _set_composer_label(self, item):
        composer = item.get_composer()
        show = False

        if composer:
166
            self._composer_info_label.props.label = composer
167 168
            show = True

169 170
        self._composer_label.props.visible = show
        self._composer_info_label.props.visible = show
171

172 173 174
    @log
    def _set_duration_label(self):
        mins = (self._duration // 60) + 1
175 176
        self._running_info_label.props.label = ngettext(
            "{} minute", "{} minutes", mins).format(mins)
177

178
    @Gtk.Template.Callback()
179
    @log
180
    def _on_selection_changed(self, widget):
181
        n_items = len(self._disc_listbox.get_selected_items())
182
        self._selection_toolbar.props.items_selected = n_items
183
        self._header_bar.items_selected = n_items
184

185
    @log
186
    def _create_disc_box(self, disc_nr, disc_songs):
187
        disc_box = DiscBox(self._model)
188
        disc_box.set_songs(disc_songs)
189
        disc_box.set_disc_number(disc_nr)
190 191 192 193
        disc_box.props.columns = 1
        disc_box.props.show_durations = True
        disc_box.props.show_favorites = True
        disc_box.props.show_song_numbers = False
194
        disc_box.connect('song-activated', self._song_activated)
195 196 197 198

        return disc_box

    @log
199
    def _song_activated(self, widget, song_widget):
200
        if self.props.selection_mode:
201
            song_widget.props.selected = not song_widget.props.selected
202 203
            return

204
        self._player.set_playlist(
205 206
            PlayerPlaylist.Type.ALBUM, self._album, song_widget.model,
            song_widget.itr)
207
        self._player.play()
208 209
        return True

210
    @log
211
    def add_item(self, source, prefs, song, remaining, data=None):
212 213
        """Add a song to the item to album list.

214 215 216 217 218
        If no song is remaining create DiscBox and display the widget.
        :param GrlTrackerSource source: The grilo source
        :param prefs: not used
        :param GrlMedia song: The grilo media object
        :param int remaining: Remaining number of items to add
219 220
        :param data: User data
        """
221 222
        if song:
            self._songs.append(song)
223
            self._duration += song.get_duration()
224 225 226
            return

        if remaining == 0:
227 228 229 230 231 232 233 234 235 236 237 238 239
            discs = {}
            for song in self._songs:
                disc_nr = song.get_album_disc_number()
                if disc_nr not in discs.keys():
                    discs[disc_nr] = [song]
                else:
                    discs[disc_nr].append(song)

            for disc_nr in discs:
                disc = self._create_disc_box(disc_nr, discs[disc_nr])
                if len(discs) == 1:
                    disc.props.show_disc_label = False
                self._disc_listbox.add(disc)
240

241
            self._set_duration_label()
242 243
            self.show_all()

244
    @log
245
    def _update_model(self, player, position=None):
246
        """Updates model when the song changes
247

248 249
        :param Player player: The main player object
        :param int position: current song position
250
        """
251
        if not player.playing_playlist(PlayerPlaylist.Type.ALBUM, self._album):
252
            return True
253

254
        current_song = player.props.current_song
255
        self._duration = 0
256 257
        song_passed = False

258
        for song in self._songs:
259
            song_widget = song.song_widget
260
            self._duration += song.get_duration()
261

262
            if (song.get_id() == current_song.get_id()):
263
                song_widget.props.state = SongWidget.State.PLAYING
264 265
                song_passed = True
            elif (song_passed):
266
                # Counter intuitive, but this is due to call order.
267
                song_widget.props.state = SongWidget.State.UNPLAYED
268
            else:
269
                song_widget.props.state = SongWidget.State.PLAYED
270

271
        self._set_duration_label()
272 273 274 275 276 277 278 279 280 281

        return True

    @log
    def select_all(self):
        self._disc_listbox.select_all()

    @log
    def select_none(self):
        self._disc_listbox.select_none()
282 283 284 285 286 287 288 289 290

    @log
    def get_selected_songs(self):
        """Return a list of selected songs.

        :returns: selected songs
        :rtype: list
        """
        return self._disc_listbox.get_selected_items()