def __init__(self, window, player): super().__init__('search', None, window, Gd.MainViewType.LIST) scale = self.get_scale_factor() loading_icon_surface = DefaultIcon(scale).get( DefaultIcon.Type.loading, ArtSize.SMALL) self._loading_icon = Gdk.pixbuf_get_from_surface( loading_icon_surface, 0, 0, loading_icon_surface.get_width(), loading_icon_surface.get_height()) self._add_list_renderers() self.player = player self._head_iters = [None, None, None, None] self._filter_model = None self.previous_view = None self.connect('no-music-found', self._no_music_found_callback) self._albums_selected = [] self._albums = {} self._albums_index = 0 self._album_widget = AlbumWidget(player, self) self.add(self._album_widget) self._artists_albums_selected = [] self._artists_albums_index = 0 self._artists = {} self._artist_albums_widget = None self._view.get_generic_view().set_show_expanders(False) self._items_selected = [] self._items_selected_callback = None self._items_found = None
def __init__(self, window, player): BaseView.__init__(self, 'search', None, window, Gd.MainViewType.LIST) self._items = {} self.isStarred = None self.iter_to_clean = None scale = self.get_scale_factor() loading_icon_surface = DefaultIcon(scale).get(DefaultIcon.Type.loading, ArtSize.small) self._loading_icon = Gdk.pixbuf_get_from_surface( loading_icon_surface, 0, 0, loading_icon_surface.get_width(), loading_icon_surface.get_height()) self._add_list_renderers() self.player = player self.head_iters = [None, None, None, None] self.songs_model = self.model self.previous_view = None self.connect('no-music-found', self._no_music_found_callback) self.albums_selected = [] self._albums = {} self._albumWidget = AlbumWidget(player, self) self.add(self._albumWidget) self.artists_albums_selected = [] self._artists = {} self._artistAlbumsWidget = None self._view.get_generic_view().set_show_expanders(False) self.items_selected = [] self.items_selected_callback = None self.found_items_number = None
def __init__(self, window, player): super().__init__('search', None, window) # FIXME: Searchbar handling does not belong here. self._searchbar = window._searchbar self._add_list_renderers() self.player = player self._head_iters = [None, None, None, None] self._filter_model = None self.previous_view = None self._albums_selected = [] self._albums = {} self._albums_index = 0 self._album_widget = AlbumWidget(player, self) self.add(self._album_widget) self._artists_albums_selected = [] self._artists_albums_index = 0 self._artists = {} self._artist_albums_widget = None self._items_selected = [] self._items_selected_callback = None self._items_found = None
def __init__(self, window, player): super().__init__('search', None, window) self._add_list_renderers() self.player = player self._head_iters = [None, None, None, None] self._filter_model = None self.previous_view = None self._albums_selected = [] self._albums = {} self._albums_index = 0 self._album_widget = AlbumWidget(player) self._album_widget.bind_property("selection-mode", self, "selection-mode", GObject.BindingFlags.BIDIRECTIONAL) self._album_widget.bind_property("selected-items-count", self, "selected-items-count") self.add(self._album_widget) self._artists_albums_selected = [] self._artists_albums_index = 0 self._artists = {} self._artist_albums_widget = None self._items_selected = [] self._items_selected_callback = None self._items_found = None self._search_mode_active = False self.connect("notify::search-state", self._on_search_state_changed)
def __init__(self, window, player): super().__init__('search', None, window, Gd.MainViewType.LIST) self._add_list_renderers() self.player = player self._head_iters = [None, None, None, None] self._filter_model = None self.previous_view = None self.connect('no-music-found', self._no_music_found_callback) self._albums_selected = [] self._albums = {} self._albums_index = 0 self._album_widget = AlbumWidget(player, self) self.add(self._album_widget) self._artists_albums_selected = [] self._artists_albums_index = 0 self._artists = {} self._artist_albums_widget = None self._view.get_generic_view().set_show_expanders(False) self._items_selected = [] self._items_selected_callback = None self._items_found = None
def __init__(self, window, player): super().__init__('search', None, window) self._add_list_renderers() self.player = player self._head_iters = [None, None, None, None] self._filter_model = None self.previous_view = None self.connect('no-music-found', self._no_music_found_callback) self._albums_selected = [] self._albums = {} self._albums_index = 0 self._album_widget = AlbumWidget(player, self, self._header_bar, self._selection_toolbar) self.add(self._album_widget) self._artists_albums_selected = [] self._artists_albums_index = 0 self._artists = {} self._artist_albums_widget = None self._items_selected = [] self._items_selected_callback = None self._items_found = None
def __init__(self, application, player=None): """Initialize AlbumsView :param application: The Application object """ super().__init__(transition_type=Gtk.StackTransitionType.CROSSFADE) # FIXME: Make these properties. self.name = "albums" self.title = _("Albums") self._window = application.props.window self._headerbar = self._window._headerbar model = self._window._app.props.coremodel.props.albums_sort self._flowbox.bind_model(model, self._create_widget) self._flowbox.connect("child-activated", self._on_child_activated) self.connect("notify::selection-mode", self._on_selection_mode_changed) self.bind_property('selection-mode', self._window, 'selection-mode', GObject.BindingFlags.BIDIRECTIONAL) self._album_widget = AlbumWidget(application.props.player, self) self._album_widget.bind_property("selection-mode", self, "selection-mode", GObject.BindingFlags.BIDIRECTIONAL) self.add(self._album_widget) self.connect("notify::search-mode-active", self._on_search_mode_changed) self.show_all()
def __init__(self, window, player): BaseView.__init__(self, 'albums', _("Albums"), window, None) self._albumWidget = AlbumWidget(player, self) self.player = player self.add(self._albumWidget) self.albums_selected = [] self.all_items = [] self.items_selected = [] self.items_selected_callback = None self._add_list_renderers()
def __init__(self, window, player): super().__init__('albums', _("Albums"), window) self.player = player self._album_widget = AlbumWidget(player, self) self.add(self._album_widget) self.albums_selected = [] self.all_items = [] self.items_selected = [] self.items_selected_callback = None
def __init__(self, window, player): super().__init__('albums', _("Albums"), window, None) self._queue = LifoQueue() self._album_widget = AlbumWidget(player, self) self.player = player self.add(self._album_widget) self.albums_selected = [] self.all_items = [] self.items_selected = [] self.items_selected_callback = None self._add_list_renderers() self._init = True
def __init__(self, window, player): self._window = window super().__init__('albums', _("Albums"), window) self.player = player self._album_widget = AlbumWidget(player, self) self._album_widget.bind_property( "selection-mode", self, "selection-mode", GObject.BindingFlags.BIDIRECTIONAL) self.add(self._album_widget) self.albums_selected = [] self.all_items = [] self.items_selected = [] self.items_selected_callback = None self.connect( "notify::search-mode-active", self._on_search_mode_changed)
def __init__(self, application): """Initialize AlbumsView :param application: The Application object """ super().__init__(transition_type=Gtk.StackTransitionType.CROSSFADE) self.props.name = "albums" self._application = application self._window = application.props.window self._headerbar = self._window._headerbar self._adjustment_timeout_id = 0 self._viewport = self._scrolled_window.get_child() self._widget_counter = 1 self._ctrl_hold = False model = self._application.props.coremodel.props.albums_sort self._flowbox.bind_model(model, self._create_widget) self._flowbox.set_hadjustment(self._scrolled_window.get_hadjustment()) self._flowbox.set_vadjustment(self._scrolled_window.get_vadjustment()) self._flowbox.connect("child-activated", self._on_child_activated) self.bind_property("selection-mode", self._window, "selection-mode", GObject.BindingFlags.DEFAULT) self._window.connect("notify::selection-mode", self._on_selection_mode_changed) self._album_widget = AlbumWidget(self._application) self._album_widget.bind_property("selection-mode", self, "selection-mode", GObject.BindingFlags.BIDIRECTIONAL) self.add(self._album_widget) self.connect("notify::search-mode-active", self._on_search_mode_changed) self._scrolled_window.props.vadjustment.connect( "value-changed", self._on_vadjustment_changed) self._scrolled_window.props.vadjustment.connect( "changed", self._on_vadjustment_changed)
def __init__(self, window, player): BaseView.__init__(self, "albums", _("Albums"), window, None) self._albumWidget = AlbumWidget(player, self) self.player = player self.add(self._albumWidget) self.albums_selected = [] self.all_items = [] self.items_selected = [] self.items_selected_callback = None self._add_list_renderers()
def __init__(self, window, player): self._coremodel = window._app.props.coremodel self._model = self._coremodel.props.songs_search self._album_model = self._coremodel.props.albums_search self._artist_model = self._coremodel.props.artists_search super().__init__('search', None, window) self.player = player self.previous_view = None self._album_widget = AlbumWidget(player, self) self._album_widget.bind_property("selection-mode", self, "selection-mode", GObject.BindingFlags.BIDIRECTIONAL) self.add(self._album_widget) self._artist_albums_widget = None self._search_mode_active = False
def __init__(self, window, player): super().__init__('albums', _("Albums"), window) self.player = player self._album_widget = AlbumWidget(player) self._album_widget.bind_property( "selection-mode", self, "selection-mode", GObject.BindingFlags.BIDIRECTIONAL) self._album_widget.bind_property( "selected-items-count", self, "selected-items-count") self.add(self._album_widget) self.albums_selected = [] self.all_items = [] self.items_selected = [] self.items_selected_callback = None self.connect( "notify::search-mode-active", self._on_search_mode_changed)
def __init__(self, window, player): BaseView.__init__(self, 'search', None, window, Gd.MainViewType.LIST) self._items = {} self.isStarred = None self.iter_to_clean = None scale = self.get_scale_factor() loading_icon_surface = DefaultIcon(scale).get(DefaultIcon.Type.loading, ArtSize.small) self._loading_icon = Gdk.pixbuf_get_from_surface( loading_icon_surface, 0, 0, loading_icon_surface.get_width(), loading_icon_surface.get_height()) self._add_list_renderers() self.player = player self.head_iters = [None, None, None, None] self.songs_model = self.model self.previous_view = None self.connect('no-music-found', self._no_music_found_callback) self.albums_selected = [] self._albums = {} self._albumWidget = AlbumWidget(player, self) self.add(self._albumWidget) self.artists_albums_selected = [] self._artists = {} self._artistAlbumsWidget = None self.view.get_generic_view().set_show_expanders(False) self.items_selected = [] self.items_selected_callback = None self.found_items_number = None
class AlbumsView(BaseView): def __repr__(self): return '<AlbumsView>' @log def __init__(self, window, player): super().__init__('albums', _("Albums"), window) self.player = player self._album_widget = AlbumWidget(player, self) self.add(self._album_widget) self.albums_selected = [] self.all_items = [] self.items_selected = [] self.items_selected_callback = None @log def _on_changes_pending(self, data=None): if (self._init and not self.props.selection_mode): self._offset = 0 self.populate() grilo.changes_pending['Albums'] = False @log def _on_selection_mode_changed(self, widget, data=None): super()._on_selection_mode_changed(widget, data) if (not self.props.selection_mode and grilo.changes_pending['Albums']): self._on_changes_pending() @log def _setup_view(self): self._view = Gtk.FlowBox(homogeneous=True, hexpand=True, halign=Gtk.Align.FILL, valign=Gtk.Align.START, selection_mode=Gtk.SelectionMode.NONE, margin=18, row_spacing=12, column_spacing=6, min_children_per_line=1, max_children_per_line=20) self._view.get_style_context().add_class('content-view') self._view.connect('child-activated', self._on_child_activated) scrolledwin = Gtk.ScrolledWindow() scrolledwin.add(self._view) scrolledwin.show() self._box.add(scrolledwin) @log def _back_button_clicked(self, widget, data=None): self._headerbar.state = HeaderBar.State.MAIN self.set_visible_child(self._grid) @log def _on_child_activated(self, widget, child, user_data=None): if self.props.selection_mode: return item = child.props.media # Update and display the album widget if not in selection mode self._album_widget.update(item) self._headerbar.props.state = HeaderBar.State.CHILD self._headerbar.props.title = utils.get_album_title(item) self._headerbar.props.subtitle = utils.get_artist_name(item) self.set_visible_child(self._album_widget) @log def populate(self): self._window.notifications_popup.push_loading() grilo.populate_albums(self._offset, self._add_item) self._init = True @log def get_selected_songs(self, callback): # FIXME: we call into private objects with full knowledge of # what is there if self._headerbar.props.state == HeaderBar.State.CHILD: callback(self._album_widget._disc_listbox.get_selected_items()) else: self.items_selected = [] self.items_selected_callback = callback self.albums_index = 0 if len(self.albums_selected): self._get_selected_album_songs() @log def _add_item(self, source, param, item, remaining=0, data=None): if item: # Store all items to optimize 'Select All' action self.all_items.append(item) # Add to the flowbox child = self._create_album_item(item) self._view.add(child) self._offset += 1 elif remaining == 0: self._view.show() self._window.notifications_popup.pop_loading() self._init = False def _create_album_item(self, item): child = AlbumCover(item) child.connect('notify::selected', self._on_selection_changed) self.bind_property('selection-mode', child, 'selection-mode', GObject.BindingFlags.BIDIRECTIONAL) return child @log def _on_selection_changed(self, child, data=None): if (child.props.selected and child.props.media not in self.albums_selected): self.albums_selected.append(child.props.media) elif (not child.props.selected and child.props.media in self.albums_selected): self.albums_selected.remove(child.props.media) self.props.selected_items_count = len(self.albums_selected) @log def _get_selected_album_songs(self): grilo.populate_album_songs(self.albums_selected[self.albums_index], self._add_selected_item) self.albums_index += 1 @log def _add_selected_item(self, source, param, item, remaining=0, data=None): if item: self.items_selected.append(item) if remaining == 0: if self.albums_index < self.props.selected_items_count: self._get_selected_album_songs() else: self.items_selected_callback(self.items_selected) def _toggle_all_selection(self, selected): """ Selects or unselects all items without sending the notify::active signal for performance purposes. """ for child in self._view.get_children(): child.props.selected = selected @log def select_all(self): self.albums_selected = list(self.all_items) self._toggle_all_selection(True) @log def unselect_all(self): self.albums_selected = [] self._toggle_all_selection(False)
class AlbumsView(BaseView): def __repr__(self): return "<AlbumsView>" @log def __init__(self, window, player): BaseView.__init__(self, "albums", _("Albums"), window, None) self._albumWidget = AlbumWidget(player, self) self.player = player self.add(self._albumWidget) self.albums_selected = [] self.all_items = [] self.items_selected = [] self.items_selected_callback = None self._add_list_renderers() @log def _on_changes_pending(self, data=None): if self._init and not self.header_bar._selectionMode: self._offset = 0 self._init = True GLib.idle_add(self.populate) grilo.changes_pending["Albums"] = False @log def _on_selection_mode_changed(self, widget, data=None): if not self.header_bar._selectionMode and grilo.changes_pending["Albums"]: self._on_changes_pending() @log def _setup_view(self, view_type): self.view = Gtk.FlowBox( homogeneous=True, hexpand=True, halign=Gtk.Align.FILL, valign=Gtk.Align.START, selection_mode=Gtk.SelectionMode.NONE, margin=18, row_spacing=12, column_spacing=6, min_children_per_line=1, max_children_per_line=25, ) self.view.connect("child-activated", self._on_child_activated) scrolledwin = Gtk.ScrolledWindow() scrolledwin.add(self.view) scrolledwin.show() self._box.add(scrolledwin) @log def _back_button_clicked(self, widget, data=None): self.header_bar.reset_header_title() self.set_visible_child(self._grid) @log def _on_child_activated(self, widget, child, user_data=None): item = child.media_item if self.star_handler.star_renderer_click: self.star_handler.star_renderer_click = False return # Toggle the selection when in selection mode if self.selection_mode: child.check.set_active(not child.check.get_active()) return title = utils.get_media_title(item) self._escaped_title = title self._artist = utils.get_artist_name(item) self._albumWidget.update(self._artist, title, item, self.header_bar, self.selection_toolbar) self.header_bar.set_state(ToolbarState.CHILD_VIEW) self.header_bar.header_bar.set_title(self._escaped_title) self.header_bar.header_bar.sub_title = self._artist self.set_visible_child(self._albumWidget) @log def update_title(self): self.header_bar.header_bar.set_title(self._escaped_title) self.header_bar.header_bar.sub_title = self._artist @log def populate(self): self.window.push_loading_notification() grilo.populate_albums(self._offset, self._add_item) @log def get_selected_tracks(self, callback): # FIXME: we call into private objects with full knowledge of # what is there if self.header_bar._state == ToolbarState.CHILD_VIEW: callback(self._albumWidget._disc_listbox.get_selected_items()) else: self.items_selected = [] self.items_selected_callback = callback self.albums_index = 0 if len(self.albums_selected): self._get_selected_album_songs() @log def _add_item(self, source, param, item, remaining=0, data=None): if item: # Store all items to optimize 'Select All' action self.all_items.append(item) # Add to the flowbox child = self._create_album_item(item) self.view.add(child) elif remaining == 0: self.window.pop_loading_notification() self.view.show() def _create_album_item(self, item): artist = utils.get_artist_name(item) title = utils.get_media_title(item) builder = Gtk.Builder.new_from_resource("/org/gnome/Music/AlbumCover.ui") child = Gtk.FlowBoxChild() child.image = builder.get_object("image") child.check = builder.get_object("check") child.title = builder.get_object("title") child.subtitle = builder.get_object("subtitle") child.events = builder.get_object("events") child.media_item = item child.title.set_label(title) child.subtitle.set_label(artist) child.image.set_from_surface(self._loading_icon_surface) # In the case of off-sized icons (eg. provided in the soundfile) # keep the size request equal to all other icons to get proper # alignment with GtkFlowBox. child.image.set_property("width-request", ArtSize.medium.width) child.image.set_property("height-request", ArtSize.medium.height) child.events.connect("button-release-event", self._on_album_event_triggered, child) child.check_handler_id = child.check.connect("notify::active", self._on_child_toggled, child) child.check.bind_property("visible", self, "selection_mode", GObject.BindingFlags.BIDIRECTIONAL) child.add(builder.get_object("main_box")) child.show() self.cache.lookup(item, ArtSize.medium, self._on_lookup_ready, child) return child @log def _on_album_event_triggered(self, evbox, event, child): if event.button is 3: self._on_selection_mode_request() if self.selection_mode: child.check.set_active(True) def _on_lookup_ready(self, icon, child): child.image.set_from_surface(icon) @log def _on_child_toggled(self, check, pspec, child): if check.get_active() and not child.media_item in self.albums_selected: self.albums_selected.append(child.media_item) elif not check.get_active() and child.media_item in self.albums_selected: self.albums_selected.remove(child.media_item) self.update_header_from_selection(len(self.albums_selected)) @log def _get_selected_album_songs(self): grilo.populate_album_songs(self.albums_selected[self.albums_index], self._add_selected_item) self.albums_index += 1 @log def _add_selected_item(self, source, param, item, remaining=0, data=None): if item: self.items_selected.append(item) if remaining == 0: if self.albums_index < len(self.albums_selected): self._get_selected_album_songs() else: self.items_selected_callback(self.items_selected) def _toggle_all_selection(self, selected): """ Selects or unselects all items without sending the notify::active signal for performance purposes. """ for child in self.view.get_children(): GObject.signal_handler_block(child.check, child.check_handler_id) # Set the checkbutton state without emiting the signal child.check.set_active(selected) GObject.signal_handler_unblock(child.check, child.check_handler_id) self.update_header_from_selection(len(self.albums_selected)) @log def select_all(self): self.albums_selected = list(self.all_items) self._toggle_all_selection(True) @log def unselect_all(self): self.albums_selected = [] self._toggle_all_selection(False)
class AlbumsView(BaseView): def __repr__(self): return '<AlbumsView>' @log def __init__(self, window, player): BaseView.__init__(self, 'albums', _("Albums"), window, None) self._albumWidget = AlbumWidget(player, self) self.player = player self.add(self._albumWidget) self.albums_selected = [] self.all_items = [] self.items_selected = [] self.items_selected_callback = None self._add_list_renderers() @log def _on_changes_pending(self, data=None): if (self._init and not self._header_bar._selectionMode): self._offset = 0 self._init = True GLib.idle_add(self.populate) grilo.changes_pending['Albums'] = False @log def _on_selection_mode_changed(self, widget, data=None): if (not self._header_bar._selectionMode and grilo.changes_pending['Albums']): self._on_changes_pending() @log def _setup_view(self, view_type): self._view = Gtk.FlowBox(homogeneous=True, hexpand=True, halign=Gtk.Align.FILL, valign=Gtk.Align.START, selection_mode=Gtk.SelectionMode.NONE, margin=18, row_spacing=12, column_spacing=6, min_children_per_line=1, max_children_per_line=25) self._view.connect('child-activated', self._on_child_activated) scrolledwin = Gtk.ScrolledWindow() scrolledwin.add(self._view) scrolledwin.show() self._box.add(scrolledwin) @log def _back_button_clicked(self, widget, data=None): self._header_bar.reset_header_title() self.set_visible_child(self._grid) @log def _on_child_activated(self, widget, child, user_data=None): item = child.media_item if self._star_handler.star_renderer_click: self._star_handler.star_renderer_click = False return # Toggle the selection when in selection mode if self.selection_mode: child.check.set_active(not child.check.get_active()) return title = utils.get_media_title(item) self._escaped_title = title self._artist = utils.get_artist_name(item) self._albumWidget.update(self._artist, title, item, self._header_bar, self._selection_toolbar) self._header_bar.set_state(ToolbarState.CHILD_VIEW) self._header_bar.header_bar.set_title(self._escaped_title) self._header_bar.header_bar.sub_title = self._artist self.set_visible_child(self._albumWidget) @log def update_title(self): self._header_bar.header_bar.set_title(self._escaped_title) self._header_bar.header_bar.sub_title = self._artist @log def populate(self): self._window.push_loading_notification() grilo.populate_albums(self._offset, self._add_item) @log def get_selected_songs(self, callback): # FIXME: we call into private objects with full knowledge of # what is there if self._header_bar._state == ToolbarState.CHILD_VIEW: callback(self._albumWidget._disc_listbox.get_selected_items()) else: self.items_selected = [] self.items_selected_callback = callback self.albums_index = 0 if len(self.albums_selected): self._get_selected_album_songs() @log def _add_item(self, source, param, item, remaining=0, data=None): if item: # Store all items to optimize 'Select All' action self.all_items.append(item) # Add to the flowbox child = self._create_album_item(item) self._view.add(child) elif remaining == 0: self._window.pop_loading_notification() self._view.show() def _create_album_item(self, item): artist = utils.get_artist_name(item) title = utils.get_media_title(item) builder = Gtk.Builder.new_from_resource( '/org/gnome/Music/AlbumCover.ui') child = Gtk.FlowBoxChild() child.image = builder.get_object('image') child.check = builder.get_object('check') child.title = builder.get_object('title') child.subtitle = builder.get_object('subtitle') child.events = builder.get_object('events') child.media_item = item child.title.set_label(title) child.subtitle.set_label(artist) child.image.set_from_surface(self._loading_icon_surface) # In the case of off-sized icons (eg. provided in the soundfile) # keep the size request equal to all other icons to get proper # alignment with GtkFlowBox. child.image.set_property("width-request", ArtSize.medium.width) child.image.set_property("height-request", ArtSize.medium.height) child.events.connect('button-release-event', self._on_album_event_triggered, child) child.check_handler_id = child.check.connect('notify::active', self._on_child_toggled, child) child.check.bind_property('visible', self, 'selection_mode', GObject.BindingFlags.BIDIRECTIONAL) child.add(builder.get_object('main_box')) child.show() self._cache.lookup(item, ArtSize.medium, self._on_lookup_ready, child) return child @log def _on_album_event_triggered(self, evbox, event, child): if event.button is 3: self._on_selection_mode_request() if self.selection_mode: child.check.set_active(True) def _on_lookup_ready(self, icon, child): child.image.set_from_surface(icon) @log def _on_child_toggled(self, check, pspec, child): if check.get_active() and not child.media_item in self.albums_selected: self.albums_selected.append(child.media_item) elif not check.get_active( ) and child.media_item in self.albums_selected: self.albums_selected.remove(child.media_item) self.update_header_from_selection(len(self.albums_selected)) @log def _get_selected_album_songs(self): grilo.populate_album_songs(self.albums_selected[self.albums_index], self._add_selected_item) self.albums_index += 1 @log def _add_selected_item(self, source, param, item, remaining=0, data=None): if item: self.items_selected.append(item) if remaining == 0: if self.albums_index < len(self.albums_selected): self._get_selected_album_songs() else: self.items_selected_callback(self.items_selected) def _toggle_all_selection(self, selected): """ Selects or unselects all items without sending the notify::active signal for performance purposes. """ for child in self._view.get_children(): GObject.signal_handler_block(child.check, child.check_handler_id) # Set the checkbutton state without emiting the signal child.check.set_active(selected) GObject.signal_handler_unblock(child.check, child.check_handler_id) self.update_header_from_selection(len(self.albums_selected)) @log def select_all(self): self.albums_selected = list(self.all_items) self._toggle_all_selection(True) @log def unselect_all(self): self.albums_selected = [] self._toggle_all_selection(False)
class SearchView(BaseView): __gsignals__ = { 'no-music-found': (GObject.SignalFlags.RUN_FIRST, None, ()) } def __repr__(self): return '<SearchView>' @log def __init__(self, window, player): BaseView.__init__(self, 'search', None, window, Gd.MainViewType.LIST) self._items = {} self.isStarred = None self.iter_to_clean = None scale = self.get_scale_factor() loading_icon_surface = DefaultIcon(scale).get(DefaultIcon.Type.loading, ArtSize.small) self._loading_icon = Gdk.pixbuf_get_from_surface( loading_icon_surface, 0, 0, loading_icon_surface.get_width(), loading_icon_surface.get_height()) self._add_list_renderers() self.player = player self.head_iters = [None, None, None, None] self.songs_model = self.model self.previous_view = None self.connect('no-music-found', self._no_music_found_callback) self.albums_selected = [] self._albums = {} self._albumWidget = AlbumWidget(player, self) self.add(self._albumWidget) self.artists_albums_selected = [] self._artists = {} self._artistAlbumsWidget = None self.view.get_generic_view().set_show_expanders(False) self.items_selected = [] self.items_selected_callback = None self.found_items_number = None @log def _no_music_found_callback(self, view): self.window._stack.set_visible_child_name('emptysearch') emptysearch = self.window._stack.get_child_by_name('emptysearch') emptysearch._artistAlbumsWidget = self._artistAlbumsWidget @log def _back_button_clicked(self, widget, data=None): self.header_bar.searchbar.show_bar(True, False) if self.get_visible_child() == self._artistAlbumsWidget: self._artistAlbumsWidget.destroy() self._artistAlbumsWidget = None elif self.get_visible_child() == self._grid: self.window.views[0].set_visible_child(self.window.views[0]._grid) self.set_visible_child(self._grid) self.window.toolbar.set_state(ToolbarState.MAIN) @log def _on_item_activated(self, widget, id, path): if self.star_handler.star_renderer_click: self.star_handler.star_renderer_click = False return try: child_path = self.filter_model.convert_path_to_child_path(path) except TypeError: return _iter = self.model.get_iter(child_path) if self.model[_iter][11] == 'album': title = self.model.get_value(_iter, 2) artist = self.model.get_value(_iter, 3) item = self.model.get_value(_iter, 5) self._albumWidget.update(artist, title, item, self.header_bar, self.selection_toolbar) self.header_bar.set_state(ToolbarState.SEARCH_VIEW) title = utils.get_media_title(item) self.header_bar.header_bar.set_title(title) self.header_bar.header_bar.sub_title = artist self.set_visible_child(self._albumWidget) self.header_bar.searchbar.show_bar(False) elif self.model[_iter][11] == 'artist': artist = self.model.get_value(_iter, 2) albums = self._artists[artist.casefold()]['albums'] self._artistAlbumsWidget = ArtistAlbumsWidget( artist, albums, self.player, self.header_bar, self.selection_toolbar, self.window, True ) self.add(self._artistAlbumsWidget) self._artistAlbumsWidget.show() self.header_bar.set_state(ToolbarState.SEARCH_VIEW) self.header_bar.header_bar.set_title(artist) self.set_visible_child(self._artistAlbumsWidget) self.header_bar.searchbar.show_bar(False) elif self.model[_iter][11] == 'song': if self.model.get_value(_iter, 12) != DiscoveryStatus.FAILED: child_iter = self.songs_model.convert_child_iter_to_iter(_iter)[1] self.player.set_playlist('Search Results', None, self.songs_model, child_iter, 5, 12) self.player.set_playing(True) else: # Headers if self.view.get_generic_view().row_expanded(path): self.view.get_generic_view().collapse_row(path) else: self.view.get_generic_view().expand_row(path, False) @log def _on_selection_mode_changed(self, widget, data=None): if self._artistAlbumsWidget is not None and self.get_visible_child() == self._artistAlbumsWidget: self._artistAlbumsWidget.set_selection_mode(self.header_bar._selectionMode) @log def _add_search_item(self, source, param, item, remaining=0, data=None): if not item: if grilo._search_callback_counter == 0 and grilo.search_source: self.emit('no-music-found') return if data != self.model: return artist = utils.get_artist_name(item) album = utils.get_album_title(item) composer = item.get_composer() key = '%s-%s' % (artist, album) if key not in self._albums: self._albums[key] = Grl.Media() self._albums[key].set_title(album) self._albums[key].add_artist(artist) self._albums[key].set_composer(composer) self._albums[key].set_source(source.get_id()) self._albums[key].tracks = [] self._add_item(source, None, self._albums[key], 0, [self.model, 'album']) self._add_item(source, None, self._albums[key], 0, [self.model, 'artist']) self._albums[key].tracks.append(item) self._add_item(source, None, item, 0, [self.model, 'song']) @log def _add_item(self, source, param, item, remaining=0, data=None): if data is None: return model, category = data self.found_items_number = ( self.model.iter_n_children(self.head_iters[0]) + self.model.iter_n_children(self.head_iters[1]) + self.model.iter_n_children(self.head_iters[2]) + self.model.iter_n_children(self.head_iters[3])) if category == 'song' and self.found_items_number == 0 and remaining == 0: if grilo.search_source: self.emit('no-music-found') # We need to remember the view before the search view if self.window.curr_view != self.window.views[5] and \ self.window.prev_view != self.window.views[5]: self.previous_view = self.window.prev_view if remaining == 0: self.window.pop_loading_notification() self.view.show() if not item or model != self.model: return self._offset += 1 title = utils.get_media_title(item) item.set_title(title) artist = utils.get_artist_name(item) # FIXME: Can't be None in treemodel composer = item.get_composer() or "" group = 3 try: group = {'album': 0, 'artist': 1, 'song': 2}[category] except: pass # FIXME: HiDPI icon lookups return a surface that can't be # scaled by GdkPixbuf, so it results in a * scale factor sized # icon for the search view. _iter = None if category == 'album': _iter = self.model.insert_with_values( self.head_iters[group], -1, [0, 2, 3, 4, 5, 9, 11, 13], [str(item.get_id()), title, artist, self._loading_icon, item, 2, category, composer]) self.cache.lookup(item, ArtSize.small, self._on_lookup_ready, _iter) elif category == 'song': _iter = self.model.insert_with_values( self.head_iters[group], -1, [0, 2, 3, 4, 5, 9, 11, 13], [str(item.get_id()), title, artist, self._loading_icon, item, 2 if source.get_id() != 'grl-tracker-source' \ else item.get_favourite(), category, composer]) self.cache.lookup(item, ArtSize.small, self._on_lookup_ready, _iter) else: if not artist.casefold() in self._artists: _iter = self.model.insert_with_values( self.head_iters[group], -1, [0, 2, 4, 5, 9, 11, 13], [str(item.get_id()), artist, self._loading_icon, item, 2, category, composer]) self.cache.lookup(item, ArtSize.small, self._on_lookup_ready, _iter) self._artists[artist.casefold()] = {'iter': _iter, 'albums': []} self._artists[artist.casefold()]['albums'].append(item) if self.model.iter_n_children(self.head_iters[group]) == 1: path = self.model.get_path(self.head_iters[group]) path = self.filter_model.convert_child_path_to_path(path) self.view.get_generic_view().expand_row(path, False) @log def _add_list_renderers(self): list_widget = self.view.get_generic_view() list_widget.set_halign(Gtk.Align.CENTER) list_widget.set_size_request(530, -1) cols = list_widget.get_columns() title_renderer = Gtk.CellRendererText( xpad=12, xalign=0.0, yalign=0.5, height=32, ellipsize=Pango.EllipsizeMode.END, weight=Pango.Weight.BOLD ) list_widget.add_renderer(title_renderer, self._on_list_widget_title_render, None) cols[0].add_attribute(title_renderer, 'text', 2) self.star_handler.add_star_renderers(list_widget, cols, hidden=False) cells = cols[0].get_cells() cols[0].reorder(cells[0], -1) cols[0].set_cell_data_func(cells[0], self._on_list_widget_selection_render, None) def _on_list_widget_selection_render(self, col, cell, model, _iter, data): cell.set_visible(self.view.get_selection_mode() and model.iter_parent(_iter) is not None) def _on_list_widget_title_render(self, col, cell, model, _iter, data): cells = col.get_cells() cells[0].set_visible(model.iter_parent(_iter) is not None) cells[1].set_visible(model.iter_parent(_iter) is not None) cells[2].set_visible(model.iter_parent(_iter) is None) @log def populate(self): self._init = True self.window.push_loading_notification() self.header_bar.set_state(ToolbarState.MAIN) @log def get_selected_tracks(self, callback): if self.get_visible_child() == self._albumWidget: callback(self._albumWidget.view.get_selected_items()) elif self.get_visible_child() == self._artistAlbumsWidget: items = [] # FIXME: calling into private model for row in self._artistAlbumsWidget._model: if row[6]: items.append(row[5]) callback(items) else: self.items_selected = [] self.items_selected_callback = callback self._get_selected_albums() @log def _get_selected_albums(self): self.albums_index = 0 self.albums_selected = [self.model[child_path][5] for child_path in [self.filter_model.convert_path_to_child_path(path) for path in self.view.get_selection()] if self.model[child_path][11] == 'album'] if len(self.albums_selected): self._get_selected_albums_songs() else: self._get_selected_artists() @log def _get_selected_albums_songs(self): grilo.populate_album_songs( self.albums_selected[self.albums_index], self._add_selected_albums_songs) self.albums_index += 1 @log def _add_selected_albums_songs(self, source, param, item, remaining=0, data=None): if item: self.items_selected.append(item) if remaining == 0: if self.albums_index < len(self.albums_selected): self._get_selected_albums_songs() else: self._get_selected_artists() @log def _get_selected_artists(self): self.artists_albums_index = 0 self.artists_selected = [self._artists[self.model[child_path][2].casefold()] for child_path in [self.filter_model.convert_path_to_child_path(path) for path in self.view.get_selection()] if self.model[child_path][11] == 'artist'] self.artists_albums_selected = [] for artist in self.artists_selected: self.artists_albums_selected.extend(artist['albums']) if len(self.artists_albums_selected): self._get_selected_artists_albums_songs() else: self._get_selected_songs() @log def _get_selected_artists_albums_songs(self): grilo.populate_album_songs( self.artists_albums_selected[self.artists_albums_index], self._add_selected_artists_albums_songs) self.artists_albums_index += 1 @log def _add_selected_artists_albums_songs(self, source, param, item, remaining=0, data=None): if item: self.items_selected.append(item) if remaining == 0: if self.artists_albums_index < len(self.artists_albums_selected): self._get_selected_artists_albums_songs() else: self._get_selected_songs() @log def _get_selected_songs(self): self.items_selected.extend([self.model[child_path][5] for child_path in [self.filter_model.convert_path_to_child_path(path) for path in self.view.get_selection()] if self.model[child_path][11] == 'song']) self.items_selected_callback(self.items_selected) @log def _filter_visible_func(self, model, _iter, data=None): return model.iter_parent(_iter) is not None or model.iter_has_child(_iter) @log def set_search_text(self, search_term, fields_filter): query_matcher = { 'album': { 'search_all': Query.get_albums_with_any_match, 'search_artist': Query.get_albums_with_artist_match, 'search_composer': Query.get_albums_with_composer_match, 'search_album': Query.get_albums_with_album_match, 'search_track': Query.get_albums_with_track_match, }, 'artist': { 'search_all': Query.get_artists_with_any_match, 'search_artist': Query.get_artists_with_artist_match, 'search_composer': Query.get_artists_with_composer_match, 'search_album': Query.get_artists_with_album_match, 'search_track': Query.get_artists_with_track_match, }, 'song': { 'search_all': Query.get_songs_with_any_match, 'search_artist': Query.get_songs_with_artist_match, 'search_composer': Query.get_songs_with_composer_match, 'search_album': Query.get_songs_with_album_match, 'search_track': Query.get_songs_with_track_match, }, } self.model = Gtk.TreeStore( GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING, # item title or header text GObject.TYPE_STRING, # artist for albums and songs GdkPixbuf.Pixbuf, # album art GObject.TYPE_OBJECT, # item GObject.TYPE_BOOLEAN, GObject.TYPE_INT, GObject.TYPE_STRING, GObject.TYPE_INT, GObject.TYPE_BOOLEAN, GObject.TYPE_STRING, # type GObject.TYPE_INT, GObject.TYPE_STRING, # composer ) self.filter_model = self.model.filter_new(None) self.filter_model.set_visible_func(self._filter_visible_func) self.view.set_model(self.filter_model) self._albums = {} self._artists = {} if search_term == "": return albums_iter = self.model.insert_with_values(None, -1, [2, 9], [_("Albums"), 2]) artists_iter = self.model.insert_with_values(None, -1, [2, 9], [_("Artists"), 2]) songs_iter = self.model.insert_with_values(None, -1, [2, 9], [_("Songs"), 2]) playlists_iter = self.model.insert_with_values(None, -1, [2, 9], [_("Playlists"), 2]) self.head_iters = [albums_iter, artists_iter, songs_iter, playlists_iter] self.songs_model = self.model.filter_new(self.model.get_path(songs_iter)) # Use queries for Tracker if not grilo.search_source or \ grilo.search_source.get_id() == 'grl-tracker-source': for category in ('album', 'artist', 'song'): query = query_matcher[category][fields_filter](search_term) grilo.populate_custom_query(query, self._add_item, -1, [self.model, category]) if not grilo.search_source or \ grilo.search_source.get_id() != 'grl-tracker-source': # nope, can't do - reverting to Search grilo.search(search_term, self._add_search_item, self.model)
class SearchView(Gtk.Stack): """Gridlike view of search results. Three sections: artists, albums, songs. """ __gtype_name__ = "SearchView" class State(IntEnum): """The different states of SearchView """ MAIN = 0 ALL_ALBUMS = 1 ALL_ARTISTS = 2 ALBUM = 3 ARTIST = 4 search_state = GObject.Property(type=int, default=Search.State.NONE) selection_mode = GObject.Property(type=bool, default=False) state = GObject.Property(type=int, default=State.MAIN) title = GObject.Property(type=str, default="", flags=GObject.ParamFlags.READABLE) _album_header = Gtk.Template.Child() _album_flowbox = Gtk.Template.Child() _album_all_flowbox = Gtk.Template.Child() _all_search_results = Gtk.Template.Child() _artist_header = Gtk.Template.Child() _artist_all_flowbox = Gtk.Template.Child() _artist_flowbox = Gtk.Template.Child() _search_results = Gtk.Template.Child() _songs_header = Gtk.Template.Child() _songs_listbox = Gtk.Template.Child() _view_all_albums = Gtk.Template.Child() _view_all_artists = Gtk.Template.Child() def __init__(self, application): """Initialize SearchView :param GtkApplication application: The Application object """ super().__init__(transition_type=Gtk.StackTransitionType.CROSSFADE) self.props.name = "search" self._application = application self._coremodel = application.props.coremodel self._model = self._coremodel.props.songs_search self._album_model = self._coremodel.props.albums_search self._album_filter = self._coremodel.props.albums_search_filter self._album_filter.set_filter_func(self._core_filter, self._album_model, 12) self._artist_model = self._coremodel.props.artists_search self._artist_filter = self._coremodel.props.artists_search_filter self._artist_filter.set_filter_func(self._core_filter, self._artist_model, 6) self._model.connect_after("items-changed", self._on_model_items_changed) self._songs_listbox.bind_model(self._model, self._create_song_widget) self._on_model_items_changed(self._model, 0, 0, 0) self._album_filter.connect_after("items-changed", self._on_album_model_items_changed) self._album_flowbox.bind_model(self._album_filter, self._create_album_widget) self._album_flowbox.connect("size-allocate", self._on_album_flowbox_size_allocate) self._on_album_model_items_changed(self._album_filter, 0, 0, 0) self._artist_filter.connect_after("items-changed", self._on_artist_model_items_changed) self._artist_flowbox.bind_model(self._artist_filter, self._create_artist_widget) self._artist_flowbox.connect("size-allocate", self._on_artist_flowbox_size_allocate) self._on_artist_model_items_changed(self._artist_filter, 0, 0, 0) self._player = self._application.props.player self._window = application.props.window self._headerbar = self._window._headerbar self.connect("notify::selection-mode", self._on_selection_mode_changed) self.bind_property('selection-mode', self._window, 'selection-mode', GObject.BindingFlags.BIDIRECTIONAL) self._album_widget = AlbumWidget(self._application) self._album_widget.bind_property("selection-mode", self, "selection-mode", GObject.BindingFlags.BIDIRECTIONAL) self.add(self._album_widget) self._scrolled_artist_window = None self._search_mode_active = False def _core_filter(self, coreitem, coremodel, nr_items): if coremodel.get_n_items() <= 5: return True for i in range(nr_items): if coremodel.get_item(i) == coreitem: return True return False def _create_song_widget(self, coresong): song_widget = SongWidget(coresong, False, True) song_widget.props.show_song_number = False self.bind_property( "selection-mode", song_widget, "selection-mode", GObject.BindingFlags.BIDIRECTIONAL | GObject.BindingFlags.SYNC_CREATE) song_widget.connect('button-release-event', self._song_activated) return song_widget def _create_album_widget(self, corealbum): album_widget = AlbumCover(corealbum) album_widget.retrieve() self.bind_property( "selection-mode", album_widget, "selection-mode", GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL) # NOTE: Adding SYNC_CREATE here will trigger all the nested # models to be created. This will slow down initial start, # but will improve initial 'select all' speed. album_widget.bind_property("selected", corealbum, "selected", GObject.BindingFlags.BIDIRECTIONAL) return album_widget def _create_artist_widget(self, coreartist): artist_tile = ArtistSearchTile(coreartist) self.bind_property( "selection-mode", artist_tile, "selection-mode", GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL) return artist_tile def _on_album_model_items_changed(self, model, position, removed, added): items_found = model.get_n_items() > 0 self._album_header.props.visible = items_found self._album_flowbox.props.visible = items_found self._check_visibility() nr_albums = self._album_model.get_n_items() self._view_all_albums.props.visible = (nr_albums > model.get_n_items()) def set_child_visible(child): child.props.visible = True self._album_flowbox.foreach(set_child_visible) def _on_artist_model_items_changed(self, model, position, removed, added): items_found = model.get_n_items() > 0 self._artist_header.props.visible = items_found self._artist_flowbox.props.visible = items_found self._check_visibility() nr_artists = self._artist_model.get_n_items() self._view_all_artists.props.visible = (nr_artists > model.get_n_items()) def set_child_visible(child): child.props.visible = True self._artist_flowbox.foreach(set_child_visible) def _on_model_items_changed(self, model, position, removed, added): items_found = model.get_n_items() > 0 self._songs_header.props.visible = items_found self._songs_listbox.props.visible = items_found self._check_visibility() def _check_visibility(self): if not self.props.search_mode_active: return items_found = (self._model.get_n_items() > 0 or self._artist_model.get_n_items() > 0 or self._album_model.get_n_items() > 0) if items_found: self.props.search_state = Search.State.RESULT else: self.props.search_state = Search.State.NO_RESULT def _song_activated(self, widget, event): if widget.props.select_click: widget.props.select_click = False return mod_mask = Gtk.accelerator_get_default_mod_mask() if ((event.get_state() & mod_mask) == Gdk.ModifierType.CONTROL_MASK and not self.props.selection_mode): self.props.selection_mode = True return if self.props.selection_mode: widget.props.select_click = True widget.props.selected = not widget.props.selected widget.props.coresong.props.selected = widget.props.selected return (_, button) = event.get_button() if (button == Gdk.BUTTON_PRIMARY and not self.props.selection_mode): self._coremodel.props.active_core_object = widget.props.coresong self._player.play(widget.props.coresong) return True def _on_album_flowbox_size_allocate(self, widget, allocation, data=None): nb_children = self._album_filter.get_n_items() if nb_children == 0: return first_child = self._album_flowbox.get_child_at_index(0) child_height = first_child.get_allocation().height if allocation.height > 2.5 * child_height: for i in range(nb_children - 1, -1, -1): child = self._album_flowbox.get_child_at_index(i) if child.props.visible is True: child.props.visible = False return children_hidden = False for idx in range(nb_children): child = self._album_flowbox.get_child_at_index(idx) if not child.props.visible: children_hidden = True break if children_hidden is False: return last_visible_child = self._album_flowbox.get_child_at_index(idx - 1) first_row_last = self._album_flowbox.get_child_at_index((idx - 1) // 2) second_row_pos = last_visible_child.get_allocation().x first_row_pos = first_row_last.get_allocation().x child_width = last_visible_child.get_allocation().width nb_children_to_add = (first_row_pos - second_row_pos) // child_width nb_children_to_add = min(nb_children_to_add + idx, nb_children) for i in range(idx, nb_children_to_add): child = self._album_flowbox.get_child_at_index(i) child.props.visible = True def _on_artist_flowbox_size_allocate(self, widget, allocation, data=None): nb_children = self._artist_filter.get_n_items() if nb_children == 0: return first_child = self._artist_flowbox.get_child_at_index(0) # FIXME: It looks like it is possible that the widget is not # yet created, resulting in a crash with first_child being # None. # Look for a cleaner solution. if first_child is None: return child_height = first_child.get_allocation().height if allocation.height > 1.5 * child_height: for i in range(nb_children - 1, -1, -1): child = self._artist_flowbox.get_child_at_index(i) if child.props.visible is True: child.props.visible = False return children_hidden = False for idx in range(nb_children): child = self._artist_flowbox.get_child_at_index(idx) if not child.props.visible: children_hidden = True break if children_hidden is False: return last_child = self._artist_flowbox.get_child_at_index(idx - 1) last_child_allocation = last_child.get_allocation() child_width = last_child_allocation.width if (last_child_allocation.x + 2 * child_width) < allocation.width: child = self._artist_flowbox.get_child_at_index(idx) child.props.visible = True @Gtk.Template.Callback() def _on_album_activated(self, widget, child, user_data=None): corealbum = child.props.corealbum if self.props.selection_mode: corealbum.props.selected = not corealbum.props.selected return # Update and display the album widget if not in selection mode self._album_widget.update(corealbum) self.props.state = SearchView.State.ALBUM self._headerbar.props.state = HeaderBar.State.SEARCH self._headerbar.props.title = corealbum.props.title self._headerbar.props.subtitle = corealbum.props.artist self.set_visible_child(self._album_widget) self.props.search_mode_active = False @Gtk.Template.Callback() def _on_artist_activated(self, widget, child, user_data=None): coreartist = child.props.coreartist if self.props.selection_mode: return artist_albums_widget = ArtistAlbumsWidget(coreartist, self._application) # FIXME: Recreating a view here. Alternate solution is used # in AlbumsView: one view created and an update function. # Settle on one design. self._scrolled_artist_window = Gtk.ScrolledWindow() self._scrolled_artist_window.add(artist_albums_widget) self._scrolled_artist_window.props.visible = True self.add(self._scrolled_artist_window) artist_albums_widget.show() self.bind_property("selection-mode", artist_albums_widget, "selection-mode", GObject.BindingFlags.BIDIRECTIONAL) self.props.state = SearchView.State.ARTIST self._headerbar.props.state = HeaderBar.State.SEARCH self._headerbar.props.title = coreartist.props.artist self._headerbar.props.subtitle = None self.set_visible_child(self._scrolled_artist_window) self.props.search_mode_active = False @Gtk.Template.Callback() def _on_all_artists_clicked(self, widget, event, user_data=None): self.props.state = SearchView.State.ALL_ARTISTS self._headerbar.props.state = HeaderBar.State.SEARCH self._headerbar.props.title = _("Artists Results") self._headerbar.props.subtitle = None self._artist_all_flowbox.props.visible = True self._album_all_flowbox.props.visible = False self._artist_all_flowbox.bind_model(self._artist_model, self._create_artist_widget) self.props.visible_child = self._all_search_results self.props.search_mode_active = False @Gtk.Template.Callback() def _on_all_albums_clicked(self, widget, event, user_data=None): self.props.state = SearchView.State.ALL_ALBUMS self._headerbar.props.state = HeaderBar.State.SEARCH self._headerbar.props.title = _("Albums Results") self._headerbar.props.subtitle = None self._artist_all_flowbox.props.visible = False self._album_all_flowbox.props.visible = True self._album_all_flowbox.bind_model(self._album_model, self._create_album_widget) self.props.visible_child = self._all_search_results self.props.search_mode_active = False def _select_all(self, value): def child_select(child): child.props.selected = value if self.props.state == SearchView.State.MAIN: with self._model.freeze_notify(): def song_select(child): song_widget = child.get_child() song_widget.props.coresong.props.selected = value self._songs_listbox.foreach(song_select) self._album_flowbox.foreach(child_select) self._artist_flowbox.foreach(child_select) elif self.props.state == SearchView.State.ALL_ALBUMS: with self._model.freeze_notify(): self._album_all_flowbox.foreach(child_select) elif self.props.state == SearchView.State.ALL_ARTISTS: with self._model.freeze_notify(): self._artist_all_flowbox.foreach(child_select) elif self.props.state == SearchView.State.ALBUM: view = self.get_visible_child() if value is True: view.select_all() else: view.deselect_all() elif self.props.state == SearchView.State.ARTIST: view = self.get_visible_child().get_child().get_child() if value is True: view.select_all() else: view.deselect_all() def select_all(self): self._select_all(True) def deselect_all(self): self._select_all(False) def _back_button_clicked(self, widget, data=None): if self.get_visible_child() == self._search_results: return elif self.get_visible_child() == self._scrolled_artist_window: self.remove(self._scrolled_artist_window) self._scrolled_artist_window.destroy() self._scrolled_artist_window = None self.set_visible_child(self._search_results) self.props.search_mode_active = True self.props.state = SearchView.State.MAIN self._headerbar.props.state = HeaderBar.State.MAIN def _on_selection_mode_changed(self, widget, data=None): if (not self.props.selection_mode and self.get_parent().get_visible_child() == self): self.deselect_all() @GObject.Property(type=bool, default=False) def search_mode_active(self): """Get search mode status. :returns: the search mode status :rtype: bool """ return self._search_mode_active @search_mode_active.setter def search_mode_active(self, value): """Set search mode status. :param bool mode: new search mode """ # FIXME: search_mode_active should not change search_state. # This is necessary because Search state cannot interact with # the child views. self._search_mode_active = value if (not self._search_mode_active and self.get_visible_child() == self._search_results): self.props.search_state = Search.State.NONE
class SearchView(BaseView): search_state = GObject.Property(type=int, default=Search.State.NONE) def __repr__(self): return '<SearchView>' @log def __init__(self, window, player): super().__init__('search', None, window) # FIXME: Searchbar handling does not belong here. self._searchbar = window._searchbar self._add_list_renderers() self.player = player self._head_iters = [None, None, None, None] self._filter_model = None self.previous_view = None self._albums_selected = [] self._albums = {} self._albums_index = 0 self._album_widget = AlbumWidget(player, self) self.add(self._album_widget) self._artists_albums_selected = [] self._artists_albums_index = 0 self._artists = {} self._artist_albums_widget = None self._items_selected = [] self._items_selected_callback = None self._items_found = None @log def _setup_view(self): view_container = Gtk.ScrolledWindow(hexpand=True, vexpand=True) self._box.pack_start(view_container, True, True, 0) self._view = Gtk.TreeView( activate_on_single_click=True, can_focus=False, halign=Gtk.Align.CENTER, headers_visible=False, show_expanders=False, width_request=530) self._view.get_style_context().add_class('view') self._view.get_style_context().add_class('content-view') self._view.get_selection().props.mode = Gtk.SelectionMode.NONE self._view.connect('row-activated', self._on_item_activated) self._ctrl = Gtk.GestureMultiPress().new(self._view) self._ctrl.props.propagation_phase = Gtk.PropagationPhase.CAPTURE self._ctrl.connect("released", self._on_view_clicked) view_container.add(self._view) @log def _back_button_clicked(self, widget, data=None): self._searchbar.reveal(True, False) if self.get_visible_child() == self._artist_albums_widget: self._artist_albums_widget.destroy() self._artist_albums_widget = None elif self.get_visible_child() == self._grid: self._window.views[View.ALBUM].set_visible_child( self._window.views[View.ALBUM]._grid) self.set_visible_child(self._grid) self._headerbar.props.state = HeaderBar.State.MAIN @log def _on_item_activated(self, treeview, path, column): if self._star_handler.star_renderer_click: self._star_handler.star_renderer_click = False return if self.props.selection_mode: return try: child_path = self._filter_model.convert_path_to_child_path(path) except TypeError: return _iter = self.model.get_iter(child_path) if self.model[_iter][12] == 'album': title = self.model[_iter][2] artist = self.model[_iter][3] item = self.model[_iter][5] self._album_widget.update(item) self._headerbar.props.state = HeaderBar.State.SEARCH self._headerbar.props.title = title self._headerbar.props.subtitle = artist self.set_visible_child(self._album_widget) self.props.search_state = Search.State.NONE elif self.model[_iter][12] == 'artist': artist = self.model[_iter][2] albums = self._artists[artist.casefold()]['albums'] self._artist_albums_widget = ArtistAlbumsWidget( artist, albums, self.player, self._window, True) self.add(self._artist_albums_widget) self._artist_albums_widget.show() self._artist_albums_widget.bind_property( 'selected-items-count', self, 'selected-items-count') self.bind_property( 'selection-mode', self._artist_albums_widget, 'selection-mode', GObject.BindingFlags.BIDIRECTIONAL) self._headerbar.props.state = HeaderBar.State.SEARCH self._headerbar.props.title = artist self._headerbar.props.subtitle = None self.set_visible_child(self._artist_albums_widget) self.props.search_state = Search.State.NONE elif self.model[_iter][12] == 'song': if self.model[_iter][11] != ValidationStatus.FAILED: c_iter = self._songs_model.convert_child_iter_to_iter(_iter)[1] self.player.set_playlist( PlayerPlaylist.Type.SEARCH_RESULT, None, self._songs_model, c_iter) self.player.play() else: # Headers if self._view.row_expanded(path): self._view.collapse_row(path) else: self._view.expand_row(path, False) @log def _on_view_clicked(self, gesture, n_press, x, y): """Ctrl+click on self._view triggers selection mode.""" _, state = Gtk.get_current_event_state() modifiers = Gtk.accelerator_get_default_mod_mask() if (state & modifiers == Gdk.ModifierType.CONTROL_MASK and not self.props.selection_mode): self._on_selection_mode_request() if (self.selection_mode and not self._star_handler.star_renderer_click): path, col, cell_x, cell_y = self._view.get_path_at_pos(x, y) iter_ = self.model.get_iter(path) self.model[iter_][6] = not self.model[iter_][6] selected_iters = self._get_selected_iters() self.props.selected_items_count = len(selected_iters) @log def _get_selected_iters(self): iters = [] for row in self.model: iter_child = self.model.iter_children(row.iter) while iter_child is not None: if self.model[iter_child][6]: iters.append(iter_child) iter_child = self.model.iter_next(iter_child) return iters @log def _on_selection_mode_changed(self, widget, data=None): super()._on_selection_mode_changed(widget, data) col = self._view.get_columns()[0] cells = col.get_cells() cells[4].props.visible = self.props.selection_mode col.queue_resize() @log def _add_search_item(self, source, param, item, remaining=0, data=None): if not item: if (grilo._search_callback_counter == 0 and grilo.search_source): self.props.search_state = Search.State.NO_RESULT return if data != self.model: return artist = utils.get_artist_name(item) album = utils.get_album_title(item) key = '%s-%s' % (artist, album) if key not in self._albums: self._albums[key] = Grl.Media() self._albums[key].set_title(album) self._albums[key].add_artist(artist) self._albums[key].set_source(source.get_id()) self._albums[key].songs = [] self._add_item( source, None, self._albums[key], 0, [self.model, 'album']) self._add_item( source, None, self._albums[key], 0, [self.model, 'artist']) self._albums[key].songs.append(item) self._add_item(source, None, item, 0, [self.model, 'song']) @log def _retrieval_finished(self, klass, model, _iter): if not model[_iter][13]: return model[_iter][13] = klass.surface @log def _add_item(self, source, param, item, remaining=0, data=None): if data is None: return model, category = data self._items_found = ( self.model.iter_n_children(self._head_iters[0]) + self.model.iter_n_children(self._head_iters[1]) + self.model.iter_n_children(self._head_iters[2]) + self.model.iter_n_children(self._head_iters[3]) ) # We need to remember the view before the search view emptysearchview = self._window.views[View.EMPTY] if (self._window.curr_view != emptysearchview and self._window.prev_view != emptysearchview): self.previous_view = self._window.prev_view if self._items_found == 0: self.props.search_state = Search.State.NO_RESULT else: self.props.search_state = Search.State.RESULT if remaining == 0: self._window.notifications_popup.pop_loading() self._view.show() if not item or model != self.model: return self._offset += 1 title = utils.get_media_title(item) item.set_title(title) artist = utils.get_artist_name(item) group = 3 try: group = {'album': 0, 'artist': 1, 'song': 2}[category] except KeyError: pass _iter = None if category == 'album': _iter = self.model.insert_with_values( self._head_iters[group], -1, [0, 2, 3, 5, 9, 12], [str(item.get_id()), title, artist, item, 2, category]) elif category == 'song': # FIXME: source specific hack if source.get_id() != 'grl-tracker-source': fav = 2 else: fav = item.get_favourite() _iter = self.model.insert_with_values( self._head_iters[group], -1, [0, 2, 3, 5, 9, 12], [str(item.get_id()), title, artist, item, fav, category]) else: if not artist.casefold() in self._artists: _iter = self.model.insert_with_values( self._head_iters[group], -1, [0, 2, 5, 9, 12], [str(item.get_id()), artist, item, 2, category]) self._artists[artist.casefold()] = { 'iter': _iter, 'albums': [] } self._artists[artist.casefold()]['albums'].append(item) # FIXME: Figure out why iter can be None here, seems illogical. if _iter is not None: scale = self._view.get_scale_factor() art = Art(Art.Size.SMALL, item, scale) self.model[_iter][13] = art.surface art.connect( 'finished', self._retrieval_finished, self.model, _iter) art.lookup() if self.model.iter_n_children(self._head_iters[group]) == 1: path = self.model.get_path(self._head_iters[group]) path = self._filter_model.convert_child_path_to_path(path) self._view.expand_row(path, False) @log def _add_list_renderers(self): column = Gtk.TreeViewColumn() # Add our own surface renderer, instead of the one provided by # Gd. This avoids us having to set the model to a cairo.Surface # which is currently not a working solution in pygobject. # https://gitlab.gnome.org/GNOME/pygobject/issues/155 pixbuf_renderer = Gtk.CellRendererPixbuf( xalign=0.5, yalign=0.5, xpad=12, ypad=2) column.pack_start(pixbuf_renderer, False) column.set_cell_data_func( pixbuf_renderer, self._on_list_widget_pixbuf_renderer) column.add_attribute(pixbuf_renderer, 'surface', 13) # With the bugfix in libgd 9117650bda, the search results # stopped aligning at the top. With the artists results not # having a second line of text, this looks off. # Revert to old behaviour by forcing the alignment to be top. two_lines_renderer = Gd.TwoLinesRenderer( wrap_mode=Pango.WrapMode.WORD_CHAR, xpad=12, xalign=0.0, yalign=0, text_lines=2) column.pack_start(two_lines_renderer, True) column.set_cell_data_func( two_lines_renderer, self._on_list_widget_two_lines_renderer) column.add_attribute(two_lines_renderer, 'text', 2) column.add_attribute(two_lines_renderer, 'line_two', 3) title_renderer = Gtk.CellRendererText( xpad=12, xalign=0.0, yalign=0.5, height=32, ellipsize=Pango.EllipsizeMode.END, weight=Pango.Weight.BOLD) column.pack_start(title_renderer, False) column.set_cell_data_func( title_renderer, self._on_list_widget_title_renderer) column.add_attribute(title_renderer, 'text', 2) self._star_handler.add_star_renderers(column) selection_renderer = Gtk.CellRendererToggle(xpad=12, xalign=1.0) column.pack_start(selection_renderer, False) column.set_cell_data_func( selection_renderer, self._on_list_widget_selection_renderer) column.add_attribute(selection_renderer, 'active', 6) self._view.append_column(column) @log def _is_header(self, model, iter_): return model.iter_parent(iter_) is None @log def _on_list_widget_title_renderer(self, col, cell, model, iter_, data): cell.props.visible = self._is_header(model, iter_) @log def _on_list_widget_pixbuf_renderer(self, col, cell, model, iter_, data): if (not model[iter_][13] or self._is_header(model, iter_)): cell.props.visible = False return cell.props.surface = model[iter_][13] cell.props.visible = True @log def _on_list_widget_two_lines_renderer( self, col, cell, model, iter_, data): if self._is_header(model, iter_): cell.props.visible = False return cell.props.visible = True @log def _on_list_widget_selection_renderer( self, col, cell, model, iter_, data): if (self.props.selection_mode and not self._is_header(model, iter_)): cell.props.visible = True else: cell.props.visible = False @log def populate(self): self._init = True self._headerbar.props.state = HeaderBar.State.MAIN @log def get_selected_songs(self, callback): if self.get_visible_child() == self._album_widget: callback(self._album_widget.get_selected_songs()) elif self.get_visible_child() == self._artist_albums_widget: callback(self._artist_albums_widget.get_selected_songs()) else: self._albums_index = 0 self._artists_albums_index = 0 self._items_selected = [] self._items_selected_callback = callback self._get_selected_albums() @log def _get_selected_albums(self): selected_iters = self._get_selected_iters() self._albums_selected = [ self.model[iter_][5] for iter_ in selected_iters if self.model[iter_][12] == 'album'] if len(self._albums_selected): self._get_selected_albums_songs() else: self._get_selected_artists() @log def _get_selected_albums_songs(self): grilo.populate_album_songs( self._albums_selected[self._albums_index], self._add_selected_albums_songs) self._albums_index += 1 @log def _add_selected_albums_songs( self, source, param, item, remaining=0, data=None): if item: self._items_selected.append(item) if remaining == 0: if self._albums_index < len(self._albums_selected): self._get_selected_albums_songs() else: self._get_selected_artists() @log def _get_selected_artists(self): selected_iters = self._get_selected_iters() artists_selected = [ self._artists[self.model[iter_][2].casefold()] for iter_ in selected_iters if self.model[iter_][12] == 'artist'] self._artists_albums_selected = [] for artist in artists_selected: self._artists_albums_selected.extend(artist['albums']) if len(self._artists_albums_selected): self._get_selected_artists_albums_songs() else: self._get_selected_songs() @log def _get_selected_artists_albums_songs(self): grilo.populate_album_songs( self._artists_albums_selected[self._artists_albums_index], self._add_selected_artists_albums_songs) self._artists_albums_index += 1 @log def _add_selected_artists_albums_songs( self, source, param, item, remaining=0, data=None): if item: self._items_selected.append(item) if remaining == 0: artist_albums = len(self._artists_albums_selected) if self._artists_albums_index < artist_albums: self._get_selected_artists_albums_songs() else: self._get_selected_songs() @log def _get_selected_songs(self): selected_iters = self._get_selected_iters() self._items_selected.extend([ self.model[iter_][5] for iter_ in selected_iters if self.model[iter_][12] == 'song']) self._items_selected_callback(self._items_selected) @log def _filter_visible_func(self, model, _iter, data=None): top_level = model.iter_parent(_iter) is None visible = (not top_level or model.iter_has_child(_iter)) return visible @log def set_search_text(self, search_term, fields_filter): query_matcher = { 'album': { 'search_all': Query.get_albums_with_any_match, 'search_artist': Query.get_albums_with_artist_match, 'search_composer': Query.get_albums_with_composer_match, 'search_album': Query.get_albums_with_album_match, 'search_track': Query.get_albums_with_track_match, }, 'artist': { 'search_all': Query.get_artists_with_any_match, 'search_artist': Query.get_artists_with_artist_match, 'search_composer': Query.get_artists_with_composer_match, 'search_album': Query.get_artists_with_album_match, 'search_track': Query.get_artists_with_track_match, }, 'song': { 'search_all': Query.get_songs_with_any_match, 'search_artist': Query.get_songs_with_artist_match, 'search_composer': Query.get_songs_with_composer_match, 'search_album': Query.get_songs_with_album_match, 'search_track': Query.get_songs_with_track_match, }, } self.model = Gtk.TreeStore( GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING, # item title or header text GObject.TYPE_STRING, # artist for albums and songs GdkPixbuf.Pixbuf, # Gd placeholder album art GObject.TYPE_OBJECT, # item GObject.TYPE_BOOLEAN, GObject.TYPE_INT, GObject.TYPE_STRING, GObject.TYPE_INT, GObject.TYPE_BOOLEAN, GObject.TYPE_INT, # validation status GObject.TYPE_STRING, # type object # album art surface ) self._filter_model = self.model.filter_new(None) self._filter_model.set_visible_func(self._filter_visible_func) self._view.set_model(self._filter_model) self._albums = {} self._artists = {} if search_term == "": return albums_iter = self.model.insert_with_values( None, -1, [2, 9], [_("Albums"), 2]) artists_iter = self.model.insert_with_values( None, -1, [2, 9], [_("Artists"), 2]) songs_iter = self.model.insert_with_values( None, -1, [2, 9], [_("Songs"), 2]) playlists_iter = self.model.insert_with_values( None, -1, [2, 9], [_("Playlists"), 2]) self._head_iters = [ albums_iter, artists_iter, songs_iter, playlists_iter ] self._songs_model = self.model.filter_new( self.model.get_path(songs_iter)) # Use queries for Tracker if (not grilo.search_source or grilo.search_source.get_id() == 'grl-tracker-source'): for category in ('album', 'artist', 'song'): query = query_matcher[category][fields_filter](search_term) self._window.notifications_popup.push_loading() grilo.populate_custom_query( query, self._add_item, -1, [self.model, category]) if (not grilo.search_source or grilo.search_source.get_id() != 'grl-tracker-source'): # nope, can't do - reverting to Search grilo.search(search_term, self._add_search_item, self.model)
def __init__(self, application): """Initialize SearchView :param GtkApplication application: The Application object """ super().__init__(transition_type=Gtk.StackTransitionType.CROSSFADE) self.props.name = "search" self._application = application self._coremodel = application.props.coremodel self._model = self._coremodel.props.songs_search self._album_model = self._coremodel.props.albums_search self._album_filter = self._coremodel.props.albums_search_filter self._album_filter.set_filter_func(self._core_filter, self._album_model, 12) self._artist_model = self._coremodel.props.artists_search self._artist_filter = self._coremodel.props.artists_search_filter self._artist_filter.set_filter_func(self._core_filter, self._artist_model, 6) self._model.connect_after("items-changed", self._on_model_items_changed) self._songs_listbox.bind_model(self._model, self._create_song_widget) self._on_model_items_changed(self._model, 0, 0, 0) self._album_filter.connect_after("items-changed", self._on_album_model_items_changed) self._album_flowbox.bind_model(self._album_filter, self._create_album_widget) self._album_flowbox.connect("size-allocate", self._on_album_flowbox_size_allocate) self._on_album_model_items_changed(self._album_filter, 0, 0, 0) self._artist_filter.connect_after("items-changed", self._on_artist_model_items_changed) self._artist_flowbox.bind_model(self._artist_filter, self._create_artist_widget) self._artist_flowbox.connect("size-allocate", self._on_artist_flowbox_size_allocate) self._on_artist_model_items_changed(self._artist_filter, 0, 0, 0) self._player = self._application.props.player self._window = application.props.window self._headerbar = self._window._headerbar self.connect("notify::selection-mode", self._on_selection_mode_changed) self.bind_property('selection-mode', self._window, 'selection-mode', GObject.BindingFlags.BIDIRECTIONAL) self._album_widget = AlbumWidget(self._application) self._album_widget.bind_property("selection-mode", self, "selection-mode", GObject.BindingFlags.BIDIRECTIONAL) self.add(self._album_widget) self._scrolled_artist_window = None self._search_mode_active = False
class SearchView(BaseView): __gsignals__ = { 'no-music-found': (GObject.SignalFlags.RUN_FIRST, None, ()) } def __repr__(self): return '<SearchView>' @log def __init__(self, window, player): BaseView.__init__(self, 'search', None, window, Gd.MainViewType.LIST) self._items = {} self.isStarred = None self.iter_to_clean = None scale = self.get_scale_factor() loading_icon_surface = DefaultIcon(scale).get(DefaultIcon.Type.loading, ArtSize.small) self._loading_icon = Gdk.pixbuf_get_from_surface( loading_icon_surface, 0, 0, loading_icon_surface.get_width(), loading_icon_surface.get_height()) self._add_list_renderers() self.player = player self.head_iters = [None, None, None, None] self.songs_model = self.model self.previous_view = None self.connect('no-music-found', self._no_music_found_callback) self.albums_selected = [] self._albums = {} self._albumWidget = AlbumWidget(player, self) self.add(self._albumWidget) self.artists_albums_selected = [] self._artists = {} self._artistAlbumsWidget = None self._view.get_generic_view().set_show_expanders(False) self.items_selected = [] self.items_selected_callback = None self.found_items_number = None @log def _no_music_found_callback(self, view): self._window._stack.set_visible_child_name('emptysearch') emptysearch = self._window._stack.get_child_by_name('emptysearch') emptysearch._artistAlbumsWidget = self._artistAlbumsWidget @log def _back_button_clicked(self, widget, data=None): self._header_bar.searchbar.show_bar(True, False) if self.get_visible_child() == self._artistAlbumsWidget: self._artistAlbumsWidget.destroy() self._artistAlbumsWidget = None elif self.get_visible_child() == self._grid: self._window.views[0].set_visible_child( self._window.views[0]._grid) self.set_visible_child(self._grid) self._window.toolbar.set_state(ToolbarState.MAIN) @log def _on_item_activated(self, widget, id, path): if self._star_handler.star_renderer_click: self._star_handler.star_renderer_click = False return try: child_path = self.filter_model.convert_path_to_child_path(path) except TypeError: return _iter = self.model.get_iter(child_path) if self.model[_iter][11] == 'album': title = self.model.get_value(_iter, 2) artist = self.model.get_value(_iter, 3) item = self.model.get_value(_iter, 5) self._albumWidget.update(artist, title, item, self._header_bar, self._selection_toolbar) self._header_bar.set_state(ToolbarState.SEARCH_VIEW) title = utils.get_media_title(item) self._header_bar.header_bar.set_title(title) self._header_bar.header_bar.sub_title = artist self.set_visible_child(self._albumWidget) self._header_bar.searchbar.show_bar(False) elif self.model[_iter][11] == 'artist': artist = self.model.get_value(_iter, 2) albums = self._artists[artist.casefold()]['albums'] self._artistAlbumsWidget = ArtistAlbumsWidget( artist, albums, self.player, self._header_bar, self._selection_toolbar, self._window, True) self.add(self._artistAlbumsWidget) self._artistAlbumsWidget.show() self._header_bar.set_state(ToolbarState.SEARCH_VIEW) self._header_bar.header_bar.set_title(artist) self.set_visible_child(self._artistAlbumsWidget) self._header_bar.searchbar.show_bar(False) elif self.model[_iter][11] == 'song': if self.model.get_value(_iter, 12) != DiscoveryStatus.FAILED: child_iter = self.songs_model.convert_child_iter_to_iter( _iter)[1] self.player.set_playlist('Search Results', None, self.songs_model, child_iter, 5, 12) self.player.set_playing(True) else: # Headers if self._view.get_generic_view().row_expanded(path): self._view.get_generic_view().collapse_row(path) else: self._view.get_generic_view().expand_row(path, False) @log def _on_selection_mode_changed(self, widget, data=None): if self._artistAlbumsWidget is not None and self.get_visible_child( ) == self._artistAlbumsWidget: self._artistAlbumsWidget.set_selection_mode( self._header_bar._selectionMode) @log def _add_search_item(self, source, param, item, remaining=0, data=None): if not item: if grilo._search_callback_counter == 0 and grilo.search_source: self.emit('no-music-found') return if data != self.model: return artist = utils.get_artist_name(item) album = utils.get_album_title(item) composer = item.get_composer() key = '%s-%s' % (artist, album) if key not in self._albums: self._albums[key] = Grl.Media() self._albums[key].set_title(album) self._albums[key].add_artist(artist) self._albums[key].set_composer(composer) self._albums[key].set_source(source.get_id()) self._albums[key].songs = [] self._add_item(source, None, self._albums[key], 0, [self.model, 'album']) self._add_item(source, None, self._albums[key], 0, [self.model, 'artist']) self._albums[key].songs.append(item) self._add_item(source, None, item, 0, [self.model, 'song']) @log def _add_item(self, source, param, item, remaining=0, data=None): if data is None: return model, category = data self.found_items_number = ( self.model.iter_n_children(self.head_iters[0]) + self.model.iter_n_children(self.head_iters[1]) + self.model.iter_n_children(self.head_iters[2]) + self.model.iter_n_children(self.head_iters[3])) if category == 'song' and self.found_items_number == 0 and remaining == 0: if grilo.search_source: self.emit('no-music-found') # We need to remember the view before the search view if self._window.curr_view != self._window.views[5] and \ self._window.prev_view != self._window.views[5]: self.previous_view = self._window.prev_view if remaining == 0: self._window.pop_loading_notification() self._view.show() if not item or model != self.model: return self._offset += 1 title = utils.get_media_title(item) item.set_title(title) artist = utils.get_artist_name(item) # FIXME: Can't be None in treemodel composer = item.get_composer() or "" group = 3 try: group = {'album': 0, 'artist': 1, 'song': 2}[category] except: pass # FIXME: HiDPI icon lookups return a surface that can't be # scaled by GdkPixbuf, so it results in a * scale factor sized # icon for the search view. _iter = None if category == 'album': _iter = self.model.insert_with_values( self.head_iters[group], -1, [0, 2, 3, 4, 5, 9, 11, 13], [ str(item.get_id()), title, artist, self._loading_icon, item, 2, category, composer ]) self._cache.lookup(item, ArtSize.small, self._on_lookup_ready, _iter) elif category == 'song': _iter = self.model.insert_with_values( self.head_iters[group], -1, [0, 2, 3, 4, 5, 9, 11, 13], [str(item.get_id()), title, artist, self._loading_icon, item, 2 if source.get_id() != 'grl-tracker-source' \ else item.get_favourite(), category, composer]) self._cache.lookup(item, ArtSize.small, self._on_lookup_ready, _iter) else: if not artist.casefold() in self._artists: _iter = self.model.insert_with_values( self.head_iters[group], -1, [0, 2, 4, 5, 9, 11, 13], [ str(item.get_id()), artist, self._loading_icon, item, 2, category, composer ]) self._cache.lookup(item, ArtSize.small, self._on_lookup_ready, _iter) self._artists[artist.casefold()] = { 'iter': _iter, 'albums': [] } self._artists[artist.casefold()]['albums'].append(item) if self.model.iter_n_children(self.head_iters[group]) == 1: path = self.model.get_path(self.head_iters[group]) path = self.filter_model.convert_child_path_to_path(path) self._view.get_generic_view().expand_row(path, False) @log def _add_list_renderers(self): list_widget = self._view.get_generic_view() list_widget.set_halign(Gtk.Align.CENTER) list_widget.set_size_request(530, -1) cols = list_widget.get_columns() title_renderer = Gtk.CellRendererText( xpad=12, xalign=0.0, yalign=0.5, height=32, ellipsize=Pango.EllipsizeMode.END, weight=Pango.Weight.BOLD) list_widget.add_renderer(title_renderer, self._on_list_widget_title_render, None) cols[0].add_attribute(title_renderer, 'text', 2) self._star_handler.add_star_renderers(list_widget, cols[0]) cells = cols[0].get_cells() cols[0].reorder(cells[0], -1) cols[0].set_cell_data_func(cells[0], self._on_list_widget_selection_render, None) def _on_list_widget_selection_render(self, col, cell, model, _iter, data): cell.set_visible(self._view.get_selection_mode() and model.iter_parent(_iter) is not None) def _on_list_widget_title_render(self, col, cell, model, _iter, data): cells = col.get_cells() cells[0].set_visible(model.iter_parent(_iter) is not None) cells[1].set_visible(model.iter_parent(_iter) is not None) cells[2].set_visible(model.iter_parent(_iter) is None) @log def populate(self): self._init = True self._window.push_loading_notification() self._header_bar.set_state(ToolbarState.MAIN) @log def get_selected_songs(self, callback): if self.get_visible_child() == self._albumWidget: callback(self._albumWidget.view.get_selected_items()) elif self.get_visible_child() == self._artistAlbumsWidget: items = [] # FIXME: calling into private model for row in self._artistAlbumsWidget._model: if row[6]: items.append(row[5]) callback(items) else: self.items_selected = [] self.items_selected_callback = callback self._get_selected_albums() @log def _get_selected_albums(self): self.albums_index = 0 self.albums_selected = [ self.model[child_path][5] for child_path in [ self.filter_model.convert_path_to_child_path(path) for path in self._view.get_selection() ] if self.model[child_path][11] == 'album' ] if len(self.albums_selected): self._get_selected_albums_songs() else: self._get_selected_artists() @log def _get_selected_albums_songs(self): grilo.populate_album_songs(self.albums_selected[self.albums_index], self._add_selected_albums_songs) self.albums_index += 1 @log def _add_selected_albums_songs(self, source, param, item, remaining=0, data=None): if item: self.items_selected.append(item) if remaining == 0: if self.albums_index < len(self.albums_selected): self._get_selected_albums_songs() else: self._get_selected_artists() @log def _get_selected_artists(self): self.artists_albums_index = 0 self.artists_selected = [ self._artists[self.model[child_path][2].casefold()] for child_path in [ self.filter_model.convert_path_to_child_path(path) for path in self._view.get_selection() ] if self.model[child_path][11] == 'artist' ] self.artists_albums_selected = [] for artist in self.artists_selected: self.artists_albums_selected.extend(artist['albums']) if len(self.artists_albums_selected): self._get_selected_artists_albums_songs() else: self._get_selected_songs() @log def _get_selected_artists_albums_songs(self): grilo.populate_album_songs( self.artists_albums_selected[self.artists_albums_index], self._add_selected_artists_albums_songs) self.artists_albums_index += 1 @log def _add_selected_artists_albums_songs(self, source, param, item, remaining=0, data=None): if item: self.items_selected.append(item) if remaining == 0: if self.artists_albums_index < len(self.artists_albums_selected): self._get_selected_artists_albums_songs() else: self._get_selected_songs() @log def _get_selected_songs(self): self.items_selected.extend([ self.model[child_path][5] for child_path in [ self.filter_model.convert_path_to_child_path(path) for path in self._view.get_selection() ] if self.model[child_path][11] == 'song' ]) self.items_selected_callback(self.items_selected) @log def _filter_visible_func(self, model, _iter, data=None): return model.iter_parent(_iter) is not None or model.iter_has_child( _iter) @log def set_search_text(self, search_term, fields_filter): query_matcher = { 'album': { 'search_all': Query.get_albums_with_any_match, 'search_artist': Query.get_albums_with_artist_match, 'search_composer': Query.get_albums_with_composer_match, 'search_album': Query.get_albums_with_album_match, 'search_track': Query.get_albums_with_track_match, }, 'artist': { 'search_all': Query.get_artists_with_any_match, 'search_artist': Query.get_artists_with_artist_match, 'search_composer': Query.get_artists_with_composer_match, 'search_album': Query.get_artists_with_album_match, 'search_track': Query.get_artists_with_track_match, }, 'song': { 'search_all': Query.get_songs_with_any_match, 'search_artist': Query.get_songs_with_artist_match, 'search_composer': Query.get_songs_with_composer_match, 'search_album': Query.get_songs_with_album_match, 'search_track': Query.get_songs_with_track_match, }, } self.model = Gtk.TreeStore( GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING, # item title or header text GObject.TYPE_STRING, # artist for albums and songs GdkPixbuf.Pixbuf, # album art GObject.TYPE_OBJECT, # item GObject.TYPE_BOOLEAN, GObject.TYPE_INT, GObject.TYPE_STRING, GObject.TYPE_INT, GObject.TYPE_BOOLEAN, GObject.TYPE_STRING, # type GObject.TYPE_INT, GObject.TYPE_STRING, # composer ) self.filter_model = self.model.filter_new(None) self.filter_model.set_visible_func(self._filter_visible_func) self._view.set_model(self.filter_model) self._albums = {} self._artists = {} if search_term == "": return albums_iter = self.model.insert_with_values(None, -1, [2, 9], [_("Albums"), 2]) artists_iter = self.model.insert_with_values(None, -1, [2, 9], [_("Artists"), 2]) songs_iter = self.model.insert_with_values(None, -1, [2, 9], [_("Songs"), 2]) playlists_iter = self.model.insert_with_values(None, -1, [2, 9], [_("Playlists"), 2]) self.head_iters = [ albums_iter, artists_iter, songs_iter, playlists_iter ] self.songs_model = self.model.filter_new( self.model.get_path(songs_iter)) # Use queries for Tracker if not grilo.search_source or \ grilo.search_source.get_id() == 'grl-tracker-source': for category in ('album', 'artist', 'song'): query = query_matcher[category][fields_filter](search_term) grilo.populate_custom_query(query, self._add_item, -1, [self.model, category]) if not grilo.search_source or \ grilo.search_source.get_id() != 'grl-tracker-source': # nope, can't do - reverting to Search grilo.search(search_term, self._add_search_item, self.model)
class AlbumsView(BaseView): search_mode_active = GObject.Property(type=bool, default=False) def __repr__(self): return '<AlbumsView>' @log def __init__(self, window, player): self._window = window super().__init__('albums', _("Albums"), window) self.player = player self._album_widget = AlbumWidget(player, self) self._album_widget.bind_property( "selection-mode", self, "selection-mode", GObject.BindingFlags.BIDIRECTIONAL) self.add(self._album_widget) self.albums_selected = [] self.all_items = [] self.items_selected = [] self.items_selected_callback = None self.connect( "notify::search-mode-active", self._on_search_mode_changed) @log def _on_selection_mode_changed(self, widget, data=None): super()._on_selection_mode_changed(widget, data) if not self.props.selection_mode: self._on_changes_pending() @log def _on_search_mode_changed(self, klass, param): if (not self.props.search_mode_active and self._headerbar.props.stack.props.visible_child == self and self.get_visible_child() == self._album_widget): self._set_album_headerbar(self._album_widget.props.album) @log def _setup_view(self): self._view = Gtk.FlowBox( homogeneous=True, hexpand=True, halign=Gtk.Align.FILL, valign=Gtk.Align.START, selection_mode=Gtk.SelectionMode.NONE, margin=18, row_spacing=12, column_spacing=6, min_children_per_line=1, max_children_per_line=20, visible=True) self._view.get_style_context().add_class('content-view') self._view.connect('child-activated', self._on_child_activated) scrolledwin = Gtk.ScrolledWindow() scrolledwin.add(self._view) scrolledwin.show() self._box.add(scrolledwin) self._model = self._window._app.props.coremodel.props.albums_sort self._view.bind_model(self._model, self._create_widget) self._view.show() @log def _create_widget(self, corealbum): album_widget = AlbumCover(corealbum) self.bind_property( "selection-mode", album_widget, "selection-mode", GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL) # NOTE: Adding SYNC_CREATE here will trigger all the nested # models to be created. This will slow down initial start, # but will improve initial 'selecte all' speed. album_widget.bind_property( "selected", corealbum, "selected", GObject.BindingFlags.BIDIRECTIONAL) return album_widget @log def _back_button_clicked(self, widget, data=None): self._headerbar.state = HeaderBar.State.MAIN self.set_visible_child(self._grid) @log def _on_child_activated(self, widget, child, user_data=None): corealbum = child.props.corealbum if self.props.selection_mode: return # Update and display the album widget if not in selection mode self._album_widget.update(corealbum) self._set_album_headerbar(corealbum) self.set_visible_child(self._album_widget) @log def _set_album_headerbar(self, corealbum): self._headerbar.props.state = HeaderBar.State.CHILD self._headerbar.props.title = corealbum.props.title self._headerbar.props.subtitle = corealbum.props.artist @log def _populate(self, data=None): # self._window.notifications_popup.push_loading() self._init = True self._view.show() def _toggle_all_selection(self, selected): """ Selects or unselects all items without sending the notify::active signal for performance purposes. """ with self._window._app.props.coreselection.freeze_notify(): for child in self._view.get_children(): child.props.selected = selected child.props.corealbum.props.selected = selected def select_all(self): self._toggle_all_selection(True) def unselect_all(self): self._toggle_all_selection(False)
class AlbumsView(BaseView): def __repr__(self): return '<AlbumsView>' @log def __init__(self, window, player): super().__init__('albums', _("Albums"), window, None) self._queue = LifoQueue() self._album_widget = AlbumWidget(player, self) self.player = player self.add(self._album_widget) self.albums_selected = [] self.all_items = [] self.items_selected = [] self.items_selected_callback = None self._add_list_renderers() self._init = True @log def _on_changes_pending(self, data=None): if (self._init and not self._header_bar.selection_mode): self._offset = 0 self._init = True GLib.idle_add(self.populate) grilo.changes_pending['Albums'] = False @log def _on_selection_mode_changed(self, widget, data=None): if (not self._header_bar.selection_mode and grilo.changes_pending['Albums']): self._on_changes_pending() @log def _setup_view(self, view_type): self._view = Gtk.FlowBox(homogeneous=True, hexpand=True, halign=Gtk.Align.FILL, valign=Gtk.Align.START, selection_mode=Gtk.SelectionMode.NONE, margin=18, row_spacing=12, column_spacing=6, min_children_per_line=1, max_children_per_line=25) self._view.get_style_context().add_class('content-view') self._view.connect('child-activated', self._on_child_activated) scrolledwin = Gtk.ScrolledWindow() scrolledwin.add(self._view) scrolledwin.show() self._box.add(scrolledwin) @log def _back_button_clicked(self, widget, data=None): self._header_bar.reset_header_title() self.set_visible_child(self._grid) @log def _on_child_activated(self, widget, child, user_data=None): item = child.media_item if self._star_handler.star_renderer_click: self._star_handler.star_renderer_click = False return # Toggle the selection when in selection mode if self.selection_mode: child.check.set_active(not child.check.get_active()) return title = utils.get_media_title(item) self._escaped_title = title self._artist = utils.get_artist_name(item) self._album_widget.update(self._artist, title, item, self._header_bar, self._selection_toolbar) self._header_bar.set_state(ToolbarState.CHILD_VIEW) self._header_bar.header_bar.set_title(self._escaped_title) self._header_bar.header_bar.sub_title = self._artist self.set_visible_child(self._album_widget) @log def update_title(self): self._header_bar.header_bar.set_title(self._escaped_title) self._header_bar.header_bar.sub_title = self._artist @log def populate(self): self._window.notifications_popup.push_loading() grilo.populate_albums(self._offset, self._add_item) @log def get_selected_songs(self, callback): # FIXME: we call into private objects with full knowledge of # what is there if self._header_bar._state == ToolbarState.CHILD_VIEW: callback(self._album_widget._disc_listbox.get_selected_items()) else: self.items_selected = [] self.items_selected_callback = callback self.albums_index = 0 if len(self.albums_selected): self._get_selected_album_songs() @log def _add_item(self, source, param, item, remaining=0, data=None): if item: # Store all items to optimize 'Select All' action self.all_items.append(item) # Add to the flowbox child = self._create_album_item(item) self._view.add(child) elif remaining == 0: self._view.show() # FIXME: To work around slow updating of the albumsview, # load album covers with a fixed delay. This results in a # quick first show with a placeholder cover and then a # reasonably responsive view while loading the actual # covers. while not self._queue.empty(): func, arg = self._queue.get() GLib.timeout_add(65 * self._queue.qsize(), func, arg, priority=GLib.PRIORITY_LOW) self._window.notifications_popup.pop_loading() def _create_album_item(self, item): artist = utils.get_artist_name(item) title = utils.get_media_title(item) builder = Gtk.Builder.new_from_resource( '/org/gnome/Music/AlbumCover.ui') child = Gtk.FlowBoxChild() child.get_style_context().add_class('tile') child.stack = builder.get_object('stack') child.check = builder.get_object('check') child.title = builder.get_object('title') child.subtitle = builder.get_object('subtitle') child.events = builder.get_object('events') child.media_item = item child.title.set_label(title) child.subtitle.set_label(artist) child.events.add_events(Gdk.EventMask.TOUCH_MASK) child.events.connect('button-release-event', self._on_album_event_triggered, child) child.check_handler_id = child.check.connect('notify::active', self._on_child_toggled, child) child.check.bind_property('visible', self, 'selection_mode', GObject.BindingFlags.BIDIRECTIONAL) child.add(builder.get_object('main_box')) child.show() cover_stack = CoverStack(child.stack, Art.Size.MEDIUM) self._queue.put((cover_stack.update, item)) return child @log def _on_album_event_triggered(self, evbox, event, child): if event.button is 3: self._on_selection_mode_request() if self.selection_mode: child.check.set_active(True) @log def _on_child_toggled(self, check, pspec, child): if (check.get_active() and child.media_item not in self.albums_selected): self.albums_selected.append(child.media_item) elif (not check.get_active() and child.media_item in self.albums_selected): self.albums_selected.remove(child.media_item) self.update_header_from_selection(len(self.albums_selected)) @log def _get_selected_album_songs(self): grilo.populate_album_songs(self.albums_selected[self.albums_index], self._add_selected_item) self.albums_index += 1 @log def _add_selected_item(self, source, param, item, remaining=0, data=None): if item: self.items_selected.append(item) if remaining == 0: if self.albums_index < len(self.albums_selected): self._get_selected_album_songs() else: self.items_selected_callback(self.items_selected) def _toggle_all_selection(self, selected): """ Selects or unselects all items without sending the notify::active signal for performance purposes. """ for child in self._view.get_children(): GObject.signal_handler_block(child.check, child.check_handler_id) # Set the checkbutton state without emiting the signal child.check.set_active(selected) GObject.signal_handler_unblock(child.check, child.check_handler_id) self.update_header_from_selection(len(self.albums_selected)) @log def select_all(self): self.albums_selected = list(self.all_items) self._toggle_all_selection(True) @log def unselect_all(self): self.albums_selected = [] self._toggle_all_selection(False)
class SearchView(BaseView): __gsignals__ = { 'no-music-found': (GObject.SignalFlags.RUN_FIRST, None, ()) } def __repr__(self): return '<SearchView>' @log def __init__(self, window, player): super().__init__('search', None, window, Gd.MainViewType.LIST) self._add_list_renderers() self.player = player self._head_iters = [None, None, None, None] self._filter_model = None self.previous_view = None self.connect('no-music-found', self._no_music_found_callback) self._albums_selected = [] self._albums = {} self._albums_index = 0 self._album_widget = AlbumWidget(player, self) self.add(self._album_widget) self._artists_albums_selected = [] self._artists_albums_index = 0 self._artists = {} self._artist_albums_widget = None self._view.get_generic_view().set_show_expanders(False) self._items_selected = [] self._items_selected_callback = None self._items_found = None @log def _no_music_found_callback(self, view): # FIXME: call into private members self._window._stack.set_visible_child_name('emptysearch') emptysearch = self._window._stack.get_child_by_name('emptysearch') emptysearch._artist_albums_widget = self._artist_albums_widget @log def _back_button_clicked(self, widget, data=None): self._header_bar.searchbar.reveal(True, False) if self.get_visible_child() == self._artist_albums_widget: self._artist_albums_widget.destroy() self._artist_albums_widget = None elif self.get_visible_child() == self._grid: self._window.views[View.ALBUM].set_visible_child( self._window.views[View.ALBUM]._grid) self.set_visible_child(self._grid) self._window.toolbar.set_state(ToolbarState.MAIN) @log def _on_item_activated(self, widget, id, path): if self._star_handler.star_renderer_click: self._star_handler.star_renderer_click = False return try: child_path = self._filter_model.convert_path_to_child_path(path) except TypeError: return _iter = self.model.get_iter(child_path) if self.model[_iter][11] == 'album': title = self.model[_iter][2] artist = self.model[_iter][3] item = self.model[_iter][5] self._album_widget.update( artist, title, item, self._header_bar, self._selection_toolbar) self._header_bar.set_state(ToolbarState.SEARCH_VIEW) self._header_bar.header_bar.set_title(title) self._header_bar.header_bar.sub_title = artist self.set_visible_child(self._album_widget) self._header_bar.searchbar.reveal(False) elif self.model[_iter][11] == 'artist': artist = self.model[_iter][2] albums = self._artists[artist.casefold()]['albums'] self._artist_albums_widget = ArtistAlbumsWidget( artist, albums, self.player, self._header_bar, self._selection_toolbar, self._window, True) self.add(self._artist_albums_widget) self._artist_albums_widget.show() self._header_bar.set_state(ToolbarState.SEARCH_VIEW) self._header_bar.header_bar.set_title(artist) self.set_visible_child(self._artist_albums_widget) self._header_bar.searchbar.reveal(False) elif self.model[_iter][11] == 'song': if self.model[_iter][12] != DiscoveryStatus.FAILED: c_iter = self._songs_model.convert_child_iter_to_iter(_iter)[1] self.player.set_playlist( 'Search Results', None, self._songs_model, c_iter, 5, 12) self.player.set_playing(True) else: # Headers if self._view.get_generic_view().row_expanded(path): self._view.get_generic_view().collapse_row(path) else: self._view.get_generic_view().expand_row(path, False) @log def _on_selection_mode_changed(self, widget, data=None): if (self._artist_albums_widget is not None and self.get_visible_child() == self._artist_albums_widget): self._artist_albums_widget.set_selection_mode( self._header_bar.selection_mode) @log def _add_search_item(self, source, param, item, remaining=0, data=None): if not item: if (grilo._search_callback_counter == 0 and grilo.search_source): self.emit('no-music-found') return if data != self.model: return artist = utils.get_artist_name(item) album = utils.get_album_title(item) key = '%s-%s' % (artist, album) if key not in self._albums: self._albums[key] = Grl.Media() self._albums[key].set_title(album) self._albums[key].add_artist(artist) self._albums[key].set_source(source.get_id()) self._albums[key].songs = [] self._add_item( source, None, self._albums[key], 0, [self.model, 'album']) self._add_item( source, None, self._albums[key], 0, [self.model, 'artist']) self._albums[key].songs.append(item) self._add_item(source, None, item, 0, [self.model, 'song']) @log def _retrieval_finished(self, klass, model, _iter): if not model[_iter][13]: return model[_iter][13] = klass.surface @log def _add_item(self, source, param, item, remaining=0, data=None): if data is None: return model, category = data self._items_found = ( self.model.iter_n_children(self._head_iters[0]) + self.model.iter_n_children(self._head_iters[1]) + self.model.iter_n_children(self._head_iters[2]) + self.model.iter_n_children(self._head_iters[3]) ) if (category == 'song' and self._items_found == 0 and remaining == 0): if grilo.search_source: self.emit('no-music-found') # We need to remember the view before the search view emptysearchview = self._window.views[View.EMPTY_SEARCH] if (self._window.curr_view != emptysearchview and self._window.prev_view != emptysearchview): self.previous_view = self._window.prev_view if remaining == 0: self._window.notifications_popup.pop_loading() self._view.show() if not item or model != self.model: return self._offset += 1 title = utils.get_media_title(item) item.set_title(title) artist = utils.get_artist_name(item) group = 3 try: group = {'album': 0, 'artist': 1, 'song': 2}[category] except: pass _iter = None if category == 'album': _iter = self.model.insert_with_values( self._head_iters[group], -1, [0, 2, 3, 5, 9, 11], [str(item.get_id()), title, artist, item, 2, category]) elif category == 'song': # FIXME: source specific hack if source.get_id() != 'grl-tracker-source': fav = 2 else: fav = item.get_favourite() _iter = self.model.insert_with_values( self._head_iters[group], -1, [0, 2, 3, 5, 9, 11], [str(item.get_id()), title, artist, item, fav, category]) else: if not artist.casefold() in self._artists: _iter = self.model.insert_with_values( self._head_iters[group], -1, [0, 2, 5, 9, 11], [str(item.get_id()), artist, item, 2, category]) self._artists[artist.casefold()] = { 'iter': _iter, 'albums': [] } self._artists[artist.casefold()]['albums'].append(item) # FIXME: Figure out why iter can be None here, seems illogical. if _iter is not None: scale = self._view.get_scale_factor() art = Art(Art.Size.SMALL, item, scale) self.model[_iter][13] = art.surface art.connect( 'finished', self._retrieval_finished, self.model, _iter) art.lookup() if self.model.iter_n_children(self._head_iters[group]) == 1: path = self.model.get_path(self._head_iters[group]) path = self._filter_model.convert_child_path_to_path(path) self._view.get_generic_view().expand_row(path, False) @log def _add_list_renderers(self): list_widget = self._view.get_generic_view() list_widget.set_halign(Gtk.Align.CENTER) list_widget.set_size_request(530, -1) cols = list_widget.get_columns() title_renderer = Gtk.CellRendererText( xpad=12, xalign=0.0, yalign=0.5, height=32, ellipsize=Pango.EllipsizeMode.END, weight=Pango.Weight.BOLD) list_widget.add_renderer( title_renderer, self._on_list_widget_title_render, None) cols[0].add_attribute(title_renderer, 'text', 2) # Add our own surface renderer, instead of the one provided by # Gd. This avoids us having to set the model to a cairo.Surface # which is currently not a working solution in pygobject. # https://gitlab.gnome.org/GNOME/pygobject/issues/155 pixbuf_renderer = Gtk.CellRendererPixbuf( xalign=0.5, yalign=0.5, xpad=12, ypad=2) list_widget.add_renderer( pixbuf_renderer, self._on_list_widget_pixbuf_renderer, None) cols[0].add_attribute(pixbuf_renderer, 'surface', 13) self._star_handler.add_star_renderers(list_widget, cols[0]) cells = cols[0].get_cells() # With the bugfix in libgd 9117650bda, the search results # stopped aligning at the top. With the artists results not # having a second line of text, this looks off. # Revert to old behaviour by forcing the alignment to be top. # FIXME: Revisit this when rewriting the searchview. gd_twolines_renderer = cells[2] gd_twolines_renderer.props.yalign = 0 cols[0].reorder(cells[0], -1) cols[0].reorder(cells[4], 1) cols[0].set_cell_data_func( cells[0], self._on_list_widget_selection_render, None) @log def _on_list_widget_pixbuf_renderer(self, col, cell, model, _iter, data): if not model[_iter][13]: return cell.set_property("surface", model[_iter][13]) @log def _on_list_widget_selection_render(self, col, cell, model, _iter, data): if (self._view.get_selection_mode() and model.iter_parent(_iter) is not None): cell.set_visible(True) else: cell.set_visible(False) @log def _on_list_widget_title_render(self, col, cell, model, _iter, data): cells = col.get_cells() cells[0].set_visible(False) cells[1].set_visible(model.iter_parent(_iter) is not None) cells[2].set_visible(model.iter_parent(_iter) is not None) cells[3].set_visible(model.iter_parent(_iter) is None) @log def populate(self): self._init = True self._window.notifications_popup.push_loading() self._header_bar.set_state(ToolbarState.MAIN) @log def get_selected_songs(self, callback): if self.get_visible_child() == self._album_widget: callback(self._album_widget.view.get_selected_items()) elif self.get_visible_child() == self._artist_albums_widget: items = [] # FIXME: calling into private model for row in self._artist_albums_widget._model: if row[6]: items.append(row[5]) callback(items) else: self._items_selected = [] self._items_selected_callback = callback self._get_selected_albums() @log def _get_selected_albums(self): paths = [ self._filter_model.convert_path_to_child_path(path) for path in self._view.get_selection()] self._albums_selected = [ self.model[child_path][5] for child_path in paths if self.model[child_path][11] == 'album'] if len(self._albums_selected): self._get_selected_albums_songs() else: self._get_selected_artists() @log def _get_selected_albums_songs(self): grilo.populate_album_songs( self._albums_selected[self._albums_index], self._add_selected_albums_songs) self._albums_index += 1 @log def _add_selected_albums_songs( self, source, param, item, remaining=0, data=None): if item: self._items_selected.append(item) if remaining == 0: if self._albums_index < len(self._albums_selected): self._get_selected_albums_songs() else: self._get_selected_artists() @log def _get_selected_artists(self): artists_selected = [ self._artists[self.model[child_path][2].casefold()] for child_path in [ self._filter_model.convert_path_to_child_path(path) for path in self._view.get_selection()] if self.model[child_path][11] == 'artist'] self._artists_albums_selected = [] for artist in artists_selected: self._artists_albums_selected.extend(artist['albums']) if len(self._artists_albums_selected): self._get_selected_artists_albums_songs() else: self._get_selected_songs() @log def _get_selected_artists_albums_songs(self): grilo.populate_album_songs( self._artists_albums_selected[self._artists_albums_index], self._add_selected_artists_albums_songs) self._artists_albums_index += 1 @log def _add_selected_artists_albums_songs( self, source, param, item, remaining=0, data=None): if item: self._items_selected.append(item) if remaining == 0: artist_albums = len(self._artists_albums_selected) if self._artists_albums_index < artist_albums: self._get_selected_artists_albums_songs() else: self._get_selected_songs() @log def _get_selected_songs(self): self._items_selected.extend([ self.model[child_path][5] for child_path in [ self._filter_model.convert_path_to_child_path(path) for path in self._view.get_selection()] if self.model[child_path][11] == 'song']) self._items_selected_callback(self._items_selected) @log def _filter_visible_func(self, model, _iter, data=None): top_level = model.iter_parent(_iter) is None visible = (not top_level or model.iter_has_child(_iter)) return visible @log def set_search_text(self, search_term, fields_filter): query_matcher = { 'album': { 'search_all': Query.get_albums_with_any_match, 'search_artist': Query.get_albums_with_artist_match, 'search_composer': Query.get_albums_with_composer_match, 'search_album': Query.get_albums_with_album_match, 'search_track': Query.get_albums_with_track_match, }, 'artist': { 'search_all': Query.get_artists_with_any_match, 'search_artist': Query.get_artists_with_artist_match, 'search_composer': Query.get_artists_with_composer_match, 'search_album': Query.get_artists_with_album_match, 'search_track': Query.get_artists_with_track_match, }, 'song': { 'search_all': Query.get_songs_with_any_match, 'search_artist': Query.get_songs_with_artist_match, 'search_composer': Query.get_songs_with_composer_match, 'search_album': Query.get_songs_with_album_match, 'search_track': Query.get_songs_with_track_match, }, } self.model = Gtk.TreeStore( GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING, # item title or header text GObject.TYPE_STRING, # artist for albums and songs GdkPixbuf.Pixbuf, # Gd placeholder album art GObject.TYPE_OBJECT, # item GObject.TYPE_BOOLEAN, GObject.TYPE_INT, GObject.TYPE_STRING, GObject.TYPE_INT, GObject.TYPE_BOOLEAN, GObject.TYPE_STRING, # type GObject.TYPE_INT, object # album art surface ) self._filter_model = self.model.filter_new(None) self._filter_model.set_visible_func(self._filter_visible_func) self._view.set_model(self._filter_model) self._albums = {} self._artists = {} if search_term == "": return albums_iter = self.model.insert_with_values( None, -1, [2, 9], [_("Albums"), 2]) artists_iter = self.model.insert_with_values( None, -1, [2, 9], [_("Artists"), 2]) songs_iter = self.model.insert_with_values( None, -1, [2, 9], [_("Songs"), 2]) playlists_iter = self.model.insert_with_values( None, -1, [2, 9], [_("Playlists"), 2]) self._head_iters = [ albums_iter, artists_iter, songs_iter, playlists_iter ] self._songs_model = self.model.filter_new( self.model.get_path(songs_iter)) # Use queries for Tracker if (not grilo.search_source or grilo.search_source.get_id() == 'grl-tracker-source'): for category in ('album', 'artist', 'song'): query = query_matcher[category][fields_filter](search_term) grilo.populate_custom_query( query, self._add_item, -1, [self.model, category]) if (not grilo.search_source or grilo.search_source.get_id() != 'grl-tracker-source'): # nope, can't do - reverting to Search grilo.search(search_term, self._add_search_item, self.model)
class SearchView(BaseView): search_state = GObject.Property(type=int, default=Search.State.NONE) def __repr__(self): return '<SearchView>' @log def __init__(self, window, player): self._coremodel = window._app.props.coremodel self._model = self._coremodel.props.songs_search self._album_model = self._coremodel.props.albums_search self._artist_model = self._coremodel.props.artists_search super().__init__('search', None, window) self.player = player self.previous_view = None self._album_widget = AlbumWidget(player, self) self._album_widget.bind_property("selection-mode", self, "selection-mode", GObject.BindingFlags.BIDIRECTIONAL) self.add(self._album_widget) self._artist_albums_widget = None self._search_mode_active = False # self.connect("notify::search-state", self._on_search_state_changed) @log def _setup_view(self): view_container = Gtk.ScrolledWindow(hexpand=True, vexpand=True) self._box.pack_start(view_container, True, True, 0) self._songs_listbox = Gtk.ListBox() self._songs_listbox.bind_model(self._model, self._create_song_widget) self._album_flowbox = Gtk.FlowBox( homogeneous=True, hexpand=True, halign=Gtk.Align.FILL, valign=Gtk.Align.START, selection_mode=Gtk.SelectionMode.NONE, margin=18, row_spacing=12, column_spacing=6, min_children_per_line=1, max_children_per_line=20, visible=True) self._album_flowbox.get_style_context().add_class('content-view') self._album_flowbox.bind_model(self._album_model, self._create_album_widget) self._album_flowbox.connect("child-activated", self._on_album_activated) self._artist_listbox = Gtk.ListBox() self._artist_listbox.bind_model(self._artist_model, self._create_artist_widget) self._all_results_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self._all_results_box.pack_start(self._album_flowbox, True, True, 0) self._all_results_box.pack_start(self._artist_listbox, True, True, 0) self._all_results_box.pack_start(self._songs_listbox, True, True, 0) # self._ctrl = Gtk.GestureMultiPress().new(self._view) # self._ctrl.props.propagation_phase = Gtk.PropagationPhase.CAPTURE # self._ctrl.connect("released", self._on_view_clicked) view_container.add(self._all_results_box) self._box.show_all() def _create_song_widget(self, coresong): song_widget = SongWidget(coresong) self.bind_property( "selection-mode", song_widget, "selection-mode", GObject.BindingFlags.BIDIRECTIONAL | GObject.BindingFlags.SYNC_CREATE) song_widget.connect('button-release-event', self._song_activated) song_widget.show_all() return song_widget def _create_album_widget(self, corealbum): album_widget = AlbumCover(corealbum) self.bind_property( "selection-mode", album_widget, "selection-mode", GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL) # NOTE: Adding SYNC_CREATE here will trigger all the nested # models to be created. This will slow down initial start, # but will improve initial 'selecte all' speed. album_widget.bind_property("selected", corealbum, "selected", GObject.BindingFlags.BIDIRECTIONAL) return album_widget def _create_artist_widget(self, coreartist): artist_tile = ArtistTile(coreartist) artist_tile.props.text = coreartist.props.artist artist_tile.connect('button-release-event', self._artist_activated) self.bind_property( "selection-mode", artist_tile, "selection-mode", GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL) return artist_tile def _song_activated(self, widget, event): mod_mask = Gtk.accelerator_get_default_mod_mask() if ((event.get_state() & mod_mask) == Gdk.ModifierType.CONTROL_MASK and not self.props.selection_mode): self.props.selection_mode = True return (_, button) = event.get_button() if (button == Gdk.BUTTON_PRIMARY and not self.props.selection_mode): # self.emit('song-activated', widget) self._coremodel.set_playlist_model( PlayerPlaylist.Type.SEARCH_RESULT, self._model) self.player.play(widget.props.coresong) # FIXME: Need to ignore the event from the checkbox. # if self.props.selection_mode: # widget.props.selected = not widget.props.selected return True def _on_album_activated(self, widget, child, user_data=None): corealbum = child.props.corealbum if self.props.selection_mode: return # Update and display the album widget if not in selection mode self._album_widget.update(corealbum) self._headerbar.props.state = HeaderBar.State.SEARCH self._headerbar.props.title = corealbum.props.title self._headerbar.props.subtitle = corealbum.props.artist self.props.search_mode_active = False self.set_visible_child(self._album_widget) def _artist_activated(self, widget, event): coreartist = widget.coreartist mod_mask = Gtk.accelerator_get_default_mod_mask() if ((event.get_state() & mod_mask) == Gdk.ModifierType.CONTROL_MASK and not self.props.selection_mode): self.props.selection_mode = True return (_, button) = event.get_button() if (button == Gdk.BUTTON_PRIMARY and not self.props.selection_mode): # self.emit('song-activated', widget) self._artist_albums_widget = ArtistAlbumsWidget( coreartist, self.player, self._window, False) self.add(self._artist_albums_widget) self._artist_albums_widget.show() self.bind_property('selection-mode', self._artist_albums_widget, 'selection-mode', GObject.BindingFlags.BIDIRECTIONAL) self._headerbar.props.state = HeaderBar.State.SEARCH self._headerbar.props.title = coreartist.artist self._headerbar.props.subtitle = None self.set_visible_child(self._artist_albums_widget) self.props.search_mode_active = False # FIXME: Need to ignore the event from the checkbox. # if self.props.selection_mode: # widget.props.selected = not widget.props.selected return True def _child_select(self, child, value): widget = child.get_child() widget.props.selected = value def _select_all(self, value): with self._model.freeze_notify(): def song_select(child): song_widget = child.get_child() song_widget.props.selected = value def album_select(child): child.props.selected = value def artist_select(child): artist_widget = child.get_child() artist_widget.props.selected = value self._songs_listbox.foreach(song_select) self._album_flowbox.foreach(album_select) self._artist_listbox.foreach(artist_select) def select_all(self): self._select_all(True) def unselect_all(self): self._select_all(False) @log def _back_button_clicked(self, widget, data=None): if self.get_visible_child() == self._artist_albums_widget: self._artist_albums_widget.destroy() self._artist_albums_widget = None elif self.get_visible_child() == self._grid: self._window.views[View.ALBUM].set_visible_child( self._window.views[View.ALBUM]._grid) self.set_visible_child(self._grid) self.props.search_mode_active = True self._headerbar.props.state = HeaderBar.State.MAIN @log def _on_view_clicked(self, gesture, n_press, x, y): """Ctrl+click on self._view triggers selection mode.""" _, state = Gtk.get_current_event_state() modifiers = Gtk.accelerator_get_default_mod_mask() if (state & modifiers == Gdk.ModifierType.CONTROL_MASK and not self.props.selection_mode): self.props.selection_mode = True if (self.selection_mode and not self._star_handler.star_renderer_click): path, col, cell_x, cell_y = self._view.get_path_at_pos(x, y) iter_ = self.model.get_iter(path) self.model[iter_][6] = not self.model[iter_][6] selected_iters = self._get_selected_iters() self.props.selected_items_count = len(selected_iters) @log def _on_selection_mode_changed(self, widget, data=None): super()._on_selection_mode_changed(widget, data) @log def _on_search_state_changed(self, klass, param): # If a search is triggered when selection mode is activated, # reset the number of selected items. if (self.props.selection_mode and self.props.search_state != Search.State.NONE): self.props.selected_items_count = 0 @GObject.Property(type=bool, default=False) def search_mode_active(self): """Get search mode status. :returns: the search mode status :rtype: bool """ return self._search_mode_active @search_mode_active.setter def search_mode_active(self, value): """Set search mode status. :param bool mode: new search mode """ # FIXME: search_mode_active should not change search_state. # This is necessary because Search state cannot interact with # the child views. self._search_mode_active = value if (not self._search_mode_active and self.get_visible_child() == self._grid): self.props.search_state = Search.State.NONE @log def _populate(self, data=None): self._init = True self._headerbar.props.state = HeaderBar.State.MAIN
class AlbumsView(BaseView): search_mode_active = GObject.Property(type=bool, default=False) def __repr__(self): return '<AlbumsView>' @log def __init__(self, window, player): super().__init__('albums', _("Albums"), window) self.player = player self._album_widget = AlbumWidget(player) self._album_widget.bind_property( "selection-mode", self, "selection-mode", GObject.BindingFlags.BIDIRECTIONAL) self._album_widget.bind_property( "selected-items-count", self, "selected-items-count") self.add(self._album_widget) self.albums_selected = [] self.all_items = [] self.items_selected = [] self.items_selected_callback = None self.connect( "notify::search-mode-active", self._on_search_mode_changed) @log def _on_changes_pending(self, data=None): if (self._init and not self.props.selection_mode): self._offset = 0 self._populate() grilo.changes_pending['Albums'] = False @log def _on_selection_mode_changed(self, widget, data=None): super()._on_selection_mode_changed(widget, data) if (not self.props.selection_mode and grilo.changes_pending['Albums']): self._on_changes_pending() @log def _on_search_mode_changed(self, klass, param): if (not self.props.search_mode_active and self._headerbar.props.stack.props.visible_child == self and self.get_visible_child() == self._album_widget): self._set_album_headerbar(self._album_widget.props.album) @log def _setup_view(self): self._view = Gtk.FlowBox( homogeneous=True, hexpand=True, halign=Gtk.Align.FILL, valign=Gtk.Align.START, selection_mode=Gtk.SelectionMode.NONE, margin=18, row_spacing=12, column_spacing=6, min_children_per_line=1, max_children_per_line=20) self._view.get_style_context().add_class('content-view') self._view.connect('child-activated', self._on_child_activated) scrolledwin = Gtk.ScrolledWindow() scrolledwin.add(self._view) scrolledwin.show() self._box.add(scrolledwin) @log def _back_button_clicked(self, widget, data=None): self._headerbar.state = HeaderBar.State.MAIN self.set_visible_child(self._grid) @log def _on_child_activated(self, widget, child, user_data=None): if self.props.selection_mode: return item = child.props.media # Update and display the album widget if not in selection mode self._album_widget.update(item) self._set_album_headerbar(item) self.set_visible_child(self._album_widget) @log def _set_album_headerbar(self, album): self._headerbar.props.state = HeaderBar.State.CHILD self._headerbar.props.title = utils.get_album_title(album) self._headerbar.props.subtitle = utils.get_artist_name(album) @log def _populate(self, data=None): self._window.notifications_popup.push_loading() grilo.populate_albums(self._offset, self._add_item) self._init = True @log def get_selected_songs(self, callback): # FIXME: we call into private objects with full knowledge of # what is there if self._headerbar.props.state == HeaderBar.State.CHILD: callback(self._album_widget._disc_listbox.get_selected_items()) else: self.items_selected = [] self.items_selected_callback = callback self.albums_index = 0 if len(self.albums_selected): self._get_selected_album_songs() @log def _add_item(self, source, param, item, remaining=0, data=None): if item: # Store all items to optimize 'Select All' action self.all_items.append(item) # Add to the flowbox child = self._create_album_item(item) self._view.add(child) self._offset += 1 elif remaining == 0: self._view.show() self._window.notifications_popup.pop_loading() self._init = False def _create_album_item(self, item): child = AlbumCover(item) child.connect('notify::selected', self._on_selection_changed) self.bind_property( 'selection-mode', child, 'selection-mode', GObject.BindingFlags.BIDIRECTIONAL) return child @log def _on_selection_changed(self, child, data=None): if (child.props.selected and child.props.media not in self.albums_selected): self.albums_selected.append(child.props.media) elif (not child.props.selected and child.props.media in self.albums_selected): self.albums_selected.remove(child.props.media) self.props.selected_items_count = len(self.albums_selected) @log def _get_selected_album_songs(self): grilo.populate_album_songs( self.albums_selected[self.albums_index], self._add_selected_item) self.albums_index += 1 @log def _add_selected_item(self, source, param, item, remaining=0, data=None): if item: self.items_selected.append(item) if remaining == 0: if self.albums_index < self.props.selected_items_count: self._get_selected_album_songs() else: self.items_selected_callback(self.items_selected) def _toggle_all_selection(self, selected): """ Selects or unselects all items without sending the notify::active signal for performance purposes. """ for child in self._view.get_children(): child.props.selected = selected @log def select_all(self): self.albums_selected = list(self.all_items) self._toggle_all_selection(True) @log def unselect_all(self): self.albums_selected = [] self._toggle_all_selection(False)
class AlbumsView(Gtk.Stack): """Gridlike view of all albums Album activation switches to AlbumWidget. """ __gtype_name__ = "AlbumsView" search_mode_active = GObject.Property(type=bool, default=False) selection_mode = GObject.Property(type=bool, default=False) title = GObject.Property(type=str, default=_("Albums"), flags=GObject.ParamFlags.READABLE) _scrolled_window = Gtk.Template.Child() _flowbox = Gtk.Template.Child() _flowbox_long_press = Gtk.Template.Child() def __init__(self, application): """Initialize AlbumsView :param application: The Application object """ super().__init__(transition_type=Gtk.StackTransitionType.CROSSFADE) self.props.name = "albums" self._application = application self._window = application.props.window self._headerbar = self._window._headerbar self._adjustment_timeout_id = 0 self._viewport = self._scrolled_window.get_child() self._widget_counter = 1 self._ctrl_hold = False model = self._application.props.coremodel.props.albums_sort self._flowbox.bind_model(model, self._create_widget) self._flowbox.set_hadjustment(self._scrolled_window.get_hadjustment()) self._flowbox.set_vadjustment(self._scrolled_window.get_vadjustment()) self._flowbox.connect("child-activated", self._on_child_activated) self.bind_property("selection-mode", self._window, "selection-mode", GObject.BindingFlags.DEFAULT) self._window.connect("notify::selection-mode", self._on_selection_mode_changed) self._album_widget = AlbumWidget(self._application) self._album_widget.bind_property("selection-mode", self, "selection-mode", GObject.BindingFlags.BIDIRECTIONAL) self.add(self._album_widget) self.connect("notify::search-mode-active", self._on_search_mode_changed) self._scrolled_window.props.vadjustment.connect( "value-changed", self._on_vadjustment_changed) self._scrolled_window.props.vadjustment.connect( "changed", self._on_vadjustment_changed) def _on_vadjustment_changed(self, adjustment): if self._adjustment_timeout_id != 0: GLib.source_remove(self._adjustment_timeout_id) self._adjustment_timeout_id = 0 self._adjustment_timeout_id = GLib.timeout_add( 200, self._retrieve_covers, adjustment.props.value, priority=GLib.PRIORITY_LOW) def _retrieve_covers(self, old_adjustment): adjustment = self._scrolled_window.props.vadjustment.props.value if old_adjustment != adjustment: return GLib.SOURCE_CONTINUE first_cover = self._flowbox.get_child_at_index(0) if first_cover is None: return GLib.SOURCE_REMOVE cover_size, _ = first_cover.get_allocated_size() if cover_size.width == 0 or cover_size.height == 0: return GLib.SOURCE_REMOVE viewport_size, _ = self._viewport.get_allocated_size() h_space = self._flowbox.get_column_spacing() v_space = self._flowbox.get_row_spacing() nr_cols = ((viewport_size.width + h_space) // (cover_size.width + h_space)) top_left_cover = self._flowbox.get_child_at_index( nr_cols * (adjustment // (cover_size.height + v_space))) covers_col = math.ceil(viewport_size.width / cover_size.width) covers_row = math.ceil(viewport_size.height / cover_size.height) children = self._flowbox.get_children() retrieve_list = [] for i, albumcover in enumerate(children): if top_left_cover == albumcover: retrieve_covers = covers_row * covers_col retrieve_list = children[i:i + retrieve_covers] break for albumcover in retrieve_list: albumcover.retrieve() self._adjustment_timeout_id = 0 return GLib.SOURCE_REMOVE def _on_selection_mode_changed(self, widget, data=None): selection_mode = self._window.props.selection_mode if (selection_mode == self.props.selection_mode or self.get_parent().get_visible_child() != self): return self.props.selection_mode = selection_mode if not self.props.selection_mode: self.deselect_all() def _on_search_mode_changed(self, klass, param): if (not self.props.search_mode_active and self._headerbar.props.stack.props.visible_child == self and self.get_visible_child() == self._album_widget): self._set_album_headerbar(self._album_widget.props.album) def _create_widget(self, corealbum): album_widget = AlbumCover(corealbum) self.bind_property( "selection-mode", album_widget, "selection-mode", GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL) # NOTE: Adding SYNC_CREATE here will trigger all the nested # models to be created. This will slow down initial start, # but will improve initial 'select all' speed. album_widget.bind_property("selected", corealbum, "selected", GObject.BindingFlags.BIDIRECTIONAL) GLib.timeout_add(self._widget_counter * 250, album_widget.retrieve, priority=GLib.PRIORITY_LOW) self._widget_counter = self._widget_counter + 1 return album_widget def _back_button_clicked(self, widget, data=None): self._headerbar.state = HeaderBar.State.MAIN self.props.visible_child = self._scrolled_window def _on_child_activated(self, widget, child, user_data=None): corealbum = child.props.corealbum if self.props.selection_mode: return # Update and display the album widget if not in selection mode self._album_widget.update(corealbum) self._set_album_headerbar(corealbum) self.set_visible_child(self._album_widget) def _set_album_headerbar(self, corealbum): self._headerbar.props.state = HeaderBar.State.CHILD self._headerbar.props.title = corealbum.props.title self._headerbar.props.subtitle = corealbum.props.artist @Gtk.Template.Callback() def _on_flowbox_press_begin(self, gesture, sequence): event = gesture.get_last_event(sequence) ok, state = event.get_state() if ((ok is True and state == Gdk.ModifierType.CONTROL_MASK) or self.props.selection_mode is True): self._flowbox.props.selection_mode = Gtk.SelectionMode.MULTIPLE if state == Gdk.ModifierType.CONTROL_MASK: self._ctrl_hold = True @Gtk.Template.Callback() def _on_flowbox_press_cancel(self, gesture, sequence): self._flowbox.props.selection_mode = Gtk.SelectionMode.NONE @Gtk.Template.Callback() def _on_selected_children_changed(self, flowbox): if self._flowbox.props.selection_mode == Gtk.SelectionMode.NONE: return if self.props.selection_mode is False: self.props.selection_mode = True rubberband_selection = len(self._flowbox.get_selected_children()) > 1 with self._application.props.coreselection.freeze_notify(): if (rubberband_selection and not self._ctrl_hold): self.deselect_all() for child in self._flowbox.get_selected_children(): if (self._ctrl_hold is True or not rubberband_selection): child.props.selected = not child.props.selected else: child.props.selected = True self._ctrl_hold = False self._flowbox.props.selection_mode = Gtk.SelectionMode.NONE def _toggle_all_selection(self, selected): """Selects or deselects all items. """ with self._application.props.coreselection.freeze_notify(): if self.get_visible_child() == self._album_widget: if selected is True: self._album_widget.select_all() else: self._album_widget.deselect_all() else: for child in self._flowbox.get_children(): child.props.selected = selected def select_all(self): self._toggle_all_selection(True) def deselect_all(self): self._toggle_all_selection(False)
class AlbumsView(Gtk.Stack): """Gridlike view of all albums Album activation switches to AlbumWidget. """ __gtype_name__ = "AlbumsView" search_mode_active = GObject.Property(type=bool, default=False) selected_items_count = GObject.Property(type=int, default=0, minimum=0) selection_mode = GObject.Property(type=bool, default=False) _all_albums = Gtk.Template.Child() _flowbox = Gtk.Template.Child() def __repr__(self): return '<AlbumsView>' def __init__(self, application, player=None): """Initialize AlbumsView :param application: The Application object """ super().__init__(transition_type=Gtk.StackTransitionType.CROSSFADE) # FIXME: Make these properties. self.name = "albums" self.title = _("Albums") self._window = application.props.window self._headerbar = self._window._headerbar model = self._window._app.props.coremodel.props.albums_sort self._flowbox.bind_model(model, self._create_widget) self._flowbox.connect("child-activated", self._on_child_activated) self.connect("notify::selection-mode", self._on_selection_mode_changed) self.bind_property('selection-mode', self._window, 'selection-mode', GObject.BindingFlags.BIDIRECTIONAL) self._album_widget = AlbumWidget(application.props.player, self) self._album_widget.bind_property("selection-mode", self, "selection-mode", GObject.BindingFlags.BIDIRECTIONAL) self.add(self._album_widget) self.connect("notify::search-mode-active", self._on_search_mode_changed) self.show_all() def _on_selection_mode_changed(self, widget, data=None): if not self.props.selection_mode: self.unselect_all() def _on_search_mode_changed(self, klass, param): if (not self.props.search_mode_active and self._headerbar.props.stack.props.visible_child == self and self.get_visible_child() == self._album_widget): self._set_album_headerbar(self._album_widget.props.album) def _create_widget(self, corealbum): album_widget = AlbumCover(corealbum) self.bind_property( "selection-mode", album_widget, "selection-mode", GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL) # NOTE: Adding SYNC_CREATE here will trigger all the nested # models to be created. This will slow down initial start, # but will improve initial 'selecte all' speed. album_widget.bind_property("selected", corealbum, "selected", GObject.BindingFlags.BIDIRECTIONAL) return album_widget def _back_button_clicked(self, widget, data=None): self._headerbar.state = HeaderBar.State.MAIN self.props.visible_child = self._all_albums def _on_child_activated(self, widget, child, user_data=None): corealbum = child.props.corealbum if self.props.selection_mode: return # Update and display the album widget if not in selection mode self._album_widget.update(corealbum) self._set_album_headerbar(corealbum) self.set_visible_child(self._album_widget) def _set_album_headerbar(self, corealbum): self._headerbar.props.state = HeaderBar.State.CHILD self._headerbar.props.title = corealbum.props.title self._headerbar.props.subtitle = corealbum.props.artist def _toggle_all_selection(self, selected): """ Selects or unselects all items without sending the notify::active signal for performance purposes. """ with self._window._app.props.coreselection.freeze_notify(): for child in self._flowbox.get_children(): child.props.selected = selected child.props.corealbum.props.selected = selected def select_all(self): self._toggle_all_selection(True) def unselect_all(self): self._toggle_all_selection(False)