[gnome-music/wip/jfelder/improve-song-album-sorting: 1/2] Use normalized strings to sort album, artists and songs names



commit 5830287443507af34c466640ba6e3366f63d7924
Author: Jean Felder <jfelder src gnome org>
Date:   Tue Dec 17 19:49:07 2019 +0100

    Use normalized strings to sort album, artists and songs names
    
    If an album, artist or song name contains an accent, it won't be
    sorted correctly in somes cases.
    For example, if an album is named "Éléor", it will be visible at the
    end of the AlbumsView while it would make more sense to insert it
    before an album starting with the letter "f".
    
    The main issue is that direct string comparisons do not handle well
    string which contains accents. Instead a caseless comparison needs to
    be done: the strings need to be normalized before the comparison.
    
    This issue is fixed by introducing a new sort function named
    sort_names which normalizes the string. This sort function is set for
    all the relevant models.

 gnomemusic/coremodel.py     | 12 +++++-------
 gnomemusic/songliststore.py | 20 +++++++++++---------
 gnomemusic/utils.py         | 22 ++++++++++++++++++++++
 3 files changed, 38 insertions(+), 16 deletions(-)
---
diff --git a/gnomemusic/coremodel.py b/gnomemusic/coremodel.py
index 13a8bc0e..c795c2bc 100644
--- a/gnomemusic/coremodel.py
+++ b/gnomemusic/coremodel.py
@@ -36,6 +36,7 @@ from gnomemusic.grilowrappers.grltrackerplaylists import Playlist
 from gnomemusic.player import PlayerPlaylist
 from gnomemusic.songliststore import SongListStore
 from gnomemusic.widgets.songwidget import SongWidget
+import gnomemusic.utils as utils
 
 
 class CoreModel(GObject.GObject):
@@ -159,20 +160,17 @@ class CoreModel(GObject.GObject):
         return coresong.props.selected
 
     def _albums_sort(self, album_a, album_b):
-        return album_b.props.title.casefold() < album_a.props.title.casefold()
+        return utils.sort_names(album_a.props.title, album_b.props.title)
 
     def _artist_sort(self, artist_a, artist_b):
-        name_a = artist_a.props.artist.casefold()
-        name_b = artist_b.props.artist.casefold()
-        return name_a > name_b
+        return utils.sort_names(artist_a.props.artist, artist_b.props.artist)
 
     def _playlists_sort(self, playlist_a, playlist_b):
         if playlist_a.props.is_smart:
             if not playlist_b.props.is_smart:
                 return -1
-            title_a = playlist_a.props.title.casefold()
-            title_b = playlist_b.props.title.casefold()
-            return title_a > title_b
+            return utils.sort_names(
+                playlist_a.props.title, playlist_b.props.title)
 
         if playlist_b.props.is_smart:
             return 1
diff --git a/gnomemusic/songliststore.py b/gnomemusic/songliststore.py
index 8ced678e..4a8e13b0 100644
--- a/gnomemusic/songliststore.py
+++ b/gnomemusic/songliststore.py
@@ -66,19 +66,21 @@ class SongListStore(Gtk.ListStore):
         return wrap
 
     def _songs_sort(self, song_a, song_b):
-        title_a = song_a.props.title.casefold()
-        title_b = song_b.props.title.casefold()
-        song_cmp = title_a == title_b
+        title_a = song_a.props.title
+        title_b = song_b.props.title
+        song_cmp = (utils.normalize_caseless(title_a)
+                    == utils.normalize_caseless(title_b))
         if not song_cmp:
-            return title_a > title_b
+            return utils.sort_names(title_a, title_b)
 
-        artist_a = song_a.props.artist.casefold()
-        artist_b = song_b.props.artist.casefold()
-        artist_cmp = artist_a == artist_b
+        artist_a = song_a.props.artist
+        artist_b = song_b.props.artist
+        artist_cmp = (utils.normalize_caseless(artist_a)
+                      == utils.normalize_caseless(artist_b))
         if not artist_cmp:
-            return artist_a > artist_b
+            return utils.sort_names(artist_a, artist_b)
 
-        return song_a.props.album.casefold() > song_b.props.album.casefold()
+        return utils.sort_names(song_a.props.album, song_b.props.album)
 
     def _on_items_changed(self, model, position, removed, added):
         if removed > 0:
diff --git a/gnomemusic/utils.py b/gnomemusic/utils.py
index 76310cb6..405a41e4 100644
--- a/gnomemusic/utils.py
+++ b/gnomemusic/utils.py
@@ -23,6 +23,7 @@
 # delete this exception statement from your version.
 
 from enum import Enum, IntEnum
+import unicodedata
 
 from gettext import gettext as _
 from gi.repository import Gio
@@ -134,3 +135,24 @@ def seconds_to_string(duration):
     seconds %= 60
 
     return '{:d}:{:02d}'.format(minutes, seconds)
+
+
+def normalize_caseless(text):
+    """Get a normalized casefolded version of a string.
+
+    :param str text: string to normalize
+    :returns: normalized casefolded string
+    :rtype: str
+    """
+    return unicodedata.normalize("NFKD", text.casefold())
+
+
+def sort_names(name_a, name_b):
+    """Caseless comparison of two strings.
+
+    :param str name_a: first string to compare
+    :param str name_b: second string to compare
+    :returns: False if name_a should be before name_b. True otherwise.
+    :rtype: boolean
+    """
+    return normalize_caseless(name_b) < normalize_caseless(name_a)


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