diff --git a/apps/filetree.c b/apps/filetree.c
index fc5e4d3934..0ceb5c0941 100644
--- a/apps/filetree.c
+++ b/apps/filetree.c
@@ -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)
- 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
- 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 */
diff --git a/apps/lang/deutsch.lang b/apps/lang/deutsch.lang
index 6654688e6c..bc91c81c68 100644
--- a/apps/lang/deutsch.lang
+++ b/apps/lang/deutsch.lang
@@ -12187,3 +12187,45 @@
quickscreen: ""
+
+ id: LANG_SORT_INTERPRET_NUMBERS
+ desc: in Settings -> File view
+ user:
+
+
+ *: "Zahlen beim Sortieren interpretieren"
+
+
+ *: "Zahlen beim Sortieren interpretieren"
+
+
+
+ id: LANG_SORT_INTERPRET_AS_DIGIT
+ desc: in Settings -> File view
+ user:
+
+
+ *: "als Ziffern"
+
+
+ *: "als Ziffern"
+
+
+
+ id: LANG_SORT_INTERPRET_AS_NUMBERS
+ desc: in Settings -> File view
+ user:
+
+
+ *: "als komplette Zahlen"
+
+
+ *: "als komplette Zahlen"
+
+
diff --git a/apps/lang/english.lang b/apps/lang/english.lang
index a335ad179e..25cab34430 100644
--- a/apps/lang/english.lang
+++ b/apps/lang/english.lang
@@ -12359,3 +12359,45 @@
*: "Credits"
+
+ id: LANG_SORT_INTERPRET_NUMBERS
+ desc: in Settings -> File view
+ user:
+
+
+ *: "Interpret numbers when sorting"
+
+
+ *: "Interpret numbers when sorting"
+
+
+
+ id: LANG_SORT_INTERPRET_AS_DIGIT
+ desc: in Settings -> File view
+ user:
+
+
+ *: "As digits"
+
+
+ *: "As digits"
+
+
+
+ id: LANG_SORT_INTERPRET_AS_NUMBERS
+ desc: in Settings -> File view
+ user:
+
+
+ *: "As whole numbers"
+
+
+ *: "As whole numbers"
+
+
diff --git a/apps/menus/settings_menu.c b/apps/menus/settings_menu.c
index 85e4f9649f..35db9b49e6 100644
--- a/apps/menus/settings_menu.c
+++ b/apps/menus/settings_menu.c
@@ -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
diff --git a/apps/settings.h b/apps/settings.h
index 7f5f025a8f..669000ea42 100644
--- a/apps/settings.h
+++ b/apps/settings.h
@@ -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 */
diff --git a/apps/settings_list.c b/apps/settings_list.c
index 97ca7cdca5..5729cdcf4a 100644
--- a/apps/settings_list.c
+++ b/apps/settings_list.c
@@ -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,
diff --git a/docs/CREDITS b/docs/CREDITS
index 5ae39f468a..e9aa7d7af2 100644
--- a/docs/CREDITS
+++ b/docs/CREDITS
@@ -450,7 +450,7 @@ Kenderes Tamas
Eric Shattow
Joshua Simmons
Sei Aoyumi
-
+Martin Pool
The libmad team
The wavpack team
diff --git a/firmware/SOURCES b/firmware/SOURCES
index de42ada0cc..0b6bb155b0 100644
--- a/firmware/SOURCES
+++ b/firmware/SOURCES
@@ -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
diff --git a/firmware/common/strnatcmp.c b/firmware/common/strnatcmp.c
new file mode 100644
index 0000000000..84e0d38362
--- /dev/null
+++ b/firmware/common/strnatcmp.c
@@ -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
+
+ 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
+#include
+#include
+
+#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);
+}
diff --git a/firmware/include/strnatcmp.h b/firmware/include/strnatcmp.h
new file mode 100644
index 0000000000..50276554e6
--- /dev/null
+++ b/firmware/include/strnatcmp.h
@@ -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
+
+ 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);