remove the need for action_signalscreenchange().
[Rockbox.git] / apps / gui / gwps-common.c
blob91f60d7d1433b4bb58eb89dd2fff39e1f5717030
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2007 Nicolas Pennequin
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.
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
18 ****************************************************************************/
19 #include "gwps-common.h"
20 #include "font.h"
21 #include <stdio.h>
22 #include <string.h>
23 #include <stdlib.h>
24 #include "system.h"
25 #include "settings.h"
26 #include "rbunicode.h"
27 #include "rtc.h"
28 #include "audio.h"
29 #include "status.h"
30 #include "power.h"
31 #include "powermgmt.h"
32 #include "sound.h"
33 #include "debug.h"
34 #ifdef HAVE_LCD_CHARCELLS
35 #include "hwcompat.h"
36 #endif
37 #include "abrepeat.h"
38 #include "mp3_playback.h"
39 #include "backlight.h"
40 #include "lang.h"
41 #include "misc.h"
42 #include "splash.h"
43 #include "scrollbar.h"
44 #include "led.h"
45 #include "lcd.h"
46 #ifdef HAVE_LCD_BITMAP
47 #include "peakmeter.h"
48 /* Image stuff */
49 #include "bmp.h"
50 #include "atoi.h"
51 #endif
52 #include "dsp.h"
53 #include "action.h"
54 #include "cuesheet.h"
56 #if (LCD_DEPTH > 1) || (defined(HAVE_LCD_REMOTE) && (LCD_REMOTE_DEPTH > 1))
57 #include "backdrop.h"
58 #endif
60 #define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */
61 /* 3% of 30min file == 54s step size */
62 #define MIN_FF_REWIND_STEP 500
64 /* draws the statusbar on the given wps-screen */
65 #ifdef HAVE_LCD_BITMAP
66 void gui_wps_statusbar_draw(struct gui_wps *wps, bool force)
68 bool draw = global_settings.statusbar;
70 if (wps->data->wps_sb_tag)
71 draw = wps->data->show_sb_on_wps;
73 if (draw)
74 gui_statusbar_draw(wps->statusbar, force);
76 #else
77 #define gui_wps_statusbar_draw(wps, force) \
78 gui_statusbar_draw((wps)->statusbar, (force))
79 #endif
81 /* fades the volume */
82 void fade(bool fade_in)
84 int fp_global_vol = global_settings.volume << 8;
85 int fp_min_vol = sound_min(SOUND_VOLUME) << 8;
86 int fp_step = (fp_global_vol - fp_min_vol) / 30;
88 if (fade_in) {
89 /* fade in */
90 int fp_volume = fp_min_vol;
92 /* zero out the sound */
93 sound_set_volume(fp_min_vol >> 8);
95 sleep(HZ/10); /* let audio thread run */
96 audio_resume();
98 while (fp_volume < fp_global_vol - fp_step) {
99 fp_volume += fp_step;
100 sound_set_volume(fp_volume >> 8);
101 sleep(1);
103 sound_set_volume(global_settings.volume);
105 else {
106 /* fade out */
107 int fp_volume = fp_global_vol;
109 while (fp_volume > fp_min_vol + fp_step) {
110 fp_volume -= fp_step;
111 sound_set_volume(fp_volume >> 8);
112 sleep(1);
114 audio_pause();
116 #if CONFIG_CODEC != SWCODEC
117 #ifndef SIMULATOR
118 /* let audio thread run and wait for the mas to run out of data */
119 while (!mp3_pause_done())
120 #endif
121 sleep(HZ/10);
122 #endif
124 /* reset volume to what it was before the fade */
125 sound_set_volume(global_settings.volume);
129 /* return true if screen restore is needed
130 return false otherwise
132 bool update_onvol_change(struct gui_wps * gwps)
134 gui_wps_statusbar_draw(gwps, false);
135 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
137 #ifdef HAVE_LCD_CHARCELLS
138 gui_splash(gwps->display, 0, "Vol: %3d dB",
139 sound_val2phys(SOUND_VOLUME, global_settings.volume));
140 return true;
141 #endif
142 return false;
145 bool ffwd_rew(int button)
147 static const int ff_rew_steps[] = {
148 1000, 2000, 3000, 4000,
149 5000, 6000, 8000, 10000,
150 15000, 20000, 25000, 30000,
151 45000, 60000
154 unsigned int step = 0; /* current ff/rewind step */
155 unsigned int max_step = 0; /* maximum ff/rewind step */
156 int ff_rewind_count = 0; /* current ff/rewind count (in ticks) */
157 int direction = -1; /* forward=1 or backward=-1 */
158 long accel_tick = 0; /* next time at which to bump the step size */
159 bool exit = false;
160 bool usb = false;
161 int i = 0;
163 if (button == ACTION_NONE)
165 status_set_ffmode(0);
166 return usb;
168 while (!exit)
170 switch ( button )
172 case ACTION_WPS_SEEKFWD:
173 direction = 1;
174 case ACTION_WPS_SEEKBACK:
175 if (wps_state.ff_rewind)
177 if (direction == 1)
179 /* fast forwarding, calc max step relative to end */
180 max_step = (wps_state.id3->length -
181 (wps_state.id3->elapsed +
182 ff_rewind_count)) *
183 FF_REWIND_MAX_PERCENT / 100;
185 else
187 /* rewinding, calc max step relative to start */
188 max_step = (wps_state.id3->elapsed + ff_rewind_count) *
189 FF_REWIND_MAX_PERCENT / 100;
192 max_step = MAX(max_step, MIN_FF_REWIND_STEP);
194 if (step > max_step)
195 step = max_step;
197 ff_rewind_count += step * direction;
199 if (global_settings.ff_rewind_accel != 0 &&
200 current_tick >= accel_tick)
202 step *= 2;
203 accel_tick = current_tick +
204 global_settings.ff_rewind_accel*HZ;
207 else
209 if ( (audio_status() & AUDIO_STATUS_PLAY) &&
210 wps_state.id3 && wps_state.id3->length )
212 if (!wps_state.paused)
213 #if (CONFIG_CODEC == SWCODEC)
214 audio_pre_ff_rewind();
215 #else
216 audio_pause();
217 #endif
218 #if CONFIG_KEYPAD == PLAYER_PAD
219 FOR_NB_SCREENS(i)
220 gui_wps[i].display->stop_scroll();
221 #endif
222 if (direction > 0)
223 status_set_ffmode(STATUS_FASTFORWARD);
224 else
225 status_set_ffmode(STATUS_FASTBACKWARD);
227 wps_state.ff_rewind = true;
229 step = ff_rew_steps[global_settings.ff_rewind_min_step];
231 accel_tick = current_tick +
232 global_settings.ff_rewind_accel*HZ;
234 else
235 break;
238 if (direction > 0) {
239 if ((wps_state.id3->elapsed + ff_rewind_count) >
240 wps_state.id3->length)
241 ff_rewind_count = wps_state.id3->length -
242 wps_state.id3->elapsed;
244 else {
245 if ((int)(wps_state.id3->elapsed + ff_rewind_count) < 0)
246 ff_rewind_count = -wps_state.id3->elapsed;
249 FOR_NB_SCREENS(i)
250 gui_wps_refresh(&gui_wps[i],
251 (wps_state.wps_time_countup == false)?
252 ff_rewind_count:-ff_rewind_count,
253 WPS_REFRESH_PLAYER_PROGRESS |
254 WPS_REFRESH_DYNAMIC);
256 break;
258 case ACTION_WPS_STOPSEEK:
259 wps_state.id3->elapsed = wps_state.id3->elapsed+ff_rewind_count;
260 audio_ff_rewind(wps_state.id3->elapsed);
261 ff_rewind_count = 0;
262 wps_state.ff_rewind = false;
263 status_set_ffmode(0);
264 #if (CONFIG_CODEC != SWCODEC)
265 if (!wps_state.paused)
266 audio_resume();
267 #endif
268 #ifdef HAVE_LCD_CHARCELLS
269 gui_wps_display();
270 #endif
271 exit = true;
272 break;
274 default:
275 if(default_event_handler(button) == SYS_USB_CONNECTED) {
276 status_set_ffmode(0);
277 usb = true;
278 exit = true;
280 break;
282 if (!exit)
283 button = get_action(CONTEXT_WPS,TIMEOUT_BLOCK);
285 return usb;
288 bool gui_wps_display(void)
290 int i;
291 if (!wps_state.id3 && !(audio_status() & AUDIO_STATUS_PLAY))
293 global_status.resume_index = -1;
294 #ifdef HAVE_LCD_BITMAP
295 gui_syncstatusbar_draw(&statusbars, true);
296 #endif
297 gui_syncsplash(HZ, str(LANG_END_PLAYLIST_RECORDER));
298 return true;
300 else
302 FOR_NB_SCREENS(i)
304 gui_wps[i].display->clear_display();
305 if (!gui_wps[i].data->wps_loaded) {
306 if ( !gui_wps[i].data->num_tokens ) {
307 /* set the default wps for the main-screen */
308 if(i == 0)
310 #ifdef HAVE_LCD_BITMAP
311 #if LCD_DEPTH > 1
312 unload_wps_backdrop();
313 #endif
314 wps_data_load(gui_wps[i].data,
315 "%s%?it<%?in<%in. |>%it|%fn>\n"
316 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
317 "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
318 "\n"
319 "%al%pc/%pt%ar[%pp:%pe]\n"
320 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
321 "%pb\n"
322 "%pm\n", false);
323 #else
324 wps_data_load(gui_wps[i].data,
325 "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
326 "%pc%?ps<*|/>%pt\n", false);
327 #endif
329 #if NB_SCREENS == 2
330 /* set the default wps for the remote-screen */
331 else if(i == 1)
333 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
334 unload_remote_wps_backdrop();
335 #endif
336 wps_data_load(gui_wps[i].data,
337 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
338 "%s%?it<%?in<%in. |>%it|%fn>\n"
339 "%al%pc/%pt%ar[%pp:%pe]\n"
340 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
341 "%pb\n", false);
343 #endif
348 yield();
349 FOR_NB_SCREENS(i)
351 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_ALL);
353 return false;
356 bool update(struct gui_wps *gwps)
358 bool track_changed = audio_has_changed_track();
359 bool retcode = false;
361 gwps->state->nid3 = audio_next_track();
362 if (track_changed)
364 gwps->display->stop_scroll();
365 gwps->state->id3 = audio_current_track();
367 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
368 && strcmp(gwps->state->id3->path, curr_cue->audio_filename))
370 /* the current cuesheet isn't the right one any more */
372 if (!strcmp(gwps->state->id3->path, temp_cue->audio_filename)) {
373 /* We have the new cuesheet in memory (temp_cue),
374 let's make it the current one ! */
375 memcpy(curr_cue, temp_cue, sizeof(struct cuesheet));
377 else {
378 /* We need to parse the new cuesheet */
380 char cuepath[MAX_PATH];
382 if (look_for_cuesheet_file(gwps->state->id3->path, cuepath) &&
383 parse_cuesheet(cuepath, curr_cue))
385 gwps->state->id3->cuesheet_type = 1;
386 strcpy(curr_cue->audio_filename, gwps->state->id3->path);
390 cue_spoof_id3(curr_cue, gwps->state->id3);
393 if (gui_wps_display())
394 retcode = true;
395 else{
396 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
399 if (gwps->state->id3)
401 strncpy(gwps->state->current_track_path, gwps->state->id3->path,
402 sizeof(gwps->state->current_track_path));
403 gwps->state->current_track_path[sizeof(gwps->state->current_track_path)-1] = '\0';
407 if (gwps->state->id3)
409 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
410 && (gwps->state->id3->elapsed < curr_cue->curr_track->offset
411 || (curr_cue->curr_track_idx < curr_cue->track_count - 1
412 && gwps->state->id3->elapsed >= (curr_cue->curr_track+1)->offset)))
414 /* We've changed tracks within the cuesheet :
415 we need to update the ID3 info and refresh the WPS */
417 cue_find_current_track(curr_cue, gwps->state->id3->elapsed);
418 cue_spoof_id3(curr_cue, gwps->state->id3);
420 gwps->display->stop_scroll();
421 if (gui_wps_display())
422 retcode = true;
423 else
424 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
426 else
427 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
430 gui_wps_statusbar_draw(gwps, false);
432 return retcode;
436 void display_keylock_text(bool locked)
438 char* s;
439 int i;
440 FOR_NB_SCREENS(i)
441 gui_wps[i].display->stop_scroll();
443 #ifdef HAVE_LCD_CHARCELLS
444 if(locked)
445 s = str(LANG_KEYLOCK_ON_PLAYER);
446 else
447 s = str(LANG_KEYLOCK_OFF_PLAYER);
448 #else
449 if(locked)
450 s = str(LANG_KEYLOCK_ON_RECORDER);
451 else
452 s = str(LANG_KEYLOCK_OFF_RECORDER);
453 #endif
454 gui_syncsplash(HZ, s);
457 #ifdef HAVE_LCD_BITMAP
459 static void draw_progressbar(struct gui_wps *gwps, int line)
461 struct wps_data *data = gwps->data;
462 struct screen *display = gwps->display;
463 struct wps_state *state = gwps->state;
464 int h = font_get(FONT_UI)->height;
466 int sb_y;
467 if (data->progress_top < 0)
468 sb_y = line*h + display->getymargin() +
469 ((h > data->progress_height + 1)
470 ? (h - data->progress_height) / 2 : 1);
471 else
472 sb_y = data->progress_top;
474 if (!data->progress_end)
475 data->progress_end=display->width;
477 if (gwps->data->progressbar.have_bitmap_pb)
478 gui_bitmap_scrollbar_draw(display, data->progressbar.bm,
479 data->progress_start, sb_y,
480 data->progress_end-data->progress_start,
481 data->progressbar.bm.height,
482 state->id3->length ? state->id3->length : 1, 0,
483 state->id3->length ? state->id3->elapsed
484 + state->ff_rewind_count : 0,
485 HORIZONTAL);
486 else
487 gui_scrollbar_draw(display, data->progress_start, sb_y,
488 data->progress_end-data->progress_start,
489 data->progress_height,
490 state->id3->length ? state->id3->length : 1, 0,
491 state->id3->length ? state->id3->elapsed
492 + state->ff_rewind_count : 0,
493 HORIZONTAL);
495 #ifdef AB_REPEAT_ENABLE
496 if ( ab_repeat_mode_enabled() )
497 ab_draw_markers(display, state->id3->length,
498 data->progress_start, data->progress_end, sb_y,
499 data->progress_height);
500 #endif
502 if ( cuesheet_is_enabled() && state->id3->cuesheet_type )
503 cue_draw_markers(display, state->id3->length,
504 data->progress_start, data->progress_end,
505 sb_y+1, data->progress_height-2);
508 /* clears the area where the image was shown */
509 static void clear_image_pos(struct gui_wps *gwps, int n)
511 if(!gwps)
512 return;
513 struct wps_data *data = gwps->data;
514 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
515 gwps->display->fillrect(data->img[n].x, data->img[n].y,
516 data->img[n].bm.width, data->img[n].bm.height);
517 gwps->display->set_drawmode(DRMODE_SOLID);
520 static void wps_draw_image(struct gui_wps *gwps, int n)
522 struct screen *display = gwps->display;
523 struct wps_data *data = gwps->data;
524 if(data->img[n].always_display)
525 display->set_drawmode(DRMODE_FG);
526 else
527 display->set_drawmode(DRMODE_SOLID);
529 #if LCD_DEPTH > 1
530 if(data->img[n].bm.format == FORMAT_MONO) {
531 #endif
532 display->mono_bitmap(data->img[n].bm.data, data->img[n].x,
533 data->img[n].y, data->img[n].bm.width,
534 data->img[n].bm.height);
535 #if LCD_DEPTH > 1
536 } else {
537 display->transparent_bitmap((fb_data *)data->img[n].bm.data,
538 data->img[n].x,
539 data->img[n].y, data->img[n].bm.width,
540 data->img[n].bm.height);
542 #endif
545 static void wps_display_images(struct gui_wps *gwps)
547 if(!gwps || !gwps->data || !gwps->display)
548 return;
550 int n;
551 struct wps_data *data = gwps->data;
552 struct screen *display = gwps->display;
554 for (n = 0; n < MAX_IMAGES; n++)
556 if (data->img[n].loaded &&
557 (data->img[n].display || data->img[n].always_display))
559 wps_draw_image(gwps, n);
562 display->set_drawmode(DRMODE_SOLID);
565 #else /* HAVE_LCD_CHARCELL */
567 static bool draw_player_progress(struct gui_wps *gwps)
569 struct wps_state *state = gwps->state;
570 struct screen *display = gwps->display;
571 unsigned char progress_pattern[7];
572 int pos = 0;
573 int i;
575 if (!state->id3)
576 return false;
578 if (state->id3->length)
579 pos = 36 * (state->id3->elapsed + state->ff_rewind_count)
580 / state->id3->length;
582 for (i = 0; i < 7; i++, pos -= 5)
584 if (pos <= 0)
585 progress_pattern[i] = 0x1f;
586 else if (pos >= 5)
587 progress_pattern[i] = 0x00;
588 else
589 progress_pattern[i] = 0x1f >> pos;
592 display->define_pattern(gwps->data->wps_progress_pat[0], progress_pattern);
593 return true;
596 static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
598 static const unsigned char numbers[10][4] = {
599 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
600 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
601 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
602 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
603 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
604 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
605 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
606 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
607 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
608 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
611 struct wps_state *state = gwps->state;
612 struct screen *display = gwps->display;
613 struct wps_data *data = gwps->data;
614 unsigned char progress_pattern[7];
615 char timestr[10];
616 int time;
617 int time_idx = 0;
618 int pos = 0;
619 int pat_idx = 1;
620 int digit, i, j;
621 bool softchar;
623 if (!state->id3 || buf_size < 34) /* worst case: 11x UTF-8 char + \0 */
624 return;
626 time = state->id3->elapsed + state->ff_rewind_count;
627 if (state->id3->length)
628 pos = 55 * time / state->id3->length;
630 memset(timestr, 0, sizeof(timestr));
631 format_time(timestr, sizeof(timestr)-2, time);
632 timestr[strlen(timestr)] = ':'; /* always safe */
634 for (i = 0; i < 11; i++, pos -= 5)
636 softchar = false;
637 memset(progress_pattern, 0, sizeof(progress_pattern));
639 if ((digit = timestr[time_idx]))
641 softchar = true;
642 digit -= '0';
644 if (timestr[time_idx + 1] == ':') /* ones, left aligned */
646 memcpy(progress_pattern, numbers[digit], 4);
647 time_idx += 2;
649 else /* tens, shifted right */
651 for (j = 0; j < 4; j++)
652 progress_pattern[j] = numbers[digit][j] >> 1;
654 if (time_idx > 0) /* not the first group, add colon in front */
656 progress_pattern[1] |= 0x10;
657 progress_pattern[3] |= 0x10;
659 time_idx++;
662 if (pos >= 5)
663 progress_pattern[5] = progress_pattern[6] = 0x1f;
666 if (pos > 0 && pos < 5)
668 softchar = true;
669 progress_pattern[5] = progress_pattern[6] = (~0x1f >> pos) & 0x1f;
672 if (softchar && pat_idx < 8)
674 display->define_pattern(data->wps_progress_pat[pat_idx],
675 progress_pattern);
676 buf = utf8encode(data->wps_progress_pat[pat_idx], buf);
677 pat_idx++;
679 else if (pos <= 0)
680 buf = utf8encode(' ', buf);
681 else
682 buf = utf8encode(0xe115, buf); /* 2/7 _ */
684 *buf = '\0';
687 #endif /* HAVE_LCD_CHARCELL */
689 /* Extract a part from a path.
691 * buf - buffer extract part to.
692 * buf_size - size of buffer.
693 * path - path to extract from.
694 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
695 * parent of parent, etc.
697 * Returns buf if the desired level was found, NULL otherwise.
699 static char* get_dir(char* buf, int buf_size, const char* path, int level)
701 const char* sep;
702 const char* last_sep;
703 int len;
705 sep = path + strlen(path);
706 last_sep = sep;
708 while (sep > path)
710 if ('/' == *(--sep))
712 if (!level)
713 break;
715 level--;
716 last_sep = sep - 1;
720 if (level || (last_sep <= sep))
721 return NULL;
723 len = MIN(last_sep - sep, buf_size - 1);
724 strncpy(buf, sep + 1, len);
725 buf[len] = 0;
726 return buf;
729 /* Return the tag found at index i and write its value in buf.
730 The return value is buf if the tag had a value, or NULL if not.
732 intval is used with conditionals/enums: when this function is called,
733 intval should contain the number of options in the conditional/enum.
734 When this function returns, intval is -1 if the tag is non numeric or,
735 if the tag is numeric, intval is the enum case we want to go to.
736 When not treating a conditional/enum, intval should be NULL.
738 static char *get_token_value(struct gui_wps *gwps,
739 struct wps_token *token,
740 char *buf, int buf_size,
741 int *intval)
743 if (!gwps)
744 return NULL;
746 struct wps_data *data = gwps->data;
747 struct wps_state *state = gwps->state;
749 if (!data || !state)
750 return NULL;
752 struct mp3entry *id3;
754 if (token->next)
755 id3 = state->nid3;
756 else
757 id3 = state->id3;
759 if (!id3)
760 return NULL;
762 #if CONFIG_RTC
763 struct tm* tm = NULL;
765 /* if the token is an RTC one, update the time
766 and do the necessary checks */
767 if (token->type >= WPS_TOKENS_RTC_BEGIN
768 && token->type <= WPS_TOKENS_RTC_END)
770 tm = get_time();
772 if (!valid_time(tm))
773 return NULL;
775 #endif
777 int limit = 1;
778 if (intval)
780 limit = *intval;
781 *intval = -1;
784 switch (token->type)
786 case WPS_TOKEN_CHARACTER:
787 return &(token->value.c);
789 case WPS_TOKEN_STRING:
790 return data->strings[token->value.i];
792 case WPS_TOKEN_TRACK_TIME_ELAPSED:
793 format_time(buf, buf_size,
794 id3->elapsed + state->ff_rewind_count);
795 return buf;
797 case WPS_TOKEN_TRACK_TIME_REMAINING:
798 format_time(buf, buf_size,
799 id3->length - id3->elapsed -
800 state->ff_rewind_count);
801 return buf;
803 case WPS_TOKEN_TRACK_LENGTH:
804 format_time(buf, buf_size, id3->length);
805 return buf;
807 case WPS_TOKEN_PLAYLIST_ENTRIES:
808 snprintf(buf, buf_size, "%d", playlist_amount());
809 return buf;
811 case WPS_TOKEN_PLAYLIST_NAME:
812 return playlist_name(NULL, buf, buf_size);
814 case WPS_TOKEN_PLAYLIST_POSITION:
815 snprintf(buf, buf_size, "%d",
816 playlist_get_display_index());
817 return buf;
819 case WPS_TOKEN_PLAYLIST_SHUFFLE:
820 if ( global_settings.playlist_shuffle )
821 return "s";
822 else
823 return NULL;
824 break;
826 case WPS_TOKEN_VOLUME:
827 snprintf(buf, buf_size, "%d", global_settings.volume);
828 if (intval)
830 if (global_settings.volume == sound_min(SOUND_VOLUME))
832 *intval = 1;
834 else if (global_settings.volume == 0)
836 *intval = limit - 1;
838 else if (global_settings.volume > 0)
840 *intval = limit;
842 else
844 *intval = (limit - 3) * (global_settings.volume
845 - sound_min(SOUND_VOLUME))
846 / (sound_max(SOUND_VOLUME)
847 - sound_min(SOUND_VOLUME)) + 2;
850 return buf;
852 case WPS_TOKEN_METADATA_ARTIST:
853 return id3->artist;
855 case WPS_TOKEN_METADATA_COMPOSER:
856 return id3->composer;
858 case WPS_TOKEN_METADATA_ALBUM:
859 return id3->album;
861 case WPS_TOKEN_METADATA_ALBUM_ARTIST:
862 return id3->albumartist;
864 case WPS_TOKEN_METADATA_GENRE:
865 return id3->genre_string;
867 case WPS_TOKEN_METADATA_TRACK_NUMBER:
868 if (id3->track_string)
869 return id3->track_string;
871 if (id3->tracknum) {
872 snprintf(buf, buf_size, "%d", id3->tracknum);
873 return buf;
875 return NULL;
877 case WPS_TOKEN_METADATA_TRACK_TITLE:
878 return id3->title;
880 case WPS_TOKEN_METADATA_VERSION:
881 switch (id3->id3version)
883 case ID3_VER_1_0:
884 return "1";
886 case ID3_VER_1_1:
887 return "1.1";
889 case ID3_VER_2_2:
890 return "2.2";
892 case ID3_VER_2_3:
893 return "2.3";
895 case ID3_VER_2_4:
896 return "2.4";
898 default:
899 return NULL;
902 case WPS_TOKEN_METADATA_YEAR:
903 if( id3->year_string )
904 return id3->year_string;
906 if (id3->year) {
907 snprintf(buf, buf_size, "%d", id3->year);
908 return buf;
910 return NULL;
912 case WPS_TOKEN_METADATA_COMMENT:
913 return id3->comment;
915 case WPS_TOKEN_FILE_BITRATE:
916 if(id3->bitrate)
917 snprintf(buf, buf_size, "%d", id3->bitrate);
918 else
919 snprintf(buf, buf_size, "?");
920 return buf;
922 case WPS_TOKEN_FILE_CODEC:
923 if (intval)
925 if(id3->codectype == AFMT_UNKNOWN)
926 *intval = AFMT_NUM_CODECS;
927 else
928 *intval = id3->codectype;
930 return id3_get_codec(id3);
932 case WPS_TOKEN_FILE_FREQUENCY:
933 snprintf(buf, buf_size, "%ld", id3->frequency);
934 return buf;
936 case WPS_TOKEN_FILE_FREQUENCY_KHZ:
937 /* ignore remainders < 100, so 22050 Hz becomes just 22k */
938 if ((id3->frequency % 1000) < 100)
939 snprintf(buf, buf_size, "%ld", id3->frequency / 1000);
940 else
941 snprintf(buf, buf_size, "%ld.%d",
942 id3->frequency / 1000,
943 (id3->frequency % 1000) / 100);
944 return buf;
946 case WPS_TOKEN_FILE_NAME:
947 if (get_dir(buf, buf_size, id3->path, 0)) {
948 /* Remove extension */
949 char* sep = strrchr(buf, '.');
950 if (NULL != sep) {
951 *sep = 0;
953 return buf;
955 else {
956 return NULL;
959 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION:
960 return get_dir(buf, buf_size, id3->path, 0);
962 case WPS_TOKEN_FILE_PATH:
963 return id3->path;
965 case WPS_TOKEN_FILE_SIZE:
966 snprintf(buf, buf_size, "%ld", id3->filesize / 1024);
967 return buf;
969 case WPS_TOKEN_FILE_VBR:
970 return id3->vbr ? "(avg)" : NULL;
972 case WPS_TOKEN_FILE_DIRECTORY:
973 return get_dir(buf, buf_size, id3->path, token->value.i);
975 case WPS_TOKEN_BATTERY_PERCENT:
977 int l = battery_level();
979 if (intval)
981 limit = MAX(limit, 2);
982 if (l > -1) {
983 /* First enum is used for "unknown level". */
984 *intval = (limit - 1) * l / 100 + 2;
985 } else {
986 *intval = 1;
990 if (l > -1) {
991 snprintf(buf, buf_size, "%d", l);
992 return buf;
993 } else {
994 return "?";
998 case WPS_TOKEN_BATTERY_VOLTS:
1000 unsigned int v = battery_voltage();
1001 snprintf(buf, buf_size, "%d.%02d", v/100, v%100);
1002 return buf;
1005 case WPS_TOKEN_BATTERY_TIME:
1007 int t = battery_time();
1008 if (t >= 0)
1009 snprintf(buf, buf_size, "%dh %dm", t / 60, t % 60);
1010 else
1011 strncpy(buf, "?h ?m", buf_size);
1012 return buf;
1015 #if CONFIG_CHARGING
1016 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED:
1018 if(charger_input_state==CHARGER)
1019 return "p";
1020 else
1021 return NULL;
1023 #endif
1024 #if CONFIG_CHARGING >= CHARGING_MONITOR
1025 case WPS_TOKEN_BATTERY_CHARGING:
1027 if (charge_state == CHARGING || charge_state == TOPOFF) {
1028 return "c";
1029 } else {
1030 return NULL;
1033 #endif
1034 case WPS_TOKEN_BATTERY_SLEEPTIME:
1036 if (get_sleep_timer() == 0)
1037 return NULL;
1038 else
1040 format_time(buf, buf_size, get_sleep_timer() * 1000);
1041 return buf;
1045 case WPS_TOKEN_PLAYBACK_STATUS:
1047 int status = audio_status();
1048 int mode = 1;
1049 if (status == AUDIO_STATUS_PLAY && \
1050 !(status & AUDIO_STATUS_PAUSE))
1051 mode = 2;
1052 if (audio_status() & AUDIO_STATUS_PAUSE && \
1053 (! status_get_ffmode()))
1054 mode = 3;
1055 if (status_get_ffmode() == STATUS_FASTFORWARD)
1056 mode = 4;
1057 if (status_get_ffmode() == STATUS_FASTBACKWARD)
1058 mode = 5;
1060 if (intval) {
1061 *intval = mode;
1064 snprintf(buf, buf_size, "%d", mode);
1065 return buf;
1068 case WPS_TOKEN_REPEAT_MODE:
1069 if (intval)
1070 *intval = global_settings.repeat_mode + 1;
1071 snprintf(buf, buf_size, "%d", *intval);
1072 return buf;
1074 #if CONFIG_RTC
1075 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1076 /* d: day of month (01..31) */
1077 snprintf(buf, buf_size, "%02d", tm->tm_mday);
1078 return buf;
1080 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1081 /* e: day of month, blank padded ( 1..31) */
1082 snprintf(buf, buf_size, "%2d", tm->tm_mday);
1083 return buf;
1085 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1086 /* H: hour (00..23) */
1087 snprintf(buf, buf_size, "%02d", tm->tm_hour);
1088 return buf;
1090 case WPS_TOKEN_RTC_HOUR_24:
1091 /* k: hour ( 0..23) */
1092 snprintf(buf, buf_size, "%2d", tm->tm_hour);
1093 return buf;
1095 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1096 /* I: hour (01..12) */
1097 snprintf(buf, buf_size, "%02d",
1098 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1099 return buf;
1101 case WPS_TOKEN_RTC_HOUR_12:
1102 /* l: hour ( 1..12) */
1103 snprintf(buf, buf_size, "%2d",
1104 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1105 return buf;
1107 case WPS_TOKEN_RTC_MONTH:
1108 /* m: month (01..12) */
1109 if (intval)
1110 *intval = tm->tm_mon + 1;
1111 snprintf(buf, buf_size, "%02d", tm->tm_mon + 1);
1112 return buf;
1114 case WPS_TOKEN_RTC_MINUTE:
1115 /* M: minute (00..59) */
1116 snprintf(buf, buf_size, "%02d", tm->tm_min);
1117 return buf;
1119 case WPS_TOKEN_RTC_SECOND:
1120 /* S: second (00..59) */
1121 snprintf(buf, buf_size, "%02d", tm->tm_sec);
1122 return buf;
1124 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1125 /* y: last two digits of year (00..99) */
1126 snprintf(buf, buf_size, "%02d", tm->tm_year % 100);
1127 return buf;
1129 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1130 /* Y: year (1970...) */
1131 snprintf(buf, buf_size, "%04d", tm->tm_year + 1900);
1132 return buf;
1134 case WPS_TOKEN_RTC_AM_PM_UPPER:
1135 /* p: upper case AM or PM indicator */
1136 snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "AM" : "PM");
1137 return buf;
1139 case WPS_TOKEN_RTC_AM_PM_LOWER:
1140 /* P: lower case am or pm indicator */
1141 snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "am" : "pm");
1142 return buf;
1144 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1145 /* a: abbreviated weekday name (Sun..Sat) */
1146 snprintf(buf, buf_size, "%s",str(dayname[tm->tm_wday]));
1147 return buf;
1149 case WPS_TOKEN_RTC_MONTH_NAME:
1150 /* b: abbreviated month name (Jan..Dec) */
1151 snprintf(buf, buf_size, "%s",str(monthname[tm->tm_mon]));
1152 return buf;
1154 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1155 /* u: day of week (1..7); 1 is Monday */
1156 if (intval)
1157 *intval = (tm->tm_wday == 0) ? 7 : tm->tm_wday;
1158 snprintf(buf, buf_size, "%1d", tm->tm_wday + 1);
1159 return buf;
1161 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1162 /* w: day of week (0..6); 0 is Sunday */
1163 if (intval)
1164 *intval = tm->tm_wday + 1;
1165 snprintf(buf, buf_size, "%1d", tm->tm_wday);
1166 return buf;
1167 #else
1168 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1169 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1170 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1171 case WPS_TOKEN_RTC_HOUR_24:
1172 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1173 case WPS_TOKEN_RTC_HOUR_12:
1174 case WPS_TOKEN_RTC_MONTH:
1175 case WPS_TOKEN_RTC_MINUTE:
1176 case WPS_TOKEN_RTC_SECOND:
1177 case WPS_TOKEN_RTC_AM_PM_UPPER:
1178 case WPS_TOKEN_RTC_AM_PM_LOWER:
1179 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1180 strncpy(buf, "--", buf_size);
1181 return buf;
1182 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1183 strncpy(buf, "----", buf_size);
1184 return buf;
1185 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1186 case WPS_TOKEN_RTC_MONTH_NAME:
1187 strncpy(buf, "---", buf_size);
1188 return buf;
1189 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1190 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1191 strncpy(buf, "-", buf_size);
1192 return buf;
1193 #endif
1195 #ifdef HAVE_LCD_CHARCELLS
1196 case WPS_TOKEN_PROGRESSBAR:
1198 char *end = utf8encode(data->wps_progress_pat[0], buf);
1199 *end = '\0';
1200 return buf;
1203 case WPS_TOKEN_PLAYER_PROGRESSBAR:
1204 if(is_new_player())
1206 /* we need 11 characters (full line) for
1207 progress-bar */
1208 snprintf(buf, buf_size, " ");
1210 else
1212 /* Tell the user if we have an OldPlayer */
1213 snprintf(buf, buf_size, " <Old LCD> ");
1215 return buf;
1216 #endif
1218 case WPS_TOKEN_DATABASE_PLAYCOUNT:
1219 if (intval) {
1220 *intval = id3->playcount + 1;
1222 snprintf(buf, buf_size, "%ld", id3->playcount);
1223 return buf;
1225 case WPS_TOKEN_DATABASE_RATING:
1226 if (intval) {
1227 *intval = id3->rating + 1;
1229 snprintf(buf, buf_size, "%d", id3->rating);
1230 return buf;
1232 case WPS_TOKEN_DATABASE_AUTOSCORE:
1233 if (intval)
1234 *intval = id3->score + 1;
1236 snprintf(buf, buf_size, "%d", id3->score);
1237 return buf;
1239 #if (CONFIG_CODEC == SWCODEC)
1240 case WPS_TOKEN_CROSSFADE:
1241 if (intval)
1242 *intval = global_settings.crossfade + 1;
1243 snprintf(buf, buf_size, "%d", global_settings.crossfade);
1244 return buf;
1246 case WPS_TOKEN_REPLAYGAIN:
1248 int val;
1250 if (global_settings.replaygain == 0)
1251 val = 1; /* off */
1252 else
1254 int type =
1255 get_replaygain_mode(id3->track_gain_string != NULL,
1256 id3->album_gain_string != NULL);
1257 if (type < 0)
1258 val = 6; /* no tag */
1259 else
1260 val = type + 2;
1262 if (global_settings.replaygain_type == REPLAYGAIN_SHUFFLE)
1263 val += 2;
1266 if (intval)
1267 *intval = val;
1269 switch (val)
1271 case 1:
1272 case 6:
1273 return "+0.00 dB";
1274 break;
1275 case 2:
1276 case 4:
1277 strncpy(buf, id3->track_gain_string, buf_size);
1278 break;
1279 case 3:
1280 case 5:
1281 strncpy(buf, id3->album_gain_string, buf_size);
1282 break;
1284 return buf;
1286 #endif /* (CONFIG_CODEC == SWCODEC) */
1288 #if (CONFIG_CODEC != MAS3507D)
1289 case WPS_TOKEN_SOUND_PITCH:
1291 int val = sound_get_pitch();
1292 snprintf(buf, buf_size, "%d.%d",
1293 val / 10, val % 10);
1294 return buf;
1296 #endif
1298 case WPS_TOKEN_MAIN_HOLD:
1299 #ifdef HAS_BUTTON_HOLD
1300 if (button_hold())
1301 #else
1302 if (is_keys_locked())
1303 #endif /*hold switch or softlock*/
1304 return "h";
1305 else
1306 return NULL;
1308 #ifdef HAS_REMOTE_BUTTON_HOLD
1309 case WPS_TOKEN_REMOTE_HOLD:
1310 if (remote_button_hold())
1311 return "r";
1312 else
1313 return NULL;
1314 #endif
1316 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
1317 case WPS_TOKEN_VLED_HDD:
1318 if(led_read(HZ/2))
1319 return "h";
1320 else
1321 return NULL;
1322 #endif
1324 default:
1325 return NULL;
1329 /* Return the index to the end token for the conditional token at index.
1330 The conditional token can be either a start token or a separator
1331 (i.e. option) token.
1333 static int find_conditional_end(struct wps_data *data, int index)
1335 int ret = index;
1336 while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END)
1337 ret = data->tokens[ret].value.i;
1339 /* ret now is the index to the end token for the conditional. */
1340 return ret;
1343 /* Return the index of the appropriate case for the conditional
1344 that starts at cond_index.
1346 static int evaluate_conditional(struct gui_wps *gwps, int cond_index)
1348 if (!gwps)
1349 return 0;
1351 struct wps_data *data = gwps->data;
1353 int ret, i;
1354 char result[128], *value;
1355 int num_options = data->tokens[cond_index].value.i;
1357 /* treat ?xx<true> constructs as if they had 2 options. */
1358 if (num_options < 2)
1359 num_options = 2;
1361 int intval = num_options;
1362 /* get_token_value needs to know the number of options in the enum */
1363 value = get_token_value(gwps, &data->tokens[cond_index + 1],
1364 result, sizeof(result), &intval);
1366 /* intval is now the number of the enum option we want to read,
1367 starting from 1. If intval is -1, we check if value is empty. */
1368 if (intval == -1)
1369 intval = (value && *value) ? 1 : num_options;
1370 else if (intval > num_options || intval < 1)
1371 intval = num_options;
1373 /* skip to the right enum case */
1374 int next = cond_index + 2;
1375 for (i = 1; i < intval; i++)
1377 next = data->tokens[next].value.i;
1379 ret = next;
1381 #ifdef HAVE_LCD_BITMAP
1382 /* clear all pictures in the conditional */
1383 for (i = 0; i < MAX_IMAGES; i++)
1385 if (data->img[i].cond_index == cond_index)
1386 clear_image_pos(gwps, i);
1388 #endif
1390 return ret;
1393 /* Read a (sub)line to the given alignment format buffer.
1394 linebuf is the buffer where the data is actually stored.
1395 align is the alignment format that'll be used to display the text.
1396 The return value indicates whether the line needs to be updated.
1398 static bool get_line(struct gui_wps *gwps,
1399 int line, int subline,
1400 struct align_pos *align,
1401 char *linebuf,
1402 int linebuf_size)
1404 struct wps_data *data = gwps->data;
1406 char temp_buf[128];
1407 char *buf = linebuf; /* will always point to the writing position */
1408 char *linebuf_end = linebuf + linebuf_size - 1;
1409 int i, last_token_idx;
1410 bool update = false;
1412 /* alignment-related variables */
1413 int cur_align;
1414 char* cur_align_start;
1415 cur_align_start = buf;
1416 cur_align = WPS_ALIGN_LEFT;
1417 align->left = NULL;
1418 align->center = NULL;
1419 align->right = NULL;
1421 /* Process all tokens of the desired subline */
1422 last_token_idx = wps_last_token_index(data, line, subline);
1423 for (i = wps_first_token_index(data, line, subline);
1424 i <= last_token_idx; i++)
1426 switch(data->tokens[i].type)
1428 case WPS_TOKEN_CONDITIONAL:
1429 /* place ourselves in the right conditional case */
1430 i = evaluate_conditional(gwps, i);
1431 update = true;
1432 break;
1434 case WPS_TOKEN_CONDITIONAL_OPTION:
1435 /* we've finished in the curent conditional case,
1436 skip to the end of the conditional structure */
1437 i = find_conditional_end(data, i);
1438 break;
1440 #ifdef HAVE_LCD_BITMAP
1441 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
1443 struct gui_img *img = data->img;
1444 int n = data->tokens[i].value.i;
1445 if (n >= 0 && n < MAX_IMAGES && img[n].loaded)
1446 img[n].display = true;
1447 break;
1449 #endif
1451 case WPS_TOKEN_ALIGN_LEFT:
1452 case WPS_TOKEN_ALIGN_CENTER:
1453 case WPS_TOKEN_ALIGN_RIGHT:
1454 /* remember where the current aligned text started */
1455 switch (cur_align)
1457 case WPS_ALIGN_LEFT:
1458 align->left = cur_align_start;
1459 break;
1461 case WPS_ALIGN_CENTER:
1462 align->center = cur_align_start;
1463 break;
1465 case WPS_ALIGN_RIGHT:
1466 align->right = cur_align_start;
1467 break;
1469 /* start a new alignment */
1470 switch (data->tokens[i].type)
1472 case WPS_TOKEN_ALIGN_LEFT:
1473 cur_align = WPS_ALIGN_LEFT;
1474 break;
1475 case WPS_TOKEN_ALIGN_CENTER:
1476 cur_align = WPS_ALIGN_CENTER;
1477 break;
1478 case WPS_TOKEN_ALIGN_RIGHT:
1479 cur_align = WPS_ALIGN_RIGHT;
1480 break;
1481 default:
1482 break;
1484 *buf++ = 0;
1485 cur_align_start = buf;
1486 break;
1488 default:
1490 /* get the value of the tag and copy it to the buffer */
1491 char *value = get_token_value(gwps, &data->tokens[i],
1492 temp_buf, sizeof(temp_buf), NULL);
1493 if (value)
1495 update = true;
1496 while (*value && (buf < linebuf_end))
1497 *buf++ = *value++;
1499 break;
1504 /* close the current alignment */
1505 switch (cur_align)
1507 case WPS_ALIGN_LEFT:
1508 align->left = cur_align_start;
1509 break;
1511 case WPS_ALIGN_CENTER:
1512 align->center = cur_align_start;
1513 break;
1515 case WPS_ALIGN_RIGHT:
1516 align->right = cur_align_start;
1517 break;
1520 return update;
1523 static void get_subline_timeout(struct gui_wps *gwps, int line, int subline)
1525 struct wps_data *data = gwps->data;
1526 int i;
1527 int subline_idx = wps_subline_index(data, line, subline);
1528 int last_token_idx = wps_last_token_index(data, line, subline);
1530 data->sublines[subline_idx].time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
1532 for (i = wps_first_token_index(data, line, subline);
1533 i <= last_token_idx; i++)
1535 switch(data->tokens[i].type)
1537 case WPS_TOKEN_CONDITIONAL:
1538 /* place ourselves in the right conditional case */
1539 i = evaluate_conditional(gwps, i);
1540 break;
1542 case WPS_TOKEN_CONDITIONAL_OPTION:
1543 /* we've finished in the curent conditional case,
1544 skip to the end of the conditional structure */
1545 i = find_conditional_end(data, i);
1546 break;
1548 case WPS_TOKEN_SUBLINE_TIMEOUT:
1549 data->sublines[subline_idx].time_mult = data->tokens[i].value.i;
1550 break;
1552 default:
1553 break;
1558 /* Calculates which subline should be displayed for the specified line
1559 Returns true iff the subline must be refreshed */
1560 static bool update_curr_subline(struct gui_wps *gwps, int line)
1562 struct wps_data *data = gwps->data;
1564 int search, search_start, num_sublines;
1565 bool reset_subline;
1566 bool new_subline_refresh;
1567 bool only_one_subline;
1569 num_sublines = data->lines[line].num_sublines;
1570 reset_subline = (data->lines[line].curr_subline == SUBLINE_RESET);
1571 new_subline_refresh = false;
1572 only_one_subline = false;
1574 /* if time to advance to next sub-line */
1575 if (TIME_AFTER(current_tick, data->lines[line].subline_expire_time - 1) ||
1576 reset_subline)
1578 /* search all sublines until the next subline with time > 0
1579 is found or we get back to the subline we started with */
1580 if (reset_subline)
1581 search_start = 0;
1582 else
1583 search_start = data->lines[line].curr_subline;
1585 for (search = 0; search < num_sublines; search++)
1587 data->lines[line].curr_subline++;
1589 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
1590 if (data->lines[line].curr_subline == num_sublines)
1592 if (data->lines[line].curr_subline == 1)
1593 only_one_subline = true;
1594 data->lines[line].curr_subline = 0;
1597 /* if back where we started after search or
1598 only one subline is defined on the line */
1599 if (((search > 0) &&
1600 (data->lines[line].curr_subline == search_start)) ||
1601 only_one_subline)
1603 /* no other subline with a time > 0 exists */
1604 data->lines[line].subline_expire_time = (reset_subline ?
1605 current_tick :
1606 data->lines[line].subline_expire_time) + 100 * HZ;
1607 break;
1609 else
1611 /* get initial time multiplier for this subline */
1612 get_subline_timeout(gwps, line, data->lines[line].curr_subline);
1614 int subline_idx = wps_subline_index(data, line,
1615 data->lines[line].curr_subline);
1617 /* only use this subline if subline time > 0 */
1618 if (data->sublines[subline_idx].time_mult > 0)
1620 new_subline_refresh = true;
1621 data->lines[line].subline_expire_time = (reset_subline ?
1622 current_tick : data->lines[line].subline_expire_time) +
1623 BASE_SUBLINE_TIME*data->sublines[subline_idx].time_mult;
1624 break;
1630 return new_subline_refresh;
1633 /* Display a line appropriately according to its alignment format.
1634 format_align contains the text, separated between left, center and right.
1635 line is the index of the line on the screen.
1636 scroll indicates whether the line is a scrolling one or not.
1638 static void write_line(struct screen *display,
1639 struct align_pos *format_align,
1640 int line,
1641 bool scroll)
1644 int left_width, left_xpos;
1645 int center_width, center_xpos;
1646 int right_width, right_xpos;
1647 int ypos;
1648 int space_width;
1649 int string_height;
1651 /* calculate different string sizes and positions */
1652 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
1653 if (format_align->left != 0) {
1654 display->getstringsize((unsigned char *)format_align->left,
1655 &left_width, &string_height);
1657 else {
1658 left_width = 0;
1660 left_xpos = 0;
1662 if (format_align->center != 0) {
1663 display->getstringsize((unsigned char *)format_align->center,
1664 &center_width, &string_height);
1666 else {
1667 center_width = 0;
1669 center_xpos=(display->width - center_width) / 2;
1671 if (format_align->right != 0) {
1672 display->getstringsize((unsigned char *)format_align->right,
1673 &right_width, &string_height);
1675 else {
1676 right_width = 0;
1678 right_xpos = (display->width - right_width);
1680 /* Checks for overlapping strings.
1681 If needed the overlapping strings will be merged, separated by a
1682 space */
1684 /* CASE 1: left and centered string overlap */
1685 /* there is a left string, need to merge left and center */
1686 if ((left_width != 0 && center_width != 0) &&
1687 (left_xpos + left_width + space_width > center_xpos)) {
1688 /* replace the former separator '\0' of left and
1689 center string with a space */
1690 *(--format_align->center) = ' ';
1691 /* calculate the new width and position of the merged string */
1692 left_width = left_width + space_width + center_width;
1693 left_xpos = 0;
1694 /* there is no centered string anymore */
1695 center_width = 0;
1697 /* there is no left string, move center to left */
1698 if ((left_width == 0 && center_width != 0) &&
1699 (left_xpos + left_width > center_xpos)) {
1700 /* move the center string to the left string */
1701 format_align->left = format_align->center;
1702 /* calculate the new width and position of the string */
1703 left_width = center_width;
1704 left_xpos = 0;
1705 /* there is no centered string anymore */
1706 center_width = 0;
1709 /* CASE 2: centered and right string overlap */
1710 /* there is a right string, need to merge center and right */
1711 if ((center_width != 0 && right_width != 0) &&
1712 (center_xpos + center_width + space_width > right_xpos)) {
1713 /* replace the former separator '\0' of center and
1714 right string with a space */
1715 *(--format_align->right) = ' ';
1716 /* move the center string to the right after merge */
1717 format_align->right = format_align->center;
1718 /* calculate the new width and position of the merged string */
1719 right_width = center_width + space_width + right_width;
1720 right_xpos = (display->width - right_width);
1721 /* there is no centered string anymore */
1722 center_width = 0;
1724 /* there is no right string, move center to right */
1725 if ((center_width != 0 && right_width == 0) &&
1726 (center_xpos + center_width > right_xpos)) {
1727 /* move the center string to the right string */
1728 format_align->right = format_align->center;
1729 /* calculate the new width and position of the string */
1730 right_width = center_width;
1731 right_xpos = (display->width - right_width);
1732 /* there is no centered string anymore */
1733 center_width = 0;
1736 /* CASE 3: left and right overlap
1737 There is no center string anymore, either there never
1738 was one or it has been merged in case 1 or 2 */
1739 /* there is a left string, need to merge left and right */
1740 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
1741 (left_xpos + left_width + space_width > right_xpos)) {
1742 /* replace the former separator '\0' of left and
1743 right string with a space */
1744 *(--format_align->right) = ' ';
1745 /* calculate the new width and position of the string */
1746 left_width = left_width + space_width + right_width;
1747 left_xpos = 0;
1748 /* there is no right string anymore */
1749 right_width = 0;
1751 /* there is no left string, move right to left */
1752 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
1753 (left_xpos + left_width > right_xpos)) {
1754 /* move the right string to the left string */
1755 format_align->left = format_align->right;
1756 /* calculate the new width and position of the string */
1757 left_width = right_width;
1758 left_xpos = 0;
1759 /* there is no right string anymore */
1760 right_width = 0;
1763 ypos = (line * string_height) + display->getymargin();
1766 if (scroll && left_width > display->width)
1768 display->puts_scroll(0, line,
1769 (unsigned char *)format_align->left);
1771 else
1773 #ifdef HAVE_LCD_BITMAP
1774 /* clear the line first */
1775 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1776 display->fillrect(0, ypos, display->width, string_height);
1777 display->set_drawmode(DRMODE_SOLID);
1778 #endif
1780 /* Nasty hack: we output an empty scrolling string,
1781 which will reset the scroller for that line */
1782 display->puts_scroll(0, line, (unsigned char *)"");
1784 /* print aligned strings */
1785 if (left_width != 0)
1787 display->putsxy(left_xpos, ypos,
1788 (unsigned char *)format_align->left);
1790 if (center_width != 0)
1792 display->putsxy(center_xpos, ypos,
1793 (unsigned char *)format_align->center);
1795 if (right_width != 0)
1797 display->putsxy(right_xpos, ypos,
1798 (unsigned char *)format_align->right);
1803 /* Refresh the WPS according to refresh_mode. */
1804 bool gui_wps_refresh(struct gui_wps *gwps,
1805 int ffwd_offset,
1806 unsigned char refresh_mode)
1808 struct wps_data *data = gwps->data;
1809 struct screen *display = gwps->display;
1810 struct wps_state *state = gwps->state;
1812 if(!gwps || !data || !state || !display)
1813 return false;
1815 int line, i, subline_idx;
1816 unsigned char flags;
1817 char linebuf[MAX_PATH];
1819 struct align_pos align;
1820 align.left = NULL;
1821 align.center = NULL;
1822 align.right = NULL;
1824 bool update_line, new_subline_refresh;
1826 #ifdef HAVE_LCD_BITMAP
1827 gui_wps_statusbar_draw(gwps, true);
1829 /* to find out wether the peak meter is enabled we
1830 assume it wasn't until we find a line that contains
1831 the peak meter. We can't use peak_meter_enabled itself
1832 because that would mean to turn off the meter thread
1833 temporarily. (That shouldn't matter unless yield
1834 or sleep is called but who knows...)
1836 bool enable_pm = false;
1838 /* Set images to not to be displayed */
1839 for (i = 0; i < MAX_IMAGES; i++)
1841 data->img[i].display = false;
1843 #endif
1845 /* reset to first subline if refresh all flag is set */
1846 if (refresh_mode == WPS_REFRESH_ALL)
1848 for (i = 0; i < data->num_lines; i++)
1850 data->lines[i].curr_subline = SUBLINE_RESET;
1854 #ifdef HAVE_LCD_CHARCELLS
1855 for (i = 0; i < 8; i++)
1857 if (data->wps_progress_pat[i] == 0)
1858 data->wps_progress_pat[i] = display->get_locked_pattern();
1860 #endif
1862 if (!state->id3)
1864 display->stop_scroll();
1865 return false;
1868 state->ff_rewind_count = ffwd_offset;
1870 for (line = 0; line < data->num_lines; line++)
1872 memset(linebuf, 0, sizeof(linebuf));
1873 update_line = false;
1875 /* get current subline for the line */
1876 new_subline_refresh = update_curr_subline(gwps, line);
1878 subline_idx = wps_subline_index(data, line,
1879 data->lines[line].curr_subline);
1880 flags = data->sublines[subline_idx].line_type;
1882 if (refresh_mode == WPS_REFRESH_ALL || (flags & refresh_mode)
1883 || new_subline_refresh)
1885 /* get_line tells us if we need to update the line */
1886 update_line = get_line(gwps, line, data->lines[line].curr_subline,
1887 &align, linebuf, sizeof(linebuf));
1890 #ifdef HAVE_LCD_BITMAP
1891 /* progressbar */
1892 if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
1894 /* the progressbar should be alone on its line */
1895 update_line = false;
1896 draw_progressbar(gwps, line);
1899 /* peakmeter */
1900 if (flags & refresh_mode & WPS_REFRESH_PEAK_METER)
1902 /* the peakmeter should be alone on its line */
1903 update_line = false;
1905 int h = font_get(FONT_UI)->height;
1906 int peak_meter_y = display->getymargin() + line * h;
1908 /* The user might decide to have the peak meter in the last
1909 line so that it is only displayed if no status bar is
1910 visible. If so we neither want do draw nor enable the
1911 peak meter. */
1912 if (peak_meter_y + h <= display->height) {
1913 /* found a line with a peak meter -> remember that we must
1914 enable it later */
1915 enable_pm = true;
1916 peak_meter_screen(gwps->display, 0, peak_meter_y,
1917 MIN(h, display->height - peak_meter_y));
1921 #else /* HAVE_LCD_CHARCELL */
1923 /* progressbar */
1924 if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
1926 if (data->full_line_progressbar)
1927 draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
1928 else
1929 draw_player_progress(gwps);
1931 #endif
1933 if (update_line)
1935 if (flags & WPS_REFRESH_SCROLL)
1937 /* if the line is a scrolling one we don't want to update
1938 too often, so that it has the time to scroll */
1939 if ((refresh_mode & WPS_REFRESH_SCROLL) || new_subline_refresh)
1940 write_line(display, &align, line, true);
1942 else
1943 write_line(display, &align, line, false);
1947 #ifdef HAVE_LCD_BITMAP
1948 data->peak_meter_enabled = enable_pm;
1949 wps_display_images(gwps);
1950 #endif
1952 display->update();
1954 #ifdef HAVE_BACKLIGHT
1955 if (global_settings.caption_backlight && state->id3)
1957 /* turn on backlight n seconds before track ends, and turn it off n
1958 seconds into the new track. n == backlight_timeout, or 5s */
1959 int n = backlight_timeout_value[global_settings.backlight_timeout]
1960 * 1000;
1962 if ( n < 1000 )
1963 n = 5000; /* use 5s if backlight is always on or off */
1965 if (((state->id3->elapsed < 1000) ||
1966 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
1967 (state->paused == false))
1968 backlight_on();
1970 #endif
1971 #ifdef HAVE_REMOTE_LCD
1972 if (global_settings.remote_caption_backlight && state->id3)
1974 /* turn on remote backlight n seconds before track ends, and turn it
1975 off n seconds into the new track. n == remote_backlight_timeout,
1976 or 5s */
1977 int n = backlight_timeout_value[global_settings.remote_backlight_timeout]
1978 * 1000;
1980 if ( n < 1000 )
1981 n = 5000; /* use 5s if backlight is always on or off */
1983 if (((state->id3->elapsed < 1000) ||
1984 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
1985 (state->paused == false))
1986 remote_backlight_on();
1988 #endif
1990 return true;