Head and fix

This commit is contained in:
Tim Bentley 2013-04-16 17:37:26 +01:00
commit a0f4db607e
15 changed files with 93 additions and 99 deletions

View File

@ -69,6 +69,14 @@ try:
MAKO_VERSION = mako.__version__ MAKO_VERSION = mako.__version__
except ImportError: except ImportError:
MAKO_VERSION = u'-' MAKO_VERSION = u'-'
try:
import icu
try:
ICU_VERSION = icu.VERSION
except AttributeError:
ICU_VERSION = u'OK'
except ImportError:
ICU_VERSION = u'-'
try: try:
import cherrypy import cherrypy
CHERRYPY_VERSION = cherrypy.__version__ CHERRYPY_VERSION = cherrypy.__version__
@ -149,6 +157,7 @@ class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog):
u'PySQLite: %s\n' % SQLITE_VERSION + \ u'PySQLite: %s\n' % SQLITE_VERSION + \
u'Mako: %s\n' % MAKO_VERSION + \ u'Mako: %s\n' % MAKO_VERSION + \
u'CherryPy: %s\n' % CHERRYPY_VERSION + \ u'CherryPy: %s\n' % CHERRYPY_VERSION + \
u'pyICU: %s\n' % ICU_VERSION + \
u'pyUNO bridge: %s\n' % UNO_VERSION + \ u'pyUNO bridge: %s\n' % UNO_VERSION + \
u'VLC: %s\n' % VLC_VERSION u'VLC: %s\n' % VLC_VERSION
if platform.system() == u'Linux': if platform.system() == u'Linux':

View File

@ -44,7 +44,7 @@ from openlp.core.lib.theme import ThemeXML, BackgroundType, VerticalType, Backgr
from openlp.core.lib.ui import critical_error_message_box, create_widget_action from openlp.core.lib.ui import critical_error_message_box, create_widget_action
from openlp.core.theme import Theme from openlp.core.theme import Theme
from openlp.core.ui import FileRenameForm, ThemeForm from openlp.core.ui import FileRenameForm, ThemeForm
from openlp.core.utils import AppLocation, delete_file, locale_compare, get_filesystem_encoding from openlp.core.utils import AppLocation, delete_file, get_locale_key, get_filesystem_encoding
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -418,7 +418,7 @@ class ThemeManager(QtGui.QWidget):
self.theme_list_widget.clear() self.theme_list_widget.clear()
files = AppLocation.get_files(self.settings_section, u'.png') files = AppLocation.get_files(self.settings_section, u'.png')
# Sort the themes by its name considering language specific # Sort the themes by its name considering language specific
files.sort(key=lambda file_name: unicode(file_name), cmp=locale_compare) files.sort(key=lambda file_name: get_locale_key(unicode(file_name)))
# now process the file list of png files # now process the file list of png files
for name in files: for name in files:
# check to see file is in theme root directory # check to see file is in theme root directory

View File

@ -38,6 +38,7 @@ import re
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
import sys import sys
import urllib2 import urllib2
import icu
from PyQt4 import QtGui, QtCore from PyQt4 import QtGui, QtCore
@ -56,10 +57,12 @@ from openlp.core.lib import translate
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
APPLICATION_VERSION = {} APPLICATION_VERSION = {}
IMAGES_FILTER = None IMAGES_FILTER = None
ICU_COLLATOR = None
UNO_CONNECTION_TYPE = u'pipe' UNO_CONNECTION_TYPE = u'pipe'
#UNO_CONNECTION_TYPE = u'socket' #UNO_CONNECTION_TYPE = u'socket'
CONTROL_CHARS = re.compile(r'[\x00-\x1F\x7F-\x9F]', re.UNICODE) CONTROL_CHARS = re.compile(r'[\x00-\x1F\x7F-\x9F]', re.UNICODE)
INVALID_FILE_CHARS = re.compile(r'[\\/:\*\?"<>\|\+\[\]%]', re.UNICODE) INVALID_FILE_CHARS = re.compile(r'[\\/:\*\?"<>\|\+\[\]%]', re.UNICODE)
DIGITS_OR_NONDIGITS = re.compile(r'\d+|\D+', re.UNICODE)
class VersionThread(QtCore.QThread): class VersionThread(QtCore.QThread):
@ -379,21 +382,32 @@ def format_time(text, local_time):
return re.sub('\%[a-zA-Z]', match_formatting, text) return re.sub('\%[a-zA-Z]', match_formatting, text)
def locale_compare(string1, string2): def get_locale_key(string):
""" """
Compares two strings according to the current locale settings. Creates a key for case insensitive, locale aware string sorting.
As any other compare function, returns a negative, or a positive value,
or 0, depending on whether string1 collates before or after string2 or
is equal to it. Comparison is case insensitive.
""" """
# Function locale.strcoll() from standard Python library does not work properly on Windows. string = string.lower()
return locale.strcoll(string1.lower(), string2.lower()) # For Python 3 on platforms other than Windows ICU is not necessary. In those cases locale.strxfrm(str) can be used.
global ICU_COLLATOR
if ICU_COLLATOR is None:
from languagemanager import LanguageManager
locale = LanguageManager.get_language()
icu_locale = icu.Locale(locale)
ICU_COLLATOR = icu.Collator.createInstance(icu_locale)
return ICU_COLLATOR.getSortKey(string)
# For performance reasons provide direct reference to compare function without wrapping it in another function making def get_natural_key(string):
# the string lowercase. This is needed for sorting songs. """
locale_direct_compare = locale.strcoll Generate a key for locale aware natural string sorting.
Returns a list of string compare keys and integers.
"""
key = DIGITS_OR_NONDIGITS.findall(string)
key = [int(part) if part.isdigit() else get_locale_key(part) for part in key]
# Python 3 does not support comparision of different types anymore. So make sure, that we do not compare str and int.
#if string[0].isdigit():
# return [''] + key
return key
from applocation import AppLocation from applocation import AppLocation
@ -403,4 +417,4 @@ from actions import ActionList
__all__ = [u'AppLocation', u'ActionList', u'LanguageManager', u'get_application_version', u'check_latest_version', __all__ = [u'AppLocation', u'ActionList', u'LanguageManager', u'get_application_version', u'check_latest_version',
u'add_actions', u'get_filesystem_encoding', u'get_web_page', u'get_uno_command', u'get_uno_instance', u'add_actions', u'get_filesystem_encoding', u'get_web_page', u'get_uno_command', u'get_uno_instance',
u'delete_file', u'clean_filename', u'format_time', u'locale_compare', u'locale_direct_compare'] u'delete_file', u'clean_filename', u'format_time', u'get_locale_key', u'get_natural_key']

View File

@ -38,7 +38,7 @@ from openlp.core.lib import Settings, UiStrings, translate
from openlp.core.lib.db import delete_database from openlp.core.lib.db import delete_database
from openlp.core.lib.ui import critical_error_message_box from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.wizard import OpenLPWizard, WizardStrings from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
from openlp.core.utils import AppLocation, locale_compare from openlp.core.utils import AppLocation, get_locale_key
from openlp.plugins.bibles.lib.manager import BibleFormat from openlp.plugins.bibles.lib.manager import BibleFormat
from openlp.plugins.bibles.lib.db import BiblesResourcesDB, clean_filename from openlp.plugins.bibles.lib.db import BiblesResourcesDB, clean_filename
@ -455,7 +455,7 @@ class BibleImportForm(OpenLPWizard):
""" """
self.webTranslationComboBox.clear() self.webTranslationComboBox.clear()
bibles = self.web_bible_list[index].keys() bibles = self.web_bible_list[index].keys()
bibles.sort(cmp=locale_compare) bibles.sort(key=get_locale_key)
self.webTranslationComboBox.addItems(bibles) self.webTranslationComboBox.addItems(bibles)
def onOsisBrowseButtonClicked(self): def onOsisBrowseButtonClicked(self):

View File

@ -36,7 +36,7 @@ from openlp.core.lib import Registry, MediaManagerItem, ItemCapabilities, Servic
from openlp.core.lib.searchedit import SearchEdit from openlp.core.lib.searchedit import SearchEdit
from openlp.core.lib.ui import set_case_insensitive_completer, create_horizontal_adjusting_combo_box, \ from openlp.core.lib.ui import set_case_insensitive_completer, create_horizontal_adjusting_combo_box, \
critical_error_message_box, find_and_set_in_combo_box, build_icon critical_error_message_box, find_and_set_in_combo_box, build_icon
from openlp.core.utils import locale_compare from openlp.core.utils import get_locale_key
from openlp.plugins.bibles.forms import BibleImportForm, EditBibleForm from openlp.plugins.bibles.forms import BibleImportForm, EditBibleForm
from openlp.plugins.bibles.lib import LayoutStyle, DisplayStyle, VerseReferenceList, get_reference_separator, \ from openlp.plugins.bibles.lib import LayoutStyle, DisplayStyle, VerseReferenceList, get_reference_separator, \
LanguageSelection, BibleStrings LanguageSelection, BibleStrings
@ -325,7 +325,7 @@ class BibleMediaItem(MediaManagerItem):
# Get all bibles and sort the list. # Get all bibles and sort the list.
bibles = self.plugin.manager.get_bibles().keys() bibles = self.plugin.manager.get_bibles().keys()
bibles = filter(None, bibles) bibles = filter(None, bibles)
bibles.sort(cmp=locale_compare) bibles.sort(key=get_locale_key)
# Load the bibles into the combo boxes. # Load the bibles into the combo boxes.
self.quickVersionComboBox.addItems(bibles) self.quickVersionComboBox.addItems(bibles)
self.quickSecondComboBox.addItems(bibles) self.quickSecondComboBox.addItems(bibles)
@ -461,7 +461,7 @@ class BibleMediaItem(MediaManagerItem):
for book in book_data: for book in book_data:
data = BiblesResourcesDB.get_book_by_id(book.book_reference_id) data = BiblesResourcesDB.get_book_by_id(book.book_reference_id)
books.append(data[u'name'] + u' ') books.append(data[u'name'] + u' ')
books.sort(cmp=locale_compare) books.sort(key=get_locale_key)
set_case_insensitive_completer(books, self.quickSearchEdit) set_case_insensitive_completer(books, self.quickSearchEdit)
def on_import_click(self): def on_import_click(self):

View File

@ -35,7 +35,7 @@ from sqlalchemy import Column, Table, types
from sqlalchemy.orm import mapper from sqlalchemy.orm import mapper
from openlp.core.lib.db import BaseModel, init_db from openlp.core.lib.db import BaseModel, init_db
from openlp.core.utils import locale_compare from openlp.core.utils import get_locale_key
class CustomSlide(BaseModel): class CustomSlide(BaseModel):
""" """
@ -44,11 +44,10 @@ class CustomSlide(BaseModel):
# By default sort the customs by its title considering language specific # By default sort the customs by its title considering language specific
# characters. # characters.
def __lt__(self, other): def __lt__(self, other):
r = locale_compare(self.title, other.title) return get_locale_key(self.title) < get_locale_key(other.title)
return True if r < 0 else False
def __eq__(self, other): def __eq__(self, other):
return 0 == locale_compare(self.title, other.title) return get_locale_key(self.title) == get_locale_key(other.title)
def init_schema(url): def init_schema(url):

View File

@ -36,7 +36,7 @@ from openlp.core.lib import ItemCapabilities, MediaManagerItem, Registry, Servic
StringContent, TreeWidgetWithDnD, UiStrings, build_icon, check_directory_exists, check_item_selected, \ StringContent, TreeWidgetWithDnD, UiStrings, build_icon, check_directory_exists, check_item_selected, \
create_thumb, translate, validate_thumb create_thumb, translate, validate_thumb
from openlp.core.lib.ui import create_widget_action, critical_error_message_box from openlp.core.lib.ui import create_widget_action, critical_error_message_box
from openlp.core.utils import AppLocation, delete_file, locale_compare, get_images_filter from openlp.core.utils import AppLocation, delete_file, get_locale_key, get_images_filter
from openlp.plugins.images.forms import AddGroupForm, ChooseGroupForm from openlp.plugins.images.forms import AddGroupForm, ChooseGroupForm
from openlp.plugins.images.lib.db import ImageFilenames, ImageGroups from openlp.plugins.images.lib.db import ImageFilenames, ImageGroups
@ -255,7 +255,7 @@ class ImageMediaItem(MediaManagerItem):
The ID of the group that will be added recursively The ID of the group that will be added recursively
""" """
image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == parent_group_id) image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == parent_group_id)
image_groups.sort(cmp=locale_compare, key=lambda group_object: group_object.group_name) image_groups.sort(key=lambda group_object: get_locale_key(group_object.group_name))
folder_icon = build_icon(u':/images/image_group.png') folder_icon = build_icon(u':/images/image_group.png')
for image_group in image_groups: for image_group in image_groups:
group = QtGui.QTreeWidgetItem() group = QtGui.QTreeWidgetItem()
@ -286,7 +286,7 @@ class ImageMediaItem(MediaManagerItem):
combobox.clear() combobox.clear()
combobox.top_level_group_added = False combobox.top_level_group_added = False
image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == parent_group_id) image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == parent_group_id)
image_groups.sort(cmp=locale_compare, key=lambda group_object: group_object.group_name) image_groups.sort(key=lambda group_object: get_locale_key(group_object.group_name))
for image_group in image_groups: for image_group in image_groups:
combobox.addItem(prefix + image_group.group_name, image_group.id) combobox.addItem(prefix + image_group.group_name, image_group.id)
self.fill_groups_combobox(combobox, image_group.id, prefix + ' ') self.fill_groups_combobox(combobox, image_group.id, prefix + ' ')
@ -338,7 +338,7 @@ class ImageMediaItem(MediaManagerItem):
self.expand_group(open_group.id) self.expand_group(open_group.id)
# Sort the images by its filename considering language specific # Sort the images by its filename considering language specific
# characters. # characters.
images.sort(cmp=locale_compare, key=lambda image_object: os.path.split(unicode(image_object.filename))[1]) images.sort(key=lambda image_object: get_locale_key(os.path.split(unicode(image_object.filename))[1]))
for imageFile in images: for imageFile in images:
log.debug(u'Loading image: %s', imageFile.filename) log.debug(u'Loading image: %s', imageFile.filename)
filename = os.path.split(imageFile.filename)[1] filename = os.path.split(imageFile.filename)[1]
@ -525,9 +525,9 @@ class ImageMediaItem(MediaManagerItem):
group_items.append(item) group_items.append(item)
if isinstance(item.data(0, QtCore.Qt.UserRole), ImageFilenames): if isinstance(item.data(0, QtCore.Qt.UserRole), ImageFilenames):
image_items.append(item) image_items.append(item)
group_items.sort(cmp=locale_compare, key=lambda item: item.text(0)) group_items.sort(key=lambda item: get_locale_key(item.text(0)))
target_group.addChildren(group_items) target_group.addChildren(group_items)
image_items.sort(cmp=locale_compare, key=lambda item: item.text(0)) image_items.sort(key=lambda item: get_locale_key(item.text(0)))
target_group.addChildren(image_items) target_group.addChildren(image_items)
def generate_slide_data(self, service_item, item=None, xmlVersion=False, def generate_slide_data(self, service_item, item=None, xmlVersion=False,

View File

@ -37,7 +37,7 @@ from openlp.core.lib import ItemCapabilities, MediaManagerItem,MediaType, Regist
from openlp.core.lib.ui import critical_error_message_box, create_horizontal_adjusting_combo_box from openlp.core.lib.ui import critical_error_message_box, create_horizontal_adjusting_combo_box
from openlp.core.ui import DisplayController, Display, DisplayControllerType from openlp.core.ui import DisplayController, Display, DisplayControllerType
from openlp.core.ui.media import get_media_players, set_media_players from openlp.core.ui.media import get_media_players, set_media_players
from openlp.core.utils import AppLocation, locale_compare from openlp.core.utils import AppLocation, get_locale_key
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -261,7 +261,7 @@ class MediaMediaItem(MediaManagerItem):
def load_list(self, media, target_group=None): def load_list(self, media, target_group=None):
# Sort the media by its filename considering language specific # Sort the media by its filename considering language specific
# characters. # characters.
media.sort(cmp=locale_compare, key=lambda filename: os.path.split(unicode(filename))[1]) media.sort(key=lambda filename: get_locale_key(os.path.split(unicode(filename))[1]))
for track in media: for track in media:
track_info = QtCore.QFileInfo(track) track_info = QtCore.QFileInfo(track)
if not os.path.exists(track): if not os.path.exists(track):
@ -287,7 +287,7 @@ class MediaMediaItem(MediaManagerItem):
def getList(self, type=MediaType.Audio): def getList(self, type=MediaType.Audio):
media = Settings().value(self.settings_section + u'/media files') media = Settings().value(self.settings_section + u'/media files')
media.sort(cmp=locale_compare, key=lambda filename: os.path.split(unicode(filename))[1]) media.sort(key=lambda filename: get_locale_key(os.path.split(unicode(filename))[1]))
ext = [] ext = []
if type == MediaType.Audio: if type == MediaType.Audio:
ext = self.media_controller.audio_extensions_list ext = self.media_controller.audio_extensions_list

View File

@ -35,7 +35,7 @@ from PyQt4 import QtCore, QtGui
from openlp.core.lib import MediaManagerItem, Registry, ItemCapabilities, ServiceItemContext, Settings, UiStrings, \ from openlp.core.lib import MediaManagerItem, Registry, ItemCapabilities, ServiceItemContext, Settings, UiStrings, \
build_icon, check_item_selected, create_thumb, translate, validate_thumb build_icon, check_item_selected, create_thumb, translate, validate_thumb
from openlp.core.lib.ui import critical_error_message_box, create_horizontal_adjusting_combo_box from openlp.core.lib.ui import critical_error_message_box, create_horizontal_adjusting_combo_box
from openlp.core.utils import locale_compare from openlp.core.utils import get_locale_key
from openlp.plugins.presentations.lib import MessageListener from openlp.plugins.presentations.lib import MessageListener
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -153,8 +153,7 @@ class PresentationMediaItem(MediaManagerItem):
if not initialLoad: if not initialLoad:
self.main_window.display_progress_bar(len(files)) self.main_window.display_progress_bar(len(files))
# Sort the presentations by its filename considering language specific characters. # Sort the presentations by its filename considering language specific characters.
files.sort(cmp=locale_compare, files.sort(key=lambda filename: get_locale_key(os.path.split(unicode(filename))[1]))
key=lambda filename: os.path.split(unicode(filename))[1])
for file in files: for file in files:
if not initialLoad: if not initialLoad:
self.main_window.increment_progress_bar() self.main_window.increment_progress_bar()

View File

@ -37,7 +37,6 @@ from PyQt4 import QtCore, QtGui
from openlp.core.lib import Registry, UiStrings, create_separated_list, build_icon, translate from openlp.core.lib import Registry, UiStrings, create_separated_list, build_icon, translate
from openlp.core.lib.ui import critical_error_message_box from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.wizard import OpenLPWizard, WizardStrings from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
from openlp.plugins.songs.lib import natcmp
from openlp.plugins.songs.lib.db import Song from openlp.plugins.songs.lib.db import Song
from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport
@ -222,7 +221,7 @@ class SongExportForm(OpenLPWizard):
# Load the list of songs. # Load the list of songs.
self.application.set_busy_cursor() self.application.set_busy_cursor()
songs = self.plugin.manager.get_all_objects(Song) songs = self.plugin.manager.get_all_objects(Song)
songs.sort(cmp=natcmp, key=lambda song: song.sort_key) songs.sort(key=lambda song: song.sort_key)
for song in songs: for song in songs:
# No need to export temporary songs. # No need to export temporary songs.
if song.temporary: if song.temporary:

View File

@ -34,7 +34,7 @@ import re
from PyQt4 import QtGui from PyQt4 import QtGui
from openlp.core.lib import translate from openlp.core.lib import translate
from openlp.core.utils import CONTROL_CHARS, locale_direct_compare from openlp.core.utils import CONTROL_CHARS
from db import Author from db import Author
from ui import SongStrings from ui import SongStrings
@ -593,37 +593,3 @@ def strip_rtf(text, default_encoding=None):
text = u''.join(out) text = u''.join(out)
return text, default_encoding return text, default_encoding
def natcmp(a, b):
"""
Natural string comparison which mimics the behaviour of Python's internal cmp function.
"""
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(str(key), b[i])
elif not isinstance(key, int) and isinstance(b[i], int):
result = locale_direct_compare(key, 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(str(a[i]), key)
elif not isinstance(a[i], int) and isinstance(key, int):
result = locale_direct_compare(a[i], str(key))
else:
result = locale_direct_compare(a[i], key)
if result != 0:
return result
return 1

View File

@ -38,6 +38,7 @@ from sqlalchemy.orm import mapper, relation, reconstructor
from sqlalchemy.sql.expression import func from sqlalchemy.sql.expression import func
from openlp.core.lib.db import BaseModel, init_db from openlp.core.lib.db import BaseModel, init_db
from openlp.core.utils import get_natural_key
class Author(BaseModel): class Author(BaseModel):
@ -69,36 +70,15 @@ class Song(BaseModel):
def __init__(self): def __init__(self):
self.sort_key = () self.sort_key = ()
def _try_int(self, s):
"""
Convert to integer if possible.
"""
try:
return int(s)
except:
return 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 is updated.
@reconstructor @reconstructor
def init_on_load(self): def init_on_load(self):
""" """
Precompute a tuple to be used for sorting. Precompute a natural sorting, locale aware sorting key.
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 sorting key.
used for comparison.
""" """
# Avoid the overhead of converting string to lowercase and to QString self.sort_key = get_natural_key(self.title)
# with every call to sort().
self.sort_key = self._natsort_key(self.title)
class Topic(BaseModel): class Topic(BaseModel):

View File

@ -43,7 +43,7 @@ from openlp.plugins.songs.forms.editsongform import EditSongForm
from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm
from openlp.plugins.songs.forms.songimportform import SongImportForm from openlp.plugins.songs.forms.songimportform import SongImportForm
from openlp.plugins.songs.forms.songexportform import SongExportForm from openlp.plugins.songs.forms.songexportform import SongExportForm
from openlp.plugins.songs.lib import VerseType, clean_string, natcmp from openlp.plugins.songs.lib import VerseType, clean_string
from openlp.plugins.songs.lib.db import Author, Song, Book, MediaFile from openlp.plugins.songs.lib.db import Author, Song, Book, MediaFile
from openlp.plugins.songs.lib.ui import SongStrings from openlp.plugins.songs.lib.ui import SongStrings
from openlp.plugins.songs.lib.xml import OpenLyrics, SongXML from openlp.plugins.songs.lib.xml import OpenLyrics, SongXML
@ -225,7 +225,7 @@ class SongMediaItem(MediaManagerItem):
log.debug(u'display results Song') log.debug(u'display results Song')
self.save_auto_select_id() self.save_auto_select_id()
self.list_view.clear() self.list_view.clear()
searchresults.sort(cmp=natcmp, key=lambda song: song.sort_key) searchresults.sort(key=lambda song: song.sort_key)
for song in searchresults: for song in searchresults:
# Do not display temporary songs # Do not display temporary songs
if song.temporary: if song.temporary:

View File

@ -84,6 +84,7 @@ MODULES = [
'cherrypy', 'cherrypy',
'migrate', 'migrate',
'uno', 'uno',
'icu',
] ]

View File

@ -5,7 +5,7 @@ from unittest import TestCase
from mock import patch from mock import patch
from openlp.core.utils import get_filesystem_encoding, _get_frozen_path from openlp.core.utils import get_filesystem_encoding, _get_frozen_path, get_locale_key, get_natural_key
class TestUtils(TestCase): class TestUtils(TestCase):
""" """
@ -56,3 +56,30 @@ class TestUtils(TestCase):
# THEN: The frozen parameter is returned # THEN: The frozen parameter is returned
assert _get_frozen_path(u'frozen', u'not frozen') == u'frozen', u'Should return "frozen"' assert _get_frozen_path(u'frozen', u'not frozen') == u'frozen', u'Should return "frozen"'
def get_locale_key_test(self):
"""
Test the get_locale_key(string) function
"""
with patch(u'openlp.core.utils.languagemanager.LanguageManager.get_language') as mocked_get_language:
# GIVEN: The language is German
# 0x00C3 (A with diaresis) should be sorted as "A". 0x00DF (sharp s) should be sorted as "ss".
mocked_get_language.return_value = u'de'
unsorted_list = [u'Auszug', u'Aushang', u'\u00C4u\u00DFerung']
# WHEN: We sort the list and use get_locale_key() to generate the sorting keys
# THEN: We get a properly sorted list
test_passes = sorted(unsorted_list, key=get_locale_key) == [u'Aushang', u'\u00C4u\u00DFerung', u'Auszug']
assert test_passes, u'Strings should be sorted properly'
def get_natural_key_test(self):
"""
Test the get_natural_key(string) function
"""
with patch(u'openlp.core.utils.languagemanager.LanguageManager.get_language') as mocked_get_language:
# GIVEN: The language is English (a language, which sorts digits before letters)
mocked_get_language.return_value = u'en'
unsorted_list = [u'item 10a', u'item 3b', u'1st item']
# WHEN: We sort the list and use get_natural_key() to generate the sorting keys
# THEN: We get a properly sorted list
test_passes = sorted(unsorted_list, key=get_natural_key) == [u'1st item', u'item 3b', u'item 10a']
assert test_passes, u'Numbers should be sorted naturally'