summaryrefslogtreecommitdiff
path: root/apps/gui/skin_engine/wps_parser.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/gui/skin_engine/wps_parser.c')
-rw-r--r--apps/gui/skin_engine/wps_parser.c1892
1 files changed, 1892 insertions, 0 deletions
diff --git a/apps/gui/skin_engine/wps_parser.c b/apps/gui/skin_engine/wps_parser.c
new file mode 100644
index 0000000000..a3e5f6861d
--- /dev/null
+++ b/apps/gui/skin_engine/wps_parser.c
@@ -0,0 +1,1892 @@
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 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include <stdio.h>
23#include <string.h>
24#include <stdlib.h>
25#include "file.h"
26#include "misc.h"
27#include "plugin.h"
28
29#ifdef __PCTOOL__
30#ifdef WPSEDITOR
31#include "proxy.h"
32#include "sysfont.h"
33#else
34#include "checkwps.h"
35#include "audio.h"
36#define DEBUGF printf
37#endif /*WPSEDITOR*/
38#else
39#include "debug.h"
40#endif /*__PCTOOL__*/
41
42#include <ctype.h>
43#include <stdbool.h>
44#include "font.h"
45
46#include "wps_internals.h"
47#include "settings.h"
48#include "settings_list.h"
49
50#ifdef HAVE_LCD_BITMAP
51#include "bmp.h"
52#endif
53
54#include "backdrop.h"
55
56#define WPS_DEFAULTCFG WPS_DIR "/rockbox_default.wps"
57#define RWPS_DEFAULTCFG WPS_DIR "/rockbox_default.rwps"
58
59#define WPS_ERROR_INVALID_PARAM -1
60
61/* level of current conditional.
62 -1 means we're not in a conditional. */
63static int level = -1;
64
65/* index of the last WPS_TOKEN_CONDITIONAL_OPTION
66 or WPS_TOKEN_CONDITIONAL_START in current level */
67static int lastcond[WPS_MAX_COND_LEVEL];
68
69/* index of the WPS_TOKEN_CONDITIONAL in current level */
70static int condindex[WPS_MAX_COND_LEVEL];
71
72/* number of condtional options in current level */
73static int numoptions[WPS_MAX_COND_LEVEL];
74
75/* the current line in the file */
76static int line;
77
78#ifdef HAVE_LCD_BITMAP
79
80#if LCD_DEPTH > 1
81#define MAX_BITMAPS (MAX_IMAGES+MAX_PROGRESSBARS+1) /* WPS images + pbar bitmap + backdrop */
82#else
83#define MAX_BITMAPS (MAX_IMAGES+MAX_PROGRESSBARS) /* WPS images + pbar bitmap */
84#endif
85
86#define PROGRESSBAR_BMP MAX_IMAGES
87#define BACKDROP_BMP (MAX_BITMAPS-1)
88
89/* pointers to the bitmap filenames in the WPS source */
90static const char *bmp_names[MAX_BITMAPS];
91
92#endif /* HAVE_LCD_BITMAP */
93
94#if defined(DEBUG) || defined(SIMULATOR)
95/* debugging function */
96extern void print_debug_info(struct wps_data *data, int fail, int line);
97#endif
98
99static void wps_reset(struct wps_data *data);
100
101/* Function for parsing of details for a token. At the moment the
102 function is called, the token type has already been set. The
103 function must fill in the details and possibly add more tokens
104 to the token array. It should return the number of chars that
105 has been consumed.
106
107 wps_bufptr points to the char following the tag (i.e. where
108 details begin).
109 token is the pointer to the 'main' token being parsed
110 */
111typedef int (*wps_tag_parse_func)(const char *wps_bufptr,
112 struct wps_token *token, struct wps_data *wps_data);
113
114struct wps_tag {
115 enum wps_token_type type;
116 const char name[3];
117 unsigned char refresh_type;
118 const wps_tag_parse_func parse_func;
119};
120static int skip_end_of_line(const char *wps_bufptr);
121/* prototypes of all special parse functions : */
122static int parse_timeout(const char *wps_bufptr,
123 struct wps_token *token, struct wps_data *wps_data);
124static int parse_progressbar(const char *wps_bufptr,
125 struct wps_token *token, struct wps_data *wps_data);
126static int parse_dir_level(const char *wps_bufptr,
127 struct wps_token *token, struct wps_data *wps_data);
128static int parse_setting(const char *wps_bufptr,
129 struct wps_token *token, struct wps_data *wps_data);
130
131#ifdef HAVE_LCD_BITMAP
132static int parse_viewport_display(const char *wps_bufptr,
133 struct wps_token *token, struct wps_data *wps_data);
134static int parse_viewport(const char *wps_bufptr,
135 struct wps_token *token, struct wps_data *wps_data);
136static int parse_statusbar_enable(const char *wps_bufptr,
137 struct wps_token *token, struct wps_data *wps_data);
138static int parse_statusbar_disable(const char *wps_bufptr,
139 struct wps_token *token, struct wps_data *wps_data);
140static int parse_image_display(const char *wps_bufptr,
141 struct wps_token *token, struct wps_data *wps_data);
142static int parse_image_load(const char *wps_bufptr,
143 struct wps_token *token, struct wps_data *wps_data);
144#endif /*HAVE_LCD_BITMAP */
145#if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
146static int parse_image_special(const char *wps_bufptr,
147 struct wps_token *token, struct wps_data *wps_data);
148#endif
149#ifdef HAVE_ALBUMART
150static int parse_albumart_load(const char *wps_bufptr,
151 struct wps_token *token, struct wps_data *wps_data);
152static int parse_albumart_conditional(const char *wps_bufptr,
153 struct wps_token *token, struct wps_data *wps_data);
154#endif /* HAVE_ALBUMART */
155#ifdef HAVE_TOUCHSCREEN
156static int parse_touchregion(const char *wps_bufptr,
157 struct wps_token *token, struct wps_data *wps_data);
158#else
159static int fulline_tag_not_supported(const char *wps_bufptr,
160 struct wps_token *token, struct wps_data *wps_data)
161{
162 (void)token; (void)wps_data;
163 return skip_end_of_line(wps_bufptr);
164}
165#define parse_touchregion fulline_tag_not_supported
166#endif
167#ifdef CONFIG_RTC
168#define WPS_RTC_REFRESH WPS_REFRESH_DYNAMIC
169#else
170#define WPS_RTC_REFRESH WPS_REFRESH_STATIC
171#endif
172
173/* array of available tags - those with more characters have to go first
174 (e.g. "xl" and "xd" before "x"). It needs to end with the unknown token. */
175static const struct wps_tag all_tags[] = {
176
177 { WPS_TOKEN_ALIGN_CENTER, "ac", 0, NULL },
178 { WPS_TOKEN_ALIGN_LEFT, "al", 0, NULL },
179 { WPS_TOKEN_ALIGN_RIGHT, "ar", 0, NULL },
180
181 { WPS_TOKEN_BATTERY_PERCENT, "bl", WPS_REFRESH_DYNAMIC, NULL },
182 { WPS_TOKEN_BATTERY_VOLTS, "bv", WPS_REFRESH_DYNAMIC, NULL },
183 { WPS_TOKEN_BATTERY_TIME, "bt", WPS_REFRESH_DYNAMIC, NULL },
184 { WPS_TOKEN_BATTERY_SLEEPTIME, "bs", WPS_REFRESH_DYNAMIC, NULL },
185#if CONFIG_CHARGING >= CHARGING_MONITOR
186 { WPS_TOKEN_BATTERY_CHARGING, "bc", WPS_REFRESH_DYNAMIC, NULL },
187#endif
188#if CONFIG_CHARGING
189 { WPS_TOKEN_BATTERY_CHARGER_CONNECTED,"bp", WPS_REFRESH_DYNAMIC, NULL },
190#endif
191
192 { WPS_TOKEN_RTC_PRESENT , "cc", WPS_REFRESH_STATIC, NULL },
193 { WPS_TOKEN_RTC_DAY_OF_MONTH, "cd", WPS_RTC_REFRESH, NULL },
194 { WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED,"ce", WPS_RTC_REFRESH, NULL },
195 { WPS_TOKEN_RTC_12HOUR_CFG, "cf", WPS_RTC_REFRESH, NULL },
196 { WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED, "cH", WPS_RTC_REFRESH, NULL },
197 { WPS_TOKEN_RTC_HOUR_24, "ck", WPS_RTC_REFRESH, NULL },
198 { WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED, "cI", WPS_RTC_REFRESH, NULL },
199 { WPS_TOKEN_RTC_HOUR_12, "cl", WPS_RTC_REFRESH, NULL },
200 { WPS_TOKEN_RTC_MONTH, "cm", WPS_RTC_REFRESH, NULL },
201 { WPS_TOKEN_RTC_MINUTE, "cM", WPS_RTC_REFRESH, NULL },
202 { WPS_TOKEN_RTC_SECOND, "cS", WPS_RTC_REFRESH, NULL },
203 { WPS_TOKEN_RTC_YEAR_2_DIGITS, "cy", WPS_RTC_REFRESH, NULL },
204 { WPS_TOKEN_RTC_YEAR_4_DIGITS, "cY", WPS_RTC_REFRESH, NULL },
205 { WPS_TOKEN_RTC_AM_PM_UPPER, "cP", WPS_RTC_REFRESH, NULL },
206 { WPS_TOKEN_RTC_AM_PM_LOWER, "cp", WPS_RTC_REFRESH, NULL },
207 { WPS_TOKEN_RTC_WEEKDAY_NAME, "ca", WPS_RTC_REFRESH, NULL },
208 { WPS_TOKEN_RTC_MONTH_NAME, "cb", WPS_RTC_REFRESH, NULL },
209 { WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON, "cu", WPS_RTC_REFRESH, NULL },
210 { WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN, "cw", WPS_RTC_REFRESH, NULL },
211
212 /* current file */
213 { WPS_TOKEN_FILE_BITRATE, "fb", WPS_REFRESH_STATIC, NULL },
214 { WPS_TOKEN_FILE_CODEC, "fc", WPS_REFRESH_STATIC, NULL },
215 { WPS_TOKEN_FILE_FREQUENCY, "ff", WPS_REFRESH_STATIC, NULL },
216 { WPS_TOKEN_FILE_FREQUENCY_KHZ, "fk", WPS_REFRESH_STATIC, NULL },
217 { WPS_TOKEN_FILE_NAME_WITH_EXTENSION, "fm", WPS_REFRESH_STATIC, NULL },
218 { WPS_TOKEN_FILE_NAME, "fn", WPS_REFRESH_STATIC, NULL },
219 { WPS_TOKEN_FILE_PATH, "fp", WPS_REFRESH_STATIC, NULL },
220 { WPS_TOKEN_FILE_SIZE, "fs", WPS_REFRESH_STATIC, NULL },
221 { WPS_TOKEN_FILE_VBR, "fv", WPS_REFRESH_STATIC, NULL },
222 { WPS_TOKEN_FILE_DIRECTORY, "d", WPS_REFRESH_STATIC,
223 parse_dir_level },
224
225 /* next file */
226 { WPS_TOKEN_FILE_BITRATE, "Fb", WPS_REFRESH_STATIC, NULL },
227 { WPS_TOKEN_FILE_CODEC, "Fc", WPS_REFRESH_STATIC, NULL },
228 { WPS_TOKEN_FILE_FREQUENCY, "Ff", WPS_REFRESH_STATIC, NULL },
229 { WPS_TOKEN_FILE_FREQUENCY_KHZ, "Fk", WPS_REFRESH_STATIC, NULL },
230 { WPS_TOKEN_FILE_NAME_WITH_EXTENSION, "Fm", WPS_REFRESH_STATIC, NULL },
231 { WPS_TOKEN_FILE_NAME, "Fn", WPS_REFRESH_STATIC, NULL },
232 { WPS_TOKEN_FILE_PATH, "Fp", WPS_REFRESH_STATIC, NULL },
233 { WPS_TOKEN_FILE_SIZE, "Fs", WPS_REFRESH_STATIC, NULL },
234 { WPS_TOKEN_FILE_VBR, "Fv", WPS_REFRESH_STATIC, NULL },
235 { WPS_TOKEN_FILE_DIRECTORY, "D", WPS_REFRESH_STATIC,
236 parse_dir_level },
237
238 /* current metadata */
239 { WPS_TOKEN_METADATA_ARTIST, "ia", WPS_REFRESH_STATIC, NULL },
240 { WPS_TOKEN_METADATA_COMPOSER, "ic", WPS_REFRESH_STATIC, NULL },
241 { WPS_TOKEN_METADATA_ALBUM, "id", WPS_REFRESH_STATIC, NULL },
242 { WPS_TOKEN_METADATA_ALBUM_ARTIST, "iA", WPS_REFRESH_STATIC, NULL },
243 { WPS_TOKEN_METADATA_GROUPING, "iG", WPS_REFRESH_STATIC, NULL },
244 { WPS_TOKEN_METADATA_GENRE, "ig", WPS_REFRESH_STATIC, NULL },
245 { WPS_TOKEN_METADATA_DISC_NUMBER, "ik", WPS_REFRESH_STATIC, NULL },
246 { WPS_TOKEN_METADATA_TRACK_NUMBER, "in", WPS_REFRESH_STATIC, NULL },
247 { WPS_TOKEN_METADATA_TRACK_TITLE, "it", WPS_REFRESH_STATIC, NULL },
248 { WPS_TOKEN_METADATA_VERSION, "iv", WPS_REFRESH_STATIC, NULL },
249 { WPS_TOKEN_METADATA_YEAR, "iy", WPS_REFRESH_STATIC, NULL },
250 { WPS_TOKEN_METADATA_COMMENT, "iC", WPS_REFRESH_STATIC, NULL },
251
252 /* next metadata */
253 { WPS_TOKEN_METADATA_ARTIST, "Ia", WPS_REFRESH_STATIC, NULL },
254 { WPS_TOKEN_METADATA_COMPOSER, "Ic", WPS_REFRESH_STATIC, NULL },
255 { WPS_TOKEN_METADATA_ALBUM, "Id", WPS_REFRESH_STATIC, NULL },
256 { WPS_TOKEN_METADATA_ALBUM_ARTIST, "IA", WPS_REFRESH_STATIC, NULL },
257 { WPS_TOKEN_METADATA_GROUPING, "IG", WPS_REFRESH_STATIC, NULL },
258 { WPS_TOKEN_METADATA_GENRE, "Ig", WPS_REFRESH_STATIC, NULL },
259 { WPS_TOKEN_METADATA_DISC_NUMBER, "Ik", WPS_REFRESH_STATIC, NULL },
260 { WPS_TOKEN_METADATA_TRACK_NUMBER, "In", WPS_REFRESH_STATIC, NULL },
261 { WPS_TOKEN_METADATA_TRACK_TITLE, "It", WPS_REFRESH_STATIC, NULL },
262 { WPS_TOKEN_METADATA_VERSION, "Iv", WPS_REFRESH_STATIC, NULL },
263 { WPS_TOKEN_METADATA_YEAR, "Iy", WPS_REFRESH_STATIC, NULL },
264 { WPS_TOKEN_METADATA_COMMENT, "IC", WPS_REFRESH_STATIC, NULL },
265
266#if (CONFIG_CODEC != MAS3507D)
267 { WPS_TOKEN_SOUND_PITCH, "Sp", WPS_REFRESH_DYNAMIC, NULL },
268#endif
269
270#if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
271 { WPS_TOKEN_VLED_HDD, "lh", WPS_REFRESH_DYNAMIC, NULL },
272#endif
273
274 { WPS_TOKEN_MAIN_HOLD, "mh", WPS_REFRESH_DYNAMIC, NULL },
275
276#ifdef HAS_REMOTE_BUTTON_HOLD
277 { WPS_TOKEN_REMOTE_HOLD, "mr", WPS_REFRESH_DYNAMIC, NULL },
278#else
279 { WPS_TOKEN_UNKNOWN, "mr", 0, NULL },
280#endif
281
282 { WPS_TOKEN_REPEAT_MODE, "mm", WPS_REFRESH_DYNAMIC, NULL },
283 { WPS_TOKEN_PLAYBACK_STATUS, "mp", WPS_REFRESH_DYNAMIC, NULL },
284 { WPS_TOKEN_BUTTON_VOLUME, "mv", WPS_REFRESH_DYNAMIC,
285 parse_timeout },
286
287#ifdef HAVE_LCD_BITMAP
288 { WPS_TOKEN_PEAKMETER, "pm", WPS_REFRESH_PEAK_METER, NULL },
289#else
290 { WPS_TOKEN_PLAYER_PROGRESSBAR, "pf",
291 WPS_REFRESH_DYNAMIC | WPS_REFRESH_PLAYER_PROGRESS, parse_progressbar },
292#endif
293 { WPS_TOKEN_PROGRESSBAR, "pb", WPS_REFRESH_PLAYER_PROGRESS,
294 parse_progressbar },
295
296 { WPS_TOKEN_VOLUME, "pv", WPS_REFRESH_DYNAMIC, NULL },
297
298 { WPS_TOKEN_TRACK_ELAPSED_PERCENT, "px", WPS_REFRESH_DYNAMIC, NULL },
299 { WPS_TOKEN_TRACK_TIME_ELAPSED, "pc", WPS_REFRESH_DYNAMIC, NULL },
300 { WPS_TOKEN_TRACK_TIME_REMAINING, "pr", WPS_REFRESH_DYNAMIC, NULL },
301 { WPS_TOKEN_TRACK_LENGTH, "pt", WPS_REFRESH_STATIC, NULL },
302
303 { WPS_TOKEN_PLAYLIST_POSITION, "pp", WPS_REFRESH_STATIC, NULL },
304 { WPS_TOKEN_PLAYLIST_ENTRIES, "pe", WPS_REFRESH_STATIC, NULL },
305 { WPS_TOKEN_PLAYLIST_NAME, "pn", WPS_REFRESH_STATIC, NULL },
306 { WPS_TOKEN_PLAYLIST_SHUFFLE, "ps", WPS_REFRESH_DYNAMIC, NULL },
307
308#ifdef HAVE_TAGCACHE
309 { WPS_TOKEN_DATABASE_PLAYCOUNT, "rp", WPS_REFRESH_DYNAMIC, NULL },
310 { WPS_TOKEN_DATABASE_RATING, "rr", WPS_REFRESH_DYNAMIC, NULL },
311 { WPS_TOKEN_DATABASE_AUTOSCORE, "ra", WPS_REFRESH_DYNAMIC, NULL },
312#endif
313
314#if CONFIG_CODEC == SWCODEC
315 { WPS_TOKEN_REPLAYGAIN, "rg", WPS_REFRESH_STATIC, NULL },
316 { WPS_TOKEN_CROSSFADE, "xf", WPS_REFRESH_DYNAMIC, NULL },
317#endif
318
319 { WPS_NO_TOKEN, "s", WPS_REFRESH_SCROLL, NULL },
320 { WPS_TOKEN_SUBLINE_TIMEOUT, "t", 0, parse_timeout },
321
322#ifdef HAVE_LCD_BITMAP
323 { WPS_NO_TOKEN, "we", 0, parse_statusbar_enable },
324 { WPS_NO_TOKEN, "wd", 0, parse_statusbar_disable },
325
326 { WPS_NO_TOKEN, "xl", 0, parse_image_load },
327
328 { WPS_TOKEN_IMAGE_PRELOAD_DISPLAY, "xd", WPS_REFRESH_STATIC,
329 parse_image_display },
330
331 { WPS_TOKEN_IMAGE_DISPLAY, "x", 0, parse_image_load },
332#ifdef HAVE_ALBUMART
333 { WPS_NO_TOKEN, "Cl", 0, parse_albumart_load },
334 { WPS_TOKEN_ALBUMART_DISPLAY, "C", WPS_REFRESH_STATIC,
335 parse_albumart_conditional },
336#endif
337
338 { WPS_VIEWPORT_ENABLE, "Vd", WPS_REFRESH_DYNAMIC,
339 parse_viewport_display },
340 { WPS_NO_TOKEN, "V", 0, parse_viewport },
341
342#if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
343 { WPS_TOKEN_IMAGE_BACKDROP, "X", 0, parse_image_special },
344#endif
345#endif
346
347 { WPS_TOKEN_SETTING, "St", WPS_REFRESH_DYNAMIC, parse_setting },
348
349 { WPS_TOKEN_LASTTOUCH, "Tl", WPS_REFRESH_DYNAMIC, parse_timeout },
350 { WPS_NO_TOKEN, "T", 0, parse_touchregion },
351
352 { WPS_TOKEN_UNKNOWN, "", 0, NULL }
353 /* the array MUST end with an empty string (first char is \0) */
354};
355
356/* Returns the number of chars that should be skipped to jump
357 immediately after the first eol, i.e. to the start of the next line */
358static int skip_end_of_line(const char *wps_bufptr)
359{
360 line++;
361 int skip = 0;
362 while(*(wps_bufptr + skip) != '\n')
363 skip++;
364 return ++skip;
365}
366
367/* Starts a new subline in the current line during parsing */
368static void wps_start_new_subline(struct wps_data *data)
369{
370 data->num_sublines++;
371 data->sublines[data->num_sublines].first_token_idx = data->num_tokens;
372 data->lines[data->num_lines].num_sublines++;
373}
374
375#ifdef HAVE_LCD_BITMAP
376
377static int parse_statusbar_enable(const char *wps_bufptr,
378 struct wps_token *token,
379 struct wps_data *wps_data)
380{
381 (void)token; /* Kill warnings */
382 wps_data->wps_sb_tag = true;
383 wps_data->show_sb_on_wps = true;
384 if (wps_data->viewports[0].vp.y == 0)
385 {
386 wps_data->viewports[0].vp.y = STATUSBAR_HEIGHT;
387 wps_data->viewports[0].vp.height -= STATUSBAR_HEIGHT;
388 }
389 return skip_end_of_line(wps_bufptr);
390}
391
392static int parse_statusbar_disable(const char *wps_bufptr,
393 struct wps_token *token,
394 struct wps_data *wps_data)
395{
396 (void)token; /* Kill warnings */
397 wps_data->wps_sb_tag = true;
398 wps_data->show_sb_on_wps = false;
399 if (wps_data->viewports[0].vp.y == STATUSBAR_HEIGHT)
400 {
401 wps_data->viewports[0].vp.y = 0;
402 wps_data->viewports[0].vp.height += STATUSBAR_HEIGHT;
403 }
404 return skip_end_of_line(wps_bufptr);
405}
406
407static bool load_bitmap(struct wps_data *wps_data,
408 char* filename,
409 struct bitmap *bm)
410{
411 int format;
412#ifdef HAVE_REMOTE_LCD
413 if (wps_data->remote_wps)
414 format = FORMAT_ANY|FORMAT_REMOTE;
415 else
416#endif
417 format = FORMAT_ANY|FORMAT_TRANSPARENT;
418
419 int ret = read_bmp_file(filename, bm,
420 wps_data->img_buf_free,
421 format,NULL);
422
423 if (ret > 0)
424 {
425#if LCD_DEPTH == 16
426 if (ret % 2) ret++;
427 /* Always consume an even number of bytes */
428#endif
429 wps_data->img_buf_ptr += ret;
430 wps_data->img_buf_free -= ret;
431
432 return true;
433 }
434 else
435 return false;
436}
437
438static int get_image_id(int c)
439{
440 if(c >= 'a' && c <= 'z')
441 return c - 'a';
442 else if(c >= 'A' && c <= 'Z')
443 return c - 'A' + 26;
444 else
445 return -1;
446}
447
448static char *get_image_filename(const char *start, const char* bmpdir,
449 char *buf, int buf_size)
450{
451 const char *end = strchr(start, '|');
452
453 if ( !end || (end - start) >= (buf_size - (int)ROCKBOX_DIR_LEN - 2) )
454 {
455 buf = "\0";
456 return NULL;
457 }
458
459 int bmpdirlen = strlen(bmpdir);
460
461 strcpy(buf, bmpdir);
462 buf[bmpdirlen] = '/';
463 memcpy( &buf[bmpdirlen + 1], start, end - start);
464 buf[bmpdirlen + 1 + end - start] = 0;
465
466 return buf;
467}
468
469static int parse_image_display(const char *wps_bufptr,
470 struct wps_token *token,
471 struct wps_data *wps_data)
472{
473 (void)wps_data;
474 int n = get_image_id(wps_bufptr[0]);
475 int subimage;
476
477 if (n == -1)
478 {
479 /* invalid picture display tag */
480 return WPS_ERROR_INVALID_PARAM;
481 }
482
483 if ((subimage = get_image_id(wps_bufptr[1])) != -1)
484 {
485 /* Sanity check */
486 if (subimage >= wps_data->img[n].num_subimages)
487 return WPS_ERROR_INVALID_PARAM;
488
489 /* Store sub-image number to display in high bits */
490 token->value.i = n | (subimage << 8);
491 return 2; /* We have consumed 2 bytes */
492 } else {
493 token->value.i = n;
494 return 1; /* We have consumed 1 byte */
495 }
496}
497
498static int parse_image_load(const char *wps_bufptr,
499 struct wps_token *token,
500 struct wps_data *wps_data)
501{
502 int n;
503 const char *ptr = wps_bufptr;
504 const char *pos;
505 const char* filename;
506 const char* id;
507 const char *newline;
508 int x,y;
509
510 /* format: %x|n|filename.bmp|x|y|
511 or %xl|n|filename.bmp|x|y|
512 or %xl|n|filename.bmp|x|y|num_subimages|
513 */
514
515 if (*ptr != '|')
516 return WPS_ERROR_INVALID_PARAM;
517
518 ptr++;
519
520 if (!(ptr = parse_list("ssdd", NULL, '|', ptr, &id, &filename, &x, &y)))
521 return WPS_ERROR_INVALID_PARAM;
522
523 /* Check there is a terminating | */
524 if (*ptr != '|')
525 return WPS_ERROR_INVALID_PARAM;
526
527 /* get the image ID */
528 n = get_image_id(*id);
529
530 /* check the image number and load state */
531 if(n < 0 || n >= MAX_IMAGES || wps_data->img[n].loaded)
532 {
533 /* Invalid image ID */
534 return WPS_ERROR_INVALID_PARAM;
535 }
536
537 /* save a pointer to the filename */
538 bmp_names[n] = filename;
539
540 wps_data->img[n].x = x;
541 wps_data->img[n].y = y;
542
543 /* save current viewport */
544 wps_data->img[n].vp = &wps_data->viewports[wps_data->num_viewports].vp;
545
546 if (token->type == WPS_TOKEN_IMAGE_DISPLAY)
547 {
548 wps_data->img[n].always_display = true;
549 }
550 else
551 {
552 /* Parse the (optional) number of sub-images */
553 ptr++;
554 newline = strchr(ptr, '\n');
555 pos = strchr(ptr, '|');
556 if (pos && pos < newline)
557 wps_data->img[n].num_subimages = atoi(ptr);
558
559 if (wps_data->img[n].num_subimages <= 0)
560 return WPS_ERROR_INVALID_PARAM;
561 }
562
563 /* Skip the rest of the line */
564 return skip_end_of_line(wps_bufptr);
565}
566
567static int parse_viewport_display(const char *wps_bufptr,
568 struct wps_token *token,
569 struct wps_data *wps_data)
570{
571 (void)wps_data;
572 char letter = wps_bufptr[0];
573
574 if (letter < 'a' || letter > 'z')
575 {
576 /* invalid viewport tag */
577 return WPS_ERROR_INVALID_PARAM;
578 }
579 token->value.i = letter;
580 return 1;
581}
582
583static int parse_viewport(const char *wps_bufptr,
584 struct wps_token *token,
585 struct wps_data *wps_data)
586{
587 (void)token; /* Kill warnings */
588 const char *ptr = wps_bufptr;
589 struct viewport* vp;
590 int depth;
591 uint32_t set = 0;
592 enum {
593 PL_X = 0,
594 PL_Y,
595 PL_WIDTH,
596 PL_HEIGHT,
597 PL_FONT,
598 PL_FG,
599 PL_BG,
600 };
601 int lcd_width = LCD_WIDTH, lcd_height = LCD_HEIGHT;
602#ifdef HAVE_REMOTE_LCD
603 if (wps_data->remote_wps)
604 {
605 lcd_width = LCD_REMOTE_WIDTH;
606 lcd_height = LCD_REMOTE_HEIGHT;
607 }
608#endif
609
610 if (wps_data->num_viewports >= WPS_MAX_VIEWPORTS)
611 return WPS_ERROR_INVALID_PARAM;
612
613 wps_data->num_viewports++;
614 /* check for the optional letter to signify its a hideable viewport */
615 /* %Vl|<label>|<rest of tags>| */
616 wps_data->viewports[wps_data->num_viewports].hidden_flags = 0;
617
618 if (*ptr == 'l')
619 {
620 if (*(ptr+1) == '|')
621 {
622 char label = *(ptr+2);
623 if (label >= 'a' && label <= 'z')
624 {
625 wps_data->viewports[wps_data->num_viewports].hidden_flags = VP_DRAW_HIDEABLE;
626 wps_data->viewports[wps_data->num_viewports].label = label;
627 }
628 else
629 return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl7 */
630 ptr += 3;
631 }
632 }
633 if (*ptr != '|')
634 return WPS_ERROR_INVALID_PARAM;
635
636 ptr++;
637 vp = &wps_data->viewports[wps_data->num_viewports].vp;
638 /* format: %V|x|y|width|height|font|fg_pattern|bg_pattern| */
639
640 /* Set the defaults for fields not user-specified */
641 vp->drawmode = DRMODE_SOLID;
642
643 /* Work out the depth of this display */
644#ifdef HAVE_REMOTE_LCD
645 depth = (wps_data->remote_wps ? LCD_REMOTE_DEPTH : LCD_DEPTH);
646#else
647 depth = LCD_DEPTH;
648#endif
649
650#ifdef HAVE_LCD_COLOR
651 if (depth == 16)
652 {
653 if (!(ptr = parse_list("dddddcc", &set, '|', ptr, &vp->x, &vp->y, &vp->width,
654 &vp->height, &vp->font, &vp->fg_pattern,&vp->bg_pattern)))
655 return WPS_ERROR_INVALID_PARAM;
656 }
657 else
658#endif
659#if (LCD_DEPTH == 2) || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH == 2)
660 if (depth == 2) {
661 /* Default to black on white */
662 vp->fg_pattern = 0;
663 vp->bg_pattern = 3;
664 if (!(ptr = parse_list("dddddgg", &set, '|', ptr, &vp->x, &vp->y, &vp->width,
665 &vp->height, &vp->font, &vp->fg_pattern, &vp->bg_pattern)))
666 return WPS_ERROR_INVALID_PARAM;
667 }
668 else
669#endif
670#if (LCD_DEPTH == 1) || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH == 1)
671 if (depth == 1)
672 {
673 if (!(ptr = parse_list("ddddd", &set, '|', ptr, &vp->x, &vp->y,
674 &vp->width, &vp->height, &vp->font)))
675 return WPS_ERROR_INVALID_PARAM;
676 }
677 else
678#endif
679 {}
680
681 /* Check for trailing | */
682 if (*ptr != '|')
683 return WPS_ERROR_INVALID_PARAM;
684
685 if (!LIST_VALUE_PARSED(set, PL_X) || !LIST_VALUE_PARSED(set, PL_Y))
686 return WPS_ERROR_INVALID_PARAM;
687
688 /* fix defaults */
689 if (!LIST_VALUE_PARSED(set, PL_WIDTH))
690 vp->width = lcd_width - vp->x;
691 if (!LIST_VALUE_PARSED(set, PL_HEIGHT))
692 vp->height = lcd_height - vp->y;
693
694 /* Default to using the user font if the font was an invalid number */
695 if (!LIST_VALUE_PARSED(set, PL_FONT) ||
696 ((vp->font != FONT_SYSFIXED) && (vp->font != FONT_UI)))
697 vp->font = FONT_UI;
698
699 /* Validate the viewport dimensions - we know that the numbers are
700 non-negative integers */
701 if ((vp->x >= lcd_width) ||
702 ((vp->x + vp->width) > lcd_width) ||
703 (vp->y >= lcd_height) ||
704 ((vp->y + vp->height) > lcd_height))
705 {
706 return WPS_ERROR_INVALID_PARAM;
707 }
708
709#ifdef HAVE_LCD_COLOR
710 if (depth == 16)
711 {
712 if (!LIST_VALUE_PARSED(set, PL_FG))
713 vp->fg_pattern = global_settings.fg_color;
714 if (!LIST_VALUE_PARSED(set, PL_BG))
715 vp->bg_pattern = global_settings.bg_color;
716 }
717#endif
718
719 wps_data->viewports[wps_data->num_viewports-1].last_line = wps_data->num_lines - 1;
720
721 wps_data->viewports[wps_data->num_viewports].first_line = wps_data->num_lines;
722
723 if (wps_data->num_sublines < WPS_MAX_SUBLINES)
724 {
725 wps_data->lines[wps_data->num_lines].first_subline_idx =
726 wps_data->num_sublines;
727
728 wps_data->sublines[wps_data->num_sublines].first_token_idx =
729 wps_data->num_tokens;
730 }
731
732 /* Skip the rest of the line */
733 return skip_end_of_line(wps_bufptr);
734}
735
736#if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
737static int parse_image_special(const char *wps_bufptr,
738 struct wps_token *token,
739 struct wps_data *wps_data)
740{
741 (void)wps_data; /* kill warning */
742 (void)token;
743 const char *pos = NULL;
744 const char *newline;
745
746 pos = strchr(wps_bufptr + 1, '|');
747 newline = strchr(wps_bufptr, '\n');
748
749 if (pos > newline)
750 return WPS_ERROR_INVALID_PARAM;
751#if LCD_DEPTH > 1
752 if (token->type == WPS_TOKEN_IMAGE_BACKDROP)
753 {
754 /* format: %X|filename.bmp| */
755 bmp_names[BACKDROP_BMP] = wps_bufptr + 1;
756 }
757#endif
758
759 /* Skip the rest of the line */
760 return skip_end_of_line(wps_bufptr);
761}
762#endif
763
764#endif /* HAVE_LCD_BITMAP */
765
766static int parse_setting(const char *wps_bufptr,
767 struct wps_token *token,
768 struct wps_data *wps_data)
769{
770 (void)wps_data;
771 const char *ptr = wps_bufptr;
772 const char *end;
773 int i;
774
775 /* Find the setting's cfg_name */
776 if (*ptr != '|')
777 return WPS_ERROR_INVALID_PARAM;
778 ptr++;
779 end = strchr(ptr,'|');
780 if (!end)
781 return WPS_ERROR_INVALID_PARAM;
782
783 /* Find the setting */
784 for (i=0; i<nb_settings; i++)
785 if (settings[i].cfg_name &&
786 !strncmp(settings[i].cfg_name,ptr,end-ptr) &&
787 /* prevent matches on cfg_name prefixes */
788 strlen(settings[i].cfg_name)==(size_t)(end-ptr))
789 break;
790 if (i == nb_settings)
791 return WPS_ERROR_INVALID_PARAM;
792
793 /* Store the setting number */
794 token->value.i = i;
795
796 /* Skip the rest of the line */
797 return end-ptr+2;
798}
799
800
801static int parse_dir_level(const char *wps_bufptr,
802 struct wps_token *token,
803 struct wps_data *wps_data)
804{
805 char val[] = { *wps_bufptr, '\0' };
806 token->value.i = atoi(val);
807 (void)wps_data; /* Kill warnings */
808 return 1;
809}
810
811static int parse_timeout(const char *wps_bufptr,
812 struct wps_token *token,
813 struct wps_data *wps_data)
814{
815 int skip = 0;
816 int val = 0;
817 bool have_point = false;
818 bool have_tenth = false;
819
820 (void)wps_data; /* Kill the warning */
821
822 while ( isdigit(*wps_bufptr) || *wps_bufptr == '.' )
823 {
824 if (*wps_bufptr != '.')
825 {
826 val *= 10;
827 val += *wps_bufptr - '0';
828 if (have_point)
829 {
830 have_tenth = true;
831 wps_bufptr++;
832 skip++;
833 break;
834 }
835 }
836 else
837 have_point = true;
838
839 wps_bufptr++;
840 skip++;
841 }
842
843 if (have_tenth == false)
844 val *= 10;
845
846 if (val == 0 && skip == 0)
847 {
848 /* decide what to do if no value was specified */
849 switch (token->type)
850 {
851 case WPS_TOKEN_SUBLINE_TIMEOUT:
852 return -1;
853 case WPS_TOKEN_BUTTON_VOLUME:
854 val = 10;
855 break;
856 }
857 }
858 token->value.i = val;
859
860 return skip;
861}
862
863static int parse_progressbar(const char *wps_bufptr,
864 struct wps_token *token,
865 struct wps_data *wps_data)
866{
867 (void)token; /* Kill warnings */
868 /* %pb or %pb|filename|x|y|width|height|
869 using - for any of the params uses "sane" values */
870#ifdef HAVE_LCD_BITMAP
871 enum {
872 PB_FILENAME = 0,
873 PB_X,
874 PB_Y,
875 PB_WIDTH,
876 PB_HEIGHT
877 };
878 const char *filename;
879 int x, y, height, width;
880 uint32_t set = 0;
881 const char *ptr = wps_bufptr;
882 struct progressbar *pb;
883 struct viewport *vp = &wps_data->viewports[wps_data->num_viewports].vp;
884#ifndef __PCTOOL__
885 int font_height = font_get(vp->font)->height;
886#else
887 int font_height = 8;
888#endif
889 int line_num = wps_data->num_lines -
890 wps_data->viewports[wps_data->num_viewports].first_line;
891
892 if (wps_data->progressbar_count >= MAX_PROGRESSBARS)
893 return WPS_ERROR_INVALID_PARAM;
894
895 pb = &wps_data->progressbar[wps_data->progressbar_count];
896 pb->have_bitmap_pb = false;
897
898 if (*wps_bufptr != '|') /* regular old style */
899 {
900 pb->x = 0;
901 pb->width = vp->width;
902 pb->height = SYSFONT_HEIGHT-2;
903 pb->y = -line_num - 1; /* Will be computed during the rendering */
904
905 wps_data->viewports[wps_data->num_viewports].pb = pb;
906 wps_data->progressbar_count++;
907 return 0;
908 }
909 ptr = wps_bufptr + 1;
910
911 if (!(ptr = parse_list("sdddd", &set, '|', ptr, &filename,
912 &x, &y, &width, &height)))
913 return WPS_ERROR_INVALID_PARAM;
914
915 if (LIST_VALUE_PARSED(set, PB_FILENAME)) /* filename */
916 bmp_names[PROGRESSBAR_BMP+wps_data->progressbar_count] = filename;
917
918 if (LIST_VALUE_PARSED(set, PB_X)) /* x */
919 pb->x = x;
920 else
921 pb->x = vp->x;
922
923 if (LIST_VALUE_PARSED(set, PB_WIDTH)) /* width */
924 {
925 /* A zero width causes a divide-by-zero error later, so reject it */
926 if (width == 0)
927 return WPS_ERROR_INVALID_PARAM;
928
929 pb->width = width;
930 }
931 else
932 pb->width = vp->width - pb->x;
933
934 if (LIST_VALUE_PARSED(set, PB_HEIGHT)) /* height, default to font height */
935 {
936 /* A zero height makes no sense - reject it */
937 if (height == 0)
938 return WPS_ERROR_INVALID_PARAM;
939
940 pb->height = height;
941 }
942 else
943 pb->height = font_height;
944
945 if (LIST_VALUE_PARSED(set, PB_Y)) /* y */
946 pb->y = y;
947 else
948 pb->y = -line_num - 1; /* Will be computed during the rendering */
949
950 wps_data->viewports[wps_data->num_viewports].pb = pb;
951 wps_data->progressbar_count++;
952
953 /* Skip the rest of the line */
954 return skip_end_of_line(wps_bufptr)-1;
955#else
956
957 if (*(wps_bufptr-1) == 'f')
958 wps_data->full_line_progressbar = true;
959 else
960 wps_data->full_line_progressbar = false;
961
962 return 0;
963
964#endif
965}
966
967#ifdef HAVE_ALBUMART
968static int parse_albumart_load(const char *wps_bufptr,
969 struct wps_token *token,
970 struct wps_data *wps_data)
971{
972 const char *_pos, *newline;
973 bool parsing;
974 (void)token; /* silence warning */
975
976 /* reset albumart info in wps */
977 wps_data->albumart_max_width = -1;
978 wps_data->albumart_max_height = -1;
979 wps_data->albumart_xalign = WPS_ALBUMART_ALIGN_CENTER; /* default */
980 wps_data->albumart_yalign = WPS_ALBUMART_ALIGN_CENTER; /* default */
981
982 /* format: %Cl|x|y|[[l|c|r]mwidth]|[[t|c|b]mheight]| */
983
984 newline = strchr(wps_bufptr, '\n');
985
986 /* initial validation and parsing of x and y components */
987 if (*wps_bufptr != '|')
988 return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl7 */
989
990 _pos = wps_bufptr + 1;
991 if (!isdigit(*_pos))
992 return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl|@ */
993 wps_data->albumart_x = atoi(_pos);
994
995 _pos = strchr(_pos, '|');
996 if (!_pos || _pos > newline || !isdigit(*(++_pos)))
997 return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl|7\n or %Cl|7|@ */
998
999 wps_data->albumart_y = atoi(_pos);
1000
1001 _pos = strchr(_pos, '|');
1002 if (!_pos || _pos > newline)
1003 return WPS_ERROR_INVALID_PARAM; /* malformed token: no | after y coordinate
1004 e.g. %Cl|7|59\n */
1005
1006 /* parsing width field */
1007 parsing = true;
1008 while (parsing)
1009 {
1010 /* apply each modifier in turn */
1011 ++_pos;
1012 switch (*_pos)
1013 {
1014 case 'l':
1015 case 'L':
1016 case '+':
1017 wps_data->albumart_xalign = WPS_ALBUMART_ALIGN_LEFT;
1018 break;
1019 case 'c':
1020 case 'C':
1021 wps_data->albumart_xalign = WPS_ALBUMART_ALIGN_CENTER;
1022 break;
1023 case 'r':
1024 case 'R':
1025 case '-':
1026 wps_data->albumart_xalign = WPS_ALBUMART_ALIGN_RIGHT;
1027 break;
1028 case 'd':
1029 case 'D':
1030 case 'i':
1031 case 'I':
1032 case 's':
1033 case 'S':
1034 /* simply ignored */
1035 break;
1036 default:
1037 parsing = false;
1038 break;
1039 }
1040 }
1041 /* extract max width data */
1042 if (*_pos != '|')
1043 {
1044 if (!isdigit(*_pos)) /* malformed token: e.g. %Cl|7|59|# */
1045 return WPS_ERROR_INVALID_PARAM;
1046
1047 wps_data->albumart_max_width = atoi(_pos);
1048
1049 _pos = strchr(_pos, '|');
1050 if (!_pos || _pos > newline)
1051 return WPS_ERROR_INVALID_PARAM; /* malformed token: no | after width field
1052 e.g. %Cl|7|59|200\n */
1053 }
1054
1055 /* parsing height field */
1056 parsing = true;
1057 while (parsing)
1058 {
1059 /* apply each modifier in turn */
1060 ++_pos;
1061 switch (*_pos)
1062 {
1063 case 't':
1064 case 'T':
1065 case '-':
1066 wps_data->albumart_yalign = WPS_ALBUMART_ALIGN_TOP;
1067 break;
1068 case 'c':
1069 case 'C':
1070 wps_data->albumart_yalign = WPS_ALBUMART_ALIGN_CENTER;
1071 break;
1072 case 'b':
1073 case 'B':
1074 case '+':
1075 wps_data->albumart_yalign = WPS_ALBUMART_ALIGN_BOTTOM;
1076 break;
1077 case 'd':
1078 case 'D':
1079 case 'i':
1080 case 'I':
1081 case 's':
1082 case 'S':
1083 /* simply ignored */
1084 break;
1085 default:
1086 parsing = false;
1087 break;
1088 }
1089 }
1090 /* extract max height data */
1091 if (*_pos != '|')
1092 {
1093 if (!isdigit(*_pos))
1094 return WPS_ERROR_INVALID_PARAM; /* malformed token e.g. %Cl|7|59|200|@ */
1095
1096 wps_data->albumart_max_height = atoi(_pos);
1097
1098 _pos = strchr(_pos, '|');
1099 if (!_pos || _pos > newline)
1100 return WPS_ERROR_INVALID_PARAM; /* malformed token: no closing |
1101 e.g. %Cl|7|59|200|200\n */
1102 }
1103
1104 /* if we got here, we parsed everything ok .. ! */
1105 if (wps_data->albumart_max_width < 0)
1106 wps_data->albumart_max_width = 0;
1107 else if (wps_data->albumart_max_width > LCD_WIDTH)
1108 wps_data->albumart_max_width = LCD_WIDTH;
1109
1110 if (wps_data->albumart_max_height < 0)
1111 wps_data->albumart_max_height = 0;
1112 else if (wps_data->albumart_max_height > LCD_HEIGHT)
1113 wps_data->albumart_max_height = LCD_HEIGHT;
1114
1115 wps_data->wps_uses_albumart = WPS_ALBUMART_LOAD;
1116
1117 /* Skip the rest of the line */
1118 return skip_end_of_line(wps_bufptr);
1119}
1120
1121static int parse_albumart_conditional(const char *wps_bufptr,
1122 struct wps_token *token,
1123 struct wps_data *wps_data)
1124{
1125 struct wps_token *prevtoken = token;
1126 --prevtoken;
1127 if (wps_data->num_tokens >= 1 && prevtoken->type == WPS_TOKEN_CONDITIONAL)
1128 {
1129 /* This %C is part of a %?C construct.
1130 It's either %?C<blah> or %?Cn<blah> */
1131 token->type = WPS_TOKEN_ALBUMART_FOUND;
1132 if (*wps_bufptr == 'n' && *(wps_bufptr + 1) == '<')
1133 {
1134 token->next = true;
1135 return 1;
1136 }
1137 else if (*wps_bufptr == '<')
1138 {
1139 return 0;
1140 }
1141 else
1142 {
1143 token->type = WPS_NO_TOKEN;
1144 return 0;
1145 }
1146 }
1147 else
1148 {
1149 /* This %C tag is in a conditional construct. */
1150 wps_data->albumart_cond_index = condindex[level];
1151 return 0;
1152 }
1153};
1154#endif /* HAVE_ALBUMART */
1155
1156#ifdef HAVE_TOUCHSCREEN
1157
1158struct touchaction {char* s; int action;};
1159static struct touchaction touchactions[] = {
1160 {"play", ACTION_WPS_PLAY }, {"stop", ACTION_WPS_STOP },
1161 {"prev", ACTION_WPS_SKIPPREV }, {"next", ACTION_WPS_SKIPNEXT },
1162 {"ffwd", ACTION_WPS_SEEKFWD }, {"rwd", ACTION_WPS_SEEKBACK },
1163 {"menu", ACTION_WPS_MENU }, {"browse", ACTION_WPS_BROWSE },
1164 {"shuffle", ACTION_TOUCH_SHUFFLE }, {"repmode", ACTION_TOUCH_REPMODE },
1165 {"quickscreen", ACTION_WPS_QUICKSCREEN },{"contextmenu", ACTION_WPS_CONTEXT },
1166 {"playlist", ACTION_WPS_VIEW_PLAYLIST },
1167};
1168static int parse_touchregion(const char *wps_bufptr,
1169 struct wps_token *token, struct wps_data *wps_data)
1170{
1171 (void)token;
1172 unsigned i, imax;
1173 struct touchregion *region;
1174 const char *ptr = wps_bufptr;
1175 const char *action;
1176 int x,y,w,h;
1177
1178 /* format: %T|x|y|width|height|action|
1179 * if action starts with & the area must be held to happen
1180 * action is one of:
1181 * play - play/pause playback
1182 * stop - stop playback, exit the wps
1183 * prev - prev track
1184 * next - next track
1185 * ffwd - seek forward
1186 * rwd - seek backwards
1187 * menu - go back to the main menu
1188 * browse - go back to the file/db browser
1189 * shuffle - toggle shuffle mode
1190 * repmode - cycle the repeat mode
1191 * quickscreen - go into the quickscreen
1192 * contextmenu - open the context menu
1193 */
1194
1195
1196 if ((wps_data->touchregion_count +1 >= MAX_TOUCHREGIONS) || (*ptr != '|'))
1197 return WPS_ERROR_INVALID_PARAM;
1198 ptr++;
1199
1200 if (!(ptr = parse_list("dddds", NULL, '|', ptr, &x, &y, &w, &h, &action)))
1201 return WPS_ERROR_INVALID_PARAM;
1202
1203 /* Check there is a terminating | */
1204 if (*ptr != '|')
1205 return WPS_ERROR_INVALID_PARAM;
1206
1207 /* should probably do some bounds checking here with the viewport... but later */
1208 region = &wps_data->touchregion[wps_data->touchregion_count];
1209 region->action = ACTION_NONE;
1210 region->x = x;
1211 region->y = y;
1212 region->width = w;
1213 region->height = h;
1214 region->wvp = &wps_data->viewports[wps_data->num_viewports];
1215 i = 0;
1216 if (*action == '&')
1217 {
1218 action++;
1219 region->repeat = true;
1220 }
1221 else
1222 region->repeat = false;
1223
1224 imax = ARRAYLEN(touchactions);
1225 while ((region->action == ACTION_NONE) &&
1226 (i < imax))
1227 {
1228 /* try to match with one of our touchregion screens */
1229 int len = strlen(touchactions[i].s);
1230 if (!strncmp(touchactions[i].s, action, len)
1231 && *(action+len) == '|')
1232 region->action = touchactions[i].action;
1233 i++;
1234 }
1235 if (region->action == ACTION_NONE)
1236 return WPS_ERROR_INVALID_PARAM;
1237 wps_data->touchregion_count++;
1238 return skip_end_of_line(wps_bufptr);
1239}
1240#endif
1241
1242/* Parse a generic token from the given string. Return the length read */
1243static int parse_token(const char *wps_bufptr, struct wps_data *wps_data)
1244{
1245 int skip = 0, taglen = 0, ret;
1246 struct wps_token *token = wps_data->tokens + wps_data->num_tokens;
1247 const struct wps_tag *tag;
1248
1249 switch(*wps_bufptr)
1250 {
1251
1252 case '%':
1253 case '<':
1254 case '|':
1255 case '>':
1256 case ';':
1257 case '#':
1258 /* escaped characters */
1259 token->type = WPS_TOKEN_CHARACTER;
1260 token->value.c = *wps_bufptr;
1261 taglen = 1;
1262 wps_data->num_tokens++;
1263 break;
1264
1265 case '?':
1266 /* conditional tag */
1267 token->type = WPS_TOKEN_CONDITIONAL;
1268 level++;
1269 condindex[level] = wps_data->num_tokens;
1270 numoptions[level] = 1;
1271 wps_data->num_tokens++;
1272 ret = parse_token(wps_bufptr + 1, wps_data);
1273 if (ret < 0) return ret;
1274 taglen = 1 + ret;
1275 break;
1276
1277 default:
1278 /* find what tag we have */
1279 for (tag = all_tags;
1280 strncmp(wps_bufptr, tag->name, strlen(tag->name)) != 0;
1281 tag++) ;
1282
1283 taglen = (tag->type != WPS_TOKEN_UNKNOWN) ? strlen(tag->name) : 2;
1284 token->type = tag->type;
1285 wps_data->sublines[wps_data->num_sublines].line_type |=
1286 tag->refresh_type;
1287
1288 /* if the tag has a special parsing function, we call it */
1289 if (tag->parse_func)
1290 {
1291 ret = tag->parse_func(wps_bufptr + taglen, token, wps_data);
1292 if (ret < 0) return ret;
1293 skip += ret;
1294 }
1295
1296 /* Some tags we don't want to save as tokens */
1297 if (tag->type == WPS_NO_TOKEN)
1298 break;
1299
1300 /* tags that start with 'F', 'I' or 'D' are for the next file */
1301 if ( *(tag->name) == 'I' || *(tag->name) == 'F' ||
1302 *(tag->name) == 'D')
1303 token->next = true;
1304
1305 wps_data->num_tokens++;
1306 break;
1307 }
1308
1309 skip += taglen;
1310 return skip;
1311}
1312
1313/* Parses the WPS.
1314 data is the pointer to the structure where the parsed WPS should be stored.
1315 It is initialised.
1316 wps_bufptr points to the string containing the WPS tags */
1317static bool wps_parse(struct wps_data *data, const char *wps_bufptr)
1318{
1319 if (!data || !wps_bufptr || !*wps_bufptr)
1320 return false;
1321
1322 char *stringbuf = data->string_buffer;
1323 int stringbuf_used = 0;
1324 enum wps_parse_error fail = PARSE_OK;
1325 int ret;
1326 line = 1;
1327 level = -1;
1328
1329 while(*wps_bufptr && !fail && data->num_tokens < WPS_MAX_TOKENS - 1
1330 && data->num_viewports < WPS_MAX_VIEWPORTS
1331 && data->num_lines < WPS_MAX_LINES)
1332 {
1333 switch(*wps_bufptr++)
1334 {
1335
1336 /* Regular tag */
1337 case '%':
1338 if ((ret = parse_token(wps_bufptr, data)) < 0)
1339 {
1340 fail = PARSE_FAIL_COND_INVALID_PARAM;
1341 break;
1342 }
1343 else if (level >= WPS_MAX_COND_LEVEL - 1)
1344 {
1345 fail = PARSE_FAIL_LIMITS_EXCEEDED;
1346 break;
1347 }
1348 wps_bufptr += ret;
1349 break;
1350
1351 /* Alternating sublines separator */
1352 case ';':
1353 if (level >= 0) /* there are unclosed conditionals */
1354 {
1355 fail = PARSE_FAIL_UNCLOSED_COND;
1356 break;
1357 }
1358
1359 if (data->num_sublines+1 < WPS_MAX_SUBLINES)
1360 wps_start_new_subline(data);
1361 else
1362 fail = PARSE_FAIL_LIMITS_EXCEEDED;
1363
1364 break;
1365
1366 /* Conditional list start */
1367 case '<':
1368 if (data->tokens[data->num_tokens-2].type != WPS_TOKEN_CONDITIONAL)
1369 {
1370 fail = PARSE_FAIL_COND_SYNTAX_ERROR;
1371 break;
1372 }
1373
1374 data->tokens[data->num_tokens].type = WPS_TOKEN_CONDITIONAL_START;
1375 lastcond[level] = data->num_tokens++;
1376 break;
1377
1378 /* Conditional list end */
1379 case '>':
1380 if (level < 0) /* not in a conditional, invalid char */
1381 {
1382 fail = PARSE_FAIL_INVALID_CHAR;
1383 break;
1384 }
1385
1386 data->tokens[data->num_tokens].type = WPS_TOKEN_CONDITIONAL_END;
1387 if (lastcond[level])
1388 data->tokens[lastcond[level]].value.i = data->num_tokens;
1389 else
1390 {
1391 fail = PARSE_FAIL_COND_SYNTAX_ERROR;
1392 break;
1393 }
1394
1395 lastcond[level] = 0;
1396 data->num_tokens++;
1397 data->tokens[condindex[level]].value.i = numoptions[level];
1398 level--;
1399 break;
1400
1401 /* Conditional list option */
1402 case '|':
1403 if (level < 0) /* not in a conditional, invalid char */
1404 {
1405 fail = PARSE_FAIL_INVALID_CHAR;
1406 break;
1407 }
1408
1409 data->tokens[data->num_tokens].type = WPS_TOKEN_CONDITIONAL_OPTION;
1410 if (lastcond[level])
1411 data->tokens[lastcond[level]].value.i = data->num_tokens;
1412 else
1413 {
1414 fail = PARSE_FAIL_COND_SYNTAX_ERROR;
1415 break;
1416 }
1417
1418 lastcond[level] = data->num_tokens;
1419 numoptions[level]++;
1420 data->num_tokens++;
1421 break;
1422
1423 /* Comment */
1424 case '#':
1425 if (level >= 0) /* there are unclosed conditionals */
1426 {
1427 fail = PARSE_FAIL_UNCLOSED_COND;
1428 break;
1429 }
1430
1431 wps_bufptr += skip_end_of_line(wps_bufptr);
1432 break;
1433
1434 /* End of this line */
1435 case '\n':
1436 if (level >= 0) /* there are unclosed conditionals */
1437 {
1438 fail = PARSE_FAIL_UNCLOSED_COND;
1439 break;
1440 }
1441
1442 line++;
1443 wps_start_new_subline(data);
1444 data->num_lines++; /* Start a new line */
1445
1446 if ((data->num_lines < WPS_MAX_LINES) &&
1447 (data->num_sublines < WPS_MAX_SUBLINES))
1448 {
1449 data->lines[data->num_lines].first_subline_idx =
1450 data->num_sublines;
1451
1452 data->sublines[data->num_sublines].first_token_idx =
1453 data->num_tokens;
1454 }
1455
1456 break;
1457
1458 /* String */
1459 default:
1460 {
1461 unsigned int len = 1;
1462 const char *string_start = wps_bufptr - 1;
1463
1464 /* find the length of the string */
1465 while (*wps_bufptr && *wps_bufptr != '#' &&
1466 *wps_bufptr != '%' && *wps_bufptr != ';' &&
1467 *wps_bufptr != '<' && *wps_bufptr != '>' &&
1468 *wps_bufptr != '|' && *wps_bufptr != '\n')
1469 {
1470 wps_bufptr++;
1471 len++;
1472 }
1473
1474 /* look if we already have that string */
1475 char **str;
1476 int i;
1477 bool found;
1478 for (i = 0, str = data->strings, found = false;
1479 i < data->num_strings &&
1480 !(found = (strlen(*str) == len &&
1481 strncmp(string_start, *str, len) == 0));
1482 i++, str++);
1483 /* If a matching string is found, found is true and i is
1484 the index of the string. If not, found is false */
1485
1486 if (!found)
1487 {
1488 /* new string */
1489
1490 if (stringbuf_used + len > STRING_BUFFER_SIZE - 1
1491 || data->num_strings >= WPS_MAX_STRINGS)
1492 {
1493 /* too many strings or characters */
1494 fail = PARSE_FAIL_LIMITS_EXCEEDED;
1495 break;
1496 }
1497
1498 strlcpy(stringbuf, string_start, len+1);
1499
1500 data->strings[data->num_strings] = stringbuf;
1501 stringbuf += len + 1;
1502 stringbuf_used += len + 1;
1503 data->tokens[data->num_tokens].value.i =
1504 data->num_strings;
1505 data->num_strings++;
1506 }
1507 else
1508 {
1509 /* another occurrence of an existing string */
1510 data->tokens[data->num_tokens].value.i = i;
1511 }
1512 data->tokens[data->num_tokens].type = WPS_TOKEN_STRING;
1513 data->num_tokens++;
1514 }
1515 break;
1516 }
1517 }
1518
1519 if (!fail && level >= 0) /* there are unclosed conditionals */
1520 fail = PARSE_FAIL_UNCLOSED_COND;
1521
1522 if (*wps_bufptr && !fail)
1523 /* one of the limits of the while loop was exceeded */
1524 fail = PARSE_FAIL_LIMITS_EXCEEDED;
1525
1526 data->viewports[data->num_viewports].last_line = data->num_lines - 1;
1527
1528 /* We have finished with the last viewport, so increment count */
1529 data->num_viewports++;
1530
1531#if defined(DEBUG) || defined(SIMULATOR)
1532 print_debug_info(data, fail, line);
1533#endif
1534
1535 return (fail == 0);
1536}
1537
1538#ifdef HAVE_LCD_BITMAP
1539/* Clear the WPS image cache */
1540static void wps_images_clear(struct wps_data *data)
1541{
1542 int i;
1543 /* set images to unloaded and not displayed */
1544 for (i = 0; i < MAX_IMAGES; i++)
1545 {
1546 data->img[i].loaded = false;
1547 data->img[i].display = -1;
1548 data->img[i].always_display = false;
1549 data->img[i].num_subimages = 1;
1550 }
1551}
1552#endif
1553
1554/* initial setup of wps_data */
1555void wps_data_init(struct wps_data *wps_data)
1556{
1557#ifdef HAVE_LCD_BITMAP
1558 wps_images_clear(wps_data);
1559 wps_data->wps_sb_tag = false;
1560 wps_data->show_sb_on_wps = false;
1561 wps_data->img_buf_ptr = wps_data->img_buf; /* where in image buffer */
1562 wps_data->img_buf_free = IMG_BUFSIZE; /* free space in image buffer */
1563 wps_data->peak_meter_enabled = false;
1564 /* progress bars */
1565 wps_data->progressbar_count = 0;
1566#else /* HAVE_LCD_CHARCELLS */
1567 int i;
1568 for (i = 0; i < 8; i++)
1569 {
1570 wps_data->wps_progress_pat[i] = 0;
1571 }
1572 wps_data->full_line_progressbar = false;
1573#endif
1574 wps_data->button_time_volume = 0;
1575 wps_data->wps_loaded = false;
1576}
1577
1578static void wps_reset(struct wps_data *data)
1579{
1580#ifdef HAVE_REMOTE_LCD
1581 bool rwps = data->remote_wps; /* remember whether the data is for a RWPS */
1582#endif
1583 memset(data, 0, sizeof(*data));
1584 wps_data_init(data);
1585#ifdef HAVE_REMOTE_LCD
1586 data->remote_wps = rwps;
1587#endif
1588}
1589
1590#ifdef HAVE_LCD_BITMAP
1591
1592static bool load_wps_bitmaps(struct wps_data *wps_data, char *bmpdir)
1593{
1594 char img_path[MAX_PATH];
1595 struct bitmap *bitmap;
1596 bool *loaded;
1597 int n;
1598 for (n = 0; n < BACKDROP_BMP; n++)
1599 {
1600 if (bmp_names[n])
1601 {
1602 get_image_filename(bmp_names[n], bmpdir,
1603 img_path, sizeof(img_path));
1604
1605 if (n >= PROGRESSBAR_BMP ) {
1606 /* progressbar bitmap */
1607 bitmap = &wps_data->progressbar[n-PROGRESSBAR_BMP].bm;
1608 loaded = &wps_data->progressbar[n-PROGRESSBAR_BMP].have_bitmap_pb;
1609 } else {
1610 /* regular bitmap */
1611 bitmap = &wps_data->img[n].bm;
1612 loaded = &wps_data->img[n].loaded;
1613 }
1614
1615 /* load the image */
1616 bitmap->data = wps_data->img_buf_ptr;
1617 if (load_bitmap(wps_data, img_path, bitmap))
1618 {
1619 *loaded = true;
1620
1621 /* Calculate and store height if this image has sub-images */
1622 if (n < MAX_IMAGES)
1623 wps_data->img[n].subimage_height = wps_data->img[n].bm.height /
1624 wps_data->img[n].num_subimages;
1625 }
1626 else
1627 {
1628 /* Abort if we can't load an image */
1629 DEBUGF("ERR: Failed to load image %d - %s\n",n,img_path);
1630 return false;
1631 }
1632 }
1633 }
1634
1635#if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
1636 if (bmp_names[BACKDROP_BMP])
1637 {
1638 get_image_filename(bmp_names[BACKDROP_BMP], bmpdir,
1639 img_path, sizeof(img_path));
1640
1641#if defined(HAVE_REMOTE_LCD)
1642 /* We only need to check LCD type if there is a remote LCD */
1643 if (!wps_data->remote_wps)
1644#endif
1645 {
1646 /* Load backdrop for the main LCD */
1647 if (!load_wps_backdrop(img_path))
1648 return false;
1649 }
1650#if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
1651 else
1652 {
1653 /* Load backdrop for the remote LCD */
1654 if (!load_remote_wps_backdrop(img_path))
1655 return false;
1656 }
1657#endif
1658 }
1659#endif /* has backdrop support */
1660
1661 /* If we got here, everything was OK */
1662 return true;
1663}
1664
1665#endif /* HAVE_LCD_BITMAP */
1666
1667/* to setup up the wps-data from a format-buffer (isfile = false)
1668 from a (wps-)file (isfile = true)*/
1669static bool wps_data_load(struct wps_data *wps_data,
1670 struct screen *display,
1671 const char *buf,
1672 bool isfile)
1673{
1674#ifdef HAVE_ALBUMART
1675 struct mp3entry *curtrack;
1676 long offset;
1677 int status;
1678 int wps_uses_albumart = wps_data->wps_uses_albumart;
1679 int albumart_max_height = wps_data->albumart_max_height;
1680 int albumart_max_width = wps_data->albumart_max_width;
1681#endif
1682 if (!wps_data || !buf)
1683 return false;
1684
1685 wps_reset(wps_data);
1686
1687 /* Initialise the first (default) viewport */
1688 wps_data->viewports[0].vp.x = 0;
1689 wps_data->viewports[0].vp.width = display->getwidth();
1690 wps_data->viewports[0].vp.height = display->getheight();
1691 switch (statusbar_position(display->screen_type))
1692 {
1693 case STATUSBAR_OFF:
1694 wps_data->viewports[0].vp.y = 0;
1695 break;
1696 case STATUSBAR_TOP:
1697 wps_data->viewports[0].vp.y = STATUSBAR_HEIGHT;
1698 wps_data->viewports[0].vp.height -= STATUSBAR_HEIGHT;
1699 break;
1700 case STATUSBAR_BOTTOM:
1701 wps_data->viewports[0].vp.y = 0;
1702 wps_data->viewports[0].vp.height -= STATUSBAR_HEIGHT;
1703 break;
1704 }
1705#ifdef HAVE_LCD_BITMAP
1706 wps_data->viewports[0].vp.font = FONT_UI;
1707 wps_data->viewports[0].vp.drawmode = DRMODE_SOLID;
1708#endif
1709#if LCD_DEPTH > 1
1710 if (display->depth > 1)
1711 {
1712 wps_data->viewports[0].vp.fg_pattern = display->get_foreground();
1713 wps_data->viewports[0].vp.bg_pattern = display->get_background();
1714 }
1715#endif
1716 if (!isfile)
1717 {
1718 return wps_parse(wps_data, buf);
1719 }
1720 else
1721 {
1722 /*
1723 * Hardcode loading WPS_DEFAULTCFG to cause a reset ideally this
1724 * wants to be a virtual file. Feel free to modify dirbrowse()
1725 * if you're feeling brave.
1726 */
1727#ifndef __PCTOOL__
1728 if (! strcmp(buf, WPS_DEFAULTCFG) )
1729 {
1730 global_settings.wps_file[0] = 0;
1731 return false;
1732 }
1733
1734#ifdef HAVE_REMOTE_LCD
1735 if (! strcmp(buf, RWPS_DEFAULTCFG) )
1736 {
1737 global_settings.rwps_file[0] = 0;
1738 return false;
1739 }
1740#endif
1741#endif /* __PCTOOL__ */
1742
1743 int fd = open_utf8(buf, O_RDONLY);
1744
1745 if (fd < 0)
1746 return false;
1747
1748 /* get buffer space from the plugin buffer */
1749 size_t buffersize = 0;
1750 char *wps_buffer = (char *)plugin_get_buffer(&buffersize);
1751
1752 if (!wps_buffer)
1753 return false;
1754
1755 /* copy the file's content to the buffer for parsing,
1756 ensuring that every line ends with a newline char. */
1757 unsigned int start = 0;
1758 while(read_line(fd, wps_buffer + start, buffersize - start) > 0)
1759 {
1760 start += strlen(wps_buffer + start);
1761 if (start < buffersize - 1)
1762 {
1763 wps_buffer[start++] = '\n';
1764 wps_buffer[start] = 0;
1765 }
1766 }
1767
1768 close(fd);
1769
1770 if (start <= 0)
1771 return false;
1772
1773#ifdef HAVE_LCD_BITMAP
1774 /* Set all filename pointers to NULL */
1775 memset(bmp_names, 0, sizeof(bmp_names));
1776#endif
1777
1778 /* parse the WPS source */
1779 if (!wps_parse(wps_data, wps_buffer)) {
1780 wps_reset(wps_data);
1781 return false;
1782 }
1783
1784 wps_data->wps_loaded = true;
1785
1786#ifdef HAVE_LCD_BITMAP
1787 /* get the bitmap dir */
1788 char bmpdir[MAX_PATH];
1789 char *dot = strrchr(buf, '.');
1790
1791 strlcpy(bmpdir, buf, dot - buf + 1);
1792
1793 /* load the bitmaps that were found by the parsing */
1794 if (!load_wps_bitmaps(wps_data, bmpdir)) {
1795 wps_reset(wps_data);
1796 return false;
1797 }
1798#endif
1799#ifdef HAVE_ALBUMART
1800 status = audio_status();
1801 if (((!wps_uses_albumart && wps_data->wps_uses_albumart) ||
1802 (wps_data->wps_uses_albumart &&
1803 (albumart_max_height != wps_data->albumart_max_height ||
1804 albumart_max_width != wps_data->albumart_max_width))) &&
1805 status & AUDIO_STATUS_PLAY)
1806 {
1807 curtrack = audio_current_track();
1808 offset = curtrack->offset;
1809 audio_stop();
1810 if (!(status & AUDIO_STATUS_PAUSE))
1811 audio_play(offset);
1812 }
1813#endif
1814 return true;
1815 }
1816}
1817
1818void skin_data_load(struct wps_data *wps_data,
1819 struct screen *display,
1820 const char *buf,
1821 bool isfile)
1822{
1823 bool loaded_ok = buf && wps_data_load(wps_data, display, buf, isfile);
1824 if (!loaded_ok) /* load the hardcoded default */
1825 {
1826 /* set the default wps for the main-screen */
1827 if(display->screen_type == SCREEN_MAIN)
1828 {
1829#if LCD_DEPTH > 1
1830 unload_wps_backdrop();
1831#endif
1832 wps_data_load(wps_data,
1833 display,
1834#ifdef HAVE_LCD_BITMAP
1835 "%s%?it<%?in<%in. |>%it|%fn>\n"
1836 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
1837 "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
1838 "\n"
1839 "%al%pc/%pt%ar[%pp:%pe]\n"
1840 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
1841 "%pb\n"
1842 "%pm\n", false);
1843#else
1844 "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
1845 "%pc%?ps<*|/>%pt\n", false);
1846#endif
1847 }
1848#ifdef HAVE_REMOTE_LCD
1849 /* set the default wps for the remote-screen */
1850 else if(display->screen_type == SCREEN_REMOTE)
1851 {
1852#if LCD_REMOTE_DEPTH > 1
1853 unload_remote_wps_backdrop();
1854#endif
1855 wps_data_load(wps_data,
1856 display,
1857 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
1858 "%s%?it<%?in<%in. |>%it|%fn>\n"
1859 "%al%pc/%pt%ar[%pp:%pe]\n"
1860 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
1861 "%pb\n", false);
1862 }
1863#endif
1864 }
1865}
1866
1867int wps_subline_index(struct wps_data *data, int line, int subline)
1868{
1869 return data->lines[line].first_subline_idx + subline;
1870}
1871
1872int wps_first_token_index(struct wps_data *data, int line, int subline)
1873{
1874 int first_subline_idx = data->lines[line].first_subline_idx;
1875 return data->sublines[first_subline_idx + subline].first_token_idx;
1876}
1877
1878int wps_last_token_index(struct wps_data *data, int line, int subline)
1879{
1880 int first_subline_idx = data->lines[line].first_subline_idx;
1881 int idx = first_subline_idx + subline;
1882 if (idx < data->num_sublines - 1)
1883 {
1884 /* This subline ends where the next begins */
1885 return data->sublines[idx+1].first_token_idx - 1;
1886 }
1887 else
1888 {
1889 /* The last subline goes to the end */
1890 return data->num_tokens - 1;
1891 }
1892}