mirror of
https://github.com/Rockbox/rockbox.git
synced 2025-10-13 10:07:38 -04:00
replace applicable calls to strlcpy with calls to strmemccpy which null terminates on truncation in theory the strmemccpy calls should be slightly faster since they don't traverse the rest of the source string on truncation but I seriously doubt there is too much of that going on in the code base Change-Id: Ia0251514e36a6242bbf3f03c5e0df123aba60ed2
2616 lines
82 KiB
C
2616 lines
82 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2007 Nicolas Pennequin, Dan Everton, Matthias Mohr
|
|
* 2010 Jonathan Gordon
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include "config.h"
|
|
#ifndef __PCTOOL__
|
|
#include "core_alloc.h"
|
|
#endif
|
|
#include "file.h"
|
|
#include "misc.h"
|
|
#include "plugin.h"
|
|
#include "viewport.h"
|
|
|
|
#include "skin_buffer.h"
|
|
#include "skin_debug.h"
|
|
#include "skin_parser.h"
|
|
#include "tag_table.h"
|
|
|
|
#ifdef __PCTOOL__
|
|
#ifdef WPSEDITOR
|
|
#include "proxy.h"
|
|
#include "sysfont.h"
|
|
#else
|
|
#include "action.h"
|
|
#include "checkwps.h"
|
|
#include "audio.h"
|
|
#define lang_is_rtl() (false)
|
|
#define DEBUGF printf
|
|
#endif /*WPSEDITOR*/
|
|
#else
|
|
#include "debug.h"
|
|
#include "language.h"
|
|
#endif /*__PCTOOL__*/
|
|
|
|
#include <ctype.h>
|
|
#include <stdbool.h>
|
|
#include "font.h"
|
|
|
|
#include "wps_internals.h"
|
|
#include "skin_engine.h"
|
|
#include "settings.h"
|
|
#include "settings_list.h"
|
|
#include "rbpaths.h"
|
|
#if CONFIG_TUNER
|
|
#include "radio.h"
|
|
#include "tuner.h"
|
|
#endif
|
|
|
|
#include "bmp.h"
|
|
|
|
#ifdef HAVE_ALBUMART
|
|
#include "playback.h"
|
|
#endif
|
|
|
|
#include "backdrop.h"
|
|
#include "statusbar-skinned.h"
|
|
|
|
#define WPS_ERROR_INVALID_PARAM -1
|
|
|
|
static char* skin_buffer = NULL;
|
|
#if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
|
|
static char *backdrop_filename;
|
|
#endif
|
|
static struct skin_stats *_stats = NULL;
|
|
|
|
static bool isdefault(struct skin_tag_parameter *param)
|
|
{
|
|
return param->type == DEFAULT;
|
|
}
|
|
|
|
static inline char*
|
|
get_param_text(struct skin_element *element, int param_number)
|
|
{
|
|
struct skin_tag_parameter* params = SKINOFFSETTOPTR(skin_buffer, element->params);
|
|
return SKINOFFSETTOPTR(skin_buffer, params[param_number].data.text);
|
|
}
|
|
|
|
static inline struct skin_element*
|
|
get_param_code(struct skin_element *element, int param_number)
|
|
{
|
|
struct skin_tag_parameter* params = SKINOFFSETTOPTR(skin_buffer, element->params);
|
|
return SKINOFFSETTOPTR(skin_buffer, params[param_number].data.code);
|
|
}
|
|
|
|
static inline struct skin_tag_parameter*
|
|
get_param(struct skin_element *element, int param_number)
|
|
{
|
|
struct skin_tag_parameter* params = SKINOFFSETTOPTR(skin_buffer, element->params);
|
|
return ¶ms[param_number];
|
|
}
|
|
|
|
/* which screen are we parsing for? */
|
|
static enum screen_type curr_screen;
|
|
|
|
/* the current viewport */
|
|
static struct skin_element *curr_viewport_element;
|
|
static struct skin_viewport *curr_vp;
|
|
static struct skin_element *first_viewport;
|
|
|
|
static struct line *curr_line;
|
|
|
|
static int follow_lang_direction = 0;
|
|
|
|
typedef int (*parse_function)(struct skin_element *element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data);
|
|
|
|
/* add a skin_token_list item to the list chain. ALWAYS appended because some of the
|
|
* chains require the order to be kept.
|
|
*/
|
|
static void add_to_ll_chain(OFFSETTYPE(struct skin_token_list *) *listoffset,
|
|
struct skin_token_list *item)
|
|
{
|
|
struct skin_token_list *list = SKINOFFSETTOPTR(skin_buffer, *listoffset);
|
|
if (list == NULL)
|
|
{
|
|
*listoffset = PTRTOSKINOFFSET(skin_buffer, item);
|
|
}
|
|
else
|
|
{
|
|
while (SKINOFFSETTOPTR(skin_buffer, list->next))
|
|
list = SKINOFFSETTOPTR(skin_buffer, list->next);
|
|
list->next = PTRTOSKINOFFSET(skin_buffer, item);
|
|
}
|
|
}
|
|
|
|
void *skin_find_item(const char *label, enum skin_find_what what,
|
|
struct wps_data *data)
|
|
{
|
|
char *databuf = get_skin_buffer(data);
|
|
union {
|
|
struct skin_token_list *linkedlist;
|
|
struct skin_element *vplist;
|
|
} list = {NULL};
|
|
bool isvplist = false;
|
|
void *ret = NULL;
|
|
if (!databuf)
|
|
databuf = skin_buffer;
|
|
switch (what)
|
|
{
|
|
case SKIN_FIND_UIVP:
|
|
case SKIN_FIND_VP:
|
|
list.vplist = SKINOFFSETTOPTR(databuf, data->tree);
|
|
isvplist = true;
|
|
break;
|
|
case SKIN_FIND_IMAGE:
|
|
list.linkedlist = SKINOFFSETTOPTR(databuf, data->images);
|
|
break;
|
|
#ifdef HAVE_TOUCHSCREEN
|
|
case SKIN_FIND_TOUCHREGION:
|
|
list.linkedlist = SKINOFFSETTOPTR(databuf, data->touchregions);
|
|
break;
|
|
#endif
|
|
#ifdef HAVE_SKIN_VARIABLES
|
|
case SKIN_VARIABLE:
|
|
list.linkedlist = SKINOFFSETTOPTR(databuf, data->skinvars);
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
while (list.linkedlist)
|
|
{
|
|
bool skip = false;
|
|
struct wps_token *token = NULL;
|
|
const char *itemlabel = NULL;
|
|
if (!isvplist)
|
|
token = SKINOFFSETTOPTR(databuf, list.linkedlist->token);
|
|
switch (what)
|
|
{
|
|
case SKIN_FIND_UIVP:
|
|
case SKIN_FIND_VP:
|
|
ret = SKINOFFSETTOPTR(databuf, list.vplist->data);
|
|
if (!ret) break;
|
|
if (((struct skin_viewport *)ret)->label == VP_DEFAULT_LABEL)
|
|
itemlabel = VP_DEFAULT_LABEL_STRING;
|
|
else
|
|
itemlabel = SKINOFFSETTOPTR(databuf, ((struct skin_viewport *)ret)->label);
|
|
skip = !(((struct skin_viewport *)ret)->is_infovp ==
|
|
(what==SKIN_FIND_UIVP));
|
|
break;
|
|
case SKIN_FIND_IMAGE:
|
|
if (!token) break;
|
|
ret = SKINOFFSETTOPTR(databuf, token->value.data);
|
|
if (!ret) break;
|
|
itemlabel = SKINOFFSETTOPTR(databuf, ((struct gui_img *)ret)->label);
|
|
break;
|
|
#ifdef HAVE_TOUCHSCREEN
|
|
case SKIN_FIND_TOUCHREGION:
|
|
if (!token) break;
|
|
ret = SKINOFFSETTOPTR(databuf, token->value.data);
|
|
if (!ret) break;
|
|
itemlabel = SKINOFFSETTOPTR(databuf, ((struct touchregion *)ret)->label);
|
|
break;
|
|
#endif
|
|
#ifdef HAVE_SKIN_VARIABLES
|
|
case SKIN_VARIABLE:
|
|
if (!token) break;
|
|
ret = SKINOFFSETTOPTR(databuf, token->value.data);
|
|
if (!ret) break;
|
|
itemlabel = SKINOFFSETTOPTR(databuf, ((struct skin_var *)ret)->label);
|
|
break;
|
|
#endif
|
|
}
|
|
if (!skip && itemlabel && !strcmp(itemlabel, label))
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
if (isvplist)
|
|
list.vplist = SKINOFFSETTOPTR(databuf, list.vplist->next);
|
|
else
|
|
list.linkedlist = SKINOFFSETTOPTR(databuf, list.linkedlist->next);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* create and init a new wpsll item.
|
|
* passing NULL to token will alloc a new one.
|
|
* You should only pass NULL for the token when the token type (table above)
|
|
* is WPS_NO_TOKEN which means it is not stored automatically in the skins token array
|
|
*/
|
|
static struct skin_token_list *new_skin_token_list_item(struct wps_token *token,
|
|
void* token_data)
|
|
{
|
|
struct skin_token_list *llitem = skin_buffer_alloc(sizeof(*llitem));
|
|
if (!token)
|
|
token = skin_buffer_alloc(sizeof(*token));
|
|
if (!llitem || !token)
|
|
return NULL;
|
|
llitem->next = PTRTOSKINOFFSET(skin_buffer, NULL);
|
|
llitem->token = PTRTOSKINOFFSET(skin_buffer, token);
|
|
if (token_data)
|
|
token->value.data = PTRTOSKINOFFSET(skin_buffer, token_data);
|
|
return llitem;
|
|
}
|
|
|
|
static int parse_statusbar_tags(struct skin_element* element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data)
|
|
{
|
|
(void)element;
|
|
if (token->type == SKIN_TOKEN_DRAW_INBUILTBAR)
|
|
{
|
|
token->value.data = PTRTOSKINOFFSET(skin_buffer, (void*)&curr_vp->vp);
|
|
}
|
|
else
|
|
{
|
|
struct skin_viewport *skin_default = SKINOFFSETTOPTR(skin_buffer, first_viewport->data);
|
|
if (first_viewport->params_count == 0)
|
|
{
|
|
wps_data->wps_sb_tag = true;
|
|
wps_data->show_sb_on_wps = (token->type == SKIN_TOKEN_ENABLE_THEME);
|
|
}
|
|
if (wps_data->show_sb_on_wps)
|
|
{
|
|
viewport_set_defaults(&skin_default->vp, curr_screen);
|
|
}
|
|
else
|
|
{
|
|
viewport_set_fullscreen(&skin_default->vp, curr_screen);
|
|
}
|
|
#ifdef HAVE_REMOTE_LCD
|
|
/* This parser requires viewports which will use the settings font to
|
|
* have font == 1, but the above viewport_set() calls set font to
|
|
* the current real font id. So force 1 here it will be set correctly
|
|
* at the end
|
|
*/
|
|
skin_default->vp.font = 1;
|
|
#endif
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int get_image_id(int c)
|
|
{
|
|
if(c >= 'a' && c <= 'z')
|
|
return c - 'a';
|
|
else if(c >= 'A' && c <= 'Z')
|
|
return c - 'A' + 26;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
void get_image_filename(const char *start, const char* bmpdir,
|
|
char *buf, int buf_size)
|
|
{
|
|
snprintf(buf, buf_size, "%s/%s", bmpdir, start);
|
|
}
|
|
|
|
static int parse_image_display(struct skin_element *element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data)
|
|
{
|
|
char *label = get_param_text(element, 0);
|
|
char sublabel = '\0';
|
|
int subimage;
|
|
struct gui_img *img;
|
|
struct image_display *id = skin_buffer_alloc(sizeof(*id));
|
|
|
|
if (element->params_count == 1 && strlen(label) <= 2)
|
|
{
|
|
/* backwards compatability. Allow %xd(Aa) to still work */
|
|
sublabel = label[1];
|
|
label[1] = '\0';
|
|
}
|
|
/* sanity check */
|
|
img = skin_find_item(label, SKIN_FIND_IMAGE, wps_data);
|
|
if (!img || !id)
|
|
{
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
}
|
|
id->label = img->label;
|
|
id->offset = 0;
|
|
id->token = PTRTOSKINOFFSET(skin_buffer, NULL);
|
|
if (img->using_preloaded_icons)
|
|
{
|
|
token->type = SKIN_TOKEN_IMAGE_DISPLAY_LISTICON;
|
|
}
|
|
|
|
if (token->type == SKIN_TOKEN_IMAGE_DISPLAY_9SEGMENT)
|
|
img->is_9_segment = true;
|
|
|
|
if (element->params_count > 1)
|
|
{
|
|
struct skin_tag_parameter *param1 = get_param(element, 1);
|
|
if (param1->type == CODE)
|
|
id->token = get_param_code(element, 1)->data;
|
|
/* specify a number. 1 being the first subimage (i.e top) NOT 0 */
|
|
else if (param1->type == INTEGER)
|
|
id->subimage = param1->data.number - 1;
|
|
if (element->params_count > 2)
|
|
id->offset = get_param(element, 2)->data.number;
|
|
}
|
|
else
|
|
{
|
|
if ((subimage = get_image_id(sublabel)) != -1)
|
|
{
|
|
if (subimage >= img->num_subimages)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
id->subimage = subimage;
|
|
} else {
|
|
id->subimage = 0;
|
|
}
|
|
}
|
|
token->value.data = PTRTOSKINOFFSET(skin_buffer, id);
|
|
return 0;
|
|
}
|
|
|
|
static int parse_image_load(struct skin_element *element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data)
|
|
{
|
|
const char* filename;
|
|
const char* id;
|
|
int x = 0,y = 0, subimages = 1;
|
|
struct gui_img *img;
|
|
|
|
/* format: %x(n,filename.bmp[,x,y])
|
|
or %xl(n,filename.bmp[,x,y])
|
|
or %xl(n,filename.bmp[,x,y,num_subimages])
|
|
*/
|
|
|
|
id = get_param_text(element, 0);
|
|
filename = get_param_text(element, 1);
|
|
/* x,y,num_subimages handling:
|
|
* If all 3 are left out use sane defaults.
|
|
* If there are 2 params it must be x,y
|
|
* if there is only 1 param it must be the num_subimages
|
|
*/
|
|
if (element->params_count == 3)
|
|
subimages = get_param(element, 2)->data.number;
|
|
else if (element->params_count > 3)
|
|
{
|
|
struct skin_tag_parameter *param2 = get_param(element, 2);
|
|
struct skin_tag_parameter *param3 = get_param(element, 3);
|
|
if (param2->type == PERCENT)
|
|
x = param2->data.number * curr_vp->vp.width / 1000;
|
|
else
|
|
x = param2->data.number;
|
|
if (param3->type == PERCENT)
|
|
y = param3->data.number * curr_vp->vp.height / 1000;
|
|
else
|
|
y = param3->data.number;
|
|
|
|
if (element->params_count == 5)
|
|
subimages = get_param(element, 4)->data.number;
|
|
}
|
|
/* check the image number and load state */
|
|
if(skin_find_item(id, SKIN_FIND_IMAGE, wps_data))
|
|
{
|
|
/* Invalid image ID */
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
}
|
|
img = skin_buffer_alloc(sizeof(*img));
|
|
if (!img)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
/* save a pointer to the filename */
|
|
img->bm.data = (char*)filename;
|
|
img->label = PTRTOSKINOFFSET(skin_buffer, (void*)id);
|
|
img->x = x;
|
|
img->y = y;
|
|
img->num_subimages = subimages;
|
|
img->display = -1;
|
|
img->using_preloaded_icons = false;
|
|
img->buflib_handle = -1;
|
|
img->is_9_segment = false;
|
|
img->loaded = false;
|
|
|
|
if (token->type == SKIN_TOKEN_IMAGE_DISPLAY)
|
|
token->value.data = PTRTOSKINOFFSET(skin_buffer, img);
|
|
|
|
if (!strcmp(img->bm.data, "__list_icons__"))
|
|
{
|
|
img->num_subimages = Icon_Last_Themeable;
|
|
img->using_preloaded_icons = true;
|
|
}
|
|
|
|
struct skin_token_list *item = new_skin_token_list_item(NULL, img);
|
|
if (!item)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
add_to_ll_chain(&wps_data->images, item);
|
|
|
|
return 0;
|
|
}
|
|
struct skin_font {
|
|
int id; /* the id from font_load */
|
|
char *name; /* filename without path and extension */
|
|
int glyphs; /* how many glyphs to reserve room for */
|
|
};
|
|
static struct skin_font skinfonts[MAXUSERFONTS];
|
|
static int parse_font_load(struct skin_element *element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data)
|
|
{
|
|
(void)wps_data; (void)token;
|
|
int id = get_param(element, 0)->data.number;
|
|
char *filename = get_param_text(element, 1);
|
|
int glyphs;
|
|
char *ptr;
|
|
|
|
if(element->params_count > 2)
|
|
glyphs = get_param(element, 2)->data.number;
|
|
else
|
|
glyphs = global_settings.glyphs_to_cache;
|
|
if (id < 2)
|
|
{
|
|
DEBUGF("font id must be >= 2 (%d)\n", id);
|
|
return -1;
|
|
}
|
|
#if defined(DEBUG) || defined(SIMULATOR)
|
|
if (skinfonts[id-2].name != NULL)
|
|
{
|
|
DEBUGF("font id %d already being used\n", id);
|
|
}
|
|
#endif
|
|
/* make sure the filename contains .fnt,
|
|
* we dont actually use it, but require it anyway */
|
|
ptr = strchr(filename, '.');
|
|
if (!ptr || strncmp(ptr, ".fnt", 4))
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
skinfonts[id-2].id = -1;
|
|
skinfonts[id-2].name = filename;
|
|
skinfonts[id-2].glyphs = glyphs;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_playlistview(struct skin_element *element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data)
|
|
{
|
|
(void)wps_data;
|
|
struct playlistviewer *viewer = skin_buffer_alloc(sizeof(*viewer));
|
|
if (!viewer)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
viewer->vp = PTRTOSKINOFFSET(skin_buffer, &curr_vp->vp);
|
|
viewer->show_icons = true;
|
|
viewer->start_offset = get_param(element, 0)->data.number;
|
|
viewer->line = PTRTOSKINOFFSET(skin_buffer, get_param_code(element, 1));
|
|
|
|
token->value.data = PTRTOSKINOFFSET(skin_buffer, (void*)viewer);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef HAVE_LCD_COLOR
|
|
static int parse_viewport_gradient_setup(struct skin_element *element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data)
|
|
{
|
|
(void)wps_data;
|
|
struct gradient_config *cfg;
|
|
if (element->params_count < 2) /* only start and end are required */
|
|
return 1;
|
|
cfg = skin_buffer_alloc(sizeof(*cfg));
|
|
if (!cfg)
|
|
return 1;
|
|
if (!parse_color(curr_screen, get_param_text(element, 0), &cfg->start) ||
|
|
!parse_color(curr_screen, get_param_text(element, 1), &cfg->end))
|
|
return 1;
|
|
if (element->params_count > 2)
|
|
{
|
|
if (!parse_color(curr_screen, get_param_text(element, 2), &cfg->text))
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
cfg->text = curr_vp->vp.fg_pattern;
|
|
}
|
|
|
|
token->value.data = PTRTOSKINOFFSET(skin_buffer, cfg);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int parse_listitem(struct skin_element *element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data)
|
|
{
|
|
(void)wps_data;
|
|
struct listitem *li = skin_buffer_alloc(sizeof(*li));
|
|
if (!li)
|
|
return -1;
|
|
token->value.data = PTRTOSKINOFFSET(skin_buffer, li);
|
|
if (element->params_count == 0)
|
|
li->offset = 0;
|
|
else
|
|
{
|
|
li->offset = get_param(element, 0)->data.number;
|
|
if (element->params_count > 1)
|
|
li->wrap = strcasecmp(get_param_text(element, 1), "nowrap") != 0;
|
|
else
|
|
li->wrap = true;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int parse_listitemviewport(struct skin_element *element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data)
|
|
{
|
|
#ifndef __PCTOOL__
|
|
struct skin_tag_parameter *param;
|
|
struct listitem_viewport_cfg *cfg = skin_buffer_alloc(sizeof(*cfg));
|
|
if (!cfg)
|
|
return -1;
|
|
cfg->data = wps_data;
|
|
cfg->tile = false;
|
|
cfg->label = PTRTOSKINOFFSET(skin_buffer, get_param_text(element, 0));
|
|
cfg->width = -1;
|
|
cfg->height = -1;
|
|
|
|
param = get_param(element, 1);
|
|
if (!isdefault(param))
|
|
cfg->width = param->data.number;
|
|
|
|
param = get_param(element, 2);
|
|
if (!isdefault(param))
|
|
cfg->height = param->data.number;
|
|
|
|
if (element->params_count > 3 &&
|
|
!strcmp(get_param_text(element, 3), "tile"))
|
|
cfg->tile = true;
|
|
token->value.data = PTRTOSKINOFFSET(skin_buffer, (void*)cfg);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
#if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
|
|
static int parse_viewporttextstyle(struct skin_element *element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data)
|
|
{
|
|
(void)wps_data;
|
|
char *mode = get_param_text(element, 0);
|
|
struct line_desc *line = skin_buffer_alloc(sizeof(*line));
|
|
*line = (struct line_desc)LINE_DESC_DEFINIT;
|
|
unsigned colour;
|
|
|
|
static const char * const vp_options[] = { "invert", "color", "colour",
|
|
"clear", "gradient", NULL};
|
|
|
|
int vp_op = string_option(mode, vp_options, false);
|
|
|
|
if (vp_op == 0) /*invert*/
|
|
{
|
|
line->style = STYLE_INVERT;
|
|
}
|
|
else if (vp_op == 1 || vp_op == 2) /*color/colour*/
|
|
{
|
|
if (element->params_count < 2 ||
|
|
!parse_color(curr_screen, get_param_text(element, 1), &colour))
|
|
return 1;
|
|
/* STYLE_COLORED is only a modifier and can't be used on its own */
|
|
line->style = STYLE_COLORED | STYLE_DEFAULT;
|
|
line->text_color = colour;
|
|
}
|
|
#ifdef HAVE_LCD_COLOR
|
|
else if (vp_op == 4) /*gradient*/
|
|
{
|
|
int num_lines;
|
|
if (element->params_count < 2)
|
|
num_lines = 1;
|
|
else /* atoi() instead of using a number in the parser is because [si]
|
|
* will select the number for something which looks like a colour
|
|
* making the "colour" case (above) harder to parse */
|
|
num_lines = atoi(get_param_text(element, 1));
|
|
line->style = STYLE_GRADIENT;
|
|
line->nlines = num_lines;
|
|
}
|
|
#endif
|
|
else if (vp_op == 3) /*clear*/
|
|
{
|
|
line->style = STYLE_DEFAULT;
|
|
}
|
|
else
|
|
return 1;
|
|
|
|
token->value.data = PTRTOSKINOFFSET(skin_buffer, line);
|
|
return 0;
|
|
}
|
|
|
|
static int parse_drawrectangle( struct skin_element *element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data)
|
|
{
|
|
(void)wps_data;
|
|
struct skin_tag_parameter *param;
|
|
struct draw_rectangle *rect = skin_buffer_alloc(sizeof(*rect));
|
|
|
|
if (!rect)
|
|
return -1;
|
|
|
|
param = get_param(element, 0);
|
|
if (param->type == PERCENT)
|
|
rect->x = param->data.number * curr_vp->vp.width / 1000;
|
|
else
|
|
rect->x = param->data.number;
|
|
|
|
param = get_param(element, 1);
|
|
if (param->type == PERCENT)
|
|
rect->y = param->data.number * curr_vp->vp.height / 1000;
|
|
else
|
|
rect->y = param->data.number;
|
|
|
|
param = get_param(element, 2);
|
|
if (isdefault(param))
|
|
rect->width = curr_vp->vp.width - rect->x;
|
|
else if (param->type == PERCENT)
|
|
rect->width = param->data.number * curr_vp->vp.width / 1000;
|
|
else
|
|
rect->width = param->data.number;
|
|
|
|
param = get_param(element, 3);
|
|
if (isdefault(param))
|
|
rect->height = curr_vp->vp.height - rect->y;
|
|
else if (param->type == PERCENT)
|
|
rect->height = param->data.number * curr_vp->vp.height / 1000;
|
|
else
|
|
rect->height = param->data.number;
|
|
|
|
rect->start_colour = curr_vp->vp.fg_pattern;
|
|
rect->end_colour = curr_vp->vp.fg_pattern;
|
|
|
|
if (element->params_count > 4)
|
|
{
|
|
if (!parse_color(curr_screen, get_param_text(element, 4),
|
|
&rect->start_colour))
|
|
return -1;
|
|
rect->end_colour = rect->start_colour;
|
|
}
|
|
if (element->params_count > 5)
|
|
{
|
|
if (!parse_color(curr_screen, get_param_text(element, 5),
|
|
&rect->end_colour))
|
|
return -1;
|
|
}
|
|
token->value.data = PTRTOSKINOFFSET(skin_buffer, rect);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_viewportcolour(struct skin_element *element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data)
|
|
{
|
|
(void)wps_data;
|
|
struct skin_tag_parameter *param = get_param(element, 0);
|
|
struct viewport_colour *colour = skin_buffer_alloc(sizeof(*colour));
|
|
if (!colour)
|
|
return -1;
|
|
if (isdefault(param))
|
|
{
|
|
colour->colour = get_viewport_default_colour(curr_screen,
|
|
token->type == SKIN_TOKEN_VIEWPORT_FGCOLOUR);
|
|
}
|
|
else
|
|
{
|
|
if (!parse_color(curr_screen, SKINOFFSETTOPTR(skin_buffer, param->data.text),
|
|
&colour->colour))
|
|
return -1;
|
|
}
|
|
colour->vp = PTRTOSKINOFFSET(skin_buffer, &curr_vp->vp);
|
|
token->value.data = PTRTOSKINOFFSET(skin_buffer, colour);
|
|
if (element->line == curr_viewport_element->line)
|
|
{
|
|
if (token->type == SKIN_TOKEN_VIEWPORT_FGCOLOUR)
|
|
curr_vp->vp.fg_pattern = colour->colour;
|
|
else
|
|
curr_vp->vp.bg_pattern = colour->colour;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int parse_image_special(struct skin_element *element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data)
|
|
{
|
|
(void)wps_data; /* kill warning */
|
|
(void)token;
|
|
|
|
#if LCD_DEPTH > 1
|
|
char *filename;
|
|
if (token->type == SKIN_TOKEN_IMAGE_BACKDROP)
|
|
{
|
|
if (isdefault(get_param(element, 0)))
|
|
{
|
|
filename = "-";
|
|
}
|
|
else
|
|
{
|
|
filename = get_param_text(element, 0);
|
|
/* format: %X(filename.bmp) or %X(d) */
|
|
if (!strcmp(filename, "d"))
|
|
filename = NULL;
|
|
}
|
|
backdrop_filename = filename;
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int parse_progressbar_tag(struct skin_element* element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data);
|
|
|
|
static int parse_setting_and_lang(struct skin_element *element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data)
|
|
{
|
|
/* NOTE: both the string validations that happen in here will
|
|
* automatically PASS on checkwps because its too hard to get
|
|
* settings_list.c and english.lang built for it.
|
|
* If that ever changes remove the #ifndef __PCTOOL__'s here
|
|
*/
|
|
(void)wps_data;
|
|
char *temp = get_param_text(element, 0);
|
|
int i;
|
|
|
|
if (token->type == SKIN_TOKEN_TRANSLATEDSTRING)
|
|
{
|
|
#ifndef __PCTOOL__
|
|
i = lang_english_to_id(temp);
|
|
if (i < 0)
|
|
i = LANG_LAST_INDEX_IN_ARRAY;
|
|
#endif
|
|
}
|
|
else if (element->params_count > 1)
|
|
{
|
|
if (element->params_count > 4)
|
|
return parse_progressbar_tag(element, token, wps_data);
|
|
else
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
}
|
|
else
|
|
{
|
|
#ifndef __PCTOOL__
|
|
if (find_setting_by_cfgname(temp, &i) == NULL)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
#endif
|
|
}
|
|
/* Store the setting number */
|
|
token->value.i = i;
|
|
return 0;
|
|
}
|
|
|
|
static int parse_logical_andor(struct skin_element *element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data)
|
|
{
|
|
(void)wps_data;
|
|
token->value.data = PTRTOSKINOFFSET(skin_buffer, element);
|
|
return 0;
|
|
}
|
|
|
|
static int parse_logical_if(struct skin_element *element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data)
|
|
{
|
|
(void)wps_data;
|
|
char *op = get_param_text(element, 1);
|
|
struct logical_if *lif = skin_buffer_alloc(sizeof(*lif));
|
|
if (!lif)
|
|
return -1;
|
|
token->value.data = PTRTOSKINOFFSET(skin_buffer, lif);
|
|
lif->token = get_param_code(element, 0)->data;
|
|
|
|
/* one or two operator conditionals */
|
|
#define OPS2VAL(op1, op2) ((int)op1 << 8 | (int)op2)
|
|
#define CLAUSE(op1, op2, symbol) {OPS2VAL(op1, op2), symbol }
|
|
|
|
struct clause_symbol {int value;int symbol;};
|
|
static const struct clause_symbol get_clause_match[] =
|
|
{
|
|
CLAUSE('=', '=', IF_EQUALS),
|
|
CLAUSE('!', '=', IF_NOTEQUALS),
|
|
CLAUSE('>', '=', IF_GREATERTHAN_EQ),
|
|
CLAUSE('<', '=', IF_LESSTHAN_EQ),
|
|
/*All Single value items @ end */
|
|
CLAUSE('>', 0, IF_GREATERTHAN),
|
|
CLAUSE('<', 0, IF_LESSTHAN),
|
|
CLAUSE('=', 0, IF_EQUALS),
|
|
};
|
|
|
|
int val1 = OPS2VAL(op[0], 0);
|
|
int val2;
|
|
if (val1 != 0) /* Empty string ?*/
|
|
{
|
|
val2 = OPS2VAL(op[0], op[1]);
|
|
for (unsigned int i = 0; i < ARRAYLEN(get_clause_match); i++)
|
|
{
|
|
const struct clause_symbol *sym = &get_clause_match[i];
|
|
if(sym->value == val1 || sym->value == val2)
|
|
{
|
|
lif->op = sym->symbol;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
memcpy(&lif->operand, get_param(element, 2), sizeof(lif->operand));
|
|
if (element->params_count > 3)
|
|
lif->num_options = get_param(element, 3)->data.number;
|
|
else
|
|
lif->num_options = TOKEN_VALUE_ONLY;
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int parse_timeout_tag(struct skin_element *element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data)
|
|
{
|
|
(void)wps_data;
|
|
int val = 0;
|
|
if (element->params_count == 0)
|
|
{
|
|
switch (token->type)
|
|
{
|
|
case SKIN_TOKEN_SUBLINE_TIMEOUT:
|
|
return -1;
|
|
case SKIN_TOKEN_BUTTON_VOLUME:
|
|
case SKIN_TOKEN_TRACK_STARTING:
|
|
case SKIN_TOKEN_TRACK_ENDING:
|
|
val = 10;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
val = get_param(element, 0)->data.number;
|
|
token->value.i = val * TIMEOUT_UNIT;
|
|
return 0;
|
|
}
|
|
|
|
static int parse_substring_tag(struct skin_element* element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data)
|
|
{
|
|
(void)wps_data;
|
|
struct substring *ss = skin_buffer_alloc(sizeof(*ss));
|
|
if (!ss)
|
|
return 1;
|
|
ss->start = get_param(element, 0)->data.number;
|
|
if (get_param(element, 1)->type == DEFAULT)
|
|
ss->length = -1;
|
|
else
|
|
ss->length = get_param(element, 1)->data.number;
|
|
ss->token = get_param_code(element, 2)->data;
|
|
if (element->params_count > 3)
|
|
ss->expect_number = !strcmp(get_param_text(element, 3), "number");
|
|
else
|
|
ss->expect_number = false;
|
|
token->value.data = PTRTOSKINOFFSET(skin_buffer, ss);
|
|
return 0;
|
|
}
|
|
|
|
static int parse_progressbar_tag(struct skin_element* element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data)
|
|
{
|
|
struct progressbar *pb;
|
|
struct viewport *vp = &curr_vp->vp;
|
|
struct skin_tag_parameter *param = get_param(element, 0);
|
|
int curr_param = 0;
|
|
char *image_filename = NULL;
|
|
#ifdef HAVE_TOUCHSCREEN
|
|
bool suppress_touchregion = false;
|
|
#endif
|
|
|
|
if (element->params_count == 0 &&
|
|
element->tag->type != SKIN_TOKEN_PROGRESSBAR)
|
|
return 0; /* nothing to do */
|
|
pb = skin_buffer_alloc(sizeof(*pb));
|
|
|
|
token->value.data = PTRTOSKINOFFSET(skin_buffer, pb);
|
|
|
|
if (!pb)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
pb->vp = PTRTOSKINOFFSET(skin_buffer, vp);
|
|
pb->follow_lang_direction = follow_lang_direction > 0;
|
|
pb->nofill = false;
|
|
pb->noborder = false;
|
|
pb->nobar = false;
|
|
pb->image = PTRTOSKINOFFSET(skin_buffer, NULL);
|
|
pb->slider = PTRTOSKINOFFSET(skin_buffer, NULL);
|
|
pb->backdrop = PTRTOSKINOFFSET(skin_buffer, NULL);
|
|
pb->setting_id = -1;
|
|
pb->invert_fill_direction = false;
|
|
pb->horizontal = true;
|
|
|
|
if (element->params_count == 0)
|
|
{
|
|
pb->x = 0;
|
|
pb->width = vp->width;
|
|
pb->height = SYSFONT_HEIGHT-2;
|
|
pb->y = -1; /* Will be computed during the rendering */
|
|
pb->type = element->tag->type;
|
|
return 0;
|
|
}
|
|
|
|
/* (x, y, width, height, ...) */
|
|
if (!isdefault(param))
|
|
{
|
|
if (param->type == PERCENT)
|
|
{
|
|
pb->x = param->data.number * vp->width / 1000;
|
|
}
|
|
else
|
|
pb->x = param->data.number;
|
|
if (pb->x < 0 || pb->x >= vp->width)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
}
|
|
else
|
|
pb->x = 0;
|
|
param++;
|
|
|
|
if (!isdefault(param))
|
|
{
|
|
if (param->type == PERCENT)
|
|
{
|
|
pb->y = param->data.number * vp->height / 1000;
|
|
}
|
|
else
|
|
pb->y = param->data.number;
|
|
if (pb->y < 0 || pb->y >= vp->height)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
}
|
|
else
|
|
pb->y = -1; /* computed at rendering */
|
|
param++;
|
|
|
|
if (!isdefault(param))
|
|
{
|
|
if (param->type == PERCENT)
|
|
{
|
|
pb->width = param->data.number * vp->width / 1000;
|
|
}
|
|
else
|
|
pb->width = param->data.number;
|
|
if (pb->width <= 0 || (pb->x + pb->width) > vp->width)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
}
|
|
else
|
|
pb->width = vp->width - pb->x;
|
|
param++;
|
|
|
|
if (!isdefault(param))
|
|
{
|
|
int max;
|
|
if (param->type == PERCENT)
|
|
{
|
|
pb->height = param->data.number * vp->height / 1000;
|
|
}
|
|
else
|
|
pb->height = param->data.number;
|
|
/* include y in check only if it was non-default */
|
|
max = (pb->y > 0) ? pb->y + pb->height : pb->height;
|
|
if (pb->height <= 0 || max > vp->height)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
}
|
|
else
|
|
{
|
|
if (vp->font > FONT_UI)
|
|
pb->height = -1; /* calculate at display time */
|
|
else
|
|
{
|
|
#ifndef __PCTOOL__
|
|
pb->height = font_get(vp->font)->height;
|
|
#else
|
|
pb->height = 8;
|
|
#endif
|
|
}
|
|
}
|
|
/* optional params, first is the image filename if it isnt recognised as a keyword */
|
|
|
|
curr_param = 4;
|
|
if (isdefault(get_param(element, curr_param)))
|
|
{
|
|
param++;
|
|
curr_param++;
|
|
}
|
|
|
|
pb->horizontal = pb->width > pb->height;
|
|
|
|
enum
|
|
{
|
|
eINVERT = 0, eNOFILL, eNOBORDER, eNOBAR, eSLIDER, eIMAGE,
|
|
eBACKDROP, eVERTICAL, eHORIZONTAL, eNOTOUCH, eSETTING,
|
|
e_PB_TAG_COUNT
|
|
};
|
|
|
|
static const char *pb_options[e_PB_TAG_COUNT + 1] = {[eINVERT] = "invert",
|
|
[eNOFILL] = "nofill", [eNOBORDER] = "noborder", [eNOBAR] = "nobar",
|
|
[eSLIDER] = "slider", [eIMAGE] = "image", [eBACKDROP] = "backdrop",
|
|
[eVERTICAL] = "vertical", [eHORIZONTAL] = "horizontal",
|
|
[eNOTOUCH] = "notouch", [eSETTING] = "setting", [e_PB_TAG_COUNT] = NULL};
|
|
int pb_op;
|
|
|
|
while (curr_param < element->params_count)
|
|
{
|
|
char* text;
|
|
param++;
|
|
text = SKINOFFSETTOPTR(skin_buffer, param->data.text);
|
|
|
|
pb_op = string_option(text, pb_options, false);
|
|
if (pb_op == eINVERT)
|
|
pb->invert_fill_direction = true;
|
|
else if (pb_op == eNOFILL)
|
|
pb->nofill = true;
|
|
else if (pb_op == eNOBORDER)
|
|
pb->noborder = true;
|
|
else if (pb_op == eNOBAR)
|
|
pb->nobar = true;
|
|
else if (pb_op == eSLIDER)
|
|
{
|
|
if (curr_param+1 < element->params_count)
|
|
{
|
|
curr_param++;
|
|
param++;
|
|
text = SKINOFFSETTOPTR(skin_buffer, param->data.text);
|
|
pb->slider = PTRTOSKINOFFSET(skin_buffer,
|
|
skin_find_item(text, SKIN_FIND_IMAGE, wps_data));
|
|
}
|
|
else /* option needs the next param */
|
|
return -1;
|
|
}
|
|
else if (pb_op == eIMAGE)
|
|
{
|
|
if (curr_param+1 < element->params_count)
|
|
{
|
|
curr_param++;
|
|
param++;
|
|
image_filename = SKINOFFSETTOPTR(skin_buffer, param->data.text);
|
|
}
|
|
else /* option needs the next param */
|
|
return -1;
|
|
}
|
|
else if (pb_op == eBACKDROP)
|
|
{
|
|
if (curr_param+1 < element->params_count)
|
|
{
|
|
curr_param++;
|
|
param++;
|
|
text = SKINOFFSETTOPTR(skin_buffer, param->data.text);
|
|
pb->backdrop = PTRTOSKINOFFSET(skin_buffer,
|
|
skin_find_item(text, SKIN_FIND_IMAGE, wps_data));
|
|
|
|
}
|
|
else /* option needs the next param */
|
|
return -1;
|
|
}
|
|
else if (pb_op == eVERTICAL)
|
|
{
|
|
pb->horizontal = false;
|
|
if (isdefault(get_param(element, 3)))
|
|
pb->height = vp->height - pb->y;
|
|
}
|
|
else if (pb_op == eHORIZONTAL)
|
|
pb->horizontal = true;
|
|
#ifdef HAVE_TOUCHSCREEN
|
|
else if (pb_op == eNOTOUCH)
|
|
suppress_touchregion = true;
|
|
#endif
|
|
else if (token->type == SKIN_TOKEN_SETTING && pb_op == eSETTING)
|
|
{
|
|
if (curr_param+1 < element->params_count)
|
|
{
|
|
curr_param++;
|
|
param++;
|
|
text = SKINOFFSETTOPTR(skin_buffer, param->data.text);
|
|
#ifndef __PCTOOL__
|
|
if (find_setting_by_cfgname(text, &pb->setting_id) == NULL)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
#endif
|
|
}
|
|
}
|
|
else if (curr_param == 4)
|
|
image_filename = text;
|
|
|
|
curr_param++;
|
|
}
|
|
|
|
if (image_filename)
|
|
{
|
|
/* noborder is incompatible together with image. There is no border
|
|
* anyway. */
|
|
if (pb->noborder)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
|
|
pb->image = PTRTOSKINOFFSET(skin_buffer,
|
|
skin_find_item(image_filename, SKIN_FIND_IMAGE, wps_data));
|
|
if (!SKINOFFSETTOPTR(skin_buffer, pb->image)) /* load later */
|
|
{
|
|
struct gui_img *img = skin_buffer_alloc(sizeof(*img));
|
|
if (!img)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
/* save a pointer to the filename */
|
|
img->bm.data = (char*)image_filename;
|
|
img->label = PTRTOSKINOFFSET(skin_buffer, image_filename);
|
|
img->x = 0;
|
|
img->y = 0;
|
|
img->num_subimages = 1;
|
|
img->display = -1;
|
|
img->using_preloaded_icons = false;
|
|
img->buflib_handle = -1;
|
|
img->loaded = false;
|
|
struct skin_token_list *item = new_skin_token_list_item(NULL, img);
|
|
if (!item)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
add_to_ll_chain(&wps_data->images, item);
|
|
pb->image = PTRTOSKINOFFSET(skin_buffer, img);
|
|
}
|
|
}
|
|
|
|
if (token->type == SKIN_TOKEN_VOLUME)
|
|
token->type = SKIN_TOKEN_VOLUMEBAR;
|
|
else if (token->type == SKIN_TOKEN_BATTERY_PERCENT)
|
|
token->type = SKIN_TOKEN_BATTERY_PERCENTBAR;
|
|
else if (token->type == SKIN_TOKEN_TUNER_RSSI)
|
|
token->type = SKIN_TOKEN_TUNER_RSSI_BAR;
|
|
else if (token->type == SKIN_TOKEN_PEAKMETER_LEFT)
|
|
token->type = SKIN_TOKEN_PEAKMETER_LEFTBAR;
|
|
else if (token->type == SKIN_TOKEN_PEAKMETER_RIGHT)
|
|
token->type = SKIN_TOKEN_PEAKMETER_RIGHTBAR;
|
|
else if (token->type == SKIN_TOKEN_LIST_NEEDS_SCROLLBAR)
|
|
token->type = SKIN_TOKEN_LIST_SCROLLBAR;
|
|
else if (token->type == SKIN_TOKEN_SETTING)
|
|
token->type = SKIN_TOKEN_SETTINGBAR;
|
|
pb->type = token->type;
|
|
|
|
#ifdef HAVE_TOUCHSCREEN
|
|
if (!suppress_touchregion &&
|
|
(token->type == SKIN_TOKEN_VOLUMEBAR ||
|
|
token->type == SKIN_TOKEN_PROGRESSBAR ||
|
|
token->type == SKIN_TOKEN_SETTINGBAR))
|
|
{
|
|
struct touchregion *region = skin_buffer_alloc(sizeof(*region));
|
|
struct skin_token_list *item;
|
|
|
|
if (!region)
|
|
return 0;
|
|
|
|
if (token->type == SKIN_TOKEN_VOLUMEBAR)
|
|
region->action = ACTION_TOUCH_VOLUME;
|
|
else if (token->type == SKIN_TOKEN_SETTINGBAR)
|
|
region->action = ACTION_TOUCH_SETTING;
|
|
else
|
|
region->action = ACTION_TOUCH_SCROLLBAR;
|
|
|
|
/* try to add some extra space on either end to make pressing the
|
|
* full bar easier. ~5% on either side
|
|
*/
|
|
region->wpad = pb->width * 5 / 100;
|
|
if (region->wpad > 10)
|
|
region->wpad = 10;
|
|
region->hpad = pb->height * 5 / 100;
|
|
if (region->hpad > 10)
|
|
region->hpad = 10;
|
|
|
|
region->x = pb->x;
|
|
if (region->x < 0)
|
|
region->x = 0;
|
|
region->width = pb->width;
|
|
if (region->x + region->width > curr_vp->vp.x + curr_vp->vp.width)
|
|
region->width = curr_vp->vp.x + curr_vp->vp.width - region->x;
|
|
|
|
region->y = pb->y;
|
|
if (region->y < 0)
|
|
region->y = 0;
|
|
region->height = pb->height;
|
|
if (region->y + region->height > curr_vp->vp.y + curr_vp->vp.height)
|
|
region->height = curr_vp->vp.y + curr_vp->vp.height - region->y;
|
|
|
|
region->wvp = PTRTOSKINOFFSET(skin_buffer, curr_vp);
|
|
region->reverse_bar = false;
|
|
region->allow_while_locked = false;
|
|
region->press_length = PRESS;
|
|
region->last_press = -1;
|
|
region->armed = false;
|
|
region->bar = PTRTOSKINOFFSET(skin_buffer, pb);
|
|
|
|
item = new_skin_token_list_item(NULL, region);
|
|
if (!item)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
add_to_ll_chain(&wps_data->touchregions, item);
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
#ifdef HAVE_ALBUMART
|
|
static int parse_albumart_load(struct skin_element* element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data)
|
|
{
|
|
struct dim dimensions;
|
|
int albumart_slot;
|
|
bool swap_for_rtl = lang_is_rtl() && follow_lang_direction;
|
|
struct skin_albumart *aa = skin_buffer_alloc(sizeof(*aa));
|
|
(void)token; /* silence warning */
|
|
if (!aa)
|
|
return -1;
|
|
|
|
/* reset albumart info in wps */
|
|
aa->width = -1;
|
|
aa->height = -1;
|
|
aa->xalign = WPS_ALBUMART_ALIGN_CENTER; /* default */
|
|
aa->yalign = WPS_ALBUMART_ALIGN_CENTER; /* default */
|
|
|
|
struct skin_tag_parameter *param0 = get_param(element, 0);
|
|
struct skin_tag_parameter *param1 = get_param(element, 1);
|
|
struct skin_tag_parameter *param2 = get_param(element, 2);
|
|
struct skin_tag_parameter *param3 = get_param(element, 3);
|
|
|
|
aa->x = param0->data.number;
|
|
aa->y = param1->data.number;
|
|
aa->width = param2->data.number;
|
|
aa->height = param3->data.number;
|
|
|
|
if (!isdefault(param0) && param0->type == PERCENT)
|
|
aa->x = param0->data.number * curr_vp->vp.width / 1000;
|
|
|
|
if (!isdefault(param1) && param1->type == PERCENT)
|
|
aa->y = param1->data.number * curr_vp->vp.height / 1000;
|
|
|
|
if (!isdefault(param2) && param2->type == PERCENT)
|
|
aa->width = param2->data.number * curr_vp->vp.width / 1000;
|
|
|
|
if (!isdefault(param3) && param3->type == PERCENT)
|
|
aa->height = param3->data.number * curr_vp->vp.height / 1000;
|
|
|
|
aa->vp = PTRTOSKINOFFSET(skin_buffer, &curr_vp->vp);
|
|
aa->draw_handle = -1;
|
|
|
|
/* if we got here, we parsed everything ok .. ! */
|
|
if (aa->width < 0)
|
|
aa->width = 0;
|
|
|
|
if (aa->height < 0)
|
|
aa->height = 0;
|
|
|
|
if (swap_for_rtl)
|
|
aa->x = (curr_vp->vp.width - aa->width - aa->x);
|
|
|
|
aa->state = WPS_ALBUMART_LOAD;
|
|
wps_data->albumart = PTRTOSKINOFFSET(skin_buffer, aa);
|
|
|
|
dimensions.width = aa->width;
|
|
dimensions.height = aa->height;
|
|
|
|
albumart_slot = playback_claim_aa_slot(&dimensions);
|
|
|
|
if (0 <= albumart_slot)
|
|
wps_data->playback_aa_slot = albumart_slot;
|
|
|
|
if (element->params_count > 4 && !isdefault(get_param(element, 4)))
|
|
{
|
|
switch (*get_param_text(element, 4))
|
|
{
|
|
case 'l':
|
|
case 'L':
|
|
if (swap_for_rtl)
|
|
aa->xalign = WPS_ALBUMART_ALIGN_RIGHT;
|
|
else
|
|
aa->xalign = WPS_ALBUMART_ALIGN_LEFT;
|
|
break;
|
|
case 'c':
|
|
case 'C':
|
|
aa->xalign = WPS_ALBUMART_ALIGN_CENTER;
|
|
break;
|
|
case 'r':
|
|
case 'R':
|
|
if (swap_for_rtl)
|
|
aa->xalign = WPS_ALBUMART_ALIGN_LEFT;
|
|
else
|
|
aa->xalign = WPS_ALBUMART_ALIGN_RIGHT;
|
|
break;
|
|
}
|
|
}
|
|
if (element->params_count > 5 && !isdefault(get_param(element, 5)))
|
|
{
|
|
switch (*get_param_text(element, 5))
|
|
{
|
|
case 't':
|
|
case 'T':
|
|
aa->yalign = WPS_ALBUMART_ALIGN_TOP;
|
|
break;
|
|
case 'c':
|
|
case 'C':
|
|
aa->yalign = WPS_ALBUMART_ALIGN_CENTER;
|
|
break;
|
|
case 'b':
|
|
case 'B':
|
|
aa->yalign = WPS_ALBUMART_ALIGN_BOTTOM;
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#endif /* HAVE_ALBUMART */
|
|
#ifdef HAVE_SKIN_VARIABLES
|
|
static struct skin_var* find_or_add_var(const char* label,
|
|
struct wps_data *data)
|
|
{
|
|
struct skin_var* ret = skin_find_item(label, SKIN_VARIABLE, data);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = skin_buffer_alloc(sizeof(*ret));
|
|
if (!ret)
|
|
return ret;
|
|
ret->label = PTRTOSKINOFFSET(skin_buffer, label);
|
|
ret->value = 1;
|
|
ret->last_changed = 0xffff;
|
|
struct skin_token_list *item = new_skin_token_list_item(NULL, ret);
|
|
if (!item)
|
|
return NULL;
|
|
add_to_ll_chain(&data->skinvars, item);
|
|
return ret;
|
|
}
|
|
static int parse_skinvar( struct skin_element *element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data)
|
|
{
|
|
const char* label = get_param_text(element, 0);
|
|
struct skin_var* var = find_or_add_var(label, wps_data);
|
|
if (!var)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
switch (token->type)
|
|
{
|
|
case SKIN_TOKEN_VAR_GETVAL:
|
|
token->value.data = PTRTOSKINOFFSET(skin_buffer, var);
|
|
return 0;
|
|
case SKIN_TOKEN_VAR_SET:
|
|
{
|
|
static const char * const sv_options[] = {"touch", "set", "inc", "dec", NULL};
|
|
|
|
struct skin_var_changer *data = skin_buffer_alloc(sizeof(*data));
|
|
if (!data)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
data->var = PTRTOSKINOFFSET(skin_buffer, var);
|
|
char *text_param1 = get_param_text(element, 1);
|
|
int sv_op = string_option(text_param1, sv_options, false);
|
|
|
|
if (!isdefault(get_param(element, 2)))
|
|
data->newval = get_param(element, 2)->data.number;
|
|
else if (sv_op != 0) /*!touch*/
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
data->max = 0;
|
|
if (sv_op == 1) /*set*/
|
|
data->direct = true;
|
|
else if (sv_op == 2) /*inc*/
|
|
{
|
|
data->direct = false;
|
|
}
|
|
else if (sv_op == 3) /*dec*/
|
|
{
|
|
data->direct = false;
|
|
data->newval *= -1;
|
|
}
|
|
else if (sv_op == 0) /*touch*/
|
|
{
|
|
data->direct = false;
|
|
data->newval = 0;
|
|
}
|
|
if (element->params_count > 3)
|
|
data->max = get_param(element, 3)->data.number;
|
|
token->value.data = PTRTOSKINOFFSET(skin_buffer, data);
|
|
}
|
|
return 0;
|
|
case SKIN_TOKEN_VAR_TIMEOUT:
|
|
{
|
|
struct skin_var_lastchange *data = skin_buffer_alloc(sizeof(*data));
|
|
if (!data)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
data->var = PTRTOSKINOFFSET(skin_buffer, var);
|
|
data->timeout = 10;
|
|
if (element->params_count > 1)
|
|
data->timeout = get_param(element, 1)->data.number;
|
|
data->timeout *= TIMEOUT_UNIT;
|
|
token->value.data = PTRTOSKINOFFSET(skin_buffer, data);
|
|
}
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
#endif /* HAVE_SKIN_VARIABLES */
|
|
#ifdef HAVE_TOUCHSCREEN
|
|
static int parse_lasttouch(struct skin_element *element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data)
|
|
{
|
|
struct touchregion_lastpress *data = skin_buffer_alloc(sizeof(*data));
|
|
int i;
|
|
struct touchregion *region = NULL;
|
|
if (!data)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
|
|
data->timeout = 10;
|
|
|
|
for (i=0; i<element->params_count; i++)
|
|
{
|
|
struct skin_tag_parameter *param = get_param(element, i);
|
|
if (param->type == STRING)
|
|
region = skin_find_item(get_param_text(element, i),
|
|
SKIN_FIND_TOUCHREGION, wps_data);
|
|
else if (param->type == INTEGER || param->type == DECIMAL)
|
|
data->timeout = param->data.number;
|
|
}
|
|
|
|
data->region = PTRTOSKINOFFSET(skin_buffer, region);
|
|
data->timeout *= TIMEOUT_UNIT;
|
|
token->value.data = PTRTOSKINOFFSET(skin_buffer, data);
|
|
return 0;
|
|
}
|
|
|
|
struct touchaction {const char* s; int action;};
|
|
static const struct touchaction touchactions[] = {
|
|
/* generic actions, convert to screen actions on use */
|
|
{"none", ACTION_TOUCHSCREEN_IGNORE},{"lock", ACTION_TOUCH_SOFTLOCK },
|
|
{"prev", ACTION_STD_PREV }, {"next", ACTION_STD_NEXT },
|
|
{"hotkey", ACTION_STD_HOTKEY}, {"select", ACTION_STD_OK },
|
|
{"menu", ACTION_STD_MENU }, {"cancel", ACTION_STD_CANCEL },
|
|
{"contextmenu", ACTION_STD_CONTEXT},{"quickscreen", ACTION_STD_QUICKSCREEN },
|
|
|
|
/* list/tree actions */
|
|
{ "resumeplayback", ACTION_TREE_WPS}, /* returns to previous music, WPS/FM */
|
|
/* not really WPS specific, but no equivilant ACTION_STD_* */
|
|
{"voldown", ACTION_WPS_VOLDOWN}, {"volup", ACTION_WPS_VOLUP},
|
|
{"mute", ACTION_TOUCH_MUTE },
|
|
|
|
/* generic settings changers */
|
|
{"setting_inc", ACTION_SETTINGS_INC}, {"setting_dec", ACTION_SETTINGS_DEC},
|
|
{"setting_set", ACTION_SETTINGS_SET},
|
|
|
|
/* WPS specific actions */
|
|
{"rwd", ACTION_WPS_SEEKBACK }, {"ffwd", ACTION_WPS_SEEKFWD },
|
|
{"wps_prev", ACTION_WPS_SKIPPREV }, {"wps_next", ACTION_WPS_SKIPNEXT },
|
|
{"browse", ACTION_WPS_BROWSE },
|
|
{"play", ACTION_WPS_PLAY }, {"stop", ACTION_WPS_STOP },
|
|
{"shuffle", ACTION_TOUCH_SHUFFLE }, {"repmode", ACTION_TOUCH_REPMODE },
|
|
{"pitch", ACTION_WPS_PITCHSCREEN}, {"trackinfo", ACTION_WPS_ID3SCREEN },
|
|
{"playlist", ACTION_WPS_VIEW_PLAYLIST },
|
|
{"listbookmarks", ACTION_WPS_LIST_BOOKMARKS },
|
|
{"createbookmark", ACTION_WPS_CREATE_BOOKMARK },
|
|
|
|
#if CONFIG_TUNER
|
|
/* FM screen actions */
|
|
/* Also allow browse, play, stop from WPS codes */
|
|
{"mode", ACTION_FM_MODE }, {"record", ACTION_FM_RECORD },
|
|
{"presets", ACTION_FM_PRESET},
|
|
#endif
|
|
};
|
|
|
|
static int touchregion_setup_setting(struct skin_element *element, int param_no,
|
|
struct touchregion *region)
|
|
{
|
|
#ifndef __PCTOOL__
|
|
int p = param_no;
|
|
char *name = get_param_text(element, p++);
|
|
int j;
|
|
|
|
region->setting_data.setting = find_setting_by_cfgname(name, &j);
|
|
if (region->setting_data.setting == NULL)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
|
|
if (region->action == ACTION_SETTINGS_SET)
|
|
{
|
|
char* text;
|
|
int temp;
|
|
struct touchsetting *setting =
|
|
®ion->setting_data;
|
|
if (element->params_count < p+1)
|
|
return -1;
|
|
|
|
text = get_param_text(element, p++);
|
|
switch (settings[j].flags&F_T_MASK)
|
|
{
|
|
case F_T_CUSTOM:
|
|
setting->value.text = PTRTOSKINOFFSET(skin_buffer, text);
|
|
break;
|
|
case F_T_INT:
|
|
case F_T_UINT:
|
|
if (settings[j].cfg_vals == NULL)
|
|
{
|
|
setting->value.number = atoi(text);
|
|
}
|
|
else if (cfg_string_to_int(j, &temp, text))
|
|
{
|
|
if (settings[j].flags&F_TABLE_SETTING)
|
|
setting->value.number =
|
|
settings[j].table_setting->values[temp];
|
|
else
|
|
setting->value.number = temp;
|
|
}
|
|
else
|
|
return -1;
|
|
break;
|
|
case F_T_BOOL:
|
|
if (cfg_string_to_int(j, &temp, text))
|
|
{
|
|
setting->value.number = temp;
|
|
}
|
|
else
|
|
return -1;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
return p-param_no;
|
|
#endif /* __PCTOOL__ */
|
|
return 0;
|
|
}
|
|
|
|
static int parse_touchregion(struct skin_element *element,
|
|
struct wps_token *token,
|
|
struct wps_data *wps_data)
|
|
{
|
|
(void)token;
|
|
unsigned i, imax;
|
|
int p;
|
|
struct touchregion *region = NULL;
|
|
const char *action;
|
|
const char pb_string[] = "progressbar";
|
|
const char vol_string[] = "volume";
|
|
|
|
/* format: %T([label,], x,y,width,height,action[, ...])
|
|
* if action starts with & the area must be held to happen
|
|
*/
|
|
|
|
|
|
region = skin_buffer_alloc(sizeof(*region));
|
|
if (!region)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
|
|
/* should probably do some bounds checking here with the viewport... but later */
|
|
region->action = ACTION_NONE;
|
|
|
|
/* padding is only for bars, user defined regions have no need of it */
|
|
region->wpad = 0;
|
|
region->hpad = 0;
|
|
|
|
if (get_param(element, 0)->type == STRING)
|
|
{
|
|
region->label = PTRTOSKINOFFSET(skin_buffer, get_param_text(element, 0));
|
|
p = 1;
|
|
if (element->params_count < 6)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
}
|
|
else
|
|
{
|
|
region->label = PTRTOSKINOFFSET(skin_buffer, NULL);
|
|
p = 0;
|
|
}
|
|
/*x*/
|
|
struct skin_tag_parameter *param = get_param(element, p);
|
|
region->x = 0;
|
|
if (!isdefault(param))
|
|
{
|
|
if (param->type == INTEGER)
|
|
region->x = param->data.number;
|
|
else if (param->type == PERCENT)
|
|
region->x = param->data.number * curr_vp->vp.width / 1000;
|
|
}
|
|
/*y*/
|
|
param = get_param(element, ++p);
|
|
region->y = 0;
|
|
if (!isdefault(param))
|
|
{
|
|
if (param->type == INTEGER)
|
|
region->y = param->data.number;
|
|
else if (param->type == PERCENT)
|
|
region->y =param->data.number * curr_vp->vp.width / 1000;
|
|
}
|
|
/*width*/
|
|
param = get_param(element, ++p);
|
|
region->width = curr_vp->vp.width;
|
|
if (!isdefault(param))
|
|
{
|
|
if (param->type == INTEGER)
|
|
region->width =param->data.number;
|
|
else if (param->type == PERCENT)
|
|
region->width = curr_vp->vp.width * param->data.number / 1000;
|
|
}
|
|
/*height*/
|
|
param = get_param(element, ++p);
|
|
region->height = curr_vp->vp.height;
|
|
if (!isdefault(param))
|
|
{
|
|
if (param->type == INTEGER)
|
|
region->height =param->data.number;
|
|
else if (param->type == PERCENT)
|
|
region->height = curr_vp->vp.height * param->data.number / 1000;
|
|
}
|
|
p++;
|
|
|
|
region->wvp = PTRTOSKINOFFSET(skin_buffer, curr_vp);
|
|
region->armed = false;
|
|
region->reverse_bar = false;
|
|
region->value = 0;
|
|
region->last_press = -1;
|
|
region->press_length = PRESS;
|
|
region->allow_while_locked = false;
|
|
region->bar = PTRTOSKINOFFSET(skin_buffer, NULL);
|
|
action = get_param_text(element, p++);
|
|
|
|
/* figure out the action */
|
|
if(!strcmp(pb_string, action))
|
|
region->action = ACTION_TOUCH_SCROLLBAR;
|
|
else if(!strcmp(vol_string, action))
|
|
region->action = ACTION_TOUCH_VOLUME;
|
|
else
|
|
{
|
|
imax = ARRAYLEN(touchactions);
|
|
for (i = 0; i < imax; i++)
|
|
{
|
|
/* try to match with one of our touchregion screens */
|
|
if (!strcmp(touchactions[i].s, action))
|
|
{
|
|
region->action = touchactions[i].action;
|
|
if (region->action == ACTION_SETTINGS_INC ||
|
|
region->action == ACTION_SETTINGS_DEC ||
|
|
region->action == ACTION_SETTINGS_SET)
|
|
{
|
|
int val;
|
|
if (element->params_count < p+1)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
val = touchregion_setup_setting(element, p, region);
|
|
if (val < 0)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
p += val;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (region->action == ACTION_NONE)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
}
|
|
static const char * const pm_options[] = {"allow_while_locked", "reverse_bar",
|
|
"repeat_press", "long_press", NULL};
|
|
int pm_op;
|
|
|
|
while (p < element->params_count)
|
|
{
|
|
char* param = get_param_text(element, p++);
|
|
pm_op = string_option(param, pm_options, false);
|
|
if (pm_op == 0)
|
|
region->allow_while_locked = true;
|
|
else if (pm_op == 1)
|
|
region->reverse_bar = true;
|
|
else if (pm_op == 2)
|
|
region->press_length = REPEAT;
|
|
else if (pm_op == 3)
|
|
region->press_length = LONG_PRESS;
|
|
}
|
|
struct skin_token_list *item = new_skin_token_list_item(NULL, region);
|
|
if (!item)
|
|
return WPS_ERROR_INVALID_PARAM;
|
|
add_to_ll_chain(&wps_data->touchregions, item);
|
|
|
|
if (region->action == ACTION_TOUCH_MUTE)
|
|
{
|
|
region->value = global_settings.volume;
|
|
}
|
|
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static bool check_feature_tag(const int type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case SKIN_TOKEN_RTC_PRESENT:
|
|
#if CONFIG_RTC
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
case SKIN_TOKEN_HAVE_RECORDING:
|
|
#ifdef HAVE_RECORDING
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
case SKIN_TOKEN_HAVE_TUNER:
|
|
#if CONFIG_TUNER
|
|
if (radio_hardware_present())
|
|
return true;
|
|
#endif
|
|
return false;
|
|
case SKIN_TOKEN_HAVE_TOUCH:
|
|
#ifdef HAVE_TOUCHSCREEN
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
|
|
#if CONFIG_TUNER
|
|
case SKIN_TOKEN_HAVE_RDS:
|
|
#ifdef HAVE_RDS_CAP
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif /* HAVE_RDS_CAP */
|
|
#endif /* CONFIG_TUNER */
|
|
default: /* not a tag we care about, just don't skip */
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/* This is used to free any buflib allocations before the rest of
|
|
* wps_data is reset.
|
|
* The call to this in settings_apply_skins() is the last chance to do
|
|
* any core_free()'s before wps_data is trashed and those handles lost
|
|
*/
|
|
void skin_data_free_buflib_allocs(struct wps_data *wps_data)
|
|
{
|
|
if (wps_data->wps_loaded)
|
|
skin_buffer = get_skin_buffer(wps_data);
|
|
|
|
#ifndef __PCTOOL__
|
|
if (!skin_buffer)
|
|
goto abort;
|
|
|
|
struct skin_token_list *list = SKINOFFSETTOPTR(skin_buffer, wps_data->images);
|
|
int *font_ids = SKINOFFSETTOPTR(skin_buffer, wps_data->font_ids);
|
|
while (list)
|
|
{
|
|
struct wps_token *token = SKINOFFSETTOPTR(skin_buffer, list->token);
|
|
struct gui_img *img = NULL;
|
|
if (token)
|
|
img = (struct gui_img*)SKINOFFSETTOPTR(skin_buffer, token->value.data);
|
|
if (img && img->buflib_handle > 0)
|
|
{
|
|
struct skin_token_list *imglist = SKINOFFSETTOPTR(skin_buffer, list->next);
|
|
|
|
core_free(img->buflib_handle);
|
|
while (imglist)
|
|
{
|
|
struct wps_token *freetoken = SKINOFFSETTOPTR(skin_buffer, imglist->token);
|
|
struct gui_img *freeimg = NULL;
|
|
if (freetoken)
|
|
freeimg = (struct gui_img*)SKINOFFSETTOPTR(skin_buffer, freetoken->value.data);
|
|
if (freeimg && img->buflib_handle == freeimg->buflib_handle)
|
|
freeimg->buflib_handle = -1;
|
|
imglist = SKINOFFSETTOPTR(skin_buffer, imglist->next);
|
|
}
|
|
}
|
|
list = SKINOFFSETTOPTR(skin_buffer, list->next);
|
|
}
|
|
if (font_ids != NULL)
|
|
{
|
|
while (wps_data->font_count > 0)
|
|
font_unload(font_ids[--wps_data->font_count]);
|
|
}
|
|
|
|
abort:
|
|
wps_data->font_ids = PTRTOSKINOFFSET(skin_buffer, NULL); /* Safe if skin_buffer is NULL */
|
|
wps_data->images = PTRTOSKINOFFSET(skin_buffer, NULL);
|
|
wps_data->buflib_handle = core_free(wps_data->buflib_handle);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* initial setup of wps_data; does reset everything
|
|
* except fields which need to survive, i.e.
|
|
* Also called if the load fails
|
|
**/
|
|
static void skin_data_reset(struct wps_data *wps_data)
|
|
{
|
|
skin_data_free_buflib_allocs(wps_data);
|
|
wps_data->images = INVALID_OFFSET;
|
|
wps_data->tree = INVALID_OFFSET;
|
|
#ifdef HAVE_BACKDROP_IMAGE
|
|
if (wps_data->backdrop_id >= 0)
|
|
skin_backdrop_unload(wps_data->backdrop_id);
|
|
backdrop_filename = NULL;
|
|
#endif
|
|
#ifdef HAVE_TOUCHSCREEN
|
|
wps_data->touchregions = INVALID_OFFSET;
|
|
#endif
|
|
#ifdef HAVE_SKIN_VARIABLES
|
|
wps_data->skinvars = INVALID_OFFSET;
|
|
#endif
|
|
#ifdef HAVE_ALBUMART
|
|
wps_data->albumart = INVALID_OFFSET;
|
|
if (wps_data->playback_aa_slot >= 0)
|
|
{
|
|
playback_release_aa_slot(wps_data->playback_aa_slot);
|
|
wps_data->playback_aa_slot = -1;
|
|
}
|
|
#endif
|
|
|
|
wps_data->peak_meter_enabled = false;
|
|
wps_data->wps_sb_tag = false;
|
|
wps_data->show_sb_on_wps = false;
|
|
wps_data->wps_loaded = false;
|
|
}
|
|
|
|
#ifndef __PCTOOL__
|
|
static int buflib_move_callback(int handle, void* current, void* new)
|
|
{
|
|
(void)handle;
|
|
(void)current;
|
|
(void)new;
|
|
/* Any active skins may be scrolling - which means using viewports which
|
|
* will be moved after this callback returns. This is a hammer to make that
|
|
* safe. TODO: use a screwdriver instead.
|
|
*/
|
|
FOR_NB_SCREENS(i)
|
|
screens[i].scroll_stop();
|
|
|
|
for (int i = 0; i < SKINNABLE_SCREENS_COUNT; i++)
|
|
skin_request_full_update(i);
|
|
|
|
return BUFLIB_CB_OK;
|
|
}
|
|
static struct buflib_callbacks buflib_ops = {buflib_move_callback, NULL, NULL};
|
|
#endif
|
|
|
|
static int load_skin_bmp(struct wps_data *wps_data, struct bitmap *bitmap, char* bmpdir)
|
|
{
|
|
(void)wps_data; /* only needed for remote targets */
|
|
char img_path[MAX_PATH];
|
|
int fd;
|
|
int handle;
|
|
get_image_filename(bitmap->data, bmpdir,
|
|
img_path, sizeof(img_path));
|
|
|
|
/* load the image */
|
|
int format;
|
|
#ifdef HAVE_REMOTE_LCD
|
|
if (curr_screen == SCREEN_REMOTE)
|
|
format = FORMAT_ANY|FORMAT_REMOTE;
|
|
else
|
|
#endif
|
|
format = FORMAT_ANY|FORMAT_TRANSPARENT;
|
|
|
|
fd = open(img_path, O_RDONLY);
|
|
if (fd < 0)
|
|
{
|
|
DEBUGF("Couldn't open %s\n", img_path);
|
|
return fd;
|
|
}
|
|
#ifndef __PCTOOL__
|
|
int buf_size = read_bmp_fd(fd, bitmap, 0,
|
|
format|FORMAT_RETURN_SIZE, NULL);
|
|
if(buf_size < 0)
|
|
{
|
|
close(fd);
|
|
return buf_size;
|
|
}
|
|
|
|
handle = core_alloc_ex(bitmap->data, buf_size, &buflib_ops);
|
|
if (handle <= 0)
|
|
{
|
|
DEBUGF("Not enough skin buffer: need %zd more.\n",
|
|
buf_size - skin_buffer_freespace());
|
|
close(fd);
|
|
return handle;
|
|
}
|
|
_stats->buflib_handles++;
|
|
_stats->images_size += buf_size;
|
|
lseek(fd, 0, SEEK_SET);
|
|
core_pin(handle);
|
|
bitmap->data = core_get_data(handle);
|
|
int ret = read_bmp_fd(fd, bitmap, buf_size, format, NULL);
|
|
bitmap->data = NULL; /* do this to force a crash later if the
|
|
caller doesnt call core_get_data() */
|
|
core_unpin(handle);
|
|
close(fd);
|
|
if (ret > 0)
|
|
{
|
|
/* free unused alpha channel, if any */
|
|
core_shrink(handle, core_get_data(handle), ret);
|
|
return handle;
|
|
}
|
|
else
|
|
{
|
|
/* Abort if we can't load an image */
|
|
DEBUGF("Couldn't load '%s'\n", img_path);
|
|
core_free(handle);
|
|
return -1;
|
|
}
|
|
#else /* !__PCTOOL__ */
|
|
close(fd);
|
|
return 1;
|
|
#endif
|
|
}
|
|
|
|
static bool load_skin_bitmaps(struct wps_data *wps_data, char *bmpdir)
|
|
{
|
|
struct skin_token_list *list;
|
|
bool retval = true; /* return false if a single image failed to load */
|
|
|
|
/* regular images */
|
|
list = SKINOFFSETTOPTR(skin_buffer, wps_data->images);
|
|
while (list)
|
|
{
|
|
struct wps_token *token = SKINOFFSETTOPTR(skin_buffer, list->token);
|
|
if (!token) goto skip;
|
|
struct gui_img *img = (struct gui_img*)SKINOFFSETTOPTR(skin_buffer, token->value.data);
|
|
|
|
if (img && !img->loaded)
|
|
{
|
|
if (img->using_preloaded_icons)
|
|
{
|
|
img->loaded = true;
|
|
token->type = SKIN_TOKEN_IMAGE_DISPLAY_LISTICON;
|
|
}
|
|
else
|
|
{
|
|
char path[MAX_PATH];
|
|
int handle;
|
|
strcpy(path, img->bm.data);
|
|
handle = load_skin_bmp(wps_data, &img->bm, bmpdir);
|
|
img->buflib_handle = handle;
|
|
img->loaded = img->buflib_handle > 0;
|
|
|
|
if (img->loaded)
|
|
{
|
|
struct skin_token_list *imglist = SKINOFFSETTOPTR(skin_buffer, list->next);
|
|
img->subimage_height = img->bm.height / img->num_subimages;
|
|
while (imglist)
|
|
{
|
|
token = SKINOFFSETTOPTR(skin_buffer, imglist->token);
|
|
if (token) {
|
|
img = (struct gui_img*)SKINOFFSETTOPTR(skin_buffer, token->value.data);
|
|
if (img && !strcmp(path, img->bm.data))
|
|
{
|
|
img->loaded = true;
|
|
img->buflib_handle = handle;
|
|
img->subimage_height = img->bm.height / img->num_subimages;
|
|
}
|
|
}
|
|
imglist = SKINOFFSETTOPTR(skin_buffer, imglist->next);
|
|
}
|
|
}
|
|
else
|
|
retval = false;
|
|
}
|
|
}
|
|
skip:
|
|
list = SKINOFFSETTOPTR(skin_buffer, list->next);
|
|
}
|
|
|
|
#ifdef HAVE_BACKDROP_IMAGE
|
|
wps_data->backdrop_id = skin_backdrop_assign(backdrop_filename, bmpdir, curr_screen);
|
|
#endif /* has backdrop support */
|
|
return retval;
|
|
}
|
|
|
|
static bool skin_load_fonts(struct wps_data *data)
|
|
{
|
|
/* don't spit out after the first failue to aid debugging */
|
|
int id_array[MAXUSERFONTS];
|
|
int font_count = 0;
|
|
bool success = true;
|
|
struct skin_element *vp_list;
|
|
int font_id;
|
|
/* walk though each viewport and assign its font */
|
|
for(vp_list = SKINOFFSETTOPTR(skin_buffer, data->tree);
|
|
vp_list; vp_list = SKINOFFSETTOPTR(skin_buffer, vp_list->next))
|
|
{
|
|
/* first, find the viewports that have a non-sys/ui-font font */
|
|
struct skin_viewport *skin_vp =
|
|
SKINOFFSETTOPTR(skin_buffer, vp_list->data);
|
|
if (!skin_vp) continue;
|
|
struct viewport *vp = &skin_vp->vp;
|
|
|
|
font_id = skin_vp->parsed_fontid;
|
|
if (font_id == 1)
|
|
{ /* the usual case -> built-in fonts */
|
|
vp->font = screens[curr_screen].getuifont();
|
|
continue;
|
|
}
|
|
else if (font_id <= 0)
|
|
{
|
|
vp->font = FONT_SYSFIXED;
|
|
continue;
|
|
}
|
|
|
|
/* now find the corresponding skin_font */
|
|
struct skin_font *font = &skinfonts[font_id-2];
|
|
if (!font->name)
|
|
{
|
|
if (success)
|
|
{
|
|
DEBUGF("font %d not specified\n", font_id);
|
|
}
|
|
success = false;
|
|
continue;
|
|
}
|
|
|
|
/* load the font - will handle loading the same font again if
|
|
* multiple viewports use the same */
|
|
if (font->id < 0)
|
|
{
|
|
char path[MAX_PATH];
|
|
snprintf(path, sizeof path, FONT_DIR "/%s", font->name);
|
|
#ifndef __PCTOOL__
|
|
font->id = font_load_ex(path, 0, skinfonts[font_id-2].glyphs);
|
|
|
|
#else
|
|
font->id = font_load(path);
|
|
#endif
|
|
//printf("[%d] %s -> %d\n",font_id, font->name, font->id);
|
|
id_array[font_count++] = font->id;
|
|
}
|
|
|
|
if (font->id < 0)
|
|
{
|
|
DEBUGF("Unable to load font %d: '%s'\n", font_id, font->name);
|
|
font->name = NULL; /* to stop trying to load it again if we fail */
|
|
success = false;
|
|
continue;
|
|
}
|
|
|
|
/* finally, assign the font_id to the viewport */
|
|
vp->font = font->id;
|
|
}
|
|
if (font_count)
|
|
{
|
|
int *font_ids = skin_buffer_alloc(font_count * sizeof(int));
|
|
if (!success || font_ids == NULL)
|
|
{
|
|
while (font_count > 0)
|
|
{
|
|
if(id_array[--font_count] != -1)
|
|
font_unload(id_array[font_count]);
|
|
}
|
|
data->font_ids = PTRTOSKINOFFSET(skin_buffer, NULL);
|
|
return false;
|
|
}
|
|
memcpy(font_ids, id_array, sizeof(int)*font_count);
|
|
data->font_count = font_count;
|
|
data->font_ids = PTRTOSKINOFFSET(skin_buffer, font_ids);
|
|
}
|
|
else
|
|
{
|
|
data->font_count = 0;
|
|
data->font_ids = PTRTOSKINOFFSET(skin_buffer, NULL);
|
|
}
|
|
return success;
|
|
}
|
|
|
|
static int convert_viewport(struct wps_data *data, struct skin_element* element)
|
|
{
|
|
struct skin_viewport *skin_vp = skin_buffer_alloc(sizeof(*skin_vp));
|
|
struct screen *display = &screens[curr_screen];
|
|
|
|
if (!skin_vp)
|
|
return CALLBACK_ERROR;
|
|
|
|
skin_vp->hidden_flags = 0;
|
|
skin_vp->label = PTRTOSKINOFFSET(skin_buffer, NULL);
|
|
skin_vp->is_infovp = false;
|
|
skin_vp->parsed_fontid = 1;
|
|
element->data = PTRTOSKINOFFSET(skin_buffer, skin_vp);
|
|
curr_vp = skin_vp;
|
|
curr_viewport_element = element;
|
|
if (!first_viewport)
|
|
first_viewport = element;
|
|
|
|
viewport_set_defaults(&skin_vp->vp, curr_screen);
|
|
|
|
#if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
|
|
skin_vp->output_to_backdrop_buffer = false;
|
|
#endif
|
|
#ifdef HAVE_LCD_COLOR
|
|
skin_vp->start_gradient.start = global_settings.lss_color;
|
|
skin_vp->start_gradient.end = global_settings.lse_color;
|
|
skin_vp->start_gradient.text = global_settings.lst_color;
|
|
#endif
|
|
|
|
|
|
struct skin_tag_parameter *param = get_param(element, 0);
|
|
if (element->params_count == 0) /* default viewport */
|
|
{
|
|
if (data->tree < 0) /* first viewport in the skin */
|
|
data->tree = PTRTOSKINOFFSET(skin_buffer, element);
|
|
skin_vp->label = VP_DEFAULT_LABEL;
|
|
return CALLBACK_OK;
|
|
}
|
|
|
|
if (element->params_count == 6)
|
|
{
|
|
if (element->tag->type == SKIN_TOKEN_UIVIEWPORT_LOAD)
|
|
{
|
|
skin_vp->is_infovp = true;
|
|
if (isdefault(param))
|
|
{
|
|
skin_vp->hidden_flags = VP_NEVER_VISIBLE;
|
|
skin_vp->label = VP_DEFAULT_LABEL;
|
|
}
|
|
else
|
|
{
|
|
skin_vp->hidden_flags = VP_NEVER_VISIBLE;
|
|
skin_vp->label = param->data.text;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
skin_vp->hidden_flags = VP_DRAW_HIDEABLE|VP_DRAW_HIDDEN;
|
|
skin_vp->label = param->data.text;
|
|
}
|
|
param++;
|
|
}
|
|
/* x */
|
|
if (!isdefault(param))
|
|
{
|
|
skin_vp->vp.x = param->data.number;
|
|
if (param->data.number < 0)
|
|
skin_vp->vp.x += display->lcdwidth;
|
|
else if (param->type == PERCENT)
|
|
skin_vp->vp.x = param->data.number * display->lcdwidth / 1000;
|
|
}
|
|
param++;
|
|
/* y */
|
|
if (!isdefault(param))
|
|
{
|
|
skin_vp->vp.y = param->data.number;
|
|
if (param->data.number < 0)
|
|
skin_vp->vp.y += display->lcdheight;
|
|
else if (param->type == PERCENT)
|
|
skin_vp->vp.y = param->data.number * display->lcdheight / 1000;
|
|
}
|
|
param++;
|
|
/* width */
|
|
if (!isdefault(param))
|
|
{
|
|
skin_vp->vp.width = param->data.number;
|
|
if (param->data.number < 0)
|
|
skin_vp->vp.width = (skin_vp->vp.width + display->lcdwidth) - skin_vp->vp.x;
|
|
else if (param->type == PERCENT)
|
|
skin_vp->vp.width = param->data.number * display->lcdwidth / 1000;
|
|
}
|
|
else
|
|
{
|
|
skin_vp->vp.width = display->lcdwidth - skin_vp->vp.x;
|
|
}
|
|
param++;
|
|
/* height */
|
|
if (!isdefault(param))
|
|
{
|
|
skin_vp->vp.height = param->data.number;
|
|
if (param->data.number < 0)
|
|
skin_vp->vp.height = (skin_vp->vp.height + display->lcdheight) - skin_vp->vp.y;
|
|
else if (param->type == PERCENT)
|
|
skin_vp->vp.height = param->data.number * display->lcdheight / 1000;
|
|
}
|
|
else
|
|
{
|
|
skin_vp->vp.height = display->lcdheight - skin_vp->vp.y;
|
|
}
|
|
param++;
|
|
/* font */
|
|
if (!isdefault(param))
|
|
skin_vp->parsed_fontid = param->data.number;
|
|
if ((unsigned) skin_vp->vp.x >= (unsigned) display->lcdwidth ||
|
|
skin_vp->vp.width + skin_vp->vp.x > display->lcdwidth ||
|
|
(unsigned) skin_vp->vp.y >= (unsigned) display->lcdheight ||
|
|
skin_vp->vp.height + skin_vp->vp.y > display->lcdheight)
|
|
return CALLBACK_ERROR;
|
|
|
|
/* Fix x position for RTL languages */
|
|
if (follow_lang_direction && lang_is_rtl())
|
|
skin_vp->vp.x = display->lcdwidth - skin_vp->vp.x - skin_vp->vp.width;
|
|
|
|
return CALLBACK_OK;
|
|
}
|
|
|
|
static int skin_element_callback(struct skin_element* element, void* data)
|
|
{
|
|
struct wps_data *wps_data = (struct wps_data *)data;
|
|
struct wps_token *token;
|
|
parse_function function = NULL;
|
|
|
|
switch (element->type)
|
|
{
|
|
/* IMPORTANT: element params are shared, so copy them if needed
|
|
* or use then NOW, dont presume they have a long lifespan
|
|
*/
|
|
case TAG:
|
|
{
|
|
token = skin_buffer_alloc(sizeof(*token));
|
|
memset(token, 0, sizeof(*token));
|
|
token->type = element->tag->type;
|
|
token->value.data = INVALID_OFFSET;
|
|
|
|
if (element->tag->flags&SKIN_RTC_REFRESH)
|
|
{
|
|
#if CONFIG_RTC
|
|
curr_line->update_mode |= SKIN_REFRESH_DYNAMIC;
|
|
#else
|
|
curr_line->update_mode |= SKIN_REFRESH_STATIC;
|
|
#endif
|
|
}
|
|
else
|
|
curr_line->update_mode |= element->tag->flags&SKIN_REFRESH_ALL;
|
|
|
|
element->data = PTRTOSKINOFFSET(skin_buffer, token);
|
|
|
|
/* Some tags need special handling for the tag, so add them here */
|
|
switch (token->type)
|
|
{
|
|
case SKIN_TOKEN_ALIGN_LANGDIRECTION:
|
|
follow_lang_direction = 2;
|
|
break;
|
|
case SKIN_TOKEN_LOGICAL_IF:
|
|
function = parse_logical_if;
|
|
break;
|
|
case SKIN_TOKEN_LOGICAL_AND:
|
|
case SKIN_TOKEN_LOGICAL_OR:
|
|
function = parse_logical_andor;
|
|
break;
|
|
case SKIN_TOKEN_SUBSTRING:
|
|
function = parse_substring_tag;
|
|
break;
|
|
case SKIN_TOKEN_PROGRESSBAR:
|
|
case SKIN_TOKEN_VOLUME:
|
|
case SKIN_TOKEN_BATTERY_PERCENT:
|
|
case SKIN_TOKEN_PLAYER_PROGRESSBAR:
|
|
case SKIN_TOKEN_PEAKMETER_LEFT:
|
|
case SKIN_TOKEN_PEAKMETER_RIGHT:
|
|
case SKIN_TOKEN_LIST_NEEDS_SCROLLBAR:
|
|
#ifdef HAVE_RADIO_RSSI
|
|
case SKIN_TOKEN_TUNER_RSSI:
|
|
#endif
|
|
function = parse_progressbar_tag;
|
|
break;
|
|
case SKIN_TOKEN_SUBLINE_TIMEOUT:
|
|
case SKIN_TOKEN_BUTTON_VOLUME:
|
|
case SKIN_TOKEN_TRACK_STARTING:
|
|
case SKIN_TOKEN_TRACK_ENDING:
|
|
function = parse_timeout_tag;
|
|
break;
|
|
case SKIN_TOKEN_LIST_ITEM_TEXT:
|
|
case SKIN_TOKEN_LIST_ITEM_ICON:
|
|
function = parse_listitem;
|
|
break;
|
|
case SKIN_TOKEN_DISABLE_THEME:
|
|
case SKIN_TOKEN_ENABLE_THEME:
|
|
case SKIN_TOKEN_DRAW_INBUILTBAR:
|
|
function = parse_statusbar_tags;
|
|
break;
|
|
case SKIN_TOKEN_LIST_TITLE_TEXT:
|
|
#ifndef __PCTOOL__
|
|
sb_skin_has_title(curr_screen);
|
|
#endif
|
|
break;
|
|
#if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
|
|
case SKIN_TOKEN_DRAWRECTANGLE:
|
|
function = parse_drawrectangle;
|
|
break;
|
|
#endif
|
|
case SKIN_TOKEN_FILE_DIRECTORY:
|
|
token->value.i = get_param(element, 0)->data.number;
|
|
break;
|
|
#ifdef HAVE_BACKDROP_IMAGE
|
|
case SKIN_TOKEN_VIEWPORT_FGCOLOUR:
|
|
case SKIN_TOKEN_VIEWPORT_BGCOLOUR:
|
|
function = parse_viewportcolour;
|
|
break;
|
|
case SKIN_TOKEN_IMAGE_BACKDROP:
|
|
function = parse_image_special;
|
|
break;
|
|
case SKIN_TOKEN_VIEWPORT_TEXTSTYLE:
|
|
function = parse_viewporttextstyle;
|
|
break;
|
|
case SKIN_TOKEN_VIEWPORT_DRAWONBG:
|
|
curr_vp->output_to_backdrop_buffer = true;
|
|
backdrop_filename = BACKDROP_BUFFERNAME;
|
|
wps_data->use_extra_framebuffer = true;
|
|
break;
|
|
#endif
|
|
#ifdef HAVE_LCD_COLOR
|
|
case SKIN_TOKEN_VIEWPORT_GRADIENT_SETUP:
|
|
function = parse_viewport_gradient_setup;
|
|
break;
|
|
#endif
|
|
case SKIN_TOKEN_TRANSLATEDSTRING:
|
|
case SKIN_TOKEN_SETTING:
|
|
function = parse_setting_and_lang;
|
|
break;
|
|
case SKIN_TOKEN_VIEWPORT_CUSTOMLIST:
|
|
function = parse_playlistview;
|
|
break;
|
|
case SKIN_TOKEN_LOAD_FONT:
|
|
function = parse_font_load;
|
|
break;
|
|
case SKIN_TOKEN_VIEWPORT_ENABLE:
|
|
case SKIN_TOKEN_UIVIEWPORT_ENABLE:
|
|
token->value.data = get_param(element, 0)->data.text;
|
|
break;
|
|
case SKIN_TOKEN_IMAGE_PRELOAD_DISPLAY:
|
|
case SKIN_TOKEN_IMAGE_DISPLAY_9SEGMENT:
|
|
function = parse_image_display;
|
|
break;
|
|
case SKIN_TOKEN_IMAGE_PRELOAD:
|
|
case SKIN_TOKEN_IMAGE_DISPLAY:
|
|
function = parse_image_load;
|
|
break;
|
|
case SKIN_TOKEN_LIST_ITEM_CFG:
|
|
function = parse_listitemviewport;
|
|
break;
|
|
#ifdef HAVE_TOUCHSCREEN
|
|
case SKIN_TOKEN_TOUCHREGION:
|
|
function = parse_touchregion;
|
|
break;
|
|
case SKIN_TOKEN_LASTTOUCH:
|
|
function = parse_lasttouch;
|
|
break;
|
|
#endif
|
|
#ifdef HAVE_ALBUMART
|
|
case SKIN_TOKEN_ALBUMART_DISPLAY:
|
|
if (SKINOFFSETTOPTR(skin_buffer, wps_data->albumart))
|
|
{
|
|
struct skin_albumart *aa = SKINOFFSETTOPTR(skin_buffer, wps_data->albumart);
|
|
aa->vp = PTRTOSKINOFFSET(skin_buffer, &curr_vp->vp);
|
|
}
|
|
break;
|
|
case SKIN_TOKEN_ALBUMART_LOAD:
|
|
function = parse_albumart_load;
|
|
break;
|
|
#endif
|
|
#ifdef HAVE_SKIN_VARIABLES
|
|
case SKIN_TOKEN_VAR_SET:
|
|
case SKIN_TOKEN_VAR_GETVAL:
|
|
case SKIN_TOKEN_VAR_TIMEOUT:
|
|
function = parse_skinvar;
|
|
break;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
if (function)
|
|
{
|
|
if (function(element, token, wps_data) != 0)
|
|
return CALLBACK_ERROR;
|
|
}
|
|
/* tags that start with 'F', 'I' or 'D' are for the next file */
|
|
if ( *(element->tag->name) == 'I' || *(element->tag->name) == 'F' ||
|
|
*(element->tag->name) == 'D')
|
|
token->next = true;
|
|
if (follow_lang_direction > 0 )
|
|
follow_lang_direction--;
|
|
break;
|
|
}
|
|
case VIEWPORT:
|
|
return convert_viewport(wps_data, element);
|
|
case LINE:
|
|
{
|
|
curr_line = skin_buffer_alloc(sizeof(*curr_line));
|
|
curr_line->update_mode = SKIN_REFRESH_STATIC;
|
|
element->data = PTRTOSKINOFFSET(skin_buffer, curr_line);
|
|
}
|
|
break;
|
|
case LINE_ALTERNATOR:
|
|
{
|
|
struct line_alternator *alternator = skin_buffer_alloc(sizeof(*alternator));
|
|
alternator->current_line = 0;
|
|
#ifndef __PCTOOL__
|
|
alternator->next_change_tick = current_tick;
|
|
#endif
|
|
element->data = PTRTOSKINOFFSET(skin_buffer, alternator);
|
|
}
|
|
break;
|
|
case CONDITIONAL:
|
|
{
|
|
struct conditional *conditional = skin_buffer_alloc(sizeof(*conditional));
|
|
conditional->last_value = -1;
|
|
conditional->token = element->data;
|
|
element->data = PTRTOSKINOFFSET(skin_buffer, conditional);
|
|
if (!check_feature_tag(element->tag->type))
|
|
{
|
|
return FEATURE_NOT_AVAILABLE;
|
|
}
|
|
return CALLBACK_OK;
|
|
}
|
|
case TEXT:
|
|
curr_line->update_mode |= SKIN_REFRESH_STATIC;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return CALLBACK_OK;
|
|
}
|
|
|
|
/* to setup up the wps-data from a format-buffer (isfile = false)
|
|
from a (wps-)file (isfile = true)*/
|
|
bool skin_data_load(enum screen_type screen, struct wps_data *wps_data,
|
|
const char *buf, bool isfile, struct skin_stats *stats)
|
|
{
|
|
char *wps_buffer = NULL;
|
|
if (!wps_data || !buf)
|
|
return false;
|
|
int i;
|
|
for (i=0;i<MAXUSERFONTS;i++)
|
|
{
|
|
skinfonts[i].id = -1;
|
|
skinfonts[i].name = NULL;
|
|
}
|
|
#ifdef DEBUG_SKIN_ENGINE
|
|
if (isfile && debug_wps)
|
|
{
|
|
DEBUGF("\n=====================\nLoading '%s'\n=====================\n", buf);
|
|
}
|
|
#endif
|
|
|
|
_stats = stats;
|
|
skin_clear_stats(stats);
|
|
/* get buffer space from the plugin buffer */
|
|
size_t buffersize = 0;
|
|
wps_buffer = (char *)plugin_get_buffer(&buffersize);
|
|
|
|
if (!wps_buffer)
|
|
return false;
|
|
|
|
skin_data_reset(wps_data);
|
|
wps_data->wps_loaded = false;
|
|
curr_screen = screen;
|
|
curr_line = NULL;
|
|
curr_vp = NULL;
|
|
curr_viewport_element = NULL;
|
|
first_viewport = NULL;
|
|
|
|
if (isfile)
|
|
{
|
|
int fd = open_utf8(buf, O_RDONLY);
|
|
|
|
if (fd < 0)
|
|
return false;
|
|
/* copy the file's content to the buffer for parsing,
|
|
ensuring that every line ends with a newline char. */
|
|
unsigned int start = 0;
|
|
while(read_line(fd, wps_buffer + start, buffersize - start) > 0)
|
|
{
|
|
start += strlen(wps_buffer + start);
|
|
if (start < buffersize - 1)
|
|
{
|
|
wps_buffer[start++] = '\n';
|
|
wps_buffer[start] = 0;
|
|
}
|
|
}
|
|
close(fd);
|
|
if (start <= 0)
|
|
return false;
|
|
start++;
|
|
skin_buffer = &wps_buffer[start];
|
|
buffersize -= start;
|
|
}
|
|
else
|
|
{
|
|
skin_buffer = wps_buffer;
|
|
wps_buffer = (char*)buf;
|
|
}
|
|
|
|
/* align to long */
|
|
ALIGN_BUFFER(skin_buffer, buffersize, sizeof(long));
|
|
#ifdef HAVE_BACKDROP_IMAGE
|
|
backdrop_filename = "-";
|
|
wps_data->backdrop_id = -1;
|
|
#endif
|
|
/* parse the skin source */
|
|
skin_buffer_init(skin_buffer, buffersize);
|
|
struct skin_element *tree = skin_parse(wps_buffer, skin_element_callback, wps_data);
|
|
wps_data->tree = PTRTOSKINOFFSET(skin_buffer, tree);
|
|
if (!SKINOFFSETTOPTR(skin_buffer, wps_data->tree)) {
|
|
#ifdef DEBUG_SKIN_ENGINE
|
|
if (isfile && debug_wps)
|
|
skin_error_format_message();
|
|
#endif
|
|
skin_data_reset(wps_data);
|
|
return false;
|
|
}
|
|
|
|
char bmpdir[MAX_PATH];
|
|
if (isfile)
|
|
{
|
|
/* get the bitmap dir */
|
|
char *dot = strrchr(buf, '.');
|
|
strmemccpy(bmpdir, buf, dot - buf + 1);
|
|
}
|
|
else
|
|
{
|
|
snprintf(bmpdir, MAX_PATH, "%s", BACKDROP_DIR);
|
|
}
|
|
/* load the bitmaps that were found by the parsing */
|
|
if (!load_skin_bitmaps(wps_data, bmpdir) ||
|
|
!skin_load_fonts(wps_data))
|
|
{
|
|
skin_data_reset(wps_data);
|
|
return false;
|
|
}
|
|
#if defined(HAVE_ALBUMART) && !defined(__PCTOOL__)
|
|
/* last_albumart_{width,height} is either both 0 or valid AA dimensions */
|
|
struct skin_albumart *aa = SKINOFFSETTOPTR(skin_buffer, wps_data->albumart);
|
|
if (aa && (aa->state != WPS_ALBUMART_NONE ||
|
|
(((wps_data->last_albumart_height != aa->height) ||
|
|
(wps_data->last_albumart_width != aa->width)))))
|
|
{
|
|
playback_update_aa_dims();
|
|
}
|
|
#endif
|
|
#ifndef __PCTOOL__
|
|
wps_data->buflib_handle = core_alloc(isfile ? buf : "failsafe skin",
|
|
skin_buffer_usage());
|
|
if (wps_data->buflib_handle > 0)
|
|
{
|
|
wps_data->wps_loaded = true;
|
|
memcpy(core_get_data(wps_data->buflib_handle), skin_buffer,
|
|
skin_buffer_usage());
|
|
stats->buflib_handles++;
|
|
stats->tree_size = skin_buffer_usage();
|
|
}
|
|
#else
|
|
wps_data->wps_loaded = wps_data->tree >= 0;
|
|
#endif
|
|
|
|
#ifdef HAVE_TOUCHSCREEN
|
|
/* Check if there are any touch regions from the skin and not just
|
|
* auto-created ones for bars */
|
|
struct skin_token_list *regions = SKINOFFSETTOPTR(skin_buffer,
|
|
wps_data->touchregions);
|
|
bool user_touch_region_found = false;
|
|
while (regions)
|
|
{
|
|
struct wps_token *token = SKINOFFSETTOPTR(skin_buffer, regions->token);
|
|
struct touchregion *r = NULL;
|
|
if (token)
|
|
r = SKINOFFSETTOPTR(skin_buffer, token->value.data);
|
|
if (r && r->action != ACTION_TOUCH_SCROLLBAR &&
|
|
r->action != ACTION_TOUCH_VOLUME)
|
|
{
|
|
user_touch_region_found = true;
|
|
break;
|
|
}
|
|
regions = SKINOFFSETTOPTR(skin_buffer, regions->next);
|
|
}
|
|
regions = SKINOFFSETTOPTR(skin_buffer, wps_data->touchregions);
|
|
if (regions && !user_touch_region_found)
|
|
wps_data->touchregions = PTRTOSKINOFFSET(skin_buffer, NULL);
|
|
#endif
|
|
|
|
skin_buffer = NULL;
|
|
return true;
|
|
}
|