summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolas Pennequin <nicolas.pennequin@free.fr>2007-04-04 14:41:40 +0000
committerNicolas Pennequin <nicolas.pennequin@free.fr>2007-04-04 14:41:40 +0000
commitab90d58801e95d01dc286a8919c7d5823d81ec41 (patch)
treed0ea19647fd72516b00bbeabb04fcc36610f530d
parent15e2aecd8f02d8c693f9413a3669b5be52a728bf (diff)
downloadrockbox-ab90d58801e95d01dc286a8919c7d5823d81ec41.tar.gz
rockbox-ab90d58801e95d01dc286a8919c7d5823d81ec41.zip
Introducing the WPS tokenizer !
When a WPS file is loaded, it is parsed to an array of tokens, which allows more efficient displaying. More info on the tracker entry : FS #6862. The parsing code is completely independant and is all in wps_parser.c. The displaying part stays in gwps-common.c. Debugging code is provided (with the right ifdefs) and is disabled by default. Overall, the code should be easier to read and maintain. Adding new WPS tags is made quite trivial. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@13019 a1c6a512-1295-4272-9138-f99709370657
-rw-r--r--apps/SOURCES2
-rw-r--r--apps/gui/gwps-common.c3644
-rw-r--r--apps/gui/gwps-common.h12
-rw-r--r--apps/gui/gwps.c141
-rw-r--r--apps/gui/gwps.h206
-rw-r--r--apps/gui/wps_debug.c407
-rw-r--r--apps/gui/wps_parser.c957
-rw-r--r--apps/lang/english.lang8
8 files changed, 3042 insertions, 2335 deletions
diff --git a/apps/SOURCES b/apps/SOURCES
index 4a4d83d0e6..7e6f84b03d 100644
--- a/apps/SOURCES
+++ b/apps/SOURCES
@@ -58,6 +58,8 @@ gui/splash.c
58gui/statusbar.c 58gui/statusbar.c
59gui/textarea.c 59gui/textarea.c
60gui/yesno.c 60gui/yesno.c
61gui/wps_debug.c
62gui/wps_parser.c
61 63
62#ifdef HAVE_LCD_CHARCELLS 64#ifdef HAVE_LCD_CHARCELLS
63player/icons.c 65player/icons.c
diff --git a/apps/gui/gwps-common.c b/apps/gui/gwps-common.c
index bce213ab4d..2bd2e4c617 100644
--- a/apps/gui/gwps-common.c
+++ b/apps/gui/gwps-common.c
@@ -7,7 +7,7 @@
7 * \/ \/ \/ \/ \/ 7 * \/ \/ \/ \/ \/
8 * $Id$ 8 * $Id$
9 * 9 *
10 * Copyright (C) 2002 Björn Stenberg 10 * Copyright (C) 2007 Nicolas Pennequin
11 * 11 *
12 * All files in this archive are subject to the GNU General Public License. 12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement. 13 * See the file COPYING in the source tree root for full license agreement.
@@ -56,16 +56,11 @@
56#include "action.h" 56#include "action.h"
57#include "cuesheet.h" 57#include "cuesheet.h"
58 58
59#ifdef HAVE_LCD_CHARCELLS
60static bool draw_player_progress(struct gui_wps *gwps);
61static void draw_player_fullbar(struct gui_wps *gwps,
62 char* buf, int buf_size);
63#endif
64
65#define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */ 59#define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */
66 /* 3% of 30min file == 54s step size */ 60 /* 3% of 30min file == 54s step size */
67#define MIN_FF_REWIND_STEP 500 61#define MIN_FF_REWIND_STEP 500
68 62
63#if 0
69/* Skip leading UTF-8 BOM, if present. */ 64/* Skip leading UTF-8 BOM, if present. */
70static char* skip_utf8_bom(char* buf) 65static char* skip_utf8_bom(char* buf)
71{ 66{
@@ -78,287 +73,14 @@ static char* skip_utf8_bom(char* buf)
78 73
79 return buf; 74 return buf;
80} 75}
81
82/*
83 * returns the image_id between
84 * a..z and A..Z
85 */
86#ifdef HAVE_LCD_BITMAP
87static int get_image_id(int c)
88{
89 if(c >= 'a' && c <= 'z')
90 return c - 'a';
91 else if(c >= 'A' && c <= 'Z')
92 return c - 'A' + 26;
93 else
94 return -1;
95}
96#endif
97
98/*
99 * parse the given buffer for following static tags:
100 * %x - load image for always display
101 * %X - load backdrop image
102 * %xl - preload image
103 * %we - enable statusbar on wps regardless of the global setting
104 * %wd - disable statusbar on wps regardless of the global setting
105 * and also for:
106 * # - a comment line
107 *
108 * it returns true if one of these tags is found and handled
109 * false otherwise
110 */
111bool wps_data_preload_tags(struct wps_data *data, char *buf,
112 const char *bmpdir, size_t bmpdirlen)
113{
114 if(!data || !buf) return false;
115
116 char c;
117#ifndef HAVE_LCD_BITMAP
118 /* no bitmap-lcd == no bitmap loading */
119 (void)bmpdir;
120 (void)bmpdirlen;
121#endif
122 buf = skip_utf8_bom(buf);
123
124 if(*buf == '#')
125 return true;
126 if('%' != *buf)
127 return false;
128 buf++;
129
130 c = *buf;
131 switch (c)
132 {
133#ifdef HAVE_LCD_BITMAP
134 case 'w':
135 /*
136 * if tag found then return because these two tags must be on
137 * must be on their own line
138 */
139 if(*(buf+1) == 'd' || *(buf+1) == 'e')
140 {
141 data->wps_sb_tag = true;
142 if( *(buf+1) == 'e' )
143 data->show_sb_on_wps = true;
144 return true;
145 }
146 break;
147
148#if LCD_DEPTH > 1
149 case 'X':
150 /* Backdrop image - must be the same size as the LCD */
151 {
152 char *ptr = buf+2;
153 char *pos = NULL;
154 char imgname[MAX_PATH];
155
156 /* format: %X|filename.bmp| */
157
158 /* get filename */
159 pos = strchr(ptr, '|');
160 if ((pos - ptr) <
161 (int)sizeof(imgname)-ROCKBOX_DIR_LEN-2)
162 {
163 memcpy(imgname, bmpdir, bmpdirlen);
164 imgname[bmpdirlen] = '/';
165 memcpy(&imgname[bmpdirlen+1],
166 ptr, pos - ptr);
167 imgname[bmpdirlen+1+pos-ptr] = 0;
168 }
169 else
170 {
171 /* filename too long */
172 imgname[0] = 0;
173 }
174
175 /* load the image */
176 return load_wps_backdrop(imgname);
177 }
178
179 break;
180#endif
181
182 case 'P':
183 /* progress bar image */
184 {
185 int ret = 0;
186 char *ptr = buf+2;
187 char *pos = NULL;
188 char imgname[MAX_PATH];
189
190 /* format: %P|filename.bmp| */
191 {
192 /* get filename */
193 pos = strchr(ptr, '|');
194 if ((pos - ptr) <
195 (int)sizeof(imgname)-ROCKBOX_DIR_LEN-2)
196 {
197 memcpy(imgname, bmpdir, bmpdirlen);
198 imgname[bmpdirlen] = '/';
199 memcpy(&imgname[bmpdirlen+1],
200 ptr, pos - ptr);
201 imgname[bmpdirlen+1+pos-ptr] = 0;
202 }
203 else
204 /* filename too long */
205 imgname[0] = 0;
206
207 ptr = pos+1;
208
209 /* load the image */
210 data->progressbar.bm.data=data->img_buf_ptr;
211 ret = read_bmp_file(imgname, &data->progressbar.bm,
212 data->img_buf_free,
213 FORMAT_ANY|FORMAT_TRANSPARENT);
214
215 if (ret > 0)
216 {
217#if LCD_DEPTH == 16
218 if (ret % 2) ret++;
219 /* Always consume an even number of bytes */
220#endif
221
222 data->img_buf_ptr += ret;
223 data->img_buf_free -= ret;
224
225 if (data->progressbar.bm.width <= LCD_WIDTH) {
226 data->progressbar.have_bitmap_pb=true;
227 return true;
228 } else
229 return false;
230 }
231
232 }
233 }
234
235 break;
236
237 case 'x':
238 /* Preload images so the %xd# tag can display it */
239 {
240 int ret = 0;
241 int n;
242 char *ptr = buf+1;
243 char *pos = NULL;
244 char imgname[MAX_PATH];
245 char qual = *ptr;
246
247 if (qual == 'l' || qual == '|') /* format:
248 %x|n|filename.bmp|x|y|
249 or
250 %xl|n|filename.bmp|x|y|
251 */
252 {
253 ptr = strchr(ptr, '|') + 1;
254 pos = strchr(ptr, '|');
255 if (pos)
256 {
257 /* get the image ID */
258 n = get_image_id(*ptr);
259
260 if(n < 0 || n >= MAX_IMAGES)
261 {
262 /* Skip the rest of the line */
263 while(*buf != '\n')
264 buf++;
265 return false;
266 }
267 ptr = pos+1;
268
269 /* check the image number and load state */
270 if (data->img[n].loaded)
271 {
272 /* Skip the rest of the line */
273 while(*buf != '\n')
274 buf++;
275 return false;
276 }
277 else
278 {
279 /* get filename */
280 pos = strchr(ptr, '|');
281
282 if (pos == NULL)
283 return false;
284
285 if ((pos - ptr) <
286 (int)sizeof(imgname)-ROCKBOX_DIR_LEN-2)
287 {
288 memcpy(imgname, bmpdir, bmpdirlen);
289 imgname[bmpdirlen] = '/';
290 memcpy(&imgname[bmpdirlen+1],
291 ptr, pos - ptr);
292 imgname[bmpdirlen+1+pos-ptr] = 0;
293 }
294 else
295 /* filename too long */
296 imgname[0] = 0;
297
298 ptr = pos+1;
299
300 /* get x-position */
301 pos = strchr(ptr, '|');
302 if (pos)
303 data->img[n].x = atoi(ptr);
304 else
305 {
306 /* weird syntax, bail out */
307 buf++;
308 return false;
309 }
310
311 /* get y-position */
312 ptr = pos+1;
313 pos = strchr(ptr, '|');
314 if (pos)
315 data->img[n].y = atoi(ptr);
316 else
317 {
318 /* weird syntax, bail out */
319 buf++;
320 return false;
321 }
322
323 /* load the image */
324 data->img[n].bm.data = data->img_buf_ptr;
325 ret = read_bmp_file(imgname, &data->img[n].bm,
326 data->img_buf_free,
327 FORMAT_ANY|FORMAT_TRANSPARENT);
328
329 if (ret > 0)
330 {
331#if LCD_DEPTH == 16
332 if (ret % 2) ret++;
333 /* Always consume an even number of bytes */
334#endif 76#endif
335 77
336 data->img_buf_ptr += ret;
337 data->img_buf_free -= ret;
338 data->img[n].loaded = true;
339 if(qual == '|')
340 data->img[n].always_display = true;
341 }
342 return true;
343 }
344 }
345 }
346 }
347
348 break;
349#endif
350 }
351 /* no of these tags found */
352 return false;
353}
354
355
356/* draws the statusbar on the given wps-screen */ 78/* draws the statusbar on the given wps-screen */
357#ifdef HAVE_LCD_BITMAP 79#ifdef HAVE_LCD_BITMAP
358static void gui_wps_statusbar_draw(struct gui_wps *wps, bool force) 80void gui_wps_statusbar_draw(struct gui_wps *wps, bool force)
359{ 81{
360 bool draw = global_settings.statusbar; 82 bool draw = global_settings.statusbar;
361 83
362 if(wps->data->wps_sb_tag 84 if(wps->data->wps_sb_tag
363 && wps->data->show_sb_on_wps) 85 && wps->data->show_sb_on_wps)
364 draw = true; 86 draw = true;
@@ -372,1235 +94,449 @@ static void gui_wps_statusbar_draw(struct gui_wps *wps, bool force)
372 gui_statusbar_draw((wps)->statusbar, (force)) 94 gui_statusbar_draw((wps)->statusbar, (force))
373#endif 95#endif
374 96
375/* Extract a part from a path. 97/* fades the volume */
376 * 98void fade(bool fade_in)
377 * buf - buffer extract part to.
378 * buf_size - size of buffer.
379 * path - path to extract from.
380 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
381 * parent of parent, etc.
382 *
383 * Returns buf if the desired level was found, NULL otherwise.
384 */
385static char* get_dir(char* buf, int buf_size, const char* path, int level)
386{ 99{
387 const char* sep; 100 int fp_global_vol = global_settings.volume << 8;
388 const char* last_sep; 101 int fp_min_vol = sound_min(SOUND_VOLUME) << 8;
389 int len; 102 int fp_step = (fp_global_vol - fp_min_vol) / 30;
390 103
391 sep = path + strlen(path); 104 if (fade_in) {
392 last_sep = sep; 105 /* fade in */
106 int fp_volume = fp_min_vol;
393 107
394 while (sep > path) 108 /* zero out the sound */
395 { 109 sound_set_volume(fp_min_vol >> 8);
396 if ('/' == *(--sep))
397 {
398 if (!level)
399 {
400 break;
401 }
402 110
403 level--; 111 sleep(HZ/10); /* let audio thread run */
404 last_sep = sep - 1; 112 audio_resume();
113
114 while (fp_volume < fp_global_vol - fp_step) {
115 fp_volume += fp_step;
116 sound_set_volume(fp_volume >> 8);
117 sleep(1);
405 } 118 }
119 sound_set_volume(global_settings.volume);
406 } 120 }
121 else {
122 /* fade out */
123 int fp_volume = fp_global_vol;
407 124
408 if (level || (last_sep <= sep)) 125 while (fp_volume > fp_min_vol + fp_step) {
409 { 126 fp_volume -= fp_step;
410 return NULL; 127 sound_set_volume(fp_volume >> 8);
411 } 128 sleep(1);
129 }
130 audio_pause();
131#ifndef SIMULATOR
132 /* let audio thread run and wait for the mas to run out of data */
133 while (!mp3_pause_done())
134#endif
135 sleep(HZ/10);
412 136
413 len = MIN(last_sep - sep, buf_size - 1); 137 /* reset volume to what it was before the fade */
414 strncpy(buf, sep + 1, len); 138 sound_set_volume(global_settings.volume);
415 buf[len] = 0; 139 }
416 return buf;
417} 140}
418 141
419/* Get the tag specified by the two characters at fmt. 142/* set volume */
420 * 143void setvol(void)
421 * cid3 - ID3 data to get tag values from.
422 * nid3 - next-song ID3 data to get tag values from.
423 * tag - string (of two characters) specifying the tag to get.
424 * buf - buffer to certain tags, such as track number, play time or
425 * directory name.
426 * buf_size - size of buffer.
427 * flags - returns the type of the line. See constants i wps-display.h
428 *
429 * Returns the tag. NULL indicates the tag wasn't available.
430 */
431static char* get_tag(struct wps_data* wps_data,
432 struct mp3entry* cid3,
433 struct mp3entry* nid3,
434 const char* tag,
435 char* buf,
436 int buf_size,
437 unsigned char* tag_len,
438 unsigned short* subline_time_mult,
439 unsigned char* flags,
440 int *intval)
441{ 144{
442 struct mp3entry *id3 = cid3; /* default to current song */ 145 if (global_settings.volume < sound_min(SOUND_VOLUME))
443 int limit = *intval; 146 global_settings.volume = sound_min(SOUND_VOLUME);
444#ifndef HAVE_LCD_CHARCELLS 147 if (global_settings.volume > sound_max(SOUND_VOLUME))
445 (void)wps_data; 148 global_settings.volume = sound_max(SOUND_VOLUME);
446#endif 149 sound_set_volume(global_settings.volume);
447 if ((0 == tag[0]) || (0 == tag[1])) 150 settings_save();
448 { 151}
449 *tag_len = 0; 152/* return true if screen restore is needed
450 return NULL; 153 return false otherwise
451 } 154*/
452 155bool update_onvol_change(struct gui_wps * gwps)
453 *tag_len = 2; 156{
454 157 gui_wps_statusbar_draw(gwps, false);
455 *intval = 0; 158 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
456
457 switch (tag[0])
458 {
459 case 'I': /* ID3 Information */
460 id3 = nid3; /* display next-song data */
461 *flags |= WPS_REFRESH_DYNAMIC;
462 if(!id3)
463 return NULL; /* no such info (yet) */
464 /* fall-through */
465 case 'i': /* ID3 Information */
466 *flags |= WPS_REFRESH_STATIC;
467 switch (tag[1])
468 {
469 case 't': /* ID3 Title */
470 return id3->title;
471
472 case 'a': /* ID3 Artist */
473 return id3->artist;
474
475 case 'n': /* ID3 Track Number */
476 if (id3->track_string)
477 return id3->track_string;
478
479 if (id3->tracknum) {
480 snprintf(buf, buf_size, "%d", id3->tracknum);
481 return buf;
482 }
483 return NULL;
484
485 case 'd': /* ID3 Album/Disc */
486 return id3->album;
487
488 case 'c': /* ID3 Composer */
489 return id3->composer;
490
491 case 'C': /* ID3 Comment */
492 return id3->comment;
493
494 case 'A': /* ID3 Albumartist */
495 return id3->albumartist;
496
497 case 'y': /* year */
498 if( id3->year_string )
499 return id3->year_string;
500
501 if (id3->year) {
502 snprintf(buf, buf_size, "%d", id3->year);
503 return buf;
504 }
505 return NULL;
506
507 case 'g': /* genre */
508 return id3->genre_string;
509
510 case 'v': /* id3 version */
511 switch (id3->id3version)
512 {
513 case ID3_VER_1_0:
514 return "1";
515
516 case ID3_VER_1_1:
517 return "1.1";
518
519 case ID3_VER_2_2:
520 return "2.2";
521
522 case ID3_VER_2_3:
523 return "2.3";
524
525 case ID3_VER_2_4:
526 return "2.4";
527
528 default:
529 return NULL;
530 }
531 }
532 break;
533
534 case 'F': /* File Information */
535 id3 = nid3;
536 *flags |= WPS_REFRESH_DYNAMIC;
537 if(!id3)
538 return NULL; /* no such info (yet) */
539 /* fall-through */
540 case 'f': /* File Information */
541 *flags |= WPS_REFRESH_STATIC;
542 switch(tag[1])
543 {
544 case 'v': /* VBR file? */
545 return id3->vbr ? "(avg)" : NULL;
546
547 case 'b': /* File Bitrate */
548 if(id3->bitrate)
549 snprintf(buf, buf_size, "%d", id3->bitrate);
550 else
551 snprintf(buf, buf_size, "?");
552 return buf;
553
554 case 'f': /* File Frequency */
555 snprintf(buf, buf_size, "%ld", id3->frequency);
556 return buf;
557
558 case 'p': /* File Path */
559 return id3->path;
560
561 case 'm': /* File Name - With Extension */
562 return get_dir(buf, buf_size, id3->path, 0);
563
564 case 'n': /* File Name */
565 if (get_dir(buf, buf_size, id3->path, 0))
566 {
567 /* Remove extension */
568 char* sep = strrchr(buf, '.');
569
570 if (NULL != sep)
571 {
572 *sep = 0;
573 }
574
575 return buf;
576 }
577 else
578 {
579 return NULL;
580 }
581
582 case 's': /* File Size (in kilobytes) */
583 snprintf(buf, buf_size, "%ld", id3->filesize / 1024);
584 return buf;
585
586 case 'c': /* File Codec */
587 if(id3->codectype == AFMT_UNKNOWN)
588 *intval = AFMT_NUM_CODECS;
589 else
590 *intval = id3->codectype;
591 return id3_get_codec(id3);
592 }
593 break;
594 159
595 case 'p': /* Playlist/Song Information */
596 switch(tag[1])
597 {
598 case 'b': /* progress bar */
599 *flags |= WPS_REFRESH_PLAYER_PROGRESS;
600#ifdef HAVE_LCD_CHARCELLS 160#ifdef HAVE_LCD_CHARCELLS
601 char *end = utf8encode(wps_data->wps_progress_pat[0], buf); 161 gui_splash(gwps->display, 0, "Vol: %3d dB",
602 *end = '\0'; 162 sound_val2phys(SOUND_VOLUME, global_settings.volume));
603 wps_data->full_line_progressbar=0; 163 return true;
604 return buf;
605#else
606 /* default values : */
607 wps_data->progress_top = -1;
608 wps_data->progress_height = 6;
609 wps_data->progress_start = 0;
610 wps_data->progress_end = 0;
611
612 char *prev=strchr(tag, '|');
613 if (prev) {
614 char *p=strchr(prev+1, '|');
615 if (p) {
616 wps_data->progress_height=atoi(++prev);
617 prev=strchr(prev, '|');
618 p=strchr(++p, '|');
619 if (p) {
620 wps_data->progress_start=atoi(++prev);
621 prev=strchr(prev, '|');
622 p=strchr(++p, '|');
623 if (p) {
624 wps_data->progress_end=atoi(++prev);
625 prev=strchr(prev, '|');
626 p=strchr(++p, '|');
627 if(p)
628 wps_data->progress_top = atoi(++prev);
629 }
630
631 if (wps_data->progress_height<3)
632 wps_data->progress_height=3;
633 if (wps_data->progress_end<wps_data->progress_start+3)
634 wps_data->progress_end=0;
635 }
636 }
637 }
638 return "\x01";
639#endif
640 case 'f': /* full-line progress bar */
641#ifdef HAVE_LCD_CHARCELLS
642 if(is_new_player()) {
643 *flags |= WPS_REFRESH_PLAYER_PROGRESS;
644 *flags |= WPS_REFRESH_DYNAMIC;
645 wps_data->full_line_progressbar=1;
646 /* we need 11 characters (full line) for
647 progress-bar */
648 snprintf(buf, buf_size, " ");
649 }
650 else
651 {
652 /* Tell the user if we have an OldPlayer */
653 snprintf(buf, buf_size, " <Old LCD> ");
654 }
655 return buf;
656#endif
657 case 'p': /* Playlist Position */
658 *flags |= WPS_REFRESH_STATIC;
659 snprintf(buf, buf_size, "%d",
660 playlist_get_display_index());
661 return buf;
662
663 case 'n': /* Playlist Name (without path) */
664 *flags |= WPS_REFRESH_STATIC;
665 return playlist_name(NULL, buf, buf_size);
666
667 case 'e': /* Playlist Total Entries */
668 *flags |= WPS_REFRESH_STATIC;
669 snprintf(buf, buf_size, "%d", playlist_amount());
670 return buf;
671
672 case 'c': /* Current Time in Song */
673 *flags |= WPS_REFRESH_DYNAMIC;
674 format_time(buf, buf_size,
675 id3->elapsed + wps_state.ff_rewind_count);
676 return buf;
677
678 case 'r': /* Remaining Time in Song */
679 *flags |= WPS_REFRESH_DYNAMIC;
680 format_time(buf, buf_size,
681 id3->length - id3->elapsed -
682 wps_state.ff_rewind_count);
683 return buf;
684
685 case 't': /* Total Time */
686 *flags |= WPS_REFRESH_STATIC;
687 format_time(buf, buf_size, id3->length);
688 return buf;
689
690#ifdef HAVE_LCD_BITMAP
691 case 'm': /* Peak Meter */
692 *flags |= WPS_REFRESH_PEAK_METER;
693 return "\x01";
694#endif 164#endif
695 case 's': /* shuffle */ 165 return false;
696 *flags |= WPS_REFRESH_DYNAMIC; 166}
697 if ( global_settings.playlist_shuffle )
698 return "s";
699 else
700 return NULL;
701 break;
702
703 case 'v': /* volume */
704 *flags |= WPS_REFRESH_DYNAMIC;
705 snprintf(buf, buf_size, "%d", global_settings.volume);
706 *intval = limit * (global_settings.volume
707 - sound_min(SOUND_VOLUME))
708 / (sound_max(SOUND_VOLUME)
709 - sound_min(SOUND_VOLUME)) + 1;
710 return buf;
711
712 }
713 break;
714 167
715#if (CONFIG_CODEC == SWCODEC) 168bool ffwd_rew(int button)
716 case 'S': /* DSP/Equalizer/Sound settings */ 169{
717 switch (tag[1]) 170 static const int ff_rew_steps[] = {
718 { 171 1000, 2000, 3000, 4000,
719 case 'p': /* pitch */ 172 5000, 6000, 8000, 10000,
720 *intval = sound_get_pitch(); 173 15000, 20000, 25000, 30000,
721 snprintf(buf, buf_size, "%d.%d", 174 45000, 60000
722 *intval / 10, *intval % 10); 175 };
723 return buf;
724 }
725 break;
726#endif
727
728 case 'm':
729 switch (tag[1])
730 {
731 case 'm': /* playback repeat mode */
732 *flags |= WPS_REFRESH_DYNAMIC;
733 *intval = global_settings.repeat_mode + 1;
734 snprintf(buf, buf_size, "%d", *intval);
735 return buf;
736
737 /* playback status */
738 case 'p': /* play */
739 *flags |= WPS_REFRESH_DYNAMIC;
740 int status = audio_status();
741 *intval = 1;
742 if (status == AUDIO_STATUS_PLAY && \
743 !(status & AUDIO_STATUS_PAUSE))
744 *intval = 2;
745 if (audio_status() & AUDIO_STATUS_PAUSE && \
746 (! status_get_ffmode()))
747 *intval = 3;
748 if (status_get_ffmode() == STATUS_FASTFORWARD)
749 *intval = 4;
750 if (status_get_ffmode() == STATUS_FASTBACKWARD)
751 *intval = 5;
752 snprintf(buf, buf_size, "%d", *intval);
753 return buf;
754 176
755#ifdef HAS_BUTTON_HOLD 177 unsigned int step = 0; /* current ff/rewind step */
756 case 'h': /* hold */ 178 unsigned int max_step = 0; /* maximum ff/rewind step */
757 *flags |= WPS_REFRESH_DYNAMIC; 179 int ff_rewind_count = 0; /* current ff/rewind count (in ticks) */
758 if (button_hold()) 180 int direction = -1; /* forward=1 or backward=-1 */
759 return "h"; 181 long accel_tick = 0; /* next time at which to bump the step size */
760 else 182 bool exit = false;
761 return NULL; 183 bool usb = false;
762#endif 184 int i = 0;
763#ifdef HAS_REMOTE_BUTTON_HOLD
764 case 'r': /* remote hold */
765 *flags |= WPS_REFRESH_DYNAMIC;
766 if (remote_button_hold())
767 return "r";
768 else
769 return NULL;
770#endif
771 }
772 break;
773 185
774 case 'b': /* battery info */ 186 if (button == ACTION_NONE)
775 *flags |= WPS_REFRESH_DYNAMIC; 187 {
776 switch (tag[1]) 188 status_set_ffmode(0);
777 { 189 return usb;
778 case 'l': /* battery level */ 190 }
191 while (!exit)
192 {
193 switch ( button )
194 {
195 case ACTION_WPS_SEEKFWD:
196 direction = 1;
197 case ACTION_WPS_SEEKBACK:
198 if (wps_state.ff_rewind)
779 { 199 {
780 int l = battery_level(); 200 if (direction == 1)
781 limit = MAX(limit, 2);
782 if (l > -1)
783 { 201 {
784 snprintf(buf, buf_size, "%d", l); 202 /* fast forwarding, calc max step relative to end */
785 /* First enum is used for "unknown level". */ 203 max_step = (wps_state.id3->length -
786 *intval = (limit - 1) * l / 100 + 1 + 1; 204 (wps_state.id3->elapsed +
205 ff_rewind_count)) *
206 FF_REWIND_MAX_PERCENT / 100;
787 } 207 }
788 else 208 else
789 { 209 {
790 *intval = 1; 210 /* rewinding, calc max step relative to start */
791 return "?"; 211 max_step = (wps_state.id3->elapsed + ff_rewind_count) *
212 FF_REWIND_MAX_PERCENT / 100;
792 } 213 }
793 return buf;
794 }
795 214
796 case 'v': /* battery voltage */ 215 max_step = MAX(max_step, MIN_FF_REWIND_STEP);
797 {
798 unsigned int v = battery_voltage();
799 snprintf(buf, buf_size, "%d.%02d", v/100, v%100);
800 return buf;
801 }
802 216
803 case 't': /* estimated battery time */ 217 if (step > max_step)
804 { 218 step = max_step;
805 int t = battery_time();
806 if (t >= 0)
807 snprintf(buf, buf_size, "%dh %dm", t / 60, t % 60);
808 else
809 strncpy(buf, "?h ?m", buf_size);
810 return buf;
811 }
812 219
813 case 's': /* sleep timer */ 220 ff_rewind_count += step * direction;
814 { 221
815 if (get_sleep_timer() == 0) 222 if (global_settings.ff_rewind_accel != 0 &&
816 { 223 current_tick >= accel_tick)
817 return NULL; 224 {
818 } 225 step *= 2;
819 else 226 accel_tick = current_tick +
820 { 227 global_settings.ff_rewind_accel*HZ;
821 format_time(buf, buf_size, \ 228 }
822 get_sleep_timer() * 1000);
823 return buf;
824 }
825 } 229 }
826 230 else
827#if CONFIG_CHARGING
828 case 'p': /* External power plugged in? */
829 { 231 {
830 if(charger_input_state==CHARGER) 232 if ( (audio_status() & AUDIO_STATUS_PLAY) &&
831 return "p"; 233 wps_state.id3 && wps_state.id3->length )
832 else 234 {
833 return NULL; 235 if (!wps_state.paused)
834 } 236#if (CONFIG_CODEC == SWCODEC)
237 audio_pre_ff_rewind();
238#else
239 audio_pause();
835#endif 240#endif
836#if CONFIG_CHARGING >= CHARGING_MONITOR 241#if CONFIG_KEYPAD == PLAYER_PAD
837 case 'c': /* Charging */ 242 FOR_NB_SCREENS(i)
838 { 243 gui_wps[i].display->stop_scroll();
839 if (charge_state == CHARGING || charge_state == TOPOFF) {
840 return "c";
841 } else {
842 return NULL;
843 }
844 }
845#endif 244#endif
846 } 245 if (direction > 0)
847 break; 246 status_set_ffmode(STATUS_FASTFORWARD);
247 else
248 status_set_ffmode(STATUS_FASTBACKWARD);
848 249
849#if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD) 250 wps_state.ff_rewind = true;
850 case 'l': /* VIRTUAL_LED */
851 {
852 switch(tag[1])
853 {
854 case 'h': /* Only one we have so far HDD LED */
855 *flags |= WPS_REFRESH_DYNAMIC;
856 if(led_read(HZ/2))
857 return "h";
858 else
859 return NULL;
860 }
861 }
862 break;
863#endif
864 251
865 case 'D': /* Directory path information */ 252 step = ff_rew_steps[global_settings.ff_rewind_min_step];
866 id3 = nid3; /* next song please! */
867 *flags |= WPS_REFRESH_DYNAMIC;
868 if(!id3)
869 return NULL; /* no such info (yet) */
870 /* fall-through */
871 case 'd': /* Directory path information */
872 {
873 int level = tag[1] - '0';
874 *flags |= WPS_REFRESH_STATIC;
875 /* d1 through d9 */
876 if ((0 < level) && (9 > level))
877 {
878 return get_dir(buf, buf_size, id3->path, level);
879 }
880 }
881 break;
882 253
883 case 't': /* set sub line time multiplier */ 254 accel_tick = current_tick +
884 { 255 global_settings.ff_rewind_accel*HZ;
885 int d = 1;
886 int time_mult = 0;
887 bool have_point = false;
888 bool have_tenth = false;
889
890 while (((tag[d] >= '0') &&
891 (tag[d] <= '9')) ||
892 (tag[d] == '.'))
893 {
894 if (tag[d] != '.')
895 {
896 time_mult = time_mult * 10;
897 time_mult = time_mult + tag[d] - '0';
898 if (have_point)
899 {
900 have_tenth = true;
901 d++;
902 break;
903 }
904 } 256 }
905 else 257 else
906 { 258 break;
907 have_point = true;
908 }
909 d++;
910 } 259 }
911 260
912 if (have_tenth == false) 261 if (direction > 0) {
913 time_mult *= 10; 262 if ((wps_state.id3->elapsed + ff_rewind_count) >
263 wps_state.id3->length)
264 ff_rewind_count = wps_state.id3->length -
265 wps_state.id3->elapsed;
266 }
267 else {
268 if ((int)(wps_state.id3->elapsed + ff_rewind_count) < 0)
269 ff_rewind_count = -wps_state.id3->elapsed;
270 }
914 271
915 *subline_time_mult = time_mult; 272 FOR_NB_SCREENS(i)
916 *tag_len = d; 273 gui_wps_refresh(&gui_wps[i],
274 (wps_state.wps_time_countup == false)?
275 ff_rewind_count:-ff_rewind_count,
276 WPS_REFRESH_PLAYER_PROGRESS |
277 WPS_REFRESH_DYNAMIC);
917 278
918 buf[0] = 0; 279 break;
919 return buf;
920 }
921 break;
922 case 'r': /* Runtime database Information and Replaygain */
923 switch(tag[1])
924 {
925 case 'p': /* Playcount */
926 *flags |= WPS_REFRESH_DYNAMIC;
927 *intval = cid3->playcount+1;
928 snprintf(buf, buf_size, "%ld", cid3->playcount);
929 return buf;
930 case 'r': /* Rating */
931 *flags |= WPS_REFRESH_DYNAMIC;
932 *intval = cid3->rating+1;
933 snprintf(buf, buf_size, "%d", cid3->rating);
934 return buf;
935#if CONFIG_CODEC == SWCODEC
936 case 'g': /* ReplayGain */
937 *flags |= WPS_REFRESH_STATIC;
938 if (global_settings.replaygain == 0)
939 *intval = 1; /* off */
940 else
941 {
942 int type = get_replaygain_mode(
943 id3->track_gain_string != NULL,
944 id3->album_gain_string != NULL);
945
946 if (type < 0)
947 *intval = 6; /* no tag */
948 else
949 *intval = type + 2;
950
951 if (global_settings.replaygain_type == REPLAYGAIN_SHUFFLE)
952 *intval += 2;
953 }
954 280
955 switch (*intval) 281 case ACTION_WPS_STOPSEEK:
956 { 282 wps_state.id3->elapsed = wps_state.id3->elapsed+ff_rewind_count;
957 case 1: 283 audio_ff_rewind(wps_state.id3->elapsed);
958 case 6: 284 ff_rewind_count = 0;
959 return "+0.00 dB"; 285 wps_state.ff_rewind = false;
960 break; 286 status_set_ffmode(0);
961 case 2: 287#if (CONFIG_CODEC != SWCODEC)
962 case 4: 288 if (!wps_state.paused)
963 strncpy(buf, id3->track_gain_string, buf_size); 289 audio_resume();
964 break;
965 case 3:
966 case 5:
967 strncpy(buf, id3->album_gain_string, buf_size);
968 break;
969 }
970 return buf;
971#endif 290#endif
972 } 291#ifdef HAVE_LCD_CHARCELLS
973 break; 292 gui_wps_display();
974#if CONFIG_RTC
975 case 'c': /* Real Time Clock display */
976 *flags |= WPS_REFRESH_DYNAMIC;
977 {
978 int value;
979 char *format = 0;
980 char *bufptr = buf;
981 struct tm* tm = get_time();
982 int i;
983 for (i=1;/*break*/;i++) {
984 switch(tag[i])
985 {
986 case 'a': /* abbreviated weekday name (Sun..Sat) */
987 value = tm->tm_wday;
988 if (value > 6 || value < 0) continue;
989 value = snprintf(
990 bufptr,buf_size,"%s",str(dayname[value]));
991 bufptr += value;
992 buf_size -= value;
993 continue;
994 case 'b': /* abbreviated month name (Jan..Dec) */
995 value = tm->tm_mon;
996 if (value > 11 || value < 0) continue;
997 value = snprintf(
998 bufptr,buf_size,"%s",str(monthname[value]));
999 bufptr += value;
1000 buf_size -= value;
1001 continue;
1002 case 'd': /* day of month (01..31) */
1003 value = tm->tm_mday;
1004 if (value > 31 || value < 1) continue;
1005 format = "%02d";
1006 break;
1007 case 'e': /* day of month, blank padded ( 1..31) */
1008 value = tm->tm_mday;
1009 if (value > 31 || value < 1) continue;
1010 format = "%2d";
1011 break;
1012 case 'H': /* hour (00..23) */
1013 value = tm->tm_hour;
1014 if (value > 23) continue;
1015 format = "%02d";
1016 break;
1017 case 'k': /* hour ( 0..23) */
1018 value = tm->tm_hour;
1019 if (value > 23) continue;
1020 format = "%2d";
1021 break;
1022 case 'I': /* hour (01..12) */
1023 value = tm->tm_hour;
1024 if (value > 23) continue;
1025 value %= 12;
1026 if (value == 0) value = 12;
1027 format = "%02d";
1028 break;
1029 case 'l': /* hour ( 1..12) */
1030 value = tm->tm_hour;
1031 if (value > 23 || value < 0) continue;
1032 value %= 12;
1033 if (value == 0) value = 12;
1034 format = "%2d";
1035 break;
1036 case 'm': /* month (01..12) */
1037 value = tm->tm_mon;
1038 if (value > 11 || value < 0) continue;
1039 value++;
1040 format = "%02d";
1041 break;
1042 case 'M': /* minute (00..59) */
1043 value = tm->tm_min;
1044 if (value > 59 || value < 0) continue;
1045 format = "%02d";
1046 break;
1047 case 'S': /* second (00..59) */
1048 value = tm->tm_sec;
1049 if (value > 59 || value < 0) continue;
1050 format = "%02d";
1051 break;
1052 case 'y': /* last two digits of year (00..99) */
1053 value = tm->tm_year;
1054 value %= 100;
1055 format = "%02d";
1056 break;
1057 case 'Y': /* year (1970...) */
1058 value = tm->tm_year;
1059 if (value > 199 || value < 100) continue;
1060 value += 1900;
1061 format = "%04d";
1062 break;
1063 case 'p': /* upper case AM or PM indicator */
1064 if (tm->tm_hour/12 == 0) format = "AM";
1065 else format = "PM";
1066 snprintf(bufptr,buf_size,"%s",format);
1067 bufptr += 2;
1068 buf_size -= 2;
1069 continue;
1070 case 'P': /* lower case am or pm indicator */
1071 if (tm->tm_hour/12 == 0) format = "am";
1072 else format = "pm";
1073 snprintf(bufptr,buf_size,"%s",format);
1074 bufptr += 2;
1075 buf_size -= 2;
1076 continue;
1077 case 'u': /* day of week (1..7); 1 is Monday */
1078 value = tm->tm_wday;
1079 if (value < 0 || value > 6) continue;
1080 value++;
1081 format = "%1d";
1082 break;
1083 case 'w': /* day of week (0..6); 0 is Sunday */
1084 value = tm->tm_wday;
1085 if (value < 0 || value > 6) continue;
1086 format = "%1d";
1087 break;
1088 default:
1089 if (tag[i] == 'c') {
1090 i++;
1091 value = -1;
1092 break;
1093 } else if (tag[i] == '\n') {
1094 value = -1;
1095 break;
1096 }
1097 snprintf(bufptr,buf_size,"%c",tag[i]);
1098 bufptr++;
1099 buf_size--;
1100 continue;
1101 } /* switch */
1102 if (value < 0) break;
1103
1104 value = snprintf(bufptr, buf_size, format, value);
1105 bufptr += value;
1106 buf_size -= value;
1107 } /* while */
1108 *tag_len = i;
1109 return buf;
1110 }
1111#endif /* CONFIG_RTC */
1112#if CONFIG_CODEC == SWCODEC
1113 case 'x':
1114 *flags |= WPS_REFRESH_DYNAMIC;
1115 switch(tag[1])
1116 {
1117 case 'd': /* crossfeed */
1118 if(global_settings.crossfeed)
1119 return "d";
1120 else
1121 return NULL;
1122 case 'f': /* crossfade */
1123 *intval = global_settings.crossfade+1;
1124 snprintf(buf, buf_size, "%d", global_settings.crossfade);
1125 return buf;
1126 }
1127 break;
1128#endif 293#endif
1129 } 294 exit = true;
1130 return NULL; 295 break;
1131}
1132 296
1133#ifdef HAVE_LCD_BITMAP 297 default:
1134/* clears the area where the image was shown */ 298 if(default_event_handler(button) == SYS_USB_CONNECTED) {
1135static void clear_image_pos(struct gui_wps *gwps, int n) 299 status_set_ffmode(0);
1136{ 300 usb = true;
1137 if(!gwps) 301 exit = true;
1138 return; 302 }
1139 struct wps_data *data = gwps->data; 303 break;
1140 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID); 304 }
1141 gwps->display->fillrect(data->img[n].x, data->img[n].y, 305 if (!exit)
1142 data->img[n].bm.width, data->img[n].bm.height); 306 button = get_action(CONTEXT_WPS,TIMEOUT_BLOCK);
1143 gwps->display->set_drawmode(DRMODE_SOLID); 307 }
308 action_signalscreenchange();
309 return usb;
1144} 310}
1145#endif
1146 311
1147/* Skip to the end of the current %? conditional. 312bool gui_wps_display(void)
1148 *
1149 * fmt - string to skip it. Should point to somewhere after the leading
1150 * "<" char (and before or at the last ">").
1151 * num - number of |'s to skip, or 0 to skip to the end (the ">").
1152 * enums - If not NULL, set to the number of |'s found in the current
1153 * conditional (sub-conditionals are ignored). num should be 0
1154 * to find all |'s.
1155 *
1156 * Returns the new position in fmt.
1157 */
1158static const char* skip_conditional(struct gui_wps *gwps, const char* fmt,
1159 int num, int *enums)
1160{ 313{
1161 int level = 1; 314 int i;
1162 int count = num; 315 if (!wps_state.id3 && !(audio_status() & AUDIO_STATUS_PLAY))
1163 const char *last_alternative = NULL; 316 {
317 global_status.resume_index = -1;
1164#ifdef HAVE_LCD_BITMAP 318#ifdef HAVE_LCD_BITMAP
1165 struct wps_data *data = NULL; 319 gui_syncstatusbar_draw(&statusbars, true);
1166 int last_x=-1, last_y=-1, last_w=-1, last_h=-1;
1167 if(gwps)
1168 data = gwps->data;
1169 if (enums)
1170 *enums = 0;
1171#else
1172 (void)gwps;
1173#endif 320#endif
1174 while (*fmt) 321 gui_syncsplash(HZ, str(LANG_END_PLAYLIST_RECORDER));
322 return true;
323 }
324 else
1175 { 325 {
1176 switch (*fmt++) 326 FOR_NB_SCREENS(i)
1177 { 327 {
1178 case '%': 328 gui_wps[i].display->clear_display();
1179#ifdef HAVE_LCD_BITMAP 329 if (!gui_wps[i].data->wps_loaded) {
1180 if(data && *(fmt) == 'x' && *(fmt+1) == 'd' ) 330 if ( !gui_wps[i].data->num_tokens ) {
1181 { 331 /* set the default wps for the main-screen */
1182 fmt +=2; 332 if(i == 0)
1183 int n = *fmt;
1184 if(n >= 'a' && n <= 'z')
1185 n -= 'a';
1186 if(n >= 'A' && n <= 'Z')
1187 n = n - 'A' + 26;
1188 if(last_x != data->img[n].x || last_y != data->img[n].y
1189 || last_w != data->img[n].bm.width
1190 || last_h != data->img[n].bm.height)
1191 { 333 {
1192 last_x = data->img[n].x; 334#ifdef HAVE_LCD_BITMAP
1193 last_y = data->img[n].y; 335#if LCD_DEPTH > 1
1194 last_w = data->img[n].bm.width; 336 unload_wps_backdrop();
1195 last_h = data->img[n].bm.height; 337#endif
1196 clear_image_pos(gwps,n); 338 wps_data_load(gui_wps[i].data,
1197 } 339 "%s%?it<%?in<%in. |>%it|%fn>\n"
1198 } 340 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
341 "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
342 "\n"
343 "%al%pc/%pt%ar[%pp:%pe]\n"
344 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
345 "%pb\n"
346 "%pm\n", false);
347#else
348 wps_data_load(gui_wps[i].data,
349 "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
350 "%pc%?ps<*|/>%pt\n", false);
1199#endif 351#endif
1200 break;
1201
1202 case '|':
1203 if(1 == level) {
1204 if (enums)
1205 (*enums)++;
1206 last_alternative = fmt;
1207 if(num) {
1208 count--;
1209 if(count == 0)
1210 return fmt;
1211 continue;
1212 }
1213 }
1214 continue;
1215
1216 case '>':
1217 if (0 == --level)
1218 {
1219 /* We're just skipping to the end */
1220 if(num == 0)
1221 return fmt;
1222
1223 /* If we are parsing an enum, we'll return the selected
1224 item. If there weren't enough items in the enum, we'll
1225 return the last one found. */
1226 if(count && last_alternative)
1227 {
1228 return last_alternative;
1229 } 352 }
1230 return fmt - 1; 353#if NB_SCREENS == 2
354 /* set the default wps for the remote-screen */
355 else if(i == 1)
356 {
357 wps_data_load(gui_wps[i].data,
358 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
359 "%s%?it<%?in<%in. |>%it|%fn>\n"
360 "%al%pc/%pt%ar[%pp:%pe]\n"
361 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
362 "%pb", false);
363 }
364#endif
1231 } 365 }
1232 continue; 366 }
1233
1234 default:
1235 continue;
1236 }
1237
1238 switch (*fmt++)
1239 {
1240 case 0:
1241 case '%':
1242 case '|':
1243 case '<':
1244 case '>':
1245 break;
1246
1247 case '?':
1248 while (*fmt && ('<' != *fmt))
1249 fmt++;
1250
1251 if ('<' == *fmt)
1252 fmt++;
1253
1254 level++;
1255 break;
1256
1257 default:
1258 break;
1259 } 367 }
1260 } 368 }
1261 369 yield();
1262 return fmt; 370 FOR_NB_SCREENS(i)
371 {
372 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_ALL);
373 }
374 return false;
1263} 375}
1264 376
1265/* Generate the display based on id3 information and format string. 377bool update(struct gui_wps *gwps)
1266 *
1267 * buf - char buffer to write the display to.
1268 * buf_size - the size of buffer.
1269 * id3 - the ID3 data to format with.
1270 * nid3 - the ID3 data of the next song (might by NULL)
1271 * fmt - format description.
1272 * flags - returns the type of the line. See constants i wps-display.h
1273 */
1274static void format_display(struct gui_wps *gwps, char* buf,
1275 int buf_size,
1276 struct mp3entry* id3,
1277 struct mp3entry* nid3, /* next song's id3 */
1278 const char* fmt,
1279 struct align_pos* align,
1280 unsigned short* subline_time_mult,
1281 unsigned char* flags)
1282{ 378{
1283 char temp_buf[128]; 379 bool track_changed = audio_has_changed_track();
1284 char* buf_start = buf; 380 bool retcode = false;
1285 char* buf_end = buf + buf_size - 1; /* Leave room for end null */
1286 char* value = NULL;
1287 int level = 0;
1288 unsigned char tag_length;
1289 int intval;
1290 int cur_align;
1291 char* cur_align_start;
1292#ifdef HAVE_LCD_BITMAP
1293 struct gui_img *img = gwps->data->img;
1294 int n;
1295#endif
1296
1297 cur_align_start = buf;
1298 cur_align = WPS_ALIGN_LEFT;
1299 *subline_time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
1300
1301 align->left = 0;
1302 align->center = 0;
1303 align->right = 0;
1304 381
1305 while (fmt && *fmt && buf < buf_end) 382 gwps->state->nid3 = audio_next_track();
383 if (track_changed)
1306 { 384 {
1307 switch (*fmt) 385 gwps->display->stop_scroll();
1308 { 386 gwps->state->id3 = audio_current_track();
1309 case '%':
1310 ++fmt;
1311 break;
1312
1313 case '|':
1314 case '>':
1315 if (level > 0)
1316 {
1317 fmt = skip_conditional(NULL, fmt, 0, NULL);
1318 level--;
1319 continue;
1320 }
1321 /* Else fall through */
1322
1323 default:
1324 *buf++ = *fmt++;
1325 continue;
1326 }
1327 387
1328 switch (*fmt) 388 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
389 && strcmp(gwps->state->id3->path, curr_cue->audio_filename))
1329 { 390 {
1330 case 0: 391 /* the current cuesheet isn't the right one any more */
1331 *buf++ = '%';
1332 break;
1333 case 'a':
1334 ++fmt;
1335 /* remember where the current aligned text started */
1336 switch (cur_align)
1337 {
1338 case WPS_ALIGN_LEFT:
1339 align->left = cur_align_start;
1340 break;
1341 392
1342 case WPS_ALIGN_CENTER: 393 if (!strcmp(gwps->state->id3->path, temp_cue->audio_filename)) {
1343 align->center = cur_align_start; 394 /* We have the new cuesheet in memory (temp_cue),
1344 break; 395 let's make it the current one ! */
396 memcpy(curr_cue, temp_cue, sizeof(struct cuesheet));
397 }
398 else {
399 /* We need to parse the new cuesheet */
1345 400
1346 case WPS_ALIGN_RIGHT: 401 char cuepath[MAX_PATH];
1347 align->right = cur_align_start; 402 strncpy(cuepath, gwps->state->id3->path, MAX_PATH);
1348 break; 403 char *dot = strrchr(cuepath, '.');
1349 } 404 strcpy(dot, ".cue");
1350 /* start a new alignment */
1351 switch (*fmt)
1352 {
1353 case 'l':
1354 cur_align = WPS_ALIGN_LEFT;
1355 break;
1356 case 'c':
1357 cur_align = WPS_ALIGN_CENTER;
1358 break;
1359 case 'r':
1360 cur_align = WPS_ALIGN_RIGHT;
1361 break;
1362 }
1363 *buf++=0;
1364 cur_align_start = buf;
1365 ++fmt;
1366 break;
1367 case 's':
1368 *flags |= WPS_REFRESH_SCROLL;
1369 ++fmt;
1370 break;
1371 405
1372 case 'x': /* image support */ 406 if (parse_cuesheet(cuepath, curr_cue))
1373#ifdef HAVE_LCD_BITMAP
1374 if ('d' == *(fmt+1) )
1375 { 407 {
1376 fmt+=2; 408 gwps->state->id3->cuesheet_type = 1;
1377 409 strcpy(curr_cue->audio_filename, gwps->state->id3->path);
1378 /* get the image ID */
1379 n = *fmt;
1380 if(n >= 'a' && n <= 'z')
1381 n -= 'a';
1382 if(n >= 'A' && n <= 'Z')
1383 n = n - 'A' + 26;
1384 if (n >= 0 && n < MAX_IMAGES && img[n].loaded) {
1385 img[n].display = true;
1386 }
1387 } 410 }
411 }
1388 412
1389#endif 413 cue_spoof_id3(curr_cue, gwps->state->id3);
1390 fmt++; 414 }
1391 break;
1392
1393
1394 case '%':
1395 case '|':
1396 case '<':
1397 case '>':
1398 case ';':
1399 *buf++ = *fmt++;
1400 break;
1401
1402 case '?':
1403 fmt++;
1404 /* Get number of "|" chars in the current conditional;
1405 * used by get_tag when calculating levels.
1406 */
1407 skip_conditional(gwps, fmt, 0, &intval);
1408 value = get_tag(gwps->data, id3, nid3, fmt, temp_buf,
1409 sizeof(temp_buf),&tag_length,
1410 subline_time_mult, flags, &intval);
1411
1412 while (*fmt && ('<' != *fmt))
1413 fmt++;
1414
1415 if ('<' == *fmt)
1416 fmt++;
1417
1418 /* No value, so skip to else part, using a sufficiently high
1419 value to "hit" the last part of the conditional */
1420 if ((!value) || (!strlen(value)))
1421 fmt = skip_conditional(NULL, fmt, 1000, NULL);
1422 else
1423 if(intval > 1) /* enum */
1424 fmt = skip_conditional(NULL, fmt, intval - 1, NULL);
1425
1426 level++;
1427 break;
1428
1429 default:
1430 intval = 1;
1431 value = get_tag(gwps->data, id3, nid3, fmt, temp_buf,
1432 sizeof(temp_buf), &tag_length,
1433 subline_time_mult, flags,&intval);
1434 fmt += tag_length;
1435 415
1436 if (value) 416 if (gui_wps_display())
1437 { 417 retcode = true;
1438 while (*value && (buf < buf_end)) 418 else{
1439 *buf++ = *value++; 419 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
1440 }
1441 } 420 }
421
422 if (gwps->state->id3)
423 memcpy(gwps->state->current_track_path, gwps->state->id3->path,
424 sizeof(gwps->state->current_track_path));
1442 } 425 }
1443 426
1444 /* remember where the current aligned text started */ 427 if (gwps->state->id3)
1445 switch (cur_align)
1446 { 428 {
1447 case WPS_ALIGN_LEFT: 429 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
1448 align->left = cur_align_start; 430 && (gwps->state->id3->elapsed < curr_cue->curr_track->offset
1449 break; 431 || (curr_cue->curr_track_idx < curr_cue->track_count - 1
432 && gwps->state->id3->elapsed >= (curr_cue->curr_track+1)->offset)))
433 {
434 /* We've changed tracks within the cuesheet :
435 we need to update the ID3 info and refresh the WPS */
1450 436
1451 case WPS_ALIGN_CENTER: 437 cue_find_current_track(curr_cue, gwps->state->id3->elapsed);
1452 align->center = cur_align_start; 438 cue_spoof_id3(curr_cue, gwps->state->id3);
1453 break;
1454 439
1455 case WPS_ALIGN_RIGHT: 440 gwps->display->stop_scroll();
1456 align->right = cur_align_start; 441 if (gui_wps_display())
1457 break; 442 retcode = true;
443 else
444 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
445 }
446 else
447 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
1458 } 448 }
1459 449
1460 *buf = 0; 450 gui_wps_statusbar_draw(gwps, false);
1461
1462 /* if resulting line is an empty line, set the subline time to 0 */
1463 if (buf - buf_start == 0)
1464 *subline_time_mult = 0;
1465 451
1466 /* If no flags have been set, the line didn't contain any format codes. 452 return retcode;
1467 We still want to refresh it. */
1468 if(*flags == 0)
1469 *flags = WPS_REFRESH_STATIC;
1470} 453}
1471 454
1472/* fades the volume */ 455
1473void fade(bool fade_in) 456void display_keylock_text(bool locked)
1474{ 457{
1475 int fp_global_vol = global_settings.volume << 8; 458 char* s;
1476 int fp_min_vol = sound_min(SOUND_VOLUME) << 8; 459 int i;
1477 int fp_step = (fp_global_vol - fp_min_vol) / 30; 460 FOR_NB_SCREENS(i)
461 gui_wps[i].display->stop_scroll();
1478 462
1479 if (fade_in) { 463#ifdef HAVE_LCD_CHARCELLS
1480 /* fade in */ 464 if(locked)
1481 int fp_volume = fp_min_vol; 465 s = str(LANG_KEYLOCK_ON_PLAYER);
466 else
467 s = str(LANG_KEYLOCK_OFF_PLAYER);
468#else
469 if(locked)
470 s = str(LANG_KEYLOCK_ON_RECORDER);
471 else
472 s = str(LANG_KEYLOCK_OFF_RECORDER);
473#endif
474 gui_syncsplash(HZ, s);
475}
1482 476
1483 /* zero out the sound */ 477#ifdef HAVE_LCD_BITMAP
1484 sound_set_volume(fp_min_vol >> 8);
1485 478
1486 sleep(HZ/10); /* let audio thread run */ 479static void draw_progressbar(struct gui_wps *gwps, int line)
1487 audio_resume(); 480{
1488 481 struct wps_data *data = gwps->data;
1489 while (fp_volume < fp_global_vol - fp_step) { 482 struct screen *display = gwps->display;
1490 fp_volume += fp_step; 483 struct wps_state *state = gwps->state;
1491 sound_set_volume(fp_volume >> 8); 484 int h = font_get(FONT_UI)->height;
1492 sleep(1);
1493 }
1494 sound_set_volume(global_settings.volume);
1495 }
1496 else {
1497 /* fade out */
1498 int fp_volume = fp_global_vol;
1499 485
1500 while (fp_volume > fp_min_vol + fp_step) { 486 int sb_y;
1501 fp_volume -= fp_step; 487 if (data->progress_top < 0)
1502 sound_set_volume(fp_volume >> 8); 488 sb_y = line*h + display->getymargin() +
1503 sleep(1); 489 ((h > data->progress_height + 1)
1504 } 490 ? (h - data->progress_height) / 2 : 1);
1505 audio_pause(); 491 else
1506#ifndef SIMULATOR 492 sb_y = data->progress_top;
1507 /* let audio thread run and wait for the mas to run out of data */ 493
1508 while (!mp3_pause_done()) 494 if (!data->progress_end)
495 data->progress_end=display->width;
496
497 if (gwps->data->progressbar.have_bitmap_pb)
498 gui_bitmap_scrollbar_draw(display, data->progressbar.bm,
499 data->progress_start, sb_y,
500 data->progress_end-data->progress_start,
501 data->progressbar.bm.height,
502 state->id3->length ? state->id3->length : 1, 0,
503 state->id3->length ? state->id3->elapsed
504 + state->ff_rewind_count : 0,
505 HORIZONTAL);
506 else
507 gui_scrollbar_draw(display, data->progress_start, sb_y,
508 data->progress_end-data->progress_start,
509 data->progress_height,
510 state->id3->length ? state->id3->length : 1, 0,
511 state->id3->length ? state->id3->elapsed
512 + state->ff_rewind_count : 0,
513 HORIZONTAL);
514
515#ifdef AB_REPEAT_ENABLE
516 if ( ab_repeat_mode_enabled() )
517 ab_draw_markers(display, state->id3->length,
518 data->progress_start, data->progress_end, sb_y,
519 data->progress_height);
1509#endif 520#endif
1510 sleep(HZ/10);
1511 521
1512 /* reset volume to what it was before the fade */ 522 if ( cuesheet_is_enabled() && state->id3->cuesheet_type )
1513 sound_set_volume(global_settings.volume); 523 cue_draw_markers(display, state->id3->length,
1514 } 524 data->progress_start, data->progress_end,
525 sb_y+1, data->progress_height-2);
1515} 526}
1516 527
1517/* Set format string to use for WPS, splitting it into lines */ 528/* clears the area where the image was shown */
1518void gui_wps_format(struct wps_data *data) 529static void clear_image_pos(struct gui_wps *gwps, int n)
1519{ 530{
1520 char* buf = data->format_buffer; 531 if(!gwps)
1521 char* start_of_line = data->format_buffer;
1522 int line = 0;
1523 int subline;
1524 char c;
1525 if(!data)
1526 return; 532 return;
1527 533 struct wps_data *data = gwps->data;
1528 for (line=0; line<WPS_MAX_LINES; line++) 534 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1529 { 535 gwps->display->fillrect(data->img[n].x, data->img[n].y,
1530 for (subline=0; subline<WPS_MAX_SUBLINES; subline++) 536 data->img[n].bm.width, data->img[n].bm.height);
1531 { 537 gwps->display->set_drawmode(DRMODE_SOLID);
1532 data->format_lines[line][subline] = 0;
1533 data->time_mult[line][subline] = 0;
1534 }
1535 data->subline_expire_time[line] = 0;
1536 data->curr_subline[line] = SUBLINE_RESET;
1537 }
1538
1539 line = 0;
1540 subline = 0;
1541 buf = skip_utf8_bom(buf);
1542 data->format_lines[line][subline] = buf;
1543
1544 while ((*buf) && (line < WPS_MAX_LINES))
1545 {
1546 c = *buf;
1547
1548 switch (c)
1549 {
1550 /*
1551 * skip % sequences so "%;" doesn't start a new subline
1552 * don't skip %x lines (pre-load bitmaps)
1553 */
1554 case '%':
1555 buf++;
1556 break;
1557
1558 case '\r': /* CR */
1559 *buf = 0;
1560 break;
1561
1562 case '\n': /* LF */
1563 *buf = 0;
1564
1565 if (*start_of_line != '#') /* A comment? */
1566 line++;
1567
1568 if (line < WPS_MAX_LINES)
1569 {
1570 /* the next line starts on the next byte */
1571 subline = 0;
1572 data->format_lines[line][subline] = buf+1;
1573 start_of_line = data->format_lines[line][subline];
1574 }
1575 break;
1576
1577 case ';': /* start a new subline */
1578 *buf = 0;
1579 subline++;
1580 if (subline < WPS_MAX_SUBLINES)
1581 {
1582 data->format_lines[line][subline] = buf+1;
1583 }
1584 else /* exceeded max sublines, skip rest of line */
1585 {
1586 while (*(++buf))
1587 {
1588 if ((*buf == '\r') || (*buf == '\n'))
1589 {
1590 break;
1591 }
1592 }
1593 buf--;
1594 subline = 0;
1595 }
1596 break;
1597 }
1598 buf++;
1599 }
1600} 538}
1601 539
1602#ifdef HAVE_LCD_BITMAP
1603/* Display images */
1604static void wps_draw_image(struct gui_wps *gwps, int n) 540static void wps_draw_image(struct gui_wps *gwps, int n)
1605{ 541{
1606 struct screen *display = gwps->display; 542 struct screen *display = gwps->display;
@@ -1625,509 +561,29 @@ static void wps_draw_image(struct gui_wps *gwps, int n)
1625 } 561 }
1626#endif 562#endif
1627} 563}
1628static void wps_display_images(struct gui_wps *gwps, bool always)
1629{
1630 if(!gwps || !gwps->data || !gwps->display) return;
1631 int n;
1632 struct wps_data *data = gwps->data;
1633 struct screen *display = gwps->display;
1634 for (n = 0; n < MAX_IMAGES; n++) {
1635 if (data->img[n].loaded) {
1636 if( (!always && data->img[n].display)
1637 || (always && data->img[n].always_display) )
1638 wps_draw_image(gwps, n);
1639 }
1640 }
1641 display->set_drawmode(DRMODE_SOLID);
1642}
1643#endif
1644 564
1645#if 0 /* currently unused */ 565static void wps_display_images(struct gui_wps *gwps)
1646void gui_wps_reset(struct gui_wps *gui_wps)
1647{ 566{
1648 if(!gui_wps || !gui_wps->data) 567 if(!gwps || !gwps->data || !gwps->display)
1649 return; 568 return;
1650 gui_wps->data->wps_loaded = false;
1651 memset(&gui_wps->data->format_buffer, 0,
1652 sizeof(gui_wps->data->format_buffer));
1653}
1654#endif
1655 569
1656bool gui_wps_refresh(struct gui_wps *gwps, int ffwd_offset, 570 int n;
1657 unsigned char refresh_mode)
1658{
1659 char buf[MAX_PATH];
1660 unsigned char flags;
1661 int i;
1662 bool update_line;
1663 bool only_one_subline;
1664 bool new_subline_refresh;
1665 bool reset_subline;
1666 int search;
1667 int search_start;
1668 struct align_pos format_align;
1669 struct wps_data *data = gwps->data; 571 struct wps_data *data = gwps->data;
1670 struct wps_state *state = gwps->state;
1671 struct screen *display = gwps->display; 572 struct screen *display = gwps->display;
1672 if(!gwps || !data || !state || !display)
1673 {
1674 return false;
1675 }
1676#ifdef HAVE_LCD_BITMAP
1677 int h = font_get(FONT_UI)->height;
1678 int offset = 0;
1679 gui_wps_statusbar_draw(gwps, true);
1680 if(data->wps_sb_tag && data->show_sb_on_wps)
1681 offset = STATUSBAR_HEIGHT;
1682 else if ( global_settings.statusbar && !data->wps_sb_tag)
1683 offset = STATUSBAR_HEIGHT;
1684
1685 /* to find out wether the peak meter is enabled we
1686 assume it wasn't until we find a line that contains
1687 the peak meter. We can't use peak_meter_enabled itself
1688 because that would mean to turn off the meter thread
1689 temporarily. (That shouldn't matter unless yield
1690 or sleep is called but who knows...)
1691 */
1692 bool enable_pm = false;
1693
1694 /* Set images to not to be displayed */
1695 for (i = 0; i < MAX_IMAGES; i++) {
1696 data->img[i].display = false;
1697 }
1698#endif
1699 /* reset to first subline if refresh all flag is set */
1700 if (refresh_mode == WPS_REFRESH_ALL)
1701 {
1702 for (i=0; i<WPS_MAX_LINES; i++)
1703 {
1704 data->curr_subline[i] = SUBLINE_RESET;
1705 }
1706 }
1707
1708#ifdef HAVE_LCD_CHARCELLS
1709 for (i=0; i<8; i++) {
1710 if (data->wps_progress_pat[i]==0)
1711 data->wps_progress_pat[i]=display->get_locked_pattern();
1712 }
1713#endif
1714
1715 if (!state->id3)
1716 {
1717 display->stop_scroll();
1718 return false;
1719 }
1720
1721 state->ff_rewind_count = ffwd_offset;
1722 573
1723 for (i = 0; i < WPS_MAX_LINES; i++) 574 for (n = 0; n < MAX_IMAGES; n++)
1724 { 575 {
1725 reset_subline = (data->curr_subline[i] == SUBLINE_RESET); 576 if (data->img[n].loaded &&
1726 new_subline_refresh = false; 577 (data->img[n].display || data->img[n].always_display))
1727 only_one_subline = false;
1728
1729 /* if time to advance to next sub-line */
1730 if (TIME_AFTER(current_tick, data->subline_expire_time[i] - 1) ||
1731 reset_subline)
1732 { 578 {
1733 /* search all sublines until the next subline with time > 0 579 wps_draw_image(gwps, n);
1734 is found or we get back to the subline we started with */
1735 if (reset_subline)
1736 search_start = 0;
1737 else
1738 search_start = data->curr_subline[i];
1739 for (search=0; search<WPS_MAX_SUBLINES; search++)
1740 {
1741 data->curr_subline[i]++;
1742
1743 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
1744 if ((!data->format_lines[i][data->curr_subline[i]]) ||
1745 (data->curr_subline[i] == WPS_MAX_SUBLINES))
1746 {
1747 if (data->curr_subline[i] == 1)
1748 only_one_subline = true;
1749 data->curr_subline[i] = 0;
1750 }
1751
1752 /* if back where we started after search or
1753 only one subline is defined on the line */
1754 if (((search > 0) && (data->curr_subline[i] == search_start)) ||
1755 only_one_subline)
1756 {
1757 /* no other subline with a time > 0 exists */
1758 data->subline_expire_time[i] = (reset_subline?
1759 current_tick : data->subline_expire_time[i]) + 100 * HZ;
1760 break;
1761 }
1762 else
1763 {
1764 /* get initial time multiplier and
1765 line type flags for this subline */
1766 format_display(gwps, buf, sizeof(buf),
1767 state->id3, state->nid3,
1768 data->format_lines[i][data->curr_subline[i]],
1769 &format_align,
1770 &data->time_mult[i][data->curr_subline[i]],
1771 &data->line_type[i][data->curr_subline[i]]);
1772
1773 /* only use this subline if subline time > 0 */
1774 if (data->time_mult[i][data->curr_subline[i]] > 0)
1775 {
1776 new_subline_refresh = true;
1777 data->subline_expire_time[i] = (reset_subline?
1778 current_tick : data->subline_expire_time[i]) +
1779 BASE_SUBLINE_TIME * data->time_mult[i][data->curr_subline[i]];
1780 break;
1781 }
1782 }
1783 }
1784
1785 }
1786
1787 update_line = false;
1788
1789 if ( !data->format_lines[i][data->curr_subline[i]] )
1790 break;
1791
1792 if ((data->line_type[i][data->curr_subline[i]] & refresh_mode) ||
1793 (refresh_mode == WPS_REFRESH_ALL) ||
1794 new_subline_refresh)
1795 {
1796 flags = 0;
1797 int left_width, left_xpos;
1798 int center_width, center_xpos;
1799 int right_width, right_xpos;
1800 int space_width;
1801 int string_height;
1802 int ypos;
1803
1804 format_display(gwps, buf, sizeof(buf),
1805 state->id3, state->nid3,
1806 data->format_lines[i][data->curr_subline[i]],
1807 &format_align,
1808 &data->time_mult[i][data->curr_subline[i]],
1809 &flags);
1810 data->line_type[i][data->curr_subline[i]] = flags;
1811
1812#ifdef HAVE_LCD_BITMAP
1813 /* progress */
1814 if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
1815 {
1816 int sb_y;
1817 if (data->progress_top == -1)
1818 sb_y = i*h + offset + ((h > data->progress_height + 1)
1819 ? (h - data->progress_height) / 2 : 1);
1820 else
1821 sb_y = data->progress_top;
1822
1823 if (!data->progress_end)
1824 data->progress_end=display->width;
1825
1826 if (gwps->data->progressbar.have_bitmap_pb)
1827 gui_bitmap_scrollbar_draw(display, data->progressbar.bm,
1828 data->progress_start, sb_y,
1829 data->progress_end-data->progress_start,
1830 data->progressbar.bm.height,
1831 state->id3->length?state->id3->length:1, 0,
1832 state->id3->length?state->id3->elapsed + state->ff_rewind_count:0,
1833 HORIZONTAL);
1834 else
1835 gui_scrollbar_draw(display, data->progress_start, sb_y,
1836 data->progress_end-data->progress_start,
1837 data->progress_height,
1838 state->id3->length?state->id3->length:1, 0,
1839 state->id3->length?state->id3->elapsed + state->ff_rewind_count:0,
1840 HORIZONTAL);
1841#ifdef AB_REPEAT_ENABLE
1842 if ( ab_repeat_mode_enabled() )
1843 ab_draw_markers(display, state->id3->length,
1844 data->progress_start, data->progress_end, sb_y,
1845 data->progress_height);
1846#endif
1847
1848 if (cuesheet_is_enabled() && state->id3->cuesheet_type)
1849 {
1850 cue_draw_markers(display, state->id3->length,
1851 data->progress_start, data->progress_end,
1852 sb_y+1, data->progress_height-2);
1853 }
1854
1855 update_line = true;
1856 }
1857 if (flags & refresh_mode & WPS_REFRESH_PEAK_METER) {
1858 /* peak meter */
1859 int peak_meter_y;
1860
1861 update_line = true;
1862 peak_meter_y = i * h + offset;
1863
1864 /* The user might decide to have the peak meter in the last
1865 line so that it is only displayed if no status bar is
1866 visible. If so we neither want do draw nor enable the
1867 peak meter. */
1868 if (peak_meter_y + h <= display->height) {
1869 /* found a line with a peak meter -> remember that we must
1870 enable it later */
1871 enable_pm = true;
1872 peak_meter_screen(gwps->display, 0, peak_meter_y,
1873 MIN(h, display->height - peak_meter_y));
1874 }
1875 }
1876#else
1877 /* progress */
1878 if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS) {
1879 if (data->full_line_progressbar)
1880 draw_player_fullbar(gwps, buf, sizeof(buf));
1881 else
1882 draw_player_progress(gwps);
1883 }
1884#endif
1885 /* calculate different string sizes and positions */
1886 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
1887 if (format_align.left != 0) {
1888 display->getstringsize((unsigned char *)format_align.left,
1889 &left_width, &string_height);
1890 }
1891 else {
1892 left_width = 0;
1893 }
1894 left_xpos = 0;
1895
1896 if (format_align.center != 0) {
1897 display->getstringsize((unsigned char *)format_align.center,
1898 &center_width, &string_height);
1899 }
1900 else {
1901 center_width = 0;
1902 }
1903 center_xpos=(display->width - center_width) / 2;
1904
1905 if (format_align.right != 0) {
1906 display->getstringsize((unsigned char *)format_align.right,
1907 &right_width, &string_height);
1908 }
1909 else {
1910 right_width = 0;
1911 }
1912 right_xpos = (display->width - right_width);
1913
1914 /* Checks for overlapping strings.
1915 If needed the overlapping strings will be merged, separated by a
1916 space */
1917
1918 /* CASE 1: left and centered string overlap */
1919 /* there is a left string, need to merge left and center */
1920 if ((left_width != 0 && center_width != 0) &&
1921 (left_xpos + left_width + space_width > center_xpos)) {
1922 /* replace the former separator '\0' of left and
1923 center string with a space */
1924 *(--format_align.center) = ' ';
1925 /* calculate the new width and position of the merged string */
1926 left_width = left_width + space_width + center_width;
1927 left_xpos = 0;
1928 /* there is no centered string anymore */
1929 center_width = 0;
1930 }
1931 /* there is no left string, move center to left */
1932 if ((left_width == 0 && center_width != 0) &&
1933 (left_xpos + left_width > center_xpos)) {
1934 /* move the center string to the left string */
1935 format_align.left = format_align.center;
1936 /* calculate the new width and position of the string */
1937 left_width = center_width;
1938 left_xpos = 0;
1939 /* there is no centered string anymore */
1940 center_width = 0;
1941 }
1942
1943 /* CASE 2: centered and right string overlap */
1944 /* there is a right string, need to merge center and right */
1945 if ((center_width != 0 && right_width != 0) &&
1946 (center_xpos + center_width + space_width > right_xpos)) {
1947 /* replace the former separator '\0' of center and
1948 right string with a space */
1949 *(--format_align.right) = ' ';
1950 /* move the center string to the right after merge */
1951 format_align.right = format_align.center;
1952 /* calculate the new width and position of the merged string */
1953 right_width = center_width + space_width + right_width;
1954 right_xpos = (display->width - right_width);
1955 /* there is no centered string anymore */
1956 center_width = 0;
1957 }
1958 /* there is no right string, move center to right */
1959 if ((center_width != 0 && right_width == 0) &&
1960 (center_xpos + center_width > right_xpos)) {
1961 /* move the center string to the right string */
1962 format_align.right = format_align.center;
1963 /* calculate the new width and position of the string */
1964 right_width = center_width;
1965 right_xpos = (display->width - right_width);
1966 /* there is no centered string anymore */
1967 center_width = 0;
1968 }
1969
1970 /* CASE 3: left and right overlap
1971 There is no center string anymore, either there never
1972 was one or it has been merged in case 1 or 2 */
1973 /* there is a left string, need to merge left and right */
1974 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
1975 (left_xpos + left_width + space_width > right_xpos)) {
1976 /* replace the former separator '\0' of left and
1977 right string with a space */
1978 *(--format_align.right) = ' ';
1979 /* calculate the new width and position of the string */
1980 left_width = left_width + space_width + right_width;
1981 left_xpos = 0;
1982 /* there is no right string anymore */
1983 right_width = 0;
1984 }
1985 /* there is no left string, move right to left */
1986 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
1987 (left_xpos + left_width > right_xpos)) {
1988 /* move the right string to the left string */
1989 format_align.left = format_align.right;
1990 /* calculate the new width and position of the string */
1991 left_width = right_width;
1992 left_xpos = 0;
1993 /* there is no right string anymore */
1994 right_width = 0;
1995 }
1996
1997 if (flags & WPS_REFRESH_SCROLL) {
1998
1999 /* scroll line */
2000 if ((refresh_mode & WPS_REFRESH_SCROLL) ||
2001 new_subline_refresh) {
2002
2003 ypos = (i*string_height)+display->getymargin();
2004 update_line = true;
2005
2006 if (left_width>display->width) {
2007 display->puts_scroll(0, i,
2008 (unsigned char *)format_align.left);
2009 } else {
2010 /* clear the line first */
2011#ifdef HAVE_LCD_BITMAP
2012 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
2013 display->fillrect(0, ypos, display->width, string_height);
2014 display->set_drawmode(DRMODE_SOLID);
2015#endif
2016 /* Nasty hack: we output an empty scrolling string,
2017 which will reset the scroller for that line */
2018 display->puts_scroll(0, i, (unsigned char *)"");
2019
2020 /* print aligned strings */
2021 if (left_width != 0)
2022 {
2023 display->putsxy(left_xpos, ypos,
2024 (unsigned char *)format_align.left);
2025 }
2026 if (center_width != 0)
2027 {
2028 display->putsxy(center_xpos, ypos,
2029 (unsigned char *)format_align.center);
2030 }
2031 if (right_width != 0)
2032 {
2033 display->putsxy(right_xpos, ypos,
2034 (unsigned char *)format_align.right);
2035 }
2036 }
2037 }
2038 }
2039 else if (flags & (WPS_REFRESH_DYNAMIC | WPS_REFRESH_STATIC))
2040 {
2041 /* dynamic / static line */
2042 if ((refresh_mode & (WPS_REFRESH_DYNAMIC|WPS_REFRESH_STATIC)) ||
2043 new_subline_refresh)
2044 {
2045 ypos = (i*string_height)+display->getymargin();
2046 update_line = true;
2047
2048#ifdef HAVE_LCD_BITMAP
2049 /* clear the line first */
2050 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
2051 display->fillrect(0, ypos, display->width, string_height);
2052 display->set_drawmode(DRMODE_SOLID);
2053#endif
2054
2055 /* Nasty hack: we output an empty scrolling string,
2056 which will reset the scroller for that line */
2057 display->puts_scroll(0, i, (unsigned char *)"");
2058
2059 /* print aligned strings */
2060 if (left_width != 0)
2061 {
2062 display->putsxy(left_xpos, ypos,
2063 (unsigned char *)format_align.left);
2064 }
2065 if (center_width != 0)
2066 {
2067 display->putsxy(center_xpos, ypos,
2068 (unsigned char *)format_align.center);
2069 }
2070 if (right_width != 0)
2071 {
2072 display->putsxy(right_xpos, ypos,
2073 (unsigned char *)format_align.right);
2074 }
2075 }
2076 }
2077 }
2078#ifdef HAVE_LCD_BITMAP
2079 if (update_line) {
2080 wps_display_images(gwps,false);
2081 } 580 }
2082#endif
2083 }
2084
2085#ifdef HAVE_LCD_BITMAP
2086 /* Display all images */
2087 wps_display_images(gwps,true);
2088 display->update();
2089 /* Now we know wether the peak meter is used.
2090 So we can enable / disable the peak meter thread */
2091 data->peak_meter_enabled = enable_pm;
2092#endif
2093
2094#if CONFIG_BACKLIGHT
2095 if (global_settings.caption_backlight && state->id3) {
2096 /* turn on backlight n seconds before track ends, and turn it off n
2097 seconds into the new track. n == backlight_timeout, or 5s */
2098 int n = backlight_timeout_value[global_settings.backlight_timeout]
2099 * 1000;
2100
2101 if ( n < 1000 )
2102 n = 5000; /* use 5s if backlight is always on or off */
2103
2104 if (((state->id3->elapsed < 1000) ||
2105 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2106 (state->paused == false))
2107 backlight_on();
2108 } 581 }
2109#endif 582 display->set_drawmode(DRMODE_SOLID);
2110#ifdef HAVE_REMOTE_LCD
2111 if (global_settings.remote_caption_backlight && state->id3) {
2112 /* turn on remote backlight n seconds before track ends, and turn it
2113 off n seconds into the new track. n == remote_backlight_timeout,
2114 or 5s */
2115 int n = backlight_timeout_value[global_settings.remote_backlight_timeout]
2116 * 1000;
2117
2118 if ( n < 1000 )
2119 n = 5000; /* use 5s if backlight is always on or off */
2120
2121 if (((state->id3->elapsed < 1000) ||
2122 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2123 (state->paused == false))
2124 remote_backlight_on();
2125 }
2126#endif
2127 return true;
2128} 583}
2129 584
2130#ifdef HAVE_LCD_CHARCELLS 585#else /* HAVE_LCD_CHARCELL */
586
2131static bool draw_player_progress(struct gui_wps *gwps) 587static bool draw_player_progress(struct gui_wps *gwps)
2132{ 588{
2133 char player_progressbar[7]; 589 char player_progressbar[7];
@@ -2150,7 +606,7 @@ static bool draw_player_progress(struct gui_wps *gwps)
2150 songpos = ((state->id3->elapsed - state->ff_rewind_count) * 36) / 606 songpos = ((state->id3->elapsed - state->ff_rewind_count) * 36) /
2151 state->id3->length; 607 state->id3->length;
2152 else 608 else
2153 songpos = ((state->id3->elapsed + state->ff_rewind_count) * 36) / 609 songpos = ((state->id3->elapsed + state->ff_rewind_count) * 36) /
2154 state->id3->length; 610 state->id3->length;
2155 } 611 }
2156 for (i=0; i < songpos; i++) 612 for (i=0; i < songpos; i++)
@@ -2215,10 +671,10 @@ static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
2215 songpos = 55; 671 songpos = 55;
2216 else { 672 else {
2217 if(state->wps_time_countup == false) 673 if(state->wps_time_countup == false)
2218 songpos = ((state->id3->elapsed - state->ff_rewind_count) * 55) / 674 songpos = ((state->id3->elapsed - state->ff_rewind_count) * 55) /
2219 state->id3->length; 675 state->id3->length;
2220 else 676 else
2221 songpos = ((state->id3->elapsed + state->ff_rewind_count) * 55) / 677 songpos = ((state->id3->elapsed + state->ff_rewind_count) * 55) /
2222 state->id3->length; 678 state->id3->length;
2223 } 679 }
2224 680
@@ -2304,342 +760,1190 @@ static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
2304 *buf = '\0'; 760 *buf = '\0';
2305 } 761 }
2306} 762}
2307#endif
2308 763
2309/* set volume */ 764#endif /* HAVE_LCD_CHARCELL */
2310void setvol(void) 765
766/* Extract a part from a path.
767 *
768 * buf - buffer extract part to.
769 * buf_size - size of buffer.
770 * path - path to extract from.
771 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
772 * parent of parent, etc.
773 *
774 * Returns buf if the desired level was found, NULL otherwise.
775 */
776static char* get_dir(char* buf, int buf_size, const char* path, int level)
2311{ 777{
2312 if (global_settings.volume < sound_min(SOUND_VOLUME)) 778 const char* sep;
2313 global_settings.volume = sound_min(SOUND_VOLUME); 779 const char* last_sep;
2314 if (global_settings.volume > sound_max(SOUND_VOLUME)) 780 int len;
2315 global_settings.volume = sound_max(SOUND_VOLUME); 781
2316 sound_set_volume(global_settings.volume); 782 sep = path + strlen(path);
2317 settings_save(); 783 last_sep = sep;
784
785 while (sep > path)
786 {
787 if ('/' == *(--sep))
788 {
789 if (!level)
790 break;
791
792 level--;
793 last_sep = sep - 1;
794 }
795 }
796
797 if (level || (last_sep <= sep))
798 return NULL;
799
800 len = MIN(last_sep - sep, buf_size - 1);
801 strncpy(buf, sep + 1, len);
802 buf[len] = 0;
803 return buf;
2318} 804}
2319/* return true if screen restore is needed 805
2320 return false otherwise 806/* Return the tag found at index i and write its value in buf.
807 The return value is buf if the tag had a value, or NULL if not.
808
809 intval is used with enums: when this function is called, it should contain
810 the number of options in the enum. When this function returns, it will
811 contain the enum case we are actually in.
812 When not treating an enum, intval should be NULL.
2321*/ 813*/
2322bool update_onvol_change(struct gui_wps * gwps) 814static char *get_tag(struct gui_wps *gwps,
815 int i,
816 char *buf,
817 int buf_size,
818 int *intval)
2323{ 819{
2324 gui_wps_statusbar_draw(gwps, false); 820 if (!gwps)
2325 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC); 821 return NULL;
2326 822
2327#ifdef HAVE_LCD_CHARCELLS 823 struct wps_data *data = gwps->data;
2328 gui_splash(gwps->display, 0, "Vol: %3d dB", 824 struct wps_state *state = gwps->state;
2329 sound_val2phys(SOUND_VOLUME, global_settings.volume));
2330 return true;
2331#endif
2332 return false;
2333}
2334 825
2335bool ffwd_rew(int button) 826 if (!data || !state)
2336{ 827 return NULL;
2337 static const int ff_rew_steps[] = {
2338 1000, 2000, 3000, 4000,
2339 5000, 6000, 8000, 10000,
2340 15000, 20000, 25000, 30000,
2341 45000, 60000
2342 };
2343 828
2344 unsigned int step = 0; /* current ff/rewind step */ 829 struct mp3entry *id3;
2345 unsigned int max_step = 0; /* maximum ff/rewind step */
2346 int ff_rewind_count = 0; /* current ff/rewind count (in ticks) */
2347 int direction = -1; /* forward=1 or backward=-1 */
2348 long accel_tick = 0; /* next time at which to bump the step size */
2349 bool exit = false;
2350 bool usb = false;
2351 int i = 0;
2352 830
2353 if (button == ACTION_NONE) 831 if (data->tokens[i].next)
2354 { 832 id3 = state->nid3;
2355 status_set_ffmode(0); 833 else
2356 return usb; 834 id3 = state->id3;
2357 } 835
2358 while (!exit) 836 if (!id3)
837 return NULL;
838
839 int limit = 1;
840 if (intval)
841 limit = *intval;
842
843#if CONFIG_RTC
844 static struct tm* tm;
845#endif
846
847 switch (data->tokens[i].type)
2359 { 848 {
2360 switch ( button ) 849 case WPS_TOKEN_CHARACTER:
2361 { 850 return &(data->tokens[i].value.c);
2362 case ACTION_WPS_SEEKFWD: 851
2363 direction = 1; 852 case WPS_TOKEN_STRING:
2364 case ACTION_WPS_SEEKBACK: 853 return data->strings[data->tokens[i].value.i];
2365 if (wps_state.ff_rewind) 854
2366 { 855 case WPS_TOKEN_TRACK_TIME_ELAPSED:
2367 if (direction == 1) 856 format_time(buf, buf_size,
2368 { 857 id3->elapsed + state->ff_rewind_count);
2369 /* fast forwarding, calc max step relative to end */ 858 return buf;
2370 max_step = (wps_state.id3->length - 859
2371 (wps_state.id3->elapsed + 860 case WPS_TOKEN_TRACK_TIME_REMAINING:
2372 ff_rewind_count)) * 861 format_time(buf, buf_size,
2373 FF_REWIND_MAX_PERCENT / 100; 862 id3->length - id3->elapsed -
2374 } 863 state->ff_rewind_count);
2375 else 864 return buf;
2376 { 865
2377 /* rewinding, calc max step relative to start */ 866 case WPS_TOKEN_TRACK_LENGTH:
2378 max_step = (wps_state.id3->elapsed + ff_rewind_count) * 867 format_time(buf, buf_size, id3->length);
2379 FF_REWIND_MAX_PERCENT / 100; 868 return buf;
2380 } 869
870 case WPS_TOKEN_PLAYLIST_ENTRIES:
871 snprintf(buf, buf_size, "%d", playlist_amount());
872 return buf;
873
874 case WPS_TOKEN_PLAYLIST_NAME:
875 return playlist_name(NULL, buf, buf_size);
876
877 case WPS_TOKEN_PLAYLIST_POSITION:
878 snprintf(buf, buf_size, "%d",
879 playlist_get_display_index());
880 return buf;
881
882 case WPS_TOKEN_PLAYLIST_SHUFFLE:
883 if ( global_settings.playlist_shuffle )
884 return "s";
885 else
886 return NULL;
887 break;
2381 888
2382 max_step = MAX(max_step, MIN_FF_REWIND_STEP); 889 case WPS_TOKEN_VOLUME:
890 snprintf(buf, buf_size, "%d", global_settings.volume);
891 if (intval)
892 {
893 *intval = limit * (global_settings.volume
894 - sound_min(SOUND_VOLUME))
895 / (sound_max(SOUND_VOLUME)
896 - sound_min(SOUND_VOLUME)) + 1;
897 }
898 return buf;
2383 899
2384 if (step > max_step) 900 case WPS_TOKEN_METADATA_ARTIST:
2385 step = max_step; 901 return id3->artist;
2386 902
2387 ff_rewind_count += step * direction; 903 case WPS_TOKEN_METADATA_COMPOSER:
904 return id3->composer;
2388 905
2389 if (global_settings.ff_rewind_accel != 0 && 906 case WPS_TOKEN_METADATA_ALBUM:
2390 current_tick >= accel_tick) 907 return id3->album;
2391 {
2392 step *= 2;
2393 accel_tick = current_tick +
2394 global_settings.ff_rewind_accel*HZ;
2395 }
2396 }
2397 else
2398 {
2399 if ( (audio_status() & AUDIO_STATUS_PLAY) &&
2400 wps_state.id3 && wps_state.id3->length )
2401 {
2402 if (!wps_state.paused)
2403#if (CONFIG_CODEC == SWCODEC)
2404 audio_pre_ff_rewind();
2405#else
2406 audio_pause();
2407#endif
2408#if CONFIG_KEYPAD == PLAYER_PAD
2409 FOR_NB_SCREENS(i)
2410 gui_wps[i].display->stop_scroll();
2411#endif
2412 if (direction > 0)
2413 status_set_ffmode(STATUS_FASTFORWARD);
2414 else
2415 status_set_ffmode(STATUS_FASTBACKWARD);
2416 908
2417 wps_state.ff_rewind = true; 909 case WPS_TOKEN_METADATA_ALBUM_ARTIST:
910 return id3->albumartist;
2418 911
2419 step = ff_rew_steps[global_settings.ff_rewind_min_step]; 912 case WPS_TOKEN_METADATA_GENRE:
913 return id3->genre_string;
2420 914
2421 accel_tick = current_tick + 915 case WPS_TOKEN_METADATA_TRACK_NUMBER:
2422 global_settings.ff_rewind_accel*HZ; 916 if (id3->track_string)
2423 } 917 return id3->track_string;
2424 else
2425 break;
2426 }
2427 918
2428 if (direction > 0) { 919 if (id3->tracknum) {
2429 if ((wps_state.id3->elapsed + ff_rewind_count) > 920 snprintf(buf, buf_size, "%d", id3->tracknum);
2430 wps_state.id3->length) 921 return buf;
2431 ff_rewind_count = wps_state.id3->length - 922 }
2432 wps_state.id3->elapsed; 923 return NULL;
924
925 case WPS_TOKEN_METADATA_TRACK_TITLE:
926 return id3->title;
927
928 case WPS_TOKEN_METADATA_VERSION:
929 switch (id3->id3version)
930 {
931 case ID3_VER_1_0:
932 return "1";
933
934 case ID3_VER_1_1:
935 return "1.1";
936
937 case ID3_VER_2_2:
938 return "2.2";
939
940 case ID3_VER_2_3:
941 return "2.3";
942
943 case ID3_VER_2_4:
944 return "2.4";
945
946 default:
947 return NULL;
948 }
949
950 case WPS_TOKEN_METADATA_YEAR:
951 if( id3->year_string )
952 return id3->year_string;
953
954 if (id3->year) {
955 snprintf(buf, buf_size, "%d", id3->year);
956 return buf;
957 }
958 return NULL;
959
960 case WPS_TOKEN_METADATA_COMMENT:
961 return id3->comment;
962
963 case WPS_TOKEN_FILE_BITRATE:
964 if(id3->bitrate)
965 snprintf(buf, buf_size, "%d", id3->bitrate);
966 else
967 snprintf(buf, buf_size, "?");
968 return buf;
969
970 case WPS_TOKEN_FILE_CODEC:
971 if (intval)
972 {
973 if(id3->codectype == AFMT_UNKNOWN)
974 *intval = AFMT_NUM_CODECS;
975 else
976 *intval = id3->codectype;
977 }
978 return id3_get_codec(id3);
979
980 case WPS_TOKEN_FILE_FREQUENCY:
981 snprintf(buf, buf_size, "%ld", id3->frequency);
982 return buf;
983
984 case WPS_TOKEN_FILE_NAME:
985 if (get_dir(buf, buf_size, id3->path, 0)) {
986 /* Remove extension */
987 char* sep = strrchr(buf, '.');
988 if (NULL != sep) {
989 *sep = 0;
2433 } 990 }
2434 else { 991 return buf;
2435 if ((int)(wps_state.id3->elapsed + ff_rewind_count) < 0) 992 }
2436 ff_rewind_count = -wps_state.id3->elapsed; 993 else {
994 return NULL;
995 }
996
997 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION:
998 return get_dir(buf, buf_size, id3->path, 0);
999
1000 case WPS_TOKEN_FILE_PATH:
1001 return id3->path;
1002
1003 case WPS_TOKEN_FILE_SIZE:
1004 snprintf(buf, buf_size, "%ld", id3->filesize / 1024);
1005 return buf;
1006
1007 case WPS_TOKEN_FILE_VBR:
1008 return id3->vbr ? "(avg)" : NULL;
1009
1010 case WPS_TOKEN_FILE_DIRECTORY:
1011 return get_dir(buf, buf_size, id3->path, data->tokens[i].value.i);
1012
1013 case WPS_TOKEN_BATTERY_PERCENT:
1014 {
1015 int l = battery_level();
1016
1017 if (intval)
1018 {
1019 limit = MAX(limit, 2);
1020 if (l > -1) {
1021 /* First enum is used for "unknown level". */
1022 *intval = (limit - 1) * l / 100 + 2;
1023 } else {
1024 *intval = 1;
2437 } 1025 }
1026 }
2438 1027
2439 FOR_NB_SCREENS(i) 1028 if (l > -1) {
2440 gui_wps_refresh(&gui_wps[i], 1029 snprintf(buf, buf_size, "%d", l);
2441 (wps_state.wps_time_countup == false)? 1030 return buf;
2442 ff_rewind_count:-ff_rewind_count, 1031 } else {
2443 WPS_REFRESH_PLAYER_PROGRESS | 1032 return "?";
2444 WPS_REFRESH_DYNAMIC); 1033 }
1034 }
2445 1035
2446 break; 1036 case WPS_TOKEN_BATTERY_VOLTS:
1037 {
1038 unsigned int v = battery_voltage();
1039 snprintf(buf, buf_size, "%d.%02d", v/100, v%100);
1040 return buf;
1041 }
2447 1042
2448 case ACTION_WPS_STOPSEEK: 1043 case WPS_TOKEN_BATTERY_TIME:
2449 wps_state.id3->elapsed = wps_state.id3->elapsed+ff_rewind_count; 1044 {
2450 audio_ff_rewind(wps_state.id3->elapsed); 1045 int t = battery_time();
2451 ff_rewind_count = 0; 1046 if (t >= 0)
2452 wps_state.ff_rewind = false; 1047 snprintf(buf, buf_size, "%dh %dm", t / 60, t % 60);
2453 status_set_ffmode(0); 1048 else
2454#if (CONFIG_CODEC != SWCODEC) 1049 strncpy(buf, "?h ?m", buf_size);
2455 if (!wps_state.paused) 1050 return buf;
2456 audio_resume(); 1051 }
1052
1053#if CONFIG_CHARGING
1054 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED:
1055 {
1056 if(charger_input_state==CHARGER)
1057 return "p";
1058 else
1059 return NULL;
1060 }
1061#endif
1062#if CONFIG_CHARGING >= CHARGING_MONITOR
1063 case WPS_TOKEN_BATTERY_CHARGING:
1064 {
1065 if (charge_state == CHARGING || charge_state == TOPOFF) {
1066 return "c";
1067 } else {
1068 return NULL;
1069 }
1070 }
1071#endif
1072
1073 case WPS_TOKEN_PLAYBACK_STATUS:
1074 {
1075 int status = audio_status();
1076 int mode = 1;
1077 if (status == AUDIO_STATUS_PLAY && \
1078 !(status & AUDIO_STATUS_PAUSE))
1079 mode = 2;
1080 if (audio_status() & AUDIO_STATUS_PAUSE && \
1081 (! status_get_ffmode()))
1082 mode = 3;
1083 if (status_get_ffmode() == STATUS_FASTFORWARD)
1084 mode = 4;
1085 if (status_get_ffmode() == STATUS_FASTBACKWARD)
1086 mode = 5;
1087
1088 if (intval) {
1089 *intval = mode;
1090 }
1091
1092 snprintf(buf, buf_size, "%d", mode);
1093 return buf;
1094 }
1095
1096 case WPS_TOKEN_REPEAT_MODE:
1097 if (intval)
1098 *intval = global_settings.repeat_mode + 1;
1099 snprintf(buf, buf_size, "%d", *intval);
1100 return buf;
1101
1102#if CONFIG_RTC
1103 case WPS_TOKEN_RTC:
1104 tm = get_time();
1105 return NULL;
1106
1107 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1108 /* d: day of month (01..31) */
1109 if (tm->tm_mday > 31 || tm->tm_mday < 1) return NULL;
1110 snprintf(buf, buf_size, "%02d", tm->tm_mday);
1111 return buf;
1112
1113 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1114 /* e: day of month, blank padded ( 1..31) */
1115 if (tm->tm_mday > 31 || tm->tm_mday < 1) return NULL;
1116 snprintf(buf, buf_size, "%2d", tm->tm_mday);
1117 return buf;
1118
1119 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1120 /* H: hour (00..23) */
1121 if (tm->tm_hour > 23) return NULL;
1122 snprintf(buf, buf_size, "%02d", tm->tm_hour);
1123 return buf;
1124
1125 case WPS_TOKEN_RTC_HOUR_24:
1126 /* k: hour ( 0..23) */
1127 if (tm->tm_hour > 23) return NULL;
1128 snprintf(buf, buf_size, "%2d", tm->tm_hour);
1129 return buf;
1130
1131 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1132 /* I: hour (01..12) */
1133 if (tm->tm_hour > 23) return NULL;
1134 snprintf(buf, buf_size, "%02d",
1135 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1136 return buf;
1137
1138 case WPS_TOKEN_RTC_HOUR_12:
1139 /* l: hour ( 1..12) */
1140 if (tm->tm_hour > 23) return NULL;
1141 snprintf(buf, buf_size, "%2d",
1142 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1143 return buf;
1144
1145 case WPS_TOKEN_RTC_MONTH:
1146 /* m: month (01..12) */
1147 if (tm->tm_mon > 11 || tm->tm_mon < 0) return NULL;
1148 snprintf(buf, buf_size, "%02d", tm->tm_mon + 1);
1149 return buf;
1150
1151 case WPS_TOKEN_RTC_MINUTE:
1152 /* M: minute (00..59) */
1153 if (tm->tm_min > 59 || tm->tm_min < 0) return NULL;
1154 snprintf(buf, buf_size, "%02d", tm->tm_min);
1155 return buf;
1156
1157 case WPS_TOKEN_RTC_SECOND:
1158 /* S: second (00..59) */
1159 if (tm->tm_sec > 59 || tm->tm_sec < 0) return NULL;
1160 snprintf(buf, buf_size, "%02d", tm->tm_sec);
1161 return buf;
1162
1163 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1164 /* y: last two digits of year (00..99) */
1165 snprintf(buf, buf_size, "%02d", tm->tm_year % 100);
1166 return buf;
1167
1168 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1169 /* Y: year (1970...) */
1170 if (tm->tm_year > 199 || tm->tm_year < 100) return NULL;
1171 snprintf(buf, buf_size, "%04d", tm->tm_year + 1900);
1172 return buf;
1173
1174 case WPS_TOKEN_RTC_AM_PM_UPPER:
1175 /* p: upper case AM or PM indicator */
1176 snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "AM" : "PM");
1177 return buf;
1178
1179 case WPS_TOKEN_RTC_AM_PM_LOWER:
1180 /* P: lower case am or pm indicator */
1181 snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "am" : "pm");
1182 return buf;
1183
1184 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1185 /* a: abbreviated weekday name (Sun..Sat) */
1186 if (tm->tm_wday > 6 || tm->tm_wday < 0) return NULL;
1187 snprintf(buf, buf_size, "%s",str(dayname[tm->tm_wday]));
1188 return buf;
1189
1190 case WPS_TOKEN_RTC_MONTH_NAME:
1191 /* b: abbreviated month name (Jan..Dec) */
1192 if (tm->tm_mon > 11 || tm->tm_mon < 0) return NULL;
1193 snprintf(buf, buf_size, "%s",str(monthname[tm->tm_mon]));
1194 return buf;
1195
1196 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1197 /* u: day of week (1..7); 1 is Monday */
1198 if (tm->tm_wday > 6 || tm->tm_wday < 0) return NULL;
1199 snprintf(buf, buf_size, "%1d", tm->tm_wday + 1);
1200 return buf;
1201
1202 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1203 /* w: day of week (0..6); 0 is Sunday */
1204 if (tm->tm_wday > 6 || tm->tm_wday < 0) return NULL;
1205 snprintf(buf, buf_size, "%1d", tm->tm_wday);
1206 return buf;
2457#endif 1207#endif
1208
2458#ifdef HAVE_LCD_CHARCELLS 1209#ifdef HAVE_LCD_CHARCELLS
2459 gui_wps_display(); 1210 case WPS_TOKEN_PROGRESSBAR:
1211 {
1212 char *end = utf8encode(data->wps_progress_pat[0], buf);
1213 *end = '\0';
1214 return buf;
1215 }
1216
1217 case WPS_TOKEN_PLAYER_PROGRESSBAR:
1218 if(is_new_player())
1219 {
1220 /* we need 11 characters (full line) for
1221 progress-bar */
1222 snprintf(buf, buf_size, " ");
1223 }
1224 else
1225 {
1226 /* Tell the user if we have an OldPlayer */
1227 snprintf(buf, buf_size, " <Old LCD> ");
1228 }
1229 return buf;
2460#endif 1230#endif
2461 exit = true;
2462 break;
2463 1231
2464 default: 1232 case WPS_TOKEN_DATABASE_PLAYCOUNT:
2465 if(default_event_handler(button) == SYS_USB_CONNECTED) { 1233 if (intval) {
2466 status_set_ffmode(0); 1234 *intval = id3->playcount + 1;
2467 usb = true; 1235 }
2468 exit = true; 1236 snprintf(buf, buf_size, "%ld", id3->playcount);
2469 } 1237 return buf;
2470 break; 1238
1239 case WPS_TOKEN_DATABASE_RATING:
1240 if (intval) {
1241 *intval = id3->rating + 1;
1242 }
1243 snprintf(buf, buf_size, "%d", id3->rating);
1244 return buf;
1245
1246#if (CONFIG_CODEC == SWCODEC)
1247 case WPS_TOKEN_REPLAYGAIN:
1248 {
1249 int val;
1250
1251 if (global_settings.replaygain == 0)
1252 val = 1; /* off */
1253 else
1254 {
1255 int type =
1256 get_replaygain_mode(id3->track_gain_string != NULL,
1257 id3->album_gain_string != NULL);
1258 if (type < 0)
1259 val = 6; /* no tag */
1260 else
1261 val = type + 2;
1262
1263 if (global_settings.replaygain_type == REPLAYGAIN_SHUFFLE)
1264 val += 2;
1265 }
1266
1267 if (intval)
1268 *intval = val;
1269
1270 switch (val)
1271 {
1272 case 1:
1273 case 6:
1274 return "+0.00 dB";
1275 break;
1276 case 2:
1277 case 4:
1278 strncpy(buf, id3->track_gain_string, buf_size);
1279 break;
1280 case 3:
1281 case 5:
1282 strncpy(buf, id3->album_gain_string, buf_size);
1283 break;
1284 }
1285 return buf;
2471 } 1286 }
2472 if (!exit) 1287
2473 button = get_action(CONTEXT_WPS,TIMEOUT_BLOCK); 1288 case WPS_TOKEN_SOUND_PITCH:
1289 snprintf(buf, buf_size, "%d.%d",
1290 *intval / 10, *intval % 10);
1291 return buf;
1292
1293#endif
1294
1295#ifdef HAS_BUTTON_HOLD
1296 case WPS_TOKEN_MAIN_HOLD:
1297 if (button_hold())
1298 return "h";
1299 else
1300 return NULL;
1301#endif
1302#ifdef HAS_REMOTE_BUTTON_HOLD
1303 case WPS_TOKEN_REMOTE_HOLD:
1304 if (remote_button_hold())
1305 return "r";
1306 else
1307 return NULL;
1308#endif
1309
1310#if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
1311 case WPS_TOKEN_VLED_HDD:
1312 if(led_read(HZ/2))
1313 return "h";
1314 else
1315 return NULL;
1316#endif
1317
1318 default:
1319 return NULL;
2474 } 1320 }
2475 action_signalscreenchange();
2476 return usb;
2477} 1321}
2478 1322
2479bool gui_wps_display(void) 1323/* Return the index to the end token for the conditional token at index.
1324 The conditional token can be either a start token or a separator
1325 (i.e. option) token.
1326*/
1327static int find_conditional_end(struct wps_data *data, int index)
2480{ 1328{
2481 int i; 1329 int type = data->tokens[index].type;
2482 if (!wps_state.id3 && !(audio_status() & AUDIO_STATUS_PLAY)) 1330
1331 if (type != WPS_TOKEN_CONDITIONAL_START
1332 && type != WPS_TOKEN_CONDITIONAL_OPTION)
2483 { 1333 {
2484 global_status.resume_index = -1; 1334 /* this function should only be used with "index" pointing to a
2485#ifdef HAVE_LCD_CHARCELLS 1335 WPS_TOKEN_CONDITIONAL_START or a WPS_TOKEN_CONDITIONAL_OPTION */
2486 gui_syncsplash(HZ, str(LANG_END_PLAYLIST_PLAYER)); 1336 return index + 1;
2487#else
2488 gui_syncstatusbar_draw(&statusbars, true);
2489 gui_syncsplash(HZ, str(LANG_END_PLAYLIST_RECORDER));
2490#endif
2491 return true;
2492 } 1337 }
2493 else 1338
1339 int ret = index;
1340 do
1341 ret = data->tokens[ret].value.i;
1342 while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END);
1343
1344 /* ret now is the index to the end token for the conditional. */
1345 return ret;
1346}
1347
1348/* Return the index of the appropriate case for the conditional
1349 that starts at cond_index.
1350*/
1351static int evaluate_conditional(struct gui_wps *gwps, int cond_index)
1352{
1353 if (!gwps)
1354 return 0;
1355
1356 struct wps_data *data = gwps->data;
1357
1358 int ret;
1359 int num_options = data->tokens[cond_index].value.i;
1360 char result[128], *value;
1361 int cond_start = cond_index;
1362
1363 /* find the index of the conditional start token */
1364 while (data->tokens[cond_start].type != WPS_TOKEN_CONDITIONAL_START
1365 && cond_start < data->num_tokens)
1366 cond_start++;
1367
1368 if (num_options > 2) /* enum */
2494 { 1369 {
2495 FOR_NB_SCREENS(i) 1370 int intval = num_options;
1371 /* get_tag needs to know the number of options in the enum */
1372 get_tag(gwps, cond_index + 1, result, sizeof(result), &intval);
1373 /* intval is now the number of the enum option we want to read,
1374 starting from 1 */
1375 if (intval > num_options || intval < 1)
1376 intval = num_options;
1377
1378 int next = cond_start;
1379 int i;
1380 for (i = 1; i < intval; i++)
2496 { 1381 {
2497 gui_wps[i].display->clear_display(); 1382 next = data->tokens[next].value.i;
2498 if (!gui_wps[i].data->wps_loaded) { 1383 }
2499 if ( !gui_wps[i].data->format_buffer[0] ) { 1384 ret = next;
2500 /* set the default wps for the main-screen */ 1385 }
2501 if(i == 0) 1386 else /* %?xx<true|false> or %?<true> */
2502 { 1387 {
1388 value = get_tag(gwps, cond_index + 1, result, sizeof(result), NULL);
1389 ret = value ? cond_start : data->tokens[cond_start].value.i;
1390 }
1391
2503#ifdef HAVE_LCD_BITMAP 1392#ifdef HAVE_LCD_BITMAP
2504#if LCD_DEPTH > 1 1393 /* clear all pictures in the conditional */
2505 unload_wps_backdrop(); 1394 int i;
2506#endif 1395 for (i=0; i < MAX_IMAGES; i++)
2507 wps_data_load(gui_wps[i].data, 1396 {
2508 "%s%?it<%?in<%in. |>%it|%fn>\n" 1397 if (data->img[i].cond_index == cond_index)
2509 "%s%?ia<%ia|%?d2<%d2|(root)>>\n" 1398 clear_image_pos(gwps, i);
2510 "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n" 1399 }
2511 "\n"
2512 "%al%pc/%pt%ar[%pp:%pe]\n"
2513 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
2514 "%pb\n"
2515 "%pm\n", false);
2516#else
2517 wps_data_load(gui_wps[i].data,
2518 "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
2519 "%pc%?ps<*|/>%pt\n", false);
2520#endif 1400#endif
2521 } 1401
2522#if NB_SCREENS == 2 1402 return ret;
2523 /* set the default wps for the remote-screen */ 1403}
2524 else if(i == 1) 1404
2525 { 1405/* Read a (sub)line to the given alignment format buffer.
2526 wps_data_load(gui_wps[i].data, 1406 linebuf is the buffer where the data is actually stored.
2527 "%s%?ia<%ia|%?d2<%d2|(root)>>\n" 1407 align is the alignment format that'll be used to display the text.
2528 "%s%?it<%?in<%in. |>%it|%fn>\n" 1408 The return value indicates whether the line needs to be updated.
2529 "%al%pc/%pt%ar[%pp:%pe]\n" 1409*/
2530 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n" 1410static bool get_line(struct gui_wps *gwps,
2531 "%pb", false); 1411 int line, int subline,
2532 } 1412 struct align_pos *align,
1413 char *linebuf,
1414 int linebuf_size)
1415{
1416 struct wps_data *data = gwps->data;
1417
1418 char temp_buf[128];
1419 char *buf = linebuf; /* will always point to the writing position */
1420 char *linebuf_end = linebuf + linebuf_size - 1;
1421 bool update = false;
1422
1423 /* alignment-related variables */
1424 int cur_align;
1425 char* cur_align_start;
1426 cur_align_start = buf;
1427 cur_align = WPS_ALIGN_LEFT;
1428 align->left = 0;
1429 align->center = 0;
1430 align->right = 0;
1431
1432 /* start at the beginning of the current (sub)line */
1433 int i = data->format_lines[line][subline];
1434
1435 while (data->tokens[i].type != WPS_TOKEN_EOL
1436 && data->tokens[i].type != WPS_TOKEN_SUBLINE_SEPARATOR
1437 && i < data->num_tokens)
1438 {
1439 switch(data->tokens[i].type)
1440 {
1441 case WPS_TOKEN_CONDITIONAL:
1442 /* place ourselves in the right conditional case */
1443 i = evaluate_conditional(gwps, i);
1444 break;
1445
1446 case WPS_TOKEN_CONDITIONAL_OPTION:
1447 /* we've finished in the curent conditional case,
1448 skip to the end of the conditional structure */
1449 i = find_conditional_end(data, i);
1450 break;
1451
1452#ifdef HAVE_LCD_BITMAP
1453 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
1454 {
1455 struct gui_img *img = data->img;
1456 int n = data->tokens[i].value.i;
1457 if (n >= 0 && n < MAX_IMAGES && img[n].loaded)
1458 img[n].display = true;
1459 break;
1460 }
2533#endif 1461#endif
1462
1463 case WPS_TOKEN_ALIGN_LEFT:
1464 case WPS_TOKEN_ALIGN_CENTER:
1465 case WPS_TOKEN_ALIGN_RIGHT:
1466 /* remember where the current aligned text started */
1467 switch (cur_align)
1468 {
1469 case WPS_ALIGN_LEFT:
1470 align->left = cur_align_start;
1471 break;
1472
1473 case WPS_ALIGN_CENTER:
1474 align->center = cur_align_start;
1475 break;
1476
1477 case WPS_ALIGN_RIGHT:
1478 align->right = cur_align_start;
1479 break;
2534 } 1480 }
1481 /* start a new alignment */
1482 switch (data->tokens[i].type)
1483 {
1484 case WPS_TOKEN_ALIGN_LEFT:
1485 cur_align = WPS_ALIGN_LEFT;
1486 break;
1487 case WPS_TOKEN_ALIGN_CENTER:
1488 cur_align = WPS_ALIGN_CENTER;
1489 break;
1490 case WPS_TOKEN_ALIGN_RIGHT:
1491 cur_align = WPS_ALIGN_RIGHT;
1492 break;
1493 default:
1494 break;
1495 }
1496 *buf++ = 0;
1497 cur_align_start = buf;
1498 break;
1499
1500 default:
1501 {
1502 /* get the value of the tag and copy it to the buffer */
1503 char *value = get_tag(gwps, i, temp_buf,
1504 sizeof(temp_buf), NULL);
1505 if (value)
1506 {
1507 update = true;
1508 while (*value && (buf < linebuf_end))
1509 *buf++ = *value++;
1510 }
1511 break;
2535 } 1512 }
2536 } 1513 }
1514 i++;
2537 } 1515 }
2538 yield(); 1516
2539 FOR_NB_SCREENS(i) 1517 /* close the current alignment */
1518 switch (cur_align)
2540 { 1519 {
2541 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_ALL); 1520 case WPS_ALIGN_LEFT:
1521 align->left = cur_align_start;
1522 break;
1523
1524 case WPS_ALIGN_CENTER:
1525 align->center = cur_align_start;
1526 break;
1527
1528 case WPS_ALIGN_RIGHT:
1529 align->right = cur_align_start;
1530 break;
2542 } 1531 }
2543 return false; 1532
1533 return update;
2544} 1534}
2545 1535
2546bool update(struct gui_wps *gwps) 1536/* Calculate which subline should be displayed for each line */
1537static bool get_curr_subline(struct wps_data *data, int line)
2547{ 1538{
2548 bool track_changed = audio_has_changed_track(); 1539 int search, search_start;
2549 bool retcode = false; 1540 bool reset_subline;
1541 bool new_subline_refresh;
1542 bool only_one_subline;
2550 1543
2551 gwps->state->nid3 = audio_next_track(); 1544 reset_subline = (data->curr_subline[line] == SUBLINE_RESET);
2552 if (track_changed) 1545 new_subline_refresh = false;
1546 only_one_subline = false;
1547
1548 /* if time to advance to next sub-line */
1549 if (TIME_AFTER(current_tick, data->subline_expire_time[line] - 1) ||
1550 reset_subline)
2553 { 1551 {
2554 gwps->display->stop_scroll(); 1552 /* search all sublines until the next subline with time > 0
2555 gwps->state->id3 = audio_current_track(); 1553 is found or we get back to the subline we started with */
1554 if (reset_subline)
1555 search_start = 0;
1556 else
1557 search_start = data->curr_subline[line];
2556 1558
2557 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type 1559 for (search = 0; search < WPS_MAX_SUBLINES; search++)
2558 && strcmp(gwps->state->id3->path, curr_cue->audio_filename))
2559 { 1560 {
2560 /* the current cuesheet isn't the right one any more */ 1561 data->curr_subline[line]++;
2561 1562
2562 if (!strcmp(gwps->state->id3->path, temp_cue->audio_filename)) { 1563 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
2563 /* We have the new cuesheet in memory (temp_cue), 1564 if ((!data->format_lines[line][data->curr_subline[line]]) ||
2564 let's make it the current one ! */ 1565 (data->curr_subline[line] == WPS_MAX_SUBLINES))
2565 memcpy(curr_cue, temp_cue, sizeof(struct cuesheet)); 1566 {
1567 if (data->curr_subline[line] == 1)
1568 only_one_subline = true;
1569 data->curr_subline[line] = 0;
2566 } 1570 }
2567 else {
2568 /* We need to parse the new cuesheet */
2569 1571
2570 char cuepath[MAX_PATH]; 1572 /* if back where we started after search or
2571 strncpy(cuepath, gwps->state->id3->path, MAX_PATH); 1573 only one subline is defined on the line */
2572 char *dot = strrchr(cuepath, '.'); 1574 if (((search > 0) && (data->curr_subline[line] == search_start)) ||
2573 strcpy(dot, ".cue"); 1575 only_one_subline)
2574 1576 {
2575 if (parse_cuesheet(cuepath, curr_cue)) 1577 /* no other subline with a time > 0 exists */
1578 data->subline_expire_time[line] = (reset_subline?
1579 current_tick : data->subline_expire_time[line]) + 100 * HZ;
1580 break;
1581 }
1582 else
1583 {
1584 /* only use this subline if subline time > 0 */
1585 if (data->time_mult[line][data->curr_subline[line]] > 0)
2576 { 1586 {
2577 gwps->state->id3->cuesheet_type = 1; 1587 new_subline_refresh = true;
2578 strcpy(curr_cue->audio_filename, gwps->state->id3->path); 1588 data->subline_expire_time[line] = (reset_subline ?
1589 current_tick : data->subline_expire_time[line]) +
1590 BASE_SUBLINE_TIME * data->time_mult[line][data->curr_subline[line]];
1591 break;
2579 } 1592 }
2580 } 1593 }
1594 }
1595 }
2581 1596
2582 cue_spoof_id3(curr_cue, gwps->state->id3); 1597 return new_subline_refresh;
1598}
1599
1600/* Display a line appropriately according to its alignment format.
1601 format_align contains the text, separated between left, center and right.
1602 line is the index of the line on the screen.
1603 scroll indicates whether the line is a scrolling one or not.
1604*/
1605static void write_line(struct screen *display,
1606 struct align_pos *format_align,
1607 int line,
1608 bool scroll)
1609{
1610
1611 int left_width, left_xpos;
1612 int center_width, center_xpos;
1613 int right_width, right_xpos;
1614 int ypos;
1615 int space_width;
1616 int string_height;
1617
1618 /* calculate different string sizes and positions */
1619 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
1620 if (format_align->left != 0) {
1621 display->getstringsize((unsigned char *)format_align->left,
1622 &left_width, &string_height);
1623 }
1624 else {
1625 left_width = 0;
1626 }
1627 left_xpos = 0;
1628
1629 if (format_align->center != 0) {
1630 display->getstringsize((unsigned char *)format_align->center,
1631 &center_width, &string_height);
1632 }
1633 else {
1634 center_width = 0;
1635 }
1636 center_xpos=(display->width - center_width) / 2;
1637
1638 if (format_align->right != 0) {
1639 display->getstringsize((unsigned char *)format_align->right,
1640 &right_width, &string_height);
1641 }
1642 else {
1643 right_width = 0;
1644 }
1645 right_xpos = (display->width - right_width);
1646
1647 /* Checks for overlapping strings.
1648 If needed the overlapping strings will be merged, separated by a
1649 space */
1650
1651 /* CASE 1: left and centered string overlap */
1652 /* there is a left string, need to merge left and center */
1653 if ((left_width != 0 && center_width != 0) &&
1654 (left_xpos + left_width + space_width > center_xpos)) {
1655 /* replace the former separator '\0' of left and
1656 center string with a space */
1657 *(--format_align->center) = ' ';
1658 /* calculate the new width and position of the merged string */
1659 left_width = left_width + space_width + center_width;
1660 left_xpos = 0;
1661 /* there is no centered string anymore */
1662 center_width = 0;
1663 }
1664 /* there is no left string, move center to left */
1665 if ((left_width == 0 && center_width != 0) &&
1666 (left_xpos + left_width > center_xpos)) {
1667 /* move the center string to the left string */
1668 format_align->left = format_align->center;
1669 /* calculate the new width and position of the string */
1670 left_width = center_width;
1671 left_xpos = 0;
1672 /* there is no centered string anymore */
1673 center_width = 0;
1674 }
1675
1676 /* CASE 2: centered and right string overlap */
1677 /* there is a right string, need to merge center and right */
1678 if ((center_width != 0 && right_width != 0) &&
1679 (center_xpos + center_width + space_width > right_xpos)) {
1680 /* replace the former separator '\0' of center and
1681 right string with a space */
1682 *(--format_align->right) = ' ';
1683 /* move the center string to the right after merge */
1684 format_align->right = format_align->center;
1685 /* calculate the new width and position of the merged string */
1686 right_width = center_width + space_width + right_width;
1687 right_xpos = (display->width - right_width);
1688 /* there is no centered string anymore */
1689 center_width = 0;
1690 }
1691 /* there is no right string, move center to right */
1692 if ((center_width != 0 && right_width == 0) &&
1693 (center_xpos + center_width > right_xpos)) {
1694 /* move the center string to the right string */
1695 format_align->right = format_align->center;
1696 /* calculate the new width and position of the string */
1697 right_width = center_width;
1698 right_xpos = (display->width - right_width);
1699 /* there is no centered string anymore */
1700 center_width = 0;
1701 }
1702
1703 /* CASE 3: left and right overlap
1704 There is no center string anymore, either there never
1705 was one or it has been merged in case 1 or 2 */
1706 /* there is a left string, need to merge left and right */
1707 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
1708 (left_xpos + left_width + space_width > right_xpos)) {
1709 /* replace the former separator '\0' of left and
1710 right string with a space */
1711 *(--format_align->right) = ' ';
1712 /* calculate the new width and position of the string */
1713 left_width = left_width + space_width + right_width;
1714 left_xpos = 0;
1715 /* there is no right string anymore */
1716 right_width = 0;
1717 }
1718 /* there is no left string, move right to left */
1719 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
1720 (left_xpos + left_width > right_xpos)) {
1721 /* move the right string to the left string */
1722 format_align->left = format_align->right;
1723 /* calculate the new width and position of the string */
1724 left_width = right_width;
1725 left_xpos = 0;
1726 /* there is no right string anymore */
1727 right_width = 0;
1728 }
1729
1730 ypos = (line * string_height) + display->getymargin();
1731
1732
1733 if (scroll && left_width > display->width)
1734 {
1735 display->puts_scroll(0, line,
1736 (unsigned char *)format_align->left);
1737 }
1738 else
1739 {
1740#ifdef HAVE_LCD_BITMAP
1741 /* clear the line first */
1742 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1743 display->fillrect(0, ypos, display->width, string_height);
1744 display->set_drawmode(DRMODE_SOLID);
1745#endif
1746
1747 /* Nasty hack: we output an empty scrolling string,
1748 which will reset the scroller for that line */
1749 display->puts_scroll(0, line, (unsigned char *)"");
1750
1751 /* print aligned strings */
1752 if (left_width != 0)
1753 {
1754 display->putsxy(left_xpos, ypos,
1755 (unsigned char *)format_align->left);
2583 } 1756 }
1757 if (center_width != 0)
1758 {
1759 display->putsxy(center_xpos, ypos,
1760 (unsigned char *)format_align->center);
1761 }
1762 if (right_width != 0)
1763 {
1764 display->putsxy(right_xpos, ypos,
1765 (unsigned char *)format_align->right);
1766 }
1767 }
1768}
2584 1769
2585 if (gui_wps_display()) 1770/* Refresh the WPS according to refresh_mode. */
2586 retcode = true; 1771bool gui_wps_refresh(struct gui_wps *gwps,
2587 else{ 1772 int ffwd_offset,
2588 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL); 1773 unsigned char refresh_mode)
1774{
1775 struct wps_data *data = gwps->data;
1776 struct screen *display = gwps->display;
1777 struct wps_state *state = gwps->state;
1778
1779 if(!gwps || !data || !state || !display)
1780 return false;
1781
1782 int line, i;
1783 unsigned char flags;
1784 char linebuf[MAX_PATH];
1785
1786 struct align_pos align;
1787 align.left = NULL;
1788 align.center = NULL;
1789 align.right = NULL;
1790
1791 bool update_line, new_subline_refresh;
1792
1793#ifdef HAVE_LCD_BITMAP
1794 gui_wps_statusbar_draw(gwps, true);
1795
1796 /* to find out wether the peak meter is enabled we
1797 assume it wasn't until we find a line that contains
1798 the peak meter. We can't use peak_meter_enabled itself
1799 because that would mean to turn off the meter thread
1800 temporarily. (That shouldn't matter unless yield
1801 or sleep is called but who knows...)
1802 */
1803 bool enable_pm = false;
1804
1805 /* Set images to not to be displayed */
1806 for (i = 0; i < MAX_IMAGES; i++)
1807 {
1808 data->img[i].display = false;
1809 }
1810#endif
1811
1812 /* reset to first subline if refresh all flag is set */
1813 if (refresh_mode == WPS_REFRESH_ALL)
1814 {
1815 for (i = 0; i < data->num_lines; i++)
1816 {
1817 data->curr_subline[i] = SUBLINE_RESET;
2589 } 1818 }
1819 }
2590 1820
2591 if (gwps->state->id3) 1821#ifdef HAVE_LCD_CHARCELLS
2592 memcpy(gwps->state->current_track_path, gwps->state->id3->path, 1822 for (i = 0; i < 8; i++)
2593 sizeof(gwps->state->current_track_path)); 1823 {
1824 if (data->wps_progress_pat[i] == 0)
1825 data->wps_progress_pat[i] = display->get_locked_pattern();
2594 } 1826 }
1827#endif
2595 1828
2596 if (gwps->state->id3) 1829 if (!state->id3)
2597 { 1830 {
2598 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type 1831 display->stop_scroll();
2599 && (gwps->state->id3->elapsed < curr_cue->curr_track->offset 1832 return false;
2600 || (curr_cue->curr_track_idx < curr_cue->track_count - 1 1833 }
2601 && gwps->state->id3->elapsed >= (curr_cue->curr_track+1)->offset))) 1834
1835 state->ff_rewind_count = ffwd_offset;
1836
1837 for (line = 0; line < data->num_lines; line++)
1838 {
1839 memset(linebuf, 0, sizeof(linebuf));
1840 update_line = false;
1841
1842 /* get current subline for the line */
1843 new_subline_refresh = get_curr_subline(data, line);
1844
1845 flags = data->line_type[line][data->curr_subline[line]];
1846
1847 if (refresh_mode == WPS_REFRESH_ALL || flags & refresh_mode
1848 || new_subline_refresh)
2602 { 1849 {
2603 /* We've changed tracks within the cuesheet : 1850 /* get_line tells us if we need to update the line */
2604 we need to update the ID3 info and refresh the WPS */ 1851 update_line = get_line(gwps, line, data->curr_subline[line],
1852 &align, linebuf, sizeof(linebuf));
1853 }
2605 1854
2606 cue_find_current_track(curr_cue, gwps->state->id3->elapsed); 1855#ifdef HAVE_LCD_BITMAP
2607 cue_spoof_id3(curr_cue, gwps->state->id3); 1856 /* progressbar */
1857 if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
1858 {
1859 /* the progressbar should be alone on its line */
1860 update_line = false;
1861 draw_progressbar(gwps, line);
1862 }
2608 1863
2609 gwps->display->stop_scroll(); 1864 /* peakmeter */
2610 if (gui_wps_display()) 1865 if (flags & refresh_mode & WPS_REFRESH_PEAK_METER)
2611 retcode = true; 1866 {
1867 /* the peakmeter should be alone on its line */
1868 update_line = false;
1869
1870 int h = font_get(FONT_UI)->height;
1871 int peak_meter_y = display->getymargin() + line * h;
1872
1873 /* The user might decide to have the peak meter in the last
1874 line so that it is only displayed if no status bar is
1875 visible. If so we neither want do draw nor enable the
1876 peak meter. */
1877 if (peak_meter_y + h <= display->height) {
1878 /* found a line with a peak meter -> remember that we must
1879 enable it later */
1880 enable_pm = true;
1881 peak_meter_screen(gwps->display, 0, peak_meter_y,
1882 MIN(h, display->height - peak_meter_y));
1883 }
1884 }
1885
1886#else /* HAVE_LCD_CHARCELL */
1887
1888 /* progressbar */
1889 if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
1890 {
1891 if (data->full_line_progressbar)
1892 draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
2612 else 1893 else
2613 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL); 1894 draw_player_progress(gwps);
1895 }
1896#endif
1897
1898 if (update_line)
1899 {
1900 /* calculate alignment and draw the strings */
1901 write_line(display, &align, line, flags & WPS_REFRESH_SCROLL);
2614 } 1902 }
2615 else
2616 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
2617 } 1903 }
2618 1904
2619 gui_wps_statusbar_draw(gwps, false); 1905#ifdef HAVE_LCD_BITMAP
1906 data->peak_meter_enabled = enable_pm;
1907 wps_display_images(gwps);
1908#endif
2620 1909
2621 return retcode; 1910 display->update();
2622}
2623 1911
1912#if CONFIG_BACKLIGHT
1913 if (global_settings.caption_backlight && state->id3)
1914 {
1915 /* turn on backlight n seconds before track ends, and turn it off n
1916 seconds into the new track. n == backlight_timeout, or 5s */
1917 int n = backlight_timeout_value[global_settings.backlight_timeout]
1918 * 1000;
2624 1919
2625void display_keylock_text(bool locked) 1920 if ( n < 1000 )
2626{ 1921 n = 5000; /* use 5s if backlight is always on or off */
2627 char* s;
2628 int i;
2629 FOR_NB_SCREENS(i)
2630 gui_wps[i].display->stop_scroll();
2631 1922
2632#ifdef HAVE_LCD_CHARCELLS 1923 if (((state->id3->elapsed < 1000) ||
2633 if(locked) 1924 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2634 s = str(LANG_KEYLOCK_ON_PLAYER); 1925 (state->paused == false))
2635 else 1926 backlight_on();
2636 s = str(LANG_KEYLOCK_OFF_PLAYER); 1927 }
2637#else 1928#endif
2638 if(locked) 1929#ifdef HAVE_REMOTE_LCD
2639 s = str(LANG_KEYLOCK_ON_RECORDER); 1930 if (global_settings.remote_caption_backlight && state->id3)
2640 else 1931 {
2641 s = str(LANG_KEYLOCK_OFF_RECORDER); 1932 /* turn on remote backlight n seconds before track ends, and turn it
1933 off n seconds into the new track. n == remote_backlight_timeout,
1934 or 5s */
1935 int n = backlight_timeout_value[global_settings.remote_backlight_timeout]
1936 * 1000;
1937
1938 if ( n < 1000 )
1939 n = 5000; /* use 5s if backlight is always on or off */
1940
1941 if (((state->id3->elapsed < 1000) ||
1942 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
1943 (state->paused == false))
1944 remote_backlight_on();
1945 }
2642#endif 1946#endif
2643 gui_syncsplash(HZ, s);
2644}
2645 1947
1948 return true;
1949}
diff --git a/apps/gui/gwps-common.h b/apps/gui/gwps-common.h
index 77bec83951..1203113be1 100644
--- a/apps/gui/gwps-common.h
+++ b/apps/gui/gwps-common.h
@@ -7,7 +7,7 @@
7 * \/ \/ \/ \/ \/ 7 * \/ \/ \/ \/ \/
8 * $Id$ 8 * $Id$
9 * 9 *
10 * Copyright (C) 2002 Björn Stenberg 10 * Copyright (C) 2007 Nicolas Pennequin
11 * 11 *
12 * All files in this archive are subject to the GNU General Public License. 12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement. 13 * See the file COPYING in the source tree root for full license agreement.
@@ -24,16 +24,14 @@
24#include "gwps.h" 24#include "gwps.h"
25 25
26void fade(bool fade_in); 26void fade(bool fade_in);
27void gui_wps_format(struct wps_data *data);
28bool gui_wps_refresh(struct gui_wps *gwps, int ffwd_offset,
29 unsigned char refresh_mode);
30bool gui_wps_display(void); 27bool gui_wps_display(void);
31void setvol(void); 28void setvol(void);
32bool update_onvol_change(struct gui_wps * gwps); 29bool update_onvol_change(struct gui_wps * gwps);
33bool update(struct gui_wps *gwps); 30bool update(struct gui_wps *gwps);
34bool ffwd_rew(int button); 31bool ffwd_rew(int button);
35bool wps_data_preload_tags(struct wps_data *data, char *buf,
36 const char *bmpdir, size_t bmpdirlen);
37void display_keylock_text(bool locked); 32void display_keylock_text(bool locked);
38#endif
39 33
34bool gui_wps_refresh(struct gui_wps *gwps,
35 int ffwd_offset,
36 unsigned char refresh_mode);
37#endif
diff --git a/apps/gui/gwps.c b/apps/gui/gwps.c
index d70863d895..b43ff9d96c 100644
--- a/apps/gui/gwps.c
+++ b/apps/gui/gwps.c
@@ -61,8 +61,6 @@
61#include "ata_idle_notify.h" 61#include "ata_idle_notify.h"
62#include "root_menu.h" 62#include "root_menu.h"
63 63
64#define WPS_DEFAULTCFG WPS_DIR "/rockbox_default.wps"
65#define RWPS_DEFAULTCFG WPS_DIR "/rockbox_default.rwps"
66/* currently only on wps_state is needed */ 64/* currently only on wps_state is needed */
67struct wps_state wps_state; 65struct wps_state wps_state;
68struct gui_wps gui_wps[NB_SCREENS]; 66struct gui_wps gui_wps[NB_SCREENS];
@@ -81,7 +79,6 @@ static void gui_wps_set_disp(struct gui_wps *gui_wps, struct screen *display);
81/* connects a wps with a statusbar*/ 79/* connects a wps with a statusbar*/
82static void gui_wps_set_statusbar(struct gui_wps *gui_wps, struct gui_statusbar *statusbar); 80static void gui_wps_set_statusbar(struct gui_wps *gui_wps, struct gui_statusbar *statusbar);
83 81
84
85#ifdef HAVE_LCD_BITMAP 82#ifdef HAVE_LCD_BITMAP
86static void gui_wps_set_margin(struct gui_wps *gwps) 83static void gui_wps_set_margin(struct gui_wps *gwps)
87{ 84{
@@ -671,144 +668,6 @@ long gui_wps_show(void)
671 668
672/* needs checking if needed end*/ 669/* needs checking if needed end*/
673 670
674/* wps_data*/
675#ifdef HAVE_LCD_BITMAP
676/* Clear the WPS image cache */
677static void wps_clear(struct wps_data *data )
678{
679 int i;
680 /* set images to unloaded and not displayed */
681 for (i = 0; i < MAX_IMAGES; i++) {
682 data->img[i].loaded = false;
683 data->img[i].display = false;
684 data->img[i].always_display = false;
685 }
686 data->wps_sb_tag = false;
687 data->show_sb_on_wps = false;
688 data->progressbar.have_bitmap_pb=false;
689}
690#else
691#define wps_clear(a)
692#endif
693
694/* initial setup of wps_data */
695void wps_data_init(struct wps_data *wps_data)
696{
697#ifdef HAVE_LCD_BITMAP
698 wps_clear(wps_data);
699#else /* HAVE_LCD_CHARCELLS */
700 {
701 int i;
702 for(i = 0; i < 8; i++)
703 wps_data->wps_progress_pat[i] = 0;
704 wps_data->full_line_progressbar = 0;
705 }
706#endif
707 wps_data->format_buffer[0] = '\0';
708 wps_data->wps_loaded = false;
709 wps_data->peak_meter_enabled = false;
710}
711
712static void wps_reset(struct wps_data *data)
713{
714 data->wps_loaded = false;
715 memset(&data->format_buffer, 0, sizeof data->format_buffer);
716 wps_data_init(data);
717}
718
719/* to setup up the wps-data from a format-buffer (isfile = false)
720 from a (wps-)file (isfile = true)*/
721bool wps_data_load(struct wps_data *wps_data,
722 const char *buf,
723 bool isfile)
724{
725 int fd;
726
727 if(!wps_data || !buf)
728 return false;
729
730 if(!isfile)
731 {
732 wps_clear(wps_data);
733 strncpy(wps_data->format_buffer, buf, sizeof(wps_data->format_buffer));
734 wps_data->format_buffer[sizeof(wps_data->format_buffer) - 1] = 0;
735 gui_wps_format(wps_data);
736 return true;
737 }
738 else
739 {
740 /*
741 * Hardcode loading WPS_DEFAULTCFG to cause a reset ideally this
742 * wants to be a virtual file. Feel free to modify dirbrowse()
743 * if you're feeling brave.
744 */
745 if (! strcmp(buf, WPS_DEFAULTCFG) )
746 {
747 wps_reset(wps_data);
748 global_settings.wps_file[0] = 0;
749 return false;
750 }
751
752#ifdef HAVE_REMOTE_LCD
753 if (! strcmp(buf, RWPS_DEFAULTCFG) )
754 {
755 wps_reset(wps_data);
756 global_settings.rwps_file[0] = 0;
757 return false;
758 }
759#endif
760
761 size_t bmpdirlen;
762 char *bmpdir = strrchr(buf, '.');
763 bmpdirlen = bmpdir - buf;
764
765 fd = open(buf, O_RDONLY);
766
767 if (fd >= 0)
768 {
769 unsigned int start = 0;
770
771 wps_reset(wps_data);
772#ifdef HAVE_LCD_BITMAP
773 wps_data->img_buf_ptr = wps_data->img_buf; /* where in image buffer */
774
775 wps_data->img_buf_free = IMG_BUFSIZE; /* free space in image buffer */
776#endif
777 while( ( read_line(fd, &wps_data->format_buffer[start],
778 sizeof(wps_data->format_buffer)-start) ) > 0 )
779 {
780 if(!wps_data_preload_tags(wps_data,
781 &wps_data->format_buffer[start],
782 buf, bmpdirlen))
783 {
784 start += strlen(&wps_data->format_buffer[start]);
785
786 if (start < sizeof(wps_data->format_buffer) - 1)
787 {
788 wps_data->format_buffer[start++] = '\n';
789 wps_data->format_buffer[start] = 0;
790 }
791 }
792 }
793
794 if (start > 0)
795 {
796 gui_wps_format(wps_data);
797 }
798
799 close(fd);
800
801 wps_data->wps_loaded = true;
802
803 return start > 0;
804 }
805 }
806
807 return false;
808}
809
810/* wps_data end */
811
812/* wps_state */ 671/* wps_state */
813 672
814static void wps_state_init(void) 673static void wps_state_init(void)
diff --git a/apps/gui/gwps.h b/apps/gui/gwps.h
index 123bb8fa7c..c18fc0c4e3 100644
--- a/apps/gui/gwps.h
+++ b/apps/gui/gwps.h
@@ -7,7 +7,7 @@
7 * \/ \/ \/ \/ \/ 7 * \/ \/ \/ \/ \/
8 * $Id$ 8 * $Id$
9 * 9 *
10 * Copyright (C) 2002 Jerome Kuptz 10 * Copyright (C) 2007 Nicolas Pennequin
11 * 11 *
12 * All files in this archive are subject to the GNU General Public License. 12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement. 13 * See the file COPYING in the source tree root for full license agreement.
@@ -49,6 +49,9 @@ struct gui_img{
49 bool loaded; /* load state */ 49 bool loaded; /* load state */
50 bool display; /* is to be displayed */ 50 bool display; /* is to be displayed */
51 bool always_display; /* not using the preload/display mechanism */ 51 bool always_display; /* not using the preload/display mechanism */
52
53 /* the index of the conditional the image is in */
54 unsigned short cond_index;
52}; 55};
53 56
54struct prog_img{ /*progressbar image*/ 57struct prog_img{ /*progressbar image*/
@@ -64,23 +67,189 @@ struct align_pos {
64}; 67};
65 68
66#ifdef HAVE_LCD_BITMAP 69#ifdef HAVE_LCD_BITMAP
70
67#define MAX_IMAGES (26*2) /* a-z and A-Z */ 71#define MAX_IMAGES (26*2) /* a-z and A-Z */
68#define IMG_BUFSIZE ((LCD_HEIGHT*LCD_WIDTH*LCD_DEPTH/8) \ 72#define IMG_BUFSIZE ((LCD_HEIGHT*LCD_WIDTH*LCD_DEPTH/8) \
69 + (2*LCD_HEIGHT*LCD_WIDTH/8)) 73 + (2*LCD_HEIGHT*LCD_WIDTH/8))
70#define WPS_MAX_LINES (LCD_HEIGHT/5+1) 74#define WPS_MAX_LINES (LCD_HEIGHT/5+1)
71#define FORMAT_BUFFER_SIZE 3072 75#define WPS_MAX_TOKENS 1024
76#define WPS_MAX_STRINGS 128
77#define STRING_BUFFER_SIZE 512
78#define WPS_MAX_COND_LEVEL 10
79
72#else 80#else
81
73#define WPS_MAX_LINES 2 82#define WPS_MAX_LINES 2
74#define FORMAT_BUFFER_SIZE 400 83#define WPS_MAX_TOKENS 64
84#define WPS_MAX_STRINGS 32
85#define STRING_BUFFER_SIZE 64
86#define WPS_MAX_COND_LEVEL 5
87
75#endif 88#endif
89
76#define WPS_MAX_SUBLINES 12 90#define WPS_MAX_SUBLINES 12
77#define DEFAULT_SUBLINE_TIME_MULTIPLIER 20 /* (10ths of sec) */ 91#define DEFAULT_SUBLINE_TIME_MULTIPLIER 20 /* (10ths of sec) */
78#define BASE_SUBLINE_TIME 10 /* base time that multiplier is applied to 92#define BASE_SUBLINE_TIME 10 /* base time that multiplier is applied to
79 (1/HZ sec, or 100ths of sec) */ 93 (1/HZ sec, or 100ths of sec) */
80#define SUBLINE_RESET -1 94#define SUBLINE_RESET -1
81 95
96enum wps_token_type {
97 WPS_NO_TOKEN, /* for WPS tags we don't want to save as tokens */
98 WPS_TOKEN_UNKNOWN,
99
100 /* Markers */
101 WPS_TOKEN_CHARACTER,
102 WPS_TOKEN_STRING,
103 WPS_TOKEN_EOL,
104
105 /* Alignment */
106 WPS_TOKEN_ALIGN_LEFT,
107 WPS_TOKEN_ALIGN_CENTER,
108 WPS_TOKEN_ALIGN_RIGHT,
109
110 /* Scrolling */
111 WPS_TOKEN_SCROLL,
112
113 /* Alternating sublines */
114 WPS_TOKEN_SUBLINE_SEPARATOR,
115 WPS_TOKEN_SUBLINE_TIMEOUT,
116
117 /* Battery */
118 WPS_TOKEN_BATTERY_PERCENT,
119 WPS_TOKEN_BATTERY_VOLTS,
120 WPS_TOKEN_BATTERY_TIME,
121 WPS_TOKEN_BATTERY_CHARGER_CONNECTED,
122 WPS_TOKEN_BATTERY_CHARGING,
123 WPS_TOKEN_BATTERY_SLEEPTIME,
124
125#if (CONFIG_CODEC == SWCODEC)
126 /* Sound */
127 WPS_TOKEN_SOUND_PITCH,
128 WPS_TOKEN_REPLAYGAIN,
129#endif
130
131#if CONFIG_RTC
132 /* Time */
133 WPS_TOKEN_RTC,
134 WPS_TOKEN_RTC_DAY_OF_MONTH,
135 WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED,
136 WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED,
137 WPS_TOKEN_RTC_HOUR_24,
138 WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED,
139 WPS_TOKEN_RTC_HOUR_12,
140 WPS_TOKEN_RTC_MONTH,
141 WPS_TOKEN_RTC_MINUTE,
142 WPS_TOKEN_RTC_SECOND,
143 WPS_TOKEN_RTC_YEAR_2_DIGITS,
144 WPS_TOKEN_RTC_YEAR_4_DIGITS,
145 WPS_TOKEN_RTC_AM_PM_UPPER,
146 WPS_TOKEN_RTC_AM_PM_LOWER,
147 WPS_TOKEN_RTC_WEEKDAY_NAME,
148 WPS_TOKEN_RTC_MONTH_NAME,
149 WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON,
150 WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN,
151#endif
152
153 /* Conditional */
154 WPS_TOKEN_CONDITIONAL,
155 WPS_TOKEN_CONDITIONAL_START,
156 WPS_TOKEN_CONDITIONAL_OPTION,
157 WPS_TOKEN_CONDITIONAL_END,
158
159 /* Database */
160 WPS_TOKEN_DATABASE_PLAYCOUNT,
161 WPS_TOKEN_DATABASE_RATING,
162
163 /* File */
164 WPS_TOKEN_FILE_BITRATE,
165 WPS_TOKEN_FILE_CODEC,
166 WPS_TOKEN_FILE_FREQUENCY,
167 WPS_TOKEN_FILE_NAME,
168 WPS_TOKEN_FILE_NAME_WITH_EXTENSION,
169 WPS_TOKEN_FILE_PATH,
170 WPS_TOKEN_FILE_SIZE,
171 WPS_TOKEN_FILE_VBR,
172 WPS_TOKEN_FILE_DIRECTORY,
173
174#ifdef HAVE_LCD_BITMAP
175 /* Image */
176 WPS_TOKEN_IMAGE_BACKDROP,
177 WPS_TOKEN_IMAGE_PROGRESS_BAR,
178 WPS_TOKEN_IMAGE_PRELOAD,
179 WPS_TOKEN_IMAGE_PRELOAD_DISPLAY,
180 WPS_TOKEN_IMAGE_DISPLAY,
181#endif
182
183 /* Metadata */
184 WPS_TOKEN_METADATA_ARTIST,
185 WPS_TOKEN_METADATA_COMPOSER,
186 WPS_TOKEN_METADATA_ALBUM_ARTIST,
187 WPS_TOKEN_METADATA_ALBUM,
188 WPS_TOKEN_METADATA_GENRE,
189 WPS_TOKEN_METADATA_TRACK_NUMBER,
190 WPS_TOKEN_METADATA_TRACK_TITLE,
191 WPS_TOKEN_METADATA_VERSION,
192 WPS_TOKEN_METADATA_YEAR,
193 WPS_TOKEN_METADATA_COMMENT,
194
195 /* Mode */
196 WPS_TOKEN_REPEAT_MODE,
197 WPS_TOKEN_PLAYBACK_STATUS,
198
199#ifdef HAS_BUTTON_HOLD
200 WPS_TOKEN_MAIN_HOLD,
201#endif
202#ifdef HAS_REMOTE_BUTTON_HOLD
203 WPS_TOKEN_REMOTE_HOLD,
204#endif
205
206 /* Progressbar */
207 WPS_TOKEN_PROGRESSBAR,
208 WPS_TOKEN_PLAYER_PROGRESSBAR,
209
210#ifdef HAVE_LCD_BITMAP
211 /* Peakmeter */
212 WPS_TOKEN_PEAKMETER,
213#endif
214
215 /* Volume level */
216 WPS_TOKEN_VOLUME,
217
218 /* Current track */
219 WPS_TOKEN_TRACK_TIME_ELAPSED,
220 WPS_TOKEN_TRACK_TIME_REMAINING,
221 WPS_TOKEN_TRACK_LENGTH,
222
223 /* Playlist */
224 WPS_TOKEN_PLAYLIST_ENTRIES,
225 WPS_TOKEN_PLAYLIST_NAME,
226 WPS_TOKEN_PLAYLIST_POSITION,
227 WPS_TOKEN_PLAYLIST_SHUFFLE,
228
229#ifdef HAVE_LCD_BITMAP
230 /* Statusbar */
231 WPS_TOKEN_STATUSBAR_ENABLED,
232 WPS_TOKEN_STATUSBAR_DISABLED,
233#endif
234
235#if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
236 /* Virtual LED */
237 WPS_TOKEN_VLED_HDD
238#endif
239};
240
241struct wps_token {
242 enum wps_token_type type;
243 bool next;
244 union {
245 char c;
246 unsigned short i;
247 } value;
248};
249
250
82/* wps_data 251/* wps_data
83 this struct old all necessary data which describes the 252 this struct holds all necessary data which describes the
84 viewable content of a wps */ 253 viewable content of a wps */
85struct wps_data 254struct wps_data
86{ 255{
@@ -92,23 +261,32 @@ struct wps_data
92 int img_buf_free; 261 int img_buf_free;
93 bool wps_sb_tag; 262 bool wps_sb_tag;
94 bool show_sb_on_wps; 263 bool show_sb_on_wps;
95#endif 264
96#ifdef HAVE_LCD_CHARCELLS 265 short progress_top;
266 short progress_height;
267 short progress_start;
268 short progress_end;
269 bool peak_meter_enabled;
270#else /*HAVE_LCD_CHARCELLS */
97 unsigned short wps_progress_pat[8]; 271 unsigned short wps_progress_pat[8];
98 bool full_line_progressbar; 272 bool full_line_progressbar;
99#endif 273#endif
100 char format_buffer[FORMAT_BUFFER_SIZE]; 274 unsigned short format_lines[WPS_MAX_LINES][WPS_MAX_SUBLINES];
101 char* format_lines[WPS_MAX_LINES][WPS_MAX_SUBLINES]; 275 unsigned char num_lines;
102 unsigned char line_type[WPS_MAX_LINES][WPS_MAX_SUBLINES]; 276 unsigned char line_type[WPS_MAX_LINES][WPS_MAX_SUBLINES];
103 unsigned short time_mult[WPS_MAX_LINES][WPS_MAX_SUBLINES]; 277 unsigned short time_mult[WPS_MAX_LINES][WPS_MAX_SUBLINES];
104 long subline_expire_time[WPS_MAX_LINES]; 278 long subline_expire_time[WPS_MAX_LINES];
105 int curr_subline[WPS_MAX_LINES]; 279 short curr_subline[WPS_MAX_LINES];
106 int progress_top; 280 unsigned char num_sublines[WPS_MAX_LINES];
107 int progress_height; 281
108 int progress_start; 282 struct wps_token tokens[WPS_MAX_TOKENS];
109 int progress_end; 283 unsigned short num_tokens;
284
285 char string_buffer[STRING_BUFFER_SIZE];
286 char *strings[WPS_MAX_STRINGS];
287 unsigned char num_strings;
288
110 bool wps_loaded; 289 bool wps_loaded;
111 bool peak_meter_enabled;
112}; 290};
113 291
114/* initial setup of wps_data */ 292/* initial setup of wps_data */
diff --git a/apps/gui/wps_debug.c b/apps/gui/wps_debug.c
new file mode 100644
index 0000000000..4532151d71
--- /dev/null
+++ b/apps/gui/wps_debug.c
@@ -0,0 +1,407 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007 Nicolas Pennequin, Dan Everton, Matthias Mohr
11 *
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
14 *
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
17 *
18 ****************************************************************************/
19
20#ifdef DEBUG
21
22#include <stdio.h>
23#include <string.h>
24#include "gwps.h"
25#include "debug.h"
26
27void dump_wps_tokens(struct wps_data *data)
28{
29 int i, j;
30 int indent = 0;
31 char buf[64];
32 bool next;
33
34 /* Dump parsed WPS */
35 for(i = 0; i < data->num_tokens && i < WPS_MAX_TOKENS; i++) {
36
37 next = data->tokens[i].next;
38
39 switch(data->tokens[i].type) {
40 case WPS_TOKEN_UNKNOWN:
41 snprintf(buf, sizeof(buf), "Unknown token");
42 break;
43 case WPS_TOKEN_CHARACTER:
44 snprintf(buf, sizeof(buf), "Character '%c'",
45 data->tokens[i].value.c);
46 break;
47
48 case WPS_TOKEN_STRING:
49 snprintf(buf, sizeof(buf), "String '%s'",
50 data->strings[data->tokens[i].value.i]);
51 break;
52
53 case WPS_TOKEN_EOL:
54 snprintf(buf, sizeof(buf), "%s", "EOL");
55 break;
56
57#ifdef HAVE_LCD_BITMAP
58 case WPS_TOKEN_ALIGN_LEFT:
59 snprintf(buf, sizeof(buf), "%s", "align left");
60 break;
61
62 case WPS_TOKEN_ALIGN_CENTER:
63 snprintf(buf, sizeof(buf), "%s", "align center");
64 break;
65
66 case WPS_TOKEN_ALIGN_RIGHT:
67 snprintf(buf, sizeof(buf), "%s", "align right");
68 break;
69#endif
70
71 case WPS_TOKEN_CONDITIONAL:
72 snprintf(buf, sizeof(buf), "%s, %d options", "conditional",
73 data->tokens[i].value.i);
74 break;
75
76 case WPS_TOKEN_CONDITIONAL_START:
77 snprintf(buf, sizeof(buf), "%s, next cond: %d",
78 "conditional start", data->tokens[i].value.i);
79 indent++;
80 break;
81
82 case WPS_TOKEN_CONDITIONAL_OPTION:
83 snprintf(buf, sizeof(buf), "%s, next cond: %d",
84 "conditional option", data->tokens[i].value.i);
85 break;
86
87 case WPS_TOKEN_CONDITIONAL_END:
88 snprintf(buf, sizeof(buf), "%s", "conditional end");
89 indent--;
90 break;
91
92#ifdef HAVE_LCD_BITMAP
93 case WPS_TOKEN_IMAGE_PRELOAD:
94 snprintf(buf, sizeof(buf), "%s", "preload image");
95 break;
96
97 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
98 snprintf(buf, sizeof(buf), "%s %d", "display preloaded image",
99 data->tokens[i].value.i);
100 break;
101
102 case WPS_TOKEN_IMAGE_DISPLAY:
103 snprintf(buf, sizeof(buf), "%s", "display image");
104 break;
105#endif
106
107#ifdef HAS_BUTTON_HOLD
108 case WPS_TOKEN_MAIN_HOLD:
109 snprintf(buf, sizeof(buf), "%s", "mode hold");
110 break;
111#endif
112
113#ifdef HAS_REMOTE_BUTTON_HOLD
114 case WPS_TOKEN_REMOTE_HOLD:
115 snprintf(buf, sizeof(buf), "%s", "mode remote hold");
116 break;
117#endif
118
119 case WPS_TOKEN_REPEAT_MODE:
120 snprintf(buf, sizeof(buf), "%s", "mode repeat");
121 break;
122
123 case WPS_TOKEN_PLAYBACK_STATUS:
124 snprintf(buf, sizeof(buf), "%s", "mode playback");
125 break;
126
127#if CONFIG_RTC
128 case WPS_TOKEN_RTC_DAY_OF_MONTH:
129 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
130 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
131 case WPS_TOKEN_RTC_HOUR_24:
132 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
133 case WPS_TOKEN_RTC_HOUR_12:
134 case WPS_TOKEN_RTC_MONTH:
135 case WPS_TOKEN_RTC_MINUTE:
136 case WPS_TOKEN_RTC_SECOND:
137 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
138 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
139 case WPS_TOKEN_RTC_AM_PM_UPPER:
140 case WPS_TOKEN_RTC_AM_PM_LOWER:
141 case WPS_TOKEN_RTC_WEEKDAY_NAME:
142 case WPS_TOKEN_RTC_MONTH_NAME:
143 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
144 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
145 case WPS_TOKEN_RTC:
146 snprintf(buf, sizeof(buf), "%s %c", "real-time clock tag:",
147 data->tokens[i].value.c);
148 break;
149#endif
150
151#ifdef HAVE_LCD_BITMAP
152 case WPS_TOKEN_IMAGE_BACKDROP:
153 snprintf(buf, sizeof(buf), "%s", "backdrop image");
154 break;
155
156 case WPS_TOKEN_IMAGE_PROGRESS_BAR:
157 snprintf(buf, sizeof(buf), "%s", "progressbar bitmap");
158 break;
159
160
161 case WPS_TOKEN_STATUSBAR_ENABLED:
162 snprintf(buf, sizeof(buf), "%s", "statusbar enable");
163 break;
164
165 case WPS_TOKEN_STATUSBAR_DISABLED:
166 snprintf(buf, sizeof(buf), "%s", "statusbar disable");
167 break;
168
169 case WPS_TOKEN_PEAKMETER:
170 snprintf(buf, sizeof(buf), "%s", "peakmeter");
171 break;
172#endif
173
174 case WPS_TOKEN_PROGRESSBAR:
175 snprintf(buf, sizeof(buf), "%s", "progressbar");
176 break;
177
178#ifdef HAVE_LCD_CHARCELLS
179 case WPS_TOKEN_PLAYER_PROGRESSBAR:
180 snprintf(buf, sizeof(buf), "%s", "full line progressbar");
181 break;
182#endif
183
184 case WPS_TOKEN_TRACK_TIME_ELAPSED:
185 snprintf(buf, sizeof(buf), "%s", "time elapsed in track");
186 break;
187
188 case WPS_TOKEN_PLAYLIST_ENTRIES:
189 snprintf(buf, sizeof(buf), "%s", "number of entries in playlist");
190 break;
191
192 case WPS_TOKEN_PLAYLIST_NAME:
193 snprintf(buf, sizeof(buf), "%s", "playlist name");
194 break;
195
196 case WPS_TOKEN_PLAYLIST_POSITION:
197 snprintf(buf, sizeof(buf), "%s", "position in playlist");
198 break;
199
200 case WPS_TOKEN_TRACK_TIME_REMAINING:
201 snprintf(buf, sizeof(buf), "%s", "time remaining in track");
202 break;
203
204 case WPS_TOKEN_PLAYLIST_SHUFFLE:
205 snprintf(buf, sizeof(buf), "%s", "playlist shuffle mode");
206 break;
207
208 case WPS_TOKEN_TRACK_LENGTH:
209 snprintf(buf, sizeof(buf), "%s", "track length");
210 break;
211
212 case WPS_TOKEN_VOLUME:
213 snprintf(buf, sizeof(buf), "%s", "volume");
214 break;
215
216 case WPS_TOKEN_METADATA_ARTIST:
217 snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "",
218 "track artist");
219 break;
220
221 case WPS_TOKEN_METADATA_COMPOSER:
222 snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "",
223 "track composer");
224 break;
225
226 case WPS_TOKEN_METADATA_ALBUM:
227 snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "",
228 "track album");
229 break;
230
231 case WPS_TOKEN_METADATA_GENRE:
232 snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "",
233 "track genre");
234 break;
235
236 case WPS_TOKEN_METADATA_TRACK_NUMBER:
237 snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "",
238 "track number");
239 break;
240
241 case WPS_TOKEN_METADATA_TRACK_TITLE:
242 snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "",
243 "track title");
244 break;
245
246 case WPS_TOKEN_METADATA_VERSION:
247 snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "",
248 "track ID3 version");
249 break;
250
251 case WPS_TOKEN_METADATA_YEAR:
252 snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "",
253 "track year");
254 break;
255
256 case WPS_TOKEN_BATTERY_PERCENT:
257 snprintf(buf, sizeof(buf), "%s", "battery percentage");
258 break;
259
260 case WPS_TOKEN_BATTERY_VOLTS:
261 snprintf(buf, sizeof(buf), "%s", "battery voltage");
262 break;
263
264 case WPS_TOKEN_BATTERY_TIME:
265 snprintf(buf, sizeof(buf), "%s", "battery time left");
266 break;
267
268 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED:
269 snprintf(buf, sizeof(buf), "%s", "battery charger connected");
270 break;
271
272 case WPS_TOKEN_BATTERY_CHARGING:
273 snprintf(buf, sizeof(buf), "%s", "battery charging");
274 break;
275
276 case WPS_TOKEN_FILE_BITRATE:
277 snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "",
278 "file bitrate");
279 break;
280
281 case WPS_TOKEN_FILE_CODEC:
282 snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "",
283 "file codec");
284 break;
285
286 case WPS_TOKEN_FILE_FREQUENCY:
287 snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "",
288 "file audio frequency");
289 break;
290
291 case WPS_TOKEN_FILE_NAME:
292 snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "",
293 "file name");
294 break;
295
296 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION:
297 snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "",
298 "file name with extension");
299 break;
300
301 case WPS_TOKEN_FILE_PATH:
302 snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "",
303 "file path");
304 break;
305
306 case WPS_TOKEN_FILE_SIZE:
307 snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "",
308 "file size");
309 break;
310
311 case WPS_TOKEN_FILE_VBR:
312 snprintf(buf, sizeof(buf), "%s%s", next ? "next " : "",
313 "file is vbr");
314 break;
315
316 case WPS_TOKEN_FILE_DIRECTORY:
317 snprintf(buf, sizeof(buf), "%s%s: %d", next ? "next " : "",
318 "file directory, level",
319 data->tokens[i].value.i);
320 break;
321
322 case WPS_TOKEN_SCROLL:
323 snprintf(buf, sizeof(buf), "%s", "scrolling line");
324 break;
325
326 case WPS_TOKEN_SUBLINE_TIMEOUT:
327 snprintf(buf, sizeof(buf), "%s: %d", "subline timeout value",
328 data->tokens[i].value.i);
329 break;
330
331 case WPS_TOKEN_SUBLINE_SEPARATOR:
332 snprintf(buf, sizeof(buf), "%s", "subline separator");
333 break;
334
335 default:
336 snprintf(buf, sizeof(buf), "%s (code: %d)", "FIXME",
337 data->tokens[i].type);
338 break;
339 }
340
341 for(j = 0; j < indent; j++) {
342 DEBUGF("\t");
343 }
344
345 DEBUGF("[%03d] = %s\n", i, buf);
346 }
347 DEBUGF("\n");
348}
349
350void print_line_info(struct wps_data *data)
351{
352 int line, subline;
353
354 DEBUGF("line/subline start indexes :\n");
355 for (line = 0; line < data->num_lines; line++)
356 {
357 DEBUGF("%2d. ", line);
358 for (subline = 0; subline < data->num_sublines[line]; subline++)
359 {
360 DEBUGF("%3d ", data->format_lines[line][subline]);
361 }
362 DEBUGF("\n");
363 }
364
365 DEBUGF("\n");
366
367 DEBUGF("subline time multipliers :\n");
368 for (line = 0; line < data->num_lines; line++)
369 {
370 DEBUGF("%2d. ", line);
371 for (subline = 0; subline < data->num_sublines[line]; subline++)
372 {
373 DEBUGF("%3d ", data->time_mult[line][subline]);
374 }
375 DEBUGF("\n");
376 }
377
378}
379
380void print_wps_strings(struct wps_data *data)
381{
382 DEBUGF("strings :\n");
383 int i, len = 0;
384 for (i=0; i < data->num_strings; i++)
385 {
386 len += strlen(data->strings[i]);
387 DEBUGF("%2d: '%s'\n", i, data->strings[i]);
388 }
389 DEBUGF("total length : %d\n", len);
390 DEBUGF("\n");
391}
392
393#ifdef HAVE_LCD_BITMAP
394void print_img_cond_indexes(struct wps_data *data)
395{
396 DEBUGF("image conditional indexes :\n");
397 int i;
398 for (i=0; i < MAX_IMAGES; i++)
399 {
400 if (data->img[i].cond_index)
401 DEBUGF("%2d: %d\n", i, data->img[i].cond_index);
402 }
403 DEBUGF("\n");
404}
405#endif /*HAVE_LCD_BITMAP */
406
407#endif /* DEBUG */
diff --git a/apps/gui/wps_parser.c b/apps/gui/wps_parser.c
new file mode 100644
index 0000000000..ef9d446444
--- /dev/null
+++ b/apps/gui/wps_parser.c
@@ -0,0 +1,957 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007 Nicolas Pennequin, Dan Everton, Matthias Mohr
11 *
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
14 *
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
17 *
18 ****************************************************************************/
19
20#include <ctype.h>
21#include <stdbool.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include "atoi.h"
26#include "gwps.h"
27#include "settings.h"
28#include "debug.h"
29#include "plugin.h"
30
31#ifdef HAVE_LCD_BITMAP
32#include "bmp.h"
33#if LCD_DEPTH > 1
34#include "backdrop.h"
35#endif
36#endif
37
38#define WPS_DEFAULTCFG WPS_DIR "/rockbox_default.wps"
39#define RWPS_DEFAULTCFG WPS_DIR "/rockbox_default.rwps"
40
41/* level of current conditional.
42 -1 means we're not in a conditional. */
43int level = -1;
44
45/* index of the last WPS_TOKEN_CONDITIONAL_OPTION
46 or WPS_TOKEN_CONDITIONAL_START in current level */
47int lastcond[WPS_MAX_COND_LEVEL];
48
49/* index of the WPS_TOKEN_CONDITIONAL in current level */
50int condindex[WPS_MAX_COND_LEVEL];
51
52/* number of condtional options in current level */
53int numoptions[WPS_MAX_COND_LEVEL];
54
55#ifdef HAVE_LCD_BITMAP
56/* pointers to the bitmap filenames in the WPS source */
57const char *bmp_names[MAX_IMAGES];
58const char *pb_bmp_name;
59#if LCD_DEPTH > 1
60const char *backdrop_bmp_name;
61#endif
62#endif
63
64#ifdef DEBUG
65/* debugging functions */
66extern void dump_wps_tokens(struct wps_data *data);
67extern void print_line_info(struct wps_data *data);
68extern void print_img_cond_indexes(struct wps_data *data);
69extern void print_wps_strings(struct wps_data *data);
70#endif
71
72typedef int (*wps_tag_parse_func)(const char *wps_token, struct wps_data *wps_data);
73
74struct wps_tag {
75 const char name[3];
76 enum wps_token_type type;
77 unsigned char refresh_type;
78 wps_tag_parse_func parse_func;
79};
80
81/* prototypes of all special parse functions : */
82
83static int parse_subline_timeout(const char *wps_token, struct wps_data *wps_data);
84static int parse_progressbar(const char *wps_token, struct wps_data *wps_data);
85static int parse_dir_level(const char *wps_token, struct wps_data *wps_data);
86#ifdef HAVE_LCD_BITMAP
87static int parse_image_special(const char *wps_token, struct wps_data *wps_data);
88static int parse_statusbar(const char *wps_token, struct wps_data *wps_data);
89static int parse_image_display(const char *wps_token, struct wps_data *wps_data);
90static int parse_image_load(const char *wps_token, struct wps_data *wps_data);
91#endif /*HAVE_LCD_BITMAP */
92#if CONFIG_RTC
93static int parse_rtc_format(const char *wps_token, struct wps_data *wps_data);
94
95/* RTC tokens array */
96static const struct wps_tag rtc_tags[] = {
97 { "d", WPS_TOKEN_RTC_DAY_OF_MONTH, 0, NULL },
98 { "e", WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED, 0, NULL },
99 { "H", WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED, 0, NULL },
100 { "k", WPS_TOKEN_RTC_HOUR_24, 0, NULL },
101 { "I", WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED, 0, NULL },
102 { "l", WPS_TOKEN_RTC_HOUR_12, 0, NULL },
103 { "m", WPS_TOKEN_RTC_MONTH, 0, NULL },
104 { "M", WPS_TOKEN_RTC_MINUTE, 0, NULL },
105 { "S", WPS_TOKEN_RTC_SECOND, 0, NULL },
106 { "y", WPS_TOKEN_RTC_YEAR_2_DIGITS, 0, NULL },
107 { "Y", WPS_TOKEN_RTC_YEAR_4_DIGITS, 0, NULL },
108 { "p", WPS_TOKEN_RTC_AM_PM_UPPER, 0, NULL },
109 { "P", WPS_TOKEN_RTC_AM_PM_LOWER, 0, NULL },
110 { "a", WPS_TOKEN_RTC_WEEKDAY_NAME, 0, NULL },
111 { "b", WPS_TOKEN_RTC_MONTH_NAME, 0, NULL },
112 { "u", WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON, 0, NULL },
113 { "w", WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN, 0, NULL },
114 { "\0",WPS_TOKEN_CHARACTER, 0, NULL }
115 /* the array MUST end with a "\0" token */
116};
117#endif
118
119/* array of available tags - those with more characters have to go first
120 (e.g. "xl" and "xd" before "x"). It needs to end with the unknown token. */
121static const struct wps_tag all_tags[] = {
122
123 { "ac", WPS_TOKEN_ALIGN_CENTER, 0, NULL },
124 { "al", WPS_TOKEN_ALIGN_LEFT, 0, NULL },
125 { "ar", WPS_TOKEN_ALIGN_RIGHT, 0, NULL },
126
127 { "bl", WPS_TOKEN_BATTERY_PERCENT, WPS_REFRESH_DYNAMIC, NULL },
128 { "bv", WPS_TOKEN_BATTERY_VOLTS, WPS_REFRESH_DYNAMIC, NULL },
129 { "bt", WPS_TOKEN_BATTERY_TIME, WPS_REFRESH_DYNAMIC, NULL },
130 { "bs", WPS_TOKEN_BATTERY_SLEEPTIME, WPS_REFRESH_DYNAMIC, NULL },
131#if CONFIG_CHARGING >= CHARGING_MONITOR
132 { "bc", WPS_TOKEN_BATTERY_CHARGING, WPS_REFRESH_DYNAMIC, NULL },
133#endif
134#if CONFIG_CHARGING
135 { "bp", WPS_TOKEN_BATTERY_CHARGER_CONNECTED,WPS_REFRESH_DYNAMIC, NULL },
136#endif
137
138#if CONFIG_RTC
139 { "c", WPS_TOKEN_RTC, WPS_REFRESH_DYNAMIC, parse_rtc_format },
140#endif
141
142 /* current file */
143 { "fb", WPS_TOKEN_FILE_BITRATE, WPS_REFRESH_STATIC, NULL },
144 { "fc", WPS_TOKEN_FILE_CODEC, WPS_REFRESH_STATIC, NULL },
145 { "ff", WPS_TOKEN_FILE_FREQUENCY, WPS_REFRESH_STATIC, NULL },
146 { "fm", WPS_TOKEN_FILE_NAME_WITH_EXTENSION, WPS_REFRESH_STATIC, NULL },
147 { "fn", WPS_TOKEN_FILE_NAME, WPS_REFRESH_STATIC, NULL },
148 { "fp", WPS_TOKEN_FILE_PATH, WPS_REFRESH_STATIC, NULL },
149 { "fs", WPS_TOKEN_FILE_SIZE, WPS_REFRESH_STATIC, NULL },
150 { "fv", WPS_TOKEN_FILE_VBR, WPS_REFRESH_STATIC, NULL },
151 { "d", WPS_TOKEN_FILE_DIRECTORY, WPS_REFRESH_STATIC, parse_dir_level },
152
153 /* next file */
154 { "Fb", WPS_TOKEN_FILE_BITRATE, WPS_REFRESH_DYNAMIC, NULL },
155 { "Fc", WPS_TOKEN_FILE_CODEC, WPS_REFRESH_DYNAMIC, NULL },
156 { "Ff", WPS_TOKEN_FILE_FREQUENCY, WPS_REFRESH_DYNAMIC, NULL },
157 { "Fm", WPS_TOKEN_FILE_NAME_WITH_EXTENSION, WPS_REFRESH_DYNAMIC, NULL },
158 { "Fn", WPS_TOKEN_FILE_NAME, WPS_REFRESH_DYNAMIC, NULL },
159 { "Fp", WPS_TOKEN_FILE_PATH, WPS_REFRESH_DYNAMIC, NULL },
160 { "Fs", WPS_TOKEN_FILE_SIZE, WPS_REFRESH_DYNAMIC, NULL },
161 { "Fv", WPS_TOKEN_FILE_VBR, WPS_REFRESH_DYNAMIC, NULL },
162 { "D", WPS_TOKEN_FILE_DIRECTORY, WPS_REFRESH_DYNAMIC,parse_dir_level },
163
164 /* current metadata */
165 { "ia", WPS_TOKEN_METADATA_ARTIST, WPS_REFRESH_STATIC, NULL },
166 { "ic", WPS_TOKEN_METADATA_COMPOSER, WPS_REFRESH_STATIC, NULL },
167 { "id", WPS_TOKEN_METADATA_ALBUM, WPS_REFRESH_STATIC, NULL },
168 { "iA", WPS_TOKEN_METADATA_ALBUM_ARTIST, WPS_REFRESH_STATIC, NULL },
169 { "ig", WPS_TOKEN_METADATA_GENRE, WPS_REFRESH_STATIC, NULL },
170 { "in", WPS_TOKEN_METADATA_TRACK_NUMBER, WPS_REFRESH_STATIC, NULL },
171 { "it", WPS_TOKEN_METADATA_TRACK_TITLE, WPS_REFRESH_STATIC, NULL },
172 { "iv", WPS_TOKEN_METADATA_VERSION, WPS_REFRESH_STATIC, NULL },
173 { "iy", WPS_TOKEN_METADATA_YEAR, WPS_REFRESH_STATIC, NULL },
174 { "iC", WPS_TOKEN_METADATA_COMMENT, WPS_REFRESH_DYNAMIC, NULL },
175
176 /* next metadata */
177 { "Ia", WPS_TOKEN_METADATA_ARTIST, WPS_REFRESH_DYNAMIC, NULL },
178 { "Ic", WPS_TOKEN_METADATA_COMPOSER, WPS_REFRESH_DYNAMIC, NULL },
179 { "Id", WPS_TOKEN_METADATA_ALBUM, WPS_REFRESH_DYNAMIC, NULL },
180 { "IA", WPS_TOKEN_METADATA_ALBUM_ARTIST, WPS_REFRESH_STATIC, NULL },
181 { "Ig", WPS_TOKEN_METADATA_GENRE, WPS_REFRESH_DYNAMIC, NULL },
182 { "In", WPS_TOKEN_METADATA_TRACK_NUMBER, WPS_REFRESH_DYNAMIC, NULL },
183 { "It", WPS_TOKEN_METADATA_TRACK_TITLE, WPS_REFRESH_DYNAMIC, NULL },
184 { "Iv", WPS_TOKEN_METADATA_VERSION, WPS_REFRESH_DYNAMIC, NULL },
185 { "Iy", WPS_TOKEN_METADATA_YEAR, WPS_REFRESH_DYNAMIC, NULL },
186 { "IC", WPS_TOKEN_METADATA_COMMENT, WPS_REFRESH_DYNAMIC, NULL },
187
188#if (CONFIG_CODEC == SWCODEC)
189 { "Sp", WPS_TOKEN_SOUND_PITCH, WPS_REFRESH_DYNAMIC, NULL },
190#endif
191
192#if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
193 { "lh", WPS_TOKEN_VLED_HDD, WPS_REFRESH_DYNAMIC, NULL },
194#endif
195
196#ifdef HAS_BUTTON_HOLD
197 { "mh", WPS_TOKEN_MAIN_HOLD, WPS_REFRESH_DYNAMIC, NULL },
198#endif
199#ifdef HAS_REMOTE_BUTTON_HOLD
200 { "mr", WPS_TOKEN_REMOTE_HOLD, WPS_REFRESH_DYNAMIC, NULL },
201#endif
202
203 { "mm", WPS_TOKEN_REPEAT_MODE, WPS_REFRESH_DYNAMIC, NULL },
204 { "mp", WPS_TOKEN_PLAYBACK_STATUS, WPS_REFRESH_DYNAMIC, NULL },
205
206#ifdef HAVE_LCD_BITMAP
207 { "pm", WPS_TOKEN_PEAKMETER,
208 WPS_REFRESH_PEAK_METER, NULL },
209#else
210 { "pf", WPS_TOKEN_PLAYER_PROGRESSBAR,
211 WPS_REFRESH_DYNAMIC | WPS_REFRESH_PLAYER_PROGRESS,
212 parse_progressbar },
213#endif
214 { "pb", WPS_TOKEN_PROGRESSBAR,
215 WPS_REFRESH_PLAYER_PROGRESS, parse_progressbar },
216
217 { "pv", WPS_TOKEN_VOLUME, WPS_REFRESH_DYNAMIC, NULL },
218
219 { "pc", WPS_TOKEN_TRACK_TIME_ELAPSED, WPS_REFRESH_DYNAMIC, NULL },
220 { "pr", WPS_TOKEN_TRACK_TIME_REMAINING, WPS_REFRESH_DYNAMIC, NULL },
221 { "pt", WPS_TOKEN_TRACK_LENGTH, WPS_REFRESH_STATIC, NULL },
222
223 { "pp", WPS_TOKEN_PLAYLIST_POSITION, WPS_REFRESH_STATIC, NULL },
224 { "pe", WPS_TOKEN_PLAYLIST_ENTRIES, WPS_REFRESH_STATIC, NULL },
225 { "pn", WPS_TOKEN_PLAYLIST_NAME, WPS_REFRESH_STATIC, NULL },
226 { "ps", WPS_TOKEN_PLAYLIST_SHUFFLE, WPS_REFRESH_DYNAMIC, NULL },
227
228 { "rp", WPS_TOKEN_DATABASE_PLAYCOUNT, WPS_REFRESH_DYNAMIC, NULL },
229 { "rr", WPS_TOKEN_DATABASE_RATING, WPS_REFRESH_DYNAMIC, NULL },
230#if CONFIG_CODEC == SWCODEC
231 { "rg", WPS_TOKEN_REPLAYGAIN, WPS_REFRESH_STATIC, NULL },
232#endif
233
234 { "s", WPS_TOKEN_SCROLL, WPS_REFRESH_SCROLL, NULL },
235 { "t", WPS_TOKEN_SUBLINE_TIMEOUT, 0, parse_subline_timeout },
236
237#ifdef HAVE_LCD_BITMAP
238 { "we", WPS_TOKEN_STATUSBAR_ENABLED, 0, parse_statusbar },
239 { "wd", WPS_TOKEN_STATUSBAR_DISABLED, 0, parse_statusbar },
240
241 { "xl", WPS_NO_TOKEN, 0, parse_image_load },
242
243 { "xd", WPS_TOKEN_IMAGE_PRELOAD_DISPLAY,
244 WPS_REFRESH_STATIC, parse_image_display },
245
246 { "x", WPS_TOKEN_IMAGE_DISPLAY, 0, parse_image_load },
247 { "P", WPS_TOKEN_IMAGE_PROGRESS_BAR, 0, parse_image_special },
248#if LCD_DEPTH > 1
249 { "X", WPS_TOKEN_IMAGE_BACKDROP, 0, parse_image_special },
250#endif
251#endif
252
253 { "\0", WPS_TOKEN_UNKNOWN, 0, NULL }
254 /* the array MUST end with a "\0" token */
255};
256
257
258static int skip_end_of_line(const char *wps_token)
259{
260 int skip = 0;
261 while(*(wps_token + skip) != '\n')
262 skip++;
263 return ++skip;
264}
265
266#if CONFIG_RTC
267static int parse_rtc_format(const char *wps_token, struct wps_data *wps_data)
268{
269 int skip = 0, i;
270
271 /* RTC tag format ends with a c or a newline */
272 while (wps_token && *wps_token != 'c' && *wps_token != '\n')
273 {
274 /* find what format char we have */
275 i = 0;
276 while (*(rtc_tags[i].name) && *wps_token != *(rtc_tags[i].name))
277 i++;
278
279 wps_data->num_tokens++;
280 wps_data->tokens[wps_data->num_tokens].type = rtc_tags[i].type;
281 wps_data->tokens[wps_data->num_tokens].value.c = *wps_token;
282 skip ++;
283 wps_token++;
284 }
285
286 /* eat the unwanted c at the end of the format */
287 if (*wps_token == 'c')
288 skip++;
289
290 return skip;
291}
292#endif
293
294#ifdef HAVE_LCD_BITMAP
295
296static int parse_statusbar(const char *wps_token, struct wps_data *wps_data)
297{
298 wps_data->wps_sb_tag = true;
299
300 if (wps_data->tokens[wps_data->num_tokens].type == WPS_TOKEN_STATUSBAR_ENABLED)
301 wps_data->show_sb_on_wps = true;
302 else
303 wps_data->show_sb_on_wps = false;
304
305 /* Skip the rest of the line */
306 return skip_end_of_line(wps_token);
307}
308
309static bool load_bitmap(struct wps_data *wps_data,
310 char* filename,
311 struct bitmap *bm)
312{
313 int ret = read_bmp_file(filename, bm,
314 wps_data->img_buf_free,
315 FORMAT_ANY|FORMAT_TRANSPARENT);
316
317 if (ret > 0)
318 {
319#if LCD_DEPTH == 16
320 if (ret % 2) ret++;
321 /* Always consume an even number of bytes */
322#endif
323 wps_data->img_buf_ptr += ret;
324 wps_data->img_buf_free -= ret;
325
326 return true;
327 }
328 else
329 return false;
330}
331
332static int get_image_id(int c)
333{
334 if(c >= 'a' && c <= 'z')
335 return c - 'a';
336 else if(c >= 'A' && c <= 'Z')
337 return c - 'A' + 26;
338 else
339 return -1;
340}
341
342static char *get_image_filename(const char *start, const char* bmpdir,
343 char *buf, int buf_size)
344{
345 const char *end = strchr(start, '|');
346
347 if ( !end || (end - start) >= (buf_size - ROCKBOX_DIR_LEN - 2) )
348 {
349 buf = "\0";
350 return NULL;
351 }
352
353 int bmpdirlen = strlen(bmpdir);
354
355 strcpy(buf, bmpdir);
356 buf[bmpdirlen] = '/';
357 memcpy( &buf[bmpdirlen + 1], start, end - start);
358 buf[bmpdirlen + 1 + end - start] = 0;
359
360 return buf;
361}
362
363static int parse_image_display(const char *wps_token, struct wps_data *wps_data)
364{
365 int n = get_image_id(*wps_token);
366 wps_data->tokens[wps_data->num_tokens].value.i = n;
367
368 /* if the image is in a conditional, remember it */
369 if (level >= 0)
370 wps_data->img[n].cond_index = condindex[level];
371
372 return 1;
373}
374
375static int parse_image_load(const char *wps_token, struct wps_data *wps_data)
376{
377 int n;
378 const char *ptr = wps_token;
379 char *pos = NULL;
380
381 /* format: %x|n|filename.bmp|x|y|
382 or %xl|n|filename.bmp|x|y| */
383
384 ptr = strchr(ptr, '|') + 1;
385 pos = strchr(ptr, '|');
386 if (pos)
387 {
388 /* get the image ID */
389 n = get_image_id(*ptr);
390
391 /* check the image number and load state */
392 if(n < 0 || n >= MAX_IMAGES || wps_data->img[n].loaded)
393 {
394 /* Skip the rest of the line */
395 return skip_end_of_line(wps_token);
396 }
397
398 ptr = pos + 1;
399
400 /* get image name */
401 bmp_names[n] = ptr;
402
403 pos = strchr(ptr, '|');
404 ptr = pos + 1;
405
406 /* get x-position */
407 pos = strchr(ptr, '|');
408 if (pos)
409 wps_data->img[n].x = atoi(ptr);
410 else
411 {
412 /* weird syntax, bail out */
413 return skip_end_of_line(wps_token);
414 }
415
416 /* get y-position */
417 ptr = pos + 1;
418 pos = strchr(ptr, '|');
419 if (pos)
420 wps_data->img[n].y = atoi(ptr);
421 else
422 {
423 /* weird syntax, bail out */
424 return skip_end_of_line(wps_token);
425 }
426
427 if (wps_data->tokens[wps_data->num_tokens].type == WPS_TOKEN_IMAGE_DISPLAY)
428 wps_data->img[n].always_display = true;
429 }
430
431 /* Skip the rest of the line */
432 return skip_end_of_line(wps_token);
433}
434
435static int parse_image_special(const char *wps_token, struct wps_data *wps_data)
436{
437 if (wps_data->tokens[wps_data->num_tokens].type == WPS_TOKEN_IMAGE_PROGRESS_BAR)
438 {
439 /* format: %P|filename.bmp| */
440 pb_bmp_name = wps_token + 1;
441 }
442#if LCD_DEPTH > 1
443 else if (wps_data->tokens[wps_data->num_tokens].type == WPS_TOKEN_IMAGE_BACKDROP)
444 {
445 /* format: %X|filename.bmp| */
446 backdrop_bmp_name = wps_token + 1;
447 }
448#endif
449
450 (void)wps_data; /* to avoid a warning */
451
452 /* Skip the rest of the line */
453 return skip_end_of_line(wps_token);
454}
455
456#endif /* HAVE_LCD_BITMAP */
457
458static int parse_dir_level(const char *wps_token, struct wps_data *wps_data)
459{
460 char val[] = { *wps_token, '\0' };
461 wps_data->tokens[wps_data->num_tokens].value.i = atoi(val);
462 return 1;
463}
464
465static int parse_subline_timeout(const char *wps_token, struct wps_data *wps_data)
466{
467 int skip = 0;
468 int val = 0;
469 bool have_point = false;
470 bool have_tenth = false;
471
472 while ( isdigit(*wps_token) || *wps_token == '.' )
473 {
474 if (*wps_token != '.')
475 {
476 val *= 10;
477 val += *wps_token - '0';
478 if (have_point)
479 {
480 have_tenth = true;
481 wps_token++;
482 skip++;
483 break;
484 }
485 }
486 else
487 have_point = true;
488
489 wps_token++;
490 skip++;
491 }
492
493 if (have_tenth == false)
494 val *= 10;
495
496 if (val > 0)
497 {
498 int line = wps_data->num_lines;
499 int subline = wps_data->num_sublines[line];
500 wps_data->time_mult[line][subline] = val;
501 }
502
503 wps_data->tokens[wps_data->num_tokens].value.i = val;
504 return skip;
505}
506
507static int parse_progressbar(const char *wps_token, struct wps_data *wps_data)
508{
509#ifdef HAVE_LCD_BITMAP
510
511 short *vals[] = {
512 &wps_data->progress_height,
513 &wps_data->progress_start,
514 &wps_data->progress_end,
515 &wps_data->progress_top };
516
517 /* default values : */
518 wps_data->progress_height = 6;
519 wps_data->progress_start = 0;
520 wps_data->progress_end = 0;
521 wps_data->progress_top = -1;
522
523 int i = 0;
524 char *newline = strchr(wps_token, '\n');
525 char *prev = strchr(wps_token, '|');
526 if (prev && prev < newline) {
527 char *next = strchr(prev+1, '|');
528 while (i < 4 && next && next < newline)
529 {
530 *(vals[i++]) = atoi(++prev);
531 prev = strchr(prev, '|');
532 next = strchr(++next, '|');
533 }
534
535 if (wps_data->progress_height < 3)
536 wps_data->progress_height = 3;
537 if (wps_data->progress_end < wps_data->progress_start + 3)
538 wps_data->progress_end = 0;
539 }
540
541 return newline - wps_token;
542
543#else
544
545 if (*(wps_token-1) == 'f')
546 wps_data->full_line_progressbar = true;
547 else
548 wps_data->full_line_progressbar = false;
549
550 return 0;
551
552#endif
553}
554
555/* Parse a generic token from the given string. Return the length read */
556static int parse_token(const char *wps_token, struct wps_data *wps_data)
557{
558 int skip = 0, taglen = 0;
559 int i = 0;
560 int line = wps_data->num_lines;
561 int subline = wps_data->num_sublines[line];
562
563 switch(*wps_token)
564 {
565
566 case '%':
567 case '<':
568 case '|':
569 case '>':
570 case ';':
571 /* escaped characters */
572 wps_data->tokens[wps_data->num_tokens].type = WPS_TOKEN_CHARACTER;
573 wps_data->tokens[wps_data->num_tokens].value.c = *wps_token;
574 wps_data->num_tokens++;
575 skip++;
576 break;
577
578 case '?':
579 /* conditional tag */
580 wps_data->tokens[wps_data->num_tokens].type = WPS_TOKEN_CONDITIONAL;
581 level++;
582 condindex[level] = wps_data->num_tokens;
583 numoptions[level] = 1;
584 wps_data->num_tokens++;
585 wps_token++;
586 skip++;
587 /* no "break" because a '?' is followed by a regular tag */
588
589 default:
590 /* find what tag we have */
591 while (all_tags[i].name &&
592 strncmp(wps_token, all_tags[i].name, strlen(all_tags[i].name)))
593 i++;
594
595 taglen = strlen(all_tags[i].name);
596 skip += taglen;
597 wps_data->tokens[wps_data->num_tokens].type = all_tags[i].type;
598
599 /* if the tag has a special parsing function, we call it */
600 if (all_tags[i].parse_func)
601 skip += all_tags[i].parse_func(wps_token + taglen, wps_data);
602
603 /* Some tags we don't want to save as tokens */
604 if (all_tags[i].type == WPS_NO_TOKEN)
605 break;
606
607 /* tags that start with 'F', 'I' or 'D' are for the next file */
608 if ( *(all_tags[i].name) == 'I' || *(all_tags[i].name) == 'F'
609 || *(all_tags[i].name) == 'D')
610 wps_data->tokens[wps_data->num_tokens].next = true;
611
612 wps_data->line_type[line][subline] |= all_tags[i].refresh_type;
613 wps_data->num_tokens++;
614 break;
615 }
616
617 return skip;
618}
619
620static bool wps_parse(struct wps_data *data, const char *wps_buffer)
621{
622 if (!data || !wps_buffer || !*wps_buffer)
623 return false;
624
625 int subline;
626 data->num_tokens = 0;
627 char *current_string = data->string_buffer;
628
629 while(wps_buffer && *wps_buffer && data->num_tokens < WPS_MAX_TOKENS
630 && data->num_lines < WPS_MAX_LINES)
631 {
632 switch(*wps_buffer++)
633 {
634
635 /* Regular tag */
636 case '%':
637 wps_buffer += parse_token(wps_buffer, data);
638 break;
639
640 /* Alternating sublines separator */
641 case ';':
642 if (data->num_sublines[data->num_lines]+1 < WPS_MAX_SUBLINES)
643 {
644 data->tokens[data->num_tokens++].type = WPS_TOKEN_SUBLINE_SEPARATOR;
645 subline = ++(data->num_sublines[data->num_lines]);
646 data->format_lines[data->num_lines][subline] = data->num_tokens;
647 }
648 else
649 wps_buffer += skip_end_of_line(wps_buffer);
650
651 break;
652
653 /* Conditional list start */
654 case '<':
655 data->tokens[data->num_tokens].type = WPS_TOKEN_CONDITIONAL_START;
656 lastcond[level] = data->num_tokens++;
657 break;
658
659 /* Conditional list end */
660 case '>':
661 if (level < 0) /* not in a conditional, ignore the char */
662 break;
663
664condlistend: /* close a conditional. sometimes we want to close them even when
665 we don't have a closing token, e.g. at the end of a line. */
666
667 data->tokens[data->num_tokens].type = WPS_TOKEN_CONDITIONAL_END;
668 if (lastcond[level])
669 data->tokens[lastcond[level]].value.i = data->num_tokens;
670
671 lastcond[level] = 0;
672 data->num_tokens++;
673 data->tokens[condindex[level]].value.i = numoptions[level];
674 level--;
675 break;
676
677 /* Conditional list option */
678 case '|':
679 if (level < 0) /* not in a conditional, ignore the char */
680 break;
681
682 data->tokens[data->num_tokens].type = WPS_TOKEN_CONDITIONAL_OPTION;
683 if (lastcond[level])
684 data->tokens[lastcond[level]].value.i = data->num_tokens;
685
686 lastcond[level] = data->num_tokens;
687 numoptions[level]++;
688 data->num_tokens++;
689 break;
690
691 /* Comment */
692 case '#':
693 wps_buffer += skip_end_of_line(wps_buffer);
694 break;
695
696 /* End of this line */
697 case '\n':
698 if (level >= 0)
699 {
700 /* We have unclosed conditionals, so we
701 close them before adding the EOL token */
702 wps_buffer--;
703 goto condlistend;
704 break;
705 }
706 data->tokens[data->num_tokens++].type = WPS_TOKEN_EOL;
707 (data->num_sublines[data->num_lines])++;
708 data->num_lines++;
709
710 if (data->num_lines < WPS_MAX_LINES)
711 {
712 data->format_lines[data->num_lines][0] = data->num_tokens;
713 }
714
715 break;
716
717 /* String */
718 default:
719 if (data->num_strings < WPS_MAX_STRINGS)
720 {
721 data->tokens[data->num_tokens].type = WPS_TOKEN_STRING;
722 data->strings[data->num_strings] = current_string;
723 data->tokens[data->num_tokens].value.i = data->num_strings++;
724 data->num_tokens++;
725
726 /* Copy the first byte */
727 *current_string++ = *(wps_buffer - 1);
728
729 /* continue until we hit something that ends the string */
730 while(wps_buffer &&
731 *wps_buffer != '%' && //*wps_buffer != '#' &&
732 *wps_buffer != '<' && *wps_buffer != '>' &&
733 *wps_buffer != '|' && *wps_buffer != '\n')
734 {
735 *current_string++ = *wps_buffer++;
736 }
737
738 /* null terminate the string */
739 *current_string++ = '\0';
740 }
741
742 break;
743 }
744 }
745
746#ifdef DEBUG
747 /* debugging code */
748 if (false)
749 {
750 dump_wps_tokens(data);
751 print_line_info(data);
752 print_wps_strings(data);
753#ifdef HAVE_LCD_BITMAP
754 print_img_cond_indexes(data);
755#endif
756 }
757#endif
758
759 return true;
760}
761
762#ifdef HAVE_LCD_BITMAP
763/* Clear the WPS image cache */
764static void wps_images_clear(struct wps_data *data)
765{
766 int i;
767 /* set images to unloaded and not displayed */
768 for (i = 0; i < MAX_IMAGES; i++)
769 {
770 data->img[i].loaded = false;
771 data->img[i].display = false;
772 data->img[i].always_display = false;
773 }
774 data->progressbar.have_bitmap_pb = false;
775}
776#endif
777
778/* initial setup of wps_data */
779void wps_data_init(struct wps_data *wps_data)
780{
781#ifdef HAVE_LCD_BITMAP
782 wps_images_clear(wps_data);
783 wps_data->wps_sb_tag = false;
784 wps_data->show_sb_on_wps = false;
785 wps_data->img_buf_ptr = wps_data->img_buf; /* where in image buffer */
786 wps_data->img_buf_free = IMG_BUFSIZE; /* free space in image buffer */
787 wps_data->peak_meter_enabled = false;
788#else /* HAVE_LCD_CHARCELLS */
789 int i;
790 for(i = 0; i < 8; i++)
791 {
792 wps_data->wps_progress_pat[i] = 0;
793 }
794 wps_data->full_line_progressbar = false;
795#endif
796 wps_data->wps_loaded = false;
797}
798
799static void wps_reset(struct wps_data *data)
800{
801 memset(data, 0, sizeof(*data));
802 data->wps_loaded = false;
803 wps_data_init(data);
804}
805
806#ifdef HAVE_LCD_BITMAP
807
808
809static void clear_bmp_names(void)
810{
811 int n;
812 for (n = 0; n < MAX_IMAGES; n++)
813 {
814 bmp_names[n] = NULL;
815 }
816 pb_bmp_name = NULL;
817#if LCD_DEPTH > 1
818 backdrop_bmp_name = NULL;
819#endif
820}
821
822static void load_wps_bitmaps(struct wps_data *wps_data, char *bmpdir)
823{
824 char img_path[MAX_PATH];
825
826 int n;
827 for (n = 0; n < MAX_IMAGES; n++)
828 {
829 if (bmp_names[n])
830 {
831 get_image_filename(bmp_names[n], bmpdir,
832 img_path, sizeof(img_path));
833
834 /* load the image */
835 wps_data->img[n].bm.data = wps_data->img_buf_ptr;
836 if (load_bitmap(wps_data, img_path, &wps_data->img[n].bm))
837 {
838 wps_data->img[n].loaded = true;
839 }
840 }
841 }
842
843 if (pb_bmp_name)
844 {
845 get_image_filename(pb_bmp_name, bmpdir, img_path, sizeof(img_path));
846
847 /* load the image */
848 wps_data->progressbar.bm.data = wps_data->img_buf_ptr;
849 if (load_bitmap(wps_data, img_path, &wps_data->progressbar.bm)
850 && wps_data->progressbar.bm.width <= LCD_WIDTH)
851 {
852 wps_data->progressbar.have_bitmap_pb = true;
853 }
854 }
855
856#if LCD_DEPTH > 1
857 if (backdrop_bmp_name)
858 {
859 get_image_filename(backdrop_bmp_name, bmpdir,
860 img_path, sizeof(img_path));
861 load_wps_backdrop(img_path);
862 }
863#endif
864}
865
866#endif /* HAVE_LCD_BITMAP */
867
868/* to setup up the wps-data from a format-buffer (isfile = false)
869 from a (wps-)file (isfile = true)*/
870bool wps_data_load(struct wps_data *wps_data,
871 const char *buf,
872 bool isfile)
873{
874 if (!wps_data || !buf)
875 return false;
876
877 wps_reset(wps_data);
878
879 if (!isfile)
880 {
881 return wps_parse(wps_data, buf);
882 }
883 else
884 {
885 /*
886 * Hardcode loading WPS_DEFAULTCFG to cause a reset ideally this
887 * wants to be a virtual file. Feel free to modify dirbrowse()
888 * if you're feeling brave.
889 */
890 if (! strcmp(buf, WPS_DEFAULTCFG) )
891 {
892 global_settings.wps_file[0] = 0;
893 return false;
894 }
895
896#ifdef HAVE_REMOTE_LCD
897 if (! strcmp(buf, RWPS_DEFAULTCFG) )
898 {
899 global_settings.rwps_file[0] = 0;
900 return false;
901 }
902#endif
903
904 int fd = open(buf, O_RDONLY);
905
906 if (fd < 0)
907 return false;
908
909 /* get buffer space from the plugin buffer */
910 unsigned int buffersize = 0;
911 char *wps_buffer = (char *)plugin_get_buffer(&buffersize);
912
913 if (!wps_buffer)
914 return false;
915
916 /* copy the file's content to the buffer for parsing */
917 unsigned int start = 0;
918 while(read_line(fd, wps_buffer + start, buffersize - start) > 0)
919 {
920 start += strlen(wps_buffer + start);
921 if (start < buffersize - 1)
922 {
923 wps_buffer[start++] = '\n';
924 wps_buffer[start] = 0;
925 }
926 }
927
928 close(fd);
929
930 if (start <= 0)
931 return false;
932
933#ifdef HAVE_LCD_BITMAP
934 clear_bmp_names();
935#endif
936
937 /* parse the WPS source */
938 if (!wps_parse(wps_data, wps_buffer))
939 return false;
940
941 wps_data->wps_loaded = true;
942
943#ifdef HAVE_LCD_BITMAP
944 /* get the bitmap dir */
945 char bmpdir[MAX_PATH];
946 size_t bmpdirlen;
947 char *dot = strrchr(buf, '.');
948 bmpdirlen = dot - buf;
949 strncpy(bmpdir, buf, dot - buf);
950 bmpdir[bmpdirlen] = 0;
951
952 /* load the bitmaps that were found by the parsing */
953 load_wps_bitmaps(wps_data, bmpdir);
954#endif
955 return true;
956 }
957}
diff --git a/apps/lang/english.lang b/apps/lang/english.lang
index 5cc37485bc..336beec32e 100644
--- a/apps/lang/english.lang
+++ b/apps/lang/english.lang
@@ -7286,13 +7286,13 @@
7286</phrase> 7286</phrase>
7287<phrase> 7287<phrase>
7288 id: LANG_END_PLAYLIST_PLAYER 7288 id: LANG_END_PLAYLIST_PLAYER
7289 desc: when playlist has finished 7289 desc: DEPRECATED
7290 user: 7290 user:
7291 <source> 7291 <source>
7292 *: "End of List" 7292 *: ""
7293 </source> 7293 </source>
7294 <dest> 7294 <dest>
7295 *: "End of List" 7295 *: deprecated
7296 </dest> 7296 </dest>
7297 <voice> 7297 <voice>
7298 *: "" 7298 *: ""
@@ -7304,9 +7304,11 @@
7304 user: 7304 user:
7305 <source> 7305 <source>
7306 *: "End of Song List" 7306 *: "End of Song List"
7307 player: "End of List"
7307 </source> 7308 </source>
7308 <dest> 7309 <dest>
7309 *: "End of Song List" 7310 *: "End of Song List"
7311 player: "End of List"
7310 </dest> 7312 </dest>
7311 <voice> 7313 <voice>
7312 *: "" 7314 *: ""