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