class SearchPopover(Gtk.Popover): """ Popover allowing user to search for tracks/albums """ def __init__(self): """ Init Popover """ Gtk.Popover.__init__(self) self.set_position(Gtk.PositionType.BOTTOM) self.connect("map", self.__on_map) self.connect("unmap", self.__on_unmap) self.__timeout = None self.__current_search = "" self.__nsearch = None self.__lsearch = None self.__history = [] builder = Gtk.Builder() builder.add_from_resource("/org/gnome/Lollypop/SearchPopover.ui") self.__new_btn = builder.get_object("new_btn") self.__entry = builder.get_object("entry") self.__view = Gtk.ListBox() self.__view.set_sort_func(self.__sort_func) self.__view.connect("button-press-event", self.__on_button_press) self.__view.connect("row-activated", self.__on_row_activated) self.__view.set_selection_mode(Gtk.SelectionMode.NONE) self.__view.set_activate_on_single_click(True) self.__view.show() self.__spinner = builder.get_object("spinner") self.__header_stack = builder.get_object("stack") self.__switch = builder.get_object("search-switch") if GLib.find_program_in_path("youtube-dl") is None: self.__switch.set_tooltip_text(_("You need to install youtube-dl")) else: self.__switch.set_state(Lp().settings.get_value("network-search")) self.__scrolled = builder.get_object("scrolled") self.__scrolled.add(self.__view) # Connect here because we don"t want previous switch.set_state() # to emit a signal on init builder.connect_signals(self) self.__stack = Gtk.Stack() self.__stack.set_transition_duration(250) self.__stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) self.__stack.show() self.__stack.add_named(builder.get_object("widget"), "search") self.add(self.__stack) def set_text(self, text): """ Set search text """ self.__entry.set_text(text) ####################### # PROTECTED # ####################### def _on_new_btn_clicked(self, button): """ Create a new playlist based on search @param button as Gtk.Button """ t = Thread(target=self.__new_playlist) t.daemon = True t.start() def _on_search_changed(self, widget): """ Timeout filtering @param widget as Gtk.TextEntry """ self.__reset_search() if self.__timeout: GLib.source_remove(self.__timeout) self.__timeout = None self.__current_search = widget.get_text().strip() if self.__current_search != "": self.__new_btn.set_sensitive(True) self.__timeout = GLib.timeout_add(500, self.__on_search_changed_thread) else: self.__new_btn.set_sensitive(False) def _on_state_set(self, switch, state): """ Save state @param switch as Gtk.switch @param state as bool """ Lp().settings.set_boolean("network-search", state) GLib.idle_add(self._on_search_changed, self.__entry) ####################### # PRIVATE # ####################### def __enable_network_search(self): """ True if shoud enable network search @return bool """ return GLib.find_program_in_path("youtube-dl") is not None and\ get_network_available() def __calculate_score(self, row): """ Calculate score for row @param row as SearchRow """ if row.score is not None: return # Network search score less if row.id is None: score = 0 artists = row.artists else: score = 1 artists = [] for artist_id in row.artist_ids: artists.append(Lp().artists.get_name(artist_id)) for item in self.__current_search.split(): try: year = int(item) if year == int(row.year): score += 2 except: pass for artist in artists: if noaccents(artist.lower()).find( noaccents(item).lower()) != -1: score += 2 if not row.is_track: score += 1 if noaccents(row.name).lower().find(noaccents(item).lower()) != -1: score += 1 if row.is_track: score += 1 row.set_score(score) def __sort_func(self, row1, row2): """ Sort rows @param row as SearchRow @param row as SearchRow """ self.__calculate_score(row1) self.__calculate_score(row2) return row1.score < row2.score def __clear(self, rows): """ Clear search view @param items as [SearchRow] @warning not thread safe """ if rows: row = rows.pop(0) self.__view.remove(row) row.destroy() GLib.idle_add(self.__clear, rows) def __populate(self): """ Populate searching items in db based on text entry current text """ self.__header_stack.set_visible_child(self.__spinner) self.__spinner.start() self.__history = [] # Network Search if self.__need_network_search(): t = Thread(target=self.__nsearch.do, args=(self.__current_search, )) t.daemon = True t.start() # Local Search search_items = [self.__current_search] for item in self.__current_search.split(): if len(item) >= 3: search_items.append(item) GLib.idle_add(self.__clear, self.__view.get_children()) t = Thread(target=self.__lsearch.do, args=(search_items, )) t.daemon = True t.start() def __download_cover(self, uri, row): """ Download row covers @param uri as str @param row as SearchRow """ try: f = Lio.File.new_for_uri(uri) (status, data, tag) = f.load_contents(None) if status: bytes = GLib.Bytes(data) stream = Gio.MemoryInputStream.new_from_bytes(bytes) bytes.unref() pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale( stream, ArtSize.MEDIUM, -1, True, None) stream.close() GLib.idle_add(row.set_cover, pixbuf) except: pass def __populate_user_playlist_by_tracks(self, track_ids, track_id): """ Set user playlist @param track_ids as [int] @param track id as int @thread safe """ Lp().player.load(Track(track_id)) Lp().player.populate_user_playlist_by_tracks(track_ids, [Type.SEARCH]) def __new_playlist(self): """ Create a new playlist based on search """ tracks = [] for child in self.__view.get_children(): if child.is_track: tracks.append(Track(child.id)) else: for track_id in Lp().albums.get_track_ids( child.id, [], child.artist_ids): tracks.append(Track(track_id)) if tracks: playlist_id = Lp().playlists.get_id(self.__current_search) if playlist_id == Type.NONE: Lp().playlists.add(self.__current_search) playlist_id = Lp().playlists.get_id(self.__current_search) Lp().playlists.add_tracks(playlist_id, tracks) def __reset_search(self): """ Reset search object """ self.__header_stack.set_visible_child(self.__new_btn) self.__spinner.stop() if self.__nsearch is not None: self.__nsearch.disconnect_by_func(self.__on_network_item_found) self.__nsearch.stop() self.__nsearch = None if self.__lsearch is not None: self.__lsearch.disconnect_by_func(self.__on_local_item_found) self.__lsearch.stop() self.__lsearch = None def __need_network_search(self): """ Return True if network search needed @return True """ return Lp().settings.get_value("network-search") and\ GLib.find_program_in_path("youtube-dl") is not None def __on_local_item_found(self, search): """ Add rows for internal results @param search as LocalSearch """ if self.__lsearch != search: return if not search.items: if self.__lsearch.finished and\ (self.__nsearch is None or self.__nsearch.finished): self.__header_stack.set_visible_child(self.__new_btn) self.__spinner.stop() return item = search.items.pop(0) search_row = SearchRow(item) search_row.show() self.__view.add(search_row) def __on_network_item_found(self, search): """ Add rows for internal results @param search as NetworkSearch """ if self.__nsearch != search: return if not search.items: if self.__nsearch.finished and self.__lsearch.finished: self.__header_stack.set_visible_child(self.__new_btn) self.__spinner.stop() return item = search.items.pop(0) if item.exists_in_db()[0]: return if item.is_track: history = "♫" + item.name + item.artists[0] else: history = item.name + item.artists[0] if history.lower() not in self.__history: self.__history.append(history.lower()) search_row = SearchRow(item, False) search_row.show() self.__view.add(search_row) t = Thread(target=self.__download_cover, args=(item.smallcover, search_row)) t.daemon = True t.start() def __on_map(self, widget): """ Disable global shortcuts and resize @param widget as Gtk.Widget """ self.__switch.set_sensitive(self.__enable_network_search()) # FIXME Not needed with GTK >= 3.18 Lp().window.enable_global_shortcuts(False) height = Lp().window.get_size()[1] self.set_size_request(450, height * 0.7) def __on_unmap(self, widget): """ Enable global shortcuts @param widget as Gtk.Widget """ # FIXME Not needed with GTK >= 3.18 Lp().window.enable_global_shortcuts(True) self.__reset_search() self.__header_stack.set_visible_child(self.__new_btn) self.__spinner.stop() def __on_search_changed_thread(self): """ Populate widget """ self.__reset_search() from lollypop.search_local import LocalSearch from lollypop.search_network import NetworkSearch self.__timeout = None self.__lsearch = LocalSearch() self.__lsearch.connect("item-found", self.__on_local_item_found) if self.__need_network_search(): self.__nsearch = NetworkSearch() self.__nsearch.connect("item-found", self.__on_network_item_found) self.__populate() def __on_row_activated(self, widget, row): """ Play searched item when selected @param widget as Gtk.ListBox @param row as SearchRow """ if row.is_loading: return if row.id is None: row.on_activated(DbPersistent.NONE) elif row.is_track: # Add to queue, and play (so remove from queue) # Allow us to not change user current playlist if not Lp().player.is_party: Lp().player.insert_in_queue(row.id, 0, False) Lp().player.load(Track(row.id)) else: album_view = AlbumBackView(row.id, [], []) album_view.connect("back-clicked", self.__on_back_clicked) album_view.show() self.__stack.add(album_view) self.__stack.set_visible_child(album_view) def __on_back_clicked(self, view): """ Show search """ search = self.__stack.get_child_by_name("search") self.__stack.set_visible_child(search) GLib.timeout_add(5000, view.destroy) def __on_button_press(self, widget, event): """ Store pressed button @param widget as Gtk.ListBox @param event as Gdk.EventButton """ rect = widget.get_allocation() rect.x = event.x rect.y = event.y rect.width = rect.height = 1 row = widget.get_row_at_y(event.y) # Internal track/album if event.button != 1 and row.id is not None: if row.is_track: track = Track(row.id) popover = TrackMenuPopover(track, TrackMenu(track)) popover.set_relative_to(widget) popover.set_pointing_to(rect) popover.show()
class SearchPopover(Gtk.Popover): """ Popover allowing user to search for tracks/albums """ def __init__(self): """ Init Popover """ Gtk.Popover.__init__(self) self.set_position(Gtk.PositionType.BOTTOM) self.connect('map', self.__on_map) self.connect('unmap', self.__on_unmap) self.__timeout = None self.__current_search = '' self.__nsearch = None self.__lsearch = None self.__history = [] builder = Gtk.Builder() builder.add_from_resource('/org/gnome/Lollypop/SearchPopover.ui') self.__new_btn = builder.get_object('new_btn') self.__entry = builder.get_object('entry') self.__view = Gtk.ListBox() self.__view.set_sort_func(self.__sort_func) self.__view.connect("button-press-event", self.__on_button_press) self.__view.connect("row-activated", self.__on_row_activated) self.__view.set_selection_mode(Gtk.SelectionMode.SINGLE) self.__view.set_activate_on_single_click(True) self.__view.show() self.__spinner = builder.get_object('spinner') self.__stack = builder.get_object('stack') switch = builder.get_object('search-switch') if which("youtube-dl") is None: switch.set_sensitive(False) switch.set_tooltip_text(_("You need to install youtube-dl")) else: switch.set_state(Lp().settings.get_value('network-search')) builder.get_object('scrolled').add(self.__view) self.add(builder.get_object('widget')) # Connect here because we don't want previous switch.set_state() # to emit a signal on init builder.connect_signals(self) def set_text(self, text): """ Set search text """ self.__entry.set_text(text) ####################### # PROTECTED # ####################### def _on_new_btn_clicked(self, button): """ Create a new playlist based on search @param button as Gtk.Button """ t = Thread(target=self.__new_playlist) t.daemon = True t.start() def _on_search_changed(self, widget): """ Timeout filtering @param widget as Gtk.TextEntry """ self.__reset_search() if self.__timeout: GLib.source_remove(self.__timeout) self.__timeout = None self.__current_search = widget.get_text().strip() if self.__current_search != "": self.__new_btn.set_sensitive(True) self.__timeout = GLib.timeout_add(500, self.__on_search_changed_thread) else: self.__new_btn.set_sensitive(False) for child in self.__view.get_children(): GLib.idle_add(child.destroy) def _on_state_set(self, switch, state): """ Save state @param switch as Gtk.switch @param state as bool """ Lp().settings.set_boolean('network-search', state) Lp().window.reload_view() if state: if Lp().charts is None: from lollypop.charts import Charts Lp().charts = Charts() Lp().charts.update() else: Lp().charts.stop() ####################### # PRIVATE # ####################### def __calculate_score(self, row): """ Calculate score for row @param row as SearchRow """ if row.score is not None: return # Network search score less if row.id is None: score = 0 artists = row.artists else: score = 1 artists = [] for artist_id in row.artist_ids: artists.append(Lp().artists.get_name(artist_id)) for item in self.__current_search.split(): for artist in artists: if noaccents(artist.lower()).find( noaccents(item).lower()) != -1: score += 2 if not row.is_track: score += 1 if noaccents(row.name).lower().find( noaccents(item).lower()) != -1: score += 1 if row.is_track: score += 1 row.set_score(score) def __sort_func(self, row1, row2): """ Sort rows @param row as SearchRow @param row as SearchRow """ self.__calculate_score(row1) self.__calculate_score(row2) return row1.score < row2.score def __clear(self, rows): """ Clear search view @param items as [SearchRow] @warning not thread safe """ if rows: row = rows.pop(0) row.destroy() GLib.idle_add(self.__clear, rows) def __populate(self): """ Populate searching items in db based on text entry current text """ self.__stack.set_visible_child(self.__spinner) self.__spinner.start() self.__history = [] # Network Search if self.__need_network_search(): t = Thread(target=self.__nsearch.do, args=(self.__current_search,)) t.daemon = True t.start() # Local Search search_items = [self.__current_search] for item in self.__current_search.split(): if len(item) >= 3: search_items.append(item) GLib.idle_add(self.__clear, self.__view.get_children()) t = Thread(target=self.__lsearch.do, args=(search_items,)) t.daemon = True t.start() def __download_cover(self, uri, row): """ Download row covers @param uri as str @param row as SearchRow """ try: f = Gio.File.new_for_uri(uri) (status, data, tag) = f.load_contents(None) if status: stream = Gio.MemoryInputStream.new_from_data(data, None) pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale( stream, ArtSize.MEDIUM, -1, True, None) GLib.idle_add(row.set_cover, pixbuf) except: pass def __populate_user_playlist_by_tracks(self, track_ids, track_id): """ Set user playlist @param track_ids as [int] @param track id as int @thread safe """ Lp().player.load(Track(track_id)) Lp().player.populate_user_playlist_by_tracks(track_ids, [Type.SEARCH]) def __play_search(self, object_id=None, is_track=True): """ Play tracks based on search @param started object id as int @param is track as bool """ track_ids = [] track_id = None for child in self.__view.get_children(): if child.is_track: track_ids.append(child.id) else: album_tracks = Lp().albums.get_track_ids(child.id) if not is_track and child.id == object_id and\ album_tracks: track_id = album_tracks[0] for tid in album_tracks: track_ids.append(tid) if track_ids: if object_id is not None and is_track: track_id = object_id elif track_id is None: track_id = track_ids[0] GLib.idle_add(self.__populate_user_playlist_by_tracks, track_ids, track_id) def __new_playlist(self): """ Create a new playlist based on search """ tracks = [] for child in self.__view.get_children(): if child.is_track: tracks.append(Track(child.id)) else: for track_id in Lp().albums.get_track_ids( child.id, [], child.artist_ids): tracks.append(Track(track_id)) if tracks: playlist_id = Lp().playlists.get_id(self.__current_search) if playlist_id == Type.NONE: Lp().playlists.add(self.__current_search) playlist_id = Lp().playlists.get_id(self.__current_search) Lp().playlists.add_tracks(playlist_id, tracks) def __reset_search(self): """ Reset search object """ self.__stack.set_visible_child(self.__new_btn) self.__spinner.stop() if self.__nsearch is not None: self.__nsearch.disconnect_by_func(self.__on_network_item_found) self.__nsearch.stop() self.__nsearch = None if self.__lsearch is not None: self.__lsearch.disconnect_by_func(self.__on_local_item_found) self.__lsearch.stop() self.__lsearch = None def __need_network_search(self): """ Return True if network search needed @return True """ return Lp().settings.get_value('network-search') and\ which("youtube-dl") is not None def __on_local_item_found(self, search): """ Add rows for internal results @param search as LocalSearch """ if self.__lsearch != search: return if not search.items: if self.__lsearch.finished and\ (self.__nsearch is None or self.__nsearch.finished): self.__stack.set_visible_child(self.__new_btn) self.__spinner.stop() return item = search.items.pop(0) search_row = SearchRow(item) search_row.show() self.__view.add(search_row) def __on_network_item_found(self, search): """ Add rows for internal results @param search as NetworkSearch """ if self.__nsearch != search: return if not search.items: if self.__nsearch.finished and self.__lsearch.finished: self.__stack.set_visible_child(self.__new_btn) self.__spinner.stop() return item = search.items.pop(0) if item.exists_in_db(): return if item.is_track: history = "♫" + item.name + item.artists[0] else: history = item.name + item.artists[0] if history not in self.__history: self.__history.append(history) search_row = SearchRow(item, False) search_row.show() self.__view.add(search_row) t = Thread(target=self.__download_cover, args=(item.smallcover, search_row)) t.daemon = True t.start() def __on_map(self, widget): """ Disable global shortcuts and resize @param widget as Gtk.Widget """ # FIXME Not needed with GTK >= 3.18 Lp().window.enable_global_shorcuts(False) height = Lp().window.get_size()[1] self.set_size_request(400, height*0.7) def __on_unmap(self, widget): """ Enable global shortcuts @param widget as Gtk.Widget """ # FIXME Not needed with GTK >= 3.18 Lp().window.enable_global_shorcuts(True) self.__reset_search() self.__stack.set_visible_child(self.__new_btn) self.__spinner.stop() def __on_search_changed_thread(self): """ Populate widget """ self.__reset_search() from lollypop.search_local import LocalSearch from lollypop.search_network import NetworkSearch self.__timeout = None self.__lsearch = LocalSearch() self.__lsearch.connect('item-found', self.__on_local_item_found) if self.__need_network_search(): self.__nsearch = NetworkSearch() self.__nsearch.connect('item-found', self.__on_network_item_found) self.__populate() def __on_row_activated(self, widget, row): """ Play searched item when selected @param widget as Gtk.ListBox @param row as SearchRow """ if Lp().player.is_party or Lp().player.locked: # External track/album if row.id is None: pass elif row.is_track: if Lp().player.locked: if row.id in Lp().player.get_queue(): Lp().player.del_from_queue(row.id) else: Lp().player.append_to_queue(row.id) row.destroy() else: Lp().player.load(Track(row.id)) elif Gtk.get_minor_version() > 16: popover = AlbumPopover(row.id, [], []) popover.set_relative_to(row) popover.show() else: t = Thread(target=self.__play_search, args=(row.id, row.is_track)) t.daemon = True t.start() else: if row.id is None: row.play() else: t = Thread(target=self.__play_search, args=(row.id, row.is_track)) t.daemon = True t.start() def __on_button_press(self, widget, event): """ Store pressed button @param widget as Gtk.ListBox @param event as Gdk.EventButton """ rect = widget.get_allocation() rect.x = event.x rect.y = event.y rect.width = rect.height = 1 row = widget.get_row_at_y(event.y) # Internal track/album if event.button != 1 and row.id is not None: if row.is_track: track = Track(row.id) popover = TrackMenuPopover(track, TrackMenu(track)) popover.set_relative_to(widget) popover.set_pointing_to(rect) popover.show() else: popover = AlbumPopover(row.id, [], row.artist_ids) popover.set_relative_to(widget) popover.set_pointing_to(rect) popover.show()
class SearchPopover(Gtk.Popover): """ Popover allowing user to search for tracks/albums """ def __init__(self): """ Init Popover """ Gtk.Popover.__init__(self) self.set_position(Gtk.PositionType.BOTTOM) self.connect('map', self.__on_map) self.connect('unmap', self.__on_unmap) self.__timeout = None self.__current_search = '' self.__nsearch = None self.__lsearch = None self.__history = [] builder = Gtk.Builder() builder.add_from_resource('/org/gnome/Lollypop/SearchPopover.ui') self.__new_btn = builder.get_object('new_btn') self.__entry = builder.get_object('entry') self.__view = Gtk.ListBox() self.__view.connect("button-press-event", self.__on_button_press) self.__view.connect("row-activated", self.__on_row_activated) self.__view.set_selection_mode(Gtk.SelectionMode.NONE) self.__view.set_activate_on_single_click(True) self.__view.show() self.__spinner = builder.get_object('spinner') self.__header_stack = builder.get_object('stack') self.__switch = builder.get_object('search-switch') if GLib.find_program_in_path("youtube-dl") is None: self.__switch.set_tooltip_text(_("You need to install youtube-dl")) else: self.__switch.set_state(Lp().settings.get_value('network-search')) self.__scrolled = builder.get_object('scrolled') self.__scrolled.add(self.__view) # Connect here because we don't want previous switch.set_state() # to emit a signal on init builder.connect_signals(self) self.__stack = Gtk.Stack() self.__stack.set_transition_duration(250) self.__stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) self.__stack.show() self.__stack.add_named(builder.get_object('widget'), "search") self.add(self.__stack) def set_text(self, text): """ Set search text """ self.__entry.set_text(text) ####################### # PROTECTED # ####################### def _on_new_btn_clicked(self, button): """ Create a new playlist based on search @param button as Gtk.Button """ t = Thread(target=self.__new_playlist) t.daemon = True t.start() def _on_search_changed(self, widget): """ Timeout filtering @param widget as Gtk.TextEntry """ self.__reset_search() if self.__timeout: GLib.source_remove(self.__timeout) self.__timeout = None self.__current_search = widget.get_text().strip() if self.__current_search != "": self.__new_btn.set_sensitive(True) self.__timeout = GLib.timeout_add(500, self.__on_search_changed_thread) else: self.__new_btn.set_sensitive(False) for child in self.__view.get_children(): GLib.idle_add(child.destroy) def _on_state_set(self, switch, state): """ Save state @param switch as Gtk.switch @param state as bool """ Lp().settings.set_boolean('network-search', state) GLib.idle_add(self._on_search_changed, self.__entry) ####################### # PRIVATE # ####################### def __enable_network_search(self): """ True if shoud enable network search @return bool """ return GLib.find_program_in_path("youtube-dl") is not None and\ get_network_available() def __calculate_score(self, row): """ Calculate score for row @param row as SearchRow """ if row.score is not None: return # Network search score less if row.id is None: score = 0 artists = row.artists else: score = 1 artists = [] for artist_id in row.artist_ids: artists.append(Lp().artists.get_name(artist_id)) for item in self.__current_search.split(): try: year = int(item) if year == int(row.year): score += 2 except: pass for artist in artists: if noaccents(artist.lower()).find( noaccents(item).lower()) != -1: score += 2 if not row.is_track: score += 1 if noaccents(row.name).lower().find( noaccents(item).lower()) != -1: score += 1 if row.is_track: score += 1 row.set_score(score) def __sort_func(self, row1, row2): """ Sort rows @param row as SearchRow @param row as SearchRow """ self.__calculate_score(row1) self.__calculate_score(row2) return row1.score < row2.score def __clear(self, rows): """ Clear search view @param items as [SearchRow] @warning not thread safe """ if rows: row = rows.pop(0) row.destroy() GLib.idle_add(self.__clear, rows) def __populate(self): """ Populate searching items in db based on text entry current text """ self.__view.set_sort_func(None) self.__header_stack.set_visible_child(self.__spinner) self.__spinner.start() self.__history = [] # Network Search if self.__need_network_search(): t = Thread(target=self.__nsearch.do, args=(self.__current_search,)) t.daemon = True t.start() # Local Search search_items = [self.__current_search] for item in self.__current_search.split(): if len(item) >= 3: search_items.append(item) GLib.idle_add(self.__clear, self.__view.get_children()) t = Thread(target=self.__lsearch.do, args=(search_items,)) t.daemon = True t.start() def __download_cover(self, uri, row): """ Download row covers @param uri as str @param row as SearchRow """ try: f = Lio.File.new_for_uri(uri) (status, data, tag) = f.load_contents(None) if status: stream = Gio.MemoryInputStream.new_from_data(data, None) pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale( stream, ArtSize.MEDIUM, -1, True, None) stream.close() GLib.idle_add(row.set_cover, pixbuf) except: pass def __populate_user_playlist_by_tracks(self, track_ids, track_id): """ Set user playlist @param track_ids as [int] @param track id as int @thread safe """ Lp().player.load(Track(track_id)) Lp().player.populate_user_playlist_by_tracks(track_ids, [Type.SEARCH]) def __new_playlist(self): """ Create a new playlist based on search """ tracks = [] for child in self.__view.get_children(): if child.is_track: tracks.append(Track(child.id)) else: for track_id in Lp().albums.get_track_ids( child.id, [], child.artist_ids): tracks.append(Track(track_id)) if tracks: playlist_id = Lp().playlists.get_id(self.__current_search) if playlist_id == Type.NONE: Lp().playlists.add(self.__current_search) playlist_id = Lp().playlists.get_id(self.__current_search) Lp().playlists.add_tracks(playlist_id, tracks) def __reset_search(self): """ Reset search object """ self.__header_stack.set_visible_child(self.__new_btn) self.__spinner.stop() if self.__nsearch is not None: self.__nsearch.disconnect_by_func(self.__on_network_item_found) self.__nsearch.stop() self.__nsearch = None if self.__lsearch is not None: self.__lsearch.disconnect_by_func(self.__on_local_item_found) self.__lsearch.stop() self.__lsearch = None def __need_network_search(self): """ Return True if network search needed @return True """ return Lp().settings.get_value('network-search') and\ GLib.find_program_in_path("youtube-dl") is not None def __on_local_item_found(self, search): """ Add rows for internal results @param search as LocalSearch """ if self.__lsearch != search: return if not search.items: if self.__lsearch.finished and\ (self.__nsearch is None or self.__nsearch.finished): self.__header_stack.set_visible_child(self.__new_btn) self.__spinner.stop() # Prevent jumping UI if self.__scrolled.get_vadjustment().get_value() == 0: self.__view.set_sort_func(self.__sort_func) return item = search.items.pop(0) search_row = SearchRow(item) search_row.show() self.__view.add(search_row) def __on_network_item_found(self, search): """ Add rows for internal results @param search as NetworkSearch """ if self.__nsearch != search: return if not search.items: if self.__nsearch.finished and self.__lsearch.finished: self.__header_stack.set_visible_child(self.__new_btn) self.__spinner.stop() # Prevent jumping UI if self.__scrolled.get_vadjustment().get_value() == 0: self.__view.set_sort_func(self.__sort_func) return item = search.items.pop(0) if item.exists_in_db()[0]: return if item.is_track: history = "♫" + item.name + item.artists[0] else: history = item.name + item.artists[0] if history.lower() not in self.__history: self.__history.append(history.lower()) search_row = SearchRow(item, False) search_row.show() self.__view.add(search_row) t = Thread(target=self.__download_cover, args=(item.smallcover, search_row)) t.daemon = True t.start() def __on_map(self, widget): """ Disable global shortcuts and resize @param widget as Gtk.Widget """ self.__switch.set_sensitive(self.__enable_network_search()) # FIXME Not needed with GTK >= 3.18 Lp().window.enable_global_shortcuts(False) height = Lp().window.get_size()[1] self.set_size_request(450, height*0.7) def __on_unmap(self, widget): """ Enable global shortcuts @param widget as Gtk.Widget """ # FIXME Not needed with GTK >= 3.18 Lp().window.enable_global_shortcuts(True) self.__reset_search() self.__header_stack.set_visible_child(self.__new_btn) self.__spinner.stop() def __on_search_changed_thread(self): """ Populate widget """ self.__reset_search() from lollypop.search_local import LocalSearch from lollypop.search_network import NetworkSearch self.__timeout = None self.__lsearch = LocalSearch() self.__lsearch.connect('item-found', self.__on_local_item_found) if self.__need_network_search(): self.__nsearch = NetworkSearch() self.__nsearch.connect('item-found', self.__on_network_item_found) self.__populate() def __on_row_activated(self, widget, row): """ Play searched item when selected @param widget as Gtk.ListBox @param row as SearchRow """ if row.is_loading: return if row.id is None: row.on_activated(DbPersistent.NONE) elif row.is_track: # Add to queue, and play (so remove from queue) # Allow us to not change user current playlist if not Lp().player.is_party: Lp().player.insert_in_queue(row.id, 0, False) Lp().player.load(Track(row.id)) else: album_view = AlbumBackView(row.id, [], []) album_view.connect('back-clicked', self.__on_back_clicked) album_view.show() self.__stack.add(album_view) self.__stack.set_visible_child(album_view) def __on_back_clicked(self, view): """ Show search """ search = self.__stack.get_child_by_name("search") self.__stack.set_visible_child(search) GLib.timeout_add(5000, view.destroy) def __on_button_press(self, widget, event): """ Store pressed button @param widget as Gtk.ListBox @param event as Gdk.EventButton """ rect = widget.get_allocation() rect.x = event.x rect.y = event.y rect.width = rect.height = 1 row = widget.get_row_at_y(event.y) # Internal track/album if event.button != 1 and row.id is not None: if row.is_track: track = Track(row.id) popover = TrackMenuPopover(track, TrackMenu(track)) popover.set_relative_to(widget) popover.set_pointing_to(rect) popover.show()