summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apps/debug_menu.c36
-rw-r--r--apps/gui/skin_engine/skin_tokens.c19
-rw-r--r--apps/gui/wps.c4
-rw-r--r--apps/lang/arabic.lang16
-rw-r--r--apps/lang/basque.lang30
-rw-r--r--apps/lang/bulgarian.lang30
-rw-r--r--apps/lang/catala.lang30
-rw-r--r--apps/lang/chinese-simp.lang110
-rw-r--r--apps/lang/chinese-trad.lang32
-rw-r--r--apps/lang/czech.lang30
-rw-r--r--apps/lang/dansk.lang30
-rw-r--r--apps/lang/deutsch.lang30
-rw-r--r--apps/lang/eesti.lang10
-rw-r--r--apps/lang/english-us.lang86
-rw-r--r--apps/lang/english.lang86
-rw-r--r--apps/lang/espanol.lang30
-rw-r--r--apps/lang/esperanto.lang10
-rw-r--r--apps/lang/finnish.lang30
-rw-r--r--apps/lang/francais.lang2654
-rw-r--r--apps/lang/galego.lang30
-rw-r--r--apps/lang/greek.lang30
-rw-r--r--apps/lang/hebrew.lang30
-rw-r--r--apps/lang/hindi.lang18
-rw-r--r--apps/lang/hrvatski.lang30
-rw-r--r--apps/lang/islenska.lang4
-rw-r--r--apps/lang/italiano.lang86
-rw-r--r--apps/lang/japanese.lang30
-rw-r--r--apps/lang/korean.lang86
-rw-r--r--apps/lang/latviesu.lang30
-rw-r--r--apps/lang/lietuviu.lang24
-rw-r--r--apps/lang/magyar.lang30
-rw-r--r--apps/lang/nederlands.lang30
-rw-r--r--apps/lang/norsk-nynorsk.lang24
-rw-r--r--apps/lang/norsk.lang30
-rw-r--r--apps/lang/polski.lang86
-rw-r--r--apps/lang/portugues-brasileiro.lang30
-rw-r--r--apps/lang/portugues.lang30
-rw-r--r--apps/lang/romaneste.lang30
-rw-r--r--apps/lang/russian.lang1589
-rw-r--r--apps/lang/slovak.lang30
-rw-r--r--apps/lang/slovenscina.lang30
-rw-r--r--apps/lang/srpski.lang30
-rw-r--r--apps/lang/svenska.lang30
-rw-r--r--apps/lang/tagalog.lang30
-rw-r--r--apps/lang/thai.lang30
-rw-r--r--apps/lang/turkce.lang32
-rw-r--r--apps/lang/ukrainian.lang30
-rw-r--r--apps/lang/vlaams.lang30
-rw-r--r--apps/lang/wallisertitsch.lang4
-rw-r--r--apps/lang/walon.lang24
-rw-r--r--apps/main.c11
-rw-r--r--apps/menus/display_menu.c5
-rw-r--r--apps/onplay.c14
-rw-r--r--apps/onplay.h7
-rw-r--r--apps/playlist.h2
-rw-r--r--apps/playlist_viewer.c207
-rw-r--r--apps/plugins/lua/lua.make2
-rw-r--r--apps/plugins/puzzles/SOURCES1
-rw-r--r--apps/plugins/puzzles/SOURCES.games9
-rw-r--r--apps/plugins/puzzles/SOURCES.rockbox1
-rw-r--r--apps/plugins/puzzles/compress.c1
-rw-r--r--apps/plugins/puzzles/dummy/nullhelp.c8
-rw-r--r--apps/plugins/puzzles/help.h4
-rw-r--r--apps/plugins/puzzles/help/blackbox.c1
-rw-r--r--apps/plugins/puzzles/help/bridges.c1
-rw-r--r--apps/plugins/puzzles/help/cube.c1
-rw-r--r--apps/plugins/puzzles/help/dominosa.c1
-rw-r--r--apps/plugins/puzzles/help/fifteen.c1
-rw-r--r--apps/plugins/puzzles/help/filling.c1
-rw-r--r--apps/plugins/puzzles/help/flip.c1
-rw-r--r--apps/plugins/puzzles/help/flood.c1
-rw-r--r--apps/plugins/puzzles/help/galaxies.c1
-rw-r--r--apps/plugins/puzzles/help/guess.c1
-rw-r--r--apps/plugins/puzzles/help/inertia.c1
-rw-r--r--apps/plugins/puzzles/help/keen.c1
-rw-r--r--apps/plugins/puzzles/help/lightup.c1
-rw-r--r--apps/plugins/puzzles/help/loopy.c1
-rw-r--r--apps/plugins/puzzles/help/magnets.c1
-rw-r--r--apps/plugins/puzzles/help/map.c1
-rw-r--r--apps/plugins/puzzles/help/mines.c1
-rw-r--r--apps/plugins/puzzles/help/mosaic.c1
-rw-r--r--apps/plugins/puzzles/help/net.c1
-rw-r--r--apps/plugins/puzzles/help/netslide.c1
-rw-r--r--apps/plugins/puzzles/help/palisade.c1
-rw-r--r--apps/plugins/puzzles/help/pattern.c1
-rw-r--r--apps/plugins/puzzles/help/pearl.c1
-rw-r--r--apps/plugins/puzzles/help/pegs.c1
-rw-r--r--apps/plugins/puzzles/help/range.c1
-rw-r--r--apps/plugins/puzzles/help/rect.c1
-rw-r--r--apps/plugins/puzzles/help/samegame.c1
-rw-r--r--apps/plugins/puzzles/help/signpost.c1
-rw-r--r--apps/plugins/puzzles/help/singles.c1
-rw-r--r--apps/plugins/puzzles/help/sixteen.c1
-rw-r--r--apps/plugins/puzzles/help/slant.c1
-rw-r--r--apps/plugins/puzzles/help/solo.c1
-rw-r--r--apps/plugins/puzzles/help/tents.c1
-rw-r--r--apps/plugins/puzzles/help/towers.c1
-rw-r--r--apps/plugins/puzzles/help/tracks.c1
-rw-r--r--apps/plugins/puzzles/help/twiddle.c1
-rw-r--r--apps/plugins/puzzles/help/undead.c1
-rw-r--r--apps/plugins/puzzles/help/unequal.c1
-rw-r--r--apps/plugins/puzzles/help/unruly.c1
-rw-r--r--apps/plugins/puzzles/help/untangle.c1
-rw-r--r--apps/plugins/puzzles/puzzles.make9
-rwxr-xr-xapps/plugins/puzzles/resync.sh15
-rw-r--r--apps/plugins/puzzles/rockbox.c56
-rw-r--r--apps/plugins/puzzles/src/unfinished/CMakeLists.txt31
-rw-r--r--apps/plugins/puzzles/src/unfinished/README14
-rw-r--r--apps/plugins/puzzles/src/unfinished/group.c2497
-rw-r--r--apps/plugins/puzzles/src/unfinished/group.gap97
-rw-r--r--apps/plugins/puzzles/src/unfinished/numgame.c1294
-rw-r--r--apps/plugins/puzzles/src/unfinished/path.c866
-rw-r--r--apps/plugins/puzzles/src/unfinished/separate.c861
-rw-r--r--apps/plugins/puzzles/src/unfinished/slide.c2444
-rw-r--r--apps/plugins/puzzles/src/unfinished/sokoban.c1476
-rw-r--r--apps/recorder/keyboard.c50
-rw-r--r--apps/settings.h8
-rw-r--r--apps/settings_list.c18
-rw-r--r--apps/shortcuts.c17
-rw-r--r--apps/tagcache.c9
-rw-r--r--apps/tagnavi.config17
-rw-r--r--apps/tagtree.c270
-rw-r--r--apps/tagtree.h1
-rw-r--r--apps/tree.c30
-rw-r--r--apps/tree.h3
-rw-r--r--bootloader/x1000/x1000bootloader.h7
-rw-r--r--docs/CREDITS2
-rw-r--r--firmware/SOURCES4
-rw-r--r--firmware/common/devicedata.c88
-rw-r--r--firmware/common/rb-loader.c8
-rw-r--r--firmware/drivers/lcd-scroll.c8
-rw-r--r--firmware/export/config.h3
-rw-r--r--firmware/export/config/erosqnative.h16
-rw-r--r--firmware/export/devicedata.h94
-rw-r--r--firmware/rolo.c13
-rw-r--r--firmware/scroll_engine.c4
-rw-r--r--firmware/target/mips/ingenic_x1000/crt0.S7
-rw-r--r--firmware/target/mips/ingenic_x1000/erosqnative/lcd-erosqnative.c146
-rw-r--r--firmware/target/mips/ingenic_x1000/system-x1000.c19
-rw-r--r--firmware/target/mips/ingenic_x1000/x1000boot.make2
-rw-r--r--lib/rbcodec/codecs/cRSID/host/file.c2
-rw-r--r--manual/appendix/config_file_options.tex3
-rwxr-xr-xmanual/configure_rockbox/display_options.tex5
-rw-r--r--manual/configure_rockbox/sound_settings.tex17
-rw-r--r--manual/getting_started/hibyos_nativeinstall.tex64
-rw-r--r--manual/getting_started/installation.tex29
-rw-r--r--manual/rockbox_interface/tagcache.tex15
-rw-r--r--tools/builds.pm13
-rwxr-xr-xtools/configure33
-rwxr-xr-xtools/hibyos_nativepatcher/hibyos_nativepatcher.sh221
-rwxr-xr-xtools/hibyos_nativepatcher/patch_manifest.pl27
-rwxr-xr-xtools/rockboxdev.sh8
-rwxr-xr-xtools/updatelang8
-rw-r--r--uisimulator/common/stubs.c6
-rw-r--r--utils/themeeditor/resources/configkeys1
155 files changed, 14838 insertions, 2310 deletions
diff --git a/apps/debug_menu.c b/apps/debug_menu.c
index a101097004..8e16ff1c21 100644
--- a/apps/debug_menu.c
+++ b/apps/debug_menu.c
@@ -124,6 +124,10 @@
124 124
125#include "talk.h" 125#include "talk.h"
126 126
127#if defined(HAVE_DEVICEDATA)// && !defined(SIMULATOR)
128#include "devicedata.h"
129#endif
130
127#if defined(HAVE_BOOTDATA) && !defined(SIMULATOR) 131#if defined(HAVE_BOOTDATA) && !defined(SIMULATOR)
128#include "bootdata.h" 132#include "bootdata.h"
129#include "multiboot.h" 133#include "multiboot.h"
@@ -2625,6 +2629,33 @@ static bool dbg_boot_data(void)
2625} 2629}
2626#endif /* defined(HAVE_BOOTDATA) && !defined(SIMULATOR) */ 2630#endif /* defined(HAVE_BOOTDATA) && !defined(SIMULATOR) */
2627 2631
2632#if defined(HAVE_DEVICEDATA)// && !defined(SIMULATOR)
2633static bool dbg_device_data(void)
2634{
2635 struct simplelist_info info;
2636 info.scroll_all = true;
2637 simplelist_info_init(&info, "Device data", 1, NULL);
2638 simplelist_set_line_count(0);
2639
2640 simplelist_addline("Device data");
2641
2642#if defined(EROS_QN)
2643 simplelist_addline("Lcd Version: %d", (int)device_data.lcd_version);
2644#endif
2645
2646 simplelist_addline("Device data RAW:");
2647 for (size_t i = 0; i < device_data.length; i += 4)
2648 {
2649 simplelist_addline("%02x: %02x %02x %02x %02x", i,
2650 device_data.payload[i + 0], device_data.payload[i + 1],
2651 device_data.payload[i + 2], device_data.payload[i + 3]);
2652 }
2653
2654 return simplelist_show_list(&info);
2655}
2656#endif /* defined(HAVE_DEVICEDATA)*/
2657
2658
2628#if defined(IPOD_6G) && !defined(SIMULATOR) 2659#if defined(IPOD_6G) && !defined(SIMULATOR)
2629#define SYSCFG_MAX_ENTRIES 9 // 9 on iPod Classic/6G 2660#define SYSCFG_MAX_ENTRIES 9 // 9 on iPod Classic/6G
2630 2661
@@ -2823,6 +2854,11 @@ static const struct {
2823#if defined(HAVE_BOOTDATA) && !defined(SIMULATOR) 2854#if defined(HAVE_BOOTDATA) && !defined(SIMULATOR)
2824 {"Boot data", dbg_boot_data }, 2855 {"Boot data", dbg_boot_data },
2825#endif 2856#endif
2857
2858#if defined(HAVE_DEVICEDATA)// && !defined(SIMULATOR)
2859 {"Device data", dbg_device_data },
2860#endif
2861
2826#if defined(IPOD_6G) && !defined(SIMULATOR) 2862#if defined(IPOD_6G) && !defined(SIMULATOR)
2827 {"View SysCfg", dbg_syscfg }, 2863 {"View SysCfg", dbg_syscfg },
2828#endif 2864#endif
diff --git a/apps/gui/skin_engine/skin_tokens.c b/apps/gui/skin_engine/skin_tokens.c
index 082619432f..4bd1ffea31 100644
--- a/apps/gui/skin_engine/skin_tokens.c
+++ b/apps/gui/skin_engine/skin_tokens.c
@@ -556,20 +556,29 @@ static struct mp3entry* get_mp3entry_from_offset(int offset, char **filename)
556 pid3 = state->nid3; 556 pid3 = state->nid3;
557 else 557 else
558 { 558 {
559 static char filename_buf[MAX_PATH + 1]; 559 static struct mp3entry tempid3; /* Note: path gets passed to outside fns */
560 fname = playlist_peek(offset, filename_buf, sizeof(filename_buf)); 560 memset(&tempid3, 0, sizeof(struct mp3entry));
561 /*static char filename_buf[MAX_PATH + 1];removed g#5926 */
562 fname = playlist_peek(offset, tempid3.path, sizeof(tempid3.path));
561 *filename = (char*)fname; 563 *filename = (char*)fname;
562 static struct mp3entry tempid3; 564
563 if ( 565 if (
564#if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE) 566#if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE)
565 tagcache_fill_tags(&tempid3, fname) || 567 tagcache_fill_tags(&tempid3, NULL) ||
566#endif 568#endif
567 audio_peek_track(&tempid3, offset) 569 audio_peek_track(&tempid3, offset)
568 ) 570 )
569 { 571 {
570 pid3 = &tempid3; 572 pid3 = &tempid3;
571 } 573 }
574 else /* failed */
575 {
576 /* ensure *filename gets the path, audio_peek_track() cleared it */
577 fname = playlist_peek(offset, tempid3.path, sizeof(tempid3.path));
578 *filename = (char*)fname;
579 }
572 } 580 }
581
573 return pid3; 582 return pid3;
574} 583}
575 584
@@ -710,8 +719,6 @@ const char *get_token_value(struct gui_wps *gwps,
710 return NULL; 719 return NULL;
711 720
712 id3 = get_mp3entry_from_offset(token->next? 1: offset, &filename); 721 id3 = get_mp3entry_from_offset(token->next? 1: offset, &filename);
713 if (id3)
714 filename = id3->path;
715 722
716#if CONFIG_RTC 723#if CONFIG_RTC
717 struct tm* tm = NULL; 724 struct tm* tm = NULL;
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/arabic.lang b/apps/lang/arabic.lang
index c9b295956b..c3a456b9cf 100644
--- a/apps/lang/arabic.lang
+++ b/apps/lang/arabic.lang
@@ -3613,13 +3613,13 @@
3613</phrase> 3613</phrase>
3614<phrase> 3614<phrase>
3615 id: LANG_TAGNAVI_ALL_TRACKS 3615 id: LANG_TAGNAVI_ALL_TRACKS
3616 desc: "<All tracks>" entry in tag browser 3616 desc: "[All tracks]" entry in tag browser
3617 user: core 3617 user: core
3618 <source> 3618 <source>
3619 *: "<All tracks>" 3619 *: "[All tracks]"
3620 </source> 3620 </source>
3621 <dest> 3621 <dest>
3622 *: "<كل الملÙات>" 3622 *: "[كل الملÙات]"
3623 </dest> 3623 </dest>
3624 <voice> 3624 <voice>
3625 *: "All tracks" 3625 *: "All tracks"
@@ -3999,10 +3999,10 @@
3999 desc: in tag viewer 3999 desc: in tag viewer
4000 user: core 4000 user: core
4001 <source> 4001 <source>
4002 *: "<No Info>" 4002 *: "[No Info]"
4003 </source> 4003 </source>
4004 <dest> 4004 <dest>
4005 *: "<المعلومات غير متوÙرة>" 4005 *: "[المعلومات غير متوÙرة]"
4006 </dest> 4006 </dest>
4007 <voice> 4007 <voice>
4008 *: "المعلومات غير متوÙرة" 4008 *: "المعلومات غير متوÙرة"
@@ -4863,13 +4863,13 @@
4863</phrase> 4863</phrase>
4864<phrase> 4864<phrase>
4865 id: LANG_TAGNAVI_RANDOM 4865 id: LANG_TAGNAVI_RANDOM
4866 desc: "<Random>" entry in tag browser 4866 desc: "[Random]" entry in tag browser
4867 user: core 4867 user: core
4868 <source> 4868 <source>
4869 *: "<Random>" 4869 *: "[Random]"
4870 </source> 4870 </source>
4871 <dest> 4871 <dest>
4872 *: "<عشوائي>" 4872 *: "[عشوائي]"
4873 </dest> 4873 </dest>
4874 <voice> 4874 <voice>
4875 *: "Random" 4875 *: "Random"
diff --git a/apps/lang/basque.lang b/apps/lang/basque.lang
index 05fae692e8..5eccf03629 100644
--- a/apps/lang/basque.lang
+++ b/apps/lang/basque.lang
@@ -476,10 +476,10 @@
476 desc: top item in the list when asking user about bookmark auto load 476 desc: top item in the list when asking user about bookmark auto load
477 user: core 477 user: core
478 <source> 478 <source>
479 *: "<Don't Resume>" 479 *: "[Don't Resume]"
480 </source> 480 </source>
481 <dest> 481 <dest>
482 *: "<Ez Berrekin>" 482 *: "[Ez Berrekin]"
483 </dest> 483 </dest>
484 <voice> 484 <voice>
485 *: "Ez Berrekin" 485 *: "Ez Berrekin"
@@ -504,10 +504,10 @@
504 desc: bookmark selection list, bookmark couldn't be parsed 504 desc: bookmark selection list, bookmark couldn't be parsed
505 user: core 505 user: core
506 <source> 506 <source>
507 *: "<Invalid Bookmark>" 507 *: "[Invalid Bookmark]"
508 </source> 508 </source>
509 <dest> 509 <dest>
510 *: "<Lastermarka Okerra>" 510 *: "[Lastermarka Okerra]"
511 </dest> 511 </dest>
512 <voice> 512 <voice>
513 *: "Lastermarka Okerra" 513 *: "Lastermarka Okerra"
@@ -2298,13 +2298,13 @@
2298</phrase> 2298</phrase>
2299<phrase> 2299<phrase>
2300 id: LANG_TAGNAVI_ALL_TRACKS 2300 id: LANG_TAGNAVI_ALL_TRACKS
2301 desc: "<All tracks>" entry in tag browser 2301 desc: "[All tracks]" entry in tag browser
2302 user: core 2302 user: core
2303 <source> 2303 <source>
2304 *: "<All tracks>" 2304 *: "[All tracks]"
2305 </source> 2305 </source>
2306 <dest> 2306 <dest>
2307 *: "<Abesti guztiak>" 2307 *: "[Abesti guztiak]"
2308 </dest> 2308 </dest>
2309 <voice> 2309 <voice>
2310 *: "Abesti guztiak" 2310 *: "Abesti guztiak"
@@ -7146,10 +7146,10 @@
7146 desc: in tag viewer 7146 desc: in tag viewer
7147 user: core 7147 user: core
7148 <source> 7148 <source>
7149 *: "<No Info>" 7149 *: "[No Info]"
7150 </source> 7150 </source>
7151 <dest> 7151 <dest>
7152 *: "<Info Gabe>" 7152 *: "[Info Gabe]"
7153 </dest> 7153 </dest>
7154 <voice> 7154 <voice>
7155 *: "Info Gabe" 7155 *: "Info Gabe"
@@ -10172,13 +10172,13 @@
10172</phrase> 10172</phrase>
10173<phrase> 10173<phrase>
10174 id: LANG_TAGNAVI_RANDOM 10174 id: LANG_TAGNAVI_RANDOM
10175 desc: "<Random>" entry in tag browser 10175 desc: "[Random]" entry in tag browser
10176 user: core 10176 user: core
10177 <source> 10177 <source>
10178 *: "<Random>" 10178 *: "[Random]"
10179 </source> 10179 </source>
10180 <dest> 10180 <dest>
10181 *: "<Ausazkoa>" 10181 *: "[Ausazkoa]"
10182 </dest> 10182 </dest>
10183 <voice> 10183 <voice>
10184 *: "Ausazkoa" 10184 *: "Ausazkoa"
@@ -12033,13 +12033,13 @@
12033</phrase> 12033</phrase>
12034<phrase> 12034<phrase>
12035 id: LANG_TAGNAVI_UNTAGGED 12035 id: LANG_TAGNAVI_UNTAGGED
12036 desc: "<untagged>" entry in tag browser 12036 desc: "[untagged]" entry in tag browser
12037 user: core 12037 user: core
12038 <source> 12038 <source>
12039 *: "<Untagged>" 12039 *: "[Untagged]"
12040 </source> 12040 </source>
12041 <dest> 12041 <dest>
12042 *: "<Etiketatu gabea>" 12042 *: "[Etiketatu gabea]"
12043 </dest> 12043 </dest>
12044 <voice> 12044 <voice>
12045 *: "Etiketatu gabea" 12045 *: "Etiketatu gabea"
diff --git a/apps/lang/bulgarian.lang b/apps/lang/bulgarian.lang
index 8c050e5f08..adb44b59f8 100644
--- a/apps/lang/bulgarian.lang
+++ b/apps/lang/bulgarian.lang
@@ -475,10 +475,10 @@
475 desc: top item in the list when asking user about bookmark auto load 475 desc: top item in the list when asking user about bookmark auto load
476 user: core 476 user: core
477 <source> 477 <source>
478 *: "<Don't Resume>" 478 *: "[Don't Resume]"
479 </source> 479 </source>
480 <dest> 480 <dest>
481 *: "<Ðе подновÑвай>" 481 *: "[Ðе подновÑвай]"
482 </dest> 482 </dest>
483 <voice> 483 <voice>
484 *: "Ðе подновÑвай" 484 *: "Ðе подновÑвай"
@@ -503,10 +503,10 @@
503 desc: bookmark selection list, bookmark couldn't be parsed 503 desc: bookmark selection list, bookmark couldn't be parsed
504 user: core 504 user: core
505 <source> 505 <source>
506 *: "<Invalid Bookmark>" 506 *: "[Invalid Bookmark]"
507 </source> 507 </source>
508 <dest> 508 <dest>
509 *: "<Ðевалидна отметка>" 509 *: "[Ðевалидна отметка]"
510 </dest> 510 </dest>
511 <voice> 511 <voice>
512 *: "Ðевалидна отметка" 512 *: "Ðевалидна отметка"
@@ -2113,13 +2113,13 @@
2113</phrase> 2113</phrase>
2114<phrase> 2114<phrase>
2115 id: LANG_TAGNAVI_ALL_TRACKS 2115 id: LANG_TAGNAVI_ALL_TRACKS
2116 desc: "<All tracks>" entry in tag browser 2116 desc: "[All tracks]" entry in tag browser
2117 user: core 2117 user: core
2118 <source> 2118 <source>
2119 *: "<All tracks>" 2119 *: "[All tracks]"
2120 </source> 2120 </source>
2121 <dest> 2121 <dest>
2122 *: "<Ð’Ñички запиÑи>" 2122 *: "[Ð’Ñички запиÑи]"
2123 </dest> 2123 </dest>
2124 <voice> 2124 <voice>
2125 *: "Ð’Ñички запиÑи" 2125 *: "Ð’Ñички запиÑи"
@@ -6574,10 +6574,10 @@
6574 desc: in tag viewer 6574 desc: in tag viewer
6575 user: core 6575 user: core
6576 <source> 6576 <source>
6577 *: "<No Info>" 6577 *: "[No Info]"
6578 </source> 6578 </source>
6579 <dest> 6579 <dest>
6580 *: "<ÐÑма информациÑ>" 6580 *: "[ÐÑма информациÑ]"
6581 </dest> 6581 </dest>
6582 <voice> 6582 <voice>
6583 *: "ÐÑма информациÑ" 6583 *: "ÐÑма информациÑ"
@@ -9222,13 +9222,13 @@
9222</phrase> 9222</phrase>
9223<phrase> 9223<phrase>
9224 id: LANG_TAGNAVI_RANDOM 9224 id: LANG_TAGNAVI_RANDOM
9225 desc: "<Random>" entry in tag browser 9225 desc: "[Random]" entry in tag browser
9226 user: core 9226 user: core
9227 <source> 9227 <source>
9228 *: "<Random>" 9228 *: "[Random]"
9229 </source> 9229 </source>
9230 <dest> 9230 <dest>
9231 *: "<Ñлучайно>" 9231 *: "[Ñлучайно]"
9232 </dest> 9232 </dest>
9233 <voice> 9233 <voice>
9234 *: "Ñлучайно" 9234 *: "Ñлучайно"
@@ -11041,13 +11041,13 @@
11041</phrase> 11041</phrase>
11042<phrase> 11042<phrase>
11043 id: LANG_TAGNAVI_UNTAGGED 11043 id: LANG_TAGNAVI_UNTAGGED
11044 desc: "<untagged>" entry in tag browser 11044 desc: "[untagged]" entry in tag browser
11045 user: core 11045 user: core
11046 <source> 11046 <source>
11047 *: "<Untagged>" 11047 *: "[Untagged]"
11048 </source> 11048 </source>
11049 <dest> 11049 <dest>
11050 *: "<Ðе е въведено>" 11050 *: "[Ðе е въведено]"
11051 </dest> 11051 </dest>
11052 <voice> 11052 <voice>
11053 *: "Ðе е въведено" 11053 *: "Ðе е въведено"
diff --git a/apps/lang/catala.lang b/apps/lang/catala.lang
index b0b8520d47..3d79fd62a4 100644
--- a/apps/lang/catala.lang
+++ b/apps/lang/catala.lang
@@ -478,10 +478,10 @@
478 desc: top item in the list when asking user about bookmark auto load 478 desc: top item in the list when asking user about bookmark auto load
479 user: core 479 user: core
480 <source> 480 <source>
481 *: "<Don't Resume>" 481 *: "[Don't Resume]"
482 </source> 482 </source>
483 <dest> 483 <dest>
484 *: "<No reprendre>" 484 *: "[No reprendre]"
485 </dest> 485 </dest>
486 <voice> 486 <voice>
487 *: "No reprendre" 487 *: "No reprendre"
@@ -506,10 +506,10 @@
506 desc: bookmark selection list, bookmark couldn't be parsed 506 desc: bookmark selection list, bookmark couldn't be parsed
507 user: core 507 user: core
508 <source> 508 <source>
509 *: "<Invalid Bookmark>" 509 *: "[Invalid Bookmark]"
510 </source> 510 </source>
511 <dest> 511 <dest>
512 *: "<Punt de pàgina invàlid>" 512 *: "[Punt de pàgina invàlid]"
513 </dest> 513 </dest>
514 <voice> 514 <voice>
515 *: "Punt de Pàgina invàlid" 515 *: "Punt de Pàgina invàlid"
@@ -2298,13 +2298,13 @@
2298</phrase> 2298</phrase>
2299<phrase> 2299<phrase>
2300 id: LANG_TAGNAVI_ALL_TRACKS 2300 id: LANG_TAGNAVI_ALL_TRACKS
2301 desc: "<All tracks>" entry in tag browser 2301 desc: "[All tracks]" entry in tag browser
2302 user: core 2302 user: core
2303 <source> 2303 <source>
2304 *: "<All tracks>" 2304 *: "[All tracks]"
2305 </source> 2305 </source>
2306 <dest> 2306 <dest>
2307 *: "<Totes les pistes>" 2307 *: "[Totes les pistes]"
2308 </dest> 2308 </dest>
2309 <voice> 2309 <voice>
2310 *: "Totes les pistes" 2310 *: "Totes les pistes"
@@ -7141,10 +7141,10 @@
7141 desc: in tag viewer 7141 desc: in tag viewer
7142 user: core 7142 user: core
7143 <source> 7143 <source>
7144 *: "<No Info>" 7144 *: "[No Info]"
7145 </source> 7145 </source>
7146 <dest> 7146 <dest>
7147 *: "<sense info>" 7147 *: "[sense info]"
7148 </dest> 7148 </dest>
7149 <voice> 7149 <voice>
7150 *: "sense info" 7150 *: "sense info"
@@ -10166,13 +10166,13 @@
10166</phrase> 10166</phrase>
10167<phrase> 10167<phrase>
10168 id: LANG_TAGNAVI_RANDOM 10168 id: LANG_TAGNAVI_RANDOM
10169 desc: "<Random>" entry in tag browser 10169 desc: "[Random]" entry in tag browser
10170 user: core 10170 user: core
10171 <source> 10171 <source>
10172 *: "<Random>" 10172 *: "[Random]"
10173 </source> 10173 </source>
10174 <dest> 10174 <dest>
10175 *: "<Aleatori>" 10175 *: "[Aleatori]"
10176 </dest> 10176 </dest>
10177 <voice> 10177 <voice>
10178 *: "Aleatori" 10178 *: "Aleatori"
@@ -12027,13 +12027,13 @@
12027</phrase> 12027</phrase>
12028<phrase> 12028<phrase>
12029 id: LANG_TAGNAVI_UNTAGGED 12029 id: LANG_TAGNAVI_UNTAGGED
12030 desc: "<untagged>" entry in tag browser 12030 desc: "[untagged]" entry in tag browser
12031 user: core 12031 user: core
12032 <source> 12032 <source>
12033 *: "<Untagged>" 12033 *: "[Untagged]"
12034 </source> 12034 </source>
12035 <dest> 12035 <dest>
12036 *: "<Sense etiquetar>" 12036 *: "[Sense etiquetar]"
12037 </dest> 12037 </dest>
12038 <voice> 12038 <voice>
12039 *: "Sense etiquetar" 12039 *: "Sense etiquetar"
diff --git a/apps/lang/chinese-simp.lang b/apps/lang/chinese-simp.lang
index c7e0e6c9f7..a35eb4bad5 100644
--- a/apps/lang/chinese-simp.lang
+++ b/apps/lang/chinese-simp.lang
@@ -936,10 +936,10 @@
936 *: "Shuffle" 936 *: "Shuffle"
937 </source> 937 </source>
938 <dest> 938 <dest>
939 *: "ä¹±åºæ’­æ”¾" 939 *: "éšæœºæ’­æ”¾"
940 </dest> 940 </dest>
941 <voice> 941 <voice>
942 *: "ä¹±åºæ’­æ”¾" 942 *: "éšæœºæ’­æ”¾"
943 </voice> 943 </voice>
944</phrase> 944</phrase>
945<phrase> 945<phrase>
@@ -978,10 +978,10 @@
978 *: "A-B" 978 *: "A-B"
979 </source> 979 </source>
980 <dest> 980 <dest>
981 *: "A-Bé‡å¤æ®µ" 981 *: "A-Bé‡å¤"
982 </dest> 982 </dest>
983 <voice> 983 <voice>
984 *: "AB段" 984 *: "区段é‡å¤"
985 </voice> 985 </voice>
986</phrase> 986</phrase>
987<phrase> 987<phrase>
@@ -1382,7 +1382,7 @@
1382 *: "Supported" 1382 *: "Supported"
1383 </source> 1383 </source>
1384 <dest> 1384 <dest>
1385 *: "仅显示Rockbox支æŒçš„" 1385 *: "仅显示Rockbox支æŒçš„文件"
1386 </dest> 1386 </dest>
1387 <voice> 1387 <voice>
1388 *: "仅显示Rockbox支æŒçš„文件" 1388 *: "仅显示Rockbox支æŒçš„文件"
@@ -4395,13 +4395,13 @@
4395 desc: in tag viewer 4395 desc: in tag viewer
4396 user: core 4396 user: core
4397 <source> 4397 <source>
4398 *: "<No Info>" 4398 *: "[No Info]"
4399 </source> 4399 </source>
4400 <dest> 4400 <dest>
4401 *: "<æ— ä¿¡æ¯>" 4401 *: "[æ— ä¿¡æ¯]"
4402 </dest> 4402 </dest>
4403 <voice> 4403 <voice>
4404 *: "<æ— ä¿¡æ¯>" 4404 *: "æ— ä¿¡æ¯"
4405 </voice> 4405 </voice>
4406</phrase> 4406</phrase>
4407<phrase> 4407<phrase>
@@ -6792,13 +6792,13 @@
6792</phrase> 6792</phrase>
6793<phrase> 6793<phrase>
6794 id: LANG_TAGNAVI_ALL_TRACKS 6794 id: LANG_TAGNAVI_ALL_TRACKS
6795 desc: "<All tracks>" entry in tag browser 6795 desc: "[All tracks]" entry in tag browser
6796 user: core 6796 user: core
6797 <source> 6797 <source>
6798 *: "<All tracks>" 6798 *: "[All tracks]"
6799 </source> 6799 </source>
6800 <dest> 6800 <dest>
6801 *: "<所有曲目>" 6801 *: "[所有曲目]"
6802 </dest> 6802 </dest>
6803 <voice> 6803 <voice>
6804 *: "所有曲目" 6804 *: "所有曲目"
@@ -8371,10 +8371,10 @@
8371 desc: top item in the list when asking user about bookmark auto load 8371 desc: top item in the list when asking user about bookmark auto load
8372 user: core 8372 user: core
8373 <source> 8373 <source>
8374 *: "<Don't Resume>" 8374 *: "[Don't Resume]"
8375 </source> 8375 </source>
8376 <dest> 8376 <dest>
8377 *: "<ä¸ä»Žä¹¦ç­¾å¤„æ¢å¤æ’­æ”¾>" 8377 *: "[ä¸ä»Žä¹¦ç­¾å¤„æ¢å¤æ’­æ”¾]"
8378 </dest> 8378 </dest>
8379 <voice> 8379 <voice>
8380 *: "ä¸ä»Žä¹¦ç­¾å¤„æ¢å¤æ’­æ”¾" 8380 *: "ä¸ä»Žä¹¦ç­¾å¤„æ¢å¤æ’­æ”¾"
@@ -8545,10 +8545,10 @@
8545 desc: bookmark selection list, bookmark couldn't be parsed 8545 desc: bookmark selection list, bookmark couldn't be parsed
8546 user: core 8546 user: core
8547 <source> 8547 <source>
8548 *: "<Invalid Bookmark>" 8548 *: "[Invalid Bookmark]"
8549 </source> 8549 </source>
8550 <dest> 8550 <dest>
8551 *: "<无效书签>" 8551 *: "[无效书签]"
8552 </dest> 8552 </dest>
8553 <voice> 8553 <voice>
8554 *: "这个书签无效" 8554 *: "这个书签无效"
@@ -9195,13 +9195,13 @@
9195</phrase> 9195</phrase>
9196<phrase> 9196<phrase>
9197 id: LANG_TAGNAVI_RANDOM 9197 id: LANG_TAGNAVI_RANDOM
9198 desc: "<Random>" entry in tag browser 9198 desc: "[Random]" entry in tag browser
9199 user: core 9199 user: core
9200 <source> 9200 <source>
9201 *: "<Random>" 9201 *: "[Random]"
9202 </source> 9202 </source>
9203 <dest> 9203 <dest>
9204 *: "<éšæœº>" 9204 *: "[éšæœº]"
9205 </dest> 9205 </dest>
9206 <voice> 9206 <voice>
9207 *: "éšæœº" 9207 *: "éšæœº"
@@ -10923,13 +10923,13 @@
10923</phrase> 10923</phrase>
10924<phrase> 10924<phrase>
10925 id: LANG_TAGNAVI_UNTAGGED 10925 id: LANG_TAGNAVI_UNTAGGED
10926 desc: "<untagged>" entry in tag browser 10926 desc: "[untagged]" entry in tag browser
10927 user: core 10927 user: core
10928 <source> 10928 <source>
10929 *: "<Untagged>" 10929 *: "[Untagged]"
10930 </source> 10930 </source>
10931 <dest> 10931 <dest>
10932 *: "<无标签/元数æ®>" 10932 *: "[无标签/元数æ®]"
10933 </dest> 10933 </dest>
10934 <voice> 10934 <voice>
10935 *: "没有标签或者元数æ®" 10935 *: "没有标签或者元数æ®"
@@ -12287,10 +12287,10 @@
12287 *: "Backlight Exemptions" 12287 *: "Backlight Exemptions"
12288 </source> 12288 </source>
12289 <dest> 12289 <dest>
12290 *: "背光è±å…(çœç”µç”¨ï¼‰" 12290 *: "背光开å¯ä¾‹å¤–规åˆ"
12291 </dest> 12291 </dest>
12292 <voice> 12292 <voice>
12293 *: "背光è±å" 12293 *: "背光开å¯ä¾‹å¤–规则"
12294 </voice> 12294 </voice>
12295</phrase> 12295</phrase>
12296<phrase> 12296<phrase>
@@ -15494,10 +15494,10 @@
15494 *: "Album Art" 15494 *: "Album Art"
15495 </source> 15495 </source>
15496 <dest> 15496 <dest>
15497 *: "曲绘" 15497 *: "专辑å°é¢æ¥æº"
15498 </dest> 15498 </dest>
15499 <voice> 15499 <voice>
15500 *: "曲绘" 15500 *: "专辑å°é¢æ¥æº"
15501 </voice> 15501 </voice>
15502</phrase> 15502</phrase>
15503<phrase> 15503<phrase>
@@ -16005,10 +16005,10 @@
16005 *: "What's Playing Screen" 16005 *: "What's Playing Screen"
16006 </source> 16006 </source>
16007 <dest> 16007 <dest>
16008 *: "What's Playing å±å¹•" 16008 *: "正在播放å±å¹•"
16009 </dest> 16009 </dest>
16010 <voice> 16010 <voice>
16011 *: "What's Playing å±å¹•" 16011 *: "正在播放å±å¹•"
16012 </voice> 16012 </voice>
16013</phrase> 16013</phrase>
16014<phrase> 16014<phrase>
@@ -16445,3 +16445,59 @@
16445 *: "剩余" 16445 *: "剩余"
16446 </voice> 16446 </voice>
16447</phrase> 16447</phrase>
16448<phrase>
16449 id: LANG_DISABLE_MAINMENU_SCROLLING
16450 desc: Disable main menu scrolling
16451 user: core
16452 <source>
16453 *: "Disable main menu scrolling"
16454 </source>
16455 <dest>
16456 *: "ç¦ç”¨ä¸»èœå•æ»šåŠ¨"
16457 </dest>
16458 <voice>
16459 *: "ç¦ç”¨ä¸»èœå•æ»šåŠ¨"
16460 </voice>
16461</phrase>
16462<phrase>
16463 id: LANG_RANDOM_SHUFFLE_RANDOM_SELECTIVE_SONGS_SUMMARY
16464 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
16465 user: core
16466 <source>
16467 *: "Selection too big, %d random tracks will be selected"
16468 </source>
16469 <dest>
16470 *: "选择了太多,将从中éšæœºé€‰å–%d首"
16471 </dest>
16472 <voice>
16473 *: "选择了太多,将从中选择更少曲目"
16474 </voice>
16475</phrase>
16476<phrase>
16477 id: LANG_DISPLAY_TITLEALBUM_FROMTAGS
16478 desc: track display options
16479 user: core
16480 <source>
16481 *: "Title & Album from ID3 tags"
16482 </source>
16483 <dest>
16484 *: "ID3 标题&专辑信æ¯"
16485 </dest>
16486 <voice>
16487 *: "I D 3 标题与专辑信æ¯"
16488 </voice>
16489</phrase>
16490<phrase>
16491 id: LANG_DISPLAY_TITLE_FROMTAGS
16492 desc: track display options
16493 user: core
16494 <source>
16495 *: "Title from ID3 tags"
16496 </source>
16497 <dest>
16498 *: "ID3标题信æ¯"
16499 </dest>
16500 <voice>
16501 *: "I D 3 标题信æ¯"
16502 </voice>
16503</phrase>
diff --git a/apps/lang/chinese-trad.lang b/apps/lang/chinese-trad.lang
index 4d8d6b270b..49f4fd824b 100644
--- a/apps/lang/chinese-trad.lang
+++ b/apps/lang/chinese-trad.lang
@@ -4502,13 +4502,13 @@
4502 desc: in tag viewer 4502 desc: in tag viewer
4503 user: core 4503 user: core
4504 <source> 4504 <source>
4505 *: "<No Info>" 4505 *: "[No Info]"
4506 </source> 4506 </source>
4507 <dest> 4507 <dest>
4508 *: "<無資訊>" 4508 *: "[無資訊]"
4509 </dest> 4509 </dest>
4510 <voice> 4510 <voice>
4511 *: "<無資訊>" 4511 *: "[無資訊]"
4512 </voice> 4512 </voice>
4513</phrase> 4513</phrase>
4514<phrase> 4514<phrase>
@@ -6913,16 +6913,16 @@
6913</phrase> 6913</phrase>
6914<phrase> 6914<phrase>
6915 id: LANG_TAGNAVI_ALL_TRACKS 6915 id: LANG_TAGNAVI_ALL_TRACKS
6916 desc: "<All tracks>" entry in tag browser 6916 desc: "[All tracks]" entry in tag browser
6917 user: core 6917 user: core
6918 <source> 6918 <source>
6919 *: "<All tracks>" 6919 *: "[All tracks]"
6920 </source> 6920 </source>
6921 <dest> 6921 <dest>
6922 *: "<所有曲目>" 6922 *: "[所有曲目]"
6923 </dest> 6923 </dest>
6924 <voice> 6924 <voice>
6925 *: "<所有曲目>" 6925 *: "[所有曲目]"
6926 </voice> 6926 </voice>
6927</phrase> 6927</phrase>
6928<phrase> 6928<phrase>
@@ -8524,13 +8524,13 @@
8524 desc: top item in the list when asking user about bookmark auto load 8524 desc: top item in the list when asking user about bookmark auto load
8525 user: core 8525 user: core
8526 <source> 8526 <source>
8527 *: "<Don't Resume>" 8527 *: "[Don't Resume]"
8528 </source> 8528 </source>
8529 <dest> 8529 <dest>
8530 *: "<ä¸è¦æ¢å¾©æ›¸ç°½>" 8530 *: "[ä¸è¦æ¢å¾©æ›¸ç°½]"
8531 </dest> 8531 </dest>
8532 <voice> 8532 <voice>
8533 *: "<ä¸è¦æ¢å¾©æ›¸ç°½>" 8533 *: "[ä¸è¦æ¢å¾©æ›¸ç°½]"
8534 </voice> 8534 </voice>
8535</phrase> 8535</phrase>
8536<phrase> 8536<phrase>
@@ -8726,13 +8726,13 @@
8726 desc: bookmark selection list, bookmark couldn't be parsed 8726 desc: bookmark selection list, bookmark couldn't be parsed
8727 user: core 8727 user: core
8728 <source> 8728 <source>
8729 *: "<Invalid Bookmark>" 8729 *: "[Invalid Bookmark]"
8730 </source> 8730 </source>
8731 <dest> 8731 <dest>
8732 *: "<無效書簽>" 8732 *: "[無效書簽]"
8733 </dest> 8733 </dest>
8734 <voice> 8734 <voice>
8735 *: "<無效書簽>" 8735 *: "[無效書簽]"
8736 </voice> 8736 </voice>
8737</phrase> 8737</phrase>
8738<phrase> 8738<phrase>
@@ -9393,13 +9393,13 @@
9393</phrase> 9393</phrase>
9394<phrase> 9394<phrase>
9395 id: LANG_TAGNAVI_RANDOM 9395 id: LANG_TAGNAVI_RANDOM
9396 desc: "<Random>" entry in tag browser 9396 desc: "[Random]" entry in tag browser
9397 user: core 9397 user: core
9398 <source> 9398 <source>
9399 *: "<Random>" 9399 *: "[Random]"
9400 </source> 9400 </source>
9401 <dest> 9401 <dest>
9402 *: "<隨機>" 9402 *: "[隨機]"
9403 </dest> 9403 </dest>
9404 <voice> 9404 <voice>
9405 *: "隨機" 9405 *: "隨機"
diff --git a/apps/lang/czech.lang b/apps/lang/czech.lang
index e3d79c91fa..e71e4dd179 100644
--- a/apps/lang/czech.lang
+++ b/apps/lang/czech.lang
@@ -482,10 +482,10 @@
482 desc: top item in the list when asking user about bookmark auto load 482 desc: top item in the list when asking user about bookmark auto load
483 user: core 483 user: core
484 <source> 484 <source>
485 *: "<Don't Resume>" 485 *: "[Don't Resume]"
486 </source> 486 </source>
487 <dest> 487 <dest>
488 *: "<NepokraÄovat>" 488 *: "[NepokraÄovat]"
489 </dest> 489 </dest>
490 <voice> 490 <voice>
491 *: "NepokraÄovat" 491 *: "NepokraÄovat"
@@ -510,10 +510,10 @@
510 desc: bookmark selection list, bookmark couldn't be parsed 510 desc: bookmark selection list, bookmark couldn't be parsed
511 user: core 511 user: core
512 <source> 512 <source>
513 *: "<Invalid Bookmark>" 513 *: "[Invalid Bookmark]"
514 </source> 514 </source>
515 <dest> 515 <dest>
516 *: "<Neplatná záložka>" 516 *: "[Neplatná záložka]"
517 </dest> 517 </dest>
518 <voice> 518 <voice>
519 *: "Neplatná záložka" 519 *: "Neplatná záložka"
@@ -2303,13 +2303,13 @@
2303</phrase> 2303</phrase>
2304<phrase> 2304<phrase>
2305 id: LANG_TAGNAVI_ALL_TRACKS 2305 id: LANG_TAGNAVI_ALL_TRACKS
2306 desc: "<All tracks>" entry in tag browser 2306 desc: "[All tracks]" entry in tag browser
2307 user: core 2307 user: core
2308 <source> 2308 <source>
2309 *: "<All tracks>" 2309 *: "[All tracks]"
2310 </source> 2310 </source>
2311 <dest> 2311 <dest>
2312 *: "<Vsechny skladby>" 2312 *: "[Vsechny skladby]"
2313 </dest> 2313 </dest>
2314 <voice> 2314 <voice>
2315 *: "VÅ¡echny skladby" 2315 *: "VÅ¡echny skladby"
@@ -7154,10 +7154,10 @@
7154 desc: in tag viewer 7154 desc: in tag viewer
7155 user: core 7155 user: core
7156 <source> 7156 <source>
7157 *: "<No Info>" 7157 *: "[No Info]"
7158 </source> 7158 </source>
7159 <dest> 7159 <dest>
7160 *: "<Bez informace>" 7160 *: "[Bez informace]"
7161 </dest> 7161 </dest>
7162 <voice> 7162 <voice>
7163 *: "Bez informace" 7163 *: "Bez informace"
@@ -10189,13 +10189,13 @@
10189</phrase> 10189</phrase>
10190<phrase> 10190<phrase>
10191 id: LANG_TAGNAVI_RANDOM 10191 id: LANG_TAGNAVI_RANDOM
10192 desc: "<Random>" entry in tag browser 10192 desc: "[Random]" entry in tag browser
10193 user: core 10193 user: core
10194 <source> 10194 <source>
10195 *: "<Random>" 10195 *: "[Random]"
10196 </source> 10196 </source>
10197 <dest> 10197 <dest>
10198 *: "<Nahodne>" 10198 *: "[Nahodne]"
10199 </dest> 10199 </dest>
10200 <voice> 10200 <voice>
10201 *: "Náhodně" 10201 *: "Náhodně"
@@ -12050,13 +12050,13 @@
12050</phrase> 12050</phrase>
12051<phrase> 12051<phrase>
12052 id: LANG_TAGNAVI_UNTAGGED 12052 id: LANG_TAGNAVI_UNTAGGED
12053 desc: "<untagged>" entry in tag browser 12053 desc: "[untagged]" entry in tag browser
12054 user: core 12054 user: core
12055 <source> 12055 <source>
12056 *: "<Untagged>" 12056 *: "[Untagged]"
12057 </source> 12057 </source>
12058 <dest> 12058 <dest>
12059 *: "<Bez popisu>" 12059 *: "[Bez popisu]"
12060 </dest> 12060 </dest>
12061 <voice> 12061 <voice>
12062 *: "Bez popisu" 12062 *: "Bez popisu"
diff --git a/apps/lang/dansk.lang b/apps/lang/dansk.lang
index 8a01d45efc..c2c6b4f09d 100644
--- a/apps/lang/dansk.lang
+++ b/apps/lang/dansk.lang
@@ -4134,10 +4134,10 @@
4134 desc: in tag viewer 4134 desc: in tag viewer
4135 user: core 4135 user: core
4136 <source> 4136 <source>
4137 *: "<No Info>" 4137 *: "[No Info]"
4138 </source> 4138 </source>
4139 <dest> 4139 <dest>
4140 *: "<Ingen info>" 4140 *: "[Ingen info]"
4141 </dest> 4141 </dest>
4142 <voice> 4142 <voice>
4143 *: "Ingen info" 4143 *: "Ingen info"
@@ -7333,13 +7333,13 @@
7333</phrase> 7333</phrase>
7334<phrase> 7334<phrase>
7335 id: LANG_TAGNAVI_ALL_TRACKS 7335 id: LANG_TAGNAVI_ALL_TRACKS
7336 desc: "<All tracks>" entry in tag browser 7336 desc: "[All tracks]" entry in tag browser
7337 user: core 7337 user: core
7338 <source> 7338 <source>
7339 *: "<All tracks>" 7339 *: "[All tracks]"
7340 </source> 7340 </source>
7341 <dest> 7341 <dest>
7342 *: "<Alle sange>" 7342 *: "[Alle sange]"
7343 </dest> 7343 </dest>
7344 <voice> 7344 <voice>
7345 *: "Alle sange" 7345 *: "Alle sange"
@@ -9048,10 +9048,10 @@
9048 desc: top item in the list when asking user about bookmark auto load 9048 desc: top item in the list when asking user about bookmark auto load
9049 user: core 9049 user: core
9050 <source> 9050 <source>
9051 *: "<Don't Resume>" 9051 *: "[Don't Resume]"
9052 </source> 9052 </source>
9053 <dest> 9053 <dest>
9054 *: "<Fortsæt ikke>" 9054 *: "[Fortsæt ikke]"
9055 </dest> 9055 </dest>
9056 <voice> 9056 <voice>
9057 *: "Fortsæt ikke" 9057 *: "Fortsæt ikke"
@@ -9135,10 +9135,10 @@
9135 desc: bookmark selection list, bookmark couldn't be parsed 9135 desc: bookmark selection list, bookmark couldn't be parsed
9136 user: core 9136 user: core
9137 <source> 9137 <source>
9138 *: "<Invalid Bookmark>" 9138 *: "[Invalid Bookmark]"
9139 </source> 9139 </source>
9140 <dest> 9140 <dest>
9141 *: "<Ugyldigt bogmærke>" 9141 *: "[Ugyldigt bogmærke]"
9142 </dest> 9142 </dest>
9143 <voice> 9143 <voice>
9144 *: "Ugyldigt bogmærke" 9144 *: "Ugyldigt bogmærke"
@@ -10169,13 +10169,13 @@
10169</phrase> 10169</phrase>
10170<phrase> 10170<phrase>
10171 id: LANG_TAGNAVI_RANDOM 10171 id: LANG_TAGNAVI_RANDOM
10172 desc: "<Random>" entry in tag browser 10172 desc: "[Random]" entry in tag browser
10173 user: core 10173 user: core
10174 <source> 10174 <source>
10175 *: "<Random>" 10175 *: "[Random]"
10176 </source> 10176 </source>
10177 <dest> 10177 <dest>
10178 *: "<Tilfældig>" 10178 *: "[Tilfældig]"
10179 </dest> 10179 </dest>
10180 <voice> 10180 <voice>
10181 *: "Tilfældig" 10181 *: "Tilfældig"
@@ -11942,13 +11942,13 @@
11942</phrase> 11942</phrase>
11943<phrase> 11943<phrase>
11944 id: LANG_TAGNAVI_UNTAGGED 11944 id: LANG_TAGNAVI_UNTAGGED
11945 desc: "<untagged>" entry in tag browser 11945 desc: "[untagged]" entry in tag browser
11946 user: core 11946 user: core
11947 <source> 11947 <source>
11948 *: "<Untagged>" 11948 *: "[Untagged]"
11949 </source> 11949 </source>
11950 <dest> 11950 <dest>
11951 *: "<Umærket>" 11951 *: "[Umærket]"
11952 </dest> 11952 </dest>
11953 <voice> 11953 <voice>
11954 *: "Umærket" 11954 *: "Umærket"
diff --git a/apps/lang/deutsch.lang b/apps/lang/deutsch.lang
index 3ea025b1ac..512bacf1ff 100644
--- a/apps/lang/deutsch.lang
+++ b/apps/lang/deutsch.lang
@@ -490,10 +490,10 @@
490 desc: top item in the list when asking user about bookmark auto load 490 desc: top item in the list when asking user about bookmark auto load
491 user: core 491 user: core
492 <source> 492 <source>
493 *: "<Don't Resume>" 493 *: "[Don't Resume]"
494 </source> 494 </source>
495 <dest> 495 <dest>
496 *: "<Nicht fortsetzen>" 496 *: "[Nicht fortsetzen]"
497 </dest> 497 </dest>
498 <voice> 498 <voice>
499 *: "Nicht fortsetzen" 499 *: "Nicht fortsetzen"
@@ -518,10 +518,10 @@
518 desc: bookmark selection list, bookmark couldn't be parsed 518 desc: bookmark selection list, bookmark couldn't be parsed
519 user: core 519 user: core
520 <source> 520 <source>
521 *: "<Invalid Bookmark>" 521 *: "[Invalid Bookmark]"
522 </source> 522 </source>
523 <dest> 523 <dest>
524 *: "<Ungültiges Lesezeichen>" 524 *: "[Ungültiges Lesezeichen]"
525 </dest> 525 </dest>
526 <voice> 526 <voice>
527 *: "Ungültiges Lesezeichen" 527 *: "Ungültiges Lesezeichen"
@@ -2112,13 +2112,13 @@
2112</phrase> 2112</phrase>
2113<phrase> 2113<phrase>
2114 id: LANG_TAGNAVI_ALL_TRACKS 2114 id: LANG_TAGNAVI_ALL_TRACKS
2115 desc: "<All tracks>" entry in tag browser 2115 desc: "[All tracks]" entry in tag browser
2116 user: core 2116 user: core
2117 <source> 2117 <source>
2118 *: "<All tracks>" 2118 *: "[All tracks]"
2119 </source> 2119 </source>
2120 <dest> 2120 <dest>
2121 *: "<Alle Titel>" 2121 *: "[Alle Titel]"
2122 </dest> 2122 </dest>
2123 <voice> 2123 <voice>
2124 *: "Alle Titel" 2124 *: "Alle Titel"
@@ -6559,10 +6559,10 @@
6559 desc: in tag viewer 6559 desc: in tag viewer
6560 user: core 6560 user: core
6561 <source> 6561 <source>
6562 *: "<No Info>" 6562 *: "[No Info]"
6563 </source> 6563 </source>
6564 <dest> 6564 <dest>
6565 *: "<keine Info>" 6565 *: "[keine Info]"
6566 </dest> 6566 </dest>
6567 <voice> 6567 <voice>
6568 *: "keine Info" 6568 *: "keine Info"
@@ -9207,13 +9207,13 @@
9207</phrase> 9207</phrase>
9208<phrase> 9208<phrase>
9209 id: LANG_TAGNAVI_RANDOM 9209 id: LANG_TAGNAVI_RANDOM
9210 desc: "<Random>" entry in tag browser 9210 desc: "[Random]" entry in tag browser
9211 user: core 9211 user: core
9212 <source> 9212 <source>
9213 *: "<Random>" 9213 *: "[Random]"
9214 </source> 9214 </source>
9215 <dest> 9215 <dest>
9216 *: "<Zufällig>" 9216 *: "[Zufällig]"
9217 </dest> 9217 </dest>
9218 <voice> 9218 <voice>
9219 *: "Zufällig" 9219 *: "Zufällig"
@@ -10907,13 +10907,13 @@
10907</phrase> 10907</phrase>
10908<phrase> 10908<phrase>
10909 id: LANG_TAGNAVI_UNTAGGED 10909 id: LANG_TAGNAVI_UNTAGGED
10910 desc: "<untagged>" entry in tag browser 10910 desc: "[untagged]" entry in tag browser
10911 user: core 10911 user: core
10912 <source> 10912 <source>
10913 *: "<Untagged>" 10913 *: "[Untagged]"
10914 </source> 10914 </source>
10915 <dest> 10915 <dest>
10916 *: "<Unbekannt>" 10916 *: "[Unbekannt]"
10917 </dest> 10917 </dest>
10918 <voice> 10918 <voice>
10919 *: "Unbekannt" 10919 *: "Unbekannt"
diff --git a/apps/lang/eesti.lang b/apps/lang/eesti.lang
index 5e80121404..2cbd86c199 100644
--- a/apps/lang/eesti.lang
+++ b/apps/lang/eesti.lang
@@ -4620,10 +4620,10 @@
4620 desc: in tag viewer 4620 desc: in tag viewer
4621 user: core 4621 user: core
4622 <source> 4622 <source>
4623 *: "<No Info>" 4623 *: "[No Info]"
4624 </source> 4624 </source>
4625 <dest> 4625 <dest>
4626 *: "<No Info>" 4626 *: "[No Info]"
4627 </dest> 4627 </dest>
4628 <voice> 4628 <voice>
4629 *: "No Info" 4629 *: "No Info"
@@ -7131,13 +7131,13 @@
7131</phrase> 7131</phrase>
7132<phrase> 7132<phrase>
7133 id: LANG_TAGNAVI_ALL_TRACKS 7133 id: LANG_TAGNAVI_ALL_TRACKS
7134 desc: "<All tracks>" entry in tag browser 7134 desc: "[All tracks]" entry in tag browser
7135 user: core 7135 user: core
7136 <source> 7136 <source>
7137 *: "<All tracks>" 7137 *: "[All tracks]"
7138 </source> 7138 </source>
7139 <dest> 7139 <dest>
7140 *: "<Kõik lood>" 7140 *: "[Kõik lood]"
7141 </dest> 7141 </dest>
7142 <voice> 7142 <voice>
7143 *: "All tracks" 7143 *: "All tracks"
diff --git a/apps/lang/english-us.lang b/apps/lang/english-us.lang
index fe8e461041..b18057f655 100644
--- a/apps/lang/english-us.lang
+++ b/apps/lang/english-us.lang
@@ -469,10 +469,10 @@
469 desc: top item in the list when asking user about bookmark auto load 469 desc: top item in the list when asking user about bookmark auto load
470 user: core 470 user: core
471 <source> 471 <source>
472 *: "<Don't Resume>" 472 *: "[Don't Resume]"
473 </source> 473 </source>
474 <dest> 474 <dest>
475 *: "<Don't Resume>" 475 *: "[Don't Resume]"
476 </dest> 476 </dest>
477 <voice> 477 <voice>
478 *: "Do not resume" 478 *: "Do not resume"
@@ -497,10 +497,10 @@
497 desc: bookmark selection list, bookmark couldn't be parsed 497 desc: bookmark selection list, bookmark couldn't be parsed
498 user: core 498 user: core
499 <source> 499 <source>
500 *: "<Invalid Bookmark>" 500 *: "[Invalid Bookmark]"
501 </source> 501 </source>
502 <dest> 502 <dest>
503 *: "<Invalid Bookmark>" 503 *: "[Invalid Bookmark]"
504 </dest> 504 </dest>
505 <voice> 505 <voice>
506 *: "Invalid Bookmark" 506 *: "Invalid Bookmark"
@@ -2121,13 +2121,13 @@
2121</phrase> 2121</phrase>
2122<phrase> 2122<phrase>
2123 id: LANG_TAGNAVI_ALL_TRACKS 2123 id: LANG_TAGNAVI_ALL_TRACKS
2124 desc: "<All tracks>" entry in tag browser 2124 desc: "[All tracks]" entry in tag browser
2125 user: core 2125 user: core
2126 <source> 2126 <source>
2127 *: "<All tracks>" 2127 *: "[All tracks]"
2128 </source> 2128 </source>
2129 <dest> 2129 <dest>
2130 *: "<All tracks>" 2130 *: "[All tracks]"
2131 </dest> 2131 </dest>
2132 <voice> 2132 <voice>
2133 *: "All tracks" 2133 *: "All tracks"
@@ -6568,10 +6568,10 @@
6568 desc: in tag viewer 6568 desc: in tag viewer
6569 user: core 6569 user: core
6570 <source> 6570 <source>
6571 *: "<No Info>" 6571 *: "[No Info]"
6572 </source> 6572 </source>
6573 <dest> 6573 <dest>
6574 *: "<No Info>" 6574 *: "[No Info]"
6575 </dest> 6575 </dest>
6576 <voice> 6576 <voice>
6577 *: "No Info" 6577 *: "No Info"
@@ -9230,13 +9230,13 @@
9230</phrase> 9230</phrase>
9231<phrase> 9231<phrase>
9232 id: LANG_TAGNAVI_RANDOM 9232 id: LANG_TAGNAVI_RANDOM
9233 desc: "<Random>" entry in tag browser 9233 desc: "[Random]" entry in tag browser
9234 user: core 9234 user: core
9235 <source> 9235 <source>
9236 *: "<Random>" 9236 *: "[Random]"
9237 </source> 9237 </source>
9238 <dest> 9238 <dest>
9239 *: "<Random>" 9239 *: "[Random]"
9240 </dest> 9240 </dest>
9241 <voice> 9241 <voice>
9242 *: "Random" 9242 *: "Random"
@@ -11080,13 +11080,13 @@
11080</phrase> 11080</phrase>
11081<phrase> 11081<phrase>
11082 id: LANG_TAGNAVI_UNTAGGED 11082 id: LANG_TAGNAVI_UNTAGGED
11083 desc: "<untagged>" entry in tag browser 11083 desc: "[untagged]" entry in tag browser
11084 user: core 11084 user: core
11085 <source> 11085 <source>
11086 *: "<Untagged>" 11086 *: "[Untagged]"
11087 </source> 11087 </source>
11088 <dest> 11088 <dest>
11089 *: "<Untagged>" 11089 *: "[Untagged]"
11090 </dest> 11090 </dest>
11091 <voice> 11091 <voice>
11092 *: "Untagged" 11092 *: "Untagged"
@@ -16434,3 +16434,59 @@
16434 *: "Remaining" 16434 *: "Remaining"
16435 </voice> 16435 </voice>
16436</phrase> 16436</phrase>
16437<phrase>
16438 id: LANG_DISABLE_MAINMENU_SCROLLING
16439 desc: Disable main menu scrolling
16440 user: core
16441 <source>
16442 *: "Disable main menu scrolling"
16443 </source>
16444 <dest>
16445 *: "Disable main menu scrolling"
16446 </dest>
16447 <voice>
16448 *: "Disable main menu scrolling"
16449 </voice>
16450</phrase>
16451<phrase>
16452 id: LANG_RANDOM_SHUFFLE_RANDOM_SELECTIVE_SONGS_SUMMARY
16453 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
16454 user: core
16455 <source>
16456 *: "Selection too big, %d random tracks will be selected"
16457 </source>
16458 <dest>
16459 *: "Selection too big, %d random tracks will be selected"
16460 </dest>
16461 <voice>
16462 *: "Selection too big, fewer random tracks will be selected"
16463 </voice>
16464</phrase>
16465<phrase>
16466 id: LANG_DISPLAY_TITLEALBUM_FROMTAGS
16467 desc: track display options
16468 user: core
16469 <source>
16470 *: "Title & Album from ID3 tags"
16471 </source>
16472 <dest>
16473 *: "Title & Album from ID3 tags"
16474 </dest>
16475 <voice>
16476 *: "Title and Album from tags"
16477 </voice>
16478</phrase>
16479<phrase>
16480 id: LANG_DISPLAY_TITLE_FROMTAGS
16481 desc: track display options
16482 user: core
16483 <source>
16484 *: "Title from ID3 tags"
16485 </source>
16486 <dest>
16487 *: "Title from ID3 tags"
16488 </dest>
16489 <voice>
16490 *: "Title from tags"
16491 </voice>
16492</phrase>
diff --git a/apps/lang/english.lang b/apps/lang/english.lang
index c41c819a7b..fa2b5f62ae 100644
--- a/apps/lang/english.lang
+++ b/apps/lang/english.lang
@@ -546,10 +546,10 @@
546 desc: top item in the list when asking user about bookmark auto load 546 desc: top item in the list when asking user about bookmark auto load
547 user: core 547 user: core
548 <source> 548 <source>
549 *: "<Don't Resume>" 549 *: "[Don't Resume]"
550 </source> 550 </source>
551 <dest> 551 <dest>
552 *: "<Don't Resume>" 552 *: "[Don't Resume]"
553 </dest> 553 </dest>
554 <voice> 554 <voice>
555 *: "Do not resume" 555 *: "Do not resume"
@@ -574,10 +574,10 @@
574 desc: bookmark selection list, bookmark couldn't be parsed 574 desc: bookmark selection list, bookmark couldn't be parsed
575 user: core 575 user: core
576 <source> 576 <source>
577 *: "<Invalid Bookmark>" 577 *: "[Invalid Bookmark]"
578 </source> 578 </source>
579 <dest> 579 <dest>
580 *: "<Invalid Bookmark>" 580 *: "[Invalid Bookmark]"
581 </dest> 581 </dest>
582 <voice> 582 <voice>
583 *: "Invalid Bookmark" 583 *: "Invalid Bookmark"
@@ -2032,6 +2032,34 @@
2032 </voice> 2032 </voice>
2033</phrase> 2033</phrase>
2034<phrase> 2034<phrase>
2035 id: LANG_DISPLAY_TITLEALBUM_FROMTAGS
2036 desc: track display options
2037 user: core
2038 <source>
2039 *: "Title & Album from ID3 tags"
2040 </source>
2041 <dest>
2042 *: "Title & Album from ID3 tags"
2043 </dest>
2044 <voice>
2045 *: "Title and Album from tags"
2046 </voice>
2047</phrase>
2048<phrase>
2049 id: LANG_DISPLAY_TITLE_FROMTAGS
2050 desc: track display options
2051 user: core
2052 <source>
2053 *: "Title from ID3 tags"
2054 </source>
2055 <dest>
2056 *: "Title from ID3 tags"
2057 </dest>
2058 <voice>
2059 *: "Title from tags"
2060 </voice>
2061</phrase>
2062<phrase>
2035 id: LANG_BUILDING_DATABASE 2063 id: LANG_BUILDING_DATABASE
2036 desc: splash database building progress 2064 desc: splash database building progress
2037 user: core 2065 user: core
@@ -2054,6 +2082,20 @@
2054 </voice> 2082 </voice>
2055</phrase> 2083</phrase>
2056<phrase> 2084<phrase>
2085 id: LANG_RANDOM_SHUFFLE_RANDOM_SELECTIVE_SONGS_SUMMARY
2086 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
2087 user: core
2088 <source>
2089 *: "Selection too big, %d random tracks will be selected"
2090 </source>
2091 <dest>
2092 *: "Selection too big, %d random tracks will be selected"
2093 </dest>
2094 <voice>
2095 *: "Selection too big, fewer random tracks will be selected"
2096 </voice>
2097</phrase>
2098<phrase>
2057 id: LANG_TAGCACHE_RAM 2099 id: LANG_TAGCACHE_RAM
2058 desc: in tag cache settings 2100 desc: in tag cache settings
2059 user: core 2101 user: core
@@ -2198,13 +2240,13 @@
2198</phrase> 2240</phrase>
2199<phrase> 2241<phrase>
2200 id: LANG_TAGNAVI_ALL_TRACKS 2242 id: LANG_TAGNAVI_ALL_TRACKS
2201 desc: "<All tracks>" entry in tag browser 2243 desc: "[All tracks]" entry in tag browser
2202 user: core 2244 user: core
2203 <source> 2245 <source>
2204 *: "<All tracks>" 2246 *: "[All tracks]"
2205 </source> 2247 </source>
2206 <dest> 2248 <dest>
2207 *: "<All tracks>" 2249 *: "[All tracks]"
2208 </dest> 2250 </dest>
2209 <voice> 2251 <voice>
2210 *: "All tracks" 2252 *: "All tracks"
@@ -6645,10 +6687,10 @@
6645 desc: in tag viewer 6687 desc: in tag viewer
6646 user: core 6688 user: core
6647 <source> 6689 <source>
6648 *: "<No Info>" 6690 *: "[No Info]"
6649 </source> 6691 </source>
6650 <dest> 6692 <dest>
6651 *: "<No Info>" 6693 *: "[No Info]"
6652 </dest> 6694 </dest>
6653 <voice> 6695 <voice>
6654 *: "No Info" 6696 *: "No Info"
@@ -9307,13 +9349,13 @@
9307</phrase> 9349</phrase>
9308<phrase> 9350<phrase>
9309 id: LANG_TAGNAVI_RANDOM 9351 id: LANG_TAGNAVI_RANDOM
9310 desc: "<Random>" entry in tag browser 9352 desc: "[Random]" entry in tag browser
9311 user: core 9353 user: core
9312 <source> 9354 <source>
9313 *: "<Random>" 9355 *: "[Random]"
9314 </source> 9356 </source>
9315 <dest> 9357 <dest>
9316 *: "<Random>" 9358 *: "[Random]"
9317 </dest> 9359 </dest>
9318 <voice> 9360 <voice>
9319 *: "Random" 9361 *: "Random"
@@ -11157,13 +11199,13 @@
11157</phrase> 11199</phrase>
11158<phrase> 11200<phrase>
11159 id: LANG_TAGNAVI_UNTAGGED 11201 id: LANG_TAGNAVI_UNTAGGED
11160 desc: "<untagged>" entry in tag browser 11202 desc: "[untagged]" entry in tag browser
11161 user: core 11203 user: core
11162 <source> 11204 <source>
11163 *: "<Untagged>" 11205 *: "[Untagged]"
11164 </source> 11206 </source>
11165 <dest> 11207 <dest>
11166 *: "<Untagged>" 11208 *: "[Untagged]"
11167 </dest> 11209 </dest>
11168 <voice> 11210 <voice>
11169 *: "Untagged" 11211 *: "Untagged"
@@ -16498,6 +16540,20 @@
16498 </voice> 16540 </voice>
16499</phrase> 16541</phrase>
16500<phrase> 16542<phrase>
16543 id: LANG_DISABLE_MAINMENU_SCROLLING
16544 desc: Disable main menu scrolling
16545 user: core
16546 <source>
16547 *: "Disable main menu scrolling"
16548 </source>
16549 <dest>
16550 *: "Disable main menu scrolling"
16551 </dest>
16552 <voice>
16553 *: "Disable main menu scrolling"
16554 </voice>
16555</phrase>
16556<phrase>
16501 id: LANG_REMAINING 16557 id: LANG_REMAINING
16502 desc: Playing Time 16558 desc: Playing Time
16503 user: core 16559 user: core
diff --git a/apps/lang/espanol.lang b/apps/lang/espanol.lang
index 9863817a52..dd5b1dbe3f 100644
--- a/apps/lang/espanol.lang
+++ b/apps/lang/espanol.lang
@@ -3188,10 +3188,10 @@
3188 desc: in tag viewer 3188 desc: in tag viewer
3189 user: core 3189 user: core
3190 <source> 3190 <source>
3191 *: "<No Info>" 3191 *: "[No Info]"
3192 </source> 3192 </source>
3193 <dest> 3193 <dest>
3194 *: "<Sin información>" 3194 *: "[Sin información]"
3195 </dest> 3195 </dest>
3196 <voice> 3196 <voice>
3197 *: "Sin información" 3197 *: "Sin información"
@@ -5681,13 +5681,13 @@
5681</phrase> 5681</phrase>
5682<phrase> 5682<phrase>
5683 id: LANG_TAGNAVI_ALL_TRACKS 5683 id: LANG_TAGNAVI_ALL_TRACKS
5684 desc: "<All tracks>" entry in tag browser 5684 desc: "[All tracks]" entry in tag browser
5685 user: core 5685 user: core
5686 <source> 5686 <source>
5687 *: "<All tracks>" 5687 *: "[All tracks]"
5688 </source> 5688 </source>
5689 <dest> 5689 <dest>
5690 *: "<Todas las pistas>" 5690 *: "[Todas las pistas]"
5691 </dest> 5691 </dest>
5692 <voice> 5692 <voice>
5693 *: "Todas las Pistas" 5693 *: "Todas las Pistas"
@@ -8935,13 +8935,13 @@
8935</phrase> 8935</phrase>
8936<phrase> 8936<phrase>
8937 id: LANG_TAGNAVI_RANDOM 8937 id: LANG_TAGNAVI_RANDOM
8938 desc: "<Random>" entry in tag browser 8938 desc: "[Random]" entry in tag browser
8939 user: core 8939 user: core
8940 <source> 8940 <source>
8941 *: "<Random>" 8941 *: "[Random]"
8942 </source> 8942 </source>
8943 <dest> 8943 <dest>
8944 *: "<Aleatorio>" 8944 *: "[Aleatorio]"
8945 </dest> 8945 </dest>
8946 <voice> 8946 <voice>
8947 *: "Aleatorio" 8947 *: "Aleatorio"
@@ -9647,10 +9647,10 @@
9647 desc: top item in the list when asking user about bookmark auto load 9647 desc: top item in the list when asking user about bookmark auto load
9648 user: core 9648 user: core
9649 <source> 9649 <source>
9650 *: "<Don't Resume>" 9650 *: "[Don't Resume]"
9651 </source> 9651 </source>
9652 <dest> 9652 <dest>
9653 *: "<No continuar>" 9653 *: "[No continuar]"
9654 </dest> 9654 </dest>
9655 <voice> 9655 <voice>
9656 *: "No continuar" 9656 *: "No continuar"
@@ -9852,10 +9852,10 @@
9852 desc: bookmark selection list, bookmark couldn't be parsed 9852 desc: bookmark selection list, bookmark couldn't be parsed
9853 user: core 9853 user: core
9854 <source> 9854 <source>
9855 *: "<Invalid Bookmark>" 9855 *: "[Invalid Bookmark]"
9856 </source> 9856 </source>
9857 <dest> 9857 <dest>
9858 *: "<Marcador incorrecto>" 9858 *: "[Marcador incorrecto]"
9859 </dest> 9859 </dest>
9860 <voice> 9860 <voice>
9861 *: "Marcador incorrecto" 9861 *: "Marcador incorrecto"
@@ -11864,13 +11864,13 @@
11864</phrase> 11864</phrase>
11865<phrase> 11865<phrase>
11866 id: LANG_TAGNAVI_UNTAGGED 11866 id: LANG_TAGNAVI_UNTAGGED
11867 desc: "<untagged>" entry in tag browser 11867 desc: "[untagged]" entry in tag browser
11868 user: core 11868 user: core
11869 <source> 11869 <source>
11870 *: "<Untagged>" 11870 *: "[Untagged]"
11871 </source> 11871 </source>
11872 <dest> 11872 <dest>
11873 *: "<Sin etiqueta>" 11873 *: "[Sin etiqueta]"
11874 </dest> 11874 </dest>
11875 <voice> 11875 <voice>
11876 *: "Sin etiqueta" 11876 *: "Sin etiqueta"
diff --git a/apps/lang/esperanto.lang b/apps/lang/esperanto.lang
index ac77eca660..9626add951 100644
--- a/apps/lang/esperanto.lang
+++ b/apps/lang/esperanto.lang
@@ -4729,10 +4729,10 @@
4729 desc: in tag viewer 4729 desc: in tag viewer
4730 user: core 4730 user: core
4731 <source> 4731 <source>
4732 *: "<No Info>" 4732 *: "[No Info]"
4733 </source> 4733 </source>
4734 <dest> 4734 <dest>
4735 *: "<Sen Informoj>" 4735 *: "[Sen Informoj]"
4736 </dest> 4736 </dest>
4737 <voice> 4737 <voice>
4738 *: "Sen Informoj" 4738 *: "Sen Informoj"
@@ -7234,13 +7234,13 @@
7234</phrase> 7234</phrase>
7235<phrase> 7235<phrase>
7236 id: LANG_TAGNAVI_ALL_TRACKS 7236 id: LANG_TAGNAVI_ALL_TRACKS
7237 desc: "<All tracks>" entry in tag browser 7237 desc: "[All tracks]" entry in tag browser
7238 user: core 7238 user: core
7239 <source> 7239 <source>
7240 *: "<All tracks>" 7240 *: "[All tracks]"
7241 </source> 7241 </source>
7242 <dest> 7242 <dest>
7243 *: "<Ĉiuj kantoj>" 7243 *: "[Ĉiuj kantoj]"
7244 </dest> 7244 </dest>
7245 <voice> 7245 <voice>
7246 *: "Ĉiuj kantoj" 7246 *: "Ĉiuj kantoj"
diff --git a/apps/lang/finnish.lang b/apps/lang/finnish.lang
index 9ec74591f8..094d122da0 100644
--- a/apps/lang/finnish.lang
+++ b/apps/lang/finnish.lang
@@ -4851,10 +4851,10 @@
4851 desc: in tag viewer 4851 desc: in tag viewer
4852 user: core 4852 user: core
4853 <source> 4853 <source>
4854 *: "<No Info>" 4854 *: "[No Info]"
4855 </source> 4855 </source>
4856 <dest> 4856 <dest>
4857 *: "<Ei tietoja>" 4857 *: "[Ei tietoja]"
4858 </dest> 4858 </dest>
4859 <voice> 4859 <voice>
4860 *: "Ei tietoja" 4860 *: "Ei tietoja"
@@ -7390,13 +7390,13 @@
7390</phrase> 7390</phrase>
7391<phrase> 7391<phrase>
7392 id: LANG_TAGNAVI_ALL_TRACKS 7392 id: LANG_TAGNAVI_ALL_TRACKS
7393 desc: "<All tracks>" entry in tag browser 7393 desc: "[All tracks]" entry in tag browser
7394 user: core 7394 user: core
7395 <source> 7395 <source>
7396 *: "<All tracks>" 7396 *: "[All tracks]"
7397 </source> 7397 </source>
7398 <dest> 7398 <dest>
7399 *: "<Kaikki kappaleet>" 7399 *: "[Kaikki kappaleet]"
7400 </dest> 7400 </dest>
7401 <voice> 7401 <voice>
7402 *: "" 7402 *: ""
@@ -8942,10 +8942,10 @@
8942 desc: top item in the list when asking user about bookmark auto load 8942 desc: top item in the list when asking user about bookmark auto load
8943 user: core 8943 user: core
8944 <source> 8944 <source>
8945 *: "<Don't Resume>" 8945 *: "[Don't Resume]"
8946 </source> 8946 </source>
8947 <dest> 8947 <dest>
8948 *: "<Älä jatka>" 8948 *: "[Älä jatka]"
8949 </dest> 8949 </dest>
8950 <voice> 8950 <voice>
8951 *: "Älä jatka" 8951 *: "Älä jatka"
@@ -8970,10 +8970,10 @@
8970 desc: bookmark selection list, bookmark couldn't be parsed 8970 desc: bookmark selection list, bookmark couldn't be parsed
8971 user: core 8971 user: core
8972 <source> 8972 <source>
8973 *: "<Invalid Bookmark>" 8973 *: "[Invalid Bookmark]"
8974 </source> 8974 </source>
8975 <dest> 8975 <dest>
8976 *: "<Kirjanmerkki ei kelpaa>" 8976 *: "[Kirjanmerkki ei kelpaa]"
8977 </dest> 8977 </dest>
8978 <voice> 8978 <voice>
8979 *: "Kirjanmerkki ei kelpaa" 8979 *: "Kirjanmerkki ei kelpaa"
@@ -10157,13 +10157,13 @@
10157</phrase> 10157</phrase>
10158<phrase> 10158<phrase>
10159 id: LANG_TAGNAVI_RANDOM 10159 id: LANG_TAGNAVI_RANDOM
10160 desc: "<Random>" entry in tag browser 10160 desc: "[Random]" entry in tag browser
10161 user: core 10161 user: core
10162 <source> 10162 <source>
10163 *: "<Random>" 10163 *: "[Random]"
10164 </source> 10164 </source>
10165 <dest> 10165 <dest>
10166 *: "<Satunnainen>" 10166 *: "[Satunnainen]"
10167 </dest> 10167 </dest>
10168 <voice> 10168 <voice>
10169 *: "Satunnainen" 10169 *: "Satunnainen"
@@ -11910,13 +11910,13 @@
11910</phrase> 11910</phrase>
11911<phrase> 11911<phrase>
11912 id: LANG_TAGNAVI_UNTAGGED 11912 id: LANG_TAGNAVI_UNTAGGED
11913 desc: "<untagged>" entry in tag browser 11913 desc: "[untagged]" entry in tag browser
11914 user: core 11914 user: core
11915 <source> 11915 <source>
11916 *: "<Untagged>" 11916 *: "[Untagged]"
11917 </source> 11917 </source>
11918 <dest> 11918 <dest>
11919 *: "<Tunnisteeton>" 11919 *: "[Tunnisteeton]"
11920 </dest> 11920 </dest>
11921 <voice> 11921 <voice>
11922 *: "Tunnisteeton" 11922 *: "Tunnisteeton"
diff --git a/apps/lang/francais.lang b/apps/lang/francais.lang
index 55b7ae9f63..0718873a56 100644
--- a/apps/lang/francais.lang
+++ b/apps/lang/francais.lang
@@ -32,6 +32,7 @@
32# - Clément Pit-Claudel 32# - Clément Pit-Claudel
33# - Michaël Burtin 33# - Michaël Burtin
34# - Olivier Kaloudoff 34# - Olivier Kaloudoff
35# - Jaussoin Timothée
35# 36#
36# Original comments below: 37# Original comments below:
37# 38#
@@ -182,7 +183,7 @@
182 *: "Chargement... %d%% fait (%s)" 183 *: "Chargement... %d%% fait (%s)"
183 </dest> 184 </dest>
184 <voice> 185 <voice>
185 *: "" 186 *: "Chargement... %d%% fait (%s)"
186 </voice> 187 </voice>
187</phrase> 188</phrase>
188<phrase> 189<phrase>
@@ -276,18 +277,15 @@
276 <source> 277 <source>
277 *: "PLAY = Yes" 278 *: "PLAY = Yes"
278 cowond2*: "MENU, or top-right = Yes" 279 cowond2*: "MENU, or top-right = Yes"
279 creativezen*: "SELECT = Yes" 280 creativezen*,gigabeat*,iaudiom5,iaudiox5,ipod*,iriverh10,iriverh10_5gb,mrobe100,sansac200*,sansaclip*,sansaconnect,sansae200*,sansafuze*: "SELECT = Yes"
280 gigabeat*,iaudiom5,iaudiox5,ipod*,iriverh10,iriverh10_5gb,mrobe100,sansac200*,sansaclip*,sansaconnect,sansae200*,sansafuze*: "SELECT = Yes"
281 iriverh100,iriverh120,iriverh300: "NAVI = Yes" 281 iriverh100,iriverh120,iriverh300: "NAVI = Yes"
282 mrobe500: "PLAY, POWER, or top-right = Yes" 282 mrobe500: "PLAY, POWER, or top-right = Yes"
283 vibe500: "OK = Yes" 283 vibe500: "OK = Yes"
284 </source> 284 </source>
285 <dest> 285 <dest>
286 *: "PLAY = Oui" 286 *: "PLAY = Oui"
287 archosplayer: "(PLAY/STOP)"
288 cowond2*: "MENU ou en haut à gauche = Oui" 287 cowond2*: "MENU ou en haut à gauche = Oui"
289 creativezen*: "SELECT = Oui" 288 creativezen*,gigabeat*,iaudiom5,iaudiox5,ipod*,iriverh10,iriverh10_5gb,mrobe100,sansac200*,sansaclip*,sansaconnect,sansae200*,sansafuze*: "SELECT = Oui"
290 gigabeat*,iaudiom5,iaudiox5,ipod*,iriverh10,iriverh10_5gb,mrobe100,sansac200*,sansaclip*,sansaconnect,sansae200*,sansafuze*: "SELECT = Oui"
291 iriverh100,iriverh120,iriverh300: "NAVI = Oui" 289 iriverh100,iriverh120,iriverh300: "NAVI = Oui"
292 mrobe500: "PLAY, POWER, ou en haut à gauche = Oui" 290 mrobe500: "PLAY, POWER, ou en haut à gauche = Oui"
293 vibe500: "OK = Oui" 291 vibe500: "OK = Oui"
@@ -305,11 +303,9 @@
305 </source> 303 </source>
306 <dest> 304 <dest>
307 *: "Autres = Non" 305 *: "Autres = Non"
308 archosplayer: none
309 </dest> 306 </dest>
310 <voice> 307 <voice>
311 *: "" 308 *: ""
312 archosplayer: none
313 </voice> 309 </voice>
314</phrase> 310</phrase>
315<phrase> 311<phrase>
@@ -505,10 +501,10 @@
505 desc: top item in the list when asking user about bookmark auto load 501 desc: top item in the list when asking user about bookmark auto load
506 user: core 502 user: core
507 <source> 503 <source>
508 *: "<Don't Resume>" 504 *: "[Don't Resume]"
509 </source> 505 </source>
510 <dest> 506 <dest>
511 *: "<Ne pas reprendre>" 507 *: "[Ne pas reprendre]"
512 </dest> 508 </dest>
513 <voice> 509 <voice>
514 *: "Ne pas reprendre" 510 *: "Ne pas reprendre"
@@ -533,10 +529,10 @@
533 desc: bookmark selection list, bookmark couldn't be parsed 529 desc: bookmark selection list, bookmark couldn't be parsed
534 user: core 530 user: core
535 <source> 531 <source>
536 *: "<Invalid Bookmark>" 532 *: "[Invalid Bookmark]"
537 </source> 533 </source>
538 <dest> 534 <dest>
539 *: "<Signet invalide>" 535 *: "[Signet invalide]"
540 </dest> 536 </dest>
541 <voice> 537 <voice>
542 *: "Signet invalide" 538 *: "Signet invalide"
@@ -571,20 +567,6 @@
571 </voice> 567 </voice>
572</phrase> 568</phrase>
573<phrase> 569<phrase>
574 id: LANG_BOOKMARK_CONTEXT_DELETE
575 desc: bookmark context menu, delete this bookmark
576 user: core
577 <source>
578 *: "Delete"
579 </source>
580 <dest>
581 *: "Supprimer"
582 </dest>
583 <voice>
584 *: "Supprimer"
585 </voice>
586</phrase>
587<phrase>
588 id: LANG_AUTO_BOOKMARK_QUERY 570 id: LANG_AUTO_BOOKMARK_QUERY
589 desc: prompt for user to decide to create an bookmark 571 desc: prompt for user to decide to create an bookmark
590 user: core 572 user: core
@@ -726,7 +708,7 @@
726</phrase> 708</phrase>
727<phrase> 709<phrase>
728 id: LANG_CHANNEL_STEREO 710 id: LANG_CHANNEL_STEREO
729 desc: in sound_settings 711 desc: in sound_settings and radio screen
730 user: core 712 user: core
731 <source> 713 <source>
732 *: "Stereo" 714 *: "Stereo"
@@ -740,7 +722,7 @@
740</phrase> 722</phrase>
741<phrase> 723<phrase>
742 id: LANG_CHANNEL_MONO 724 id: LANG_CHANNEL_MONO
743 desc: in sound_settings 725 desc: in sound_settings and radio screen
744 user: core 726 user: core
745 <source> 727 <source>
746 *: "Mono" 728 *: "Mono"
@@ -1022,20 +1004,6 @@
1022 </voice> 1004 </voice>
1023</phrase> 1005</phrase>
1024<phrase> 1006<phrase>
1025 id: LANG_EQUALIZER_EDIT_MODE
1026 desc: in the equalizer settings menu
1027 user: core
1028 <source>
1029 *: "Edit mode: %s"
1030 </source>
1031 <dest>
1032 *: "Mode édition : %s"
1033 </dest>
1034 <voice>
1035 *: ""
1036 </voice>
1037</phrase>
1038<phrase>
1039 id: LANG_EQUALIZER_GAIN_ITEM 1007 id: LANG_EQUALIZER_GAIN_ITEM
1040 desc: in the equalizer settings menu 1008 desc: in the equalizer settings menu
1041 user: core 1009 user: core
@@ -1527,7 +1495,7 @@
1527 *: "ReplayGain" 1495 *: "ReplayGain"
1528 </dest> 1496 </dest>
1529 <voice> 1497 <voice>
1530 *: "Replaygain" 1498 *: "ReplayGain"
1531 </voice> 1499 </voice>
1532</phrase> 1500</phrase>
1533<phrase> 1501<phrase>
@@ -1717,16 +1685,16 @@
1717</phrase> 1685</phrase>
1718<phrase> 1686<phrase>
1719 id: LANG_AUDIOSCROBBLER 1687 id: LANG_AUDIOSCROBBLER
1720 desc: "Last.fm Log" in the playback menu 1688 desc: "Last.fm Logger" in Plugin/apps/scrobbler
1721 user: core 1689 user: core
1722 <source> 1690 <source>
1723 *: "Last.fm Log" 1691 *: "Last.fm Logger"
1724 </source> 1692 </source>
1725 <dest> 1693 <dest>
1726 *: "Log Last.fm" 1694 *: "Journaux Last.fm"
1727 </dest> 1695 </dest>
1728 <voice> 1696 <voice>
1729 *: "Log Last.fm" 1697 *: "Journaux Last.fm"
1730 </voice> 1698 </voice>
1731</phrase> 1699</phrase>
1732<phrase> 1700<phrase>
@@ -2005,6 +1973,34 @@
2005 </voice> 1973 </voice>
2006</phrase> 1974</phrase>
2007<phrase> 1975<phrase>
1976 id: LANG_DISPLAY_TITLEALBUM_FROMTAGS
1977 desc: track display options
1978 user: core
1979 <source>
1980 *: "Title & Album from ID3 tags"
1981 </source>
1982 <dest>
1983 *: "Titre & Album depuis les tags ID3"
1984 </dest>
1985 <voice>
1986 *: "Titre et Album depuis les tags"
1987 </voice>
1988</phrase>
1989<phrase>
1990 id: LANG_DISPLAY_TITLE_FROMTAGS
1991 desc: track display options
1992 user: core
1993 <source>
1994 *: "Title from ID3 tags"
1995 </source>
1996 <dest>
1997 *: "Titre depuis les tags ID3"
1998 </dest>
1999 <voice>
2000 *: "Titre depuis les tags"
2001 </voice>
2002</phrase>
2003<phrase>
2008 id: LANG_BUILDING_DATABASE 2004 id: LANG_BUILDING_DATABASE
2009 desc: splash database building progress 2005 desc: splash database building progress
2010 user: core 2006 user: core
@@ -2017,7 +2013,6 @@
2017 </source> 2013 </source>
2018 <dest> 2014 <dest>
2019 *: "Création base de données... %d trouvés (OFF = retour)" 2015 *: "Création base de données... %d trouvés (OFF = retour)"
2020 archosplayer: "Création BD %d trouvés"
2021 gigabeat*,iaudiom5,iaudiox5,mrobe100,samsungyh*: "Création base de données... %d trouvés (LEFT = retour)" 2016 gigabeat*,iaudiom5,iaudiox5,mrobe100,samsungyh*: "Création base de données... %d trouvés (LEFT = retour)"
2022 gogearsa9200: "Création base de données... %d trouvés (REW = retour)" 2017 gogearsa9200: "Création base de données... %d trouvés (REW = retour)"
2023 ipod*,iriverh10,iriverh10_5gb,sansac200*,sansae200*,sansafuze*,vibe500: "Création base de données... %d trouvés (PREV = retour)" 2018 ipod*,iriverh10,iriverh10_5gb,sansac200*,sansae200*,sansafuze*,vibe500: "Création base de données... %d trouvés (PREV = retour)"
@@ -2028,6 +2023,20 @@
2028 </voice> 2023 </voice>
2029</phrase> 2024</phrase>
2030<phrase> 2025<phrase>
2026 id: LANG_RANDOM_SHUFFLE_RANDOM_SELECTIVE_SONGS_SUMMARY
2027 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
2028 user: core
2029 <source>
2030 *: "Selection too big, %d random tracks will be selected"
2031 </source>
2032 <dest>
2033 *: "Selection trop grande, %d pistes seront sélectionnées aléatoirement"
2034 </dest>
2035 <voice>
2036 *: "Selection trop grande, donc des pistes seront sélectionnées aléatoirement"
2037 </voice>
2038</phrase>
2039<phrase>
2031 id: LANG_TAGCACHE_RAM 2040 id: LANG_TAGCACHE_RAM
2032 desc: in tag cache settings 2041 desc: in tag cache settings
2033 user: core 2042 user: core
@@ -2172,13 +2181,13 @@
2172</phrase> 2181</phrase>
2173<phrase> 2182<phrase>
2174 id: LANG_TAGNAVI_ALL_TRACKS 2183 id: LANG_TAGNAVI_ALL_TRACKS
2175 desc: "<All tracks>" entry in tag browser 2184 desc: "[All tracks]" entry in tag browser
2176 user: core 2185 user: core
2177 <source> 2186 <source>
2178 *: "<All tracks>" 2187 *: "[All tracks]"
2179 </source> 2188 </source>
2180 <dest> 2189 <dest>
2181 *: "<Toutes les pistes>" 2190 *: "[Toutes les pistes]"
2182 </dest> 2191 </dest>
2183 <voice> 2192 <voice>
2184 *: "Toutes les pistes" 2193 *: "Toutes les pistes"
@@ -2293,15 +2302,15 @@
2293 desc: in lcd settings 2302 desc: in lcd settings
2294 user: core 2303 user: core
2295 <source> 2304 <source>
2296 *: none 2305 *: "Backlight on Lock"
2297 hold_button: "Backlight on Hold" 2306 hold_button: "Backlight on Hold"
2298 </source> 2307 </source>
2299 <dest> 2308 <dest>
2300 *: none 2309 *: "Backlight on Lock"
2301 hold_button: "Rétro-éclairage quand touches verrouillées" 2310 hold_button: "Rétro-éclairage quand touches verrouillées"
2302 </dest> 2311 </dest>
2303 <voice> 2312 <voice>
2304 *: none 2313 *: "Backlight on Lock"
2305 hold_button: "Rétro-éclairage quand touches verrouillées" 2314 hold_button: "Rétro-éclairage quand touches verrouillées"
2306 </voice> 2315 </voice>
2307</phrase> 2316</phrase>
@@ -2970,11 +2979,9 @@
2970 </source> 2979 </source>
2971 <dest> 2980 <dest>
2972 *: "Indicateur de niveau des pics" 2981 *: "Indicateur de niveau des pics"
2973 masd: none
2974 </dest> 2982 </dest>
2975 <voice> 2983 <voice>
2976 *: "Indicateur de niveau des pics" 2984 *: "Indicateur de niveau des pics"
2977 masd: none
2978 </voice> 2985 </voice>
2979</phrase> 2986</phrase>
2980<phrase> 2987<phrase>
@@ -2986,11 +2993,9 @@
2986 </source> 2993 </source>
2987 <dest> 2994 <dest>
2988 *: "Persistance marqueur seuil max." 2995 *: "Persistance marqueur seuil max."
2989 masd: none
2990 </dest> 2996 </dest>
2991 <voice> 2997 <voice>
2992 *: "Persistance marqueur seuil max." 2998 *: "Persistance marqueur seuil max."
2993 masd: none
2994 </voice> 2999 </voice>
2995</phrase> 3000</phrase>
2996<phrase> 3001<phrase>
@@ -3002,11 +3007,9 @@
3002 </source> 3007 </source>
3003 <dest> 3008 <dest>
3004 *: "Temps de persistance du pic" 3009 *: "Temps de persistance du pic"
3005 masd: none
3006 </dest> 3010 </dest>
3007 <voice> 3011 <voice>
3008 *: "Temps de persistance du pic" 3012 *: "Temps de persistance du pic"
3009 masd: none
3010 </voice> 3013 </voice>
3011</phrase> 3014</phrase>
3012<phrase> 3015<phrase>
@@ -3018,11 +3021,9 @@
3018 </source> 3021 </source>
3019 <dest> 3022 <dest>
3020 *: "Infini" 3023 *: "Infini"
3021 masd: none
3022 </dest> 3024 </dest>
3023 <voice> 3025 <voice>
3024 *: "Infini" 3026 *: "Infini"
3025 masd: none
3026 </voice> 3027 </voice>
3027</phrase> 3028</phrase>
3028<phrase> 3029<phrase>
@@ -3034,11 +3035,9 @@
3034 </source> 3035 </source>
3035 <dest> 3036 <dest>
3036 *: "Taux de baisse de l'indicateur" 3037 *: "Taux de baisse de l'indicateur"
3037 masd: none
3038 </dest> 3038 </dest>
3039 <voice> 3039 <voice>
3040 *: "Taux de baisse de l'indicateur" 3040 *: "Taux de baisse de l'indicateur"
3041 masd: none
3042 </voice> 3041 </voice>
3043</phrase> 3042</phrase>
3044<phrase> 3043<phrase>
@@ -3050,11 +3049,9 @@
3050 </source> 3049 </source>
3051 <dest> 3050 <dest>
3052 *: "Echelle" 3051 *: "Echelle"
3053 masd: none
3054 </dest> 3052 </dest>
3055 <voice> 3053 <voice>
3056 *: "échelle" 3054 *: "échelle"
3057 masd: none
3058 </voice> 3055 </voice>
3059</phrase> 3056</phrase>
3060<phrase> 3057<phrase>
@@ -3066,11 +3063,9 @@
3066 </source> 3063 </source>
3067 <dest> 3064 <dest>
3068 *: "Logarithmique (dB)" 3065 *: "Logarithmique (dB)"
3069 masd: none
3070 </dest> 3066 </dest>
3071 <voice> 3067 <voice>
3072 *: "Logarithmique en décibel" 3068 *: "Logarithmique en décibel"
3073 masd: none
3074 </voice> 3069 </voice>
3075</phrase> 3070</phrase>
3076<phrase> 3071<phrase>
@@ -3082,11 +3077,9 @@
3082 </source> 3077 </source>
3083 <dest> 3078 <dest>
3084 *: "Linéaire (%)" 3079 *: "Linéaire (%)"
3085 masd: none
3086 </dest> 3080 </dest>
3087 <voice> 3081 <voice>
3088 *: "Linéaire en pourcentage" 3082 *: "Linéaire en pourcentage"
3089 masd: none
3090 </voice> 3083 </voice>
3091</phrase> 3084</phrase>
3092<phrase> 3085<phrase>
@@ -3098,11 +3091,9 @@
3098 </source> 3091 </source>
3099 <dest> 3092 <dest>
3100 *: "Minimum de l'intervalle" 3093 *: "Minimum de l'intervalle"
3101 masd: none
3102 </dest> 3094 </dest>
3103 <voice> 3095 <voice>
3104 *: "Minimum de l'intervalle" 3096 *: "Minimum de l'intervalle"
3105 masd: none
3106 </voice> 3097 </voice>
3107</phrase> 3098</phrase>
3108<phrase> 3099<phrase>
@@ -3114,11 +3105,9 @@
3114 </source> 3105 </source>
3115 <dest> 3106 <dest>
3116 *: "Maximum de l'intervalle" 3107 *: "Maximum de l'intervalle"
3117 masd: none
3118 </dest> 3108 </dest>
3119 <voice> 3109 <voice>
3120 *: "Maximum de l'intervalle" 3110 *: "Maximum de l'intervalle"
3121 masd: none
3122 </voice> 3111 </voice>
3123</phrase> 3112</phrase>
3124<phrase> 3113<phrase>
@@ -3448,14 +3437,17 @@
3448 <source> 3437 <source>
3449 *: none 3438 *: none
3450 battery_types: "Alkaline" 3439 battery_types: "Alkaline"
3440 xduoox3: "Newer (2000 mAh)"
3451 </source> 3441 </source>
3452 <dest> 3442 <dest>
3453 *: none 3443 *: none
3454 battery_types: "Alcaline" 3444 battery_types: "Alcaline"
3445 xduoox3: "Nouveau (2000 mAh)"
3455 </dest> 3446 </dest>
3456 <voice> 3447 <voice>
3457 *: none 3448 *: none
3458 battery_types: "Alcaline" 3449 battery_types: "Alcaline"
3450 xduoox3: "Nouveau 2000 milliampère heure"
3459 </voice> 3451 </voice>
3460</phrase> 3452</phrase>
3461<phrase> 3453<phrase>
@@ -3465,14 +3457,17 @@
3465 <source> 3457 <source>
3466 *: none 3458 *: none
3467 battery_types: "NiMH" 3459 battery_types: "NiMH"
3460 xduoox3: "Older (1500 mAh)"
3468 </source> 3461 </source>
3469 <dest> 3462 <dest>
3470 *: none 3463 *: none
3471 battery_types: "NiMH" 3464 battery_types: "NiMH"
3465 xduoox3: "Nouveau (1500 mAh)"
3472 </dest> 3466 </dest>
3473 <voice> 3467 <voice>
3474 *: none 3468 *: none
3475 battery_types: "Nickel metal hydride" 3469 battery_types: "Nickel metal hydride"
3470 xduoox3: "Nouveau 1500 milliampère heure"
3476 </voice> 3471 </voice>
3477</phrase> 3472</phrase>
3478<phrase> 3473<phrase>
@@ -3662,7 +3657,7 @@
3662 </dest> 3657 </dest>
3663 <voice> 3658 <voice>
3664 *: none 3659 *: none
3665 gigabeat*,gogearsa9200,iaudiom5,iaudiox5,ipod*,iriverh10,iriverh100,iriverh10_5gb,iriverh120,iriverh300,mrobe100,rtc,sansac200*,sansae200*: "" 3660 gigabeat*,gogearsa9200,iaudiom5,iaudiox5,ipod*,iriverh10,iriverh100,iriverh10_5gb,iriverh120,iriverh300,mrobe100,rtc,samsungyh*,sansac200*,sansae200*: ""
3666 </voice> 3661 </voice>
3667</phrase> 3662</phrase>
3668<phrase> 3663<phrase>
@@ -4108,23 +4103,6 @@
4108 </voice> 4103 </voice>
4109</phrase> 4104</phrase>
4110<phrase> 4105<phrase>
4111 id: LANG_ALARM_MOD_SHUTDOWN
4112 desc: The text that tells the user that the alarm time is ok and the device shuts off (for the RTC alarm mod).
4113 user: core
4114 <source>
4115 *: none
4116 alarm: "Alarm Set"
4117 </source>
4118 <dest>
4119 *: none
4120 alarm: "Réveil activé"
4121 </dest>
4122 <voice>
4123 *: none
4124 alarm: "Réveil activé"
4125 </voice>
4126</phrase>
4127<phrase>
4128 id: LANG_ALARM_MOD_ERROR 4106 id: LANG_ALARM_MOD_ERROR
4129 desc: The text that tells that the time is incorrect (for the RTC alarm mod). 4107 desc: The text that tells that the time is incorrect (for the RTC alarm mod).
4130 user: core 4108 user: core
@@ -4142,35 +4120,6 @@
4142 </voice> 4120 </voice>
4143</phrase> 4121</phrase>
4144<phrase> 4122<phrase>
4145 id: LANG_ALARM_MOD_KEYS
4146 desc: Shown key functions in alarm menu (for the RTC alarm mod).
4147 user: core
4148 <source>
4149 *: none
4150 alarm: "PLAY=Set OFF=Cancel"
4151 gigabeats: "SELECT=Set POWER=Cancel"
4152 ipod*: "SELECT=Set MENU=Cancel"
4153 iriverh10,iriverh10_5gb: "SELECT=Set PREV=Cancel"
4154 mpiohd300: "ENTER=Set MENU=Cancel"
4155 sansafuzeplus: "SELECT=Set BACK=Cancel"
4156 vibe500: "OK=Set C=Cancel"
4157 </source>
4158 <dest>
4159 *: none
4160 alarm: "PLAY=Valider OFF=Annuler"
4161 gigabeats: "SELECT=Valider POWER=Annuler"
4162 ipod*: "SELECT=Valider MENU=Annuler"
4163 iriverh10,iriverh10_5gb: "SELECT=Valider PREV=Annuler"
4164 mpiohd300: "ENTER=Valider MENU=Annuler"
4165 sansafuzeplus: "SELECT=Valider BACK=Annuler"
4166 vibe500: "OK=Valider C=Annuler"
4167 </dest>
4168 <voice>
4169 *: none
4170 alarm,ipod*: ""
4171 </voice>
4172</phrase>
4173<phrase>
4174 id: LANG_ALARM_MOD_DISABLE 4123 id: LANG_ALARM_MOD_DISABLE
4175 desc: Announce that the RTC alarm has been turned off 4124 desc: Announce that the RTC alarm has been turned off
4176 user: core 4125 user: core
@@ -5366,40 +5315,6 @@
5366 </voice> 5315 </voice>
5367</phrase> 5316</phrase>
5368<phrase> 5317<phrase>
5369 id: LANG_RECORD_DIRECTORY
5370 desc: in recording settings_menu
5371 user: core
5372 <source>
5373 *: none
5374 recording: "Directory"
5375 </source>
5376 <dest>
5377 *: none
5378 recording: "Répertoire"
5379 </dest>
5380 <voice>
5381 *: none
5382 recording: "Répertoire"
5383 </voice>
5384</phrase>
5385<phrase>
5386 id: LANG_SET_AS_REC_DIR
5387 desc: used in the onplay menu to set a recording dir
5388 user: core
5389 <source>
5390 *: none
5391 recording: "Set As Recording Directory"
5392 </source>
5393 <dest>
5394 *: none
5395 recording: "Choisir comme dossier d'enreg."
5396 </dest>
5397 <voice>
5398 *: none
5399 recording: "Choisir comme dossier d'enregistrement"
5400 </voice>
5401</phrase>
5402<phrase>
5403 id: LANG_CLEAR_REC_DIR 5318 id: LANG_CLEAR_REC_DIR
5404 desc: 5319 desc:
5405 user: core 5320 user: core
@@ -6023,7 +5938,6 @@
6023 </source> 5938 </source>
6024 <dest> 5939 <dest>
6025 *: "Tampon:" 5940 *: "Tampon:"
6026 archosplayer: "Tamp.:"
6027 </dest> 5941 </dest>
6028 <voice> 5942 <voice>
6029 *: "Tampon" 5943 *: "Tampon"
@@ -6036,12 +5950,10 @@
6036 <source> 5950 <source>
6037 *: "Battery: %d%% %dh %dm" 5951 *: "Battery: %d%% %dh %dm"
6038 ipodmini1g,ipodmini2g,iriverh10: "Batt: %d%% %dh %dm" 5952 ipodmini1g,ipodmini2g,iriverh10: "Batt: %d%% %dh %dm"
6039 iriverifp7xx: "%d%% %dh %dm"
6040 </source> 5953 </source>
6041 <dest> 5954 <dest>
6042 *: "Batterie: %d%% %dh %dm" 5955 *: "Batterie: %d%% %dh %dm"
6043 ipodmini1g,ipodmini2g,iriverh10: "Batt: %d%% %dh %dm" 5956 ipodmini1g,ipodmini2g,iriverh10: "Batt: %d%% %dh %dm"
6044 iriverifp7xx: "%d%% %dh %dm"
6045 </dest> 5957 </dest>
6046 <voice> 5958 <voice>
6047 *: "Niveau de la batterie" 5959 *: "Niveau de la batterie"
@@ -6081,14 +5993,17 @@
6081 user: core 5993 user: core
6082 <source> 5994 <source>
6083 *: "Int:" 5995 *: "Int:"
5996 hibylinux: "mSD:"
6084 xduoox3: "mSD1:" 5997 xduoox3: "mSD1:"
6085 </source> 5998 </source>
6086 <dest> 5999 <dest>
6087 *: "Int:" 6000 *: "Int:"
6001 hibylinux: "mSD:"
6088 xduoox3: "mSD1:" 6002 xduoox3: "mSD1:"
6089 </dest> 6003 </dest>
6090 <voice> 6004 <voice>
6091 *: "Interne" 6005 *: "Interne"
6006 hibylinux: "micro S D"
6092 xduoox3: "micro S D 1" 6007 xduoox3: "micro S D 1"
6093 </voice> 6008 </voice>
6094</phrase> 6009</phrase>
@@ -6098,20 +6013,21 @@
6098 user: core 6013 user: core
6099 <source> 6014 <source>
6100 *: none 6015 *: none
6101 multivolume: "HD1" 6016 hibylinux: "USB:"
6017 multivolume: "HD1:"
6102 sansac200*,sansaclipplus,sansae200*,sansafuze*: "mSD:" 6018 sansac200*,sansaclipplus,sansae200*,sansafuze*: "mSD:"
6103 xduoox3: "mSD2:" 6019 xduoox3: "mSD2:"
6104 </source> 6020 </source>
6105 <dest> 6021 <dest>
6106 *: none 6022 *: none
6107 archosondio*: "MMC:" 6023 hibylinux: "USB:"
6108 multivolume: "DD1" 6024 multivolume: "DD1"
6109 sansac200*,sansaclipplus,sansae200*,sansafuze*: "mSD:" 6025 sansac200*,sansaclipplus,sansae200*,sansafuze*: "mSD:"
6110 xduoox3: "mSD2:" 6026 xduoox3: "mSD2:"
6111 </dest> 6027 </dest>
6112 <voice> 6028 <voice>
6113 *: none 6029 *: none
6114 archosondio*: "M M C" 6030 hibylinux: "U S B"
6115 multivolume: "D D 1" 6031 multivolume: "D D 1"
6116 sansac200*,sansaclipplus,sansae200*,sansafuze*: "micro S D" 6032 sansac200*,sansaclipplus,sansae200*,sansafuze*: "micro S D"
6117 xduoox3: "micro S D 2" 6033 xduoox3: "micro S D 2"
@@ -6202,62 +6118,6 @@
6202 </voice> 6118 </voice>
6203</phrase> 6119</phrase>
6204<phrase> 6120<phrase>
6205 id: LANG_INSERT
6206 desc: in onplay menu. insert a track/playlist into dynamic playlist.
6207 user: core
6208 <source>
6209 *: "Insert"
6210 </source>
6211 <dest>
6212 *: "Insérer"
6213 </dest>
6214 <voice>
6215 *: "Insérer"
6216 </voice>
6217</phrase>
6218<phrase>
6219 id: LANG_INSERT_FIRST
6220 desc: in onplay menu. insert a track/playlist into dynamic playlist.
6221 user: core
6222 <source>
6223 *: "Insert Next"
6224 </source>
6225 <dest>
6226 *: "Insérer suivant"
6227 </dest>
6228 <voice>
6229 *: "Insérer suivant"
6230 </voice>
6231</phrase>
6232<phrase>
6233 id: LANG_INSERT_LAST
6234 desc: in onplay menu. append a track/playlist into dynamic playlist.
6235 user: core
6236 <source>
6237 *: "Insert Last"
6238 </source>
6239 <dest>
6240 *: "Insérer en dernier"
6241 </dest>
6242 <voice>
6243 *: "Insérer en dernier"
6244 </voice>
6245</phrase>
6246<phrase>
6247 id: LANG_INSERT_SHUFFLED
6248 desc: in onplay menu. insert a track/playlist randomly into dynamic playlist
6249 user: core
6250 <source>
6251 *: "Insert Shuffled"
6252 </source>
6253 <dest>
6254 *: "Insérer mélangé"
6255 </dest>
6256 <voice>
6257 *: "Insérer mélangé"
6258 </voice>
6259</phrase>
6260<phrase>
6261 id: LANG_QUEUE 6121 id: LANG_QUEUE
6262 desc: The verb/action Queue 6122 desc: The verb/action Queue
6263 user: core 6123 user: core
@@ -6314,20 +6174,6 @@
6314 </voice> 6174 </voice>
6315</phrase> 6175</phrase>
6316<phrase> 6176<phrase>
6317 id: LANG_REPLACE
6318 desc: in onplay menu. Replace the current playlist with a new one.
6319 user: core
6320 <source>
6321 *: "Play Next"
6322 </source>
6323 <dest>
6324 *: "Lire suivant (remplacer)"
6325 </dest>
6326 <voice>
6327 *: "Lire suivant (remplacer)"
6328 </voice>
6329</phrase>
6330<phrase>
6331 id: LANG_PLAYLIST_INSERT_COUNT 6177 id: LANG_PLAYLIST_INSERT_COUNT
6332 desc: splash number of tracks inserted 6178 desc: splash number of tracks inserted
6333 user: core 6179 user: core
@@ -6394,7 +6240,7 @@
6394 *: "Recherche... %d trouvé (%s)" 6240 *: "Recherche... %d trouvé (%s)"
6395 </dest> 6241 </dest>
6396 <voice> 6242 <voice>
6397 *: "" 6243 *: "Recherche... %d trouvé (%s)"
6398 </voice> 6244 </voice>
6399</phrase> 6245</phrase>
6400<phrase> 6246<phrase>
@@ -6412,34 +6258,6 @@
6412 </voice> 6258 </voice>
6413</phrase> 6259</phrase>
6414<phrase> 6260<phrase>
6415 id: LANG_CATALOG_VIEW
6416 desc: in onplay playlist catalogue submenu
6417 user: core
6418 <source>
6419 *: "View Catalogue"
6420 </source>
6421 <dest>
6422 *: "Afficher le catalogue"
6423 </dest>
6424 <voice>
6425 *: "Afficher le catalogue"
6426 </voice>
6427</phrase>
6428<phrase>
6429 id: LANG_CATALOG_ADD_TO
6430 desc: in onplay playlist catalogue submenu
6431 user: core
6432 <source>
6433 *: "Add to Playlist"
6434 </source>
6435 <dest>
6436 *: "Ajouter à la liste de lecture"
6437 </dest>
6438 <voice>
6439 *: "Ajouter à la liste de lecture"
6440 </voice>
6441</phrase>
6442<phrase>
6443 id: LANG_CATALOG_ADD_TO_NEW 6261 id: LANG_CATALOG_ADD_TO_NEW
6444 desc: in onplay playlist catalogue submenu 6262 desc: in onplay playlist catalogue submenu
6445 user: core 6263 user: core
@@ -6790,20 +6608,6 @@
6790 </voice> 6608 </voice>
6791</phrase> 6609</phrase>
6792<phrase> 6610<phrase>
6793 id: LANG_ID3_ALBUM_GAIN
6794 desc: in tag viewer
6795 user: core
6796 <source>
6797 *: "Album Gain"
6798 </source>
6799 <dest>
6800 *: "Gain par album"
6801 </dest>
6802 <voice>
6803 *: "Gain par album"
6804 </voice>
6805</phrase>
6806<phrase>
6807 id: LANG_ID3_PATH 6611 id: LANG_ID3_PATH
6808 desc: in tag viewer 6612 desc: in tag viewer
6809 user: core 6613 user: core
@@ -6822,10 +6626,10 @@
6822 desc: in tag viewer 6626 desc: in tag viewer
6823 user: core 6627 user: core
6824 <source> 6628 <source>
6825 *: "<No Info>" 6629 *: "[No Info]"
6826 </source> 6630 </source>
6827 <dest> 6631 <dest>
6828 *: "<Pas d'Info>" 6632 *: "[Pas d'Info]"
6829 </dest> 6633 </dest>
6830 <voice> 6634 <voice>
6831 *: "Pas d'Info" 6635 *: "Pas d'Info"
@@ -7180,7 +6984,6 @@
7180 </source> 6984 </source>
7181 <dest> 6985 <dest>
7182 *: "Fin de la liste de lecture" 6986 *: "Fin de la liste de lecture"
7183 archosplayer: "Fin de la liste"
7184 </dest> 6987 </dest>
7185 <voice> 6988 <voice>
7186 *: "Fin de la liste de lecture" 6989 *: "Fin de la liste de lecture"
@@ -7215,20 +7018,6 @@
7215 </voice> 7018 </voice>
7216</phrase> 7019</phrase>
7217<phrase> 7020<phrase>
7218 id: LANG_PLAYLIST_CONTROL_UPDATE_ERROR
7219 desc: Playlist error
7220 user: core
7221 <source>
7222 *: "Error updating playlist control file"
7223 </source>
7224 <dest>
7225 *: "Erreur mise à jour fichier de contrôle de liste de lecture"
7226 </dest>
7227 <voice>
7228 *: "Erreur mise à jour fichier de contrôle de liste de lecture"
7229 </voice>
7230</phrase>
7231<phrase>
7232 id: LANG_PLAYLIST_ACCESS_ERROR 7021 id: LANG_PLAYLIST_ACCESS_ERROR
7233 desc: Playlist error 7022 desc: Playlist error
7234 user: core 7023 user: core
@@ -7309,7 +7098,7 @@
7309 *: "Mode:" 7098 *: "Mode:"
7310 </dest> 7099 </dest>
7311 <voice> 7100 <voice>
7312 *: "Mode:" 7101 *: "Mode"
7313 </voice> 7102 </voice>
7314</phrase> 7103</phrase>
7315<phrase> 7104<phrase>
@@ -7410,24 +7199,7 @@
7410 </dest> 7199 </dest>
7411 <voice> 7200 <voice>
7412 *: none 7201 *: none
7413 iaudiom5,iaudiox5,iriverh100,iriverh120,iriverh300,recording,sansac200*,sansae200*: "" 7202 iaudiom5,iaudiox5,iriverh100,iriverh120,iriverh300,recording,samsungyh*,sansac200*,sansae200*,vibe500: ""
7414 </voice>
7415</phrase>
7416<phrase>
7417 id: LANG_DB_INF
7418 desc: -inf db for values below measurement
7419 user: core
7420 <source>
7421 *: none
7422 recording: "-inf"
7423 </source>
7424 <dest>
7425 *: none
7426 recording: "-inf"
7427 </dest>
7428 <voice>
7429 *: none
7430 recording: "Inifini négatif"
7431 </voice> 7203 </voice>
7432</phrase> 7204</phrase>
7433<phrase> 7205<phrase>
@@ -7551,7 +7323,7 @@
7551 *: "Modèle incompatible" 7323 *: "Modèle incompatible"
7552 </dest> 7324 </dest>
7553 <voice> 7325 <voice>
7554 *: "" 7326 *: "Modèle incompatible"
7555 </voice> 7327 </voice>
7556</phrase> 7328</phrase>
7557<phrase> 7329<phrase>
@@ -7565,7 +7337,7 @@
7565 *: "Version incompatible" 7337 *: "Version incompatible"
7566 </dest> 7338 </dest>
7567 <voice> 7339 <voice>
7568 *: "" 7340 *: "Version incompatible"
7569 </voice> 7341 </voice>
7570</phrase> 7342</phrase>
7571<phrase> 7343<phrase>
@@ -7576,10 +7348,10 @@
7576 *: "Plugin returned error" 7348 *: "Plugin returned error"
7577 </source> 7349 </source>
7578 <dest> 7350 <dest>
7579 *: "Erreur Retournée par le module d'extension" 7351 *: "Erreur retournée par le module"
7580 </dest> 7352 </dest>
7581 <voice> 7353 <voice>
7582 *: "" 7354 *: "Erreur retournée par le module"
7583 </voice> 7355 </voice>
7584</phrase> 7356</phrase>
7585<phrase> 7357<phrase>
@@ -7656,23 +7428,6 @@
7656 </voice> 7428 </voice>
7657</phrase> 7429</phrase>
7658<phrase> 7430<phrase>
7659 id: LANG_BATTERY_TRICKLE_CHARGE
7660 desc: in info display, shows that trickle charge is running
7661 user: core
7662 <source>
7663 *: none
7664 charging: "Battery: Trickle Chg"
7665 </source>
7666 <dest>
7667 *: none
7668 charging: "Batterie: charge persistante"
7669 </dest>
7670 <voice>
7671 *: none
7672 charging: "Charge persistante de la batterie"
7673 </voice>
7674</phrase>
7675<phrase>
7676 id: LANG_WARNING_BATTERY_LOW 7431 id: LANG_WARNING_BATTERY_LOW
7677 desc: general warning 7432 desc: general warning
7678 user: core 7433 user: core
@@ -9519,13 +9274,13 @@
9519</phrase> 9274</phrase>
9520<phrase> 9275<phrase>
9521 id: LANG_TAGNAVI_RANDOM 9276 id: LANG_TAGNAVI_RANDOM
9522 desc: "<Random>" entry in tag browser 9277 desc: "[Random]" entry in tag browser
9523 user: core 9278 user: core
9524 <source> 9279 <source>
9525 *: "<Random>" 9280 *: "[Random]"
9526 </source> 9281 </source>
9527 <dest> 9282 <dest>
9528 *: "<Aléatoire>" 9283 *: "[Aléatoire]"
9529 </dest> 9284 </dest>
9530 <voice> 9285 <voice>
9531 *: "Aléatoire" 9286 *: "Aléatoire"
@@ -10573,20 +10328,6 @@
10573 </voice> 10328 </voice>
10574</phrase> 10329</phrase>
10575<phrase> 10330<phrase>
10576 id: LANG_SCROLLBAR_POSITION
10577 desc: in Settings -> General -> Display -> Status-/Scrollbar
10578 user: core
10579 <source>
10580 *: "Scroll Bar Position"
10581 </source>
10582 <dest>
10583 *: "Position barre de défilement"
10584 </dest>
10585 <voice>
10586 *: "Position de la barre de défilement"
10587 </voice>
10588</phrase>
10589<phrase>
10590 id: LANG_COMPRESSOR 10331 id: LANG_COMPRESSOR
10591 desc: in sound settings 10332 desc: in sound settings
10592 user: core 10333 user: core
@@ -10848,20 +10589,6 @@
10848 </voice> 10589 </voice>
10849</phrase> 10590</phrase>
10850<phrase> 10591<phrase>
10851 id: LANG_STATUSBAR_CUSTOM
10852 desc: if this translation is compatible with LANG_CHANNEL_CUSTOM, then please use the same translation. it can be combined later then
10853 user: core
10854 <source>
10855 *: "Custom"
10856 </source>
10857 <dest>
10858 *: "Personnalisé"
10859 </dest>
10860 <voice>
10861 *: "Personnalisé"
10862 </voice>
10863</phrase>
10864<phrase>
10865 id: VOICE_EXT_SBS 10592 id: VOICE_EXT_SBS
10866 desc: spoken only, for file extension 10593 desc: spoken only, for file extension
10867 user: core 10594 user: core
@@ -10910,20 +10637,6 @@
10910 </voice> 10637 </voice>
10911</phrase> 10638</phrase>
10912<phrase> 10639<phrase>
10913 id: LANG_INSERT_LAST_SHUFFLED
10914 desc: in onplay menu. insert a playlist randomly at end of dynamic playlist
10915 user: core
10916 <source>
10917 *: "Insert Last Shuffled"
10918 </source>
10919 <dest>
10920 *: "Insérer mélangé en fin"
10921 </dest>
10922 <voice>
10923 *: "Insérer mélangé en fin"
10924 </voice>
10925</phrase>
10926<phrase>
10927 id: LANG_QUEUE_LAST_SHUFFLED 10640 id: LANG_QUEUE_LAST_SHUFFLED
10928 desc: in onplay menu. queue a playlist randomly at end of dynamic playlist 10641 desc: in onplay menu. queue a playlist randomly at end of dynamic playlist
10929 user: core 10642 user: core
@@ -10982,7 +10695,7 @@
10982 *: "Titre suivant:" 10695 *: "Titre suivant:"
10983 </dest> 10696 </dest>
10984 <voice> 10697 <voice>
10985 *: "Titre suivant:" 10698 *: "Titre suivant"
10986 </voice> 10699 </voice>
10987</phrase> 10700</phrase>
10988<phrase> 10701<phrase>
@@ -10996,7 +10709,7 @@
10996 *: "Suivant:" 10709 *: "Suivant:"
10997 </dest> 10710 </dest>
10998 <voice> 10711 <voice>
10999 *: "Suivant:" 10712 *: "Suivant"
11000 </voice> 10713 </voice>
11001</phrase> 10714</phrase>
11002<phrase> 10715<phrase>
@@ -11380,13 +11093,13 @@
11380</phrase> 11093</phrase>
11381<phrase> 11094<phrase>
11382 id: LANG_TAGNAVI_UNTAGGED 11095 id: LANG_TAGNAVI_UNTAGGED
11383 desc: "<untagged>" entry in tag browser 11096 desc: "[untagged]" entry in tag browser
11384 user: core 11097 user: core
11385 <source> 11098 <source>
11386 *: "<Untagged>" 11099 *: "[Untagged]"
11387 </source> 11100 </source>
11388 <dest> 11101 <dest>
11389 *: "<Inconnu>" 11102 *: "[Inconnu]"
11390 </dest> 11103 </dest>
11391 <voice> 11104 <voice>
11392 *: "Inconnu" 11105 *: "Inconnu"
@@ -11517,20 +11230,6 @@
11517 </voice> 11230 </voice>
11518</phrase> 11231</phrase>
11519<phrase> 11232<phrase>
11520 id: LANG_SET_AS_START_DIR
11521 desc: used in the onplay menu to set a starting browser dir
11522 user: core
11523 <source>
11524 *: "Start File Browser Here"
11525 </source>
11526 <dest>
11527 *: "Démarrer navigation fichiers ici"
11528 </dest>
11529 <voice>
11530 *: "Démarrer navigation des fichiers ici"
11531 </voice>
11532</phrase>
11533<phrase>
11534 id: LANG_RESET_START_DIR 11233 id: LANG_RESET_START_DIR
11535 desc: reset the browser start directory 11234 desc: reset the browser start directory
11536 user: core 11235 user: core
@@ -11663,20 +11362,6 @@
11663 </voice> 11362 </voice>
11664</phrase> 11363</phrase>
11665<phrase> 11364<phrase>
11666 id: LANG_SET_AS_PLAYLISTCAT_DIR
11667 desc: used in the onplay menu to set a playlist catalogue dir
11668 user: core
11669 <source>
11670 *: "Set As Playlist Catalogue Directory"
11671 </source>
11672 <dest>
11673 *: "Sélectionner comme répertoire des catalogues de listes de lecture"
11674 </dest>
11675 <voice>
11676 *: "Sélectionner comme répertoire des catalogues de listes de lecture"
11677 </voice>
11678</phrase>
11679<phrase>
11680 id: LANG_LIST_LINE_PADDING 11365 id: LANG_LIST_LINE_PADDING
11681 desc: list padding, in display settings 11366 desc: list padding, in display settings
11682 user: core 11367 user: core
@@ -11708,20 +11393,6 @@
11708 </voice> 11393 </voice>
11709</phrase> 11394</phrase>
11710<phrase> 11395<phrase>
11711 id: LANG_AUTOMATIC
11712 desc: generic automatic
11713 user: core
11714 <source>
11715 *: "Automatic"
11716 </source>
11717 <dest>
11718 *: "Automatique"
11719 </dest>
11720 <voice>
11721 *: "Automatique"
11722 </voice>
11723</phrase>
11724<phrase>
11725 id: LANG_SLEEP_TIMER_CANCEL_CURRENT 11396 id: LANG_SLEEP_TIMER_CANCEL_CURRENT
11726 desc: shown instead of sleep timer when it's running 11397 desc: shown instead of sleep timer when it's running
11727 user: core 11398 user: core
@@ -12296,7 +11967,7 @@
12296 desc: Selective Actions 11967 desc: Selective Actions
12297 user: core 11968 user: core
12298 <source> 11969 <source>
12299 *: "Seek" 11970 *: "Exempt Seek"
12300 </source> 11971 </source>
12301 <dest> 11972 <dest>
12302 *: "Chercher" 11973 *: "Chercher"
@@ -12310,7 +11981,7 @@
12310 desc: Selective Actions 11981 desc: Selective Actions
12311 user: core 11982 user: core
12312 <source> 11983 <source>
12313 *: "Play" 11984 *: "Exempt Play"
12314 </source> 11985 </source>
12315 <dest> 11986 <dest>
12316 *: "Jouer" 11987 *: "Jouer"
@@ -12352,7 +12023,7 @@
12352 desc: Softlock behaviour setting 12023 desc: Softlock behaviour setting
12353 user: core 12024 user: core
12354 <source> 12025 <source>
12355 *: "Disable Notify" 12026 *: "Disable Locked Reminders"
12356 </source> 12027 </source>
12357 <dest> 12028 <dest>
12358 *: "Désactiver les notifications" 12029 *: "Désactiver les notifications"
@@ -12422,7 +12093,7 @@
12422 desc: Selective Actions 12093 desc: Selective Actions
12423 user: core 12094 user: core
12424 <source> 12095 <source>
12425 *: "Skip" 12096 *: "Exempt Skip"
12426 </source> 12097 </source>
12427 <dest> 12098 <dest>
12428 *: "Passer" 12099 *: "Passer"
@@ -12481,10 +12152,10 @@
12481 desc: playing time screen 12152 desc: playing time screen
12482 user: core 12153 user: core
12483 <source> 12154 <source>
12484 *: "Playlist elapsed: %s / %s %ld%%" 12155 *: "Playlist elapsed:"
12485 </source> 12156 </source>
12486 <dest> 12157 <dest>
12487 *: "Liste de lecture, temps écoulé: %s / %s %ld%%" 12158 *: "Liste de lecture, temps écoulé :"
12488 </dest> 12159 </dest>
12489 <voice> 12160 <voice>
12490 *: "Liste de lecture, temps écoulé" 12161 *: "Liste de lecture, temps écoulé"
@@ -12495,10 +12166,10 @@
12495 desc: playing time screen 12166 desc: playing time screen
12496 user: core 12167 user: core
12497 <source> 12168 <source>
12498 *: "Track remaining: %s" 12169 *: "Track remaining:"
12499 </source> 12170 </source>
12500 <dest> 12171 <dest>
12501 *: "Durée de piste restante: %s" 12172 *: "Durée de piste restante :"
12502 </dest> 12173 </dest>
12503 <voice> 12174 <voice>
12504 *: "Durée de piste restante" 12175 *: "Durée de piste restante"
@@ -12509,10 +12180,10 @@
12509 desc: playing time screen 12180 desc: playing time screen
12510 user: core 12181 user: core
12511 <source> 12182 <source>
12512 *: "Track elapsed: %s / %s %ld%%" 12183 *: "Track elapsed:"
12513 </source> 12184 </source>
12514 <dest> 12185 <dest>
12515 *: "Temps écoulé de la piste: %s / %s %ld%%" 12186 *: "Temps écoulé de la piste :"
12516 </dest> 12187 </dest>
12517 <voice> 12188 <voice>
12518 *: "Temps écoulé de la piste" 12189 *: "Temps écoulé de la piste"
@@ -12735,20 +12406,6 @@
12735 </voice> 12406 </voice>
12736</phrase> 12407</phrase>
12737<phrase> 12408<phrase>
12738 id: LANG_PROPERTIES_TITLE
12739 desc: in properties plugin
12740 user: core
12741 <source>
12742 *: "[Title]"
12743 </source>
12744 <dest>
12745 *: "[Titre]"
12746 </dest>
12747 <voice>
12748 *: "Titre"
12749 </voice>
12750</phrase>
12751<phrase>
12752 id: VOICE_PITCH_TIMESTRETCH_MODE 12409 id: VOICE_PITCH_TIMESTRETCH_MODE
12753 desc: spoken only 12410 desc: spoken only
12754 user: core 12411 user: core
@@ -12897,7 +12554,7 @@
12897 *: "Réflexion en cours..." 12554 *: "Réflexion en cours..."
12898 </dest> 12555 </dest>
12899 <voice> 12556 <voice>
12900 *: "" 12557 *: "Réflexion en cours..."
12901 </voice> 12558 </voice>
12902</phrase> 12559</phrase>
12903<phrase> 12560<phrase>
@@ -12933,16 +12590,16 @@
12933</phrase> 12590</phrase>
12934<phrase> 12591<phrase>
12935 id: LANG_PLAYTIME_REMAINING 12592 id: LANG_PLAYTIME_REMAINING
12936 desc: playing time screen 12593 desc: deprecated
12937 user: core 12594 user: core
12938 <source> 12595 <source>
12939 *: "Playlist remaining: %s" 12596 *: ""
12940 </source> 12597 </source>
12941 <dest> 12598 <dest>
12942 *: "Playlist remaining: %s" 12599 *: ""
12943 </dest> 12600 </dest>
12944 <voice> 12601 <voice>
12945 *: "Playlist remaining" 12602 *: ""
12946 </voice> 12603 </voice>
12947</phrase> 12604</phrase>
12948<phrase> 12605<phrase>
@@ -12995,10 +12652,10 @@
12995 *: "Display FPS" 12652 *: "Display FPS"
12996 </source> 12653 </source>
12997 <dest> 12654 <dest>
12998 *: "Display FPS" 12655 *: "Afficher les FPS"
12999 </dest> 12656 </dest>
13000 <voice> 12657 <voice>
13001 *: "Display FPS" 12658 *: "Afficher les FPS"
13002 </voice> 12659 </voice>
13003</phrase> 12660</phrase>
13004<phrase> 12661<phrase>
@@ -13009,10 +12666,10 @@
13009 *: "Remote Control" 12666 *: "Remote Control"
13010 </source> 12667 </source>
13011 <dest> 12668 <dest>
13012 *: "Remote Control" 12669 *: "Télécommande"
13013 </dest> 12670 </dest>
13014 <voice> 12671 <voice>
13015 *: "Remote Control" 12672 *: "Télécommande"
13016 </voice> 12673 </voice>
13017</phrase> 12674</phrase>
13018<phrase> 12675<phrase>
@@ -13141,10 +12798,10 @@
13141 *: "[Subdirs]" 12798 *: "[Subdirs]"
13142 </source> 12799 </source>
13143 <dest> 12800 <dest>
13144 *: "[Subdirs]" 12801 *: "[Sous-rep]"
13145 </dest> 12802 </dest>
13146 <voice> 12803 <voice>
13147 *: "Subdirs" 12804 *: "[Sous-rep]"
13148 </voice> 12805 </voice>
13149</phrase> 12806</phrase>
13150<phrase> 12807<phrase>
@@ -13183,10 +12840,10 @@
13183 *: "Go to WPS" 12840 *: "Go to WPS"
13184 </source> 12841 </source>
13185 <dest> 12842 <dest>
13186 *: "Go to WPS" 12843 *: "Aller au WPS"
13187 </dest> 12844 </dest>
13188 <voice> 12845 <voice>
13189 *: "Go to WPS" 12846 *: "Aller au WPS"
13190 </voice> 12847 </voice>
13191</phrase> 12848</phrase>
13192<phrase> 12849<phrase>
@@ -13197,24 +12854,10 @@
13197 *: "Not a VBR file" 12854 *: "Not a VBR file"
13198 </source> 12855 </source>
13199 <dest> 12856 <dest>
13200 *: "Not a VBR file" 12857 *: "Ce n'est pas un fichier VBR"
13201 </dest>
13202 <voice>
13203 *: "Not a VBR file"
13204 </voice>
13205</phrase>
13206<phrase>
13207 id: LANG_PROPERTIES_ALBUM
13208 desc: in properties plugin
13209 user: core
13210 <source>
13211 *: "[Album]"
13212 </source>
13213 <dest>
13214 *: "[Album]"
13215 </dest> 12858 </dest>
13216 <voice> 12859 <voice>
13217 *: "Album" 12860 *: "Ce n'est pas un fichier VBR"
13218 </voice> 12861 </voice>
13219</phrase> 12862</phrase>
13220<phrase> 12863<phrase>
@@ -13239,10 +12882,10 @@
13239 *: "Show album title" 12882 *: "Show album title"
13240 </source> 12883 </source>
13241 <dest> 12884 <dest>
13242 *: "Show album title" 12885 *: "Afficher le titre de l'album"
13243 </dest> 12886 </dest>
13244 <voice> 12887 <voice>
13245 *: "Show album title" 12888 *: "Afficher le titre de l'album"
13246 </voice> 12889 </voice>
13247</phrase> 12890</phrase>
13248<phrase> 12891<phrase>
@@ -13250,13 +12893,13 @@
13250 desc: playing time screen 12893 desc: playing time screen
13251 user: core 12894 user: core
13252 <source> 12895 <source>
13253 *: "Average bitrate: %ld kbps" 12896 *: "Average bitrate:"
13254 </source> 12897 </source>
13255 <dest> 12898 <dest>
13256 *: "Average bitrate: %ld kbps" 12899 *: "Débit binaire moyen :"
13257 </dest> 12900 </dest>
13258 <voice> 12901 <voice>
13259 *: "Average bit rate" 12902 *: "Débit binaire moyen :"
13260 </voice> 12903 </voice>
13261</phrase> 12904</phrase>
13262<phrase> 12905<phrase>
@@ -13322,20 +12965,6 @@
13322 </voice> 12965 </voice>
13323</phrase> 12966</phrase>
13324<phrase> 12967<phrase>
13325 id: LANG_CLEAR_PLAYLIST
13326 desc: in the pictureflow main menu
13327 user: core
13328 <source>
13329 *: "Clear playlist"
13330 </source>
13331 <dest>
13332 *: "Effacer la liste de lecture"
13333 </dest>
13334 <voice>
13335 *: "Effacer la liste de lecture"
13336 </voice>
13337</phrase>
13338<phrase>
13339 id: VOICE_CHESSBOX_QUEENSIDE 12968 id: VOICE_CHESSBOX_QUEENSIDE
13340 desc: spoken only, for announcing chess moves 12969 desc: spoken only, for announcing chess moves
13341 user: core 12970 user: core
@@ -13371,10 +13000,10 @@
13371 *: "Playback Control" 13000 *: "Playback Control"
13372 </source> 13001 </source>
13373 <dest> 13002 <dest>
13374 *: "Playback Control" 13003 *: "Contrôle de lecture"
13375 </dest> 13004 </dest>
13376 <voice> 13005 <voice>
13377 *: "Playback Control" 13006 *: "Contrôle de lecture"
13378 </voice> 13007 </voice>
13379</phrase> 13008</phrase>
13380<phrase> 13009<phrase>
@@ -13475,7 +13104,7 @@
13475 *: "File properties" 13104 *: "File properties"
13476 </source> 13105 </source>
13477 <dest> 13106 <dest>
13478 *: "File properties" 13107 *: "Propriétés du fichier"
13479 </dest> 13108 </dest>
13480 <voice> 13109 <voice>
13481 *: "" 13110 *: ""
@@ -13562,10 +13191,10 @@
13562 *: "Move Item Down" 13191 *: "Move Item Down"
13563 </source> 13192 </source>
13564 <dest> 13193 <dest>
13565 *: "Move Item Down" 13194 *: "Déplacer vers le bas"
13566 </dest> 13195 </dest>
13567 <voice> 13196 <voice>
13568 *: "Move Item Down" 13197 *: "Déplacer vers le bas"
13569 </voice> 13198 </voice>
13570</phrase> 13199</phrase>
13571<phrase> 13200<phrase>
@@ -13642,20 +13271,6 @@
13642 </voice> 13271 </voice>
13643</phrase> 13272</phrase>
13644<phrase> 13273<phrase>
13645 id: LANG_PLAYLIST_CLEARED
13646 desc: in the pictureflow splash messages
13647 user: core
13648 <source>
13649 *: "Playlist Cleared"
13650 </source>
13651 <dest>
13652 *: "Playlist Cleared"
13653 </dest>
13654 <voice>
13655 *: "Playlist Cleared"
13656 </voice>
13657</phrase>
13658<phrase>
13659 id: LANG_REC_DIR 13274 id: LANG_REC_DIR
13660 desc: used in the info screen to show a recording dir 13275 desc: used in the info screen to show a recording dir
13661 user: core 13276 user: core
@@ -13680,10 +13295,10 @@
13680 *: "Shuffle Mode" 13295 *: "Shuffle Mode"
13681 </source> 13296 </source>
13682 <dest> 13297 <dest>
13683 *: "Shuffle Mode" 13298 *: "Mode mélangé"
13684 </dest> 13299 </dest>
13685 <voice> 13300 <voice>
13686 *: "Shuffle Mode" 13301 *: "Mode mélangé"
13687 </voice> 13302 </voice>
13688</phrase> 13303</phrase>
13689<phrase> 13304<phrase>
@@ -13812,7 +13427,7 @@
13812 *: "" 13427 *: ""
13813 </dest> 13428 </dest>
13814 <voice> 13429 <voice>
13815 *: "Moving track" 13430 *: "Déplacement de la piste"
13816 </voice> 13431 </voice>
13817</phrase> 13432</phrase>
13818<phrase> 13433<phrase>
@@ -13920,20 +13535,6 @@
13920 </voice> 13535 </voice>
13921</phrase> 13536</phrase>
13922<phrase> 13537<phrase>
13923 id: LANG_ADDED_TO_PLAYLIST
13924 desc: in the pictureflow splash messages
13925 user: core
13926 <source>
13927 *: "Added to playlist"
13928 </source>
13929 <dest>
13930 *: "Ajouté à la liste de lecture"
13931 </dest>
13932 <voice>
13933 *: "Ajouté à la liste de lecture"
13934 </voice>
13935</phrase>
13936<phrase>
13937 id: VOICE_PITCH_ABSOLUTE_MODE 13538 id: VOICE_PITCH_ABSOLUTE_MODE
13938 desc: spoken only 13539 desc: spoken only
13939 user: core 13540 user: core
@@ -14039,7 +13640,7 @@
14039</phrase> 13640</phrase>
14040<phrase> 13641<phrase>
14041 id: LANG_DIRECT 13642 id: LANG_DIRECT
14042 desc: in the pictureflow settings 13643 desc: in the pictureflow settings, also a volume adjustment mode
14043 user: core 13644 user: core
14044 <source> 13645 <source>
14045 *: "Direct" 13646 *: "Direct"
@@ -14073,10 +13674,10 @@
14073 *: "Change Repeat Mode" 13674 *: "Change Repeat Mode"
14074 </source> 13675 </source>
14075 <dest> 13676 <dest>
14076 *: "Change Repeat Mode" 13677 *: "Changer le mode de répétition"
14077 </dest> 13678 </dest>
14078 <voice> 13679 <voice>
14079 *: "Change Repeat Mode" 13680 *: "Changer le mode de répétition"
14080 </voice> 13681 </voice>
14081</phrase> 13682</phrase>
14082<phrase> 13683<phrase>
@@ -14174,10 +13775,10 @@
14174 *: "Cache will be rebuilt on next restart" 13775 *: "Cache will be rebuilt on next restart"
14175 </source> 13776 </source>
14176 <dest> 13777 <dest>
14177 *: "Cache will be rebuilt on next restart" 13778 *: "Le cache sera reconstruit au prochain redémarrage"
14178 </dest> 13779 </dest>
14179 <voice> 13780 <voice>
14180 *: "Cache will be rebuilt on next restart" 13781 *: "Le cache sera reconstruit au prochain redémarrage"
14181 </voice> 13782 </voice>
14182</phrase> 13783</phrase>
14183<phrase> 13784<phrase>
@@ -14188,10 +13789,10 @@
14188 *: "Change Volume" 13789 *: "Change Volume"
14189 </source> 13790 </source>
14190 <dest> 13791 <dest>
14191 *: "Change Volume" 13792 *: "Changer le volume"
14192 </dest> 13793 </dest>
14193 <voice> 13794 <voice>
14194 *: "Change Volume" 13795 *: "Changer le volume"
14195 </voice> 13796 </voice>
14196</phrase> 13797</phrase>
14197<phrase> 13798<phrase>
@@ -14268,20 +13869,6 @@
14268 </voice> 13869 </voice>
14269</phrase> 13870</phrase>
14270<phrase> 13871<phrase>
14271 id: LANG_PROPERTIES_ARTIST
14272 desc: in properties plugin
14273 user: core
14274 <source>
14275 *: "[Artist]"
14276 </source>
14277 <dest>
14278 *: "[Interprète]"
14279 </dest>
14280 <voice>
14281 *: "Interprète"
14282 </voice>
14283</phrase>
14284<phrase>
14285 id: LANG_PROPERTIES_SIZE 13872 id: LANG_PROPERTIES_SIZE
14286 desc: in properties plugin 13873 desc: in properties plugin
14287 user: core 13874 user: core
@@ -14303,10 +13890,10 @@
14303 *: "Via Track list" 13890 *: "Via Track list"
14304 </source> 13891 </source>
14305 <dest> 13892 <dest>
14306 *: "Via Track list" 13893 *: "Via la liste des pistes"
14307 </dest> 13894 </dest>
14308 <voice> 13895 <voice>
14309 *: "Via Track list" 13896 *: "Via la liste des pistes"
14310 </voice> 13897 </voice>
14311</phrase> 13898</phrase>
14312<phrase> 13899<phrase>
@@ -14359,10 +13946,10 @@
14359 *: "Load Default Configuration" 13946 *: "Load Default Configuration"
14360 </source> 13947 </source>
14361 <dest> 13948 <dest>
14362 *: "Load Default Configuration" 13949 *: "Charger la configuration par défaut"
14363 </dest> 13950 </dest>
14364 <voice> 13951 <voice>
14365 *: "Load Default Configuration" 13952 *: "Charger la configuration par défaut"
14366 </voice> 13953 </voice>
14367</phrase> 13954</phrase>
14368<phrase> 13955<phrase>
@@ -14373,10 +13960,10 @@
14373 *: "Checkmate!" 13960 *: "Checkmate!"
14374 </source> 13961 </source>
14375 <dest> 13962 <dest>
14376 *: "Checkmate!" 13963 *: "Échec et mat !"
14377 </dest> 13964 </dest>
14378 <voice> 13965 <voice>
14379 *: "Checkmate!" 13966 *: "Échec et mat !"
14380 </voice> 13967 </voice>
14381</phrase> 13968</phrase>
14382<phrase> 13969<phrase>
@@ -14454,13 +14041,13 @@
14454 desc: playing time screen 14041 desc: playing time screen
14455 user: core 14042 user: core
14456 <source> 14043 <source>
14457 *: "Storage: %s (done %s, remaining %s)" 14044 *: "Storage (Done / Remaining):"
14458 </source> 14045 </source>
14459 <dest> 14046 <dest>
14460 *: "Storage: %s (done %s, remaining %s)" 14047 *: "Stockage (Terminé / Restant) :"
14461 </dest> 14048 </dest>
14462 <voice> 14049 <voice>
14463 *: "Storage" 14050 *: "Stockage"
14464 </voice> 14051 </voice>
14465</phrase> 14052</phrase>
14466<phrase> 14053<phrase>
@@ -14471,10 +14058,10 @@
14471 *: "Pause / Play" 14058 *: "Pause / Play"
14472 </source> 14059 </source>
14473 <dest> 14060 <dest>
14474 *: "Pause / Play" 14061 *: "Pause / Lecture"
14475 </dest> 14062 </dest>
14476 <voice> 14063 <voice>
14477 *: "Pause / Play" 14064 *: "Pause / Lecture"
14478 </voice> 14065 </voice>
14479</phrase> 14066</phrase>
14480<phrase> 14067<phrase>
@@ -14485,10 +14072,10 @@
14485 *: "Previous Track" 14072 *: "Previous Track"
14486 </source> 14073 </source>
14487 <dest> 14074 <dest>
14488 *: "Previous Track" 14075 *: "Piste précédente"
14489 </dest> 14076 </dest>
14490 <voice> 14077 <voice>
14491 *: "Previous Track" 14078 *: "Piste précédente"
14492 </voice> 14079 </voice>
14493</phrase> 14080</phrase>
14494<phrase> 14081<phrase>
@@ -14520,20 +14107,6 @@
14520 </voice> 14107 </voice>
14521</phrase> 14108</phrase>
14522<phrase> 14109<phrase>
14523 id: LANG_PROPERTIES_DURATION
14524 desc: in properties plugin
14525 user: core
14526 <source>
14527 *: "[Duration]"
14528 </source>
14529 <dest>
14530 *: "[Durée]"
14531 </dest>
14532 <voice>
14533 *: "Durée"
14534 </voice>
14535</phrase>
14536<phrase>
14537 id: LANG_PROPERTIES_FAIL 14110 id: LANG_PROPERTIES_FAIL
14538 desc: in properties plugin 14111 desc: in properties plugin
14539 user: core 14112 user: core
@@ -14541,10 +14114,10 @@
14541 *: "Failed to gather information" 14114 *: "Failed to gather information"
14542 </source> 14115 </source>
14543 <dest> 14116 <dest>
14544 *: "Failed to gather information" 14117 *: "Échec de la collecte d'informations"
14545 </dest> 14118 </dest>
14546 <voice> 14119 <voice>
14547 *: "Failed to gather information" 14120 *: "Échec de la collecte d'informations"
14548 </voice> 14121 </voice>
14549</phrase> 14122</phrase>
14550<phrase> 14123<phrase>
@@ -14570,11 +14143,11 @@
14570 lowmem: none 14143 lowmem: none
14571 </source> 14144 </source>
14572 <dest> 14145 <dest>
14573 *: "Resume automatically" 14146 *: "Reprendre automatiquement"
14574 lowmem: none 14147 lowmem: none
14575 </dest> 14148 </dest>
14576 <voice> 14149 <voice>
14577 *: "Resume automatically" 14150 *: "Reprendre automatiquement"
14578 lowmem: none 14151 lowmem: none
14579 </voice> 14152 </voice>
14580</phrase> 14153</phrase>
@@ -14587,11 +14160,11 @@
14587 lowmem: none 14160 lowmem: none
14588 </source> 14161 </source>
14589 <dest> 14162 <dest>
14590 *: "Reverberation" 14163 *: "Réverbération"
14591 lowmem: none 14164 lowmem: none
14592 </dest> 14165 </dest>
14593 <voice> 14166 <voice>
14594 *: "Reverberation" 14167 *: "Réverbération"
14595 lowmem: none 14168 lowmem: none
14596 </voice> 14169 </voice>
14597</phrase> 14170</phrase>
@@ -14642,10 +14215,10 @@
14642 desc: playing time screen 14215 desc: playing time screen
14643 user: core 14216 user: core
14644 <source> 14217 <source>
14645 *: "Average track size: %s" 14218 *: "Average track size:"
14646 </source> 14219 </source>
14647 <dest> 14220 <dest>
14648 *: "Taille moyenne des pistes: %s" 14221 *: "Taille moyenne des pistes :"
14649 </dest> 14222 </dest>
14650 <voice> 14223 <voice>
14651 *: "Taille moyenne des pistes" 14224 *: "Taille moyenne des pistes"
@@ -14656,13 +14229,13 @@
14656 desc: playing time screen 14229 desc: playing time screen
14657 user: core 14230 user: core
14658 <source> 14231 <source>
14659 *: "Track %d / %d %d%%" 14232 *: "Track:"
14660 </source> 14233 </source>
14661 <dest> 14234 <dest>
14662 *: "Track %d / %d %d%%" 14235 *: "Piste :"
14663 </dest> 14236 </dest>
14664 <voice> 14237 <voice>
14665 *: "Track" 14238 *: "Piste :"
14666 </voice> 14239 </voice>
14667</phrase> 14240</phrase>
14668<phrase> 14241<phrase>
@@ -14725,20 +14298,6 @@
14725 </voice> 14298 </voice>
14726</phrase> 14299</phrase>
14727<phrase> 14300<phrase>
14728 id: LANG_NO_VIEWERS
14729 desc: text for splash to indicate that no viewers are available
14730 user: core
14731 <source>
14732 *: "No viewers found"
14733 </source>
14734 <dest>
14735 *: "Aucune visionneuse trouvée"
14736 </dest>
14737 <voice>
14738 *: "Aucune visionneuse trouvée"
14739 </voice>
14740</phrase>
14741<phrase>
14742 id: VOICE_BISHOP 14301 id: VOICE_BISHOP
14743 desc: spoken only, for announcing chess piece names 14302 desc: spoken only, for announcing chess piece names
14744 user: core 14303 user: core
@@ -14959,10 +14518,10 @@
14959 *: "Hide album title" 14518 *: "Hide album title"
14960 </source> 14519 </source>
14961 <dest> 14520 <dest>
14962 *: "Hide album title" 14521 *: "Cacher le titre de l'album"
14963 </dest> 14522 </dest>
14964 <voice> 14523 <voice>
14965 *: "Hide album title" 14524 *: "Cacher le titre de l'album"
14966 </voice> 14525 </voice>
14967</phrase> 14526</phrase>
14968<phrase> 14527<phrase>
@@ -14974,11 +14533,11 @@
14974 lowmem: none 14533 lowmem: none
14975 </source> 14534 </source>
14976 <dest> 14535 <dest>
14977 *: "Set resume time (min)" 14536 *: "Définir le temps de reprise (min)"
14978 lowmem: none 14537 lowmem: none
14979 </dest> 14538 </dest>
14980 <voice> 14539 <voice>
14981 *: "Set resume time" 14540 *: "Définir le temps de reprise (min)"
14982 lowmem: none 14541 lowmem: none
14983 </voice> 14542 </voice>
14984</phrase> 14543</phrase>
@@ -15080,3 +14639,1882 @@
15080 *: "Français" 14639 *: "Français"
15081 </voice> 14640 </voice>
15082</phrase> 14641</phrase>
14642<phrase>
14643 id: LANG_DISABLE_MAINMENU_SCROLLING
14644 desc: Disable main menu scrolling
14645 user: core
14646 <source>
14647 *: "Disable main menu scrolling"
14648 </source>
14649 <dest>
14650 *: "Désactiver défilement dans le menu principal"
14651 </dest>
14652 <voice>
14653 *: "Désactiver défilement dans le menu principal"
14654 </voice>
14655</phrase>
14656<phrase>
14657 id: LANG_SINGLE_MODE
14658 desc: single mode
14659 user: core
14660 <source>
14661 *: "Single Mode"
14662 </source>
14663 <dest>
14664 *: "Mode lecture seule"
14665 </dest>
14666 <voice>
14667 *: "Mode lecture seule"
14668 </voice>
14669</phrase>
14670<phrase>
14671 id: LANG_TALK_MIXER_LEVEL
14672 desc: Relative volume of voice prompts
14673 user: core
14674 <source>
14675 *: "Voice prompt volume"
14676 </source>
14677 <dest>
14678 *: "Volume des messages vocaux"
14679 </dest>
14680 <voice>
14681 *: "Volume des messages vocaux"
14682 </voice>
14683</phrase>
14684<phrase>
14685 id: LANG_FILTER_SHORT_SHARP
14686 desc: in sound settings
14687 user: core
14688 <source>
14689 *: none
14690 filter_roll_off: "Short Sharp"
14691 </source>
14692 <dest>
14693 *: none
14694 filter_roll_off: "Short Sharp"
14695 </dest>
14696 <voice>
14697 *: none
14698 filter_roll_off: "Short Sharp"
14699 </voice>
14700</phrase>
14701<phrase>
14702 id: LANG_FILTER_SHORT_SLOW
14703 desc: in sound settings
14704 user: core
14705 <source>
14706 *: none
14707 filter_roll_off: "Short Slow"
14708 </source>
14709 <dest>
14710 *: none
14711 filter_roll_off: "Short Slow"
14712 </dest>
14713 <voice>
14714 *: none
14715 filter_roll_off: "Short Slow"
14716 </voice>
14717</phrase>
14718<phrase>
14719 id: LANG_FILTER_SUPER_SLOW
14720 desc: in sound settings
14721 user: core
14722 <source>
14723 *: none
14724 filter_roll_off: "Super Slow"
14725 </source>
14726 <dest>
14727 *: none
14728 filter_roll_off: "Super Slow"
14729 </dest>
14730 <voice>
14731 *: none
14732 filter_roll_off: "Super Slow"
14733 </voice>
14734</phrase>
14735<phrase>
14736 id: LANG_FILTER_LINEAR_FAST
14737 desc: in sound settings
14738 user: core
14739 <source>
14740 *: none
14741 es9218: "Linear Fast"
14742 </source>
14743 <dest>
14744 *: none
14745 es9218: "Linear Fast"
14746 </dest>
14747 <voice>
14748 *: none
14749 es9218: "Linear Fast"
14750 </voice>
14751</phrase>
14752<phrase>
14753 id: LANG_FILTER_LINEAR_SLOW
14754 desc: in sound settings
14755 user: core
14756 <source>
14757 *: none
14758 es9218: "Linear Slow"
14759 </source>
14760 <dest>
14761 *: none
14762 es9218: "Linear Slow"
14763 </dest>
14764 <voice>
14765 *: none
14766 es9218: "Linear Slow"
14767 </voice>
14768</phrase>
14769<phrase>
14770 id: LANG_FILTER_MINIMUM_FAST
14771 desc: in sound settings
14772 user: core
14773 <source>
14774 *: none
14775 es9218: "Minimum Fast"
14776 </source>
14777 <dest>
14778 *: none
14779 es9218: "Minimum Fast"
14780 </dest>
14781 <voice>
14782 *: none
14783 es9218: "Minimum Fast"
14784 </voice>
14785</phrase>
14786<phrase>
14787 id: LANG_FILTER_MINIMUM_SLOW
14788 desc: in sound settings
14789 user: core
14790 <source>
14791 *: none
14792 es9218: "Minimum Slow"
14793 </source>
14794 <dest>
14795 *: none
14796 es9218: "Minimum Slow"
14797 </dest>
14798 <voice>
14799 *: none
14800 es9218: "Minimum Slow"
14801 </voice>
14802</phrase>
14803<phrase>
14804 id: LANG_FILTER_APODIZING_1
14805 desc: in sound settings
14806 user: core
14807 <source>
14808 *: none
14809 es9218: "Apodizing type 1"
14810 </source>
14811 <dest>
14812 *: none
14813 es9218: "Apodizing type 1"
14814 </dest>
14815 <voice>
14816 *: none
14817 es9218: "Apodizing type 1"
14818 </voice>
14819</phrase>
14820<phrase>
14821 id: LANG_FILTER_APODIZING_2
14822 desc: in sound settings
14823 user: core
14824 <source>
14825 *: none
14826 es9218: "Apodizing type 2"
14827 </source>
14828 <dest>
14829 *: none
14830 es9218: "Apodizing type 2"
14831 </dest>
14832 <voice>
14833 *: none
14834 es9218: "Apodizing type 2"
14835 </voice>
14836</phrase>
14837<phrase>
14838 id: LANG_FILTER_HYBRID_FAST
14839 desc: in sound settings
14840 user: core
14841 <source>
14842 *: none
14843 es9218: "Hybrid Fast"
14844 </source>
14845 <dest>
14846 *: none
14847 es9218: "Hybrid Fast"
14848 </dest>
14849 <voice>
14850 *: none
14851 es9218: "Hybrid Fast"
14852 </voice>
14853</phrase>
14854<phrase>
14855 id: LANG_FILTER_BRICK_WALL
14856 desc: in sound settings
14857 user: core
14858 <source>
14859 *: none
14860 es9218: "Brick Wall"
14861 </source>
14862 <dest>
14863 *: none
14864 es9218: "Brick Wall"
14865 </dest>
14866 <voice>
14867 *: none
14868 es9218: "Brick Wall"
14869 </voice>
14870</phrase>
14871<phrase>
14872 id: LANG_DAC_POWER_MODE
14873 desc: in sound settings
14874 user: core
14875 <source>
14876 *: none
14877 dac_power_mode: "DAC power mode"
14878 es9218: "DAC output level"
14879 </source>
14880 <dest>
14881 *: none
14882 dac_power_mode: "DAC power mode"
14883 es9218: "DAC output level"
14884 </dest>
14885 <voice>
14886 *: none
14887 dac_power_mode: "DAC power mode"
14888 es9218: "DAC output level"
14889 </voice>
14890</phrase>
14891<phrase>
14892 id: LANG_DAC_POWER_HIGH
14893 desc: in sound settings
14894 user: core
14895 <source>
14896 *: none
14897 dac_power_mode: "High performance"
14898 es9218: "High Gain (2 Vrms)"
14899 </source>
14900 <dest>
14901 *: none
14902 dac_power_mode: "High performance"
14903 es9218: "High Gain (2 Vrms)"
14904 </dest>
14905 <voice>
14906 *: none
14907 dac_power_mode: "High performance"
14908 es9218: "High Gain (2 Vrms)"
14909 </voice>
14910</phrase>
14911<phrase>
14912 id: LANG_DAC_POWER_LOW
14913 desc: in sound settings
14914 user: core
14915 <source>
14916 *: none
14917 dac_power_mode: "Save battery"
14918 es9218: "Low Gain (1 Vrms)"
14919 </source>
14920 <dest>
14921 *: none
14922 dac_power_mode: "Save battery"
14923 es9218: "Low Gain (1 Vrms)"
14924 </dest>
14925 <voice>
14926 *: none
14927 dac_power_mode: "Save battery"
14928 es9218: "Low Gain (1 Vrms)"
14929 </voice>
14930</phrase>
14931<phrase>
14932 id: LANG_MIKMOD_HQMIXER
14933 desc: in mikmod settings menu
14934 user: core
14935 <source>
14936 *: "HQ Mixer"
14937 lowmem: none
14938 </source>
14939 <dest>
14940 *: "HQ Mixer"
14941 lowmem: none
14942 </dest>
14943 <voice>
14944 *: "High Quality Mixer"
14945 lowmem: none
14946 </voice>
14947</phrase>
14948<phrase>
14949 id: LANG_MIKMOD_SAMPLERATE
14950 desc: in mikmod settings menu
14951 user: core
14952 <source>
14953 *: "Sample Rate"
14954 lowmem: none
14955 </source>
14956 <dest>
14957 *: "Sample Rate"
14958 lowmem: none
14959 </dest>
14960 <voice>
14961 *: "Sample Rate"
14962 lowmem: none
14963 </voice>
14964</phrase>
14965<phrase>
14966 id: LANG_ACTION_STD_CANCEL
14967 desc: standard press x to cancel string
14968 user: core
14969 <source>
14970 *: "Press LEFT to cancel."
14971 android,hifietma*,zenvision: "Press BACK to cancel."
14972 cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Press POWER to cancel."
14973 ihifi760,ihifi960: "Double tap RETURN to cancel."
14974 ihifi770,ihifi770c,ihifi800: "Press HOME to cancel."
14975 iriverh10,samsungyh*: "Double tap LEFT to cancel."
14976 mpiohd200: "Double tap REC to cancel."
14977 mpiohd300: "Double tap MENU to cancel."
14978 rx27generic: "Press VOLUME to cancel."
14979 sonynwza860: "Keymaps incomplete."
14980 touchscreen: "Press Middle Left to cancel."
14981 vibe500: "Press PREV to cancel."
14982 xduoox20,xduoox3,xduoox3ii: "Double tap HOME to cancel."
14983 </source>
14984 <dest>
14985 *: "Press LEFT to cancel."
14986 android,hifietma*,zenvision: "Press BACK to cancel."
14987 cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Press POWER to cancel."
14988 ihifi760,ihifi960: "Double tap RETURN to cancel."
14989 ihifi770,ihifi770c,ihifi800: "Press HOME to cancel."
14990 iriverh10,samsungyh*: "Double tap LEFT to cancel."
14991 mpiohd200: "Double tap REC to cancel."
14992 mpiohd300: "Double tap MENU to cancel."
14993 rx27generic: "Press VOLUME to cancel."
14994 sonynwza860: "Keymaps incomplete."
14995 touchscreen: "Press Middle Left to cancel."
14996 vibe500: "Press PREV to cancel."
14997 xduoox20,xduoox3,xduoox3ii: "Double tap HOME to cancel."
14998 </dest>
14999 <voice>
15000 *: "Press LEFT to cancel."
15001 android,hifietma*,zenvision: "Press BACK to cancel."
15002 cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Press POWER to cancel."
15003 ihifi760,ihifi960: "Double tap RETURN to cancel."
15004 ihifi770,ihifi770c,ihifi800: "Press HOME to cancel."
15005 iriverh10,samsungyh*: "Double tap LEFT to cancel."
15006 mpiohd200: "Double tap REC to cancel."
15007 mpiohd300: "Double tap MENU to cancel."
15008 rx27generic: "Press VOLUME to cancel."
15009 touchscreen: "Press Middle Left to cancel."
15010 vibe500: "Press PREV to cancel."
15011 xduoox20,xduoox3,xduoox3ii: "Double tap HOME to cancel."
15012 </voice>
15013</phrase>
15014<phrase>
15015 id: LANG_DATE
15016 desc: for constructing time and date announcements
15017 user: core
15018 <source>
15019 *: "Date"
15020 </source>
15021 <dest>
15022 *: "Tout effacer"
15023 </dest>
15024 <voice>
15025 *: "Tout effacer"
15026 </voice>
15027</phrase>
15028<phrase>
15029 id: LANG_CLEAR_ALL
15030 desc:
15031 user: core
15032 <source>
15033 *: "Clear all"
15034 </source>
15035 <dest>
15036 *: "Clear all"
15037 </dest>
15038 <voice>
15039 *: "Clear all"
15040 </voice>
15041</phrase>
15042<phrase>
15043 id: LANG_CANCEL_0
15044 desc: CANCEL.
15045 user: core
15046 <source>
15047 *: "Cancel"
15048 </source>
15049 <dest>
15050 *: "Annuler"
15051 </dest>
15052 <voice>
15053 *: "Annuler"
15054 </voice>
15055</phrase>
15056<phrase>
15057 id: LANG_SAVE
15058 desc:
15059 user: core
15060 <source>
15061 *: "Save"
15062 </source>
15063 <dest>
15064 *: "Sauvegarder"
15065 </dest>
15066 <voice>
15067 *: "Sauvegarder"
15068 </voice>
15069</phrase>
15070<phrase>
15071 id: LANG_TIMEOUT
15072 desc:
15073 user: core
15074 <source>
15075 *: "Timeout"
15076 </source>
15077 <dest>
15078 *: "Temps expiré"
15079 </dest>
15080 <voice>
15081 *: "Temps expiré"
15082 </voice>
15083</phrase>
15084<phrase>
15085 id: LANG_TRACK
15086 desc: used in track x of y constructs
15087 user: core
15088 <source>
15089 *: "Track"
15090 </source>
15091 <dest>
15092 *: "Piste"
15093 </dest>
15094 <voice>
15095 *: "Piste"
15096 </voice>
15097</phrase>
15098<phrase>
15099 id: LANG_ELAPSED
15100 desc: prefix for elapsed playtime announcement
15101 user: core
15102 <source>
15103 *: "Elapsed"
15104 </source>
15105 <dest>
15106 *: "Écoulé"
15107 </dest>
15108 <voice>
15109 *: "Écoulé"
15110 </voice>
15111</phrase>
15112<phrase>
15113 id: LANG_ANNOUNCEMENT_FMT
15114 desc: format for wps hotkey announcement
15115 user: core
15116 <source>
15117 *: none
15118 hotkey: "Announcement format"
15119 </source>
15120 <dest>
15121 *: none
15122 hotkey: "Format des annonces"
15123 </dest>
15124 <voice>
15125 *: none
15126 hotkey: "Format des annonces"
15127 </voice>
15128</phrase>
15129<phrase>
15130 id: LANG_REMAIN
15131 desc: for constructs such as number of tracks remaining etc
15132 user: core
15133 <source>
15134 *: none
15135 hotkey: "Remain"
15136 </source>
15137 <dest>
15138 *: none
15139 hotkey: "Restant"
15140 </dest>
15141 <voice>
15142 *: none
15143 hotkey: "Restant"
15144 </voice>
15145</phrase>
15146<phrase>
15147 id: LANG_GROUPING
15148 desc:
15149 user: core
15150 <source>
15151 *: none
15152 hotkey: "Grouping"
15153 </source>
15154 <dest>
15155 *: none
15156 hotkey: "Regroupement"
15157 </dest>
15158 <voice>
15159 *: none
15160 hotkey: "Regroupement"
15161 </voice>
15162</phrase>
15163<phrase>
15164 id: LANG_ANNOUNCE_ON
15165 desc:
15166 user: core
15167 <source>
15168 *: none
15169 hotkey: "Announce on"
15170 </source>
15171 <dest>
15172 *: none
15173 hotkey: "Annoncer sur"
15174 </dest>
15175 <voice>
15176 *: none
15177 hotkey: "Annoncer sur"
15178 </voice>
15179</phrase>
15180<phrase>
15181 id: LANG_TRACK_CHANGE
15182 desc:
15183 user: core
15184 <source>
15185 *: none
15186 hotkey: "Track change"
15187 </source>
15188 <dest>
15189 *: none
15190 hotkey: "Changement de piste"
15191 </dest>
15192 <voice>
15193 *: none
15194 hotkey: "Changement de piste"
15195 </voice>
15196</phrase>
15197<phrase>
15198 id: LANG_HOLD_FOR_SETTINGS
15199 desc:
15200 user: core
15201 <source>
15202 *: none
15203 hotkey: "Hold for settings"
15204 </source>
15205 <dest>
15206 *: none
15207 hotkey: "Maintenir pour accéder aux paramètres"
15208 </dest>
15209 <voice>
15210 *: none
15211 hotkey: "Maintenir pour accéder aux paramètres"
15212 </voice>
15213</phrase>
15214<phrase>
15215 id: LANG_OPEN_PLUGIN
15216 desc: onplay open plugin
15217 user: core
15218 <source>
15219 *: "Open Plugin"
15220 </source>
15221 <dest>
15222 *: "Lancer le module"
15223 </dest>
15224 <voice>
15225 *: "Lancer le module"
15226 </voice>
15227</phrase>
15228<phrase>
15229 id: LANG_OPEN_PLUGIN_NOT_A_PLUGIN
15230 desc: open plugin module
15231 user: core
15232 <source>
15233 *: "Not a plugin: %s"
15234 </source>
15235 <dest>
15236 *: "N'est pas un module: %s"
15237 </dest>
15238 <voice>
15239 *: "N'est pas un module: %s"
15240 </voice>
15241</phrase>
15242<phrase>
15243 id: LANG_OPEN_PLUGIN_SET_WPS_CONTEXT_PLUGIN
15244 desc: open plugin module
15245 user: core
15246 <source>
15247 *: "Set Wps Context Plugin"
15248 </source>
15249 <dest>
15250 *: "Set Wps Context Plugin"
15251 </dest>
15252 <voice>
15253 *: "Set WPS Context Plugin"
15254 </voice>
15255</phrase>
15256<phrase>
15257 id: LANG_PARAMETER
15258 desc:
15259 user: core
15260 <source>
15261 *: "Parameter"
15262 </source>
15263 <dest>
15264 *: "Paramètre"
15265 </dest>
15266 <voice>
15267 *: "Paramètre"
15268 </voice>
15269</phrase>
15270<phrase>
15271 id: LANG_NAME
15272 desc:
15273 user: core
15274 <source>
15275 *: "Name"
15276 </source>
15277 <dest>
15278 *: "Nom"
15279 </dest>
15280 <voice>
15281 *: "Nom"
15282 </voice>
15283</phrase>
15284<phrase>
15285 id: LANG_ADD
15286 desc:
15287 user: core
15288 <source>
15289 *: "Add"
15290 </source>
15291 <dest>
15292 *: "Ajouter"
15293 </dest>
15294 <voice>
15295 *: "Ajouter"
15296 </voice>
15297</phrase>
15298<phrase>
15299 id: LANG_BACK
15300 desc:
15301 user: core
15302 <source>
15303 *: "Back"
15304 </source>
15305 <dest>
15306 *: "Revenir"
15307 </dest>
15308 <voice>
15309 *: "Éditer"
15310 </voice>
15311</phrase>
15312<phrase>
15313 id: LANG_EDIT
15314 desc:
15315 user: core
15316 <source>
15317 *: "Edit"
15318 </source>
15319 <dest>
15320 *: "Éditer"
15321 </dest>
15322 <voice>
15323 *: "Éditer"
15324 </voice>
15325</phrase>
15326<phrase>
15327 id: LANG_RUN
15328 desc:
15329 user: core
15330 <source>
15331 *: "Run"
15332 </source>
15333 <dest>
15334 *: "Exécuter"
15335 </dest>
15336 <voice>
15337 *: "Exécuter"
15338 </voice>
15339</phrase>
15340<phrase>
15341 id: LANG_EXPORT
15342 desc:
15343 user: core
15344 <source>
15345 *: "Export"
15346 </source>
15347 <dest>
15348 *: "Exporter"
15349 </dest>
15350 <voice>
15351 *: "Exporter"
15352 </voice>
15353</phrase>
15354<phrase>
15355 id: LANG_BROWSE
15356 desc:
15357 user: core
15358 <source>
15359 *: "Browse"
15360 </source>
15361 <dest>
15362 *: "Parcourir"
15363 </dest>
15364 <voice>
15365 *: "Parcourir"
15366 </voice>
15367</phrase>
15368<phrase>
15369 id: LANG_ENTER_USB_STORAGE_MODE_QUERY
15370 desc: upon plugging in USB
15371 user: core
15372 <source>
15373 *: "Enter USB mass storage mode?"
15374 </source>
15375 <dest>
15376 *: "Passer en mode stockage de masse USB ?"
15377 </dest>
15378 <voice>
15379 *: "Passer en mode stockage de masse USB ?"
15380 </voice>
15381</phrase>
15382<phrase>
15383 id: LANG_QUEUE_MENU
15384 desc: in onplay menu
15385 user: core
15386 <source>
15387 *: "Queue..."
15388 </source>
15389 <dest>
15390 *: "File d'attente"
15391 </dest>
15392 <voice>
15393 *: "File d'attente"
15394 </voice>
15395</phrase>
15396<phrase>
15397 id: LANG_SHOW_QUEUE_OPTIONS
15398 desc: in Current Playlist settings
15399 user: core
15400 <source>
15401 *: "Show Queue Options"
15402 </source>
15403 <dest>
15404 *: "Afficher les options de la file d'attente"
15405 </dest>
15406 <voice>
15407 *: "Afficher les options de la file d'attente"
15408 </voice>
15409</phrase>
15410<phrase>
15411 id: LANG_SHOW_SHUFFLED_ADDING_OPTIONS
15412 desc: in Current Playlist settings
15413 user: core
15414 <source>
15415 *: "Show Shuffled Adding Options"
15416 </source>
15417 <dest>
15418 *: "Afficher les options d'ajout aléatoire"
15419 </dest>
15420 <voice>
15421 *: "Afficher les options d'ajout aléatoire"
15422 </voice>
15423</phrase>
15424<phrase>
15425 id: LANG_IN_SUBMENU
15426 desc: in Settings
15427 user: core
15428 <source>
15429 *: "In Submenu"
15430 </source>
15431 <dest>
15432 *: "En sous-menu"
15433 </dest>
15434 <voice>
15435 *: "En sous-menu"
15436 </voice>
15437</phrase>
15438<phrase>
15439 id: LANG_SOFTLOCK_DISABLE_ALL_NOTIFY
15440 desc: disable all softlock notifications
15441 user: core
15442 <source>
15443 *: "Disable All Lock Notifications"
15444 </source>
15445 <dest>
15446 *: "Désactiver toutes les notifications de verrouillage"
15447 </dest>
15448 <voice>
15449 *: "Désactiver toutes les notifications de verrouillage"
15450 </voice>
15451</phrase>
15452<phrase>
15453 id: LANG_ACTION_VOLUME
15454 desc: exempt volume from softlock
15455 user: core
15456 <source>
15457 *: "Exempt Volume"
15458 </source>
15459 <dest>
15460 *: "À l'exception du volume"
15461 </dest>
15462 <voice>
15463 *: "À l'exception du volume"
15464 </voice>
15465</phrase>
15466<phrase>
15467 id: LANG_ACTION_ALWAYSAUTOLOCK
15468 desc: always prime autolock
15469 user: core
15470 <source>
15471 *: "Always Autolock"
15472 </source>
15473 <dest>
15474 *: "Always Autolock"
15475 </dest>
15476 <voice>
15477 *: "Always Autolock"
15478 </voice>
15479</phrase>
15480<phrase>
15481 id: VOICE_NUMERIC_TENS_SWAP_SEPARATOR
15482 desc: voice only, for speaking numbers in languages that swap the tens and ones fields. Leave blank for languages that do not need it, such as English ("231" => "two hundred thirty one") but other languages may speak it as "two hundred one [AND] thirty"
15483 user: core
15484 <source>
15485 *: ""
15486 </source>
15487 <dest>
15488 *: ""
15489 </dest>
15490 <voice>
15491 *: ""
15492 </voice>
15493</phrase>
15494<phrase>
15495 id: LANG_VOICED_DATE_FORMAT
15496 desc: format string for how dates will be read back. Y == 4-digit year, A == month name, m == numeric month, d == numeric day. For example, "AdY" will read "January 21 2021"
15497 user: core
15498 <source>
15499 *: "dAY"
15500 </source>
15501 <dest>
15502 *: "dAY"
15503 </dest>
15504 <voice>
15505 *: ""
15506 </voice>
15507</phrase>
15508<phrase>
15509 id: LANG_LIST_WRAPAROUND
15510 desc: in Settings
15511 user: core
15512 <source>
15513 *: "List Wraparound"
15514 </source>
15515 <dest>
15516 *: "List Wraparound"
15517 </dest>
15518 <voice>
15519 *: "List Wraparound"
15520 </voice>
15521</phrase>
15522<phrase>
15523 id: LANG_SHOW_SHUTDOWN_MESSAGE
15524 desc: in Settings
15525 user: core
15526 <source>
15527 *: "Show Shutdown Message"
15528 </source>
15529 <dest>
15530 *: "Afficher le message d'arrêt"
15531 </dest>
15532 <voice>
15533 *: "Afficher le message d'arrêt"
15534 </voice>
15535</phrase>
15536<phrase>
15537 id: LANG_LIST_ORDER
15538 desc: in Settings
15539 user: core
15540 <source>
15541 *: "List Order"
15542 </source>
15543 <dest>
15544 *: "Ordre de la liste"
15545 </dest>
15546 <voice>
15547 *: "Ordre de la liste"
15548 </voice>
15549</phrase>
15550<phrase>
15551 id: LANG_ASCENDING
15552 desc: in Settings
15553 user: core
15554 <source>
15555 *: "Ascending"
15556 </source>
15557 <dest>
15558 *: "Croissante"
15559 </dest>
15560 <voice>
15561 *: "Croissante"
15562 </voice>
15563</phrase>
15564<phrase>
15565 id: LANG_DESCENDING
15566 desc: in Settings
15567 user: core
15568 <source>
15569 *: "Descending"
15570 </source>
15571 <dest>
15572 *: "Décroissante"
15573 </dest>
15574 <voice>
15575 *: "Décroissante"
15576 </voice>
15577</phrase>
15578<phrase>
15579 id: LANG_ALBUM_ART
15580 desc: in Settings
15581 user: core
15582 <source>
15583 *: "Album Art"
15584 </source>
15585 <dest>
15586 *: "Pochette d'album"
15587 </dest>
15588 <voice>
15589 *: "Pochette d'album"
15590 </voice>
15591</phrase>
15592<phrase>
15593 id: LANG_PREFER_EMBEDDED
15594 desc: in Settings
15595 user: core
15596 <source>
15597 *: "Prefer Embedded"
15598 </source>
15599 <dest>
15600 *: "Privilégier les images intégrées"
15601 </dest>
15602 <voice>
15603 *: "Privilégier les images intégrées"
15604 </voice>
15605</phrase>
15606<phrase>
15607 id: LANG_PREFER_IMAGE_FILE
15608 desc: in Settings
15609 user: core
15610 <source>
15611 *: "Prefer Image File"
15612 </source>
15613 <dest>
15614 *: "Privilégier les images en fichier"
15615 </dest>
15616 <voice>
15617 *: "Privilégier les images en fichier"
15618 </voice>
15619</phrase>
15620<phrase>
15621 id: LANG_FM_SYNC_RDS_TIME
15622 desc: in radio screen and Settings
15623 user: core
15624 <source>
15625 *: none
15626 rds: "Sync RDS Time"
15627 </source>
15628 <dest>
15629 *: none
15630 rds: "Sync RDS Time"
15631 </dest>
15632 <voice>
15633 *: none
15634 rds: "Sync RDS Time"
15635 </voice>
15636</phrase>
15637<phrase>
15638 id: LANG_SORT_ALBUMS_BY
15639 desc: in Settings
15640 user: core
15641 <source>
15642 *: "Sort albums by"
15643 </source>
15644 <dest>
15645 *: "Trier les albums par"
15646 </dest>
15647 <voice>
15648 *: "Trier les albums par"
15649 </voice>
15650</phrase>
15651<phrase>
15652 id: LANG_ARTIST_PLUS_NAME
15653 desc: in Settings
15654 user: core
15655 <source>
15656 *: "Artist + Name"
15657 </source>
15658 <dest>
15659 *: "Artiste + nom"
15660 </dest>
15661 <voice>
15662 *: "Artiste + nom"
15663 </voice>
15664</phrase>
15665<phrase>
15666 id: LANG_ARTIST_PLUS_YEAR
15667 desc: in Settings
15668 user: core
15669 <source>
15670 *: "Artist + Year"
15671 </source>
15672 <dest>
15673 *: "Artiste + année"
15674 </dest>
15675 <voice>
15676 *: "Artiste + année"
15677 </voice>
15678</phrase>
15679<phrase>
15680 id: LANG_YEAR_SORT_ORDER
15681 desc: in Settings
15682 user: core
15683 <source>
15684 *: "Year sort order"
15685 </source>
15686 <dest>
15687 *: "Order du tri par années"
15688 </dest>
15689 <voice>
15690 *: "Order du tri par années"
15691 </voice>
15692</phrase>
15693<phrase>
15694 id: LANG_SHOW_YEAR_IN_ALBUM_TITLE
15695 desc: in Settings
15696 user: core
15697 <source>
15698 *: "Show year in album title"
15699 </source>
15700 <dest>
15701 *: "Afficher l'année avec le titre de l'album"
15702 </dest>
15703 <voice>
15704 *: "Afficher l'année avec le titre de l'album"
15705 </voice>
15706</phrase>
15707<phrase>
15708 id: LANG_WAIT_FOR_CACHE
15709 desc: in Settings
15710 user: core
15711 <source>
15712 *: "Cache needs to finish updating first!"
15713 </source>
15714 <dest>
15715 *: "La mise à jour du cache doit finir au préalable !"
15716 </dest>
15717 <voice>
15718 *: "La mise à jour du cache doit finir au préalable !"
15719 </voice>
15720</phrase>
15721<phrase>
15722 id: LANG_TRACK_INFO
15723 desc: Track Info Title
15724 user: core
15725 <source>
15726 *: "Track Info"
15727 </source>
15728 <dest>
15729 *: "Informations sur la piste"
15730 </dest>
15731 <voice>
15732 *: "Informations sur la piste"
15733 </voice>
15734</phrase>
15735<phrase>
15736 id: LANG_PLAY
15737 desc: play selected file/directory, in playlist context menu
15738 user: core
15739 <source>
15740 *: "Play"
15741 </source>
15742 <dest>
15743 *: "Lire"
15744 </dest>
15745 <voice>
15746 *: "Lire"
15747 </voice>
15748</phrase>
15749<phrase>
15750 id: LANG_PLAY_SHUFFLED
15751 desc: play selected files in shuffled order, in playlist context menu
15752 user: core
15753 <source>
15754 *: "Play Shuffled"
15755 </source>
15756 <dest>
15757 *: "Lire mélangé"
15758 </dest>
15759 <voice>
15760 *: "Lire mélangé"
15761 </voice>
15762</phrase>
15763<phrase>
15764 id: LANG_KEEP_CURRENT_TRACK_ON_REPLACE
15765 desc: used in the playlist settings menu
15766 user: core
15767 <source>
15768 *: "Keep Current Track When Replacing Playlist"
15769 </source>
15770 <dest>
15771 *: "Garder la piste courante lors du remplacement de la liste de lecture"
15772 </dest>
15773 <voice>
15774 *: "Garder la piste courante lors du remplacement de la liste de lecture"
15775 </voice>
15776</phrase>
15777<phrase>
15778 id: LANG_CLEAR_SETTINGS_ON_HOLD
15779 desc: in the system sub menu
15780 user: core
15781 <source>
15782 *: none
15783 clear_settings_on_hold,iriverh10: "Clear settings when reset button is held during startup"
15784 ipod4g,ipodcolor,ipodmini1g,ipodmini2g,ipodnano1g,ipodvideo: "Clear settings when hold switch is on during startup"
15785 </source>
15786 <dest>
15787 *: none
15788 clear_settings_on_hold,iriverh10,ipod4g,ipodcolor,ipodmini1g,ipodmini2g,ipodnano1g,ipodvideo: "Effacer les paramètres lorsque le bouton de réinitialisation est maintenu enfoncé pendant le démarrage"
15789 </dest>
15790 <voice>
15791 *: none
15792 clear_settings_on_hold,iriverh10,ipod4g,ipodcolor,ipodmini1g,ipodmini2g,ipodnano1g,ipodvideo: "Effacer les paramètres lorsque le bouton de réinitialisation est maintenu enfoncé pendant le démarrage"
15793 </voice>
15794</phrase>
15795<phrase>
15796 id: LANG_REWIND_ACROSS_TRACKS
15797 desc: in playback settings menu
15798 user: core
15799 <source>
15800 *: "Rewind Across Tracks"
15801 </source>
15802 <dest>
15803 *: "Rembobiner entre les pistes"
15804 </dest>
15805 <voice>
15806 *: "Rembobiner entre les pistes"
15807 </voice>
15808</phrase>
15809<phrase>
15810 id: LANG_SET_AS
15811 desc: used in the onplay menu
15812 user: core
15813 <source>
15814 *: "Set As..."
15815 </source>
15816 <dest>
15817 *: "Définir comme..."
15818 </dest>
15819 <voice>
15820 *: "Définir comme"
15821 </voice>
15822</phrase>
15823<phrase>
15824 id: LANG_PLAYLIST_DIR
15825 desc: used in the onplay menu
15826 user: core
15827 <source>
15828 *: "Playlist Directory"
15829 </source>
15830 <dest>
15831 *: "Répertoire des listes de lecture"
15832 </dest>
15833 <voice>
15834 *: "Répertoire des listes de lecture"
15835 </voice>
15836</phrase>
15837<phrase>
15838 id: LANG_START_DIR
15839 desc: used in the onplay menu
15840 user: core
15841 <source>
15842 *: "Start Directory"
15843 </source>
15844 <dest>
15845 *: "Répertoire de démarrage"
15846 </dest>
15847 <voice>
15848 *: "Répertoire de démarrage"
15849 </voice>
15850</phrase>
15851<phrase>
15852 id: LANG_RECORDING_DIR
15853 desc: used in the onplay menu
15854 user: core
15855 <source>
15856 *: none
15857 recording: "Recording Directory"
15858 </source>
15859 <dest>
15860 *: none
15861 recording: "Répertoire des enregistrements"
15862 </dest>
15863 <voice>
15864 *: none
15865 recording: "Répertoire des enregistrements"
15866 </voice>
15867</phrase>
15868<phrase>
15869 id: LANG_ADD_TO_PL
15870 desc: used in the onplay menu
15871 user: core
15872 <source>
15873 *: "Add to Playlist..."
15874 </source>
15875 <dest>
15876 *: "Ajouter à la liste de lecture"
15877 </dest>
15878 <voice>
15879 *: "Ajouter à la liste de lecture"
15880 </voice>
15881</phrase>
15882<phrase>
15883 id: LANG_ADD_TO_EXISTING_PL
15884 desc: used in the onplay menu
15885 user: core
15886 <source>
15887 *: "Add to Existing Playlist"
15888 </source>
15889 <dest>
15890 *: "Ajouter à la liste de lecture existante"
15891 </dest>
15892 <voice>
15893 *: "Ajouter à la liste de lecture existante"
15894 </voice>
15895</phrase>
15896<phrase>
15897 id: LANG_PLAYING_NEXT
15898 desc: used in the onplay menu
15899 user: core
15900 <source>
15901 *: "Playing Next..."
15902 </source>
15903 <dest>
15904 *: "Ajouter à la suite..."
15905 </dest>
15906 <voice>
15907 *: "Ajouter à la suite"
15908 </voice>
15909</phrase>
15910<phrase>
15911 id: LANG_PLAY_NEXT
15912 desc: used in the onplay menu
15913 user: core
15914 <source>
15915 *: "Play Next"
15916 </source>
15917 <dest>
15918 *: "Ajouter à la suite"
15919 </dest>
15920 <voice>
15921 *: "Ajouter à la suite"
15922 </voice>
15923</phrase>
15924<phrase>
15925 id: LANG_ADD_SHUFFLED
15926 desc: used in the onplay menu
15927 user: core
15928 <source>
15929 *: "Add Shuffled"
15930 </source>
15931 <dest>
15932 *: "Ajouter mélangé"
15933 </dest>
15934 <voice>
15935 *: "Ajouter mélangé"
15936 </voice>
15937</phrase>
15938<phrase>
15939 id: LANG_PLAY_LAST
15940 desc: used in the onplay menu
15941 user: core
15942 <source>
15943 *: "Play Last"
15944 </source>
15945 <dest>
15946 *: "Lire en dernier"
15947 </dest>
15948 <voice>
15949 *: "Lire en dernier"
15950 </voice>
15951</phrase>
15952<phrase>
15953 id: LANG_PLAY_LAST_SHUFFLED
15954 desc: used in the onplay menu
15955 user: core
15956 <source>
15957 *: "Play Last Shuffled"
15958 </source>
15959 <dest>
15960 *: "Lire en dernier mélangé"
15961 </dest>
15962 <voice>
15963 *: "Lire en dernier mélangé"
15964 </voice>
15965</phrase>
15966<phrase>
15967 id: LANG_VOLUME_ADJUST_MODE
15968 desc: in system settings
15969 user: core
15970 <source>
15971 *: none
15972 perceptual_volume: "Volume Adjustment Mode"
15973 </source>
15974 <dest>
15975 *: none
15976 perceptual_volume: "Méthode d'ajustement du volume"
15977 </dest>
15978 <voice>
15979 *: none
15980 perceptual_volume: "Méthode d'ajustement du volume"
15981 </voice>
15982</phrase>
15983<phrase>
15984 id: LANG_VOLUME_ADJUST_NORM_STEPS
15985 desc: in system settings
15986 user: core
15987 <source>
15988 *: none
15989 perceptual_volume: "Number of Volume Steps"
15990 </source>
15991 <dest>
15992 *: none
15993 perceptual_volume: "Nombre de niveaux de volume"
15994 </dest>
15995 <voice>
15996 *: none
15997 perceptual_volume: "Nombre de niveaux de volume"
15998 </voice>
15999</phrase>
16000<phrase>
16001 id: LANG_PERCEPTUAL
16002 desc: in system settings -> volume adjustment mode
16003 user: core
16004 <source>
16005 *: none
16006 perceptual_volume: "Perceptual"
16007 </source>
16008 <dest>
16009 *: none
16010 perceptual_volume: "Perceptual"
16011 </dest>
16012 <voice>
16013 *: none
16014 perceptual_volume: "Perceptual"
16015 </voice>
16016</phrase>
16017<phrase>
16018 id: LANG_SHOW_TRACKS_WHILE_BROWSING
16019 desc: in PictureFlow Main Menu
16020 user: core
16021 <source>
16022 *: "Show Tracks While Browsing"
16023 </source>
16024 <dest>
16025 *: "Afficher les pistes lors de la navigation"
16026 </dest>
16027 <voice>
16028 *: "Afficher les pistes lors de la navigation"
16029 </voice>
16030</phrase>
16031<phrase>
16032 id: LANG_GOTO_LAST_ALBUM
16033 desc: in PictureFlow Main Menu
16034 user: core
16035 <source>
16036 *: "Go to Last Album"
16037 </source>
16038 <dest>
16039 *: "Aller au dernier album"
16040 </dest>
16041 <voice>
16042 *: "Aller au dernier album"
16043 </voice>
16044</phrase>
16045<phrase>
16046 id: LANG_DATABASE_DIR
16047 desc: in database settings menu
16048 user: core
16049 <source>
16050 *: "Database Directory"
16051 </source>
16052 <dest>
16053 *: "Répertoire de la base de données"
16054 </dest>
16055 <voice>
16056 *: "Répertoire de la base de données"
16057 </voice>
16058</phrase>
16059<phrase>
16060 id: LANG_REMOVE_QUEUED_TRACKS
16061 desc: Confirmation dialog
16062 user: core
16063 <source>
16064 *: "Remove Queued Tracks?"
16065 </source>
16066 <dest>
16067 *: "Retirer les pistes en file attente ?"
16068 </dest>
16069 <voice>
16070 *: "Retirer les pistes en file attente ?"
16071 </voice>
16072</phrase>
16073<phrase>
16074 id: LANG_QUICK_IGNORE_DIRACHE
16075 desc: in Settings
16076 user: core
16077 <source>
16078 *: "Quick (Ignore Directory Cache)"
16079 </source>
16080 <dest>
16081 *: "Rapide (Ignorer le cache des répertoires)"
16082 </dest>
16083 <voice>
16084 *: "Rapide (Ignorer le cache des répertoires)"
16085 </voice>
16086</phrase>
16087<phrase>
16088 id: LANG_WPS
16089 desc: in Settings
16090 user: core
16091 <source>
16092 *: "What's Playing Screen"
16093 </source>
16094 <dest>
16095 *: "Écran En Lecture"
16096 </dest>
16097 <voice>
16098 *: "Écran En Lecture"
16099 </voice>
16100</phrase>
16101<phrase>
16102 id: LANG_DEFAULT_BROWSER
16103 desc: in Settings
16104 user: core
16105 <source>
16106 *: "Default Browser"
16107 </source>
16108 <dest>
16109 *: "Navigateur par défaut"
16110 </dest>
16111 <voice>
16112 *: "Navigateur par défaut"
16113 </voice>
16114</phrase>
16115<phrase>
16116 id: LANG_AMAZE_MENU
16117 desc: Amaze game
16118 user: core
16119 <source>
16120 *: "Amaze Main Menu"
16121 </source>
16122 <dest>
16123 *: "Amaze Main Menu"
16124 </dest>
16125 <voice>
16126 *: "Amaze Main Menu"
16127 </voice>
16128</phrase>
16129<phrase>
16130 id: LANG_SET_MAZE_SIZE
16131 desc: Maze size in Amaze game
16132 user: core
16133 <source>
16134 *: "Set Maze Size"
16135 </source>
16136 <dest>
16137 *: "Set Maze Size"
16138 </dest>
16139 <voice>
16140 *: "Set Maze Size"
16141 </voice>
16142</phrase>
16143<phrase>
16144 id: LANG_VIEW_MAP
16145 desc: Map in Amaze game
16146 user: core
16147 <source>
16148 *: "View Map"
16149 </source>
16150 <dest>
16151 *: "View Map"
16152 </dest>
16153 <voice>
16154 *: "View Map"
16155 </voice>
16156</phrase>
16157<phrase>
16158 id: LANG_SHOW_COMPASS
16159 desc: Compass in Amaze game
16160 user: core
16161 <source>
16162 *: "Show Compass"
16163 </source>
16164 <dest>
16165 *: "Show Compass"
16166 </dest>
16167 <voice>
16168 *: "Show Compass"
16169 </voice>
16170</phrase>
16171<phrase>
16172 id: LANG_SHOW_MAP
16173 desc: Map in Amaze game
16174 user: core
16175 <source>
16176 *: "Show Map"
16177 </source>
16178 <dest>
16179 *: "Show Map"
16180 </dest>
16181 <voice>
16182 *: "Show Map"
16183 </voice>
16184</phrase>
16185<phrase>
16186 id: LANG_REMEMBER_PATH
16187 desc: Map in Amaze game
16188 user: core
16189 <source>
16190 *: "Remember Path"
16191 </source>
16192 <dest>
16193 *: "Remember Path"
16194 </dest>
16195 <voice>
16196 *: "Remember Path"
16197 </voice>
16198</phrase>
16199<phrase>
16200 id: LANG_USE_LARGE_TILES
16201 desc: Map in Amaze game
16202 user: core
16203 <source>
16204 *: "Use Large Tiles"
16205 </source>
16206 <dest>
16207 *: "Use Large Tiles"
16208 </dest>
16209 <voice>
16210 *: "Use Large Tiles"
16211 </voice>
16212</phrase>
16213<phrase>
16214 id: LANG_SHOW_SOLUTION
16215 desc: Map in Amaze game
16216 user: core
16217 <source>
16218 *: "Show Solution"
16219 </source>
16220 <dest>
16221 *: "Show Solution"
16222 </dest>
16223 <voice>
16224 *: "Show Solution"
16225 </voice>
16226</phrase>
16227<phrase>
16228 id: LANG_QUIT_WITHOUT_SAVING
16229 desc:
16230 user: core
16231 <source>
16232 *: "Quit without saving"
16233 </source>
16234 <dest>
16235 *: "Quit without saving"
16236 </dest>
16237 <voice>
16238 *: "Quit without saving"
16239 </voice>
16240</phrase>
16241<phrase>
16242 id: LANG_GENERATING_MAZE
16243 desc: Amaze game
16244 user: core
16245 <source>
16246 *: "Generating maze..."
16247 </source>
16248 <dest>
16249 *: "Generating maze..."
16250 </dest>
16251 <voice>
16252 *: "Generating maze"
16253 </voice>
16254</phrase>
16255<phrase>
16256 id: LANG_YOU_WIN
16257 desc: Success in game
16258 user: core
16259 <source>
16260 *: "You win!"
16261 </source>
16262 <dest>
16263 *: "You win!"
16264 </dest>
16265 <voice>
16266 *: "You win!"
16267 </voice>
16268</phrase>
16269<phrase>
16270 id: LANG_YOU_CHEATED
16271 desc: Cheated in game
16272 user: core
16273 <source>
16274 *: "You cheated!"
16275 </source>
16276 <dest>
16277 *: "You cheated!"
16278 </dest>
16279 <voice>
16280 *: "You cheated!"
16281 </voice>
16282</phrase>
16283<phrase>
16284 id: LANG_DIFFICULTY_EASY
16285 desc: Game difficulty
16286 user: core
16287 <source>
16288 *: "Easy"
16289 </source>
16290 <dest>
16291 *: "Easy"
16292 </dest>
16293 <voice>
16294 *: "Easy"
16295 </voice>
16296</phrase>
16297<phrase>
16298 id: LANG_DIFFICULTY_MEDIUM
16299 desc: Game difficulty
16300 user: core
16301 <source>
16302 *: "Medium"
16303 </source>
16304 <dest>
16305 *: "Medium"
16306 </dest>
16307 <voice>
16308 *: "Medium"
16309 </voice>
16310</phrase>
16311<phrase>
16312 id: LANG_DIFFICULTY_HARD
16313 desc: Game difficulty
16314 user: core
16315 <source>
16316 *: "Hard"
16317 </source>
16318 <dest>
16319 *: "Hard"
16320 </dest>
16321 <voice>
16322 *: "Hard"
16323 </voice>
16324</phrase>
16325<phrase>
16326 id: LANG_DIFFICULTY_EXPERT
16327 desc: Game difficulty
16328 user: core
16329 <source>
16330 *: "Expert"
16331 </source>
16332 <dest>
16333 *: "Expert"
16334 </dest>
16335 <voice>
16336 *: "Expert"
16337 </voice>
16338</phrase>
16339<phrase>
16340 id: LANG_STEREOSW_MODE
16341 desc: Stereo Switch Mode
16342 user: core
16343 <source>
16344 *: "Stereo Switch Mode"
16345 </source>
16346 <dest>
16347 *: "Stereo Switch Mode"
16348 </dest>
16349 <voice>
16350 *: "Stereo Switch Mode"
16351 </voice>
16352</phrase>
16353<phrase>
16354 id: LANG_REVERSE
16355 desc: in settings_menu
16356 user: core
16357 <source>
16358 *: "Reverse"
16359 </source>
16360 <dest>
16361 *: "Inversé"
16362 </dest>
16363 <voice>
16364 *: "Inversé"
16365 </voice>
16366</phrase>
16367<phrase>
16368 id: LANG_ALWAYS_ZERO
16369 desc: in settings_menu
16370 user: core
16371 <source>
16372 *: "Always 0"
16373 </source>
16374 <dest>
16375 *: "Toujours 0"
16376 </dest>
16377 <voice>
16378 *: "Toujours 0"
16379 </voice>
16380</phrase>
16381<phrase>
16382 id: LANG_ALWAYS_ONE
16383 desc: in settings_menu
16384 user: core
16385 <source>
16386 *: "Always 1"
16387 </source>
16388 <dest>
16389 *: "Toujours 1"
16390 </dest>
16391 <voice>
16392 *: "Toujours 1"
16393 </voice>
16394</phrase>
16395<phrase>
16396 id: LANG_LEGAL_NOTICES
16397 desc: in system menu
16398 user: core
16399 <source>
16400 *: "Legal Notices"
16401 </source>
16402 <dest>
16403 *: "Mentions Légales"
16404 </dest>
16405 <voice>
16406 *: "Mentions Légales"
16407 </voice>
16408</phrase>
16409<phrase>
16410 id: LANG_ERROR_FORMATSTR
16411 desc: for general use
16412 user: core
16413 <source>
16414 *: "Error: %s"
16415 </source>
16416 <dest>
16417 *: "Erreur: %s"
16418 </dest>
16419 <voice>
16420 *: "Erreur"
16421 </voice>
16422</phrase>
16423<phrase>
16424 id: LANG_MIKMOD_SETTINGS
16425 desc: mikmod plugin
16426 user: core
16427 <source>
16428 *: "Mikmod Settings"
16429 </source>
16430 <dest>
16431 *: "Mikmod Settings"
16432 </dest>
16433 <voice>
16434 *: "Mik mod Settings"
16435 </voice>
16436</phrase>
16437<phrase>
16438 id: LANG_MIKMOD_MENU
16439 desc: mikmod plugin
16440 user: core
16441 <source>
16442 *: "Mikmod Menu"
16443 </source>
16444 <dest>
16445 *: "Mikmod Menu"
16446 </dest>
16447 <voice>
16448 *: "Mik mod Menu"
16449 </voice>
16450</phrase>
16451<phrase>
16452 id: LANG_CHESSBOX_MENU
16453 desc: chessbox plugin
16454 user: core
16455 <source>
16456 *: "Chessbox Menu"
16457 </source>
16458 <dest>
16459 *: "Chessbox Menu"
16460 </dest>
16461 <voice>
16462 *: "Chess box Menu"
16463 </voice>
16464</phrase>
16465<phrase>
16466 id: VOICE_INVALID_VOICE_FILE
16467 desc: played if the voice file fails to load
16468 user: core
16469 <source>
16470 *: ""
16471 </source>
16472 <dest>
16473 *: ""
16474 </dest>
16475 <voice>
16476 *: "Fichier Voix non valide"
16477 </voice>
16478</phrase>
16479<phrase>
16480 id: LANG_PERCENT_FORMAT
16481 desc: percent formatting ( `10%` is default , for `10 %` use '%ld %%' , for `%10` use '%%%ld' and so on)
16482 user: core
16483 <source>
16484 *: "%ld%%"
16485 </source>
16486 <dest>
16487 *: "%ld%%"
16488 </dest>
16489 <voice>
16490 *: none
16491 </voice>
16492</phrase>
16493<phrase>
16494 id: LANG_CHOOSE_FILE
16495 desc: file_picker plugin ask user to select a file
16496 user: core
16497 <source>
16498 *: "Choose File"
16499 </source>
16500 <dest>
16501 *: "Choisir un fichier"
16502 </dest>
16503 <voice>
16504 *: "Choisir un fichier"
16505 </voice>
16506</phrase>
16507<phrase>
16508 id: LANG_REMAINING
16509 desc: Playing Time
16510 user: core
16511 <source>
16512 *: "Remaining"
16513 </source>
16514 <dest>
16515 *: "Restant"
16516 </dest>
16517 <voice>
16518 *: "Restant"
16519 </voice>
16520</phrase>
diff --git a/apps/lang/galego.lang b/apps/lang/galego.lang
index 958cfd2b2f..197f69aa56 100644
--- a/apps/lang/galego.lang
+++ b/apps/lang/galego.lang
@@ -3375,10 +3375,10 @@
3375 desc: in tag viewer 3375 desc: in tag viewer
3376 user: core 3376 user: core
3377 <source> 3377 <source>
3378 *: "<No Info>" 3378 *: "[No Info]"
3379 </source> 3379 </source>
3380 <dest> 3380 <dest>
3381 *: "<Sen info>" 3381 *: "[Sen info]"
3382 </dest> 3382 </dest>
3383 <voice> 3383 <voice>
3384 *: "Sen información" 3384 *: "Sen información"
@@ -6166,13 +6166,13 @@
6166</phrase> 6166</phrase>
6167<phrase> 6167<phrase>
6168 id: LANG_TAGNAVI_ALL_TRACKS 6168 id: LANG_TAGNAVI_ALL_TRACKS
6169 desc: "<All tracks>" entry in tag browser 6169 desc: "[All tracks]" entry in tag browser
6170 user: core 6170 user: core
6171 <source> 6171 <source>
6172 *: "<All tracks>" 6172 *: "[All tracks]"
6173 </source> 6173 </source>
6174 <dest> 6174 <dest>
6175 *: "<Todas as pistas>" 6175 *: "[Todas as pistas]"
6176 </dest> 6176 </dest>
6177 <voice> 6177 <voice>
6178 *: "Todas as pistas" 6178 *: "Todas as pistas"
@@ -7898,13 +7898,13 @@
7898</phrase> 7898</phrase>
7899<phrase> 7899<phrase>
7900 id: LANG_TAGNAVI_RANDOM 7900 id: LANG_TAGNAVI_RANDOM
7901 desc: "<Random>" entry in tag browser 7901 desc: "[Random]" entry in tag browser
7902 user: core 7902 user: core
7903 <source> 7903 <source>
7904 *: "<Random>" 7904 *: "[Random]"
7905 </source> 7905 </source>
7906 <dest> 7906 <dest>
7907 *: "<Aleatorio>" 7907 *: "[Aleatorio]"
7908 </dest> 7908 </dest>
7909 <voice> 7909 <voice>
7910 *: "Aleatorio" 7910 *: "Aleatorio"
@@ -9033,13 +9033,13 @@
9033</phrase> 9033</phrase>
9034<phrase> 9034<phrase>
9035 id: LANG_TAGNAVI_UNTAGGED 9035 id: LANG_TAGNAVI_UNTAGGED
9036 desc: "<untagged>" entry in tag browser 9036 desc: "[untagged]" entry in tag browser
9037 user: core 9037 user: core
9038 <source> 9038 <source>
9039 *: "<Untagged>" 9039 *: "[Untagged]"
9040 </source> 9040 </source>
9041 <dest> 9041 <dest>
9042 *: "<Sen datos>" 9042 *: "[Sen datos]"
9043 </dest> 9043 </dest>
9044 <voice> 9044 <voice>
9045 *: "Sen datos" 9045 *: "Sen datos"
@@ -10605,10 +10605,10 @@
10605 desc: top item in the list when asking user about bookmark auto load 10605 desc: top item in the list when asking user about bookmark auto load
10606 user: core 10606 user: core
10607 <source> 10607 <source>
10608 *: "<Don't Resume>" 10608 *: "[Don't Resume]"
10609 </source> 10609 </source>
10610 <dest> 10610 <dest>
10611 *: "<Non retomar>" 10611 *: "[Non retomar]"
10612 </dest> 10612 </dest>
10613 <voice> 10613 <voice>
10614 *: "Non retomar" 10614 *: "Non retomar"
@@ -11091,10 +11091,10 @@
11091 desc: bookmark selection list, bookmark couldn't be parsed 11091 desc: bookmark selection list, bookmark couldn't be parsed
11092 user: core 11092 user: core
11093 <source> 11093 <source>
11094 *: "<Invalid Bookmark>" 11094 *: "[Invalid Bookmark]"
11095 </source> 11095 </source>
11096 <dest> 11096 <dest>
11097 *: "<Marcador inválido>" 11097 *: "[Marcador inválido]"
11098 </dest> 11098 </dest>
11099 <voice> 11099 <voice>
11100 *: "Marcador inválido" 11100 *: "Marcador inválido"
diff --git a/apps/lang/greek.lang b/apps/lang/greek.lang
index 2006801a47..8ceb26d5c3 100644
--- a/apps/lang/greek.lang
+++ b/apps/lang/greek.lang
@@ -482,10 +482,10 @@
482 desc: top item in the list when asking user about bookmark auto load 482 desc: top item in the list when asking user about bookmark auto load
483 user: core 483 user: core
484 <source> 484 <source>
485 *: "<Don't Resume>" 485 *: "[Don't Resume]"
486 </source> 486 </source>
487 <dest> 487 <dest>
488 *: "<ΧωÏίς συνέχιση>" 488 *: "[ΧωÏίς συνέχιση]"
489 </dest> 489 </dest>
490 <voice> 490 <voice>
491 *: "ΧωÏίς συνέχιση" 491 *: "ΧωÏίς συνέχιση"
@@ -510,10 +510,10 @@
510 desc: bookmark selection list, bookmark couldn't be parsed 510 desc: bookmark selection list, bookmark couldn't be parsed
511 user: core 511 user: core
512 <source> 512 <source>
513 *: "<Invalid Bookmark>" 513 *: "[Invalid Bookmark]"
514 </source> 514 </source>
515 <dest> 515 <dest>
516 *: "<Μη έγκυÏος σελιδοδείκτης>" 516 *: "[Μη έγκυÏος σελιδοδείκτης]"
517 </dest> 517 </dest>
518 <voice> 518 <voice>
519 *: "Μη έγκυÏος σελιδοδείκτης" 519 *: "Μη έγκυÏος σελιδοδείκτης"
@@ -2286,13 +2286,13 @@
2286</phrase> 2286</phrase>
2287<phrase> 2287<phrase>
2288 id: LANG_TAGNAVI_ALL_TRACKS 2288 id: LANG_TAGNAVI_ALL_TRACKS
2289 desc: "<All tracks>" entry in tag browser 2289 desc: "[All tracks]" entry in tag browser
2290 user: core 2290 user: core
2291 <source> 2291 <source>
2292 *: "<All tracks>" 2292 *: "[All tracks]"
2293 </source> 2293 </source>
2294 <dest> 2294 <dest>
2295 *: "<Όλα τα κομμάτια>" 2295 *: "[Όλα τα κομμάτια]"
2296 </dest> 2296 </dest>
2297 <voice> 2297 <voice>
2298 *: "Όλα τα κομμάτια" 2298 *: "Όλα τα κομμάτια"
@@ -7100,10 +7100,10 @@
7100 desc: in tag viewer 7100 desc: in tag viewer
7101 user: core 7101 user: core
7102 <source> 7102 <source>
7103 *: "<No Info>" 7103 *: "[No Info]"
7104 </source> 7104 </source>
7105 <dest> 7105 <dest>
7106 *: "<ΧωÏίς πληÏοφοÏίες>" 7106 *: "[ΧωÏίς πληÏοφοÏίες]"
7107 </dest> 7107 </dest>
7108 <voice> 7108 <voice>
7109 *: "ΧωÏίς πληÏοφοÏίες" 7109 *: "ΧωÏίς πληÏοφοÏίες"
@@ -10177,13 +10177,13 @@
10177</phrase> 10177</phrase>
10178<phrase> 10178<phrase>
10179 id: LANG_TAGNAVI_RANDOM 10179 id: LANG_TAGNAVI_RANDOM
10180 desc: "<Random>" entry in tag browser 10180 desc: "[Random]" entry in tag browser
10181 user: core 10181 user: core
10182 <source> 10182 <source>
10183 *: "<Random>" 10183 *: "[Random]"
10184 </source> 10184 </source>
10185 <dest> 10185 <dest>
10186 *: "<Τυχαία>" 10186 *: "[Τυχαία]"
10187 </dest> 10187 </dest>
10188 <voice> 10188 <voice>
10189 *: "Τυχαία" 10189 *: "Τυχαία"
@@ -11478,13 +11478,13 @@
11478</phrase> 11478</phrase>
11479<phrase> 11479<phrase>
11480 id: LANG_TAGNAVI_UNTAGGED 11480 id: LANG_TAGNAVI_UNTAGGED
11481 desc: "<untagged>" entry in tag browser 11481 desc: "[untagged]" entry in tag browser
11482 user: core 11482 user: core
11483 <source> 11483 <source>
11484 *: "<Untagged>" 11484 *: "[Untagged]"
11485 </source> 11485 </source>
11486 <dest> 11486 <dest>
11487 *: "<ΧωÏίς ετικέτα>" 11487 *: "[ΧωÏίς ετικέτα]"
11488 </dest> 11488 </dest>
11489 <voice> 11489 <voice>
11490 *: "ΧωÏίς ετικέτα" 11490 *: "ΧωÏίς ετικέτα"
diff --git a/apps/lang/hebrew.lang b/apps/lang/hebrew.lang
index 272676a62e..e5576bea23 100644
--- a/apps/lang/hebrew.lang
+++ b/apps/lang/hebrew.lang
@@ -482,10 +482,10 @@
482 desc: top item in the list when asking user about bookmark auto load 482 desc: top item in the list when asking user about bookmark auto load
483 user: core 483 user: core
484 <source> 484 <source>
485 *: "<Don't Resume>" 485 *: "[Don't Resume]"
486 </source> 486 </source>
487 <dest> 487 <dest>
488 *: "<×ל תמשיך>" 488 *: "[×ל תמשיך]"
489 </dest> 489 </dest>
490 <voice> 490 <voice>
491 *: "×ל תמשיך" 491 *: "×ל תמשיך"
@@ -510,10 +510,10 @@
510 desc: bookmark selection list, bookmark couldn't be parsed 510 desc: bookmark selection list, bookmark couldn't be parsed
511 user: core 511 user: core
512 <source> 512 <source>
513 *: "<Invalid Bookmark>" 513 *: "[Invalid Bookmark]"
514 </source> 514 </source>
515 <dest> 515 <dest>
516 *: "<סימניה פגומה>" 516 *: "[סימניה פגומה]"
517 </dest> 517 </dest>
518 <voice> 518 <voice>
519 *: "סימניה פגומה" 519 *: "סימניה פגומה"
@@ -2332,13 +2332,13 @@
2332</phrase> 2332</phrase>
2333<phrase> 2333<phrase>
2334 id: LANG_TAGNAVI_ALL_TRACKS 2334 id: LANG_TAGNAVI_ALL_TRACKS
2335 desc: "<All tracks>" entry in tag browser 2335 desc: "[All tracks]" entry in tag browser
2336 user: core 2336 user: core
2337 <source> 2337 <source>
2338 *: "<All tracks>" 2338 *: "[All tracks]"
2339 </source> 2339 </source>
2340 <dest> 2340 <dest>
2341 *: "<כל השירי×>" 2341 *: "[כל השירי×]"
2342 </dest> 2342 </dest>
2343 <voice> 2343 <voice>
2344 *: "כל השירי×" 2344 *: "כל השירי×"
@@ -7179,10 +7179,10 @@
7179 desc: in tag viewer 7179 desc: in tag viewer
7180 user: core 7180 user: core
7181 <source> 7181 <source>
7182 *: "<No Info>" 7182 *: "[No Info]"
7183 </source> 7183 </source>
7184 <dest> 7184 <dest>
7185 *: "<×ין מידע>" 7185 *: "[×ין מידע]"
7186 </dest> 7186 </dest>
7187 <voice> 7187 <voice>
7188 *: "×ין מידע" 7188 *: "×ין מידע"
@@ -10205,13 +10205,13 @@
10205</phrase> 10205</phrase>
10206<phrase> 10206<phrase>
10207 id: LANG_TAGNAVI_RANDOM 10207 id: LANG_TAGNAVI_RANDOM
10208 desc: "<Random>" entry in tag browser 10208 desc: "[Random]" entry in tag browser
10209 user: core 10209 user: core
10210 <source> 10210 <source>
10211 *: "<Random>" 10211 *: "[Random]"
10212 </source> 10212 </source>
10213 <dest> 10213 <dest>
10214 *: "<×קר××™>" 10214 *: "[×קר××™]"
10215 </dest> 10215 </dest>
10216 <voice> 10216 <voice>
10217 *: "×קר××™" 10217 *: "×קר××™"
@@ -11961,13 +11961,13 @@
11961</phrase> 11961</phrase>
11962<phrase> 11962<phrase>
11963 id: LANG_TAGNAVI_UNTAGGED 11963 id: LANG_TAGNAVI_UNTAGGED
11964 desc: "<untagged>" entry in tag browser 11964 desc: "[untagged]" entry in tag browser
11965 user: core 11965 user: core
11966 <source> 11966 <source>
11967 *: "<Untagged>" 11967 *: "[Untagged]"
11968 </source> 11968 </source>
11969 <dest> 11969 <dest>
11970 *: "<Untagged>" 11970 *: "[Untagged]"
11971 </dest> 11971 </dest>
11972 <voice> 11972 <voice>
11973 *: "Untagged" 11973 *: "Untagged"
diff --git a/apps/lang/hindi.lang b/apps/lang/hindi.lang
index 0bd1429ca8..a8a6f76bd5 100644
--- a/apps/lang/hindi.lang
+++ b/apps/lang/hindi.lang
@@ -444,10 +444,10 @@
444 desc: top item in the list when asking user about bookmark auto load 444 desc: top item in the list when asking user about bookmark auto load
445 user: core 445 user: core
446 <source> 446 <source>
447 *: "<Don't Resume>" 447 *: "[Don't Resume]"
448 </source> 448 </source>
449 <dest> 449 <dest>
450 *: "<शà¥à¤°à¥‚ मत कर>" 450 *: "[शà¥à¤°à¥‚ मत कर]"
451 </dest> 451 </dest>
452 <voice> 452 <voice>
453 *: "शà¥à¤°à¥‚ मत कर" 453 *: "शà¥à¤°à¥‚ मत कर"
@@ -472,10 +472,10 @@
472 desc: bookmark selection list, bookmark couldn't be parsed 472 desc: bookmark selection list, bookmark couldn't be parsed
473 user: core 473 user: core
474 <source> 474 <source>
475 *: "<Invalid Bookmark>" 475 *: "[Invalid Bookmark]"
476 </source> 476 </source>
477 <dest> 477 <dest>
478 *: "<गलत बूकमारक>" 478 *: "[गलत बूकमारक]"
479 </dest> 479 </dest>
480 <voice> 480 <voice>
481 *: "गलत बूकमारक" 481 *: "गलत बूकमारक"
@@ -1252,13 +1252,13 @@
1252</phrase> 1252</phrase>
1253<phrase> 1253<phrase>
1254 id: LANG_TAGNAVI_ALL_TRACKS 1254 id: LANG_TAGNAVI_ALL_TRACKS
1255 desc: "<All tracks>" entry in tag browser 1255 desc: "[All tracks]" entry in tag browser
1256 user: core 1256 user: core
1257 <source> 1257 <source>
1258 *: "<All tracks>" 1258 *: "[All tracks]"
1259 </source> 1259 </source>
1260 <dest> 1260 <dest>
1261 *: "<सब tracks>" 1261 *: "[सब tracks]"
1262 </dest> 1262 </dest>
1263 <voice> 1263 <voice>
1264 *: "सब tracks" 1264 *: "सब tracks"
@@ -3266,10 +3266,10 @@
3266 desc: in tag viewer 3266 desc: in tag viewer
3267 user: core 3267 user: core
3268 <source> 3268 <source>
3269 *: "<No Info>" 3269 *: "[No Info]"
3270 </source> 3270 </source>
3271 <dest> 3271 <dest>
3272 *: "<कोई इनफो नही है>" 3272 *: "[कोई इनफो नही है]"
3273 </dest> 3273 </dest>
3274 <voice> 3274 <voice>
3275 *: "कोई इनफो नही है" 3275 *: "कोई इनफो नही है"
diff --git a/apps/lang/hrvatski.lang b/apps/lang/hrvatski.lang
index b4fd23699d..19c7049858 100644
--- a/apps/lang/hrvatski.lang
+++ b/apps/lang/hrvatski.lang
@@ -476,10 +476,10 @@
476 desc: top item in the list when asking user about bookmark auto load 476 desc: top item in the list when asking user about bookmark auto load
477 user: core 477 user: core
478 <source> 478 <source>
479 *: "<Don't Resume>" 479 *: "[Don't Resume]"
480 </source> 480 </source>
481 <dest> 481 <dest>
482 *: "<Nemoj nastaviti>" 482 *: "[Nemoj nastaviti]"
483 </dest> 483 </dest>
484 <voice> 484 <voice>
485 *: "Nemoj nastaviti" 485 *: "Nemoj nastaviti"
@@ -504,10 +504,10 @@
504 desc: bookmark selection list, bookmark couldn't be parsed 504 desc: bookmark selection list, bookmark couldn't be parsed
505 user: core 505 user: core
506 <source> 506 <source>
507 *: "<Invalid Bookmark>" 507 *: "[Invalid Bookmark]"
508 </source> 508 </source>
509 <dest> 509 <dest>
510 *: "<Nevaljala zabilješka>" 510 *: "[Nevaljala zabilješka]"
511 </dest> 511 </dest>
512 <voice> 512 <voice>
513 *: "Nevaljala zabilješka" 513 *: "Nevaljala zabilješka"
@@ -2296,13 +2296,13 @@
2296</phrase> 2296</phrase>
2297<phrase> 2297<phrase>
2298 id: LANG_TAGNAVI_ALL_TRACKS 2298 id: LANG_TAGNAVI_ALL_TRACKS
2299 desc: "<All tracks>" entry in tag browser 2299 desc: "[All tracks]" entry in tag browser
2300 user: core 2300 user: core
2301 <source> 2301 <source>
2302 *: "<All tracks>" 2302 *: "[All tracks]"
2303 </source> 2303 </source>
2304 <dest> 2304 <dest>
2305 *: "<Sve pjesme>" 2305 *: "[Sve pjesme]"
2306 </dest> 2306 </dest>
2307 <voice> 2307 <voice>
2308 *: "Sve pjesme" 2308 *: "Sve pjesme"
@@ -7143,10 +7143,10 @@
7143 desc: in tag viewer 7143 desc: in tag viewer
7144 user: core 7144 user: core
7145 <source> 7145 <source>
7146 *: "<No Info>" 7146 *: "[No Info]"
7147 </source> 7147 </source>
7148 <dest> 7148 <dest>
7149 *: "<Nema informacija>" 7149 *: "[Nema informacija]"
7150 </dest> 7150 </dest>
7151 <voice> 7151 <voice>
7152 *: "Nema informacija" 7152 *: "Nema informacija"
@@ -10169,13 +10169,13 @@
10169</phrase> 10169</phrase>
10170<phrase> 10170<phrase>
10171 id: LANG_TAGNAVI_RANDOM 10171 id: LANG_TAGNAVI_RANDOM
10172 desc: "<Random>" entry in tag browser 10172 desc: "[Random]" entry in tag browser
10173 user: core 10173 user: core
10174 <source> 10174 <source>
10175 *: "<Random>" 10175 *: "[Random]"
10176 </source> 10176 </source>
10177 <dest> 10177 <dest>
10178 *: "<NasumiÄno>" 10178 *: "[NasumiÄno]"
10179 </dest> 10179 </dest>
10180 <voice> 10180 <voice>
10181 *: "NasumiÄno" 10181 *: "NasumiÄno"
@@ -12026,13 +12026,13 @@
12026</phrase> 12026</phrase>
12027<phrase> 12027<phrase>
12028 id: LANG_TAGNAVI_UNTAGGED 12028 id: LANG_TAGNAVI_UNTAGGED
12029 desc: "<untagged>" entry in tag browser 12029 desc: "[untagged]" entry in tag browser
12030 user: core 12030 user: core
12031 <source> 12031 <source>
12032 *: "<Untagged>" 12032 *: "[Untagged]"
12033 </source> 12033 </source>
12034 <dest> 12034 <dest>
12035 *: "<bez oznake>" 12035 *: "[bez oznake]"
12036 </dest> 12036 </dest>
12037 <voice> 12037 <voice>
12038 *: "Bez oznake" 12038 *: "Bez oznake"
diff --git a/apps/lang/islenska.lang b/apps/lang/islenska.lang
index 2fb075f278..b4d51d409c 100644
--- a/apps/lang/islenska.lang
+++ b/apps/lang/islenska.lang
@@ -3713,10 +3713,10 @@
3713 desc: in tag viewer 3713 desc: in tag viewer
3714 user: core 3714 user: core
3715 <source> 3715 <source>
3716 *: "<No Info>" 3716 *: "[No Info]"
3717 </source> 3717 </source>
3718 <dest> 3718 <dest>
3719 *: "<Engar uppl.>" 3719 *: "[Engar uppl.]"
3720 </dest> 3720 </dest>
3721 <voice> 3721 <voice>
3722 *: "Engar uppl." 3722 *: "Engar uppl."
diff --git a/apps/lang/italiano.lang b/apps/lang/italiano.lang
index e0c80b885d..7fc6b09bfa 100644
--- a/apps/lang/italiano.lang
+++ b/apps/lang/italiano.lang
@@ -473,10 +473,10 @@
473 desc: top item in the list when asking user about bookmark auto load 473 desc: top item in the list when asking user about bookmark auto load
474 user: core 474 user: core
475 <source> 475 <source>
476 *: "<Don't Resume>" 476 *: "[Don't Resume]"
477 </source> 477 </source>
478 <dest> 478 <dest>
479 *: "<Non Riprendere>" 479 *: "[Non Riprendere]"
480 </dest> 480 </dest>
481 <voice> 481 <voice>
482 *: "Non Riprendere" 482 *: "Non Riprendere"
@@ -501,10 +501,10 @@
501 desc: bookmark selection list, bookmark couldn't be parsed 501 desc: bookmark selection list, bookmark couldn't be parsed
502 user: core 502 user: core
503 <source> 503 <source>
504 *: "<Invalid Bookmark>" 504 *: "[Invalid Bookmark]"
505 </source> 505 </source>
506 <dest> 506 <dest>
507 *: "<Segnalibro non valido>" 507 *: "[Segnalibro non valido]"
508 </dest> 508 </dest>
509 <voice> 509 <voice>
510 *: "Segnalibro non valido" 510 *: "Segnalibro non valido"
@@ -2111,13 +2111,13 @@
2111</phrase> 2111</phrase>
2112<phrase> 2112<phrase>
2113 id: LANG_TAGNAVI_ALL_TRACKS 2113 id: LANG_TAGNAVI_ALL_TRACKS
2114 desc: "<All tracks>" entry in tag browser 2114 desc: "[All tracks]" entry in tag browser
2115 user: core 2115 user: core
2116 <source> 2116 <source>
2117 *: "<All tracks>" 2117 *: "[All tracks]"
2118 </source> 2118 </source>
2119 <dest> 2119 <dest>
2120 *: "<Tutte le tracce>" 2120 *: "[Tutte le tracce]"
2121 </dest> 2121 </dest>
2122 <voice> 2122 <voice>
2123 *: "Tutte le tracce" 2123 *: "Tutte le tracce"
@@ -6558,10 +6558,10 @@
6558 desc: in tag viewer 6558 desc: in tag viewer
6559 user: core 6559 user: core
6560 <source> 6560 <source>
6561 *: "<No Info>" 6561 *: "[No Info]"
6562 </source> 6562 </source>
6563 <dest> 6563 <dest>
6564 *: "<Nessuna0 Info>" 6564 *: "[Nessuna0 Info]"
6565 </dest> 6565 </dest>
6566 <voice> 6566 <voice>
6567 *: "Nessuna Informazione" 6567 *: "Nessuna Informazione"
@@ -9206,13 +9206,13 @@
9206</phrase> 9206</phrase>
9207<phrase> 9207<phrase>
9208 id: LANG_TAGNAVI_RANDOM 9208 id: LANG_TAGNAVI_RANDOM
9209 desc: "<Random>" entry in tag browser 9209 desc: "[Random]" entry in tag browser
9210 user: core 9210 user: core
9211 <source> 9211 <source>
9212 *: "<Random>" 9212 *: "[Random]"
9213 </source> 9213 </source>
9214 <dest> 9214 <dest>
9215 *: "<Casuale>" 9215 *: "[Casuale]"
9216 </dest> 9216 </dest>
9217 <voice> 9217 <voice>
9218 *: "Casuale" 9218 *: "Casuale"
@@ -11025,13 +11025,13 @@
11025</phrase> 11025</phrase>
11026<phrase> 11026<phrase>
11027 id: LANG_TAGNAVI_UNTAGGED 11027 id: LANG_TAGNAVI_UNTAGGED
11028 desc: "<untagged>" entry in tag browser 11028 desc: "[untagged]" entry in tag browser
11029 user: core 11029 user: core
11030 <source> 11030 <source>
11031 *: "<Untagged>" 11031 *: "[Untagged]"
11032 </source> 11032 </source>
11033 <dest> 11033 <dest>
11034 *: "<Nessun Tag>" 11034 *: "[Nessun Tag]"
11035 </dest> 11035 </dest>
11036 <voice> 11036 <voice>
11037 *: "Nessun Tag" 11037 *: "Nessun Tag"
@@ -16438,3 +16438,59 @@
16438 *: "Rimanente" 16438 *: "Rimanente"
16439 </voice> 16439 </voice>
16440</phrase> 16440</phrase>
16441<phrase>
16442 id: LANG_RANDOM_SHUFFLE_RANDOM_SELECTIVE_SONGS_SUMMARY
16443 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
16444 user: core
16445 <source>
16446 *: "Selection too big, %d random tracks will be selected"
16447 </source>
16448 <dest>
16449 *: "Selezione troppo grande, verranno prese solamente %d tracce casuali dalla selezione"
16450 </dest>
16451 <voice>
16452 *: "Selezione troppo grande, verranno prese solamente alcune tracce casuali dalla selezione"
16453 </voice>
16454</phrase>
16455<phrase>
16456 id: LANG_DISABLE_MAINMENU_SCROLLING
16457 desc: Disable main menu scrolling
16458 user: core
16459 <source>
16460 *: "Disable main menu scrolling"
16461 </source>
16462 <dest>
16463 *: "Disabilita lo scorrimento del menu principale"
16464 </dest>
16465 <voice>
16466 *: "Disabilita lo scorrimento del menu principale"
16467 </voice>
16468</phrase>
16469<phrase>
16470 id: LANG_DISPLAY_TITLEALBUM_FROMTAGS
16471 desc: track display options
16472 user: core
16473 <source>
16474 *: "Title & Album from ID3 tags"
16475 </source>
16476 <dest>
16477 *: "Titolo e Album da ID3 tag"
16478 </dest>
16479 <voice>
16480 *: "Titolo e Album da tag"
16481 </voice>
16482</phrase>
16483<phrase>
16484 id: LANG_DISPLAY_TITLE_FROMTAGS
16485 desc: track display options
16486 user: core
16487 <source>
16488 *: "Title from ID3 tags"
16489 </source>
16490 <dest>
16491 *: "Titolo da ID3 tag"
16492 </dest>
16493 <voice>
16494 *: "Titolo da tag"
16495 </voice>
16496</phrase>
diff --git a/apps/lang/japanese.lang b/apps/lang/japanese.lang
index 1d05b8ddf0..a150ef1664 100644
--- a/apps/lang/japanese.lang
+++ b/apps/lang/japanese.lang
@@ -484,10 +484,10 @@
484 desc: top item in the list when asking user about bookmark auto load 484 desc: top item in the list when asking user about bookmark auto load
485 user: core 485 user: core
486 <source> 486 <source>
487 *: "<Don't Resume>" 487 *: "[Don't Resume]"
488 </source> 488 </source>
489 <dest> 489 <dest>
490 *: "<レジュームã—ãªã„>" 490 *: "[レジュームã—ãªã„]"
491 </dest> 491 </dest>
492 <voice> 492 <voice>
493 *: "レジュームã—ãªã„" 493 *: "レジュームã—ãªã„"
@@ -512,10 +512,10 @@
512 desc: bookmark selection list, bookmark couldn't be parsed 512 desc: bookmark selection list, bookmark couldn't be parsed
513 user: core 513 user: core
514 <source> 514 <source>
515 *: "<Invalid Bookmark>" 515 *: "[Invalid Bookmark]"
516 </source> 516 </source>
517 <dest> 517 <dest>
518 *: "<無効ãªãƒ–ックマーク>" 518 *: "[無効ãªãƒ–ックマーク]"
519 </dest> 519 </dest>
520 <voice> 520 <voice>
521 *: "無効ãªãƒ–ックマーク" 521 *: "無効ãªãƒ–ックマーク"
@@ -2306,13 +2306,13 @@
2306</phrase> 2306</phrase>
2307<phrase> 2307<phrase>
2308 id: LANG_TAGNAVI_ALL_TRACKS 2308 id: LANG_TAGNAVI_ALL_TRACKS
2309 desc: "<All tracks>" entry in tag browser 2309 desc: "[All tracks]" entry in tag browser
2310 user: core 2310 user: core
2311 <source> 2311 <source>
2312 *: "<All tracks>" 2312 *: "[All tracks]"
2313 </source> 2313 </source>
2314 <dest> 2314 <dest>
2315 *: "<ã™ã¹ã¦ã®ãƒˆãƒ©ãƒƒã‚¯>" 2315 *: "[ã™ã¹ã¦ã®ãƒˆãƒ©ãƒƒã‚¯]"
2316 </dest> 2316 </dest>
2317 <voice> 2317 <voice>
2318 *: "ã™ã¹ã¦ã®ãƒˆãƒ©ãƒƒã‚¯" 2318 *: "ã™ã¹ã¦ã®ãƒˆãƒ©ãƒƒã‚¯"
@@ -7154,10 +7154,10 @@
7154 desc: in tag viewer 7154 desc: in tag viewer
7155 user: core 7155 user: core
7156 <source> 7156 <source>
7157 *: "<No Info>" 7157 *: "[No Info]"
7158 </source> 7158 </source>
7159 <dest> 7159 <dest>
7160 *: "<情報無ã—>" 7160 *: "[情報無ã—]"
7161 </dest> 7161 </dest>
7162 <voice> 7162 <voice>
7163 *: "情報無ã—" 7163 *: "情報無ã—"
@@ -10180,13 +10180,13 @@
10180</phrase> 10180</phrase>
10181<phrase> 10181<phrase>
10182 id: LANG_TAGNAVI_RANDOM 10182 id: LANG_TAGNAVI_RANDOM
10183 desc: "<Random>" entry in tag browser 10183 desc: "[Random]" entry in tag browser
10184 user: core 10184 user: core
10185 <source> 10185 <source>
10186 *: "<Random>" 10186 *: "[Random]"
10187 </source> 10187 </source>
10188 <dest> 10188 <dest>
10189 *: "<ランダム>" 10189 *: "[ランダム]"
10190 </dest> 10190 </dest>
10191 <voice> 10191 <voice>
10192 *: "ランダム" 10192 *: "ランダム"
@@ -12041,13 +12041,13 @@
12041</phrase> 12041</phrase>
12042<phrase> 12042<phrase>
12043 id: LANG_TAGNAVI_UNTAGGED 12043 id: LANG_TAGNAVI_UNTAGGED
12044 desc: "<untagged>" entry in tag browser 12044 desc: "[untagged]" entry in tag browser
12045 user: core 12045 user: core
12046 <source> 12046 <source>
12047 *: "<Untagged>" 12047 *: "[Untagged]"
12048 </source> 12048 </source>
12049 <dest> 12049 <dest>
12050 *: "<ã‚¿ã‚°ç„¡ã—>" 12050 *: "[ã‚¿ã‚°ç„¡ã—]"
12051 </dest> 12051 </dest>
12052 <voice> 12052 <voice>
12053 *: "ã‚¿ã‚°ç„¡ã—" 12053 *: "ã‚¿ã‚°ç„¡ã—"
diff --git a/apps/lang/korean.lang b/apps/lang/korean.lang
index 022222fd77..baaaf944ca 100644
--- a/apps/lang/korean.lang
+++ b/apps/lang/korean.lang
@@ -486,10 +486,10 @@
486 desc: top item in the list when asking user about bookmark auto load 486 desc: top item in the list when asking user about bookmark auto load
487 user: core 487 user: core
488 <source> 488 <source>
489 *: "<Don't Resume>" 489 *: "[Don't Resume]"
490 </source> 490 </source>
491 <dest> 491 <dest>
492 *: "<재시작하지 ì•ŠìŒ>" 492 *: "[재시작하지 ì•ŠìŒ]"
493 </dest> 493 </dest>
494 <voice> 494 <voice>
495 *: "재시작하지 ì•ŠìŒ" 495 *: "재시작하지 ì•ŠìŒ"
@@ -514,10 +514,10 @@
514 desc: bookmark selection list, bookmark couldn't be parsed 514 desc: bookmark selection list, bookmark couldn't be parsed
515 user: core 515 user: core
516 <source> 516 <source>
517 *: "<Invalid Bookmark>" 517 *: "[Invalid Bookmark]"
518 </source> 518 </source>
519 <dest> 519 <dest>
520 *: "<ìž˜ëª»ëœ ë¶ë§ˆí¬>" 520 *: "[ìž˜ëª»ëœ ë¶ë§ˆí¬]"
521 </dest> 521 </dest>
522 <voice> 522 <voice>
523 *: "ìž˜ëª»ëœ ë¶ë§ˆí¬" 523 *: "ìž˜ëª»ëœ ë¶ë§ˆí¬"
@@ -2138,13 +2138,13 @@
2138</phrase> 2138</phrase>
2139<phrase> 2139<phrase>
2140 id: LANG_TAGNAVI_ALL_TRACKS 2140 id: LANG_TAGNAVI_ALL_TRACKS
2141 desc: "<All tracks>" entry in tag browser 2141 desc: "[All tracks]" entry in tag browser
2142 user: core 2142 user: core
2143 <source> 2143 <source>
2144 *: "<All tracks>" 2144 *: "[All tracks]"
2145 </source> 2145 </source>
2146 <dest> 2146 <dest>
2147 *: "<모든 트랙>" 2147 *: "[모든 트랙]"
2148 </dest> 2148 </dest>
2149 <voice> 2149 <voice>
2150 *: "모든 트랙" 2150 *: "모든 트랙"
@@ -6585,10 +6585,10 @@
6585 desc: in tag viewer 6585 desc: in tag viewer
6586 user: core 6586 user: core
6587 <source> 6587 <source>
6588 *: "<No Info>" 6588 *: "[No Info]"
6589 </source> 6589 </source>
6590 <dest> 6590 <dest>
6591 *: "<ì •ë³´ ì—†ìŒ>" 6591 *: "[ì •ë³´ ì—†ìŒ]"
6592 </dest> 6592 </dest>
6593 <voice> 6593 <voice>
6594 *: "ì •ë³´ ì—†ìŒ" 6594 *: "ì •ë³´ ì—†ìŒ"
@@ -9247,13 +9247,13 @@
9247</phrase> 9247</phrase>
9248<phrase> 9248<phrase>
9249 id: LANG_TAGNAVI_RANDOM 9249 id: LANG_TAGNAVI_RANDOM
9250 desc: "<Random>" entry in tag browser 9250 desc: "[Random]" entry in tag browser
9251 user: core 9251 user: core
9252 <source> 9252 <source>
9253 *: "<Random>" 9253 *: "[Random]"
9254 </source> 9254 </source>
9255 <dest> 9255 <dest>
9256 *: "<ëžœë¤>" 9256 *: "[ëžœë¤]"
9257 </dest> 9257 </dest>
9258 <voice> 9258 <voice>
9259 *: "ëžœë¤" 9259 *: "ëžœë¤"
@@ -11097,13 +11097,13 @@
11097</phrase> 11097</phrase>
11098<phrase> 11098<phrase>
11099 id: LANG_TAGNAVI_UNTAGGED 11099 id: LANG_TAGNAVI_UNTAGGED
11100 desc: "<untagged>" entry in tag browser 11100 desc: "[untagged]" entry in tag browser
11101 user: core 11101 user: core
11102 <source> 11102 <source>
11103 *: "<Untagged>" 11103 *: "[Untagged]"
11104 </source> 11104 </source>
11105 <dest> 11105 <dest>
11106 *: "<태그가 지정ë˜ì§€ ì•ŠìŒ>" 11106 *: "[태그가 지정ë˜ì§€ ì•ŠìŒ]"
11107 </dest> 11107 </dest>
11108 <voice> 11108 <voice>
11109 *: "태그가 지정ë˜ì§€ ì•ŠìŒ" 11109 *: "태그가 지정ë˜ì§€ ì•ŠìŒ"
@@ -16451,3 +16451,59 @@
16451 *: "남ì€" 16451 *: "남ì€"
16452 </voice> 16452 </voice>
16453</phrase> 16453</phrase>
16454<phrase>
16455 id: LANG_DISABLE_MAINMENU_SCROLLING
16456 desc: Disable main menu scrolling
16457 user: core
16458 <source>
16459 *: "Disable main menu scrolling"
16460 </source>
16461 <dest>
16462 *: "ë©”ì¸ ë©”ë‰´ 스í¬ë¡¤ 비활성화"
16463 </dest>
16464 <voice>
16465 *: "ë©”ì¸ ë©”ë‰´ 스í¬ë¡¤ 비활성화"
16466 </voice>
16467</phrase>
16468<phrase>
16469 id: LANG_RANDOM_SHUFFLE_RANDOM_SELECTIVE_SONGS_SUMMARY
16470 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
16471 user: core
16472 <source>
16473 *: "Selection too big, %d random tracks will be selected"
16474 </source>
16475 <dest>
16476 *: "ì„ íƒì´ 너무 커서, %d ê°œì˜ ë¬´ìž‘ìœ„ íŠ¸ëž™ì´ ì„ íƒë¨"
16477 </dest>
16478 <voice>
16479 *: "ì„ íƒì´ 너무 커서 무작위로 ì„ íƒë˜ëŠ” íŠ¸ëž™ì´ ì¤„ì–´ë“¬"
16480 </voice>
16481</phrase>
16482<phrase>
16483 id: LANG_DISPLAY_TITLEALBUM_FROMTAGS
16484 desc: track display options
16485 user: core
16486 <source>
16487 *: "Title & Album from ID3 tags"
16488 </source>
16489 <dest>
16490 *: "ID3 태그 제목 & 앨범"
16491 </dest>
16492 <voice>
16493 *: "íƒœê·¸ì˜ ì œëª© ë° ì•¨ë²”"
16494 </voice>
16495</phrase>
16496<phrase>
16497 id: LANG_DISPLAY_TITLE_FROMTAGS
16498 desc: track display options
16499 user: core
16500 <source>
16501 *: "Title from ID3 tags"
16502 </source>
16503 <dest>
16504 *: "ID3 태그 제목"
16505 </dest>
16506 <voice>
16507 *: "태그 제목"
16508 </voice>
16509</phrase>
diff --git a/apps/lang/latviesu.lang b/apps/lang/latviesu.lang
index d43109eed2..7216993ff9 100644
--- a/apps/lang/latviesu.lang
+++ b/apps/lang/latviesu.lang
@@ -476,10 +476,10 @@
476 desc: top item in the list when asking user about bookmark auto load 476 desc: top item in the list when asking user about bookmark auto load
477 user: core 477 user: core
478 <source> 478 <source>
479 *: "<Don't Resume>" 479 *: "[Don't Resume]"
480 </source> 480 </source>
481 <dest> 481 <dest>
482 *: "<NeatsÄkt>" 482 *: "[NeatsÄkt]"
483 </dest> 483 </dest>
484 <voice> 484 <voice>
485 *: "neatsaakt" 485 *: "neatsaakt"
@@ -504,10 +504,10 @@
504 desc: bookmark selection list, bookmark couldn't be parsed 504 desc: bookmark selection list, bookmark couldn't be parsed
505 user: core 505 user: core
506 <source> 506 <source>
507 *: "<Invalid Bookmark>" 507 *: "[Invalid Bookmark]"
508 </source> 508 </source>
509 <dest> 509 <dest>
510 *: "<NederÄ«ga GrÄmatzÄ«me>" 510 *: "[NederÄ«ga GrÄmatzÄ«me]"
511 </dest> 511 </dest>
512 <voice> 512 <voice>
513 *: "nederiiga graamatziime" 513 *: "nederiiga graamatziime"
@@ -2297,13 +2297,13 @@
2297</phrase> 2297</phrase>
2298<phrase> 2298<phrase>
2299 id: LANG_TAGNAVI_ALL_TRACKS 2299 id: LANG_TAGNAVI_ALL_TRACKS
2300 desc: "<All tracks>" entry in tag browser 2300 desc: "[All tracks]" entry in tag browser
2301 user: core 2301 user: core
2302 <source> 2302 <source>
2303 *: "<All tracks>" 2303 *: "[All tracks]"
2304 </source> 2304 </source>
2305 <dest> 2305 <dest>
2306 *: "<Visas dziesmas>" 2306 *: "[Visas dziesmas]"
2307 </dest> 2307 </dest>
2308 <voice> 2308 <voice>
2309 *: "visas dziesmas" 2309 *: "visas dziesmas"
@@ -7145,10 +7145,10 @@
7145 desc: in tag viewer 7145 desc: in tag viewer
7146 user: core 7146 user: core
7147 <source> 7147 <source>
7148 *: "<No Info>" 7148 *: "[No Info]"
7149 </source> 7149 </source>
7150 <dest> 7150 <dest>
7151 *: "<Nav InformÄcijas>" 7151 *: "[Nav InformÄcijas]"
7152 </dest> 7152 </dest>
7153 <voice> 7153 <voice>
7154 *: "Nav InformÄcijas" 7154 *: "Nav InformÄcijas"
@@ -10171,13 +10171,13 @@
10171</phrase> 10171</phrase>
10172<phrase> 10172<phrase>
10173 id: LANG_TAGNAVI_RANDOM 10173 id: LANG_TAGNAVI_RANDOM
10174 desc: "<Random>" entry in tag browser 10174 desc: "[Random]" entry in tag browser
10175 user: core 10175 user: core
10176 <source> 10176 <source>
10177 *: "<Random>" 10177 *: "[Random]"
10178 </source> 10178 </source>
10179 <dest> 10179 <dest>
10180 *: "<DažÄdi>" 10180 *: "[DažÄdi]"
10181 </dest> 10181 </dest>
10182 <voice> 10182 <voice>
10183 *: "dazhaadi" 10183 *: "dazhaadi"
@@ -12015,13 +12015,13 @@
12015</phrase> 12015</phrase>
12016<phrase> 12016<phrase>
12017 id: LANG_TAGNAVI_UNTAGGED 12017 id: LANG_TAGNAVI_UNTAGGED
12018 desc: "<untagged>" entry in tag browser 12018 desc: "[untagged]" entry in tag browser
12019 user: core 12019 user: core
12020 <source> 12020 <source>
12021 *: "<Untagged>" 12021 *: "[Untagged]"
12022 </source> 12022 </source>
12023 <dest> 12023 <dest>
12024 *: "<Neatzīmēts>" 12024 *: "[Neatzīmēts]"
12025 </dest> 12025 </dest>
12026 <voice> 12026 <voice>
12027 *: "neatziimeets" 12027 *: "neatziimeets"
diff --git a/apps/lang/lietuviu.lang b/apps/lang/lietuviu.lang
index 51aece5107..41bac94417 100644
--- a/apps/lang/lietuviu.lang
+++ b/apps/lang/lietuviu.lang
@@ -543,10 +543,10 @@
543 desc: top item in the list when asking user about bookmark auto load 543 desc: top item in the list when asking user about bookmark auto load
544 user: core 544 user: core
545 <source> 545 <source>
546 *: "<Don't Resume>" 546 *: "[Don't Resume]"
547 </source> 547 </source>
548 <dest> 548 <dest>
549 *: "<Nesugrįžti>" 549 *: "[Nesugrįžti]"
550 </dest> 550 </dest>
551 <voice> 551 <voice>
552 *: "Nesugrįžti" 552 *: "Nesugrįžti"
@@ -571,10 +571,10 @@
571 desc: bookmark selection list, bookmark couldn't be parsed 571 desc: bookmark selection list, bookmark couldn't be parsed
572 user: core 572 user: core
573 <source> 573 <source>
574 *: "<Invalid Bookmark>" 574 *: "[Invalid Bookmark]"
575 </source> 575 </source>
576 <dest> 576 <dest>
577 *: "<Klaidinga žymelė>" 577 *: "[Klaidinga žymelė]"
578 </dest> 578 </dest>
579 <voice> 579 <voice>
580 *: "Klaidinga žymelė" 580 *: "Klaidinga žymelė"
@@ -2378,13 +2378,13 @@
2378</phrase> 2378</phrase>
2379<phrase> 2379<phrase>
2380 id: LANG_TAGNAVI_ALL_TRACKS 2380 id: LANG_TAGNAVI_ALL_TRACKS
2381 desc: "<All tracks>" entry in tag browser 2381 desc: "[All tracks]" entry in tag browser
2382 user: core 2382 user: core
2383 <source> 2383 <source>
2384 *: "<All tracks>" 2384 *: "[All tracks]"
2385 </source> 2385 </source>
2386 <dest> 2386 <dest>
2387 *: "<Visi įrašai>" 2387 *: "[Visi įrašai]"
2388 </dest> 2388 </dest>
2389 <voice> 2389 <voice>
2390 *: "Visi įrašai" 2390 *: "Visi įrašai"
@@ -7221,10 +7221,10 @@
7221 desc: in tag viewer 7221 desc: in tag viewer
7222 user: core 7222 user: core
7223 <source> 7223 <source>
7224 *: "<No Info>" 7224 *: "[No Info]"
7225 </source> 7225 </source>
7226 <dest> 7226 <dest>
7227 *: "<NÄ—ra informacijos>" 7227 *: "[NÄ—ra informacijos]"
7228 </dest> 7228 </dest>
7229 <voice> 7229 <voice>
7230 *: "NÄ—ra informacijos" 7230 *: "NÄ—ra informacijos"
@@ -10247,13 +10247,13 @@
10247</phrase> 10247</phrase>
10248<phrase> 10248<phrase>
10249 id: LANG_TAGNAVI_RANDOM 10249 id: LANG_TAGNAVI_RANDOM
10250 desc: "<Random>" entry in tag browser 10250 desc: "[Random]" entry in tag browser
10251 user: core 10251 user: core
10252 <source> 10252 <source>
10253 *: "<Random>" 10253 *: "[Random]"
10254 </source> 10254 </source>
10255 <dest> 10255 <dest>
10256 *: "<Atsitiktinis>" 10256 *: "[Atsitiktinis]"
10257 </dest> 10257 </dest>
10258 <voice> 10258 <voice>
10259 *: "Atsitiktinis" 10259 *: "Atsitiktinis"
diff --git a/apps/lang/magyar.lang b/apps/lang/magyar.lang
index 96bcd1e4ad..cdad5f3924 100644
--- a/apps/lang/magyar.lang
+++ b/apps/lang/magyar.lang
@@ -470,10 +470,10 @@
470 desc: top item in the list when asking user about bookmark auto load 470 desc: top item in the list when asking user about bookmark auto load
471 user: core 471 user: core
472 <source> 472 <source>
473 *: "<Don't Resume>" 473 *: "[Don't Resume]"
474 </source> 474 </source>
475 <dest> 475 <dest>
476 *: "<Nincs folytatás>" 476 *: "[Nincs folytatás]"
477 </dest> 477 </dest>
478 <voice> 478 <voice>
479 *: "Do not resume" 479 *: "Do not resume"
@@ -498,10 +498,10 @@
498 desc: bookmark selection list, bookmark couldn't be parsed 498 desc: bookmark selection list, bookmark couldn't be parsed
499 user: core 499 user: core
500 <source> 500 <source>
501 *: "<Invalid Bookmark>" 501 *: "[Invalid Bookmark]"
502 </source> 502 </source>
503 <dest> 503 <dest>
504 *: "<Érvénytelen könyvjelző>" 504 *: "[Érvénytelen könyvjelző]"
505 </dest> 505 </dest>
506 <voice> 506 <voice>
507 *: "Invalid Bookmark" 507 *: "Invalid Bookmark"
@@ -2118,13 +2118,13 @@
2118</phrase> 2118</phrase>
2119<phrase> 2119<phrase>
2120 id: LANG_TAGNAVI_ALL_TRACKS 2120 id: LANG_TAGNAVI_ALL_TRACKS
2121 desc: "<All tracks>" entry in tag browser 2121 desc: "[All tracks]" entry in tag browser
2122 user: core 2122 user: core
2123 <source> 2123 <source>
2124 *: "<All tracks>" 2124 *: "[All tracks]"
2125 </source> 2125 </source>
2126 <dest> 2126 <dest>
2127 *: "<Minden szám>" 2127 *: "[Minden szám]"
2128 </dest> 2128 </dest>
2129 <voice> 2129 <voice>
2130 *: "All tracks" 2130 *: "All tracks"
@@ -6751,10 +6751,10 @@
6751 desc: in tag viewer 6751 desc: in tag viewer
6752 user: core 6752 user: core
6753 <source> 6753 <source>
6754 *: "<No Info>" 6754 *: "[No Info]"
6755 </source> 6755 </source>
6756 <dest> 6756 <dest>
6757 *: "<Nincs adat>" 6757 *: "[Nincs adat]"
6758 </dest> 6758 </dest>
6759 <voice> 6759 <voice>
6760 *: "Nincs adat" 6760 *: "Nincs adat"
@@ -9513,13 +9513,13 @@
9513</phrase> 9513</phrase>
9514<phrase> 9514<phrase>
9515 id: LANG_TAGNAVI_RANDOM 9515 id: LANG_TAGNAVI_RANDOM
9516 desc: "<Random>" entry in tag browser 9516 desc: "[Random]" entry in tag browser
9517 user: core 9517 user: core
9518 <source> 9518 <source>
9519 *: "<Random>" 9519 *: "[Random]"
9520 </source> 9520 </source>
9521 <dest> 9521 <dest>
9522 *: "<Véletlenszerűen>" 9522 *: "[Véletlenszerűen]"
9523 </dest> 9523 </dest>
9524 <voice> 9524 <voice>
9525 *: "Random" 9525 *: "Random"
@@ -10868,13 +10868,13 @@
10868</phrase> 10868</phrase>
10869<phrase> 10869<phrase>
10870 id: LANG_TAGNAVI_UNTAGGED 10870 id: LANG_TAGNAVI_UNTAGGED
10871 desc: "<untagged>" entry in tag browser 10871 desc: "[untagged]" entry in tag browser
10872 user: core 10872 user: core
10873 <source> 10873 <source>
10874 *: "<Untagged>" 10874 *: "[Untagged]"
10875 </source> 10875 </source>
10876 <dest> 10876 <dest>
10877 *: "<Ismeretlen>" 10877 *: "[Ismeretlen]"
10878 </dest> 10878 </dest>
10879 <voice> 10879 <voice>
10880 *: "Ismeretlen" 10880 *: "Ismeretlen"
diff --git a/apps/lang/nederlands.lang b/apps/lang/nederlands.lang
index 42ed25b897..7b30907261 100644
--- a/apps/lang/nederlands.lang
+++ b/apps/lang/nederlands.lang
@@ -4534,10 +4534,10 @@
4534 desc: in tag viewer 4534 desc: in tag viewer
4535 user: core 4535 user: core
4536 <source> 4536 <source>
4537 *: "<No Info>" 4537 *: "[No Info]"
4538 </source> 4538 </source>
4539 <dest> 4539 <dest>
4540 *: "<geen info>" 4540 *: "[geen info]"
4541 </dest> 4541 </dest>
4542 <voice> 4542 <voice>
4543 *: "Geen info" 4543 *: "Geen info"
@@ -6945,13 +6945,13 @@
6945</phrase> 6945</phrase>
6946<phrase> 6946<phrase>
6947 id: LANG_TAGNAVI_ALL_TRACKS 6947 id: LANG_TAGNAVI_ALL_TRACKS
6948 desc: "<All tracks>" entry in tag browser 6948 desc: "[All tracks]" entry in tag browser
6949 user: core 6949 user: core
6950 <source> 6950 <source>
6951 *: "<All tracks>" 6951 *: "[All tracks]"
6952 </source> 6952 </source>
6953 <dest> 6953 <dest>
6954 *: "<Alle nummers>" 6954 *: "[Alle nummers]"
6955 </dest> 6955 </dest>
6956 <voice> 6956 <voice>
6957 *: "Alle nummers" 6957 *: "Alle nummers"
@@ -8350,10 +8350,10 @@
8350 desc: top item in the list when asking user about bookmark auto load 8350 desc: top item in the list when asking user about bookmark auto load
8351 user: core 8351 user: core
8352 <source> 8352 <source>
8353 *: "<Don't Resume>" 8353 *: "[Don't Resume]"
8354 </source> 8354 </source>
8355 <dest> 8355 <dest>
8356 *: "<Niet hervatten>" 8356 *: "[Niet hervatten]"
8357 </dest> 8357 </dest>
8358 <voice> 8358 <voice>
8359 *: "Niet hervatten" 8359 *: "Niet hervatten"
@@ -8437,10 +8437,10 @@
8437 desc: bookmark selection list, bookmark couldn't be parsed 8437 desc: bookmark selection list, bookmark couldn't be parsed
8438 user: core 8438 user: core
8439 <source> 8439 <source>
8440 *: "<Invalid Bookmark>" 8440 *: "[Invalid Bookmark]"
8441 </source> 8441 </source>
8442 <dest> 8442 <dest>
8443 *: "<Ongeldige bladwijzer>" 8443 *: "[Ongeldige bladwijzer]"
8444 </dest> 8444 </dest>
8445 <voice> 8445 <voice>
8446 *: "Ongeldige bladwijzer" 8446 *: "Ongeldige bladwijzer"
@@ -9447,13 +9447,13 @@
9447</phrase> 9447</phrase>
9448<phrase> 9448<phrase>
9449 id: LANG_TAGNAVI_RANDOM 9449 id: LANG_TAGNAVI_RANDOM
9450 desc: "<Random>" entry in tag browser 9450 desc: "[Random]" entry in tag browser
9451 user: core 9451 user: core
9452 <source> 9452 <source>
9453 *: "<Random>" 9453 *: "[Random]"
9454 </source> 9454 </source>
9455 <dest> 9455 <dest>
9456 *: "<Willekeurig>" 9456 *: "[Willekeurig]"
9457 </dest> 9457 </dest>
9458 <voice> 9458 <voice>
9459 *: "Willekeurig" 9459 *: "Willekeurig"
@@ -11189,13 +11189,13 @@
11189</phrase> 11189</phrase>
11190<phrase> 11190<phrase>
11191 id: LANG_TAGNAVI_UNTAGGED 11191 id: LANG_TAGNAVI_UNTAGGED
11192 desc: "<untagged>" entry in tag browser 11192 desc: "[untagged]" entry in tag browser
11193 user: core 11193 user: core
11194 <source> 11194 <source>
11195 *: "<Untagged>" 11195 *: "[Untagged]"
11196 </source> 11196 </source>
11197 <dest> 11197 <dest>
11198 *: "<Geen tags>" 11198 *: "[Geen tags]"
11199 </dest> 11199 </dest>
11200 <voice> 11200 <voice>
11201 *: "Geen tags" 11201 *: "Geen tags"
diff --git a/apps/lang/norsk-nynorsk.lang b/apps/lang/norsk-nynorsk.lang
index 165dfeea14..840c5fd922 100644
--- a/apps/lang/norsk-nynorsk.lang
+++ b/apps/lang/norsk-nynorsk.lang
@@ -4801,10 +4801,10 @@
4801 desc: in tag viewer 4801 desc: in tag viewer
4802 user: core 4802 user: core
4803 <source> 4803 <source>
4804 *: "<No Info>" 4804 *: "[No Info]"
4805 </source> 4805 </source>
4806 <dest> 4806 <dest>
4807 *: "<Ingen info>" 4807 *: "[Ingen info]"
4808 </dest> 4808 </dest>
4809 <voice> 4809 <voice>
4810 *: "Ingen info" 4810 *: "Ingen info"
@@ -7322,13 +7322,13 @@
7322</phrase> 7322</phrase>
7323<phrase> 7323<phrase>
7324 id: LANG_TAGNAVI_ALL_TRACKS 7324 id: LANG_TAGNAVI_ALL_TRACKS
7325 desc: "<All tracks>" entry in tag browser 7325 desc: "[All tracks]" entry in tag browser
7326 user: core 7326 user: core
7327 <source> 7327 <source>
7328 *: "<All tracks>" 7328 *: "[All tracks]"
7329 </source> 7329 </source>
7330 <dest> 7330 <dest>
7331 *: "<Alle spor>" 7331 *: "[Alle spor]"
7332 </dest> 7332 </dest>
7333 <voice> 7333 <voice>
7334 *: "Alle spor" 7334 *: "Alle spor"
@@ -9511,10 +9511,10 @@
9511 desc: top item in the list when asking user about bookmark auto load 9511 desc: top item in the list when asking user about bookmark auto load
9512 user: core 9512 user: core
9513 <source> 9513 <source>
9514 *: "<Don't Resume>" 9514 *: "[Don't Resume]"
9515 </source> 9515 </source>
9516 <dest> 9516 <dest>
9517 *: "<Ikkje hald fram>" 9517 *: "[Ikkje hald fram]"
9518 </dest> 9518 </dest>
9519 <voice> 9519 <voice>
9520 *: "Ikkje hald fram" 9520 *: "Ikkje hald fram"
@@ -9654,10 +9654,10 @@
9654 desc: bookmark selection list, bookmark couldn't be parsed 9654 desc: bookmark selection list, bookmark couldn't be parsed
9655 user: core 9655 user: core
9656 <source> 9656 <source>
9657 *: "<Invalid Bookmark>" 9657 *: "[Invalid Bookmark]"
9658 </source> 9658 </source>
9659 <dest> 9659 <dest>
9660 *: "<Ugyldig bokmerke>" 9660 *: "[Ugyldig bokmerke]"
9661 </dest> 9661 </dest>
9662 <voice> 9662 <voice>
9663 *: "Ugyldig bokmerke" 9663 *: "Ugyldig bokmerke"
@@ -10202,13 +10202,13 @@
10202</phrase> 10202</phrase>
10203<phrase> 10203<phrase>
10204 id: LANG_TAGNAVI_RANDOM 10204 id: LANG_TAGNAVI_RANDOM
10205 desc: "<Random>" entry in tag browser 10205 desc: "[Random]" entry in tag browser
10206 user: core 10206 user: core
10207 <source> 10207 <source>
10208 *: "<Random>" 10208 *: "[Random]"
10209 </source> 10209 </source>
10210 <dest> 10210 <dest>
10211 *: "<Tilfeldig>" 10211 *: "[Tilfeldig]"
10212 </dest> 10212 </dest>
10213 <voice> 10213 <voice>
10214 *: "Tilfeldig" 10214 *: "Tilfeldig"
diff --git a/apps/lang/norsk.lang b/apps/lang/norsk.lang
index a3ac67564f..d74a485f0e 100644
--- a/apps/lang/norsk.lang
+++ b/apps/lang/norsk.lang
@@ -4122,10 +4122,10 @@
4122 desc: in tag viewer 4122 desc: in tag viewer
4123 user: core 4123 user: core
4124 <source> 4124 <source>
4125 *: "<No Info>" 4125 *: "[No Info]"
4126 </source> 4126 </source>
4127 <dest> 4127 <dest>
4128 *: "<Ingen informasjon>" 4128 *: "[Ingen informasjon]"
4129 </dest> 4129 </dest>
4130 <voice> 4130 <voice>
4131 *: "Ingen informasjon" 4131 *: "Ingen informasjon"
@@ -7321,13 +7321,13 @@
7321</phrase> 7321</phrase>
7322<phrase> 7322<phrase>
7323 id: LANG_TAGNAVI_ALL_TRACKS 7323 id: LANG_TAGNAVI_ALL_TRACKS
7324 desc: "<All tracks>" entry in tag browser 7324 desc: "[All tracks]" entry in tag browser
7325 user: core 7325 user: core
7326 <source> 7326 <source>
7327 *: "<All tracks>" 7327 *: "[All tracks]"
7328 </source> 7328 </source>
7329 <dest> 7329 <dest>
7330 *: "<Alle spor>" 7330 *: "[Alle spor]"
7331 </dest> 7331 </dest>
7332 <voice> 7332 <voice>
7333 *: "Alle spor" 7333 *: "Alle spor"
@@ -9709,10 +9709,10 @@
9709 desc: top item in the list when asking user about bookmark auto load 9709 desc: top item in the list when asking user about bookmark auto load
9710 user: core 9710 user: core
9711 <source> 9711 <source>
9712 *: "<Don't Resume>" 9712 *: "[Don't Resume]"
9713 </source> 9713 </source>
9714 <dest> 9714 <dest>
9715 *: "<Ikke gjenoppta>" 9715 *: "[Ikke gjenoppta]"
9716 </dest> 9716 </dest>
9717 <voice> 9717 <voice>
9718 *: "Ikke gjenoppta" 9718 *: "Ikke gjenoppta"
@@ -9869,10 +9869,10 @@
9869 desc: bookmark selection list, bookmark couldn't be parsed 9869 desc: bookmark selection list, bookmark couldn't be parsed
9870 user: core 9870 user: core
9871 <source> 9871 <source>
9872 *: "<Invalid Bookmark>" 9872 *: "[Invalid Bookmark]"
9873 </source> 9873 </source>
9874 <dest> 9874 <dest>
9875 *: "<Ugyldig bokmerke>" 9875 *: "[Ugyldig bokmerke]"
9876 </dest> 9876 </dest>
9877 <voice> 9877 <voice>
9878 *: "Ugyldig bokmerke" 9878 *: "Ugyldig bokmerke"
@@ -10133,13 +10133,13 @@
10133</phrase> 10133</phrase>
10134<phrase> 10134<phrase>
10135 id: LANG_TAGNAVI_RANDOM 10135 id: LANG_TAGNAVI_RANDOM
10136 desc: "<Random>" entry in tag browser 10136 desc: "[Random]" entry in tag browser
10137 user: core 10137 user: core
10138 <source> 10138 <source>
10139 *: "<Random>" 10139 *: "[Random]"
10140 </source> 10140 </source>
10141 <dest> 10141 <dest>
10142 *: "<Tilfeldig>" 10142 *: "[Tilfeldig]"
10143 </dest> 10143 </dest>
10144 <voice> 10144 <voice>
10145 *: "Random" 10145 *: "Random"
@@ -11528,13 +11528,13 @@
11528</phrase> 11528</phrase>
11529<phrase> 11529<phrase>
11530 id: LANG_TAGNAVI_UNTAGGED 11530 id: LANG_TAGNAVI_UNTAGGED
11531 desc: "<untagged>" entry in tag browser 11531 desc: "[untagged]" entry in tag browser
11532 user: core 11532 user: core
11533 <source> 11533 <source>
11534 *: "<Untagged>" 11534 *: "[Untagged]"
11535 </source> 11535 </source>
11536 <dest> 11536 <dest>
11537 *: "<Uten tagg>" 11537 *: "[Uten tagg]"
11538 </dest> 11538 </dest>
11539 <voice> 11539 <voice>
11540 *: "Uten tagg" 11540 *: "Uten tagg"
diff --git a/apps/lang/polski.lang b/apps/lang/polski.lang
index 03b5c008cd..30b02e4f48 100644
--- a/apps/lang/polski.lang
+++ b/apps/lang/polski.lang
@@ -479,10 +479,10 @@
479 desc: top item in the list when asking user about bookmark auto load 479 desc: top item in the list when asking user about bookmark auto load
480 user: core 480 user: core
481 <source> 481 <source>
482 *: "<Don't Resume>" 482 *: "[Don't Resume]"
483 </source> 483 </source>
484 <dest> 484 <dest>
485 *: "<Nie wznawiaj>" 485 *: "[Nie wznawiaj]"
486 </dest> 486 </dest>
487 <voice> 487 <voice>
488 *: "Nie wznawiaj" 488 *: "Nie wznawiaj"
@@ -507,10 +507,10 @@
507 desc: bookmark selection list, bookmark couldn't be parsed 507 desc: bookmark selection list, bookmark couldn't be parsed
508 user: core 508 user: core
509 <source> 509 <source>
510 *: "<Invalid Bookmark>" 510 *: "[Invalid Bookmark]"
511 </source> 511 </source>
512 <dest> 512 <dest>
513 *: "<Nieprawidłowa zakładka>" 513 *: "[Nieprawidłowa zakładka]"
514 </dest> 514 </dest>
515 <voice> 515 <voice>
516 *: "Nieprawidłowa zakładka" 516 *: "Nieprawidłowa zakładka"
@@ -2117,13 +2117,13 @@
2117</phrase> 2117</phrase>
2118<phrase> 2118<phrase>
2119 id: LANG_TAGNAVI_ALL_TRACKS 2119 id: LANG_TAGNAVI_ALL_TRACKS
2120 desc: "<All tracks>" entry in tag browser 2120 desc: "[All tracks]" entry in tag browser
2121 user: core 2121 user: core
2122 <source> 2122 <source>
2123 *: "<All tracks>" 2123 *: "[All tracks]"
2124 </source> 2124 </source>
2125 <dest> 2125 <dest>
2126 *: "<Wszystkie utwory>" 2126 *: "[Wszystkie utwory]"
2127 </dest> 2127 </dest>
2128 <voice> 2128 <voice>
2129 *: "Wszystkie utwory" 2129 *: "Wszystkie utwory"
@@ -6564,10 +6564,10 @@
6564 desc: in tag viewer 6564 desc: in tag viewer
6565 user: core 6565 user: core
6566 <source> 6566 <source>
6567 *: "<No Info>" 6567 *: "[No Info]"
6568 </source> 6568 </source>
6569 <dest> 6569 <dest>
6570 *: "<brak danych>" 6570 *: "[brak danych]"
6571 </dest> 6571 </dest>
6572 <voice> 6572 <voice>
6573 *: "brak danych" 6573 *: "brak danych"
@@ -9212,13 +9212,13 @@
9212</phrase> 9212</phrase>
9213<phrase> 9213<phrase>
9214 id: LANG_TAGNAVI_RANDOM 9214 id: LANG_TAGNAVI_RANDOM
9215 desc: "<Random>" entry in tag browser 9215 desc: "[Random]" entry in tag browser
9216 user: core 9216 user: core
9217 <source> 9217 <source>
9218 *: "<Random>" 9218 *: "[Random]"
9219 </source> 9219 </source>
9220 <dest> 9220 <dest>
9221 *: "<Losowo>" 9221 *: "[Losowo]"
9222 </dest> 9222 </dest>
9223 <voice> 9223 <voice>
9224 *: "Losowo" 9224 *: "Losowo"
@@ -10647,13 +10647,13 @@
10647</phrase> 10647</phrase>
10648<phrase> 10648<phrase>
10649 id: LANG_TAGNAVI_UNTAGGED 10649 id: LANG_TAGNAVI_UNTAGGED
10650 desc: "<untagged>" entry in tag browser 10650 desc: "[untagged]" entry in tag browser
10651 user: core 10651 user: core
10652 <source> 10652 <source>
10653 *: "<Untagged>" 10653 *: "[Untagged]"
10654 </source> 10654 </source>
10655 <dest> 10655 <dest>
10656 *: "<Bez opisu>" 10656 *: "[Bez opisu]"
10657 </dest> 10657 </dest>
10658 <voice> 10658 <voice>
10659 *: "Bez opisu" 10659 *: "Bez opisu"
@@ -16444,3 +16444,59 @@
16444 *: "Pozostały" 16444 *: "Pozostały"
16445 </voice> 16445 </voice>
16446</phrase> 16446</phrase>
16447<phrase>
16448 id: LANG_DISABLE_MAINMENU_SCROLLING
16449 desc: Disable main menu scrolling
16450 user: core
16451 <source>
16452 *: "Disable main menu scrolling"
16453 </source>
16454 <dest>
16455 *: "Wyłącz przewijanie menu głównego"
16456 </dest>
16457 <voice>
16458 *: "Wyłącz przewijanie menu głównego"
16459 </voice>
16460</phrase>
16461<phrase>
16462 id: LANG_RANDOM_SHUFFLE_RANDOM_SELECTIVE_SONGS_SUMMARY
16463 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
16464 user: core
16465 <source>
16466 *: "Selection too big, %d random tracks will be selected"
16467 </source>
16468 <dest>
16469 *: "Wybór jest zbyt duży, zostanie z niego wybranych %d losowych utworów"
16470 </dest>
16471 <voice>
16472 *: "Wybór jest zbyt duży, zostanie z niego wybrana mniejsza liczba losowych utworów"
16473 </voice>
16474</phrase>
16475<phrase>
16476 id: LANG_DISPLAY_TITLEALBUM_FROMTAGS
16477 desc: track display options
16478 user: core
16479 <source>
16480 *: "Title & Album from ID3 tags"
16481 </source>
16482 <dest>
16483 *: "Tytuł i album ze znaczników ID3"
16484 </dest>
16485 <voice>
16486 *: "Tytuł i album ze znaczników i de trzy"
16487 </voice>
16488</phrase>
16489<phrase>
16490 id: LANG_DISPLAY_TITLE_FROMTAGS
16491 desc: track display options
16492 user: core
16493 <source>
16494 *: "Title from ID3 tags"
16495 </source>
16496 <dest>
16497 *: "Tytuł ze znaczników ID3"
16498 </dest>
16499 <voice>
16500 *: "Tytuł ze znaczników i de trzy"
16501 </voice>
16502</phrase>
diff --git a/apps/lang/portugues-brasileiro.lang b/apps/lang/portugues-brasileiro.lang
index 1100758535..0fca053f95 100644
--- a/apps/lang/portugues-brasileiro.lang
+++ b/apps/lang/portugues-brasileiro.lang
@@ -477,10 +477,10 @@
477 desc: top item in the list when asking user about bookmark auto load 477 desc: top item in the list when asking user about bookmark auto load
478 user: core 478 user: core
479 <source> 479 <source>
480 *: "<Don't Resume>" 480 *: "[Don't Resume]"
481 </source> 481 </source>
482 <dest> 482 <dest>
483 *: "<Não Retomar>" 483 *: "[Não Retomar]"
484 </dest> 484 </dest>
485 <voice> 485 <voice>
486 *: "Não Retomar" 486 *: "Não Retomar"
@@ -505,10 +505,10 @@
505 desc: bookmark selection list, bookmark couldn't be parsed 505 desc: bookmark selection list, bookmark couldn't be parsed
506 user: core 506 user: core
507 <source> 507 <source>
508 *: "<Invalid Bookmark>" 508 *: "[Invalid Bookmark]"
509 </source> 509 </source>
510 <dest> 510 <dest>
511 *: "<Favorito Inválido>" 511 *: "[Favorito Inválido]"
512 </dest> 512 </dest>
513 <voice> 513 <voice>
514 *: "Favorito Inválido" 514 *: "Favorito Inválido"
@@ -2298,13 +2298,13 @@
2298</phrase> 2298</phrase>
2299<phrase> 2299<phrase>
2300 id: LANG_TAGNAVI_ALL_TRACKS 2300 id: LANG_TAGNAVI_ALL_TRACKS
2301 desc: "<All tracks>" entry in tag browser 2301 desc: "[All tracks]" entry in tag browser
2302 user: core 2302 user: core
2303 <source> 2303 <source>
2304 *: "<All tracks>" 2304 *: "[All tracks]"
2305 </source> 2305 </source>
2306 <dest> 2306 <dest>
2307 *: "<Todas as Faixas>" 2307 *: "[Todas as Faixas]"
2308 </dest> 2308 </dest>
2309 <voice> 2309 <voice>
2310 *: "Todas as Faixas" 2310 *: "Todas as Faixas"
@@ -7145,10 +7145,10 @@
7145 desc: in tag viewer 7145 desc: in tag viewer
7146 user: core 7146 user: core
7147 <source> 7147 <source>
7148 *: "<No Info>" 7148 *: "[No Info]"
7149 </source> 7149 </source>
7150 <dest> 7150 <dest>
7151 *: "<Informação Ausente>" 7151 *: "[Informação Ausente]"
7152 </dest> 7152 </dest>
7153 <voice> 7153 <voice>
7154 *: "Informação Ausente" 7154 *: "Informação Ausente"
@@ -10171,13 +10171,13 @@
10171</phrase> 10171</phrase>
10172<phrase> 10172<phrase>
10173 id: LANG_TAGNAVI_RANDOM 10173 id: LANG_TAGNAVI_RANDOM
10174 desc: "<Random>" entry in tag browser 10174 desc: "[Random]" entry in tag browser
10175 user: core 10175 user: core
10176 <source> 10176 <source>
10177 *: "<Random>" 10177 *: "[Random]"
10178 </source> 10178 </source>
10179 <dest> 10179 <dest>
10180 *: "<Aleatório>" 10180 *: "[Aleatório]"
10181 </dest> 10181 </dest>
10182 <voice> 10182 <voice>
10183 *: "Aleatório" 10183 *: "Aleatório"
@@ -12032,13 +12032,13 @@
12032</phrase> 12032</phrase>
12033<phrase> 12033<phrase>
12034 id: LANG_TAGNAVI_UNTAGGED 12034 id: LANG_TAGNAVI_UNTAGGED
12035 desc: "<untagged>" entry in tag browser 12035 desc: "[untagged]" entry in tag browser
12036 user: core 12036 user: core
12037 <source> 12037 <source>
12038 *: "<Untagged>" 12038 *: "[Untagged]"
12039 </source> 12039 </source>
12040 <dest> 12040 <dest>
12041 *: "<Sem etiqueta>" 12041 *: "[Sem etiqueta]"
12042 </dest> 12042 </dest>
12043 <voice> 12043 <voice>
12044 *: "Sem etiqueta" 12044 *: "Sem etiqueta"
diff --git a/apps/lang/portugues.lang b/apps/lang/portugues.lang
index 69de38544d..a5e79b6275 100644
--- a/apps/lang/portugues.lang
+++ b/apps/lang/portugues.lang
@@ -4501,10 +4501,10 @@
4501 desc: in tag viewer 4501 desc: in tag viewer
4502 user: core 4502 user: core
4503 <source> 4503 <source>
4504 *: "<No Info>" 4504 *: "[No Info]"
4505 </source> 4505 </source>
4506 <dest> 4506 <dest>
4507 *: "<Sem Informação>" 4507 *: "[Sem Informação]"
4508 </dest> 4508 </dest>
4509 <voice> 4509 <voice>
4510 *: "Sem Informação" 4510 *: "Sem Informação"
@@ -7503,13 +7503,13 @@
7503</phrase> 7503</phrase>
7504<phrase> 7504<phrase>
7505 id: LANG_TAGNAVI_RANDOM 7505 id: LANG_TAGNAVI_RANDOM
7506 desc: "<Random>" entry in tag browser 7506 desc: "[Random]" entry in tag browser
7507 user: core 7507 user: core
7508 <source> 7508 <source>
7509 *: "<Random>" 7509 *: "[Random]"
7510 </source> 7510 </source>
7511 <dest> 7511 <dest>
7512 *: "<Aleatório>" 7512 *: "[Aleatório]"
7513 </dest> 7513 </dest>
7514 <voice> 7514 <voice>
7515 *: "Aleatório" 7515 *: "Aleatório"
@@ -8693,10 +8693,10 @@
8693 desc: top item in the list when asking user about bookmark auto load 8693 desc: top item in the list when asking user about bookmark auto load
8694 user: core 8694 user: core
8695 <source> 8695 <source>
8696 *: "<Don't Resume>" 8696 *: "[Don't Resume]"
8697 </source> 8697 </source>
8698 <dest> 8698 <dest>
8699 *: "<Não Retomar>" 8699 *: "[Não Retomar]"
8700 </dest> 8700 </dest>
8701 <voice> 8701 <voice>
8702 *: "Não Retomar" 8702 *: "Não Retomar"
@@ -8929,10 +8929,10 @@
8929 desc: bookmark selection list, bookmark couldn't be parsed 8929 desc: bookmark selection list, bookmark couldn't be parsed
8930 user: core 8930 user: core
8931 <source> 8931 <source>
8932 *: "<Invalid Bookmark>" 8932 *: "[Invalid Bookmark]"
8933 </source> 8933 </source>
8934 <dest> 8934 <dest>
8935 *: "<Favorito Inválido>" 8935 *: "[Favorito Inválido]"
8936 </dest> 8936 </dest>
8937 <voice> 8937 <voice>
8938 *: "Favorito Inválido" 8938 *: "Favorito Inválido"
@@ -9446,13 +9446,13 @@
9446</phrase> 9446</phrase>
9447<phrase> 9447<phrase>
9448 id: LANG_TAGNAVI_ALL_TRACKS 9448 id: LANG_TAGNAVI_ALL_TRACKS
9449 desc: "<All tracks>" entry in tag browser 9449 desc: "[All tracks]" entry in tag browser
9450 user: core 9450 user: core
9451 <source> 9451 <source>
9452 *: "<All tracks>" 9452 *: "[All tracks]"
9453 </source> 9453 </source>
9454 <dest> 9454 <dest>
9455 *: "<Todas as faixas>" 9455 *: "[Todas as faixas]"
9456 </dest> 9456 </dest>
9457 <voice> 9457 <voice>
9458 *: "Todas as faixas" 9458 *: "Todas as faixas"
@@ -11666,13 +11666,13 @@
11666</phrase> 11666</phrase>
11667<phrase> 11667<phrase>
11668 id: LANG_TAGNAVI_UNTAGGED 11668 id: LANG_TAGNAVI_UNTAGGED
11669 desc: "<untagged>" entry in tag browser 11669 desc: "[untagged]" entry in tag browser
11670 user: core 11670 user: core
11671 <source> 11671 <source>
11672 *: "<Untagged>" 11672 *: "[Untagged]"
11673 </source> 11673 </source>
11674 <dest> 11674 <dest>
11675 *: "<Sem Etiqueta>" 11675 *: "[Sem Etiqueta]"
11676 </dest> 11676 </dest>
11677 <voice> 11677 <voice>
11678 *: "Sem etiqueta" 11678 *: "Sem etiqueta"
diff --git a/apps/lang/romaneste.lang b/apps/lang/romaneste.lang
index e4c867e0b1..82b9be2433 100644
--- a/apps/lang/romaneste.lang
+++ b/apps/lang/romaneste.lang
@@ -1772,10 +1772,10 @@
1772 desc: in tag viewer 1772 desc: in tag viewer
1773 user: core 1773 user: core
1774 <source> 1774 <source>
1775 *: "<No Info>" 1775 *: "[No Info]"
1776 </source> 1776 </source>
1777 <dest> 1777 <dest>
1778 *: "<date lipsă>" 1778 *: "[date lipsă]"
1779 </dest> 1779 </dest>
1780 <voice> 1780 <voice>
1781 *: "date lipsă" 1781 *: "date lipsă"
@@ -2789,13 +2789,13 @@
2789</phrase> 2789</phrase>
2790<phrase> 2790<phrase>
2791 id: LANG_TAGNAVI_ALL_TRACKS 2791 id: LANG_TAGNAVI_ALL_TRACKS
2792 desc: "<All tracks>" entry in tag browser 2792 desc: "[All tracks]" entry in tag browser
2793 user: core 2793 user: core
2794 <source> 2794 <source>
2795 *: "<All tracks>" 2795 *: "[All tracks]"
2796 </source> 2796 </source>
2797 <dest> 2797 <dest>
2798 *: "<Toate pistele>" 2798 *: "[Toate pistele]"
2799 </dest> 2799 </dest>
2800 <voice> 2800 <voice>
2801 *: "All tracks" 2801 *: "All tracks"
@@ -4838,13 +4838,13 @@
4838</phrase> 4838</phrase>
4839<phrase> 4839<phrase>
4840 id: LANG_TAGNAVI_RANDOM 4840 id: LANG_TAGNAVI_RANDOM
4841 desc: "<Random>" entry in tag browser 4841 desc: "[Random]" entry in tag browser
4842 user: core 4842 user: core
4843 <source> 4843 <source>
4844 *: "<Random>" 4844 *: "[Random]"
4845 </source> 4845 </source>
4846 <dest> 4846 <dest>
4847 *: "<Aleator>" 4847 *: "[Aleator]"
4848 </dest> 4848 </dest>
4849 <voice> 4849 <voice>
4850 *: "Random" 4850 *: "Random"
@@ -8336,10 +8336,10 @@
8336 desc: top item in the list when asking user about bookmark auto load 8336 desc: top item in the list when asking user about bookmark auto load
8337 user: core 8337 user: core
8338 <source> 8338 <source>
8339 *: "<Don't Resume>" 8339 *: "[Don't Resume]"
8340 </source> 8340 </source>
8341 <dest> 8341 <dest>
8342 *: "<Nu continua redarea>" 8342 *: "[Nu continua redarea]"
8343 </dest> 8343 </dest>
8344 <voice> 8344 <voice>
8345 *: "Do not resume" 8345 *: "Do not resume"
@@ -8960,10 +8960,10 @@
8960 desc: bookmark selection list, bookmark couldn't be parsed 8960 desc: bookmark selection list, bookmark couldn't be parsed
8961 user: core 8961 user: core
8962 <source> 8962 <source>
8963 *: "<Invalid Bookmark>" 8963 *: "[Invalid Bookmark]"
8964 </source> 8964 </source>
8965 <dest> 8965 <dest>
8966 *: "<Semn de carte nevalid>" 8966 *: "[Semn de carte nevalid]"
8967 </dest> 8967 </dest>
8968 <voice> 8968 <voice>
8969 *: "Invalid Bookmark" 8969 *: "Invalid Bookmark"
@@ -11894,13 +11894,13 @@
11894</phrase> 11894</phrase>
11895<phrase> 11895<phrase>
11896 id: LANG_TAGNAVI_UNTAGGED 11896 id: LANG_TAGNAVI_UNTAGGED
11897 desc: "<untagged>" entry in tag browser 11897 desc: "[untagged]" entry in tag browser
11898 user: core 11898 user: core
11899 <source> 11899 <source>
11900 *: "<Untagged>" 11900 *: "[Untagged]"
11901 </source> 11901 </source>
11902 <dest> 11902 <dest>
11903 *: "<fără taguri>" 11903 *: "[fără taguri]"
11904 </dest> 11904 </dest>
11905 <voice> 11905 <voice>
11906 *: "fără taguri" 11906 *: "fără taguri"
diff --git a/apps/lang/russian.lang b/apps/lang/russian.lang
index 857dde6f97..6279da478a 100644
--- a/apps/lang/russian.lang
+++ b/apps/lang/russian.lang
@@ -4,12 +4,11 @@
4# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 4# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
5# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 5# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
6# \/ \/ \/ \/ \/ 6# \/ \/ \/ \/ \/
7# $Id$
8# 7#
9# This program is free software; you can redistribute it and/or 8# This program is free software; you can redistribute it and/or modify
10# modify it under the terms of the GNU General Public License 9# it under the terms of the GNU General Public License as published by
11# as published by the Free Software Foundation; either version 2 10# the Free Software Foundation; either version 2 of the License, or (at
12# of the License, or (at your option) any later version. 11# your option) any later version.
13# 12#
14# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 13# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
15# KIND, either express or implied. 14# KIND, either express or implied.
@@ -29,6 +28,8 @@
29# - James Hunt 28# - James Hunt
30# - Dmitriy Gamza 29# - Dmitriy Gamza
31# - Roman Levkin-Taymenev 30# - Roman Levkin-Taymenev
31# - Igor Poretsky
32# - Dmitry Prozorov
32<phrase> 33<phrase>
33 id: LANG_SET_BOOL_YES 34 id: LANG_SET_BOOL_YES
34 desc: bool true representation 35 desc: bool true representation
@@ -317,7 +318,7 @@
317</phrase> 318</phrase>
318<phrase> 319<phrase>
319 id: LANG_CHANNEL_STEREO 320 id: LANG_CHANNEL_STEREO
320 desc: in sound_settings 321 desc: in sound_settings and radio screen
321 user: core 322 user: core
322 <source> 323 <source>
323 *: "Stereo" 324 *: "Stereo"
@@ -331,7 +332,7 @@
331</phrase> 332</phrase>
332<phrase> 333<phrase>
333 id: LANG_CHANNEL_MONO 334 id: LANG_CHANNEL_MONO
334 desc: in sound_settings 335 desc: in sound_settings and radio screen
335 user: core 336 user: core
336 <source> 337 <source>
337 *: "Mono" 338 *: "Mono"
@@ -697,20 +698,6 @@
697 </voice> 698 </voice>
698</phrase> 699</phrase>
699<phrase> 700<phrase>
700 id: LANG_EQUALIZER_EDIT_MODE
701 desc: in the equalizer settings menu
702 user: core
703 <source>
704 *: "Edit mode: %s"
705 </source>
706 <dest>
707 *: "Режим редактированиÑ: %s"
708 </dest>
709 <voice>
710 *: ""
711 </voice>
712</phrase>
713<phrase>
714 id: LANG_EQUALIZER_GAIN_ITEM 701 id: LANG_EQUALIZER_GAIN_ITEM
715 desc: in the equalizer settings menu 702 desc: in the equalizer settings menu
716 user: core 703 user: core
@@ -1431,15 +1418,12 @@
1431 user: core 1418 user: core
1432 <source> 1419 <source>
1433 *: "Peak Meter" 1420 *: "Peak Meter"
1434 masd: none
1435 </source> 1421 </source>
1436 <dest> 1422 <dest>
1437 *: "Уровень Ñигнала" 1423 *: "Уровень Ñигнала"
1438 masd: none
1439 </dest> 1424 </dest>
1440 <voice> 1425 <voice>
1441 *: "Уровень Ñигнала" 1426 *: "Уровень Ñигнала"
1442 masd: none
1443 </voice> 1427 </voice>
1444</phrase> 1428</phrase>
1445<phrase> 1429<phrase>
@@ -2036,23 +2020,6 @@
2036 </voice> 2020 </voice>
2037</phrase> 2021</phrase>
2038<phrase> 2022<phrase>
2039 id: LANG_RECORD_DIRECTORY
2040 desc: in recording settings_menu
2041 user: core
2042 <source>
2043 *: none
2044 recording: "Directory"
2045 </source>
2046 <dest>
2047 *: none
2048 recording: "Папка"
2049 </dest>
2050 <voice>
2051 *: none
2052 recording: "Папка"
2053 </voice>
2054</phrase>
2055<phrase>
2056 id: LANG_RECORD_TRIGGER 2023 id: LANG_RECORD_TRIGGER
2057 desc: in recording settings_menu 2024 desc: in recording settings_menu
2058 user: core 2025 user: core
@@ -2818,15 +2785,12 @@
2818 user: core 2785 user: core
2819 <source> 2786 <source>
2820 *: "Peak Release" 2787 *: "Peak Release"
2821 masd: none
2822 </source> 2788 </source>
2823 <dest> 2789 <dest>
2824 *: "Ð¡Ð±Ñ€Ð¾Ñ Ð¿Ð¸ÐºÐ¾Ð²" 2790 *: "Ð¡Ð±Ñ€Ð¾Ñ Ð¿Ð¸ÐºÐ¾Ð²"
2825 masd: none
2826 </dest> 2791 </dest>
2827 <voice> 2792 <voice>
2828 *: "Ð¡Ð±Ñ€Ð¾Ñ Ð¿Ð¸ÐºÐ¾Ð²" 2793 *: "Ð¡Ð±Ñ€Ð¾Ñ Ð¿Ð¸ÐºÐ¾Ð²"
2829 masd: none
2830 </voice> 2794 </voice>
2831</phrase> 2795</phrase>
2832<phrase> 2796<phrase>
@@ -2835,15 +2799,12 @@
2835 user: core 2799 user: core
2836 <source> 2800 <source>
2837 *: "Peak Hold Time" 2801 *: "Peak Hold Time"
2838 masd: none
2839 </source> 2802 </source>
2840 <dest> 2803 <dest>
2841 *: "Ð’Ñ€ÐµÐ¼Ñ ÑƒÐ´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¿Ð¸ÐºÐ¾Ð²" 2804 *: "Ð’Ñ€ÐµÐ¼Ñ ÑƒÐ´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¿Ð¸ÐºÐ¾Ð²"
2842 masd: none
2843 </dest> 2805 </dest>
2844 <voice> 2806 <voice>
2845 *: "Ð’Ñ€ÐµÐ¼Ñ ÑƒÐ´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¿Ð¸ÐºÐ¾Ð²" 2807 *: "Ð’Ñ€ÐµÐ¼Ñ ÑƒÐ´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¿Ð¸ÐºÐ¾Ð²"
2846 masd: none
2847 </voice> 2808 </voice>
2848</phrase> 2809</phrase>
2849<phrase> 2810<phrase>
@@ -2852,15 +2813,12 @@
2852 user: core 2813 user: core
2853 <source> 2814 <source>
2854 *: "Clip Hold Time" 2815 *: "Clip Hold Time"
2855 masd: none
2856 </source> 2816 </source>
2857 <dest> 2817 <dest>
2858 *: "Ð’Ñ€ÐµÐ¼Ñ ÑƒÐ´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¸Ð½Ð´Ð¸ÐºÐ°Ñ‚Ð¾Ñ€Ð°" 2818 *: "Ð’Ñ€ÐµÐ¼Ñ ÑƒÐ´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¸Ð½Ð´Ð¸ÐºÐ°Ñ‚Ð¾Ñ€Ð°"
2859 masd: none
2860 </dest> 2819 </dest>
2861 <voice> 2820 <voice>
2862 *: "Ð’Ñ€ÐµÐ¼Ñ ÑƒÐ´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¸Ð½Ð´Ð¸ÐºÐ°Ñ‚Ð¾Ñ€Ð°" 2821 *: "Ð’Ñ€ÐµÐ¼Ñ ÑƒÐ´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¸Ð½Ð´Ð¸ÐºÐ°Ñ‚Ð¾Ñ€Ð°"
2863 masd: none
2864 </voice> 2822 </voice>
2865</phrase> 2823</phrase>
2866<phrase> 2824<phrase>
@@ -2869,15 +2827,12 @@
2869 user: core 2827 user: core
2870 <source> 2828 <source>
2871 *: "Eternal" 2829 *: "Eternal"
2872 masd: none
2873 </source> 2830 </source>
2874 <dest> 2831 <dest>
2875 *: "ПоÑтоÑнно" 2832 *: "ПоÑтоÑнно"
2876 masd: none
2877 </dest> 2833 </dest>
2878 <voice> 2834 <voice>
2879 *: "ПоÑтоÑнно" 2835 *: "ПоÑтоÑнно"
2880 masd: none
2881 </voice> 2836 </voice>
2882</phrase> 2837</phrase>
2883<phrase> 2838<phrase>
@@ -2886,15 +2841,12 @@
2886 user: core 2841 user: core
2887 <source> 2842 <source>
2888 *: "Scale" 2843 *: "Scale"
2889 masd: none
2890 </source> 2844 </source>
2891 <dest> 2845 <dest>
2892 *: "Шкала" 2846 *: "Шкала"
2893 masd: none
2894 </dest> 2847 </dest>
2895 <voice> 2848 <voice>
2896 *: "Шкала" 2849 *: "Шкала"
2897 masd: none
2898 </voice> 2850 </voice>
2899</phrase> 2851</phrase>
2900<phrase> 2852<phrase>
@@ -2903,15 +2855,12 @@
2903 user: core 2855 user: core
2904 <source> 2856 <source>
2905 *: "Logarithmic (dB)" 2857 *: "Logarithmic (dB)"
2906 masd: none
2907 </source> 2858 </source>
2908 <dest> 2859 <dest>
2909 *: "ЛогарифмичеÑÐºÐ°Ñ (дБ)" 2860 *: "ЛогарифмичеÑÐºÐ°Ñ (дБ)"
2910 masd: none
2911 </dest> 2861 </dest>
2912 <voice> 2862 <voice>
2913 *: "ЛогарифмичеÑÐºÐ°Ñ Ð² децибелах" 2863 *: "ЛогарифмичеÑÐºÐ°Ñ Ð² децибелах"
2914 masd: none
2915 </voice> 2864 </voice>
2916</phrase> 2865</phrase>
2917<phrase> 2866<phrase>
@@ -2920,15 +2869,12 @@
2920 user: core 2869 user: core
2921 <source> 2870 <source>
2922 *: "Linear (%)" 2871 *: "Linear (%)"
2923 masd: none
2924 </source> 2872 </source>
2925 <dest> 2873 <dest>
2926 *: "Ð›Ð¸Ð½ÐµÐ¹Ð½Ð°Ñ (%)" 2874 *: "Ð›Ð¸Ð½ÐµÐ¹Ð½Ð°Ñ (%)"
2927 masd: none
2928 </dest> 2875 </dest>
2929 <voice> 2876 <voice>
2930 *: "Ð›Ð¸Ð½ÐµÐ¹Ð½Ð°Ñ Ð² процентах" 2877 *: "Ð›Ð¸Ð½ÐµÐ¹Ð½Ð°Ñ Ð² процентах"
2931 masd: none
2932 </voice> 2878 </voice>
2933</phrase> 2879</phrase>
2934<phrase> 2880<phrase>
@@ -2937,15 +2883,12 @@
2937 user: core 2883 user: core
2938 <source> 2884 <source>
2939 *: "Minimum Of Range" 2885 *: "Minimum Of Range"
2940 masd: none
2941 </source> 2886 </source>
2942 <dest> 2887 <dest>
2943 *: "ÐижнÑÑ Ð³Ñ€Ð°Ð½Ð¸Ñ†Ð° диапазона" 2888 *: "ÐижнÑÑ Ð³Ñ€Ð°Ð½Ð¸Ñ†Ð° диапазона"
2944 masd: none
2945 </dest> 2889 </dest>
2946 <voice> 2890 <voice>
2947 *: "ÐижнÑÑ Ð³Ñ€Ð°Ð½Ð¸Ñ†Ð° диапазона" 2891 *: "ÐижнÑÑ Ð³Ñ€Ð°Ð½Ð¸Ñ†Ð° диапазона"
2948 masd: none
2949 </voice> 2892 </voice>
2950</phrase> 2893</phrase>
2951<phrase> 2894<phrase>
@@ -2954,15 +2897,12 @@
2954 user: core 2897 user: core
2955 <source> 2898 <source>
2956 *: "Maximum Of Range" 2899 *: "Maximum Of Range"
2957 masd: none
2958 </source> 2900 </source>
2959 <dest> 2901 <dest>
2960 *: "ВерхнÑÑ Ð³Ñ€Ð°Ð½Ð¸Ñ†Ð° диапазона" 2902 *: "ВерхнÑÑ Ð³Ñ€Ð°Ð½Ð¸Ñ†Ð° диапазона"
2961 masd: none
2962 </dest> 2903 </dest>
2963 <voice> 2904 <voice>
2964 *: "ВерхнÑÑ Ð³Ñ€Ð°Ð½Ð¸Ñ†Ð° диапазона" 2905 *: "ВерхнÑÑ Ð³Ñ€Ð°Ð½Ð¸Ñ†Ð° диапазона"
2965 masd: none
2966 </voice> 2906 </voice>
2967</phrase> 2907</phrase>
2968<phrase> 2908<phrase>
@@ -3354,62 +3294,6 @@
3354 </voice> 3294 </voice>
3355</phrase> 3295</phrase>
3356<phrase> 3296<phrase>
3357 id: LANG_INSERT
3358 desc: in onplay menu. insert a track/playlist into dynamic playlist.
3359 user: core
3360 <source>
3361 *: "Insert"
3362 </source>
3363 <dest>
3364 *: "Ð’Ñтавить"
3365 </dest>
3366 <voice>
3367 *: "Ð’Ñтавить"
3368 </voice>
3369</phrase>
3370<phrase>
3371 id: LANG_INSERT_FIRST
3372 desc: in onplay menu. insert a track/playlist into dynamic playlist.
3373 user: core
3374 <source>
3375 *: "Insert Next"
3376 </source>
3377 <dest>
3378 *: "Ð’Ñтавить Ñледующим"
3379 </dest>
3380 <voice>
3381 *: "Ð’Ñтавить Ñледующим"
3382 </voice>
3383</phrase>
3384<phrase>
3385 id: LANG_INSERT_LAST
3386 desc: in onplay menu. append a track/playlist into dynamic playlist.
3387 user: core
3388 <source>
3389 *: "Insert Last"
3390 </source>
3391 <dest>
3392 *: "Ð’Ñтавить в конец"
3393 </dest>
3394 <voice>
3395 *: "Ð’Ñтавить в конец"
3396 </voice>
3397</phrase>
3398<phrase>
3399 id: LANG_INSERT_SHUFFLED
3400 desc: in onplay menu. insert a track/playlist randomly into dynamic playlist
3401 user: core
3402 <source>
3403 *: "Insert Shuffled"
3404 </source>
3405 <dest>
3406 *: "Ð’Ñтавить в Ñлучайное меÑто"
3407 </dest>
3408 <voice>
3409 *: "Ð’Ñтавить в Ñлучайное меÑто"
3410 </voice>
3411</phrase>
3412<phrase>
3413 id: LANG_QUEUE 3297 id: LANG_QUEUE
3414 desc: The verb/action Queue 3298 desc: The verb/action Queue
3415 user: core 3299 user: core
@@ -3521,24 +3405,7 @@
3521 </dest> 3405 </dest>
3522 <voice> 3406 <voice>
3523 *: none 3407 *: none
3524 charging: "ÐккумулÑтор: ЗарÑжаетÑÑ" 3408 charging: "ÐккумулÑтор зарÑжен"
3525 </voice>
3526</phrase>
3527<phrase>
3528 id: LANG_BATTERY_TRICKLE_CHARGE
3529 desc: in info display, shows that trickle charge is running
3530 user: core
3531 <source>
3532 *: none
3533 charging: "Battery: Trickle Chg"
3534 </source>
3535 <dest>
3536 *: none
3537 charging: "ÐккумулÑтор: ИмпульÑн. зарÑд"
3538 </dest>
3539 <voice>
3540 *: none
3541 charging: "ÐккумулÑтор: ИмпульÑн. зарÑд"
3542 </voice> 3409 </voice>
3543</phrase> 3410</phrase>
3544<phrase> 3411<phrase>
@@ -3548,11 +3415,10 @@
3548 <source> 3415 <source>
3549 *: "Battery: %d%% %dh %dm" 3416 *: "Battery: %d%% %dh %dm"
3550 ipodmini1g,ipodmini2g,iriverh10: "Batt: %d%% %dh %dm" 3417 ipodmini1g,ipodmini2g,iriverh10: "Batt: %d%% %dh %dm"
3551 iriverifp7xx: "%d%% %dh %dm"
3552 </source> 3418 </source>
3553 <dest> 3419 <dest>
3554 *: "%d%% %dч %dм" 3420 *: "%d%% %dч %dм"
3555 ipodmini1g,ipodmini2g,iriverh10,iriverifp7xx: "%d%% %dч %dм" 3421 ipodmini1g,ipodmini2g,iriverh10: "%d%% %dч %dм"
3556 </dest> 3422 </dest>
3557 <voice> 3423 <voice>
3558 *: "ЗарÑд аккумулÑтора" 3424 *: "ЗарÑд аккумулÑтора"
@@ -3569,7 +3435,7 @@
3569 *: "ДиÑк:" 3435 *: "ДиÑк:"
3570 </dest> 3436 </dest>
3571 <voice> 3437 <voice>
3572 *: "ÐиÑк:" 3438 *: "Размер диÑка"
3573 </voice> 3439 </voice>
3574</phrase> 3440</phrase>
3575<phrase> 3441<phrase>
@@ -3592,39 +3458,41 @@
3592 user: core 3458 user: core
3593 <source> 3459 <source>
3594 *: "Int:" 3460 *: "Int:"
3595 xduoox3: "mSD1:"
3596 hibylinux: "mSD:" 3461 hibylinux: "mSD:"
3462 xduoox3: "mSD1:"
3597 </source> 3463 </source>
3598 <dest> 3464 <dest>
3599 *: "Внутр:" 3465 *: "Внутр:"
3600 xduoox3: "mSD1:" 3466 hibylinux,xduoox3: "микро Ð­Ñ Ð”Ð¸:"
3601 hibylinux: "mSD:"
3602 </dest> 3467 </dest>
3603 <voice> 3468 <voice>
3604 *: "Внутр" 3469 *: "Внутр"
3605 xduoox3: "микро Ð­Ñ Ð”Ð¸ 1"
3606 hibylinux: "микро Ð­Ñ Ð”Ð¸" 3470 hibylinux: "микро Ð­Ñ Ð”Ð¸"
3471 xduoox3: "микро Ð­Ñ Ð”Ð¸ 1"
3607 </voice> 3472 </voice>
3608</phrase> 3473</phrase>
3609<phrase> 3474<phrase>
3610 id: LANG_DISK_NAME_MMC 3475 id: LANG_DISK_NAME_MMC
3611 desc: in info menu; name for external disk with multivolume (Ondio; keep short!) 3476 desc: in info menu; name for external disk with multivolume (keep short!)
3612 user: core 3477 user: core
3613 <source> 3478 <source>
3614 *: none 3479 *: none
3615 multivolume: "HD1" 3480 hibylinux: "USB:"
3481 multivolume: "HD1:"
3616 sansac200*,sansaclipplus,sansae200*,sansafuze*: "mSD:" 3482 sansac200*,sansaclipplus,sansae200*,sansafuze*: "mSD:"
3617 xduoox3: "mSD2:" 3483 xduoox3: "mSD2:"
3618 </source> 3484 </source>
3619 <dest> 3485 <dest>
3620 *: none 3486 *: none
3621 multivolume: "HD1" 3487 hibylinux: "ЮСБ:"
3622 sansac200*,sansaclipplus,sansae200*,sansafuze*: "mSD:" 3488 multivolume: "ХД1"
3623 xduoox3: "mSD2:" 3489 sansac200*,sansaclipplus,sansae200*,sansafuze*: "микро Ð­Ñ Ð”Ð¸:"
3490 xduoox3: "микро Ð­Ñ Ð”Ð¸2:"
3624 </dest> 3491 </dest>
3625 <voice> 3492 <voice>
3626 *: none 3493 *: none
3627 multivolume: "Эйч Ди 1" 3494 hibylinux: "Ю С Б"
3495 multivolume: "Х Д 1"
3628 sansac200*,sansaclipplus,sansae200*,sansafuze*: "микро Ð­Ñ Ð”Ð¸" 3496 sansac200*,sansaclipplus,sansae200*,sansafuze*: "микро Ð­Ñ Ð”Ð¸"
3629 xduoox3: "микро Ð­Ñ Ð”Ð¸ 2" 3497 xduoox3: "микро Ð­Ñ Ð”Ð¸ 2"
3630 </voice> 3498 </voice>
@@ -3643,7 +3511,7 @@
3643 </dest> 3511 </dest>
3644 <voice> 3512 <voice>
3645 *: none 3513 *: none
3646 rtc: "Текущее времÑ:" 3514 rtc: "Текущее времÑ"
3647 </voice> 3515 </voice>
3648</phrase> 3516</phrase>
3649<phrase> 3517<phrase>
@@ -3771,7 +3639,7 @@
3771 <dest> 3639 <dest>
3772 *: none 3640 *: none
3773 gigabeatfx,mrobe500,rtc: "ВЫКЛ. = Отмена" 3641 gigabeatfx,mrobe500,rtc: "ВЫКЛ. = Отмена"
3774 gigabeats: "ÐÐЗÐД = Отмена" 3642 gigabeats,sansafuzeplus: "ÐÐЗÐД = Отмена"
3775 gogearsa9200: "ЛЕВО = Отмена" 3643 gogearsa9200: "ЛЕВО = Отмена"
3776 iaudiom5,iaudiox5: "ЗÐПИСЬ = Отмена" 3644 iaudiom5,iaudiox5: "ЗÐПИСЬ = Отмена"
3777 ipod*,mpiohd300,sansac200*: "МЕÐЮ = Отмена" 3645 ipod*,mpiohd300,sansac200*: "МЕÐЮ = Отмена"
@@ -3783,6 +3651,7 @@
3783 </dest> 3651 </dest>
3784 <voice> 3652 <voice>
3785 *: none 3653 *: none
3654 gigabeat*,gogearsa9200,iaudiom5,iaudiox5,ipod*,iriverh10,iriverh100,iriverh10_5gb,iriverh120,iriverh300,mrobe100,rtc,samsungyh*,sansac200*,sansae200*: ""
3786 </voice> 3655 </voice>
3787</phrase> 3656</phrase>
3788<phrase> 3657<phrase>
@@ -3840,6 +3709,7 @@
3840 </dest> 3709 </dest>
3841 <voice> 3710 <voice>
3842 *: none 3711 *: none
3712 iaudiom5,iaudiox5,iriverh100,iriverh120,iriverh300,recording,samsungyh*,sansac200*,sansae200*,vibe500: ""
3843 </voice> 3713 </voice>
3844</phrase> 3714</phrase>
3845<phrase> 3715<phrase>
@@ -3911,23 +3781,6 @@
3911 </voice> 3781 </voice>
3912</phrase> 3782</phrase>
3913<phrase> 3783<phrase>
3914 id: LANG_DB_INF
3915 desc: -inf db for values below measurement
3916 user: core
3917 <source>
3918 *: none
3919 recording: "-inf"
3920 </source>
3921 <dest>
3922 *: none
3923 recording: "-inf"
3924 </dest>
3925 <voice>
3926 *: none
3927 recording: "-inf"
3928 </voice>
3929</phrase>
3930<phrase>
3931 id: LANG_ALARM_MOD_TIME 3784 id: LANG_ALARM_MOD_TIME
3932 desc: The current alarm time shown in the alarm menu for the RTC alarm mod. 3785 desc: The current alarm time shown in the alarm menu for the RTC alarm mod.
3933 user: core 3786 user: core
@@ -3962,23 +3815,6 @@
3962 </voice> 3815 </voice>
3963</phrase> 3816</phrase>
3964<phrase> 3817<phrase>
3965 id: LANG_ALARM_MOD_SHUTDOWN
3966 desc: The text that tells the user that the alarm time is ok and the device shuts off (for the RTC alarm mod).
3967 user: core
3968 <source>
3969 *: none
3970 alarm: "Alarm Set"
3971 </source>
3972 <dest>
3973 *: none
3974 alarm: "УÑтановить будильник"
3975 </dest>
3976 <voice>
3977 *: none
3978 alarm: "УÑтановить будильник"
3979 </voice>
3980</phrase>
3981<phrase>
3982 id: LANG_ALARM_MOD_ERROR 3818 id: LANG_ALARM_MOD_ERROR
3983 desc: The text that tells that the time is incorrect (for the RTC alarm mod). 3819 desc: The text that tells that the time is incorrect (for the RTC alarm mod).
3984 user: core 3820 user: core
@@ -3996,35 +3832,6 @@
3996 </voice> 3832 </voice>
3997</phrase> 3833</phrase>
3998<phrase> 3834<phrase>
3999 id: LANG_ALARM_MOD_KEYS
4000 desc: Shown key functions in alarm menu (for the RTC alarm mod).
4001 user: core
4002 <source>
4003 *: none
4004 alarm: "PLAY=Set OFF=Cancel"
4005 gigabeats: "SELECT=Set POWER=Cancel"
4006 ipod*: "SELECT=Set MENU=Cancel"
4007 iriverh10,iriverh10_5gb: "SELECT=Set PREV=Cancel"
4008 mpiohd300: "ENTER=Set MENU=Cancel"
4009 sansafuzeplus: "SELECT=Set BACK=Cancel"
4010 vibe500: "OK=Set C=Cancel"
4011 </source>
4012 <dest>
4013 *: none
4014 alarm: "ВОСПР.=УÑÑ‚., ВЫКЛ.=Отм."
4015 gigabeats: "ВЫБОР=УÑÑ‚., ВЫКЛ.=Отм."
4016 ipod*: "ВЫБОР=УÑÑ‚., МЕÐЮ=Отм."
4017 iriverh10,iriverh10_5gb: "ВЫБОР=УÑÑ‚., ПРЕД.=Отм."
4018 mpiohd300: "ENTER=УÑÑ‚., MENU=Отм."
4019 sansafuzeplus: "SELECT=УÑÑ‚., BACK=Отм."
4020 vibe500: "OK=УÑÑ‚., C=Отм."
4021 </dest>
4022 <voice>
4023 *: none
4024 alarm,gigabeats,ipod*,iriverh10,iriverh10_5gb: ""
4025 </voice>
4026</phrase>
4027<phrase>
4028 id: LANG_ALARM_MOD_DISABLE 3835 id: LANG_ALARM_MOD_DISABLE
4029 desc: Announce that the RTC alarm has been turned off 3836 desc: Announce that the RTC alarm has been turned off
4030 user: core 3837 user: core
@@ -4196,20 +4003,6 @@
4196 </voice> 4003 </voice>
4197</phrase> 4004</phrase>
4198<phrase> 4005<phrase>
4199 id: LANG_ID3_ALBUM_GAIN
4200 desc: in tag viewer
4201 user: core
4202 <source>
4203 *: "Album Gain"
4204 </source>
4205 <dest>
4206 *: "УÑиление альбома"
4207 </dest>
4208 <voice>
4209 *: "УÑиление альбома"
4210 </voice>
4211</phrase>
4212<phrase>
4213 id: LANG_ID3_PATH 4006 id: LANG_ID3_PATH
4214 desc: in tag viewer 4007 desc: in tag viewer
4215 user: core 4008 user: core
@@ -4228,10 +4021,10 @@
4228 desc: in tag viewer 4021 desc: in tag viewer
4229 user: core 4022 user: core
4230 <source> 4023 <source>
4231 *: "<No Info>" 4024 *: "[No Info]"
4232 </source> 4025 </source>
4233 <dest> 4026 <dest>
4234 *: "<Ðет информ.>" 4027 *: "[Ðет информ.]"
4235 </dest> 4028 </dest>
4236 <voice> 4029 <voice>
4237 *: "Ðет информации" 4030 *: "Ðет информации"
@@ -5218,7 +5011,7 @@
5218 *: "." 5011 *: "."
5219 </source> 5012 </source>
5220 <dest> 5013 <dest>
5221 *: "." 5014 *: "~."
5222 </dest> 5015 </dest>
5223 <voice> 5016 <voice>
5224 *: "точка" 5017 *: "точка"
@@ -5823,10 +5616,10 @@
5823 *: "Saved %d tracks (%s)" 5616 *: "Saved %d tracks (%s)"
5824 </source> 5617 </source>
5825 <dest> 5618 <dest>
5826 *: "Сохранено %d треков (%d)" 5619 *: "Сохранено %d треков (%s)"
5827 </dest> 5620 </dest>
5828 <voice> 5621 <voice>
5829 *: "ТрÑков Ñохранено" 5622 *: "Треков Ñохранено"
5830 </voice> 5623 </voice>
5831</phrase> 5624</phrase>
5832<phrase> 5625<phrase>
@@ -5872,20 +5665,6 @@
5872 </voice> 5665 </voice>
5873</phrase> 5666</phrase>
5874<phrase> 5667<phrase>
5875 id: LANG_PLAYLIST_CONTROL_UPDATE_ERROR
5876 desc: Playlist error
5877 user: core
5878 <source>
5879 *: "Error updating playlist control file"
5880 </source>
5881 <dest>
5882 *: "Ошибка при обновлении файла ÑпиÑка воÑпроизведениÑ"
5883 </dest>
5884 <voice>
5885 *: "Ошибка при обновлении файла ÑпиÑка воÑпроизведениÑ"
5886 </voice>
5887</phrase>
5888<phrase>
5889 id: LANG_PLAYLIST_ACCESS_ERROR 5668 id: LANG_PLAYLIST_ACCESS_ERROR
5890 desc: Playlist error 5669 desc: Playlist error
5891 user: core 5670 user: core
@@ -6044,91 +5823,6 @@
6044 </voice> 5823 </voice>
6045</phrase> 5824</phrase>
6046<phrase> 5825<phrase>
6047 id: LANG_BUTTONBAR_MENU
6048 desc: in button bar
6049 user: core
6050 <source>
6051 *: none
6052 radio_screen_button_bar: "Menu"
6053 </source>
6054 <dest>
6055 *: none
6056 radio_screen_button_bar: "Меню"
6057 </dest>
6058 <voice>
6059 *: none
6060 radio_screen_button_bar: ""
6061 </voice>
6062</phrase>
6063<phrase>
6064 id: LANG_FM_BUTTONBAR_EXIT
6065 desc: in radio screen
6066 user: core
6067 <source>
6068 *: none
6069 radio_screen_button_bar: "Exit"
6070 </source>
6071 <dest>
6072 *: none
6073 radio_screen_button_bar: "Выход"
6074 </dest>
6075 <voice>
6076 *: none
6077 radio_screen_button_bar: ""
6078 </voice>
6079</phrase>
6080<phrase>
6081 id: LANG_FM_BUTTONBAR_ACTION
6082 desc: in radio screen
6083 user: core
6084 <source>
6085 *: none
6086 radio_screen_button_bar: "Action"
6087 </source>
6088 <dest>
6089 *: none
6090 radio_screen_button_bar: "ДейÑтвие"
6091 </dest>
6092 <voice>
6093 *: none
6094 radio_screen_button_bar: ""
6095 </voice>
6096</phrase>
6097<phrase>
6098 id: LANG_FM_BUTTONBAR_ADD
6099 desc: in radio screen
6100 user: core
6101 <source>
6102 *: none
6103 radio_screen_button_bar: "Add"
6104 </source>
6105 <dest>
6106 *: none
6107 radio_screen_button_bar: "Добавить"
6108 </dest>
6109 <voice>
6110 *: none
6111 radio_screen_button_bar: ""
6112 </voice>
6113</phrase>
6114<phrase>
6115 id: LANG_FM_BUTTONBAR_RECORD
6116 desc: in radio screen
6117 user: core
6118 <source>
6119 *: none
6120 radio_screen_button_bar: "Record"
6121 </source>
6122 <dest>
6123 *: none
6124 radio_screen_button_bar: "ЗапиÑÑŒ"
6125 </dest>
6126 <voice>
6127 *: none
6128 radio_screen_button_bar: ""
6129 </voice>
6130</phrase>
6131<phrase>
6132 id: LANG_FM_MONO_MODE 5826 id: LANG_FM_MONO_MODE
6133 desc: in radio screen 5827 desc: in radio screen
6134 user: core 5828 user: core
@@ -6333,7 +6027,7 @@
6333</phrase> 6027</phrase>
6334<phrase> 6028<phrase>
6335 id: LANG_OFF_ABORT 6029 id: LANG_OFF_ABORT
6336 desc: Used on archosrecorder models 6030 desc: Used on many models
6337 user: core 6031 user: core
6338 <source> 6032 <source>
6339 *: "OFF to abort" 6033 *: "OFF to abort"
@@ -6347,7 +6041,7 @@
6347 <dest> 6041 <dest>
6348 *: "ВЫКЛ. Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹" 6042 *: "ВЫКЛ. Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹"
6349 gigabeatfx: "ВЫКЛ. Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹" 6043 gigabeatfx: "ВЫКЛ. Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹"
6350 gigabeats: "ÐÐЗÐД Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹" 6044 gigabeats,sansafuzeplus: "ÐÐЗÐД Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹"
6351 iaudiom5,iaudiox5: "ВОСПР. Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹" 6045 iaudiom5,iaudiox5: "ВОСПР. Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹"
6352 ipod*: "ПÐÐ£Ð—Ð Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹" 6046 ipod*: "ПÐÐ£Ð—Ð Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹"
6353 iriverh10,iriverh10_5gb,sansac200*,sansae200*,vibe500: "ПРЕД. Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹" 6047 iriverh10,iriverh10_5gb,sansac200*,sansae200*,vibe500: "ПРЕД. Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹"
@@ -6485,7 +6179,7 @@
6485</phrase> 6179</phrase>
6486<phrase> 6180<phrase>
6487 id: LANG_PLUGIN_WRONG_MODEL 6181 id: LANG_PLUGIN_WRONG_MODEL
6488 desc: The plugin is not compatible with the archos model trying to run it 6182 desc: The plugin is not compatible with the player model trying to run it
6489 user: core 6183 user: core
6490 <source> 6184 <source>
6491 *: "Incompatible model" 6185 *: "Incompatible model"
@@ -6697,7 +6391,6 @@
6697 user: core 6391 user: core
6698 <source> 6392 <source>
6699 *: "Building database... %d found (OFF to return)" 6393 *: "Building database... %d found (OFF to return)"
6700 archosplayer: "Building DB %d found"
6701 gigabeat*,iaudiom5,iaudiox5,mrobe100,samsungyh*: "Building database... %d found (LEFT to return)" 6394 gigabeat*,iaudiom5,iaudiox5,mrobe100,samsungyh*: "Building database... %d found (LEFT to return)"
6702 gogearsa9200: "Building database... %d found (REW to return)" 6395 gogearsa9200: "Building database... %d found (REW to return)"
6703 ipod*,iriverh10,iriverh10_5gb,sansac200*,sansae200*,sansafuze*,vibe500: "Building database... %d found (PREV to return)" 6396 ipod*,iriverh10,iriverh10_5gb,sansac200*,sansae200*,sansafuze*,vibe500: "Building database... %d found (PREV to return)"
@@ -6705,7 +6398,6 @@
6705 </source> 6398 </source>
6706 <dest> 6399 <dest>
6707 *: "ПоÑтроение базы... %d найдено (ВЫКЛ. Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹)" 6400 *: "ПоÑтроение базы... %d найдено (ВЫКЛ. Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹)"
6708 archosplayer: "ПоÑтроение БД... %d найдено"
6709 gigabeat*,iaudiom5,iaudiox5,mrobe100,samsungyh*: "ПоÑтроение базы... %d найдено (ВЛЕВО Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹)" 6401 gigabeat*,iaudiom5,iaudiox5,mrobe100,samsungyh*: "ПоÑтроение базы... %d найдено (ВЛЕВО Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹)"
6710 gogearsa9200: "ПоÑтроение базы... %d найдено (РЕВЕРС. Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹)" 6402 gogearsa9200: "ПоÑтроение базы... %d найдено (РЕВЕРС. Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹)"
6711 ipod*,iriverh10,iriverh10_5gb,sansac200*,sansae200*,sansafuze*,vibe500: "ПоÑтроение базы... %d найдено (ПРЕД. Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹)" 6403 ipod*,iriverh10,iriverh10_5gb,sansac200*,sansae200*,sansafuze*,vibe500: "ПоÑтроение базы... %d найдено (ПРЕД. Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹)"
@@ -6726,18 +6418,18 @@
6726 *: "Сканирование диÑка..." 6418 *: "Сканирование диÑка..."
6727 </dest> 6419 </dest>
6728 <voice> 6420 <voice>
6729 *: "Сканирование диÑка..." 6421 *: "Сканирование диÑка"
6730 </voice> 6422 </voice>
6731</phrase> 6423</phrase>
6732<phrase> 6424<phrase>
6733 id: LANG_TAGNAVI_ALL_TRACKS 6425 id: LANG_TAGNAVI_ALL_TRACKS
6734 desc: "<All tracks>" entry in tag browser 6426 desc: "[All tracks]" entry in tag browser
6735 user: core 6427 user: core
6736 <source> 6428 <source>
6737 *: "<All tracks>" 6429 *: "[All tracks]"
6738 </source> 6430 </source>
6739 <dest> 6431 <dest>
6740 *: "<Ð’Ñе треки>" 6432 *: "[Ð’Ñе треки]"
6741 </dest> 6433 </dest>
6742 <voice> 6434 <voice>
6743 *: "Ð’Ñе Ñ‚Ñ€Ñки" 6435 *: "Ð’Ñе Ñ‚Ñ€Ñки"
@@ -6892,7 +6584,7 @@
6892 *: "Удаление..." 6584 *: "Удаление..."
6893 </dest> 6585 </dest>
6894 <voice> 6586 <voice>
6895 *: "Удаление..." 6587 *: "Удаление"
6896 </voice> 6588 </voice>
6897</phrase> 6589</phrase>
6898<phrase> 6590<phrase>
@@ -7318,14 +7010,12 @@
7318 user: core 7010 user: core
7319 <source> 7011 <source>
7320 *: "Buffer:" 7012 *: "Buffer:"
7321 archosplayer: "Buf:"
7322 </source> 7013 </source>
7323 <dest> 7014 <dest>
7324 *: "Буфер:" 7015 *: "Буфер:"
7325 archosplayer: "Буф:"
7326 </dest> 7016 </dest>
7327 <voice> 7017 <voice>
7328 *: "Ðуфер:" 7018 *: "Размер буфера"
7329 </voice> 7019 </voice>
7330</phrase> 7020</phrase>
7331<phrase> 7021<phrase>
@@ -7396,17 +7086,14 @@
7396 user: core 7086 user: core
7397 <source> 7087 <source>
7398 *: "PLAY = Yes" 7088 *: "PLAY = Yes"
7399 archosplayer: "(PLAY/STOP)"
7400 cowond2*: "MENU, or top-right = Yes" 7089 cowond2*: "MENU, or top-right = Yes"
7401 creativezen*: "SELECT = Yes" 7090 creativezen*,gigabeat*,iaudiom5,iaudiox5,ipod*,iriverh10,iriverh10_5gb,mrobe100,sansac200*,sansaclip*,sansaconnect,sansae200*,sansafuze*: "SELECT = Yes"
7402 gigabeat*,iaudiom5,iaudiox5,ipod*,iriverh10,iriverh10_5gb,mrobe100,sansac200*,sansaclip*,sansaconnect,sansae200*,sansafuze*: "SELECT = Yes"
7403 iriverh100,iriverh120,iriverh300: "NAVI = Yes" 7091 iriverh100,iriverh120,iriverh300: "NAVI = Yes"
7404 mrobe500: "PLAY, POWER, or top-right = Yes" 7092 mrobe500: "PLAY, POWER, or top-right = Yes"
7405 vibe500: "OK = Yes" 7093 vibe500: "OK = Yes"
7406 </source> 7094 </source>
7407 <dest> 7095 <dest>
7408 *: "ВОСПР. = Да" 7096 *: "ВОСПР. = Да"
7409 archosplayer: "(ВОСПР./СТОП)"
7410 cowond2*: "МЕÐЮ или прав. верх. = Да" 7097 cowond2*: "МЕÐЮ или прав. верх. = Да"
7411 creativezen*: "Выбрать = Да" 7098 creativezen*: "Выбрать = Да"
7412 gigabeat*,iaudiom5,iaudiox5,ipod*,iriverh10,iriverh10_5gb,mrobe100,sansac200*,sansaclip*,sansaconnect,sansae200*,sansafuze*: "ВЫБРÐТЬ = Да" 7099 gigabeat*,iaudiom5,iaudiox5,ipod*,iriverh10,iriverh10_5gb,mrobe100,sansac200*,sansaclip*,sansaconnect,sansae200*,sansafuze*: "ВЫБРÐТЬ = Да"
@@ -7434,7 +7121,7 @@
7434 </source> 7121 </source>
7435 <dest> 7122 <dest>
7436 *: none 7123 *: none
7437 gigabeat*,iaudiom5,iaudiox5,ipod*,iriverh10,iriverh10_5gb,mrobe100,sansac200*,sansaclip*,sansae200*,sansafuze*: "ВЫБОР = УÑтановить" 7124 gigabeat*,iaudiom5,iaudiox5,ipod*,iriverh10,iriverh10_5gb,mrobe100,sansac200*,sansaclip*,sansaconnect,sansae200*,sansafuze*: "ВЫБОР = УÑтановить"
7438 gogearsa9200,samsungyh*: "ВОСПР. = УÑтановить" 7125 gogearsa9200,samsungyh*: "ВОСПР. = УÑтановить"
7439 iriverh100,iriverh120,iriverh300: "ÐÐВИГ. = УÑтановить" 7126 iriverh100,iriverh120,iriverh300: "ÐÐВИГ. = УÑтановить"
7440 mpiohd300: "ENTER = УÑтановить" 7127 mpiohd300: "ENTER = УÑтановить"
@@ -7444,7 +7131,7 @@
7444 </dest> 7131 </dest>
7445 <voice> 7132 <voice>
7446 *: none 7133 *: none
7447 gigabeat*,gogearsa9200,iaudiom5,iaudiox5,ipod*,iriverh10,iriverh100,iriverh10_5gb,iriverh120,iriverh300,mrobe100,rtc,sansac200*,sansae200*: "" 7134 gigabeat*,gogearsa9200,iaudiom5,iaudiox5,ipod*,iriverh10,iriverh100,iriverh10_5gb,iriverh120,iriverh300,mrobe100,rtc,samsungyh*,sansac200*,sansae200*: ""
7448 </voice> 7135 </voice>
7449</phrase> 7136</phrase>
7450<phrase> 7137<phrase>
@@ -7453,15 +7140,12 @@
7453 user: core 7140 user: core
7454 <source> 7141 <source>
7455 *: "Any Other = No" 7142 *: "Any Other = No"
7456 archosplayer: none
7457 </source> 7143 </source>
7458 <dest> 7144 <dest>
7459 *: "Ð›ÑŽÐ±Ð°Ñ Ð´Ñ€ÑƒÐ³Ð°Ñ = Ðет" 7145 *: "Ð›ÑŽÐ±Ð°Ñ Ð´Ñ€ÑƒÐ³Ð°Ñ = Ðет"
7460 archosplayer: none
7461 </dest> 7146 </dest>
7462 <voice> 7147 <voice>
7463 *: "" 7148 *: ""
7464 archosplayer: none
7465 </voice> 7149 </voice>
7466</phrase> 7150</phrase>
7467<phrase> 7151<phrase>
@@ -7556,13 +7240,13 @@
7556</phrase> 7240</phrase>
7557<phrase> 7241<phrase>
7558 id: LANG_TAGNAVI_RANDOM 7242 id: LANG_TAGNAVI_RANDOM
7559 desc: "<Random>" entry in tag browser 7243 desc: "[Random]" entry in tag browser
7560 user: core 7244 user: core
7561 <source> 7245 <source>
7562 *: "<Random>" 7246 *: "[Random]"
7563 </source> 7247 </source>
7564 <dest> 7248 <dest>
7565 *: "<Случайный>" 7249 *: "[Случайный]"
7566 </dest> 7250 </dest>
7567 <voice> 7251 <voice>
7568 *: "Случайный" 7252 *: "Случайный"
@@ -7768,11 +7452,9 @@
7768 user: core 7452 user: core
7769 <source> 7453 <source>
7770 *: "End of Song List" 7454 *: "End of Song List"
7771 archosplayer: "End of List"
7772 </source> 7455 </source>
7773 <dest> 7456 <dest>
7774 *: "Конец ÑпиÑка" 7457 *: "Конец ÑпиÑка"
7775 archosplayer: "Конец ÑпиÑка"
7776 </dest> 7458 </dest>
7777 <voice> 7459 <voice>
7778 *: "Конец ÑпиÑка" 7460 *: "Конец ÑпиÑка"
@@ -8039,11 +7721,11 @@
8039 </source> 7721 </source>
8040 <dest> 7722 <dest>
8041 *: none 7723 *: none
8042 remote: "(Vol- : РеактивиÑовать)" 7724 remote: "(ГромкоÑÑÑŒ - : чтобы Ñнова включить)"
8043 </dest> 7725 </dest>
8044 <voice> 7726 <voice>
8045 *: none 7727 *: none
8046 remote: "(Vol- : РеакÑивировать)" 7728 remote: "(Уменьшите громкоÑÑ‚ÑŒ, чтобы Ñнова включить)"
8047 </voice> 7729 </voice>
8048</phrase> 7730</phrase>
8049<phrase> 7731<phrase>
@@ -8071,7 +7753,7 @@
8071 *: "Режим:" 7753 *: "Режим:"
8072 </dest> 7754 </dest>
8073 <voice> 7755 <voice>
8074 *: "Режим:" 7756 *: "Режим"
8075 </voice> 7757 </voice>
8076</phrase> 7758</phrase>
8077<phrase> 7759<phrase>
@@ -8202,20 +7884,6 @@
8202 </voice> 7884 </voice>
8203</phrase> 7885</phrase>
8204<phrase> 7886<phrase>
8205 id: LANG_REPLACE
8206 desc: in onplay menu. Replace the current playlist with a new one.
8207 user: core
8208 <source>
8209 *: "Play Next"
8210 </source>
8211 <dest>
8212 *: "Играть Ñледующий"
8213 </dest>
8214 <voice>
8215 *: "Играть Ñледующий"
8216 </voice>
8217</phrase>
8218<phrase>
8219 id: LANG_CATALOG 7887 id: LANG_CATALOG
8220 desc: in main menu and onplay menu 7888 desc: in main menu and onplay menu
8221 user: core 7889 user: core
@@ -8720,20 +8388,6 @@
8720 </voice> 8388 </voice>
8721</phrase> 8389</phrase>
8722<phrase> 8390<phrase>
8723 id: LANG_CATALOG_ADD_TO
8724 desc: in onplay playlist catalogue submenu
8725 user: core
8726 <source>
8727 *: "Add to Playlist"
8728 </source>
8729 <dest>
8730 *: "Добавить в ÑпиÑок"
8731 </dest>
8732 <voice>
8733 *: "Добавить в ÑпиÑок"
8734 </voice>
8735</phrase>
8736<phrase>
8737 id: LANG_BUTTONLIGHT_BRIGHTNESS 8391 id: LANG_BUTTONLIGHT_BRIGHTNESS
8738 desc: in settings_menu 8392 desc: in settings_menu
8739 user: core 8393 user: core
@@ -8907,30 +8561,16 @@
8907 desc: top item in the list when asking user about bookmark auto load 8561 desc: top item in the list when asking user about bookmark auto load
8908 user: core 8562 user: core
8909 <source> 8563 <source>
8910 *: "<Don't Resume>" 8564 *: "[Don't Resume]"
8911 </source> 8565 </source>
8912 <dest> 8566 <dest>
8913 *: "<Ðе продолжать>" 8567 *: "[Ðе продолжать]"
8914 </dest> 8568 </dest>
8915 <voice> 8569 <voice>
8916 *: "Ðе продолжать" 8570 *: "Ðе продолжать"
8917 </voice> 8571 </voice>
8918</phrase> 8572</phrase>
8919<phrase> 8573<phrase>
8920 id: LANG_BOOKMARK_CONTEXT_DELETE
8921 desc: bookmark context menu, delete this bookmark
8922 user: core
8923 <source>
8924 *: "Delete"
8925 </source>
8926 <dest>
8927 *: "Удалить"
8928 </dest>
8929 <voice>
8930 *: "Удалить"
8931 </voice>
8932</phrase>
8933<phrase>
8934 id: LANG_CROSSFEED_CROSS_GAIN 8574 id: LANG_CROSSFEED_CROSS_GAIN
8935 desc: in crossfeed settings 8575 desc: in crossfeed settings
8936 user: core 8576 user: core
@@ -9093,7 +8733,7 @@
9093 *: "Перемещение..." 8733 *: "Перемещение..."
9094 </dest> 8734 </dest>
9095 <voice> 8735 <voice>
9096 *: "Перемещение..." 8736 *: "Перемещение"
9097 </voice> 8737 </voice>
9098</phrase> 8738</phrase>
9099<phrase> 8739<phrase>
@@ -9139,20 +8779,6 @@
9139 </voice> 8779 </voice>
9140</phrase> 8780</phrase>
9141<phrase> 8781<phrase>
9142 id: LANG_CATALOG_VIEW
9143 desc: in onplay playlist catalogue submenu
9144 user: core
9145 <source>
9146 *: "View Catalogue"
9147 </source>
9148 <dest>
9149 *: "Каталог"
9150 </dest>
9151 <voice>
9152 *: "Каталог"
9153 </voice>
9154</phrase>
9155<phrase>
9156 id: VOICE_EXT_CUESHEET 8782 id: VOICE_EXT_CUESHEET
9157 desc: 8783 desc:
9158 user: core 8784 user: core
@@ -9202,10 +8828,10 @@
9202 desc: bookmark selection list, bookmark couldn't be parsed 8828 desc: bookmark selection list, bookmark couldn't be parsed
9203 user: core 8829 user: core
9204 <source> 8830 <source>
9205 *: "<Invalid Bookmark>" 8831 *: "[Invalid Bookmark]"
9206 </source> 8832 </source>
9207 <dest> 8833 <dest>
9208 *: "<ÐÐµÐ²ÐµÑ€Ð½Ð°Ñ Ð·Ð°ÐºÐ»Ð°Ð´ÐºÐ°>" 8834 *: "[ÐÐµÐ²ÐµÑ€Ð½Ð°Ñ Ð·Ð°ÐºÐ»Ð°Ð´ÐºÐ°]"
9209 </dest> 8835 </dest>
9210 <voice> 8836 <voice>
9211 *: "ÐÐµÐ²ÐµÑ€Ð½Ð°Ñ Ð·Ð°ÐºÐ»Ð°Ð´ÐºÐ°" 8837 *: "ÐÐµÐ²ÐµÑ€Ð½Ð°Ñ Ð·Ð°ÐºÐ»Ð°Ð´ÐºÐ°"
@@ -9269,11 +8895,11 @@
9269 </source> 8895 </source>
9270 <dest> 8896 <dest>
9271 *: none 8897 *: none
9272 recording: "MPEG Layer 3" 8898 recording: "MPEG Слой 3"
9273 </dest> 8899 </dest>
9274 <voice> 8900 <voice>
9275 *: none 8901 *: none
9276 recording: "MPEG Layer 3" 8902 recording: "MPEG Слой 3"
9277 </voice> 8903 </voice>
9278</phrase> 8904</phrase>
9279<phrase> 8905<phrase>
@@ -9410,10 +9036,10 @@
9410</phrase> 9036</phrase>
9411<phrase> 9037<phrase>
9412 id: LANG_AUDIOSCROBBLER 9038 id: LANG_AUDIOSCROBBLER
9413 desc: "Last.fm Log" in the playback menu 9039 desc: "Last.fm Logger" in Plugin/apps/scrobbler
9414 user: core 9040 user: core
9415 <source> 9041 <source>
9416 *: "Last.fm Log" 9042 *: "Last.fm Logger"
9417 </source> 9043 </source>
9418 <dest> 9044 <dest>
9419 *: "Отчёт Ð´Ð»Ñ Last.fm" 9045 *: "Отчёт Ð´Ð»Ñ Last.fm"
@@ -9534,7 +9160,7 @@
9534 *: "%s не ÑущеÑтвует" 9160 *: "%s не ÑущеÑтвует"
9535 </dest> 9161 </dest>
9536 <voice> 9162 <voice>
9537 *: "" 9163 *: "Каталог плейлиÑтов не ÑущеÑтвует"
9538 </voice> 9164 </voice>
9539</phrase> 9165</phrase>
9540<phrase> 9166<phrase>
@@ -9583,23 +9209,6 @@
9583 </voice> 9209 </voice>
9584</phrase> 9210</phrase>
9585<phrase> 9211<phrase>
9586 id: LANG_SET_AS_REC_DIR
9587 desc: used in the onplay menu to set a recording dir
9588 user: core
9589 <source>
9590 *: none
9591 recording: "Set As Recording Directory"
9592 </source>
9593 <dest>
9594 *: none
9595 recording: "УÑтановить как папку запиÑи"
9596 </dest>
9597 <voice>
9598 *: none
9599 recording: "УÑтановить как папку запиÑи"
9600 </voice>
9601</phrase>
9602<phrase>
9603 id: LANG_FM_MENU 9212 id: LANG_FM_MENU
9604 desc: fm menu title 9213 desc: fm menu title
9605 user: core 9214 user: core
@@ -9832,7 +9441,7 @@
9832 *: "" 9441 *: ""
9833 </dest> 9442 </dest>
9834 <voice> 9443 <voice>
9835 *: "OK" 9444 *: "Oк"
9836 </voice> 9445 </voice>
9837</phrase> 9446</phrase>
9838<phrase> 9447<phrase>
@@ -10369,7 +9978,7 @@
10369 *: "Следующий трек:" 9978 *: "Следующий трек:"
10370 </dest> 9979 </dest>
10371 <voice> 9980 <voice>
10372 *: "Следующий Ñ‚Ñ€Ñк:" 9981 *: "Следующий Ñ‚Ñ€Ñк"
10373 </voice> 9982 </voice>
10374</phrase> 9983</phrase>
10375<phrase> 9984<phrase>
@@ -10383,7 +9992,7 @@
10383 *: "Следующий:" 9992 *: "Следующий:"
10384 </dest> 9993 </dest>
10385 <voice> 9994 <voice>
10386 *: "Следующий:" 9995 *: "Следующий"
10387 </voice> 9996 </voice>
10388</phrase> 9997</phrase>
10389<phrase> 9998<phrase>
@@ -10438,11 +10047,11 @@
10438 </source> 10047 </source>
10439 <dest> 10048 <dest>
10440 *: none 10049 *: none
10441 touchscreen: "OK" 10050 touchscreen: "~OK"
10442 </dest> 10051 </dest>
10443 <voice> 10052 <voice>
10444 *: none 10053 *: none
10445 touchscreen: "OK" 10054 touchscreen: "~OK"
10446 </voice> 10055 </voice>
10447</phrase> 10056</phrase>
10448<phrase> 10057<phrase>
@@ -10604,20 +10213,6 @@
10604 </voice> 10213 </voice>
10605</phrase> 10214</phrase>
10606<phrase> 10215<phrase>
10607 id: LANG_SCROLLBAR_POSITION
10608 desc: in Settings -> General -> Display -> Status-/Scrollbar
10609 user: core
10610 <source>
10611 *: "Scroll Bar Position"
10612 </source>
10613 <dest>
10614 *: "ÐŸÐ¾Ð·Ð¸Ñ†Ð¸Ñ Ð¿Ð¾Ð»Ð¾ÑÑ‹ прокрутки"
10615 </dest>
10616 <voice>
10617 *: "ÐŸÐ¾Ð·Ð¸Ñ†Ð¸Ñ Ð¿Ð¾Ð»Ð¾ÑÑ‹ прокрутки"
10618 </voice>
10619</phrase>
10620<phrase>
10621 id: LANG_REMOTE_STATUSBAR 10216 id: LANG_REMOTE_STATUSBAR
10622 desc: in Settings -> General -> Display -> statusbar 10217 desc: in Settings -> General -> Display -> statusbar
10623 user: core 10218 user: core
@@ -10694,20 +10289,6 @@
10694 </voice> 10289 </voice>
10695</phrase> 10290</phrase>
10696<phrase> 10291<phrase>
10697 id: LANG_INSERT_LAST_SHUFFLED
10698 desc: in onplay menu. insert a playlist randomly at end of dynamic playlist
10699 user: core
10700 <source>
10701 *: "Insert Last Shuffled"
10702 </source>
10703 <dest>
10704 *: "Добавить Ñлучайный к концу"
10705 </dest>
10706 <voice>
10707 *: "Добавить Ñлучайный к концу"
10708 </voice>
10709</phrase>
10710<phrase>
10711 id: LANG_COMPRESSOR_SOFT_KNEE 10292 id: LANG_COMPRESSOR_SOFT_KNEE
10712 desc: in sound settings 10293 desc: in sound settings
10713 user: core 10294 user: core
@@ -11014,13 +10595,13 @@
11014</phrase> 10595</phrase>
11015<phrase> 10596<phrase>
11016 id: LANG_TAGNAVI_UNTAGGED 10597 id: LANG_TAGNAVI_UNTAGGED
11017 desc: "<untagged>" entry in tag browser 10598 desc: "[untagged]" entry in tag browser
11018 user: core 10599 user: core
11019 <source> 10600 <source>
11020 *: "<Untagged>" 10601 *: "[Untagged]"
11021 </source> 10602 </source>
11022 <dest> 10603 <dest>
11023 *: "<Ðе тегированный>" 10604 *: "[Ðе тегированный]"
11024 </dest> 10605 </dest>
11025 <voice> 10606 <voice>
11026 *: "Ðе тегированный" 10607 *: "Ðе тегированный"
@@ -11270,20 +10851,6 @@
11270 </voice> 10851 </voice>
11271</phrase> 10852</phrase>
11272<phrase> 10853<phrase>
11273 id: LANG_STATUSBAR_CUSTOM
11274 desc: if this translation is compatible with LANG_CHANNEL_CUSTOM, then please use the same translation. it can be combined later then
11275 user: core
11276 <source>
11277 *: "Custom"
11278 </source>
11279 <dest>
11280 *: "ПользовательÑкие"
11281 </dest>
11282 <voice>
11283 *: "ПользовательÑкие"
11284 </voice>
11285</phrase>
11286<phrase>
11287 id: LANG_SCROLLBAR_WIDTH 10854 id: LANG_SCROLLBAR_WIDTH
11288 desc: in Settings -> General -> Display -> Status-/Scrollbar 10855 desc: in Settings -> General -> Display -> Status-/Scrollbar
11289 user: core 10856 user: core
@@ -11619,20 +11186,6 @@
11619 </voice> 11186 </voice>
11620</phrase> 11187</phrase>
11621<phrase> 11188<phrase>
11622 id: LANG_SET_AS_START_DIR
11623 desc: used in the onplay menu to set a starting browser dir
11624 user: core
11625 <source>
11626 *: "Start File Browser Here"
11627 </source>
11628 <dest>
11629 *: "Ðачинать показ файлов здеÑÑŒ"
11630 </dest>
11631 <voice>
11632 *: "Ðачинать показ файлов здеÑÑŒ"
11633 </voice>
11634</phrase>
11635<phrase>
11636 id: LANG_FM_RSSI 11189 id: LANG_FM_RSSI
11637 desc: Signal strength of a received FM station 11190 desc: Signal strength of a received FM station
11638 user: core 11191 user: core
@@ -11729,25 +11282,11 @@
11729 </source> 11282 </source>
11730 <dest> 11283 <dest>
11731 *: none 11284 *: none
11732 multidrive_usb: "USB: Скрыть внутренний диÑк" 11285 multidrive_usb: "Скрыть внутренний диÑк USB"
11733 </dest> 11286 </dest>
11734 <voice> 11287 <voice>
11735 *: none 11288 *: none
11736 multidrive_usb: "USB: Скрыть внутренний диÑк" 11289 multidrive_usb: "Скрыть внутренний диÑк USB"
11737 </voice>
11738</phrase>
11739<phrase>
11740 id: LANG_SET_AS_PLAYLISTCAT_DIR
11741 desc: used in the onplay menu to set a playlist catalogue dir
11742 user: core
11743 <source>
11744 *: "Set As Playlist Catalogue Directory"
11745 </source>
11746 <dest>
11747 *: "УÑтановить как папку каталога ÑпиÑков"
11748 </dest>
11749 <voice>
11750 *: "УÑтановить как папку каталога ÑпиÑков"
11751 </voice> 11290 </voice>
11752</phrase> 11291</phrase>
11753<phrase> 11292<phrase>
@@ -11796,20 +11335,6 @@
11796 </voice> 11335 </voice>
11797</phrase> 11336</phrase>
11798<phrase> 11337<phrase>
11799 id: LANG_AUTOMATIC
11800 desc: generic automatic
11801 user: core
11802 <source>
11803 *: "Automatic"
11804 </source>
11805 <dest>
11806 *: "Ðвтоматич."
11807 </dest>
11808 <voice>
11809 *: "ÐвтоматичеÑки"
11810 </voice>
11811</phrase>
11812<phrase>
11813 id: LANG_SLEEP_TIMER_CANCEL_CURRENT 11338 id: LANG_SLEEP_TIMER_CANCEL_CURRENT
11814 desc: shown instead of sleep timer when it's running 11339 desc: shown instead of sleep timer when it's running
11815 user: core 11340 user: core
@@ -12342,7 +11867,7 @@
12342 desc: Selective Actions 11867 desc: Selective Actions
12343 user: core 11868 user: core
12344 <source> 11869 <source>
12345 *: "Seek" 11870 *: "Exempt Seek"
12346 </source> 11871 </source>
12347 <dest> 11872 <dest>
12348 *: "Перемотка" 11873 *: "Перемотка"
@@ -12401,7 +11926,7 @@
12401 desc: Softlock behaviour setting 11926 desc: Softlock behaviour setting
12402 user: core 11927 user: core
12403 <source> 11928 <source>
12404 *: "Disable Notify" 11929 *: "Disable Locked Reminders"
12405 </source> 11930 </source>
12406 <dest> 11931 <dest>
12407 *: "Запрет оповещениÑ" 11932 *: "Запрет оповещениÑ"
@@ -12516,7 +12041,7 @@
12516 desc: Selective Actions 12041 desc: Selective Actions
12517 user: core 12042 user: core
12518 <source> 12043 <source>
12519 *: "Skip" 12044 *: "Exempt Skip"
12520 </source> 12045 </source>
12521 <dest> 12046 <dest>
12522 *: "Смена трека" 12047 *: "Смена трека"
@@ -12596,20 +12121,6 @@
12596 </voice> 12121 </voice>
12597</phrase> 12122</phrase>
12598<phrase> 12123<phrase>
12599 id: LANG_NO_VIEWERS
12600 desc: text for splash to indicate that no viewers are available
12601 user: core
12602 <source>
12603 *: "No viewers found"
12604 </source>
12605 <dest>
12606 *: "ПроÑмотрщиков не найдено"
12607 </dest>
12608 <voice>
12609 *: "ПроÑмотрщиков не найдено"
12610 </voice>
12611</phrase>
12612<phrase>
12613 id: LANG_PBE 12124 id: LANG_PBE
12614 desc: in sound settings 12125 desc: in sound settings
12615 user: core 12126 user: core
@@ -12712,7 +12223,7 @@
12712 desc: Selective Actions 12223 desc: Selective Actions
12713 user: core 12224 user: core
12714 <source> 12225 <source>
12715 *: "Play" 12226 *: "Exempt Play"
12716 </source> 12227 </source>
12717 <dest> 12228 <dest>
12718 *: "ВоÑпроизведение или пауза" 12229 *: "ВоÑпроизведение или пауза"
@@ -13034,20 +12545,6 @@
13034 </voice> 12545 </voice>
13035</phrase> 12546</phrase>
13036<phrase> 12547<phrase>
13037 id: LANG_CLEAR_PLAYLIST
13038 desc: in the pictureflow main menu
13039 user: core
13040 <source>
13041 *: "Clear playlist"
13042 </source>
13043 <dest>
13044 *: "ОчиÑтить ÑпиÑок воÑпроизведениÑ"
13045 </dest>
13046 <voice>
13047 *: "ОчиÑтить ÑпиÑок воÑпроизведениÑ"
13048 </voice>
13049</phrase>
13050<phrase>
13051 id: LANG_HIDE_ALBUM_TITLE 12548 id: LANG_HIDE_ALBUM_TITLE
13052 desc: in the pictureflow settings 12549 desc: in the pictureflow settings
13053 user: core 12550 user: core
@@ -13091,7 +12588,7 @@
13091</phrase> 12588</phrase>
13092<phrase> 12589<phrase>
13093 id: LANG_DIRECT 12590 id: LANG_DIRECT
13094 desc: in the pictureflow settings 12591 desc: in the pictureflow settings, also a volume adjustment mode
13095 user: core 12592 user: core
13096 <source> 12593 <source>
13097 *: "Direct" 12594 *: "Direct"
@@ -13160,34 +12657,6 @@
13160 </voice> 12657 </voice>
13161</phrase> 12658</phrase>
13162<phrase> 12659<phrase>
13163 id: LANG_PLAYLIST_CLEARED
13164 desc: in the pictureflow splash messages
13165 user: core
13166 <source>
13167 *: "Playlist Cleared"
13168 </source>
13169 <dest>
13170 *: "СпиÑок воÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾Ñ‡Ð¸Ñ‰ÐµÐ½"
13171 </dest>
13172 <voice>
13173 *: "СпиÑок воÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾Ñ‡Ð¸Ñ‰ÐµÐ½"
13174 </voice>
13175</phrase>
13176<phrase>
13177 id: LANG_ADDED_TO_PLAYLIST
13178 desc: in the pictureflow splash messages
13179 user: core
13180 <source>
13181 *: "Added to playlist"
13182 </source>
13183 <dest>
13184 *: "Добавлено к ÑпиÑку воÑпроизведениÑ"
13185 </dest>
13186 <voice>
13187 *: "Добавлено к ÑпиÑку воÑпроизведениÑ"
13188 </voice>
13189</phrase>
13190<phrase>
13191 id: LANG_ERROR_WRITING_CONFIG 12660 id: LANG_ERROR_WRITING_CONFIG
13192 desc: in the pictureflow splash messages 12661 desc: in the pictureflow splash messages
13193 user: core 12662 user: core
@@ -13226,7 +12695,7 @@
13226 *: "Уровень 8: 1 ход / 15 мин." 12695 *: "Уровень 8: 1 ход / 15 мин."
13227 </dest> 12696 </dest>
13228 <voice> 12697 <voice>
13229 *: "Уровень 8: 1 ход за 15 минут" 12698 *: "Уровень 8, 1 ход за 15 минут"
13230 </voice> 12699 </voice>
13231</phrase> 12700</phrase>
13232<phrase> 12701<phrase>
@@ -13338,7 +12807,7 @@
13338 *: "Уровень 3: 60 ходов / 30 min." 12807 *: "Уровень 3: 60 ходов / 30 min."
13339 </dest> 12808 </dest>
13340 <voice> 12809 <voice>
13341 *: "Уровень 3: 60 ходов за 30 минут" 12810 *: "Уровень 3, 60 ходов за 30 минут"
13342 </voice> 12811 </voice>
13343</phrase> 12812</phrase>
13344<phrase> 12813<phrase>
@@ -13380,7 +12849,7 @@
13380 *: "Уровень 5: 40 ходов / 60 мин." 12849 *: "Уровень 5: 40 ходов / 60 мин."
13381 </dest> 12850 </dest>
13382 <voice> 12851 <voice>
13383 *: "Уровень 5: 40 ходов за 60 минут" 12852 *: "Уровень 5, 40 ходов за 60 минут"
13384 </voice> 12853 </voice>
13385</phrase> 12854</phrase>
13386<phrase> 12855<phrase>
@@ -13394,7 +12863,7 @@
13394 *: "" 12863 *: ""
13395 </dest> 12864 </dest>
13396 <voice> 12865 <voice>
13397 *: "GNU Chess" 12866 *: "GNU Шахматы"
13398 </voice> 12867 </voice>
13399</phrase> 12868</phrase>
13400<phrase> 12869<phrase>
@@ -13408,7 +12877,7 @@
13408 *: "Уровень 9: 1 ход / 60 мин." 12877 *: "Уровень 9: 1 ход / 60 мин."
13409 </dest> 12878 </dest>
13410 <voice> 12879 <voice>
13411 *: "Уровень 9: 1 ход за 60 минут" 12880 *: "Уровень 9, 1 ход за 60 минут"
13412 </voice> 12881 </voice>
13413</phrase> 12882</phrase>
13414<phrase> 12883<phrase>
@@ -13573,16 +13042,16 @@
13573</phrase> 13042</phrase>
13574<phrase> 13043<phrase>
13575 id: LANG_PLAYTIME_REMAINING 13044 id: LANG_PLAYTIME_REMAINING
13576 desc: playing time screen 13045 desc: deprecated
13577 user: core 13046 user: core
13578 <source> 13047 <source>
13579 *: "Playlist remaining:" 13048 *: ""
13580 </source> 13049 </source>
13581 <dest> 13050 <dest>
13582 *: "Ð’ ÑпиÑке оÑталоÑÑŒ:" 13051 *: ""
13583 </dest> 13052 </dest>
13584 <voice> 13053 <voice>
13585 *: "Ð’ ÑпиÑке оÑталоÑÑŒ" 13054 *: ""
13586 </voice> 13055 </voice>
13587</phrase> 13056</phrase>
13588<phrase> 13057<phrase>
@@ -13610,7 +13079,7 @@
13610 *: "Уровень 4: 40 ходов / 30 мин." 13079 *: "Уровень 4: 40 ходов / 30 мин."
13611 </dest> 13080 </dest>
13612 <voice> 13081 <voice>
13613 *: "Уровень 4: 40 ходов за 30 минут" 13082 *: "Уровень 4, 40 ходов за 30 минут"
13614 </voice> 13083 </voice>
13615</phrase> 13084</phrase>
13616<phrase> 13085<phrase>
@@ -13784,7 +13253,7 @@
13784 *: "Думаю..." 13253 *: "Думаю..."
13785 </dest> 13254 </dest>
13786 <voice> 13255 <voice>
13787 *: "" 13256 *: "Думаю"
13788 </voice> 13257 </voice>
13789</phrase> 13258</phrase>
13790<phrase> 13259<phrase>
@@ -13916,7 +13385,7 @@
13916 *: "Уровень 2: 60 ходов / 15 мин." 13385 *: "Уровень 2: 60 ходов / 15 мин."
13917 </dest> 13386 </dest>
13918 <voice> 13387 <voice>
13919 *: "Уровень 2: 60 ходов за 15 минут" 13388 *: "Уровень 2, 60 ходов за 15 минут"
13920 </voice> 13389 </voice>
13921</phrase> 13390</phrase>
13922<phrase> 13391<phrase>
@@ -13972,7 +13441,7 @@
13972 *: "Уровень 1: 60 ходов / 5 мин." 13441 *: "Уровень 1: 60 ходов / 5 мин."
13973 </dest> 13442 </dest>
13974 <voice> 13443 <voice>
13975 *: "Уровень 1: 60 ходов за 5 минут" 13444 *: "Уровень 1, 60 ходов за 5 минут"
13976 </voice> 13445 </voice>
13977</phrase> 13446</phrase>
13978<phrase> 13447<phrase>
@@ -14365,7 +13834,7 @@
14365 *: "Уровень 7: 40 ходов / 240 мин." 13834 *: "Уровень 7: 40 ходов / 240 мин."
14366 </dest> 13835 </dest>
14367 <voice> 13836 <voice>
14368 *: "Уровень 7: 40 ходов за 240 минут" 13837 *: "Уровень 7, 40 ходов за 240 минут"
14369 </voice> 13838 </voice>
14370</phrase> 13839</phrase>
14371<phrase> 13840<phrase>
@@ -14641,7 +14110,7 @@
14641 *: "Уровень 10: 1 ход / 600 min." 14110 *: "Уровень 10: 1 ход / 600 min."
14642 </dest> 14111 </dest>
14643 <voice> 14112 <voice>
14644 *: "Уровень 10: 1 ход за 600 минут" 14113 *: "Уровень 10, 1 ход за 600 минут"
14645 </voice> 14114 </voice>
14646</phrase> 14115</phrase>
14647<phrase> 14116<phrase>
@@ -14798,7 +14267,7 @@
14798 *: "Уровень 6: 40 ходов / 120 мин." 14267 *: "Уровень 6: 40 ходов / 120 мин."
14799 </dest> 14268 </dest>
14800 <voice> 14269 <voice>
14801 *: "Уровень 6: 40 ходов за 120 минут" 14270 *: "Уровень 6, 40 ходов за 120 минут"
14802 </voice> 14271 </voice>
14803</phrase> 14272</phrase>
14804<phrase> 14273<phrase>
@@ -14945,62 +14414,6 @@
14945 </voice> 14414 </voice>
14946</phrase> 14415</phrase>
14947<phrase> 14416<phrase>
14948 id: LANG_PROPERTIES_ARTIST
14949 desc: in properties plugin
14950 user: core
14951 <source>
14952 *: "[Artist]"
14953 </source>
14954 <dest>
14955 *: "[ИÑполнитель]"
14956 </dest>
14957 <voice>
14958 *: "ИÑполнитель"
14959 </voice>
14960</phrase>
14961<phrase>
14962 id: LANG_PROPERTIES_TITLE
14963 desc: in properties plugin
14964 user: core
14965 <source>
14966 *: "[Title]"
14967 </source>
14968 <dest>
14969 *: "[Ðазвание]"
14970 </dest>
14971 <voice>
14972 *: "Ðазвание"
14973 </voice>
14974</phrase>
14975<phrase>
14976 id: LANG_PROPERTIES_ALBUM
14977 desc: in properties plugin
14978 user: core
14979 <source>
14980 *: "[Album]"
14981 </source>
14982 <dest>
14983 *: "[Ðльбом]"
14984 </dest>
14985 <voice>
14986 *: "Ðльбом"
14987 </voice>
14988</phrase>
14989<phrase>
14990 id: LANG_PROPERTIES_DURATION
14991 desc: in properties plugin
14992 user: core
14993 <source>
14994 *: "[Duration]"
14995 </source>
14996 <dest>
14997 *: "[ПродолжительноÑÑ‚ÑŒ]"
14998 </dest>
14999 <voice>
15000 *: "ПродолжительноÑÑ‚ÑŒ"
15001 </voice>
15002</phrase>
15003<phrase>
15004 id: LANG_PROPERTIES_SUBDIRS 14417 id: LANG_PROPERTIES_SUBDIRS
15005 desc: in properties plugin 14418 desc: in properties plugin
15006 user: core 14419 user: core
@@ -15645,20 +15058,6 @@
15645 es9218: "ÐÐ¸Ð·ÐºÐ°Ñ Ð³Ñ€Ð¾Ð¼ÐºÐ¾ÑÑ‚ÑŒ" 15058 es9218: "ÐÐ¸Ð·ÐºÐ°Ñ Ð³Ñ€Ð¾Ð¼ÐºÐ¾ÑÑ‚ÑŒ"
15646 </voice> 15059 </voice>
15647</phrase> 15060</phrase>
15648 <phrase>
15649 id: LANG_CLEAR_LIST_AND_PLAY_NEXT
15650 desc: in onplay menu. Replace current playlist with selected tracks
15651 user: core
15652 <source>
15653 *: "Clear List & Play Next"
15654 </source>
15655 <dest>
15656 *: "ОчиÑтить ÑпиÑок и играть Ñледующий"
15657 </dest>
15658 <voice>
15659 *: "ОчиÑтить ÑпиÑок и играть Ñледующий"
15660 </voice>
15661</phrase>
15662<phrase> 15061<phrase>
15663 id: LANG_QUEUE_MENU 15062 id: LANG_QUEUE_MENU
15664 desc: in onplay menu 15063 desc: in onplay menu
@@ -15670,7 +15069,7 @@
15670 *: "Очередь..." 15069 *: "Очередь..."
15671 </dest> 15070 </dest>
15672 <voice> 15071 <voice>
15673 *: "Очередь..." 15072 *: "Очередь"
15674 </voice> 15073 </voice>
15675</phrase> 15074</phrase>
15676<phrase> 15075<phrase>
@@ -15716,20 +15115,6 @@
15716 </voice> 15115 </voice>
15717</phrase> 15116</phrase>
15718<phrase> 15117<phrase>
15719 id: LANG_CLEAR_LIST_AND_PLAY_SHUFFLED
15720 desc: in onplay menu. Replace current playlist with selected tracks in random order.
15721 user: core
15722 <source>
15723 *: "Clear List & Play Shuffled"
15724 </source>
15725 <dest>
15726 *: "ОчиÑтить ÑпиÑок и играть в Ñлучайном порÑдке"
15727 </dest>
15728 <voice>
15729 *: "ОчиÑтить ÑпиÑок и играть в Ñлучайном порÑдке"
15730 </voice>
15731</phrase>
15732<phrase>
15733 id: LANG_SOFTLOCK_DISABLE_ALL_NOTIFY 15118 id: LANG_SOFTLOCK_DISABLE_ALL_NOTIFY
15734 desc: disable all softlock notifications 15119 desc: disable all softlock notifications
15735 user: core 15120 user: core
@@ -15772,20 +15157,6 @@
15772 </voice> 15157 </voice>
15773</phrase> 15158</phrase>
15774<phrase> 15159<phrase>
15775 id: LANG_PLAYLIST_RELOAD_AFTER_SAVE
15776 desc: reload playlist after saving
15777 user: core
15778 <source>
15779 *: "Reload After Saving"
15780 </source>
15781 <dest>
15782 *: "Перезагрузить поÑле ÑохранениÑ"
15783 </dest>
15784 <voice>
15785 *: "Перезагрузить поÑле ÑохранениÑ"
15786 </voice>
15787</phrase>
15788<phrase>
15789 id: LANG_FILTER_LINEAR_FAST 15160 id: LANG_FILTER_LINEAR_FAST
15790 desc: in sound settings 15161 desc: in sound settings
15791 user: core 15162 user: core
@@ -15939,132 +15310,6 @@
15939 </voice> 15310 </voice>
15940</phrase> 15311</phrase>
15941<phrase> 15312<phrase>
15942 id: LANG_PROPERTIES_ALBUMARTIST
15943 desc: in properties plugin
15944 user: core
15945 <source>
15946 *: "[Album Artist]"
15947 </source>
15948 <dest>
15949 *: "[ИÑполнитель альбома]"
15950 </dest>
15951 <voice>
15952 *: "ИÑполнитель альбома"
15953 </voice>
15954</phrase>
15955<phrase>
15956 id: LANG_PROPERTIES_GENRE
15957 desc: in properties plugin
15958 user: core
15959 <source>
15960 *: "[Genre]"
15961 </source>
15962 <dest>
15963 *: "[Жанр]"
15964 </dest>
15965 <voice>
15966 *: "Жанр"
15967 </voice>
15968</phrase>
15969<phrase>
15970 id: LANG_PROPERTIES_COMMENT
15971 desc: in properties plugin
15972 user: core
15973 <source>
15974 *: "[Comment]"
15975 </source>
15976 <dest>
15977 *: "[Комментарий]"
15978 </dest>
15979 <voice>
15980 *: "Комментарий"
15981 </voice>
15982</phrase>
15983<phrase>
15984 id: LANG_PROPERTIES_COMPOSER
15985 desc: in properties plugin
15986 user: core
15987 <source>
15988 *: "[Composer]"
15989 </source>
15990 <dest>
15991 *: "[Композитор]"
15992 </dest>
15993 <voice>
15994 *: "Композитор"
15995 </voice>
15996</phrase>
15997<phrase>
15998 id: LANG_PROPERTIES_YEAR
15999 desc: in properties plugin
16000 user: core
16001 <source>
16002 *: "[Year]"
16003 </source>
16004 <dest>
16005 *: "[Год]"
16006 </dest>
16007 <voice>
16008 *: "Год"
16009 </voice>
16010</phrase>
16011<phrase>
16012 id: LANG_PROPERTIES_TRACKNUM
16013 desc: in properties plugin
16014 user: core
16015 <source>
16016 *: "[Tracknum]"
16017 </source>
16018 <dest>
16019 *: "[Ðомер трека]"
16020 </dest>
16021 <voice>
16022 *: "Ðомер трека"
16023 </voice>
16024</phrase>
16025<phrase>
16026 id: LANG_PROPERTIES_DISCNUM
16027 desc: in properties plugin
16028 user: core
16029 <source>
16030 *: "[Discnum]"
16031 </source>
16032 <dest>
16033 *: "[Ðомер диÑка]"
16034 </dest>
16035 <voice>
16036 *: "Ðомер диÑка"
16037 </voice>
16038</phrase>
16039<phrase>
16040 id: LANG_PROPERTIES_FREQUENCY
16041 desc: in properties plugin
16042 user: core
16043 <source>
16044 *: "[Frequency]"
16045 </source>
16046 <dest>
16047 *: "[ЧаÑтота]"
16048 </dest>
16049 <voice>
16050 *: "ЧаÑтота"
16051 </voice>
16052</phrase>
16053<phrase>
16054 id: LANG_PROPERTIES_BITRATE
16055 desc: in properties plugin
16056 user: core
16057 <source>
16058 *: "[Bitrate]"
16059 </source>
16060 <dest>
16061 *: "[Битрейт]"
16062 </dest>
16063 <voice>
16064 *: "Битрейт"
16065 </voice>
16066</phrase>
16067<phrase>
16068 id: LANG_SINGLE_MODE 15313 id: LANG_SINGLE_MODE
16069 desc: single mode 15314 desc: single mode
16070 user: core 15315 user: core
@@ -16353,18 +15598,18 @@
16353 user: core 15598 user: core
16354 <source> 15599 <source>
16355 *: none 15600 *: none
16356 ipodcolor,ipodnano1g,ipodvideo,ipod4g,ipodmini1g,ipodmini2g: "Clear settings when hold switch is on during startup" 15601 clear_settings_on_hold,iriverh10: "Clear settings when reset button is held during startup"
16357 clear_settings_on_hold, iriverh10: "Clear settings when reset button is held during startup" 15602 ipod4g,ipodcolor,ipodmini1g,ipodmini2g,ipodnano1g,ipodvideo: "Clear settings when hold switch is on during startup"
16358 </source> 15603 </source>
16359 <dest> 15604 <dest>
16360 *: none 15605 *: none
16361 ipodcolor,ipodnano1g,ipodvideo,ipod4g,ipodmini1g,ipodmini2g: "СброÑить наÑтройки при включенной блокировке во Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸" 15606 clear_settings_on_hold,iriverh10: "СброÑит наÑтройки при нажатой кнопке ÑброÑа во Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸"
16362 clear_settings_on_hold, iriverh10: "СброÑит наÑтройки при нажатой кнопке ÑброÑа во Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸" 15607 ipod4g,ipodcolor,ipodmini1g,ipodmini2g,ipodnano1g,ipodvideo: "СброÑить наÑтройки при включенной блокировке во Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸"
16363 </dest> 15608 </dest>
16364 <voice> 15609 <voice>
16365 *: none 15610 *: none
16366 ipodcolor,ipodnano1g,ipodvideo,ipod4g,ipodmini1g,ipodmini2g: "СброÑить наÑтройки при включенной блокировке во Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸" 15611 clear_settings_on_hold,iriverh10: "СброÑит наÑтройки при нажатой кнопке ÑброÑа во Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸"
16367 clear_settings_on_hold, iriverh10: "СброÑит наÑтройки при нажатой кнопке ÑброÑа во Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸" 15612 ipod4g,ipodcolor,ipodmini1g,ipodmini2g,ipodnano1g,ipodvideo: "СброÑить наÑтройки при включенной блокировке во Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸"
16368 </voice> 15613 </voice>
16369</phrase> 15614</phrase>
16370<phrase> 15615<phrase>
@@ -16392,7 +15637,7 @@
16392 *: "УÑтановить как..." 15637 *: "УÑтановить как..."
16393 </dest> 15638 </dest>
16394 <voice> 15639 <voice>
16395 *: "УÑтановить как..." 15640 *: "УÑтановить как"
16396 </voice> 15641 </voice>
16397</phrase> 15642</phrase>
16398<phrase> 15643<phrase>
@@ -16448,10 +15693,10 @@
16448 *: "Add to Playlist..." 15693 *: "Add to Playlist..."
16449 </source> 15694 </source>
16450 <dest> 15695 <dest>
16451 *: "Добавить к ÑпиÑку проигрываниÑ..." 15696 *: "Добавить к ÑпиÑку воÑпроизведениÑ.."
16452 </dest> 15697 </dest>
16453 <voice> 15698 <voice>
16454 *: "Добавить к ÑпиÑку проигрываниÑ..." 15699 *: "Добавить к ÑпиÑку воÑпроизведениÑ"
16455 </voice> 15700 </voice>
16456</phrase> 15701</phrase>
16457<phrase> 15702<phrase>
@@ -16476,10 +15721,10 @@
16476 *: "Playing Next..." 15721 *: "Playing Next..."
16477 </source> 15722 </source>
16478 <dest> 15723 <dest>
16479 *: "Ðальнейшее проигÑÑвание..." 15724 *: "ÐоÑпроизвеÑÑи далее..."
16480 </dest> 15725 </dest>
16481 <voice> 15726 <voice>
16482 *: "Ðальнейшее проигÑÑвание..." 15727 *: "ÐоÑпроизвеÑÑи далее"
16483 </voice> 15728 </voice>
16484</phrase> 15729</phrase>
16485<phrase> 15730<phrase>
@@ -16631,3 +15876,631 @@
16631 *: "РуÑÑкий" 15876 *: "РуÑÑкий"
16632 </voice> 15877 </voice>
16633</phrase> 15878</phrase>
15879<phrase>
15880 id: LANG_RANDOM_SHUFFLE_RANDOM_SELECTIVE_SONGS_SUMMARY
15881 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
15882 user: core
15883 <source>
15884 *: "Selection too big, %d random tracks will be selected"
15885 </source>
15886 <dest>
15887 *: "Выбор Ñлишком велик, из него будут выбраны %d Ñлучайных треков"
15888 </dest>
15889 <voice>
15890 *: "Выбор Ñлишком велик, из него будет выбрано меньше Ñлучайных треков"
15891 </voice>
15892</phrase>
15893<phrase>
15894 id: LANG_FILTER_SHORT_SHARP
15895 desc: in sound settings
15896 user: core
15897 <source>
15898 *: none
15899 filter_roll_off: "Short Sharp"
15900 </source>
15901 <dest>
15902 *: none
15903 filter_roll_off: "Короткий ОÑтрый"
15904 </dest>
15905 <voice>
15906 *: none
15907 filter_roll_off: "Короткий ОÑтрый"
15908 </voice>
15909</phrase>
15910<phrase>
15911 id: LANG_FILTER_SHORT_SLOW
15912 desc: in sound settings
15913 user: core
15914 <source>
15915 *: none
15916 filter_roll_off: "Short Slow"
15917 </source>
15918 <dest>
15919 *: none
15920 filter_roll_off: "Короткий Медленный"
15921 </dest>
15922 <voice>
15923 *: none
15924 filter_roll_off: "Короткий Медленный"
15925 </voice>
15926</phrase>
15927<phrase>
15928 id: LANG_MIKMOD_HQMIXER
15929 desc: in mikmod settings menu
15930 user: core
15931 <source>
15932 *: "HQ Mixer"
15933 lowmem: none
15934 </source>
15935 <dest>
15936 *: "HQ Микшер"
15937 lowmem: none
15938 </dest>
15939 <voice>
15940 *: "High Quality Микшер"
15941 lowmem: none
15942 </voice>
15943</phrase>
15944<phrase>
15945 id: LANG_MIKMOD_SAMPLERATE
15946 desc: in mikmod settings menu
15947 user: core
15948 <source>
15949 *: "Sample Rate"
15950 lowmem: none
15951 </source>
15952 <dest>
15953 *: "ЧаÑтота диÑкретизации"
15954 lowmem: none
15955 </dest>
15956 <voice>
15957 *: "ЧаÑтота диÑкретизации"
15958 lowmem: none
15959 </voice>
15960</phrase>
15961<phrase>
15962 id: VOICE_NUMERIC_TENS_SWAP_SEPARATOR
15963 desc: voice only, for speaking numbers in languages that swap the tens and ones fields. Leave blank for languages that do not need it, such as English ("231" => "two hundred thirty one") but other languages may speak it as "two hundred one [AND] thirty"
15964 user: core
15965 <source>
15966 *: ""
15967 </source>
15968 <dest>
15969 *: ""
15970 </dest>
15971 <voice>
15972 *: ""
15973 </voice>
15974</phrase>
15975<phrase>
15976 id: LANG_VOICED_DATE_FORMAT
15977 desc: format string for how dates will be read back. Y == 4-digit year, A == month name, m == numeric month, d == numeric day. For example, "AdY" will read "January 21 2021"
15978 user: core
15979 <source>
15980 *: "dAY"
15981 </source>
15982 <dest>
15983 *: "~dAY"
15984 </dest>
15985 <voice>
15986 *: ""
15987 </voice>
15988</phrase>
15989<phrase>
15990 id: LANG_DATABASE_DIR
15991 desc: in database settings menu
15992 user: core
15993 <source>
15994 *: "Database Directory"
15995 </source>
15996 <dest>
15997 *: "Каталог базы данных"
15998 </dest>
15999 <voice>
16000 *: "Каталог базы данных"
16001 </voice>
16002</phrase>
16003<phrase>
16004 id: LANG_REMOVE_QUEUED_TRACKS
16005 desc: Confirmation dialog
16006 user: core
16007 <source>
16008 *: "Remove Queued Tracks?"
16009 </source>
16010 <dest>
16011 *: "УдалÑÑ‚ÑŒ треки, поÑтавленные в очередь?"
16012 </dest>
16013 <voice>
16014 *: "УдалÑÑ‚ÑŒ треки, поÑтавленные в очередь?"
16015 </voice>
16016</phrase>
16017<phrase>
16018 id: LANG_QUICK_IGNORE_DIRACHE
16019 desc: in Settings
16020 user: core
16021 <source>
16022 *: "Quick (Ignore Directory Cache)"
16023 </source>
16024 <dest>
16025 *: "Quick (игнорировать кÑш каталогов)"
16026 </dest>
16027 <voice>
16028 *: "Quick (игнорировать кÑш каталогов)"
16029 </voice>
16030</phrase>
16031<phrase>
16032 id: LANG_WPS
16033 desc: in Settings
16034 user: core
16035 <source>
16036 *: "What's Playing Screen"
16037 </source>
16038 <dest>
16039 *: "Что проиÑходит на Ñкране воÑпроизведениÑ"
16040 </dest>
16041 <voice>
16042 *: "Что проиÑходит на Ñкране воÑпроизведениÑ"
16043 </voice>
16044</phrase>
16045<phrase>
16046 id: LANG_DEFAULT_BROWSER
16047 desc: in Settings
16048 user: core
16049 <source>
16050 *: "Default Browser"
16051 </source>
16052 <dest>
16053 *: "Браузер по умолчанию"
16054 </dest>
16055 <voice>
16056 *: "Браузер по умолчанию"
16057 </voice>
16058</phrase>
16059<phrase>
16060 id: LANG_AMAZE_MENU
16061 desc: Amaze game
16062 user: core
16063 <source>
16064 *: "Amaze Main Menu"
16065 </source>
16066 <dest>
16067 *: "Главное меню Amaze"
16068 </dest>
16069 <voice>
16070 *: "Главное меню Amaze"
16071 </voice>
16072</phrase>
16073<phrase>
16074 id: LANG_SET_MAZE_SIZE
16075 desc: Maze size in Amaze game
16076 user: core
16077 <source>
16078 *: "Set Maze Size"
16079 </source>
16080 <dest>
16081 *: "УÑтановить размер Maze "
16082 </dest>
16083 <voice>
16084 *: "УÑтановить размер Maze "
16085 </voice>
16086</phrase>
16087<phrase>
16088 id: LANG_VIEW_MAP
16089 desc: Map in Amaze game
16090 user: core
16091 <source>
16092 *: "View Map"
16093 </source>
16094 <dest>
16095 *: "ПоÑмотреть карту"
16096 </dest>
16097 <voice>
16098 *: "ПоÑмотреть карту"
16099 </voice>
16100</phrase>
16101<phrase>
16102 id: LANG_SHOW_COMPASS
16103 desc: Compass in Amaze game
16104 user: core
16105 <source>
16106 *: "Show Compass"
16107 </source>
16108 <dest>
16109 *: "Показать компаÑ"
16110 </dest>
16111 <voice>
16112 *: "Показать компаÑ"
16113 </voice>
16114</phrase>
16115<phrase>
16116 id: LANG_SHOW_MAP
16117 desc: Map in Amaze game
16118 user: core
16119 <source>
16120 *: "Show Map"
16121 </source>
16122 <dest>
16123 *: "Показать карту"
16124 </dest>
16125 <voice>
16126 *: "Показать карту"
16127 </voice>
16128</phrase>
16129<phrase>
16130 id: LANG_REMEMBER_PATH
16131 desc: Map in Amaze game
16132 user: core
16133 <source>
16134 *: "Remember Path"
16135 </source>
16136 <dest>
16137 *: "Запомнить путь"
16138 </dest>
16139 <voice>
16140 *: "Запомнить путь"
16141 </voice>
16142</phrase>
16143<phrase>
16144 id: LANG_USE_LARGE_TILES
16145 desc: Map in Amaze game
16146 user: core
16147 <source>
16148 *: "Use Large Tiles"
16149 </source>
16150 <dest>
16151 *: "ИÑпользуйте большие Tiles"
16152 </dest>
16153 <voice>
16154 *: "ИÑпользуйте большие Tiles"
16155 </voice>
16156</phrase>
16157<phrase>
16158 id: LANG_SHOW_SOLUTION
16159 desc: Map in Amaze game
16160 user: core
16161 <source>
16162 *: "Show Solution"
16163 </source>
16164 <dest>
16165 *: "Показать решение"
16166 </dest>
16167 <voice>
16168 *: "Показать решение"
16169 </voice>
16170</phrase>
16171<phrase>
16172 id: LANG_QUIT_WITHOUT_SAVING
16173 desc:
16174 user: core
16175 <source>
16176 *: "Quit without saving"
16177 </source>
16178 <dest>
16179 *: "Выйти без ÑохранениÑ"
16180 </dest>
16181 <voice>
16182 *: "Выйти без ÑохранениÑ"
16183 </voice>
16184</phrase>
16185<phrase>
16186 id: LANG_GENERATING_MAZE
16187 desc: Amaze game
16188 user: core
16189 <source>
16190 *: "Generating maze..."
16191 </source>
16192 <dest>
16193 *: "Создать лабиринт"
16194 </dest>
16195 <voice>
16196 *: "Создать лабиринт"
16197 </voice>
16198</phrase>
16199<phrase>
16200 id: LANG_YOU_WIN
16201 desc: Success in game
16202 user: core
16203 <source>
16204 *: "You win!"
16205 </source>
16206 <dest>
16207 *: "Ты победил"
16208 </dest>
16209 <voice>
16210 *: "Ты победил"
16211 </voice>
16212</phrase>
16213<phrase>
16214 id: LANG_YOU_CHEATED
16215 desc: Cheated in game
16216 user: core
16217 <source>
16218 *: "You cheated!"
16219 </source>
16220 <dest>
16221 *: "Ты обманул"
16222 </dest>
16223 <voice>
16224 *: "Ты обманул"
16225 </voice>
16226</phrase>
16227<phrase>
16228 id: LANG_DIFFICULTY_EASY
16229 desc: Game difficulty
16230 user: core
16231 <source>
16232 *: "Easy"
16233 </source>
16234 <dest>
16235 *: "Легкий"
16236 </dest>
16237 <voice>
16238 *: "Легкий"
16239 </voice>
16240</phrase>
16241<phrase>
16242 id: LANG_DIFFICULTY_MEDIUM
16243 desc: Game difficulty
16244 user: core
16245 <source>
16246 *: "Medium"
16247 </source>
16248 <dest>
16249 *: "Средний"
16250 </dest>
16251 <voice>
16252 *: "Средний"
16253 </voice>
16254</phrase>
16255<phrase>
16256 id: LANG_DIFFICULTY_HARD
16257 desc: Game difficulty
16258 user: core
16259 <source>
16260 *: "Hard"
16261 </source>
16262 <dest>
16263 *: "Сложный"
16264 </dest>
16265 <voice>
16266 *: "Сложный"
16267 </voice>
16268</phrase>
16269<phrase>
16270 id: LANG_DIFFICULTY_EXPERT
16271 desc: Game difficulty
16272 user: core
16273 <source>
16274 *: "Expert"
16275 </source>
16276 <dest>
16277 *: "ЭкÑперт"
16278 </dest>
16279 <voice>
16280 *: "ЭкÑперт"
16281 </voice>
16282</phrase>
16283<phrase>
16284 id: LANG_STEREOSW_MODE
16285 desc: Stereo Switch Mode
16286 user: core
16287 <source>
16288 *: "Stereo Switch Mode"
16289 </source>
16290 <dest>
16291 *: "Режим Ð¿ÐµÑ€ÐµÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ñтерео"
16292 </dest>
16293 <voice>
16294 *: "Режим Ð¿ÐµÑ€ÐµÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ñтерео"
16295 </voice>
16296</phrase>
16297<phrase>
16298 id: LANG_REVERSE
16299 desc: in settings_menu
16300 user: core
16301 <source>
16302 *: "Reverse"
16303 </source>
16304 <dest>
16305 *: "Обратный / ÐžÐ±Ñ€Ð°Ñ‚Ð½Ð°Ñ Ñторона"
16306 </dest>
16307 <voice>
16308 *: "Обратный / ÐžÐ±Ñ€Ð°Ñ‚Ð½Ð°Ñ Ñторона"
16309 </voice>
16310</phrase>
16311<phrase>
16312 id: LANG_ALWAYS_ZERO
16313 desc: in settings_menu
16314 user: core
16315 <source>
16316 *: "Always 0"
16317 </source>
16318 <dest>
16319 *: "Ð’Ñегда 0"
16320 </dest>
16321 <voice>
16322 *: "Ð’Ñегда 0"
16323 </voice>
16324</phrase>
16325<phrase>
16326 id: LANG_ALWAYS_ONE
16327 desc: in settings_menu
16328 user: core
16329 <source>
16330 *: "Always 1"
16331 </source>
16332 <dest>
16333 *: "Ð’Ñегда 1"
16334 </dest>
16335 <voice>
16336 *: "Ð’Ñегда 1"
16337 </voice>
16338</phrase>
16339<phrase>
16340 id: LANG_LEGAL_NOTICES
16341 desc: in system menu
16342 user: core
16343 <source>
16344 *: "Legal Notices"
16345 </source>
16346 <dest>
16347 *: "официальное уведомление"
16348 </dest>
16349 <voice>
16350 *: "официальное уведомление"
16351 </voice>
16352</phrase>
16353<phrase>
16354 id: LANG_ERROR_FORMATSTR
16355 desc: for general use
16356 user: core
16357 <source>
16358 *: "Error: %s"
16359 </source>
16360 <dest>
16361 *: "Ошибка: %s"
16362 </dest>
16363 <voice>
16364 *: "Ошибка"
16365 </voice>
16366</phrase>
16367<phrase>
16368 id: LANG_MIKMOD_SETTINGS
16369 desc: mikmod plugin
16370 user: core
16371 <source>
16372 *: "Mikmod Settings"
16373 </source>
16374 <dest>
16375 *: "Mikmod ÐаÑтройки"
16376 </dest>
16377 <voice>
16378 *: "Mik mod ÐаÑтройки"
16379 </voice>
16380</phrase>
16381<phrase>
16382 id: LANG_MIKMOD_MENU
16383 desc: mikmod plugin
16384 user: core
16385 <source>
16386 *: "Mikmod Menu"
16387 </source>
16388 <dest>
16389 *: "Mikmod Меню"
16390 </dest>
16391 <voice>
16392 *: "Mik mod Меню"
16393 </voice>
16394</phrase>
16395<phrase>
16396 id: LANG_CHESSBOX_MENU
16397 desc: chessbox plugin
16398 user: core
16399 <source>
16400 *: "Chessbox Menu"
16401 </source>
16402 <dest>
16403 *: "Шахматы меню"
16404 </dest>
16405 <voice>
16406 *: "Шахматы меню"
16407 </voice>
16408</phrase>
16409<phrase>
16410 id: VOICE_INVALID_VOICE_FILE
16411 desc: played if the voice file fails to load
16412 user: core
16413 <source>
16414 *: ""
16415 </source>
16416 <dest>
16417 *: ""
16418 </dest>
16419 <voice>
16420 *: "ÐедопуÑтимый голоÑовой файл"
16421 </voice>
16422</phrase>
16423<phrase>
16424 id: LANG_PERCENT_FORMAT
16425 desc: percent formatting ( `10%` is default , for `10 %` use '%ld %%' , for `%10` use '%%%ld' and so on)
16426 user: core
16427 <source>
16428 *: "%ld%%"
16429 </source>
16430 <dest>
16431 *: "~%ld%%"
16432 </dest>
16433 <voice>
16434 *: none
16435 </voice>
16436</phrase>
16437<phrase>
16438 id: LANG_CHOOSE_FILE
16439 desc: file_picker plugin ask user to select a file
16440 user: core
16441 <source>
16442 *: "Choose File"
16443 </source>
16444 <dest>
16445 *: "Выберите файл"
16446 </dest>
16447 <voice>
16448 *: "Выберите файл"
16449 </voice>
16450</phrase>
16451<phrase>
16452 id: LANG_DISABLE_MAINMENU_SCROLLING
16453 desc: Disable main menu scrolling
16454 user: core
16455 <source>
16456 *: "Disable main menu scrolling"
16457 </source>
16458 <dest>
16459 *: "Отключить прокрутку главного меню"
16460 </dest>
16461 <voice>
16462 *: "Отключить прокрутку главного меню"
16463 </voice>
16464</phrase>
16465<phrase>
16466 id: LANG_REMAINING
16467 desc: Playing Time
16468 user: core
16469 <source>
16470 *: "Remaining"
16471 </source>
16472 <dest>
16473 *: "ОÑтавшийÑÑ"
16474 </dest>
16475 <voice>
16476 *: "ОÑтавшийÑÑ"
16477 </voice>
16478</phrase>
16479<phrase>
16480 id: LANG_DISPLAY_TITLEALBUM_FROMTAGS
16481 desc: track display options
16482 user: core
16483 <source>
16484 *: "Title & Album from ID3 tags"
16485 </source>
16486 <dest>
16487 *: "Ðазвание и альбом по тегам ID3"
16488 </dest>
16489 <voice>
16490 *: "Ðазвание и альбом по тегам"
16491 </voice>
16492</phrase>
16493<phrase>
16494 id: LANG_DISPLAY_TITLE_FROMTAGS
16495 desc: track display options
16496 user: core
16497 <source>
16498 *: "Title from ID3 tags"
16499 </source>
16500 <dest>
16501 *: "Ðазвание из ID3-тегов"
16502 </dest>
16503 <voice>
16504 *: "Ðазвание из тегов"
16505 </voice>
16506</phrase>
diff --git a/apps/lang/slovak.lang b/apps/lang/slovak.lang
index b01f2a38dc..7f68a0c5f0 100644
--- a/apps/lang/slovak.lang
+++ b/apps/lang/slovak.lang
@@ -471,10 +471,10 @@
471 desc: top item in the list when asking user about bookmark auto load 471 desc: top item in the list when asking user about bookmark auto load
472 user: core 472 user: core
473 <source> 473 <source>
474 *: "<Don't Resume>" 474 *: "[Don't Resume]"
475 </source> 475 </source>
476 <dest> 476 <dest>
477 *: "<Neobnoviť>" 477 *: "[Neobnoviť]"
478 </dest> 478 </dest>
479 <voice> 479 <voice>
480 *: "Neobnoviť" 480 *: "Neobnoviť"
@@ -499,10 +499,10 @@
499 desc: bookmark selection list, bookmark couldn't be parsed 499 desc: bookmark selection list, bookmark couldn't be parsed
500 user: core 500 user: core
501 <source> 501 <source>
502 *: "<Invalid Bookmark>" 502 *: "[Invalid Bookmark]"
503 </source> 503 </source>
504 <dest> 504 <dest>
505 *: "<Neplatná Záložka>" 505 *: "[Neplatná Záložka]"
506 </dest> 506 </dest>
507 <voice> 507 <voice>
508 *: "Neplatná Záložka" 508 *: "Neplatná Záložka"
@@ -2137,13 +2137,13 @@
2137</phrase> 2137</phrase>
2138<phrase> 2138<phrase>
2139 id: LANG_TAGNAVI_ALL_TRACKS 2139 id: LANG_TAGNAVI_ALL_TRACKS
2140 desc: "<All tracks>" entry in tag browser 2140 desc: "[All tracks]" entry in tag browser
2141 user: core 2141 user: core
2142 <source> 2142 <source>
2143 *: "<All tracks>" 2143 *: "[All tracks]"
2144 </source> 2144 </source>
2145 <dest> 2145 <dest>
2146 *: "<VÅ¡etky stopy>" 2146 *: "[VÅ¡etky stopy]"
2147 </dest> 2147 </dest>
2148 <voice> 2148 <voice>
2149 *: "VÅ¡etky stopy" 2149 *: "VÅ¡etky stopy"
@@ -6758,10 +6758,10 @@
6758 desc: in tag viewer 6758 desc: in tag viewer
6759 user: core 6759 user: core
6760 <source> 6760 <source>
6761 *: "<No Info>" 6761 *: "[No Info]"
6762 </source> 6762 </source>
6763 <dest> 6763 <dest>
6764 *: "<Bez Infa>" 6764 *: "[Bez Infa]"
6765 </dest> 6765 </dest>
6766 <voice> 6766 <voice>
6767 *: "Bez Infa" 6767 *: "Bez Infa"
@@ -9454,13 +9454,13 @@
9454</phrase> 9454</phrase>
9455<phrase> 9455<phrase>
9456 id: LANG_TAGNAVI_RANDOM 9456 id: LANG_TAGNAVI_RANDOM
9457 desc: "<Random>" entry in tag browser 9457 desc: "[Random]" entry in tag browser
9458 user: core 9458 user: core
9459 <source> 9459 <source>
9460 *: "<Random>" 9460 *: "[Random]"
9461 </source> 9461 </source>
9462 <dest> 9462 <dest>
9463 *: "<Náhodne>" 9463 *: "[Náhodne]"
9464 </dest> 9464 </dest>
9465 <voice> 9465 <voice>
9466 *: "Náhodne" 9466 *: "Náhodne"
@@ -11231,13 +11231,13 @@
11231</phrase> 11231</phrase>
11232<phrase> 11232<phrase>
11233 id: LANG_TAGNAVI_UNTAGGED 11233 id: LANG_TAGNAVI_UNTAGGED
11234 desc: "<untagged>" entry in tag browser 11234 desc: "[untagged]" entry in tag browser
11235 user: core 11235 user: core
11236 <source> 11236 <source>
11237 *: "<Untagged>" 11237 *: "[Untagged]"
11238 </source> 11238 </source>
11239 <dest> 11239 <dest>
11240 *: "<Netagované>" 11240 *: "[Netagované]"
11241 </dest> 11241 </dest>
11242 <voice> 11242 <voice>
11243 *: "Netagované" 11243 *: "Netagované"
diff --git a/apps/lang/slovenscina.lang b/apps/lang/slovenscina.lang
index bda482762d..4bab9937c1 100644
--- a/apps/lang/slovenscina.lang
+++ b/apps/lang/slovenscina.lang
@@ -1895,10 +1895,10 @@
1895 desc: in tag viewer 1895 desc: in tag viewer
1896 user: core 1896 user: core
1897 <source> 1897 <source>
1898 *: "<No Info>" 1898 *: "[No Info]"
1899 </source> 1899 </source>
1900 <dest> 1900 <dest>
1901 *: "<Ni podatka>" 1901 *: "[Ni podatka]"
1902 </dest> 1902 </dest>
1903 <voice> 1903 <voice>
1904 *: "Ni podatka" 1904 *: "Ni podatka"
@@ -3211,13 +3211,13 @@
3211</phrase> 3211</phrase>
3212<phrase> 3212<phrase>
3213 id: LANG_TAGNAVI_ALL_TRACKS 3213 id: LANG_TAGNAVI_ALL_TRACKS
3214 desc: "<All tracks>" entry in tag browser 3214 desc: "[All tracks]" entry in tag browser
3215 user: core 3215 user: core
3216 <source> 3216 <source>
3217 *: "<All tracks>" 3217 *: "[All tracks]"
3218 </source> 3218 </source>
3219 <dest> 3219 <dest>
3220 *: "<All tracks>" 3220 *: "[All tracks]"
3221 </dest> 3221 </dest>
3222 <voice> 3222 <voice>
3223 *: "All tracks" 3223 *: "All tracks"
@@ -5525,13 +5525,13 @@
5525</phrase> 5525</phrase>
5526<phrase> 5526<phrase>
5527 id: LANG_TAGNAVI_RANDOM 5527 id: LANG_TAGNAVI_RANDOM
5528 desc: "<Random>" entry in tag browser 5528 desc: "[Random]" entry in tag browser
5529 user: core 5529 user: core
5530 <source> 5530 <source>
5531 *: "<Random>" 5531 *: "[Random]"
5532 </source> 5532 </source>
5533 <dest> 5533 <dest>
5534 *: "<Random>" 5534 *: "[Random]"
5535 </dest> 5535 </dest>
5536 <voice> 5536 <voice>
5537 *: "Random" 5537 *: "Random"
@@ -7194,13 +7194,13 @@
7194</phrase> 7194</phrase>
7195<phrase> 7195<phrase>
7196 id: LANG_TAGNAVI_UNTAGGED 7196 id: LANG_TAGNAVI_UNTAGGED
7197 desc: "<untagged>" entry in tag browser 7197 desc: "[untagged]" entry in tag browser
7198 user: core 7198 user: core
7199 <source> 7199 <source>
7200 *: "<Untagged>" 7200 *: "[Untagged]"
7201 </source> 7201 </source>
7202 <dest> 7202 <dest>
7203 *: "<Untagged>" 7203 *: "[Untagged]"
7204 </dest> 7204 </dest>
7205 <voice> 7205 <voice>
7206 *: "Untagged" 7206 *: "Untagged"
@@ -9558,10 +9558,10 @@
9558 desc: top item in the list when asking user about bookmark auto load 9558 desc: top item in the list when asking user about bookmark auto load
9559 user: core 9559 user: core
9560 <source> 9560 <source>
9561 *: "<Don't Resume>" 9561 *: "[Don't Resume]"
9562 </source> 9562 </source>
9563 <dest> 9563 <dest>
9564 *: "<Don't Resume>" 9564 *: "[Don't Resume]"
9565 </dest> 9565 </dest>
9566 <voice> 9566 <voice>
9567 *: "Do not resume" 9567 *: "Do not resume"
@@ -10258,10 +10258,10 @@
10258 desc: bookmark selection list, bookmark couldn't be parsed 10258 desc: bookmark selection list, bookmark couldn't be parsed
10259 user: core 10259 user: core
10260 <source> 10260 <source>
10261 *: "<Invalid Bookmark>" 10261 *: "[Invalid Bookmark]"
10262 </source> 10262 </source>
10263 <dest> 10263 <dest>
10264 *: "<Invalid Bookmark>" 10264 *: "[Invalid Bookmark]"
10265 </dest> 10265 </dest>
10266 <voice> 10266 <voice>
10267 *: "Invalid Bookmark" 10267 *: "Invalid Bookmark"
diff --git a/apps/lang/srpski.lang b/apps/lang/srpski.lang
index da940e8311..be75e05ed5 100644
--- a/apps/lang/srpski.lang
+++ b/apps/lang/srpski.lang
@@ -471,10 +471,10 @@
471 desc: top item in the list when asking user about bookmark auto load 471 desc: top item in the list when asking user about bookmark auto load
472 user: core 472 user: core
473 <source> 473 <source>
474 *: "<Don't Resume>" 474 *: "[Don't Resume]"
475 </source> 475 </source>
476 <dest> 476 <dest>
477 *: "<Ðемој да наÑтавиш>" 477 *: "[Ðемој да наÑтавиш]"
478 </dest> 478 </dest>
479 <voice> 479 <voice>
480 *: "Ðемој да наÑтавиш" 480 *: "Ðемој да наÑтавиш"
@@ -499,10 +499,10 @@
499 desc: bookmark selection list, bookmark couldn't be parsed 499 desc: bookmark selection list, bookmark couldn't be parsed
500 user: core 500 user: core
501 <source> 501 <source>
502 *: "<Invalid Bookmark>" 502 *: "[Invalid Bookmark]"
503 </source> 503 </source>
504 <dest> 504 <dest>
505 *: "<ÐеиÑправан маркер>" 505 *: "[ÐеиÑправан маркер]"
506 </dest> 506 </dest>
507 <voice> 507 <voice>
508 *: "ÐеиÑправан маркер" 508 *: "ÐеиÑправан маркер"
@@ -2120,13 +2120,13 @@
2120</phrase> 2120</phrase>
2121<phrase> 2121<phrase>
2122 id: LANG_TAGNAVI_ALL_TRACKS 2122 id: LANG_TAGNAVI_ALL_TRACKS
2123 desc: "<All tracks>" entry in tag browser 2123 desc: "[All tracks]" entry in tag browser
2124 user: core 2124 user: core
2125 <source> 2125 <source>
2126 *: "<All tracks>" 2126 *: "[All tracks]"
2127 </source> 2127 </source>
2128 <dest> 2128 <dest>
2129 *: "<Све нумере>" 2129 *: "[Све нумере]"
2130 </dest> 2130 </dest>
2131 <voice> 2131 <voice>
2132 *: "Све нумере" 2132 *: "Све нумере"
@@ -6742,10 +6742,10 @@
6742 desc: in tag viewer 6742 desc: in tag viewer
6743 user: core 6743 user: core
6744 <source> 6744 <source>
6745 *: "<No Info>" 6745 *: "[No Info]"
6746 </source> 6746 </source>
6747 <dest> 6747 <dest>
6748 *: "<Ðема инфо>" 6748 *: "[Ðема инфо]"
6749 </dest> 6749 </dest>
6750 <voice> 6750 <voice>
6751 *: "Ðема инфо" 6751 *: "Ðема инфо"
@@ -9438,13 +9438,13 @@
9438</phrase> 9438</phrase>
9439<phrase> 9439<phrase>
9440 id: LANG_TAGNAVI_RANDOM 9440 id: LANG_TAGNAVI_RANDOM
9441 desc: "<Random>" entry in tag browser 9441 desc: "[Random]" entry in tag browser
9442 user: core 9442 user: core
9443 <source> 9443 <source>
9444 *: "<Random>" 9444 *: "[Random]"
9445 </source> 9445 </source>
9446 <dest> 9446 <dest>
9447 *: "<ÐаÑумично>" 9447 *: "[ÐаÑумично]"
9448 </dest> 9448 </dest>
9449 <voice> 9449 <voice>
9450 *: "ÐаÑумично" 9450 *: "ÐаÑумично"
@@ -11197,13 +11197,13 @@
11197</phrase> 11197</phrase>
11198<phrase> 11198<phrase>
11199 id: LANG_TAGNAVI_UNTAGGED 11199 id: LANG_TAGNAVI_UNTAGGED
11200 desc: "<untagged>" entry in tag browser 11200 desc: "[untagged]" entry in tag browser
11201 user: core 11201 user: core
11202 <source> 11202 <source>
11203 *: "<Untagged>" 11203 *: "[Untagged]"
11204 </source> 11204 </source>
11205 <dest> 11205 <dest>
11206 *: "<Ðеобележенo>" 11206 *: "[Ðеобележенo]"
11207 </dest> 11207 </dest>
11208 <voice> 11208 <voice>
11209 *: "Ðеобележенo" 11209 *: "Ðеобележенo"
diff --git a/apps/lang/svenska.lang b/apps/lang/svenska.lang
index 523b363e98..8b28a79815 100644
--- a/apps/lang/svenska.lang
+++ b/apps/lang/svenska.lang
@@ -484,10 +484,10 @@
484 desc: top item in the list when asking user about bookmark auto load 484 desc: top item in the list when asking user about bookmark auto load
485 user: core 485 user: core
486 <source> 486 <source>
487 *: "<Don't Resume>" 487 *: "[Don't Resume]"
488 </source> 488 </source>
489 <dest> 489 <dest>
490 *: "<Ã…teruppta inte>" 490 *: "[Ã…teruppta inte]"
491 </dest> 491 </dest>
492 <voice> 492 <voice>
493 *: "Ã…teruppta inte" 493 *: "Ã…teruppta inte"
@@ -512,10 +512,10 @@
512 desc: bookmark selection list, bookmark couldn't be parsed 512 desc: bookmark selection list, bookmark couldn't be parsed
513 user: core 513 user: core
514 <source> 514 <source>
515 *: "<Invalid Bookmark>" 515 *: "[Invalid Bookmark]"
516 </source> 516 </source>
517 <dest> 517 <dest>
518 *: "<Felaktigt bokmärke>" 518 *: "[Felaktigt bokmärke]"
519 </dest> 519 </dest>
520 <voice> 520 <voice>
521 *: "Felaktigt bokmärke" 521 *: "Felaktigt bokmärke"
@@ -2305,13 +2305,13 @@
2305</phrase> 2305</phrase>
2306<phrase> 2306<phrase>
2307 id: LANG_TAGNAVI_ALL_TRACKS 2307 id: LANG_TAGNAVI_ALL_TRACKS
2308 desc: "<All tracks>" entry in tag browser 2308 desc: "[All tracks]" entry in tag browser
2309 user: core 2309 user: core
2310 <source> 2310 <source>
2311 *: "<All tracks>" 2311 *: "[All tracks]"
2312 </source> 2312 </source>
2313 <dest> 2313 <dest>
2314 *: "<Alla spår>" 2314 *: "[Alla spår]"
2315 </dest> 2315 </dest>
2316 <voice> 2316 <voice>
2317 *: "Alla spår" 2317 *: "Alla spår"
@@ -7153,10 +7153,10 @@
7153 desc: in tag viewer 7153 desc: in tag viewer
7154 user: core 7154 user: core
7155 <source> 7155 <source>
7156 *: "<No Info>" 7156 *: "[No Info]"
7157 </source> 7157 </source>
7158 <dest> 7158 <dest>
7159 *: "<Saknas>" 7159 *: "[Saknas]"
7160 </dest> 7160 </dest>
7161 <voice> 7161 <voice>
7162 *: "Saknas" 7162 *: "Saknas"
@@ -10179,13 +10179,13 @@
10179</phrase> 10179</phrase>
10180<phrase> 10180<phrase>
10181 id: LANG_TAGNAVI_RANDOM 10181 id: LANG_TAGNAVI_RANDOM
10182 desc: "<Random>" entry in tag browser 10182 desc: "[Random]" entry in tag browser
10183 user: core 10183 user: core
10184 <source> 10184 <source>
10185 *: "<Random>" 10185 *: "[Random]"
10186 </source> 10186 </source>
10187 <dest> 10187 <dest>
10188 *: "<Slumpvald>" 10188 *: "[Slumpvald]"
10189 </dest> 10189 </dest>
10190 <voice> 10190 <voice>
10191 *: "Slumpvald" 10191 *: "Slumpvald"
@@ -12040,13 +12040,13 @@
12040</phrase> 12040</phrase>
12041<phrase> 12041<phrase>
12042 id: LANG_TAGNAVI_UNTAGGED 12042 id: LANG_TAGNAVI_UNTAGGED
12043 desc: "<untagged>" entry in tag browser 12043 desc: "[untagged]" entry in tag browser
12044 user: core 12044 user: core
12045 <source> 12045 <source>
12046 *: "<Untagged>" 12046 *: "[Untagged]"
12047 </source> 12047 </source>
12048 <dest> 12048 <dest>
12049 *: "<Ej taggad>" 12049 *: "[Ej taggad]"
12050 </dest> 12050 </dest>
12051 <voice> 12051 <voice>
12052 *: "Ej taggad" 12052 *: "Ej taggad"
diff --git a/apps/lang/tagalog.lang b/apps/lang/tagalog.lang
index b5ccde1ced..a08b3a4d97 100644
--- a/apps/lang/tagalog.lang
+++ b/apps/lang/tagalog.lang
@@ -476,10 +476,10 @@
476 desc: top item in the list when asking user about bookmark auto load 476 desc: top item in the list when asking user about bookmark auto load
477 user: core 477 user: core
478 <source> 478 <source>
479 *: "<Don't Resume>" 479 *: "[Don't Resume]"
480 </source> 480 </source>
481 <dest> 481 <dest>
482 *: "<Hindi Tuloy>" 482 *: "[Hindi Tuloy]"
483 </dest> 483 </dest>
484 <voice> 484 <voice>
485 *: "Hindi Tuloy" 485 *: "Hindi Tuloy"
@@ -504,10 +504,10 @@
504 desc: bookmark selection list, bookmark couldn't be parsed 504 desc: bookmark selection list, bookmark couldn't be parsed
505 user: core 505 user: core
506 <source> 506 <source>
507 *: "<Invalid Bookmark>" 507 *: "[Invalid Bookmark]"
508 </source> 508 </source>
509 <dest> 509 <dest>
510 *: "<Walang-saysay Bookmark>" 510 *: "[Walang-saysay Bookmark]"
511 </dest> 511 </dest>
512 <voice> 512 <voice>
513 *: "Walang-saysay Bookmark" 513 *: "Walang-saysay Bookmark"
@@ -2296,13 +2296,13 @@
2296</phrase> 2296</phrase>
2297<phrase> 2297<phrase>
2298 id: LANG_TAGNAVI_ALL_TRACKS 2298 id: LANG_TAGNAVI_ALL_TRACKS
2299 desc: "<All tracks>" entry in tag browser 2299 desc: "[All tracks]" entry in tag browser
2300 user: core 2300 user: core
2301 <source> 2301 <source>
2302 *: "<All tracks>" 2302 *: "[All tracks]"
2303 </source> 2303 </source>
2304 <dest> 2304 <dest>
2305 *: "<Lahat ng landas>" 2305 *: "[Lahat ng landas]"
2306 </dest> 2306 </dest>
2307 <voice> 2307 <voice>
2308 *: "All tracks" 2308 *: "All tracks"
@@ -6490,10 +6490,10 @@
6490 desc: in tag viewer 6490 desc: in tag viewer
6491 user: core 6491 user: core
6492 <source> 6492 <source>
6493 *: "<No Info>" 6493 *: "[No Info]"
6494 </source> 6494 </source>
6495 <dest> 6495 <dest>
6496 *: "<Wala Inpormasyon>" 6496 *: "[Wala Inpormasyon]"
6497 </dest> 6497 </dest>
6498 <voice> 6498 <voice>
6499 *: "Wala Inpormasyon" 6499 *: "Wala Inpormasyon"
@@ -7194,13 +7194,13 @@
7194</phrase> 7194</phrase>
7195<phrase> 7195<phrase>
7196 id: LANG_TAGNAVI_RANDOM 7196 id: LANG_TAGNAVI_RANDOM
7197 desc: "<Random>" entry in tag browser 7197 desc: "[Random]" entry in tag browser
7198 user: core 7198 user: core
7199 <source> 7199 <source>
7200 *: "<Random>" 7200 *: "[Random]"
7201 </source> 7201 </source>
7202 <dest> 7202 <dest>
7203 *: "<Pagkakataon>" 7203 *: "[Pagkakataon]"
7204 </dest> 7204 </dest>
7205 <voice> 7205 <voice>
7206 *: "Random" 7206 *: "Random"
@@ -11701,13 +11701,13 @@
11701</phrase> 11701</phrase>
11702<phrase> 11702<phrase>
11703 id: LANG_TAGNAVI_UNTAGGED 11703 id: LANG_TAGNAVI_UNTAGGED
11704 desc: "<untagged>" entry in tag browser 11704 desc: "[untagged]" entry in tag browser
11705 user: core 11705 user: core
11706 <source> 11706 <source>
11707 *: "<Untagged>" 11707 *: "[Untagged]"
11708 </source> 11708 </source>
11709 <dest> 11709 <dest>
11710 *: "<Untagged>" 11710 *: "[Untagged]"
11711 </dest> 11711 </dest>
11712 <voice> 11712 <voice>
11713 *: "Untagged" 11713 *: "Untagged"
diff --git a/apps/lang/thai.lang b/apps/lang/thai.lang
index 36aa488496..f37e44dfdb 100644
--- a/apps/lang/thai.lang
+++ b/apps/lang/thai.lang
@@ -477,10 +477,10 @@
477 desc: top item in the list when asking user about bookmark auto load 477 desc: top item in the list when asking user about bookmark auto load
478 user: core 478 user: core
479 <source> 479 <source>
480 *: "<Don't Resume>" 480 *: "[Don't Resume]"
481 </source> 481 </source>
482 <dest> 482 <dest>
483 *: "<ไม่ต้องเปิดเพลงที่เล่นค้างไว้>" 483 *: "[ไม่ต้องเปิดเพลงที่เล่นค้างไว้]"
484 </dest> 484 </dest>
485 <voice> 485 <voice>
486 *: "Do not resume" 486 *: "Do not resume"
@@ -505,10 +505,10 @@
505 desc: bookmark selection list, bookmark couldn't be parsed 505 desc: bookmark selection list, bookmark couldn't be parsed
506 user: core 506 user: core
507 <source> 507 <source>
508 *: "<Invalid Bookmark>" 508 *: "[Invalid Bookmark]"
509 </source> 509 </source>
510 <dest> 510 <dest>
511 *: "<บุ๊คมาร์à¸à¹„ม่ถูà¸à¸•à¹‰à¸­à¸‡>" 511 *: "[บุ๊คมาร์à¸à¹„ม่ถูà¸à¸•à¹‰à¸­à¸‡]"
512 </dest> 512 </dest>
513 <voice> 513 <voice>
514 *: "Invalid Bookmark" 514 *: "Invalid Bookmark"
@@ -2296,13 +2296,13 @@
2296</phrase> 2296</phrase>
2297<phrase> 2297<phrase>
2298 id: LANG_TAGNAVI_ALL_TRACKS 2298 id: LANG_TAGNAVI_ALL_TRACKS
2299 desc: "<All tracks>" entry in tag browser 2299 desc: "[All tracks]" entry in tag browser
2300 user: core 2300 user: core
2301 <source> 2301 <source>
2302 *: "<All tracks>" 2302 *: "[All tracks]"
2303 </source> 2303 </source>
2304 <dest> 2304 <dest>
2305 *: "<เพลงทั้งหมด>" 2305 *: "[เพลงทั้งหมด]"
2306 </dest> 2306 </dest>
2307 <voice> 2307 <voice>
2308 *: "All tracks" 2308 *: "All tracks"
@@ -7124,10 +7124,10 @@
7124 desc: in tag viewer 7124 desc: in tag viewer
7125 user: core 7125 user: core
7126 <source> 7126 <source>
7127 *: "<No Info>" 7127 *: "[No Info]"
7128 </source> 7128 </source>
7129 <dest> 7129 <dest>
7130 *: "<ไม่มีข้อมูล>" 7130 *: "[ไม่มีข้อมูล]"
7131 </dest> 7131 </dest>
7132 <voice> 7132 <voice>
7133 *: "ไม่มีข้อมูล" 7133 *: "ไม่มีข้อมูล"
@@ -10007,13 +10007,13 @@
10007</phrase> 10007</phrase>
10008<phrase> 10008<phrase>
10009 id: LANG_TAGNAVI_RANDOM 10009 id: LANG_TAGNAVI_RANDOM
10010 desc: "<Random>" entry in tag browser 10010 desc: "[Random]" entry in tag browser
10011 user: core 10011 user: core
10012 <source> 10012 <source>
10013 *: "<Random>" 10013 *: "[Random]"
10014 </source> 10014 </source>
10015 <dest> 10015 <dest>
10016 *: "<ทำà¸à¸²à¸£à¸ªà¸¸à¹ˆà¸¡>" 10016 *: "[ทำà¸à¸²à¸£à¸ªà¸¸à¹ˆà¸¡]"
10017 </dest> 10017 </dest>
10018 <voice> 10018 <voice>
10019 *: "Random" 10019 *: "Random"
@@ -11941,13 +11941,13 @@
11941</phrase> 11941</phrase>
11942<phrase> 11942<phrase>
11943 id: LANG_TAGNAVI_UNTAGGED 11943 id: LANG_TAGNAVI_UNTAGGED
11944 desc: "<untagged>" entry in tag browser 11944 desc: "[untagged]" entry in tag browser
11945 user: core 11945 user: core
11946 <source> 11946 <source>
11947 *: "<Untagged>" 11947 *: "[Untagged]"
11948 </source> 11948 </source>
11949 <dest> 11949 <dest>
11950 *: "<ลบป้าย>" 11950 *: "[ลบป้าย]"
11951 </dest> 11951 </dest>
11952 <voice> 11952 <voice>
11953 *: "Untagged" 11953 *: "Untagged"
diff --git a/apps/lang/turkce.lang b/apps/lang/turkce.lang
index 560c63f5ca..99dea2272f 100644
--- a/apps/lang/turkce.lang
+++ b/apps/lang/turkce.lang
@@ -446,10 +446,10 @@
446 desc: top item in the list when asking user about bookmark auto load 446 desc: top item in the list when asking user about bookmark auto load
447 user: core 447 user: core
448 <source> 448 <source>
449 *: "<Don't Resume>" 449 *: "[Don't Resume]"
450 </source> 450 </source>
451 <dest> 451 <dest>
452 *: "<Devam etme>" 452 *: "[Devam etme]"
453 </dest> 453 </dest>
454 <voice> 454 <voice>
455 *: "Devam etme" 455 *: "Devam etme"
@@ -474,10 +474,10 @@
474 desc: bookmark selection list, bookmark couldn't be parsed 474 desc: bookmark selection list, bookmark couldn't be parsed
475 user: core 475 user: core
476 <source> 476 <source>
477 *: "<Invalid Bookmark>" 477 *: "[Invalid Bookmark]"
478 </source> 478 </source>
479 <dest> 479 <dest>
480 *: "<Geçersiz Yer imi>" 480 *: "[Geçersiz Yer imi]"
481 </dest> 481 </dest>
482 <voice> 482 <voice>
483 *: "Geçersiz Yer imi" 483 *: "Geçersiz Yer imi"
@@ -2980,13 +2980,13 @@
2980</phrase> 2980</phrase>
2981<phrase> 2981<phrase>
2982 id: LANG_TAGNAVI_RANDOM 2982 id: LANG_TAGNAVI_RANDOM
2983 desc: "<Random>" entry in tag browser 2983 desc: "[Random]" entry in tag browser
2984 user: core 2984 user: core
2985 <source> 2985 <source>
2986 *: "<Random>" 2986 *: "[Random]"
2987 </source> 2987 </source>
2988 <dest> 2988 <dest>
2989 *: "<Rastgele>" 2989 *: "[Rastgele]"
2990 </dest> 2990 </dest>
2991 <voice> 2991 <voice>
2992 *: "Rastgele" 2992 *: "Rastgele"
@@ -8017,16 +8017,16 @@
8017</phrase> 8017</phrase>
8018<phrase> 8018<phrase>
8019 id: LANG_TAGNAVI_UNTAGGED 8019 id: LANG_TAGNAVI_UNTAGGED
8020 desc: "<untagged>" entry in tag browser 8020 desc: "[untagged]" entry in tag browser
8021 user: core 8021 user: core
8022 <source> 8022 <source>
8023 *: "<Untagged>" 8023 *: "[Untagged]"
8024 </source> 8024 </source>
8025 <dest> 8025 <dest>
8026 *: "<EtiketlenmemiÅŸ>" 8026 *: "[EtiketlenmemiÅŸ]"
8027 </dest> 8027 </dest>
8028 <voice> 8028 <voice>
8029 *: "<EtiketlenmemiÅŸ>" 8029 *: "[EtiketlenmemiÅŸ]"
8030 </voice> 8030 </voice>
8031</phrase> 8031</phrase>
8032<phrase> 8032<phrase>
@@ -8304,10 +8304,10 @@
8304 desc: in tag viewer 8304 desc: in tag viewer
8305 user: core 8305 user: core
8306 <source> 8306 <source>
8307 *: "<No Info>" 8307 *: "[No Info]"
8308 </source> 8308 </source>
8309 <dest> 8309 <dest>
8310 *: "<Bilgi Yok>" 8310 *: "[Bilgi Yok]"
8311 </dest> 8311 </dest>
8312 <voice> 8312 <voice>
8313 *: "Bilgi yok" 8313 *: "Bilgi yok"
@@ -10335,13 +10335,13 @@
10335</phrase> 10335</phrase>
10336<phrase> 10336<phrase>
10337 id: LANG_TAGNAVI_ALL_TRACKS 10337 id: LANG_TAGNAVI_ALL_TRACKS
10338 desc: "<All tracks>" entry in tag browser 10338 desc: "[All tracks]" entry in tag browser
10339 user: core 10339 user: core
10340 <source> 10340 <source>
10341 *: "<All tracks>" 10341 *: "[All tracks]"
10342 </source> 10342 </source>
10343 <dest> 10343 <dest>
10344 *: "<Tüm parçalar>" 10344 *: "[Tüm parçalar]"
10345 </dest> 10345 </dest>
10346 <voice> 10346 <voice>
10347 *: "Tüm parçalar" 10347 *: "Tüm parçalar"
diff --git a/apps/lang/ukrainian.lang b/apps/lang/ukrainian.lang
index 91603a03c0..595224ac8e 100644
--- a/apps/lang/ukrainian.lang
+++ b/apps/lang/ukrainian.lang
@@ -476,10 +476,10 @@
476 desc: top item in the list when asking user about bookmark auto load 476 desc: top item in the list when asking user about bookmark auto load
477 user: core 477 user: core
478 <source> 478 <source>
479 *: "<Don't Resume>" 479 *: "[Don't Resume]"
480 </source> 480 </source>
481 <dest> 481 <dest>
482 *: "<Ðе Продовжувати>" 482 *: "[Ðе Продовжувати]"
483 </dest> 483 </dest>
484 <voice> 484 <voice>
485 *: "Ðе Продовжувати" 485 *: "Ðе Продовжувати"
@@ -504,10 +504,10 @@
504 desc: bookmark selection list, bookmark couldn't be parsed 504 desc: bookmark selection list, bookmark couldn't be parsed
505 user: core 505 user: core
506 <source> 506 <source>
507 *: "<Invalid Bookmark>" 507 *: "[Invalid Bookmark]"
508 </source> 508 </source>
509 <dest> 509 <dest>
510 *: "<Помилкова Закладка>" 510 *: "[Помилкова Закладка]"
511 </dest> 511 </dest>
512 <voice> 512 <voice>
513 *: "Помилкова Закладка" 513 *: "Помилкова Закладка"
@@ -2297,13 +2297,13 @@
2297</phrase> 2297</phrase>
2298<phrase> 2298<phrase>
2299 id: LANG_TAGNAVI_ALL_TRACKS 2299 id: LANG_TAGNAVI_ALL_TRACKS
2300 desc: "<All tracks>" entry in tag browser 2300 desc: "[All tracks]" entry in tag browser
2301 user: core 2301 user: core
2302 <source> 2302 <source>
2303 *: "<All tracks>" 2303 *: "[All tracks]"
2304 </source> 2304 </source>
2305 <dest> 2305 <dest>
2306 *: "<Ð’Ñi треки>" 2306 *: "[Ð’Ñi треки]"
2307 </dest> 2307 </dest>
2308 <voice> 2308 <voice>
2309 *: "Ð’Ñi треки" 2309 *: "Ð’Ñi треки"
@@ -7143,10 +7143,10 @@
7143 desc: in tag viewer 7143 desc: in tag viewer
7144 user: core 7144 user: core
7145 <source> 7145 <source>
7146 *: "<No Info>" 7146 *: "[No Info]"
7147 </source> 7147 </source>
7148 <dest> 7148 <dest>
7149 *: "<IнформацiÑ Ð²iдÑутнÑ>" 7149 *: "[IнформацiÑ Ð²iдÑутнÑ]"
7150 </dest> 7150 </dest>
7151 <voice> 7151 <voice>
7152 *: "IнформацiÑ Ð²iдÑутнÑ" 7152 *: "IнформацiÑ Ð²iдÑутнÑ"
@@ -10169,13 +10169,13 @@
10169</phrase> 10169</phrase>
10170<phrase> 10170<phrase>
10171 id: LANG_TAGNAVI_RANDOM 10171 id: LANG_TAGNAVI_RANDOM
10172 desc: "<Random>" entry in tag browser 10172 desc: "[Random]" entry in tag browser
10173 user: core 10173 user: core
10174 <source> 10174 <source>
10175 *: "<Random>" 10175 *: "[Random]"
10176 </source> 10176 </source>
10177 <dest> 10177 <dest>
10178 *: "<Випадково>" 10178 *: "[Випадково]"
10179 </dest> 10179 </dest>
10180 <voice> 10180 <voice>
10181 *: "Випадково" 10181 *: "Випадково"
@@ -11835,13 +11835,13 @@
11835</phrase> 11835</phrase>
11836<phrase> 11836<phrase>
11837 id: LANG_TAGNAVI_UNTAGGED 11837 id: LANG_TAGNAVI_UNTAGGED
11838 desc: "<untagged>" entry in tag browser 11838 desc: "[untagged]" entry in tag browser
11839 user: core 11839 user: core
11840 <source> 11840 <source>
11841 *: "<Untagged>" 11841 *: "[Untagged]"
11842 </source> 11842 </source>
11843 <dest> 11843 <dest>
11844 *: "<Ðе тегований>" 11844 *: "[Ðе тегований]"
11845 </dest> 11845 </dest>
11846 <voice> 11846 <voice>
11847 *: "Ðе тегований" 11847 *: "Ðе тегований"
diff --git a/apps/lang/vlaams.lang b/apps/lang/vlaams.lang
index de5ea8778e..6b93febd88 100644
--- a/apps/lang/vlaams.lang
+++ b/apps/lang/vlaams.lang
@@ -4866,10 +4866,10 @@
4866 desc: in tag viewer 4866 desc: in tag viewer
4867 user: core 4867 user: core
4868 <source> 4868 <source>
4869 *: "<No Info>" 4869 *: "[No Info]"
4870 </source> 4870 </source>
4871 <dest> 4871 <dest>
4872 *: "<geen info>" 4872 *: "[geen info]"
4873 </dest> 4873 </dest>
4874 <voice> 4874 <voice>
4875 *: "Geen info" 4875 *: "Geen info"
@@ -7368,13 +7368,13 @@
7368</phrase> 7368</phrase>
7369<phrase> 7369<phrase>
7370 id: LANG_TAGNAVI_ALL_TRACKS 7370 id: LANG_TAGNAVI_ALL_TRACKS
7371 desc: "<All tracks>" entry in tag browser 7371 desc: "[All tracks]" entry in tag browser
7372 user: core 7372 user: core
7373 <source> 7373 <source>
7374 *: "<All tracks>" 7374 *: "[All tracks]"
7375 </source> 7375 </source>
7376 <dest> 7376 <dest>
7377 *: "<Alle nummers>" 7377 *: "[Alle nummers]"
7378 </dest> 7378 </dest>
7379 <voice> 7379 <voice>
7380 *: "Alle nummers" 7380 *: "Alle nummers"
@@ -8962,10 +8962,10 @@
8962 desc: top item in the list when asking user about bookmark auto load 8962 desc: top item in the list when asking user about bookmark auto load
8963 user: core 8963 user: core
8964 <source> 8964 <source>
8965 *: "<Don't Resume>" 8965 *: "[Don't Resume]"
8966 </source> 8966 </source>
8967 <dest> 8967 <dest>
8968 *: "<Niet hervatten>" 8968 *: "[Niet hervatten]"
8969 </dest> 8969 </dest>
8970 <voice> 8970 <voice>
8971 *: "Niet hervatten" 8971 *: "Niet hervatten"
@@ -9049,10 +9049,10 @@
9049 desc: bookmark selection list, bookmark couldn't be parsed 9049 desc: bookmark selection list, bookmark couldn't be parsed
9050 user: core 9050 user: core
9051 <source> 9051 <source>
9052 *: "<Invalid Bookmark>" 9052 *: "[Invalid Bookmark]"
9053 </source> 9053 </source>
9054 <dest> 9054 <dest>
9055 *: "<Ongeldigen bladwijzer>" 9055 *: "[Ongeldigen bladwijzer]"
9056 </dest> 9056 </dest>
9057 <voice> 9057 <voice>
9058 *: "Ongeldigen bladwijzer" 9058 *: "Ongeldigen bladwijzer"
@@ -10133,13 +10133,13 @@
10133</phrase> 10133</phrase>
10134<phrase> 10134<phrase>
10135 id: LANG_TAGNAVI_RANDOM 10135 id: LANG_TAGNAVI_RANDOM
10136 desc: "<Random>" entry in tag browser 10136 desc: "[Random]" entry in tag browser
10137 user: core 10137 user: core
10138 <source> 10138 <source>
10139 *: "<Random>" 10139 *: "[Random]"
10140 </source> 10140 </source>
10141 <dest> 10141 <dest>
10142 *: "<Willekeurig>" 10142 *: "[Willekeurig]"
10143 </dest> 10143 </dest>
10144 <voice> 10144 <voice>
10145 *: "Willekeurig" 10145 *: "Willekeurig"
@@ -11875,13 +11875,13 @@
11875</phrase> 11875</phrase>
11876<phrase> 11876<phrase>
11877 id: LANG_TAGNAVI_UNTAGGED 11877 id: LANG_TAGNAVI_UNTAGGED
11878 desc: "<untagged>" entry in tag browser 11878 desc: "[untagged]" entry in tag browser
11879 user: core 11879 user: core
11880 <source> 11880 <source>
11881 *: "<Untagged>" 11881 *: "[Untagged]"
11882 </source> 11882 </source>
11883 <dest> 11883 <dest>
11884 *: "<Geen tags>" 11884 *: "[Geen tags]"
11885 </dest> 11885 </dest>
11886 <voice> 11886 <voice>
11887 *: "Geen tags" 11887 *: "Geen tags"
diff --git a/apps/lang/wallisertitsch.lang b/apps/lang/wallisertitsch.lang
index c58e61aafd..6317342ec7 100644
--- a/apps/lang/wallisertitsch.lang
+++ b/apps/lang/wallisertitsch.lang
@@ -2849,10 +2849,10 @@
2849 desc: ID3 info is missing 2849 desc: ID3 info is missing
2850 user: core 2850 user: core
2851 <source> 2851 <source>
2852 *: "<No Info>" 2852 *: "[No Info]"
2853 </source> 2853 </source>
2854 <dest> 2854 <dest>
2855 *: "<kei Info>" 2855 *: "[kei Info]"
2856 </dest> 2856 </dest>
2857 <voice> 2857 <voice>
2858 *: "kei Info" 2858 *: "kei Info"
diff --git a/apps/lang/walon.lang b/apps/lang/walon.lang
index 82ed676828..fcd3477761 100644
--- a/apps/lang/walon.lang
+++ b/apps/lang/walon.lang
@@ -480,10 +480,10 @@
480 desc: top item in the list when asking user about bookmark auto load 480 desc: top item in the list when asking user about bookmark auto load
481 user: core 481 user: core
482 <source> 482 <source>
483 *: "<Don't Resume>" 483 *: "[Don't Resume]"
484 </source> 484 </source>
485 <dest> 485 <dest>
486 *: "<Nén rataker>" 486 *: "[Nén rataker]"
487 </dest> 487 </dest>
488 <voice> 488 <voice>
489 *: "Nén rataker" 489 *: "Nén rataker"
@@ -508,10 +508,10 @@
508 desc: bookmark selection list, bookmark couldn't be parsed 508 desc: bookmark selection list, bookmark couldn't be parsed
509 user: core 509 user: core
510 <source> 510 <source>
511 *: "<Invalid Bookmark>" 511 *: "[Invalid Bookmark]"
512 </source> 512 </source>
513 <dest> 513 <dest>
514 *: "<Rimåke nén valåbe>" 514 *: "[Rimåke nén valåbe]"
515 </dest> 515 </dest>
516 <voice> 516 <voice>
517 *: "Rimåke nén valåbe" 517 *: "Rimåke nén valåbe"
@@ -2316,13 +2316,13 @@
2316</phrase> 2316</phrase>
2317<phrase> 2317<phrase>
2318 id: LANG_TAGNAVI_ALL_TRACKS 2318 id: LANG_TAGNAVI_ALL_TRACKS
2319 desc: "<All tracks>" entry in tag browser 2319 desc: "[All tracks]" entry in tag browser
2320 user: core 2320 user: core
2321 <source> 2321 <source>
2322 *: "<All tracks>" 2322 *: "[All tracks]"
2323 </source> 2323 </source>
2324 <dest> 2324 <dest>
2325 *: "<Tos les bokets>" 2325 *: "[Tos les bokets]"
2326 </dest> 2326 </dest>
2327 <voice> 2327 <voice>
2328 *: "Tos les bokets" 2328 *: "Tos les bokets"
@@ -7162,10 +7162,10 @@
7162 desc: in tag viewer 7162 desc: in tag viewer
7163 user: core 7163 user: core
7164 <source> 7164 <source>
7165 *: "<No Info>" 7165 *: "[No Info]"
7166 </source> 7166 </source>
7167 <dest> 7167 <dest>
7168 *: "<Nole infô.>" 7168 *: "[Nole infô.]"
7169 </dest> 7169 </dest>
7170 <voice> 7170 <voice>
7171 *: "Nole infô." 7171 *: "Nole infô."
@@ -10188,13 +10188,13 @@
10188</phrase> 10188</phrase>
10189<phrase> 10189<phrase>
10190 id: LANG_TAGNAVI_RANDOM 10190 id: LANG_TAGNAVI_RANDOM
10191 desc: "<Random>" entry in tag browser 10191 desc: "[Random]" entry in tag browser
10192 user: core 10192 user: core
10193 <source> 10193 <source>
10194 *: "<Random>" 10194 *: "[Random]"
10195 </source> 10195 </source>
10196 <dest> 10196 <dest>
10197 *: "<D'astcheyance>" 10197 *: "[D'astcheyance]"
10198 </dest> 10198 </dest>
10199 <voice> 10199 <voice>
10200 *: "astcheyance" 10200 *: "astcheyance"
diff --git a/apps/main.c b/apps/main.c
index 4c7689ce8c..1e8e872296 100644
--- a/apps/main.c
+++ b/apps/main.c
@@ -78,6 +78,10 @@
78#include "bootchart.h" 78#include "bootchart.h"
79#include "logdiskf.h" 79#include "logdiskf.h"
80#include "bootdata.h" 80#include "bootdata.h"
81#if defined(HAVE_DEVICEDATA)
82#include "devicedata.h"
83#endif
84
81#if (CONFIG_PLATFORM & PLATFORM_ANDROID) 85#if (CONFIG_PLATFORM & PLATFORM_ANDROID)
82#include "notification.h" 86#include "notification.h"
83#endif 87#endif
@@ -176,6 +180,9 @@ int main(void)
176 } 180 }
177 list_init(); 181 list_init();
178 tree_init(); 182 tree_init();
183#if defined(HAVE_DEVICEDATA) && !defined(BOOTLOADER) /* SIMULATOR */
184 verify_device_data();
185#endif
179 /* Keep the order of this 3 186 /* Keep the order of this 3
180 * Must be done before any code uses the multi-screen API */ 187 * Must be done before any code uses the multi-screen API */
181#ifdef HAVE_USBSTACK 188#ifdef HAVE_USBSTACK
@@ -459,6 +466,10 @@ static void init(void)
459 verify_boot_data(); 466 verify_boot_data();
460#endif 467#endif
461 468
469#if defined(HAVE_DEVICEDATA) && !defined(BOOTLOADER)
470 verify_device_data();
471#endif
472
462 /* early early early! */ 473 /* early early early! */
463 filesystem_init(); 474 filesystem_init();
464 475
diff --git a/apps/menus/display_menu.c b/apps/menus/display_menu.c
index c5c5e6d908..2ce566a888 100644
--- a/apps/menus/display_menu.c
+++ b/apps/menus/display_menu.c
@@ -331,6 +331,7 @@ MENUITEM_SETTING(list_accel_start_delay,
331MENUITEM_SETTING(list_accel_wait, &global_settings.list_accel_wait, NULL); 331MENUITEM_SETTING(list_accel_wait, &global_settings.list_accel_wait, NULL);
332#endif /* HAVE_WHEEL_ACCELERATION */ 332#endif /* HAVE_WHEEL_ACCELERATION */
333MENUITEM_SETTING(offset_out_of_view, &global_settings.offset_out_of_view, NULL); 333MENUITEM_SETTING(offset_out_of_view, &global_settings.offset_out_of_view, NULL);
334MENUITEM_SETTING(disable_mainmenu_scrolling, &global_settings.disable_mainmenu_scrolling, NULL);
334MENUITEM_SETTING(screen_scroll_step, &global_settings.screen_scroll_step, NULL); 335MENUITEM_SETTING(screen_scroll_step, &global_settings.screen_scroll_step, NULL);
335MENUITEM_SETTING(scroll_paginated, &global_settings.scroll_paginated, NULL); 336MENUITEM_SETTING(scroll_paginated, &global_settings.scroll_paginated, NULL);
336MENUITEM_SETTING(list_wraparound, &global_settings.list_wraparound, NULL); 337MENUITEM_SETTING(list_wraparound, &global_settings.list_wraparound, NULL);
@@ -343,7 +344,9 @@ MAKE_MENU(scroll_settings_menu, ID2P(LANG_SCROLL_MENU), 0, Icon_NOICON,
343#ifdef HAVE_REMOTE_LCD 344#ifdef HAVE_REMOTE_LCD
344 &remote_scroll_sets, 345 &remote_scroll_sets,
345#endif 346#endif
346 &offset_out_of_view, &screen_scroll_step, 347 &offset_out_of_view,
348 &disable_mainmenu_scrolling,
349 &screen_scroll_step,
347 &scroll_paginated, 350 &scroll_paginated,
348 &list_wraparound, 351 &list_wraparound,
349 &list_order, 352 &list_order,
diff --git a/apps/onplay.c b/apps/onplay.c
index 4f748204df..ab507f08ac 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,14 @@ 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 {
1299 int returnCode = add_to_playlist(&addtopl_replace_shuffled);
1300 if (returnCode == 1)
1301 // User did not want to erase his current playlist, so let's show again the database main menu
1302 return ONPLAY_RELOAD_DIR;
1303 return ONPLAY_START_PLAY;
1304 }
1297 1305
1298 push_current_activity(ACTIVITY_CONTEXTMENU); 1306 push_current_activity(ACTIVITY_CONTEXTMENU);
1299 if (from_context == CONTEXT_WPS) 1307 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.h b/apps/playlist.h
index f7426df9a3..2fb1ce100e 100644
--- a/apps/playlist.h
+++ b/apps/playlist.h
@@ -33,6 +33,8 @@
33#define PLAYLIST_ATTR_QUEUED 0x01 33#define PLAYLIST_ATTR_QUEUED 0x01
34#define PLAYLIST_ATTR_INSERTED 0x02 34#define PLAYLIST_ATTR_INSERTED 0x02
35#define PLAYLIST_ATTR_SKIPPED 0x04 35#define PLAYLIST_ATTR_SKIPPED 0x04
36#define PLAYLIST_ATTR_RETRIEVE_ID3_ATTEMPTED 0x08
37#define PLAYLIST_ATTR_RETRIEVE_ID3_SUCCEEDED 0x10
36 38
37#define PLAYLIST_DISPLAY_COUNT 10 39#define PLAYLIST_DISPLAY_COUNT 10
38 40
diff --git a/apps/playlist_viewer.c b/apps/playlist_viewer.c
index 70072f59f5..d556f3b557 100644
--- a/apps/playlist_viewer.c
+++ b/apps/playlist_viewer.c
@@ -50,6 +50,7 @@
50#include "playlist_menu.h" 50#include "playlist_menu.h"
51#include "menus/exported_menus.h" 51#include "menus/exported_menus.h"
52#include "yesno.h" 52#include "yesno.h"
53#include "playback.h"
53 54
54/* Maximum number of tracks we can have loaded at one time */ 55/* Maximum number of tracks we can have loaded at one time */
55#define MAX_PLAYLIST_ENTRIES 200 56#define MAX_PLAYLIST_ENTRIES 200
@@ -142,7 +143,7 @@ static bool playlist_viewer_init(struct playlist_viewer * viewer,
142 const char* filename, bool reload, 143 const char* filename, bool reload,
143 int *most_recent_selection); 144 int *most_recent_selection);
144 145
145static void format_line(const struct playlist_entry* track, char* str, 146static void format_line(struct playlist_entry* track, char* str,
146 int len); 147 int len);
147 148
148static bool update_playlist(bool force); 149static bool update_playlist(bool force);
@@ -159,6 +160,27 @@ static void playlist_buffer_init(struct playlist_buffer *pb, char *names_buffer,
159 pb->num_loaded = 0; 160 pb->num_loaded = 0;
160} 161}
161 162
163static int playlist_buffer_get_index(struct playlist_buffer *pb, int index)
164{
165 int buffer_index;
166 if (pb->direction == FORWARD)
167 {
168 if (index >= pb->first_index)
169 buffer_index = index-pb->first_index;
170 else /* rotation : track0 in buffer + requested track */
171 buffer_index = viewer.num_tracks-pb->first_index+index;
172 }
173 else
174 {
175 if (index <= pb->first_index)
176 buffer_index = pb->first_index-index;
177 else /* rotation : track0 in buffer + dist from the last track
178 to the requested track (num_tracks-requested track) */
179 buffer_index = pb->first_index+viewer.num_tracks-index;
180 }
181 return buffer_index;
182}
183
162/* 184/*
163 * Loads the entries following 'index' in the playlist buffer 185 * Loads the entries following 'index' in the playlist buffer
164 */ 186 */
@@ -227,6 +249,25 @@ static void playlist_buffer_load_entries_screen(struct playlist_buffer * pb,
227 playlist_buffer_load_entries(pb, start, direction); 249 playlist_buffer_load_entries(pb, start, direction);
228} 250}
229 251
252static bool retrieve_id3_tags(const int index, const char* name, struct mp3entry *id3, int flags)
253{
254 bool id3_retrieval_successful = false;
255
256 if (!viewer.playlist &&
257 (audio_status() & AUDIO_STATUS_PLAY) &&
258 (playlist_get_resume_info(&viewer.current_playing_track) == index))
259 {
260 copy_mp3entry(id3, audio_current_track()); /* retrieve id3 from RAM */
261 id3_retrieval_successful = true;
262 }
263 else
264 {
265 /* Read from disk, the database, doesn't store frequency, file size or codec (g4470) ChrisS*/
266 id3_retrieval_successful = get_metadata_ex(id3, -1, name, flags);
267 }
268 return id3_retrieval_successful;
269}
270
230static int playlist_entry_load(struct playlist_entry *entry, int index, 271static int playlist_entry_load(struct playlist_entry *entry, int index,
231 char* name_buffer, int remaining_size) 272 char* name_buffer, int remaining_size)
232{ 273{
@@ -242,6 +283,13 @@ static int playlist_entry_load(struct playlist_entry *entry, int index,
242 283
243 len = strlcpy(name_buffer, info.filename, remaining_size) + 1; 284 len = strlcpy(name_buffer, info.filename, remaining_size) + 1;
244 285
286 if (global_settings.playlist_viewer_track_display >
287 PLAYLIST_VIEWER_ENTRY_SHOW_FULL_PATH && len <= remaining_size)
288 {
289 /* Allocate space for the id3viewc if the option is enabled */
290 len += MAX_PATH + 1;
291 }
292
245 if (len <= remaining_size) 293 if (len <= remaining_size)
246 { 294 {
247 entry->name = name_buffer; 295 entry->name = name_buffer;
@@ -253,27 +301,6 @@ static int playlist_entry_load(struct playlist_entry *entry, int index,
253 return -1; 301 return -1;
254} 302}
255 303
256static int playlist_buffer_get_index(struct playlist_buffer *pb, int index)
257{
258 int buffer_index;
259 if (pb->direction == FORWARD)
260 {
261 if (index >= pb->first_index)
262 buffer_index = index-pb->first_index;
263 else /* rotation : track0 in buffer + requested track */
264 buffer_index = viewer.num_tracks-pb->first_index+index;
265 }
266 else
267 {
268 if (index <= pb->first_index)
269 buffer_index = pb->first_index-index;
270 else /* rotation : track0 in buffer + dist from the last track
271 to the requested track (num_tracks-requested track) */
272 buffer_index = pb->first_index+viewer.num_tracks-index;
273 }
274 return buffer_index;
275}
276
277#define distance(a, b) \ 304#define distance(a, b) \
278 a>b? (a) - (b) : (b) - (a) 305 a>b? (a) - (b) : (b) - (a)
279static bool playlist_buffer_needs_reload(struct playlist_buffer* pb, 306static bool playlist_buffer_needs_reload(struct playlist_buffer* pb,
@@ -440,7 +467,9 @@ static void format_name(char* dest, const char* src, size_t bufsz)
440{ 467{
441 switch (global_settings.playlist_viewer_track_display) 468 switch (global_settings.playlist_viewer_track_display)
442 { 469 {
443 case 0: 470 case PLAYLIST_VIEWER_ENTRY_SHOW_FILE_NAME:
471 case PLAYLIST_VIEWER_ENTRY_SHOW_ID3_TITLE_AND_ALBUM: /* If loading from tags failed, only display the file name */
472 case PLAYLIST_VIEWER_ENTRY_SHOW_ID3_TITLE: /* If loading from tags failed, only display the file name */
444 default: 473 default:
445 { 474 {
446 /* Only display the filename */ 475 /* Only display the filename */
@@ -450,7 +479,7 @@ static void format_name(char* dest, const char* src, size_t bufsz)
450 strrsplt(dest, '.'); 479 strrsplt(dest, '.');
451 break; 480 break;
452 } 481 }
453 case 1: 482 case PLAYLIST_VIEWER_ENTRY_SHOW_FULL_PATH:
454 /* Full path */ 483 /* Full path */
455 strlcpy(dest, src, bufsz); 484 strlcpy(dest, src, bufsz);
456 break; 485 break;
@@ -458,22 +487,99 @@ static void format_name(char* dest, const char* src, size_t bufsz)
458} 487}
459 488
460/* Format display line */ 489/* Format display line */
461static void format_line(const struct playlist_entry* track, char* str, 490static void format_line(struct playlist_entry* track, char* str,
462 int len) 491 int len)
463{ 492{
464 char name[MAX_PATH]; 493 char *id3viewc = NULL;
465 char *skipped = ""; 494 char *skipped = "";
466 format_name(name, track->name, sizeof(name));
467
468 if (track->attr & PLAYLIST_ATTR_SKIPPED) 495 if (track->attr & PLAYLIST_ATTR_SKIPPED)
469 skipped = "(ERR) "; 496 skipped = "(ERR) ";
470 497 if (!(track->attr & PLAYLIST_ATTR_RETRIEVE_ID3_ATTEMPTED) &&
471 if (global_settings.playlist_viewer_indices) 498 (global_settings.playlist_viewer_track_display ==
472 /* Display playlist index */ 499 PLAYLIST_VIEWER_ENTRY_SHOW_ID3_TITLE_AND_ALBUM ||
473 snprintf(str, len, "%d. %s%s", track->display_index, skipped, name); 500 global_settings.playlist_viewer_track_display ==
501 PLAYLIST_VIEWER_ENTRY_SHOW_ID3_TITLE
502 ))
503 {
504 track->attr |= PLAYLIST_ATTR_RETRIEVE_ID3_ATTEMPTED;
505 struct mp3entry id3;
506 bool retrieve_success = retrieve_id3_tags(track->index, track->name,
507 &id3, METADATA_EXCLUDE_ID3_PATH);
508 if (retrieve_success)
509 {
510 if (!id3viewc)
511 {
512 id3viewc = track->name + strlen(track->name) + 1;
513 }
514 struct mp3entry * pid3 = &id3;
515 id3viewc[0] = '\0';
516 if (global_settings.playlist_viewer_track_display ==
517 PLAYLIST_VIEWER_ENTRY_SHOW_ID3_TITLE_AND_ALBUM)
518 {
519 /* Title & Album */
520 if (pid3->title && pid3->title[0] != '\0')
521 {
522 char* cur_str = id3viewc;
523 int title_len = strlen(pid3->title);
524 int rem_space = MAX_PATH;
525 for (int i = 0; i < title_len && rem_space > 0; i++)
526 {
527 cur_str[0] = pid3->title[i];
528 cur_str++;
529 rem_space--;
530 }
531 if (rem_space > 10)
532 {
533 cur_str[0] = (char) ' ';
534 cur_str[1] = (char) '-';
535 cur_str[2] = (char) ' ';
536 cur_str += 3;
537 rem_space -= 3;
538 cur_str = strmemccpy(cur_str, pid3->album && pid3->album[0] != '\0' ?
539 pid3->album : (char*) str(LANG_TAGNAVI_UNTAGGED), rem_space);
540 if (cur_str)
541 track->attr |= PLAYLIST_ATTR_RETRIEVE_ID3_SUCCEEDED;
542 }
543 }
544 }
545 else if (global_settings.playlist_viewer_track_display ==
546 PLAYLIST_VIEWER_ENTRY_SHOW_ID3_TITLE)
547 {
548 /* Just the title */
549 if (pid3->title && pid3->title[0] != '\0' &&
550 strmemccpy(id3viewc, pid3->title, MAX_PATH)
551 )
552 track->attr |= PLAYLIST_ATTR_RETRIEVE_ID3_SUCCEEDED;
553 }
554 /* Yield to reduce as much as possible the perceived UI lag,
555 because retrieving id3 tags is an expensive operation */
556 yield();
557 }
558 }
559
560 if (!(track->attr & PLAYLIST_ATTR_RETRIEVE_ID3_SUCCEEDED))
561 {
562 /* Simply use a formatted file name */
563 char name[MAX_PATH];
564 format_name(name, track->name, sizeof(name));
565 if (global_settings.playlist_viewer_indices)
566 /* Display playlist index */
567 snprintf(str, len, "%d. %s%s", track->display_index, skipped, name);
568 else
569 snprintf(str, len, "%s%s", skipped, name);
570 }
474 else 571 else
475 snprintf(str, len, "%s%s", skipped, name); 572 {
476 573 if (!id3viewc)
574 {
575 id3viewc = track->name + strlen(track->name) + 1;
576 }
577 if (global_settings.playlist_viewer_indices)
578 /* Display playlist index */
579 snprintf(str, len, "%d. %s%s", track->display_index, skipped, id3viewc);
580 else
581 snprintf(str, len, "%s%s", skipped, id3viewc);
582 }
477} 583}
478 584
479/* Update playlist in case something has changed or forced */ 585/* Update playlist in case something has changed or forced */
@@ -512,20 +618,7 @@ static bool update_playlist(bool force)
512static enum pv_onplay_result show_track_info(const struct playlist_entry *current_track) 618static enum pv_onplay_result show_track_info(const struct playlist_entry *current_track)
513{ 619{
514 struct mp3entry id3; 620 struct mp3entry id3;
515 bool id3_retrieval_successful = false; 621 bool id3_retrieval_successful = retrieve_id3_tags(current_track->index, current_track->name, &id3, 0);
516
517 if (!viewer.playlist &&
518 (audio_status() & AUDIO_STATUS_PLAY) &&
519 (playlist_get_resume_info(&viewer.current_playing_track) == current_track->index))
520 {
521 copy_mp3entry(&id3, audio_current_track()); /* retrieve id3 from RAM */
522 id3_retrieval_successful = true;
523 }
524 else
525 {
526 /* Read from disk, the database, doesn't store frequency, file size or codec (g4470) ChrisS*/
527 id3_retrieval_successful = get_metadata(&id3, -1, current_track->name);
528 }
529 622
530 return id3_retrieval_successful && 623 return id3_retrieval_successful &&
531 browse_id3_ex(&id3, viewer.playlist, current_track->display_index, 624 browse_id3_ex(&id3, viewer.playlist, current_track->display_index,
@@ -790,11 +883,17 @@ static int playlist_callback_voice(int selected_item, void *data)
790 883
791 switch(global_settings.playlist_viewer_track_display) 884 switch(global_settings.playlist_viewer_track_display)
792 { 885 {
793 case 1: /*full path*/ 886 case PLAYLIST_VIEWER_ENTRY_SHOW_FULL_PATH:
887 /*full path*/
794 talk_fullpath(track->name, true); 888 talk_fullpath(track->name, true);
795 break; 889 break;
796 default: 890 default:
797 case 0: /*filename only*/ 891 case PLAYLIST_VIEWER_ENTRY_SHOW_FILE_NAME:
892 /*filename only*/
893 case PLAYLIST_VIEWER_ENTRY_SHOW_ID3_TITLE_AND_ALBUM:
894 /* If loading from tags failed, only talk the file name */
895 case PLAYLIST_VIEWER_ENTRY_SHOW_ID3_TITLE:
896 /* If loading from tags failed, only talk the file name */
798 talk_file_or_spell(NULL, track->name, NULL, true); 897 talk_file_or_spell(NULL, track->name, NULL, true);
799 break; 898 break;
800 } 899 }
@@ -1107,7 +1206,7 @@ enum playlist_viewer_result playlist_viewer_ex(const char* filename,
1107 } 1206 }
1108 } 1207 }
1109 else 1208 else
1110 onplay(current_track->name, FILE_ATTR_AUDIO, CONTEXT_STD, true); 1209 onplay(current_track->name, FILE_ATTR_AUDIO, CONTEXT_STD, true, ONPLAY_NO_CUSTOMACTION);
1111 break; 1210 break;
1112 } 1211 }
1113#endif /* HAVE_HOTKEY */ 1212#endif /* HAVE_HOTKEY */
@@ -1154,7 +1253,8 @@ static int say_search_item(int selected_item, void *data)
1154{ 1253{
1155 struct playlist_search_data *s_data = data; 1254 struct playlist_search_data *s_data = data;
1156 playlist_get_track_info(viewer.playlist, s_data->found_indicies[selected_item], s_data->track); 1255 playlist_get_track_info(viewer.playlist, s_data->found_indicies[selected_item], s_data->track);
1157 if(global_settings.playlist_viewer_track_display == 1) /* full path*/ 1256 if(global_settings.playlist_viewer_track_display == PLAYLIST_VIEWER_ENTRY_SHOW_FULL_PATH)
1257 /* full path*/
1158 talk_fullpath(s_data->track->filename, false); 1258 talk_fullpath(s_data->track->filename, false);
1159 else talk_file_or_spell(NULL, s_data->track->filename, NULL, false); 1259 else talk_file_or_spell(NULL, s_data->track->filename, NULL, false);
1160 return 0; 1260 return 0;
@@ -1197,7 +1297,8 @@ bool search_playlist(void)
1197 1297
1198 playlist_get_track_info(viewer.playlist, i, &track); 1298 playlist_get_track_info(viewer.playlist, i, &track);
1199 const char *trackname = track.filename; 1299 const char *trackname = track.filename;
1200 if (track_display == 0) /* if we only display filename only search filename */ 1300 if (track_display != PLAYLIST_VIEWER_ENTRY_SHOW_FULL_PATH)
1301 /* if we only display filename only search filename */
1201 trackname = strrchr(track.filename, '/'); 1302 trackname = strrchr(track.filename, '/');
1202 1303
1203 if (trackname && strcasestr(trackname, search_str)) 1304 if (trackname && strcasestr(trackname, search_str))
diff --git a/apps/plugins/lua/lua.make b/apps/plugins/lua/lua.make
index c85182880b..a5d1813a8b 100644
--- a/apps/plugins/lua/lua.make
+++ b/apps/plugins/lua/lua.make
@@ -40,7 +40,7 @@ $(LUA_BUILDDIR)/settings.lua: $(LUA_OBJ) $(LUA_SRCDIR)/settings_helper.pl
40 40
41HOST_INCLUDES := $(filter-out %/libc/include,$(INCLUDES)) 41HOST_INCLUDES := $(filter-out %/libc/include,$(INCLUDES))
42$(LUA_BUILDDIR)/buttons.lua: $(LUA_OBJ) $(LUA_SRCDIR)/button_helper.pl 42$(LUA_BUILDDIR)/buttons.lua: $(LUA_OBJ) $(LUA_SRCDIR)/button_helper.pl
43 $(SILENT)$(CC) $(INCLUDES) $(TARGET) $(CFLAGS) -dM -E -P -include button-target.h - < /dev/null | $(LUA_SRCDIR)/button_helper.pl | $(HOSTCC) $(TARGET) -fno-builtin $(HOST_INCLUDES) -x c -o $(LUA_BUILDDIR)/button_helper - 43 $(SILENT)$(CC) $(INCLUDES) $(TARGET) $(CFLAGS) -dM -E -P -include button-target.h - < /dev/null | $(LUA_SRCDIR)/button_helper.pl | $(HOSTCC) $(TARGET) -fno-builtin $(HOST_INCLUDES) $(EXTRA_DEFINES) -x c -o $(LUA_BUILDDIR)/button_helper -
44 $(call PRINTS,GEN $(@F))$(LUA_BUILDDIR)/button_helper > $(LUA_BUILDDIR)/buttons.lua 44 $(call PRINTS,GEN $(@F))$(LUA_BUILDDIR)/button_helper > $(LUA_BUILDDIR)/buttons.lua
45 45
46$(LUA_BUILDDIR)/rb_defines.lua: $(LUA_OBJ) $(LUA_SRCDIR)/rbdefines_helper.pl 46$(LUA_BUILDDIR)/rb_defines.lua: $(LUA_OBJ) $(LUA_SRCDIR)/rbdefines_helper.pl
diff --git a/apps/plugins/puzzles/SOURCES b/apps/plugins/puzzles/SOURCES
index f0d2ed27f7..dfbd184ba2 100644
--- a/apps/plugins/puzzles/SOURCES
+++ b/apps/plugins/puzzles/SOURCES
@@ -3,6 +3,7 @@ rockbox.c
3rbwrappers.c 3rbwrappers.c
4rbmalloc.c 4rbmalloc.c
5lz4tiny.c 5lz4tiny.c
6dummy/nullhelp.c
6 7
7/* puzzles core sources */ 8/* puzzles core sources */
8src/combi.c 9src/combi.c
diff --git a/apps/plugins/puzzles/SOURCES.games b/apps/plugins/puzzles/SOURCES.games
index 190412295b..70f34f3334 100644
--- a/apps/plugins/puzzles/SOURCES.games
+++ b/apps/plugins/puzzles/SOURCES.games
@@ -35,13 +35,8 @@ src/undead.c
35src/unequal.c 35src/unequal.c
36src/unruly.c 36src/unruly.c
37src/untangle.c 37src/untangle.c
38 38src/unfinished/slide.c
39/* Disabled for now. Fix puzzles.make and CATEGORIES to accomodate these. */ 39src/unfinished/sokoban.c
40/* The help system would also need to be patched to compile these. */
41/*src/unfinished/group.c*/
42/*src/unfinished/separate.c*/
43/*src/unfinished/slide.c*/
44/*src/unfinished/sokoban.c*/
45 40
46/* no c200v2 */ 41/* no c200v2 */
47#if PLUGIN_BUFFER_SIZE > 0x14000 42#if PLUGIN_BUFFER_SIZE > 0x14000
diff --git a/apps/plugins/puzzles/SOURCES.rockbox b/apps/plugins/puzzles/SOURCES.rockbox
index c5bbb9af70..61ce4275ff 100644
--- a/apps/plugins/puzzles/SOURCES.rockbox
+++ b/apps/plugins/puzzles/SOURCES.rockbox
@@ -2,3 +2,4 @@ rockbox.c
2rbwrappers.c 2rbwrappers.c
3rbmalloc.c 3rbmalloc.c
4lz4tiny.c 4lz4tiny.c
5dummy/nullhelp.c
diff --git a/apps/plugins/puzzles/compress.c b/apps/plugins/puzzles/compress.c
index 127a02bf0d..d127a9af17 100644
--- a/apps/plugins/puzzles/compress.c
+++ b/apps/plugins/puzzles/compress.c
@@ -158,6 +158,7 @@ int main()
158 printf("};\n\n"); 158 printf("};\n\n");
159 printf("const unsigned short help_text_len = %d;\n", help_text_len); 159 printf("const unsigned short help_text_len = %d;\n", help_text_len);
160 printf("const unsigned short help_text_words = %d;\n", word_idx); 160 printf("const unsigned short help_text_words = %d;\n", word_idx);
161 printf("const bool help_valid = true;\n");
161 162
162 return 0; 163 return 0;
163} 164}
diff --git a/apps/plugins/puzzles/dummy/nullhelp.c b/apps/plugins/puzzles/dummy/nullhelp.c
new file mode 100644
index 0000000000..79c36c902b
--- /dev/null
+++ b/apps/plugins/puzzles/dummy/nullhelp.c
@@ -0,0 +1,8 @@
1#include "help.h"
2
3const char help_text[] __attribute__((weak)) = "";
4const char quick_help_text[] __attribute__((weak)) = "";
5const unsigned short help_text_len __attribute__((weak)) = 0, quick_help_text_len __attribute__((weak)) = 0, help_text_words __attribute__((weak)) = 0;
6struct style_text help_text_style[] __attribute__((weak)) = {};
7
8const bool help_valid __attribute__((weak)) = false;
diff --git a/apps/plugins/puzzles/help.h b/apps/plugins/puzzles/help.h
index e9cac9b337..2f870393e8 100644
--- a/apps/plugins/puzzles/help.h
+++ b/apps/plugins/puzzles/help.h
@@ -1,3 +1,5 @@
1#include <stdbool.h>
2
1#ifdef ROCKBOX 3#ifdef ROCKBOX
2#include "lib/display_text.h" 4#include "lib/display_text.h"
3#endif 5#endif
@@ -12,3 +14,5 @@ extern const unsigned short help_text_len, quick_help_text_len, help_text_words;
12#if defined(ROCKBOX) 14#if defined(ROCKBOX)
13extern struct style_text help_text_style[]; 15extern struct style_text help_text_style[];
14#endif 16#endif
17
18extern const bool help_valid;
diff --git a/apps/plugins/puzzles/help/blackbox.c b/apps/plugins/puzzles/help/blackbox.c
index c05daf5fd3..90933604c1 100644
--- a/apps/plugins/puzzles/help/blackbox.c
+++ b/apps/plugins/puzzles/help/blackbox.c
@@ -340,4 +340,5 @@ const char help_text[] = {
340 340
341const unsigned short help_text_len = 5480; 341const unsigned short help_text_len = 5480;
342const unsigned short help_text_words = 1016; 342const unsigned short help_text_words = 1016;
343const bool help_valid = true;
343const char quick_help_text[] = "Find the hidden balls in the box by bouncing laser beams off them."; 344const char quick_help_text[] = "Find the hidden balls in the box by bouncing laser beams off them.";
diff --git a/apps/plugins/puzzles/help/bridges.c b/apps/plugins/puzzles/help/bridges.c
index 3e1c91bcc3..4d42314e4b 100644
--- a/apps/plugins/puzzles/help/bridges.c
+++ b/apps/plugins/puzzles/help/bridges.c
@@ -358,4 +358,5 @@ const char help_text[] = {
358 358
359const unsigned short help_text_len = 5613; 359const unsigned short help_text_len = 5613;
360const unsigned short help_text_words = 1026; 360const unsigned short help_text_words = 1026;
361const bool help_valid = true;
361const char quick_help_text[] = "Connect all the islands with a network of bridges."; 362const char quick_help_text[] = "Connect all the islands with a network of bridges.";
diff --git a/apps/plugins/puzzles/help/cube.c b/apps/plugins/puzzles/help/cube.c
index fbdd380d57..0e2fc94081 100644
--- a/apps/plugins/puzzles/help/cube.c
+++ b/apps/plugins/puzzles/help/cube.c
@@ -166,4 +166,5 @@ const char help_text[] = {
166 166
167const unsigned short help_text_len = 2071; 167const unsigned short help_text_len = 2071;
168const unsigned short help_text_words = 386; 168const unsigned short help_text_words = 386;
169const bool help_valid = true;
169const char quick_help_text[] = "Pick up all the blue squares by rolling the cube over them."; 170const char quick_help_text[] = "Pick up all the blue squares by rolling the cube over them.";
diff --git a/apps/plugins/puzzles/help/dominosa.c b/apps/plugins/puzzles/help/dominosa.c
index 98947a6ee6..94d876794b 100644
--- a/apps/plugins/puzzles/help/dominosa.c
+++ b/apps/plugins/puzzles/help/dominosa.c
@@ -176,4 +176,5 @@ const char help_text[] = {
176 176
177const unsigned short help_text_len = 2299; 177const unsigned short help_text_len = 2299;
178const unsigned short help_text_words = 401; 178const unsigned short help_text_words = 401;
179const bool help_valid = true;
179const char quick_help_text[] = "Tile the rectangle with a full set of dominoes."; 180const char quick_help_text[] = "Tile the rectangle with a full set of dominoes.";
diff --git a/apps/plugins/puzzles/help/fifteen.c b/apps/plugins/puzzles/help/fifteen.c
index 7fc434fc3d..32c8cba399 100644
--- a/apps/plugins/puzzles/help/fifteen.c
+++ b/apps/plugins/puzzles/help/fifteen.c
@@ -152,4 +152,5 @@ const char help_text[] = {
152 152
153const unsigned short help_text_len = 1927; 153const unsigned short help_text_len = 1927;
154const unsigned short help_text_words = 353; 154const unsigned short help_text_words = 353;
155const bool help_valid = true;
155const char quick_help_text[] = "Slide the tiles around to arrange them into order."; 156const char quick_help_text[] = "Slide the tiles around to arrange them into order.";
diff --git a/apps/plugins/puzzles/help/filling.c b/apps/plugins/puzzles/help/filling.c
index c0fe6f47e1..785cfce815 100644
--- a/apps/plugins/puzzles/help/filling.c
+++ b/apps/plugins/puzzles/help/filling.c
@@ -142,4 +142,5 @@ const char help_text[] = {
142 142
143const unsigned short help_text_len = 1821; 143const unsigned short help_text_len = 1821;
144const unsigned short help_text_words = 328; 144const unsigned short help_text_words = 328;
145const bool help_valid = true;
145const char quick_help_text[] = "Mark every square with the area of its containing region."; 146const char quick_help_text[] = "Mark every square with the area of its containing region.";
diff --git a/apps/plugins/puzzles/help/flip.c b/apps/plugins/puzzles/help/flip.c
index fd287cb37b..4f847069cc 100644
--- a/apps/plugins/puzzles/help/flip.c
+++ b/apps/plugins/puzzles/help/flip.c
@@ -131,4 +131,5 @@ const char help_text[] = {
131 131
132const unsigned short help_text_len = 1539; 132const unsigned short help_text_len = 1539;
133const unsigned short help_text_words = 299; 133const unsigned short help_text_words = 299;
134const bool help_valid = true;
134const char quick_help_text[] = "Flip groups of squares to light them all up at once."; 135const char quick_help_text[] = "Flip groups of squares to light them all up at once.";
diff --git a/apps/plugins/puzzles/help/flood.c b/apps/plugins/puzzles/help/flood.c
index 28e18a15a9..ad25a5cf34 100644
--- a/apps/plugins/puzzles/help/flood.c
+++ b/apps/plugins/puzzles/help/flood.c
@@ -182,4 +182,5 @@ const char help_text[] = {
182 182
183const unsigned short help_text_len = 2395; 183const unsigned short help_text_len = 2395;
184const unsigned short help_text_words = 452; 184const unsigned short help_text_words = 452;
185const bool help_valid = true;
185const char quick_help_text[] = "Turn the grid the same colour in as few flood fills as possible."; 186const char quick_help_text[] = "Turn the grid the same colour in as few flood fills as possible.";
diff --git a/apps/plugins/puzzles/help/galaxies.c b/apps/plugins/puzzles/help/galaxies.c
index 8482abc14e..10bcda7b3b 100644
--- a/apps/plugins/puzzles/help/galaxies.c
+++ b/apps/plugins/puzzles/help/galaxies.c
@@ -208,4 +208,5 @@ const char help_text[] = {
208 208
209const unsigned short help_text_len = 2766; 209const unsigned short help_text_len = 2766;
210const unsigned short help_text_words = 498; 210const unsigned short help_text_words = 498;
211const bool help_valid = true;
211const char quick_help_text[] = "Divide the grid into rotationally symmetric regions each centred on a dot."; 212const char quick_help_text[] = "Divide the grid into rotationally symmetric regions each centred on a dot.";
diff --git a/apps/plugins/puzzles/help/guess.c b/apps/plugins/puzzles/help/guess.c
index 594f5910cb..3c77344424 100644
--- a/apps/plugins/puzzles/help/guess.c
+++ b/apps/plugins/puzzles/help/guess.c
@@ -238,4 +238,5 @@ const char help_text[] = {
238 238
239const unsigned short help_text_len = 3506; 239const unsigned short help_text_len = 3506;
240const unsigned short help_text_words = 650; 240const unsigned short help_text_words = 650;
241const bool help_valid = true;
241const char quick_help_text[] = "Guess the hidden combination of colours."; 242const char quick_help_text[] = "Guess the hidden combination of colours.";
diff --git a/apps/plugins/puzzles/help/inertia.c b/apps/plugins/puzzles/help/inertia.c
index 2755cc2eb6..a0f33a9dc3 100644
--- a/apps/plugins/puzzles/help/inertia.c
+++ b/apps/plugins/puzzles/help/inertia.c
@@ -179,4 +179,5 @@ const char help_text[] = {
179 179
180const unsigned short help_text_len = 2286; 180const unsigned short help_text_len = 2286;
181const unsigned short help_text_words = 431; 181const unsigned short help_text_words = 431;
182const bool help_valid = true;
182const char quick_help_text[] = "Collect all the gems without running into any of the mines."; 183const char quick_help_text[] = "Collect all the gems without running into any of the mines.";
diff --git a/apps/plugins/puzzles/help/keen.c b/apps/plugins/puzzles/help/keen.c
index bcf5b38a1f..0c69aea0d6 100644
--- a/apps/plugins/puzzles/help/keen.c
+++ b/apps/plugins/puzzles/help/keen.c
@@ -260,4 +260,5 @@ const char help_text[] = {
260 260
261const unsigned short help_text_len = 3969; 261const unsigned short help_text_len = 3969;
262const unsigned short help_text_words = 762; 262const unsigned short help_text_words = 762;
263const bool help_valid = true;
263const char quick_help_text[] = "Complete the latin square in accordance with the arithmetic clues."; 264const char quick_help_text[] = "Complete the latin square in accordance with the arithmetic clues.";
diff --git a/apps/plugins/puzzles/help/lightup.c b/apps/plugins/puzzles/help/lightup.c
index c3ddd209fa..c109773bf6 100644
--- a/apps/plugins/puzzles/help/lightup.c
+++ b/apps/plugins/puzzles/help/lightup.c
@@ -191,4 +191,5 @@ const char help_text[] = {
191 191
192const unsigned short help_text_len = 2549; 192const unsigned short help_text_len = 2549;
193const unsigned short help_text_words = 468; 193const unsigned short help_text_words = 468;
194const bool help_valid = true;
194const char quick_help_text[] = "Place bulbs to light up all the squares."; 195const char quick_help_text[] = "Place bulbs to light up all the squares.";
diff --git a/apps/plugins/puzzles/help/loopy.c b/apps/plugins/puzzles/help/loopy.c
index f65d2d2793..76c441511c 100644
--- a/apps/plugins/puzzles/help/loopy.c
+++ b/apps/plugins/puzzles/help/loopy.c
@@ -264,4 +264,5 @@ const char help_text[] = {
264 264
265const unsigned short help_text_len = 3584; 265const unsigned short help_text_len = 3584;
266const unsigned short help_text_words = 660; 266const unsigned short help_text_words = 660;
267const bool help_valid = true;
267const char quick_help_text[] = "Draw a single closed loop, given clues about number of adjacent edges."; 268const char quick_help_text[] = "Draw a single closed loop, given clues about number of adjacent edges.";
diff --git a/apps/plugins/puzzles/help/magnets.c b/apps/plugins/puzzles/help/magnets.c
index 9a54586203..aaaa3bcdad 100644
--- a/apps/plugins/puzzles/help/magnets.c
+++ b/apps/plugins/puzzles/help/magnets.c
@@ -190,4 +190,5 @@ const char help_text[] = {
190 190
191const unsigned short help_text_len = 2522; 191const unsigned short help_text_len = 2522;
192const unsigned short help_text_words = 439; 192const unsigned short help_text_words = 439;
193const bool help_valid = true;
193const char quick_help_text[] = "Place magnets to satisfy the clues and avoid like poles touching."; 194const char quick_help_text[] = "Place magnets to satisfy the clues and avoid like poles touching.";
diff --git a/apps/plugins/puzzles/help/map.c b/apps/plugins/puzzles/help/map.c
index 3532ecebbf..05474e3181 100644
--- a/apps/plugins/puzzles/help/map.c
+++ b/apps/plugins/puzzles/help/map.c
@@ -271,4 +271,5 @@ const char help_text[] = {
271 271
272const unsigned short help_text_len = 3752; 272const unsigned short help_text_len = 3752;
273const unsigned short help_text_words = 686; 273const unsigned short help_text_words = 686;
274const bool help_valid = true;
274const char quick_help_text[] = "Colour the map so that adjacent regions are never the same colour."; 275const char quick_help_text[] = "Colour the map so that adjacent regions are never the same colour.";
diff --git a/apps/plugins/puzzles/help/mines.c b/apps/plugins/puzzles/help/mines.c
index 458034ccaa..80cd81ed3f 100644
--- a/apps/plugins/puzzles/help/mines.c
+++ b/apps/plugins/puzzles/help/mines.c
@@ -260,4 +260,5 @@ const char help_text[] = {
260 260
261const unsigned short help_text_len = 3814; 261const unsigned short help_text_len = 3814;
262const unsigned short help_text_words = 732; 262const unsigned short help_text_words = 732;
263const bool help_valid = true;
263const char quick_help_text[] = "Find all the mines without treading on any of them."; 264const char quick_help_text[] = "Find all the mines without treading on any of them.";
diff --git a/apps/plugins/puzzles/help/mosaic.c b/apps/plugins/puzzles/help/mosaic.c
index 9a7d2dd394..eb0c52c0bf 100644
--- a/apps/plugins/puzzles/help/mosaic.c
+++ b/apps/plugins/puzzles/help/mosaic.c
@@ -147,4 +147,5 @@ const char help_text[] = {
147 147
148const unsigned short help_text_len = 1673; 148const unsigned short help_text_len = 1673;
149const unsigned short help_text_words = 285; 149const unsigned short help_text_words = 285;
150const bool help_valid = true;
150const char quick_help_text[] = "Fill in the grid given clues about number of nearby black squares."; 151const char quick_help_text[] = "Fill in the grid given clues about number of nearby black squares.";
diff --git a/apps/plugins/puzzles/help/net.c b/apps/plugins/puzzles/help/net.c
index 83cd785ed2..de528b53bc 100644
--- a/apps/plugins/puzzles/help/net.c
+++ b/apps/plugins/puzzles/help/net.c
@@ -297,4 +297,5 @@ const char help_text[] = {
297 297
298const unsigned short help_text_len = 3919; 298const unsigned short help_text_len = 3919;
299const unsigned short help_text_words = 689; 299const unsigned short help_text_words = 689;
300const bool help_valid = true;
300const char quick_help_text[] = "Rotate each tile to reassemble the network."; 301const char quick_help_text[] = "Rotate each tile to reassemble the network.";
diff --git a/apps/plugins/puzzles/help/netslide.c b/apps/plugins/puzzles/help/netslide.c
index f4067a2303..17ec9ec440 100644
--- a/apps/plugins/puzzles/help/netslide.c
+++ b/apps/plugins/puzzles/help/netslide.c
@@ -58,4 +58,5 @@ const char help_text[] = {
58 58
59const unsigned short help_text_len = 546; 59const unsigned short help_text_len = 546;
60const unsigned short help_text_words = 99; 60const unsigned short help_text_words = 99;
61const bool help_valid = true;
61const char quick_help_text[] = "Slide a row at a time to reassemble the network."; 62const char quick_help_text[] = "Slide a row at a time to reassemble the network.";
diff --git a/apps/plugins/puzzles/help/palisade.c b/apps/plugins/puzzles/help/palisade.c
index d1ee5e7354..36d3c65248 100644
--- a/apps/plugins/puzzles/help/palisade.c
+++ b/apps/plugins/puzzles/help/palisade.c
@@ -140,4 +140,5 @@ const char help_text[] = {
140 140
141const unsigned short help_text_len = 1672; 141const unsigned short help_text_len = 1672;
142const unsigned short help_text_words = 285; 142const unsigned short help_text_words = 285;
143const bool help_valid = true;
143const char quick_help_text[] = "Divide the grid into equal-sized areas in accordance with the clues."; 144const char quick_help_text[] = "Divide the grid into equal-sized areas in accordance with the clues.";
diff --git a/apps/plugins/puzzles/help/pattern.c b/apps/plugins/puzzles/help/pattern.c
index a0e4edc579..fae35c0f64 100644
--- a/apps/plugins/puzzles/help/pattern.c
+++ b/apps/plugins/puzzles/help/pattern.c
@@ -168,4 +168,5 @@ const char help_text[] = {
168 168
169const unsigned short help_text_len = 2167; 169const unsigned short help_text_len = 2167;
170const unsigned short help_text_words = 389; 170const unsigned short help_text_words = 389;
171const bool help_valid = true;
171const char quick_help_text[] = "Fill in the pattern in the grid, given only the lengths of runs of black squares."; 172const char quick_help_text[] = "Fill in the pattern in the grid, given only the lengths of runs of black squares.";
diff --git a/apps/plugins/puzzles/help/pearl.c b/apps/plugins/puzzles/help/pearl.c
index efb3cd0d5a..033aca17fa 100644
--- a/apps/plugins/puzzles/help/pearl.c
+++ b/apps/plugins/puzzles/help/pearl.c
@@ -249,4 +249,5 @@ const char help_text[] = {
249 249
250const unsigned short help_text_len = 3598; 250const unsigned short help_text_len = 3598;
251const unsigned short help_text_words = 659; 251const unsigned short help_text_words = 659;
252const bool help_valid = true;
252const char quick_help_text[] = "Draw a single closed loop, given clues about corner and straight squares."; 253const char quick_help_text[] = "Draw a single closed loop, given clues about corner and straight squares.";
diff --git a/apps/plugins/puzzles/help/pegs.c b/apps/plugins/puzzles/help/pegs.c
index 8375f87bcf..32552a87fd 100644
--- a/apps/plugins/puzzles/help/pegs.c
+++ b/apps/plugins/puzzles/help/pegs.c
@@ -148,4 +148,5 @@ const char help_text[] = {
148 148
149const unsigned short help_text_len = 1734; 149const unsigned short help_text_len = 1734;
150const unsigned short help_text_words = 326; 150const unsigned short help_text_words = 326;
151const bool help_valid = true;
151const char quick_help_text[] = "Jump pegs over each other to remove all but one."; 152const char quick_help_text[] = "Jump pegs over each other to remove all but one.";
diff --git a/apps/plugins/puzzles/help/range.c b/apps/plugins/puzzles/help/range.c
index d5035ef8d2..65495dc3c1 100644
--- a/apps/plugins/puzzles/help/range.c
+++ b/apps/plugins/puzzles/help/range.c
@@ -170,4 +170,5 @@ const char help_text[] = {
170 170
171const unsigned short help_text_len = 2223; 171const unsigned short help_text_len = 2223;
172const unsigned short help_text_words = 395; 172const unsigned short help_text_words = 395;
173const bool help_valid = true;
173const char quick_help_text[] = "Place black squares to limit the visible distance from each numbered cell."; 174const char quick_help_text[] = "Place black squares to limit the visible distance from each numbered cell.";
diff --git a/apps/plugins/puzzles/help/rect.c b/apps/plugins/puzzles/help/rect.c
index bf2197aa47..dfd597eb0f 100644
--- a/apps/plugins/puzzles/help/rect.c
+++ b/apps/plugins/puzzles/help/rect.c
@@ -258,4 +258,5 @@ const char help_text[] = {
258 258
259const unsigned short help_text_len = 3536; 259const unsigned short help_text_len = 3536;
260const unsigned short help_text_words = 603; 260const unsigned short help_text_words = 603;
261const bool help_valid = true;
261const char quick_help_text[] = "Divide the grid into rectangles with areas equal to the numbers."; 262const char quick_help_text[] = "Divide the grid into rectangles with areas equal to the numbers.";
diff --git a/apps/plugins/puzzles/help/samegame.c b/apps/plugins/puzzles/help/samegame.c
index 3c632fca2b..62589b4f11 100644
--- a/apps/plugins/puzzles/help/samegame.c
+++ b/apps/plugins/puzzles/help/samegame.c
@@ -188,4 +188,5 @@ const char help_text[] = {
188 188
189const unsigned short help_text_len = 2492; 189const unsigned short help_text_len = 2492;
190const unsigned short help_text_words = 445; 190const unsigned short help_text_words = 445;
191const bool help_valid = true;
191const char quick_help_text[] = "Clear the grid by removing touching groups of the same colour squares."; 192const char quick_help_text[] = "Clear the grid by removing touching groups of the same colour squares.";
diff --git a/apps/plugins/puzzles/help/signpost.c b/apps/plugins/puzzles/help/signpost.c
index 753a202f3e..34e043da57 100644
--- a/apps/plugins/puzzles/help/signpost.c
+++ b/apps/plugins/puzzles/help/signpost.c
@@ -222,4 +222,5 @@ const char help_text[] = {
222 222
223const unsigned short help_text_len = 3255; 223const unsigned short help_text_len = 3255;
224const unsigned short help_text_words = 595; 224const unsigned short help_text_words = 595;
225const bool help_valid = true;
225const char quick_help_text[] = "Connect the squares into a path following the arrows."; 226const char quick_help_text[] = "Connect the squares into a path following the arrows.";
diff --git a/apps/plugins/puzzles/help/singles.c b/apps/plugins/puzzles/help/singles.c
index a906addeb6..9a1771e3c7 100644
--- a/apps/plugins/puzzles/help/singles.c
+++ b/apps/plugins/puzzles/help/singles.c
@@ -149,4 +149,5 @@ const char help_text[] = {
149 149
150const unsigned short help_text_len = 1780; 150const unsigned short help_text_len = 1780;
151const unsigned short help_text_words = 309; 151const unsigned short help_text_words = 309;
152const bool help_valid = true;
152const char quick_help_text[] = "Black out the right set of duplicate numbers."; 153const char quick_help_text[] = "Black out the right set of duplicate numbers.";
diff --git a/apps/plugins/puzzles/help/sixteen.c b/apps/plugins/puzzles/help/sixteen.c
index 92e64e29bb..1ba92291f3 100644
--- a/apps/plugins/puzzles/help/sixteen.c
+++ b/apps/plugins/puzzles/help/sixteen.c
@@ -197,4 +197,5 @@ const char help_text[] = {
197 197
198const unsigned short help_text_len = 2553; 198const unsigned short help_text_len = 2553;
199const unsigned short help_text_words = 454; 199const unsigned short help_text_words = 454;
200const bool help_valid = true;
200const char quick_help_text[] = "Slide a row at a time to arrange the tiles into order."; 201const char quick_help_text[] = "Slide a row at a time to arrange the tiles into order.";
diff --git a/apps/plugins/puzzles/help/slant.c b/apps/plugins/puzzles/help/slant.c
index f51b141827..7c24a1bac3 100644
--- a/apps/plugins/puzzles/help/slant.c
+++ b/apps/plugins/puzzles/help/slant.c
@@ -199,4 +199,5 @@ const char help_text[] = {
199 199
200const unsigned short help_text_len = 2582; 200const unsigned short help_text_len = 2582;
201const unsigned short help_text_words = 474; 201const unsigned short help_text_words = 474;
202const bool help_valid = true;
202const char quick_help_text[] = "Draw a maze of slanting lines that matches the clues."; 203const char quick_help_text[] = "Draw a maze of slanting lines that matches the clues.";
diff --git a/apps/plugins/puzzles/help/solo.c b/apps/plugins/puzzles/help/solo.c
index cc3d96cf84..68927490e6 100644
--- a/apps/plugins/puzzles/help/solo.c
+++ b/apps/plugins/puzzles/help/solo.c
@@ -383,4 +383,5 @@ const char help_text[] = {
383 383
384const unsigned short help_text_len = 6259; 384const unsigned short help_text_len = 6259;
385const unsigned short help_text_words = 1153; 385const unsigned short help_text_words = 1153;
386const bool help_valid = true;
386const char quick_help_text[] = "Fill in the grid so that each row, column and square block contains one of every digit."; 387const char quick_help_text[] = "Fill in the grid so that each row, column and square block contains one of every digit.";
diff --git a/apps/plugins/puzzles/help/tents.c b/apps/plugins/puzzles/help/tents.c
index c486960ec2..8b14490a51 100644
--- a/apps/plugins/puzzles/help/tents.c
+++ b/apps/plugins/puzzles/help/tents.c
@@ -163,4 +163,5 @@ const char help_text[] = {
163 163
164const unsigned short help_text_len = 2158; 164const unsigned short help_text_len = 2158;
165const unsigned short help_text_words = 401; 165const unsigned short help_text_words = 401;
166const bool help_valid = true;
166const char quick_help_text[] = "Place a tent next to each tree."; 167const char quick_help_text[] = "Place a tent next to each tree.";
diff --git a/apps/plugins/puzzles/help/towers.c b/apps/plugins/puzzles/help/towers.c
index 43f1cef5ea..ff5a0a495a 100644
--- a/apps/plugins/puzzles/help/towers.c
+++ b/apps/plugins/puzzles/help/towers.c
@@ -263,4 +263,5 @@ const char help_text[] = {
263 263
264const unsigned short help_text_len = 3906; 264const unsigned short help_text_len = 3906;
265const unsigned short help_text_words = 732; 265const unsigned short help_text_words = 732;
266const bool help_valid = true;
266const char quick_help_text[] = "Complete the latin square of towers in accordance with the clues."; 267const char quick_help_text[] = "Complete the latin square of towers in accordance with the clues.";
diff --git a/apps/plugins/puzzles/help/tracks.c b/apps/plugins/puzzles/help/tracks.c
index c088145a09..f3677352b5 100644
--- a/apps/plugins/puzzles/help/tracks.c
+++ b/apps/plugins/puzzles/help/tracks.c
@@ -150,4 +150,5 @@ const char help_text[] = {
150 150
151const unsigned short help_text_len = 1881; 151const unsigned short help_text_len = 1881;
152const unsigned short help_text_words = 337; 152const unsigned short help_text_words = 337;
153const bool help_valid = true;
153const char quick_help_text[] = "Fill in the railway track according to the clues."; 154const char quick_help_text[] = "Fill in the railway track according to the clues.";
diff --git a/apps/plugins/puzzles/help/twiddle.c b/apps/plugins/puzzles/help/twiddle.c
index bea3e25ab8..a07ba931d4 100644
--- a/apps/plugins/puzzles/help/twiddle.c
+++ b/apps/plugins/puzzles/help/twiddle.c
@@ -205,4 +205,5 @@ const char help_text[] = {
205 205
206const unsigned short help_text_len = 2945; 206const unsigned short help_text_len = 2945;
207const unsigned short help_text_words = 549; 207const unsigned short help_text_words = 549;
208const bool help_valid = true;
208const char quick_help_text[] = "Rotate the tiles around themselves to arrange them into order."; 209const char quick_help_text[] = "Rotate the tiles around themselves to arrange them into order.";
diff --git a/apps/plugins/puzzles/help/undead.c b/apps/plugins/puzzles/help/undead.c
index 78ecdf386c..dc75e88743 100644
--- a/apps/plugins/puzzles/help/undead.c
+++ b/apps/plugins/puzzles/help/undead.c
@@ -248,4 +248,5 @@ const char help_text[] = {
248 248
249const unsigned short help_text_len = 3574; 249const unsigned short help_text_len = 3574;
250const unsigned short help_text_words = 660; 250const unsigned short help_text_words = 660;
251const bool help_valid = true;
251const char quick_help_text[] = "Place ghosts, vampires and zombies so that the right numbers of them can be seen in mirrors."; 252const char quick_help_text[] = "Place ghosts, vampires and zombies so that the right numbers of them can be seen in mirrors.";
diff --git a/apps/plugins/puzzles/help/unequal.c b/apps/plugins/puzzles/help/unequal.c
index 563f9d113b..6bccf6bef3 100644
--- a/apps/plugins/puzzles/help/unequal.c
+++ b/apps/plugins/puzzles/help/unequal.c
@@ -257,4 +257,5 @@ const char help_text[] = {
257 257
258const unsigned short help_text_len = 3954; 258const unsigned short help_text_len = 3954;
259const unsigned short help_text_words = 731; 259const unsigned short help_text_words = 731;
260const bool help_valid = true;
260const char quick_help_text[] = "Complete the latin square in accordance with the > signs."; 261const char quick_help_text[] = "Complete the latin square in accordance with the > signs.";
diff --git a/apps/plugins/puzzles/help/unruly.c b/apps/plugins/puzzles/help/unruly.c
index dafb3274db..97348e5c26 100644
--- a/apps/plugins/puzzles/help/unruly.c
+++ b/apps/plugins/puzzles/help/unruly.c
@@ -145,4 +145,5 @@ const char help_text[] = {
145 145
146const unsigned short help_text_len = 1707; 146const unsigned short help_text_len = 1707;
147const unsigned short help_text_words = 306; 147const unsigned short help_text_words = 306;
148const bool help_valid = true;
148const char quick_help_text[] = "Fill in the black and white grid to avoid runs of three."; 149const char quick_help_text[] = "Fill in the black and white grid to avoid runs of three.";
diff --git a/apps/plugins/puzzles/help/untangle.c b/apps/plugins/puzzles/help/untangle.c
index cdf9b96d25..88b6e39d5a 100644
--- a/apps/plugins/puzzles/help/untangle.c
+++ b/apps/plugins/puzzles/help/untangle.c
@@ -97,4 +97,5 @@ const char help_text[] = {
97 97
98const unsigned short help_text_len = 974; 98const unsigned short help_text_len = 974;
99const unsigned short help_text_words = 174; 99const unsigned short help_text_words = 174;
100const bool help_valid = true;
100const char quick_help_text[] = "Reposition the points so that the lines do not cross."; 101const char quick_help_text[] = "Reposition the points so that the lines do not cross.";
diff --git a/apps/plugins/puzzles/puzzles.make b/apps/plugins/puzzles/puzzles.make
index 604208cbdd..8c5bc1de40 100644
--- a/apps/plugins/puzzles/puzzles.make
+++ b/apps/plugins/puzzles/puzzles.make
@@ -25,6 +25,8 @@ PUZZLES_OBJ = $(call c2obj, $(PUZZLES_SRC))
25PUZZLES_ROCKS = $(addprefix $(PUZZLES_OBJDIR)/sgt-, $(notdir $(PUZZLES_GAMES_SRC:.c=.rock))) 25PUZZLES_ROCKS = $(addprefix $(PUZZLES_OBJDIR)/sgt-, $(notdir $(PUZZLES_GAMES_SRC:.c=.rock)))
26 26
27OTHER_SRC += $(PUZZLES_SRC) 27OTHER_SRC += $(PUZZLES_SRC)
28OTHER_INC += -I$(PUZZLES_SRCDIR)/src -I $(PUZZLES_SRCDIR)
29
28ROCKS += $(PUZZLES_ROCKS) 30ROCKS += $(PUZZLES_ROCKS)
29 31
30PUZZLES_OPTIMIZE = -O2 32PUZZLES_OPTIMIZE = -O2
@@ -49,6 +51,13 @@ $(PUZZLES_OBJDIR)/sgt-%.rock: $(PUZZLES_OBJDIR)/src/%.o $(PUZZLES_OBJDIR)/help/%
49 -lgcc $(filter-out -Wl%.map, $(PLUGINLDFLAGS)) -Wl,-Map,$(PUZZLES_OBJDIR)/src/$*.map 51 -lgcc $(filter-out -Wl%.map, $(PLUGINLDFLAGS)) -Wl,-Map,$(PUZZLES_OBJDIR)/src/$*.map
50 $(SILENT)$(call objcopy,$(PUZZLES_OBJDIR)/$*.elf,$@) 52 $(SILENT)$(call objcopy,$(PUZZLES_OBJDIR)/$*.elf,$@)
51 53
54$(PUZZLES_OBJDIR)/sgt-%.rock: $(PUZZLES_OBJDIR)/src/unfinished/%.o $(PUZZLES_SHARED_OBJ) $(TLSFLIB)
55 $(call PRINTS,LD $(@F))$(CC) $(PLUGINFLAGS) -o $(PUZZLES_OBJDIR)/$*.elf \
56 $(filter %.o, $^) \
57 $(filter %.a, $+) \
58 -lgcc $(filter-out -Wl%.map, $(PLUGINLDFLAGS)) -Wl,-Map,$(PUZZLES_OBJDIR)/src/$*.map
59 $(SILENT)$(call objcopy,$(PUZZLES_OBJDIR)/$*.elf,$@)
60
52$(PUZZLES_SRCDIR)/rbcompat.h: $(APPSDIR)/plugin.h \ 61$(PUZZLES_SRCDIR)/rbcompat.h: $(APPSDIR)/plugin.h \
53 $(APPSDIR)/plugins/lib/pluginlib_exit.h \ 62 $(APPSDIR)/plugins/lib/pluginlib_exit.h \
54 $(BUILDDIR)/sysfont.h \ 63 $(BUILDDIR)/sysfont.h \
diff --git a/apps/plugins/puzzles/resync.sh b/apps/plugins/puzzles/resync.sh
index 7c2df45c7e..3431a6f695 100755
--- a/apps/plugins/puzzles/resync.sh
+++ b/apps/plugins/puzzles/resync.sh
@@ -30,8 +30,8 @@ then
30 echo "[1/5] Removing current src/ directory" 30 echo "[1/5] Removing current src/ directory"
31 rm -rf src 31 rm -rf src
32 echo "[2/5] Copying new sources" 32 echo "[2/5] Copying new sources"
33 mkdir src 33 mkdir -p src/unfinished
34 cp -r "$1"/{*.h,puzzles.but,LICENCE,README,CMakeLists.txt} src 34 cp -r "$1"/{*.h,puzzles.but,LICENCE,README,CMakeLists.txt,unfinished} src
35 35
36 # Parse out definitions of core, core_obj, and common from 36 # Parse out definitions of core, core_obj, and common from
37 # CMakeLists. Extract the .c filenames, except malloc.c, and store 37 # CMakeLists. Extract the .c filenames, except malloc.c, and store
@@ -46,17 +46,12 @@ then
46 SRC="$(cat SOURCES.games SOURCES.core | sed 's/src\///' | tr '\n' ' ' | head -c-1) loopy.c pearl.c solo.c" 46 SRC="$(cat SOURCES.games SOURCES.core | sed 's/src\///' | tr '\n' ' ' | head -c-1) loopy.c pearl.c solo.c"
47 echo "Detected sources:" $SRC 47 echo "Detected sources:" $SRC
48 pushd "$1" > /dev/null 48 pushd "$1" > /dev/null
49 cp $SRC "$ROOT"/src 49 cp -r $SRC "$ROOT"/src
50 popd > /dev/null 50 popd > /dev/null
51 51
52 cat <<EOF >> SOURCES.games 52 cat src/unfinished/CMakeLists.txt | awk '/puzzle\(/{p=1} p{print} /\)/{p=0}' | grep -Eo "\(.*$" | tr -dc "a-z\n" | awk '{print "src/unfinished/"$0".c"}' | grep -v "group" | grep -v "separate" >> SOURCES.games
53 53
54/* Disabled for now. Fix puzzles.make and CATEGORIES to accomodate these. */ 54 cat <<EOF >> SOURCES.games
55/* The help system would also need to be patched to compile these. */
56/*src/unfinished/group.c*/
57/*src/unfinished/separate.c*/
58/*src/unfinished/slide.c*/
59/*src/unfinished/sokoban.c*/
60 55
61/* no c200v2 */ 56/* no c200v2 */
62#if PLUGIN_BUFFER_SIZE > 0x14000 57#if PLUGIN_BUFFER_SIZE > 0x14000
diff --git a/apps/plugins/puzzles/rockbox.c b/apps/plugins/puzzles/rockbox.c
index 27005a447d..27060208fc 100644
--- a/apps/plugins/puzzles/rockbox.c
+++ b/apps/plugins/puzzles/rockbox.c
@@ -322,6 +322,7 @@ static struct viewport clip_rect;
322static bool clipped = false, zoom_enabled = false, view_mode = true, mouse_mode = false; 322static bool clipped = false, zoom_enabled = false, view_mode = true, mouse_mode = false;
323 323
324static int mouse_x, mouse_y; 324static int mouse_x, mouse_y;
325static bool mouse_dragging = false; /* for sticky mode only */
325 326
326extern bool audiobuf_available; /* defined in rbmalloc.c */ 327extern bool audiobuf_available; /* defined in rbmalloc.c */
327 328
@@ -346,6 +347,7 @@ static struct {
346 bool ignore_repeats; /* ignore repeated button events (currently in all games but Untangle) */ 347 bool ignore_repeats; /* ignore repeated button events (currently in all games but Untangle) */
347 bool rclick_on_hold; /* if in mouse mode, send right-click on long-press of select */ 348 bool rclick_on_hold; /* if in mouse mode, send right-click on long-press of select */
348 bool numerical_chooser; /* repurpose select to activate a numerical chooser */ 349 bool numerical_chooser; /* repurpose select to activate a numerical chooser */
350 bool sticky_mouse; /* if mouse left button should be persistent and toggled on/off */
349} input_settings; 351} input_settings;
350 352
351static bool accept_input = true; 353static bool accept_input = true;
@@ -748,7 +750,8 @@ static void rb_color(int n)
748 fatal("bad color %d", n); 750 fatal("bad color %d", n);
749 return; 751 return;
750 } 752 }
751 rb->lcd_set_foreground(colors[n]); 753 if(colors)
754 rb->lcd_set_foreground(colors[n]);
752} 755}
753 756
754/* clipping is implemented through viewports and offsetting 757/* clipping is implemented through viewports and offsetting
@@ -1284,7 +1287,8 @@ static void draw_title(bool clear_first)
1284 rb->lcd_setfont(cur_font = FONT_UI); 1287 rb->lcd_setfont(cur_font = FONT_UI);
1285 rb->lcd_getstringsize(str, &w, &h); 1288 rb->lcd_getstringsize(str, &w, &h);
1286 1289
1287 rb->lcd_set_foreground(BG_COLOR); 1290
1291 rb->lcd_set_foreground(colors ? colors[0] : BG_COLOR);
1288 rb->lcd_fillrect(0, LCD_HEIGHT - h, clear_first ? LCD_WIDTH : w, h); 1292 rb->lcd_fillrect(0, LCD_HEIGHT - h, clear_first ? LCD_WIDTH : w, h);
1289 1293
1290 rb->lcd_set_drawmode(DRMODE_FG); 1294 rb->lcd_set_drawmode(DRMODE_FG);
@@ -1682,9 +1686,17 @@ static int process_input(int tmo, bool do_pausemenu)
1682 LOGF("sending left click"); 1686 LOGF("sending left click");
1683 send_click(LEFT_BUTTON, true); /* right-click is handled earlier */ 1687 send_click(LEFT_BUTTON, true); /* right-click is handled earlier */
1684 } 1688 }
1685 } 1689 } else if(input_settings.sticky_mouse) {
1686 else 1690 if(pressed & BTN_FIRE) {
1687 { 1691 send_click(LEFT_BUTTON, false);
1692 accept_input = false;
1693 mouse_dragging = !mouse_dragging;
1694 } else if(mouse_dragging) {
1695 send_click(LEFT_DRAG, false);
1696 } else {
1697 send_click(LEFT_RELEASE, false);
1698 }
1699 } else {
1688 if(pressed & BTN_FIRE) { 1700 if(pressed & BTN_FIRE) {
1689 send_click(LEFT_BUTTON, false); 1701 send_click(LEFT_BUTTON, false);
1690 accept_input = false; 1702 accept_input = false;
@@ -2482,6 +2494,7 @@ static bool presets_menu(void)
2482 2494
2483static void quick_help(void) 2495static void quick_help(void)
2484{ 2496{
2497#ifndef NO_HELP_TEXT
2485#if defined(FOR_REAL) && defined(DEBUG_MENU) 2498#if defined(FOR_REAL) && defined(DEBUG_MENU)
2486 if(++help_times >= 5) 2499 if(++help_times >= 5)
2487 { 2500 {
@@ -2492,11 +2505,12 @@ static void quick_help(void)
2492 2505
2493 rb->splash(0, quick_help_text); 2506 rb->splash(0, quick_help_text);
2494 rb->button_get(true); 2507 rb->button_get(true);
2495 return; 2508#endif
2496} 2509}
2497 2510
2498static void full_help(const char *name) 2511static void full_help(const char *name)
2499{ 2512{
2513#ifndef NO_HELP_TEXT
2500 unsigned old_bg = rb->lcd_get_background(); 2514 unsigned old_bg = rb->lcd_get_background();
2501 2515
2502 bool orig_clipped = clipped; 2516 bool orig_clipped = clipped;
@@ -2551,6 +2565,7 @@ static void full_help(const char *name)
2551 2565
2552 if(orig_clipped) 2566 if(orig_clipped)
2553 rb_clip(NULL, clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height); 2567 rb_clip(NULL, clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height);
2568#endif
2554} 2569}
2555 2570
2556static void init_default_settings(void) 2571static void init_default_settings(void)
@@ -2701,6 +2716,11 @@ static int pausemenu_cb(int action,
2701 if(!midend_which_game(me)->can_solve) 2716 if(!midend_which_game(me)->can_solve)
2702 return ACTION_EXIT_MENUITEM; 2717 return ACTION_EXIT_MENUITEM;
2703 break; 2718 break;
2719 case 7:
2720 case 8:
2721 if(!help_valid)
2722 return ACTION_EXIT_MENUITEM;
2723 break;
2704 case 9: 2724 case 9:
2705 if(audiobuf_available) 2725 if(audiobuf_available)
2706 break; 2726 break;
@@ -2751,7 +2771,7 @@ static void reset_drawing(void)
2751 rb->lcd_set_viewport(NULL); 2771 rb->lcd_set_viewport(NULL);
2752 rb->lcd_set_backdrop(NULL); 2772 rb->lcd_set_backdrop(NULL);
2753 rb->lcd_set_foreground(LCD_BLACK); 2773 rb->lcd_set_foreground(LCD_BLACK);
2754 rb->lcd_set_background(BG_COLOR); 2774 rb->lcd_set_background(colors ? colors[0] : BG_COLOR);
2755} 2775}
2756 2776
2757/* Make a new game, but tell the user through a splash so they don't 2777/* Make a new game, but tell the user through a splash so they don't
@@ -2876,7 +2896,7 @@ static int pause_menu(void)
2876 break; 2896 break;
2877 } 2897 }
2878 } 2898 }
2879 rb->lcd_set_background(BG_COLOR); 2899 rb->lcd_set_background(colors ? colors[0] : BG_COLOR);
2880 rb->lcd_clear_display(); 2900 rb->lcd_clear_display();
2881 midend_force_redraw(me); 2901 midend_force_redraw(me);
2882 rb->lcd_update(); 2902 rb->lcd_update();
@@ -2923,6 +2943,7 @@ static void init_colors(void)
2923 float *floatcolors = midend_colors(me, &ncolors); 2943 float *floatcolors = midend_colors(me, &ncolors);
2924 2944
2925 /* convert them to packed RGB */ 2945 /* convert them to packed RGB */
2946 sfree(colors);
2926 colors = smalloc(ncolors * sizeof(unsigned)); 2947 colors = smalloc(ncolors * sizeof(unsigned));
2927 unsigned *ptr = colors; 2948 unsigned *ptr = colors;
2928 float *floatptr = floatcolors; 2949 float *floatptr = floatcolors;
@@ -3007,6 +3028,7 @@ static void tune_input(const char *name)
3007 static const char *no_rclick_on_hold[] = { 3028 static const char *no_rclick_on_hold[] = {
3008 "Map", 3029 "Map",
3009 "Signpost", 3030 "Signpost",
3031 "Slide",
3010 "Untangle", 3032 "Untangle",
3011 NULL 3033 NULL
3012 }; 3034 };
@@ -3015,11 +3037,21 @@ static void tune_input(const char *name)
3015 3037
3016 static const char *mouse_games[] = { 3038 static const char *mouse_games[] = {
3017 "Loopy", 3039 "Loopy",
3040 "Slide",
3018 NULL 3041 NULL
3019 }; 3042 };
3020 3043
3021 mouse_mode = string_in_list(name, mouse_games); 3044 mouse_mode = string_in_list(name, mouse_games);
3022 3045
3046 static const char *sticky_mouse_games[] = {
3047 "Map",
3048 "Signpost",
3049 "Slide",
3050 "Untangle",
3051 };
3052
3053 input_settings.sticky_mouse = string_in_list(name, sticky_mouse_games);
3054
3023 static const char *number_chooser_games[] = { 3055 static const char *number_chooser_games[] = {
3024 "Filling", 3056 "Filling",
3025 "Keen", 3057 "Keen",
@@ -3312,8 +3344,11 @@ static int mainmenu_cb(int action,
3312 if(!load_success) 3344 if(!load_success)
3313 return ACTION_EXIT_MENUITEM; 3345 return ACTION_EXIT_MENUITEM;
3314 break; 3346 break;
3347 case 2:
3315 case 3: 3348 case 3:
3316 break; 3349 if(!help_valid)
3350 return ACTION_EXIT_MENUITEM;
3351 break;
3317 case 4: 3352 case 4:
3318 if(audiobuf_available) 3353 if(audiobuf_available)
3319 break; 3354 break;
@@ -3476,12 +3511,14 @@ static void puzzles_main(void)
3476 /* quit without saving */ 3511 /* quit without saving */
3477 midend_free(me); 3512 midend_free(me);
3478 sfree(colors); 3513 sfree(colors);
3514 colors = NULL;
3479 return; 3515 return;
3480 case -3: 3516 case -3:
3481 /* save and quit */ 3517 /* save and quit */
3482 save_game(); 3518 save_game();
3483 midend_free(me); 3519 midend_free(me);
3484 sfree(colors); 3520 sfree(colors);
3521 colors = NULL;
3485 return; 3522 return;
3486 default: 3523 default:
3487 break; 3524 break;
@@ -3511,6 +3548,7 @@ static void puzzles_main(void)
3511 rb->yield(); 3548 rb->yield();
3512 } 3549 }
3513 sfree(colors); 3550 sfree(colors);
3551 colors = NULL;
3514 } 3552 }
3515} 3553}
3516 3554
diff --git a/apps/plugins/puzzles/src/unfinished/CMakeLists.txt b/apps/plugins/puzzles/src/unfinished/CMakeLists.txt
new file mode 100644
index 0000000000..0c1e331f9b
--- /dev/null
+++ b/apps/plugins/puzzles/src/unfinished/CMakeLists.txt
@@ -0,0 +1,31 @@
1puzzle(group
2 DISPLAYNAME "Group"
3 DESCRIPTION "Group theory puzzle"
4 OBJECTIVE "Complete the unfinished Cayley table of a group.")
5solver(group ${CMAKE_SOURCE_DIR}/latin.c)
6
7puzzle(separate
8 DISPLAYNAME "Separate"
9 DESCRIPTION "Rectangle-dividing puzzle"
10 OBJECTIVE "Partition the grid into regions containing one of each letter.")
11
12puzzle(slide
13 DISPLAYNAME "Slide"
14 DESCRIPTION "Sliding block puzzle"
15 OBJECTIVE "Slide the blocks to let the key block out.")
16solver(slide)
17
18puzzle(sokoban
19 DISPLAYNAME "Sokoban"
20 DESCRIPTION "Barrel-pushing puzzle"
21 OBJECTIVE "Push all the barrels into the target squares.")
22
23# These unfinished programs don't even have the structure of a puzzle
24# game yet; they're just command-line programs containing test
25# implementations of some of the needed functionality.
26
27cliprogram(numgame numgame.c)
28
29cliprogram(path path.c COMPILE_DEFINITIONS TEST_GEN)
30
31export_variables_to_parent_scope()
diff --git a/apps/plugins/puzzles/src/unfinished/README b/apps/plugins/puzzles/src/unfinished/README
new file mode 100644
index 0000000000..c96ccc935a
--- /dev/null
+++ b/apps/plugins/puzzles/src/unfinished/README
@@ -0,0 +1,14 @@
1This subdirectory contains puzzle implementations which are
2half-written, fundamentally flawed, or in other ways unready to be
3shipped as part of the polished Puzzles collection.
4
5The CMake build system will _build_ all of the source in this
6directory (to ensure it hasn't become unbuildable), but they won't be
7included in all-in-one puzzle binaries or installed by 'make install'
8targets. If you want to temporarily change that, you can reconfigure
9your build by defining the CMake variable PUZZLES_ENABLE_UNFINISHED.
10For example,
11
12 cmake . -DPUZZLES_ENABLE_UNFINISHED="group;slide"
13
14will build as if both Group and Slide were fully official puzzles.
diff --git a/apps/plugins/puzzles/src/unfinished/group.c b/apps/plugins/puzzles/src/unfinished/group.c
new file mode 100644
index 0000000000..faffa89485
--- /dev/null
+++ b/apps/plugins/puzzles/src/unfinished/group.c
@@ -0,0 +1,2497 @@
1/*
2 * group.c: a Latin-square puzzle, but played with groups' Cayley
3 * tables. That is, you are given a Cayley table of a group with
4 * most elements blank and a few clues, and you must fill it in
5 * so as to preserve the group axioms.
6 *
7 * This is a perfectly playable and fully working puzzle, but I'm
8 * leaving it for the moment in the 'unfinished' directory because
9 * it's just too esoteric (not to mention _hard_) for me to be
10 * comfortable presenting it to the general public as something they
11 * might (implicitly) actually want to play.
12 *
13 * TODO:
14 *
15 * - more solver techniques?
16 * * Inverses: once we know that gh = e, we can immediately
17 * deduce hg = e as well; then for any gx=y we can deduce
18 * hy=x, and for any xg=y we have yh=x.
19 * * Hard-mode associativity: we currently deduce based on
20 * definite numbers in the grid, but we could also winnow
21 * based on _possible_ numbers.
22 * * My overambitious original thoughts included wondering if we
23 * could infer that there must be elements of certain orders
24 * (e.g. a group of order divisible by 5 must contain an
25 * element of order 5), but I think in fact this is probably
26 * silly.
27 */
28
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <assert.h>
33#include <ctype.h>
34#ifdef NO_TGMATH_H
35# include <math.h>
36#else
37# include <tgmath.h>
38#endif
39
40#include "puzzles.h"
41#include "latin.h"
42
43/*
44 * Difficulty levels. I do some macro ickery here to ensure that my
45 * enum and the various forms of my name list always match up.
46 */
47#define DIFFLIST(A) \
48 A(TRIVIAL,Trivial,NULL,t) \
49 A(NORMAL,Normal,solver_normal,n) \
50 A(HARD,Hard,solver_hard,h) \
51 A(EXTREME,Extreme,NULL,x) \
52 A(UNREASONABLE,Unreasonable,NULL,u)
53#define ENUM(upper,title,func,lower) DIFF_ ## upper,
54#define TITLE(upper,title,func,lower) #title,
55#define ENCODE(upper,title,func,lower) #lower
56#define CONFIG(upper,title,func,lower) ":" #title
57enum { DIFFLIST(ENUM) DIFFCOUNT };
58static char const *const group_diffnames[] = { DIFFLIST(TITLE) };
59static char const group_diffchars[] = DIFFLIST(ENCODE);
60#define DIFFCONFIG DIFFLIST(CONFIG)
61
62enum {
63 COL_BACKGROUND,
64 COL_GRID,
65 COL_USER,
66 COL_HIGHLIGHT,
67 COL_ERROR,
68 COL_PENCIL,
69 COL_DIAGONAL,
70 NCOLOURS
71};
72
73/*
74 * In identity mode, we number the elements e,a,b,c,d,f,g,h,...
75 * Otherwise, they're a,b,c,d,e,f,g,h,... in the obvious way.
76 */
77#define E_TO_FRONT(c,id) ( (id) && (c)<=5 ? (c) % 5 + 1 : (c) )
78#define E_FROM_FRONT(c,id) ( (id) && (c)<=5 ? ((c) + 3) % 5 + 1 : (c) )
79
80#define FROMCHAR(c,id) E_TO_FRONT((((c)-('A'-1)) & ~0x20), id)
81#define ISCHAR(c) (((c)>='A'&&(c)<='Z') || ((c)>='a'&&(c)<='z'))
82#define TOCHAR(c,id) (E_FROM_FRONT(c,id) + ('a'-1))
83
84struct game_params {
85 int w, diff;
86 bool id;
87};
88
89typedef struct group_common {
90 int refcount;
91 bool *immutable;
92} group_common;
93
94struct game_state {
95 game_params par;
96 digit *grid;
97 int *pencil; /* bitmaps using bits 1<<1..1<<n */
98 group_common *common;
99 bool completed, cheated;
100 digit *sequence; /* sequence of group elements shown */
101
102 /*
103 * This array indicates thick lines separating rows and columns
104 * placed and unplaced manually by the user as a visual aid, e.g.
105 * to delineate a subgroup and its cosets.
106 *
107 * When a line is placed, it's deemed to be between the two
108 * particular group elements that are on either side of it at the
109 * time; dragging those two away from each other automatically
110 * gets rid of the line. Hence, for a given element i, dividers[i]
111 * is either -1 (indicating no divider to the right of i), or some
112 * other element (indicating a divider to the right of i iff that
113 * element is the one right of it). These are eagerly cleared
114 * during drags.
115 */
116 int *dividers; /* thick lines between rows/cols */
117};
118
119static game_params *default_params(void)
120{
121 game_params *ret = snew(game_params);
122
123 ret->w = 6;
124 ret->diff = DIFF_NORMAL;
125 ret->id = true;
126
127 return ret;
128}
129
130static const struct game_params group_presets[] = {
131 { 6, DIFF_NORMAL, true },
132 { 6, DIFF_NORMAL, false },
133 { 8, DIFF_NORMAL, true },
134 { 8, DIFF_NORMAL, false },
135 { 8, DIFF_HARD, true },
136 { 8, DIFF_HARD, false },
137 { 12, DIFF_NORMAL, true },
138};
139
140static bool game_fetch_preset(int i, char **name, game_params **params)
141{
142 game_params *ret;
143 char buf[80];
144
145 if (i < 0 || i >= lenof(group_presets))
146 return false;
147
148 ret = snew(game_params);
149 *ret = group_presets[i]; /* structure copy */
150
151 sprintf(buf, "%dx%d %s%s", ret->w, ret->w, group_diffnames[ret->diff],
152 ret->id ? "" : ", identity hidden");
153
154 *name = dupstr(buf);
155 *params = ret;
156 return true;
157}
158
159static void free_params(game_params *params)
160{
161 sfree(params);
162}
163
164static game_params *dup_params(const game_params *params)
165{
166 game_params *ret = snew(game_params);
167 *ret = *params; /* structure copy */
168 return ret;
169}
170
171static void decode_params(game_params *params, char const *string)
172{
173 char const *p = string;
174
175 params->w = atoi(p);
176 while (*p && isdigit((unsigned char)*p)) p++;
177 params->diff = DIFF_NORMAL;
178 params->id = true;
179
180 while (*p) {
181 if (*p == 'd') {
182 int i;
183 p++;
184 params->diff = DIFFCOUNT+1; /* ...which is invalid */
185 if (*p) {
186 for (i = 0; i < DIFFCOUNT; i++) {
187 if (*p == group_diffchars[i])
188 params->diff = i;
189 }
190 p++;
191 }
192 } else if (*p == 'i') {
193 params->id = false;
194 p++;
195 } else {
196 /* unrecognised character */
197 p++;
198 }
199 }
200}
201
202static char *encode_params(const game_params *params, bool full)
203{
204 char ret[80];
205
206 sprintf(ret, "%d", params->w);
207 if (full)
208 sprintf(ret + strlen(ret), "d%c", group_diffchars[params->diff]);
209 if (!params->id)
210 sprintf(ret + strlen(ret), "i");
211
212 return dupstr(ret);
213}
214
215static config_item *game_configure(const game_params *params)
216{
217 config_item *ret;
218 char buf[80];
219
220 ret = snewn(4, config_item);
221
222 ret[0].name = "Grid size";
223 ret[0].type = C_STRING;
224 sprintf(buf, "%d", params->w);
225 ret[0].u.string.sval = dupstr(buf);
226
227 ret[1].name = "Difficulty";
228 ret[1].type = C_CHOICES;
229 ret[1].u.choices.choicenames = DIFFCONFIG;
230 ret[1].u.choices.selected = params->diff;
231
232 ret[2].name = "Show identity";
233 ret[2].type = C_BOOLEAN;
234 ret[2].u.boolean.bval = params->id;
235
236 ret[3].name = NULL;
237 ret[3].type = C_END;
238
239 return ret;
240}
241
242static game_params *custom_params(const config_item *cfg)
243{
244 game_params *ret = snew(game_params);
245
246 ret->w = atoi(cfg[0].u.string.sval);
247 ret->diff = cfg[1].u.choices.selected;
248 ret->id = cfg[2].u.boolean.bval;
249
250 return ret;
251}
252
253static const char *validate_params(const game_params *params, bool full)
254{
255 if (params->w < 3 || params->w > 26)
256 return "Grid size must be between 3 and 26";
257 if (params->diff >= DIFFCOUNT)
258 return "Unknown difficulty rating";
259 if (!params->id && params->diff == DIFF_TRIVIAL) {
260 /*
261 * We can't have a Trivial-difficulty puzzle (i.e. latin
262 * square deductions only) without a clear identity, because
263 * identityless puzzles always have two rows and two columns
264 * entirely blank, and no latin-square deduction permits the
265 * distinguishing of two such rows.
266 */
267 return "Trivial puzzles must have an identity";
268 }
269 if (!params->id && params->w == 3) {
270 /*
271 * We can't have a 3x3 puzzle without an identity either,
272 * because 3x3 puzzles can't ever be harder than Trivial
273 * (there are no 3x3 latin squares which aren't also valid
274 * group tables, so enabling group-based deductions doesn't
275 * rule out any possible solutions) and - as above - Trivial
276 * puzzles can't not have an identity.
277 */
278 return "3x3 puzzles must have an identity";
279 }
280 return NULL;
281}
282
283/* ----------------------------------------------------------------------
284 * Solver.
285 */
286
287static int find_identity(struct latin_solver *solver)
288{
289 int w = solver->o;
290 digit *grid = solver->grid;
291 int i, j;
292
293 for (i = 0; i < w; i++)
294 for (j = 0; j < w; j++) {
295 if (grid[i*w+j] == i+1)
296 return j+1;
297 if (grid[i*w+j] == j+1)
298 return i+1;
299 }
300
301 return 0;
302}
303
304static int solver_normal(struct latin_solver *solver, void *vctx)
305{
306 int w = solver->o;
307#ifdef STANDALONE_SOLVER
308 char **names = solver->names;
309#endif
310 digit *grid = solver->grid;
311 int i, j, k;
312
313 /*
314 * Deduce using associativity: (ab)c = a(bc).
315 *
316 * So we pick any a,b,c we like; then if we know ab, bc, and
317 * (ab)c we can fill in a(bc).
318 */
319 for (i = 0; i < w; i++)
320 for (j = 0; j < w; j++)
321 for (k = 0; k < w; k++) {
322 if (!grid[i*w+j] || !grid[j*w+k])
323 continue;
324 if (grid[(grid[i*w+j]-1)*w+k] &&
325 !grid[i*w+(grid[j*w+k]-1)]) {
326 int x = grid[j*w+k]-1, y = i;
327 int n = grid[(grid[i*w+j]-1)*w+k];
328#ifdef STANDALONE_SOLVER
329 if (solver_show_working) {
330 printf("%*sassociativity on %s,%s,%s: %s*%s = %s*%s\n",
331 solver_recurse_depth*4, "",
332 names[i], names[j], names[k],
333 names[grid[i*w+j]-1], names[k],
334 names[i], names[grid[j*w+k]-1]);
335 printf("%*s placing %s at (%d,%d)\n",
336 solver_recurse_depth*4, "",
337 names[n-1], x+1, y+1);
338 }
339#endif
340 if (solver->cube[(x*w+y)*w+n-1]) {
341 latin_solver_place(solver, x, y, n);
342 return 1;
343 } else {
344#ifdef STANDALONE_SOLVER
345 if (solver_show_working)
346 printf("%*s contradiction!\n",
347 solver_recurse_depth*4, "");
348 return -1;
349#endif
350 }
351 }
352 if (!grid[(grid[i*w+j]-1)*w+k] &&
353 grid[i*w+(grid[j*w+k]-1)]) {
354 int x = k, y = grid[i*w+j]-1;
355 int n = grid[i*w+(grid[j*w+k]-1)];
356#ifdef STANDALONE_SOLVER
357 if (solver_show_working) {
358 printf("%*sassociativity on %s,%s,%s: %s*%s = %s*%s\n",
359 solver_recurse_depth*4, "",
360 names[i], names[j], names[k],
361 names[grid[i*w+j]-1], names[k],
362 names[i], names[grid[j*w+k]-1]);
363 printf("%*s placing %s at (%d,%d)\n",
364 solver_recurse_depth*4, "",
365 names[n-1], x+1, y+1);
366 }
367#endif
368 if (solver->cube[(x*w+y)*w+n-1]) {
369 latin_solver_place(solver, x, y, n);
370 return 1;
371 } else {
372#ifdef STANDALONE_SOLVER
373 if (solver_show_working)
374 printf("%*s contradiction!\n",
375 solver_recurse_depth*4, "");
376 return -1;
377#endif
378 }
379 }
380 }
381
382 /*
383 * Fill in the row and column for the group identity, if it's not
384 * already known and if we've just found out what it is.
385 */
386 i = find_identity(solver);
387 if (i) {
388 bool done_something = false;
389 for (j = 1; j <= w; j++) {
390 if (!grid[(i-1)*w+(j-1)] || !grid[(j-1)*w+(i-1)]) {
391 done_something = true;
392 }
393 }
394 if (done_something) {
395#ifdef STANDALONE_SOLVER
396 if (solver_show_working) {
397 printf("%*s%s is the group identity\n",
398 solver_recurse_depth*4, "", names[i-1]);
399 }
400#endif
401 for (j = 1; j <= w; j++) {
402 if (!grid[(j-1)*w+(i-1)]) {
403 if (!cube(i-1, j-1, j)) {
404#ifdef STANDALONE_SOLVER
405 if (solver_show_working) {
406 printf("%*s but %s cannot go at (%d,%d) - "
407 "contradiction!\n",
408 solver_recurse_depth*4, "",
409 names[j-1], i, j);
410 }
411#endif
412 return -1;
413 }
414#ifdef STANDALONE_SOLVER
415 if (solver_show_working) {
416 printf("%*s placing %s at (%d,%d)\n",
417 solver_recurse_depth*4, "",
418 names[j-1], i, j);
419 }
420#endif
421 latin_solver_place(solver, i-1, j-1, j);
422 }
423 if (!grid[(i-1)*w+(j-1)]) {
424 if (!cube(j-1, i-1, j)) {
425#ifdef STANDALONE_SOLVER
426 if (solver_show_working) {
427 printf("%*s but %s cannot go at (%d,%d) - "
428 "contradiction!\n",
429 solver_recurse_depth*4, "",
430 names[j-1], j, i);
431 }
432#endif
433 return -1;
434 }
435#ifdef STANDALONE_SOLVER
436 if (solver_show_working) {
437 printf("%*s placing %s at (%d,%d)\n",
438 solver_recurse_depth*4, "",
439 names[j-1], j, i);
440 }
441#endif
442 latin_solver_place(solver, j-1, i-1, j);
443 }
444 }
445 return 1;
446 }
447 }
448
449 return 0;
450}
451
452static int solver_hard(struct latin_solver *solver, void *vctx)
453{
454 bool done_something = false;
455 int w = solver->o;
456#ifdef STANDALONE_SOLVER
457 char **names = solver->names;
458#endif
459 int i, j;
460
461 /*
462 * In identity-hidden mode, systematically rule out possibilities
463 * for the group identity.
464 *
465 * In solver_normal, we used the fact that any filled square in
466 * the grid whose contents _does_ match one of the elements it's
467 * the product of - that is, ab=a or ab=b - tells you immediately
468 * that the other element is the identity.
469 *
470 * Here, we use the flip side of that: any filled square in the
471 * grid whose contents does _not_ match either its row or column -
472 * that is, if ab is neither a nor b - tells you immediately that
473 * _neither_ of those elements is the identity. And if that's
474 * true, then we can also immediately rule out the possibility
475 * that it acts as the identity on any element at all.
476 */
477 for (i = 0; i < w; i++) {
478 bool i_can_be_id = true;
479#ifdef STANDALONE_SOLVER
480 char title[80];
481#endif
482
483 for (j = 0; j < w; j++) {
484 if (grid(i,j) && grid(i,j) != j+1) {
485#ifdef STANDALONE_SOLVER
486 if (solver_show_working)
487 sprintf(title, "%s cannot be the identity: "
488 "%s%s = %s =/= %s", names[i], names[i], names[j],
489 names[grid(i,j)-1], names[j]);
490#endif
491 i_can_be_id = false;
492 break;
493 }
494 if (grid(j,i) && grid(j,i) != j+1) {
495#ifdef STANDALONE_SOLVER
496 if (solver_show_working)
497 sprintf(title, "%s cannot be the identity: "
498 "%s%s = %s =/= %s", names[i], names[j], names[i],
499 names[grid(j,i)-1], names[j]);
500#endif
501 i_can_be_id = false;
502 break;
503 }
504 }
505
506 if (!i_can_be_id) {
507 /* Now rule out ij=j or ji=j for all j. */
508 for (j = 0; j < w; j++) {
509 if (cube(i, j, j+1)) {
510#ifdef STANDALONE_SOLVER
511 if (solver_show_working) {
512 if (title[0]) {
513 printf("%*s%s\n", solver_recurse_depth*4, "",
514 title);
515 title[0] = '\0';
516 }
517 printf("%*s ruling out %s at (%d,%d)\n",
518 solver_recurse_depth*4, "", names[j], i, j);
519 }
520#endif
521 cube(i, j, j+1) = false;
522 }
523 if (cube(j, i, j+1)) {
524#ifdef STANDALONE_SOLVER
525 if (solver_show_working) {
526 if (title[0]) {
527 printf("%*s%s\n", solver_recurse_depth*4, "",
528 title);
529 title[0] = '\0';
530 }
531 printf("%*s ruling out %s at (%d,%d)\n",
532 solver_recurse_depth*4, "", names[j], j, i);
533 }
534#endif
535 cube(j, i, j+1) = false;
536 }
537 }
538 }
539 }
540
541 return done_something;
542}
543
544#define SOLVER(upper,title,func,lower) func,
545static usersolver_t const group_solvers[] = { DIFFLIST(SOLVER) };
546
547static bool group_valid(struct latin_solver *solver, void *ctx)
548{
549 int w = solver->o;
550#ifdef STANDALONE_SOLVER
551 char **names = solver->names;
552#endif
553 int i, j, k;
554
555 for (i = 0; i < w; i++)
556 for (j = 0; j < w; j++)
557 for (k = 0; k < w; k++) {
558 int ij = grid(i, j) - 1;
559 int jk = grid(j, k) - 1;
560 int ij_k = grid(ij, k) - 1;
561 int i_jk = grid(i, jk) - 1;
562 if (ij_k != i_jk) {
563#ifdef STANDALONE_SOLVER
564 if (solver_show_working) {
565 printf("%*sfailure of associativity: "
566 "(%s%s)%s = %s%s = %s but "
567 "%s(%s%s) = %s%s = %s\n",
568 solver_recurse_depth*4, "",
569 names[i], names[j], names[k],
570 names[ij], names[k], names[ij_k],
571 names[i], names[j], names[k],
572 names[i], names[jk], names[i_jk]);
573 }
574#endif
575 return false;
576 }
577 }
578
579 return true;
580}
581
582static int solver(const game_params *params, digit *grid, int maxdiff)
583{
584 int w = params->w;
585 int ret;
586 struct latin_solver solver;
587
588#ifdef STANDALONE_SOLVER
589 char *p, text[100], *names[50];
590 int i;
591
592 for (i = 0, p = text; i < w; i++) {
593 names[i] = p;
594 *p++ = TOCHAR(i+1, params->id);
595 *p++ = '\0';
596 }
597 solver.names = names;
598#endif
599
600 if (latin_solver_alloc(&solver, grid, w))
601 ret = latin_solver_main(&solver, maxdiff,
602 DIFF_TRIVIAL, DIFF_HARD, DIFF_EXTREME,
603 DIFF_EXTREME, DIFF_UNREASONABLE,
604 group_solvers, group_valid, NULL, NULL, NULL);
605 else
606 ret = diff_impossible;
607
608 latin_solver_free(&solver);
609
610 return ret;
611}
612
613/* ----------------------------------------------------------------------
614 * Grid generation.
615 */
616
617static char *encode_grid(char *desc, digit *grid, int area)
618{
619 int run, i;
620 char *p = desc;
621
622 run = 0;
623 for (i = 0; i <= area; i++) {
624 int n = (i < area ? grid[i] : -1);
625
626 if (!n)
627 run++;
628 else {
629 if (run) {
630 while (run > 0) {
631 int c = 'a' - 1 + run;
632 if (run > 26)
633 c = 'z';
634 *p++ = c;
635 run -= c - ('a' - 1);
636 }
637 } else {
638 /*
639 * If there's a number in the very top left or
640 * bottom right, there's no point putting an
641 * unnecessary _ before or after it.
642 */
643 if (p > desc && n > 0)
644 *p++ = '_';
645 }
646 if (n > 0)
647 p += sprintf(p, "%d", n);
648 run = 0;
649 }
650 }
651 return p;
652}
653
654/* ----- data generated by group.gap begins ----- */
655
656struct group {
657 unsigned long autosize;
658 int order, ngens;
659 const char *gens;
660};
661struct groups {
662 int ngroups;
663 const struct group *groups;
664};
665
666static const struct group groupdata[] = {
667 /* order 2 */
668 {1L, 2, 1, "BA"},
669 /* order 3 */
670 {2L, 3, 1, "BCA"},
671 /* order 4 */
672 {2L, 4, 1, "BCDA"},
673 {6L, 4, 2, "BADC" "CDAB"},
674 /* order 5 */
675 {4L, 5, 1, "BCDEA"},
676 /* order 6 */
677 {6L, 6, 2, "CFEBAD" "BADCFE"},
678 {2L, 6, 1, "DCFEBA"},
679 /* order 7 */
680 {6L, 7, 1, "BCDEFGA"},
681 /* order 8 */
682 {4L, 8, 1, "BCEFDGHA"},
683 {8L, 8, 2, "BDEFGAHC" "EGBHDCFA"},
684 {8L, 8, 2, "EGBHDCFA" "BAEFCDHG"},
685 {24L, 8, 2, "BDEFGAHC" "CHDGBEAF"},
686 {168L, 8, 3, "BAEFCDHG" "CEAGBHDF" "DFGAHBCE"},
687 /* order 9 */
688 {6L, 9, 1, "BDECGHFIA"},
689 {48L, 9, 2, "BDEAGHCIF" "CEFGHAIBD"},
690 /* order 10 */
691 {20L, 10, 2, "CJEBGDIFAH" "BADCFEHGJI"},
692 {4L, 10, 1, "DCFEHGJIBA"},
693 /* order 11 */
694 {10L, 11, 1, "BCDEFGHIJKA"},
695 /* order 12 */
696 {12L, 12, 2, "GLDKJEHCBIAF" "BCEFAGIJDKLH"},
697 {4L, 12, 1, "EHIJKCBLDGFA"},
698 {24L, 12, 2, "BEFGAIJKCDLH" "FJBKHLEGDCIA"},
699 {12L, 12, 2, "GLDKJEHCBIAF" "BAEFCDIJGHLK"},
700 {12L, 12, 2, "FDIJGHLBKAEC" "GIDKFLHCJEAB"},
701 /* order 13 */
702 {12L, 13, 1, "BCDEFGHIJKLMA"},
703 /* order 14 */
704 {42L, 14, 2, "ELGNIBKDMFAHCJ" "BADCFEHGJILKNM"},
705 {6L, 14, 1, "FEHGJILKNMBADC"},
706 /* order 15 */
707 {8L, 15, 1, "EGHCJKFMNIOBLDA"},
708 /* order 16 */
709 {8L, 16, 1, "MKNPFOADBGLCIEHJ"},
710 {96L, 16, 2, "ILKCONFPEDJHGMAB" "BDFGHIAKLMNCOEPJ"},
711 {32L, 16, 2, "MIHPFDCONBLAKJGE" "BEFGHJKALMNOCDPI"},
712 {32L, 16, 2, "IFACOGLMDEJBNPKH" "BEFGHJKALMNOCDPI"},
713 {16L, 16, 2, "MOHPFKCINBLADJGE" "BDFGHIEKLMNJOAPC"},
714 {16L, 16, 2, "MIHPFDJONBLEKCGA" "BDFGHIEKLMNJOAPC"},
715 {32L, 16, 2, "MOHPFDCINBLEKJGA" "BAFGHCDELMNIJKPO"},
716 {16L, 16, 2, "MIHPFKJONBLADCGE" "GDPHNOEKFLBCIAMJ"},
717 {32L, 16, 2, "MIBPFDJOGHLEKCNA" "CLEIJGMPKAOHNFDB"},
718 {192L, 16, 3,
719 "MCHPFAIJNBLDEOGK" "BEFGHJKALMNOCDPI" "GKLBNOEDFPHJIAMC"},
720 {64L, 16, 3, "MCHPFAIJNBLDEOGK" "LOGFPKJIBNMEDCHA" "CMAIJHPFDEONBLKG"},
721 {192L, 16, 3,
722 "IPKCOGMLEDJBNFAH" "BEFGHJKALMNOCDPI" "CMEIJBPFKAOGHLDN"},
723 {48L, 16, 3, "IPDJONFLEKCBGMAH" "FJBLMEOCGHPKAIND" "DGIEKLHNJOAMPBCF"},
724 {20160L, 16, 4,
725 "EHJKAMNBOCDPFGIL" "BAFGHCDELMNIJKPO" "CFAIJBLMDEOGHPKN"
726 "DGIAKLBNCOEFPHJM"},
727 /* order 17 */
728 {16L, 17, 1, "EFGHIJKLMNOPQABCD"},
729 /* order 18 */
730 {54L, 18, 2, "MKIQOPNAGLRECDBJHF" "BAEFCDJKLGHIOPMNRQ"},
731 {6L, 18, 1, "ECJKGHFOPDMNLRIQBA"},
732 {12L, 18, 2, "ECJKGHBOPAMNFRDQLI" "KNOPQCFREIGHLJAMBD"},
733 {432L, 18, 3,
734 "IFNAKLQCDOPBGHREMJ" "NOQCFRIGHKLJAMPBDE" "BAEFCDJKLGHIOPMNRQ"},
735 {48L, 18, 2, "ECJKGHBOPAMNFRDQLI" "FDKLHIOPBMNAREQCJG"},
736 /* order 19 */
737 {18L, 19, 1, "EFGHIJKLMNOPQRSABCD"},
738 /* order 20 */
739 {40L, 20, 2, "GTDKREHOBILSFMPCJQAN" "EABICDFMGHJQKLNTOPRS"},
740 {8L, 20, 1, "EHIJLCMNPGQRSKBTDOFA"},
741 {20L, 20, 2, "DJSHQNCLTRGPEBKAIFOM" "EABICDFMGHJQKLNTOPRS"},
742 {40L, 20, 2, "GTDKREHOBILSFMPCJQAN" "ECBIAGFMDKJQHONTLSRP"},
743 {24L, 20, 2, "IGFMDKJQHONTLSREPCBA" "FDIJGHMNKLQROPTBSAEC"},
744 /* order 21 */
745 {42L, 21, 2, "ITLSBOUERDHAGKCJNFMQP" "EJHLMKOPNRSQAUTCDBFGI"},
746 {12L, 21, 1, "EGHCJKFMNIPQLSTOUBRDA"},
747 /* order 22 */
748 {110L, 22, 2, "ETGVIBKDMFOHQJSLUNAPCR" "BADCFEHGJILKNMPORQTSVU"},
749 {10L, 22, 1, "FEHGJILKNMPORQTSVUBADC"},
750 /* order 23 */
751 {22L, 23, 1, "EFGHIJKLMNOPQRSTUVWABCD"},
752 /* order 24 */
753 {24L, 24, 2, "QXEJWPUMKLRIVBFTSACGHNDO" "HRNOPSWCTUVBLDIJXFGAKQME"},
754 {8L, 24, 1, "MQBTUDRWFGHXJELINOPKSAVC"},
755 {24L, 24, 2, "IOQRBEUVFWGHKLAXMNPSCDTJ" "NJXOVGDKSMTFIPQELCURBWAH"},
756 {48L, 24, 2, "QUEJWVXFKLRIPGMNSACBOTDH" "HSNOPWLDTUVBRIAKXFGCQEMJ"},
757 {24L, 24, 2, "QXEJWPUMKLRIVBFTSACGHNDO" "TWHNXLRIOPUMSACQVBFDEJGK"},
758 {48L, 24, 2, "QUEJWVXFKLRIPGMNSACBOTDH" "BAFGHCDEMNOPIJKLTUVQRSXW"},
759 {48L, 24, 3,
760 "QXKJWVUMESRIPGFTLDCBONAH" "JUEQRPXFKLWCVBMNSAIGHTDO"
761 "HSNOPWLDTUVBRIAKXFGCQEMJ"},
762 {24L, 24, 3,
763 "QUKJWPXFESRIVBMNLDCGHTAO" "JXEQRVUMKLWCPGFTSAIBONDH"
764 "TRONXLWCHVUMSAIJPGFDEQBK"},
765 {16L, 24, 2, "MRGTULWIOPFXSDJQBVNEKCHA" "VKXHOQASNTPBCWDEUFGIJLMR"},
766 {16L, 24, 2, "MRGTULWIOPFXSDJQBVNEKCHA" "RMLWIGTUSDJQOPFXEKCBVNAH"},
767 {48L, 24, 2, "IULQRGXMSDCWOPNTEKJBVFAH" "GLMOPRSDTUBVWIEKFXHJQANC"},
768 {24L, 24, 2, "UJPXMRCSNHGTLWIKFVBEDQOA" "NRUFVLWIPXMOJEDQHGTCSABK"},
769 {24L, 24, 2, "MIBTUAQRFGHXCDEWNOPJKLVS" "OKXVFWSCGUTNDRQJBPMALIHE"},
770 {144L, 24, 3,
771 "QXKJWVUMESRIPGFTLDCBONAH" "JUEQRPXFKLWCVBMNSAIGHTDO"
772 "BAFGHCDEMNOPIJKLTUVQRSXW"},
773 {336L, 24, 3,
774 "QTKJWONXESRIHVUMLDCPGFAB" "JNEQRHTUKLWCOPXFSAIVBMDG"
775 "HENOPJKLTUVBQRSAXFGWCDMI"},
776 /* order 25 */
777 {20L, 25, 1, "EHILMNPQRSFTUVBJWXDOYGAKC"},
778 {480L, 25, 2, "EHILMNPQRSCTUVBFWXDJYGOKA" "BDEGHIKLMNAPQRSCTUVFWXJYO"},
779 /* order 26 */
780 {156L, 26, 2,
781 "EXGZIBKDMFOHQJSLUNWPYRATCV" "BADCFEHGJILKNMPORQTSVUXWZY"},
782 {12L, 26, 1, "FEHGJILKNMPORQTSVUXWZYBADC"},
783};
784
785static const struct groups groups[] = {
786 {0, NULL}, /* trivial case: 0 */
787 {0, NULL}, /* trivial case: 1 */
788 {1, groupdata + 0}, /* 2 */
789 {1, groupdata + 1}, /* 3 */
790 {2, groupdata + 2}, /* 4 */
791 {1, groupdata + 4}, /* 5 */
792 {2, groupdata + 5}, /* 6 */
793 {1, groupdata + 7}, /* 7 */
794 {5, groupdata + 8}, /* 8 */
795 {2, groupdata + 13}, /* 9 */
796 {2, groupdata + 15}, /* 10 */
797 {1, groupdata + 17}, /* 11 */
798 {5, groupdata + 18}, /* 12 */
799 {1, groupdata + 23}, /* 13 */
800 {2, groupdata + 24}, /* 14 */
801 {1, groupdata + 26}, /* 15 */
802 {14, groupdata + 27}, /* 16 */
803 {1, groupdata + 41}, /* 17 */
804 {5, groupdata + 42}, /* 18 */
805 {1, groupdata + 47}, /* 19 */
806 {5, groupdata + 48}, /* 20 */
807 {2, groupdata + 53}, /* 21 */
808 {2, groupdata + 55}, /* 22 */
809 {1, groupdata + 57}, /* 23 */
810 {15, groupdata + 58}, /* 24 */
811 {2, groupdata + 73}, /* 25 */
812 {2, groupdata + 75}, /* 26 */
813};
814
815/* ----- data generated by group.gap ends ----- */
816
817static char *new_game_desc(const game_params *params, random_state *rs,
818 char **aux, bool interactive)
819{
820 int w = params->w, a = w*w;
821 digit *grid, *soln, *soln2;
822 int *indices;
823 int i, j, k, qh, qt;
824 int diff = params->diff;
825 const struct group *group;
826 char *desc, *p;
827
828 /*
829 * Difficulty exceptions: some combinations of size and
830 * difficulty cannot be satisfied, because all puzzles of at
831 * most that difficulty are actually even easier.
832 *
833 * Remember to re-test this whenever a change is made to the
834 * solver logic!
835 *
836 * I tested it using the following shell command:
837
838for d in t n h x u; do
839 for id in '' i; do
840 for i in {3..9}; do
841 echo -n "./group --generate 1 ${i}d${d}${id}: "
842 perl -e 'alarm 30; exec @ARGV' \
843 ./group --generate 1 ${i}d${d}${id} >/dev/null && echo ok
844 done
845 done
846done
847
848 * Of course, it's better to do that after taking the exceptions
849 * _out_, so as to detect exceptions that should be removed as
850 * well as those which should be added.
851 */
852 if (w < 5 && diff == DIFF_UNREASONABLE)
853 diff--;
854 if ((w < 5 || ((w == 6 || w == 8) && params->id)) && diff == DIFF_EXTREME)
855 diff--;
856 if ((w < 6 || (w == 6 && params->id)) && diff == DIFF_HARD)
857 diff--;
858 if ((w < 4 || (w == 4 && params->id)) && diff == DIFF_NORMAL)
859 diff--;
860
861 grid = snewn(a, digit);
862 soln = snewn(a, digit);
863 soln2 = snewn(a, digit);
864 indices = snewn(a, int);
865
866 while (1) {
867 /*
868 * Construct a valid group table, by picking a group from
869 * the above data table, decompressing it into a full
870 * representation by BFS, and then randomly permuting its
871 * non-identity elements.
872 *
873 * We build the canonical table in 'soln' (and use 'grid' as
874 * our BFS queue), then transfer the table into 'grid'
875 * having shuffled the rows.
876 */
877 assert(w >= 2);
878 assert(w < lenof(groups));
879 group = groups[w].groups + random_upto(rs, groups[w].ngroups);
880 assert(group->order == w);
881 memset(soln, 0, a);
882 for (i = 0; i < w; i++)
883 soln[i] = i+1;
884 qh = qt = 0;
885 grid[qt++] = 1;
886 while (qh < qt) {
887 digit *row, *newrow;
888
889 i = grid[qh++];
890 row = soln + (i-1)*w;
891
892 for (j = 0; j < group->ngens; j++) {
893 int nri;
894 const char *gen = group->gens + j*w;
895
896 /*
897 * Apply each group generator to row, constructing a
898 * new row.
899 */
900 nri = gen[row[0]-1] - 'A' + 1; /* which row is it? */
901 newrow = soln + (nri-1)*w;
902 if (!newrow[0]) { /* not done yet */
903 for (k = 0; k < w; k++)
904 newrow[k] = gen[row[k]-1] - 'A' + 1;
905 grid[qt++] = nri;
906 }
907 }
908 }
909 /* That's got the canonical table. Now shuffle it. */
910 for (i = 0; i < w; i++)
911 soln2[i] = i;
912 if (params->id) /* do we shuffle in the identity? */
913 shuffle(soln2+1, w-1, sizeof(*soln2), rs);
914 else
915 shuffle(soln2, w, sizeof(*soln2), rs);
916 for (i = 0; i < w; i++)
917 for (j = 0; j < w; j++)
918 grid[(soln2[i])*w+(soln2[j])] = soln2[soln[i*w+j]-1]+1;
919
920 /*
921 * Remove entries one by one while the puzzle is still
922 * soluble at the appropriate difficulty level.
923 */
924 memcpy(soln, grid, a);
925 if (!params->id) {
926 /*
927 * Start by blanking the entire identity row and column,
928 * and also another row and column so that the player
929 * can't trivially determine which element is the
930 * identity.
931 */
932
933 j = 1 + random_upto(rs, w-1); /* pick a second row/col to blank */
934 for (i = 0; i < w; i++) {
935 grid[(soln2[0])*w+i] = grid[i*w+(soln2[0])] = 0;
936 grid[(soln2[j])*w+i] = grid[i*w+(soln2[j])] = 0;
937 }
938
939 memcpy(soln2, grid, a);
940 if (solver(params, soln2, diff) > diff)
941 continue; /* go round again if that didn't work */
942 }
943
944 k = 0;
945 for (i = (params->id ? 1 : 0); i < w; i++)
946 for (j = (params->id ? 1 : 0); j < w; j++)
947 if (grid[i*w+j])
948 indices[k++] = i*w+j;
949 shuffle(indices, k, sizeof(*indices), rs);
950
951 for (i = 0; i < k; i++) {
952 memcpy(soln2, grid, a);
953 soln2[indices[i]] = 0;
954 if (solver(params, soln2, diff) <= diff)
955 grid[indices[i]] = 0;
956 }
957
958 /*
959 * Make sure the puzzle isn't too easy.
960 */
961 if (diff > 0) {
962 memcpy(soln2, grid, a);
963 if (solver(params, soln2, diff-1) < diff)
964 continue; /* go round and try again */
965 }
966
967 /*
968 * Done.
969 */
970 break;
971 }
972
973 /*
974 * Encode the puzzle description.
975 */
976 desc = snewn(a*20, char);
977 p = encode_grid(desc, grid, a);
978 *p++ = '\0';
979 desc = sresize(desc, p - desc, char);
980
981 /*
982 * Encode the solution.
983 */
984 *aux = snewn(a+2, char);
985 (*aux)[0] = 'S';
986 for (i = 0; i < a; i++)
987 (*aux)[i+1] = TOCHAR(soln[i], params->id);
988 (*aux)[a+1] = '\0';
989
990 sfree(grid);
991 sfree(soln);
992 sfree(soln2);
993 sfree(indices);
994
995 return desc;
996}
997
998/* ----------------------------------------------------------------------
999 * Gameplay.
1000 */
1001
1002static const char *validate_grid_desc(const char **pdesc, int range, int area)
1003{
1004 const char *desc = *pdesc;
1005 int squares = 0;
1006 while (*desc && *desc != ',') {
1007 int n = *desc++;
1008 if (n >= 'a' && n <= 'z') {
1009 squares += n - 'a' + 1;
1010 } else if (n == '_') {
1011 /* do nothing */;
1012 } else if (n > '0' && n <= '9') {
1013 int val = atoi(desc-1);
1014 if (val < 1 || val > range)
1015 return "Out-of-range number in game description";
1016 squares++;
1017 while (*desc >= '0' && *desc <= '9')
1018 desc++;
1019 } else
1020 return "Invalid character in game description";
1021 }
1022
1023 if (squares < area)
1024 return "Not enough data to fill grid";
1025
1026 if (squares > area)
1027 return "Too much data to fit in grid";
1028 *pdesc = desc;
1029 return NULL;
1030}
1031
1032static const char *validate_desc(const game_params *params, const char *desc)
1033{
1034 int w = params->w, a = w*w;
1035 const char *p = desc;
1036
1037 return validate_grid_desc(&p, w, a);
1038}
1039
1040static const char *spec_to_grid(const char *desc, digit *grid, int area)
1041{
1042 int i = 0;
1043 while (*desc && *desc != ',') {
1044 int n = *desc++;
1045 if (n >= 'a' && n <= 'z') {
1046 int run = n - 'a' + 1;
1047 assert(i + run <= area);
1048 while (run-- > 0)
1049 grid[i++] = 0;
1050 } else if (n == '_') {
1051 /* do nothing */;
1052 } else if (n > '0' && n <= '9') {
1053 assert(i < area);
1054 grid[i++] = atoi(desc-1);
1055 while (*desc >= '0' && *desc <= '9')
1056 desc++;
1057 } else {
1058 assert(!"We can't get here");
1059 }
1060 }
1061 assert(i == area);
1062 return desc;
1063}
1064
1065static game_state *new_game(midend *me, const game_params *params,
1066 const char *desc)
1067{
1068 int w = params->w, a = w*w;
1069 game_state *state = snew(game_state);
1070 int i;
1071
1072 state->par = *params; /* structure copy */
1073 state->grid = snewn(a, digit);
1074 state->common = snew(group_common);
1075 state->common->refcount = 1;
1076 state->common->immutable = snewn(a, bool);
1077 state->pencil = snewn(a, int);
1078 for (i = 0; i < a; i++) {
1079 state->grid[i] = 0;
1080 state->common->immutable[i] = false;
1081 state->pencil[i] = 0;
1082 }
1083 state->sequence = snewn(w, digit);
1084 state->dividers = snewn(w, int);
1085 for (i = 0; i < w; i++) {
1086 state->sequence[i] = i;
1087 state->dividers[i] = -1;
1088 }
1089
1090 desc = spec_to_grid(desc, state->grid, a);
1091 for (i = 0; i < a; i++)
1092 if (state->grid[i] != 0)
1093 state->common->immutable[i] = true;
1094
1095 state->completed = false;
1096 state->cheated = false;
1097
1098 return state;
1099}
1100
1101static game_state *dup_game(const game_state *state)
1102{
1103 int w = state->par.w, a = w*w;
1104 game_state *ret = snew(game_state);
1105
1106 ret->par = state->par; /* structure copy */
1107
1108 ret->grid = snewn(a, digit);
1109 ret->common = state->common;
1110 ret->common->refcount++;
1111 ret->pencil = snewn(a, int);
1112 ret->sequence = snewn(w, digit);
1113 ret->dividers = snewn(w, int);
1114 memcpy(ret->grid, state->grid, a*sizeof(digit));
1115 memcpy(ret->pencil, state->pencil, a*sizeof(int));
1116 memcpy(ret->sequence, state->sequence, w*sizeof(digit));
1117 memcpy(ret->dividers, state->dividers, w*sizeof(int));
1118
1119 ret->completed = state->completed;
1120 ret->cheated = state->cheated;
1121
1122 return ret;
1123}
1124
1125static void free_game(game_state *state)
1126{
1127 sfree(state->grid);
1128 if (--state->common->refcount == 0) {
1129 sfree(state->common->immutable);
1130 sfree(state->common);
1131 }
1132 sfree(state->pencil);
1133 sfree(state->sequence);
1134 sfree(state);
1135}
1136
1137static char *solve_game(const game_state *state, const game_state *currstate,
1138 const char *aux, const char **error)
1139{
1140 int w = state->par.w, a = w*w;
1141 int i, ret;
1142 digit *soln;
1143 char *out;
1144
1145 if (aux)
1146 return dupstr(aux);
1147
1148 soln = snewn(a, digit);
1149 memcpy(soln, state->grid, a*sizeof(digit));
1150
1151 ret = solver(&state->par, soln, DIFFCOUNT-1);
1152
1153 if (ret == diff_impossible) {
1154 *error = "No solution exists for this puzzle";
1155 out = NULL;
1156 } else if (ret == diff_ambiguous) {
1157 *error = "Multiple solutions exist for this puzzle";
1158 out = NULL;
1159 } else {
1160 out = snewn(a+2, char);
1161 out[0] = 'S';
1162 for (i = 0; i < a; i++)
1163 out[i+1] = TOCHAR(soln[i], state->par.id);
1164 out[a+1] = '\0';
1165 }
1166
1167 sfree(soln);
1168 return out;
1169}
1170
1171static bool game_can_format_as_text_now(const game_params *params)
1172{
1173 return true;
1174}
1175
1176static char *game_text_format(const game_state *state)
1177{
1178 int w = state->par.w;
1179 int x, y;
1180 char *ret, *p, ch;
1181
1182 ret = snewn(2*w*w+1, char); /* leave room for terminating NUL */
1183
1184 p = ret;
1185 for (y = 0; y < w; y++) {
1186 for (x = 0; x < w; x++) {
1187 digit d = state->grid[y*w+x];
1188
1189 if (d == 0) {
1190 ch = '.';
1191 } else {
1192 ch = TOCHAR(d, state->par.id);
1193 }
1194
1195 *p++ = ch;
1196 if (x == w-1) {
1197 *p++ = '\n';
1198 } else {
1199 *p++ = ' ';
1200 }
1201 }
1202 }
1203
1204 assert(p - ret == 2*w*w);
1205 *p = '\0';
1206 return ret;
1207}
1208
1209struct game_ui {
1210 /*
1211 * These are the coordinates of the primary highlighted square on
1212 * the grid, if hshow = 1.
1213 */
1214 int hx, hy;
1215 /*
1216 * These are the coordinates hx,hy _before_ they go through
1217 * state->sequence.
1218 */
1219 int ohx, ohy;
1220 /*
1221 * These variables give the length and displacement of a diagonal
1222 * sequence of highlighted squares starting at ohx,ohy (still if
1223 * hshow = 1). To find the squares' real coordinates, for 0<=i<dn,
1224 * compute ohx+i*odx and ohy+i*ody and then map through
1225 * state->sequence.
1226 */
1227 int odx, ody, odn;
1228 /*
1229 * This indicates whether the current highlight is a
1230 * pencil-mark one or a real one.
1231 */
1232 bool hpencil;
1233 /*
1234 * This indicates whether or not we're showing the highlight
1235 * (used to be hx = hy = -1); important so that when we're
1236 * using the cursor keys it doesn't keep coming back at a
1237 * fixed position. When hshow = 1, pressing a valid number
1238 * or letter key or Space will enter that number or letter in the grid.
1239 */
1240 bool hshow;
1241 /*
1242 * This indicates whether we're using the highlight as a cursor;
1243 * it means that it doesn't vanish on a keypress, and that it is
1244 * allowed on immutable squares.
1245 */
1246 bool hcursor;
1247 /*
1248 * This indicates whether we're dragging a table header to
1249 * reposition an entire row or column.
1250 */
1251 int drag; /* 0=none 1=row 2=col */
1252 int dragnum; /* element being dragged */
1253 int dragpos; /* its current position */
1254 int edgepos;
1255
1256 /*
1257 * User preference option: if the user right-clicks in a square
1258 * and presses a letter key to add/remove a pencil mark, do we
1259 * hide the mouse highlight again afterwards?
1260 *
1261 * Historically our answer was yes. The Android port prefers no.
1262 * There are advantages both ways, depending how much you dislike
1263 * the highlight cluttering your view. So it's a preference.
1264 */
1265 bool pencil_keep_highlight;
1266};
1267
1268static game_ui *new_ui(const game_state *state)
1269{
1270 game_ui *ui = snew(game_ui);
1271
1272 ui->hx = ui->hy = 0;
1273 ui->hpencil = false;
1274 ui->hshow = false;
1275 ui->hcursor = false;
1276 ui->drag = 0;
1277
1278 ui->pencil_keep_highlight = false;
1279
1280 return ui;
1281}
1282
1283static void free_ui(game_ui *ui)
1284{
1285 sfree(ui);
1286}
1287
1288static config_item *get_prefs(game_ui *ui)
1289{
1290 config_item *ret;
1291
1292 ret = snewn(2, config_item);
1293
1294 ret[0].name = "Keep mouse highlight after changing a pencil mark";
1295 ret[0].kw = "pencil-keep-highlight";
1296 ret[0].type = C_BOOLEAN;
1297 ret[0].u.boolean.bval = ui->pencil_keep_highlight;
1298
1299 ret[1].name = NULL;
1300 ret[1].type = C_END;
1301
1302 return ret;
1303}
1304
1305static void set_prefs(game_ui *ui, const config_item *cfg)
1306{
1307 ui->pencil_keep_highlight = cfg[0].u.boolean.bval;
1308}
1309
1310static void game_changed_state(game_ui *ui, const game_state *oldstate,
1311 const game_state *newstate)
1312{
1313 int w = newstate->par.w;
1314 /*
1315 * We prevent pencil-mode highlighting of a filled square, unless
1316 * we're using the cursor keys. So if the user has just filled in
1317 * a square which we had a pencil-mode highlight in (by Undo, or
1318 * by Redo, or by Solve), then we cancel the highlight.
1319 */
1320 if (ui->hshow && ui->hpencil && !ui->hcursor &&
1321 newstate->grid[ui->hy * w + ui->hx] != 0) {
1322 ui->hshow = false;
1323 }
1324 if (ui->hshow && ui->odn > 1) {
1325 /*
1326 * Reordering of rows or columns within the range of a
1327 * multifill selection cancels the multifill and deselects
1328 * everything.
1329 */
1330 int i;
1331 for (i = 0; i < ui->odn; i++) {
1332 if (oldstate->sequence[ui->ohx + i*ui->odx] !=
1333 newstate->sequence[ui->ohx + i*ui->odx]) {
1334 ui->hshow = false;
1335 break;
1336 }
1337 if (oldstate->sequence[ui->ohy + i*ui->ody] !=
1338 newstate->sequence[ui->ohy + i*ui->ody]) {
1339 ui->hshow = false;
1340 break;
1341 }
1342 }
1343 } else if (ui->hshow &&
1344 (newstate->sequence[ui->ohx] != ui->hx ||
1345 newstate->sequence[ui->ohy] != ui->hy)) {
1346 /*
1347 * Otherwise, reordering of the row or column containing the
1348 * selection causes the selection to move with it.
1349 */
1350 int i;
1351 for (i = 0; i < w; i++) {
1352 if (newstate->sequence[i] == ui->hx)
1353 ui->ohx = i;
1354 if (newstate->sequence[i] == ui->hy)
1355 ui->ohy = i;
1356 }
1357 }
1358}
1359
1360static const char *current_key_label(const game_ui *ui,
1361 const game_state *state, int button)
1362{
1363 if (ui->hshow && button == CURSOR_SELECT)
1364 return ui->hpencil ? "Ink" : "Pencil";
1365 if (ui->hshow && button == CURSOR_SELECT2) {
1366 int w = state->par.w;
1367 int i;
1368 for (i = 0; i < ui->odn; i++) {
1369 int x = state->sequence[ui->ohx + i*ui->odx];
1370 int y = state->sequence[ui->ohy + i*ui->ody];
1371 int index = y*w+x;
1372 if (ui->hpencil && state->grid[index]) return "";
1373 if (state->common->immutable[index]) return "";
1374 }
1375 return "Clear";
1376 }
1377 return "";
1378}
1379
1380#define PREFERRED_TILESIZE 48
1381#define TILESIZE (ds->tilesize)
1382#define BORDER (TILESIZE / 2)
1383#define LEGEND (TILESIZE)
1384#define GRIDEXTRA max((TILESIZE / 32),1)
1385#define COORD(x) ((x)*TILESIZE + BORDER + LEGEND)
1386#define FROMCOORD(x) (((x)+(TILESIZE-BORDER-LEGEND)) / TILESIZE - 1)
1387
1388#define FLASH_TIME 0.4F
1389
1390#define DF_DIVIDER_TOP 0x1000
1391#define DF_DIVIDER_BOT 0x2000
1392#define DF_DIVIDER_LEFT 0x4000
1393#define DF_DIVIDER_RIGHT 0x8000
1394#define DF_HIGHLIGHT 0x0400
1395#define DF_HIGHLIGHT_PENCIL 0x0200
1396#define DF_IMMUTABLE 0x0100
1397#define DF_LEGEND 0x0080
1398#define DF_DIGIT_MASK 0x001F
1399
1400#define EF_DIGIT_SHIFT 5
1401#define EF_DIGIT_MASK ((1 << EF_DIGIT_SHIFT) - 1)
1402#define EF_LEFT_SHIFT 0
1403#define EF_RIGHT_SHIFT (3*EF_DIGIT_SHIFT)
1404#define EF_LEFT_MASK ((1UL << (3*EF_DIGIT_SHIFT)) - 1UL)
1405#define EF_RIGHT_MASK (EF_LEFT_MASK << EF_RIGHT_SHIFT)
1406#define EF_LATIN (1UL << (6*EF_DIGIT_SHIFT))
1407
1408struct game_drawstate {
1409 game_params par;
1410 int w, tilesize;
1411 bool started;
1412 long *tiles, *legend, *pencil, *errors;
1413 long *errtmp;
1414 digit *sequence;
1415};
1416
1417static bool check_errors(const game_state *state, long *errors)
1418{
1419 int w = state->par.w, a = w*w;
1420 digit *grid = state->grid;
1421 int i, j, k, x, y;
1422 bool errs = false;
1423
1424 /*
1425 * To verify that we have a valid group table, it suffices to
1426 * test latin-square-hood and associativity only. All the other
1427 * group axioms follow from those two.
1428 *
1429 * Proof:
1430 *
1431 * Associativity is given; closure is obvious from latin-
1432 * square-hood. We need to show that an identity exists and that
1433 * every element has an inverse.
1434 *
1435 * Identity: take any element a. There will be some element e
1436 * such that ea=a (in a latin square, every element occurs in
1437 * every row and column, so a must occur somewhere in the a
1438 * column, say on row e). For any other element b, there must
1439 * exist x such that ax=b (same argument from latin-square-hood
1440 * again), and then associativity gives us eb = e(ax) = (ea)x =
1441 * ax = b. Hence eb=b for all b, i.e. e is a left-identity. A
1442 * similar argument tells us that there must be some f which is
1443 * a right-identity, and then we show they are the same element
1444 * by observing that ef must simultaneously equal e and equal f.
1445 *
1446 * Inverses: given any a, by the latin-square argument again,
1447 * there must exist p and q such that pa=e and aq=e (i.e. left-
1448 * and right-inverses). We can show these are equal by
1449 * associativity: p = pe = p(aq) = (pa)q = eq = q. []
1450 */
1451
1452 if (errors)
1453 for (i = 0; i < a; i++)
1454 errors[i] = 0;
1455
1456 for (y = 0; y < w; y++) {
1457 unsigned long mask = 0, errmask = 0;
1458 for (x = 0; x < w; x++) {
1459 unsigned long bit = 1UL << grid[y*w+x];
1460 errmask |= (mask & bit);
1461 mask |= bit;
1462 }
1463
1464 if (mask != (1 << (w+1)) - (1 << 1)) {
1465 errs = true;
1466 errmask &= ~1UL;
1467 if (errors) {
1468 for (x = 0; x < w; x++)
1469 if (errmask & (1UL << grid[y*w+x]))
1470 errors[y*w+x] |= EF_LATIN;
1471 }
1472 }
1473 }
1474
1475 for (x = 0; x < w; x++) {
1476 unsigned long mask = 0, errmask = 0;
1477 for (y = 0; y < w; y++) {
1478 unsigned long bit = 1UL << grid[y*w+x];
1479 errmask |= (mask & bit);
1480 mask |= bit;
1481 }
1482
1483 if (mask != (1 << (w+1)) - (1 << 1)) {
1484 errs = true;
1485 errmask &= ~1UL;
1486 if (errors) {
1487 for (y = 0; y < w; y++)
1488 if (errmask & (1UL << grid[y*w+x]))
1489 errors[y*w+x] |= EF_LATIN;
1490 }
1491 }
1492 }
1493
1494 for (i = 1; i < w; i++)
1495 for (j = 1; j < w; j++)
1496 for (k = 1; k < w; k++)
1497 if (grid[i*w+j] && grid[j*w+k] &&
1498 grid[(grid[i*w+j]-1)*w+k] &&
1499 grid[i*w+(grid[j*w+k]-1)] &&
1500 grid[(grid[i*w+j]-1)*w+k] != grid[i*w+(grid[j*w+k]-1)]) {
1501 if (errors) {
1502 int a = i+1, b = j+1, c = k+1;
1503 int ab = grid[i*w+j], bc = grid[j*w+k];
1504 int left = (ab-1)*w+(c-1), right = (a-1)*w+(bc-1);
1505 /*
1506 * If the appropriate error slot is already
1507 * used for one of the squares, we don't
1508 * fill either of them.
1509 */
1510 if (!(errors[left] & EF_LEFT_MASK) &&
1511 !(errors[right] & EF_RIGHT_MASK)) {
1512 long err;
1513 err = a;
1514 err = (err << EF_DIGIT_SHIFT) | b;
1515 err = (err << EF_DIGIT_SHIFT) | c;
1516 errors[left] |= err << EF_LEFT_SHIFT;
1517 errors[right] |= err << EF_RIGHT_SHIFT;
1518 }
1519 }
1520 errs = true;
1521 }
1522
1523 return errs;
1524}
1525
1526static int find_in_sequence(digit *seq, int len, digit n)
1527{
1528 int i;
1529
1530 for (i = 0; i < len; i++)
1531 if (seq[i] == n)
1532 return i;
1533
1534 assert(!"Should never get here");
1535 return -1;
1536}
1537
1538static char *interpret_move(const game_state *state, game_ui *ui,
1539 const game_drawstate *ds,
1540 int x, int y, int button)
1541{
1542 int w = state->par.w;
1543 int tx, ty;
1544 char buf[80];
1545
1546 button = STRIP_BUTTON_MODIFIERS(button);
1547
1548 tx = FROMCOORD(x);
1549 ty = FROMCOORD(y);
1550
1551 if (ui->drag) {
1552 if (IS_MOUSE_DRAG(button)) {
1553 int tcoord = ((ui->drag &~ 4) == 1 ? ty : tx);
1554 ui->drag |= 4; /* some movement has happened */
1555 if (tcoord >= 0 && tcoord < w) {
1556 ui->dragpos = tcoord;
1557 return MOVE_UI_UPDATE;
1558 }
1559 } else if (IS_MOUSE_RELEASE(button)) {
1560 if (ui->drag & 4) {
1561 ui->drag = 0; /* end drag */
1562 if (state->sequence[ui->dragpos] == ui->dragnum)
1563 return MOVE_UI_UPDATE; /* drag was a no-op overall */
1564 sprintf(buf, "D%d,%d", ui->dragnum, ui->dragpos);
1565 return dupstr(buf);
1566 } else {
1567 ui->drag = 0; /* end 'drag' */
1568 if (ui->edgepos > 0 && ui->edgepos < w) {
1569 sprintf(buf, "V%d,%d",
1570 state->sequence[ui->edgepos-1],
1571 state->sequence[ui->edgepos]);
1572 return dupstr(buf);
1573 } else
1574 return MOVE_UI_UPDATE; /* no-op */
1575 }
1576 }
1577 } else if (IS_MOUSE_DOWN(button)) {
1578 if (tx >= 0 && tx < w && ty >= 0 && ty < w) {
1579 int otx = tx, oty = ty;
1580 tx = state->sequence[tx];
1581 ty = state->sequence[ty];
1582 if (button == LEFT_BUTTON) {
1583 if (tx == ui->hx && ty == ui->hy &&
1584 ui->hshow && !ui->hpencil) {
1585 ui->hshow = false;
1586 } else {
1587 ui->hx = tx;
1588 ui->hy = ty;
1589 ui->ohx = otx;
1590 ui->ohy = oty;
1591 ui->odx = ui->ody = 0;
1592 ui->odn = 1;
1593 ui->hshow = !state->common->immutable[ty*w+tx];
1594 ui->hpencil = false;
1595 }
1596 ui->hcursor = false;
1597 return MOVE_UI_UPDATE;
1598 }
1599 if (button == RIGHT_BUTTON) {
1600 /*
1601 * Pencil-mode highlighting for non filled squares.
1602 */
1603 if (state->grid[ty*w+tx] == 0) {
1604 if (tx == ui->hx && ty == ui->hy &&
1605 ui->hshow && ui->hpencil) {
1606 ui->hshow = false;
1607 } else {
1608 ui->hpencil = true;
1609 ui->hx = tx;
1610 ui->hy = ty;
1611 ui->ohx = otx;
1612 ui->ohy = oty;
1613 ui->odx = ui->ody = 0;
1614 ui->odn = 1;
1615 ui->hshow = true;
1616 }
1617 } else {
1618 ui->hshow = false;
1619 }
1620 ui->hcursor = false;
1621 return MOVE_UI_UPDATE;
1622 }
1623 } else if (tx >= 0 && tx < w && ty == -1) {
1624 ui->drag = 2;
1625 ui->dragnum = state->sequence[tx];
1626 ui->dragpos = tx;
1627 ui->edgepos = FROMCOORD(x + TILESIZE/2);
1628 return MOVE_UI_UPDATE;
1629 } else if (ty >= 0 && ty < w && tx == -1) {
1630 ui->drag = 1;
1631 ui->dragnum = state->sequence[ty];
1632 ui->dragpos = ty;
1633 ui->edgepos = FROMCOORD(y + TILESIZE/2);
1634 return MOVE_UI_UPDATE;
1635 }
1636 } else if (IS_MOUSE_DRAG(button)) {
1637 if (!ui->hpencil &&
1638 tx >= 0 && tx < w && ty >= 0 && ty < w &&
1639 abs(tx - ui->ohx) == abs(ty - ui->ohy)) {
1640 ui->odn = abs(tx - ui->ohx) + 1;
1641 ui->odx = (tx < ui->ohx ? -1 : +1);
1642 ui->ody = (ty < ui->ohy ? -1 : +1);
1643 } else {
1644 ui->odx = ui->ody = 0;
1645 ui->odn = 1;
1646 }
1647 return MOVE_UI_UPDATE;
1648 }
1649
1650 if (IS_CURSOR_MOVE(button)) {
1651 int cx = find_in_sequence(state->sequence, w, ui->hx);
1652 int cy = find_in_sequence(state->sequence, w, ui->hy);
1653 move_cursor(button, &cx, &cy, w, w, false, NULL);
1654 ui->hx = state->sequence[cx];
1655 ui->hy = state->sequence[cy];
1656 ui->hshow = true;
1657 ui->hcursor = true;
1658 ui->ohx = cx;
1659 ui->ohy = cy;
1660 ui->odx = ui->ody = 0;
1661 ui->odn = 1;
1662 return MOVE_UI_UPDATE;
1663 }
1664 if (ui->hshow &&
1665 (button == CURSOR_SELECT)) {
1666 ui->hpencil = !ui->hpencil;
1667 ui->hcursor = true;
1668 return MOVE_UI_UPDATE;
1669 }
1670
1671 if (ui->hshow &&
1672 ((ISCHAR(button) && FROMCHAR(button, state->par.id) <= w) ||
1673 button == CURSOR_SELECT2 || button == '\b')) {
1674 int n = FROMCHAR(button, state->par.id);
1675 int i, buflen;
1676 char *movebuf;
1677
1678 if (button == CURSOR_SELECT2 || button == '\b')
1679 n = 0;
1680
1681 for (i = 0; i < ui->odn; i++) {
1682 int x = state->sequence[ui->ohx + i*ui->odx];
1683 int y = state->sequence[ui->ohy + i*ui->ody];
1684 int index = y*w+x;
1685
1686 /*
1687 * Can't make pencil marks in a filled square. This can only
1688 * become highlighted if we're using cursor keys.
1689 */
1690 if (ui->hpencil && state->grid[index])
1691 return NULL;
1692
1693 /*
1694 * Can't do anything to an immutable square. Exception:
1695 * trying to set it to what it already was is OK (so that
1696 * multifilling can set a whole diagonal to a without
1697 * having to detour round the one immutable square in the
1698 * middle that already said a).
1699 */
1700 if (!ui->hpencil && state->grid[index] == n)
1701 /* OK even if it is immutable */;
1702 else if (state->common->immutable[index])
1703 return NULL;
1704 }
1705
1706 movebuf = snewn(80 * ui->odn, char);
1707 buflen = sprintf(movebuf, "%c%d,%d,%d",
1708 (char)(ui->hpencil && n > 0 ? 'P' : 'R'),
1709 ui->hx, ui->hy, n);
1710 for (i = 1; i < ui->odn; i++) {
1711 assert(buflen < i*80);
1712 buflen += sprintf(movebuf + buflen, "+%d,%d",
1713 state->sequence[ui->ohx + i*ui->odx],
1714 state->sequence[ui->ohy + i*ui->ody]);
1715 }
1716 movebuf = sresize(movebuf, buflen+1, char);
1717
1718 /*
1719 * Hide the highlight after a keypress, if it was mouse-
1720 * generated. Also, don't hide it if this move has changed
1721 * pencil marks and the user preference says not to hide the
1722 * highlight in that situation.
1723 */
1724 if (!ui->hcursor && !(ui->hpencil && ui->pencil_keep_highlight))
1725 ui->hshow = false;
1726
1727 return movebuf;
1728 }
1729
1730 if (button == 'M' || button == 'm')
1731 return dupstr("M");
1732
1733 return NULL;
1734}
1735
1736static game_state *execute_move(const game_state *from, const char *move)
1737{
1738 int w = from->par.w, a = w*w;
1739 game_state *ret;
1740 int x, y, i, j, n, pos;
1741
1742 if (move[0] == 'S') {
1743 ret = dup_game(from);
1744 ret->completed = ret->cheated = true;
1745
1746 for (i = 0; i < a; i++) {
1747 if (!ISCHAR(move[i+1]) || FROMCHAR(move[i+1], from->par.id) > w) {
1748 free_game(ret);
1749 return NULL;
1750 }
1751 ret->grid[i] = FROMCHAR(move[i+1], from->par.id);
1752 ret->pencil[i] = 0;
1753 }
1754
1755 if (move[a+1] != '\0') {
1756 free_game(ret);
1757 return NULL;
1758 }
1759
1760 return ret;
1761 } else if ((move[0] == 'P' || move[0] == 'R') &&
1762 sscanf(move+1, "%d,%d,%d%n", &x, &y, &n, &pos) == 3 &&
1763 n >= 0 && n <= w) {
1764 const char *mp = move + 1 + pos;
1765 bool pencil = (move[0] == 'P');
1766 ret = dup_game(from);
1767
1768 while (1) {
1769 if (x < 0 || x >= w || y < 0 || y >= w) {
1770 free_game(ret);
1771 return NULL;
1772 }
1773 if (from->common->immutable[y*w+x] &&
1774 !(!pencil && from->grid[y*w+x] == n))
1775 return NULL;
1776
1777 if (move[0] == 'P' && n > 0) {
1778 ret->pencil[y*w+x] ^= 1 << n;
1779 } else {
1780 ret->grid[y*w+x] = n;
1781 ret->pencil[y*w+x] = 0;
1782 }
1783
1784 if (!*mp)
1785 break;
1786
1787 if (*mp != '+')
1788 return NULL;
1789 if (sscanf(mp, "+%d,%d%n", &x, &y, &pos) < 2)
1790 return NULL;
1791 mp += pos;
1792 }
1793
1794 if (!ret->completed && !check_errors(ret, NULL))
1795 ret->completed = true;
1796
1797 return ret;
1798 } else if (move[0] == 'M') {
1799 /*
1800 * Fill in absolutely all pencil marks everywhere. (I
1801 * wouldn't use this for actual play, but it's a handy
1802 * starting point when following through a set of
1803 * diagnostics output by the standalone solver.)
1804 */
1805 ret = dup_game(from);
1806 for (i = 0; i < a; i++) {
1807 if (!ret->grid[i])
1808 ret->pencil[i] = (1 << (w+1)) - (1 << 1);
1809 }
1810 return ret;
1811 } else if (move[0] == 'D' &&
1812 sscanf(move+1, "%d,%d", &x, &y) == 2) {
1813 /*
1814 * Reorder the rows and columns so that digit x is in position
1815 * y.
1816 */
1817 ret = dup_game(from);
1818 for (i = j = 0; i < w; i++) {
1819 if (i == y) {
1820 ret->sequence[i] = x;
1821 } else {
1822 if (from->sequence[j] == x)
1823 j++;
1824 ret->sequence[i] = from->sequence[j++];
1825 }
1826 }
1827 /*
1828 * Eliminate any obsoleted dividers.
1829 */
1830 for (x = 0; x < w; x++) {
1831 int i = ret->sequence[x];
1832 int j = (x+1 < w ? ret->sequence[x+1] : -1);
1833 if (ret->dividers[i] != j)
1834 ret->dividers[i] = -1;
1835 }
1836 return ret;
1837 } else if (move[0] == 'V' &&
1838 sscanf(move+1, "%d,%d", &i, &j) == 2) {
1839 ret = dup_game(from);
1840 if (ret->dividers[i] == j)
1841 ret->dividers[i] = -1;
1842 else
1843 ret->dividers[i] = j;
1844 return ret;
1845 } else
1846 return NULL; /* couldn't parse move string */
1847}
1848
1849/* ----------------------------------------------------------------------
1850 * Drawing routines.
1851 */
1852
1853#define SIZE(w) ((w) * TILESIZE + 2*BORDER + LEGEND)
1854
1855static void game_compute_size(const game_params *params, int tilesize,
1856 const game_ui *ui, int *x, int *y)
1857{
1858 /* Ick: fake up `ds->tilesize' for macro expansion purposes */
1859 struct { int tilesize; } ads, *ds = &ads;
1860 ads.tilesize = tilesize;
1861
1862 *x = *y = SIZE(params->w);
1863}
1864
1865static void game_set_size(drawing *dr, game_drawstate *ds,
1866 const game_params *params, int tilesize)
1867{
1868 ds->tilesize = tilesize;
1869}
1870
1871static float *game_colours(frontend *fe, int *ncolours)
1872{
1873 float *ret = snewn(3 * NCOLOURS, float);
1874
1875 frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
1876
1877 ret[COL_GRID * 3 + 0] = 0.0F;
1878 ret[COL_GRID * 3 + 1] = 0.0F;
1879 ret[COL_GRID * 3 + 2] = 0.0F;
1880
1881 ret[COL_USER * 3 + 0] = 0.0F;
1882 ret[COL_USER * 3 + 1] = 0.6F * ret[COL_BACKGROUND * 3 + 1];
1883 ret[COL_USER * 3 + 2] = 0.0F;
1884
1885 ret[COL_HIGHLIGHT * 3 + 0] = 0.78F * ret[COL_BACKGROUND * 3 + 0];
1886 ret[COL_HIGHLIGHT * 3 + 1] = 0.78F * ret[COL_BACKGROUND * 3 + 1];
1887 ret[COL_HIGHLIGHT * 3 + 2] = 0.78F * ret[COL_BACKGROUND * 3 + 2];
1888
1889 ret[COL_ERROR * 3 + 0] = 1.0F;
1890 ret[COL_ERROR * 3 + 1] = 0.0F;
1891 ret[COL_ERROR * 3 + 2] = 0.0F;
1892
1893 ret[COL_PENCIL * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0];
1894 ret[COL_PENCIL * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1];
1895 ret[COL_PENCIL * 3 + 2] = ret[COL_BACKGROUND * 3 + 2];
1896
1897 ret[COL_DIAGONAL * 3 + 0] = 0.95F * ret[COL_BACKGROUND * 3 + 0];
1898 ret[COL_DIAGONAL * 3 + 1] = 0.95F * ret[COL_BACKGROUND * 3 + 1];
1899 ret[COL_DIAGONAL * 3 + 2] = 0.95F * ret[COL_BACKGROUND * 3 + 2];
1900
1901 *ncolours = NCOLOURS;
1902 return ret;
1903}
1904
1905static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
1906{
1907 int w = state->par.w, a = w*w;
1908 struct game_drawstate *ds = snew(struct game_drawstate);
1909 int i;
1910
1911 ds->w = w;
1912 ds->par = state->par; /* structure copy */
1913 ds->tilesize = 0;
1914 ds->started = false;
1915 ds->tiles = snewn(a, long);
1916 ds->legend = snewn(w, long);
1917 ds->pencil = snewn(a, long);
1918 ds->errors = snewn(a, long);
1919 ds->sequence = snewn(a, digit);
1920 for (i = 0; i < a; i++)
1921 ds->tiles[i] = ds->pencil[i] = -1;
1922 for (i = 0; i < w; i++)
1923 ds->legend[i] = -1;
1924 ds->errtmp = snewn(a, long);
1925
1926 return ds;
1927}
1928
1929static void game_free_drawstate(drawing *dr, game_drawstate *ds)
1930{
1931 sfree(ds->tiles);
1932 sfree(ds->pencil);
1933 sfree(ds->errors);
1934 sfree(ds->errtmp);
1935 sfree(ds->sequence);
1936 sfree(ds);
1937}
1938
1939static void draw_tile(drawing *dr, game_drawstate *ds, int x, int y, long tile,
1940 long pencil, long error)
1941{
1942 int w = ds->w /* , a = w*w */;
1943 int tx, ty, tw, th;
1944 int cx, cy, cw, ch;
1945 char str[64];
1946
1947 tx = BORDER + LEGEND + x * TILESIZE + 1;
1948 ty = BORDER + LEGEND + y * TILESIZE + 1;
1949
1950 cx = tx;
1951 cy = ty;
1952 cw = tw = TILESIZE-1;
1953 ch = th = TILESIZE-1;
1954
1955 if (tile & DF_LEGEND) {
1956 cx += TILESIZE/10;
1957 cy += TILESIZE/10;
1958 cw -= TILESIZE/5;
1959 ch -= TILESIZE/5;
1960 tile |= DF_IMMUTABLE;
1961 }
1962
1963 clip(dr, cx, cy, cw, ch);
1964
1965 /* background needs erasing */
1966 draw_rect(dr, cx, cy, cw, ch,
1967 (tile & DF_HIGHLIGHT) ? COL_HIGHLIGHT :
1968 (x == y) ? COL_DIAGONAL : COL_BACKGROUND);
1969
1970 /* dividers */
1971 if (tile & DF_DIVIDER_TOP)
1972 draw_rect(dr, cx, cy, cw, 1, COL_GRID);
1973 if (tile & DF_DIVIDER_BOT)
1974 draw_rect(dr, cx, cy+ch-1, cw, 1, COL_GRID);
1975 if (tile & DF_DIVIDER_LEFT)
1976 draw_rect(dr, cx, cy, 1, ch, COL_GRID);
1977 if (tile & DF_DIVIDER_RIGHT)
1978 draw_rect(dr, cx+cw-1, cy, 1, ch, COL_GRID);
1979
1980 /* pencil-mode highlight */
1981 if (tile & DF_HIGHLIGHT_PENCIL) {
1982 int coords[6];
1983 coords[0] = cx;
1984 coords[1] = cy;
1985 coords[2] = cx+cw/2;
1986 coords[3] = cy;
1987 coords[4] = cx;
1988 coords[5] = cy+ch/2;
1989 draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT);
1990 }
1991
1992 /* new number needs drawing? */
1993 if (tile & DF_DIGIT_MASK) {
1994 str[1] = '\0';
1995 str[0] = TOCHAR(tile & DF_DIGIT_MASK, ds->par.id);
1996 draw_text(dr, tx + TILESIZE/2, ty + TILESIZE/2,
1997 FONT_VARIABLE, TILESIZE/2, ALIGN_VCENTRE | ALIGN_HCENTRE,
1998 (error & EF_LATIN) ? COL_ERROR :
1999 (tile & DF_IMMUTABLE) ? COL_GRID : COL_USER, str);
2000
2001 if (error & EF_LEFT_MASK) {
2002 int a = (error >> (EF_LEFT_SHIFT+2*EF_DIGIT_SHIFT))&EF_DIGIT_MASK;
2003 int b = (error >> (EF_LEFT_SHIFT+1*EF_DIGIT_SHIFT))&EF_DIGIT_MASK;
2004 int c = (error >> (EF_LEFT_SHIFT ))&EF_DIGIT_MASK;
2005 char buf[10];
2006 sprintf(buf, "(%c%c)%c", TOCHAR(a, ds->par.id),
2007 TOCHAR(b, ds->par.id), TOCHAR(c, ds->par.id));
2008 draw_text(dr, tx + TILESIZE/2, ty + TILESIZE/6,
2009 FONT_VARIABLE, TILESIZE/6, ALIGN_VCENTRE | ALIGN_HCENTRE,
2010 COL_ERROR, buf);
2011 }
2012 if (error & EF_RIGHT_MASK) {
2013 int a = (error >> (EF_RIGHT_SHIFT+2*EF_DIGIT_SHIFT))&EF_DIGIT_MASK;
2014 int b = (error >> (EF_RIGHT_SHIFT+1*EF_DIGIT_SHIFT))&EF_DIGIT_MASK;
2015 int c = (error >> (EF_RIGHT_SHIFT ))&EF_DIGIT_MASK;
2016 char buf[10];
2017 sprintf(buf, "%c(%c%c)", TOCHAR(a, ds->par.id),
2018 TOCHAR(b, ds->par.id), TOCHAR(c, ds->par.id));
2019 draw_text(dr, tx + TILESIZE/2, ty + TILESIZE - TILESIZE/6,
2020 FONT_VARIABLE, TILESIZE/6, ALIGN_VCENTRE | ALIGN_HCENTRE,
2021 COL_ERROR, buf);
2022 }
2023 } else {
2024 int i, j, npencil;
2025 int pl, pr, pt, pb;
2026 float bestsize;
2027 int pw, ph, minph, pbest, fontsize;
2028
2029 /* Count the pencil marks required. */
2030 for (i = 1, npencil = 0; i <= w; i++)
2031 if (pencil & (1 << i))
2032 npencil++;
2033 if (npencil) {
2034
2035 minph = 2;
2036
2037 /*
2038 * Determine the bounding rectangle within which we're going
2039 * to put the pencil marks.
2040 */
2041 /* Start with the whole square */
2042 pl = tx + GRIDEXTRA;
2043 pr = pl + TILESIZE - GRIDEXTRA;
2044 pt = ty + GRIDEXTRA;
2045 pb = pt + TILESIZE - GRIDEXTRA;
2046
2047 /*
2048 * We arrange our pencil marks in a grid layout, with
2049 * the number of rows and columns adjusted to allow the
2050 * maximum font size.
2051 *
2052 * So now we work out what the grid size ought to be.
2053 */
2054 bestsize = 0.0;
2055 pbest = 0;
2056 /* Minimum */
2057 for (pw = 3; pw < max(npencil,4); pw++) {
2058 float fw, fh, fs;
2059
2060 ph = (npencil + pw - 1) / pw;
2061 ph = max(ph, minph);
2062 fw = (pr - pl) / (float)pw;
2063 fh = (pb - pt) / (float)ph;
2064 fs = min(fw, fh);
2065 if (fs > bestsize) {
2066 bestsize = fs;
2067 pbest = pw;
2068 }
2069 }
2070 assert(pbest > 0);
2071 pw = pbest;
2072 ph = (npencil + pw - 1) / pw;
2073 ph = max(ph, minph);
2074
2075 /*
2076 * Now we've got our grid dimensions, work out the pixel
2077 * size of a grid element, and round it to the nearest
2078 * pixel. (We don't want rounding errors to make the
2079 * grid look uneven at low pixel sizes.)
2080 */
2081 fontsize = min((pr - pl) / pw, (pb - pt) / ph);
2082
2083 /*
2084 * Centre the resulting figure in the square.
2085 */
2086 pl = tx + (TILESIZE - fontsize * pw) / 2;
2087 pt = ty + (TILESIZE - fontsize * ph) / 2;
2088
2089 /*
2090 * Now actually draw the pencil marks.
2091 */
2092 for (i = 1, j = 0; i <= w; i++)
2093 if (pencil & (1 << i)) {
2094 int dx = j % pw, dy = j / pw;
2095
2096 str[1] = '\0';
2097 str[0] = TOCHAR(i, ds->par.id);
2098 draw_text(dr, pl + fontsize * (2*dx+1) / 2,
2099 pt + fontsize * (2*dy+1) / 2,
2100 FONT_VARIABLE, fontsize,
2101 ALIGN_VCENTRE | ALIGN_HCENTRE, COL_PENCIL, str);
2102 j++;
2103 }
2104 }
2105 }
2106
2107 unclip(dr);
2108
2109 draw_update(dr, cx, cy, cw, ch);
2110}
2111
2112static void game_redraw(drawing *dr, game_drawstate *ds,
2113 const game_state *oldstate, const game_state *state,
2114 int dir, const game_ui *ui,
2115 float animtime, float flashtime)
2116{
2117 int w = state->par.w /*, a = w*w */;
2118 int x, y, i, j;
2119
2120 if (!ds->started) {
2121 /*
2122 * Big containing rectangle.
2123 */
2124 draw_rect(dr, COORD(0) - GRIDEXTRA, COORD(0) - GRIDEXTRA,
2125 w*TILESIZE+1+GRIDEXTRA*2, w*TILESIZE+1+GRIDEXTRA*2,
2126 COL_GRID);
2127
2128 draw_update(dr, 0, 0, SIZE(w), SIZE(w));
2129
2130 ds->started = true;
2131 }
2132
2133 check_errors(state, ds->errtmp);
2134
2135 /*
2136 * Construct a modified version of state->sequence which takes
2137 * into account an unfinished drag operation.
2138 */
2139 if (ui->drag) {
2140 x = ui->dragnum;
2141 y = ui->dragpos;
2142 } else {
2143 x = y = -1;
2144 }
2145 for (i = j = 0; i < w; i++) {
2146 if (i == y) {
2147 ds->sequence[i] = x;
2148 } else {
2149 if (state->sequence[j] == x)
2150 j++;
2151 ds->sequence[i] = state->sequence[j++];
2152 }
2153 }
2154
2155 /*
2156 * Draw the table legend.
2157 */
2158 for (x = 0; x < w; x++) {
2159 int sx = ds->sequence[x];
2160 long tile = (sx+1) | DF_LEGEND;
2161 if (ds->legend[x] != tile) {
2162 ds->legend[x] = tile;
2163 draw_tile(dr, ds, -1, x, tile, 0, 0);
2164 draw_tile(dr, ds, x, -1, tile, 0, 0);
2165 }
2166 }
2167
2168 for (y = 0; y < w; y++) {
2169 int sy = ds->sequence[y];
2170 for (x = 0; x < w; x++) {
2171 long tile = 0L, pencil = 0L, error;
2172 int sx = ds->sequence[x];
2173
2174 if (state->grid[sy*w+sx])
2175 tile = state->grid[sy*w+sx];
2176 else
2177 pencil = (long)state->pencil[sy*w+sx];
2178
2179 if (state->common->immutable[sy*w+sx])
2180 tile |= DF_IMMUTABLE;
2181
2182 if ((ui->drag == 5 && ui->dragnum == sy) ||
2183 (ui->drag == 6 && ui->dragnum == sx)) {
2184 tile |= DF_HIGHLIGHT;
2185 } else if (ui->hshow) {
2186 int i = abs(x - ui->ohx);
2187 bool highlight = false;
2188 if (ui->odn > 1) {
2189 /*
2190 * When a diagonal multifill selection is shown,
2191 * we show it in its original grid position
2192 * regardless of in-progress row/col drags. Moving
2193 * every square about would be horrible.
2194 */
2195 if (i >= 0 && i < ui->odn &&
2196 x == ui->ohx + i*ui->odx &&
2197 y == ui->ohy + i*ui->ody)
2198 highlight = true;
2199 } else {
2200 /*
2201 * For a single square, we move its highlight
2202 * around with the drag.
2203 */
2204 highlight = (ui->hx == sx && ui->hy == sy);
2205 }
2206 if (highlight)
2207 tile |= (ui->hpencil ? DF_HIGHLIGHT_PENCIL : DF_HIGHLIGHT);
2208 }
2209
2210 if (flashtime > 0 &&
2211 (flashtime <= FLASH_TIME/3 ||
2212 flashtime >= FLASH_TIME*2/3))
2213 tile |= DF_HIGHLIGHT; /* completion flash */
2214
2215 if (y <= 0 || state->dividers[ds->sequence[y-1]] == sy)
2216 tile |= DF_DIVIDER_TOP;
2217 if (y+1 >= w || state->dividers[sy] == ds->sequence[y+1])
2218 tile |= DF_DIVIDER_BOT;
2219 if (x <= 0 || state->dividers[ds->sequence[x-1]] == sx)
2220 tile |= DF_DIVIDER_LEFT;
2221 if (x+1 >= w || state->dividers[sx] == ds->sequence[x+1])
2222 tile |= DF_DIVIDER_RIGHT;
2223
2224 error = ds->errtmp[sy*w+sx];
2225
2226 if (ds->tiles[y*w+x] != tile ||
2227 ds->pencil[y*w+x] != pencil ||
2228 ds->errors[y*w+x] != error) {
2229 ds->tiles[y*w+x] = tile;
2230 ds->pencil[y*w+x] = pencil;
2231 ds->errors[y*w+x] = error;
2232 draw_tile(dr, ds, x, y, tile, pencil, error);
2233 }
2234 }
2235 }
2236}
2237
2238static float game_anim_length(const game_state *oldstate,
2239 const game_state *newstate, int dir, game_ui *ui)
2240{
2241 return 0.0F;
2242}
2243
2244static float game_flash_length(const game_state *oldstate,
2245 const game_state *newstate, int dir, game_ui *ui)
2246{
2247 if (!oldstate->completed && newstate->completed &&
2248 !oldstate->cheated && !newstate->cheated)
2249 return FLASH_TIME;
2250 return 0.0F;
2251}
2252
2253static void game_get_cursor_location(const game_ui *ui,
2254 const game_drawstate *ds,
2255 const game_state *state,
2256 const game_params *params,
2257 int *x, int *y, int *w, int *h)
2258{
2259}
2260
2261static int game_status(const game_state *state)
2262{
2263 return state->completed ? +1 : 0;
2264}
2265
2266static bool game_timing_state(const game_state *state, game_ui *ui)
2267{
2268 if (state->completed)
2269 return false;
2270 return true;
2271}
2272
2273static void game_print_size(const game_params *params, const game_ui *ui,
2274 float *x, float *y)
2275{
2276 int pw, ph;
2277
2278 /*
2279 * We use 9mm squares by default, like Solo.
2280 */
2281 game_compute_size(params, 900, ui, &pw, &ph);
2282 *x = pw / 100.0F;
2283 *y = ph / 100.0F;
2284}
2285
2286static void game_print(drawing *dr, const game_state *state, const game_ui *ui,
2287 int tilesize)
2288{
2289 int w = state->par.w;
2290 int ink = print_mono_colour(dr, 0);
2291 int x, y;
2292
2293 /* Ick: fake up `ds->tilesize' for macro expansion purposes */
2294 game_drawstate ads, *ds = &ads;
2295 game_set_size(dr, ds, NULL, tilesize);
2296
2297 /*
2298 * Border.
2299 */
2300 print_line_width(dr, 3 * TILESIZE / 40);
2301 draw_rect_outline(dr, BORDER + LEGEND, BORDER + LEGEND,
2302 w*TILESIZE, w*TILESIZE, ink);
2303
2304 /*
2305 * Legend on table.
2306 */
2307 for (x = 0; x < w; x++) {
2308 char str[2];
2309 str[1] = '\0';
2310 str[0] = TOCHAR(x+1, state->par.id);
2311 draw_text(dr, BORDER+LEGEND + x*TILESIZE + TILESIZE/2,
2312 BORDER + TILESIZE/2,
2313 FONT_VARIABLE, TILESIZE/2,
2314 ALIGN_VCENTRE | ALIGN_HCENTRE, ink, str);
2315 draw_text(dr, BORDER + TILESIZE/2,
2316 BORDER+LEGEND + x*TILESIZE + TILESIZE/2,
2317 FONT_VARIABLE, TILESIZE/2,
2318 ALIGN_VCENTRE | ALIGN_HCENTRE, ink, str);
2319 }
2320
2321 /*
2322 * Main grid.
2323 */
2324 for (x = 1; x < w; x++) {
2325 print_line_width(dr, TILESIZE / 40);
2326 draw_line(dr, BORDER+LEGEND+x*TILESIZE, BORDER+LEGEND,
2327 BORDER+LEGEND+x*TILESIZE, BORDER+LEGEND+w*TILESIZE, ink);
2328 }
2329 for (y = 1; y < w; y++) {
2330 print_line_width(dr, TILESIZE / 40);
2331 draw_line(dr, BORDER+LEGEND, BORDER+LEGEND+y*TILESIZE,
2332 BORDER+LEGEND+w*TILESIZE, BORDER+LEGEND+y*TILESIZE, ink);
2333 }
2334
2335 /*
2336 * Numbers.
2337 */
2338 for (y = 0; y < w; y++)
2339 for (x = 0; x < w; x++)
2340 if (state->grid[y*w+x]) {
2341 char str[2];
2342 str[1] = '\0';
2343 str[0] = TOCHAR(state->grid[y*w+x], state->par.id);
2344 draw_text(dr, BORDER+LEGEND + x*TILESIZE + TILESIZE/2,
2345 BORDER+LEGEND + y*TILESIZE + TILESIZE/2,
2346 FONT_VARIABLE, TILESIZE/2,
2347 ALIGN_VCENTRE | ALIGN_HCENTRE, ink, str);
2348 }
2349}
2350
2351#ifdef COMBINED
2352#define thegame group
2353#endif
2354
2355const struct game thegame = {
2356 "Group", NULL, NULL,
2357 default_params,
2358 game_fetch_preset, NULL,
2359 decode_params,
2360 encode_params,
2361 free_params,
2362 dup_params,
2363 true, game_configure, custom_params,
2364 validate_params,
2365 new_game_desc,
2366 validate_desc,
2367 new_game,
2368 dup_game,
2369 free_game,
2370 true, solve_game,
2371 true, game_can_format_as_text_now, game_text_format,
2372 get_prefs, set_prefs,
2373 new_ui,
2374 free_ui,
2375 NULL, /* encode_ui */
2376 NULL, /* decode_ui */
2377 NULL, /* game_request_keys */
2378 game_changed_state,
2379 current_key_label,
2380 interpret_move,
2381 execute_move,
2382 PREFERRED_TILESIZE, game_compute_size, game_set_size,
2383 game_colours,
2384 game_new_drawstate,
2385 game_free_drawstate,
2386 game_redraw,
2387 game_anim_length,
2388 game_flash_length,
2389 game_get_cursor_location,
2390 game_status,
2391 true, false, game_print_size, game_print,
2392 false, /* wants_statusbar */
2393 false, game_timing_state,
2394 REQUIRE_RBUTTON | REQUIRE_NUMPAD, /* flags */
2395};
2396
2397#ifdef STANDALONE_SOLVER
2398
2399#include <stdarg.h>
2400
2401int main(int argc, char **argv)
2402{
2403 game_params *p;
2404 game_state *s;
2405 char *id = NULL, *desc;
2406 const char *err;
2407 digit *grid;
2408 bool grade = false;
2409 int ret, diff;
2410 bool really_show_working = false;
2411
2412 while (--argc > 0) {
2413 char *p = *++argv;
2414 if (!strcmp(p, "-v")) {
2415 really_show_working = true;
2416 } else if (!strcmp(p, "-g")) {
2417 grade = true;
2418 } else if (*p == '-') {
2419 fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
2420 return 1;
2421 } else {
2422 id = p;
2423 }
2424 }
2425
2426 if (!id) {
2427 fprintf(stderr, "usage: %s [-g | -v] <game_id>\n", argv[0]);
2428 return 1;
2429 }
2430
2431 desc = strchr(id, ':');
2432 if (!desc) {
2433 fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]);
2434 return 1;
2435 }
2436 *desc++ = '\0';
2437
2438 p = default_params();
2439 decode_params(p, id);
2440 err = validate_desc(p, desc);
2441 if (err) {
2442 fprintf(stderr, "%s: %s\n", argv[0], err);
2443 return 1;
2444 }
2445 s = new_game(NULL, p, desc);
2446
2447 grid = snewn(p->w * p->w, digit);
2448
2449 /*
2450 * When solving a Normal puzzle, we don't want to bother the
2451 * user with Hard-level deductions. For this reason, we grade
2452 * the puzzle internally before doing anything else.
2453 */
2454 ret = -1; /* placate optimiser */
2455 solver_show_working = 0;
2456 for (diff = 0; diff < DIFFCOUNT; diff++) {
2457 memcpy(grid, s->grid, p->w * p->w);
2458 ret = solver(&s->par, grid, diff);
2459 if (ret <= diff)
2460 break;
2461 }
2462
2463 if (diff == DIFFCOUNT) {
2464 if (really_show_working) {
2465 solver_show_working = true;
2466 memcpy(grid, s->grid, p->w * p->w);
2467 ret = solver(&s->par, grid, DIFFCOUNT - 1);
2468 }
2469 if (grade)
2470 printf("Difficulty rating: ambiguous\n");
2471 else
2472 printf("Unable to find a unique solution\n");
2473 } else {
2474 if (grade) {
2475 if (ret == diff_impossible)
2476 printf("Difficulty rating: impossible (no solution exists)\n");
2477 else
2478 printf("Difficulty rating: %s\n", group_diffnames[ret]);
2479 } else {
2480 solver_show_working = really_show_working;
2481 memcpy(grid, s->grid, p->w * p->w);
2482 ret = solver(&s->par, grid, diff);
2483 if (ret != diff)
2484 printf("Puzzle is inconsistent\n");
2485 else {
2486 memcpy(s->grid, grid, p->w * p->w);
2487 fputs(game_text_format(s), stdout);
2488 }
2489 }
2490 }
2491
2492 return 0;
2493}
2494
2495#endif
2496
2497/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/apps/plugins/puzzles/src/unfinished/group.gap b/apps/plugins/puzzles/src/unfinished/group.gap
new file mode 100644
index 0000000000..280adf4664
--- /dev/null
+++ b/apps/plugins/puzzles/src/unfinished/group.gap
@@ -0,0 +1,97 @@
1# run this file with
2# gap -b -q < /dev/null group.gap | perl -pe 's/\\\n//s' | indent -kr
3
4Print("/* ----- data generated by group.gap begins ----- */\n\n");
5Print("struct group {\n unsigned long autosize;\n");
6Print(" int order, ngens;\n const char *gens;\n};\n");
7Print("struct groups {\n int ngroups;\n");
8Print(" const struct group *groups;\n};\n\n");
9Print("static const struct group groupdata[] = {\n");
10offsets := [0];
11offset := 0;
12for n in [2..26] do
13 Print(" /* order ", n, " */\n");
14 for G in AllSmallGroups(n) do
15
16 # Construct a representation of the group G as a subgroup
17 # of a permutation group, and find its generators in that
18 # group.
19
20 # GAP has the 'IsomorphismPermGroup' function, but I don't want
21 # to use it because it doesn't guarantee that the permutation
22 # representation of the group forms a Cayley table. For example,
23 # C_4 could be represented as a subgroup of S_4 in many ways,
24 # and not all of them work: the group generated by (12) and (34)
25 # is clearly isomorphic to C_4 but its four elements do not form
26 # a Cayley table. The group generated by (12)(34) and (13)(24)
27 # is OK, though.
28 #
29 # Hence I construct the permutation representation _as_ the
30 # Cayley table, and then pick generators of that. This
31 # guarantees that when we rebuild the full group by BFS in
32 # group.c, we will end up with the right thing.
33
34 ge := Elements(G);
35 gi := [];
36 for g in ge do
37 gr := [];
38 for h in ge do
39 k := g*h;
40 for i in [1..n] do
41 if k = ge[i] then
42 Add(gr, i);
43 fi;
44 od;
45 od;
46 Add(gi, PermList(gr));
47 od;
48
49 # GAP has the 'GeneratorsOfGroup' function, but we don't want to
50 # use it because it's bad at picking generators - it thinks the
51 # generators of C_4 are [ (1,2)(3,4), (1,3,2,4) ] and that those
52 # of C_6 are [ (1,2,3)(4,5,6), (1,4)(2,5)(3,6) ] !
53
54 gl := ShallowCopy(Elements(gi));
55 Sort(gl, function(v,w) return Order(v) > Order(w); end);
56
57 gens := [];
58 for x in gl do
59 if gens = [] or not (x in gp) then
60 Add(gens, x);
61 gp := GroupWithGenerators(gens);
62 fi;
63 od;
64
65 # Construct the C representation of the group generators.
66 s := [];
67 for x in gens do
68 if Size(s) > 0 then
69 Add(s, '"');
70 Add(s, ' ');
71 Add(s, '"');
72 fi;
73 sep := "\\0";
74 for i in ListPerm(x) do
75 chars := "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
76 Add(s, chars[i]);
77 od;
78 od;
79 s := JoinStringsWithSeparator([" {", String(Size(AutomorphismGroup(G))),
80 "L, ", String(Size(G)),
81 ", ", String(Size(gens)),
82 ", \"", s, "\"},\n"],"");
83 Print(s);
84 offset := offset + 1;
85 od;
86 Add(offsets, offset);
87od;
88Print("};\n\nstatic const struct groups groups[] = {\n");
89Print(" {0, NULL}, /* trivial case: 0 */\n");
90Print(" {0, NULL}, /* trivial case: 1 */\n");
91n := 2;
92for i in [1..Size(offsets)-1] do
93 Print(" {", offsets[i+1] - offsets[i], ", groupdata+",
94 offsets[i], "}, /* ", i+1, " */\n");
95od;
96Print("};\n\n/* ----- data generated by group.gap ends ----- */\n");
97quit;
diff --git a/apps/plugins/puzzles/src/unfinished/numgame.c b/apps/plugins/puzzles/src/unfinished/numgame.c
new file mode 100644
index 0000000000..cf919922e7
--- /dev/null
+++ b/apps/plugins/puzzles/src/unfinished/numgame.c
@@ -0,0 +1,1294 @@
1/*
2 * This program implements a breadth-first search which
3 * exhaustively solves the Countdown numbers game, and related
4 * games with slightly different rule sets such as `Flippo'.
5 *
6 * Currently it is simply a standalone command-line utility to
7 * which you provide a set of numbers and it tells you everything
8 * it can make together with how many different ways it can be
9 * made. I would like ultimately to turn it into the generator for
10 * a Puzzles puzzle, but I haven't even started on writing a
11 * Puzzles user interface yet.
12 */
13
14/*
15 * TODO:
16 *
17 * - start thinking about difficulty ratings
18 * + anything involving associative operations will be flagged
19 * as many-paths because of the associative options (e.g.
20 * 2*3*4 can be (2*3)*4 or 2*(3*4), or indeed (2*4)*3). This
21 * is probably a _good_ thing, since those are unusually
22 * easy.
23 * + tree-structured calculations ((a*b)/(c+d)) have multiple
24 * paths because the independent branches of the tree can be
25 * evaluated in either order, whereas straight-line
26 * calculations with no branches will be considered easier.
27 * Can we do anything about this? It's certainly not clear to
28 * me that tree-structure calculations are _easier_, although
29 * I'm also not convinced they're harder.
30 * + I think for a realistic difficulty assessment we must also
31 * consider the `obviousness' of the arithmetic operations in
32 * some heuristic sense, and also (in Countdown) how many
33 * numbers ended up being used.
34 * - actually try some generations
35 * - at this point we're probably ready to start on the Puzzles
36 * integration.
37 */
38
39#include <stdio.h>
40#include <string.h>
41#include <limits.h>
42#include <assert.h>
43#ifdef NO_TGMATH_H
44# include <math.h>
45#else
46# include <tgmath.h>
47#endif
48
49#include "puzzles.h"
50#include "tree234.h"
51
52/*
53 * To search for numbers we can make, we employ a breadth-first
54 * search across the space of sets of input numbers. That is, for
55 * example, we start with the set (3,6,25,50,75,100); we apply
56 * moves which involve combining two numbers (e.g. adding the 50
57 * and the 75 takes us to the set (3,6,25,100,125); and then we see
58 * if we ever end up with a set containing (say) 952.
59 *
60 * If the rules are changed so that all the numbers must be used,
61 * this is easy to adjust to: we simply see if we end up with a set
62 * containing _only_ (say) 952.
63 *
64 * Obviously, we can vary the rules about permitted arithmetic
65 * operations simply by altering the set of valid moves in the bfs.
66 * However, there's one common rule in this sort of puzzle which
67 * takes a little more thought, and that's _concatenation_. For
68 * example, if you are given (say) four 4s and required to make 10,
69 * you are permitted to combine two of the 4s into a 44 to begin
70 * with, making (44-4)/4 = 10. However, you are generally not
71 * allowed to concatenate two numbers that _weren't_ both in the
72 * original input set (you couldn't multiply two 4s to get 16 and
73 * then concatenate a 4 on to it to make 164), so concatenation is
74 * not an operation which is valid in all situations.
75 *
76 * We could enforce this restriction by storing a flag alongside
77 * each number indicating whether or not it's an original number;
78 * the rules being that concatenation of two numbers is only valid
79 * if they both have the original flag, and that its output _also_
80 * has the original flag (so that you can concatenate three 4s into
81 * a 444), but that applying any other arithmetic operation clears
82 * the original flag on the output. However, we can get marginally
83 * simpler than that by observing that since concatenation has to
84 * happen to a number before any other operation, we can simply
85 * place all the concatenations at the start of the search. In
86 * other words, we have a global flag on an entire number _set_
87 * which indicates whether we are still permitted to perform
88 * concatenations; if so, we can concatenate any of the numbers in
89 * that set. Performing any other operation clears the flag.
90 */
91
92#define SETFLAG_CONCAT 1 /* we can do concatenation */
93
94struct sets;
95
96struct ancestor {
97 struct set *prev; /* index of ancestor set in set list */
98 unsigned char pa, pb, po, pr; /* operation that got here from prev */
99};
100
101struct set {
102 int *numbers; /* rationals stored as n,d pairs */
103 short nnumbers; /* # of rationals, so half # of ints */
104 short flags; /* SETFLAG_CONCAT only, at present */
105 int npaths; /* number of ways to reach this set */
106 struct ancestor a; /* primary ancestor */
107 struct ancestor *as; /* further ancestors, if we care */
108 int nas, assize;
109};
110
111struct output {
112 int number;
113 struct set *set;
114 int index; /* which number in the set is it? */
115 int npaths; /* number of ways to reach this */
116};
117
118#define SETLISTLEN 1024
119#define NUMBERLISTLEN 32768
120#define OUTPUTLISTLEN 1024
121struct operation;
122struct sets {
123 struct set **setlists;
124 int nsets, nsetlists, setlistsize;
125 tree234 *settree;
126 int **numberlists;
127 int nnumbers, nnumberlists, numberlistsize;
128 struct output **outputlists;
129 int noutputs, noutputlists, outputlistsize;
130 tree234 *outputtree;
131 const struct operation *const *ops;
132};
133
134#define OPFLAG_NEEDS_CONCAT 1
135#define OPFLAG_KEEPS_CONCAT 2
136#define OPFLAG_UNARY 4
137#define OPFLAG_UNARYPREFIX 8
138#define OPFLAG_FN 16
139
140struct operation {
141 /*
142 * Most operations should be shown in the output working, but
143 * concatenation should not; we just take the result of the
144 * concatenation and assume that it's obvious how it was
145 * derived.
146 */
147 int display;
148
149 /*
150 * Text display of the operator, in expressions and for
151 * debugging respectively.
152 */
153 const char *text, *dbgtext;
154
155 /*
156 * Flags dictating when the operator can be applied.
157 */
158 int flags;
159
160 /*
161 * Priority of the operator (for avoiding unnecessary
162 * parentheses when formatting it into a string).
163 */
164 int priority;
165
166 /*
167 * Associativity of the operator. Bit 0 means we need parens
168 * when the left operand of one of these operators is another
169 * instance of it, e.g. (2^3)^4. Bit 1 means we need parens
170 * when the right operand is another instance of the same
171 * operator, e.g. 2-(3-4). Thus:
172 *
173 * - this field is 0 for a fully associative operator, since
174 * we never need parens.
175 * - it's 1 for a right-associative operator.
176 * - it's 2 for a left-associative operator.
177 * - it's 3 for a _non_-associative operator (which always
178 * uses parens just to be sure).
179 */
180 int assoc;
181
182 /*
183 * Whether the operator is commutative. Saves time in the
184 * search if we don't have to try it both ways round.
185 */
186 int commutes;
187
188 /*
189 * Function which implements the operator. Returns true on
190 * success, false on failure. Takes two rationals and writes
191 * out a third.
192 */
193 int (*perform)(int *a, int *b, int *output);
194};
195
196struct rules {
197 const struct operation *const *ops;
198 int use_all;
199};
200
201#define MUL(r, a, b) do { \
202 (r) = (a) * (b); \
203 if ((b) && (a) && (r) / (b) != (a)) return false; \
204} while (0)
205
206#define ADD(r, a, b) do { \
207 (r) = (a) + (b); \
208 if ((a) > 0 && (b) > 0 && (r) < 0) return false; \
209 if ((a) < 0 && (b) < 0 && (r) > 0) return false; \
210} while (0)
211
212#define OUT(output, n, d) do { \
213 int g = gcd((n),(d)); \
214 if (g < 0) g = -g; \
215 if ((d) < 0) g = -g; \
216 if (g == -1 && (n) < -INT_MAX) return false; \
217 if (g == -1 && (d) < -INT_MAX) return false; \
218 (output)[0] = (n)/g; \
219 (output)[1] = (d)/g; \
220 assert((output)[1] > 0); \
221} while (0)
222
223static int gcd(int x, int y)
224{
225 while (x != 0 && y != 0) {
226 int t = x;
227 x = y;
228 y = t % y;
229 }
230
231 return abs(x + y); /* i.e. whichever one isn't zero */
232}
233
234static int perform_add(int *a, int *b, int *output)
235{
236 int at, bt, tn, bn;
237 /*
238 * a0/a1 + b0/b1 = (a0*b1 + b0*a1) / (a1*b1)
239 */
240 MUL(at, a[0], b[1]);
241 MUL(bt, b[0], a[1]);
242 ADD(tn, at, bt);
243 MUL(bn, a[1], b[1]);
244 OUT(output, tn, bn);
245 return true;
246}
247
248static int perform_sub(int *a, int *b, int *output)
249{
250 int at, bt, tn, bn;
251 /*
252 * a0/a1 - b0/b1 = (a0*b1 - b0*a1) / (a1*b1)
253 */
254 MUL(at, a[0], b[1]);
255 MUL(bt, b[0], a[1]);
256 ADD(tn, at, -bt);
257 MUL(bn, a[1], b[1]);
258 OUT(output, tn, bn);
259 return true;
260}
261
262static int perform_mul(int *a, int *b, int *output)
263{
264 int tn, bn;
265 /*
266 * a0/a1 * b0/b1 = (a0*b0) / (a1*b1)
267 */
268 MUL(tn, a[0], b[0]);
269 MUL(bn, a[1], b[1]);
270 OUT(output, tn, bn);
271 return true;
272}
273
274static int perform_div(int *a, int *b, int *output)
275{
276 int tn, bn;
277
278 /*
279 * Division by zero is outlawed.
280 */
281 if (b[0] == 0)
282 return false;
283
284 /*
285 * a0/a1 / b0/b1 = (a0*b1) / (a1*b0)
286 */
287 MUL(tn, a[0], b[1]);
288 MUL(bn, a[1], b[0]);
289 OUT(output, tn, bn);
290 return true;
291}
292
293static int perform_exact_div(int *a, int *b, int *output)
294{
295 int tn, bn;
296
297 /*
298 * Division by zero is outlawed.
299 */
300 if (b[0] == 0)
301 return false;
302
303 /*
304 * a0/a1 / b0/b1 = (a0*b1) / (a1*b0)
305 */
306 MUL(tn, a[0], b[1]);
307 MUL(bn, a[1], b[0]);
308 OUT(output, tn, bn);
309
310 /*
311 * Exact division means we require the result to be an integer.
312 */
313 return (output[1] == 1);
314}
315
316static int max_p10(int n, int *p10_r)
317{
318 /*
319 * Find the smallest power of ten strictly greater than n.
320 *
321 * Special case: we must return at least 10, even if n is
322 * zero. (This is because this function is used for finding
323 * the power of ten by which to multiply a number being
324 * concatenated to the front of n, and concatenating 1 to 0
325 * should yield 10 and not 1.)
326 */
327 int p10 = 10;
328 while (p10 <= (INT_MAX/10) && p10 <= n)
329 p10 *= 10;
330 if (p10 > INT_MAX/10)
331 return false; /* integer overflow */
332 *p10_r = p10;
333 return true;
334}
335
336static int perform_concat(int *a, int *b, int *output)
337{
338 int t1, t2, p10;
339
340 /*
341 * We can't concatenate anything which isn't a non-negative
342 * integer.
343 */
344 if (a[1] != 1 || b[1] != 1 || a[0] < 0 || b[0] < 0)
345 return false;
346
347 /*
348 * For concatenation, we can safely assume leading zeroes
349 * aren't an issue. It isn't clear whether they `should' be
350 * allowed, but it turns out not to matter: concatenating a
351 * leading zero on to a number in order to harmlessly get rid
352 * of the zero is never necessary because unwanted zeroes can
353 * be disposed of by adding them to something instead. So we
354 * disallow them always.
355 *
356 * The only other possibility is that you might want to
357 * concatenate a leading zero on to something and then
358 * concatenate another non-zero digit on to _that_ (to make,
359 * for example, 106); but that's also unnecessary, because you
360 * can make 106 just as easily by concatenating the 0 on to the
361 * _end_ of the 1 first.
362 */
363 if (a[0] == 0)
364 return false;
365
366 if (!max_p10(b[0], &p10)) return false;
367
368 MUL(t1, p10, a[0]);
369 ADD(t2, t1, b[0]);
370 OUT(output, t2, 1);
371 return true;
372}
373
374#define IPOW(ret, x, y) do { \
375 int ipow_limit = (y); \
376 if ((x) == 1 || (x) == 0) ipow_limit = 1; \
377 else if ((x) == -1) ipow_limit &= 1; \
378 (ret) = 1; \
379 while (ipow_limit-- > 0) { \
380 int tmp; \
381 MUL(tmp, ret, x); \
382 ret = tmp; \
383 } \
384} while (0)
385
386static int perform_exp(int *a, int *b, int *output)
387{
388 int an, ad, xn, xd;
389
390 /*
391 * Exponentiation is permitted if the result is rational. This
392 * means that:
393 *
394 * - first we see whether we can take the (denominator-of-b)th
395 * root of a and get a rational; if not, we give up.
396 *
397 * - then we do take that root of a
398 *
399 * - then we multiply by itself (numerator-of-b) times.
400 */
401 if (b[1] > 1) {
402 an = (int)(0.5 + pow(a[0], 1.0/b[1]));
403 ad = (int)(0.5 + pow(a[1], 1.0/b[1]));
404 IPOW(xn, an, b[1]);
405 IPOW(xd, ad, b[1]);
406 if (xn != a[0] || xd != a[1])
407 return false;
408 } else {
409 an = a[0];
410 ad = a[1];
411 }
412 if (b[0] >= 0) {
413 IPOW(xn, an, b[0]);
414 IPOW(xd, ad, b[0]);
415 } else {
416 IPOW(xd, an, -b[0]);
417 IPOW(xn, ad, -b[0]);
418 }
419 if (xd == 0)
420 return false;
421
422 OUT(output, xn, xd);
423 return true;
424}
425
426static int perform_factorial(int *a, int *b, int *output)
427{
428 int ret, t, i;
429
430 /*
431 * Factorials of non-negative integers are permitted.
432 */
433 if (a[1] != 1 || a[0] < 0)
434 return false;
435
436 /*
437 * However, a special case: we don't take a factorial of
438 * anything which would thereby remain the same.
439 */
440 if (a[0] == 1 || a[0] == 2)
441 return false;
442
443 ret = 1;
444 for (i = 1; i <= a[0]; i++) {
445 MUL(t, ret, i);
446 ret = t;
447 }
448
449 OUT(output, ret, 1);
450 return true;
451}
452
453static int perform_decimal(int *a, int *b, int *output)
454{
455 int p10;
456
457 /*
458 * Add a decimal digit to the front of a number;
459 * fail if it's not an integer.
460 * So, 1 --> 0.1, 15 --> 0.15,
461 * or, rather, 1 --> 1/10, 15 --> 15/100,
462 * x --> x / (smallest power of 10 > than x)
463 *
464 */
465 if (a[1] != 1) return false;
466
467 if (!max_p10(a[0], &p10)) return false;
468
469 OUT(output, a[0], p10);
470 return true;
471}
472
473static int perform_recur(int *a, int *b, int *output)
474{
475 int p10, tn, bn;
476
477 /*
478 * This converts a number like .4 to .44444..., or .45 to .45454...
479 * The input number must be -1 < a < 1.
480 *
481 * Calculate the smallest power of 10 that divides the denominator exactly,
482 * returning if no such power of 10 exists. Then multiply the numerator
483 * up accordingly, and the new denominator becomes that power of 10 - 1.
484 */
485 if (abs(a[0]) >= abs(a[1])) return false; /* -1 < a < 1 */
486
487 p10 = 10;
488 while (p10 <= (INT_MAX/10)) {
489 if ((a[1] <= p10) && (p10 % a[1]) == 0) goto found;
490 p10 *= 10;
491 }
492 return false;
493found:
494 tn = a[0] * (p10 / a[1]);
495 bn = p10 - 1;
496
497 OUT(output, tn, bn);
498 return true;
499}
500
501static int perform_root(int *a, int *b, int *output)
502{
503 /*
504 * A root B is: 1 iff a == 0
505 * B ^ (1/A) otherwise
506 */
507 int ainv[2], res;
508
509 if (a[0] == 0) {
510 OUT(output, 1, 1);
511 return true;
512 }
513
514 OUT(ainv, a[1], a[0]);
515 res = perform_exp(b, ainv, output);
516 return res;
517}
518
519static int perform_perc(int *a, int *b, int *output)
520{
521 if (a[0] == 0) return false; /* 0% = 0, uninteresting. */
522 if (a[1] > (INT_MAX/100)) return false;
523
524 OUT(output, a[0], a[1]*100);
525 return true;
526}
527
528static int perform_gamma(int *a, int *b, int *output)
529{
530 int asub1[2];
531
532 /*
533 * gamma(a) = (a-1)!
534 *
535 * special case not caught by perform_fact: gamma(1) is 1 so
536 * don't bother.
537 */
538 if (a[0] == 1 && a[1] == 1) return false;
539
540 OUT(asub1, a[0]-a[1], a[1]);
541 return perform_factorial(asub1, b, output);
542}
543
544static int perform_sqrt(int *a, int *b, int *output)
545{
546 int half[2] = { 1, 2 };
547
548 /*
549 * sqrt(0) == 0, sqrt(1) == 1: don't perform unary noops.
550 */
551 if (a[0] == 0 || (a[0] == 1 && a[1] == 1)) return false;
552
553 return perform_exp(a, half, output);
554}
555
556static const struct operation op_add = {
557 true, "+", "+", 0, 10, 0, true, perform_add
558};
559static const struct operation op_sub = {
560 true, "-", "-", 0, 10, 2, false, perform_sub
561};
562static const struct operation op_mul = {
563 true, "*", "*", 0, 20, 0, true, perform_mul
564};
565static const struct operation op_div = {
566 true, "/", "/", 0, 20, 2, false, perform_div
567};
568static const struct operation op_xdiv = {
569 true, "/", "/", 0, 20, 2, false, perform_exact_div
570};
571static const struct operation op_concat = {
572 false, "", "concat", OPFLAG_NEEDS_CONCAT | OPFLAG_KEEPS_CONCAT,
573 1000, 0, false, perform_concat
574};
575static const struct operation op_exp = {
576 true, "^", "^", 0, 30, 1, false, perform_exp
577};
578static const struct operation op_factorial = {
579 true, "!", "!", OPFLAG_UNARY, 40, 0, false, perform_factorial
580};
581static const struct operation op_decimal = {
582 true, ".", ".", OPFLAG_UNARY | OPFLAG_UNARYPREFIX | OPFLAG_NEEDS_CONCAT | OPFLAG_KEEPS_CONCAT, 50, 0, false, perform_decimal
583};
584static const struct operation op_recur = {
585 true, "...", "recur", OPFLAG_UNARY | OPFLAG_NEEDS_CONCAT, 45, 2, false, perform_recur
586};
587static const struct operation op_root = {
588 true, "v~", "root", 0, 30, 1, false, perform_root
589};
590static const struct operation op_perc = {
591 true, "%", "%", OPFLAG_UNARY | OPFLAG_NEEDS_CONCAT, 45, 1, false, perform_perc
592};
593static const struct operation op_gamma = {
594 true, "gamma", "gamma", OPFLAG_UNARY | OPFLAG_UNARYPREFIX | OPFLAG_FN, 1, 3, false, perform_gamma
595};
596static const struct operation op_sqrt = {
597 true, "v~", "sqrt", OPFLAG_UNARY | OPFLAG_UNARYPREFIX, 30, 1, false, perform_sqrt
598};
599
600/*
601 * In Countdown, divisions resulting in fractions are disallowed.
602 * http://www.askoxford.com/wordgames/countdown/rules/
603 */
604static const struct operation *const ops_countdown[] = {
605 &op_add, &op_mul, &op_sub, &op_xdiv, NULL
606};
607static const struct rules rules_countdown = {
608 ops_countdown, false
609};
610
611/*
612 * A slightly different rule set which handles the reasonably well
613 * known puzzle of making 24 using two 3s and two 8s. For this we
614 * need rational rather than integer division.
615 */
616static const struct operation *const ops_3388[] = {
617 &op_add, &op_mul, &op_sub, &op_div, NULL
618};
619static const struct rules rules_3388 = {
620 ops_3388, true
621};
622
623/*
624 * A still more permissive rule set usable for the four-4s problem
625 * and similar things. Permits concatenation.
626 */
627static const struct operation *const ops_four4s[] = {
628 &op_add, &op_mul, &op_sub, &op_div, &op_concat, NULL
629};
630static const struct rules rules_four4s = {
631 ops_four4s, true
632};
633
634/*
635 * The most permissive ruleset I can think of. Permits
636 * exponentiation, and also silly unary operators like factorials.
637 */
638static const struct operation *const ops_anythinggoes[] = {
639 &op_add, &op_mul, &op_sub, &op_div, &op_concat, &op_exp, &op_factorial,
640 &op_decimal, &op_recur, &op_root, &op_perc, &op_gamma, &op_sqrt, NULL
641};
642static const struct rules rules_anythinggoes = {
643 ops_anythinggoes, true
644};
645
646#define ratcmp(a,op,b) ( (long long)(a)[0] * (b)[1] op \
647 (long long)(b)[0] * (a)[1] )
648
649static int addtoset(struct set *set, int newnumber[2])
650{
651 int i, j;
652
653 /* Find where we want to insert the new number */
654 for (i = 0; i < set->nnumbers &&
655 ratcmp(set->numbers+2*i, <, newnumber); i++);
656
657 /* Move everything else up */
658 for (j = set->nnumbers; j > i; j--) {
659 set->numbers[2*j] = set->numbers[2*j-2];
660 set->numbers[2*j+1] = set->numbers[2*j-1];
661 }
662
663 /* Insert the new number */
664 set->numbers[2*i] = newnumber[0];
665 set->numbers[2*i+1] = newnumber[1];
666
667 set->nnumbers++;
668
669 return i;
670}
671
672#define ensure(array, size, newlen, type) do { \
673 if ((newlen) > (size)) { \
674 (size) = (newlen) + 512; \
675 (array) = sresize((array), (size), type); \
676 } \
677} while (0)
678
679static int setcmp(void *av, void *bv)
680{
681 struct set *a = (struct set *)av;
682 struct set *b = (struct set *)bv;
683 int i;
684
685 if (a->nnumbers < b->nnumbers)
686 return -1;
687 else if (a->nnumbers > b->nnumbers)
688 return +1;
689
690 if (a->flags < b->flags)
691 return -1;
692 else if (a->flags > b->flags)
693 return +1;
694
695 for (i = 0; i < a->nnumbers; i++) {
696 if (ratcmp(a->numbers+2*i, <, b->numbers+2*i))
697 return -1;
698 else if (ratcmp(a->numbers+2*i, >, b->numbers+2*i))
699 return +1;
700 }
701
702 return 0;
703}
704
705static int outputcmp(void *av, void *bv)
706{
707 struct output *a = (struct output *)av;
708 struct output *b = (struct output *)bv;
709
710 if (a->number < b->number)
711 return -1;
712 else if (a->number > b->number)
713 return +1;
714
715 return 0;
716}
717
718static int outputfindcmp(void *av, void *bv)
719{
720 int *a = (int *)av;
721 struct output *b = (struct output *)bv;
722
723 if (*a < b->number)
724 return -1;
725 else if (*a > b->number)
726 return +1;
727
728 return 0;
729}
730
731static void addset(struct sets *s, struct set *set, int multiple,
732 struct set *prev, int pa, int po, int pb, int pr)
733{
734 struct set *s2;
735 int npaths = (prev ? prev->npaths : 1);
736
737 assert(set == s->setlists[s->nsets / SETLISTLEN] + s->nsets % SETLISTLEN);
738 s2 = add234(s->settree, set);
739 if (s2 == set) {
740 /*
741 * New set added to the tree.
742 */
743 set->a.prev = prev;
744 set->a.pa = pa;
745 set->a.po = po;
746 set->a.pb = pb;
747 set->a.pr = pr;
748 set->npaths = npaths;
749 s->nsets++;
750 s->nnumbers += 2 * set->nnumbers;
751 set->as = NULL;
752 set->nas = set->assize = 0;
753 } else {
754 /*
755 * Rediscovered an existing set. Update its npaths.
756 */
757 s2->npaths += npaths;
758 /*
759 * And optionally enter it as an additional ancestor.
760 */
761 if (multiple) {
762 if (s2->nas >= s2->assize) {
763 s2->assize = s2->nas * 3 / 2 + 4;
764 s2->as = sresize(s2->as, s2->assize, struct ancestor);
765 }
766 s2->as[s2->nas].prev = prev;
767 s2->as[s2->nas].pa = pa;
768 s2->as[s2->nas].po = po;
769 s2->as[s2->nas].pb = pb;
770 s2->as[s2->nas].pr = pr;
771 s2->nas++;
772 }
773 }
774}
775
776static struct set *newset(struct sets *s, int nnumbers, int flags)
777{
778 struct set *sn;
779
780 ensure(s->setlists, s->setlistsize, s->nsets/SETLISTLEN+1, struct set *);
781 while (s->nsetlists <= s->nsets / SETLISTLEN)
782 s->setlists[s->nsetlists++] = snewn(SETLISTLEN, struct set);
783 sn = s->setlists[s->nsets / SETLISTLEN] + s->nsets % SETLISTLEN;
784
785 if (s->nnumbers + nnumbers * 2 > s->nnumberlists * NUMBERLISTLEN)
786 s->nnumbers = s->nnumberlists * NUMBERLISTLEN;
787 ensure(s->numberlists, s->numberlistsize,
788 s->nnumbers/NUMBERLISTLEN+1, int *);
789 while (s->nnumberlists <= s->nnumbers / NUMBERLISTLEN)
790 s->numberlists[s->nnumberlists++] = snewn(NUMBERLISTLEN, int);
791 sn->numbers = s->numberlists[s->nnumbers / NUMBERLISTLEN] +
792 s->nnumbers % NUMBERLISTLEN;
793
794 /*
795 * Start the set off empty.
796 */
797 sn->nnumbers = 0;
798
799 sn->flags = flags;
800
801 return sn;
802}
803
804static int addoutput(struct sets *s, struct set *ss, int index, int *n)
805{
806 struct output *o, *o2;
807
808 /*
809 * Target numbers are always integers.
810 */
811 if (ss->numbers[2*index+1] != 1)
812 return false;
813
814 ensure(s->outputlists, s->outputlistsize, s->noutputs/OUTPUTLISTLEN+1,
815 struct output *);
816 while (s->noutputlists <= s->noutputs / OUTPUTLISTLEN)
817 s->outputlists[s->noutputlists++] = snewn(OUTPUTLISTLEN,
818 struct output);
819 o = s->outputlists[s->noutputs / OUTPUTLISTLEN] +
820 s->noutputs % OUTPUTLISTLEN;
821
822 o->number = ss->numbers[2*index];
823 o->set = ss;
824 o->index = index;
825 o->npaths = ss->npaths;
826 o2 = add234(s->outputtree, o);
827 if (o2 != o) {
828 o2->npaths += o->npaths;
829 } else {
830 s->noutputs++;
831 }
832 *n = o->number;
833 return true;
834}
835
836static struct sets *do_search(int ninputs, int *inputs,
837 const struct rules *rules, int *target,
838 int debug, int multiple)
839{
840 struct sets *s;
841 struct set *sn;
842 int qpos, i;
843 const struct operation *const *ops = rules->ops;
844
845 s = snew(struct sets);
846 s->setlists = NULL;
847 s->nsets = s->nsetlists = s->setlistsize = 0;
848 s->numberlists = NULL;
849 s->nnumbers = s->nnumberlists = s->numberlistsize = 0;
850 s->outputlists = NULL;
851 s->noutputs = s->noutputlists = s->outputlistsize = 0;
852 s->settree = newtree234(setcmp);
853 s->outputtree = newtree234(outputcmp);
854 s->ops = ops;
855
856 /*
857 * Start with the input set.
858 */
859 sn = newset(s, ninputs, SETFLAG_CONCAT);
860 for (i = 0; i < ninputs; i++) {
861 int newnumber[2];
862 newnumber[0] = inputs[i];
863 newnumber[1] = 1;
864 addtoset(sn, newnumber);
865 }
866 addset(s, sn, multiple, NULL, 0, 0, 0, 0);
867
868 /*
869 * Now perform the breadth-first search: keep looping over sets
870 * until we run out of steam.
871 */
872 qpos = 0;
873 while (qpos < s->nsets) {
874 struct set *ss = s->setlists[qpos / SETLISTLEN] + qpos % SETLISTLEN;
875 struct set *sn;
876 int i, j, k, m;
877
878 if (debug) {
879 int i;
880 printf("processing set:");
881 for (i = 0; i < ss->nnumbers; i++) {
882 printf(" %d", ss->numbers[2*i]);
883 if (ss->numbers[2*i+1] != 1)
884 printf("/%d", ss->numbers[2*i+1]);
885 }
886 printf("\n");
887 }
888
889 /*
890 * Record all the valid output numbers in this state. We
891 * can always do this if there's only one number in the
892 * state; otherwise, we can only do it if we aren't
893 * required to use all the numbers in coming to our answer.
894 */
895 if (ss->nnumbers == 1 || !rules->use_all) {
896 for (i = 0; i < ss->nnumbers; i++) {
897 int n;
898
899 if (addoutput(s, ss, i, &n) && target && n == *target)
900 return s;
901 }
902 }
903
904 /*
905 * Try every possible operation from this state.
906 */
907 for (k = 0; ops[k] && ops[k]->perform; k++) {
908 if ((ops[k]->flags & OPFLAG_NEEDS_CONCAT) &&
909 !(ss->flags & SETFLAG_CONCAT))
910 continue; /* can't use this operation here */
911 for (i = 0; i < ss->nnumbers; i++) {
912 int jlimit = (ops[k]->flags & OPFLAG_UNARY ? 1 : ss->nnumbers);
913 for (j = 0; j < jlimit; j++) {
914 int n[2], newnn = ss->nnumbers;
915 int pa, po, pb, pr;
916
917 if (!(ops[k]->flags & OPFLAG_UNARY)) {
918 if (i == j)
919 continue; /* can't combine a number with itself */
920 if (i > j && ops[k]->commutes)
921 continue; /* no need to do this both ways round */
922 newnn--;
923 }
924 if (!ops[k]->perform(ss->numbers+2*i, ss->numbers+2*j, n))
925 continue; /* operation failed */
926
927 sn = newset(s, newnn, ss->flags);
928
929 if (!(ops[k]->flags & OPFLAG_KEEPS_CONCAT))
930 sn->flags &= ~SETFLAG_CONCAT;
931
932 for (m = 0; m < ss->nnumbers; m++) {
933 if (m == i || (!(ops[k]->flags & OPFLAG_UNARY) &&
934 m == j))
935 continue;
936 sn->numbers[2*sn->nnumbers] = ss->numbers[2*m];
937 sn->numbers[2*sn->nnumbers + 1] = ss->numbers[2*m + 1];
938 sn->nnumbers++;
939 }
940 pa = i;
941 if (ops[k]->flags & OPFLAG_UNARY)
942 pb = sn->nnumbers+10;
943 else
944 pb = j;
945 po = k;
946 pr = addtoset(sn, n);
947 addset(s, sn, multiple, ss, pa, po, pb, pr);
948 if (debug) {
949 int i;
950 if (ops[k]->flags & OPFLAG_UNARYPREFIX)
951 printf(" %s %d ->", ops[po]->dbgtext, pa);
952 else if (ops[k]->flags & OPFLAG_UNARY)
953 printf(" %d %s ->", pa, ops[po]->dbgtext);
954 else
955 printf(" %d %s %d ->", pa, ops[po]->dbgtext, pb);
956 for (i = 0; i < sn->nnumbers; i++) {
957 printf(" %d", sn->numbers[2*i]);
958 if (sn->numbers[2*i+1] != 1)
959 printf("/%d", sn->numbers[2*i+1]);
960 }
961 printf("\n");
962 }
963 }
964 }
965 }
966
967 qpos++;
968 }
969
970 return s;
971}
972
973static void free_sets(struct sets *s)
974{
975 int i;
976
977 freetree234(s->settree);
978 freetree234(s->outputtree);
979 for (i = 0; i < s->nsetlists; i++)
980 sfree(s->setlists[i]);
981 sfree(s->setlists);
982 for (i = 0; i < s->nnumberlists; i++)
983 sfree(s->numberlists[i]);
984 sfree(s->numberlists);
985 for (i = 0; i < s->noutputlists; i++)
986 sfree(s->outputlists[i]);
987 sfree(s->outputlists);
988 sfree(s);
989}
990
991/*
992 * Print a text formula for producing a given output.
993 */
994static void print_recurse(struct sets *s, struct set *ss, int pathindex,
995 int index, int priority, int assoc, int child);
996static void print_recurse_inner(struct sets *s, struct set *ss,
997 struct ancestor *a, int pathindex, int index,
998 int priority, int assoc, int child)
999{
1000 if (a->prev && index != a->pr) {
1001 int pi;
1002
1003 /*
1004 * This number was passed straight down from this set's
1005 * predecessor. Find its index in the previous set and
1006 * recurse to there.
1007 */
1008 pi = index;
1009 assert(pi != a->pr);
1010 if (pi > a->pr)
1011 pi--;
1012 if (pi >= min(a->pa, a->pb)) {
1013 pi++;
1014 if (pi >= max(a->pa, a->pb))
1015 pi++;
1016 }
1017 print_recurse(s, a->prev, pathindex, pi, priority, assoc, child);
1018 } else if (a->prev && index == a->pr &&
1019 s->ops[a->po]->display) {
1020 /*
1021 * This number was created by a displayed operator in the
1022 * transition from this set to its predecessor. Hence we
1023 * write an open paren, then recurse into the first
1024 * operand, then write the operator, then the second
1025 * operand, and finally close the paren.
1026 */
1027 const char *op;
1028 int parens, thispri, thisassoc;
1029
1030 /*
1031 * Determine whether we need parentheses.
1032 */
1033 thispri = s->ops[a->po]->priority;
1034 thisassoc = s->ops[a->po]->assoc;
1035 parens = (thispri < priority ||
1036 (thispri == priority && (assoc & child)));
1037
1038 if (parens)
1039 putchar('(');
1040
1041 if (s->ops[a->po]->flags & OPFLAG_UNARYPREFIX)
1042 for (op = s->ops[a->po]->text; *op; op++)
1043 putchar(*op);
1044
1045 if (s->ops[a->po]->flags & OPFLAG_FN)
1046 putchar('(');
1047
1048 print_recurse(s, a->prev, pathindex, a->pa, thispri, thisassoc, 1);
1049
1050 if (s->ops[a->po]->flags & OPFLAG_FN)
1051 putchar(')');
1052
1053 if (!(s->ops[a->po]->flags & OPFLAG_UNARYPREFIX))
1054 for (op = s->ops[a->po]->text; *op; op++)
1055 putchar(*op);
1056
1057 if (!(s->ops[a->po]->flags & OPFLAG_UNARY))
1058 print_recurse(s, a->prev, pathindex, a->pb, thispri, thisassoc, 2);
1059
1060 if (parens)
1061 putchar(')');
1062 } else {
1063 /*
1064 * This number is either an original, or something formed
1065 * by a non-displayed operator (concatenation). Either way,
1066 * we display it as is.
1067 */
1068 printf("%d", ss->numbers[2*index]);
1069 if (ss->numbers[2*index+1] != 1)
1070 printf("/%d", ss->numbers[2*index+1]);
1071 }
1072}
1073static void print_recurse(struct sets *s, struct set *ss, int pathindex,
1074 int index, int priority, int assoc, int child)
1075{
1076 if (!ss->a.prev || pathindex < ss->a.prev->npaths) {
1077 print_recurse_inner(s, ss, &ss->a, pathindex,
1078 index, priority, assoc, child);
1079 } else {
1080 int i;
1081 pathindex -= ss->a.prev->npaths;
1082 for (i = 0; i < ss->nas; i++) {
1083 if (pathindex < ss->as[i].prev->npaths) {
1084 print_recurse_inner(s, ss, &ss->as[i], pathindex,
1085 index, priority, assoc, child);
1086 break;
1087 }
1088 pathindex -= ss->as[i].prev->npaths;
1089 }
1090 }
1091}
1092static void print(int pathindex, struct sets *s, struct output *o)
1093{
1094 print_recurse(s, o->set, pathindex, o->index, 0, 0, 0);
1095}
1096
1097/*
1098 * gcc -g -O0 -o numgame numgame.c -I.. ../{malloc,tree234,nullfe}.c -lm
1099 */
1100int main(int argc, char **argv)
1101{
1102 int doing_opts = true;
1103 const struct rules *rules = NULL;
1104 char *pname = argv[0];
1105 int got_target = false, target = 0;
1106 int numbers[10], nnumbers = 0;
1107 int verbose = false;
1108 int pathcounts = false;
1109 int multiple = false;
1110 int debug_bfs = false;
1111 int got_range = false, rangemin = 0, rangemax = 0;
1112
1113 struct output *o;
1114 struct sets *s;
1115 int i, start, limit;
1116
1117 while (--argc) {
1118 char *p = *++argv;
1119 int c;
1120
1121 if (doing_opts && *p == '-') {
1122 p++;
1123
1124 if (!strcmp(p, "-")) {
1125 doing_opts = false;
1126 continue;
1127 } else if (*p == '-') {
1128 p++;
1129 if (!strcmp(p, "debug-bfs")) {
1130 debug_bfs = true;
1131 } else {
1132 fprintf(stderr, "%s: option '--%s' not recognised\n",
1133 pname, p);
1134 }
1135 } else while (p && *p) switch (c = *p++) {
1136 case 'C':
1137 rules = &rules_countdown;
1138 break;
1139 case 'B':
1140 rules = &rules_3388;
1141 break;
1142 case 'D':
1143 rules = &rules_four4s;
1144 break;
1145 case 'A':
1146 rules = &rules_anythinggoes;
1147 break;
1148 case 'v':
1149 verbose = true;
1150 break;
1151 case 'p':
1152 pathcounts = true;
1153 break;
1154 case 'm':
1155 multiple = true;
1156 break;
1157 case 't':
1158 case 'r':
1159 {
1160 char *v;
1161 if (*p) {
1162 v = p;
1163 p = NULL;
1164 } else if (--argc) {
1165 v = *++argv;
1166 } else {
1167 fprintf(stderr, "%s: option '-%c' expects an"
1168 " argument\n", pname, c);
1169 return 1;
1170 }
1171 switch (c) {
1172 case 't':
1173 got_target = true;
1174 target = atoi(v);
1175 break;
1176 case 'r':
1177 {
1178 char *sep = strchr(v, '-');
1179 got_range = true;
1180 if (sep) {
1181 rangemin = atoi(v);
1182 rangemax = atoi(sep+1);
1183 } else {
1184 rangemin = 0;
1185 rangemax = atoi(v);
1186 }
1187 }
1188 break;
1189 }
1190 }
1191 break;
1192 default:
1193 fprintf(stderr, "%s: option '-%c' not"
1194 " recognised\n", pname, c);
1195 return 1;
1196 }
1197 } else {
1198 if (nnumbers >= lenof(numbers)) {
1199 fprintf(stderr, "%s: internal limit of %d numbers exceeded\n",
1200 pname, (int)lenof(numbers));
1201 return 1;
1202 } else {
1203 numbers[nnumbers++] = atoi(p);
1204 }
1205 }
1206 }
1207
1208 if (!rules) {
1209 fprintf(stderr, "%s: no rule set specified; use -C,-B,-D,-A\n", pname);
1210 return 1;
1211 }
1212
1213 if (!nnumbers) {
1214 fprintf(stderr, "%s: no input numbers specified\n", pname);
1215 return 1;
1216 }
1217
1218 if (got_range) {
1219 if (got_target) {
1220 fprintf(stderr, "%s: only one of -t and -r may be specified\n", pname);
1221 return 1;
1222 }
1223 if (rangemin >= rangemax) {
1224 fprintf(stderr, "%s: range not sensible (%d - %d)\n", pname, rangemin, rangemax);
1225 return 1;
1226 }
1227 }
1228
1229 s = do_search(nnumbers, numbers, rules, (got_target ? &target : NULL),
1230 debug_bfs, multiple);
1231
1232 if (got_target) {
1233 o = findrelpos234(s->outputtree, &target, outputfindcmp,
1234 REL234_LE, &start);
1235 if (!o)
1236 start = -1;
1237 o = findrelpos234(s->outputtree, &target, outputfindcmp,
1238 REL234_GE, &limit);
1239 if (!o)
1240 limit = -1;
1241 assert(start != -1 || limit != -1);
1242 if (start == -1)
1243 start = limit;
1244 else if (limit == -1)
1245 limit = start;
1246 limit++;
1247 } else if (got_range) {
1248 if (!findrelpos234(s->outputtree, &rangemin, outputfindcmp,
1249 REL234_GE, &start) ||
1250 !findrelpos234(s->outputtree, &rangemax, outputfindcmp,
1251 REL234_LE, &limit)) {
1252 printf("No solutions available in specified range %d-%d\n", rangemin, rangemax);
1253 return 1;
1254 }
1255 limit++;
1256 } else {
1257 start = 0;
1258 limit = count234(s->outputtree);
1259 }
1260
1261 for (i = start; i < limit; i++) {
1262 char buf[256];
1263
1264 o = index234(s->outputtree, i);
1265
1266 sprintf(buf, "%d", o->number);
1267
1268 if (pathcounts)
1269 sprintf(buf + strlen(buf), " [%d]", o->npaths);
1270
1271 if (got_target || verbose) {
1272 int j, npaths;
1273
1274 if (multiple)
1275 npaths = o->npaths;
1276 else
1277 npaths = 1;
1278
1279 for (j = 0; j < npaths; j++) {
1280 printf("%s = ", buf);
1281 print(j, s, o);
1282 putchar('\n');
1283 }
1284 } else {
1285 printf("%s\n", buf);
1286 }
1287 }
1288
1289 free_sets(s);
1290
1291 return 0;
1292}
1293
1294/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/apps/plugins/puzzles/src/unfinished/path.c b/apps/plugins/puzzles/src/unfinished/path.c
new file mode 100644
index 0000000000..2515ed0b71
--- /dev/null
+++ b/apps/plugins/puzzles/src/unfinished/path.c
@@ -0,0 +1,866 @@
1/*
2 * Experimental grid generator for Nikoli's `Number Link' puzzle.
3 */
4
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8#include <assert.h>
9#include "puzzles.h"
10
11/*
12 * 2005-07-08: This is currently a Path grid generator which will
13 * construct valid grids at a plausible speed. However, the grids
14 * are not of suitable quality to be used directly as puzzles.
15 *
16 * The basic strategy is to start with an empty grid, and
17 * repeatedly either (a) add a new path to it, or (b) extend one
18 * end of a path by one square in some direction and push other
19 * paths into new shapes in the process. The effect of this is that
20 * we are able to construct a set of paths which between them fill
21 * the entire grid.
22 *
23 * Quality issues: if we set the main loop to do (a) where possible
24 * and (b) only where necessary, we end up with a grid containing a
25 * few too many small paths, which therefore doesn't make for an
26 * interesting puzzle. If we reverse the priority so that we do (b)
27 * where possible and (a) only where necessary, we end up with some
28 * staggeringly interwoven grids with very very few separate paths,
29 * but the result of this is that there's invariably a solution
30 * other than the intended one which leaves many grid squares
31 * unfilled. There's also a separate problem which is that many
32 * grids have really boring and obvious paths in them, such as the
33 * entire bottom row of the grid being taken up by a single path.
34 *
35 * It's not impossible that a few tweaks might eliminate or reduce
36 * the incidence of boring paths, and might also find a happy
37 * medium between too many and too few. There remains the question
38 * of unique solutions, however. I fear there is no alternative but
39 * to write - somehow! - a solver.
40 *
41 * While I'm here, some notes on UI strategy for the parts of the
42 * puzzle implementation that _aren't_ the generator:
43 *
44 * - data model is to track connections between adjacent squares,
45 * so that you aren't limited to extending a path out from each
46 * number but can also mark sections of path which you know
47 * _will_ come in handy later.
48 *
49 * - user interface is to click in one square and drag to an
50 * adjacent one, thus creating a link between them. We can
51 * probably tolerate rapid mouse motion causing a drag directly
52 * to a square which is a rook move away, but any other rapid
53 * motion is ambiguous and probably the best option is to wait
54 * until the mouse returns to a square we know how to reach.
55 *
56 * - a drag causing the current path to backtrack has the effect
57 * of removing bits of it.
58 *
59 * - the UI should enforce at all times the constraint that at
60 * most two links can come into any square.
61 *
62 * - my Cunning Plan for actually implementing this: the game_ui
63 * contains a grid-sized array, which is copied from the current
64 * game_state on starting a drag. While a drag is active, the
65 * contents of the game_ui is adjusted with every mouse motion,
66 * and is displayed _in place_ of the game_state itself. On
67 * termination of a drag, the game_ui array is copied back into
68 * the new game_state (or rather, a string move is encoded which
69 * has precisely the set of link changes to cause that effect).
70 */
71
72/*
73 * 2020-05-11: some thoughts on a solver.
74 *
75 * Consider this example puzzle, from Wikipedia:
76 *
77 * ---4---
78 * -3--25-
79 * ---31--
80 * ---5---
81 * -------
82 * --1----
83 * 2---4--
84 *
85 * The kind of deduction that a human wants to make here is: which way
86 * does the path between the 4s go? In particular, does it go round
87 * the left of the W-shaped cluster of endpoints, or round the right
88 * of it? It's clear at a glance that it must go to the right, because
89 * _any_ path between the 4s that goes to the left of that cluster, no
90 * matter what detailed direction it takes, will disconnect the
91 * remaining grid squares into two components, with the two 2s not in
92 * the same component. So we immediately know that the path between
93 * the 4s _must_ go round the right-hand side of the grid.
94 *
95 * How do you model that global and topological reasoning in a
96 * computer?
97 *
98 * The most plausible idea I've seen so far is to use fundamental
99 * groups. The fundamental group of loops based at a given point in a
100 * space is a free group, under loop concatenation and up to homotopy,
101 * generated by the loops that go in each direction around each hole
102 * in the space. In this case, the 'holes' are clues, or connected
103 * groups of clues.
104 *
105 * So you might be able to enumerate all the homotopy classes of paths
106 * between (say) the two 4s as follows. Start with any old path
107 * between them (say, find the first one that breadth-first search
108 * will give you). Choose one of the 4s to regard as the base point
109 * (arbitrarily). Then breadth-first search among the space of _paths_
110 * by the following procedure. Given a candidate path, append to it
111 * each of the possible loops that starts from the base point,
112 * circumnavigates one clue cluster, and returns to the base point.
113 * The result will typically be a path that retraces its steps and
114 * self-intersects. Now adjust it homotopically so that it doesn't. If
115 * that can't be done, then we haven't generated a fresh candidate
116 * path; if it can, then we've got a new path that is not homotopic to
117 * any path we already had, so add it to our list and queue it up to
118 * become the starting point of this search later.
119 *
120 * The idea is that this should exhaustively enumerate, up to
121 * homotopy, the different ways in which the two 4s can connect to
122 * each other within the constraint that you have to actually fit the
123 * path non-self-intersectingly into this grid. Then you can keep a
124 * list of those homotopy classes in mind, and start ruling them out
125 * by techniques like the connectivity approach described above.
126 * Hopefully you end up narrowing down to few enough homotopy classes
127 * that you can deduce something concrete about actual squares of the
128 * grid - for example, here, that if the path between 4s has to go
129 * round the right, then we know some specific squares it must go
130 * through, so we can fill those in. And then, having filled in a
131 * piece of the middle of a path, you can now regard connecting the
132 * ultimate endpoints to that mid-section as two separate subproblems,
133 * so you've reduced to a simpler instance of the same puzzle.
134 *
135 * But I don't know whether all of this actually works. I more or less
136 * believe the process for enumerating elements of the free group; but
137 * I'm not as confident that when you find a group element that won't
138 * fit in the grid, you'll never have to consider its descendants in
139 * the BFS either. And I'm assuming that 'unwind the self-intersection
140 * homotopically' is a thing that can actually be turned into a
141 * sensible algorithm.
142 *
143 * --------
144 *
145 * Another thing that might be needed is to characterise _which_
146 * homotopy class a given path is in.
147 *
148 * For this I think it's sufficient to choose a collection of paths
149 * along the _edges_ of the square grid, each of which connects two of
150 * the holes in the grid (including the grid exterior, which counts as
151 * a huge hole), such that they form a spanning tree between the
152 * holes. Then assign each of those paths an orientation, so that
153 * crossing it in one direction counts as 'positive' and the other
154 * 'negative'. Now analyse a candidate path from one square to another
155 * by following it and noting down which of those paths it crosses in
156 * which direction, then simplifying the result like a free group word
157 * (i.e. adjacent + and - crossings of the same path cancel out).
158 *
159 * --------
160 *
161 * If we choose those paths to be of minimal length, then we can get
162 * an upper bound on the number of homotopy classes by observing that
163 * you can't traverse any of those barriers more times than will fit
164 * non-self-intersectingly in the grid. That might be an alternative
165 * method of bounding the search through the fundamental group to only
166 * finitely many possibilities.
167 */
168
169/*
170 * Standard notation for directions.
171 */
172#define L 0
173#define U 1
174#define R 2
175#define D 3
176#define DX(dir) ( (dir)==L ? -1 : (dir)==R ? +1 : 0)
177#define DY(dir) ( (dir)==U ? -1 : (dir)==D ? +1 : 0)
178
179/*
180 * Perform a breadth-first search over a grid of squares with the
181 * colour of square (X,Y) given by grid[Y*w+X]. The search begins
182 * at (x,y), and finds all squares which are the same colour as
183 * (x,y) and reachable from it by orthogonal moves. On return:
184 * - dist[Y*w+X] gives the distance of (X,Y) from (x,y), or -1 if
185 * unreachable or a different colour
186 * - the returned value is the number of reachable squares,
187 * including (x,y) itself
188 * - list[0] up to list[returned value - 1] list those squares, in
189 * increasing order of distance from (x,y) (and in arbitrary
190 * order within that).
191 */
192static int bfs(int w, int h, int *grid, int x, int y, int *dist, int *list)
193{
194 int i, j, c, listsize, listdone;
195
196 /*
197 * Start by clearing the output arrays.
198 */
199 for (i = 0; i < w*h; i++)
200 dist[i] = list[i] = -1;
201
202 /*
203 * Set up the initial list.
204 */
205 listsize = 1;
206 listdone = 0;
207 list[0] = y*w+x;
208 dist[y*w+x] = 0;
209 c = grid[y*w+x];
210
211 /*
212 * Repeatedly process a square and add any extra squares to the
213 * end of list.
214 */
215 while (listdone < listsize) {
216 i = list[listdone++];
217 y = i / w;
218 x = i % w;
219 for (j = 0; j < 4; j++) {
220 int xx, yy, ii;
221
222 xx = x + DX(j);
223 yy = y + DY(j);
224 ii = yy*w+xx;
225
226 if (xx >= 0 && xx < w && yy >= 0 && yy < h &&
227 grid[ii] == c && dist[ii] == -1) {
228 dist[ii] = dist[i] + 1;
229 assert(listsize < w*h);
230 list[listsize++] = ii;
231 }
232 }
233 }
234
235 return listsize;
236}
237
238struct genctx {
239 int w, h;
240 int *grid, *sparegrid, *sparegrid2, *sparegrid3;
241 int *dist, *list;
242
243 int npaths, pathsize;
244 int *pathends, *sparepathends; /* 2*npaths entries */
245 int *pathspare; /* npaths entries */
246 int *extends; /* 8*npaths entries */
247};
248
249static struct genctx *new_genctx(int w, int h)
250{
251 struct genctx *ctx = snew(struct genctx);
252 ctx->w = w;
253 ctx->h = h;
254 ctx->grid = snewn(w * h, int);
255 ctx->sparegrid = snewn(w * h, int);
256 ctx->sparegrid2 = snewn(w * h, int);
257 ctx->sparegrid3 = snewn(w * h, int);
258 ctx->dist = snewn(w * h, int);
259 ctx->list = snewn(w * h, int);
260 ctx->npaths = ctx->pathsize = 0;
261 ctx->pathends = ctx->sparepathends = ctx->pathspare = ctx->extends = NULL;
262 return ctx;
263}
264
265static void free_genctx(struct genctx *ctx)
266{
267 sfree(ctx->grid);
268 sfree(ctx->sparegrid);
269 sfree(ctx->sparegrid2);
270 sfree(ctx->sparegrid3);
271 sfree(ctx->dist);
272 sfree(ctx->list);
273 sfree(ctx->pathends);
274 sfree(ctx->sparepathends);
275 sfree(ctx->pathspare);
276 sfree(ctx->extends);
277}
278
279static int newpath(struct genctx *ctx)
280{
281 int n;
282
283 n = ctx->npaths++;
284 if (ctx->npaths > ctx->pathsize) {
285 ctx->pathsize += 16;
286 ctx->pathends = sresize(ctx->pathends, ctx->pathsize*2, int);
287 ctx->sparepathends = sresize(ctx->sparepathends, ctx->pathsize*2, int);
288 ctx->pathspare = sresize(ctx->pathspare, ctx->pathsize, int);
289 ctx->extends = sresize(ctx->extends, ctx->pathsize*8, int);
290 }
291 return n;
292}
293
294static int is_endpoint(struct genctx *ctx, int x, int y)
295{
296 int w = ctx->w, h = ctx->h, c;
297
298 assert(x >= 0 && x < w && y >= 0 && y < h);
299
300 c = ctx->grid[y*w+x];
301 if (c < 0)
302 return false; /* empty square is not an endpoint! */
303 assert(c >= 0 && c < ctx->npaths);
304 if (ctx->pathends[c*2] == y*w+x || ctx->pathends[c*2+1] == y*w+x)
305 return true;
306 return false;
307}
308
309/*
310 * Tries to extend a path by one square in the given direction,
311 * pushing other paths around if necessary. Returns true on success
312 * or false on failure.
313 */
314static int extend_path(struct genctx *ctx, int path, int end, int direction)
315{
316 int w = ctx->w, h = ctx->h;
317 int x, y, xe, ye, cut;
318 int i, j, jp, n, first, last;
319
320 assert(path >= 0 && path < ctx->npaths);
321 assert(end == 0 || end == 1);
322
323 /*
324 * Find the endpoint of the path and the point we plan to
325 * extend it into.
326 */
327 y = ctx->pathends[path * 2 + end] / w;
328 x = ctx->pathends[path * 2 + end] % w;
329 assert(x >= 0 && x < w && y >= 0 && y < h);
330
331 xe = x + DX(direction);
332 ye = y + DY(direction);
333 if (xe < 0 || xe >= w || ye < 0 || ye >= h)
334 return false; /* could not extend in this direction */
335
336 /*
337 * We don't extend paths _directly_ into endpoints of other
338 * paths, although we don't mind too much if a knock-on effect
339 * of an extension is to push part of another path into a third
340 * path's endpoint.
341 */
342 if (is_endpoint(ctx, xe, ye))
343 return false;
344
345 /*
346 * We can't extend a path back the way it came.
347 */
348 if (ctx->grid[ye*w+xe] == path)
349 return false;
350
351 /*
352 * Paths may not double back on themselves. Check if the new
353 * point is adjacent to any point of this path other than (x,y).
354 */
355 for (j = 0; j < 4; j++) {
356 int xf, yf;
357
358 xf = xe + DX(j);
359 yf = ye + DY(j);
360
361 if (xf >= 0 && xf < w && yf >= 0 && yf < h &&
362 (xf != x || yf != y) && ctx->grid[yf*w+xf] == path)
363 return false;
364 }
365
366 /*
367 * Now we're convinced it's valid to _attempt_ the extension.
368 * It may still fail if we run out of space to push other paths
369 * into.
370 *
371 * So now we can set up our temporary data structures. We will
372 * need:
373 *
374 * - a spare copy of the grid on which to gradually move paths
375 * around (sparegrid)
376 *
377 * - a second spare copy with which to remember how paths
378 * looked just before being cut (sparegrid2). FIXME: is
379 * sparegrid2 necessary? right now it's never different from
380 * grid itself
381 *
382 * - a third spare copy with which to do the internal
383 * calculations involved in reconstituting a cut path
384 * (sparegrid3)
385 *
386 * - something to track which paths currently need
387 * reconstituting after being cut, and which have already
388 * been cut (pathspare)
389 *
390 * - a spare copy of pathends to store the altered states in
391 * (sparepathends)
392 */
393 memcpy(ctx->sparegrid, ctx->grid, w*h*sizeof(int));
394 memcpy(ctx->sparegrid2, ctx->grid, w*h*sizeof(int));
395 memcpy(ctx->sparepathends, ctx->pathends, ctx->npaths*2*sizeof(int));
396 for (i = 0; i < ctx->npaths; i++)
397 ctx->pathspare[i] = 0; /* 0=untouched, 1=broken, 2=fixed */
398
399 /*
400 * Working in sparegrid, actually extend the path. If it cuts
401 * another, begin a loop in which we restore any cut path by
402 * moving it out of the way.
403 */
404 cut = ctx->sparegrid[ye*w+xe];
405 ctx->sparegrid[ye*w+xe] = path;
406 ctx->sparepathends[path*2+end] = ye*w+xe;
407 ctx->pathspare[path] = 2; /* this one is sacrosanct */
408 if (cut >= 0) {
409 assert(cut >= 0 && cut < ctx->npaths);
410 ctx->pathspare[cut] = 1; /* broken */
411
412 while (1) {
413 for (i = 0; i < ctx->npaths; i++)
414 if (ctx->pathspare[i] == 1)
415 break;
416 if (i == ctx->npaths)
417 break; /* we're done */
418
419 /*
420 * Path i needs restoring. So walk along its original
421 * track (as given in sparegrid2) and see where it's
422 * been cut. Where it has, surround the cut points in
423 * the same colour, without overwriting already-fixed
424 * paths.
425 */
426 memcpy(ctx->sparegrid3, ctx->sparegrid, w*h*sizeof(int));
427 n = bfs(w, h, ctx->sparegrid2,
428 ctx->pathends[i*2] % w, ctx->pathends[i*2] / w,
429 ctx->dist, ctx->list);
430 first = last = -1;
431if (ctx->sparegrid3[ctx->pathends[i*2]] != i ||
432 ctx->sparegrid3[ctx->pathends[i*2+1]] != i) return false;/* FIXME */
433 for (j = 0; j < n; j++) {
434 jp = ctx->list[j];
435 assert(ctx->dist[jp] == j);
436 assert(ctx->sparegrid2[jp] == i);
437
438 /*
439 * Wipe out the original path in sparegrid.
440 */
441 if (ctx->sparegrid[jp] == i)
442 ctx->sparegrid[jp] = -1;
443
444 /*
445 * Be prepared to shorten the path at either end if
446 * the endpoints have been stomped on.
447 */
448 if (ctx->sparegrid3[jp] == i) {
449 if (first < 0)
450 first = jp;
451 last = jp;
452 }
453
454 if (ctx->sparegrid3[jp] != i) {
455 int jx = jp % w, jy = jp / w;
456 int dx, dy;
457 for (dy = -1; dy <= +1; dy++)
458 for (dx = -1; dx <= +1; dx++) {
459 int newp, newv;
460 if (!dy && !dx)
461 continue; /* central square */
462 if (jx+dx < 0 || jx+dx >= w ||
463 jy+dy < 0 || jy+dy >= h)
464 continue; /* out of range */
465 newp = (jy+dy)*w+(jx+dx);
466 newv = ctx->sparegrid3[newp];
467 if (newv >= 0 && (newv == i ||
468 ctx->pathspare[newv] == 2))
469 continue; /* can't use this square */
470 ctx->sparegrid3[newp] = i;
471 }
472 }
473 }
474
475 if (first < 0 || last < 0)
476 return false; /* path is completely wiped out! */
477
478 /*
479 * Now we've covered sparegrid3 in possible squares for
480 * the new layout of path i. Find the actual layout
481 * we're going to use by bfs: we want the shortest path
482 * from one endpoint to the other.
483 */
484 n = bfs(w, h, ctx->sparegrid3, first % w, first / w,
485 ctx->dist, ctx->list);
486 if (ctx->dist[last] < 2) {
487 /*
488 * Either there is no way to get between the path's
489 * endpoints, or the remaining endpoints simply
490 * aren't far enough apart to make the path viable
491 * any more. This means the entire push operation
492 * has failed.
493 */
494 return false;
495 }
496
497 /*
498 * Write the new path into sparegrid. Also save the new
499 * endpoint locations, in case they've changed.
500 */
501 jp = last;
502 j = ctx->dist[jp];
503 while (1) {
504 int d;
505
506 if (ctx->sparegrid[jp] >= 0) {
507 if (ctx->pathspare[ctx->sparegrid[jp]] == 2)
508 return false; /* somehow we've hit a fixed path */
509 ctx->pathspare[ctx->sparegrid[jp]] = 1; /* broken */
510 }
511 ctx->sparegrid[jp] = i;
512
513 if (j == 0)
514 break;
515
516 /*
517 * Now look at the neighbours of jp to find one
518 * which has dist[] one less.
519 */
520 for (d = 0; d < 4; d++) {
521 int jx = (jp % w) + DX(d), jy = (jp / w) + DY(d);
522 if (jx >= 0 && jx < w && jy >= 0 && jy < w &&
523 ctx->dist[jy*w+jx] == j-1) {
524 jp = jy*w+jx;
525 j--;
526 break;
527 }
528 }
529 assert(d < 4);
530 }
531
532 ctx->sparepathends[i*2] = first;
533 ctx->sparepathends[i*2+1] = last;
534/* printf("new ends of path %d: %d,%d\n", i, first, last); */
535 ctx->pathspare[i] = 2; /* fixed */
536 }
537 }
538
539 /*
540 * If we got here, the extension was successful!
541 */
542 memcpy(ctx->grid, ctx->sparegrid, w*h*sizeof(int));
543 memcpy(ctx->pathends, ctx->sparepathends, ctx->npaths*2*sizeof(int));
544 return true;
545}
546
547/*
548 * Tries to add a new path to the grid.
549 */
550static int add_path(struct genctx *ctx, random_state *rs)
551{
552 int w = ctx->w, h = ctx->h;
553 int i, ii, n;
554
555 /*
556 * Our strategy is:
557 * - randomly choose an empty square in the grid
558 * - do a BFS from that point to find a long path starting
559 * from it
560 * - if we run out of viable empty squares, return failure.
561 */
562
563 /*
564 * Use `sparegrid' to collect a list of empty squares.
565 */
566 n = 0;
567 for (i = 0; i < w*h; i++)
568 if (ctx->grid[i] == -1)
569 ctx->sparegrid[n++] = i;
570
571 /*
572 * Shuffle the grid.
573 */
574 for (i = n; i-- > 1 ;) {
575 int k = random_upto(rs, i+1);
576 if (k != i) {
577 int t = ctx->sparegrid[i];
578 ctx->sparegrid[i] = ctx->sparegrid[k];
579 ctx->sparegrid[k] = t;
580 }
581 }
582
583 /*
584 * Loop over it trying to add paths. This looks like a
585 * horrifying N^4 algorithm (that is, (w*h)^2), but I predict
586 * that in fact the worst case will very rarely arise because
587 * when there's lots of grid space an attempt will succeed very
588 * quickly.
589 */
590 for (ii = 0; ii < n; ii++) {
591 int i = ctx->sparegrid[ii];
592 int y = i / w, x = i % w, nsq;
593 int r, c, j;
594
595 /*
596 * BFS from here to find long paths.
597 */
598 nsq = bfs(w, h, ctx->grid, x, y, ctx->dist, ctx->list);
599
600 /*
601 * If there aren't any long enough, give up immediately.
602 */
603 assert(nsq > 0); /* must be the start square at least! */
604 if (ctx->dist[ctx->list[nsq-1]] < 3)
605 continue;
606
607 /*
608 * Find the first viable endpoint in ctx->list (i.e. the
609 * first point with distance at least three). I could
610 * binary-search for this, but that would be O(log N)
611 * whereas in fact I can get a constant time bound by just
612 * searching up from the start - after all, there can be at
613 * most 13 points at _less_ than distance 3 from the
614 * starting one!
615 */
616 for (j = 0; j < nsq; j++)
617 if (ctx->dist[ctx->list[j]] >= 3)
618 break;
619 assert(j < nsq); /* we tested above that there was one */
620
621 /*
622 * Now we know that any element of `list' between j and nsq
623 * would be valid in principle. However, we want a few long
624 * paths rather than many small ones, so select only those
625 * elements which are either the maximum length or one
626 * below it.
627 */
628 while (ctx->dist[ctx->list[j]] + 1 < ctx->dist[ctx->list[nsq-1]])
629 j++;
630 r = j + random_upto(rs, nsq - j);
631 j = ctx->list[r];
632
633 /*
634 * And that's our endpoint. Mark the new path on the grid.
635 */
636 c = newpath(ctx);
637 ctx->pathends[c*2] = i;
638 ctx->pathends[c*2+1] = j;
639 ctx->grid[j] = c;
640 while (j != i) {
641 int d, np, index, pts[4];
642 np = 0;
643 for (d = 0; d < 4; d++) {
644 int xn = (j % w) + DX(d), yn = (j / w) + DY(d);
645 if (xn >= 0 && xn < w && yn >= 0 && yn < w &&
646 ctx->dist[yn*w+xn] == ctx->dist[j] - 1)
647 pts[np++] = yn*w+xn;
648 }
649 if (np > 1)
650 index = random_upto(rs, np);
651 else
652 index = 0;
653 j = pts[index];
654 ctx->grid[j] = c;
655 }
656
657 return true;
658 }
659
660 return false;
661}
662
663/*
664 * The main grid generation loop.
665 */
666static void gridgen_mainloop(struct genctx *ctx, random_state *rs)
667{
668 int w = ctx->w, h = ctx->h;
669 int i, n;
670
671 /*
672 * The generation algorithm doesn't always converge. Loop round
673 * until it does.
674 */
675 while (1) {
676 for (i = 0; i < w*h; i++)
677 ctx->grid[i] = -1;
678 ctx->npaths = 0;
679
680 while (1) {
681 /*
682 * See if the grid is full.
683 */
684 for (i = 0; i < w*h; i++)
685 if (ctx->grid[i] < 0)
686 break;
687 if (i == w*h)
688 return;
689
690#ifdef GENERATION_DIAGNOSTICS
691 {
692 int x, y;
693 for (y = 0; y < h; y++) {
694 printf("|");
695 for (x = 0; x < w; x++) {
696 if (ctx->grid[y*w+x] >= 0)
697 printf("%2d", ctx->grid[y*w+x]);
698 else
699 printf(" .");
700 }
701 printf(" |\n");
702 }
703 }
704#endif
705 /*
706 * Try adding a path.
707 */
708 if (add_path(ctx, rs)) {
709#ifdef GENERATION_DIAGNOSTICS
710 printf("added path\n");
711#endif
712 continue;
713 }
714
715 /*
716 * Try extending a path. First list all the possible
717 * extensions.
718 */
719 for (i = 0; i < ctx->npaths * 8; i++)
720 ctx->extends[i] = i;
721 n = i;
722
723 /*
724 * Then shuffle the list.
725 */
726 for (i = n; i-- > 1 ;) {
727 int k = random_upto(rs, i+1);
728 if (k != i) {
729 int t = ctx->extends[i];
730 ctx->extends[i] = ctx->extends[k];
731 ctx->extends[k] = t;
732 }
733 }
734
735 /*
736 * Now try each one in turn until one works.
737 */
738 for (i = 0; i < n; i++) {
739 int p, d, e;
740 p = ctx->extends[i];
741 d = p % 4;
742 p /= 4;
743 e = p % 2;
744 p /= 2;
745
746#ifdef GENERATION_DIAGNOSTICS
747 printf("trying to extend path %d end %d (%d,%d) in dir %d\n", p, e,
748 ctx->pathends[p*2+e] % w,
749 ctx->pathends[p*2+e] / w, d);
750#endif
751 if (extend_path(ctx, p, e, d)) {
752#ifdef GENERATION_DIAGNOSTICS
753 printf("extended path %d end %d (%d,%d) in dir %d\n", p, e,
754 ctx->pathends[p*2+e] % w,
755 ctx->pathends[p*2+e] / w, d);
756#endif
757 break;
758 }
759 }
760
761 if (i < n)
762 continue;
763
764 break;
765 }
766 }
767}
768
769/*
770 * Wrapper function which deals with the boring bits such as
771 * removing the solution from the generated grid, shuffling the
772 * numeric labels and creating/disposing of the context structure.
773 */
774static int *gridgen(int w, int h, random_state *rs)
775{
776 struct genctx *ctx;
777 int *ret;
778 int i;
779
780 ctx = new_genctx(w, h);
781
782 gridgen_mainloop(ctx, rs);
783
784 /*
785 * There is likely to be an ordering bias in the numbers
786 * (longer paths on lower numbers due to there having been more
787 * grid space when laying them down). So we must shuffle the
788 * numbers. We use ctx->pathspare for this.
789 *
790 * This is also as good a time as any to shift to numbering
791 * from 1, for display to the user.
792 */
793 for (i = 0; i < ctx->npaths; i++)
794 ctx->pathspare[i] = i+1;
795 for (i = ctx->npaths; i-- > 1 ;) {
796 int k = random_upto(rs, i+1);
797 if (k != i) {
798 int t = ctx->pathspare[i];
799 ctx->pathspare[i] = ctx->pathspare[k];
800 ctx->pathspare[k] = t;
801 }
802 }
803
804 /* FIXME: remove this at some point! */
805 {
806 int y, x;
807 for (y = 0; y < h; y++) {
808 printf("|");
809 for (x = 0; x < w; x++) {
810 assert(ctx->grid[y*w+x] >= 0);
811 printf("%2d", ctx->pathspare[ctx->grid[y*w+x]]);
812 }
813 printf(" |\n");
814 }
815 printf("\n");
816 }
817
818 /*
819 * Clear the grid, and write in just the endpoints.
820 */
821 for (i = 0; i < w*h; i++)
822 ctx->grid[i] = 0;
823 for (i = 0; i < ctx->npaths; i++) {
824 ctx->grid[ctx->pathends[i*2]] =
825 ctx->grid[ctx->pathends[i*2+1]] = ctx->pathspare[i];
826 }
827
828 ret = ctx->grid;
829 ctx->grid = NULL;
830
831 free_genctx(ctx);
832
833 return ret;
834}
835
836#ifdef TEST_GEN
837
838#define TEST_GENERAL
839
840int main(void)
841{
842 int w = 10, h = 8;
843 random_state *rs = random_new("12345", 5);
844 int x, y, i, *grid;
845
846 for (i = 0; i < 10; i++) {
847 grid = gridgen(w, h, rs);
848
849 for (y = 0; y < h; y++) {
850 printf("|");
851 for (x = 0; x < w; x++) {
852 if (grid[y*w+x] > 0)
853 printf("%2d", grid[y*w+x]);
854 else
855 printf(" .");
856 }
857 printf(" |\n");
858 }
859 printf("\n");
860
861 sfree(grid);
862 }
863
864 return 0;
865}
866#endif
diff --git a/apps/plugins/puzzles/src/unfinished/separate.c b/apps/plugins/puzzles/src/unfinished/separate.c
new file mode 100644
index 0000000000..6ca07252ad
--- /dev/null
+++ b/apps/plugins/puzzles/src/unfinished/separate.c
@@ -0,0 +1,861 @@
1/*
2 * separate.c: Implementation of `Block Puzzle', a Japanese-only
3 * Nikoli puzzle seen at
4 * http://www.nikoli.co.jp/ja/puzzles/block_puzzle/
5 *
6 * It's difficult to be absolutely sure of the rules since online
7 * Japanese translators are so bad, but looking at the sample
8 * puzzle it seems fairly clear that the rules of this one are
9 * very simple. You have an mxn grid in which every square
10 * contains a letter, there are k distinct letters with k dividing
11 * mn, and every letter occurs the same number of times; your aim
12 * is to find a partition of the grid into disjoint k-ominoes such
13 * that each k-omino contains exactly one of each letter.
14 *
15 * (It may be that Nikoli always have m,n,k equal to one another.
16 * However, I don't see that that's critical to the puzzle; k|mn
17 * is the only really important constraint, and even that could
18 * probably be dispensed with if some squares were marked as
19 * unused.)
20 */
21
22/*
23 * Current status: only the solver/generator is yet written, and
24 * although working in principle it's _very_ slow. It generates
25 * 5x5n5 or 6x6n4 readily enough, 6x6n6 with a bit of effort, and
26 * 7x7n7 only with a serious strain. I haven't dared try it higher
27 * than that yet.
28 *
29 * One idea to speed it up is to implement more of the solver.
30 * Ideas I've so far had include:
31 *
32 * - Generalise the deduction currently expressed as `an
33 * undersized chain with only one direction to extend must take
34 * it'. More generally, the deduction should say `if all the
35 * possible k-ominoes containing a given chain also contain
36 * square x, then mark square x as part of that k-omino'.
37 * + For example, consider this case:
38 *
39 * a ? b This represents the top left of a board; the letters
40 * ? ? ? a,b,c do not represent the letters used in the puzzle,
41 * c ? ? but indicate that those three squares are known to be
42 * of different ominoes. Now if k >= 4, we can immediately
43 * deduce that the square midway between b and c belongs to the
44 * same omino as a, because there is no way we can make a 4-or-
45 * more-omino containing a which does not also contain that square.
46 * (Most easily seen by imagining cutting that square out of the
47 * grid; then, clearly, the omino containing a has only two
48 * squares to expand into, and needs at least three.)
49 *
50 * The key difficulty with this mode of reasoning is
51 * identifying such squares. I can't immediately think of a
52 * simple algorithm for finding them on a wholesale basis.
53 *
54 * - Bfs out from a chain looking for the letters it lacks. For
55 * example, in this situation (top three rows of a 7x7n7 grid):
56 *
57 * +-----------+-+
58 * |E-A-F-B-C D|D|
59 * +------- ||
60 * |E-C-G-D G|G E|
61 * +-+--- |
62 * |E|E G A B F A|
63 *
64 * In this situation we can be sure that the top left chain
65 * E-A-F-B-C does extend rightwards to the D, because there is
66 * no other D within reach of that chain. Note also that the
67 * bfs can skip squares which are known to belong to other
68 * ominoes than this one.
69 *
70 * (This deduction, I fear, should only be used in an
71 * emergency, because it relies on _all_ squares within range
72 * of the bfs having particular values and so using it during
73 * incremental generation rather nails down a lot of the grid.)
74 *
75 * It's conceivable that another thing we could do would be to
76 * increase the flexibility in the grid generator: instead of
77 * nailing down the _value_ of any square depended on, merely nail
78 * down its equivalence to other squares. Unfortunately this turns
79 * the letter-selection phase of generation into a general graph
80 * colouring problem (we must draw a graph with equivalence
81 * classes of squares as the vertices, and an edge between any two
82 * vertices representing equivalence classes which contain squares
83 * that share an omino, and then k-colour the result) and hence
84 * requires recursion, which bodes ill for something we're doing
85 * that many times per generation.
86 *
87 * I suppose a simple thing I could try would be tuning the retry
88 * count, just in case it's set too high or too low for efficient
89 * generation.
90 */
91
92#include <stdio.h>
93#include <stdlib.h>
94#include <string.h>
95#include <assert.h>
96#include <ctype.h>
97#ifdef NO_TGMATH_H
98# include <math.h>
99#else
100# include <tgmath.h>
101#endif
102
103#include "puzzles.h"
104
105enum {
106 COL_BACKGROUND,
107 NCOLOURS
108};
109
110struct game_params {
111 int w, h, k;
112};
113
114struct game_state {
115 int FIXME;
116};
117
118static game_params *default_params(void)
119{
120 game_params *ret = snew(game_params);
121
122 ret->w = ret->h = ret->k = 5; /* FIXME: a bit bigger? */
123
124 return ret;
125}
126
127static bool game_fetch_preset(int i, char **name, game_params **params)
128{
129 return false;
130}
131
132static void free_params(game_params *params)
133{
134 sfree(params);
135}
136
137static game_params *dup_params(const game_params *params)
138{
139 game_params *ret = snew(game_params);
140 *ret = *params; /* structure copy */
141 return ret;
142}
143
144static void decode_params(game_params *params, char const *string)
145{
146 params->w = params->h = params->k = atoi(string);
147 while (*string && isdigit((unsigned char)*string)) string++;
148 if (*string == 'x') {
149 string++;
150 params->h = atoi(string);
151 while (*string && isdigit((unsigned char)*string)) string++;
152 }
153 if (*string == 'n') {
154 string++;
155 params->k = atoi(string);
156 while (*string && isdigit((unsigned char)*string)) string++;
157 }
158}
159
160static char *encode_params(const game_params *params, bool full)
161{
162 char buf[256];
163 sprintf(buf, "%dx%dn%d", params->w, params->h, params->k);
164 return dupstr(buf);
165}
166
167static config_item *game_configure(const game_params *params)
168{
169 return NULL;
170}
171
172static game_params *custom_params(const config_item *cfg)
173{
174 return NULL;
175}
176
177static const char *validate_params(const game_params *params, bool full)
178{
179 return NULL;
180}
181
182/* ----------------------------------------------------------------------
183 * Solver and generator.
184 */
185
186struct solver_scratch {
187 int w, h, k;
188
189 /*
190 * Tracks connectedness between squares.
191 */
192 DSF *dsf;
193
194 /*
195 * size[dsf_canonify(dsf, yx)] tracks the size of the
196 * connected component containing yx.
197 */
198 int *size;
199
200 /*
201 * contents[dsf_canonify(dsf, yx)*k+i] tracks whether or not
202 * the connected component containing yx includes letter i. If
203 * the value is -1, it doesn't; otherwise its value is the
204 * index in the main grid of the square which contributes that
205 * letter to the component.
206 */
207 int *contents;
208
209 /*
210 * disconnect[dsf_canonify(dsf, yx1)*w*h + dsf_canonify(dsf, yx2)]
211 * tracks whether or not the connected components containing
212 * yx1 and yx2 are known to be distinct.
213 */
214 bool *disconnect;
215
216 /*
217 * Temporary space used only inside particular solver loops.
218 */
219 int *tmp;
220};
221
222static struct solver_scratch *solver_scratch_new(int w, int h, int k)
223{
224 int wh = w*h;
225 struct solver_scratch *sc = snew(struct solver_scratch);
226
227 sc->w = w;
228 sc->h = h;
229 sc->k = k;
230
231 sc->dsf = dsf_new(wh);
232 sc->size = snewn(wh, int);
233 sc->contents = snewn(wh * k, int);
234 sc->disconnect = snewn(wh*wh, bool);
235 sc->tmp = snewn(wh, int);
236
237 return sc;
238}
239
240static void solver_scratch_free(struct solver_scratch *sc)
241{
242 dsf_free(sc->dsf);
243 sfree(sc->size);
244 sfree(sc->contents);
245 sfree(sc->disconnect);
246 sfree(sc->tmp);
247 sfree(sc);
248}
249
250static void solver_connect(struct solver_scratch *sc, int yx1, int yx2)
251{
252 int w = sc->w, h = sc->h, k = sc->k;
253 int wh = w*h;
254 int i, yxnew;
255
256 yx1 = dsf_canonify(sc->dsf, yx1);
257 yx2 = dsf_canonify(sc->dsf, yx2);
258 assert(yx1 != yx2);
259
260 /*
261 * To connect two components together into a bigger one, we
262 * start by merging them in the dsf itself.
263 */
264 dsf_merge(sc->dsf, yx1, yx2);
265 yxnew = dsf_canonify(sc->dsf, yx2);
266
267 /*
268 * The size of the new component is the sum of the sizes of the
269 * old ones.
270 */
271 sc->size[yxnew] = sc->size[yx1] + sc->size[yx2];
272
273 /*
274 * The contents bitmap of the new component is the union of the
275 * contents of the old ones.
276 *
277 * Given two numbers at most one of which is not -1, we can
278 * find the other one by adding the two and adding 1; this
279 * will yield -1 if both were -1 to begin with, otherwise the
280 * other.
281 *
282 * (A neater approach would be to take their bitwise AND, but
283 * this is unfortunately not well-defined standard C when done
284 * to signed integers.)
285 */
286 for (i = 0; i < k; i++) {
287 assert(sc->contents[yx1*k+i] < 0 || sc->contents[yx2*k+i] < 0);
288 sc->contents[yxnew*k+i] = (sc->contents[yx1*k+i] +
289 sc->contents[yx2*k+i] + 1);
290 }
291
292 /*
293 * We must combine the rows _and_ the columns in the disconnect
294 * matrix.
295 */
296 for (i = 0; i < wh; i++)
297 sc->disconnect[yxnew*wh+i] = (sc->disconnect[yx1*wh+i] ||
298 sc->disconnect[yx2*wh+i]);
299 for (i = 0; i < wh; i++)
300 sc->disconnect[i*wh+yxnew] = (sc->disconnect[i*wh+yx1] ||
301 sc->disconnect[i*wh+yx2]);
302}
303
304static void solver_disconnect(struct solver_scratch *sc, int yx1, int yx2)
305{
306 int w = sc->w, h = sc->h;
307 int wh = w*h;
308
309 yx1 = dsf_canonify(sc->dsf, yx1);
310 yx2 = dsf_canonify(sc->dsf, yx2);
311 assert(yx1 != yx2);
312 assert(!sc->disconnect[yx1*wh+yx2]);
313 assert(!sc->disconnect[yx2*wh+yx1]);
314
315 /*
316 * Mark the components as disconnected from each other in the
317 * disconnect matrix.
318 */
319 sc->disconnect[yx1*wh+yx2] = true;
320 sc->disconnect[yx2*wh+yx1] = true;
321}
322
323static void solver_init(struct solver_scratch *sc)
324{
325 int w = sc->w, h = sc->h;
326 int wh = w*h;
327 int i;
328
329 /*
330 * Set up most of the scratch space. We don't set up the
331 * contents array, however, because this will change if we
332 * adjust the letter arrangement and re-run the solver.
333 */
334 dsf_reinit(sc->dsf);
335 for (i = 0; i < wh; i++) sc->size[i] = 1;
336 memset(sc->disconnect, 0, wh*wh * sizeof(bool));
337}
338
339static int solver_attempt(struct solver_scratch *sc, const unsigned char *grid,
340 bool *gen_lock)
341{
342 int w = sc->w, h = sc->h, k = sc->k;
343 int wh = w*h;
344 int i, x, y;
345 bool done_something_overall = false;
346
347 /*
348 * Set up the contents array from the grid.
349 */
350 for (i = 0; i < wh*k; i++)
351 sc->contents[i] = -1;
352 for (i = 0; i < wh; i++)
353 sc->contents[dsf_canonify(sc->dsf, i)*k+grid[i]] = i;
354
355 while (1) {
356 bool done_something = false;
357
358 /*
359 * Go over the grid looking for reasons to add to the
360 * disconnect matrix. We're after pairs of squares which:
361 *
362 * - are adjacent in the grid
363 * - belong to distinct dsf components
364 * - their components are not already marked as
365 * disconnected
366 * - their components share a letter in common.
367 */
368 for (y = 0; y < h; y++) {
369 for (x = 0; x < w; x++) {
370 int dir;
371 for (dir = 0; dir < 2; dir++) {
372 int x2 = x + dir, y2 = y + 1 - dir;
373 int yx = y*w+x, yx2 = y2*w+x2;
374
375 if (x2 >= w || y2 >= h)
376 continue; /* one square is outside the grid */
377
378 yx = dsf_canonify(sc->dsf, yx);
379 yx2 = dsf_canonify(sc->dsf, yx2);
380 if (yx == yx2)
381 continue; /* same dsf component */
382
383 if (sc->disconnect[yx*wh+yx2])
384 continue; /* already known disconnected */
385
386 for (i = 0; i < k; i++)
387 if (sc->contents[yx*k+i] >= 0 &&
388 sc->contents[yx2*k+i] >= 0)
389 break;
390 if (i == k)
391 continue; /* no letter in common */
392
393 /*
394 * We've found one. Mark yx and yx2 as
395 * disconnected from each other.
396 */
397#ifdef SOLVER_DIAGNOSTICS
398 printf("Disconnecting %d and %d (%c)\n", yx, yx2, 'A'+i);
399#endif
400 solver_disconnect(sc, yx, yx2);
401 done_something = done_something_overall = true;
402
403 /*
404 * We have just made a deduction which hinges
405 * on two particular grid squares being the
406 * same. If we are feeding back to a generator
407 * loop, we must therefore mark those squares
408 * as fixed in the generator, so that future
409 * rearrangement of the grid will not break
410 * the information on which we have already
411 * based deductions.
412 */
413 if (gen_lock) {
414 gen_lock[sc->contents[yx*k+i]] = true;
415 gen_lock[sc->contents[yx2*k+i]] = true;
416 }
417 }
418 }
419 }
420
421 /*
422 * Now go over the grid looking for dsf components which
423 * are below maximum size and only have one way to extend,
424 * and extending them.
425 */
426 for (i = 0; i < wh; i++)
427 sc->tmp[i] = -1;
428 for (y = 0; y < h; y++) {
429 for (x = 0; x < w; x++) {
430 int yx = dsf_canonify(sc->dsf, y*w+x);
431 int dir;
432
433 if (sc->size[yx] == k)
434 continue;
435
436 for (dir = 0; dir < 4; dir++) {
437 int x2 = x + (dir==0 ? -1 : dir==2 ? 1 : 0);
438 int y2 = y + (dir==1 ? -1 : dir==3 ? 1 : 0);
439 int yx2, yx2c;
440
441 if (y2 < 0 || y2 >= h || x2 < 0 || x2 >= w)
442 continue;
443 yx2 = y2*w+x2;
444 yx2c = dsf_canonify(sc->dsf, yx2);
445
446 if (yx2c != yx && !sc->disconnect[yx2c*wh+yx]) {
447 /*
448 * Component yx can be extended into square
449 * yx2.
450 */
451 if (sc->tmp[yx] == -1)
452 sc->tmp[yx] = yx2;
453 else if (sc->tmp[yx] != yx2)
454 sc->tmp[yx] = -2; /* multiple choices found */
455 }
456 }
457 }
458 }
459 for (i = 0; i < wh; i++) {
460 if (sc->tmp[i] >= 0) {
461 /*
462 * Make sure we haven't connected the two already
463 * during this loop (which could happen if for
464 * _both_ components this was the only way to
465 * extend them).
466 */
467 if (dsf_canonify(sc->dsf, i) ==
468 dsf_canonify(sc->dsf, sc->tmp[i]))
469 continue;
470
471#ifdef SOLVER_DIAGNOSTICS
472 printf("Connecting %d and %d\n", i, sc->tmp[i]);
473#endif
474 solver_connect(sc, i, sc->tmp[i]);
475 done_something = done_something_overall = true;
476 break;
477 }
478 }
479
480 if (!done_something)
481 break;
482 }
483
484 /*
485 * Return 0 if we haven't made any progress; 1 if we've done
486 * something but not solved it completely; 2 if we've solved
487 * it completely.
488 */
489 for (i = 0; i < wh; i++)
490 if (sc->size[dsf_canonify(sc->dsf, i)] != k)
491 break;
492 if (i == wh)
493 return 2;
494 if (done_something_overall)
495 return 1;
496 return 0;
497}
498
499static unsigned char *generate(int w, int h, int k, random_state *rs)
500{
501 int wh = w*h;
502 int n = wh/k;
503 struct solver_scratch *sc;
504 unsigned char *grid;
505 unsigned char *shuffled;
506 int i, j, m, retries;
507 int *permutation;
508 bool *gen_lock;
509
510 sc = solver_scratch_new(w, h, k);
511 grid = snewn(wh, unsigned char);
512 shuffled = snewn(k, unsigned char);
513 permutation = snewn(wh, int);
514 gen_lock = snewn(wh, bool);
515
516 do {
517 DSF *dsf = divvy_rectangle(w, h, k, rs);
518
519 /*
520 * Go through the dsf and find the indices of all the
521 * squares involved in each omino, in a manner conducive
522 * to per-omino indexing. We set permutation[i*k+j] to be
523 * the index of the jth square (ordered arbitrarily) in
524 * omino i.
525 */
526 for (i = j = 0; i < wh; i++)
527 if (dsf_canonify(dsf, i) == i) {
528 sc->tmp[i] = j;
529 /*
530 * During this loop and the following one, we use
531 * the last element of each row of permutation[]
532 * as a counter of the number of indices so far
533 * placed in it. When we place the final index of
534 * an omino, that counter is overwritten, but that
535 * doesn't matter because we'll never use it
536 * again. Of course this depends critically on
537 * divvy_rectangle() having returned correct
538 * results, or else chaos would ensue.
539 */
540 permutation[j*k+k-1] = 0;
541 j++;
542 }
543 for (i = 0; i < wh; i++) {
544 j = sc->tmp[dsf_canonify(dsf, i)];
545 m = permutation[j*k+k-1]++;
546 permutation[j*k+m] = i;
547 }
548
549 /*
550 * Track which squares' letters we have already depended
551 * on for deductions. This is gradually updated by
552 * solver_attempt().
553 */
554 memset(gen_lock, 0, wh * sizeof(bool));
555
556 /*
557 * Now repeatedly fill the grid with letters, and attempt
558 * to solve it. If the solver makes progress but does not
559 * fail completely, then gen_lock will have been updated
560 * and we try again. On a complete failure, though, we
561 * have no option but to give up and abandon this set of
562 * ominoes.
563 */
564 solver_init(sc);
565 retries = k*k;
566 while (1) {
567 /*
568 * Fill the grid with letters. We can safely use
569 * sc->tmp to hold the set of letters required at each
570 * stage, since it's at least size k and is currently
571 * unused.
572 */
573 for (i = 0; i < n; i++) {
574 /*
575 * First, determine the set of letters already
576 * placed in this omino by gen_lock.
577 */
578 for (j = 0; j < k; j++)
579 sc->tmp[j] = j;
580 for (j = 0; j < k; j++) {
581 int index = permutation[i*k+j];
582 int letter = grid[index];
583 if (gen_lock[index])
584 sc->tmp[letter] = -1;
585 }
586 /*
587 * Now collect together all the remaining letters
588 * and randomly shuffle them.
589 */
590 for (j = m = 0; j < k; j++)
591 if (sc->tmp[j] >= 0)
592 sc->tmp[m++] = sc->tmp[j];
593 shuffle(sc->tmp, m, sizeof(*sc->tmp), rs);
594 /*
595 * Finally, write the shuffled letters into the
596 * grid.
597 */
598 for (j = 0; j < k; j++) {
599 int index = permutation[i*k+j];
600 if (!gen_lock[index])
601 grid[index] = sc->tmp[--m];
602 }
603 assert(m == 0);
604 }
605
606 /*
607 * Now we have a candidate grid. Attempt to progress
608 * the solution.
609 */
610 m = solver_attempt(sc, grid, gen_lock);
611 if (m == 2 || /* success */
612 (m == 0 && retries-- <= 0)) /* failure */
613 break;
614 if (m == 1)
615 retries = k*k; /* reset this counter, and continue */
616 }
617
618 dsf_free(dsf);
619 } while (m == 0);
620
621 sfree(gen_lock);
622 sfree(permutation);
623 sfree(shuffled);
624 solver_scratch_free(sc);
625
626 return grid;
627}
628
629/* ----------------------------------------------------------------------
630 * End of solver/generator code.
631 */
632
633static char *new_game_desc(const game_params *params, random_state *rs,
634 char **aux, bool interactive)
635{
636 int w = params->w, h = params->h, wh = w*h, k = params->k;
637 unsigned char *grid;
638 char *desc;
639 int i;
640
641 grid = generate(w, h, k, rs);
642
643 desc = snewn(wh+1, char);
644 for (i = 0; i < wh; i++)
645 desc[i] = 'A' + grid[i];
646 desc[wh] = '\0';
647
648 sfree(grid);
649
650 return desc;
651}
652
653static const char *validate_desc(const game_params *params, const char *desc)
654{
655 return NULL;
656}
657
658static game_state *new_game(midend *me, const game_params *params,
659 const char *desc)
660{
661 game_state *state = snew(game_state);
662
663 state->FIXME = 0;
664
665 return state;
666}
667
668static game_state *dup_game(const game_state *state)
669{
670 game_state *ret = snew(game_state);
671
672 ret->FIXME = state->FIXME;
673
674 return ret;
675}
676
677static void free_game(game_state *state)
678{
679 sfree(state);
680}
681
682static char *solve_game(const game_state *state, const game_state *currstate,
683 const char *aux, const char **error)
684{
685 return NULL;
686}
687
688static bool game_can_format_as_text_now(const game_params *params)
689{
690 return true;
691}
692
693static char *game_text_format(const game_state *state)
694{
695 return NULL;
696}
697
698static game_ui *new_ui(const game_state *state)
699{
700 return NULL;
701}
702
703static void free_ui(game_ui *ui)
704{
705}
706
707static void game_changed_state(game_ui *ui, const game_state *oldstate,
708 const game_state *newstate)
709{
710}
711
712struct game_drawstate {
713 int tilesize;
714 int FIXME;
715};
716
717static char *interpret_move(const game_state *state, game_ui *ui,
718 const game_drawstate *ds,
719 int x, int y, int button)
720{
721 return NULL;
722}
723
724static game_state *execute_move(const game_state *state, const char *move)
725{
726 return NULL;
727}
728
729/* ----------------------------------------------------------------------
730 * Drawing routines.
731 */
732
733static void game_compute_size(const game_params *params, int tilesize,
734 const game_ui *ui, int *x, int *y)
735{
736 *x = *y = 10 * tilesize; /* FIXME */
737}
738
739static void game_set_size(drawing *dr, game_drawstate *ds,
740 const game_params *params, int tilesize)
741{
742 ds->tilesize = tilesize;
743}
744
745static float *game_colours(frontend *fe, int *ncolours)
746{
747 float *ret = snewn(3 * NCOLOURS, float);
748
749 frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
750
751 *ncolours = NCOLOURS;
752 return ret;
753}
754
755static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
756{
757 struct game_drawstate *ds = snew(struct game_drawstate);
758
759 ds->tilesize = 0;
760 ds->FIXME = 0;
761
762 return ds;
763}
764
765static void game_free_drawstate(drawing *dr, game_drawstate *ds)
766{
767 sfree(ds);
768}
769
770static void game_redraw(drawing *dr, game_drawstate *ds,
771 const game_state *oldstate, const game_state *state,
772 int dir, const game_ui *ui,
773 float animtime, float flashtime)
774{
775}
776
777static float game_anim_length(const game_state *oldstate,
778 const game_state *newstate, int dir, game_ui *ui)
779{
780 return 0.0F;
781}
782
783static float game_flash_length(const game_state *oldstate,
784 const game_state *newstate, int dir, game_ui *ui)
785{
786 return 0.0F;
787}
788
789static void game_get_cursor_location(const game_ui *ui,
790 const game_drawstate *ds,
791 const game_state *state,
792 const game_params *params,
793 int *x, int *y, int *w, int *h)
794{
795}
796
797static int game_status(const game_state *state)
798{
799 return 0;
800}
801
802static bool game_timing_state(const game_state *state, game_ui *ui)
803{
804 return true;
805}
806
807static void game_print_size(const game_params *params, const game_ui *ui,
808 float *x, float *y)
809{
810}
811
812static void game_print(drawing *dr, const game_state *state, const game_ui *ui,
813 int tilesize)
814{
815}
816
817#ifdef COMBINED
818#define thegame separate
819#endif
820
821const struct game thegame = {
822 "Separate", NULL, NULL,
823 default_params,
824 game_fetch_preset, NULL,
825 decode_params,
826 encode_params,
827 free_params,
828 dup_params,
829 false, game_configure, custom_params,
830 validate_params,
831 new_game_desc,
832 validate_desc,
833 new_game,
834 dup_game,
835 free_game,
836 false, solve_game,
837 false, game_can_format_as_text_now, game_text_format,
838 NULL, NULL, /* get_prefs, set_prefs */
839 new_ui,
840 free_ui,
841 NULL, /* encode_ui */
842 NULL, /* decode_ui */
843 NULL, /* game_request_keys */
844 game_changed_state,
845 NULL, /* current_key_label */
846 interpret_move,
847 execute_move,
848 20 /* FIXME */, game_compute_size, game_set_size,
849 game_colours,
850 game_new_drawstate,
851 game_free_drawstate,
852 game_redraw,
853 game_anim_length,
854 game_flash_length,
855 game_get_cursor_location,
856 game_status,
857 false, false, game_print_size, game_print,
858 false, /* wants_statusbar */
859 false, game_timing_state,
860 0, /* flags */
861};
diff --git a/apps/plugins/puzzles/src/unfinished/slide.c b/apps/plugins/puzzles/src/unfinished/slide.c
new file mode 100644
index 0000000000..4c4d98943e
--- /dev/null
+++ b/apps/plugins/puzzles/src/unfinished/slide.c
@@ -0,0 +1,2444 @@
1/*
2 * slide.c: Implementation of the block-sliding puzzle `Klotski'.
3 */
4
5/*
6 * TODO:
7 *
8 * - Improve the generator.
9 * * actually, we seem to be mostly sensible already now. I
10 * want more choice over the type of main block and location
11 * of the exit/target, and I think I probably ought to give
12 * up on compactness and just bite the bullet and have the
13 * target area right outside the main wall, but mostly I
14 * think it's OK.
15 * * the move limit tends to make the game _slower_ to
16 * generate, which is odd. Perhaps investigate why.
17 *
18 * - Improve the graphics.
19 * * All the colours are a bit wishy-washy. _Some_ dark
20 * colours would surely not be excessive? Probably darken
21 * the tiles, the walls and the main block, and leave the
22 * target marker pale.
23 * * The cattle grid effect is still disgusting. Think of
24 * something completely different.
25 * * The highlight for next-piece-to-move in the solver is
26 * excessive, and the shadow blends in too well with the
27 * piece lowlights. Adjust both.
28 */
29
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <assert.h>
34#include <ctype.h>
35#ifdef NO_TGMATH_H
36# include <math.h>
37#else
38# include <tgmath.h>
39#endif
40
41#include "puzzles.h"
42#include "tree234.h"
43
44/*
45 * The implementation of this game revolves around the insight
46 * which makes an exhaustive-search solver feasible: although
47 * there are many blocks which can be rearranged in many ways, any
48 * two blocks of the same shape are _indistinguishable_ and hence
49 * the number of _distinct_ board layouts is generally much
50 * smaller. So we adopt a representation for board layouts which
51 * is inherently canonical, i.e. there are no two distinct
52 * representations which encode indistinguishable layouts.
53 *
54 * The way we do this is to encode each square of the board, in
55 * the normal left-to-right top-to-bottom order, as being one of
56 * the following things:
57 * - the first square (in the given order) of a block (`anchor')
58 * - special case of the above: the anchor for the _main_ block
59 * (i.e. the one which the aim of the game is to get to the
60 * target position)
61 * - a subsequent square of a block whose previous square was N
62 * squares ago
63 * - an impassable wall
64 *
65 * (We also separately store data about which board positions are
66 * forcefields only passable by the main block. We can't encode
67 * that in the main board data, because then the main block would
68 * destroy forcefields as it went over them.)
69 *
70 * Hence, for example, a 2x2 square block would be encoded as
71 * ANCHOR, followed by DIST(1), and w-2 squares later on there
72 * would be DIST(w-1) followed by DIST(1). So if you start at the
73 * last of those squares, the DIST numbers give you a linked list
74 * pointing back through all the other squares in the same block.
75 *
76 * So the solver simply does a bfs over all reachable positions,
77 * encoding them in this format and storing them in a tree234 to
78 * ensure it doesn't ever revisit an already-analysed position.
79 */
80
81enum {
82 /*
83 * The colours are arranged here so that every base colour is
84 * directly followed by its highlight colour and then its
85 * lowlight colour. Do not break this, or draw_tile() will get
86 * confused.
87 */
88 COL_BACKGROUND,
89 COL_HIGHLIGHT,
90 COL_LOWLIGHT,
91 COL_DRAGGING,
92 COL_DRAGGING_HIGHLIGHT,
93 COL_DRAGGING_LOWLIGHT,
94 COL_MAIN,
95 COL_MAIN_HIGHLIGHT,
96 COL_MAIN_LOWLIGHT,
97 COL_MAIN_DRAGGING,
98 COL_MAIN_DRAGGING_HIGHLIGHT,
99 COL_MAIN_DRAGGING_LOWLIGHT,
100 COL_TARGET,
101 COL_TARGET_HIGHLIGHT,
102 COL_TARGET_LOWLIGHT,
103 NCOLOURS
104};
105
106/*
107 * Board layout is a simple array of bytes. Each byte holds:
108 */
109#define ANCHOR 255 /* top-left-most square of some piece */
110#define MAINANCHOR 254 /* anchor of _main_ piece */
111#define EMPTY 253 /* empty square */
112#define WALL 252 /* immovable wall */
113#define MAXDIST 251
114/* all other values indicate distance back to previous square of same block */
115#define ISDIST(x) ( (unsigned char)((x)-1) <= MAXDIST-1 )
116#define DIST(x) (x)
117#define ISANCHOR(x) ( (x)==ANCHOR || (x)==MAINANCHOR )
118#define ISBLOCK(x) ( ISANCHOR(x) || ISDIST(x) )
119
120/*
121 * MAXDIST is the largest DIST value we can encode. This must
122 * therefore also be the maximum puzzle width in theory (although
123 * solver running time will dictate a much smaller limit in
124 * practice).
125 */
126#define MAXWID MAXDIST
127
128struct game_params {
129 int w, h;
130 int maxmoves;
131};
132
133struct game_immutable_state {
134 int refcount;
135 bool *forcefield;
136};
137
138struct game_solution {
139 int nmoves;
140 int *moves; /* just like from solve_board() */
141 int refcount;
142};
143
144struct game_state {
145 int w, h;
146 unsigned char *board;
147 int tx, ty; /* target coords for MAINANCHOR */
148 int minmoves; /* for display only */
149 int lastmoved, lastmoved_pos; /* for move counting */
150 int movecount;
151 int completed;
152 bool cheated;
153 struct game_immutable_state *imm;
154 struct game_solution *soln;
155 int soln_index;
156};
157
158static game_params *default_params(void)
159{
160 game_params *ret = snew(game_params);
161
162 ret->w = 7;
163 ret->h = 6;
164 ret->maxmoves = 40;
165
166 return ret;
167}
168
169static const struct game_params slide_presets[] = {
170 {7, 6, 25},
171 {7, 6, -1},
172 {8, 6, -1},
173};
174
175static bool game_fetch_preset(int i, char **name, game_params **params)
176{
177 game_params *ret;
178 char str[80];
179
180 if (i < 0 || i >= lenof(slide_presets))
181 return false;
182
183 ret = snew(game_params);
184 *ret = slide_presets[i];
185
186 sprintf(str, "%dx%d", ret->w, ret->h);
187 if (ret->maxmoves >= 0)
188 sprintf(str + strlen(str), ", max %d moves", ret->maxmoves);
189 else
190 sprintf(str + strlen(str), ", no move limit");
191
192 *name = dupstr(str);
193 *params = ret;
194 return true;
195}
196
197static void free_params(game_params *params)
198{
199 sfree(params);
200}
201
202static game_params *dup_params(const game_params *params)
203{
204 game_params *ret = snew(game_params);
205 *ret = *params; /* structure copy */
206 return ret;
207}
208
209static void decode_params(game_params *params, char const *string)
210{
211 params->w = params->h = atoi(string);
212 while (*string && isdigit((unsigned char)*string)) string++;
213 if (*string == 'x') {
214 string++;
215 params->h = atoi(string);
216 while (*string && isdigit((unsigned char)*string)) string++;
217 }
218 if (*string == 'm') {
219 string++;
220 params->maxmoves = atoi(string);
221 while (*string && isdigit((unsigned char)*string)) string++;
222 } else if (*string == 'u') {
223 string++;
224 params->maxmoves = -1;
225 }
226}
227
228static char *encode_params(const game_params *params, bool full)
229{
230 char data[256];
231
232 sprintf(data, "%dx%d", params->w, params->h);
233 if (params->maxmoves >= 0)
234 sprintf(data + strlen(data), "m%d", params->maxmoves);
235 else
236 sprintf(data + strlen(data), "u");
237
238 return dupstr(data);
239}
240
241static config_item *game_configure(const game_params *params)
242{
243 config_item *ret;
244 char buf[80];
245
246 ret = snewn(4, config_item);
247
248 ret[0].name = "Width";
249 ret[0].type = C_STRING;
250 sprintf(buf, "%d", params->w);
251 ret[0].u.string.sval = dupstr(buf);
252
253 ret[1].name = "Height";
254 ret[1].type = C_STRING;
255 sprintf(buf, "%d", params->h);
256 ret[1].u.string.sval = dupstr(buf);
257
258 ret[2].name = "Solution length limit";
259 ret[2].type = C_STRING;
260 sprintf(buf, "%d", params->maxmoves);
261 ret[2].u.string.sval = dupstr(buf);
262
263 ret[3].name = NULL;
264 ret[3].type = C_END;
265
266 return ret;
267}
268
269static game_params *custom_params(const config_item *cfg)
270{
271 game_params *ret = snew(game_params);
272
273 ret->w = atoi(cfg[0].u.string.sval);
274 ret->h = atoi(cfg[1].u.string.sval);
275 ret->maxmoves = atoi(cfg[2].u.string.sval);
276
277 return ret;
278}
279
280static const char *validate_params(const game_params *params, bool full)
281{
282 if (params->w > MAXWID)
283 return "Width must be at most " STR(MAXWID);
284
285 if (params->w < 5)
286 return "Width must be at least 5";
287 if (params->h < 4)
288 return "Height must be at least 4";
289
290 return NULL;
291}
292
293static char *board_text_format(int w, int h, unsigned char *data,
294 bool *forcefield)
295{
296 int wh = w*h;
297 DSF *dsf = dsf_new(wh);
298 int i, x, y;
299 int retpos, retlen = (w*2+2)*(h*2+1)+1;
300 char *ret = snewn(retlen, char);
301
302 for (i = 0; i < wh; i++)
303 if (ISDIST(data[i]))
304 dsf_merge(dsf, i - data[i], i);
305 retpos = 0;
306 for (y = 0; y < 2*h+1; y++) {
307 for (x = 0; x < 2*w+1; x++) {
308 int v;
309 int i = (y/2)*w+(x/2);
310
311#define dtype(i) (ISBLOCK(data[i]) ? \
312 dsf_canonify(dsf, i) : data[i])
313#define dchar(t) ((t)==EMPTY ? ' ' : (t)==WALL ? '#' : \
314 data[t] == MAINANCHOR ? '*' : '%')
315
316 if (y % 2 && x % 2) {
317 int j = dtype(i);
318 v = dchar(j);
319 } else if (y % 2 && !(x % 2)) {
320 int j1 = (x > 0 ? dtype(i-1) : -1);
321 int j2 = (x < 2*w ? dtype(i) : -1);
322 if (j1 != j2)
323 v = '|';
324 else
325 v = dchar(j1);
326 } else if (!(y % 2) && (x % 2)) {
327 int j1 = (y > 0 ? dtype(i-w) : -1);
328 int j2 = (y < 2*h ? dtype(i) : -1);
329 if (j1 != j2)
330 v = '-';
331 else
332 v = dchar(j1);
333 } else {
334 int j1 = (x > 0 && y > 0 ? dtype(i-w-1) : -1);
335 int j2 = (x > 0 && y < 2*h ? dtype(i-1) : -1);
336 int j3 = (x < 2*w && y > 0 ? dtype(i-w) : -1);
337 int j4 = (x < 2*w && y < 2*h ? dtype(i) : -1);
338 if (j1 == j2 && j2 == j3 && j3 == j4)
339 v = dchar(j1);
340 else if (j1 == j2 && j3 == j4)
341 v = '|';
342 else if (j1 == j3 && j2 == j4)
343 v = '-';
344 else
345 v = '+';
346 }
347
348 assert(retpos < retlen);
349 ret[retpos++] = v;
350 }
351 assert(retpos < retlen);
352 ret[retpos++] = '\n';
353 }
354 assert(retpos < retlen);
355 ret[retpos++] = '\0';
356 assert(retpos == retlen);
357
358 return ret;
359}
360
361/* ----------------------------------------------------------------------
362 * Solver.
363 */
364
365/*
366 * During solver execution, the set of visited board positions is
367 * stored as a tree234 of the following structures. `w', `h' and
368 * `data' are obvious in meaning; `dist' represents the minimum
369 * distance to reach this position from the starting point.
370 *
371 * `prev' links each board to the board position from which it was
372 * most efficiently derived.
373 */
374struct board {
375 int w, h;
376 int dist;
377 struct board *prev;
378 unsigned char *data;
379};
380
381static int boardcmp(void *av, void *bv)
382{
383 struct board *a = (struct board *)av;
384 struct board *b = (struct board *)bv;
385 return memcmp(a->data, b->data, a->w * a->h);
386}
387
388static struct board *newboard(int w, int h, unsigned char *data)
389{
390 struct board *b = malloc(sizeof(struct board) + w*h);
391 b->data = (unsigned char *)b + sizeof(struct board);
392 memcpy(b->data, data, w*h);
393 b->w = w;
394 b->h = h;
395 b->dist = -1;
396 b->prev = NULL;
397 return b;
398}
399
400/*
401 * The actual solver. Given a board, attempt to find the minimum
402 * length of move sequence which moves MAINANCHOR to (tx,ty), or
403 * -1 if no solution exists. Returns that minimum length.
404 *
405 * Also, if `moveout' is provided, writes out the moves in the
406 * form of a sequence of pairs of integers indicating the source
407 * and destination points of the anchor of the moved piece in each
408 * move. Exactly twice as many integers are written as the number
409 * returned from solve_board(), and `moveout' receives an int *
410 * which is a pointer to a dynamically allocated array.
411 */
412static int solve_board(int w, int h, unsigned char *board,
413 bool *forcefield, int tx, int ty,
414 int movelimit, int **moveout)
415{
416 int wh = w*h;
417 struct board *b, *b2, *b3;
418 int *next, *which;
419 bool *anchors, *movereached;
420 int *movequeue, mqhead, mqtail;
421 tree234 *sorted, *queue;
422 int i, j, dir;
423 int qlen, lastdist;
424 int ret;
425
426#ifdef SOLVER_DIAGNOSTICS
427 {
428 char *t = board_text_format(w, h, board);
429 for (i = 0; i < h; i++) {
430 for (j = 0; j < w; j++) {
431 int c = board[i*w+j];
432 if (ISDIST(c))
433 printf("D%-3d", c);
434 else if (c == MAINANCHOR)
435 printf("M ");
436 else if (c == ANCHOR)
437 printf("A ");
438 else if (c == WALL)
439 printf("W ");
440 else if (c == EMPTY)
441 printf("E ");
442 }
443 printf("\n");
444 }
445
446 printf("Starting solver for:\n%s\n", t);
447 sfree(t);
448 }
449#endif
450
451 sorted = newtree234(boardcmp);
452 queue = newtree234(NULL);
453
454 b = newboard(w, h, board);
455 b->dist = 0;
456 add234(sorted, b);
457 addpos234(queue, b, 0);
458 qlen = 1;
459
460 next = snewn(wh, int);
461 anchors = snewn(wh, bool);
462 which = snewn(wh, int);
463 movereached = snewn(wh, bool);
464 movequeue = snewn(wh, int);
465 lastdist = -1;
466
467 while ((b = delpos234(queue, 0)) != NULL) {
468 qlen--;
469 if (movelimit >= 0 && b->dist >= movelimit) {
470 /*
471 * The problem is not soluble in under `movelimit'
472 * moves, so we can quit right now.
473 */
474 b2 = NULL;
475 goto done;
476 }
477 if (b->dist != lastdist) {
478#ifdef SOLVER_DIAGNOSTICS
479 printf("dist %d (%d)\n", b->dist, count234(sorted));
480#endif
481 lastdist = b->dist;
482 }
483 /*
484 * Find all the anchors and form a linked list of the
485 * squares within each block.
486 */
487 for (i = 0; i < wh; i++) {
488 next[i] = -1;
489 anchors[i] = false;
490 which[i] = -1;
491 if (ISANCHOR(b->data[i])) {
492 anchors[i] = true;
493 which[i] = i;
494 } else if (ISDIST(b->data[i])) {
495 j = i - b->data[i];
496 next[j] = i;
497 which[i] = which[j];
498 }
499 }
500
501 /*
502 * For each anchor, do an array-based BFS to find all the
503 * places we can slide it to.
504 */
505 for (i = 0; i < wh; i++) {
506 if (!anchors[i])
507 continue;
508
509 mqhead = mqtail = 0;
510 for (j = 0; j < wh; j++)
511 movereached[j] = false;
512 movequeue[mqtail++] = i;
513 while (mqhead < mqtail) {
514 int pos = movequeue[mqhead++];
515
516 /*
517 * Try to move in each direction from here.
518 */
519 for (dir = 0; dir < 4; dir++) {
520 int dx = (dir == 0 ? -1 : dir == 1 ? +1 : 0);
521 int dy = (dir == 2 ? -1 : dir == 3 ? +1 : 0);
522 int offset = dy*w + dx;
523 int newpos = pos + offset;
524 int d = newpos - i;
525
526 /*
527 * For each square involved in this block,
528 * check to see if the square d spaces away
529 * from it is either empty or part of the same
530 * block.
531 */
532 for (j = i; j >= 0; j = next[j]) {
533 int jy = (pos+j-i) / w + dy, jx = (pos+j-i) % w + dx;
534 if (jy >= 0 && jy < h && jx >= 0 && jx < w &&
535 ((b->data[j+d] == EMPTY || which[j+d] == i) &&
536 (b->data[i] == MAINANCHOR || !forcefield[j+d])))
537 /* ok */;
538 else
539 break;
540 }
541 if (j >= 0)
542 continue; /* this direction wasn't feasible */
543
544 /*
545 * If we've already tried moving this piece
546 * here, leave it.
547 */
548 if (movereached[newpos])
549 continue;
550 movereached[newpos] = true;
551 movequeue[mqtail++] = newpos;
552
553 /*
554 * We have a viable move. Make it.
555 */
556 b2 = newboard(w, h, b->data);
557 for (j = i; j >= 0; j = next[j])
558 b2->data[j] = EMPTY;
559 for (j = i; j >= 0; j = next[j])
560 b2->data[j+d] = b->data[j];
561
562 b3 = add234(sorted, b2);
563 if (b3 != b2) {
564 sfree(b2); /* we already got one */
565 } else {
566 b2->dist = b->dist + 1;
567 b2->prev = b;
568 addpos234(queue, b2, qlen++);
569 if (b2->data[ty*w+tx] == MAINANCHOR)
570 goto done; /* search completed! */
571 }
572 }
573 }
574 }
575 }
576 b2 = NULL;
577
578 done:
579
580 if (b2) {
581 ret = b2->dist;
582 if (moveout) {
583 /*
584 * Now b2 represents the solved position. Backtrack to
585 * output the solution.
586 */
587 *moveout = snewn(ret * 2, int);
588 j = ret * 2;
589
590 while (b2->prev) {
591 int from = -1, to = -1;
592
593 b = b2->prev;
594
595 /*
596 * Scan b and b2 to find out which piece has
597 * moved.
598 */
599 for (i = 0; i < wh; i++) {
600 if (ISANCHOR(b->data[i]) && !ISANCHOR(b2->data[i])) {
601 assert(from == -1);
602 from = i;
603 } else if (!ISANCHOR(b->data[i]) && ISANCHOR(b2->data[i])){
604 assert(to == -1);
605 to = i;
606 }
607 }
608
609 assert(from >= 0 && to >= 0);
610 assert(j >= 2);
611 (*moveout)[--j] = to;
612 (*moveout)[--j] = from;
613
614 b2 = b;
615 }
616 assert(j == 0);
617 }
618 } else {
619 ret = -1; /* no solution */
620 if (moveout)
621 *moveout = NULL;
622 }
623
624 freetree234(queue);
625
626 while ((b = delpos234(sorted, 0)) != NULL)
627 sfree(b);
628 freetree234(sorted);
629
630 sfree(next);
631 sfree(anchors);
632 sfree(movereached);
633 sfree(movequeue);
634 sfree(which);
635
636 return ret;
637}
638
639/* ----------------------------------------------------------------------
640 * Random board generation.
641 */
642
643static void generate_board(int w, int h, int *rtx, int *rty, int *minmoves,
644 random_state *rs, unsigned char **rboard,
645 bool **rforcefield, int movelimit)
646{
647 int wh = w*h;
648 unsigned char *board, *board2;
649 bool *forcefield;
650 bool *tried_merge;
651 DSF *dsf;
652 int *list, nlist, pos;
653 int tx, ty;
654 int i, j;
655 int moves = 0; /* placate optimiser */
656
657 /*
658 * Set up a board and fill it with singletons, except for a
659 * border of walls.
660 */
661 board = snewn(wh, unsigned char);
662 forcefield = snewn(wh, bool);
663 board2 = snewn(wh, unsigned char);
664 memset(board, ANCHOR, wh);
665 memset(forcefield, 0, wh * sizeof(bool));
666 for (i = 0; i < w; i++)
667 board[i] = board[i+w*(h-1)] = WALL;
668 for (i = 0; i < h; i++)
669 board[i*w] = board[i*w+(w-1)] = WALL;
670
671 tried_merge = snewn(wh * wh, bool);
672 memset(tried_merge, 0, wh*wh * sizeof(bool));
673 dsf = dsf_new(wh);
674
675 /*
676 * Invent a main piece at one extreme. (FIXME: vary the
677 * extreme, and the piece.)
678 */
679 board[w+1] = MAINANCHOR;
680 board[w+2] = DIST(1);
681 board[w*2+1] = DIST(w-1);
682 board[w*2+2] = DIST(1);
683
684 /*
685 * Invent a target position. (FIXME: vary this too.)
686 */
687 tx = w-2;
688 ty = h-3;
689 forcefield[ty*w+tx+1] = true;
690 forcefield[(ty+1)*w+tx+1] = true;
691 board[ty*w+tx+1] = board[(ty+1)*w+tx+1] = EMPTY;
692
693 /*
694 * Gradually remove singletons until the game becomes soluble.
695 */
696 for (j = w; j-- > 0 ;)
697 for (i = h; i-- > 0 ;)
698 if (board[i*w+j] == ANCHOR) {
699 /*
700 * See if the board is already soluble.
701 */
702 if ((moves = solve_board(w, h, board, forcefield,
703 tx, ty, movelimit, NULL)) >= 0)
704 goto soluble;
705
706 /*
707 * Otherwise, remove this piece.
708 */
709 board[i*w+j] = EMPTY;
710 }
711 assert(!"We shouldn't get here");
712 soluble:
713
714 /*
715 * Make a list of all the inter-block edges on the board.
716 */
717 list = snewn(wh*2, int);
718 nlist = 0;
719 for (i = 0; i+1 < w; i++)
720 for (j = 0; j < h; j++)
721 list[nlist++] = (j*w+i) * 2 + 0; /* edge to the right of j*w+i */
722 for (j = 0; j+1 < h; j++)
723 for (i = 0; i < w; i++)
724 list[nlist++] = (j*w+i) * 2 + 1; /* edge below j*w+i */
725
726 /*
727 * Now go through that list in random order, trying to merge
728 * the blocks on each side of each edge.
729 */
730 shuffle(list, nlist, sizeof(*list), rs);
731 while (nlist > 0) {
732 int x1, y1, p1, c1;
733 int x2, y2, p2, c2;
734
735 pos = list[--nlist];
736 y1 = y2 = pos / (w*2);
737 x1 = x2 = (pos / 2) % w;
738 if (pos % 2)
739 y2++;
740 else
741 x2++;
742 p1 = y1*w+x1;
743 p2 = y2*w+x2;
744
745 /*
746 * Immediately abandon the attempt if we've already tried
747 * to merge the same pair of blocks along a different
748 * edge.
749 */
750 c1 = dsf_canonify(dsf, p1);
751 c2 = dsf_canonify(dsf, p2);
752 if (tried_merge[c1 * wh + c2])
753 continue;
754
755 /*
756 * In order to be mergeable, these two squares must each
757 * either be, or belong to, a non-main anchor, and their
758 * anchors must also be distinct.
759 */
760 if (!ISBLOCK(board[p1]) || !ISBLOCK(board[p2]))
761 continue;
762 while (ISDIST(board[p1]))
763 p1 -= board[p1];
764 while (ISDIST(board[p2]))
765 p2 -= board[p2];
766 if (board[p1] == MAINANCHOR || board[p2] == MAINANCHOR || p1 == p2)
767 continue;
768
769 /*
770 * We can merge these blocks. Try it, and see if the
771 * puzzle remains soluble.
772 */
773 memcpy(board2, board, wh);
774 j = -1;
775 while (p1 < wh || p2 < wh) {
776 /*
777 * p1 and p2 are the squares at the head of each block
778 * list. Pick the smaller one and put it on the output
779 * block list.
780 */
781 i = min(p1, p2);
782 if (j < 0) {
783 board[i] = ANCHOR;
784 } else {
785 assert(i - j <= MAXDIST);
786 board[i] = DIST(i - j);
787 }
788 j = i;
789
790 /*
791 * Now advance whichever list that came from.
792 */
793 if (i == p1) {
794 do {
795 p1++;
796 } while (p1 < wh && board[p1] != DIST(p1-i));
797 } else {
798 do {
799 p2++;
800 } while (p2 < wh && board[p2] != DIST(p2-i));
801 }
802 }
803 j = solve_board(w, h, board, forcefield, tx, ty, movelimit, NULL);
804 if (j < 0) {
805 /*
806 * Didn't work. Revert the merge.
807 */
808 memcpy(board, board2, wh);
809 tried_merge[c1 * wh + c2] = true;
810 tried_merge[c2 * wh + c1] = true;
811 } else {
812 int c;
813
814 moves = j;
815
816 dsf_merge(dsf, c1, c2);
817 c = dsf_canonify(dsf, c1);
818 for (i = 0; i < wh; i++)
819 tried_merge[c*wh+i] = (tried_merge[c1*wh+i] ||
820 tried_merge[c2*wh+i]);
821 for (i = 0; i < wh; i++)
822 tried_merge[i*wh+c] = (tried_merge[i*wh+c1] ||
823 tried_merge[i*wh+c2]);
824 }
825 }
826
827 dsf_free(dsf);
828 sfree(list);
829 sfree(tried_merge);
830 sfree(board2);
831
832 *rtx = tx;
833 *rty = ty;
834 *rboard = board;
835 *rforcefield = forcefield;
836 *minmoves = moves;
837}
838
839/* ----------------------------------------------------------------------
840 * End of solver/generator code.
841 */
842
843static char *new_game_desc(const game_params *params, random_state *rs,
844 char **aux, bool interactive)
845{
846 int w = params->w, h = params->h, wh = w*h;
847 int tx, ty, minmoves;
848 unsigned char *board;
849 bool *forcefield;
850 char *ret, *p;
851 int i;
852
853 generate_board(params->w, params->h, &tx, &ty, &minmoves, rs,
854 &board, &forcefield, params->maxmoves);
855#ifdef GENERATOR_DIAGNOSTICS
856 {
857 char *t = board_text_format(params->w, params->h, board);
858 printf("%s\n", t);
859 sfree(t);
860 }
861#endif
862
863 /*
864 * Encode as a game ID.
865 */
866 ret = snewn(wh * 6 + 40, char);
867 p = ret;
868 i = 0;
869 while (i < wh) {
870 if (ISDIST(board[i])) {
871 p += sprintf(p, "d%d", board[i]);
872 i++;
873 } else {
874 int count = 1;
875 int b = board[i];
876 bool f = forcefield[i];
877 int c = (b == ANCHOR ? 'a' :
878 b == MAINANCHOR ? 'm' :
879 b == EMPTY ? 'e' :
880 /* b == WALL ? */ 'w');
881 if (f) *p++ = 'f';
882 *p++ = c;
883 i++;
884 while (i < wh && board[i] == b && forcefield[i] == f)
885 i++, count++;
886 if (count > 1)
887 p += sprintf(p, "%d", count);
888 }
889 }
890 p += sprintf(p, ",%d,%d,%d", tx, ty, minmoves);
891 ret = sresize(ret, p+1 - ret, char);
892
893 sfree(board);
894 sfree(forcefield);
895
896 return ret;
897}
898
899static const char *validate_desc(const game_params *params, const char *desc)
900{
901 int w = params->w, h = params->h, wh = w*h;
902 bool *active;
903 int *link;
904 int mains = 0;
905 int i, tx, ty, minmoves;
906 const char *ret;
907
908 active = snewn(wh, bool);
909 link = snewn(wh, int);
910 i = 0;
911
912 while (*desc && *desc != ',') {
913 if (i >= wh) {
914 ret = "Too much data in game description";
915 goto done;
916 }
917 link[i] = -1;
918 active[i] = false;
919 if (*desc == 'f' || *desc == 'F') {
920 desc++;
921 if (!*desc) {
922 ret = "Expected another character after 'f' in game "
923 "description";
924 goto done;
925 }
926 }
927
928 if (*desc == 'd' || *desc == 'D') {
929 int dist;
930
931 desc++;
932 if (!isdigit((unsigned char)*desc)) {
933 ret = "Expected a number after 'd' in game description";
934 goto done;
935 }
936 dist = atoi(desc);
937 while (*desc && isdigit((unsigned char)*desc)) desc++;
938
939 if (dist <= 0 || dist > i) {
940 ret = "Out-of-range number after 'd' in game description";
941 goto done;
942 }
943
944 if (!active[i - dist]) {
945 ret = "Invalid back-reference in game description";
946 goto done;
947 }
948
949 link[i] = i - dist;
950
951 active[i] = true;
952 active[link[i]] = false;
953 i++;
954 } else {
955 int c = *desc++;
956 int count = 1;
957
958 if (!strchr("aAmMeEwW", c)) {
959 ret = "Invalid character in game description";
960 goto done;
961 }
962 if (isdigit((unsigned char)*desc)) {
963 count = atoi(desc);
964 while (*desc && isdigit((unsigned char)*desc)) desc++;
965 }
966 if (i + count > wh) {
967 ret = "Too much data in game description";
968 goto done;
969 }
970 while (count-- > 0) {
971 active[i] = (strchr("aAmM", c) != NULL);
972 link[i] = -1;
973 if (strchr("mM", c) != NULL) {
974 mains++;
975 }
976 i++;
977 }
978 }
979 }
980 if (mains != 1) {
981 ret = (mains == 0 ? "No main piece specified in game description" :
982 "More than one main piece specified in game description");
983 goto done;
984 }
985 if (i < wh) {
986 ret = "Not enough data in game description";
987 goto done;
988 }
989
990 /*
991 * Now read the target coordinates.
992 */
993 i = sscanf(desc, ",%d,%d,%d", &tx, &ty, &minmoves);
994 if (i < 2) {
995 ret = "No target coordinates specified";
996 goto done;
997 /*
998 * (but minmoves is optional)
999 */
1000 }
1001
1002 ret = NULL;
1003
1004 done:
1005 sfree(active);
1006 sfree(link);
1007 return ret;
1008}
1009
1010static game_state *new_game(midend *me, const game_params *params,
1011 const char *desc)
1012{
1013 int w = params->w, h = params->h, wh = w*h;
1014 game_state *state;
1015 int i;
1016
1017 state = snew(game_state);
1018 state->w = w;
1019 state->h = h;
1020 state->board = snewn(wh, unsigned char);
1021 state->lastmoved = state->lastmoved_pos = -1;
1022 state->movecount = 0;
1023 state->imm = snew(struct game_immutable_state);
1024 state->imm->refcount = 1;
1025 state->imm->forcefield = snewn(wh, bool);
1026
1027 i = 0;
1028
1029 while (*desc && *desc != ',') {
1030 bool f = false;
1031
1032 assert(i < wh);
1033
1034 if (*desc == 'f') {
1035 f = true;
1036 desc++;
1037 assert(*desc);
1038 }
1039
1040 if (*desc == 'd' || *desc == 'D') {
1041 int dist;
1042
1043 desc++;
1044 dist = atoi(desc);
1045 while (*desc && isdigit((unsigned char)*desc)) desc++;
1046
1047 state->board[i] = DIST(dist);
1048 state->imm->forcefield[i] = f;
1049
1050 i++;
1051 } else {
1052 int c = *desc++;
1053 int count = 1;
1054
1055 if (isdigit((unsigned char)*desc)) {
1056 count = atoi(desc);
1057 while (*desc && isdigit((unsigned char)*desc)) desc++;
1058 }
1059 assert(i + count <= wh);
1060
1061 c = (c == 'a' || c == 'A' ? ANCHOR :
1062 c == 'm' || c == 'M' ? MAINANCHOR :
1063 c == 'e' || c == 'E' ? EMPTY :
1064 /* c == 'w' || c == 'W' ? */ WALL);
1065
1066 while (count-- > 0) {
1067 state->board[i] = c;
1068 state->imm->forcefield[i] = f;
1069 i++;
1070 }
1071 }
1072 }
1073
1074 /*
1075 * Now read the target coordinates.
1076 */
1077 state->tx = state->ty = 0;
1078 state->minmoves = -1;
1079 i = sscanf(desc, ",%d,%d,%d", &state->tx, &state->ty, &state->minmoves);
1080
1081 if (state->board[state->ty*w+state->tx] == MAINANCHOR)
1082 state->completed = 0; /* already complete! */
1083 else
1084 state->completed = -1;
1085
1086 state->cheated = false;
1087 state->soln = NULL;
1088 state->soln_index = -1;
1089
1090 return state;
1091}
1092
1093static game_state *dup_game(const game_state *state)
1094{
1095 int w = state->w, h = state->h, wh = w*h;
1096 game_state *ret = snew(game_state);
1097
1098 ret->w = state->w;
1099 ret->h = state->h;
1100 ret->board = snewn(wh, unsigned char);
1101 memcpy(ret->board, state->board, wh);
1102 ret->tx = state->tx;
1103 ret->ty = state->ty;
1104 ret->minmoves = state->minmoves;
1105 ret->lastmoved = state->lastmoved;
1106 ret->lastmoved_pos = state->lastmoved_pos;
1107 ret->movecount = state->movecount;
1108 ret->completed = state->completed;
1109 ret->cheated = state->cheated;
1110 ret->imm = state->imm;
1111 ret->imm->refcount++;
1112 ret->soln = state->soln;
1113 ret->soln_index = state->soln_index;
1114 if (ret->soln)
1115 ret->soln->refcount++;
1116
1117 return ret;
1118}
1119
1120static void free_game(game_state *state)
1121{
1122 if (--state->imm->refcount <= 0) {
1123 sfree(state->imm->forcefield);
1124 sfree(state->imm);
1125 }
1126 if (state->soln && --state->soln->refcount <= 0) {
1127 sfree(state->soln->moves);
1128 sfree(state->soln);
1129 }
1130 sfree(state->board);
1131 sfree(state);
1132}
1133
1134static char *solve_game(const game_state *state, const game_state *currstate,
1135 const char *aux, const char **error)
1136{
1137 int *moves;
1138 int nmoves;
1139 int i;
1140 char *ret, *p, sep;
1141
1142 /*
1143 * Run the solver and attempt to find the shortest solution
1144 * from the current position.
1145 */
1146 nmoves = solve_board(state->w, state->h, state->board,
1147 state->imm->forcefield, state->tx, state->ty,
1148 -1, &moves);
1149
1150 if (nmoves < 0) {
1151 *error = "Unable to find a solution to this puzzle";
1152 return NULL;
1153 }
1154 if (nmoves == 0) {
1155 *error = "Puzzle is already solved";
1156 return NULL;
1157 }
1158
1159 /*
1160 * Encode the resulting solution as a move string.
1161 */
1162 ret = snewn(nmoves * 40, char);
1163 p = ret;
1164 sep = 'S';
1165
1166 for (i = 0; i < nmoves; i++) {
1167 p += sprintf(p, "%c%d-%d", sep, moves[i*2], moves[i*2+1]);
1168 sep = ',';
1169 }
1170
1171 sfree(moves);
1172 assert(p - ret < nmoves * 40);
1173 ret = sresize(ret, p+1 - ret, char);
1174
1175 return ret;
1176}
1177
1178static bool game_can_format_as_text_now(const game_params *params)
1179{
1180 return true;
1181}
1182
1183static char *game_text_format(const game_state *state)
1184{
1185 return board_text_format(state->w, state->h, state->board,
1186 state->imm->forcefield);
1187}
1188
1189struct game_ui {
1190 bool dragging;
1191 int drag_anchor;
1192 int drag_offset_x, drag_offset_y;
1193 int drag_currpos;
1194 bool *reachable;
1195 int *bfs_queue; /* used as scratch in interpret_move */
1196};
1197
1198static game_ui *new_ui(const game_state *state)
1199{
1200 int w = state->w, h = state->h, wh = w*h;
1201 game_ui *ui = snew(game_ui);
1202
1203 ui->dragging = false;
1204 ui->drag_anchor = ui->drag_currpos = -1;
1205 ui->drag_offset_x = ui->drag_offset_y = -1;
1206 ui->reachable = snewn(wh, bool);
1207 memset(ui->reachable, 0, wh * sizeof(bool));
1208 ui->bfs_queue = snewn(wh, int);
1209
1210 return ui;
1211}
1212
1213static void free_ui(game_ui *ui)
1214{
1215 sfree(ui->bfs_queue);
1216 sfree(ui->reachable);
1217 sfree(ui);
1218}
1219
1220static void game_changed_state(game_ui *ui, const game_state *oldstate,
1221 const game_state *newstate)
1222{
1223}
1224
1225#define PREFERRED_TILESIZE 32
1226#define TILESIZE (ds->tilesize)
1227#define BORDER (TILESIZE/2)
1228#define COORD(x) ( (x) * TILESIZE + BORDER )
1229#define FROMCOORD(x) ( ((x) - BORDER + TILESIZE) / TILESIZE - 1 )
1230#define BORDER_WIDTH (1 + TILESIZE/20)
1231#define HIGHLIGHT_WIDTH (1 + TILESIZE/16)
1232
1233#define FLASH_INTERVAL 0.10F
1234#define FLASH_TIME 3*FLASH_INTERVAL
1235
1236struct game_drawstate {
1237 int tilesize;
1238 int w, h;
1239 unsigned long *grid; /* what's currently displayed */
1240};
1241
1242static char *interpret_move(const game_state *state, game_ui *ui,
1243 const game_drawstate *ds,
1244 int x, int y, int button)
1245{
1246 int w = state->w, h = state->h, wh = w*h;
1247 int tx, ty, i, j;
1248 int qhead, qtail;
1249
1250 if (button == LEFT_BUTTON) {
1251 tx = FROMCOORD(x);
1252 ty = FROMCOORD(y);
1253
1254 if (tx < 0 || tx >= w || ty < 0 || ty >= h ||
1255 !ISBLOCK(state->board[ty*w+tx]))
1256 return NULL; /* this click has no effect */
1257
1258 /*
1259 * User has clicked on a block. Find the block's anchor
1260 * and register that we've started dragging it.
1261 */
1262 i = ty*w+tx;
1263 while (ISDIST(state->board[i]))
1264 i -= state->board[i];
1265 assert(i >= 0 && i < wh);
1266
1267 ui->dragging = true;
1268 ui->drag_anchor = i;
1269 ui->drag_offset_x = tx - (i % w);
1270 ui->drag_offset_y = ty - (i / w);
1271 ui->drag_currpos = i;
1272
1273 /*
1274 * Now we immediately bfs out from the current location of
1275 * the anchor, to find all the places to which this block
1276 * can be dragged.
1277 */
1278 memset(ui->reachable, 0, wh * sizeof(bool));
1279 qhead = qtail = 0;
1280 ui->reachable[i] = true;
1281 ui->bfs_queue[qtail++] = i;
1282 for (j = i; j < wh; j++)
1283 if (state->board[j] == DIST(j - i))
1284 i = j;
1285 while (qhead < qtail) {
1286 int pos = ui->bfs_queue[qhead++];
1287 int x = pos % w, y = pos / w;
1288 int dir;
1289
1290 for (dir = 0; dir < 4; dir++) {
1291 int dx = (dir == 0 ? -1 : dir == 1 ? +1 : 0);
1292 int dy = (dir == 2 ? -1 : dir == 3 ? +1 : 0);
1293 int newpos;
1294
1295 if (x + dx < 0 || x + dx >= w ||
1296 y + dy < 0 || y + dy >= h)
1297 continue;
1298
1299 newpos = pos + dy*w + dx;
1300 if (ui->reachable[newpos])
1301 continue; /* already done this one */
1302
1303 /*
1304 * Now search the grid to see if the block we're
1305 * dragging could fit into this space.
1306 */
1307 for (j = i; j >= 0; j = (ISDIST(state->board[j]) ?
1308 j - state->board[j] : -1)) {
1309 int jx = (j+pos-ui->drag_anchor) % w;
1310 int jy = (j+pos-ui->drag_anchor) / w;
1311 int j2;
1312
1313 if (jx + dx < 0 || jx + dx >= w ||
1314 jy + dy < 0 || jy + dy >= h)
1315 break; /* this position isn't valid at all */
1316
1317 j2 = (j+pos-ui->drag_anchor) + dy*w + dx;
1318
1319 if (state->board[j2] == EMPTY &&
1320 (!state->imm->forcefield[j2] ||
1321 state->board[ui->drag_anchor] == MAINANCHOR))
1322 continue;
1323 while (ISDIST(state->board[j2]))
1324 j2 -= state->board[j2];
1325 assert(j2 >= 0 && j2 < wh);
1326 if (j2 == ui->drag_anchor)
1327 continue;
1328 else
1329 break;
1330 }
1331
1332 if (j < 0) {
1333 /*
1334 * If we got to the end of that loop without
1335 * disqualifying this position, mark it as
1336 * reachable for this drag.
1337 */
1338 ui->reachable[newpos] = true;
1339 ui->bfs_queue[qtail++] = newpos;
1340 }
1341 }
1342 }
1343
1344 /*
1345 * And that's it. Update the display to reflect the start
1346 * of a drag.
1347 */
1348 return MOVE_UI_UPDATE;
1349 } else if (button == LEFT_DRAG && ui->dragging) {
1350 int dist, distlimit, dx, dy, s, px, py;
1351
1352 tx = FROMCOORD(x);
1353 ty = FROMCOORD(y);
1354
1355 tx -= ui->drag_offset_x;
1356 ty -= ui->drag_offset_y;
1357
1358 /*
1359 * Now search outwards from (tx,ty), in order of Manhattan
1360 * distance, until we find a reachable square.
1361 */
1362 distlimit = w+tx;
1363 distlimit = max(distlimit, h+ty);
1364 distlimit = max(distlimit, tx);
1365 distlimit = max(distlimit, ty);
1366 for (dist = 0; dist <= distlimit; dist++) {
1367 for (dx = -dist; dx <= dist; dx++)
1368 for (s = -1; s <= +1; s += 2) {
1369 dy = s * (dist - abs(dx));
1370 px = tx + dx;
1371 py = ty + dy;
1372 if (px >= 0 && px < w && py >= 0 && py < h &&
1373 ui->reachable[py*w+px]) {
1374 ui->drag_currpos = py*w+px;
1375 return MOVE_UI_UPDATE;
1376 }
1377 }
1378 }
1379 return NULL; /* give up - this drag has no effect */
1380 } else if (button == LEFT_RELEASE && ui->dragging) {
1381 char data[256], *str;
1382
1383 /*
1384 * Terminate the drag, and if the piece has actually moved
1385 * then return a move string quoting the old and new
1386 * locations of the piece's anchor.
1387 */
1388 if (ui->drag_anchor != ui->drag_currpos) {
1389 sprintf(data, "M%d-%d", ui->drag_anchor, ui->drag_currpos);
1390 str = dupstr(data);
1391 } else
1392 str = MOVE_UI_UPDATE;
1393
1394 ui->dragging = false;
1395 ui->drag_anchor = ui->drag_currpos = -1;
1396 ui->drag_offset_x = ui->drag_offset_y = -1;
1397 memset(ui->reachable, 0, wh * sizeof(bool));
1398
1399 return str;
1400 } else if (button == ' ' && state->soln) {
1401 /*
1402 * Make the next move in the stored solution.
1403 */
1404 char data[256];
1405 int a1, a2;
1406
1407 a1 = state->soln->moves[state->soln_index*2];
1408 a2 = state->soln->moves[state->soln_index*2+1];
1409 if (a1 == state->lastmoved_pos)
1410 a1 = state->lastmoved;
1411
1412 sprintf(data, "M%d-%d", a1, a2);
1413 return dupstr(data);
1414 }
1415
1416 return NULL;
1417}
1418
1419static bool move_piece(int w, int h, const unsigned char *src,
1420 unsigned char *dst, bool *ff, int from, int to)
1421{
1422 int wh = w*h;
1423 int i, j;
1424
1425 if (!ISANCHOR(dst[from]))
1426 return false;
1427
1428 /*
1429 * Scan to the far end of the piece's linked list.
1430 */
1431 for (i = j = from; j < wh; j++)
1432 if (src[j] == DIST(j - i))
1433 i = j;
1434
1435 /*
1436 * Remove the piece from its old location in the new
1437 * game state.
1438 */
1439 for (j = i; j >= 0; j = (ISDIST(src[j]) ? j - src[j] : -1))
1440 dst[j] = EMPTY;
1441
1442 /*
1443 * And put it back in at the new location.
1444 */
1445 for (j = i; j >= 0; j = (ISDIST(src[j]) ? j - src[j] : -1)) {
1446 int jn = j + to - from;
1447 if (jn < 0 || jn >= wh)
1448 return false;
1449 if (dst[jn] == EMPTY && (!ff[jn] || src[from] == MAINANCHOR)) {
1450 dst[jn] = src[j];
1451 } else {
1452 return false;
1453 }
1454 }
1455
1456 return true;
1457}
1458
1459static game_state *execute_move(const game_state *state, const char *move)
1460{
1461 int w = state->w, h = state->h /* , wh = w*h */;
1462 char c;
1463 int a1, a2, n, movesize;
1464 game_state *ret = dup_game(state);
1465
1466 while (*move) {
1467 c = *move;
1468 if (c == 'S') {
1469 /*
1470 * This is a solve move, so we just set up a stored
1471 * solution path.
1472 */
1473 if (ret->soln && --ret->soln->refcount <= 0) {
1474 sfree(ret->soln->moves);
1475 sfree(ret->soln);
1476 }
1477 ret->soln = snew(struct game_solution);
1478 ret->soln->nmoves = 0;
1479 ret->soln->moves = NULL;
1480 ret->soln->refcount = 1;
1481 ret->soln_index = 0;
1482 ret->cheated = true;
1483
1484 movesize = 0;
1485 move++;
1486 while (1) {
1487 if (sscanf(move, "%d-%d%n", &a1, &a2, &n) != 2) {
1488 free_game(ret);
1489 return NULL;
1490 }
1491
1492 /*
1493 * Special case: if the first move in the solution
1494 * involves the piece for which we already have a
1495 * partial stored move, adjust the source point to
1496 * the original starting point of that piece.
1497 */
1498 if (ret->soln->nmoves == 0 && a1 == ret->lastmoved)
1499 a1 = ret->lastmoved_pos;
1500
1501 if (ret->soln->nmoves >= movesize) {
1502 movesize = (ret->soln->nmoves + 48) * 4 / 3;
1503 ret->soln->moves = sresize(ret->soln->moves,
1504 2*movesize, int);
1505 }
1506
1507 ret->soln->moves[2*ret->soln->nmoves] = a1;
1508 ret->soln->moves[2*ret->soln->nmoves+1] = a2;
1509 ret->soln->nmoves++;
1510 move += n;
1511 if (*move != ',')
1512 break;
1513 move++; /* eat comma */
1514 }
1515 } else if (c == 'M') {
1516 move++;
1517 if (sscanf(move, "%d-%d%n", &a1, &a2, &n) != 2 ||
1518 !move_piece(w, h, state->board, ret->board,
1519 state->imm->forcefield, a1, a2)) {
1520 free_game(ret);
1521 return NULL;
1522 }
1523 if (a1 == ret->lastmoved) {
1524 /*
1525 * If the player has moved the same piece as they
1526 * moved last time, don't increment the move
1527 * count. In fact, if they've put the piece back
1528 * where it started from, _decrement_ the move
1529 * count.
1530 */
1531 if (a2 == ret->lastmoved_pos) {
1532 ret->movecount--; /* reverted last move */
1533 ret->lastmoved = ret->lastmoved_pos = -1;
1534 } else {
1535 ret->lastmoved = a2;
1536 /* don't change lastmoved_pos */
1537 }
1538 } else {
1539 ret->lastmoved = a2;
1540 ret->lastmoved_pos = a1;
1541 ret->movecount++;
1542 }
1543
1544 /*
1545 * If we have a stored solution path, see if we've
1546 * strayed from it or successfully made the next move
1547 * along it.
1548 */
1549 if (ret->soln && ret->lastmoved_pos >= 0) {
1550 if (ret->lastmoved_pos !=
1551 ret->soln->moves[ret->soln_index*2]) {
1552 /* strayed from the path */
1553 ret->soln->refcount--;
1554 assert(ret->soln->refcount > 0);
1555 /* `state' at least still exists */
1556 ret->soln = NULL;
1557 ret->soln_index = -1;
1558 } else if (ret->lastmoved ==
1559 ret->soln->moves[ret->soln_index*2+1]) {
1560 /* advanced along the path */
1561 ret->soln_index++;
1562 if (ret->soln_index >= ret->soln->nmoves) {
1563 /* finished the path! */
1564 ret->soln->refcount--;
1565 assert(ret->soln->refcount > 0);
1566 /* `state' at least still exists */
1567 ret->soln = NULL;
1568 ret->soln_index = -1;
1569 }
1570 }
1571 }
1572
1573 if (ret->board[a2] == MAINANCHOR &&
1574 a2 == ret->ty * w + ret->tx && ret->completed < 0)
1575 ret->completed = ret->movecount;
1576 move += n;
1577 } else {
1578 free_game(ret);
1579 return NULL;
1580 }
1581 if (*move == ';')
1582 move++;
1583 else if (*move) {
1584 free_game(ret);
1585 return NULL;
1586 }
1587 }
1588
1589 return ret;
1590}
1591
1592/* ----------------------------------------------------------------------
1593 * Drawing routines.
1594 */
1595
1596static void game_compute_size(const game_params *params, int tilesize,
1597 const game_ui *ui, int *x, int *y)
1598{
1599 /* fool the macros */
1600 struct dummy { int tilesize; } dummy, *ds = &dummy;
1601 dummy.tilesize = tilesize;
1602
1603 *x = params->w * TILESIZE + 2*BORDER;
1604 *y = params->h * TILESIZE + 2*BORDER;
1605}
1606
1607static void game_set_size(drawing *dr, game_drawstate *ds,
1608 const game_params *params, int tilesize)
1609{
1610 ds->tilesize = tilesize;
1611}
1612
1613static void raise_colour(float *target, float *src, float *limit)
1614{
1615 int i;
1616 for (i = 0; i < 3; i++)
1617 target[i] = (2*src[i] + limit[i]) / 3;
1618}
1619
1620static float *game_colours(frontend *fe, int *ncolours)
1621{
1622 float *ret = snewn(3 * NCOLOURS, float);
1623
1624 game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
1625
1626 /*
1627 * When dragging a tile, we light it up a bit.
1628 */
1629 raise_colour(ret+3*COL_DRAGGING,
1630 ret+3*COL_BACKGROUND, ret+3*COL_HIGHLIGHT);
1631 raise_colour(ret+3*COL_DRAGGING_HIGHLIGHT,
1632 ret+3*COL_HIGHLIGHT, ret+3*COL_HIGHLIGHT);
1633 raise_colour(ret+3*COL_DRAGGING_LOWLIGHT,
1634 ret+3*COL_LOWLIGHT, ret+3*COL_HIGHLIGHT);
1635
1636 /*
1637 * The main tile is tinted blue.
1638 */
1639 ret[COL_MAIN * 3 + 0] = ret[COL_BACKGROUND * 3 + 0];
1640 ret[COL_MAIN * 3 + 1] = ret[COL_BACKGROUND * 3 + 1];
1641 ret[COL_MAIN * 3 + 2] = ret[COL_HIGHLIGHT * 3 + 2];
1642 game_mkhighlight_specific(fe, ret, COL_MAIN,
1643 COL_MAIN_HIGHLIGHT, COL_MAIN_LOWLIGHT);
1644
1645 /*
1646 * And we light that up a bit too when dragging.
1647 */
1648 raise_colour(ret+3*COL_MAIN_DRAGGING,
1649 ret+3*COL_MAIN, ret+3*COL_MAIN_HIGHLIGHT);
1650 raise_colour(ret+3*COL_MAIN_DRAGGING_HIGHLIGHT,
1651 ret+3*COL_MAIN_HIGHLIGHT, ret+3*COL_MAIN_HIGHLIGHT);
1652 raise_colour(ret+3*COL_MAIN_DRAGGING_LOWLIGHT,
1653 ret+3*COL_MAIN_LOWLIGHT, ret+3*COL_MAIN_HIGHLIGHT);
1654
1655 /*
1656 * The target area on the floor is tinted green.
1657 */
1658 ret[COL_TARGET * 3 + 0] = ret[COL_BACKGROUND * 3 + 0];
1659 ret[COL_TARGET * 3 + 1] = ret[COL_HIGHLIGHT * 3 + 1];
1660 ret[COL_TARGET * 3 + 2] = ret[COL_BACKGROUND * 3 + 2];
1661 game_mkhighlight_specific(fe, ret, COL_TARGET,
1662 COL_TARGET_HIGHLIGHT, COL_TARGET_LOWLIGHT);
1663
1664 *ncolours = NCOLOURS;
1665 return ret;
1666}
1667
1668static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
1669{
1670 int w = state->w, h = state->h, wh = w*h;
1671 struct game_drawstate *ds = snew(struct game_drawstate);
1672 int i;
1673
1674 ds->tilesize = 0;
1675 ds->w = w;
1676 ds->h = h;
1677 ds->grid = snewn(wh, unsigned long);
1678 for (i = 0; i < wh; i++)
1679 ds->grid[i] = ~(unsigned long)0;
1680
1681 return ds;
1682}
1683
1684static void game_free_drawstate(drawing *dr, game_drawstate *ds)
1685{
1686 sfree(ds->grid);
1687 sfree(ds);
1688}
1689
1690#define BG_NORMAL 0x00000001UL
1691#define BG_TARGET 0x00000002UL
1692#define BG_FORCEFIELD 0x00000004UL
1693#define FLASH_LOW 0x00000008UL
1694#define FLASH_HIGH 0x00000010UL
1695#define FG_WALL 0x00000020UL
1696#define FG_MAIN 0x00000040UL
1697#define FG_NORMAL 0x00000080UL
1698#define FG_DRAGGING 0x00000100UL
1699#define FG_SHADOW 0x00000200UL
1700#define FG_SOLVEPIECE 0x00000400UL
1701#define FG_MAINPIECESH 11
1702#define FG_SHADOWSH 19
1703
1704#define PIECE_LBORDER 0x00000001UL
1705#define PIECE_TBORDER 0x00000002UL
1706#define PIECE_RBORDER 0x00000004UL
1707#define PIECE_BBORDER 0x00000008UL
1708#define PIECE_TLCORNER 0x00000010UL
1709#define PIECE_TRCORNER 0x00000020UL
1710#define PIECE_BLCORNER 0x00000040UL
1711#define PIECE_BRCORNER 0x00000080UL
1712#define PIECE_MASK 0x000000FFUL
1713
1714/*
1715 * Utility function.
1716 */
1717#define TYPE_MASK 0xF000
1718#define COL_MASK 0x0FFF
1719#define TYPE_RECT 0x0000
1720#define TYPE_TLCIRC 0x4000
1721#define TYPE_TRCIRC 0x5000
1722#define TYPE_BLCIRC 0x6000
1723#define TYPE_BRCIRC 0x7000
1724static void maybe_rect(drawing *dr, int x, int y, int w, int h,
1725 int coltype, int col2)
1726{
1727 int colour = coltype & COL_MASK, type = coltype & TYPE_MASK;
1728
1729 if (colour > NCOLOURS)
1730 return;
1731 if (type == TYPE_RECT) {
1732 draw_rect(dr, x, y, w, h, colour);
1733 } else {
1734 int cx, cy, r;
1735
1736 clip(dr, x, y, w, h);
1737
1738 cx = x;
1739 cy = y;
1740 r = w-1;
1741 if (type & 0x1000)
1742 cx += r;
1743 if (type & 0x2000)
1744 cy += r;
1745
1746 if (col2 == -1 || col2 == coltype) {
1747 assert(w == h);
1748 draw_circle(dr, cx, cy, r, colour, colour);
1749 } else {
1750 /*
1751 * We aim to draw a quadrant of a circle in two
1752 * different colours. We do this using Bresenham's
1753 * algorithm directly, because the Puzzles drawing API
1754 * doesn't have a draw-sector primitive.
1755 */
1756 int bx, by, bd, bd2;
1757 int xm = (type & 0x1000 ? -1 : +1);
1758 int ym = (type & 0x2000 ? -1 : +1);
1759
1760 by = r;
1761 bx = 0;
1762 bd = 0;
1763 while (by >= bx) {
1764 /*
1765 * Plot the point.
1766 */
1767 {
1768 int x1 = cx+xm*bx, y1 = cy+ym*bx;
1769 int x2, y2;
1770
1771 x2 = cx+xm*by; y2 = y1;
1772 draw_rect(dr, min(x1,x2), min(y1,y2),
1773 abs(x1-x2)+1, abs(y1-y2)+1, colour);
1774 x2 = x1; y2 = cy+ym*by;
1775 draw_rect(dr, min(x1,x2), min(y1,y2),
1776 abs(x1-x2)+1, abs(y1-y2)+1, col2);
1777 }
1778
1779 bd += 2*bx + 1;
1780 bd2 = bd - (2*by - 1);
1781 if (abs(bd2) < abs(bd)) {
1782 bd = bd2;
1783 by--;
1784 }
1785 bx++;
1786 }
1787 }
1788
1789 unclip(dr);
1790 }
1791}
1792
1793static void draw_wallpart(drawing *dr, game_drawstate *ds,
1794 int tx, int ty, unsigned long val,
1795 int cl, int cc, int ch)
1796{
1797 int coords[6];
1798
1799 draw_rect(dr, tx, ty, TILESIZE, TILESIZE, cc);
1800 if (val & PIECE_LBORDER)
1801 draw_rect(dr, tx, ty, HIGHLIGHT_WIDTH, TILESIZE,
1802 ch);
1803 if (val & PIECE_RBORDER)
1804 draw_rect(dr, tx+TILESIZE-HIGHLIGHT_WIDTH, ty,
1805 HIGHLIGHT_WIDTH, TILESIZE, cl);
1806 if (val & PIECE_TBORDER)
1807 draw_rect(dr, tx, ty, TILESIZE, HIGHLIGHT_WIDTH, ch);
1808 if (val & PIECE_BBORDER)
1809 draw_rect(dr, tx, ty+TILESIZE-HIGHLIGHT_WIDTH,
1810 TILESIZE, HIGHLIGHT_WIDTH, cl);
1811 if (!((PIECE_BBORDER | PIECE_LBORDER) &~ val)) {
1812 draw_rect(dr, tx, ty+TILESIZE-HIGHLIGHT_WIDTH,
1813 HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH, cl);
1814 clip(dr, tx, ty+TILESIZE-HIGHLIGHT_WIDTH,
1815 HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH);
1816 coords[0] = tx - 1;
1817 coords[1] = ty + TILESIZE - HIGHLIGHT_WIDTH - 1;
1818 coords[2] = tx + HIGHLIGHT_WIDTH;
1819 coords[3] = ty + TILESIZE - HIGHLIGHT_WIDTH - 1;
1820 coords[4] = tx - 1;
1821 coords[5] = ty + TILESIZE;
1822 draw_polygon(dr, coords, 3, ch, ch);
1823 unclip(dr);
1824 } else if (val & PIECE_BLCORNER) {
1825 draw_rect(dr, tx, ty+TILESIZE-HIGHLIGHT_WIDTH,
1826 HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH, ch);
1827 clip(dr, tx, ty+TILESIZE-HIGHLIGHT_WIDTH,
1828 HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH);
1829 coords[0] = tx - 1;
1830 coords[1] = ty + TILESIZE - HIGHLIGHT_WIDTH - 1;
1831 coords[2] = tx + HIGHLIGHT_WIDTH;
1832 coords[3] = ty + TILESIZE - HIGHLIGHT_WIDTH - 1;
1833 coords[4] = tx - 1;
1834 coords[5] = ty + TILESIZE;
1835 draw_polygon(dr, coords, 3, cl, cl);
1836 unclip(dr);
1837 }
1838 if (!((PIECE_TBORDER | PIECE_RBORDER) &~ val)) {
1839 draw_rect(dr, tx+TILESIZE-HIGHLIGHT_WIDTH, ty,
1840 HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH, cl);
1841 clip(dr, tx+TILESIZE-HIGHLIGHT_WIDTH, ty,
1842 HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH);
1843 coords[0] = tx + TILESIZE - HIGHLIGHT_WIDTH - 1;
1844 coords[1] = ty - 1;
1845 coords[2] = tx + TILESIZE;
1846 coords[3] = ty - 1;
1847 coords[4] = tx + TILESIZE - HIGHLIGHT_WIDTH - 1;
1848 coords[5] = ty + HIGHLIGHT_WIDTH;
1849 draw_polygon(dr, coords, 3, ch, ch);
1850 unclip(dr);
1851 } else if (val & PIECE_TRCORNER) {
1852 draw_rect(dr, tx+TILESIZE-HIGHLIGHT_WIDTH, ty,
1853 HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH, ch);
1854 clip(dr, tx+TILESIZE-HIGHLIGHT_WIDTH, ty,
1855 HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH);
1856 coords[0] = tx + TILESIZE - HIGHLIGHT_WIDTH - 1;
1857 coords[1] = ty - 1;
1858 coords[2] = tx + TILESIZE;
1859 coords[3] = ty - 1;
1860 coords[4] = tx + TILESIZE - HIGHLIGHT_WIDTH - 1;
1861 coords[5] = ty + HIGHLIGHT_WIDTH;
1862 draw_polygon(dr, coords, 3, cl, cl);
1863 unclip(dr);
1864 }
1865 if (val & PIECE_TLCORNER)
1866 draw_rect(dr, tx, ty, HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH, ch);
1867 if (val & PIECE_BRCORNER)
1868 draw_rect(dr, tx+TILESIZE-HIGHLIGHT_WIDTH,
1869 ty+TILESIZE-HIGHLIGHT_WIDTH,
1870 HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH, cl);
1871}
1872
1873static void draw_piecepart(drawing *dr, game_drawstate *ds,
1874 int tx, int ty, unsigned long val,
1875 int cl, int cc, int ch)
1876{
1877 int x[6], y[6];
1878
1879 /*
1880 * Drawing the blocks is hellishly fiddly. The blocks don't
1881 * stretch to the full size of the tile; there's a border
1882 * around them of size BORDER_WIDTH. Then they have bevelled
1883 * borders of size HIGHLIGHT_WIDTH, and also rounded corners.
1884 *
1885 * I tried for some time to find a clean and clever way to
1886 * figure out what needed drawing from the corner and border
1887 * flags, but in the end the cleanest way I could find was the
1888 * following. We divide the grid square into 25 parts by
1889 * ruling four horizontal and four vertical lines across it;
1890 * those lines are at BORDER_WIDTH and BORDER_WIDTH +
1891 * HIGHLIGHT_WIDTH from the top, from the bottom, from the
1892 * left and from the right. Then we carefully consider each of
1893 * the resulting 25 sections of square, and decide separately
1894 * what needs to go in it based on the flags. In complicated
1895 * cases there can be up to five possibilities affecting any
1896 * given section (no corner or border flags, just the corner
1897 * flag, one border flag, the other border flag, both border
1898 * flags). So there's a lot of very fiddly logic here and all
1899 * I could really think to do was give it my best shot and
1900 * then test it and correct all the typos. Not fun to write,
1901 * and I'm sure it isn't fun to read either, but it seems to
1902 * work.
1903 */
1904
1905 x[0] = tx;
1906 x[1] = x[0] + BORDER_WIDTH;
1907 x[2] = x[1] + HIGHLIGHT_WIDTH;
1908 x[5] = tx + TILESIZE;
1909 x[4] = x[5] - BORDER_WIDTH;
1910 x[3] = x[4] - HIGHLIGHT_WIDTH;
1911
1912 y[0] = ty;
1913 y[1] = y[0] + BORDER_WIDTH;
1914 y[2] = y[1] + HIGHLIGHT_WIDTH;
1915 y[5] = ty + TILESIZE;
1916 y[4] = y[5] - BORDER_WIDTH;
1917 y[3] = y[4] - HIGHLIGHT_WIDTH;
1918
1919#define RECT(p,q) x[p], y[q], x[(p)+1]-x[p], y[(q)+1]-y[q]
1920
1921 maybe_rect(dr, RECT(0,0),
1922 (val & (PIECE_TLCORNER | PIECE_TBORDER |
1923 PIECE_LBORDER)) ? -1 : cc, -1);
1924 maybe_rect(dr, RECT(1,0),
1925 (val & PIECE_TLCORNER) ? ch : (val & PIECE_TBORDER) ? -1 :
1926 (val & PIECE_LBORDER) ? ch : cc, -1);
1927 maybe_rect(dr, RECT(2,0),
1928 (val & PIECE_TBORDER) ? -1 : cc, -1);
1929 maybe_rect(dr, RECT(3,0),
1930 (val & PIECE_TRCORNER) ? cl : (val & PIECE_TBORDER) ? -1 :
1931 (val & PIECE_RBORDER) ? cl : cc, -1);
1932 maybe_rect(dr, RECT(4,0),
1933 (val & (PIECE_TRCORNER | PIECE_TBORDER |
1934 PIECE_RBORDER)) ? -1 : cc, -1);
1935 maybe_rect(dr, RECT(0,1),
1936 (val & PIECE_TLCORNER) ? ch : (val & PIECE_LBORDER) ? -1 :
1937 (val & PIECE_TBORDER) ? ch : cc, -1);
1938 maybe_rect(dr, RECT(1,1),
1939 (val & PIECE_TLCORNER) ? cc : -1, -1);
1940 maybe_rect(dr, RECT(1,1),
1941 (val & PIECE_TLCORNER) ? ch | TYPE_TLCIRC :
1942 !((PIECE_TBORDER | PIECE_LBORDER) &~ val) ? ch | TYPE_BRCIRC :
1943 (val & (PIECE_TBORDER | PIECE_LBORDER)) ? ch : cc, -1);
1944 maybe_rect(dr, RECT(2,1),
1945 (val & PIECE_TBORDER) ? ch : cc, -1);
1946 maybe_rect(dr, RECT(3,1),
1947 (val & PIECE_TRCORNER) ? cc : -1, -1);
1948 maybe_rect(dr, RECT(3,1),
1949 (val & (PIECE_TBORDER | PIECE_RBORDER)) == PIECE_TBORDER ? ch :
1950 (val & (PIECE_TBORDER | PIECE_RBORDER)) == PIECE_RBORDER ? cl :
1951 !((PIECE_TBORDER|PIECE_RBORDER) &~ val) ? cl | TYPE_BLCIRC :
1952 (val & PIECE_TRCORNER) ? cl | TYPE_TRCIRC :
1953 cc, ch);
1954 maybe_rect(dr, RECT(4,1),
1955 (val & PIECE_TRCORNER) ? ch : (val & PIECE_RBORDER) ? -1 :
1956 (val & PIECE_TBORDER) ? ch : cc, -1);
1957 maybe_rect(dr, RECT(0,2),
1958 (val & PIECE_LBORDER) ? -1 : cc, -1);
1959 maybe_rect(dr, RECT(1,2),
1960 (val & PIECE_LBORDER) ? ch : cc, -1);
1961 maybe_rect(dr, RECT(2,2),
1962 cc, -1);
1963 maybe_rect(dr, RECT(3,2),
1964 (val & PIECE_RBORDER) ? cl : cc, -1);
1965 maybe_rect(dr, RECT(4,2),
1966 (val & PIECE_RBORDER) ? -1 : cc, -1);
1967 maybe_rect(dr, RECT(0,3),
1968 (val & PIECE_BLCORNER) ? cl : (val & PIECE_LBORDER) ? -1 :
1969 (val & PIECE_BBORDER) ? cl : cc, -1);
1970 maybe_rect(dr, RECT(1,3),
1971 (val & PIECE_BLCORNER) ? cc : -1, -1);
1972 maybe_rect(dr, RECT(1,3),
1973 (val & (PIECE_BBORDER | PIECE_LBORDER)) == PIECE_BBORDER ? cl :
1974 (val & (PIECE_BBORDER | PIECE_LBORDER)) == PIECE_LBORDER ? ch :
1975 !((PIECE_BBORDER|PIECE_LBORDER) &~ val) ? ch | TYPE_TRCIRC :
1976 (val & PIECE_BLCORNER) ? ch | TYPE_BLCIRC :
1977 cc, cl);
1978 maybe_rect(dr, RECT(2,3),
1979 (val & PIECE_BBORDER) ? cl : cc, -1);
1980 maybe_rect(dr, RECT(3,3),
1981 (val & PIECE_BRCORNER) ? cc : -1, -1);
1982 maybe_rect(dr, RECT(3,3),
1983 (val & PIECE_BRCORNER) ? cl | TYPE_BRCIRC :
1984 !((PIECE_BBORDER | PIECE_RBORDER) &~ val) ? cl | TYPE_TLCIRC :
1985 (val & (PIECE_BBORDER | PIECE_RBORDER)) ? cl : cc, -1);
1986 maybe_rect(dr, RECT(4,3),
1987 (val & PIECE_BRCORNER) ? cl : (val & PIECE_RBORDER) ? -1 :
1988 (val & PIECE_BBORDER) ? cl : cc, -1);
1989 maybe_rect(dr, RECT(0,4),
1990 (val & (PIECE_BLCORNER | PIECE_BBORDER |
1991 PIECE_LBORDER)) ? -1 : cc, -1);
1992 maybe_rect(dr, RECT(1,4),
1993 (val & PIECE_BLCORNER) ? ch : (val & PIECE_BBORDER) ? -1 :
1994 (val & PIECE_LBORDER) ? ch : cc, -1);
1995 maybe_rect(dr, RECT(2,4),
1996 (val & PIECE_BBORDER) ? -1 : cc, -1);
1997 maybe_rect(dr, RECT(3,4),
1998 (val & PIECE_BRCORNER) ? cl : (val & PIECE_BBORDER) ? -1 :
1999 (val & PIECE_RBORDER) ? cl : cc, -1);
2000 maybe_rect(dr, RECT(4,4),
2001 (val & (PIECE_BRCORNER | PIECE_BBORDER |
2002 PIECE_RBORDER)) ? -1 : cc, -1);
2003
2004#undef RECT
2005}
2006
2007static void draw_tile(drawing *dr, game_drawstate *ds,
2008 int x, int y, unsigned long val)
2009{
2010 int tx = COORD(x), ty = COORD(y);
2011 int cc, ch, cl;
2012
2013 /*
2014 * Draw the tile background.
2015 */
2016 if (val & BG_TARGET)
2017 cc = COL_TARGET;
2018 else
2019 cc = COL_BACKGROUND;
2020 ch = cc+1;
2021 cl = cc+2;
2022 if (val & FLASH_LOW)
2023 cc = cl;
2024 else if (val & FLASH_HIGH)
2025 cc = ch;
2026
2027 draw_rect(dr, tx, ty, TILESIZE, TILESIZE, cc);
2028 if (val & BG_FORCEFIELD) {
2029 /*
2030 * Cattle-grid effect to indicate that nothing but the
2031 * main block can slide over this square.
2032 */
2033 int n = 3 * (TILESIZE / (3*HIGHLIGHT_WIDTH));
2034 int i;
2035
2036 for (i = 1; i < n; i += 3) {
2037 draw_rect(dr, tx,ty+(TILESIZE*i/n), TILESIZE,HIGHLIGHT_WIDTH, cl);
2038 draw_rect(dr, tx+(TILESIZE*i/n),ty, HIGHLIGHT_WIDTH,TILESIZE, cl);
2039 }
2040 }
2041
2042 /*
2043 * Draw the tile midground: a shadow of a block, for
2044 * displaying partial solutions.
2045 */
2046 if (val & FG_SHADOW) {
2047 draw_piecepart(dr, ds, tx, ty, (val >> FG_SHADOWSH) & PIECE_MASK,
2048 cl, cl, cl);
2049 }
2050
2051 /*
2052 * Draw the tile foreground, i.e. some section of a block or
2053 * wall.
2054 */
2055 if (val & FG_WALL) {
2056 cc = COL_BACKGROUND;
2057 ch = cc+1;
2058 cl = cc+2;
2059 if (val & FLASH_LOW)
2060 cc = cl;
2061 else if (val & FLASH_HIGH)
2062 cc = ch;
2063
2064 draw_wallpart(dr, ds, tx, ty, (val >> FG_MAINPIECESH) & PIECE_MASK,
2065 cl, cc, ch);
2066 } else if (val & (FG_MAIN | FG_NORMAL)) {
2067 if (val & FG_DRAGGING)
2068 cc = (val & FG_MAIN ? COL_MAIN_DRAGGING : COL_DRAGGING);
2069 else
2070 cc = (val & FG_MAIN ? COL_MAIN : COL_BACKGROUND);
2071 ch = cc+1;
2072 cl = cc+2;
2073
2074 if (val & FLASH_LOW)
2075 cc = cl;
2076 else if (val & (FLASH_HIGH | FG_SOLVEPIECE))
2077 cc = ch;
2078
2079 draw_piecepart(dr, ds, tx, ty, (val >> FG_MAINPIECESH) & PIECE_MASK,
2080 cl, cc, ch);
2081 }
2082
2083 draw_update(dr, tx, ty, TILESIZE, TILESIZE);
2084}
2085
2086static unsigned long find_piecepart(int w, int h, DSF *dsf, int x, int y)
2087{
2088 int i = y*w+x;
2089 int canon = dsf_canonify(dsf, i);
2090 unsigned long val = 0;
2091
2092 if (x == 0 || canon != dsf_canonify(dsf, i-1))
2093 val |= PIECE_LBORDER;
2094 if (y== 0 || canon != dsf_canonify(dsf, i-w))
2095 val |= PIECE_TBORDER;
2096 if (x == w-1 || canon != dsf_canonify(dsf, i+1))
2097 val |= PIECE_RBORDER;
2098 if (y == h-1 || canon != dsf_canonify(dsf, i+w))
2099 val |= PIECE_BBORDER;
2100 if (!(val & (PIECE_TBORDER | PIECE_LBORDER)) &&
2101 canon != dsf_canonify(dsf, i-1-w))
2102 val |= PIECE_TLCORNER;
2103 if (!(val & (PIECE_TBORDER | PIECE_RBORDER)) &&
2104 canon != dsf_canonify(dsf, i+1-w))
2105 val |= PIECE_TRCORNER;
2106 if (!(val & (PIECE_BBORDER | PIECE_LBORDER)) &&
2107 canon != dsf_canonify(dsf, i-1+w))
2108 val |= PIECE_BLCORNER;
2109 if (!(val & (PIECE_BBORDER | PIECE_RBORDER)) &&
2110 canon != dsf_canonify(dsf, i+1+w))
2111 val |= PIECE_BRCORNER;
2112 return val;
2113}
2114
2115static void game_redraw(drawing *dr, game_drawstate *ds,
2116 const game_state *oldstate, const game_state *state,
2117 int dir, const game_ui *ui,
2118 float animtime, float flashtime)
2119{
2120 int w = state->w, h = state->h, wh = w*h;
2121 unsigned char *board;
2122 DSF *dsf;
2123 int x, y, mainanchor, mainpos, dragpos, solvepos, solvesrc, solvedst;
2124
2125 /*
2126 * Construct the board we'll be displaying (which may be
2127 * different from the one in state if ui describes a drag in
2128 * progress).
2129 */
2130 board = snewn(wh, unsigned char);
2131 memcpy(board, state->board, wh);
2132 if (ui->dragging) {
2133 bool mpret = move_piece(w, h, state->board, board,
2134 state->imm->forcefield,
2135 ui->drag_anchor, ui->drag_currpos);
2136 assert(mpret);
2137 }
2138
2139 if (state->soln) {
2140 solvesrc = state->soln->moves[state->soln_index*2];
2141 solvedst = state->soln->moves[state->soln_index*2+1];
2142 if (solvesrc == state->lastmoved_pos)
2143 solvesrc = state->lastmoved;
2144 if (solvesrc == ui->drag_anchor)
2145 solvesrc = ui->drag_currpos;
2146 } else
2147 solvesrc = solvedst = -1;
2148
2149 /*
2150 * Build a dsf out of that board, so we can conveniently tell
2151 * which edges are connected and which aren't.
2152 */
2153 dsf = dsf_new(wh);
2154 mainanchor = -1;
2155 for (y = 0; y < h; y++)
2156 for (x = 0; x < w; x++) {
2157 int i = y*w+x;
2158
2159 if (ISDIST(board[i]))
2160 dsf_merge(dsf, i, i - board[i]);
2161 if (board[i] == MAINANCHOR)
2162 mainanchor = i;
2163 if (board[i] == WALL) {
2164 if (x > 0 && board[i-1] == WALL)
2165 dsf_merge(dsf, i, i-1);
2166 if (y > 0 && board[i-w] == WALL)
2167 dsf_merge(dsf, i, i-w);
2168 }
2169 }
2170 assert(mainanchor >= 0);
2171 mainpos = dsf_canonify(dsf, mainanchor);
2172 dragpos = ui->drag_currpos > 0 ? dsf_canonify(dsf, ui->drag_currpos) : -1;
2173 solvepos = solvesrc >= 0 ? dsf_canonify(dsf, solvesrc) : -1;
2174
2175 /*
2176 * Now we can construct the data about what we want to draw.
2177 */
2178 for (y = 0; y < h; y++)
2179 for (x = 0; x < w; x++) {
2180 int i = y*w+x;
2181 int j;
2182 unsigned long val;
2183 int canon;
2184
2185 /*
2186 * See if this square is part of the target area.
2187 */
2188 j = i + mainanchor - (state->ty * w + state->tx);
2189 while (j >= 0 && j < wh && ISDIST(board[j]))
2190 j -= board[j];
2191 if (j == mainanchor)
2192 val = BG_TARGET;
2193 else
2194 val = BG_NORMAL;
2195
2196 if (state->imm->forcefield[i])
2197 val |= BG_FORCEFIELD;
2198
2199 if (flashtime > 0) {
2200 int flashtype = (int)(flashtime / FLASH_INTERVAL) & 1;
2201 val |= (flashtype ? FLASH_LOW : FLASH_HIGH);
2202 }
2203
2204 if (board[i] != EMPTY) {
2205 canon = dsf_canonify(dsf, i);
2206
2207 if (board[i] == WALL)
2208 val |= FG_WALL;
2209 else if (canon == mainpos)
2210 val |= FG_MAIN;
2211 else
2212 val |= FG_NORMAL;
2213 if (canon == dragpos)
2214 val |= FG_DRAGGING;
2215 if (canon == solvepos)
2216 val |= FG_SOLVEPIECE;
2217
2218 /*
2219 * Now look around to see if other squares
2220 * belonging to the same block are adjacent to us.
2221 */
2222 val |= find_piecepart(w, h, dsf, x, y) << FG_MAINPIECESH;
2223 }
2224
2225 /*
2226 * If we're in the middle of showing a solution,
2227 * display a shadow piece for the target of the
2228 * current move.
2229 */
2230 if (solvepos >= 0) {
2231 int si = i - solvedst + solvesrc;
2232 if (si >= 0 && si < wh && dsf_canonify(dsf, si) == solvepos) {
2233 val |= find_piecepart(w, h, dsf,
2234 si % w, si / w) << FG_SHADOWSH;
2235 val |= FG_SHADOW;
2236 }
2237 }
2238
2239 if (val != ds->grid[i]) {
2240 draw_tile(dr, ds, x, y, val);
2241 ds->grid[i] = val;
2242 }
2243 }
2244
2245 /*
2246 * Update the status bar.
2247 */
2248 {
2249 char statusbuf[256];
2250
2251 sprintf(statusbuf, "%sMoves: %d",
2252 (state->completed >= 0 ?
2253 (state->cheated ? "Auto-solved. " : "COMPLETED! ") :
2254 (state->cheated ? "Auto-solver used. " : "")),
2255 (state->completed >= 0 ? state->completed : state->movecount));
2256 if (state->minmoves >= 0)
2257 sprintf(statusbuf+strlen(statusbuf), " (min %d)",
2258 state->minmoves);
2259
2260 status_bar(dr, statusbuf);
2261 }
2262
2263 dsf_free(dsf);
2264 sfree(board);
2265}
2266
2267static float game_anim_length(const game_state *oldstate,
2268 const game_state *newstate, int dir, game_ui *ui)
2269{
2270 return 0.0F;
2271}
2272
2273static float game_flash_length(const game_state *oldstate,
2274 const game_state *newstate, int dir, game_ui *ui)
2275{
2276 if (oldstate->completed < 0 && newstate->completed >= 0)
2277 return FLASH_TIME;
2278
2279 return 0.0F;
2280}
2281
2282static void game_get_cursor_location(const game_ui *ui,
2283 const game_drawstate *ds,
2284 const game_state *state,
2285 const game_params *params,
2286 int *x, int *y, int *w, int *h)
2287{
2288}
2289
2290static int game_status(const game_state *state)
2291{
2292 return state->completed ? +1 : 0;
2293}
2294
2295static bool game_timing_state(const game_state *state, game_ui *ui)
2296{
2297 return true;
2298}
2299
2300static void game_print_size(const game_params *params, const game_ui *ui,
2301 float *x, float *y)
2302{
2303}
2304
2305static void game_print(drawing *dr, const game_state *state, const game_ui *ui,
2306 int tilesize)
2307{
2308}
2309
2310#ifdef COMBINED
2311#define thegame slide
2312#endif
2313
2314const struct game thegame = {
2315 "Slide", NULL, NULL,
2316 default_params,
2317 game_fetch_preset, NULL,
2318 decode_params,
2319 encode_params,
2320 free_params,
2321 dup_params,
2322 true, game_configure, custom_params,
2323 validate_params,
2324 new_game_desc,
2325 validate_desc,
2326 new_game,
2327 dup_game,
2328 free_game,
2329 true, solve_game,
2330 true, game_can_format_as_text_now, game_text_format,
2331 NULL, NULL, /* get_prefs, set_prefs */
2332 new_ui,
2333 free_ui,
2334 NULL, /* encode_ui */
2335 NULL, /* decode_ui */
2336 NULL, /* game_request_keys */
2337 game_changed_state,
2338 NULL, /* current_key_label */
2339 interpret_move,
2340 execute_move,
2341 PREFERRED_TILESIZE, game_compute_size, game_set_size,
2342 game_colours,
2343 game_new_drawstate,
2344 game_free_drawstate,
2345 game_redraw,
2346 game_anim_length,
2347 game_flash_length,
2348 game_get_cursor_location,
2349 game_status,
2350 false, false, game_print_size, game_print,
2351 true, /* wants_statusbar */
2352 false, game_timing_state,
2353 0, /* flags */
2354};
2355
2356#ifdef STANDALONE_SOLVER
2357
2358#include <stdarg.h>
2359
2360int main(int argc, char **argv)
2361{
2362 game_params *p;
2363 game_state *s;
2364 char *id = NULL, *desc;
2365 const char *err;
2366 bool count = false;
2367 int ret;
2368 int *moves;
2369
2370 while (--argc > 0) {
2371 char *p = *++argv;
2372 /*
2373 if (!strcmp(p, "-v")) {
2374 verbose = true;
2375 } else
2376 */
2377 if (!strcmp(p, "-c")) {
2378 count = true;
2379 } else if (*p == '-') {
2380 fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
2381 return 1;
2382 } else {
2383 id = p;
2384 }
2385 }
2386
2387 if (!id) {
2388 fprintf(stderr, "usage: %s [-c | -v] <game_id>\n", argv[0]);
2389 return 1;
2390 }
2391
2392 desc = strchr(id, ':');
2393 if (!desc) {
2394 fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]);
2395 return 1;
2396 }
2397 *desc++ = '\0';
2398
2399 p = default_params();
2400 decode_params(p, id);
2401 err = validate_desc(p, desc);
2402 if (err) {
2403 fprintf(stderr, "%s: %s\n", argv[0], err);
2404 return 1;
2405 }
2406 s = new_game(NULL, p, desc);
2407
2408 ret = solve_board(s->w, s->h, s->board, s->imm->forcefield,
2409 s->tx, s->ty, -1, &moves);
2410 if (ret < 0) {
2411 printf("No solution found\n");
2412 } else {
2413 int index = 0;
2414 if (count) {
2415 printf("%d moves required\n", ret);
2416 return 0;
2417 }
2418 while (1) {
2419 bool moveret;
2420 char *text = board_text_format(s->w, s->h, s->board,
2421 s->imm->forcefield);
2422 game_state *s2;
2423
2424 printf("position %d:\n%s", index, text);
2425
2426 if (index >= ret)
2427 break;
2428
2429 s2 = dup_game(s);
2430 moveret = move_piece(s->w, s->h, s->board,
2431 s2->board, s->imm->forcefield,
2432 moves[index*2], moves[index*2+1]);
2433 assert(moveret);
2434
2435 free_game(s);
2436 s = s2;
2437 index++;
2438 }
2439 }
2440
2441 return 0;
2442}
2443
2444#endif
diff --git a/apps/plugins/puzzles/src/unfinished/sokoban.c b/apps/plugins/puzzles/src/unfinished/sokoban.c
new file mode 100644
index 0000000000..1f3c688af1
--- /dev/null
+++ b/apps/plugins/puzzles/src/unfinished/sokoban.c
@@ -0,0 +1,1476 @@
1/*
2 * sokoban.c: An implementation of the well-known Sokoban barrel-
3 * pushing game. Random generation is too simplistic to be
4 * credible, but the rest of the gameplay works well enough to use
5 * it with hand-written level descriptions.
6 */
7
8/*
9 * TODO:
10 *
11 * - I think it would be better to ditch the `prev' array, and
12 * instead make the `dist' array strictly monotonic (by having
13 * each distance be something like I*A+S, where A is the grid
14 * area, I the number of INITIAL squares trampled on, and S the
15 * number of harmless spaces moved through). This would permit
16 * the path-tracing when a pull is actually made to choose
17 * randomly from all the possible shortest routes, which would
18 * be superior in terms of eliminating directional bias.
19 * + So when tracing the path back to the current px,py, we
20 * look at all four adjacent squares, find the minimum
21 * distance, check that it's _strictly smaller_ than that of
22 * the current square, and restrict our choice to precisely
23 * those squares with that minimum distance.
24 * + The other place `prev' is currently used is in the check
25 * for consistency of a pull. We would have to replace the
26 * check for whether prev[ny*w+nx]==oy*w+ox with a check that
27 * made sure there was at least one adjacent square with a
28 * smaller distance which _wasn't_ oy*w+ox. Then when we did
29 * the path-tracing we'd also have to take this special case
30 * into account.
31 *
32 * - More discriminating choice of pull. (Snigger.)
33 * + favour putting targets in clumps
34 * + try to shoot for a reasonably consistent number of barrels
35 * (adjust willingness to generate a new barrel depending on
36 * how many are already present)
37 * + adjust willingness to break new ground depending on how
38 * much is already broken
39 *
40 * - generation time parameters:
41 * + enable NetHack mode (and find a better place for the hole)
42 * + decide how many of the remaining Is should be walls
43 *
44 * - at the end of generation, randomly position the starting
45 * player coordinates, probably by (somehow) reusing the same
46 * bfs currently inside the loop.
47 *
48 * - possible backtracking?
49 *
50 * - IWBNI we could spot completely unreachable bits of level at
51 * the outside, and not bother drawing grid lines for them. The
52 * NH levels currently look a bit weird with grid lines on the
53 * outside of the boundary.
54 */
55
56#include <stdio.h>
57#include <stdlib.h>
58#include <string.h>
59#include <assert.h>
60#include <ctype.h>
61#ifdef NO_TGMATH_H
62# include <math.h>
63#else
64# include <tgmath.h>
65#endif
66
67#include "puzzles.h"
68
69/*
70 * Various subsets of these constants are used during game
71 * generation, game play, game IDs and the game_drawstate.
72 */
73#define INITIAL 'i' /* used only in game generation */
74#define SPACE 's'
75#define WALL 'w'
76#define PIT 'p'
77#define DEEP_PIT 'd'
78#define TARGET 't'
79#define BARREL 'b'
80#define BARRELTARGET 'f' /* target is 'f'illed */
81#define PLAYER 'u' /* yo'u'; used in game IDs */
82#define PLAYERTARGET 'v' /* bad letter: v is to u as t is to s */
83#define INVALID '!' /* used in drawstate to force redraw */
84/*
85 * We also support the use of any capital letter as a barrel, which
86 * will be displayed with that letter as a label. (This facilitates
87 * people distributing annotated game IDs for particular Sokoban
88 * levels, so they can accompany them with verbal instructions
89 * about pushing particular barrels in particular ways.) Therefore,
90 * to find out whether something is a barrel, we need a test
91 * function which does a bit more than just comparing to BARREL.
92 *
93 * When resting on target squares, capital-letter barrels are
94 * replaced with their control-character value (A -> ^A).
95 */
96#define IS_PLAYER(c) ( (c)==PLAYER || (c)==PLAYERTARGET )
97#define IS_BARREL(c) ( (c)==BARREL || (c)==BARRELTARGET || \
98 ((c)>='A' && (c)<='Z') || ((c)>=1 && (c)<=26) )
99#define IS_ON_TARGET(c) ( (c)==TARGET || (c)==BARRELTARGET || \
100 (c)==PLAYERTARGET || ((c)>=1 && (c)<=26) )
101#define TARGETISE(b) ( (b)==BARREL ? BARRELTARGET : (b)-('A'-1) )
102#define DETARGETISE(b) ( (b)==BARRELTARGET ? BARREL : (b)+('A'-1) )
103#define BARREL_LABEL(b) ( (b)>='A'&&(b)<='Z' ? (b) : \
104 (b)>=1 && (b)<=26 ? (b)+('A'-1) : 0 )
105
106#define DX(d) (d == 0 ? -1 : d == 2 ? +1 : 0)
107#define DY(d) (d == 1 ? -1 : d == 3 ? +1 : 0)
108
109#define FLASH_LENGTH 0.3F
110
111enum {
112 COL_BACKGROUND,
113 COL_TARGET,
114 COL_PIT,
115 COL_DEEP_PIT,
116 COL_BARREL,
117 COL_PLAYER,
118 COL_TEXT,
119 COL_GRID,
120 COL_OUTLINE,
121 COL_HIGHLIGHT,
122 COL_LOWLIGHT,
123 COL_WALL,
124 NCOLOURS
125};
126
127struct game_params {
128 int w, h;
129 /*
130 * FIXME: a parameter involving degree of filling in?
131 */
132};
133
134struct game_state {
135 game_params p;
136 unsigned char *grid;
137 int px, py;
138 bool completed;
139};
140
141static game_params *default_params(void)
142{
143 game_params *ret = snew(game_params);
144
145 ret->w = 12;
146 ret->h = 10;
147
148 return ret;
149}
150
151static void free_params(game_params *params)
152{
153 sfree(params);
154}
155
156static game_params *dup_params(const game_params *params)
157{
158 game_params *ret = snew(game_params);
159 *ret = *params; /* structure copy */
160 return ret;
161}
162
163static const struct game_params sokoban_presets[] = {
164 { 12, 10 },
165 { 16, 12 },
166 { 20, 16 },
167};
168
169static bool game_fetch_preset(int i, char **name, game_params **params)
170{
171 game_params p, *ret;
172 char *retname;
173 char namebuf[80];
174
175 if (i < 0 || i >= lenof(sokoban_presets))
176 return false;
177
178 p = sokoban_presets[i];
179 ret = dup_params(&p);
180 sprintf(namebuf, "%dx%d", ret->w, ret->h);
181 retname = dupstr(namebuf);
182
183 *params = ret;
184 *name = retname;
185 return true;
186}
187
188static void decode_params(game_params *params, char const *string)
189{
190 params->w = params->h = atoi(string);
191 while (*string && isdigit((unsigned char)*string)) string++;
192 if (*string == 'x') {
193 string++;
194 params->h = atoi(string);
195 }
196}
197
198static char *encode_params(const game_params *params, bool full)
199{
200 char data[256];
201
202 sprintf(data, "%dx%d", params->w, params->h);
203
204 return dupstr(data);
205}
206
207static config_item *game_configure(const game_params *params)
208{
209 config_item *ret;
210 char buf[80];
211
212 ret = snewn(3, config_item);
213
214 ret[0].name = "Width";
215 ret[0].type = C_STRING;
216 sprintf(buf, "%d", params->w);
217 ret[0].u.string.sval = dupstr(buf);
218
219 ret[1].name = "Height";
220 ret[1].type = C_STRING;
221 sprintf(buf, "%d", params->h);
222 ret[1].u.string.sval = dupstr(buf);
223
224 ret[2].name = NULL;
225 ret[2].type = C_END;
226
227 return ret;
228}
229
230static game_params *custom_params(const config_item *cfg)
231{
232 game_params *ret = snew(game_params);
233
234 ret->w = atoi(cfg[0].u.string.sval);
235 ret->h = atoi(cfg[1].u.string.sval);
236
237 return ret;
238}
239
240static const char *validate_params(const game_params *params, bool full)
241{
242 if (params->w < 4 || params->h < 4)
243 return "Width and height must both be at least 4";
244
245 return NULL;
246}
247
248/* ----------------------------------------------------------------------
249 * Game generation mechanism.
250 *
251 * To generate a Sokoban level, we begin with a completely blank
252 * grid and make valid inverse moves. Grid squares can be in a
253 * number of states. The states are:
254 *
255 * - INITIAL: this square has not as yet been touched by any
256 * inverse move, which essentially means we haven't decided what
257 * it is yet.
258 *
259 * - SPACE: this square is a space.
260 *
261 * - TARGET: this square is a space which is also the target for a
262 * barrel.
263 *
264 * - BARREL: this square contains a barrel.
265 *
266 * - BARRELTARGET: this square contains a barrel _on_ a target.
267 *
268 * - WALL: this square is a wall.
269 *
270 * - PLAYER: this square contains the player.
271 *
272 * - PLAYERTARGET: this square contains the player on a target.
273 *
274 * We begin with every square of the in state INITIAL, apart from a
275 * solid ring of WALLs around the edge. We randomly position the
276 * PLAYER somewhere. Thereafter our valid moves are:
277 *
278 * - to move the PLAYER in one direction _pulling_ a barrel after
279 * us. For this to work, we must have SPACE or INITIAL in the
280 * direction we're moving, and BARREL or BARRELTARGET in the
281 * direction we're moving away from. We leave SPACE or TARGET
282 * respectively in the vacated square.
283 *
284 * - to create a new barrel by transforming an INITIAL square into
285 * BARRELTARGET.
286 *
287 * - to move the PLAYER freely through SPACE and TARGET squares,
288 * leaving SPACE or TARGET where it started.
289 *
290 * - to move the player through INITIAL squares, carving a tunnel
291 * of SPACEs as it goes.
292 *
293 * We try to avoid destroying INITIAL squares wherever possible (if
294 * there's a path to where we want to be using only SPACE, then we
295 * should always use that). At the end of generation, every square
296 * still in state INITIAL is one which was not required at any
297 * point during generation, which means we can randomly choose
298 * whether to make it SPACE or WALL.
299 *
300 * It's unclear as yet what the right strategy for wall placement
301 * should be. Too few WALLs will yield many alternative solutions
302 * to the puzzle, whereas too many might rule out so many
303 * possibilities that the intended solution becomes obvious.
304 */
305
306static void sokoban_generate(int w, int h, unsigned char *grid, int moves,
307 bool nethack, random_state *rs)
308{
309 struct pull {
310 int ox, oy, nx, ny, score;
311 };
312
313 struct pull *pulls;
314 int *dist, *prev, *heap;
315 int x, y, px, py, i, j, d, heapsize, npulls;
316
317 pulls = snewn(w * h * 4, struct pull);
318 dist = snewn(w * h, int);
319 prev = snewn(w * h, int);
320 heap = snewn(w * h, int);
321
322 /*
323 * Configure the initial grid.
324 */
325 for (y = 0; y < h; y++)
326 for (x = 0; x < w; x++)
327 grid[y*w+x] = (x == 0 || y == 0 || x == w-1 || y == h-1 ?
328 WALL : INITIAL);
329 if (nethack)
330 grid[1] = DEEP_PIT;
331
332 /*
333 * Place the player.
334 */
335 i = random_upto(rs, (w-2) * (h-2));
336 x = 1 + i % (w-2);
337 y = 1 + i / (w-2);
338 grid[y*w+x] = SPACE;
339 px = x;
340 py = y;
341
342 /*
343 * Now loop around making random inverse Sokoban moves. In this
344 * loop we aim to make one actual barrel-pull per iteration,
345 * plus as many free moves as are necessary to get into
346 * position for that pull.
347 */
348 while (moves-- >= 0) {
349 /*
350 * First enumerate all the viable barrel-pulls we can
351 * possibly make, counting two pulls of the same barrel in
352 * different directions as different. We also include pulls
353 * we can perform by creating a new barrel. Each pull is
354 * marked with the amount of violence it would have to do
355 * to the grid.
356 */
357 npulls = 0;
358 for (y = 0; y < h; y++)
359 for (x = 0; x < w; x++)
360 for (d = 0; d < 4; d++) {
361 int dx = DX(d);
362 int dy = DY(d);
363 int nx = x + dx, ny = y + dy;
364 int npx = nx + dx, npy = ny + dy;
365 int score = 0;
366
367 /*
368 * The candidate move is to put the player at
369 * (nx,ny), and move him to (npx,npy), pulling
370 * a barrel at (x,y) to (nx,ny). So first we
371 * must check that all those squares are within
372 * the boundaries of the grid. For this it is
373 * sufficient to check npx,npy.
374 */
375 if (npx < 0 || npx >= w || npy < 0 || npy >= h)
376 continue;
377
378 /*
379 * (x,y) must either be a barrel, or a square
380 * which we can convert into a barrel.
381 */
382 switch (grid[y*w+x]) {
383 case BARREL: case BARRELTARGET:
384 break;
385 case INITIAL:
386 if (nethack)
387 continue;
388 score += 10 /* new_barrel_score */;
389 break;
390 case DEEP_PIT:
391 if (!nethack)
392 continue;
393 break;
394 default:
395 continue;
396 }
397
398 /*
399 * (nx,ny) must either be a space, or a square
400 * which we can convert into a space.
401 */
402 switch (grid[ny*w+nx]) {
403 case SPACE: case TARGET:
404 break;
405 case INITIAL:
406 score += 3 /* new_space_score */;
407 break;
408 default:
409 continue;
410 }
411
412 /*
413 * (npx,npy) must also either be a space, or a
414 * square which we can convert into a space.
415 */
416 switch (grid[npy*w+npx]) {
417 case SPACE: case TARGET:
418 break;
419 case INITIAL:
420 score += 3 /* new_space_score */;
421 break;
422 default:
423 continue;
424 }
425
426 /*
427 * That's sufficient to tag this as a possible
428 * pull right now. We still don't know if we
429 * can reach the required player position, but
430 * that's a job for the subsequent BFS phase to
431 * tell us.
432 */
433 pulls[npulls].ox = x;
434 pulls[npulls].oy = y;
435 pulls[npulls].nx = nx;
436 pulls[npulls].ny = ny;
437 pulls[npulls].score = score;
438#ifdef GENERATION_DIAGNOSTICS
439 printf("found potential pull: (%d,%d)-(%d,%d) cost %d\n",
440 pulls[npulls].ox, pulls[npulls].oy,
441 pulls[npulls].nx, pulls[npulls].ny,
442 pulls[npulls].score);
443#endif
444 npulls++;
445 }
446#ifdef GENERATION_DIAGNOSTICS
447 printf("found %d potential pulls\n", npulls);
448#endif
449
450 /*
451 * If there are no pulls available at all, we give up.
452 *
453 * (FIXME: or perhaps backtrack?)
454 */
455 if (npulls == 0)
456 break;
457
458 /*
459 * Now we do a BFS from our current position, to find all
460 * the squares we can get the player into.
461 *
462 * This BFS is unusually tricky. We want to give a positive
463 * distance only to squares which we have to carve through
464 * INITIALs to get to, which means we can't just stick
465 * every square we reach on the end of our to-do list.
466 * Instead, we must maintain our list as a proper priority
467 * queue.
468 */
469 for (i = 0; i < w*h; i++)
470 dist[i] = prev[i] = -1;
471
472 heap[0] = py*w+px;
473 heapsize = 1;
474 dist[py*w+px] = 0;
475
476#define PARENT(n) ( ((n)-1)/2 )
477#define LCHILD(n) ( 2*(n)+1 )
478#define RCHILD(n) ( 2*(n)+2 )
479#define SWAP(i,j) do { int swaptmp = (i); (i) = (j); (j) = swaptmp; } while (0)
480
481 while (heapsize > 0) {
482 /*
483 * Pull the smallest element off the heap: it's at
484 * position 0. Move the arbitrary element from the very
485 * end of the heap into position 0.
486 */
487 y = heap[0] / w;
488 x = heap[0] % w;
489
490 heapsize--;
491 heap[0] = heap[heapsize];
492
493 /*
494 * Now repeatedly move that arbitrary element down the
495 * heap by swapping it with the more suitable of its
496 * children.
497 */
498 i = 0;
499 while (1) {
500 int lc, rc;
501
502 lc = LCHILD(i);
503 rc = RCHILD(i);
504
505 if (lc >= heapsize)
506 break; /* we've hit bottom */
507
508 if (rc >= heapsize) {
509 /*
510 * Special case: there is only one child to
511 * check.
512 */
513 if (dist[heap[i]] > dist[heap[lc]])
514 SWAP(heap[i], heap[lc]);
515
516 /* _Now_ we've hit bottom. */
517 break;
518 } else {
519 /*
520 * The common case: there are two children and
521 * we must check them both.
522 */
523 if (dist[heap[i]] > dist[heap[lc]] ||
524 dist[heap[i]] > dist[heap[rc]]) {
525 /*
526 * Pick the more appropriate child to swap with
527 * (i.e. the one which would want to be the
528 * parent if one were above the other - as one
529 * is about to be).
530 */
531 if (dist[heap[lc]] > dist[heap[rc]]) {
532 SWAP(heap[i], heap[rc]);
533 i = rc;
534 } else {
535 SWAP(heap[i], heap[lc]);
536 i = lc;
537 }
538 } else {
539 /* This element is in the right place; we're done. */
540 break;
541 }
542 }
543 }
544
545 /*
546 * OK, that's given us (x,y) for this phase of the
547 * search. Now try all directions from here.
548 */
549
550 for (d = 0; d < 4; d++) {
551 int dx = DX(d);
552 int dy = DY(d);
553 int nx = x + dx, ny = y + dy;
554 if (nx < 0 || nx >= w || ny < 0 || ny >= h)
555 continue;
556 if (grid[ny*w+nx] != SPACE && grid[ny*w+nx] != TARGET &&
557 grid[ny*w+nx] != INITIAL)
558 continue;
559 if (dist[ny*w+nx] == -1) {
560 dist[ny*w+nx] = dist[y*w+x] + (grid[ny*w+nx] == INITIAL);
561 prev[ny*w+nx] = y*w+x;
562
563 /*
564 * Now insert ny*w+nx at the end of the heap,
565 * and move it down to its appropriate resting
566 * place.
567 */
568 i = heapsize;
569 heap[heapsize++] = ny*w+nx;
570
571 /*
572 * Swap element n with its parent repeatedly to
573 * preserve the heap property.
574 */
575
576 while (i > 0) {
577 int p = PARENT(i);
578
579 if (dist[heap[p]] > dist[heap[i]]) {
580 SWAP(heap[p], heap[i]);
581 i = p;
582 } else
583 break;
584 }
585 }
586 }
587 }
588
589#undef PARENT
590#undef LCHILD
591#undef RCHILD
592#undef SWAP
593
594#ifdef GENERATION_DIAGNOSTICS
595 printf("distance map:\n");
596 for (i = 0; i < h; i++) {
597 for (j = 0; j < w; j++) {
598 int d = dist[i*w+j];
599 int c;
600 if (d < 0)
601 c = '#';
602 else if (d >= 36)
603 c = '!';
604 else if (d >= 10)
605 c = 'A' - 10 + d;
606 else
607 c = '0' + d;
608 putchar(c);
609 }
610 putchar('\n');
611 }
612#endif
613
614 /*
615 * Now we can go back through the `pulls' array, adjusting
616 * the score for each pull depending on how hard it is to
617 * reach its starting point, and also throwing out any
618 * whose starting points are genuinely unreachable even
619 * with the possibility of carving through INITIAL squares.
620 */
621 for (i = j = 0; i < npulls; i++) {
622#ifdef GENERATION_DIAGNOSTICS
623 printf("potential pull (%d,%d)-(%d,%d)",
624 pulls[i].ox, pulls[i].oy,
625 pulls[i].nx, pulls[i].ny);
626#endif
627 x = pulls[i].nx;
628 y = pulls[i].ny;
629 if (dist[y*w+x] < 0) {
630#ifdef GENERATION_DIAGNOSTICS
631 printf(" unreachable\n");
632#endif
633 continue; /* this pull isn't feasible at all */
634 } else {
635 /*
636 * Another nasty special case we have to check is
637 * whether the initial barrel location (ox,oy) is
638 * on the path used to reach the square. This can
639 * occur if that square is in state INITIAL: the
640 * pull is initially considered valid on the basis
641 * that the INITIAL can become BARRELTARGET, and
642 * it's also considered reachable on the basis that
643 * INITIAL can be turned into SPACE, but it can't
644 * be both at once.
645 *
646 * Fortunately, if (ox,oy) is on the path at all,
647 * it must be only one space from the end, so this
648 * is easy to spot and rule out.
649 */
650 if (prev[y*w+x] == pulls[i].oy*w+pulls[i].ox) {
651#ifdef GENERATION_DIAGNOSTICS
652 printf(" goes through itself\n");
653#endif
654 continue; /* this pull isn't feasible at all */
655 }
656 pulls[j] = pulls[i]; /* structure copy */
657 pulls[j].score += dist[y*w+x] * 3 /* new_space_score */;
658#ifdef GENERATION_DIAGNOSTICS
659 printf(" reachable at distance %d (cost now %d)\n",
660 dist[y*w+x], pulls[j].score);
661#endif
662 j++;
663 }
664 }
665 npulls = j;
666
667 /*
668 * Again, if there are no pulls available at all, we give
669 * up.
670 *
671 * (FIXME: or perhaps backtrack?)
672 */
673 if (npulls == 0)
674 break;
675
676 /*
677 * Now choose which pull to make. On the one hand we should
678 * prefer pulls which do less damage to the INITIAL squares
679 * (thus, ones for which we can already get into position
680 * via existing SPACEs, and for which the barrel already
681 * exists and doesn't have to be invented); on the other,
682 * we want to avoid _always_ preferring such pulls, on the
683 * grounds that that will lead to levels without very much
684 * stuff in.
685 *
686 * When creating new barrels, we prefer creations which are
687 * next to existing TARGET squares.
688 *
689 * FIXME: for the moment I'll make this very simple indeed.
690 */
691 i = random_upto(rs, npulls);
692
693 /*
694 * Actually make the pull, including carving a path to get
695 * to the site if necessary.
696 */
697 x = pulls[i].nx;
698 y = pulls[i].ny;
699 while (prev[y*w+x] >= 0) {
700 int p;
701
702 if (grid[y*w+x] == INITIAL)
703 grid[y*w+x] = SPACE;
704
705 p = prev[y*w+x];
706 y = p / w;
707 x = p % w;
708 }
709 px = 2*pulls[i].nx - pulls[i].ox;
710 py = 2*pulls[i].ny - pulls[i].oy;
711 if (grid[py*w+px] == INITIAL)
712 grid[py*w+px] = SPACE;
713 if (grid[pulls[i].ny*w+pulls[i].nx] == TARGET)
714 grid[pulls[i].ny*w+pulls[i].nx] = BARRELTARGET;
715 else
716 grid[pulls[i].ny*w+pulls[i].nx] = BARREL;
717 if (grid[pulls[i].oy*w+pulls[i].ox] == BARREL)
718 grid[pulls[i].oy*w+pulls[i].ox] = SPACE;
719 else if (grid[pulls[i].oy*w+pulls[i].ox] != DEEP_PIT)
720 grid[pulls[i].oy*w+pulls[i].ox] = TARGET;
721 }
722
723 sfree(heap);
724 sfree(prev);
725 sfree(dist);
726 sfree(pulls);
727
728 if (grid[py*w+px] == TARGET)
729 grid[py*w+px] = PLAYERTARGET;
730 else
731 grid[py*w+px] = PLAYER;
732}
733
734static char *new_game_desc(const game_params *params, random_state *rs,
735 char **aux, bool interactive)
736{
737 int w = params->w, h = params->h;
738 char *desc;
739 int desclen, descpos, descsize, prev, count;
740 unsigned char *grid;
741 int i, j;
742
743 /*
744 * FIXME: perhaps some more interesting means of choosing how
745 * many moves to try?
746 */
747 grid = snewn(w*h, unsigned char);
748 sokoban_generate(w, h, grid, w*h, false, rs);
749
750 desclen = descpos = descsize = 0;
751 desc = NULL;
752 prev = -1;
753 count = 0;
754 for (i = 0; i < w*h; i++) {
755 if (descsize < desclen + 40) {
756 descsize = desclen + 100;
757 desc = sresize(desc, descsize, char);
758 desc[desclen] = '\0';
759 }
760 switch (grid[i]) {
761 case INITIAL:
762 j = 'w'; /* FIXME: make some of these 's'? */
763 break;
764 case SPACE:
765 j = 's';
766 break;
767 case WALL:
768 j = 'w';
769 break;
770 case TARGET:
771 j = 't';
772 break;
773 case BARREL:
774 j = 'b';
775 break;
776 case BARRELTARGET:
777 j = 'f';
778 break;
779 case DEEP_PIT:
780 j = 'd';
781 break;
782 case PLAYER:
783 j = 'u';
784 break;
785 case PLAYERTARGET:
786 j = 'v';
787 break;
788 default:
789 j = '?';
790 break;
791 }
792 assert(j != '?');
793 if (j != prev) {
794 desc[desclen++] = j;
795 descpos = desclen;
796 prev = j;
797 count = 1;
798 } else {
799 count++;
800 desclen = descpos + sprintf(desc+descpos, "%d", count);
801 }
802 }
803
804 sfree(grid);
805
806 return desc;
807}
808
809static const char *validate_desc(const game_params *params, const char *desc)
810{
811 int w = params->w, h = params->h;
812 int area = 0;
813 int nplayers = 0;
814
815 while (*desc) {
816 int c = *desc++;
817 int n = 1;
818 if (*desc && isdigit((unsigned char)*desc)) {
819 n = atoi(desc);
820 while (*desc && isdigit((unsigned char)*desc)) desc++;
821 }
822
823 area += n;
824
825 if (c == PLAYER || c == PLAYERTARGET)
826 nplayers += n;
827 else if (c == INITIAL || c == SPACE || c == WALL || c == TARGET ||
828 c == PIT || c == DEEP_PIT || IS_BARREL(c))
829 /* ok */;
830 else
831 return "Invalid character in game description";
832 }
833
834 if (area > w*h)
835 return "Too much data in game description";
836 if (area < w*h)
837 return "Too little data in game description";
838 if (nplayers < 1)
839 return "No starting player position specified";
840 if (nplayers > 1)
841 return "More than one starting player position specified";
842
843 return NULL;
844}
845
846static game_state *new_game(midend *me, const game_params *params,
847 const char *desc)
848{
849 int w = params->w, h = params->h;
850 game_state *state = snew(game_state);
851 int i;
852
853 state->p = *params; /* structure copy */
854 state->grid = snewn(w*h, unsigned char);
855 state->px = state->py = -1;
856 state->completed = false;
857
858 i = 0;
859
860 while (*desc) {
861 int c = *desc++;
862 int n = 1;
863 if (*desc && isdigit((unsigned char)*desc)) {
864 n = atoi(desc);
865 while (*desc && isdigit((unsigned char)*desc)) desc++;
866 }
867
868 if (c == PLAYER || c == PLAYERTARGET) {
869 state->py = i / w;
870 state->px = i % w;
871 c = IS_ON_TARGET(c) ? TARGET : SPACE;
872 }
873
874 while (n-- > 0)
875 state->grid[i++] = c;
876 }
877
878 assert(i == w*h);
879 assert(state->px != -1 && state->py != -1);
880
881 return state;
882}
883
884static game_state *dup_game(const game_state *state)
885{
886 int w = state->p.w, h = state->p.h;
887 game_state *ret = snew(game_state);
888
889 ret->p = state->p; /* structure copy */
890 ret->grid = snewn(w*h, unsigned char);
891 memcpy(ret->grid, state->grid, w*h);
892 ret->px = state->px;
893 ret->py = state->py;
894 ret->completed = state->completed;
895
896 return ret;
897}
898
899static void free_game(game_state *state)
900{
901 sfree(state->grid);
902 sfree(state);
903}
904
905static char *solve_game(const game_state *state, const game_state *currstate,
906 const char *aux, const char **error)
907{
908 return NULL;
909}
910
911static bool game_can_format_as_text_now(const game_params *params)
912{
913 return true;
914}
915
916static char *game_text_format(const game_state *state)
917{
918 return NULL;
919}
920
921static game_ui *new_ui(const game_state *state)
922{
923 return NULL;
924}
925
926static void free_ui(game_ui *ui)
927{
928}
929
930static void game_changed_state(game_ui *ui, const game_state *oldstate,
931 const game_state *newstate)
932{
933}
934
935struct game_drawstate {
936 game_params p;
937 int tilesize;
938 bool started;
939 unsigned short *grid;
940};
941
942#define PREFERRED_TILESIZE 32
943#define TILESIZE (ds->tilesize)
944#define BORDER (TILESIZE)
945#define HIGHLIGHT_WIDTH (TILESIZE / 10)
946#define COORD(x) ( (x) * TILESIZE + BORDER )
947#define FROMCOORD(x) ( ((x) - BORDER + TILESIZE) / TILESIZE - 1 )
948
949/*
950 * I'm going to need to do most of the move-type analysis in both
951 * interpret_move and execute_move, so I'll abstract it out into a
952 * subfunction. move_type() returns -1 for an illegal move, 0 for a
953 * movement, and 1 for a push.
954 */
955static int move_type(const game_state *state, int dx, int dy)
956{
957 int w = state->p.w, h = state->p.h;
958 int px = state->px, py = state->py;
959 int nx, ny, nbx, nby;
960
961 assert(dx >= -1 && dx <= +1);
962 assert(dy >= -1 && dy <= +1);
963 assert(dx || dy);
964
965 nx = px + dx;
966 ny = py + dy;
967
968 /*
969 * Disallow any move that goes off the grid.
970 */
971 if (nx < 0 || nx >= w || ny < 0 || ny >= h)
972 return -1;
973
974 /*
975 * Examine the target square of the move to see whether it's a
976 * space, a barrel, or a wall.
977 */
978
979 if (state->grid[ny*w+nx] == WALL ||
980 state->grid[ny*w+nx] == PIT ||
981 state->grid[ny*w+nx] == DEEP_PIT)
982 return -1; /* this one's easy; just disallow it */
983
984 if (IS_BARREL(state->grid[ny*w+nx])) {
985 /*
986 * This is a push move. For a start, that means it must not
987 * be diagonal.
988 */
989 if (dy && dx)
990 return -1;
991
992 /*
993 * Now find the location of the third square involved in
994 * the push, and stop if it's off the edge.
995 */
996 nbx = nx + dx;
997 nby = ny + dy;
998 if (nbx < 0 || nbx >= w || nby < 0 || nby >= h)
999 return -1;
1000
1001 /*
1002 * That third square must be able to accept a barrel.
1003 */
1004 if (state->grid[nby*w+nbx] == SPACE ||
1005 state->grid[nby*w+nbx] == TARGET ||
1006 state->grid[nby*w+nbx] == PIT ||
1007 state->grid[nby*w+nbx] == DEEP_PIT) {
1008 /*
1009 * The push is valid.
1010 */
1011 return 1;
1012 } else {
1013 return -1;
1014 }
1015 } else {
1016 /*
1017 * This is just an ordinary move. We've already checked the
1018 * target square, so the only thing left to check is that a
1019 * diagonal move has a space on one side to have notionally
1020 * gone through.
1021 */
1022 if (dx && dy &&
1023 state->grid[(py+dy)*w+px] != SPACE &&
1024 state->grid[(py+dy)*w+px] != TARGET &&
1025 state->grid[py*w+(px+dx)] != SPACE &&
1026 state->grid[py*w+(px+dx)] != TARGET)
1027 return -1;
1028
1029 /*
1030 * Otherwise, the move is valid.
1031 */
1032 return 0;
1033 }
1034}
1035
1036static char *interpret_move(const game_state *state, game_ui *ui,
1037 const game_drawstate *ds,
1038 int x, int y, int button)
1039{
1040 int dx=0, dy=0;
1041 char *move;
1042
1043 /*
1044 * Diagonal movement is supported as it is in NetHack: it's
1045 * for movement only (never pushing), and one of the two
1046 * squares adjacent to both the source and destination
1047 * squares must be free to move through. In other words, it
1048 * is only a shorthand for two orthogonal moves and cannot
1049 * change the nature of the actual puzzle game.
1050 */
1051 if (button == CURSOR_UP || button == (MOD_NUM_KEYPAD | '8'))
1052 dx = 0, dy = -1;
1053 else if (button == CURSOR_DOWN || button == (MOD_NUM_KEYPAD | '2'))
1054 dx = 0, dy = +1;
1055 else if (button == CURSOR_LEFT || button == (MOD_NUM_KEYPAD | '4'))
1056 dx = -1, dy = 0;
1057 else if (button == CURSOR_RIGHT || button == (MOD_NUM_KEYPAD | '6'))
1058 dx = +1, dy = 0;
1059 else if (button == (MOD_NUM_KEYPAD | '7'))
1060 dx = -1, dy = -1;
1061 else if (button == (MOD_NUM_KEYPAD | '9'))
1062 dx = +1, dy = -1;
1063 else if (button == (MOD_NUM_KEYPAD | '1'))
1064 dx = -1, dy = +1;
1065 else if (button == (MOD_NUM_KEYPAD | '3'))
1066 dx = +1, dy = +1;
1067 else if (button == LEFT_BUTTON)
1068 {
1069 if(x < COORD(state->px))
1070 dx = -1;
1071 else if (x > COORD(state->px + 1))
1072 dx = 1;
1073 if(y < COORD(state->py))
1074 dy = -1;
1075 else if (y > COORD(state->py + 1))
1076 dy = 1;
1077 }
1078 else
1079 return NULL;
1080
1081 if((dx == 0) && (dy == 0))
1082 return(NULL);
1083
1084 if (move_type(state, dx, dy) < 0)
1085 return NULL;
1086
1087 move = snewn(2, char);
1088 move[1] = '\0';
1089 move[0] = '5' - 3*dy + dx;
1090 return move;
1091}
1092
1093static game_state *execute_move(const game_state *state, const char *move)
1094{
1095 int w = state->p.w, h = state->p.h;
1096 int px = state->px, py = state->py;
1097 int dx, dy, nx, ny, nbx, nby, type, m, i;
1098 bool freebarrels, freetargets;
1099 game_state *ret;
1100
1101 if (*move < '1' || *move == '5' || *move > '9' || move[1])
1102 return NULL; /* invalid move string */
1103
1104 m = *move - '0';
1105 dx = (m + 2) % 3 - 1;
1106 dy = 2 - (m + 2) / 3;
1107 type = move_type(state, dx, dy);
1108 if (type < 0)
1109 return NULL;
1110
1111 ret = dup_game(state);
1112
1113 nx = px + dx;
1114 ny = py + dy;
1115 nbx = nx + dx;
1116 nby = ny + dy;
1117
1118 if (type) {
1119 int b;
1120
1121 /*
1122 * Push.
1123 */
1124 b = ret->grid[ny*w+nx];
1125 if (IS_ON_TARGET(b)) {
1126 ret->grid[ny*w+nx] = TARGET;
1127 b = DETARGETISE(b);
1128 } else
1129 ret->grid[ny*w+nx] = SPACE;
1130
1131 if (ret->grid[nby*w+nbx] == PIT)
1132 ret->grid[nby*w+nbx] = SPACE;
1133 else if (ret->grid[nby*w+nbx] == DEEP_PIT)
1134 /* do nothing - the pit eats the barrel and remains there */;
1135 else if (ret->grid[nby*w+nbx] == TARGET)
1136 ret->grid[nby*w+nbx] = TARGETISE(b);
1137 else
1138 ret->grid[nby*w+nbx] = b;
1139 }
1140
1141 ret->px = nx;
1142 ret->py = ny;
1143
1144 /*
1145 * Check for completion. This is surprisingly complicated,
1146 * given the presence of pits and deep pits, and also the fact
1147 * that some Sokoban levels with pits have fewer pits than
1148 * barrels (due to providing spares, e.g. NetHack's). I think
1149 * the completion condition in fact must be that the game
1150 * cannot become any _more_ complete. That is, _either_ there
1151 * are no remaining barrels not on targets, _or_ there is a
1152 * good reason why any such barrels cannot be placed. The only
1153 * available good reason is that there are no remaining pits,
1154 * no free target squares, and no deep pits at all.
1155 */
1156 if (!ret->completed) {
1157 freebarrels = false;
1158 freetargets = false;
1159 for (i = 0; i < w*h; i++) {
1160 int v = ret->grid[i];
1161
1162 if (IS_BARREL(v) && !IS_ON_TARGET(v))
1163 freebarrels = true;
1164 if (v == DEEP_PIT || v == PIT ||
1165 (!IS_BARREL(v) && IS_ON_TARGET(v)))
1166 freetargets = true;
1167 }
1168
1169 if (!freebarrels || !freetargets)
1170 ret->completed = true;
1171 }
1172
1173 return ret;
1174}
1175
1176/* ----------------------------------------------------------------------
1177 * Drawing routines.
1178 */
1179
1180static void game_compute_size(const game_params *params, int tilesize,
1181 const game_ui *ui, int *x, int *y)
1182{
1183 /* Ick: fake up `ds->tilesize' for macro expansion purposes */
1184 struct { int tilesize; } ads, *ds = &ads;
1185 ads.tilesize = tilesize;
1186
1187 *x = 2 * BORDER + 1 + params->w * TILESIZE;
1188 *y = 2 * BORDER + 1 + params->h * TILESIZE;
1189}
1190
1191static void game_set_size(drawing *dr, game_drawstate *ds,
1192 const game_params *params, int tilesize)
1193{
1194 ds->tilesize = tilesize;
1195}
1196
1197static float *game_colours(frontend *fe, int *ncolours)
1198{
1199 float *ret = snewn(3 * NCOLOURS, float);
1200 int i;
1201
1202 game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
1203
1204 ret[COL_OUTLINE * 3 + 0] = 0.0F;
1205 ret[COL_OUTLINE * 3 + 1] = 0.0F;
1206 ret[COL_OUTLINE * 3 + 2] = 0.0F;
1207
1208 ret[COL_PLAYER * 3 + 0] = 0.0F;
1209 ret[COL_PLAYER * 3 + 1] = 1.0F;
1210 ret[COL_PLAYER * 3 + 2] = 0.0F;
1211
1212 ret[COL_BARREL * 3 + 0] = 0.6F;
1213 ret[COL_BARREL * 3 + 1] = 0.3F;
1214 ret[COL_BARREL * 3 + 2] = 0.0F;
1215
1216 ret[COL_TARGET * 3 + 0] = ret[COL_LOWLIGHT * 3 + 0];
1217 ret[COL_TARGET * 3 + 1] = ret[COL_LOWLIGHT * 3 + 1];
1218 ret[COL_TARGET * 3 + 2] = ret[COL_LOWLIGHT * 3 + 2];
1219
1220 ret[COL_PIT * 3 + 0] = ret[COL_LOWLIGHT * 3 + 0] / 2;
1221 ret[COL_PIT * 3 + 1] = ret[COL_LOWLIGHT * 3 + 1] / 2;
1222 ret[COL_PIT * 3 + 2] = ret[COL_LOWLIGHT * 3 + 2] / 2;
1223
1224 ret[COL_DEEP_PIT * 3 + 0] = 0.0F;
1225 ret[COL_DEEP_PIT * 3 + 1] = 0.0F;
1226 ret[COL_DEEP_PIT * 3 + 2] = 0.0F;
1227
1228 ret[COL_TEXT * 3 + 0] = 1.0F;
1229 ret[COL_TEXT * 3 + 1] = 1.0F;
1230 ret[COL_TEXT * 3 + 2] = 1.0F;
1231
1232 ret[COL_GRID * 3 + 0] = ret[COL_LOWLIGHT * 3 + 0];
1233 ret[COL_GRID * 3 + 1] = ret[COL_LOWLIGHT * 3 + 1];
1234 ret[COL_GRID * 3 + 2] = ret[COL_LOWLIGHT * 3 + 2];
1235
1236 ret[COL_OUTLINE * 3 + 0] = 0.0F;
1237 ret[COL_OUTLINE * 3 + 1] = 0.0F;
1238 ret[COL_OUTLINE * 3 + 2] = 0.0F;
1239
1240 for (i = 0; i < 3; i++) {
1241 ret[COL_WALL * 3 + i] = (3 * ret[COL_BACKGROUND * 3 + i] +
1242 1 * ret[COL_HIGHLIGHT * 3 + i]) / 4;
1243 }
1244
1245 *ncolours = NCOLOURS;
1246 return ret;
1247}
1248
1249static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
1250{
1251 int w = state->p.w, h = state->p.h;
1252 struct game_drawstate *ds = snew(struct game_drawstate);
1253 int i;
1254
1255 ds->tilesize = 0;
1256 ds->p = state->p; /* structure copy */
1257 ds->grid = snewn(w*h, unsigned short);
1258 for (i = 0; i < w*h; i++)
1259 ds->grid[i] = INVALID;
1260 ds->started = false;
1261
1262 return ds;
1263}
1264
1265static void game_free_drawstate(drawing *dr, game_drawstate *ds)
1266{
1267 sfree(ds->grid);
1268 sfree(ds);
1269}
1270
1271static void draw_tile(drawing *dr, game_drawstate *ds, int x, int y, int v)
1272{
1273 int tx = COORD(x), ty = COORD(y);
1274 int bg = (v & 0x100 ? COL_HIGHLIGHT : COL_BACKGROUND);
1275
1276 v &= 0xFF;
1277
1278 clip(dr, tx+1, ty+1, TILESIZE-1, TILESIZE-1);
1279 draw_rect(dr, tx+1, ty+1, TILESIZE-1, TILESIZE-1, bg);
1280
1281 if (v == WALL) {
1282 int coords[6];
1283
1284 coords[0] = tx + TILESIZE;
1285 coords[1] = ty + TILESIZE;
1286 coords[2] = tx + TILESIZE;
1287 coords[3] = ty + 1;
1288 coords[4] = tx + 1;
1289 coords[5] = ty + TILESIZE;
1290 draw_polygon(dr, coords, 3, COL_LOWLIGHT, COL_LOWLIGHT);
1291
1292 coords[0] = tx + 1;
1293 coords[1] = ty + 1;
1294 draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT);
1295
1296 draw_rect(dr, tx + 1 + HIGHLIGHT_WIDTH, ty + 1 + HIGHLIGHT_WIDTH,
1297 TILESIZE - 2*HIGHLIGHT_WIDTH,
1298 TILESIZE - 2*HIGHLIGHT_WIDTH, COL_WALL);
1299 } else if (v == PIT) {
1300 draw_circle(dr, tx + TILESIZE/2, ty + TILESIZE/2,
1301 TILESIZE*3/7, COL_PIT, COL_OUTLINE);
1302 } else if (v == DEEP_PIT) {
1303 draw_circle(dr, tx + TILESIZE/2, ty + TILESIZE/2,
1304 TILESIZE*3/7, COL_DEEP_PIT, COL_OUTLINE);
1305 } else {
1306 if (IS_ON_TARGET(v)) {
1307 draw_circle(dr, tx + TILESIZE/2, ty + TILESIZE/2,
1308 TILESIZE*3/7, COL_TARGET, COL_OUTLINE);
1309 }
1310 if (IS_PLAYER(v)) {
1311 draw_circle(dr, tx + TILESIZE/2, ty + TILESIZE/2,
1312 TILESIZE/3, COL_PLAYER, COL_OUTLINE);
1313 } else if (IS_BARREL(v)) {
1314 char str[2];
1315
1316 draw_circle(dr, tx + TILESIZE/2, ty + TILESIZE/2,
1317 TILESIZE/3, COL_BARREL, COL_OUTLINE);
1318 str[1] = '\0';
1319 str[0] = BARREL_LABEL(v);
1320 if (str[0]) {
1321 draw_text(dr, tx + TILESIZE/2, ty + TILESIZE/2,
1322 FONT_VARIABLE, TILESIZE/2,
1323 ALIGN_VCENTRE | ALIGN_HCENTRE, COL_TEXT, str);
1324 }
1325 }
1326 }
1327
1328 unclip(dr);
1329 draw_update(dr, tx, ty, TILESIZE, TILESIZE);
1330}
1331
1332static void game_redraw(drawing *dr, game_drawstate *ds,
1333 const game_state *oldstate, const game_state *state,
1334 int dir, const game_ui *ui,
1335 float animtime, float flashtime)
1336{
1337 int w = state->p.w, h = state->p.h /*, wh = w*h */;
1338 int x, y;
1339 int flashtype;
1340
1341 if (flashtime &&
1342 !((int)(flashtime * 3 / FLASH_LENGTH) % 2))
1343 flashtype = 0x100;
1344 else
1345 flashtype = 0;
1346
1347 /*
1348 * Initialise a fresh drawstate.
1349 */
1350 if (!ds->started) {
1351 /*
1352 * Draw the grid lines.
1353 */
1354 for (y = 0; y <= h; y++)
1355 draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y),
1356 COL_LOWLIGHT);
1357 for (x = 0; x <= w; x++)
1358 draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h),
1359 COL_LOWLIGHT);
1360
1361 ds->started = true;
1362 }
1363
1364 /*
1365 * Draw the grid contents.
1366 */
1367 for (y = 0; y < h; y++)
1368 for (x = 0; x < w; x++) {
1369 int v = state->grid[y*w+x];
1370 if (y == state->py && x == state->px) {
1371 if (v == TARGET)
1372 v = PLAYERTARGET;
1373 else {
1374 assert(v == SPACE);
1375 v = PLAYER;
1376 }
1377 }
1378
1379 v |= flashtype;
1380
1381 if (ds->grid[y*w+x] != v) {
1382 draw_tile(dr, ds, x, y, v);
1383 ds->grid[y*w+x] = v;
1384 }
1385 }
1386
1387}
1388
1389static float game_anim_length(const game_state *oldstate,
1390 const game_state *newstate, int dir, game_ui *ui)
1391{
1392 return 0.0F;
1393}
1394
1395static float game_flash_length(const game_state *oldstate,
1396 const game_state *newstate, int dir, game_ui *ui)
1397{
1398 if (!oldstate->completed && newstate->completed)
1399 return FLASH_LENGTH;
1400 else
1401 return 0.0F;
1402}
1403
1404static void game_get_cursor_location(const game_ui *ui,
1405 const game_drawstate *ds,
1406 const game_state *state,
1407 const game_params *params,
1408 int *x, int *y, int *w, int *h)
1409{
1410}
1411
1412static int game_status(const game_state *state)
1413{
1414 return state->completed ? +1 : 0;
1415}
1416
1417static bool game_timing_state(const game_state *state, game_ui *ui)
1418{
1419 return true;
1420}
1421
1422static void game_print_size(const game_params *params, const game_ui *ui,
1423 float *x, float *y)
1424{
1425}
1426
1427static void game_print(drawing *dr, const game_state *state, const game_ui *ui,
1428 int tilesize)
1429{
1430}
1431
1432#ifdef COMBINED
1433#define thegame sokoban
1434#endif
1435
1436const struct game thegame = {
1437 "Sokoban", NULL, NULL,
1438 default_params,
1439 game_fetch_preset, NULL,
1440 decode_params,
1441 encode_params,
1442 free_params,
1443 dup_params,
1444 true, game_configure, custom_params,
1445 validate_params,
1446 new_game_desc,
1447 validate_desc,
1448 new_game,
1449 dup_game,
1450 free_game,
1451 false, solve_game,
1452 false, game_can_format_as_text_now, game_text_format,
1453 NULL, NULL, /* get_prefs, set_prefs */
1454 new_ui,
1455 free_ui,
1456 NULL, /* encode_ui */
1457 NULL, /* decode_ui */
1458 NULL, /* game_request_keys */
1459 game_changed_state,
1460 NULL, /* current_key_label */
1461 interpret_move,
1462 execute_move,
1463 PREFERRED_TILESIZE, game_compute_size, game_set_size,
1464 game_colours,
1465 game_new_drawstate,
1466 game_free_drawstate,
1467 game_redraw,
1468 game_anim_length,
1469 game_flash_length,
1470 game_get_cursor_location,
1471 game_status,
1472 false, false, game_print_size, game_print,
1473 false, /* wants_statusbar */
1474 false, game_timing_state,
1475 0, /* flags */
1476};
diff --git a/apps/recorder/keyboard.c b/apps/recorder/keyboard.c
index 736e9738c8..26ac6c1ae7 100644
--- a/apps/recorder/keyboard.c
+++ b/apps/recorder/keyboard.c
@@ -951,8 +951,8 @@ static void kbd_draw_picker(struct keyboard_parameters *pm,
951#ifdef HAVE_MORSE_INPUT 951#ifdef HAVE_MORSE_INPUT
952 if (state->morse_mode) 952 if (state->morse_mode)
953 { 953 {
954 const int w = 6, h = 8; /* sysfixed font width, height */ 954 const int w = 6, h = 9; /* sysfixed font width, height */
955 int i, j, x, y; 955 int i, iNext, j, x, y;
956 int sc_w = vp->width, sc_h = vp->height;//pm->main_y - pm->keyboard_margin - 1; 956 int sc_w = vp->width, sc_h = vp->height;//pm->main_y - pm->keyboard_margin - 1;
957 957
958 /* Draw morse code screen with sysfont */ 958 /* Draw morse code screen with sysfont */
@@ -960,37 +960,47 @@ static void kbd_draw_picker(struct keyboard_parameters *pm,
960 x = 0; 960 x = 0;
961 y = 0; 961 y = 0;
962 outline[1] = '\0'; 962 outline[1] = '\0';
963 963
964 /* Draw morse code table with code descriptions. */ 964 /* Draw morse code table with code descriptions. */
965 for (i = 0; morse_alphabets[i] != '\0'; i++) 965 for (i = 0; morse_alphabets[i] != '\0'; i++) {
966 {
967 int morse_code; 966 int morse_code;
968
969 outline[0] = morse_alphabets[i]; 967 outline[0] = morse_alphabets[i];
970 sc->putsxy(x, y, outline); 968 sc->putsxy(x, y, outline);
971
972 morse_code = morse_codes[i]; 969 morse_code = morse_codes[i];
973 for (j = 0; morse_code > 0x01; morse_code >>= 1) 970 for (j = 0; morse_code > 0x01; morse_code >>= 1) {
974 j++; 971 j++;
975 972 }
976 x += w + 3 + j*4; 973 x += w + 3 + j * 4;
977 morse_code = morse_codes[i]; 974 morse_code = morse_codes[i];
978 for (; morse_code > 0x01; morse_code >>= 1) 975 for (; morse_code > 0x01; morse_code >>= 1) {
979 {
980 x -= 4; 976 x -= 4;
981 if (morse_code & 0x01) 977 if (morse_code & 0x01) {
982 sc->fillrect(x, y + 2, 3, 4); 978 sc->fillrect(x, y + 2, 3, 4);
983 else 979 } else {
984 sc->fillrect(x, y + 3, 1, 2); 980 sc->fillrect(x, y + 3, 1, 2);
981 }
985 } 982 }
986 983 x += j * 4;
987 x += w*5 - 3; 984 iNext = i + 1;
988 if (x + w*6 >= sc_w) 985 if (morse_alphabets[iNext] == '\0') {
989 { 986 break;
987 }
988 morse_code = morse_codes[iNext];
989 for (j = 0; morse_code > 0x01; morse_code >>= 1) {
990 j++;
991 }
992 // If the next one will go out of line
993 bool needNewLine = x + w + 3 + j * 4 + w >= sc_w;
994 if (needNewLine) {
995 if (y + h >= sc_h) {
996 // No more height space
997 break;
998 }
990 x = 0; 999 x = 0;
991 y += h; 1000 y += h;
992 if (y + h >= sc_h) 1001 } else {
993 break; 1002 // Some pixels for spacing in the same line
1003 x += w;
994 } 1004 }
995 } 1005 }
996 } 1006 }
diff --git a/apps/settings.h b/apps/settings.h
index 056f40df6a..a9a9f647e3 100644
--- a/apps/settings.h
+++ b/apps/settings.h
@@ -78,6 +78,13 @@ enum
78 TRIG_TYPE_NEW_FILE 78 TRIG_TYPE_NEW_FILE
79}; 79};
80 80
81enum {
82 PLAYLIST_VIEWER_ENTRY_SHOW_FILE_NAME = 0,
83 PLAYLIST_VIEWER_ENTRY_SHOW_FULL_PATH = 1,
84 PLAYLIST_VIEWER_ENTRY_SHOW_ID3_TITLE_AND_ALBUM = 2,
85 PLAYLIST_VIEWER_ENTRY_SHOW_ID3_TITLE = 3
86};
87
81#ifdef HAVE_CROSSFADE 88#ifdef HAVE_CROSSFADE
82enum { 89enum {
83 CROSSFADE_ENABLE_OFF = 0, 90 CROSSFADE_ENABLE_OFF = 0,
@@ -686,6 +693,7 @@ struct user_settings
686 int screen_scroll_step; 693 int screen_scroll_step;
687 int show_path_in_browser; /* 0=off, 1=current directory, 2=full path */ 694 int show_path_in_browser; /* 0=off, 1=current directory, 2=full path */
688 bool offset_out_of_view; 695 bool offset_out_of_view;
696 bool disable_mainmenu_scrolling;
689 unsigned char icon_file[MAX_FILENAME+1]; 697 unsigned char icon_file[MAX_FILENAME+1];
690 unsigned char viewers_icon_file[MAX_FILENAME+1]; 698 unsigned char viewers_icon_file[MAX_FILENAME+1];
691 unsigned char font_file[MAX_FILENAME+1]; /* last font */ 699 unsigned char font_file[MAX_FILENAME+1]; /* last font */
diff --git a/apps/settings_list.c b/apps/settings_list.c
index 98e8dec5a8..a9f627bfa6 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,
@@ -1319,6 +1323,8 @@ const struct settings_list settings[] = {
1319#endif 1323#endif
1320 OFFON_SETTING(0, offset_out_of_view, LANG_SCREEN_SCROLL_VIEW, 1324 OFFON_SETTING(0, offset_out_of_view, LANG_SCREEN_SCROLL_VIEW,
1321 false, "Screen Scrolls Out Of View", NULL), 1325 false, "Screen Scrolls Out Of View", NULL),
1326 OFFON_SETTING(0, disable_mainmenu_scrolling, LANG_DISABLE_MAINMENU_SCROLLING,
1327 false, "Disable main menu scrolling", NULL),
1322 INT_SETTING(F_PADTITLE, scroll_step, LANG_SCROLL_STEP, 6, "scroll step", 1328 INT_SETTING(F_PADTITLE, scroll_step, LANG_SCROLL_STEP, 6, "scroll step",
1323 UNIT_PIXEL, 1, LCD_WIDTH, 1, NULL, NULL, lcd_scroll_step), 1329 UNIT_PIXEL, 1, LCD_WIDTH, 1, NULL, NULL, lcd_scroll_step),
1324 INT_SETTING(F_PADTITLE, screen_scroll_step, LANG_SCREEN_SCROLL_STEP, 16, 1330 INT_SETTING(F_PADTITLE, screen_scroll_step, LANG_SCREEN_SCROLL_STEP, 16,
@@ -1415,9 +1421,11 @@ const struct settings_list settings[] = {
1415 OFFON_SETTING(0,playlist_viewer_indices,LANG_SHOW_INDICES,true, 1421 OFFON_SETTING(0,playlist_viewer_indices,LANG_SHOW_INDICES,true,
1416 "playlist viewer indices",NULL), 1422 "playlist viewer indices",NULL),
1417 CHOICE_SETTING(0, playlist_viewer_track_display, LANG_TRACK_DISPLAY, 0, 1423 CHOICE_SETTING(0, playlist_viewer_track_display, LANG_TRACK_DISPLAY, 0,
1418 "playlist viewer track display","track name,full path", 1424 "playlist viewer track display",
1419 NULL, 2, ID2P(LANG_DISPLAY_TRACK_NAME_ONLY), 1425 "track name,full path,title and album from tags,title from tags",
1420 ID2P(LANG_DISPLAY_FULL_PATH)), 1426 NULL, 4, ID2P(LANG_DISPLAY_TRACK_NAME_ONLY),
1427 ID2P(LANG_DISPLAY_FULL_PATH),ID2P(LANG_DISPLAY_TITLEALBUM_FROMTAGS),
1428 ID2P(LANG_DISPLAY_TITLE_FROMTAGS)),
1421 CHOICE_SETTING(0, recursive_dir_insert, LANG_RECURSE_DIRECTORY , RECURSE_ON, 1429 CHOICE_SETTING(0, recursive_dir_insert, LANG_RECURSE_DIRECTORY , RECURSE_ON,
1422 "recursive directory insert", off_on_ask, NULL , 3 , 1430 "recursive directory insert", off_on_ask, NULL , 3 ,
1423 ID2P(LANG_OFF), ID2P(LANG_ON), ID2P(LANG_ASK)), 1431 ID2P(LANG_OFF), ID2P(LANG_ON), ID2P(LANG_ASK)),
@@ -1852,7 +1860,7 @@ const struct settings_list settings[] = {
1852 true, "warn when erasing dynamic playlist",NULL), 1860 true, "warn when erasing dynamic playlist",NULL),
1853 OFFON_SETTING(0, keep_current_track_on_replace_playlist, LANG_KEEP_CURRENT_TRACK_ON_REPLACE, 1861 OFFON_SETTING(0, keep_current_track_on_replace_playlist, LANG_KEEP_CURRENT_TRACK_ON_REPLACE,
1854 true, "keep current track when replacing playlist",NULL), 1862 true, "keep current track when replacing playlist",NULL),
1855 OFFON_SETTING(0, show_shuffled_adding_options, LANG_SHOW_SHUFFLED_ADDING_OPTIONS, false, 1863 OFFON_SETTING(0, show_shuffled_adding_options, LANG_SHOW_SHUFFLED_ADDING_OPTIONS, true,
1856 "show shuffled adding options", NULL), 1864 "show shuffled adding options", NULL),
1857 CHOICE_SETTING(0, show_queue_options, LANG_SHOW_QUEUE_OPTIONS, 0, 1865 CHOICE_SETTING(0, show_queue_options, LANG_SHOW_QUEUE_OPTIONS, 0,
1858 "show queue options", "off,on,in submenu", 1866 "show queue options", "off,on,in submenu",
diff --git a/apps/shortcuts.c b/apps/shortcuts.c
index 5453422b43..adac5e3327 100644
--- a/apps/shortcuts.c
+++ b/apps/shortcuts.c
@@ -482,15 +482,15 @@ static int shortcut_menu_speak_item(int selected_item, void * data)
482 { 482 {
483 case SHORTCUT_BROWSER: 483 case SHORTCUT_BROWSER:
484 { 484 {
485 static char path[MAX_PATH];
486 DIR* dir; 485 DIR* dir;
487 struct dirent* entry; 486 struct dirent* entry;
488 char* filename = strrchr(sc->u.path, PATH_SEPCH) + 1; 487 char* slash = strrchr(sc->u.path, PATH_SEPCH);
489 if (*filename != '\0') 488 char* filename = slash + 1;
489 if (slash && *filename != '\0')
490 { 490 {
491 int dirlen = (filename - sc->u.path); 491 *slash = '\0'; /* terminate the path to open the directory */
492 strmemccpy(path, sc->u.path, dirlen + 1); 492 dir = opendir(sc->u.path);
493 dir = opendir(path); 493 *slash = PATH_SEPCH; /* restore fullpath */
494 if (dir) 494 if (dir)
495 { 495 {
496 while (0 != (entry = readdir(dir))) 496 while (0 != (entry = readdir(dir)))
@@ -498,9 +498,12 @@ static int shortcut_menu_speak_item(int selected_item, void * data)
498 if (!strcmp(entry->d_name, filename)) 498 if (!strcmp(entry->d_name, filename))
499 { 499 {
500 struct dirinfo info = dir_get_info(dir, entry); 500 struct dirinfo info = dir_get_info(dir, entry);
501
501 if (info.attribute & ATTR_DIRECTORY) 502 if (info.attribute & ATTR_DIRECTORY)
502 talk_dir_or_spell(sc->u.path, NULL, false); 503 talk_dir_or_spell(sc->u.path, NULL, false);
503 else talk_file_or_spell(path, filename, NULL, false); 504 else
505 talk_file_or_spell(NULL, sc->u.path, NULL, false);
506
504 closedir(dir); 507 closedir(dir);
505 return 0; 508 return 0;
506 } 509 }
diff --git a/apps/tagcache.c b/apps/tagcache.c
index 1412647368..5bfeb82481 100644
--- a/apps/tagcache.c
+++ b/apps/tagcache.c
@@ -2085,13 +2085,18 @@ bool tagcache_fill_tags(struct mp3entry *id3, const char *filename)
2085 return false; 2085 return false;
2086 2086
2087 /* Find the corresponding entry in tagcache. */ 2087 /* Find the corresponding entry in tagcache. */
2088
2089 if (filename != NULL)
2090 memset(id3, 0, sizeof(struct mp3entry));
2091 else /* Note: caller clears id3 prior to call */
2092 filename = id3->path;
2093
2088 idx_id = find_entry_ram(filename); 2094 idx_id = find_entry_ram(filename);
2089 if (idx_id < 0) 2095 if (idx_id < 0)
2090 return false; 2096 return false;
2091 2097
2092 entry = &tcramcache.hdr->indices[idx_id]; 2098 entry = &tcramcache.hdr->indices[idx_id];
2093 2099
2094 memset(id3, 0, sizeof(struct mp3entry));
2095 char* buf = id3->id3v2buf; 2100 char* buf = id3->id3v2buf;
2096 ssize_t remaining = sizeof(id3->id3v2buf); 2101 ssize_t remaining = sizeof(id3->id3v2buf);
2097 2102
diff --git a/apps/tagnavi.config b/apps/tagnavi.config
index 6eda05ec44..b599f9577c 100644
--- a/apps/tagnavi.config
+++ b/apps/tagnavi.config
@@ -10,7 +10,7 @@
10# is included by this file and will not be overwritten automatically. 10# is included by this file and will not be overwritten automatically.
11 11
12# Basic format declarations 12# Basic format declarations
13%format "fmt_title" "%s - %02d:%02d (%s)" basename Lm Ls filename ? title == "<Untagged>" 13%format "fmt_title" "%s - %02d:%02d (%s)" basename Lm Ls filename ? title == "[Untagged]"
14%format "fmt_title" "%d.%02d. %s - %02d:%02d" discnum tracknum title Lm Ls ? discnum > "0" 14%format "fmt_title" "%d.%02d. %s - %02d:%02d" discnum tracknum title Lm Ls ? discnum > "0"
15%format "fmt_title" "%02d. %s - %02d:%02d" tracknum title Lm Ls ? tracknum > "0" 15%format "fmt_title" "%02d. %s - %02d:%02d" tracknum title Lm Ls ? tracknum > "0"
16%format "fmt_title" "%s - %02d:%02d" title Lm Ls 16%format "fmt_title" "%s - %02d:%02d" title Lm Ls
@@ -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..1a49936f45 100644
--- a/apps/tagtree.c
+++ b/apps/tagtree.c
@@ -56,6 +56,8 @@
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"
60#include "plugin.h"
59 61
60#define str_or_empty(x) (x ? x : "(NULL)") 62#define str_or_empty(x) (x ? x : "(NULL)")
61 63
@@ -64,6 +66,10 @@
64 66
65static int tagtree_play_folder(struct tree_context* c); 67static int tagtree_play_folder(struct tree_context* c);
66 68
69/* reuse of tagtree data after tagtree_play_folder() */
70static uint32_t loaded_entries_crc = 0;
71
72
67/* this needs to be same size as struct entry (tree.h) and name needs to be 73/* this needs to be same size as struct entry (tree.h) and name needs to be
68 * the first; so that they're compatible enough to walk arrays of both 74 * the first; so that they're compatible enough to walk arrays of both
69 * derefencing the name member*/ 75 * derefencing the name member*/
@@ -71,6 +77,7 @@ struct tagentry {
71 char* name; 77 char* name;
72 int newtable; 78 int newtable;
73 int extraseek; 79 int extraseek;
80 int customaction;
74}; 81};
75 82
76static struct tagentry* tagtree_get_entry(struct tree_context *c, int id); 83static struct tagentry* tagtree_get_entry(struct tree_context *c, int id);
@@ -78,10 +85,10 @@ static struct tagentry* tagtree_get_entry(struct tree_context *c, int id);
78#define SEARCHSTR_SIZE 256 85#define SEARCHSTR_SIZE 256
79 86
80enum table { 87enum table {
81 ROOT = 1, 88 TABLE_ROOT = 1,
82 NAVIBROWSE, 89 TABLE_NAVIBROWSE,
83 ALLSUBENTRIES, 90 TABLE_ALLSUBENTRIES,
84 PLAYTRACK, 91 TABLE_PLAYTRACK,
85}; 92};
86 93
87static const struct id3_to_search_mapping { 94static const struct id3_to_search_mapping {
@@ -108,6 +115,7 @@ enum variables {
108 menu_next, 115 menu_next,
109 menu_load, 116 menu_load,
110 menu_reload, 117 menu_reload,
118 menu_shuffle_songs,
111}; 119};
112 120
113/* Capacity 10 000 entries (for example 10k different artists) */ 121/* Capacity 10 000 entries (for example 10k different artists) */
@@ -276,6 +284,20 @@ static struct buflib_callbacks ops = {
276 .shrink_callback = NULL, 284 .shrink_callback = NULL,
277}; 285};
278 286
287static uint32_t tagtree_data_crc(struct tree_context* c)
288{
289 char* buf;
290 uint32_t crc;
291 buf = core_get_data(tagtree_handle); /* data for the search clauses etc */
292 crc = crc_32(buf, tagtree_buf_used, c->dirlength);
293 buf = core_get_data(c->cache.name_buffer_handle); /* names */
294 crc = crc_32(buf, c->cache.name_buffer_size, crc);
295 buf = core_get_data(c->cache.entries_handle); /* tagentries */
296 crc = crc_32(buf, c->cache.max_entries * sizeof(struct tagentry), crc);
297 logf("%s 0x%x", __func__, crc);
298 return crc;
299}
300
279static void* tagtree_alloc(size_t size) 301static void* tagtree_alloc(size_t size)
280{ 302{
281 size = ALIGN_UP(size, sizeof(void*)); 303 size = ALIGN_UP(size, sizeof(void*));
@@ -338,6 +360,7 @@ static int get_tag(int *tag)
338 TAG_MATCH("Pm", tag_virt_playtime_min), 360 TAG_MATCH("Pm", tag_virt_playtime_min),
339 TAG_MATCH("Ps", tag_virt_playtime_sec), 361 TAG_MATCH("Ps", tag_virt_playtime_sec),
340 TAG_MATCH("->", menu_next), 362 TAG_MATCH("->", menu_next),
363 TAG_MATCH("~>", menu_shuffle_songs),
341 364
342 TAG_MATCH("==>", menu_load), 365 TAG_MATCH("==>", menu_load),
343 366
@@ -820,7 +843,7 @@ static bool parse_search(struct menu_entry *entry, const char *str)
820 return true; 843 return true;
821 } 844 }
822 845
823 if (entry->type != menu_next) 846 if (entry->type != menu_next && entry->type != menu_shuffle_songs)
824 return false; 847 return false;
825 848
826 while (inst->tagorder_count < MAX_TAGS) 849 while (inst->tagorder_count < MAX_TAGS)
@@ -847,7 +870,7 @@ static bool parse_search(struct menu_entry *entry, const char *str)
847 870
848 inst->tagorder_count++; 871 inst->tagorder_count++;
849 872
850 if (get_tag(&type) <= 0 || type != menu_next) 873 if (get_tag(&type) <= 0 || (type != menu_next && type != menu_shuffle_songs))
851 break; 874 break;
852 } 875 }
853 876
@@ -1245,6 +1268,7 @@ static void tagtree_unload(struct tree_context *c)
1245 dptr->name = NULL; 1268 dptr->name = NULL;
1246 dptr->newtable = 0; 1269 dptr->newtable = 0;
1247 dptr->extraseek = 0; 1270 dptr->extraseek = 0;
1271 dptr->customaction = ONPLAY_NO_CUSTOMACTION;
1248 dptr++; 1272 dptr++;
1249 } 1273 }
1250 } 1274 }
@@ -1454,7 +1478,7 @@ static int retrieve_entries(struct tree_context *c, int offset, bool init)
1454#endif 1478#endif
1455 , 0, 0, 0); 1479 , 0, 0, 0);
1456 1480
1457 if (c->currtable == ALLSUBENTRIES) 1481 if (c->currtable == TABLE_ALLSUBENTRIES)
1458 { 1482 {
1459 tag = tag_title; 1483 tag = tag_title;
1460 level--; 1484 level--;
@@ -1544,17 +1568,19 @@ static int retrieve_entries(struct tree_context *c, int offset, bool init)
1544 { 1568 {
1545 if (offset == 0) 1569 if (offset == 0)
1546 { 1570 {
1547 dptr->newtable = ALLSUBENTRIES; 1571 dptr->newtable = TABLE_ALLSUBENTRIES;
1548 dptr->name = str(LANG_TAGNAVI_ALL_TRACKS); 1572 dptr->name = str(LANG_TAGNAVI_ALL_TRACKS);
1573 dptr->customaction = ONPLAY_NO_CUSTOMACTION;
1549 dptr++; 1574 dptr++;
1550 current_entry_count++; 1575 current_entry_count++;
1551 special_entry_count++; 1576 special_entry_count++;
1552 } 1577 }
1553 if (offset <= 1) 1578 if (offset <= 1)
1554 { 1579 {
1555 dptr->newtable = NAVIBROWSE; 1580 dptr->newtable = TABLE_NAVIBROWSE;
1556 dptr->name = str(LANG_TAGNAVI_RANDOM); 1581 dptr->name = str(LANG_TAGNAVI_RANDOM);
1557 dptr->extraseek = -1; 1582 dptr->extraseek = -1;
1583 dptr->customaction = ONPLAY_NO_CUSTOMACTION;
1558 dptr++; 1584 dptr++;
1559 current_entry_count++; 1585 current_entry_count++;
1560 special_entry_count++; 1586 special_entry_count++;
@@ -1568,14 +1594,15 @@ static int retrieve_entries(struct tree_context *c, int offset, bool init)
1568 if (total_count++ < offset) 1594 if (total_count++ < offset)
1569 continue; 1595 continue;
1570 1596
1571 dptr->newtable = NAVIBROWSE; 1597 dptr->newtable = TABLE_NAVIBROWSE;
1572 if (tag == tag_title || tag == tag_filename) 1598 if (tag == tag_title || tag == tag_filename)
1573 { 1599 {
1574 dptr->newtable = PLAYTRACK; 1600 dptr->newtable = TABLE_PLAYTRACK;
1575 dptr->extraseek = tcs.idx_id; 1601 dptr->extraseek = tcs.idx_id;
1576 } 1602 }
1577 else 1603 else
1578 dptr->extraseek = tcs.result_seek; 1604 dptr->extraseek = tcs.result_seek;
1605 dptr->customaction = ONPLAY_NO_CUSTOMACTION;
1579 1606
1580 fmt = NULL; 1607 fmt = NULL;
1581 /* Check the format */ 1608 /* Check the format */
@@ -1758,7 +1785,7 @@ static int load_root(struct tree_context *c)
1758 int i; 1785 int i;
1759 1786
1760 tc = c; 1787 tc = c;
1761 c->currtable = ROOT; 1788 c->currtable = TABLE_ROOT;
1762 if (c->dirlevel == 0) 1789 if (c->dirlevel == 0)
1763 c->currextra = rootmenu; 1790 c->currextra = rootmenu;
1764 1791
@@ -1775,13 +1802,21 @@ static int load_root(struct tree_context *c)
1775 switch (menu->items[i]->type) 1802 switch (menu->items[i]->type)
1776 { 1803 {
1777 case menu_next: 1804 case menu_next:
1778 dptr->newtable = NAVIBROWSE; 1805 dptr->newtable = TABLE_NAVIBROWSE;
1779 dptr->extraseek = i; 1806 dptr->extraseek = i;
1807 dptr->customaction = ONPLAY_NO_CUSTOMACTION;
1780 break; 1808 break;
1781 1809
1782 case menu_load: 1810 case menu_load:
1783 dptr->newtable = ROOT; 1811 dptr->newtable = TABLE_ROOT;
1784 dptr->extraseek = menu->items[i]->link; 1812 dptr->extraseek = menu->items[i]->link;
1813 dptr->customaction = ONPLAY_NO_CUSTOMACTION;
1814 break;
1815
1816 case menu_shuffle_songs:
1817 dptr->newtable = TABLE_NAVIBROWSE;
1818 dptr->extraseek = i;
1819 dptr->customaction = ONPLAY_CUSTOMACTION_SHUFFLE_SONGS;
1785 break; 1820 break;
1786 } 1821 }
1787 1822
@@ -1796,6 +1831,8 @@ static int load_root(struct tree_context *c)
1796 1831
1797int tagtree_load(struct tree_context* c) 1832int tagtree_load(struct tree_context* c)
1798{ 1833{
1834 logf( "%s", __func__);
1835
1799 int count; 1836 int count;
1800 int table = c->currtable; 1837 int table = c->currtable;
1801 1838
@@ -1804,20 +1841,32 @@ int tagtree_load(struct tree_context* c)
1804 if (!table) 1841 if (!table)
1805 { 1842 {
1806 c->dirfull = false; 1843 c->dirfull = false;
1807 table = ROOT; 1844 table = TABLE_ROOT;
1808 c->currtable = table; 1845 c->currtable = table;
1809 c->currextra = rootmenu; 1846 c->currextra = rootmenu;
1810 } 1847 }
1811 1848
1812 switch (table) 1849 switch (table)
1813 { 1850 {
1814 case ROOT: 1851 case TABLE_ROOT:
1852 logf( "root...");
1815 count = load_root(c); 1853 count = load_root(c);
1816 break; 1854 break;
1817 1855
1818 case ALLSUBENTRIES: 1856 case TABLE_ALLSUBENTRIES:
1819 case NAVIBROWSE: 1857 case TABLE_NAVIBROWSE:
1820 logf("navibrowse..."); 1858 logf("navibrowse...");
1859
1860 if (loaded_entries_crc != 0)
1861 {
1862 if (loaded_entries_crc == tagtree_data_crc(c))
1863 {
1864 count = c->dirlength;
1865 logf("Reusing %d entries", count);
1866 break;
1867 }
1868 }
1869
1821 cpu_boost(true); 1870 cpu_boost(true);
1822 count = retrieve_entries(c, 0, true); 1871 count = retrieve_entries(c, 0, true);
1823 cpu_boost(false); 1872 cpu_boost(false);
@@ -1828,6 +1877,8 @@ int tagtree_load(struct tree_context* c)
1828 return -1; 1877 return -1;
1829 } 1878 }
1830 1879
1880 loaded_entries_crc = 0;
1881
1831 if (count < 0) 1882 if (count < 0)
1832 { 1883 {
1833 if (count != RELOAD_TAGTREE) 1884 if (count != RELOAD_TAGTREE)
@@ -1860,6 +1911,8 @@ int tagtree_load(struct tree_context* c)
1860 */ 1911 */
1861int tagtree_enter(struct tree_context* c, bool is_visible) 1912int tagtree_enter(struct tree_context* c, bool is_visible)
1862{ 1913{
1914 logf( "%s", __func__);
1915
1863 int rc = 0; 1916 int rc = 0;
1864 struct tagentry *dptr; 1917 struct tagentry *dptr;
1865 struct mp3entry *id3; 1918 struct mp3entry *id3;
@@ -1921,16 +1974,16 @@ int tagtree_enter(struct tree_context* c, bool is_visible)
1921 core_pin(tagtree_handle); 1974 core_pin(tagtree_handle);
1922 1975
1923 switch (c->currtable) { 1976 switch (c->currtable) {
1924 case ROOT: 1977 case TABLE_ROOT:
1925 c->currextra = newextra; 1978 c->currextra = newextra;
1926 1979
1927 if (newextra == ROOT) 1980 if (newextra == TABLE_ROOT)
1928 { 1981 {
1929 menu = menus[seek]; 1982 menu = menus[seek];
1930 c->currextra = seek; 1983 c->currextra = seek;
1931 } 1984 }
1932 1985
1933 else if (newextra == NAVIBROWSE) 1986 else if (newextra == TABLE_NAVIBROWSE)
1934 { 1987 {
1935 int i, j; 1988 int i, j;
1936 1989
@@ -2005,9 +2058,9 @@ int tagtree_enter(struct tree_context* c, bool is_visible)
2005 2058
2006 break; 2059 break;
2007 2060
2008 case NAVIBROWSE: 2061 case TABLE_NAVIBROWSE:
2009 case ALLSUBENTRIES: 2062 case TABLE_ALLSUBENTRIES:
2010 if (newextra == PLAYTRACK) 2063 if (newextra == TABLE_PLAYTRACK)
2011 { 2064 {
2012 adjust_selection = false; 2065 adjust_selection = false;
2013 2066
@@ -2059,6 +2112,7 @@ int tagtree_enter(struct tree_context* c, bool is_visible)
2059/* Exits current database menu or table */ 2112/* Exits current database menu or table */
2060void tagtree_exit(struct tree_context* c, bool is_visible) 2113void tagtree_exit(struct tree_context* c, bool is_visible)
2061{ 2114{
2115 logf( "%s", __func__);
2062 if (is_visible) /* update selection history only for user-selected items */ 2116 if (is_visible) /* update selection history only for user-selected items */
2063 { 2117 {
2064 if (c->selected_item != selected_item_history[c->dirlevel]) 2118 if (c->selected_item != selected_item_history[c->dirlevel])
@@ -2102,15 +2156,54 @@ int tagtree_get_filename(struct tree_context* c, char *buf, int buflen)
2102 return 0; 2156 return 0;
2103} 2157}
2104 2158
2159int tagtree_get_custom_action(struct tree_context* c)
2160{
2161 return tagtree_get_entry(c, c->selected_item)->customaction;
2162}
2163
2164static void swap_array_bool(bool *a, bool *b)
2165{
2166 bool temp = *a;
2167 *a = *b;
2168 *b = temp;
2169}
2170
2171/**
2172 * Randomly shuffle an array using the Fisher-Yates algorithm :
2173 * https://en.wikipedia.org/wiki/Random_permutation
2174 * This algorithm has a linear complexity.
2175 * Don't forget to srand before call to use it with a relevant seed.
2176 */
2177static bool* fill_random_playlist_indexes(bool *bool_array, size_t arr_sz,
2178 size_t track_count, size_t max_slots)
2179{
2180 size_t i;
2181 if (track_count * sizeof(bool) > arr_sz || max_slots > track_count)
2182 return NULL;
2183
2184 for (i = 0; i < arr_sz; i++) /* fill max_slots with TRUE */
2185 bool_array[i] = i < max_slots;
2186
2187 /* shuffle bool array */
2188 for (i = track_count - 1; i > 0; i--)
2189 {
2190 int j = rand() % (i + 1);
2191 swap_array_bool(&bool_array[i], &bool_array[j]);
2192 }
2193 return bool_array;
2194}
2105 2195
2106static bool insert_all_playlist(struct tree_context *c, 2196static bool insert_all_playlist(struct tree_context *c,
2107 const char* playlist, bool new_playlist, 2197 const char* playlist, bool new_playlist,
2108 int position, bool queue) 2198 int position, bool queue)
2109{ 2199{
2110 struct tagcache_search tcs; 2200 struct tagcache_search tcs;
2111 int i, n; 2201 int n;
2112 int fd = -1; 2202 int fd = -1;
2113 unsigned long last_tick; 2203 unsigned long last_tick;
2204 int slots_remaining = 0;
2205 bool fill_randomly = false;
2206 bool *rand_bool_array = NULL;
2114 char buf[MAX_PATH]; 2207 char buf[MAX_PATH];
2115 2208
2116 cpu_boost(true); 2209 cpu_boost(true);
@@ -2148,40 +2241,114 @@ static bool insert_all_playlist(struct tree_context *c,
2148 last_tick = current_tick + HZ/2; /* Show splash after 0.5 seconds have passed */ 2241 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 */ 2242 splash_progress_set_delay(HZ / 2); /* wait 1/2 sec before progress */
2150 n = c->filesindir; 2243 n = c->filesindir;
2151 for (i = 0; i < n; i++) 2244
2245 if (playlist == NULL)
2152 { 2246 {
2247 int max_playlist_size = playlist_get_current()->max_playlist_size;
2248 slots_remaining = max_playlist_size - playlist_get_current()->amount;
2249 if (slots_remaining <= 0)
2250 {
2251 logf("Playlist has no space remaining");
2252 cpu_boost(false);
2253 return false;
2254 }
2153 2255
2154 splash_progress(i, n, "%s (%s)", str(LANG_WAIT), str(LANG_OFF_ABORT)); 2256 fill_randomly = n > slots_remaining;
2257
2258 if (fill_randomly)
2259 {
2260 srand(current_tick);
2261 size_t bufsize = 0;
2262 bool *buffer = (bool *) plugin_get_buffer(&bufsize);
2263 rand_bool_array = fill_random_playlist_indexes(buffer, bufsize,
2264 n, slots_remaining);
2265
2266 talk_id(LANG_RANDOM_SHUFFLE_RANDOM_SELECTIVE_SONGS_SUMMARY, true);
2267 splashf(HZ * 2, str(LANG_RANDOM_SHUFFLE_RANDOM_SELECTIVE_SONGS_SUMMARY),
2268 slots_remaining);
2269 }
2270 }
2271
2272 bool exit_loop_now = false;
2273 for (int i = 0; i < n; i++)
2274 {
2155 if (TIME_AFTER(current_tick, last_tick + HZ/4)) 2275 if (TIME_AFTER(current_tick, last_tick + HZ/4))
2156 { 2276 {
2277 splash_progress(i, n, "%s (%s)", str(LANG_WAIT), str(LANG_OFF_ABORT));
2157 if (action_userabort(TIMEOUT_NOBLOCK)) 2278 if (action_userabort(TIMEOUT_NOBLOCK))
2279 {
2280 exit_loop_now = true;
2158 break; 2281 break;
2282 }
2159 last_tick = current_tick; 2283 last_tick = current_tick;
2160 } 2284 }
2161 2285
2162 if (!tagcache_retrieve(&tcs, tagtree_get_entry(c, i)->extraseek, 2286 if (playlist == NULL)
2163 tcs.type, buf, sizeof buf))
2164 { 2287 {
2165 continue; 2288 if (fill_randomly)
2289 {
2290 int remaining_tracks = n - i;
2291 if (remaining_tracks > slots_remaining)
2292 {
2293 if (rand_bool_array)
2294 {
2295 /* Skip the track if rand_bool_array[i] is FALSE */
2296 if (!rand_bool_array[i])
2297 continue;
2298 }
2299 else
2300 {
2301 /* Generate random value between 0 and remaining_tracks - 1 */
2302 int selrange = RAND_MAX / remaining_tracks; /* Improve distribution */
2303 int random;
2304
2305 for (int r = 0; r < 0x0FFF; r++) /* limit loops */
2306 {
2307 random = rand() / selrange;
2308 if (random < remaining_tracks)
2309 break;
2310 else
2311 random = 0;
2312 }
2313 /* Skip the track if random >= slots_remaining */
2314 if (random >= slots_remaining)
2315 continue;
2316 }
2317 }
2318 }
2166 } 2319 }
2167 2320
2321 if (!tagcache_retrieve(&tcs, tagtree_get_entry(c, i)->extraseek, tcs.type, buf, sizeof buf))
2322 continue;
2323
2168 if (playlist == NULL) 2324 if (playlist == NULL)
2169 { 2325 {
2170 if (playlist_insert_track(NULL, buf, position, queue, false) < 0) 2326 if (fill_randomly)
2171 { 2327 {
2328 if (--slots_remaining <= 0)
2329 {
2330 exit_loop_now = true;
2331 break;
2332 }
2333 }
2334
2335 if (playlist_insert_track(NULL, buf, position, queue, false) < 0) {
2172 logf("playlist_insert_track failed"); 2336 logf("playlist_insert_track failed");
2337 exit_loop_now = true;
2173 break; 2338 break;
2174 } 2339 }
2175 } 2340 }
2176 else if (fdprintf(fd, "%s\n", buf) <= 0) 2341 else if (fdprintf(fd, "%s\n", buf) <= 0)
2177 break; 2342 {
2178 2343 exit_loop_now = true;
2344 break;
2345 }
2179 yield(); 2346 yield();
2180
2181 if (playlist == NULL && position == PLAYLIST_INSERT_FIRST) 2347 if (playlist == NULL && position == PLAYLIST_INSERT_FIRST)
2182 {
2183 position = PLAYLIST_INSERT; 2348 position = PLAYLIST_INSERT;
2184 } 2349
2350 if (exit_loop_now)
2351 break;
2185 } 2352 }
2186 if (playlist == NULL) 2353 if (playlist == NULL)
2187 playlist_sync(NULL); 2354 playlist_sync(NULL);
@@ -2196,14 +2363,14 @@ static bool insert_all_playlist(struct tree_context *c,
2196static bool goto_allsubentries(int newtable) 2363static bool goto_allsubentries(int newtable)
2197{ 2364{
2198 int i = 0; 2365 int i = 0;
2199 while (i < 2 && (newtable == NAVIBROWSE || newtable == ALLSUBENTRIES)) 2366 while (i < 2 && (newtable == TABLE_NAVIBROWSE || newtable == TABLE_ALLSUBENTRIES))
2200 { 2367 {
2201 tagtree_enter(tc, false); 2368 tagtree_enter(tc, false);
2202 tagtree_load(tc); 2369 tagtree_load(tc);
2203 newtable = tagtree_get_entry(tc, tc->selected_item)->newtable; 2370 newtable = tagtree_get_entry(tc, tc->selected_item)->newtable;
2204 i++; 2371 i++;
2205 } 2372 }
2206 return (newtable == PLAYTRACK); 2373 return (newtable == TABLE_PLAYTRACK);
2207} 2374}
2208 2375
2209static void reset_tc_to_prev(int dirlevel, int selected_item) 2376static void reset_tc_to_prev(int dirlevel, int selected_item)
@@ -2233,7 +2400,7 @@ static bool tagtree_insert_selection(int position, bool queue,
2233 2400
2234 newtable = tagtree_get_entry(tc, tc->selected_item)->newtable; 2401 newtable = tagtree_get_entry(tc, tc->selected_item)->newtable;
2235 2402
2236 if (newtable == PLAYTRACK) /* Insert a single track? */ 2403 if (newtable == TABLE_PLAYTRACK) /* Insert a single track? */
2237 { 2404 {
2238 if (tagtree_get_filename(tc, buf, sizeof buf) < 0) 2405 if (tagtree_get_filename(tc, buf, sizeof buf) < 0)
2239 return false; 2406 return false;
@@ -2342,6 +2509,7 @@ int tagtree_add_to_playlist(const char* playlist, bool new_playlist)
2342 2509
2343static int tagtree_play_folder(struct tree_context* c) 2510static int tagtree_play_folder(struct tree_context* c)
2344{ 2511{
2512 logf( "%s", __func__);
2345 int start_index = c->selected_item; 2513 int start_index = c->selected_item;
2346 2514
2347 if (playlist_create(NULL, NULL) < 0) 2515 if (playlist_create(NULL, NULL) < 0)
@@ -2353,14 +2521,26 @@ static int tagtree_play_folder(struct tree_context* c)
2353 if (!insert_all_playlist(c, NULL, false, PLAYLIST_INSERT_LAST, false)) 2521 if (!insert_all_playlist(c, NULL, false, PLAYLIST_INSERT_LAST, false))
2354 return -2; 2522 return -2;
2355 2523
2524 int n = c->filesindir;
2525 bool has_playlist_been_randomized = n > playlist_get_current()->max_playlist_size;
2526 if (has_playlist_been_randomized)
2527 {
2528 /* We need to recalculate the start index based on a percentage to put the user
2529 around its desired start position and avoid out of bounds */
2530
2531 int percentage_start_index = 100 * start_index / n;
2532 start_index = percentage_start_index * playlist_get_current()->amount / 100;
2533 }
2534
2356 if (global_settings.playlist_shuffle) 2535 if (global_settings.playlist_shuffle)
2357 { 2536 {
2358 start_index = playlist_shuffle(current_tick, c->selected_item); 2537 start_index = playlist_shuffle(current_tick, start_index);
2359 if (!global_settings.play_selected) 2538 if (!global_settings.play_selected)
2360 start_index = 0; 2539 start_index = 0;
2361 } 2540 }
2362 2541
2363 playlist_start(start_index, 0, 0); 2542 playlist_start(start_index, 0, 0);
2543 loaded_entries_crc = tagtree_data_crc(c); /* save crc in case we return */
2364 return 0; 2544 return 0;
2365} 2545}
2366 2546
@@ -2403,11 +2583,11 @@ char *tagtree_get_title(struct tree_context* c)
2403{ 2583{
2404 switch (c->currtable) 2584 switch (c->currtable)
2405 { 2585 {
2406 case ROOT: 2586 case TABLE_ROOT:
2407 return menu->title; 2587 return menu->title;
2408 2588
2409 case NAVIBROWSE: 2589 case TABLE_NAVIBROWSE:
2410 case ALLSUBENTRIES: 2590 case TABLE_ALLSUBENTRIES:
2411 return current_title[c->currextra]; 2591 return current_title[c->currextra];
2412 } 2592 }
2413 2593
@@ -2419,7 +2599,7 @@ int tagtree_get_attr(struct tree_context* c)
2419 int attr = -1; 2599 int attr = -1;
2420 switch (c->currtable) 2600 switch (c->currtable)
2421 { 2601 {
2422 case NAVIBROWSE: 2602 case TABLE_NAVIBROWSE:
2423 if (csi->tagorder[c->currextra] == tag_title 2603 if (csi->tagorder[c->currextra] == tag_title
2424 || csi->tagorder[c->currextra] == tag_virt_basename) 2604 || csi->tagorder[c->currextra] == tag_virt_basename)
2425 attr = FILE_ATTR_AUDIO; 2605 attr = FILE_ATTR_AUDIO;
@@ -2427,7 +2607,7 @@ int tagtree_get_attr(struct tree_context* c)
2427 attr = ATTR_DIRECTORY; 2607 attr = ATTR_DIRECTORY;
2428 break; 2608 break;
2429 2609
2430 case ALLSUBENTRIES: 2610 case TABLE_ALLSUBENTRIES:
2431 attr = FILE_ATTR_AUDIO; 2611 attr = FILE_ATTR_AUDIO;
2432 break; 2612 break;
2433 2613
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..721fb8c1ef 100644
--- a/apps/tree.c
+++ b/apps/tree.c
@@ -735,6 +735,20 @@ 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 {
743 customaction = tagtree_get_custom_action(&tc);
744 if (customaction == ONPLAY_CUSTOMACTION_SHUFFLE_SONGS)
745 {
746 /* The code to insert shuffled is on the context branch of the switch so we always go here */
747 button = ACTION_STD_CONTEXT;
748 do_restore_display = false;
749 }
750 }
751 #endif
738 switch ( button ) { 752 switch ( button ) {
739 case ACTION_STD_OK: 753 case ACTION_STD_OK:
740 /* nothing to do if no files to display */ 754 /* nothing to do if no files to display */
@@ -773,7 +787,7 @@ static int dirbrowse(void)
773 default: 787 default:
774 break; 788 break;
775 } 789 }
776 restore = true; 790 restore = do_restore_display;
777 break; 791 break;
778 792
779 case ACTION_STD_CANCEL: 793 case ACTION_STD_CANCEL:
@@ -798,12 +812,12 @@ static int dirbrowse(void)
798 if (ft_exit(&tc) == 3) 812 if (ft_exit(&tc) == 3)
799 exit_func = true; 813 exit_func = true;
800 814
801 restore = true; 815 restore = do_restore_display;
802 break; 816 break;
803 817
804 case ACTION_TREE_STOP: 818 case ACTION_TREE_STOP:
805 if (list_stop_handler()) 819 if (list_stop_handler())
806 restore = true; 820 restore = do_restore_display;
807 break; 821 break;
808 822
809 case ACTION_STD_MENU: 823 case ACTION_STD_MENU:
@@ -851,7 +865,7 @@ static int dirbrowse(void)
851 skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_ALL); 865 skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_ALL);
852 } 866 }
853 867
854 restore = true; 868 restore = do_restore_display;
855 break; 869 break;
856 } 870 }
857#endif 871#endif
@@ -872,7 +886,7 @@ static int dirbrowse(void)
872 break; 886 break;
873 887
874 if(!numentries) 888 if(!numentries)
875 onplay_result = onplay(NULL, 0, curr_context, hotkey); 889 onplay_result = onplay(NULL, 0, curr_context, hotkey, customaction);
876 else { 890 else {
877#ifdef HAVE_TAGCACHE 891#ifdef HAVE_TAGCACHE
878 if (id3db) 892 if (id3db)
@@ -902,7 +916,7 @@ static int dirbrowse(void)
902 ft_assemble_path(buf, sizeof(buf), currdir, entry->name); 916 ft_assemble_path(buf, sizeof(buf), currdir, entry->name);
903 917
904 } 918 }
905 onplay_result = onplay(buf, attr, curr_context, hotkey); 919 onplay_result = onplay(buf, attr, curr_context, hotkey, customaction);
906 } 920 }
907 switch (onplay_result) 921 switch (onplay_result)
908 { 922 {
@@ -911,7 +925,7 @@ static int dirbrowse(void)
911 break; 925 break;
912 926
913 case ONPLAY_OK: 927 case ONPLAY_OK:
914 restore = true; 928 restore = do_restore_display;
915 break; 929 break;
916 930
917 case ONPLAY_RELOAD_DIR: 931 case ONPLAY_RELOAD_DIR:
@@ -988,7 +1002,7 @@ static int dirbrowse(void)
988 1002
989 lastfilter = *tc.dirfilter; 1003 lastfilter = *tc.dirfilter;
990 lastsortcase = global_settings.sort_case; 1004 lastsortcase = global_settings.sort_case;
991 restore = true; 1005 restore = do_restore_display;
992 } 1006 }
993 1007
994 if (exit_func) 1008 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/bootloader/x1000/x1000bootloader.h b/bootloader/x1000/x1000bootloader.h
index 7118017a23..0b5b02969d 100644
--- a/bootloader/x1000/x1000bootloader.h
+++ b/bootloader/x1000/x1000bootloader.h
@@ -107,10 +107,9 @@ struct uimage_header;
107 " init=/linuxrc ubi.mtd=4 root=ubi0:rootfs ubi.mtd=5 rootfstype=ubifs \ 107 " init=/linuxrc ubi.mtd=4 root=ubi0:rootfs ubi.mtd=5 rootfstype=ubifs \
108sn_no=00000000000000000000000000000000 bt_mac=xxxxxxxxxxxx wifi_mac=xxxxxxxxxxxx rw" 108sn_no=00000000000000000000000000000000 bt_mac=xxxxxxxxxxxx wifi_mac=xxxxxxxxxxxx rw"
109# define OF_PLAYER_BTN BUTTON_PLAY 109# define OF_PLAYER_BTN BUTTON_PLAY
110/* Note: OF Recovery boots, but is otherwise untested. */ 110# define OF_RECOVERY_NAME "Aigo Recovery"
111//# define OF_RECOVERY_NAME "Aigo Recovery" 111# define OF_RECOVERY_ADDR 0x900000
112//# define OF_RECOVERY_ADDR 0x900000 112# define OF_RECOVERY_LENGTH (7 * 1024 * 1024)
113//# define OF_RECOVERY_LENGTH (7 * 1024 * 1024)
114# define OF_RECOVERY_ARGS \ 113# define OF_RECOVERY_ARGS \
115 "console=ttyS2,115200n8 mem=32M@0x0 no_console_suspend lpj=5009408 ip=off" 114 "console=ttyS2,115200n8 mem=32M@0x0 no_console_suspend lpj=5009408 ip=off"
116#else 115#else
diff --git a/docs/CREDITS b/docs/CREDITS
index 577da626f3..a4b23cd412 100644
--- a/docs/CREDITS
+++ b/docs/CREDITS
@@ -726,6 +726,8 @@ Evan Kenny
726Medu Hedan 726Medu Hedan
727Uwe Schächterle 727Uwe Schächterle
728Hoseok Seo 728Hoseok Seo
729Paul Sauro
730Dmitry Prozorov
729 731
730The libmad team 732The libmad team
731The wavpack team 733The wavpack team
diff --git a/firmware/SOURCES b/firmware/SOURCES
index 4e6fcbf70c..192cdb711d 100644
--- a/firmware/SOURCES
+++ b/firmware/SOURCES
@@ -62,6 +62,10 @@ common/bootdata.c
62#endif 62#endif
63#endif 63#endif
64 64
65#if defined(HAVE_DEVICEDATA)
66common/devicedata.c
67#endif
68
65#ifdef HAVE_SDL 69#ifdef HAVE_SDL
66target/hosted/sdl/button-sdl.c 70target/hosted/sdl/button-sdl.c
67target/hosted/sdl/kernel-sdl.c 71target/hosted/sdl/kernel-sdl.c
diff --git a/firmware/common/devicedata.c b/firmware/common/devicedata.c
new file mode 100644
index 0000000000..75fe79d7fa
--- /dev/null
+++ b/firmware/common/devicedata.c
@@ -0,0 +1,88 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 *
9 * Copyright (C) 2024 by William Wilgus
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
15 *
16 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
17 * KIND, either express or implied.
18 *
19 ****************************************************************************/
20
21#include "devicedata.h"
22#include "crc32.h"
23#include <stddef.h>
24#include <string.h>
25#include "debug.h"
26
27#ifndef BOOTLOADER
28void verify_device_data(void) INIT_ATTR;
29void verify_device_data(void)
30{
31 DEBUGF("%s", __func__);
32 /* verify payload with checksum */
33 uint32_t crc = crc_32(device_data.payload, device_data.length, 0xffffffff);
34 if (crc == device_data.crc)
35 return; /* return if data is valid */
36
37 /* Write the default if data is invalid */
38 memset(device_data.payload, 0xff, DEVICE_DATA_PAYLOAD_SIZE); /* Invalid data */
39 device_data.length = DEVICE_DATA_PAYLOAD_SIZE;
40 device_data.crc = crc_32(device_data.payload, device_data.length, 0xffffffff);
41
42}
43
44/******************************************************************************/
45#endif /* ndef BOOTLOADER ******************************************************/
46/******************************************************************************/
47
48#if defined(HAVE_DEVICEDATA)
49void __attribute__((weak)) fill_devicedata(struct device_data_t *data)
50{
51 memset(data->payload, 0xff, data->length);
52}
53#endif
54
55/* Write bootdata into location in FIRMWARE marked by magic header
56 * Assumes buffer is already loaded with the firmware image
57 * We just need to find the location and write data into the
58 * payload region along with the crc for later verification and use.
59 * Returns payload len on success,
60 * On error returns false
61 */
62bool write_devicedata(unsigned char* buf, int len)
63{
64 int search_len = MIN(len, DEVICE_DATA_SEARCH_SIZE) - sizeof(struct device_data_t);
65
66 /* search for decvice data header prior to search_len */
67 for(int i = 0; i < search_len; i++)
68 {
69 struct device_data_t *data = (struct device_data_t *)&buf[i];
70 if (data->magic[0] != DEVICE_DATA_MAGIC0 ||
71 data->magic[1] != DEVICE_DATA_MAGIC1)
72 continue;
73
74 /* Ignore it if the length extends past the end of the buffer. */
75 int data_len = offsetof(struct device_data_t, payload) + data->length;
76 if (i + data_len > len)
77 continue;
78
79 fill_devicedata(data);
80
81 /* Calculate payload CRC */
82 data->crc = crc_32(data->payload, data->length, 0xffffffff);
83 return true;
84 }
85
86 return false;
87}
88
diff --git a/firmware/common/rb-loader.c b/firmware/common/rb-loader.c
index 61d8b1ddd2..2f5e06e165 100644
--- a/firmware/common/rb-loader.c
+++ b/firmware/common/rb-loader.c
@@ -30,6 +30,9 @@
30#include "multiboot.h" 30#include "multiboot.h"
31#endif 31#endif
32 32
33#ifdef HAVE_DEVICEDATA
34#include "devicedata.h"
35#endif
33/* loads a firmware file from supplied filename 36/* loads a firmware file from supplied filename
34 * file opened, checks firmware size and checksum 37 * file opened, checks firmware size and checksum
35 * if no error, firmware loaded to supplied buffer 38 * if no error, firmware loaded to supplied buffer
@@ -118,7 +121,6 @@ int load_firmware(unsigned char* buf, const char* firmware, int buffer_size)
118 /* if ret is valid breaks from loop to continue loading */ 121 /* if ret is valid breaks from loop to continue loading */
119 } 122 }
120#endif 123#endif
121
122 if (ret < 0) /* Check default volume, no valid firmware file loaded yet */ 124 if (ret < 0) /* Check default volume, no valid firmware file loaded yet */
123 { 125 {
124 /* First check in BOOTDIR */ 126 /* First check in BOOTDIR */
@@ -141,5 +143,9 @@ int load_firmware(unsigned char* buf, const char* firmware, int buffer_size)
141 else /* full path passed ROLO etc.*/ 143 else /* full path passed ROLO etc.*/
142 ret = load_firmware_filename(buf, firmware, buffer_size); 144 ret = load_firmware_filename(buf, firmware, buffer_size);
143 145
146#ifdef HAVE_DEVICEDATA
147 write_devicedata(buf, ret);
148#endif
149
144 return ret; 150 return ret;
145} 151}
diff --git a/firmware/drivers/lcd-scroll.c b/firmware/drivers/lcd-scroll.c
index 895cf98cba..2a58d6ff21 100644
--- a/firmware/drivers/lcd-scroll.c
+++ b/firmware/drivers/lcd-scroll.c
@@ -195,8 +195,14 @@ static void LCDFN(scroll_worker)(void)
195 s = &si->scroll[index]; 195 s = &si->scroll[index];
196 196
197 /* check pause */ 197 /* check pause */
198 if (TIME_BEFORE(current_tick, s->start_tick)) 198 if (TIME_BEFORE(current_tick, s->start_tick)) {
199 continue; 199 continue;
200 }
201
202 if (global_settings.disable_mainmenu_scrolling && get_current_activity() == ACTIVITY_MAINMENU) {
203 // No scrolling on the main menu if disabled (to not break themes with lockscreens)
204 continue;
205 }
200 206
201 s->start_tick = current_tick; 207 s->start_tick = current_tick;
202 208
diff --git a/firmware/export/config.h b/firmware/export/config.h
index 37ea9a1dd7..ede1825f88 100644
--- a/firmware/export/config.h
+++ b/firmware/export/config.h
@@ -893,7 +893,8 @@ Lyre prototype 1 */
893#endif 893#endif
894 894
895/* Bootloaders don't need multivolume awareness */ 895/* Bootloaders don't need multivolume awareness */
896#if defined(BOOTLOADER) && defined(HAVE_MULTIVOLUME) && !(CONFIG_PLATFORM & PLATFORM_HOSTED) 896#if defined(BOOTLOADER) && defined(HAVE_MULTIVOLUME) \
897 && !(CONFIG_PLATFORM & PLATFORM_HOSTED) && !defined(BOOT_REDIR)
897#undef HAVE_MULTIVOLUME 898#undef HAVE_MULTIVOLUME
898#endif 899#endif
899 900
diff --git a/firmware/export/config/erosqnative.h b/firmware/export/config/erosqnative.h
index 26073a5f34..adb1b29e01 100644
--- a/firmware/export/config/erosqnative.h
+++ b/firmware/export/config/erosqnative.h
@@ -9,6 +9,19 @@
9#define BOOTFILE "rockbox." BOOTFILE_EXT 9#define BOOTFILE "rockbox." BOOTFILE_EXT
10#define BOOTDIR "/.rockbox" 10#define BOOTDIR "/.rockbox"
11 11
12/* Define EROSQN_VER as an "extradefine" in the configure script -
13 * v1, v2 players: "1"
14 * v3 players: "3"
15 * Only bootloader will be affected.
16 *
17 * This allows us to fix the LCD init issues with v3 players.
18 */
19#ifdef BOOTLOADER
20#ifndef EROSQN_VER
21#error "Must define EROSQN_VER"
22#endif
23#endif
24
12/* CPU defines */ 25/* CPU defines */
13#define CONFIG_CPU X1000 26#define CONFIG_CPU X1000
14#define X1000_EXCLK_FREQ 24000000 27#define X1000_EXCLK_FREQ 24000000
@@ -106,6 +119,9 @@
106#define HAVE_BOOTDATA 119#define HAVE_BOOTDATA
107#define BOOT_REDIR "rockbox_main.aigo_erosqn" 120#define BOOT_REDIR "rockbox_main.aigo_erosqn"
108 121
122/* DeviceData */
123#define HAVE_DEVICEDATA
124
109/* USB support */ 125/* USB support */
110#ifndef SIMULATOR 126#ifndef SIMULATOR
111#define CONFIG_USBOTG USBOTG_DESIGNWARE 127#define CONFIG_USBOTG USBOTG_DESIGNWARE
diff --git a/firmware/export/devicedata.h b/firmware/export/devicedata.h
new file mode 100644
index 0000000000..c19b0ca25d
--- /dev/null
+++ b/firmware/export/devicedata.h
@@ -0,0 +1,94 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 *
9 * Copyright (C) 2017 by Amaury Pouly
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
15 *
16 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
17 * KIND, either express or implied.
18 *
19 ****************************************************************************/
20#ifndef __RB_DEVICEDATA__
21#define __RB_DEVICEDATA__
22
23#ifndef __ASSEMBLER__
24#include <stdint.h>
25#include "system.h"
26#endif
27
28/* /!\ This file can be included in assembly files /!\ */
29
30/** The device data will be filled by the bootloader with information that might
31 * be relevant for Rockbox. The bootloader will search for the structure using
32 * the magic header within the first DEVICE_DATA_SEARCH_SIZE bytes of the binary.
33 * Typically, this structure should be as close as possible to the entry point */
34
35/* Search size for the data structure after entry point */
36#define DEVICE_DATA_SEARCH_SIZE 1024
37
38#define DEVICE_DATA_MAGIC0 ('r' | 'b' << 8 | 'd' << 16 | 'e' << 24)
39#define DEVICE_DATA_MAGIC1 ('v' | 'i' << 8 | 'c' << 16 | 'e' << 24)
40
41/* maximum size of payload */
42#define DEVICE_DATA_PAYLOAD_SIZE 4
43
44#ifndef __ASSEMBLER__
45/* This is the C structure */
46struct device_data_t
47{
48 union
49 {
50 uint32_t crc; /* crc of payload data (CRC32 with 0xffffffff for initial value) */
51 uint32_t magic[2]; /* DEVICE_DATA_MAGIC0/1 */
52 };
53
54 uint32_t length; /* length of the payload */
55
56 /* add fields here */
57 union
58 {
59 struct
60 {
61#if defined(EROS_QN)
62 uint8_t lcd_version;
63#endif
64 };
65 uint8_t payload[DEVICE_DATA_PAYLOAD_SIZE];
66 };
67} __attribute__((packed));
68
69
70void fill_devicedata(struct device_data_t *data);
71bool write_devicedata(unsigned char* buf, int len);
72#ifndef BOOTLOADER
73extern struct device_data_t device_data;
74
75void verify_device_data(void) INIT_ATTR;
76
77#endif
78
79#else /* __ASSEMBLER__ */
80
81/* This assembler macro implements an empty device data structure with just the magic
82 * string and payload size */
83.macro put_device_data_here
84.global device_data
85device_data:
86 .word DEVICE_DATA_MAGIC0
87 .word DEVICE_DATA_MAGIC1
88 .word DEVICE_DATA_PAYLOAD_SIZE
89 .space BOOT_DATA_PAYLOAD_SIZE, 0xff /* payload, initialised with value 0xff */
90.endm
91
92#endif
93
94#endif /* __RB_DEVICEDATA__ */
diff --git a/firmware/rolo.c b/firmware/rolo.c
index f9b0cc9e61..38d350d432 100644
--- a/firmware/rolo.c
+++ b/firmware/rolo.c
@@ -56,6 +56,10 @@
56#include "crc32.h" 56#include "crc32.h"
57#endif 57#endif
58 58
59#if defined(HAVE_DEVICEDATA) && !defined(SIMULATOR)
60#include "devicedata.h"
61#endif
62
59#if CONFIG_CPU == AS3525v2 63#if CONFIG_CPU == AS3525v2
60#include "ascodec.h" 64#include "ascodec.h"
61#endif 65#endif
@@ -250,9 +254,8 @@ int rolo_load(const char* filename)
250 254
251 err = LOAD_FIRMWARE(filebuf, filename, filebuf_size); 255 err = LOAD_FIRMWARE(filebuf, filename, filebuf_size);
252#if defined(HAVE_BOOTDATA) && !defined(SIMULATOR) 256#if defined(HAVE_BOOTDATA) && !defined(SIMULATOR)
253 /* write the bootdata as if rolo were the bootloader 257 // Write bootdata as long as the existing bootdata is valid
254 * FIXME: this won't work for root redirect... */ 258 if (boot_data_valid)
255 if (!strcmp(filename, BOOTDIR "/" BOOTFILE) && boot_data_valid)
256 { 259 {
257 int volume = 0; 260 int volume = 0;
258 261
@@ -275,6 +278,10 @@ int rolo_load(const char* filename)
275 } 278 }
276#endif 279#endif
277 280
281#if defined(HAVE_DEVICEDATA)
282 write_devicedata(filebuf, filebuf_size);
283#endif
284
278 if (err <= 0) 285 if (err <= 0)
279 { 286 {
280 rolo_error(loader_strerror(err)); 287 rolo_error(loader_strerror(err));
diff --git a/firmware/scroll_engine.c b/firmware/scroll_engine.c
index c32f4632e2..74c08c1da5 100644
--- a/firmware/scroll_engine.c
+++ b/firmware/scroll_engine.c
@@ -32,6 +32,10 @@
32#include "usb.h" 32#include "usb.h"
33#include "lcd.h" 33#include "lcd.h"
34#include "font.h" 34#include "font.h"
35#if !defined(BOOTLOADER)
36#include "misc.h"
37#include "settings.h"
38#endif
35#ifdef HAVE_REMOTE_LCD 39#ifdef HAVE_REMOTE_LCD
36#include "lcd-remote.h" 40#include "lcd-remote.h"
37#endif 41#endif
diff --git a/firmware/target/mips/ingenic_x1000/crt0.S b/firmware/target/mips/ingenic_x1000/crt0.S
index 6c0942b0db..23daaefb5e 100644
--- a/firmware/target/mips/ingenic_x1000/crt0.S
+++ b/firmware/target/mips/ingenic_x1000/crt0.S
@@ -23,6 +23,10 @@
23#include "mips.h" 23#include "mips.h"
24#include "bootdata.h" 24#include "bootdata.h"
25 25
26#if defined(HAVE_DEVICEDATA) && !defined(BOOTLOADER)
27#include "devicedata.h"
28#endif
29
26 .text 30 .text
27 .extern main 31 .extern main
28 .extern system_early_init 32 .extern system_early_init
@@ -52,6 +56,9 @@ _header:
52#ifndef BOOTLOADER 56#ifndef BOOTLOADER
53 /* Multiboot support header; this is not part of the above header. */ 57 /* Multiboot support header; this is not part of the above header. */
54 put_boot_data_here 58 put_boot_data_here
59#ifdef HAVE_DEVICEDATA
60 put_device_data_here
61#endif
55#endif 62#endif
56 63
57_realstart: 64_realstart:
diff --git a/firmware/target/mips/ingenic_x1000/erosqnative/lcd-erosqnative.c b/firmware/target/mips/ingenic_x1000/erosqnative/lcd-erosqnative.c
index 0d43a3f010..bcc30a71bd 100644
--- a/firmware/target/mips/ingenic_x1000/erosqnative/lcd-erosqnative.c
+++ b/firmware/target/mips/ingenic_x1000/erosqnative/lcd-erosqnative.c
@@ -25,11 +25,138 @@
25#include "lcd-x1000.h" 25#include "lcd-x1000.h"
26#include "gpio-x1000.h" 26#include "gpio-x1000.h"
27#include "system.h" 27#include "system.h"
28#include "devicedata.h"
28 29
29/* for reference on these command/data hex values, see the mipi dcs lcd spec. * 30/* for reference on these command/data hex values, see the mipi dcs lcd spec. *
30 * Not everything here is there, but all the standard stuff is. */ 31 * Not everything here is there, but all the standard stuff is. */
31 32
32static const uint32_t erosqnative_lcd_cmd_enable[] = { 33/* New Display Eroq 2.1 / Hifiwalker 1.7+ / Surfans v3.2, unknown Controller *
34 * (partially GC9A01 register compatible) *
35 * https://espruino.microcosm.app/api/v1/files/ \ *
36 * 9dc1b976d621a2ab3854312cce862c4a9a50dc1b.html#GC9A01 , *
37 * https://www.buydisplay.com/download/ic/GC9A01A.pdf , *
38 * https://lcddisplay.co/wp-content/uploads/2023/02/GC9A01.pdf *
39 * Init sequence From 'EROS Q (cå£ï¼‰_V2.1_20231209固件.zip' *
40 * update.upt/.iso -> In 'uboot.bin' at 0x52da0-0x5305f *
41 * http://www.eroshifi.com/download/firmware/122.html */
42static const uint32_t erosqnative_lcd_cmd_enable_v3[] = {
43
44 /* Unlock EXTC? */
45 LCD_INSTR_CMD, 0xfe, // Inter Register Enable1
46 LCD_INSTR_CMD, 0xef, // Inter Register Enable2
47
48 LCD_INSTR_CMD, 0x36, // Memory Access Control
49/* Bit7 1:vertical flip 0:no vertical flip
50 Bit6 1:horizontal flip 0:no horizontal flip
51 Bit3 1:BGR 0:RGB */
52 LCD_INSTR_DAT, 0x90,
53 /* Pixel Format Set */
54 LCD_INSTR_CMD, 0x3a,
55 LCD_INSTR_DAT, 0x55, /* Rockbox uses 16pp, OF specified 18 bpp */
56
57 LCD_INSTR_CMD, 0x84, // ?? (undocumented)
58 LCD_INSTR_DAT, 0x04,
59 LCD_INSTR_CMD, 0x86, // ??
60 LCD_INSTR_DAT, 0xfb,
61 LCD_INSTR_CMD, 0x87, // ??
62 LCD_INSTR_DAT, 0x79,
63 LCD_INSTR_CMD, 0x89, // ??
64 LCD_INSTR_DAT, 0x0b,
65 LCD_INSTR_CMD, 0x8a, // ??
66 LCD_INSTR_DAT, 0x20,
67 LCD_INSTR_CMD, 0x8b, // ??
68 LCD_INSTR_DAT, 0x80,
69 LCD_INSTR_CMD, 0x8d, // ??
70 LCD_INSTR_DAT, 0x3b,
71 LCD_INSTR_CMD, 0x8e, // ??
72 LCD_INSTR_DAT, 0xcf,
73
74 LCD_INSTR_CMD, 0xec, // Charge Pump Frequent Control
75 LCD_INSTR_DAT, 0x33,
76 LCD_INSTR_DAT, 0x02,
77 LCD_INSTR_DAT, 0x4c,
78
79 LCD_INSTR_CMD, 0x98, // ?? (undocumented)
80 LCD_INSTR_DAT, 0x3e,
81 LCD_INSTR_CMD, 0x9c, // ??
82 LCD_INSTR_DAT, 0x4b,
83 LCD_INSTR_CMD, 0x99, // ??
84 LCD_INSTR_DAT, 0x3e,
85 LCD_INSTR_CMD, 0x9d, // ??
86 LCD_INSTR_DAT, 0x4b,
87 LCD_INSTR_CMD, 0x9b, // ??
88 LCD_INSTR_DAT, 0x55,
89
90 LCD_INSTR_CMD, 0xe8, // Frame Rate
91 LCD_INSTR_DAT, 0x11,
92 LCD_INSTR_DAT, 0x00,
93
94 LCD_INSTR_CMD, 0xff, // ?? (Adafruit & Co lib. C:0xFF, D:0x60, D:0x01, D:0x04)
95 LCD_INSTR_DAT, 0x62, // LCD_INSTR_DAT, 0x01, LCD_INSTR_DAT, 0x04,
96 LCD_INSTR_CMD, 0xc3, // Vreg1a voltage Control
97 LCD_INSTR_DAT, 0x20,
98 LCD_INSTR_CMD, 0xc4, // Vreg1b voltage Control
99 LCD_INSTR_DAT, 0x03,
100 LCD_INSTR_CMD, 0xc9, // Vreg2a voltage Control
101 LCD_INSTR_DAT, 0x2a,
102
103 LCD_INSTR_CMD, 0xf0, // SET_GAMMA1
104 LCD_INSTR_DAT, 0x4a,
105 LCD_INSTR_DAT, 0x10,
106 LCD_INSTR_DAT, 0x0a,
107 LCD_INSTR_DAT, 0x0a,
108 LCD_INSTR_DAT, 0x26,
109 LCD_INSTR_DAT, 0x39,
110
111 LCD_INSTR_CMD, 0xf2, // SET_GAMMA3
112 LCD_INSTR_DAT, 0x4a,
113 LCD_INSTR_DAT, 0x10,
114 LCD_INSTR_DAT, 0x0a,
115 LCD_INSTR_DAT, 0x0a,
116 LCD_INSTR_DAT, 0x26,
117 LCD_INSTR_DAT, 0x39,
118
119 LCD_INSTR_CMD, 0xf1, // SET_GAMMA2
120 LCD_INSTR_DAT, 0x50,
121 LCD_INSTR_DAT, 0x8f,
122 LCD_INSTR_DAT, 0xaf,
123 LCD_INSTR_DAT, 0x3b,
124 LCD_INSTR_DAT, 0x3f,
125 LCD_INSTR_DAT, 0x7f,
126
127 LCD_INSTR_CMD, 0xf3, // SET_GAMMA4
128 LCD_INSTR_DAT, 0x50,
129 LCD_INSTR_DAT, 0x8f,
130 LCD_INSTR_DAT, 0xaf,
131 LCD_INSTR_DAT, 0x3b,
132 LCD_INSTR_DAT, 0x3f,
133 LCD_INSTR_DAT, 0x7f,
134
135 LCD_INSTR_CMD, 0xba, // TE Control
136 LCD_INSTR_DAT, 0x0a,
137
138#ifdef BOOTLOADER
139 LCD_INSTR_CMD, 0x35, // Tearing Effect Line ON
140 LCD_INSTR_DAT, 0x00,
141#endif
142
143 LCD_INSTR_CMD, 0x21, /* Invert */
144
145 /* Lock EXTC? */
146 LCD_INSTR_CMD, 0xfe, // Inter Register Enable1
147 LCD_INSTR_CMD, 0xee,
148
149 /* Exit Sleep */
150 LCD_INSTR_CMD, 0x11,
151 LCD_INSTR_UDELAY, 120000,
152 /* Display On */
153 LCD_INSTR_CMD, 0x29,
154 LCD_INSTR_UDELAY, 20000,
155 LCD_INSTR_END,
156};
157
158/* Original Display / Hifiwalker -1.5 / Surfans -2.7 */
159static const uint32_t erosqnative_lcd_cmd_enable_v1[] = {
33 /* Set EXTC? */ 160 /* Set EXTC? */
34 LCD_INSTR_CMD, 0xc8, 161 LCD_INSTR_CMD, 0xc8,
35 LCD_INSTR_DAT, 0xff, 162 LCD_INSTR_DAT, 0xff,
@@ -179,7 +306,22 @@ void lcd_tgt_enable(bool enable)
179 mdelay(5); 306 mdelay(5);
180 gpio_set_level(GPIO_LCD_CE, 0); 307 gpio_set_level(GPIO_LCD_CE, 0);
181 308
182 lcd_exec_commands(&erosqnative_lcd_cmd_enable[0]); 309#ifdef BOOTLOADER
310# if EROSQN_VER == 3
311 lcd_exec_commands(&erosqnative_lcd_cmd_enable_v3[0]);
312# else
313 lcd_exec_commands(&erosqnative_lcd_cmd_enable_v1[0]);
314# endif
315#else
316 if (device_data.lcd_version == 3)
317 {
318 lcd_exec_commands(&erosqnative_lcd_cmd_enable_v3[0]);
319 }
320 else
321 {
322 lcd_exec_commands(&erosqnative_lcd_cmd_enable_v1[0]);
323 }
324#endif
183 } else { 325 } else {
184 /* doesn't flash white if we don't do anything... */ 326 /* doesn't flash white if we don't do anything... */
185#if 0 327#if 0
diff --git a/firmware/target/mips/ingenic_x1000/system-x1000.c b/firmware/target/mips/ingenic_x1000/system-x1000.c
index 64890a6c3a..1c850736b6 100644
--- a/firmware/target/mips/ingenic_x1000/system-x1000.c
+++ b/firmware/target/mips/ingenic_x1000/system-x1000.c
@@ -20,6 +20,7 @@
20 ****************************************************************************/ 20 ****************************************************************************/
21 21
22#include "system.h" 22#include "system.h"
23#include <string.h>
23#include "mips.h" 24#include "mips.h"
24#include "panic.h" 25#include "panic.h"
25#include "button.h" 26#include "button.h"
@@ -36,6 +37,10 @@
36#include "x1000/msc.h" 37#include "x1000/msc.h"
37#include "x1000/aic.h" 38#include "x1000/aic.h"
38 39
40#if defined(HAVE_DEVICEDATA)
41#include "devicedata.h"
42#endif
43
39#ifdef X1000_CPUIDLE_STATS 44#ifdef X1000_CPUIDLE_STATS
40int __cpu_idle_avg = 0; 45int __cpu_idle_avg = 0;
41int __cpu_idle_cur = 0; 46int __cpu_idle_cur = 0;
@@ -81,6 +86,20 @@ void system_early_init(void)
81 clk_init(); 86 clk_init();
82} 87}
83 88
89#if defined (HAVE_DEVICEDATA) && defined(EROS_QN)
90void fill_devicedata(struct device_data_t *data)
91{
92#ifdef BOOTLOADER
93 memset(data->payload, 0xff, data->length);
94 data->lcd_version = EROSQN_VER;
95#else
96 uint8_t lcd_version = device_data.lcd_version;
97 memset(data->payload, 0xff, data->length);
98 data->lcd_version = lcd_version;
99#endif
100}
101#endif
102
84/* First thing called from Rockbox main() */ 103/* First thing called from Rockbox main() */
85void system_init(void) 104void system_init(void)
86{ 105{
diff --git a/firmware/target/mips/ingenic_x1000/x1000boot.make b/firmware/target/mips/ingenic_x1000/x1000boot.make
index 0bdf5cf7b4..7a861b0a3d 100644
--- a/firmware/target/mips/ingenic_x1000/x1000boot.make
+++ b/firmware/target/mips/ingenic_x1000/x1000boot.make
@@ -12,7 +12,7 @@ include $(ROOTDIR)/lib/microtar/microtar.make
12INCLUDES += -I$(APPSDIR) 12INCLUDES += -I$(APPSDIR)
13SRC += $(call preprocess, $(APPSDIR)/SOURCES) 13SRC += $(call preprocess, $(APPSDIR)/SOURCES)
14 14
15LDSDEP := $(FIRMDIR)/export/cpu.h $(FIRMDIR)/export/config/$(MODELNAME).h 15LDSDEP := $(FIRMDIR)/export/cpu.h $(FIRMDIR)/export/config.h
16 16
17BOOTLDS := $(FIRMDIR)/target/$(CPU)/$(MANUFACTURER)/boot.lds 17BOOTLDS := $(FIRMDIR)/target/$(CPU)/$(MANUFACTURER)/boot.lds
18BOOTLINK := $(BUILDDIR)/boot.link 18BOOTLINK := $(BUILDDIR)/boot.link
diff --git a/lib/rbcodec/codecs/cRSID/host/file.c b/lib/rbcodec/codecs/cRSID/host/file.c
index c87f37ebb4..817cff6c2b 100644
--- a/lib/rbcodec/codecs/cRSID/host/file.c
+++ b/lib/rbcodec/codecs/cRSID/host/file.c
@@ -39,7 +39,7 @@ cRSID_SIDheader* cRSID_processSIDfile(cRSID_C64instance* C64, unsigned char* fil
39 for (i=1; i < (int)(sizeof(MagicStringPSID)-1); ++i) { if (SIDheader->MagicString[i] != MagicStringPSID[i]) return NULL; } 39 for (i=1; i < (int)(sizeof(MagicStringPSID)-1); ++i) { if (SIDheader->MagicString[i] != MagicStringPSID[i]) return NULL; }
40 C64->RealSIDmode = ( SIDheader->MagicString[0] == 'R' ); 40 C64->RealSIDmode = ( SIDheader->MagicString[0] == 'R' );
41 41
42 if (SIDheader->LoadAddressH==0 && SIDheader->LoadAddressH==0) { //load-address taken from first 2 bytes of the C64 PRG 42 if (SIDheader->LoadAddressH==0 && SIDheader->LoadAddressL==0) { //load-address taken from first 2 bytes of the C64 PRG
43 C64->LoadAddress = (filedata[SIDheader->HeaderSize+1]<<8) + (filedata[SIDheader->HeaderSize+0]); 43 C64->LoadAddress = (filedata[SIDheader->HeaderSize+1]<<8) + (filedata[SIDheader->HeaderSize+0]);
44 SIDdataOffset = SIDheader->HeaderSize+2; 44 SIDdataOffset = SIDheader->HeaderSize+2;
45 } 45 }
diff --git a/manual/appendix/config_file_options.tex b/manual/appendix/config_file_options.tex
index b3e4363815..cb31068964 100644
--- a/manual/appendix/config_file_options.tex
+++ b/manual/appendix/config_file_options.tex
@@ -58,7 +58,7 @@
58 playlist viewer indices 58 playlist viewer indices
59 & on, off & N/A\\ 59 & on, off & N/A\\
60 playlist viewer track display 60 playlist viewer track display
61 & track name,full path 61 & track name,full path,title and album from tags,title from tags
62 & N/A\\ 62 & N/A\\
63 recursive directory insert 63 recursive directory insert
64 & on, off, ask & N/A\\ 64 & on, off, ask & N/A\\
@@ -67,6 +67,7 @@
67 scroll step & \fixme{devise a way to get ranges from config-*.h} & pixels\\ 67 scroll step & \fixme{devise a way to get ranges from config-*.h} & pixels\\
68 screen scroll step & \fixme{devise a way to get ranges from config-*.h} & pixels\\ 68 screen scroll step & \fixme{devise a way to get ranges from config-*.h} & pixels\\
69 Screen Scrolls Out Of View & on, off & N/A\\ 69 Screen Scrolls Out Of View & on, off & N/A\\
70 Disable main menu scrolling & on, off & N/A\\
70 bidir limit & 0 to 200 & \% screen\\ 71 bidir limit & 0 to 200 & \% screen\\
71 scroll paginated & on, off & N/A\\ 72 scroll paginated & on, off & N/A\\
72 list wraparound & on, off & N/A\\ 73 list wraparound & on, off & N/A\\
diff --git a/manual/configure_rockbox/display_options.tex b/manual/configure_rockbox/display_options.tex
index e2b5c3eccd..0d42115efe 100755
--- a/manual/configure_rockbox/display_options.tex
+++ b/manual/configure_rockbox/display_options.tex
@@ -198,6 +198,11 @@
198 will keep the list entries at their fixed positions and allow them to be 198 will keep the list entries at their fixed positions and allow them to be
199 scrolled out of view, whereas \setting{No} will only scroll those entries 199 scrolled out of view, whereas \setting{No} will only scroll those entries
200 which surpass the right margin. 200 which surpass the right margin.
201 \item[Disable main menu scrolling]
202 Setting this option to \setting{Yes}
203 will stop all kind of text scrollings while you are laying on the main menu
204 which may greatly help at avoiding visual glitches if you are using a theme
205 that has a lockscreen.
201 \item[Screen Scroll Step Size.] 206 \item[Screen Scroll Step Size.]
202 Defines the number of pixels the horizontal manual screen scroll should move 207 Defines the number of pixels the horizontal manual screen scroll should move
203 for each step. 208 for each step.
diff --git a/manual/configure_rockbox/sound_settings.tex b/manual/configure_rockbox/sound_settings.tex
index fd69d63317..0aeada59b2 100644
--- a/manual/configure_rockbox/sound_settings.tex
+++ b/manual/configure_rockbox/sound_settings.tex
@@ -44,10 +44,6 @@ change to customise your listening experience.
44 limit, select a volume from the list and the maximum volume will be limited to 44 limit, select a volume from the list and the maximum volume will be limited to
45 the selected value all over the system. 45 the selected value all over the system.
46 \opt{xduoox3}{This setting also applies to the Line Out of the \dap{}, as at full scale the \dap{} overdrives the signal.} 46 \opt{xduoox3}{This setting also applies to the Line Out of the \dap{}, as at full scale the \dap{} overdrives the signal.}
47 \opt{erosqnative}{On the \playertype{}, when using the Line Out without anything connected to the Headphone port,
48 the volume is fixed to the \setting{Volume Limit} value.
49 \note{On the \playertype{}, the Line Out level at -2~dB is approximately +7~dBV, -4~dB is approximately +4~dBu,
50 and -18~dB is approximately -10~dBV.}}
51 47
52\section{Bass} 48\section{Bass}
53 This setting emphasises 49 This setting emphasises
@@ -196,8 +192,7 @@ change to customise your listening experience.
196\opt{erosqnative}{ 192\opt{erosqnative}{
197 \section{Stereo Switch Mode} 193 \section{Stereo Switch Mode}
198 The Eros Q and related devices contain a stereo switch in the audio path. 194 The Eros Q and related devices contain a stereo switch in the audio path.
199 This may be connected differently depending on the hardware revision. This 195 This setting allows the behavior of the stereo switch to be changed if one of
200 setting allows the behavior of the stereo switch to be changed if one of
201 the two outputs (Headphones or Line Out) is not working. There are four modes: 196 the two outputs (Headphones or Line Out) is not working. There are four modes:
202 \begin{description} 197 \begin{description}
203 \item[Normal.] 198 \item[Normal.]
@@ -209,6 +204,16 @@ change to customise your listening experience.
209 \item[Always 1.] 204 \item[Always 1.]
210 Both outputs use a value of 1. 205 Both outputs use a value of 1.
211 \end{description} 206 \end{description}
207
208 \section{DAC filter roll-off}
209 Later versions of the Eros Q and related devices contain a DAC with user-selectable
210 filter roll-off modes. This allows switching between the following modes:
211 \begin{itemize}
212 \item "Sharp"
213 \item "Slow"
214 \item "Short"
215 \item "Bypass"
216 \end{itemize}
212} 217}
213 218
214\opt{dac_power_mode}{ 219\opt{dac_power_mode}{
diff --git a/manual/getting_started/hibyos_nativeinstall.tex b/manual/getting_started/hibyos_nativeinstall.tex
new file mode 100644
index 0000000000..a5c4f07fed
--- /dev/null
+++ b/manual/getting_started/hibyos_nativeinstall.tex
@@ -0,0 +1,64 @@
1We will install the bootloader with the original firmware's recovery
2mode. The process is summed up as the following:
3
4\begin{itemize}
5 \item Determine what hardware version your player is and download
6 the correct bootloader update file
7 \item Place the bootloader \fname{update.upt} file on the SD card
8 \item In the original firmware, run the Firmware Update: \\
9 \fname{System Settings --> Firmware Update}
10\end{itemize}
11
12\subsubsection{Determine hardware version}\label{ref:determine_hardware_version}
13Determine what hardware version your player is. Go to \fname{System Settings --> About The Player --> Version} and reference
14the list below. hw1, hw1.5, and hw2 players all use the same update
15file (with one exception), while hw3 players use a different one.
16
17\note{Important: If your player's version is not contained in this list,
18for example if the firmware version is newer than what is listed here,
19we cannot be sure that the hardware is the same. The best thing to do is
20contact the manufacturer and ask them two things: (1) for an update file
21of your version, and (2) if a player with the most recent version listed
22here can be upgraded to the firmware version on your player. If they say
23yes, we can be more certain that the hardware has not changed. These lists
24may not be the most up to date, please see the wiki page at
25\url{https://www.rockbox.org/wiki/AIGOErosQK} for the most up-to-date list.}
26
27\begin{description}
28\item[hw1/hw1.5/hw2 players]
29 \begin{itemize}
30 \item Aigo Eros Q V1.8 - V2.0
31 \item Hifiwalker H2 V1.1 - V1.6
32 \item Surfans F20 V2.2 - V2.7
33 \end{itemize}
34 These players use \fname{erosqnative-hw1hw2-erosq.upt} as the update file.
35 The lone exception is the Hifiwalker H2 V1.3, which uses the update file
36 \fname{erosqnative-hw1hw2-eros\_h2.upt}.
37\item[hw3 players]
38 \begin{itemize}
39 \item Aigo Eros Q V2.1
40 \item Hifiwalker H2 V1.7 - V1.8
41 \item Surfans F20 V3.0 - V3.3
42 \end{itemize}
43
44 These players use \fname{erosqnative-hw3-erosq.upt} as the update file.
45\end{description}
46
47Download the \fname{.upt} file for these players from \download{bootloader/aigo/native/}.
48
49\note{All players use the same Rockbox build, only the bootloader is different.}
50
51\subsubsection{Place update file on SD card}\label{ref:place_on_sd_card}
52Place the appropriate bootloader file on the root of the SD card and name it
53\emph{exactly} \fname{update.upt}.
54
55\note{This is a good time to ensure that your Rockbox installation \fname{.rockbox}
56is present on your SD card.}
57
58Don't forget to safely eject/unmount your player.
59
60\subsubsection{Run Firmware Update}\label{ref:run_firmware_update}
61In the original firmware, run the firmware updater by going to
62\fname{System Settings --> Firmware Update}. At this point, you can delete
63\fname{update.upt} from the SD card if you wish. \emph{Do not delete .rockbox,
64this is your Rockbox installation and needs to stay there!}
diff --git a/manual/getting_started/installation.tex b/manual/getting_started/installation.tex
index 661964dd8a..b59a4e1f38 100644
--- a/manual/getting_started/installation.tex
+++ b/manual/getting_started/installation.tex
@@ -118,6 +118,15 @@ of before installing.
118\begin{description} 118\begin{description}
119 \item[Filesystem support.] Rockbox only supports the FAT32 filesystem. Other 119 \item[Filesystem support.] Rockbox only supports the FAT32 filesystem. Other
120 filesystems such as exFAT or NTFS are not supported. 120 filesystems such as exFAT or NTFS are not supported.
121 \opt{erosqnative}{
122 \note{Many SD cards come with exFAT on them from the
123 factory. The original firmware can be used to reformat them to FAT
124 by going to: \fname{System Settings --> Reset --> Format TF Card}.}
125 \note{The SD card that sometimes come bundled with these players are usually
126 of substandard quality and are not to be trusted. It is recommended to source
127 a card of a reputable brand, from a reputable source, such as direct from
128 the manufacturer.}
129 }
121 \item[USB DAC.] This feature is not supported by Rockbox, but you can 130 \item[USB DAC.] This feature is not supported by Rockbox, but you can
122 dual-boot the original firmware if you want to use it. 131 dual-boot the original firmware if you want to use it.
123 \opt{shanlingq1,agptekrocker,xduoox3ii,xduoox20,aigoerosq,erosqnative}{\item[Wireless.] There is no support for Bluetooth\opt{shanlingq1}{ or WiFi}. 132 \opt{shanlingq1,agptekrocker,xduoox3ii,xduoox20,aigoerosq,erosqnative}{\item[Wireless.] There is no support for Bluetooth\opt{shanlingq1}{ or WiFi}.
@@ -545,7 +554,8 @@ by Rockbox, in the main directory of your \daps{} drive.
545 \opt{samsungyh}{\input{getting_started/samsungyh_install.tex}} 554 \opt{samsungyh}{\input{getting_started/samsungyh_install.tex}}
546 \opt{xduoox3}{\input{getting_started/xduoox3_install.tex}} 555 \opt{xduoox3}{\input{getting_started/xduoox3_install.tex}}
547 \opt{xduoox3ii,xduoox20,agptekrocker,aigoerosq}{\input{getting_started/hibyos_install.tex}} 556 \opt{xduoox3ii,xduoox20,agptekrocker,aigoerosq}{\input{getting_started/hibyos_install.tex}}
548 \opt{fiiom3k,shanlingq1,erosqnative}{\input{getting_started/jztool_install.tex}} 557 \opt{fiiom3k,shanlingq1}{\input{getting_started/jztool_install.tex}}
558 \opt{erosqnative}{\input{getting_started/hibyos_nativeinstall.tex}}
549 } 559 }
550} 560}
551 561
@@ -807,9 +817,9 @@ completely follow the manual uninstallation instructions below.}}
807 \playerman{} firmware. 817 \playerman{} firmware.
808} 818}
809 819
810\opt{fiiom3k,shanlingq1,erosqnative}{ 820\opt{fiiom3k,shanlingq1}{
811 If you want to remove the Rockbox bootloader, copy an original firmware 821 If you want to remove the Rockbox bootloader, copy an original firmware
812 update to your microSD card and run the \playerman{} update by 822 update to your microSD card and run the \playerman{} update by
813 \opt{erosqnative}{running it from the Original Firmware's System Settings menu.} 823 \opt{erosqnative}{running it from the Original Firmware's System Settings menu.}
814 \nopt{erosqnative}{holding \ActionBootOFRecovery{} while powering on the \dap{}.} 824 \nopt{erosqnative}{holding \ActionBootOFRecovery{} while powering on the \dap{}.}
815 825
@@ -821,6 +831,19 @@ completely follow the manual uninstallation instructions below.}}
821 and enter the recovery menu -- see \reference{ref:jztool_load_bootloader}. 831 and enter the recovery menu -- see \reference{ref:jztool_load_bootloader}.
822} 832}
823 833
834\opt{erosqnative}{
835 If you want to remove the Rockbox bootloader, you will need to acquire the correct
836 update file for your brand player and original firmware version number. The
837 uninstallation procedure can then be done as follows:
838
839 \begin{itemize}
840 \item If the update file is unmodified, use \fname{tools/hibyos\_nativepatcher/hibyos\_nativepatcher.sh} to mark the original bootloader for updating
841 \item Place the update file on the SD card and ensure it is named \emph{exactly} \fname{update.upt}
842 \item Boot into the Rockbox bootloader menu: With the player off, hold \ButtonVolUp{} + \ButtonPower{}
843 \item Select "Aigo Recovery"
844 \end{itemize}
845}
846
824\nopt{gigabeats}{ 847\nopt{gigabeats}{
825 If you wish to clean up your disk, you may also wish to delete the 848 If you wish to clean up your disk, you may also wish to delete the
826 \fname{.rockbox} directory and its contents. 849 \fname{.rockbox} directory and its contents.
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}}{}{}
diff --git a/tools/builds.pm b/tools/builds.pm
index 8efd22bbdf..0b7c768888 100644
--- a/tools/builds.pm
+++ b/tools/builds.pm
@@ -474,16 +474,17 @@ $releasenotes="/wiki/ReleaseNotes315";
474 }, 474 },
475 'fiiom3k' => { 475 'fiiom3k' => {
476 name => 'FiiO M3K', 476 name => 'FiiO M3K',
477 status => 2, 477 status => 3,
478 manualok => 1, # Remove once status moves to 3 478 release => '4.0', # Remove once 4.0 lands
479 }, 479 },
480 'aigoerosq' => { 480 'aigoerosq' => {
481 name => 'AIGO EROS Q / K (Hosted)', 481 name => 'AIGO EROS Q / K (Hosted)',
482 status => 2, 482 status => 2, # Do we promote this to stable?
483 }, 483 },
484 'erosqnative' => { 484 'erosqnative' => {
485 name => 'AIGO EROS Q / K (Native)', 485 name => 'AIGO EROS Q / K (Native)',
486 status => 2, 486 status => 3,
487 release => '4.0', # Remove once 4.0 lands
487 }, 488 },
488 'ihifi770' => { 489 'ihifi770' => {
489 name => 'Xuelin iHIFI 770', 490 name => 'Xuelin iHIFI 770',
@@ -499,8 +500,8 @@ $releasenotes="/wiki/ReleaseNotes315";
499 }, 500 },
500 'shanlingq1' => { 501 'shanlingq1' => {
501 name => 'Shanling Q1', 502 name => 'Shanling Q1',
502 status => 2, 503 status => 3,
503 manualok => 1, # Remove once status moves to 3 504 release => '4.0', # Remove once 4.0 lands
504 }, 505 },
505); 506);
506 507
diff --git a/tools/configure b/tools/configure
index 8698c6a38d..43f119ae17 100755
--- a/tools/configure
+++ b/tools/configure
@@ -1676,6 +1676,8 @@ cat <<EOF
1676 ==AIGO== 244) M3K Linux 1676 ==AIGO== 244) M3K Linux
1677 245) Eros Q / K 246) M3K baremetal ==Shanling== 1677 245) Eros Q / K 246) M3K baremetal ==Shanling==
1678 247) Eros Q / K native 260) Q1 1678 247) Eros Q / K native 260) Q1
1679 248) Eros Q / K native v3
1680 (GC9A01 LCD Controller)
1679EOF 1681EOF
1680 1682
1681 buildfor=`input`; 1683 buildfor=`input`;
@@ -4190,8 +4192,39 @@ fi
4190 t_cpu="mips" 4192 t_cpu="mips"
4191 t_manufacturer="ingenic_x1000" 4193 t_manufacturer="ingenic_x1000"
4192 t_model="erosqnative" 4194 t_model="erosqnative"
4195 # player version, for bootloader usage
4196 # versions 1 and 2 both use 1
4197 extradefines="$extradefines -DEROSQN_VER=1"
4193 ;; 4198 ;;
4194 4199
4200 248|erosqnative_v3)
4201 target_id=117
4202 modelname="erosqnative_v3"
4203 target="EROS_QN"
4204 memory=32
4205 mipsr2elcc
4206 appextra="recorder:gui"
4207 plugins="yes"
4208 tool="$rootdir/tools/scramble -add=erosqnative "
4209 boottool="$rootdir/tools/mkspl-x1000 -type=nand -ppb=2 -bpp=2 "
4210 output="rockbox.erosq"
4211 bootoutput="bootloader.erosq"
4212 sysfontbl="16-Terminus"
4213 # toolset is the tools within the tools directory that we build for
4214 # this particular target.
4215 toolset="$x1000tools"
4216 bmp2rb_mono="$rootdir/tools/bmp2rb -f 0"
4217 bmp2rb_native="$rootdir/tools/bmp2rb -f 4"
4218 # architecture, manufacturer and model for the target-tree build
4219 t_cpu="mips"
4220 t_manufacturer="ingenic_x1000"
4221 t_model="erosqnative"
4222 # player version, for bootloader usage
4223 # version 3
4224 extradefines="$extradefines -DEROSQN_VER=3"
4225 ;;
4226
4227
4195 *) 4228 *)
4196 echo "Please select a supported target platform!" 4229 echo "Please select a supported target platform!"
4197 exit 7 4230 exit 7
diff --git a/tools/hibyos_nativepatcher/hibyos_nativepatcher.sh b/tools/hibyos_nativepatcher/hibyos_nativepatcher.sh
new file mode 100755
index 0000000000..3c6661863f
--- /dev/null
+++ b/tools/hibyos_nativepatcher/hibyos_nativepatcher.sh
@@ -0,0 +1,221 @@
1#!/bin/bash
2# hibyos_nativepatcher.sh
3#
4# NOTE: THIS SCRIPT IS NOT TOLERANT OF WHITESPACE IN FILENAMES OR PATHS
5
6usage="hibyos_nativepatcher.sh
7
8 USAGE:
9
10 hibyos_nativepatcher.sh <mkrbinstall/mkstockuboot> [arguments depend on mode, see below]
11
12 hibyos_nativepatcher.sh mkrbinstall <OFVERNAME (erosq or eros_h2)>
13 <path/to/output> <path/to/bootloader.erosq> <HWVER (hw1hw2 or hw3)>
14 Output file will be path/to/output/erosqnative_RBVER-HWVER-OFVERNAME.upt.
15 Only the Hifiwalker H2 v1.3 uses "eros_h2", everything else uses "erosq".
16
17 hibyos_nativepatcher.sh mkstockuboot <path/to/OFupdatefile.upt>
18 Output file will be path/to/OFupdatefile-rbuninstall.upt.
19
20 NOTE: THIS SCRIPT IS NOT TOLERANT OF WHITESPACE IN FILENAMES OR PATHS!"
21
22# check OS type and for any needed tools
23if [[ "$OSTYPE" == "darwin"* ]]; then
24 echo "$OSTYPE DETECTED"
25elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
26 echo "$OSTYPE DETECTED"
27 if !(which 7z > /dev/null); then
28 echo "PLEASE INSTALL 7z (usually part of p7zip-full package)"
29 exit 1
30 fi
31 if !(which genisoimage > /dev/null); then
32 echo "PLEASE INSTALL genisoimage"
33 exit 1
34 fi
35else
36 echo "SCRIPT NOT IMPLEMENTED ON $OSTYPE YET!"
37 exit 1
38fi
39
40# make sure we can find patch_manifest
41SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
42if !(which $SCRIPT_DIR/patch_manifest.pl > /dev/null); then
43 echo "couldn't find patch_manifest.pl!"
44 exit 1
45fi
46
47###########################################################################
48# MKRBINSTALL
49###########################################################################
50if [[ "$1" == "mkrbinstall" ]]; then
51 echo "Creating installation image from bootloader file..."
52
53 # make sure all arguments are accounted for...
54 if [[ -z "$5" ]]; then
55 echo "not all parameters included, please see usage:"
56 echo "$usage"
57 exit 1
58 fi
59
60 # validate arguments
61 outputdir=$(realpath --relative-base=$(pwd) $3)
62 if !(ls $outputdir >& /dev/null); then
63 echo "directory $outputdir doesn't seem to exist. Please make sure it exists, then re-run hibyos_nativepatcher.sh."
64 exit 1
65 fi
66
67 # note, bootloaderfile might still be a valid path, but not a valid bootloader file... check to make sure tar can extract it okay.
68 bootloaderfile=$(realpath --relative-base=$(pwd) $4)
69 if !(ls $bootloaderfile >& /dev/null); then
70 echo "bootloader file $bootloaderfile doesn't seem to exist. Please make sure it exists, then re-run hibyos_nativepatcher.sh."
71 exit 1
72 fi
73
74 # make working directory...
75 mkdir $outputdir/working_dir
76 workingdir=$(realpath $outputdir/working_dir)
77 mkdir $workingdir/bootloader
78
79 # extract bootloader file
80 if [[ "$OSTYPE" == "darwin"* ]]; then
81 # macos
82 tar -xvf $bootloaderfile --cd $workingdir/bootloader
83 elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
84 # linux-gnu
85 tar -xvf $bootloaderfile -C $workingdir/bootloader
86 fi
87
88 # make sure we got what we wanted
89 if !(ls $workingdir/bootloader/bootloader.ucl >& /dev/null); then
90 echo "can't find bootloader.ucl! help!"
91 rm -rf $workingdir
92 exit 1
93 elif !(ls $workingdir/bootloader/spl.erosq >& /dev/null); then
94 echo "can't find spl.erosq! help!"
95 rm -rf $workingdir
96 exit 1
97 fi
98
99 bootver=$(cat $workingdir/bootloader/bootloader-info.txt)
100 if [ -z "$bootver" ]; then
101 echo "COULDN'T FIND BOOTLOADER-INFO!"
102 rm -rf $workingdir
103 exit 1
104 fi
105
106 # if uboot.bin already exists, something is weird.
107 if (ls $workingdir/image_contents/uboot.bin >& /dev/null); then
108 echo "$workingdir/image_contents/uboot.bin already exists, something went weird."
109 rm -rf $workingdir
110 exit 1
111 fi
112
113 # everything exists, make the bin
114 mkdir $workingdir/image_contents/
115 touch $workingdir/image_contents/uboot.bin
116 echo "PATCHING!"
117 dd if=$workingdir/bootloader/spl.erosq of=$workingdir/image_contents/uboot.bin obs=1 seek=0 conv=notrunc
118 dd if=$workingdir/bootloader/bootloader.ucl of=$workingdir/image_contents/uboot.bin obs=1 seek=26624 conv=notrunc
119
120 # create update.txt
121 md5=($(md5sum $workingdir/image_contents/uboot.bin))
122 if [ -z "$md5" ]; then
123 echo "COULDN'T MD5SUM UBOOT.BIN!"
124 rm -rf $workingdir
125 exit 1
126 fi
127 echo "Create update manifest with md5sum $md5"
128 echo "" > $workingdir/image_contents/update.txt
129 $SCRIPT_DIR/patch_manifest.pl $md5 $workingdir/image_contents/update.txt
130
131 # create version.txt
132 echo "version={
133 name=$2
134 ver=2024-09-10T14:42:18+08:00
135}" > $workingdir/image_contents/version.txt
136
137 outputfilename="erosqnative_$bootver-$5-$2"
138
139
140###########################################################################
141# MKSTOCKUBOOT
142###########################################################################
143elif [[ "$1" == "mkstockuboot" ]]; then
144 echo "Creating uninstallation image from stock update image..."
145
146 # make sure all arguments are accounted for...
147 if [[ -z "$2" ]]; then
148 echo "not all parameters included, please see usage:"
149 echo "$usage"
150 exit 1
151 fi
152
153 updatefile=$(realpath --relative-base=$(pwd) $2)
154 updatefile_path=$(echo "$updatefile" | perl -ne "s/\/[\w\.\_\-]*$// && print")
155 updatefile_name=$(basename $updatefile)
156 updatefile_name_noext=$(echo "$updatefile_name" | perl -ne "s/\.\w*$// && print")
157 outputdir=$updatefile_path
158 outputfilename="$updatefile_name_noext-rbuninstall"
159
160 mkdir $updatefile_path/working_dir
161 workingdir=$(realpath $updatefile_path/working_dir)
162
163 # copy update.upt to update.iso
164 cp $updatefile $workingdir/$updatefile_name_noext-cpy.iso
165
166 mkdir $workingdir/image_contents
167
168 # extract iso
169 if [[ "$OSTYPE" == "darwin"* ]]; then
170 # macos
171 hdiutil attach $workingdir/$updatefile_name_noext-cpy.iso -mountpoint $workingdir/contentsiso
172
173 # copy out iso contents
174 cp $workingdir/contentsiso/* $workingdir/image_contents
175
176 # unmount iso
177 hdiutil detach $workingdir/contentsiso
178 elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
179 # linux-gnu
180 7z -o$workingdir/image_contents x $workingdir/$updatefile_name_noext-cpy.iso
181 fi
182
183 chmod 777 $workingdir/image_contents/*
184
185 # modify update.txt
186 md5=($(md5sum $workingdir/image_contents/uboot.bin))
187 if [ -z "$md5" ]; then
188 echo "COULDN'T MD5SUM UBOOT.BIN!"
189 rm -rf $working_dir
190 exit 1
191 fi
192 echo "add to update manifest with md5sum $md5"
193 $SCRIPT_DIR/patch_manifest.pl $md5 $workingdir/image_contents/update.txt
194
195######################################################################
196# PRINT USAGE
197######################################################################
198else
199 echo "$usage"
200 exit 1
201fi
202
203######################################################################
204# Common: make the image
205######################################################################
206# make the image
207if [[ "$OSTYPE" == "darwin"* ]]; then
208 # macos
209 hdiutil makehybrid -iso -joliet -o $outputdir/output.iso $workingdir/image_contents/
210elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
211 # linux-gnu
212 genisoimage -o $outputdir/output.iso $workingdir/image_contents/
213fi
214
215# rename
216mv $outputdir/output.iso $outputdir/$outputfilename.upt
217
218# cleaning up
219rm -rf $workingdir
220
221exit 0
diff --git a/tools/hibyos_nativepatcher/patch_manifest.pl b/tools/hibyos_nativepatcher/patch_manifest.pl
new file mode 100755
index 0000000000..82c6378c65
--- /dev/null
+++ b/tools/hibyos_nativepatcher/patch_manifest.pl
@@ -0,0 +1,27 @@
1#!/usr/bin/perl
2# add bootloader info to update manifest
3# usage: ./patch_manifest.pl <md5sum> <path/to/update.txt>
4
5my $md5 = $ARGV[0];
6my $updatefile = $ARGV[1];
7my $bootloader_manif =
8"bootloader={
9 name=uboot
10 file_path=autoupdate/uboot.bin
11 md5=$md5
12}\n";
13
14# read in existing manifest
15open(FH, '<', "$updatefile");
16read(FH, my $manifest, -s FH);
17close(FH);
18
19# delete existing bootloader entry if exists
20$manifest =~ s/bootloader\s*=\s*{[^}]*}//;
21
22# add our own bootloader entry
23$manifest = "$bootloader_manif$manifest";
24
25open(FH, '>', "$updatefile");
26print FH $manifest;
27close(FH);
diff --git a/tools/rockboxdev.sh b/tools/rockboxdev.sh
index 63b1a1f5a1..f8e9ab2140 100755
--- a/tools/rockboxdev.sh
+++ b/tools/rockboxdev.sh
@@ -378,8 +378,14 @@ buildtool() {
378 $config_opt 378 $config_opt
379 elif [ "$config_opt" != "NO_CONFIGURE" ]; then 379 elif [ "$config_opt" != "NO_CONFIGURE" ]; then
380 echo "ROCKBOXDEV: $toolname/configure" 380 echo "ROCKBOXDEV: $toolname/configure"
381 cflags='-U_FORTIFY_SOURCE -fgnu89-inline -O2'
382 if [ "$tool" == "glib" ]; then
383 run_cmd "$logfile" sed -i -e 's/m4_copy/m4_copy_force/g' "$cfg_dir/m4macros/glib-gettext.m4"
384 run_cmd "$logfile" autoreconf -fiv "$cfg_dir"
385 cflags="$cflags -Wno-format-nonliteral -Wno-format-overflow"
386 fi
381 # NOTE glibc requires to be compiled with optimization 387 # NOTE glibc requires to be compiled with optimization
382 CFLAGS='-U_FORTIFY_SOURCE -fgnu89-inline -O2' CXXFLAGS="$CXXFLAGS" run_cmd "$logfile" \ 388 CFLAGS="$cflags" CXXFLAGS="$CXXFLAGS" run_cmd "$logfile" \
383 "$cfg_dir/configure" "--prefix=$prefix" \ 389 "$cfg_dir/configure" "--prefix=$prefix" \
384 --disable-docs $config_opt 390 --disable-docs $config_opt
385 fi 391 fi
diff --git a/tools/updatelang b/tools/updatelang
index 28f259bdbb..1fb594d596 100755
--- a/tools/updatelang
+++ b/tools/updatelang
@@ -402,9 +402,9 @@ foreach my $id (@langorder) {
402 402
403 my $sane = $lang{$id}{'dest'}{$tgt}; 403 my $sane = $lang{$id}{'dest'}{$tgt};
404 $sane =~ s/^~?(.*)/$1/; # Strip off leading ~ if it's there as it's not a legal character otherwise 404 $sane =~ s/^~?(.*)/$1/; # Strip off leading ~ if it's there as it's not a legal character otherwise
405 if ($sane =~ tr/"~//) { 405 if ($sane =~ tr/"~<>//) {
406 # If it has suspicious characters that are not allowed 406 # If it has suspicious characters that are not allowed
407 $lang{$id}{'notes'} .= "### The <dest> section for '$id:$tgt' has some suspicious characters (eg '~'), please double-check!\n"; 407 $lang{$id}{'notes'} .= "### The <dest> section for '$id:$tgt' has some suspicious characters (eg \",~,<,>), please double-check!\n";
408# print "#!! '$id:$tgt' suspicious characters\n"; 408# print "#!! '$id:$tgt' suspicious characters\n";
409 } 409 }
410 } 410 }
@@ -476,9 +476,9 @@ foreach my $id (@langorder) {
476 } 476 }
477 my $sane = $lang{$id}{'voice'}{$tgt}; 477 my $sane = $lang{$id}{'voice'}{$tgt};
478 $sane =~ s/^~?(.*)/$1/; # Strip off leading ~ if it's there as it's not a legal character otherwise 478 $sane =~ s/^~?(.*)/$1/; # Strip off leading ~ if it's there as it's not a legal character otherwise
479 if ($sane =~ tr/%"~://) { 479 if ($sane =~ tr/%"~:\[\]<>{}//) {
480 # Suspicious characters that are not typically voiced.. 480 # Suspicious characters that are not typically voiced..
481 $lang{$id}{'notes'} .= "### The <voice> section for '$id:$tgt' has some suspicious characters (eg '%' or '~' or ':'), please correct!\n"; 481 $lang{$id}{'notes'} .= "### The <voice> section for '$id:$tgt' has some suspicious characters (eg %,\",~,:,<,>,[,],{,}), please correct!\n";
482# print "#!! '$id:$tgt' suspicious characters\n"; 482# print "#!! '$id:$tgt' suspicious characters\n";
483 } 483 }
484 if ($lang{$id}{'voice'}{$tgt} =~ /\.\.\./) { 484 if ($lang{$id}{'voice'}{$tgt} =~ /\.\.\./) {
diff --git a/uisimulator/common/stubs.c b/uisimulator/common/stubs.c
index bce7e07227..ba5e2c9908 100644
--- a/uisimulator/common/stubs.c
+++ b/uisimulator/common/stubs.c
@@ -35,6 +35,12 @@
35 35
36static bool storage_spinning = false; 36static bool storage_spinning = false;
37 37
38#if defined(HAVE_DEVICEDATA)
39#include "devicedata.h"
40struct device_data_t device_data =
41 {.length = DEVICE_DATA_PAYLOAD_SIZE};
42#endif /* def HAVE_DEVICEDATA */
43
38int fat_startsector(void) 44int fat_startsector(void)
39{ 45{
40 return 63; 46 return 63;
diff --git a/utils/themeeditor/resources/configkeys b/utils/themeeditor/resources/configkeys
index 67b84b8e68..b4c2d6e105 100644
--- a/utils/themeeditor/resources/configkeys
+++ b/utils/themeeditor/resources/configkeys
@@ -40,6 +40,7 @@ scroll delay
40scroll step 40scroll step
41screen scroll step 41screen scroll step
42Screen Scrolls Out Of View 42Screen Scrolls Out Of View
43Disable main menu scrolling
43bidir limit 44bidir limit
44scroll paginated 45scroll paginated
45hold_lr_for_scroll_in_list 46hold_lr_for_scroll_in_list