[gnome-music/wip/jfelder/gtk4-v3: 222/259] songsview: Basic working




commit 913550212dca9ee835ea8a72e298146801f67c4d
Author: Marinus Schraal <mschraal gnome org>
Date:   Fri Feb 11 16:44:06 2022 +0100

    songsview: Basic working

 data/org.gnome.Music.gresource.xml |   1 +
 data/ui/SongListItem.ui            |  37 ++++++
 data/ui/SongsView.ui               | 109 ++----------------
 gnomemusic/views/songsview.py      | 227 +++++++++++++++----------------------
 4 files changed, 138 insertions(+), 236 deletions(-)
---
diff --git a/data/org.gnome.Music.gresource.xml b/data/org.gnome.Music.gresource.xml
index b2de9b6c8..1bbd8520e 100644
--- a/data/org.gnome.Music.gresource.xml
+++ b/data/org.gnome.Music.gresource.xml
@@ -34,6 +34,7 @@
     <file preprocess="xml-stripblanks">ui/SearchView.ui</file>
     <file preprocess="xml-stripblanks">ui/SelectionBarMenuButton.ui</file>
     <file preprocess="xml-stripblanks">ui/SelectionToolbar.ui</file>
+    <file preprocess="xml-stripblanks">ui/SongListItem.ui</file>
     <file preprocess="xml-stripblanks">ui/SongsView.ui</file>
     <file preprocess="xml-stripblanks">ui/SongWidget.ui</file>
     <file preprocess="xml-stripblanks">ui/SongWidgetMenu.ui</file>
diff --git a/data/ui/SongListItem.ui b/data/ui/SongListItem.ui
new file mode 100644
index 000000000..58cee7430
--- /dev/null
+++ b/data/ui/SongListItem.ui
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="4.0"/>
+  <object class="GtkBox" id="_song_box">
+    <property name="focusable">False</property>
+    <property name="valign">start</property>
+    <child>
+      <object class="GtkCheckButton" id="_check">
+        <property name="visible">False</property>
+      </object>
+    </child>
+    <child>
+      <object class="GtkBox">
+        <property name="homogeneous">True</property>
+          <child>
+            <object class="GtkLabel" id="_title_label">
+            <property name="halign">start</property>
+              <property name="hexpand">True</property>
+              <property name="ellipsize">end</property>
+            </object>
+          </child>
+          <child>
+            <object class="GtkLabel" id="_album_label">
+              <property name="ellipsize">end</property>
+              <property name="halign">start</property>
+            </object>
+          </child>
+          <child>
+            <object class="GtkLabel" id="_artist_label">
+              <property name="ellipsize">end</property>
+              <property name="halign">start</property>
+            </object>
+          </child>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/data/ui/SongsView.ui b/data/ui/SongsView.ui
index 0e0acdd20..ae519e1b2 100644
--- a/data/ui/SongsView.ui
+++ b/data/ui/SongsView.ui
@@ -1,115 +1,20 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk" version="4.0"/>
-  <template class="SongsView" parent="GtkScrolledWindow">
-    <property name="hexpand">True</property>
-    <property name="vexpand">True</property>
+  <template class="SongsView" parent="GtkBox">
     <child>
-      <object class="GtkTreeView" id="_songs_view">
-        <property name="activate-on-single-click">True</property>
-        <property name="headers_visible">False</property>
-        <property name="valign">start</property>
-        <signal name="row-activated" handler="_on_item_activated" swapped="no"/>
-        <style>
-          <class name="songs-list-old"/>
-        </style>
+      <object class="AdwClampScrollable" id="_adw_clamp_scrollable">
+        <property name="hexpand">True</property>
+        <property name="vexpand">True</property>
+        <property name="maximum-size">1000</property>
         <child>
-          <object class="GtkGestureClick" id="_songs_ctrlr">
-            <property name="propagation-phase">capture</property>
-            <signal name="released" handler="_on_view_clicked" swapped="no"/>
-          </object>
-        </child>
-        <child internal-child="selection">
-          <object class="GtkTreeSelection">
-            <property name="mode">single</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkTreeViewColumn" id="_now_playing_column">
-            <property name="fixed_width">48</property>
-            <child>
-              <object class="GtkCellRendererPixbuf" id="_now_playing_cell">
-                <property name="xalign">0.5</property>
-                <property name="xpad">0</property>
-                <property name="yalign">0.5</property>
-              </object>
-            </child>
-          </object>
-        </child>
-        <child>
-          <object class="GtkTreeViewColumn" id="_selection_column">
-            <property name="fixed_width">48</property>
-            <property name="visible">False</property>
-            <child>
-              <object class="GtkCellRendererToggle">
-              </object>
-              <attributes>
-                <attribute name="active">1</attribute>
-              </attributes>
-            </child>
-          </object>
-        </child>
-        <child>
-          <object class="GtkTreeViewColumn" id="_title_column">
-            <property name="expand">True</property>
-            <child>
-              <object class="GtkCellRendererText">
-                <property name="ellipsize">end</property>
-                <property name="height">48</property>
-                <property name="xalign">0</property>
-                <property name="xpad">0</property>
-                <property name="yalign">0.5</property>
-              </object>
-              <attributes>
-                <attribute name="text">2</attribute>
-              </attributes>
-            </child>
-          </object>
-        </child>
-        <child>
-          <object class="GtkTreeViewColumn" id="_artist_column">
-            <property name="expand">True</property>
-            <child>
-              <object class="GtkCellRendererText">
-                <property name="ellipsize">end</property>
-                <property name="xpad">32</property>
-              </object>
-              <attributes>
-                <attribute name="text">3</attribute>
-              </attributes>
-            </child>
-          </object>
-        </child>
-        <child>
-          <object class="GtkTreeViewColumn" id="_album_column">
-            <property name="expand">True</property>
+          <object class="GtkScrolledWindow">
             <child>
-              <object class="GtkCellRendererText">
-                <property name="ellipsize">end</property>
-                <property name="xpad">32</property>
+              <object class="GtkListView" id="_listview">
               </object>
-              <attributes>
-                <attribute name="text">4</attribute>
-              </attributes>
             </child>
           </object>
         </child>
-        <child>
-          <object class="GtkTreeViewColumn" id="_duration_column">
-            <child>
-              <object class="GtkCellRendererText" id="_duration_renderer">
-                <property name="xalign">1</property>
-              </object>
-              <attributes>
-                <attribute name="text">5</attribute>
-              </attributes>
-            </child>
-          </object>
-        </child>
-        <child>
-          <object class="GtkTreeViewColumn" id="_star_column">
-          </object>
-        </child>
       </object>
     </child>
   </template>
diff --git a/gnomemusic/views/songsview.py b/gnomemusic/views/songsview.py
index ee7752ee4..531abef3a 100644
--- a/gnomemusic/views/songsview.py
+++ b/gnomemusic/views/songsview.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2016 The GNOME Music Developers
+# Copyright 2022 The GNOME Music Developers
 #
 # GNOME Music is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -23,7 +23,7 @@
 # delete this exception statement from your version.
 
 from gettext import gettext as _
-from gi.repository import Gdk, GObject, Gtk, Pango
+from gi.repository import Adw, GObject, Gtk
 
 from gnomemusic.coresong import CoreSong
 from gnomemusic.utils import SongStateIcon
@@ -31,7 +31,7 @@ from gnomemusic.widgets.starhandlerwidget import StarHandlerWidget
 
 
 @Gtk.Template(resource_path="/org/gnome/Music/ui/SongsView.ui")
-class SongsView(Gtk.ScrolledWindow):
+class SongsView(Gtk.Box):
     """Main view of all songs sorted artistwise
 
     Consists all songs along with songname, star, length, artist
@@ -46,12 +46,7 @@ class SongsView(Gtk.ScrolledWindow):
     title = GObject.Property(
         type=str, default=_("Songs"), flags=GObject.ParamFlags.READABLE)
 
-    _duration_renderer = Gtk.Template.Child()
-    _now_playing_column = Gtk.Template.Child()
-    _now_playing_cell = Gtk.Template.Child()
-    _songs_ctrlr = Gtk.Template.Child()
-    _songs_view = Gtk.Template.Child()
-    _star_column = Gtk.Template.Child()
+    _listview = Gtk.Template.Child()
 
     def __init__(self, application):
         """Initialize
@@ -62,50 +57,98 @@ class SongsView(Gtk.ScrolledWindow):
 
         self.props.name = "songs"
 
-        self._window = application.props.window
         self._coremodel = application.props.coremodel
-
-        self._iter_to_clean = None
-        self._set_list_renderers()
+        self._coreselection = application.props.coreselection
+        self._window = application.props.window
 
         self._playlist_model = self._coremodel.props.playlist_sort
-        self._songs_view.props.model = self._coremodel.props.songs_gtkliststore
-        self._model = self._songs_view.props.model
+        self._model = self._coremodel.props.songs
+        self._selection_model = Gtk.MultiSelection.new(self._model)
+
+        list_item_factory = Gtk.SignalListItemFactory()
+        list_item_factory.connect("setup", self._setup_list_item)
+        list_item_factory.connect("bind", self._bind_list_item)
+
+        self._listview.props.factory = list_item_factory
+        self._listview.props.model = self._selection_model
 
         self._player = application.props.player
-        self._player.connect('song-changed', self._update_model)
+        # self._player.connect('song-changed', self._update_model)
 
         self._selection_mode = False
 
+        self.bind_property(
+            "selection-mode", self._listview, "single-click-activate",
+            GObject.BindingFlags.SYNC_CREATE |
+                GObject.BindingFlags.INVERT_BOOLEAN)
+        self.bind_property(
+            "selection-mode", self._listview, "enable-rubberband",
+            GObject.BindingFlags.SYNC_CREATE)
         self._window.bind_property(
             "selection-mode", self, "selection-mode",
             GObject.BindingFlags.BIDIRECTIONAL)
 
-    def _set_list_renderers(self):
-        self._now_playing_column.set_cell_data_func(
-            self._now_playing_cell, self._on_list_widget_icon_render, None)
-
-        self._star_handler = StarHandlerWidget(self, 6)
-        self._star_handler.add_star_renderers(self._star_column)
-
-        attrs = Pango.AttrList()
-        attrs.insert(Pango.AttrFontFeatures.new("tnum=1"))
-        self._duration_renderer.props.attributes = attrs
-
-    def _on_list_widget_icon_render(self, col, cell, model, itr, data):
-        current_song = self._player.props.current_song
-        if current_song is None:
-            return
-
-        coresong = model[itr][7]
-        if coresong.props.validation == CoreSong.Validation.FAILED:
-            cell.props.icon_name = SongStateIcon.ERROR.value
-            cell.props.visible = True
-        elif coresong.props.grlid == current_song.props.grlid:
-            cell.props.icon_name = SongStateIcon.PLAYING.value
-            cell.props.visible = True
-        else:
-            cell.props.visible = False
+    def _setup_list_item(
+            self, factory: Gtk.SignalListItemFactory,
+            list_item: Gtk.ListItem) -> None:
+        builder = Gtk.Builder.new_from_resource(
+            "/org/gnome/Music/ui/SongListItem.ui")
+        list_item.props.child = builder.get_object("_song_box")
+
+        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:
+        list_row = list_item.props.child
+        coresong = list_item.props.item
+
+        check = list_row.get_first_child()
+        info_box = check.get_next_sibling()
+        title_label = info_box.get_first_child()
+        album_label = title_label.get_next_sibling()
+        artist_label = album_label.get_next_sibling()
+
+        coresong.bind_property(
+            "title", title_label, "label",
+            GObject.BindingFlags.SYNC_CREATE)
+        coresong.bind_property(
+            "album", album_label, "label",
+            GObject.BindingFlags.SYNC_CREATE)
+        coresong.bind_property(
+            "artist", artist_label, "label",
+            GObject.BindingFlags.SYNC_CREATE)
+
+        list_item.bind_property(
+            "selected", coresong, "selected",
+            GObject.BindingFlags.SYNC_CREATE)
+        self.bind_property(
+            "selection-mode", check, "visible",
+            GObject.BindingFlags.SYNC_CREATE)
+        check.bind_property(
+            "active", coresong, "selected",
+            GObject.BindingFlags.SYNC_CREATE
+            | GObject.BindingFlags.BIDIRECTIONAL)
+
+        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)
 
     @GObject.Property(type=bool, default=False)
     def selection_mode(self):
@@ -130,101 +173,17 @@ class SongsView(Gtk.ScrolledWindow):
         if self._selection_mode is False:
             self.deselect_all()
 
-        cols = self._songs_view.get_columns()
-        cols[1].props.visible = self._selection_mode
-
-    @Gtk.Template.Callback()
-    def _on_item_activated(self, treeview, path, column):
-        """Action performed when clicking on a song
-
-        clicking on star column toggles favorite
-        clicking on an other columns launches player
-
-        :param Gtk.TreeView treeview: self._songs_view
-        :param Gtk.TreePath path: activated row index
-        :param Gtk.TreeViewColumn column: activated column
-        """
-        if self._star_handler.star_renderer_click:
-            self._star_handler.star_renderer_click = False
-            return
-
-        if self.props.selection_mode:
-            return
-
-        itr = self._model.get_iter(path)
-        coresong = self._model[itr][7]
-        self._coremodel.props.active_core_object = coresong
-
-        self._player.play(coresong)
-
-    @Gtk.Template.Callback()
-    def _on_view_clicked(self, gesture, n_press, x, y):
-        """Ctrl+click on self._songs_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
-
-        # FIXME: In selection mode, star clicks might still trigger
-        # activation.
-        if self.props.selection_mode:
-            path = self._songs_view.get_path_at_pos(x, y)
-            if path is None:
-                return
-
-            iter_ = self._model.get_iter(path[0])
-            new_fav_status = not self._model[iter_][1]
-            self._model[iter_][1] = new_fav_status
-            self._model[iter_][7].props.selected = new_fav_status
-
-    def _update_model(self, player):
-        """Updates model when the song changes
-
-        :param Player player: The main player object
-        """
-        # iter_to_clean is necessary because of a bug in GtkTreeView
-        # See https://gitlab.gnome.org/GNOME/gtk/issues/503
-        if self._iter_to_clean:
-            self._model[self._iter_to_clean][9] = False
-
-        index = self._player.props.position
-        current_coresong = self._playlist_model[index]
-        for idx, liststore in enumerate(self._model):
-            if liststore[7] == current_coresong:
-                break
-
-        iter_ = self._model.get_iter_from_string(str(idx))
-        path = self._model.get_path(iter_)
-        self._model[iter_][9] = True
-        self._songs_view.scroll_to_cell(path, None, True, 0.5, 0.5)
-
-        if self._model[iter_][0] != SongStateIcon.ERROR.value:
-            self._iter_to_clean = iter_.copy()
-
-        return False
-
-    @GObject.Property(
-        type=Gtk.ListStore, default=None, flags=GObject.ParamFlags.READABLE)
-    def model(self):
-        """Get songs view model
-
-        :returns: songs view model
-        :rtype: Gtk.ListStore
+    def _toggle_all_selection(self, selected):
+        """Selects or deselects all items.
         """
-        return self._model
-
-    def _select(self, value):
-        with self._model.freeze_notify():
-            itr = self._model.iter_children(None)
-            while itr is not None:
-                self._model[itr][7].props.selected = value
-                self._model[itr][1] = value
-
-                itr = self._model.iter_next(itr)
+        with self._coreselection.freeze_notify():
+            if selected:
+                self._selection_model.select_all()
+            else:
+                self._selection_model.unselect_all()
 
     def select_all(self):
-        self._select(True)
+        self._toggle_all_selection(True)
 
     def deselect_all(self):
-        self._select(False)
+        self._toggle_all_selection(False)


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]