def _get_view_artists(self, genre_ids, artist_ids): """ Get artists view for genres/artists @param genre ids as [int] @param artist ids as [int] """ def load(): if genre_ids and genre_ids[0] == Type.ALL: items = App().albums.get_ids(artist_ids, []) else: items = [] if artist_ids and artist_ids[0] == Type.COMPILATIONS: items += App().albums.get_compilation_ids(genre_ids) items += App().albums.get_ids(artist_ids, genre_ids) return [ Album(album_id, genre_ids, artist_ids) for album_id in items ] if App().window.is_adaptive: from lollypop.view_albums_list import AlbumsListView view = AlbumsListView(RowListType.DEFAULT, artist_ids, genre_ids) else: from lollypop.view_artist import ArtistView view = ArtistView(artist_ids, genre_ids) loader = Loader(target=load, view=view) loader.start() view.show() return view
def __init__(self): """ Init Popover """ BaseView.__init__(self) Gtk.Bin.__init__(self) self.connect("map", self.__on_map) self.connect("unmap", self.__on_unmap) self.__timeout_id = None self.__current_search = "" self.__cancellable = Gio.Cancellable() self.__history = [] builder = Gtk.Builder() builder.add_from_resource("/org/gnome/Lollypop/SearchView.ui") builder.connect_signals(self) self.__widget = builder.get_object("widget") self.__new_button = builder.get_object("new_button") self.__play_button = builder.get_object("play_button") self.__entry = builder.get_object("entry") self.__spinner = builder.get_object("spinner") self.__header_stack = builder.get_object("header_stack") self.__stack = builder.get_object("stack") self.__placeholder = builder.get_object("placeholder") self.__view = AlbumsListView(RowListType.SEARCH) self.__view.show() self.__stack.add_named(self.__view, "view") self.__set_default_placeholder() self.add(self.__widget)
def __init__(self, playlist_id, view_type): """ Init PlaylistView @parma playlist_id as int @param view_type as ViewType """ AlbumsListView.__init__(self, [], [], view_type | ViewType.SCROLLED | ViewType.OVERLAY) SizeAllocationHelper.__init__(self) self.__playlist_id = playlist_id self.box.set_width(Size.MEDIUM) if view_type & ViewType.DND: self.dnd_helper.connect("dnd-finished", self.__on_dnd_finished) self.__banner = PlaylistBannerWidget(playlist_id, self) self.__banner.show() self.add_widget(self.box, self.__banner) return [ (App().playlists, "playlist-track-added", "_on_playlist_track_added"), (App().playlists, "playlist-track-removed", "_on_playlist_track_removed"), (App().playlists, "playlists-removed", "_on_playlist_removed"), (App().playlists, "playlists-renamed", "_on_playlist_renamed"), (App().playlists, "playlists-updated", "_on_playlist_updated") ]
def __init__(self, view_type): """ Init view @param view_type as ViewType """ AlbumsListView.__init__(self, [], [], view_type | ViewType.SCROLLED | ViewType.OVERLAY | ViewType.PLAYBACK) self.box.set_width(Size.MEDIUM) if view_type & ViewType.DND: self.dnd_helper.connect("dnd-finished", self.__on_dnd_finished) self.__banner = CurrentAlbumsBannerWidget(self) self.__banner.show() self.add_widget(self.box, self.__banner) self.allow_duplicate("_on_queue_changed") self.allow_duplicate("_on_playback_added") self.allow_duplicate("_on_playback_updated") self.allow_duplicate("_on_playback_removed") return [ (App().player, "queue-changed", "_on_queue_changed"), (App().player, "playback-added", "_on_playback_added"), (App().player, "playback-setted", "_on_playback_setted"), (App().player, "playback-updated", "_on_playback_updated"), (App().player, "playback-removed", "_on_playback_removed") ]
def clear(self): """ Clear the view """ if not App().player.radio_cancellable.is_cancelled(): App().player.radio_cancellable.cancel() GLib.timeout_add(500, self.clear) else: AlbumsListView.clear(self) App().player.clear_albums()
def populate(self, artist_id=None): """ Show information for artists @param artist_id as int """ builder = Gtk.Builder() builder.add_from_resource("/org/gnome/Lollypop/ArtistInformation.ui") builder.connect_signals(self) self.__scrolled = builder.get_object("scrolled") widget = builder.get_object("widget") self.add(widget) self.__stack = builder.get_object("stack") self.__artist_label = builder.get_object("artist_label") title_label = builder.get_object("title_label") self.__artist_artwork = builder.get_object("artist_artwork") eventbox = builder.get_object("eventbox") eventbox.connect("button-release-event", self.__on_label_button_release_event) self.__bio_label = builder.get_object("bio_label") if artist_id is None and App().player.current_track.id is not None: builder.get_object("header").show() builder.get_object("lyrics_button").show() builder.get_object("lyrics_button").connect( "clicked", self.__on_lyrics_button_clicked, App().player.current_track) artist_id = App().player.current_track.artist_ids[0] title_label.set_text(App().player.current_track.title) self.__artist_name = App().artists.get_name(artist_id) if self.__minimal: self.__artist_artwork.hide() else: builder.get_object("header").show() self.__artist_label.set_text(self.__artist_name) self.__artist_label.show() title_label.show() App().art_helper.set_artist_artwork( self.__artist_name, ArtSize.ARTIST_SMALL * 3, ArtSize.ARTIST_SMALL * 3, self.__artist_artwork.get_scale_factor(), self.__on_artist_artwork, ArtHelperEffect.ROUNDED) albums_view = AlbumsListView(RowListType.READ_ONLY) albums_view.set_size_request(300, -1) albums_view.show() albums_view.set_margin_start(5) widget.attach(albums_view, 2, 1, 1, 2) albums = [] for album_id in App().albums.get_ids([artist_id], []): albums.append(Album(album_id)) albums_view.populate(albums) App().task_helper.run(InformationStore.get_bio, self.__artist_name, callback=(self.__on_get_bio, ))
def _get_view_albums(self, genre_ids, artist_ids): """ Get albums view for genres/artists @param genre ids as [int] @param is compilation as bool """ def load(): items = [] is_compilation = artist_ids and artist_ids[0] == Type.COMPILATIONS if genre_ids and genre_ids[0] == Type.ALL: if is_compilation or\ App().settings.get_value( "show-compilations-in-album-view"): items = App().albums.get_compilation_ids([]) if not is_compilation: items += App().albums.get_ids([], []) elif genre_ids and genre_ids[0] == Type.POPULARS: items = App().albums.get_rated() count = 100 - len(items) for album in App().albums.get_populars(count): if album not in items: items.append(album) elif genre_ids and genre_ids[0] == Type.LOVED: items = App().albums.get_loved_albums() elif genre_ids and genre_ids[0] == Type.RECENTS: items = App().albums.get_recents() elif genre_ids and genre_ids[0] == Type.NEVER: items = App().albums.get_never_listened_to() elif genre_ids and genre_ids[0] == Type.RANDOMS: items = App().albums.get_randoms() else: if is_compilation or\ App().settings.get_value( "show-compilations-in-album-view"): items = App().albums.get_compilation_ids(genre_ids) if not is_compilation: items += App().albums.get_ids([], genre_ids) return [ Album(album_id, genre_ids, artist_ids) for album_id in items ] if App().window.is_adaptive: from lollypop.view_albums_list import AlbumsListView view = AlbumsListView(RowListType.DEFAULT, genre_ids, artist_ids) else: from lollypop.view_albums_box import AlbumsBoxView view = AlbumsBoxView(genre_ids, artist_ids) loader = Loader(target=load, view=view) loader.start() view.show() return view
def __init__(self, view_type): """ Init view @param view_type as ViewType """ AlbumsListView.__init__(self, [], [], view_type) self.__clear_button = Gtk.Button.new_from_icon_name( "edit-clear-all-symbolic", Gtk.IconSize.MENU) self.__clear_button.set_relief(Gtk.ReliefStyle.NONE) self.__clear_button.set_tooltip_text(_("Clear albums")) self.__clear_button.get_style_context().add_class("light-button") self.__clear_button.get_style_context().add_class( "light-button-no-right-border") self.__clear_button.set_sensitive(App().player.albums) self.__clear_button.connect("clicked", self.__on_clear_clicked) self.__clear_button.set_margin_end(MARGIN_SMALL) self.__save_button = Gtk.Button.new_from_icon_name( "document-new-symbolic", Gtk.IconSize.MENU) self.__save_button.set_relief(Gtk.ReliefStyle.NONE) self.__save_button.set_tooltip_text(_("Create a new playlist")) self.__save_button.get_style_context().add_class("light-button") self.__save_button.set_sensitive(App().player.albums) self.__save_button.connect("clicked", self.__on_save_clicked) self.__jump_button = Gtk.Button.new_from_icon_name( "go-jump-symbolic", Gtk.IconSize.MENU) self.__jump_button.set_relief(Gtk.ReliefStyle.NONE) self.__jump_button.connect("clicked", self.__on_jump_clicked) self.__jump_button.get_style_context().add_class("light-button") self.__jump_button.set_tooltip_text(_("Go to current track")) self.__jump_button.set_sensitive(App().player.albums) label = Gtk.Label.new("<b>" + _("Playing albums") + "</b>") label.set_use_markup(True) label.set_hexpand(True) label.set_margin_start(2) label.get_style_context().add_class("dim-label") label.set_property("halign", Gtk.Align.START) grid = Gtk.Grid() grid.set_column_spacing(5) grid.add(label) grid.set_property("valign", Gtk.Align.CENTER) if view_type & ViewType.POPOVER: grid.add(self.__jump_button) grid.add(self.__save_button) grid.add(self.__clear_button) grid.show_all() grid.set_margin_bottom(MARGIN_SMALL) self.set_row_spacing(2) self.insert_row(0) self.attach(grid, 0, 0, 1, 1)
def populate(self): """ Populate view """ if App().player.queue: tracks = [Track(track_id) for track_id in App().player.queue] albums = tracks_to_albums(tracks) else: albums = App().player.albums if albums: if len(albums) == 1: self.add_reveal_albums(albums) AlbumsListView.populate(self, albums) self.show_placeholder(False) else: self.show_placeholder(True)
def __init__(self): """ Init Popover """ BaseView.__init__(self) Gtk.Bin.__init__(self) self.connect("map", self.__on_map) self.connect("unmap", self.__on_unmap) self.__timeout_id = None self.__new_album_signal_id = None self.__search_finished_signal_id = None self.__current_search = "" self.__cancellable = Gio.Cancellable() self.__history = [] self.__search_type_action = Gio.SimpleAction.new_stateful( "search_type", GLib.VariantType.new("s"), GLib.Variant("s", "local")) self.__search_type_action.connect("change-state", self.__on_search_action_change_state) App().add_action(self.__search_type_action) builder = Gtk.Builder() builder.add_from_resource("/org/gnome/Lollypop/SearchView.ui") self.__widget = builder.get_object("widget") self.__new_button = builder.get_object("new_button") self.__play_button = builder.get_object("play_button") self.__bottom_buttons = builder.get_object("bottom_buttons") self.__entry = builder.get_object("entry") self.__spinner = builder.get_object("spinner") self.__header_stack = builder.get_object("header_stack") self.__combo_locale = builder.get_object("combo_locale") self.__combo_locale.set_active_id( App().settings.get_value("spotify-charts-locale").get_string()) self.__button_stack = builder.get_object("button_stack") self.__stack = builder.get_object("stack") self.__placeholder = builder.get_object("placeholder") self.__view = AlbumsListView([], [], ViewType.SEARCH) self.__view.set_margin_start(MARGIN_SMALL) self.__view.show() self.__stack.add_named(self.__view, "view") self.__set_default_placeholder() self.add(self.__widget) builder.connect_signals(self)
def _get_view_albums_years(self, years): """ Get album view for years @param years as [int] """ def load(): items = [] for year in years: items += App().albums.get_compilations_for_year(year) items += App().albums.get_albums_for_year(year) return [Album(album_id, [Type.YEARS], []) for album_id in items] if App().window.is_adaptive: from lollypop.view_albums_list import AlbumsListView view = AlbumsListView(RowListType.DEFAULT, years, [Type.YEARS]) else: from lollypop.view_albums_box import AlbumsBoxView view = AlbumsBoxView([Type.YEARS], years) loader = Loader(target=load, view=view) loader.start() view.show() return view
def _on_queue_changed(self, *ignore): """ Clean view and reload if empty """ queue = App().player.queue if queue: for row in self.children: if row.revealed: for subrow in row.children: if subrow.track.id not in queue: subrow.destroy() break count = len(row.album.tracks) for track in row.album.tracks: if track.id not in queue: row.album.remove_track(track) if count == 1: row.destroy() break else: self.stop() AlbumsListView.clear(self) self.populate()
def _on_playback_setted(self, player, albums): """ Add album @param player as Player @param albums as [Album] """ if albums: self.stop() AlbumsListView.clear(self) AlbumsListView.populate(self, albums) self.show_placeholder(False) else: self.stop() AlbumsListView.clear(self) self.show_placeholder(True)
class SearchView(BaseView, Gtk.Bin): """ View for searching albums/tracks """ def __init__(self): """ Init Popover """ BaseView.__init__(self) Gtk.Bin.__init__(self) self.connect("map", self.__on_map) self.connect("unmap", self.__on_unmap) self.__timeout_id = None self.__current_search = "" self.__cancellable = Gio.Cancellable() self.__history = [] builder = Gtk.Builder() builder.add_from_resource("/org/gnome/Lollypop/SearchView.ui") builder.connect_signals(self) self.__widget = builder.get_object("widget") self.__new_button = builder.get_object("new_button") self.__play_button = builder.get_object("play_button") self.__entry = builder.get_object("entry") self.__spinner = builder.get_object("spinner") self.__header_stack = builder.get_object("header_stack") self.__stack = builder.get_object("stack") self.__placeholder = builder.get_object("placeholder") self.__view = AlbumsListView(RowListType.SEARCH) self.__view.show() self.__stack.add_named(self.__view, "view") self.__set_default_placeholder() self.add(self.__widget) def set_text(self, text): """ Set search text """ self.__entry.set_text(text) ####################### # PROTECTED # ####################### def _on_play_button_clicked(self, button): """ Play search @param button as Gtk.Button """ try: App().player.clear_albums() shuffle_setting = App().settings.get_enum("shuffle") children = self.__view.children if shuffle_setting == Shuffle.ALBUMS: shuffle(children) for child in children: App().player.add_album(child.album) App().player.load(App().player.albums[0].tracks[0]) except Exception as e: Logger.error("SearchPopover::_on_play_button_clicked(): %s", e) def _on_new_button_clicked(self, button): """ Create a new playlist based on search @param button as Gtk.Button """ button.set_sensitive(False) App().task_helper.run(self.__search_to_playlist) def _on_search_changed(self, widget): """ Timeout filtering @param widget as Gtk.TextEntry """ if self.__timeout_id: GLib.source_remove(self.__timeout_id) self.__timeout_id = None self.__cancellable.cancel() self.__view.stop() self.__current_search = widget.get_text().strip() self.__timeout_id = GLib.timeout_add(500, self.__on_search_changed_timeout) ####################### # PRIVATE # ####################### def __set_no_result_placeholder(self): """ Set placeholder for no result """ self.__placeholder.set_markup( _("<big>No results for this search</big>")) def __set_default_placeholder(self): """ Set placeholder for no result """ self.__placeholder.set_markup( _("<big>Search for artists, albums and tracks</big>")) def __populate(self): """ Populate searching items in db based on text entry current text """ self.__cancellable.reset() self.__header_stack.set_visible_child(self.__spinner) self.__spinner.start() self.__history = [] if self.__current_search: search = Search() search.get(self.__current_search, self.__cancellable, callback=(self.__on_search_get, )) else: self.__stack.set_visible_child_name("placeholder") self.__set_default_placeholder() self.__header_stack.set_visible_child(self.__new_button) GLib.idle_add(self.__spinner.stop) def __search_to_playlist(self): """ Create a new playlist based on search """ tracks = [] for child in self.__view.children: tracks += child.album.tracks if tracks: playlist_id = App().playlists.get_id(self.__current_search) if playlist_id == Type.NONE: App().playlists.add(self.__current_search) playlist_id = App().playlists.get_id(self.__current_search) App().playlists.add_tracks(playlist_id, tracks) def __on_search_get(self, result): """ Add rows for internal results @param result as [(int, Album, bool)] """ if result: albums = [] reveal_albums = [] for (score, album, in_tracks) in result: albums.append(album) if in_tracks: reveal_albums.append(album.id) self.__view.set_reveal(reveal_albums) self.__view.populate(albums) self.__stack.set_visible_child_name("view") else: self.__stack.set_visible_child_name("placeholder") self.__set_no_result_placeholder() self.__header_stack.set_visible_child(self.__new_button) GLib.idle_add(self.__spinner.stop) def __on_map(self, widget): """ Grab focus @param widget as Gtk.Widget """ GLib.idle_add(self.__entry.grab_focus) def __on_unmap(self, widget): """ Stop loading @param widget as Gtk.Widget """ self.__cancellable.cancel() self.__view.stop() self.__header_stack.set_visible_child(self.__new_button) self.__spinner.stop() def __on_search_changed_timeout(self): """ Populate widget """ if self.__view.children: self.__view.stop() self.__view.clear() return True self.__timeout_id = None self.__populate() if self.__current_search != "": self.__play_button.set_sensitive(True) self.__new_button.set_sensitive(True) else: self.__play_button.set_sensitive(False) self.__new_button.set_sensitive(False)
def on_load(albums): self.banner.spinner.stop() self.add_reveal_albums(albums) AlbumsListView.populate(self, albums)
class FullScreen(Gtk.Window, InformationController, PlaybackController, ProgressController): """ Show a fullscreen window showing current track context """ def __init__(self, app): """ Init window for app @param app as Gio.Application """ Gtk.Window.__init__(self) self.set_title("Lollypop") rotate_album = App().settings.get_value("rotate-fullscreen-album") PlaybackController.__init__(self) ProgressController.__init__(self) if rotate_album: InformationController.__init__(self, False, ArtHelperEffect.ROUNDED) else: InformationController.__init__(self, False, ArtHelperEffect.NONE) self.set_application(app) self.__timeout_id = None self.__signal1_id = self.__signal2_id = self.__signal3_id = None self.set_decorated(False) builder = Gtk.Builder() builder.add_from_resource("/org/gnome/Lollypop/FullScreen.ui") builder.connect_signals(self) # Calculate cover size screen = Gdk.Screen.get_default() monitor = screen.get_monitor_at_window(App().window.get_window()) geometry = screen.get_monitor_geometry(monitor) # We want 500 and 200 in full hd if geometry.width > geometry.height: self.__artsize = int(ArtSize.FULLSCREEN * geometry.height / 1080) else: self.__artsize = int(ArtSize.FULLSCREEN * geometry.width / 1920) self.__font_size = int(14 * geometry.height / 1080) widget = builder.get_object("widget") grid = builder.get_object("grid") self._play_button = builder.get_object("play_btn") self._next_button = builder.get_object("next_btn") self._prev_button = builder.get_object("prev_btn") self._play_image = builder.get_object("play_image") self._pause_image = builder.get_object("pause_image") close_btn = builder.get_object("close_btn") preferences = Gio.Settings.new("org.gnome.desktop.wm.preferences") layout = preferences.get_value("button-layout").get_string() if layout.split(":")[0] == "close": grid.attach(close_btn, 0, 0, 1, 1) close_btn.set_property("halign", Gtk.Align.START) else: grid.attach(close_btn, 2, 0, 1, 1) close_btn.set_property("halign", Gtk.Align.END) self._artwork = builder.get_object("cover") if rotate_album: self._artwork.get_style_context().add_class("image-rotate") else: self._artwork.get_style_context().add_class("cover-frame") self._title_label = builder.get_object("title") self._artist_label = builder.get_object("artist") self._album_label = builder.get_object("album") self.__revealer = builder.get_object("reveal") self._datetime = builder.get_object("datetime") self._progress = builder.get_object("progress_scale") self._timelabel = builder.get_object("playback") self._total_time_label = builder.get_object("duration") self.connect("key-release-event", self.__on_key_release_event) # Add an AlbumListView on the right self.__view = AlbumsListView(RowListType.DND) self.__view.get_style_context().add_class("background-opacity") self.__view.show() self.__revealer.add(self.__view) self.add(widget) def do_show(self): """ Init signals, set color and go party mode if nothing is playing """ App().window.hide() self.__signal1_id = App().player.connect("current-changed", self.on_current_changed) self.__signal2_id = App().player.connect("status-changed", self.on_status_changed) self.__signal3_id = App().player.connect("party-changed", self.__on_party_changed) self.on_status_changed(App().player) self.on_current_changed(App().player) self.__view.populate(App().player.albums) Gtk.Window.do_show(self) if self.__timeout_id is None: try: interface = Gio.Settings.new("org.gnome.desktop.interface") show_seconds = interface.get_value("clock-show-seconds") except: show_seconds = False self.__update_datetime(show_seconds) self.__timeout_id = GLib.timeout_add(1000, self.__update_datetime, show_seconds) self.update_position(App().player.position / Gst.SECOND) screen = Gdk.Screen.get_default() monitor = screen.get_monitor_at_window(App().window.get_window()) self.fullscreen_on_monitor(screen, monitor) # Disable screensaver (idle) App().inhibitor.manual_inhibit(Gtk.ApplicationInhibitFlags.IDLE | Gtk.ApplicationInhibitFlags.SUSPEND) def do_hide(self): """ Remove signals and unset color """ App().window.setup() App().window.show() self.__view.stop() Gtk.Window.do_hide(self) if self.__signal1_id is not None: App().player.disconnect(self.__signal1_id) self.__signal1_id = None if self.__signal2_id is not None: App().player.disconnect(self.__signal2_id) self.__signal2_id = None if self.__signal3_id is not None: App().player.disconnect(self.__signal3_id) self.__signal3_id = None if self.__timeout_id is not None: GLib.source_remove(self.__timeout_id) self.__timeout_id = None App().inhibitor.manual_uninhibit() ProgressController.do_destroy(self) def show_hide_volume_control(self): """ Show/Hide volume control """ self._show_volume_control = not self._show_volume_control self._update_state() def on_status_changed(self, player): """ Update controller @param player as Player """ ProgressController.on_status_changed(self, player) PlaybackController.on_status_changed(self, player) context = self._artwork.get_style_context() if not App().settings.get_value("rotate-fullscreen-album"): return if player.is_playing: context.add_class("image-rotate") else: context.remove_class("image-rotate") def on_current_changed(self, player): """ Update controllers @param player as Player """ InformationController.on_current_changed(self, self.__artsize, self.__font_size) ProgressController.on_current_changed(self, player) if player.current_track.id is not None: album_name = player.current_track.album.name if player.current_track.year: album_name += " (%s)" % player.current_track.year self._album_label.set_markup( "<span font='%s'>%s</span>" % (self.__font_size - 1, GLib.markup_escape_text(album_name))) ####################### # PROTECTED # ####################### def _on_close_button_clicked(self, widget): """ Destroy self @param widget as Gtk.Button """ self.__view.stop() self.destroy() def _on_motion_notify_event(self, widget, event): """ Show/Hide track list if mouse on the right @param widget as Gtk.Widget @param event as Gdk.EventMotion """ if event.window == widget.get_window(): allocated_width = widget.get_allocated_width() reveal = event.x > allocated_width -\ self.__view.get_allocated_width() - 100 and\ event.x < allocated_width + 100 self.__revealer.set_reveal_child(reveal) def _on_image_realize(self, eventbox): """ Set cursor @param eventbox as Gtk.EventBox """ try: eventbox.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.HAND2)) except: Logger.warning(_("You are using a broken cursor theme!")) def _on_image_button_release_event(self, widget, event): """ Change artwork type @param widget as Gtk.Widget @param event as Gdk.Event """ rotate_album = not App().settings.get_value("rotate-fullscreen-album") App().settings.set_value("rotate-fullscreen-album", GLib.Variant("b", rotate_album)) context = self._artwork.get_style_context() if rotate_album: context.add_class("image-rotate") context.remove_class("cover-frame") InformationController.__init__(self, False, ArtHelperEffect.ROUNDED) else: context.remove_class("image-rotate") context.add_class("cover-frame") InformationController.__init__(self, False, ArtHelperEffect.NONE) InformationController.on_current_changed(self, self.__artsize, self.__font_size) ####################### # PRIVATE # ####################### def __update_datetime(self, show_seconds=False): """ Update datetime in headerbar @param show_seconds as bool """ now = datetime.now() if show_seconds: self._datetime.set_label(now.strftime("%a %d %b, %X")) else: self._datetime.set_label(now.strftime("%a %d %b, %X")[:-3]) if self.__timeout_id is None: self.__timeout_id = GLib.timeout_add(60000, self.__update_datetime) return False return True def __on_party_changed(self, player, party): """ Populate view again @param player as Player @param party as bool """ self.__view.populate(player.albums) def __on_key_release_event(self, widget, event): """ Destroy window if Esc @param widget as Gtk.Widget @param event as Gdk.event """ if event.keyval == Gdk.KEY_Escape: self.destroy()
class SearchView(BaseView, Gtk.Bin): """ View for searching albums/tracks """ def __init__(self): """ Init Popover """ BaseView.__init__(self) Gtk.Bin.__init__(self) self.connect("map", self.__on_map) self.connect("unmap", self.__on_unmap) self.__timeout_id = None self.__new_album_signal_id = None self.__search_finished_signal_id = None self.__current_search = "" self.__cancellable = Gio.Cancellable() self.__history = [] self.__search_type_action = Gio.SimpleAction.new_stateful( "search_type", GLib.VariantType.new("s"), GLib.Variant("s", "local")) self.__search_type_action.connect("change-state", self.__on_search_action_change_state) App().add_action(self.__search_type_action) builder = Gtk.Builder() builder.add_from_resource("/org/gnome/Lollypop/SearchView.ui") self.__widget = builder.get_object("widget") self.__new_button = builder.get_object("new_button") self.__play_button = builder.get_object("play_button") self.__bottom_buttons = builder.get_object("bottom_buttons") self.__entry = builder.get_object("entry") self.__spinner = builder.get_object("spinner") self.__header_stack = builder.get_object("header_stack") self.__combo_locale = builder.get_object("combo_locale") self.__combo_locale.set_active_id( App().settings.get_value("spotify-charts-locale").get_string()) self.__button_stack = builder.get_object("button_stack") self.__stack = builder.get_object("stack") self.__placeholder = builder.get_object("placeholder") self.__view = AlbumsListView([], [], ViewType.SEARCH) self.__view.set_margin_start(MARGIN_SMALL) self.__view.show() self.__stack.add_named(self.__view, "view") self.__set_default_placeholder() self.add(self.__widget) builder.connect_signals(self) def set_search(self, search): """ Set search text @param search as str """ parsed = urlparse(search) search = search.replace("%s://" % parsed.scheme, "") if parsed.scheme == "local": self.__entry.set_text(search) GLib.idle_add(self.__search_type_action.set_state, GLib.Variant("s", "local")) elif parsed.scheme == "web": self.__entry.set_text(search) GLib.idle_add(self.__search_type_action.set_state, GLib.Variant("s", "web")) def cancel(self): """ Cancel current search and replace cancellable """ self.__cancellable.cancel() self.__cancellable = Gio.Cancellable() @property def should_destroy(self): """ User want to get search between mode """ return False ####################### # PROTECTED # ####################### def _on_play_button_clicked(self, button): """ Play search @param button as Gtk.Button """ try: App().player.clear_albums() shuffle_setting = App().settings.get_enum("shuffle") children = self.__view.children if shuffle_setting == Shuffle.ALBUMS: shuffle(children) for child in children: App().player.add_album(child.album) App().player.load(App().player.albums[0].tracks[0]) except Exception as e: Logger.error("SearchPopover::_on_play_button_clicked(): %s", e) def _on_new_button_clicked(self, button): """ Create a new playlist based on search @param button as Gtk.Button """ App().task_helper.run(self.__search_to_playlist) def _on_search_changed(self, widget): """ Timeout filtering @param widget as Gtk.TextEntry """ state = self.__search_type_action.get_state().get_string() if state == "local": timeout = 500 else: timeout = 1000 if self.__timeout_id: GLib.source_remove(self.__timeout_id) self.__timeout_id = None self.cancel() self.__view.stop() self.__current_search = widget.get_text().strip() self.__timeout_id = GLib.timeout_add(timeout, self.__on_search_changed_timeout) def _on_combo_locale_changed(self, combobox): """ Save setting @param combobox as Gtk.ComboBoxText """ App().settings.set_value("spotify-charts-locale", GLib.Variant("s", combobox.get_active_id())) self.__on_search_action_change_state(self.__search_type_action, GLib.Variant("s", "charts")) ####################### # PRIVATE # ####################### def __set_no_result_placeholder(self): """ Set placeholder for no result """ self.__placeholder.set_markup( _("<big>No results for this search</big>")) def __set_default_placeholder(self): """ Set placeholder for no result """ self.__placeholder.set_markup( _("<big>Search for artists, albums and tracks</big>")) def __populate(self): """ Populate searching items in db based on text entry current text """ self.__cancellable = Gio.Cancellable() self.__button_stack.set_visible_child(self.__spinner) self.__history = [] if len(self.__current_search) > 2: self.__spinner.start() state = self.__search_type_action.get_state().get_string() current_search = self.__current_search.lower() if state == "local": search = Search() search.get(current_search, self.__cancellable, callback=(self.__on_search_get, current_search)) elif state == "web": App().task_helper.run(App().spotify.search, current_search, self.__cancellable) else: self.__stack.set_visible_child_name("placeholder") self.__set_default_placeholder() self.__button_stack.set_visible_child(self.__new_button) GLib.idle_add(self.__spinner.stop) def __search_to_playlist(self): """ Create a new playlist based on search """ tracks = [] for child in self.__view.children: tracks += child.album.tracks if tracks: playlist_id = App().playlists.get_id(self.__current_search) if playlist_id == Type.NONE: App().playlists.add(self.__current_search) playlist_id = App().playlists.get_id(self.__current_search) App().playlists.add_tracks(playlist_id, tracks) GLib.idle_add(self.__show_playlist, playlist_id) def __show_playlist(self, playlist_id): """ Hide if in a popover and show playlist """ if App().settings.get_value("show-sidebar"): App().window.container.show_lists([Type.PLAYLISTS], [playlist_id]) else: App().window.container.show_view(Type.PLAYLISTS, [playlist_id]) popover = self.get_ancestor(Gtk.Popover) if popover is not None: popover.hide() else: self.destroy() def __on_search_get(self, result, search): """ Add rows for internal results @param result as [(int, Album, bool)] """ self.__on_search_finished(None) if result: albums = [] reveal_albums = [] for (album, in_tracks) in result: albums.append(album) if in_tracks: reveal_albums.append(album) self.__view.set_reveal(reveal_albums) self.__view.populate(albums) self.__stack.set_visible_child_name("view") def __on_map(self, widget): """ Init signals and grab focus @param widget as Gtk.Widget """ App().enable_special_shortcuts(False) if GLib.find_program_in_path("youtube-dl") is None or\ not get_network_available("SPOTIFY") or\ not get_network_available("YOUTUBE"): self.__bottom_buttons.hide() else: self.__bottom_buttons.show() if self.__new_album_signal_id is None: self.__new_album_signal_id = App().spotify.connect( "new-album", self.__on_new_spotify_album) if self.__search_finished_signal_id is None: self.__search_finished_signal_id = App().spotify.connect( "search-finished", self.__on_search_finished) GLib.idle_add(self.__entry.grab_focus) def __on_unmap(self, widget): """ Clean up @param widget as Gtk.Widget """ App().enable_special_shortcuts(True) if self.__new_album_signal_id is not None: App().spotify.disconnect(self.__new_album_signal_id) self.__new_album_signal_id = None if self.__search_finished_signal_id is not None: App().spotify.disconnect(self.__search_finished_signal_id) self.__search_finished_signal_id = None self.cancel() self.__view.stop() self.__button_stack.set_visible_child(self.__new_button) self.__spinner.stop() def __on_new_spotify_album(self, spotify, album, cover_uri): """ Add album @param spotify as SpotifyHelper @param album as Album @param cover_uri as str """ self.__view.insert_album(album, len(album.tracks) == 1, -1, cover_uri) self.__stack.set_visible_child_name("view") def __on_search_finished(self, api): """ Stop spinner @param api ignored """ self.__spinner.stop() self.__button_stack.set_visible_child(self.__new_button) if not self.__view.children: self.__stack.set_visible_child_name("placeholder") self.__set_no_result_placeholder() def __on_search_changed_timeout(self): """ Populate widget """ if self.__view.children: self.__view.stop() self.__view.clear() return True self.__timeout_id = None self.__populate() if self.__current_search != "": self.__new_button.set_sensitive(True) else: self.__new_button.set_sensitive(False) def __on_search_action_change_state(self, action, value): """ Update action value @param action as Gio.SimpleAction @param value as GLib.Variant """ self.cancel() self.__view.stop() action.set_state(value) state = value.get_string() # A new album signal may be in queue, so clear after GLib.idle_add(self.__view.clear) if state == "local": self.__new_button.show() self.__button_stack.set_visible_child(self.__new_button) else: self.__new_button.hide() if state == "charts": self.__header_stack.set_visible_child_name("locale") self.__play_button.set_sensitive(True) self.__button_stack.set_visible_child(self.__spinner) self.__history = [] self.__spinner.start() self.__stack.set_visible_child_name("view") App().task_helper.run(App().spotify.charts, self.__cancellable, self.__combo_locale.get_active_id()) else: self.__header_stack.set_visible_child_name("entry") self.__populate() GLib.idle_add(self.__entry.grab_focus)
def populate(self, artist_id=None): """ Show information for artists @param artist_id as int """ builder = Gtk.Builder() builder.add_from_resource("/org/gnome/Lollypop/ArtistInformation.ui") builder.connect_signals(self) self.__scrolled = builder.get_object("scrolled") widget = builder.get_object("widget") self.add(widget) self.__stack = builder.get_object("stack") self.__artist_label = builder.get_object("artist_label") self.__artist_label.connect("realize", on_realize) title_label = builder.get_object("title_label") self.__artist_artwork = builder.get_object("artist_artwork") eventbox = builder.get_object("eventbox") eventbox.connect("button-release-event", self.__on_label_button_release_event) self.__information_label = builder.get_object("bio_label") if artist_id is None and App().player.current_track.id is not None: builder.get_object("header").show() builder.get_object("lyrics_button").show() builder.get_object("lyrics_button").connect( "clicked", self.__on_lyrics_button_clicked, App().player.current_track) artist_id = App().player.current_track.album.artist_ids[0] title_label.set_text(App().player.current_track.title) self.__artist_name = App().artists.get_name(artist_id) if self.__minimal: self.__information_label.set_margin_start(MARGIN) self.__information_label.set_margin_end(MARGIN) self.__information_label.set_margin_top(MARGIN) self.__information_label.set_margin_bottom(MARGIN) self.__artist_artwork.hide() else: self.__artist_artwork.set_margin_start(MARGIN_SMALL) builder.get_object("header").show() self.__artist_label.set_text(self.__artist_name) self.__artist_label.show() title_label.show() App().art_helper.set_artist_artwork( self.__artist_name, ArtSize.ARTIST_SMALL * 3, ArtSize.ARTIST_SMALL * 3, self.__artist_artwork.get_scale_factor(), ArtBehaviour.ROUNDED | ArtBehaviour.CROP_SQUARE | ArtBehaviour.CACHE, self.__on_artist_artwork) albums_view = AlbumsListView([], [], ViewType.POPOVER) albums_view.set_size_request(300, -1) albums_view.show() albums_view.set_margin_start(5) widget.attach(albums_view, 2, 1, 1, 2) albums = [] for album_id in App().albums.get_ids([artist_id], []): albums.append(Album(album_id)) if not albums: albums = [App().player.current_track.album] # Allows view to be shown without lag GLib.idle_add(albums_view.populate, albums) App().task_helper.run(self.__information_store.get_information, self.__artist_name, callback=(self.__set_information_content, True))
def populate(self, artist_id=None): """ Show information for artists @param artist_id as int """ builder = Gtk.Builder() builder.add_from_resource( "/org/gnome/Lollypop/ArtistInformation.ui") builder.connect_signals(self) self.__scrolled = builder.get_object("scrolled") widget = builder.get_object("widget") self.add(widget) self.__stack = builder.get_object("stack") self.__listbox = builder.get_object("listbox") self.__artist_label = builder.get_object("artist_label") title_label = builder.get_object("title_label") self.__artist_artwork = builder.get_object("artist_artwork") bio_eventbox = builder.get_object("bio_eventbox") artist_label_eventbox = builder.get_object("artist_label_eventbox") bio_eventbox.connect("realize", set_cursor_type) artist_label_eventbox.connect("realize", set_cursor_type) self.__gesture1 = GesturesHelper( bio_eventbox, primary_press_callback=self._on_info_label_press) self.__gesture2 = GesturesHelper( artist_label_eventbox, primary_press_callback=self._on_artist_label_press) self.__bio_label = builder.get_object("bio_label") if artist_id is None and App().player.current_track.id is not None: builder.get_object("header").show() if App().player.current_track.album.artist_ids[0] ==\ Type.COMPILATIONS: artist_id = App().player.current_track.artist_ids[0] else: artist_id = App().player.current_track.album.artist_ids[0] title_label.set_text(App().player.current_track.title) self.__artist_name = App().artists.get_name(artist_id) if self.__minimal: self.__bio_label.set_margin_start(MARGIN) self.__bio_label.set_margin_end(MARGIN) self.__bio_label.set_margin_top(MARGIN) self.__bio_label.set_margin_bottom(MARGIN) self.__artist_artwork.hide() else: self.__artist_artwork.set_margin_start(MARGIN_SMALL) builder.get_object("header").show() self.__artist_label.set_text(self.__artist_name) self.__artist_label.show() title_label.show() App().art_helper.set_artist_artwork( self.__artist_name, ArtSize.SMALL * 3, ArtSize.SMALL * 3, self.__artist_artwork.get_scale_factor(), ArtBehaviour.ROUNDED | ArtBehaviour.CROP_SQUARE | ArtBehaviour.CACHE, self.__on_artist_artwork) albums_view = AlbumsListView([], [], ViewType.SCROLLED) albums_view.set_size_request(300, -1) albums_view.show() albums_view.set_margin_start(5) albums_view.add_widget(albums_view.box) widget.attach(albums_view, 2, 1, 1, 2) albums = [] storage_type = get_default_storage_type() for album_id in App().albums.get_ids([], [artist_id], storage_type, True): albums.append(Album(album_id)) if not albums: albums = [App().player.current_track.album] albums_view.populate(albums) content = self.__information_store.get_information(self.__artist_name, ARTISTS_PATH) if content is None: self.__bio_label.set_text(_("Loading information")) from lollypop.information_downloader import InformationDownloader downloader = InformationDownloader() downloader.get_information(self.__artist_name, self.__on_artist_information, self.__artist_name) else: App().task_helper.run(self.__to_markup, content, callback=(self.__bio_label.set_markup,))
def __init__(self, app): """ Init window for app @param app as Gio.Application """ Gtk.Window.__init__(self) self.set_title("Lollypop") rotate_album = App().settings.get_value("rotate-fullscreen-album") PlaybackController.__init__(self) ProgressController.__init__(self) if rotate_album: InformationController.__init__(self, False, ArtHelperEffect.ROUNDED) else: InformationController.__init__(self, False, ArtHelperEffect.NONE) self.set_application(app) self.__timeout_id = None self.__signal1_id = self.__signal2_id = self.__signal3_id = None self.set_decorated(False) builder = Gtk.Builder() builder.add_from_resource("/org/gnome/Lollypop/FullScreen.ui") builder.connect_signals(self) # Calculate cover size screen = Gdk.Screen.get_default() monitor = screen.get_monitor_at_window(App().window.get_window()) geometry = screen.get_monitor_geometry(monitor) # We want 500 and 200 in full hd if geometry.width > geometry.height: self.__artsize = int(ArtSize.FULLSCREEN * geometry.height / 1080) else: self.__artsize = int(ArtSize.FULLSCREEN * geometry.width / 1920) self.__font_size = int(14 * geometry.height / 1080) widget = builder.get_object("widget") grid = builder.get_object("grid") self._play_button = builder.get_object("play_btn") self._next_button = builder.get_object("next_btn") self._prev_button = builder.get_object("prev_btn") self._play_image = builder.get_object("play_image") self._pause_image = builder.get_object("pause_image") close_btn = builder.get_object("close_btn") preferences = Gio.Settings.new("org.gnome.desktop.wm.preferences") layout = preferences.get_value("button-layout").get_string() if layout.split(":")[0] == "close": grid.attach(close_btn, 0, 0, 1, 1) close_btn.set_property("halign", Gtk.Align.START) else: grid.attach(close_btn, 2, 0, 1, 1) close_btn.set_property("halign", Gtk.Align.END) self._artwork = builder.get_object("cover") if rotate_album: self._artwork.get_style_context().add_class("image-rotate") else: self._artwork.get_style_context().add_class("cover-frame") self._title_label = builder.get_object("title") self._artist_label = builder.get_object("artist") self._album_label = builder.get_object("album") self.__revealer = builder.get_object("reveal") self._datetime = builder.get_object("datetime") self._progress = builder.get_object("progress_scale") self._timelabel = builder.get_object("playback") self._total_time_label = builder.get_object("duration") self.connect("key-release-event", self.__on_key_release_event) # Add an AlbumListView on the right self.__view = AlbumsListView(RowListType.DND) self.__view.get_style_context().add_class("background-opacity") self.__view.show() self.__revealer.add(self.__view) self.add(widget)
def on_load(albums): self.add_reveal_albums(albums) AlbumsListView.populate(self, albums)