diff options
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) | ||
2633 | static 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, | |||
331 | MENUITEM_SETTING(list_accel_wait, &global_settings.list_accel_wait, NULL); | 331 | MENUITEM_SETTING(list_accel_wait, &global_settings.list_accel_wait, NULL); |
332 | #endif /* HAVE_WHEEL_ACCELERATION */ | 332 | #endif /* HAVE_WHEEL_ACCELERATION */ |
333 | MENUITEM_SETTING(offset_out_of_view, &global_settings.offset_out_of_view, NULL); | 333 | MENUITEM_SETTING(offset_out_of_view, &global_settings.offset_out_of_view, NULL); |
334 | MENUITEM_SETTING(disable_mainmenu_scrolling, &global_settings.disable_mainmenu_scrolling, NULL); | ||
334 | MENUITEM_SETTING(screen_scroll_step, &global_settings.screen_scroll_step, NULL); | 335 | MENUITEM_SETTING(screen_scroll_step, &global_settings.screen_scroll_step, NULL); |
335 | MENUITEM_SETTING(scroll_paginated, &global_settings.scroll_paginated, NULL); | 336 | MENUITEM_SETTING(scroll_paginated, &global_settings.scroll_paginated, NULL); |
336 | MENUITEM_SETTING(list_wraparound, &global_settings.list_wraparound, NULL); | 337 | MENUITEM_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 | ||
346 | static bool view_playlist(void) | 346 | static 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 | ||
1258 | int onplay(char* file, int attr, int from_context, bool hotkey) | 1258 | int 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 | ||
28 | int onplay(char* file, int attr, int from_context, bool hotkey); | 28 | enum { |
29 | ONPLAY_NO_CUSTOMACTION, | ||
30 | ONPLAY_CUSTOMACTION_SHUFFLE_SONGS, | ||
31 | }; | ||
32 | |||
33 | int onplay(char* file, int attr, int from_context, bool hotkey, int customaction); | ||
29 | int get_onplay_context(void); | 34 | int get_onplay_context(void); |
30 | 35 | ||
31 | enum { | 36 | enum { |
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 | ||
145 | static void format_line(const struct playlist_entry* track, char* str, | 146 | static void format_line(struct playlist_entry* track, char* str, |
146 | int len); | 147 | int len); |
147 | 148 | ||
148 | static bool update_playlist(bool force); | 149 | static 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 | ||
163 | static 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 | ||
252 | static 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 | |||
230 | static int playlist_entry_load(struct playlist_entry *entry, int index, | 271 | static 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 | ||
256 | static 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) |
279 | static bool playlist_buffer_needs_reload(struct playlist_buffer* pb, | 306 | static 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 */ |
461 | static void format_line(const struct playlist_entry* track, char* str, | 490 | static 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) | |||
512 | static enum pv_onplay_result show_track_info(const struct playlist_entry *current_track) | 618 | static 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 | ||
41 | HOST_INCLUDES := $(filter-out %/libc/include,$(INCLUDES)) | 41 | HOST_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 | |||
3 | rbwrappers.c | 3 | rbwrappers.c |
4 | rbmalloc.c | 4 | rbmalloc.c |
5 | lz4tiny.c | 5 | lz4tiny.c |
6 | dummy/nullhelp.c | ||
6 | 7 | ||
7 | /* puzzles core sources */ | 8 | /* puzzles core sources */ |
8 | src/combi.c | 9 | src/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 | |||
35 | src/unequal.c | 35 | src/unequal.c |
36 | src/unruly.c | 36 | src/unruly.c |
37 | src/untangle.c | 37 | src/untangle.c |
38 | 38 | src/unfinished/slide.c | |
39 | /* Disabled for now. Fix puzzles.make and CATEGORIES to accomodate these. */ | 39 | src/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 | |||
2 | rbwrappers.c | 2 | rbwrappers.c |
3 | rbmalloc.c | 3 | rbmalloc.c |
4 | lz4tiny.c | 4 | lz4tiny.c |
5 | dummy/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 | |||
3 | const char help_text[] __attribute__((weak)) = ""; | ||
4 | const char quick_help_text[] __attribute__((weak)) = ""; | ||
5 | const unsigned short help_text_len __attribute__((weak)) = 0, quick_help_text_len __attribute__((weak)) = 0, help_text_words __attribute__((weak)) = 0; | ||
6 | struct style_text help_text_style[] __attribute__((weak)) = {}; | ||
7 | |||
8 | const 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) |
13 | extern struct style_text help_text_style[]; | 15 | extern struct style_text help_text_style[]; |
14 | #endif | 16 | #endif |
17 | |||
18 | extern 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 | ||
341 | const unsigned short help_text_len = 5480; | 341 | const unsigned short help_text_len = 5480; |
342 | const unsigned short help_text_words = 1016; | 342 | const unsigned short help_text_words = 1016; |
343 | const bool help_valid = true; | ||
343 | const char quick_help_text[] = "Find the hidden balls in the box by bouncing laser beams off them."; | 344 | const 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 | ||
359 | const unsigned short help_text_len = 5613; | 359 | const unsigned short help_text_len = 5613; |
360 | const unsigned short help_text_words = 1026; | 360 | const unsigned short help_text_words = 1026; |
361 | const bool help_valid = true; | ||
361 | const char quick_help_text[] = "Connect all the islands with a network of bridges."; | 362 | const 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 | ||
167 | const unsigned short help_text_len = 2071; | 167 | const unsigned short help_text_len = 2071; |
168 | const unsigned short help_text_words = 386; | 168 | const unsigned short help_text_words = 386; |
169 | const bool help_valid = true; | ||
169 | const char quick_help_text[] = "Pick up all the blue squares by rolling the cube over them."; | 170 | const 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 | ||
177 | const unsigned short help_text_len = 2299; | 177 | const unsigned short help_text_len = 2299; |
178 | const unsigned short help_text_words = 401; | 178 | const unsigned short help_text_words = 401; |
179 | const bool help_valid = true; | ||
179 | const char quick_help_text[] = "Tile the rectangle with a full set of dominoes."; | 180 | const 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 | ||
153 | const unsigned short help_text_len = 1927; | 153 | const unsigned short help_text_len = 1927; |
154 | const unsigned short help_text_words = 353; | 154 | const unsigned short help_text_words = 353; |
155 | const bool help_valid = true; | ||
155 | const char quick_help_text[] = "Slide the tiles around to arrange them into order."; | 156 | const 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 | ||
143 | const unsigned short help_text_len = 1821; | 143 | const unsigned short help_text_len = 1821; |
144 | const unsigned short help_text_words = 328; | 144 | const unsigned short help_text_words = 328; |
145 | const bool help_valid = true; | ||
145 | const char quick_help_text[] = "Mark every square with the area of its containing region."; | 146 | const 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 | ||
132 | const unsigned short help_text_len = 1539; | 132 | const unsigned short help_text_len = 1539; |
133 | const unsigned short help_text_words = 299; | 133 | const unsigned short help_text_words = 299; |
134 | const bool help_valid = true; | ||
134 | const char quick_help_text[] = "Flip groups of squares to light them all up at once."; | 135 | const 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 | ||
183 | const unsigned short help_text_len = 2395; | 183 | const unsigned short help_text_len = 2395; |
184 | const unsigned short help_text_words = 452; | 184 | const unsigned short help_text_words = 452; |
185 | const bool help_valid = true; | ||
185 | const char quick_help_text[] = "Turn the grid the same colour in as few flood fills as possible."; | 186 | const 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 | ||
209 | const unsigned short help_text_len = 2766; | 209 | const unsigned short help_text_len = 2766; |
210 | const unsigned short help_text_words = 498; | 210 | const unsigned short help_text_words = 498; |
211 | const bool help_valid = true; | ||
211 | const char quick_help_text[] = "Divide the grid into rotationally symmetric regions each centred on a dot."; | 212 | const 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 | ||
239 | const unsigned short help_text_len = 3506; | 239 | const unsigned short help_text_len = 3506; |
240 | const unsigned short help_text_words = 650; | 240 | const unsigned short help_text_words = 650; |
241 | const bool help_valid = true; | ||
241 | const char quick_help_text[] = "Guess the hidden combination of colours."; | 242 | const 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 | ||
180 | const unsigned short help_text_len = 2286; | 180 | const unsigned short help_text_len = 2286; |
181 | const unsigned short help_text_words = 431; | 181 | const unsigned short help_text_words = 431; |
182 | const bool help_valid = true; | ||
182 | const char quick_help_text[] = "Collect all the gems without running into any of the mines."; | 183 | const 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 | ||
261 | const unsigned short help_text_len = 3969; | 261 | const unsigned short help_text_len = 3969; |
262 | const unsigned short help_text_words = 762; | 262 | const unsigned short help_text_words = 762; |
263 | const bool help_valid = true; | ||
263 | const char quick_help_text[] = "Complete the latin square in accordance with the arithmetic clues."; | 264 | const 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 | ||
192 | const unsigned short help_text_len = 2549; | 192 | const unsigned short help_text_len = 2549; |
193 | const unsigned short help_text_words = 468; | 193 | const unsigned short help_text_words = 468; |
194 | const bool help_valid = true; | ||
194 | const char quick_help_text[] = "Place bulbs to light up all the squares."; | 195 | const 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 | ||
265 | const unsigned short help_text_len = 3584; | 265 | const unsigned short help_text_len = 3584; |
266 | const unsigned short help_text_words = 660; | 266 | const unsigned short help_text_words = 660; |
267 | const bool help_valid = true; | ||
267 | const char quick_help_text[] = "Draw a single closed loop, given clues about number of adjacent edges."; | 268 | const 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 | ||
191 | const unsigned short help_text_len = 2522; | 191 | const unsigned short help_text_len = 2522; |
192 | const unsigned short help_text_words = 439; | 192 | const unsigned short help_text_words = 439; |
193 | const bool help_valid = true; | ||
193 | const char quick_help_text[] = "Place magnets to satisfy the clues and avoid like poles touching."; | 194 | const 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 | ||
272 | const unsigned short help_text_len = 3752; | 272 | const unsigned short help_text_len = 3752; |
273 | const unsigned short help_text_words = 686; | 273 | const unsigned short help_text_words = 686; |
274 | const bool help_valid = true; | ||
274 | const char quick_help_text[] = "Colour the map so that adjacent regions are never the same colour."; | 275 | const 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 | ||
261 | const unsigned short help_text_len = 3814; | 261 | const unsigned short help_text_len = 3814; |
262 | const unsigned short help_text_words = 732; | 262 | const unsigned short help_text_words = 732; |
263 | const bool help_valid = true; | ||
263 | const char quick_help_text[] = "Find all the mines without treading on any of them."; | 264 | const 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 | ||
148 | const unsigned short help_text_len = 1673; | 148 | const unsigned short help_text_len = 1673; |
149 | const unsigned short help_text_words = 285; | 149 | const unsigned short help_text_words = 285; |
150 | const bool help_valid = true; | ||
150 | const char quick_help_text[] = "Fill in the grid given clues about number of nearby black squares."; | 151 | const 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 | ||
298 | const unsigned short help_text_len = 3919; | 298 | const unsigned short help_text_len = 3919; |
299 | const unsigned short help_text_words = 689; | 299 | const unsigned short help_text_words = 689; |
300 | const bool help_valid = true; | ||
300 | const char quick_help_text[] = "Rotate each tile to reassemble the network."; | 301 | const 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 | ||
59 | const unsigned short help_text_len = 546; | 59 | const unsigned short help_text_len = 546; |
60 | const unsigned short help_text_words = 99; | 60 | const unsigned short help_text_words = 99; |
61 | const bool help_valid = true; | ||
61 | const char quick_help_text[] = "Slide a row at a time to reassemble the network."; | 62 | const 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 | ||
141 | const unsigned short help_text_len = 1672; | 141 | const unsigned short help_text_len = 1672; |
142 | const unsigned short help_text_words = 285; | 142 | const unsigned short help_text_words = 285; |
143 | const bool help_valid = true; | ||
143 | const char quick_help_text[] = "Divide the grid into equal-sized areas in accordance with the clues."; | 144 | const 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 | ||
169 | const unsigned short help_text_len = 2167; | 169 | const unsigned short help_text_len = 2167; |
170 | const unsigned short help_text_words = 389; | 170 | const unsigned short help_text_words = 389; |
171 | const bool help_valid = true; | ||
171 | const char quick_help_text[] = "Fill in the pattern in the grid, given only the lengths of runs of black squares."; | 172 | const 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 | ||
250 | const unsigned short help_text_len = 3598; | 250 | const unsigned short help_text_len = 3598; |
251 | const unsigned short help_text_words = 659; | 251 | const unsigned short help_text_words = 659; |
252 | const bool help_valid = true; | ||
252 | const char quick_help_text[] = "Draw a single closed loop, given clues about corner and straight squares."; | 253 | const 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 | ||
149 | const unsigned short help_text_len = 1734; | 149 | const unsigned short help_text_len = 1734; |
150 | const unsigned short help_text_words = 326; | 150 | const unsigned short help_text_words = 326; |
151 | const bool help_valid = true; | ||
151 | const char quick_help_text[] = "Jump pegs over each other to remove all but one."; | 152 | const 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 | ||
171 | const unsigned short help_text_len = 2223; | 171 | const unsigned short help_text_len = 2223; |
172 | const unsigned short help_text_words = 395; | 172 | const unsigned short help_text_words = 395; |
173 | const bool help_valid = true; | ||
173 | const char quick_help_text[] = "Place black squares to limit the visible distance from each numbered cell."; | 174 | const 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 | ||
259 | const unsigned short help_text_len = 3536; | 259 | const unsigned short help_text_len = 3536; |
260 | const unsigned short help_text_words = 603; | 260 | const unsigned short help_text_words = 603; |
261 | const bool help_valid = true; | ||
261 | const char quick_help_text[] = "Divide the grid into rectangles with areas equal to the numbers."; | 262 | const 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 | ||
189 | const unsigned short help_text_len = 2492; | 189 | const unsigned short help_text_len = 2492; |
190 | const unsigned short help_text_words = 445; | 190 | const unsigned short help_text_words = 445; |
191 | const bool help_valid = true; | ||
191 | const char quick_help_text[] = "Clear the grid by removing touching groups of the same colour squares."; | 192 | const 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 | ||
223 | const unsigned short help_text_len = 3255; | 223 | const unsigned short help_text_len = 3255; |
224 | const unsigned short help_text_words = 595; | 224 | const unsigned short help_text_words = 595; |
225 | const bool help_valid = true; | ||
225 | const char quick_help_text[] = "Connect the squares into a path following the arrows."; | 226 | const 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 | ||
150 | const unsigned short help_text_len = 1780; | 150 | const unsigned short help_text_len = 1780; |
151 | const unsigned short help_text_words = 309; | 151 | const unsigned short help_text_words = 309; |
152 | const bool help_valid = true; | ||
152 | const char quick_help_text[] = "Black out the right set of duplicate numbers."; | 153 | const 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 | ||
198 | const unsigned short help_text_len = 2553; | 198 | const unsigned short help_text_len = 2553; |
199 | const unsigned short help_text_words = 454; | 199 | const unsigned short help_text_words = 454; |
200 | const bool help_valid = true; | ||
200 | const char quick_help_text[] = "Slide a row at a time to arrange the tiles into order."; | 201 | const 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 | ||
200 | const unsigned short help_text_len = 2582; | 200 | const unsigned short help_text_len = 2582; |
201 | const unsigned short help_text_words = 474; | 201 | const unsigned short help_text_words = 474; |
202 | const bool help_valid = true; | ||
202 | const char quick_help_text[] = "Draw a maze of slanting lines that matches the clues."; | 203 | const 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 | ||
384 | const unsigned short help_text_len = 6259; | 384 | const unsigned short help_text_len = 6259; |
385 | const unsigned short help_text_words = 1153; | 385 | const unsigned short help_text_words = 1153; |
386 | const bool help_valid = true; | ||
386 | const char quick_help_text[] = "Fill in the grid so that each row, column and square block contains one of every digit."; | 387 | const 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 | ||
164 | const unsigned short help_text_len = 2158; | 164 | const unsigned short help_text_len = 2158; |
165 | const unsigned short help_text_words = 401; | 165 | const unsigned short help_text_words = 401; |
166 | const bool help_valid = true; | ||
166 | const char quick_help_text[] = "Place a tent next to each tree."; | 167 | const 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 | ||
264 | const unsigned short help_text_len = 3906; | 264 | const unsigned short help_text_len = 3906; |
265 | const unsigned short help_text_words = 732; | 265 | const unsigned short help_text_words = 732; |
266 | const bool help_valid = true; | ||
266 | const char quick_help_text[] = "Complete the latin square of towers in accordance with the clues."; | 267 | const 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 | ||
151 | const unsigned short help_text_len = 1881; | 151 | const unsigned short help_text_len = 1881; |
152 | const unsigned short help_text_words = 337; | 152 | const unsigned short help_text_words = 337; |
153 | const bool help_valid = true; | ||
153 | const char quick_help_text[] = "Fill in the railway track according to the clues."; | 154 | const 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 | ||
206 | const unsigned short help_text_len = 2945; | 206 | const unsigned short help_text_len = 2945; |
207 | const unsigned short help_text_words = 549; | 207 | const unsigned short help_text_words = 549; |
208 | const bool help_valid = true; | ||
208 | const char quick_help_text[] = "Rotate the tiles around themselves to arrange them into order."; | 209 | const 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 | ||
249 | const unsigned short help_text_len = 3574; | 249 | const unsigned short help_text_len = 3574; |
250 | const unsigned short help_text_words = 660; | 250 | const unsigned short help_text_words = 660; |
251 | const bool help_valid = true; | ||
251 | const char quick_help_text[] = "Place ghosts, vampires and zombies so that the right numbers of them can be seen in mirrors."; | 252 | const 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 | ||
258 | const unsigned short help_text_len = 3954; | 258 | const unsigned short help_text_len = 3954; |
259 | const unsigned short help_text_words = 731; | 259 | const unsigned short help_text_words = 731; |
260 | const bool help_valid = true; | ||
260 | const char quick_help_text[] = "Complete the latin square in accordance with the > signs."; | 261 | const 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 | ||
146 | const unsigned short help_text_len = 1707; | 146 | const unsigned short help_text_len = 1707; |
147 | const unsigned short help_text_words = 306; | 147 | const unsigned short help_text_words = 306; |
148 | const bool help_valid = true; | ||
148 | const char quick_help_text[] = "Fill in the black and white grid to avoid runs of three."; | 149 | const 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 | ||
98 | const unsigned short help_text_len = 974; | 98 | const unsigned short help_text_len = 974; |
99 | const unsigned short help_text_words = 174; | 99 | const unsigned short help_text_words = 174; |
100 | const bool help_valid = true; | ||
100 | const char quick_help_text[] = "Reposition the points so that the lines do not cross."; | 101 | const 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)) | |||
25 | PUZZLES_ROCKS = $(addprefix $(PUZZLES_OBJDIR)/sgt-, $(notdir $(PUZZLES_GAMES_SRC:.c=.rock))) | 25 | PUZZLES_ROCKS = $(addprefix $(PUZZLES_OBJDIR)/sgt-, $(notdir $(PUZZLES_GAMES_SRC:.c=.rock))) |
26 | 26 | ||
27 | OTHER_SRC += $(PUZZLES_SRC) | 27 | OTHER_SRC += $(PUZZLES_SRC) |
28 | OTHER_INC += -I$(PUZZLES_SRCDIR)/src -I $(PUZZLES_SRCDIR) | ||
29 | |||
28 | ROCKS += $(PUZZLES_ROCKS) | 30 | ROCKS += $(PUZZLES_ROCKS) |
29 | 31 | ||
30 | PUZZLES_OPTIMIZE = -O2 | 32 | PUZZLES_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; | |||
322 | static bool clipped = false, zoom_enabled = false, view_mode = true, mouse_mode = false; | 322 | static bool clipped = false, zoom_enabled = false, view_mode = true, mouse_mode = false; |
323 | 323 | ||
324 | static int mouse_x, mouse_y; | 324 | static int mouse_x, mouse_y; |
325 | static bool mouse_dragging = false; /* for sticky mode only */ | ||
325 | 326 | ||
326 | extern bool audiobuf_available; /* defined in rbmalloc.c */ | 327 | extern 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 | ||
351 | static bool accept_input = true; | 353 | static 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 | ||
2483 | static void quick_help(void) | 2495 | static 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 | ||
2498 | static void full_help(const char *name) | 2511 | static 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 | ||
2556 | static void init_default_settings(void) | 2571 | static 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 @@ | |||
1 | puzzle(group | ||
2 | DISPLAYNAME "Group" | ||
3 | DESCRIPTION "Group theory puzzle" | ||
4 | OBJECTIVE "Complete the unfinished Cayley table of a group.") | ||
5 | solver(group ${CMAKE_SOURCE_DIR}/latin.c) | ||
6 | |||
7 | puzzle(separate | ||
8 | DISPLAYNAME "Separate" | ||
9 | DESCRIPTION "Rectangle-dividing puzzle" | ||
10 | OBJECTIVE "Partition the grid into regions containing one of each letter.") | ||
11 | |||
12 | puzzle(slide | ||
13 | DISPLAYNAME "Slide" | ||
14 | DESCRIPTION "Sliding block puzzle" | ||
15 | OBJECTIVE "Slide the blocks to let the key block out.") | ||
16 | solver(slide) | ||
17 | |||
18 | puzzle(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 | |||
27 | cliprogram(numgame numgame.c) | ||
28 | |||
29 | cliprogram(path path.c COMPILE_DEFINITIONS TEST_GEN) | ||
30 | |||
31 | export_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 @@ | |||
1 | This subdirectory contains puzzle implementations which are | ||
2 | half-written, fundamentally flawed, or in other ways unready to be | ||
3 | shipped as part of the polished Puzzles collection. | ||
4 | |||
5 | The CMake build system will _build_ all of the source in this | ||
6 | directory (to ensure it hasn't become unbuildable), but they won't be | ||
7 | included in all-in-one puzzle binaries or installed by 'make install' | ||
8 | targets. If you want to temporarily change that, you can reconfigure | ||
9 | your build by defining the CMake variable PUZZLES_ENABLE_UNFINISHED. | ||
10 | For example, | ||
11 | |||
12 | cmake . -DPUZZLES_ENABLE_UNFINISHED="group;slide" | ||
13 | |||
14 | will 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 | ||
57 | enum { DIFFLIST(ENUM) DIFFCOUNT }; | ||
58 | static char const *const group_diffnames[] = { DIFFLIST(TITLE) }; | ||
59 | static char const group_diffchars[] = DIFFLIST(ENCODE); | ||
60 | #define DIFFCONFIG DIFFLIST(CONFIG) | ||
61 | |||
62 | enum { | ||
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 | |||
84 | struct game_params { | ||
85 | int w, diff; | ||
86 | bool id; | ||
87 | }; | ||
88 | |||
89 | typedef struct group_common { | ||
90 | int refcount; | ||
91 | bool *immutable; | ||
92 | } group_common; | ||
93 | |||
94 | struct 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 | |||
119 | static 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 | |||
130 | static 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 | |||
140 | static 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 | |||
159 | static void free_params(game_params *params) | ||
160 | { | ||
161 | sfree(params); | ||
162 | } | ||
163 | |||
164 | static 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 | |||
171 | static 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 | |||
202 | static 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 | |||
215 | static 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 | |||
242 | static 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 | |||
253 | static 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 | |||
287 | static 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 | |||
304 | static 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 | |||
452 | static 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, | ||
545 | static usersolver_t const group_solvers[] = { DIFFLIST(SOLVER) }; | ||
546 | |||
547 | static 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 | |||
582 | static 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 | |||
617 | static 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 | |||
656 | struct group { | ||
657 | unsigned long autosize; | ||
658 | int order, ngens; | ||
659 | const char *gens; | ||
660 | }; | ||
661 | struct groups { | ||
662 | int ngroups; | ||
663 | const struct group *groups; | ||
664 | }; | ||
665 | |||
666 | static 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 | |||
785 | static 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 | |||
817 | static 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 | |||
838 | for 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 | ||
846 | done | ||
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 | |||
1002 | static 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 | |||
1032 | static 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 | |||
1040 | static 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 | |||
1065 | static 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 | |||
1101 | static 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 | |||
1125 | static 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 | |||
1137 | static 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 | |||
1171 | static bool game_can_format_as_text_now(const game_params *params) | ||
1172 | { | ||
1173 | return true; | ||
1174 | } | ||
1175 | |||
1176 | static 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 | |||
1209 | struct 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 | |||
1268 | static 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 | |||
1283 | static void free_ui(game_ui *ui) | ||
1284 | { | ||
1285 | sfree(ui); | ||
1286 | } | ||
1287 | |||
1288 | static 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 | |||
1305 | static void set_prefs(game_ui *ui, const config_item *cfg) | ||
1306 | { | ||
1307 | ui->pencil_keep_highlight = cfg[0].u.boolean.bval; | ||
1308 | } | ||
1309 | |||
1310 | static 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 | |||
1360 | static 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 | |||
1408 | struct 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 | |||
1417 | static 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 | |||
1526 | static 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 | |||
1538 | static 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 | |||
1736 | static 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 | |||
1855 | static 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 | |||
1865 | static void game_set_size(drawing *dr, game_drawstate *ds, | ||
1866 | const game_params *params, int tilesize) | ||
1867 | { | ||
1868 | ds->tilesize = tilesize; | ||
1869 | } | ||
1870 | |||
1871 | static 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 | |||
1905 | static 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 | |||
1929 | static 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 | |||
1939 | static 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 | |||
2112 | static 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 | |||
2238 | static 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 | |||
2244 | static 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 | |||
2253 | static 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 | |||
2261 | static int game_status(const game_state *state) | ||
2262 | { | ||
2263 | return state->completed ? +1 : 0; | ||
2264 | } | ||
2265 | |||
2266 | static bool game_timing_state(const game_state *state, game_ui *ui) | ||
2267 | { | ||
2268 | if (state->completed) | ||
2269 | return false; | ||
2270 | return true; | ||
2271 | } | ||
2272 | |||
2273 | static 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 | |||
2286 | static 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 | |||
2355 | const 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 | |||
2401 | int 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 | |||
4 | Print("/* ----- data generated by group.gap begins ----- */\n\n"); | ||
5 | Print("struct group {\n unsigned long autosize;\n"); | ||
6 | Print(" int order, ngens;\n const char *gens;\n};\n"); | ||
7 | Print("struct groups {\n int ngroups;\n"); | ||
8 | Print(" const struct group *groups;\n};\n\n"); | ||
9 | Print("static const struct group groupdata[] = {\n"); | ||
10 | offsets := [0]; | ||
11 | offset := 0; | ||
12 | for 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); | ||
87 | od; | ||
88 | Print("};\n\nstatic const struct groups groups[] = {\n"); | ||
89 | Print(" {0, NULL}, /* trivial case: 0 */\n"); | ||
90 | Print(" {0, NULL}, /* trivial case: 1 */\n"); | ||
91 | n := 2; | ||
92 | for i in [1..Size(offsets)-1] do | ||
93 | Print(" {", offsets[i+1] - offsets[i], ", groupdata+", | ||
94 | offsets[i], "}, /* ", i+1, " */\n"); | ||
95 | od; | ||
96 | Print("};\n\n/* ----- data generated by group.gap ends ----- */\n"); | ||
97 | quit; | ||
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 | |||
94 | struct sets; | ||
95 | |||
96 | struct 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 | |||
101 | struct 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 | |||
111 | struct 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 | ||
121 | struct operation; | ||
122 | struct 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 | |||
140 | struct 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 | |||
196 | struct 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 | |||
223 | static 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 | |||
234 | static 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 | |||
248 | static 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 | |||
262 | static 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 | |||
274 | static 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 | |||
293 | static 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 | |||
316 | static 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 | |||
336 | static 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 | |||
386 | static 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 | |||
426 | static 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 | |||
453 | static 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 | |||
473 | static 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; | ||
493 | found: | ||
494 | tn = a[0] * (p10 / a[1]); | ||
495 | bn = p10 - 1; | ||
496 | |||
497 | OUT(output, tn, bn); | ||
498 | return true; | ||
499 | } | ||
500 | |||
501 | static 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 | |||
519 | static 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 | |||
528 | static 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 | |||
544 | static 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 | |||
556 | static const struct operation op_add = { | ||
557 | true, "+", "+", 0, 10, 0, true, perform_add | ||
558 | }; | ||
559 | static const struct operation op_sub = { | ||
560 | true, "-", "-", 0, 10, 2, false, perform_sub | ||
561 | }; | ||
562 | static const struct operation op_mul = { | ||
563 | true, "*", "*", 0, 20, 0, true, perform_mul | ||
564 | }; | ||
565 | static const struct operation op_div = { | ||
566 | true, "/", "/", 0, 20, 2, false, perform_div | ||
567 | }; | ||
568 | static const struct operation op_xdiv = { | ||
569 | true, "/", "/", 0, 20, 2, false, perform_exact_div | ||
570 | }; | ||
571 | static const struct operation op_concat = { | ||
572 | false, "", "concat", OPFLAG_NEEDS_CONCAT | OPFLAG_KEEPS_CONCAT, | ||
573 | 1000, 0, false, perform_concat | ||
574 | }; | ||
575 | static const struct operation op_exp = { | ||
576 | true, "^", "^", 0, 30, 1, false, perform_exp | ||
577 | }; | ||
578 | static const struct operation op_factorial = { | ||
579 | true, "!", "!", OPFLAG_UNARY, 40, 0, false, perform_factorial | ||
580 | }; | ||
581 | static const struct operation op_decimal = { | ||
582 | true, ".", ".", OPFLAG_UNARY | OPFLAG_UNARYPREFIX | OPFLAG_NEEDS_CONCAT | OPFLAG_KEEPS_CONCAT, 50, 0, false, perform_decimal | ||
583 | }; | ||
584 | static const struct operation op_recur = { | ||
585 | true, "...", "recur", OPFLAG_UNARY | OPFLAG_NEEDS_CONCAT, 45, 2, false, perform_recur | ||
586 | }; | ||
587 | static const struct operation op_root = { | ||
588 | true, "v~", "root", 0, 30, 1, false, perform_root | ||
589 | }; | ||
590 | static const struct operation op_perc = { | ||
591 | true, "%", "%", OPFLAG_UNARY | OPFLAG_NEEDS_CONCAT, 45, 1, false, perform_perc | ||
592 | }; | ||
593 | static const struct operation op_gamma = { | ||
594 | true, "gamma", "gamma", OPFLAG_UNARY | OPFLAG_UNARYPREFIX | OPFLAG_FN, 1, 3, false, perform_gamma | ||
595 | }; | ||
596 | static 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 | */ | ||
604 | static const struct operation *const ops_countdown[] = { | ||
605 | &op_add, &op_mul, &op_sub, &op_xdiv, NULL | ||
606 | }; | ||
607 | static 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 | */ | ||
616 | static const struct operation *const ops_3388[] = { | ||
617 | &op_add, &op_mul, &op_sub, &op_div, NULL | ||
618 | }; | ||
619 | static 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 | */ | ||
627 | static const struct operation *const ops_four4s[] = { | ||
628 | &op_add, &op_mul, &op_sub, &op_div, &op_concat, NULL | ||
629 | }; | ||
630 | static 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 | */ | ||
638 | static 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 | }; | ||
642 | static 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 | |||
649 | static 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 | |||
679 | static 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 | |||
705 | static 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 | |||
718 | static 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 | |||
731 | static 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 | |||
776 | static 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 | |||
804 | static 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 | |||
836 | static 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 | |||
973 | static 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 | */ | ||
994 | static void print_recurse(struct sets *s, struct set *ss, int pathindex, | ||
995 | int index, int priority, int assoc, int child); | ||
996 | static 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 | } | ||
1073 | static 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 | } | ||
1092 | static 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 | */ | ||
1100 | int 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 | */ | ||
192 | static 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 | |||
238 | struct 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 | |||
249 | static 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 | |||
265 | static 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 | |||
279 | static 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 | |||
294 | static 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 | */ | ||
314 | static 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; | ||
431 | if (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 | */ | ||
550 | static 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 | */ | ||
666 | static 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 | */ | ||
774 | static 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 | |||
840 | int 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 | |||
105 | enum { | ||
106 | COL_BACKGROUND, | ||
107 | NCOLOURS | ||
108 | }; | ||
109 | |||
110 | struct game_params { | ||
111 | int w, h, k; | ||
112 | }; | ||
113 | |||
114 | struct game_state { | ||
115 | int FIXME; | ||
116 | }; | ||
117 | |||
118 | static 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 | |||
127 | static bool game_fetch_preset(int i, char **name, game_params **params) | ||
128 | { | ||
129 | return false; | ||
130 | } | ||
131 | |||
132 | static void free_params(game_params *params) | ||
133 | { | ||
134 | sfree(params); | ||
135 | } | ||
136 | |||
137 | static 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 | |||
144 | static 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 | |||
160 | static 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 | |||
167 | static config_item *game_configure(const game_params *params) | ||
168 | { | ||
169 | return NULL; | ||
170 | } | ||
171 | |||
172 | static game_params *custom_params(const config_item *cfg) | ||
173 | { | ||
174 | return NULL; | ||
175 | } | ||
176 | |||
177 | static const char *validate_params(const game_params *params, bool full) | ||
178 | { | ||
179 | return NULL; | ||
180 | } | ||
181 | |||
182 | /* ---------------------------------------------------------------------- | ||
183 | * Solver and generator. | ||
184 | */ | ||
185 | |||
186 | struct 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 | |||
222 | static 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 | |||
240 | static 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 | |||
250 | static 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 | |||
304 | static 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 | |||
323 | static 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 | |||
339 | static 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 | |||
499 | static 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 | |||
633 | static 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 | |||
653 | static const char *validate_desc(const game_params *params, const char *desc) | ||
654 | { | ||
655 | return NULL; | ||
656 | } | ||
657 | |||
658 | static 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 | |||
668 | static 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 | |||
677 | static void free_game(game_state *state) | ||
678 | { | ||
679 | sfree(state); | ||
680 | } | ||
681 | |||
682 | static char *solve_game(const game_state *state, const game_state *currstate, | ||
683 | const char *aux, const char **error) | ||
684 | { | ||
685 | return NULL; | ||
686 | } | ||
687 | |||
688 | static bool game_can_format_as_text_now(const game_params *params) | ||
689 | { | ||
690 | return true; | ||
691 | } | ||
692 | |||
693 | static char *game_text_format(const game_state *state) | ||
694 | { | ||
695 | return NULL; | ||
696 | } | ||
697 | |||
698 | static game_ui *new_ui(const game_state *state) | ||
699 | { | ||
700 | return NULL; | ||
701 | } | ||
702 | |||
703 | static void free_ui(game_ui *ui) | ||
704 | { | ||
705 | } | ||
706 | |||
707 | static void game_changed_state(game_ui *ui, const game_state *oldstate, | ||
708 | const game_state *newstate) | ||
709 | { | ||
710 | } | ||
711 | |||
712 | struct game_drawstate { | ||
713 | int tilesize; | ||
714 | int FIXME; | ||
715 | }; | ||
716 | |||
717 | static 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 | |||
724 | static game_state *execute_move(const game_state *state, const char *move) | ||
725 | { | ||
726 | return NULL; | ||
727 | } | ||
728 | |||
729 | /* ---------------------------------------------------------------------- | ||
730 | * Drawing routines. | ||
731 | */ | ||
732 | |||
733 | static 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 | |||
739 | static void game_set_size(drawing *dr, game_drawstate *ds, | ||
740 | const game_params *params, int tilesize) | ||
741 | { | ||
742 | ds->tilesize = tilesize; | ||
743 | } | ||
744 | |||
745 | static 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 | |||
755 | static 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 | |||
765 | static void game_free_drawstate(drawing *dr, game_drawstate *ds) | ||
766 | { | ||
767 | sfree(ds); | ||
768 | } | ||
769 | |||
770 | static 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 | |||
777 | static 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 | |||
783 | static 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 | |||
789 | static 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 | |||
797 | static int game_status(const game_state *state) | ||
798 | { | ||
799 | return 0; | ||
800 | } | ||
801 | |||
802 | static bool game_timing_state(const game_state *state, game_ui *ui) | ||
803 | { | ||
804 | return true; | ||
805 | } | ||
806 | |||
807 | static void game_print_size(const game_params *params, const game_ui *ui, | ||
808 | float *x, float *y) | ||
809 | { | ||
810 | } | ||
811 | |||
812 | static 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 | |||
821 | const 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 | |||
81 | enum { | ||
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 | |||
128 | struct game_params { | ||
129 | int w, h; | ||
130 | int maxmoves; | ||
131 | }; | ||
132 | |||
133 | struct game_immutable_state { | ||
134 | int refcount; | ||
135 | bool *forcefield; | ||
136 | }; | ||
137 | |||
138 | struct game_solution { | ||
139 | int nmoves; | ||
140 | int *moves; /* just like from solve_board() */ | ||
141 | int refcount; | ||
142 | }; | ||
143 | |||
144 | struct 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 | |||
158 | static 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 | |||
169 | static const struct game_params slide_presets[] = { | ||
170 | {7, 6, 25}, | ||
171 | {7, 6, -1}, | ||
172 | {8, 6, -1}, | ||
173 | }; | ||
174 | |||
175 | static 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 | |||
197 | static void free_params(game_params *params) | ||
198 | { | ||
199 | sfree(params); | ||
200 | } | ||
201 | |||
202 | static 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 | |||
209 | static 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 | |||
228 | static 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 | |||
241 | static 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 | |||
269 | static 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 | |||
280 | static 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 | |||
293 | static 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 | */ | ||
374 | struct board { | ||
375 | int w, h; | ||
376 | int dist; | ||
377 | struct board *prev; | ||
378 | unsigned char *data; | ||
379 | }; | ||
380 | |||
381 | static 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 | |||
388 | static 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 | */ | ||
412 | static 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 | |||
643 | static 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 | |||
843 | static 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 | |||
899 | static 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 | |||
1010 | static 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 | |||
1093 | static 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 | |||
1120 | static 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 | |||
1134 | static 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 | |||
1178 | static bool game_can_format_as_text_now(const game_params *params) | ||
1179 | { | ||
1180 | return true; | ||
1181 | } | ||
1182 | |||
1183 | static 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 | |||
1189 | struct 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 | |||
1198 | static 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 | |||
1213 | static void free_ui(game_ui *ui) | ||
1214 | { | ||
1215 | sfree(ui->bfs_queue); | ||
1216 | sfree(ui->reachable); | ||
1217 | sfree(ui); | ||
1218 | } | ||
1219 | |||
1220 | static 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 | |||
1236 | struct game_drawstate { | ||
1237 | int tilesize; | ||
1238 | int w, h; | ||
1239 | unsigned long *grid; /* what's currently displayed */ | ||
1240 | }; | ||
1241 | |||
1242 | static 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 | |||
1419 | static 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 | |||
1459 | static 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 | |||
1596 | static 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 | |||
1607 | static void game_set_size(drawing *dr, game_drawstate *ds, | ||
1608 | const game_params *params, int tilesize) | ||
1609 | { | ||
1610 | ds->tilesize = tilesize; | ||
1611 | } | ||
1612 | |||
1613 | static 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 | |||
1620 | static 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 | |||
1668 | static 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 | |||
1684 | static 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 | ||
1724 | static 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 | |||
1793 | static 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 | |||
1873 | static 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 | |||
2007 | static 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 | |||
2086 | static 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 | |||
2115 | static 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 | |||
2267 | static 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 | |||
2273 | static 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 | |||
2282 | static 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 | |||
2290 | static int game_status(const game_state *state) | ||
2291 | { | ||
2292 | return state->completed ? +1 : 0; | ||
2293 | } | ||
2294 | |||
2295 | static bool game_timing_state(const game_state *state, game_ui *ui) | ||
2296 | { | ||
2297 | return true; | ||
2298 | } | ||
2299 | |||
2300 | static void game_print_size(const game_params *params, const game_ui *ui, | ||
2301 | float *x, float *y) | ||
2302 | { | ||
2303 | } | ||
2304 | |||
2305 | static 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 | |||
2314 | const 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 | |||
2360 | int 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 | |||
111 | enum { | ||
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 | |||
127 | struct game_params { | ||
128 | int w, h; | ||
129 | /* | ||
130 | * FIXME: a parameter involving degree of filling in? | ||
131 | */ | ||
132 | }; | ||
133 | |||
134 | struct game_state { | ||
135 | game_params p; | ||
136 | unsigned char *grid; | ||
137 | int px, py; | ||
138 | bool completed; | ||
139 | }; | ||
140 | |||
141 | static 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 | |||
151 | static void free_params(game_params *params) | ||
152 | { | ||
153 | sfree(params); | ||
154 | } | ||
155 | |||
156 | static 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 | |||
163 | static const struct game_params sokoban_presets[] = { | ||
164 | { 12, 10 }, | ||
165 | { 16, 12 }, | ||
166 | { 20, 16 }, | ||
167 | }; | ||
168 | |||
169 | static 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 | |||
188 | static 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 | |||
198 | static 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 | |||
207 | static 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 | |||
230 | static 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 | |||
240 | static 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 | |||
306 | static 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 | |||
734 | static 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 | |||
809 | static 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 | |||
846 | static 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 | |||
884 | static 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 | |||
899 | static void free_game(game_state *state) | ||
900 | { | ||
901 | sfree(state->grid); | ||
902 | sfree(state); | ||
903 | } | ||
904 | |||
905 | static char *solve_game(const game_state *state, const game_state *currstate, | ||
906 | const char *aux, const char **error) | ||
907 | { | ||
908 | return NULL; | ||
909 | } | ||
910 | |||
911 | static bool game_can_format_as_text_now(const game_params *params) | ||
912 | { | ||
913 | return true; | ||
914 | } | ||
915 | |||
916 | static char *game_text_format(const game_state *state) | ||
917 | { | ||
918 | return NULL; | ||
919 | } | ||
920 | |||
921 | static game_ui *new_ui(const game_state *state) | ||
922 | { | ||
923 | return NULL; | ||
924 | } | ||
925 | |||
926 | static void free_ui(game_ui *ui) | ||
927 | { | ||
928 | } | ||
929 | |||
930 | static void game_changed_state(game_ui *ui, const game_state *oldstate, | ||
931 | const game_state *newstate) | ||
932 | { | ||
933 | } | ||
934 | |||
935 | struct 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 | */ | ||
955 | static 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 | |||
1036 | static 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 | |||
1093 | static 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 | |||
1180 | static 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 | |||
1191 | static void game_set_size(drawing *dr, game_drawstate *ds, | ||
1192 | const game_params *params, int tilesize) | ||
1193 | { | ||
1194 | ds->tilesize = tilesize; | ||
1195 | } | ||
1196 | |||
1197 | static 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 | |||
1249 | static 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 | |||
1265 | static void game_free_drawstate(drawing *dr, game_drawstate *ds) | ||
1266 | { | ||
1267 | sfree(ds->grid); | ||
1268 | sfree(ds); | ||
1269 | } | ||
1270 | |||
1271 | static 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 | |||
1332 | static 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 | |||
1389 | static 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 | |||
1395 | static 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 | |||
1404 | static 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 | |||
1412 | static int game_status(const game_state *state) | ||
1413 | { | ||
1414 | return state->completed ? +1 : 0; | ||
1415 | } | ||
1416 | |||
1417 | static bool game_timing_state(const game_state *state, game_ui *ui) | ||
1418 | { | ||
1419 | return true; | ||
1420 | } | ||
1421 | |||
1422 | static void game_print_size(const game_params *params, const game_ui *ui, | ||
1423 | float *x, float *y) | ||
1424 | { | ||
1425 | } | ||
1426 | |||
1427 | static 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 | |||
1436 | const 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 | ||
81 | enum { | ||
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 |
82 | enum { | 89 | enum { |
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 | ||
65 | static int tagtree_play_folder(struct tree_context* c); | 67 | static int tagtree_play_folder(struct tree_context* c); |
66 | 68 | ||
69 | /* reuse of tagtree data after tagtree_play_folder() */ | ||
70 | static 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 | ||
76 | static struct tagentry* tagtree_get_entry(struct tree_context *c, int id); | 83 | static 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 | ||
80 | enum table { | 87 | enum 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 | ||
87 | static const struct id3_to_search_mapping { | 94 | static 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 | ||
287 | static 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 | |||
279 | static void* tagtree_alloc(size_t size) | 301 | static 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 | ||
1797 | int tagtree_load(struct tree_context* c) | 1832 | int 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 | */ |
1861 | int tagtree_enter(struct tree_context* c, bool is_visible) | 1912 | int 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 */ |
2060 | void tagtree_exit(struct tree_context* c, bool is_visible) | 2113 | void 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 | ||
2159 | int tagtree_get_custom_action(struct tree_context* c) | ||
2160 | { | ||
2161 | return tagtree_get_entry(c, c->selected_item)->customaction; | ||
2162 | } | ||
2163 | |||
2164 | static 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 | */ | ||
2177 | static 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 | ||
2106 | static bool insert_all_playlist(struct tree_context *c, | 2196 | static 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, | |||
2196 | static bool goto_allsubentries(int newtable) | 2363 | static 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 | ||
2209 | static void reset_tc_to_prev(int dirlevel, int selected_item) | 2376 | static 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 | ||
2343 | static int tagtree_play_folder(struct tree_context* c) | 2510 | static 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); | |||
45 | int tagtree_get_attr(struct tree_context* c); | 45 | int tagtree_get_attr(struct tree_context* c); |
46 | int tagtree_get_icon(struct tree_context* c); | 46 | int tagtree_get_icon(struct tree_context* c); |
47 | int tagtree_get_filename(struct tree_context* c, char *buf, int buflen); | 47 | int tagtree_get_filename(struct tree_context* c, char *buf, int buflen); |
48 | int tagtree_get_custom_action(struct tree_context* c); | ||
48 | bool tagtree_get_subentry_filename(char *buf, size_t bufsize); | 49 | bool tagtree_get_subentry_filename(char *buf, size_t bufsize); |
49 | bool tagtree_subentries_do_action(bool (*action_cb)(const char *file_name)); | 50 | bool 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 \ |
108 | sn_no=00000000000000000000000000000000 bt_mac=xxxxxxxxxxxx wifi_mac=xxxxxxxxxxxx rw" | 108 | sn_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 | |||
726 | Medu Hedan | 726 | Medu Hedan |
727 | Uwe Schächterle | 727 | Uwe Schächterle |
728 | Hoseok Seo | 728 | Hoseok Seo |
729 | Paul Sauro | ||
730 | Dmitry Prozorov | ||
729 | 731 | ||
730 | The libmad team | 732 | The libmad team |
731 | The wavpack team | 733 | The 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) | ||
66 | common/devicedata.c | ||
67 | #endif | ||
68 | |||
65 | #ifdef HAVE_SDL | 69 | #ifdef HAVE_SDL |
66 | target/hosted/sdl/button-sdl.c | 70 | target/hosted/sdl/button-sdl.c |
67 | target/hosted/sdl/kernel-sdl.c | 71 | target/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 | ||
28 | void verify_device_data(void) INIT_ATTR; | ||
29 | void 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) | ||
49 | void __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 | */ | ||
62 | bool 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 */ | ||
46 | struct 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 | |||
70 | void fill_devicedata(struct device_data_t *data); | ||
71 | bool write_devicedata(unsigned char* buf, int len); | ||
72 | #ifndef BOOTLOADER | ||
73 | extern struct device_data_t device_data; | ||
74 | |||
75 | void 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 | ||
85 | device_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 | ||
32 | static 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 */ | ||
42 | static 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 */ | ||
159 | static 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 |
40 | int __cpu_idle_avg = 0; | 45 | int __cpu_idle_avg = 0; |
41 | int __cpu_idle_cur = 0; | 46 | int __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) | ||
90 | void 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() */ |
85 | void system_init(void) | 104 | void 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 | |||
12 | INCLUDES += -I$(APPSDIR) | 12 | INCLUDES += -I$(APPSDIR) |
13 | SRC += $(call preprocess, $(APPSDIR)/SOURCES) | 13 | SRC += $(call preprocess, $(APPSDIR)/SOURCES) |
14 | 14 | ||
15 | LDSDEP := $(FIRMDIR)/export/cpu.h $(FIRMDIR)/export/config/$(MODELNAME).h | 15 | LDSDEP := $(FIRMDIR)/export/cpu.h $(FIRMDIR)/export/config.h |
16 | 16 | ||
17 | BOOTLDS := $(FIRMDIR)/target/$(CPU)/$(MANUFACTURER)/boot.lds | 17 | BOOTLDS := $(FIRMDIR)/target/$(CPU)/$(MANUFACTURER)/boot.lds |
18 | BOOTLINK := $(BUILDDIR)/boot.link | 18 | BOOTLINK := $(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 @@ | |||
1 | We will install the bootloader with the original firmware's recovery | ||
2 | mode. 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} | ||
13 | Determine what hardware version your player is. Go to \fname{System Settings --> About The Player --> Version} and reference | ||
14 | the list below. hw1, hw1.5, and hw2 players all use the same update | ||
15 | file (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, | ||
18 | for example if the firmware version is newer than what is listed here, | ||
19 | we cannot be sure that the hardware is the same. The best thing to do is | ||
20 | contact the manufacturer and ask them two things: (1) for an update file | ||
21 | of your version, and (2) if a player with the most recent version listed | ||
22 | here can be upgraded to the firmware version on your player. If they say | ||
23 | yes, we can be more certain that the hardware has not changed. These lists | ||
24 | may 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 | |||
47 | Download 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} | ||
52 | Place 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} | ||
56 | is present on your SD card.} | ||
57 | |||
58 | Don't forget to safely eject/unmount your player. | ||
59 | |||
60 | \subsubsection{Run Firmware Update}\label{ref:run_firmware_update} | ||
61 | In 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, | ||
64 | this 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 | |||
137 | to use it just do not do the initial build of the database and do not load it | 137 | to use it just do not do the initial build of the database and do not load it |
138 | to RAM.}% | 138 | to RAM.}% |
139 | 139 | ||
140 | If your total amount of music tracks exceeds the value of the | ||
141 | \setting{Max Playlist Size} setting (\setting{Settings $\rightarrow$ General | ||
142 | Settings $\rightarrow$ System $\rightarrow$ Limits}), using the database | ||
143 | will be your only way to shuffle between all songs from your music library. | ||
144 | Any view on the database browser that exceeds the maximum value of this option | ||
145 | will be automatically adjusted and randomized to fit into the available space | ||
146 | when you will create a dynamic playlist from the view. | ||
147 | Using the database browser is recommended if you shuffle regularly between a lot of | ||
148 | songs rather than increasing your limit, so you will get the best possible performance | ||
149 | on this action. | ||
150 | |||
151 | \note{For your convenience, a shortcut button "Shuffle Songs" is available directly | ||
152 | from the \setting{Database} menu to create and start a mix with all of your | ||
153 | existing 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) | ||
1679 | EOF | 1681 | EOF |
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 | |||
6 | usage="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 | ||
23 | if [[ "$OSTYPE" == "darwin"* ]]; then | ||
24 | echo "$OSTYPE DETECTED" | ||
25 | elif [[ "$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 | ||
35 | else | ||
36 | echo "SCRIPT NOT IMPLEMENTED ON $OSTYPE YET!" | ||
37 | exit 1 | ||
38 | fi | ||
39 | |||
40 | # make sure we can find patch_manifest | ||
41 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) | ||
42 | if !(which $SCRIPT_DIR/patch_manifest.pl > /dev/null); then | ||
43 | echo "couldn't find patch_manifest.pl!" | ||
44 | exit 1 | ||
45 | fi | ||
46 | |||
47 | ########################################################################### | ||
48 | # MKRBINSTALL | ||
49 | ########################################################################### | ||
50 | if [[ "$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 | ########################################################################### | ||
143 | elif [[ "$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 | ###################################################################### | ||
198 | else | ||
199 | echo "$usage" | ||
200 | exit 1 | ||
201 | fi | ||
202 | |||
203 | ###################################################################### | ||
204 | # Common: make the image | ||
205 | ###################################################################### | ||
206 | # make the image | ||
207 | if [[ "$OSTYPE" == "darwin"* ]]; then | ||
208 | # macos | ||
209 | hdiutil makehybrid -iso -joliet -o $outputdir/output.iso $workingdir/image_contents/ | ||
210 | elif [[ "$OSTYPE" == "linux-gnu"* ]]; then | ||
211 | # linux-gnu | ||
212 | genisoimage -o $outputdir/output.iso $workingdir/image_contents/ | ||
213 | fi | ||
214 | |||
215 | # rename | ||
216 | mv $outputdir/output.iso $outputdir/$outputfilename.upt | ||
217 | |||
218 | # cleaning up | ||
219 | rm -rf $workingdir | ||
220 | |||
221 | exit 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 | |||
5 | my $md5 = $ARGV[0]; | ||
6 | my $updatefile = $ARGV[1]; | ||
7 | my $bootloader_manif = | ||
8 | "bootloader={ | ||
9 | name=uboot | ||
10 | file_path=autoupdate/uboot.bin | ||
11 | md5=$md5 | ||
12 | }\n"; | ||
13 | |||
14 | # read in existing manifest | ||
15 | open(FH, '<', "$updatefile"); | ||
16 | read(FH, my $manifest, -s FH); | ||
17 | close(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 | |||
25 | open(FH, '>', "$updatefile"); | ||
26 | print FH $manifest; | ||
27 | close(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 | ||
36 | static bool storage_spinning = false; | 36 | static bool storage_spinning = false; |
37 | 37 | ||
38 | #if defined(HAVE_DEVICEDATA) | ||
39 | #include "devicedata.h" | ||
40 | struct device_data_t device_data = | ||
41 | {.length = DEVICE_DATA_PAYLOAD_SIZE}; | ||
42 | #endif /* def HAVE_DEVICEDATA */ | ||
43 | |||
38 | int fat_startsector(void) | 44 | int 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 | |||
40 | scroll step | 40 | scroll step |
41 | screen scroll step | 41 | screen scroll step |
42 | Screen Scrolls Out Of View | 42 | Screen Scrolls Out Of View |
43 | Disable main menu scrolling | ||
43 | bidir limit | 44 | bidir limit |
44 | scroll paginated | 45 | scroll paginated |
45 | hold_lr_for_scroll_in_list | 46 | hold_lr_for_scroll_in_list |