[gnome-music/wip/jfelder/improve-song-album-sorting: 5/5] Use natural order to sort album, artists and song names



commit 9429d60c38c056f0f2e18baf9b9834c11315b647
Author: Jean Felder <jfelder src gnome org>
Date:   Tue Dec 17 20:32:45 2019 +0100

    Use natural order to sort album, artists and song names
    
    A natural order is an alphabetical order which takes into account digit
    numbers. For example, the list ["Album 10", "Album 3"] should be
    sorted ["Album 3", "Album 10"] in an natural order.
    
    This can happen for compilation albums. There can be
    naming schemes like: Album, Album 2, Album 3,..., Album 10.
    
    This issue is fixed by replacing the alphabetical sort function of the
    main models by a natural sort function. This new sort function splits
    the names by the occurences of a digit. The digits are then replaced by
    an integer which allows to make a direct list comparison.
    
    Closes: #22

 gnomemusic/coremodel.py     |  8 +++++---
 gnomemusic/songliststore.py |  6 +++---
 gnomemusic/utils.py         | 17 ++++++++++++++---
 3 files changed, 22 insertions(+), 9 deletions(-)
---
diff --git a/gnomemusic/coremodel.py b/gnomemusic/coremodel.py
index c795c2bc..a8579159 100644
--- a/gnomemusic/coremodel.py
+++ b/gnomemusic/coremodel.py
@@ -160,16 +160,18 @@ class CoreModel(GObject.GObject):
         return coresong.props.selected
 
     def _albums_sort(self, album_a, album_b):
-        return utils.sort_names(album_a.props.title, album_b.props.title)
+        return utils.natural_sort_names(
+            album_a.props.title, album_b.props.title)
 
     def _artist_sort(self, artist_a, artist_b):
-        return utils.sort_names(artist_a.props.artist, artist_b.props.artist)
+        return utils.natural_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
-            return utils.sort_names(
+            return utils.natural_sort_names(
                 playlist_a.props.title, playlist_b.props.title)
 
         if playlist_b.props.is_smart:
diff --git a/gnomemusic/songliststore.py b/gnomemusic/songliststore.py
index 4a8e13b0..baf80810 100644
--- a/gnomemusic/songliststore.py
+++ b/gnomemusic/songliststore.py
@@ -71,16 +71,16 @@ class SongListStore(Gtk.ListStore):
         song_cmp = (utils.normalize_caseless(title_a)
                     == utils.normalize_caseless(title_b))
         if not song_cmp:
-            return utils.sort_names(title_a, title_b)
+            return utils.natural_sort_names(title_a, title_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 utils.sort_names(artist_a, artist_b)
+            return utils.natural_sort_names(artist_a, artist_b)
 
-        return utils.sort_names(song_a.props.album, song_b.props.album)
+        return utils.natural_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 405a41e4..8090cf27 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 re
 import unicodedata
 
 from gettext import gettext as _
@@ -147,12 +148,22 @@ def normalize_caseless(text):
     return unicodedata.normalize("NFKD", text.casefold())
 
 
-def sort_names(name_a, name_b):
-    """Caseless comparison of two strings.
+def natural_sort_names(name_a, name_b):
+    """Natural order comparison of two strings.
+
+    A natural order is an alphabetical order which takes into account
+    digit numbers. For example, it returns ["Album 3", "Album 10"]
+    instead of ["Album 10", "Album 3"] for an alphabetical order.
+    The names are also normalized to properly take into account names
+    which contain accents.
 
     :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)
+    def _extract_numbers(text):
+        return [int(tmp) if tmp.isdigit() else tmp
+                for tmp in re.split(r"(\d+)", normalize_caseless(text))]
+
+    return _extract_numbers(name_b) < _extract_numbers(name_a)


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