Fix song "10 bla" comes before "2 foo"

bzr-revno: 2113
This commit is contained in:
Raoul Snyman 2012-11-21 18:45:37 +00:00 committed by Tim Bentley
commit 12e7f7384d
2 changed files with 58 additions and 6 deletions

View File

@ -31,6 +31,8 @@ The :mod:`db` module provides the database and schema that is the backend for
the Songs plugin the Songs plugin
""" """
import re
from sqlalchemy import Column, ForeignKey, Table, types from sqlalchemy import Column, ForeignKey, Table, types
from sqlalchemy.orm import mapper, relation, reconstructor from sqlalchemy.orm import mapper, relation, reconstructor
from sqlalchemy.sql.expression import func from sqlalchemy.sql.expression import func
@ -38,6 +40,7 @@ from PyQt4 import QtCore
from openlp.core.lib.db import BaseModel, init_db from openlp.core.lib.db import BaseModel, init_db
class Author(BaseModel): class Author(BaseModel):
""" """
Author model Author model
@ -66,21 +69,33 @@ class Song(BaseModel):
Song model Song model
""" """
def __init__(self): 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 # This decorator tells sqlalchemy to call this method everytime
# any data on this object are updated. # any data on this object is updated.
@reconstructor @reconstructor
def init_on_load(self): 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. Song sorting is performance sensitive operation.
To get maximum speed lets precompute the string To get maximum speed lets precompute the string
used for comparison. used for comparison.
""" """
# Avoid the overhead of converting string to lowercase and to QString # 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): class Topic(BaseModel):

View File

@ -50,6 +50,44 @@ from openlp.plugins.songs.lib.ui import SongStrings
log = logging.getLogger(__name__) 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): class SongSearch(object):
""" """
An enumeration for song search methods. An enumeration for song search methods.
@ -260,8 +298,7 @@ class SongMediaItem(MediaManagerItem):
log.debug(u'display results Song') log.debug(u'display results Song')
self.saveAutoSelectId() self.saveAutoSelectId()
self.listView.clear() self.listView.clear()
searchresults.sort( searchresults.sort(cmp=natcmp, key=lambda song: song.sort_key)
cmp=locale_direct_compare, key=lambda song: song.sort_string)
for song in searchresults: for song in searchresults:
# Do not display temporary songs # Do not display temporary songs
if song.temporary: if song.temporary: