albumwidget.py 10.1 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
Jean Felder's avatar
Jean Felder committed
31
from gnomemusic.gstplayer import Playback
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/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 59 60 61 62 63 64 65 66 67 68
    _duration = 0

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

    @log
    def __init__(self, player, parent_view):
        """Initialize the AlbumWidget.

        :param player: The player object
        :param parent_view: The view this widget is part of
        """
69
        super().__init__()
70

71
        self._songs = []
72

73
        self._parent_view = parent_view
74 75 76
        self._player = player
        self._iter_to_clean = None

77 78
        self._selection_mode = False

79 80 81
        self._create_model()
        self._album = None
        self._header_bar = None
82 83

        # FIXME: Assigned to appease searchview
84
        # _get_selected_songs
85 86
        self.view = self._disc_listbox

87 88 89 90 91 92 93 94 95 96
        self.show_all()

    @log
    def _on_selection_mode_request(self, *args):
        """Selection mode toggled."""
        self._header_bar._select_button.clicked()

    @log
    def _create_model(self):
        """Create the ListStore model for this widget."""
97
        self._model = Gtk.ListStore(
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
            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
113
    def update(self, item, header_bar, selection_toolbar):
114 115 116 117 118 119
        """Update the album widget.

        :param item: The grilo media item
        :param header_bar: The header bar object
        :param selection_toolbar: The selection toolbar object
        """
120
        # reset view
121
        self._songs = []
122 123 124 125
        self._create_model()
        for widget in self._disc_listbox.get_children():
            self._disc_listbox.remove(widget)

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

132 133 134 135 136
        GLib.idle_add(grilo.populate_album_songs, item, self.add_item)
        header_bar._select_button.connect(
            'toggled', self._on_header_select_button_toggled)
        header_bar._cancel_button.connect(
            'clicked', self._on_header_cancel_button_clicked)
137

138
        self._album = utils.get_album_title(item)
139 140
        self._artist_label.props.label = utils.get_artist_name(item)
        self._title_label.props.label = self._album
141 142 143 144

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

        self._set_composer_label(item)

149
        self._player.connect('song-changed', self._update_model)
150

151 152 153 154 155 156
    @log
    def _set_composer_label(self, item):
        composer = item.get_composer()
        show = False

        if composer:
157
            self._composer_info_label.props.label = composer
158 159
            show = True

160 161
        self._composer_label.props.visible = show
        self._composer_info_label.props.visible = show
162

163 164 165
    @log
    def _set_duration_label(self):
        mins = (self._duration // 60) + 1
166 167
        self._running_info_label.props.label = ngettext(
            "{} minute", "{} minutes", mins).format(mins)
168

169
    @Gtk.Template.Callback()
170
    @log
171 172
    def _on_selection_changed(self, widget):
        items = self._disc_listbox.get_selected_items()
173
        self.selection_toolbar.add_to_playlist_button.set_sensitive(
174
            len(items) > 0)
175
        self._header_bar.items_selected = len(items)
176 177 178 179

    @log
    def _on_header_cancel_button_clicked(self, button):
        """Cancel selection mode callback."""
180
        self._disc_listbox.props.selection_mode = False
181 182
        self._header_bar.props.selection_mode = False
        self._header_bar.props.title = self._album
183 184 185 186 187

    @log
    def _on_header_select_button_toggled(self, button):
        """Selection mode button clicked callback."""
        if button.get_active():
188
            self._selection_mode = True
189
            self._disc_listbox.props.selection_mode = True
190
            self._header_bar.props.selection_mode = True
191
            self._parent_view.set_player_visible(False)
192
        else:
193
            self._selection_mode = False
194
            self._disc_listbox.props.selection_mode = False
195
            self._header_bar.props.selection_mode = False
Jean Felder's avatar
Jean Felder committed
196
            if self._player.get_playback_status() != Playback.STOPPED:
197
                self._parent_view.set_player_visible(True)
198

199
    @log
200
    def _create_disc_box(self, disc_nr, disc_songs):
201
        disc_box = DiscBox(self._model)
202
        disc_box.set_songs(disc_songs)
203
        disc_box.set_disc_number(disc_nr)
204 205 206 207
        disc_box.props.columns = 1
        disc_box.props.show_durations = True
        disc_box.props.show_favorites = True
        disc_box.props.show_song_numbers = False
208
        disc_box.connect('song-activated', self._song_activated)
209 210 211 212 213 214 215 216 217 218
        disc_box.connect('selection-toggle', self._selection_mode_toggled)

        return disc_box

    @log
    def _selection_mode_toggled(self, widget):
        self._selection_mode = not self._selection_mode
        self._on_selection_mode_request()

    @log
219
    def _song_activated(self, widget, song_widget):
220
        if self._selection_mode:
221
            song_widget.props.selected = not song_widget.props.selected
222 223 224
            return

        self._player.stop()
225 226
        self._player.set_playlist(
            'Album', self._album, song_widget.model, song_widget.itr)
227 228 229
        self._player.set_playing(True)
        return True

230
    @log
231
    def add_item(self, source, prefs, song, remaining, data=None):
232 233 234 235
        """Add a song to the item to album list.

        :param source: The grilo source
        :param prefs:
236
        :param song: The grilo media object
237 238 239
        :param remaining: Remaining number of items to add
        :param data: User data
        """
240 241
        if song:
            self._songs.append(song)
242

243
            self._duration = self._duration + song.get_duration()
244 245 246
            return

        discs = {}
247 248
        for song in self._songs:
            disc_nr = song.get_album_disc_number()
249
            if disc_nr not in discs.keys():
250
                discs[disc_nr] = [song]
251
            else:
252
                discs[disc_nr].append(song)
253 254
        for disc_nr in discs:
            disc = self._create_disc_box(disc_nr, discs[disc_nr])
255
            self._disc_listbox.add(disc)
256
            if len(discs) == 1:
257
                disc.props.show_disc_label = False
258 259

        if remaining == 0:
260
            self._set_duration_label()
261

262 263
            self.show_all()

264 265 266 267 268 269 270 271
    @log
    def _update_model(self, player, playlist, current_iter):
        """Player changed callback.

        :param player: The player object
        :param playlist: The current playlist
        :param current_iter: The current iter of the playlist model
        """
272
        if not player.running_playlist('Album', self._album):
273
            return True
274

275
        current_song = playlist[current_iter][player.Field.SONG]
276 277 278

        self._duration = 0

279 280 281 282
        song_passed = False
        _iter = playlist.get_iter_first()

        while _iter:
283
            song = playlist[_iter][player.Field.SONG]
284
            song_widget = song.song_widget
285
            self._duration += song.get_duration()
286

287
            if (song == current_song):
288
                song_widget.props.state = SongWidget.State.PLAYING
289 290
                song_passed = True
            elif (song_passed):
291
                # Counter intuitive, but this is due to call order.
292
                song_widget.props.state = SongWidget.State.UNPLAYED
293
            else:
294
                song_widget.props.state = SongWidget.State.PLAYED
295

296 297
            _iter = playlist.iter_next(_iter)

298
        self._set_duration_label()
299 300 301 302 303 304 305 306 307 308

        return True

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

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