summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apps/gui/wps.c4
-rw-r--r--apps/lang/english.lang14
-rw-r--r--apps/lang/francais.lang14
-rw-r--r--apps/onplay.c13
-rw-r--r--apps/onplay.h7
-rw-r--r--apps/playlist_viewer.c2
-rw-r--r--apps/settings_list.c8
-rw-r--r--apps/tagnavi.config15
-rw-r--r--apps/tagtree.c228
-rw-r--r--apps/tagtree.h1
-rw-r--r--apps/tree.c27
-rw-r--r--apps/tree.h3
-rw-r--r--manual/rockbox_interface/tagcache.tex15
13 files changed, 263 insertions, 88 deletions
diff --git a/apps/gui/wps.c b/apps/gui/wps.c
index 0de805bd02..82b0394a53 100644
--- a/apps/gui/wps.c
+++ b/apps/gui/wps.c
@@ -806,7 +806,7 @@ long gui_wps_show(void)
806 theme_enabled = false; 806 theme_enabled = false;
807 gwps_leave_wps(theme_enabled); 807 gwps_leave_wps(theme_enabled);
808 onplay(state->id3->path, 808 onplay(state->id3->path,
809 FILE_ATTR_AUDIO, CONTEXT_WPS, hotkey); 809 FILE_ATTR_AUDIO, CONTEXT_WPS, hotkey, ONPLAY_NO_CUSTOMACTION);
810 if (!audio_status()) 810 if (!audio_status())
811 { 811 {
812 /* re-enable theme since we're returning to SBS */ 812 /* re-enable theme since we're returning to SBS */
@@ -823,7 +823,7 @@ long gui_wps_show(void)
823 { 823 {
824 gwps_leave_wps(true); 824 gwps_leave_wps(true);
825 int retval = onplay(state->id3->path, 825 int retval = onplay(state->id3->path,
826 FILE_ATTR_AUDIO, CONTEXT_WPS, hotkey); 826 FILE_ATTR_AUDIO, CONTEXT_WPS, hotkey, ONPLAY_NO_CUSTOMACTION);
827 /* if music is stopped in the context menu we want to exit the wps */ 827 /* if music is stopped in the context menu we want to exit the wps */
828 if (retval == ONPLAY_MAINMENU 828 if (retval == ONPLAY_MAINMENU
829 || !audio_status()) 829 || !audio_status())
diff --git a/apps/lang/english.lang b/apps/lang/english.lang
index b0c7aed8f7..348f339239 100644
--- a/apps/lang/english.lang
+++ b/apps/lang/english.lang
@@ -2054,6 +2054,20 @@
2054 </voice> 2054 </voice>
2055</phrase> 2055</phrase>
2056<phrase> 2056<phrase>
2057 id: LANG_RANDOM_SHUFFLE_RANDOM_SELECTIVE_SONGS_SUMMARY
2058 desc: a summary splash screen that appear on the database browser when you try to create a playlist from the database browser that exceeds your system limit
2059 user: core
2060 <source>
2061 *: "Selection too big, %d random tracks will be picked from it"
2062 </source>
2063 <dest>
2064 *: "Selection too big, %d random tracks will be picked from it"
2065 </dest>
2066 <voice>
2067 *: "Selection too big, fewer random tracks will be picked from it"
2068 </voice>
2069</phrase>
2070<phrase>
2057 id: LANG_TAGCACHE_RAM 2071 id: LANG_TAGCACHE_RAM
2058 desc: in tag cache settings 2072 desc: in tag cache settings
2059 user: core 2073 user: core
diff --git a/apps/lang/francais.lang b/apps/lang/francais.lang
index ce907372b0..518b48fb96 100644
--- a/apps/lang/francais.lang
+++ b/apps/lang/francais.lang
@@ -2028,6 +2028,20 @@
2028 </voice> 2028 </voice>
2029</phrase> 2029</phrase>
2030<phrase> 2030<phrase>
2031 id: LANG_RANDOM_SHUFFLE_RANDOM_SELECTIVE_SONGS_SUMMARY
2032 desc: a summary splash screen that appear on the database browser when you try to create a playlist from the database browser that exceeds your system limit
2033 user: core
2034 <source>
2035 *: "Selection too big, %d random tracks will be picked from it"
2036 </source>
2037 <dest>
2038 *: "Selection trop grande, %d pistes seront sélectionnées aléatoirement depuis celle-ci"
2039 </dest>
2040 <voice>
2041 *: "Selection trop grande, donc des pistes seront sélectionnées aléatoirement depuis celle-ci"
2042 </voice>
2043</phrase>
2044<phrase>
2031 id: LANG_TAGCACHE_RAM 2045 id: LANG_TAGCACHE_RAM
2032 desc: in tag cache settings 2046 desc: in tag cache settings
2033 user: core 2047 user: core
diff --git a/apps/onplay.c b/apps/onplay.c
index 4f748204df..045af275bc 100644
--- a/apps/onplay.c
+++ b/apps/onplay.c
@@ -302,7 +302,7 @@ static int add_to_playlist(void* arg)
302 302
303 /* warn if replacing the playlist */ 303 /* warn if replacing the playlist */
304 if (new_playlist && !warn_on_pl_erase()) 304 if (new_playlist && !warn_on_pl_erase())
305 return 0; 305 return 1;
306 306
307 splash(0, ID2P(LANG_WAIT)); 307 splash(0, ID2P(LANG_WAIT));
308 308
@@ -340,7 +340,7 @@ static int add_to_playlist(void* arg)
340 } 340 }
341 341
342 playlist_set_modified(NULL, true); 342 playlist_set_modified(NULL, true);
343 return false; 343 return 0;
344} 344}
345 345
346static bool view_playlist(void) 346static bool view_playlist(void)
@@ -1255,7 +1255,7 @@ static int execute_hotkey(bool is_wps)
1255} 1255}
1256#endif /* HOTKEY */ 1256#endif /* HOTKEY */
1257 1257
1258int onplay(char* file, int attr, int from_context, bool hotkey) 1258int onplay(char* file, int attr, int from_context, bool hotkey, int customaction)
1259{ 1259{
1260 const struct menu_item_ex *menu; 1260 const struct menu_item_ex *menu;
1261 onplay_result = ONPLAY_OK; 1261 onplay_result = ONPLAY_OK;
@@ -1294,6 +1294,13 @@ int onplay(char* file, int attr, int from_context, bool hotkey)
1294#else 1294#else
1295 (void)hotkey; 1295 (void)hotkey;
1296#endif 1296#endif
1297 if (customaction == ONPLAY_CUSTOMACTION_SHUFFLE_SONGS) {
1298 int returnCode = add_to_playlist(&addtopl_replace_shuffled);
1299 if (returnCode == 1)
1300 // User did not want to erase his current playlist, so let's show again the database main menu
1301 return ONPLAY_RELOAD_DIR;
1302 return ONPLAY_START_PLAY;
1303 }
1297 1304
1298 push_current_activity(ACTIVITY_CONTEXTMENU); 1305 push_current_activity(ACTIVITY_CONTEXTMENU);
1299 if (from_context == CONTEXT_WPS) 1306 if (from_context == CONTEXT_WPS)
diff --git a/apps/onplay.h b/apps/onplay.h
index 74dc045db3..03861e9cf6 100644
--- a/apps/onplay.h
+++ b/apps/onplay.h
@@ -25,7 +25,12 @@
25#include "menu.h" 25#include "menu.h"
26#endif 26#endif
27 27
28int onplay(char* file, int attr, int from_context, bool hotkey); 28enum {
29 ONPLAY_NO_CUSTOMACTION,
30 ONPLAY_CUSTOMACTION_SHUFFLE_SONGS,
31};
32
33int onplay(char* file, int attr, int from_context, bool hotkey, int customaction);
29int get_onplay_context(void); 34int get_onplay_context(void);
30 35
31enum { 36enum {
diff --git a/apps/playlist_viewer.c b/apps/playlist_viewer.c
index 70072f59f5..5bf547a3fc 100644
--- a/apps/playlist_viewer.c
+++ b/apps/playlist_viewer.c
@@ -1107,7 +1107,7 @@ enum playlist_viewer_result playlist_viewer_ex(const char* filename,
1107 } 1107 }
1108 } 1108 }
1109 else 1109 else
1110 onplay(current_track->name, FILE_ATTR_AUDIO, CONTEXT_STD, true); 1110 onplay(current_track->name, FILE_ATTR_AUDIO, CONTEXT_STD, true, ONPLAY_NO_CUSTOMACTION);
1111 break; 1111 break;
1112 } 1112 }
1113#endif /* HAVE_HOTKEY */ 1113#endif /* HAVE_HOTKEY */
diff --git a/apps/settings_list.c b/apps/settings_list.c
index 8ec434bd9b..3b29703fe9 100644
--- a/apps/settings_list.c
+++ b/apps/settings_list.c
@@ -1120,7 +1120,11 @@ const struct settings_list settings[] = {
1120 SYSTEM_SETTING(NVRAM(4), topruntime, 0), 1120 SYSTEM_SETTING(NVRAM(4), topruntime, 0),
1121 INT_SETTING(F_BANFROMQS, max_files_in_playlist, 1121 INT_SETTING(F_BANFROMQS, max_files_in_playlist,
1122 LANG_MAX_FILES_IN_PLAYLIST, 1122 LANG_MAX_FILES_IN_PLAYLIST,
1123#if MEMORYSIZE > 1 1123#if CONFIG_CPU == PP5002 || CONFIG_CPU == PP5020 || CONFIG_CPU == PP5022
1124 /** Slow CPU benefits greatly from building smaller playlists
1125 On the iPod Mini 2nd gen, creating a playlist of 2000 entries takes around 10 seconds */
1126 2000,
1127#elif MEMORYSIZE > 1
1124 10000, 1128 10000,
1125#else 1129#else
1126 400, 1130 400,
@@ -1854,7 +1858,7 @@ const struct settings_list settings[] = {
1854 true, "warn when erasing dynamic playlist",NULL), 1858 true, "warn when erasing dynamic playlist",NULL),
1855 OFFON_SETTING(0, keep_current_track_on_replace_playlist, LANG_KEEP_CURRENT_TRACK_ON_REPLACE, 1859 OFFON_SETTING(0, keep_current_track_on_replace_playlist, LANG_KEEP_CURRENT_TRACK_ON_REPLACE,
1856 true, "keep current track when replacing playlist",NULL), 1860 true, "keep current track when replacing playlist",NULL),
1857 OFFON_SETTING(0, show_shuffled_adding_options, LANG_SHOW_SHUFFLED_ADDING_OPTIONS, false, 1861 OFFON_SETTING(0, show_shuffled_adding_options, LANG_SHOW_SHUFFLED_ADDING_OPTIONS, true,
1858 "show shuffled adding options", NULL), 1862 "show shuffled adding options", NULL),
1859 CHOICE_SETTING(0, show_queue_options, LANG_SHOW_QUEUE_OPTIONS, 0, 1863 CHOICE_SETTING(0, show_queue_options, LANG_SHOW_QUEUE_OPTIONS, 0,
1860 "show queue options", "off,on,in submenu", 1864 "show queue options", "off,on,in submenu",
diff --git a/apps/tagnavi.config b/apps/tagnavi.config
index 6eda05ec44..6baa6e1328 100644
--- a/apps/tagnavi.config
+++ b/apps/tagnavi.config
@@ -176,20 +176,21 @@
176 176
177# Define the title of the main menu 177# Define the title of the main menu
178%menu_start "main" "Database" 178%menu_start "main" "Database"
179"Artist" -> canonicalartist -> album -> title = "fmt_title"
180"Album Artist" -> albumartist -> album -> title = "fmt_title" 179"Album Artist" -> albumartist -> album -> title = "fmt_title"
180"Artist" -> canonicalartist -> album -> title = "fmt_title"
181"Album" -> album -> title = "fmt_title" 181"Album" -> album -> title = "fmt_title"
182"Genre" -> genre -> canonicalartist -> album -> title = "fmt_title" 182"Genre" -> genre -> canonicalartist -> album -> title = "fmt_title"
183"Year" -> year ? year > "0" -> canonicalartist -> album -> title = "fmt_title"
183"Composer" -> composer -> album -> title = "fmt_title" 184"Composer" -> composer -> album -> title = "fmt_title"
185"A to Z" ==> "a2z"
184"Track" ==> "track" 186"Track" ==> "track"
185"Year" -> year ? year > "0" -> canonicalartist -> album -> title = "fmt_title" 187"Shuffle Songs" ~> title = "fmt_title"
188"Search" ==> "search"
186"User Rating" -> rating -> title = "fmt_title" 189"User Rating" -> rating -> title = "fmt_title"
187"Recently Added" -> album ? entryage < "4" & commitid > "0" -> title = "fmt_title" 190"Recently Added" -> album ? entryage < "4" & commitid > "0" -> title = "fmt_title"
188"A to Z..." ==> "a2z" 191"History" ==> "runtime"
189"History..." ==> "runtime" 192"Same as current" ==> "same"
190"Same as current..." ==> "same" 193"Custom view" ==> "custom"
191"Search..." ==> "search"
192"Custom view..." ==> "custom"
193 194
194# And finally set main menu as our root menu 195# And finally set main menu as our root menu
195%root_menu "main" 196%root_menu "main"
diff --git a/apps/tagtree.c b/apps/tagtree.c
index d2e27a3e58..4a0bff32bd 100644
--- a/apps/tagtree.c
+++ b/apps/tagtree.c
@@ -56,6 +56,7 @@
56#include "playback.h" 56#include "playback.h"
57#include "strnatcmp.h" 57#include "strnatcmp.h"
58#include "panic.h" 58#include "panic.h"
59#include "onplay.h"
59 60
60#define str_or_empty(x) (x ? x : "(NULL)") 61#define str_or_empty(x) (x ? x : "(NULL)")
61 62
@@ -71,6 +72,7 @@ struct tagentry {
71 char* name; 72 char* name;
72 int newtable; 73 int newtable;
73 int extraseek; 74 int extraseek;
75 int customaction;
74}; 76};
75 77
76static struct tagentry* tagtree_get_entry(struct tree_context *c, int id); 78static struct tagentry* tagtree_get_entry(struct tree_context *c, int id);
@@ -78,10 +80,10 @@ static struct tagentry* tagtree_get_entry(struct tree_context *c, int id);
78#define SEARCHSTR_SIZE 256 80#define SEARCHSTR_SIZE 256
79 81
80enum table { 82enum table {
81 ROOT = 1, 83 TABLE_ROOT = 1,
82 NAVIBROWSE, 84 TABLE_NAVIBROWSE,
83 ALLSUBENTRIES, 85 TABLE_ALLSUBENTRIES,
84 PLAYTRACK, 86 TABLE_PLAYTRACK,
85}; 87};
86 88
87static const struct id3_to_search_mapping { 89static const struct id3_to_search_mapping {
@@ -108,12 +110,21 @@ enum variables {
108 menu_next, 110 menu_next,
109 menu_load, 111 menu_load,
110 menu_reload, 112 menu_reload,
113 menu_shuffle_songs,
111}; 114};
112 115
113/* Capacity 10 000 entries (for example 10k different artists) */ 116/* Capacity 10 000 entries (for example 10k different artists) */
114#define UNIQBUF_SIZE (64*1024) 117#define UNIQBUF_SIZE (64*1024)
115static uint32_t uniqbuf[UNIQBUF_SIZE / sizeof(uint32_t)]; 118static uint32_t uniqbuf[UNIQBUF_SIZE / sizeof(uint32_t)];
116 119
120#if MEMORYSIZE > 2
121 #define INSERT_ALL_PLAYLIST_MAX_SEGMENT_SIZE (1024)
122#else
123 /* Lower quality randomness for low-ram devices using smaller segments */
124 #define INSERT_ALL_PLAYLIST_MAX_SEGMENT_SIZE (128)
125#endif
126static bool selective_random_playlist_indexes[INSERT_ALL_PLAYLIST_MAX_SEGMENT_SIZE];
127
117#define MAX_TAGS 5 128#define MAX_TAGS 5
118#define MAX_MENU_ID_SIZE 32 129#define MAX_MENU_ID_SIZE 32
119 130
@@ -338,6 +349,7 @@ static int get_tag(int *tag)
338 TAG_MATCH("Pm", tag_virt_playtime_min), 349 TAG_MATCH("Pm", tag_virt_playtime_min),
339 TAG_MATCH("Ps", tag_virt_playtime_sec), 350 TAG_MATCH("Ps", tag_virt_playtime_sec),
340 TAG_MATCH("->", menu_next), 351 TAG_MATCH("->", menu_next),
352 TAG_MATCH("~>", menu_shuffle_songs),
341 353
342 TAG_MATCH("==>", menu_load), 354 TAG_MATCH("==>", menu_load),
343 355
@@ -820,7 +832,7 @@ static bool parse_search(struct menu_entry *entry, const char *str)
820 return true; 832 return true;
821 } 833 }
822 834
823 if (entry->type != menu_next) 835 if (entry->type != menu_next && entry->type != menu_shuffle_songs)
824 return false; 836 return false;
825 837
826 while (inst->tagorder_count < MAX_TAGS) 838 while (inst->tagorder_count < MAX_TAGS)
@@ -847,7 +859,7 @@ static bool parse_search(struct menu_entry *entry, const char *str)
847 859
848 inst->tagorder_count++; 860 inst->tagorder_count++;
849 861
850 if (get_tag(&type) <= 0 || type != menu_next) 862 if (get_tag(&type) <= 0 || (type != menu_next && type != menu_shuffle_songs))
851 break; 863 break;
852 } 864 }
853 865
@@ -1245,6 +1257,7 @@ static void tagtree_unload(struct tree_context *c)
1245 dptr->name = NULL; 1257 dptr->name = NULL;
1246 dptr->newtable = 0; 1258 dptr->newtable = 0;
1247 dptr->extraseek = 0; 1259 dptr->extraseek = 0;
1260 dptr->customaction = ONPLAY_NO_CUSTOMACTION;
1248 dptr++; 1261 dptr++;
1249 } 1262 }
1250 } 1263 }
@@ -1454,7 +1467,7 @@ static int retrieve_entries(struct tree_context *c, int offset, bool init)
1454#endif 1467#endif
1455 , 0, 0, 0); 1468 , 0, 0, 0);
1456 1469
1457 if (c->currtable == ALLSUBENTRIES) 1470 if (c->currtable == TABLE_ALLSUBENTRIES)
1458 { 1471 {
1459 tag = tag_title; 1472 tag = tag_title;
1460 level--; 1473 level--;
@@ -1544,17 +1557,19 @@ static int retrieve_entries(struct tree_context *c, int offset, bool init)
1544 { 1557 {
1545 if (offset == 0) 1558 if (offset == 0)
1546 { 1559 {
1547 dptr->newtable = ALLSUBENTRIES; 1560 dptr->newtable = TABLE_ALLSUBENTRIES;
1548 dptr->name = str(LANG_TAGNAVI_ALL_TRACKS); 1561 dptr->name = str(LANG_TAGNAVI_ALL_TRACKS);
1562 dptr->customaction = ONPLAY_NO_CUSTOMACTION;
1549 dptr++; 1563 dptr++;
1550 current_entry_count++; 1564 current_entry_count++;
1551 special_entry_count++; 1565 special_entry_count++;
1552 } 1566 }
1553 if (offset <= 1) 1567 if (offset <= 1)
1554 { 1568 {
1555 dptr->newtable = NAVIBROWSE; 1569 dptr->newtable = TABLE_NAVIBROWSE;
1556 dptr->name = str(LANG_TAGNAVI_RANDOM); 1570 dptr->name = str(LANG_TAGNAVI_RANDOM);
1557 dptr->extraseek = -1; 1571 dptr->extraseek = -1;
1572 dptr->customaction = ONPLAY_NO_CUSTOMACTION;
1558 dptr++; 1573 dptr++;
1559 current_entry_count++; 1574 current_entry_count++;
1560 special_entry_count++; 1575 special_entry_count++;
@@ -1568,14 +1583,15 @@ static int retrieve_entries(struct tree_context *c, int offset, bool init)
1568 if (total_count++ < offset) 1583 if (total_count++ < offset)
1569 continue; 1584 continue;
1570 1585
1571 dptr->newtable = NAVIBROWSE; 1586 dptr->newtable = TABLE_NAVIBROWSE;
1572 if (tag == tag_title || tag == tag_filename) 1587 if (tag == tag_title || tag == tag_filename)
1573 { 1588 {
1574 dptr->newtable = PLAYTRACK; 1589 dptr->newtable = TABLE_PLAYTRACK;
1575 dptr->extraseek = tcs.idx_id; 1590 dptr->extraseek = tcs.idx_id;
1576 } 1591 }
1577 else 1592 else
1578 dptr->extraseek = tcs.result_seek; 1593 dptr->extraseek = tcs.result_seek;
1594 dptr->customaction = ONPLAY_NO_CUSTOMACTION;
1579 1595
1580 fmt = NULL; 1596 fmt = NULL;
1581 /* Check the format */ 1597 /* Check the format */
@@ -1758,7 +1774,7 @@ static int load_root(struct tree_context *c)
1758 int i; 1774 int i;
1759 1775
1760 tc = c; 1776 tc = c;
1761 c->currtable = ROOT; 1777 c->currtable = TABLE_ROOT;
1762 if (c->dirlevel == 0) 1778 if (c->dirlevel == 0)
1763 c->currextra = rootmenu; 1779 c->currextra = rootmenu;
1764 1780
@@ -1775,13 +1791,21 @@ static int load_root(struct tree_context *c)
1775 switch (menu->items[i]->type) 1791 switch (menu->items[i]->type)
1776 { 1792 {
1777 case menu_next: 1793 case menu_next:
1778 dptr->newtable = NAVIBROWSE; 1794 dptr->newtable = TABLE_NAVIBROWSE;
1779 dptr->extraseek = i; 1795 dptr->extraseek = i;
1796 dptr->customaction = ONPLAY_NO_CUSTOMACTION;
1780 break; 1797 break;
1781 1798
1782 case menu_load: 1799 case menu_load:
1783 dptr->newtable = ROOT; 1800 dptr->newtable = TABLE_ROOT;
1784 dptr->extraseek = menu->items[i]->link; 1801 dptr->extraseek = menu->items[i]->link;
1802 dptr->customaction = ONPLAY_NO_CUSTOMACTION;
1803 break;
1804
1805 case menu_shuffle_songs:
1806 dptr->newtable = TABLE_NAVIBROWSE;
1807 dptr->extraseek = i;
1808 dptr->customaction = ONPLAY_CUSTOMACTION_SHUFFLE_SONGS;
1785 break; 1809 break;
1786 } 1810 }
1787 1811
@@ -1804,19 +1828,19 @@ int tagtree_load(struct tree_context* c)
1804 if (!table) 1828 if (!table)
1805 { 1829 {
1806 c->dirfull = false; 1830 c->dirfull = false;
1807 table = ROOT; 1831 table = TABLE_ROOT;
1808 c->currtable = table; 1832 c->currtable = table;
1809 c->currextra = rootmenu; 1833 c->currextra = rootmenu;
1810 } 1834 }
1811 1835
1812 switch (table) 1836 switch (table)
1813 { 1837 {
1814 case ROOT: 1838 case TABLE_ROOT:
1815 count = load_root(c); 1839 count = load_root(c);
1816 break; 1840 break;
1817 1841
1818 case ALLSUBENTRIES: 1842 case TABLE_ALLSUBENTRIES:
1819 case NAVIBROWSE: 1843 case TABLE_NAVIBROWSE:
1820 logf("navibrowse..."); 1844 logf("navibrowse...");
1821 cpu_boost(true); 1845 cpu_boost(true);
1822 count = retrieve_entries(c, 0, true); 1846 count = retrieve_entries(c, 0, true);
@@ -1921,16 +1945,16 @@ int tagtree_enter(struct tree_context* c, bool is_visible)
1921 core_pin(tagtree_handle); 1945 core_pin(tagtree_handle);
1922 1946
1923 switch (c->currtable) { 1947 switch (c->currtable) {
1924 case ROOT: 1948 case TABLE_ROOT:
1925 c->currextra = newextra; 1949 c->currextra = newextra;
1926 1950
1927 if (newextra == ROOT) 1951 if (newextra == TABLE_ROOT)
1928 { 1952 {
1929 menu = menus[seek]; 1953 menu = menus[seek];
1930 c->currextra = seek; 1954 c->currextra = seek;
1931 } 1955 }
1932 1956
1933 else if (newextra == NAVIBROWSE) 1957 else if (newextra == TABLE_NAVIBROWSE)
1934 { 1958 {
1935 int i, j; 1959 int i, j;
1936 1960
@@ -2005,9 +2029,9 @@ int tagtree_enter(struct tree_context* c, bool is_visible)
2005 2029
2006 break; 2030 break;
2007 2031
2008 case NAVIBROWSE: 2032 case TABLE_NAVIBROWSE:
2009 case ALLSUBENTRIES: 2033 case TABLE_ALLSUBENTRIES:
2010 if (newextra == PLAYTRACK) 2034 if (newextra == TABLE_PLAYTRACK)
2011 { 2035 {
2012 adjust_selection = false; 2036 adjust_selection = false;
2013 2037
@@ -2102,13 +2126,46 @@ int tagtree_get_filename(struct tree_context* c, char *buf, int buflen)
2102 return 0; 2126 return 0;
2103} 2127}
2104 2128
2129int tagtree_get_custom_action(struct tree_context* c)
2130{
2131 return tagtree_get_entry(c, c->selected_item)->customaction;
2132}
2133
2134static void swap_array_bool(bool *a, bool *b) {
2135 bool temp = *a;
2136 *a = *b;
2137 *b = temp;
2138}
2139
2140/**
2141 * Randomly shuffle an array using the Fisher-Yates algorithm : https://en.wikipedia.org/wiki/Random_permutation
2142 * This algorithm has a linear complexity. Don't forget to srand before call to use it with a relevant seed.
2143 */
2144static void shuffle_bool_array(bool array[], int size) {
2145 for (int i = size - 1; i > 0; i--) {
2146 int j = rand() % (i + 1);
2147 swap_array_bool(&array[i], &array[j]);
2148 }
2149}
2150
2151static bool fill_selective_random_playlist_indexes(int current_segment_n, int current_segment_max_available_space) {
2152 if (current_segment_n == 0 || current_segment_max_available_space == 0)
2153 return false;
2154 if (current_segment_max_available_space > current_segment_n)
2155 current_segment_max_available_space = current_segment_n;
2156 for (int i = 0; i < current_segment_n; i++)
2157 selective_random_playlist_indexes[i] = i < current_segment_max_available_space;
2158 srand(current_tick);
2159 shuffle_bool_array(selective_random_playlist_indexes, current_segment_n);
2160 return true;
2161}
2105 2162
2106static bool insert_all_playlist(struct tree_context *c, 2163static bool insert_all_playlist(struct tree_context *c,
2107 const char* playlist, bool new_playlist, 2164 const char* playlist, bool new_playlist,
2108 int position, bool queue) 2165 int position, bool queue)
2109{ 2166{
2110 struct tagcache_search tcs; 2167 struct tagcache_search tcs;
2111 int i, n; 2168 int n;
2112 int fd = -1; 2169 int fd = -1;
2113 unsigned long last_tick; 2170 unsigned long last_tick;
2114 char buf[MAX_PATH]; 2171 char buf[MAX_PATH];
@@ -2144,44 +2201,77 @@ static bool insert_all_playlist(struct tree_context *c,
2144 return false; 2201 return false;
2145 } 2202 }
2146 } 2203 }
2147
2148 last_tick = current_tick + HZ/2; /* Show splash after 0.5 seconds have passed */ 2204 last_tick = current_tick + HZ/2; /* Show splash after 0.5 seconds have passed */
2149 splash_progress_set_delay(HZ / 2); /* wait 1/2 sec before progress */ 2205 splash_progress_set_delay(HZ / 2); /* wait 1/2 sec before progress */
2150 n = c->filesindir; 2206 n = c->filesindir;
2151 for (i = 0; i < n; i++) 2207 int segment_size = INSERT_ALL_PLAYLIST_MAX_SEGMENT_SIZE;
2152 { 2208 int segments_count = n / segment_size;
2153 2209 int leftovers_segment_size = n % segment_size;
2154 splash_progress(i, n, "%s (%s)", str(LANG_WAIT), str(LANG_OFF_ABORT)); 2210 bool fill_randomly = false;
2155 if (TIME_AFTER(current_tick, last_tick + HZ/4)) 2211 if (playlist == NULL) {
2156 { 2212 bool will_exceed = n > playlist_get_current()->max_playlist_size;
2157 if (action_userabort(TIMEOUT_NOBLOCK)) 2213 fill_randomly = will_exceed;
2158 break; 2214 }
2159 last_tick = current_tick; 2215 if (leftovers_segment_size > 0 && fill_randomly) {
2216 // We need to re-balance the segments so the randomness will be coherent and balanced the same through all segments
2217 while (leftovers_segment_size + segments_count < segment_size) {
2218 segment_size--; // -1 to all other segments
2219 leftovers_segment_size += segments_count;
2160 } 2220 }
2161 2221 }
2162 if (!tagcache_retrieve(&tcs, tagtree_get_entry(c, i)->extraseek, 2222 if (leftovers_segment_size > 0)
2163 tcs.type, buf, sizeof buf)) 2223 segments_count += 1;
2164 { 2224 int max_available_space = playlist_get_current()->max_playlist_size - playlist_get_current()->amount;
2165 continue; 2225 int max_available_space_per_segment = max_available_space / segments_count;
2226 if (fill_randomly) {
2227 talk_id(LANG_RANDOM_SHUFFLE_RANDOM_SELECTIVE_SONGS_SUMMARY, true);
2228 splashf(HZ * 3, str(LANG_RANDOM_SHUFFLE_RANDOM_SELECTIVE_SONGS_SUMMARY), max_available_space_per_segment * segments_count);
2229 //splashf(HZ * 5, "sz=%d lsz=%d sc=%d rcps=%d", segment_size, leftovers_segment_size, segments_count, max_available_space_per_segment);
2230 }
2231 for (int i = 0; i < segments_count; i++) {
2232 bool is_leftovers_segment = leftovers_segment_size > 0 && i + 1 >= segments_count;
2233 if (fill_randomly) {
2234 if (is_leftovers_segment)
2235 fill_randomly = fill_selective_random_playlist_indexes(leftovers_segment_size, max_available_space_per_segment);
2236 else
2237 fill_randomly = fill_selective_random_playlist_indexes(segment_size, max_available_space_per_segment);
2166 } 2238 }
2167 2239 bool exit_loop_now = false;
2168 if (playlist == NULL) 2240 int cur_segment_start = i * segment_size;
2169 { 2241 int cur_segment_end;
2170 if (playlist_insert_track(NULL, buf, position, queue, false) < 0) 2242 if (is_leftovers_segment)
2171 { 2243 cur_segment_end = cur_segment_start + leftovers_segment_size;
2172 logf("playlist_insert_track failed"); 2244 else
2173 break; 2245 cur_segment_end = cur_segment_start + segment_size;
2246 for (int j = cur_segment_start; j < cur_segment_end && !exit_loop_now; j++) {
2247 if (fill_randomly && !selective_random_playlist_indexes[j % segment_size])
2248 continue;
2249 splash_progress(j, n, "%s (%s)", str(LANG_WAIT), str(LANG_OFF_ABORT));
2250 if (TIME_AFTER(current_tick, last_tick + HZ/4)) {
2251 if (action_userabort(TIMEOUT_NOBLOCK)) {
2252 exit_loop_now = true;
2253 break;
2254 }
2255 last_tick = current_tick;
2174 } 2256 }
2175 } 2257 if (!tagcache_retrieve(&tcs, tagtree_get_entry(c, j)->extraseek, tcs.type, buf, sizeof buf))
2176 else if (fdprintf(fd, "%s\n", buf) <= 0) 2258 continue;
2259 if (playlist == NULL) {
2260 if (playlist_insert_track(NULL, buf, position, queue, false) < 0) {
2261 logf("playlist_insert_track failed");
2262 exit_loop_now = true;
2263 break;
2264 }
2265 } else if (fdprintf(fd, "%s\n", buf) <= 0) {
2266 exit_loop_now = true;
2177 break; 2267 break;
2178 2268 }
2179 yield(); 2269 yield();
2180 2270 if (playlist == NULL && position == PLAYLIST_INSERT_FIRST)
2181 if (playlist == NULL && position == PLAYLIST_INSERT_FIRST) 2271 position = PLAYLIST_INSERT;
2182 {
2183 position = PLAYLIST_INSERT;
2184 } 2272 }
2273 if (exit_loop_now)
2274 break;
2185 } 2275 }
2186 if (playlist == NULL) 2276 if (playlist == NULL)
2187 playlist_sync(NULL); 2277 playlist_sync(NULL);
@@ -2196,14 +2286,14 @@ static bool insert_all_playlist(struct tree_context *c,
2196static bool goto_allsubentries(int newtable) 2286static bool goto_allsubentries(int newtable)
2197{ 2287{
2198 int i = 0; 2288 int i = 0;
2199 while (i < 2 && (newtable == NAVIBROWSE || newtable == ALLSUBENTRIES)) 2289 while (i < 2 && (newtable == TABLE_NAVIBROWSE || newtable == TABLE_ALLSUBENTRIES))
2200 { 2290 {
2201 tagtree_enter(tc, false); 2291 tagtree_enter(tc, false);
2202 tagtree_load(tc); 2292 tagtree_load(tc);
2203 newtable = tagtree_get_entry(tc, tc->selected_item)->newtable; 2293 newtable = tagtree_get_entry(tc, tc->selected_item)->newtable;
2204 i++; 2294 i++;
2205 } 2295 }
2206 return (newtable == PLAYTRACK); 2296 return (newtable == TABLE_PLAYTRACK);
2207} 2297}
2208 2298
2209static void reset_tc_to_prev(int dirlevel, int selected_item) 2299static void reset_tc_to_prev(int dirlevel, int selected_item)
@@ -2233,7 +2323,7 @@ static bool tagtree_insert_selection(int position, bool queue,
2233 2323
2234 newtable = tagtree_get_entry(tc, tc->selected_item)->newtable; 2324 newtable = tagtree_get_entry(tc, tc->selected_item)->newtable;
2235 2325
2236 if (newtable == PLAYTRACK) /* Insert a single track? */ 2326 if (newtable == TABLE_PLAYTRACK) /* Insert a single track? */
2237 { 2327 {
2238 if (tagtree_get_filename(tc, buf, sizeof buf) < 0) 2328 if (tagtree_get_filename(tc, buf, sizeof buf) < 0)
2239 return false; 2329 return false;
@@ -2353,9 +2443,19 @@ static int tagtree_play_folder(struct tree_context* c)
2353 if (!insert_all_playlist(c, NULL, false, PLAYLIST_INSERT_LAST, false)) 2443 if (!insert_all_playlist(c, NULL, false, PLAYLIST_INSERT_LAST, false))
2354 return -2; 2444 return -2;
2355 2445
2446 int n = c->filesindir;
2447 bool has_playlist_been_randomized = n > playlist_get_current()->max_playlist_size;
2448 if (has_playlist_been_randomized) {
2449 /* We need to recalculate the start index based on a percentage to put the user
2450 around its desired start position and avoid out of bounds */
2451
2452 int percentage_start_index = 100 * start_index / n;
2453 start_index = percentage_start_index * playlist_get_current()->amount / 100;
2454 }
2455
2356 if (global_settings.playlist_shuffle) 2456 if (global_settings.playlist_shuffle)
2357 { 2457 {
2358 start_index = playlist_shuffle(current_tick, c->selected_item); 2458 start_index = playlist_shuffle(current_tick, start_index);
2359 if (!global_settings.play_selected) 2459 if (!global_settings.play_selected)
2360 start_index = 0; 2460 start_index = 0;
2361 } 2461 }
@@ -2403,11 +2503,11 @@ char *tagtree_get_title(struct tree_context* c)
2403{ 2503{
2404 switch (c->currtable) 2504 switch (c->currtable)
2405 { 2505 {
2406 case ROOT: 2506 case TABLE_ROOT:
2407 return menu->title; 2507 return menu->title;
2408 2508
2409 case NAVIBROWSE: 2509 case TABLE_NAVIBROWSE:
2410 case ALLSUBENTRIES: 2510 case TABLE_ALLSUBENTRIES:
2411 return current_title[c->currextra]; 2511 return current_title[c->currextra];
2412 } 2512 }
2413 2513
@@ -2419,7 +2519,7 @@ int tagtree_get_attr(struct tree_context* c)
2419 int attr = -1; 2519 int attr = -1;
2420 switch (c->currtable) 2520 switch (c->currtable)
2421 { 2521 {
2422 case NAVIBROWSE: 2522 case TABLE_NAVIBROWSE:
2423 if (csi->tagorder[c->currextra] == tag_title 2523 if (csi->tagorder[c->currextra] == tag_title
2424 || csi->tagorder[c->currextra] == tag_virt_basename) 2524 || csi->tagorder[c->currextra] == tag_virt_basename)
2425 attr = FILE_ATTR_AUDIO; 2525 attr = FILE_ATTR_AUDIO;
@@ -2427,7 +2527,7 @@ int tagtree_get_attr(struct tree_context* c)
2427 attr = ATTR_DIRECTORY; 2527 attr = ATTR_DIRECTORY;
2428 break; 2528 break;
2429 2529
2430 case ALLSUBENTRIES: 2530 case TABLE_ALLSUBENTRIES:
2431 attr = FILE_ATTR_AUDIO; 2531 attr = FILE_ATTR_AUDIO;
2432 break; 2532 break;
2433 2533
diff --git a/apps/tagtree.h b/apps/tagtree.h
index 39ab545bd0..a57a5c2f80 100644
--- a/apps/tagtree.h
+++ b/apps/tagtree.h
@@ -45,6 +45,7 @@ char *tagtree_get_title(struct tree_context* c);
45int tagtree_get_attr(struct tree_context* c); 45int tagtree_get_attr(struct tree_context* c);
46int tagtree_get_icon(struct tree_context* c); 46int tagtree_get_icon(struct tree_context* c);
47int tagtree_get_filename(struct tree_context* c, char *buf, int buflen); 47int tagtree_get_filename(struct tree_context* c, char *buf, int buflen);
48int tagtree_get_custom_action(struct tree_context* c);
48bool tagtree_get_subentry_filename(char *buf, size_t bufsize); 49bool tagtree_get_subentry_filename(char *buf, size_t bufsize);
49bool tagtree_subentries_do_action(bool (*action_cb)(const char *file_name)); 50bool tagtree_subentries_do_action(bool (*action_cb)(const char *file_name));
50 51
diff --git a/apps/tree.c b/apps/tree.c
index 71a7ee3f62..b4cd9d77b0 100644
--- a/apps/tree.c
+++ b/apps/tree.c
@@ -735,6 +735,17 @@ static int dirbrowse(void)
735 oldbutton = button; 735 oldbutton = button;
736 gui_synclist_do_button(&tree_lists, &button); 736 gui_synclist_do_button(&tree_lists, &button);
737 tc.selected_item = gui_synclist_get_sel_pos(&tree_lists); 737 tc.selected_item = gui_synclist_get_sel_pos(&tree_lists);
738 int customaction = ONPLAY_NO_CUSTOMACTION;
739 bool do_restore_display = true;
740 #ifdef HAVE_TAGCACHE
741 if (id3db && (button == ACTION_STD_OK || button == ACTION_STD_CONTEXT)) {
742 customaction = tagtree_get_custom_action(&tc);
743 if (customaction == ONPLAY_CUSTOMACTION_SHUFFLE_SONGS) {
744 button = ACTION_STD_CONTEXT; /** The code to insert shuffled is on the context branch of the switch so we always go here */
745 do_restore_display = false;
746 }
747 }
748 #endif
738 switch ( button ) { 749 switch ( button ) {
739 case ACTION_STD_OK: 750 case ACTION_STD_OK:
740 /* nothing to do if no files to display */ 751 /* nothing to do if no files to display */
@@ -773,7 +784,7 @@ static int dirbrowse(void)
773 default: 784 default:
774 break; 785 break;
775 } 786 }
776 restore = true; 787 restore = do_restore_display;
777 break; 788 break;
778 789
779 case ACTION_STD_CANCEL: 790 case ACTION_STD_CANCEL:
@@ -798,12 +809,12 @@ static int dirbrowse(void)
798 if (ft_exit(&tc) == 3) 809 if (ft_exit(&tc) == 3)
799 exit_func = true; 810 exit_func = true;
800 811
801 restore = true; 812 restore = do_restore_display;
802 break; 813 break;
803 814
804 case ACTION_TREE_STOP: 815 case ACTION_TREE_STOP:
805 if (list_stop_handler()) 816 if (list_stop_handler())
806 restore = true; 817 restore = do_restore_display;
807 break; 818 break;
808 819
809 case ACTION_STD_MENU: 820 case ACTION_STD_MENU:
@@ -851,7 +862,7 @@ static int dirbrowse(void)
851 skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_ALL); 862 skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_ALL);
852 } 863 }
853 864
854 restore = true; 865 restore = do_restore_display;
855 break; 866 break;
856 } 867 }
857#endif 868#endif
@@ -872,7 +883,7 @@ static int dirbrowse(void)
872 break; 883 break;
873 884
874 if(!numentries) 885 if(!numentries)
875 onplay_result = onplay(NULL, 0, curr_context, hotkey); 886 onplay_result = onplay(NULL, 0, curr_context, hotkey, customaction);
876 else { 887 else {
877#ifdef HAVE_TAGCACHE 888#ifdef HAVE_TAGCACHE
878 if (id3db) 889 if (id3db)
@@ -902,7 +913,7 @@ static int dirbrowse(void)
902 ft_assemble_path(buf, sizeof(buf), currdir, entry->name); 913 ft_assemble_path(buf, sizeof(buf), currdir, entry->name);
903 914
904 } 915 }
905 onplay_result = onplay(buf, attr, curr_context, hotkey); 916 onplay_result = onplay(buf, attr, curr_context, hotkey, customaction);
906 } 917 }
907 switch (onplay_result) 918 switch (onplay_result)
908 { 919 {
@@ -911,7 +922,7 @@ static int dirbrowse(void)
911 break; 922 break;
912 923
913 case ONPLAY_OK: 924 case ONPLAY_OK:
914 restore = true; 925 restore = do_restore_display;
915 break; 926 break;
916 927
917 case ONPLAY_RELOAD_DIR: 928 case ONPLAY_RELOAD_DIR:
@@ -988,7 +999,7 @@ static int dirbrowse(void)
988 999
989 lastfilter = *tc.dirfilter; 1000 lastfilter = *tc.dirfilter;
990 lastsortcase = global_settings.sort_case; 1001 lastsortcase = global_settings.sort_case;
991 restore = true; 1002 restore = do_restore_display;
992 } 1003 }
993 1004
994 if (exit_func) 1005 if (exit_func)
diff --git a/apps/tree.h b/apps/tree.h
index d13c75d434..e958bbf109 100644
--- a/apps/tree.h
+++ b/apps/tree.h
@@ -33,6 +33,9 @@ struct entry {
33 char *name; 33 char *name;
34 int attr; /* FAT attributes + file type flags */ 34 int attr; /* FAT attributes + file type flags */
35 unsigned time_write; /* Last write time */ 35 unsigned time_write; /* Last write time */
36 #ifdef HAVE_TAGCACHE
37 int customaction; /* db use */
38 #endif
36}; 39};
37 40
38#define BROWSE_SELECTONLY 0x0001 /* exit on selecting a file */ 41#define BROWSE_SELECTONLY 0x0001 /* exit on selecting a file */
diff --git a/manual/rockbox_interface/tagcache.tex b/manual/rockbox_interface/tagcache.tex
index 3c3e6d2df8..ea34b5ba9e 100644
--- a/manual/rockbox_interface/tagcache.tex
+++ b/manual/rockbox_interface/tagcache.tex
@@ -137,6 +137,21 @@ There is no option to turn off database completely. If you do not want
137to use it just do not do the initial build of the database and do not load it 137to use it just do not do the initial build of the database and do not load it
138to RAM.}% 138to RAM.}%
139 139
140If your total amount of music tracks exceeds the value of the
141\setting{Max Playlist Size} setting (\setting{Settings $\rightarrow$ General
142Settings $\rightarrow$ System $\rightarrow$ Limits}), using the database
143will be your only way to shuffle between all songs from your music library.
144Any view on the database browser that exceeds the maximum value of this option
145will be automatically adjusted and randomized to fit into the available space
146when you will create a dynamic playlist from the view.
147Using the database browser is recommended if you shuffle regularly between a lot of
148songs rather than increasing your limit, so you will get the best possible performance
149on this action.
150
151\note{For your convenience, a shortcut button "Shuffle Songs" is available directly
152from the \setting{Database} menu to create and start a mix with all of your
153existing music tracks.}
154
140\begin{table} 155\begin{table}
141 \begin{rbtabular}{.75\textwidth}{XXX}% 156 \begin{rbtabular}{.75\textwidth}{XXX}%
142 {\textbf{Tag} & \textbf{Type} & \textbf{Origin}}{}{} 157 {\textbf{Tag} & \textbf{Type} & \textbf{Origin}}{}{}