1# Copyright (c) 2014-2021 Cedric Bellegarde <cedric.bellegarde@adishatz.org>
2# This program is free software: you can redistribute it and/or modify
3# it under the terms of the GNU General Public License as published by
4# the Free Software Foundation, either version 3 of the License, or
5# (at your option) any later version.
6# This program is distributed in the hope that it will be useful,
7# but WITHOUT ANY WARRANTY; without even the implied warranty of
8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9# GNU General Public License for more details.
10# You should have received a copy of the GNU General Public License
11# along with this program. If not, see <http://www.gnu.org/licenses/>.
12
13from gi.repository import GLib, Gtk, Gio, Pango
14
15from gettext import gettext as _
16
17from lollypop.define import App, Type, MARGIN, ViewType, StorageType
18from lollypop.objects_album import Album
19from lollypop.utils import get_network_available, get_default_storage_type
20from lollypop.helper_signals import signals
21from lollypop.helper_horizontal_scrolling import HorizontalScrollingHelper
22from lollypop.view_albums_box import AlbumsBoxView
23
24
25class AlbumsLineView(AlbumsBoxView, HorizontalScrollingHelper):
26    """
27        Albums on a line
28    """
29
30    ITEMS = 20
31
32    def __init__(self, storage_type, view_type):
33        """
34            Init view
35            @param view_type as ViewType
36            @param storage_type as StorageType
37        """
38        AlbumsBoxView.__init__(self, [], [], storage_type, view_type)
39        self.set_property("valign", Gtk.Align.START)
40        self._label = Gtk.Label.new()
41        self._label.set_ellipsize(Pango.EllipsizeMode.END)
42        self._label.set_hexpand(True)
43        self._label.set_property("halign", Gtk.Align.START)
44        self._label.get_style_context().add_class("dim-label")
45        self.__update_label(App().window.folded)
46        self._backward_button = Gtk.Button.new_from_icon_name(
47                                                        "go-previous-symbolic",
48                                                        Gtk.IconSize.BUTTON)
49        self._forward_button = Gtk.Button.new_from_icon_name(
50                                                       "go-next-symbolic",
51                                                       Gtk.IconSize.BUTTON)
52        self._backward_button.get_style_context().add_class("menu-button")
53        self._forward_button.get_style_context().add_class("menu-button")
54        header = Gtk.Grid()
55        header.set_column_spacing(10)
56        header.add(self._label)
57        header.add(self._backward_button)
58        header.add(self._forward_button)
59        header.set_margin_end(MARGIN)
60        header.show_all()
61        HorizontalScrollingHelper.__init__(self)
62        self.add(header)
63        self.add_widget(self._box)
64
65    def populate(self, albums):
66        """
67            Configure widget based on albums
68            @param items as [Album]
69        """
70        if albums:
71            self.show()
72            self._box.set_min_children_per_line(len(albums))
73            AlbumsBoxView.populate(self, albums)
74
75    def add_value(self, album):
76        """
77            Add a new album
78            @param album as Album
79        """
80        AlbumsBoxView.add_value_unsorted(self, album)
81        self._box.set_min_children_per_line(len(self._box.get_children()))
82        self.update_buttons()
83
84    @property
85    def args(self):
86        return None
87
88#######################
89# PROTECTED           #
90#######################
91    def _on_collection_updated(self, scanner, item, scan_update):
92        pass
93
94    def _on_container_folded(self, leaflet, folded):
95        """
96            Handle libhandy folded status
97            @param leaflet as Handy.Leaflet
98            @param folded as Gparam
99        """
100        AlbumsBoxView._on_container_folded(self, leaflet, folded)
101        self.__update_label(folded)
102
103    def _on_populated(self, widget):
104        """
105            Update buttons
106            @param widget as Gtk.Widget
107        """
108        self.update_buttons()
109        AlbumsBoxView._on_populated(self, widget)
110
111#######################
112# PRIVATE             #
113#######################
114    def __update_label(self, folded):
115        """
116            Update label style based on current adaptive state
117            @param folded as bool
118        """
119        style_context = self._label.get_style_context()
120        if folded:
121            style_context.remove_class("text-x-large")
122        else:
123            style_context.add_class("text-x-large")
124
125
126class AlbumsArtistLineView(AlbumsLineView):
127    """
128        Artist album line
129    """
130
131    def __init__(self,  artist_id, genre_ids, storage_type, view_type):
132        """
133            Init view
134            @param artist_id as artist_id
135            @param genre_ids as [int]
136            @param storage_type as StorageType
137            @param view_type as ViewType
138        """
139        AlbumsLineView.__init__(self, storage_type, view_type)
140        self.__artist_id = artist_id
141        self.__genre_ids = genre_ids
142
143    def populate(self, excluded_album_id):
144        """
145            Populate view less excluded_album_id
146            @param excluded_album_id as int
147        """
148        def on_load(items):
149            AlbumsLineView.populate(self, items)
150
151        def load():
152            if self.__artist_id == Type.COMPILATIONS:
153                album_ids = App().albums.get_compilation_ids(
154                    self.__genre_ids, self.storage_type, True)
155            else:
156                album_ids = App().albums.get_ids(
157                    self.__genre_ids, [self.__artist_id],
158                    self.storage_type, True)
159            if excluded_album_id in album_ids:
160                album_ids.remove(excluded_album_id)
161            return [Album(album_id) for album_id in album_ids]
162
163        if self.__artist_id == Type.COMPILATIONS:
164            self._label.set_text(_("Others compilations"))
165        else:
166            self._label.set_text(App().artists.get_name(self.__artist_id))
167        App().task_helper.run(load, callback=(on_load,))
168
169
170class AlbumsArtistAppearsOnLineView(AlbumsLineView):
171    """
172        Show albums where artist is in featured
173    """
174
175    def __init__(self,  artist_ids, genre_ids, storage_type, view_type):
176        """
177            Init view
178            @param storage_type as StorageType
179            @param view_type as ViewType
180        """
181        AlbumsLineView.__init__(self, storage_type, view_type)
182        self.__artist_ids = artist_ids
183        self.__genre_ids = genre_ids
184
185    def populate(self):
186        """
187            Populate view
188        """
189        def on_load(items):
190            AlbumsLineView.populate(self, items)
191
192        def load():
193            album_ids = App().artists.get_featured(self.__genre_ids,
194                                                   self.__artist_ids,
195                                                   self.storage_type,
196                                                   True)
197            return [Album(album_id) for album_id in album_ids]
198
199        self._label.set_text(_("Appears on"))
200        App().task_helper.run(load, callback=(on_load,))
201
202
203class AlbumsPopularsLineView(AlbumsLineView):
204    """
205        Populars albums line
206    """
207
208    def __init__(self, storage_type, view_type):
209        """
210            Init view
211            @param storage_type as StorageType
212            @param view_type as ViewType
213        """
214        AlbumsLineView.__init__(self, storage_type, view_type)
215
216    def populate(self):
217        """
218            Populate view
219        """
220        def on_load(items):
221            AlbumsLineView.populate(self, items)
222
223        def load():
224            storage_type = get_default_storage_type()
225            album_ids = App().albums.get_populars_at_the_moment(storage_type,
226                                                                False,
227                                                                self.ITEMS)
228            return [Album(album_id) for album_id in album_ids]
229
230        self._label.set_text(_("Popular albums at the moment"))
231        App().task_helper.run(load, callback=(on_load,))
232
233
234class AlbumsRandomGenresLineView(AlbumsLineView):
235    """
236        Populars albums line
237    """
238
239    def __init__(self, storage_type, view_type):
240        """
241            Init view
242            @param storage_type as StorageType
243            @param view_type as ViewType
244        """
245        AlbumsLineView.__init__(self, storage_type, view_type)
246
247    def populate(self):
248        """
249            Populate view
250        """
251        def on_load(items):
252            AlbumsLineView.populate(self, items)
253
254        def load():
255            (genre_id, genre) = App().genres.get_random()
256            GLib.idle_add(self._label.set_text, genre)
257            storage_type = get_default_storage_type()
258            album_ids = App().albums.get_randoms(storage_type,
259                                                 genre_id,
260                                                 False,
261                                                 self.ITEMS)
262            return [Album(album_id) for album_id in album_ids]
263
264        App().task_helper.run(load, callback=(on_load,))
265
266
267class AlbumsSearchLineView(AlbumsLineView):
268    """
269        Search album line
270    """
271    def __init__(self, storage_type):
272        """
273            Init view
274            @param storage_type as StorageType
275        """
276        AlbumsLineView.__init__(self, storage_type, ViewType.SEARCH |
277                                ViewType.SCROLLED | ViewType.ALBUM)
278        self.__album_ids = []
279        self._label.set_text(_("Albums"))
280
281    def add_value(self, album):
282        """
283            Add a new album
284            @param album as Album
285        """
286        if album.id in self.__album_ids:
287            return
288        self.__album_ids.append(album.id)
289        AlbumsLineView.add_value(self, album)
290
291    def clear(self):
292        """
293            Clear and hide the view
294        """
295        self.__album_ids = []
296        AlbumsLineView.clear(self)
297        GLib.idle_add(self.hide)
298
299
300class AlbumsStorageTypeLineView(AlbumsLineView):
301    """
302        Storage type album line
303    """
304
305    @signals
306    def __init__(self, text, storage_type, view_type):
307        """
308            Init view
309            @param text as str
310            @param storage_type as StorageType
311            @param view_type as ViewType
312        """
313        AlbumsLineView.__init__(self, storage_type, view_type)
314        self._label.set_text(text)
315        self.__cancellable = Gio.Cancellable()
316        self.__storage_type = StorageType.NONE
317        return [
318                (App().ws_director.collection_ws, "match-album",
319                 "_on_album_match"),
320                (App().settings, "changed::network-access",
321                 "_on_network_access_changed"),
322                (App().settings, "changed::network-access-acl",
323                 "_on_network_access_changed")
324        ]
325
326    def populate(self, storage_type):
327        """
328            Populate view
329            @param storage_type as StorageType
330        """
331        def on_load(items):
332            AlbumsLineView.populate(self, items)
333
334        def load():
335            album_ids = App().albums.get_for_storage_type(storage_type, 20)
336            return [Album(album_id) for album_id in album_ids]
337
338        App().task_helper.run(load, callback=(on_load,))
339        self.__storage_type |= storage_type
340
341#######################
342# PROTECTED           #
343#######################
344    def _on_unmap(self, widget):
345        """
346            Cancel search
347            @param widget as Gtk.Widget
348        """
349        self.__cancellable.cancel()
350        self.__cancellable = Gio.Cancellable()
351
352    def _on_album_match(self, spotify_helper, album_id, storage_type):
353        """
354            Handles changes in collection
355            @param scanner as CollectionScanner
356            @param album_id as int
357            @param storage_type as StorageType
358        """
359        count = len(self.children)
360        if count == self.ITEMS:
361            return
362        if self.__storage_type & storage_type:
363            self.add_value(Album(album_id))
364            self.show()
365
366    def _on_network_access_changed(self, *ignore):
367        """
368            Destroy if not allowed anymore
369        """
370        if not get_network_available("SPOTIFY") or\
371                not get_network_available("YOUTUBE"):
372            self.destroy()
373
374#####################
375# PRIVATE           #
376#####################
377