It ain't pretty, but it works...

This commit is contained in:
Raoul Snyman 2012-11-08 23:28:42 +02:00
parent 11cb7754a5
commit 6d306f45a5
2 changed files with 55 additions and 26 deletions

View File

@ -39,6 +39,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,43 +67,34 @@ class Song(BaseModel):
""" """
Song model Song model
""" """
_DIGITS = 6 # Do not expect a number greater than 999999.
_RE = re.compile(r'(\d+)') # Match any number at start of string.
def __init__(self): def __init__(self):
self.sort_string = u'' 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.
""" """
title = self.title
# Ensure titles starting with numbers are sorted properly.
# By default titles like '2 bla' and '10 foo' are sorted like
# 10 foo
# 2 bla
# This is not the desired behaviour. They order should be
# 2 foo
# 10 bla
#
# Usually the workaround done by the user would be to add leading zeros
# 002 foo
# 010 bla
# This this trick is implemented for sort_string where leading zeros are
# added if the title starts with a number.
match = Song._RE.match(title)
if match:
match_len = len(match.group())
title = title.zfill(len(title) + (Song._DIGITS - match_len))
# Avoid the overhead of converting string to lowercase and to QString # Avoid the overhead of converting string to lowercase and to QString
# with every call to sort(). # with every call to sort().
self.sort_string = QtCore.QString(title.lower()) self.sort_key = self._natsort_key(self.title)
class Topic(BaseModel): class Topic(BaseModel):

View File

@ -49,6 +49,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.
@ -259,8 +297,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: