diff options
Diffstat (limited to 'gnomemusic/views/albumsview.py')
-rw-r--r-- | gnomemusic/views/albumsview.py | 281 |
1 files changed, 138 insertions, 143 deletions
diff --git a/gnomemusic/views/albumsview.py b/gnomemusic/views/albumsview.py index 36978a8e..44123267 100644 --- a/gnomemusic/views/albumsview.py +++ b/gnomemusic/views/albumsview.py @@ -22,14 +22,20 @@ # code, but you are not obligated to do so. If you do not wish to do so, # delete this exception statement from your version. -import math +from __future__ import annotations +import typing from gettext import gettext as _ -from gi.repository import Gdk, GLib, GObject, Gtk +from gi.repository import GObject, Gtk +from typing import Dict, List +from gnomemusic.coverpaintable import CoverPaintable +from gnomemusic.utils import ArtSize, DefaultIconType from gnomemusic.widgets.headerbar import HeaderBar -from gnomemusic.widgets.albumcover import AlbumCover from gnomemusic.widgets.albumwidget import AlbumWidget +if typing.TYPE_CHECKING: + from gnomemusic.application import Application + from gnomemusic.corealbum import CoreAlbum @Gtk.Template(resource_path="/org/gnome/Music/ui/AlbumsView.ui") @@ -51,10 +57,9 @@ class AlbumsView(Gtk.Stack): _album_scrolled_window = Gtk.Template.Child() _scrolled_window = Gtk.Template.Child() - _flowbox = Gtk.Template.Child() - _flowbox_long_press = Gtk.Template.Child() + _gridview = Gtk.Template.Child() - def __init__(self, application): + def __init__(self, application: Application) -> None: """Initialize AlbumsView :param application: The Application object @@ -65,18 +70,33 @@ class AlbumsView(Gtk.Stack): 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._headerbar = self._window.props.headerbar + self._list_item_bindings: Dict[ + Gtk.ListItem, List[GObject.Binding]] = {} + self._list_item_star_controllers: Dict[ + Gtk.ListItem, List[GObject.Binding]] = {} + + list_item_factory = Gtk.SignalListItemFactory() + list_item_factory.connect("setup", self._setup_list_item) + list_item_factory.connect("bind", self._bind_list_item) + list_item_factory.connect("unbind", self._unbind_list_item) + + self._gridview.props.factory = list_item_factory + + self._selection_model = Gtk.MultiSelection.new( + self._application.props.coremodel.props.albums_sort) + self._gridview.props.model = self._selection_model + + self._gridview.connect("activate", self._on_album_activated) + + self.bind_property( + "selection-mode", self._gridview, "single-click-activate", + GObject.BindingFlags.SYNC_CREATE + | GObject.BindingFlags.INVERT_BOOLEAN) + self.bind_property( + "selection-mode", self._gridview, "enable-rubberband", + GObject.BindingFlags.SYNC_CREATE) self.bind_property( "selection-mode", self._window, "selection-mode", GObject.BindingFlags.DEFAULT) @@ -89,67 +109,12 @@ class AlbumsView(Gtk.Stack): "selection-mode", self, "selection-mode", GObject.BindingFlags.BIDIRECTIONAL) - self._album_scrolled_window.add(self._album_widget) + viewport = self._album_scrolled_window.get_first_child() + viewport.set_child(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_DEFAULT - 10) - - 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 @@ -159,40 +124,24 @@ class AlbumsView(Gtk.Stack): self.props.selection_mode = selection_mode if not self.props.selection_mode: self.deselect_all() - self._flowbox.props.selection_mode = Gtk.SelectionMode.NONE 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): + and self.get_visible_child_name() == "widget"): self._set_album_headerbar(self._album_widget.props.corealbum) - 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_album_activated(self, widget, position): + corealbum = widget.props.model[position] + + self._album_widget.props.corealbum = corealbum + self._set_album_headerbar(corealbum) + self.props.visible_child = self._album_scrolled_window + def _on_child_activated(self, widget, child, user_data=None): corealbum = child.props.corealbum if self.props.selection_mode: @@ -202,66 +151,112 @@ class AlbumsView(Gtk.Stack): self._album_widget.props.corealbum = corealbum self._set_album_headerbar(corealbum) - self.set_visible_child(self._album_scrolled_window) + self.set_visible_child_name("widget") - def _set_album_headerbar(self, corealbum): + def _set_album_headerbar(self, corealbum: CoreAlbum) -> None: 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 + self._headerbar.set_label_title( + corealbum.props.title, corealbum.props.artist) 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 self.get_visible_child_name() == "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 + if selected: + self._selection_model.select_all() + else: + self._selection_model.unselect_all() def select_all(self): self._toggle_all_selection(True) def deselect_all(self): self._toggle_all_selection(False) + + def _setup_list_item( + self, factory: Gtk.SignalListItemFactory, + list_item: Gtk.ListItem) -> None: + builder = Gtk.Builder.new_from_resource( + "/org/gnome/Music/ui/AlbumCoverListItem.ui") + list_item.props.child = builder.get_object("_album_cover") + cover_image = list_item.props.child.get_first_child().get_first_child() + cover_image.set_size_request( + ArtSize.MEDIUM.width, ArtSize.MEDIUM.height) + cover_image.props.paintable = CoverPaintable( + self, ArtSize.MEDIUM, icon_type=DefaultIconType.ALBUM) + + self.bind_property( + "selection-mode", list_item, "selectable", + GObject.BindingFlags.SYNC_CREATE) + self.bind_property( + "selection-mode", list_item, "activatable", + GObject.BindingFlags.SYNC_CREATE + | GObject.BindingFlags.INVERT_BOOLEAN) + + def _bind_list_item( + self, factory: Gtk.SignalListItemFactory, + list_item: Gtk.ListItem) -> None: + album_cover = list_item.props.child + corealbum = list_item.props.item + + cover_image = album_cover.get_first_child().get_first_child() + check = cover_image.get_next_sibling() + album_label = album_cover.get_first_child().get_next_sibling() + artist_label = album_label.get_next_sibling() + + b1 = corealbum.bind_property( + "corealbum", cover_image.props.paintable, "coreobject", + GObject.BindingFlags.SYNC_CREATE) + b2 = corealbum.bind_property( + "title", album_label, "label", GObject.BindingFlags.SYNC_CREATE) + b3 = corealbum.bind_property( + "artist", artist_label, "label", GObject.BindingFlags.SYNC_CREATE) + + b4 = list_item.bind_property( + "selected", corealbum, "selected", + GObject.BindingFlags.SYNC_CREATE) + b5 = list_item.bind_property( + "selected", check, "active", GObject.BindingFlags.SYNC_CREATE) + b6 = self.bind_property( + "selection-mode", check, "visible", + GObject.BindingFlags.SYNC_CREATE) + + def on_activated(widget, value): + if check.props.active: + self._selection_model.select_item( + list_item.get_position(), False) + else: + self._selection_model.unselect_item( + list_item.get_position()) + + # the listitem selected property is read-only. + # It cannot be bound from the check active property. + # It is necessary to update the selection model in order + # to update it. + check.connect("notify::active", on_activated) + + self._list_item_bindings[list_item] = [b1, b2, b3, b4, b5, b6] + + def _unbind_list_item( + self, factory: Gtk.SignalListItemFactory, + list_item: Gtk.ListItem) -> None: + bindings = self._list_item_bindings.pop(list_item) + [binding.unbind() for binding in bindings] + + album_cover = list_item.props.child + + cover_image = album_cover.get_first_child().get_first_child() + check = cover_image.get_next_sibling() + + signal_id, detail_id = GObject.signal_parse_name( + "notify::active", check, True) + handler_id = GObject.signal_handler_find( + check, GObject.SignalMatchType.ID, signal_id, detail_id, None, 0, + 0) + check.disconnect(handler_id) |