← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~raoul-snyman/openlp/bug-1066853 into lp:openlp

 

Raoul Snyman has proposed merging lp:~raoul-snyman/openlp/bug-1066853 into lp:openlp.

Requested reviews:
  OpenLP Core (openlp-core)

For more details, see:
https://code.launchpad.net/~raoul-snyman/openlp/bug-1066853/+merge/134281

Implement locale-aware natural sorting.
-- 
https://code.launchpad.net/~raoul-snyman/openlp/bug-1066853/+merge/134281
Your team OpenLP Core is requested to review the proposed merge of lp:~raoul-snyman/openlp/bug-1066853 into lp:openlp.
=== modified file 'openlp/plugins/songs/lib/db.py'
--- openlp/plugins/songs/lib/db.py	2012-11-11 21:16:14 +0000
+++ openlp/plugins/songs/lib/db.py	2012-11-14 10:57:26 +0000
@@ -31,6 +31,8 @@
 the Songs plugin
 """
 
+import re
+
 from sqlalchemy import Column, ForeignKey, Table, types
 from sqlalchemy.orm import mapper, relation, reconstructor
 from sqlalchemy.sql.expression import func
@@ -38,6 +40,7 @@
 
 from openlp.core.lib.db import BaseModel, init_db
 
+
 class Author(BaseModel):
     """
     Author model
@@ -66,21 +69,33 @@
     Song model
     """
     def __init__(self):
-        self.sort_string = ''
+        self.sort_key = ()
+
+    def _try_int(self, s):
+        "Convert to integer if possible."
+        try:
+            return int(s)
+        except:
+            return QtCore.QString(s.lower())
+
+    def _natsort_key(self, s):
+        "Used internally to get a tuple by which s is sorted."
+        return map(self._try_int, re.findall(r'(\d+|\D+)', s))
 
     # This decorator tells sqlalchemy to call this method everytime
-    # any data on this object are updated.
+    # any data on this object is updated.
     @reconstructor
     def init_on_load(self):
         """
-        Precompute string to be used for sorting.
+        Precompute a tuple to be used for sorting.
 
         Song sorting is performance sensitive operation.
         To get maximum speed lets precompute the string
         used for comparison.
         """
         # Avoid the overhead of converting string to lowercase and to QString
-        self.sort_string = QtCore.QString(self.title.lower())
+        # with every call to sort().
+        self.sort_key = self._natsort_key(self.title)
 
 
 class Topic(BaseModel):

=== modified file 'openlp/plugins/songs/lib/mediaitem.py'
--- openlp/plugins/songs/lib/mediaitem.py	2012-11-11 21:16:14 +0000
+++ openlp/plugins/songs/lib/mediaitem.py	2012-11-14 10:57:26 +0000
@@ -50,6 +50,44 @@
 
 log = logging.getLogger(__name__)
 
+
+def natcmp(a, b):
+    """
+    Natural string comparison which mimics the behaviour of Python's internal
+    cmp function.
+    """
+    log.debug('a: %s; b: %s', a, b)
+    if len(a) <= len(b):
+        for i, key in enumerate(a):
+            if isinstance(key, int) and isinstance(b[i], int):
+                result = cmp(key, b[i])
+            elif isinstance(key, int) and not isinstance(b[i], int):
+                result = locale_direct_compare(QtCore.QString(str(key)), b[i])
+            elif not isinstance(key, int) and isinstance(b[i], int):
+                result = locale_direct_compare(key, QtCore.QString(str(b[i])))
+            else:
+                result = locale_direct_compare(key, b[i])
+            if result != 0:
+                return result
+        if len(a) == len(b):
+            return 0
+        else:
+            return -1
+    else:
+        for i, key in enumerate(b):
+            if isinstance(a[i], int) and isinstance(key, int):
+                result = cmp(a[i], key)
+            elif isinstance(a[i], int) and not isinstance(key, int):
+                result = locale_direct_compare(QtCore.QString(str(a[i])), key)
+            elif not isinstance(a[i], int) and isinstance(key, int):
+                result = locale_direct_compare(a[i], QtCore.QString(str(key)))
+            else:
+                result = locale_direct_compare(a[i], key)
+            if result != 0:
+                return result
+        return 1
+
+
 class SongSearch(object):
     """
     An enumeration for song search methods.
@@ -260,8 +298,7 @@
         log.debug(u'display results Song')
         self.saveAutoSelectId()
         self.listView.clear()
-        searchresults.sort(
-            cmp=locale_direct_compare, key=lambda song: song.sort_string)
+        searchresults.sort(cmp=natcmp, key=lambda song: song.sort_key)
         for song in searchresults:
             # Do not display temporary songs
             if song.temporary:


Follow ups