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:
parent
e6c023cb64
commit
d13f1a485f
10 changed files with 332 additions and 17 deletions
|
@ -45,6 +45,7 @@
|
|||
#include "cuesheet.h"
|
||||
#include "filetree.h"
|
||||
#include "misc.h"
|
||||
#include "strnatcmp.h"
|
||||
#ifdef HAVE_LCD_BITMAP
|
||||
#include "keyboard.h"
|
||||
#endif
|
||||
|
@ -242,12 +243,26 @@ static int compare(const void* p1, const void* p2)
|
|||
|
||||
case SORT_ALPHA:
|
||||
case SORT_ALPHA_REVERSED:
|
||||
{
|
||||
if (global_settings.sort_case)
|
||||
{
|
||||
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
|
||||
{
|
||||
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 */
|
||||
|
|
|
@ -12187,3 +12187,45 @@
|
|||
quickscreen: ""
|
||||
</voice>
|
||||
</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>
|
||||
|
|
|
@ -12359,3 +12359,45 @@
|
|||
*: "Credits"
|
||||
</voice>
|
||||
</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>
|
||||
|
|
|
@ -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_dir, &global_settings.sort_dir, 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(show_filename_ext, &global_settings.show_filename_ext, 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,
|
||||
&sort_case, &sort_dir, &sort_file,
|
||||
&sort_case, &sort_dir, &sort_file, &interpret_numbers,
|
||||
&dirfilter, &show_filename_ext, &browse_current,
|
||||
#ifdef HAVE_LCD_BITMAP
|
||||
&show_path_in_browser
|
||||
|
|
|
@ -147,6 +147,7 @@ enum { SHOW_ALL, SHOW_SUPPORTED, SHOW_MUSIC, SHOW_PLAYLIST, SHOW_ID3DB,
|
|||
/* file and dir sort options */
|
||||
enum { SORT_ALPHA, SORT_DATE, SORT_DATE_REVERSED, SORT_TYPE, /* available as settings */
|
||||
SORT_ALPHA_REVERSED, SORT_TYPE_REVERSED }; /* internal use only */
|
||||
enum { SORT_INTERPRET_AS_DIGIT, SORT_INTERPRET_AS_NUMBER };
|
||||
|
||||
/* recursive dir insert options */
|
||||
enum { RECURSE_OFF, RECURSE_ON, RECURSE_ASK };
|
||||
|
@ -612,8 +613,9 @@ struct user_settings
|
|||
|
||||
/* file browser sorting */
|
||||
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_file; /* 0=alpha, 1=date, 2=date (new first), 3=type */
|
||||
int interpret_numbers; /* true=strnatcmp, false=strcmp */
|
||||
|
||||
/* power settings */
|
||||
int poweroff; /* idle power off timer */
|
||||
|
|
|
@ -783,7 +783,20 @@ const struct settings_list settings[] = {
|
|||
"all,supported,music,playlists", NULL, 4, ID2P(LANG_ALL),
|
||||
ID2P(LANG_FILTER_SUPPORTED), ID2P(LANG_FILTER_MUSIC),
|
||||
ID2P(LANG_PLAYLISTS)),
|
||||
/* file sorting */
|
||||
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,
|
||||
"show filename exts", "off,on,unknown,view_all", NULL , 4 ,
|
||||
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,
|
||||
"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
|
||||
/* recording */
|
||||
STRINGCHOICE_SETTING(F_RECSETTING, rec_timesplit, LANG_SPLIT_TIME, 0,
|
||||
|
|
|
@ -450,7 +450,7 @@ Kenderes Tamas
|
|||
Eric Shattow
|
||||
Joshua Simmons
|
||||
Sei Aoyumi
|
||||
|
||||
Martin Pool
|
||||
|
||||
The libmad team
|
||||
The wavpack team
|
||||
|
|
|
@ -49,6 +49,7 @@ common/strcasestr.c
|
|||
common/strcat.c
|
||||
common/strchr.c
|
||||
common/strcmp.c
|
||||
common/strnatcmp.c
|
||||
common/strcpy.c
|
||||
common/strncmp.c
|
||||
common/strncpy.c
|
||||
|
|
179
firmware/common/strnatcmp.c
Normal file
179
firmware/common/strnatcmp.c
Normal 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);
|
||||
}
|
30
firmware/include/strnatcmp.h
Normal file
30
firmware/include/strnatcmp.h
Normal 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);
|
Loading…
Reference in a new issue