From 2f9d3e1c9f0ee114d68514353a7d69216be75bbc Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 23 Feb 2013 14:36:13 +0530 Subject: [PATCH] When sorting the book list on text fields like title, recognize numbers inside the text and sort them as number. So the text 'Book 2' will sort before 'Book 100'. If you prefer the old behavior, you can restore it via Preferences->Tweaks. Fixes #1132025 (Incorrect sort when title contains numbers) --- resources/default_tweaks.py | 8 ++++++++ src/calibre/utils/icu.c | 20 ++++++++++++++++++++ src/calibre/utils/icu.py | 2 ++ 3 files changed, 30 insertions(+) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index 1aa820819c..6b351ed18c 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -517,3 +517,11 @@ default_tweak_format = None # your library and your personal editing style. preselect_first_completion = False +#: Recognize numbers inside text when sorting +# This means that when sorting on text fields like title the text "Book 2" +# will sort before the text "Book 100". This is how humans usually expect +# things to sort. If you prefer the computer sort (which is a little faster, +# but will cause Book 100 to sort before Book 2), then set numeric_collation +# to False. +numeric_collation = True + diff --git a/src/calibre/utils/icu.c b/src/calibre/utils/icu.c index 3c133418b1..f1e8fce299 100644 --- a/src/calibre/utils/icu.c +++ b/src/calibre/utils/icu.c @@ -59,6 +59,7 @@ icu_Collator_new(PyTypeObject *type, PyObject *args, PyObject *kwds) PyErr_SetString(PyExc_Exception, "Failed to create collator."); return NULL; } + ucol_setAttribute(collator, UCOL_NUMERIC_COLLATION, UCOL_ON, &status); self = (icu_Collator *)type->tp_alloc(type, 0); if (self != NULL) { @@ -110,6 +111,21 @@ icu_Collator_set_strength(icu_Collator *self, PyObject *val, void *closure) { } // }}} +// Collator.numeric {{{ +static PyObject * +icu_Collator_get_numeric(icu_Collator *self, void *closure) { + UErrorCode status = U_ZERO_ERROR; + return Py_BuildValue("O", (ucol_getAttribute(self->collator, UCOL_NUMERIC_COLLATION, &status) == UCOL_ON) ? Py_True : Py_False); +} + +static int +icu_Collator_set_numeric(icu_Collator *self, PyObject *val, void *closure) { + UErrorCode status = U_ZERO_ERROR; + ucol_setAttribute(self->collator, UCOL_NUMERIC_COLLATION, (PyObject_IsTrue(val)) ? UCOL_ON : UCOL_OFF, &status); + return 0; +} +// }}} + // Collator.actual_locale {{{ static PyObject * icu_Collator_actual_locale(icu_Collator *self, void *closure) { @@ -415,6 +431,10 @@ static PyGetSetDef icu_Collator_getsetters[] = { (char *)"The strength of this collator.", NULL}, + {(char *)"numeric", + (getter)icu_Collator_get_numeric, (setter)icu_Collator_set_numeric, + (char *)"If True the collator sorts contiguous digits as numbers rather than strings, so 2 will sort before 10.", + NULL}, {NULL} /* Sentinel */ }; diff --git a/src/calibre/utils/icu.py b/src/calibre/utils/icu.py index 66ee8fd59f..aa08f293fa 100644 --- a/src/calibre/utils/icu.py +++ b/src/calibre/utils/icu.py @@ -46,6 +46,8 @@ def load_collator(): icu = load_icu() if icu is not None: _collator = icu.Collator(get_locale()) + if not tweaks['numeric_collation']: + _collator.numeric = False return _collator def primary_collator():