Commit FS#8314. This adds strnat[case]cmp written by Martin Pool, which respects numbers within strings, and gives a more intuitive

sorting. This also adds a setting, so that the sorting can be used in the file browser. The implementation is very generic, and can possibly 
be used in other places.


git-svn-id: svn://svn.rockbox.org/rockbox/trunk@20155 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
Thomas Martitz 2009-03-01 17:55:59 +00:00
parent e6c023cb64
commit d13f1a485f
10 changed files with 332 additions and 17 deletions

View file

@ -45,6 +45,7 @@
#include "cuesheet.h" #include "cuesheet.h"
#include "filetree.h" #include "filetree.h"
#include "misc.h" #include "misc.h"
#include "strnatcmp.h"
#ifdef HAVE_LCD_BITMAP #ifdef HAVE_LCD_BITMAP
#include "keyboard.h" #include "keyboard.h"
#endif #endif
@ -242,12 +243,26 @@ static int compare(const void* p1, const void* p2)
case SORT_ALPHA: case SORT_ALPHA:
case SORT_ALPHA_REVERSED: case SORT_ALPHA_REVERSED:
{
if (global_settings.sort_case) if (global_settings.sort_case)
return strncmp(e1->name, e2->name, MAX_PATH) {
* (criteria == SORT_ALPHA_REVERSED ? -1 : 1); if (global_settings.interpret_numbers == SORT_INTERPRET_AS_NUMBER)
return strnatcmp(e1->name, e2->name)
* (criteria == SORT_ALPHA_REVERSED ? -1 : 1);
else
return strncmp(e1->name, e2->name, MAX_PATH)
* (criteria == SORT_ALPHA_REVERSED ? -1 : 1);
}
else else
return strncasecmp(e1->name, e2->name, MAX_PATH) {
* (criteria == SORT_ALPHA_REVERSED ? -1 : 1); if (global_settings.interpret_numbers == SORT_INTERPRET_AS_NUMBER)
return strnatcasecmp(e1->name, e2->name)
* (criteria == SORT_ALPHA_REVERSED ? -1 : 1);
else
return strncasecmp(e1->name, e2->name, MAX_PATH)
* (criteria == SORT_ALPHA_REVERSED ? -1 : 1);
}
}
} }
return 0; /* never reached */ return 0; /* never reached */

View file

@ -12187,3 +12187,45 @@
quickscreen: "" quickscreen: ""
</voice> </voice>
</phrase> </phrase>
<phrase>
id: LANG_SORT_INTERPRET_NUMBERS
desc: in Settings -> File view
user:
<source>
*: "Interpret numbers when sorting"
</source>
<dest>
*: "Zahlen beim Sortieren interpretieren"
</dest>
<voice>
*: "Zahlen beim Sortieren interpretieren"
</voice>
</phrase>
<phrase>
id: LANG_SORT_INTERPRET_AS_DIGIT
desc: in Settings -> File view
user:
<source>
*: "As digits"
</source>
<dest>
*: "als Ziffern"
</dest>
<voice>
*: "als Ziffern"
</voice>
</phrase>
<phrase>
id: LANG_SORT_INTERPRET_AS_NUMBERS
desc: in Settings -> File view
user:
<source>
*: "As whole numbers"
</source>
<dest>
*: "als komplette Zahlen"
</dest>
<voice>
*: "als komplette Zahlen"
</voice>
</phrase>

View file

@ -12359,3 +12359,45 @@
*: "Credits" *: "Credits"
</voice> </voice>
</phrase> </phrase>
<phrase>
id: LANG_SORT_INTERPRET_NUMBERS
desc: in Settings -> File view
user:
<source>
*: "Interpret numbers when sorting"
</source>
<dest>
*: "Interpret numbers when sorting"
</dest>
<voice>
*: "Interpret numbers when sorting"
</voice>
</phrase>
<phrase>
id: LANG_SORT_INTERPRET_AS_DIGIT
desc: in Settings -> File view
user:
<source>
*: "As digits"
</source>
<dest>
*: "As digits"
</dest>
<voice>
*: "As digits"
</voice>
</phrase>
<phrase>
id: LANG_SORT_INTERPRET_AS_NUMBERS
desc: in Settings -> File view
user:
<source>
*: "As whole numbers"
</source>
<dest>
*: "As whole numbers"
</dest>
<voice>
*: "As whole numbers"
</voice>
</phrase>

View file

@ -100,6 +100,7 @@ static int fileview_callback(int action,const struct menu_item_ex *this_item);
MENUITEM_SETTING(sort_case, &global_settings.sort_case, NULL); MENUITEM_SETTING(sort_case, &global_settings.sort_case, NULL);
MENUITEM_SETTING(sort_dir, &global_settings.sort_dir, fileview_callback); MENUITEM_SETTING(sort_dir, &global_settings.sort_dir, fileview_callback);
MENUITEM_SETTING(sort_file, &global_settings.sort_file, fileview_callback); MENUITEM_SETTING(sort_file, &global_settings.sort_file, fileview_callback);
MENUITEM_SETTING(interpret_numbers, &global_settings.interpret_numbers, fileview_callback);
MENUITEM_SETTING(dirfilter, &global_settings.dirfilter, NULL); MENUITEM_SETTING(dirfilter, &global_settings.dirfilter, NULL);
MENUITEM_SETTING(show_filename_ext, &global_settings.show_filename_ext, NULL); MENUITEM_SETTING(show_filename_ext, &global_settings.show_filename_ext, NULL);
MENUITEM_SETTING(browse_current, &global_settings.browse_current, NULL); MENUITEM_SETTING(browse_current, &global_settings.browse_current, NULL);
@ -124,7 +125,7 @@ static int fileview_callback(int action,const struct menu_item_ex *this_item)
} }
MAKE_MENU(file_menu, ID2P(LANG_FILE), 0, Icon_file_view_menu, MAKE_MENU(file_menu, ID2P(LANG_FILE), 0, Icon_file_view_menu,
&sort_case, &sort_dir, &sort_file, &sort_case, &sort_dir, &sort_file, &interpret_numbers,
&dirfilter, &show_filename_ext, &browse_current, &dirfilter, &show_filename_ext, &browse_current,
#ifdef HAVE_LCD_BITMAP #ifdef HAVE_LCD_BITMAP
&show_path_in_browser &show_path_in_browser

View file

@ -147,6 +147,7 @@ enum { SHOW_ALL, SHOW_SUPPORTED, SHOW_MUSIC, SHOW_PLAYLIST, SHOW_ID3DB,
/* file and dir sort options */ /* file and dir sort options */
enum { SORT_ALPHA, SORT_DATE, SORT_DATE_REVERSED, SORT_TYPE, /* available as settings */ enum { SORT_ALPHA, SORT_DATE, SORT_DATE_REVERSED, SORT_TYPE, /* available as settings */
SORT_ALPHA_REVERSED, SORT_TYPE_REVERSED }; /* internal use only */ SORT_ALPHA_REVERSED, SORT_TYPE_REVERSED }; /* internal use only */
enum { SORT_INTERPRET_AS_DIGIT, SORT_INTERPRET_AS_NUMBER };
/* recursive dir insert options */ /* recursive dir insert options */
enum { RECURSE_OFF, RECURSE_ON, RECURSE_ASK }; enum { RECURSE_OFF, RECURSE_ON, RECURSE_ASK };
@ -612,8 +613,9 @@ struct user_settings
/* file browser sorting */ /* file browser sorting */
bool sort_case; /* dir sort order: 0=case insensitive, 1=sensitive */ bool sort_case; /* dir sort order: 0=case insensitive, 1=sensitive */
int sort_file; /* 0=alpha, 1=date, 2=date (new first), 3=type */
int sort_dir; /* 0=alpha, 1=date (old first), 2=date (new first) */ int sort_dir; /* 0=alpha, 1=date (old first), 2=date (new first) */
int sort_file; /* 0=alpha, 1=date, 2=date (new first), 3=type */
int interpret_numbers; /* true=strnatcmp, false=strcmp */
/* power settings */ /* power settings */
int poweroff; /* idle power off timer */ int poweroff; /* idle power off timer */

View file

@ -783,7 +783,20 @@ const struct settings_list settings[] = {
"all,supported,music,playlists", NULL, 4, ID2P(LANG_ALL), "all,supported,music,playlists", NULL, 4, ID2P(LANG_ALL),
ID2P(LANG_FILTER_SUPPORTED), ID2P(LANG_FILTER_MUSIC), ID2P(LANG_FILTER_SUPPORTED), ID2P(LANG_FILTER_MUSIC),
ID2P(LANG_PLAYLISTS)), ID2P(LANG_PLAYLISTS)),
/* file sorting */
OFFON_SETTING(0, sort_case, LANG_SORT_CASE, false, "sort case", NULL), OFFON_SETTING(0, sort_case, LANG_SORT_CASE, false, "sort case", NULL),
CHOICE_SETTING(0, sort_dir, LANG_SORT_DIR, 0 ,
"sort dirs", "alpha,oldest,newest", NULL, 3,
ID2P(LANG_SORT_ALPHA), ID2P(LANG_SORT_DATE),
ID2P(LANG_SORT_DATE_REVERSE)),
CHOICE_SETTING(0, sort_file, LANG_SORT_FILE, 0 ,
"sort files", "alpha,oldest,newest,type", NULL, 4,
ID2P(LANG_SORT_ALPHA), ID2P(LANG_SORT_DATE),
ID2P(LANG_SORT_DATE_REVERSE) , ID2P(LANG_SORT_TYPE)),
CHOICE_SETTING(0, interpret_numbers, LANG_SORT_INTERPRET_NUMBERS, 1,
"sort interpret number", "digits,numbers",NULL, 2,
ID2P(LANG_SORT_INTERPRET_AS_DIGIT),
ID2P(LANG_SORT_INTERPRET_AS_NUMBERS)),
CHOICE_SETTING(0, show_filename_ext, LANG_SHOW_FILENAME_EXT, 3, CHOICE_SETTING(0, show_filename_ext, LANG_SHOW_FILENAME_EXT, 3,
"show filename exts", "off,on,unknown,view_all", NULL , 4 , "show filename exts", "off,on,unknown,view_all", NULL , 4 ,
ID2P(LANG_OFF), ID2P(LANG_ON), ID2P(LANG_UNKNOWN_TYPES), ID2P(LANG_OFF), ID2P(LANG_ON), ID2P(LANG_UNKNOWN_TYPES),
@ -893,16 +906,6 @@ const struct settings_list settings[] = {
OFFON_SETTING(F_TEMPVAR, talk_battery_level, LANG_TALK_BATTERY_LEVEL, false, OFFON_SETTING(F_TEMPVAR, talk_battery_level, LANG_TALK_BATTERY_LEVEL, false,
"Announce Battery Level", NULL), "Announce Battery Level", NULL),
/* file sorting */
CHOICE_SETTING(0, sort_file, LANG_SORT_FILE, 0 ,
"sort files", "alpha,oldest,newest,type", NULL, 4,
ID2P(LANG_SORT_ALPHA), ID2P(LANG_SORT_DATE),
ID2P(LANG_SORT_DATE_REVERSE) , ID2P(LANG_SORT_TYPE)),
CHOICE_SETTING(0, sort_dir, LANG_SORT_DIR, 0 ,
"sort dirs", "alpha,oldest,newest", NULL, 3,
ID2P(LANG_SORT_ALPHA), ID2P(LANG_SORT_DATE),
ID2P(LANG_SORT_DATE_REVERSE)),
#ifdef HAVE_RECORDING #ifdef HAVE_RECORDING
/* recording */ /* recording */
STRINGCHOICE_SETTING(F_RECSETTING, rec_timesplit, LANG_SPLIT_TIME, 0, STRINGCHOICE_SETTING(F_RECSETTING, rec_timesplit, LANG_SPLIT_TIME, 0,

View file

@ -450,7 +450,7 @@ Kenderes Tamas
Eric Shattow Eric Shattow
Joshua Simmons Joshua Simmons
Sei Aoyumi Sei Aoyumi
Martin Pool
The libmad team The libmad team
The wavpack team The wavpack team

View file

@ -49,6 +49,7 @@ common/strcasestr.c
common/strcat.c common/strcat.c
common/strchr.c common/strchr.c
common/strcmp.c common/strcmp.c
common/strnatcmp.c
common/strcpy.c common/strcpy.c
common/strncmp.c common/strncmp.c
common/strncpy.c common/strncpy.c

179
firmware/common/strnatcmp.c Normal file
View file

@ -0,0 +1,179 @@
/* -*- mode: c; c-file-style: "k&r" -*-
strnatcmp.c -- Perform 'natural order' comparisons of strings in C.
Copyright (C) 2000, 2004 by Martin Pool <mbp sourcefrog net>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/* partial change history:
*
* 2004-10-10 mbp: Lift out character type dependencies into macros.
*
* Eric Sosman pointed out that ctype functions take a parameter whose
* value must be that of an unsigned int, even on platforms that have
* negative chars in their default char type.
*/
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include "strnatcmp.h"
#define assert(x) /* nothing */
/* These are defined as macros to make it easier to adapt this code to
* different characters types or comparison functions. */
static inline int
nat_isdigit(char a)
{
return isdigit((unsigned char) a);
}
static inline int
nat_isspace(char a)
{
return a == '0' || isspace((unsigned char) a);
}
static inline char
nat_toupper(char a)
{
return toupper((unsigned char) a);
}
static int
compare_right(char const *a, char const *b)
{
int bias = 0;
/* The longest run of digits wins. That aside, the greatest
value wins, but we can't know that it will until we've scanned
both numbers to know that they have the same magnitude, so we
remember it in BIAS. */
for (;; a++, b++) {
if (!nat_isdigit(*a) && !nat_isdigit(*b))
return bias;
else if (!nat_isdigit(*a))
return -1;
else if (!nat_isdigit(*b))
return +1;
else if (*a < *b) {
if (!bias)
bias = -1;
} else if (*a > *b) {
if (!bias)
bias = +1;
} else if (!*a && !*b)
return bias;
}
return 0;
}
static int
compare_left(char const *a, char const *b)
{
/* Compare two left-aligned numbers: the first to have a
different value wins. */
for (;; a++, b++) {
if (!nat_isdigit(*a) && !nat_isdigit(*b))
return 0;
else if (!nat_isdigit(*a))
return -1;
else if (!nat_isdigit(*b))
return +1;
else if (*a < *b)
return -1;
else if (*a > *b)
return +1;
}
return 0;
}
static int strnatcmp0(char const *a, char const *b, int fold_case)
{
int ai, bi;
char ca, cb;
int fractional, result;
assert(a && b);
ai = bi = 0;
while (1) {
ca = a[ai]; cb = b[bi];
/* skip over leading spaces or zeros */
while (nat_isspace(ca))
ca = a[++ai];
while (nat_isspace(cb))
cb = b[++bi];
/* process run of digits */
if (nat_isdigit(ca) && nat_isdigit(cb)) {
fractional = (ca == '0' || cb == '0');
if (fractional) {
if ((result = compare_left(a+ai, b+bi)) != 0)
return result;
} else {
if ((result = compare_right(a+ai, b+bi)) != 0)
return result;
}
}
if (!ca && !cb) {
/* The strings compare the same. Perhaps the caller
will want to call strcmp to break the tie. */
return 0;
}
if (fold_case) {
ca = nat_toupper(ca);
cb = nat_toupper(cb);
}
if (ca < cb)
return -1;
else if (ca > cb)
return +1;
++ai; ++bi;
}
}
int strnatcmp(const char *a, const char *b) {
return strnatcmp0(a, b, 0);
}
/* Compare, recognizing numeric string and ignoring case. */
int strnatcasecmp(const char *a, const char *b) {
return strnatcmp0(a, b, 1);
}

View file

@ -0,0 +1,30 @@
/* -*- mode: c; c-file-style: "k&r" -*-
strnatcmp.c -- Perform 'natural order' comparisons of strings in C.
Copyright (C) 2000, 2004 by Martin Pool <mbp sourcefrog net>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/* CUSTOMIZATION SECTION
*
* You can change this typedef, but must then also change the inline
* functions in strnatcmp.c */
int strnatcmp(const char *a, const char *b);
int strnatcasecmp(const char *a, const char *b);