diff options
Diffstat (limited to 'glcdskin')
-rw-r--r-- | glcdskin/Makefile | 20 | ||||
-rw-r--r-- | glcdskin/cache.c | 70 | ||||
-rw-r--r-- | glcdskin/cache.h | 16 | ||||
-rw-r--r-- | glcdskin/config.h | 4 | ||||
-rw-r--r-- | glcdskin/display.c | 15 | ||||
-rw-r--r-- | glcdskin/display.h | 2 | ||||
-rw-r--r-- | glcdskin/font.c | 101 | ||||
-rw-r--r-- | glcdskin/font.h | 3 | ||||
-rw-r--r-- | glcdskin/function.c | 87 | ||||
-rw-r--r-- | glcdskin/function.h | 7 | ||||
-rw-r--r-- | glcdskin/object.c | 594 | ||||
-rw-r--r-- | glcdskin/object.h | 90 | ||||
-rw-r--r-- | glcdskin/parser.c | 304 | ||||
-rw-r--r-- | glcdskin/parser.h | 12 | ||||
-rw-r--r-- | glcdskin/skin.c | 14 | ||||
-rw-r--r-- | glcdskin/skin.h | 12 | ||||
-rw-r--r-- | glcdskin/string.c | 155 | ||||
-rw-r--r-- | glcdskin/string.h | 4 | ||||
-rw-r--r-- | glcdskin/type.h | 4 | ||||
-rw-r--r-- | glcdskin/variable.c | 82 | ||||
-rw-r--r-- | glcdskin/variable.h | 19 | ||||
-rw-r--r-- | glcdskin/xml.c | 161 | ||||
-rw-r--r-- | glcdskin/xml.h | 14 |
23 files changed, 1527 insertions, 263 deletions
diff --git a/glcdskin/Makefile b/glcdskin/Makefile index 9b3b867..90fe310 100644 --- a/glcdskin/Makefile +++ b/glcdskin/Makefile @@ -7,8 +7,8 @@ CXXFLAGS += -fPIC -VERMAJOR = 1 -VERMINOR = 0 +VERMAJOR = 2 +VERMINOR = 1 VERMICRO = 0 BASENAME = libglcdskin.so @@ -19,17 +19,23 @@ OBJS = cache.o config.o display.o font.o function.o object.o parser.o skin.o str HEADERS = cache.h config.h display.h font.h function.h object.h parser.h skin.h string.h type.h variable.h xml.h + +### Inner graphlcd-base dependencies +LIBS += -L../glcdgraphics -lglcdgraphics -L../glcddrivers -lglcddrivers + +ifdef HAVE_FONTCONFIG + LIBS += -lfontconfig + DEFINES += -DHAVE_FONTCONFIG +endif + ### Implicit rules: %.o: %.c - $(CXX) $(CXXFLAGS) -I.. -c $(DEFINES) $(INCLUDES) $< + $(CXX) $(CXXEXTRA) $(CXXFLAGS) -I.. -c $(DEFINES) $(INCLUDES) $< # Dependencies: -MAKEDEP = g++ -MM -MG -DEPFILE = .dependencies -$(DEPFILE): Makefile - @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@ +DEPFILE = $(OBJS:%.o=%.d) -include $(DEPFILE) diff --git a/glcdskin/cache.c b/glcdskin/cache.c index 83629a0..f0543d9 100644 --- a/glcdskin/cache.c +++ b/glcdskin/cache.c @@ -12,20 +12,25 @@ #include <glcdgraphics/image.h> #include <glcdgraphics/glcd.h> #include <glcdgraphics/pbm.h> +#include <glcdgraphics/extformats.h> #include <stdlib.h> #include <string.h> +#include <syslog.h> + #include "cache.h" #include "skin.h" namespace GLCD { -cImageItem::cImageItem(const std::string & path, cImage * image) +cImageItem::cImageItem(const std::string & path, cImage * image, uint16_t scalew, uint16_t scaleh) : path(path), counter(0), - image(image) + image(image), + scale_width(scalew), + scale_height(scaleh) { } @@ -43,25 +48,40 @@ cImageCache::cImageCache(cSkin * Parent, int Size) cImageCache::~cImageCache() { + Clear(); +} + +void cImageCache::Clear(void) +{ for (unsigned int i = 0; i < images.size(); i++) { delete images[i]; } images.clear(); + failedpaths.clear(); } -cImage * cImageCache::Get(const std::string & path) +cImage * cImageCache::Get(const std::string & path, uint16_t & scalew, uint16_t & scaleh) { std::vector <cImageItem *>::iterator it; cImageItem * item; uint64_t maxCounter; std::vector <cImageItem *>::iterator oldest; + // test if this path has already been stored as invalid path / invalid/non-existent image + for (size_t i = 0; i < failedpaths.size(); i++) { + if (failedpaths[i] == path) { + return NULL; + } + } + maxCounter = 0; item = NULL; for (it = images.begin(); it != images.end(); it++) { - if (item == NULL && path == (*it)->Path()) + uint16_t scw = 0, sch = 0; + (*it)->ScalingGeometry(scw, sch); + if (item == NULL && path == (*it)->Path() && ( (scw == 0 && sch == 0 && ! (scalew || scaleh)) || (scw == scalew && sch == scaleh))) { (*it)->ResetCounter(); item = (*it); @@ -81,21 +101,25 @@ cImage * cImageCache::Get(const std::string & path) return item->Image(); } - item = LoadImage(path); + item = LoadImage(path, scalew, scaleh); if (item) { + syslog(LOG_INFO, "INFO: graphlcd: successfully loaded image '%s'\n", path.c_str()); if (images.size() == size) { images.erase(oldest); } images.push_back(item); return item->Image(); + } else { + failedpaths.push_back(path); } return NULL; } -cImageItem * cImageCache::LoadImage(const std::string & path) +cImageItem * cImageCache::LoadImage(const std::string & path, uint16_t scalew, uint16_t scaleh) { + //fprintf(stderr, "### loading image %s\n", path.c_str()); cImageItem * item; cImage * image; char str[8]; @@ -138,6 +162,28 @@ cImageItem * cImageCache::LoadImage(const std::string & path) } file += path; } + + cImageFile* imgFile = NULL; + + if (strcmp(str, "PBM") == 0) { + imgFile = new cPBMFile(); + } else if (strcmp(str, "GLCD") == 0) { + imgFile = new cGLCDFile(); + } else { + imgFile = new cExtFormatFile(); + } + + uint16_t scale_width = scalew; + uint16_t scale_height = scaleh; + // scale_width and scale_height are set to 0 if image was NOT scaled + if (!imgFile || (imgFile->LoadScaled(*image, file, scalew /*scale_width*/, scaleh /*scale_height*/) == false) ) { + delete image; + if (imgFile) delete imgFile; + return NULL; + } + delete imgFile; + +#if 0 if (strcmp(str, "PBM") == 0) { cPBMFile pbm; @@ -160,11 +206,17 @@ cImageItem * cImageCache::LoadImage(const std::string & path) } else { - delete image; - return NULL; + cExtFormatFile extformat; + + if (extformat.Load(*image, file) == false) + { + delete image; + return NULL; + } } +#endif - item = new cImageItem(path, image); + item = new cImageItem(path, image, scale_width, scale_height); if (!item) { delete image; diff --git a/glcdskin/cache.h b/glcdskin/cache.h index 925ebcc..533f009 100644 --- a/glcdskin/cache.h +++ b/glcdskin/cache.h @@ -29,11 +29,13 @@ private: std::string path; uint64_t counter; cImage * image; + uint16_t scale_width, scale_height; public: - cImageItem(const std::string & path, cImage * image); + cImageItem(const std::string & path, cImage * image, uint16_t scalew, uint16_t scaleh); ~cImageItem(); const std::string & Path() const { return path; } + void ScalingGeometry(uint16_t & scalew, uint16_t & scaleh) { scalew = scale_width; scaleh = scale_height; } uint64_t Counter() const { return counter; } cImage * Image() { return image; } void ResetCounter() { counter = 0; } @@ -46,13 +48,21 @@ private: cSkin * skin; size_t size; std::vector <cImageItem *> images; + std::vector <std::string> failedpaths; - cImageItem * LoadImage(const std::string & path); + cImageItem * LoadImage(const std::string & path, uint16_t scalew, uint16_t scaleh); public: cImageCache(cSkin * Parent, int Size); ~cImageCache(); - cImage * Get(const std::string & path); + cImage * Get(const std::string & path, uint16_t & scalew, uint16_t & scaleh); + cImage * Get(const std::string & path) { + uint16_t scalew = 0; + uint16_t scaleh = 0; + return Get(path, scalew, scaleh) ; + } + + void Clear(void); }; } // end of namespace diff --git a/glcdskin/config.h b/glcdskin/config.h index ce68513..eede1cc 100644 --- a/glcdskin/config.h +++ b/glcdskin/config.h @@ -17,12 +17,15 @@ #include <stdint.h> +#include "../glcddrivers/driver.h" + namespace GLCD { class cType; class cFont; struct tSkinToken; +class cDriver; class cSkinConfig { @@ -37,6 +40,7 @@ public: virtual int GetTokenId(const std::string & Name); virtual int GetTabPosition(int Index, int MaxWidth, const cFont & Font); virtual uint64_t Now(void); + virtual cDriver * GetDriver(void) const { return NULL; } }; } // end of namespace diff --git a/glcdskin/display.c b/glcdskin/display.c index a926bd9..6f7a673 100644 --- a/glcdskin/display.c +++ b/glcdskin/display.c @@ -39,6 +39,21 @@ bool cSkinDisplay::NeedsUpdate(uint64_t CurrentTime) } +std::string cSkinDisplay::CheckAction(cGLCDEvent * ev) { + std::string rv = ""; + + if (!ev) + return ""; + + for (uint32_t i = 0; i < NumObjects(); ++i) { + if ( (rv = GetObject(i)->CheckAction(ev) ) != "" ) { + return rv; + } + } + return ""; +} + + cSkinDisplays::cSkinDisplays(void) { } diff --git a/glcdskin/display.h b/glcdskin/display.h index 3b19b0c..ec0ee8b 100644 --- a/glcdskin/display.h +++ b/glcdskin/display.h @@ -45,6 +45,8 @@ public: void Render(cBitmap * screen); bool NeedsUpdate(uint64_t CurrentTime); + + std::string CheckAction(cGLCDEvent * ev); }; class cSkinDisplays: public std::vector<cSkinDisplay *> diff --git a/glcdskin/font.c b/glcdskin/font.c index 731b1fa..a8c22c8 100644 --- a/glcdskin/font.c +++ b/glcdskin/font.c @@ -6,9 +6,16 @@ #include "skin.h" #include "function.h" +#ifdef HAVE_FONTCONFIG + #include <fontconfig/fontconfig.h> +#endif + namespace GLCD { +int cSkinFont::FcInitCount = 0; + + cSkinFont::cSkinFont(cSkin * Parent) : mSkin(Parent), mCondition(NULL), @@ -17,36 +24,114 @@ cSkinFont::cSkinFont(cSkin * Parent) { } +cSkinFont::~cSkinFont(void) { +#ifdef HAVE_FONTCONFIG + cSkinFont::FcInitCount --; + if (cSkinFont::FcInitCount <= 0) { + FcFini(); + } +#endif +} + bool cSkinFont::ParseUrl(const std::string & url) { - std::string::size_type count = std::string::npos; + bool isFontconfig = false; + std::string rawfont = ""; if (url.find("fnt:") == 0) { mType = ftFNT; + rawfont = url.substr(4); mSize = 0; } else if (url.find("ft2:") == 0) { mType = ftFT2; - std::string::size_type pos = url.find(":", 4); + rawfont = url.substr(4); + std::string::size_type pos = rawfont.find(":"); if (pos == std::string::npos) { syslog(LOG_ERR, "cFontElement::Load(): No font size specified in %s\n", url.c_str()); return false; } - std::string tmp = url.substr(pos + 1); + std::string tmp = rawfont.substr(pos + 1); mSize = atoi(tmp.c_str()); - count = pos - 4; + rawfont = rawfont.substr(0,pos); } +#ifdef HAVE_FONTCONFIG + else if (url.find("fc:") == 0) + { + mType = ftFT2; + isFontconfig = true; + rawfont = url.substr(3); + + std::string::size_type pos = rawfont.find(":size="); + if (pos == std::string::npos) { + syslog(LOG_ERR, "cFontElement::Load(): No font size specified in %s (e.g.: 'size=<fontsize>')\n", url.c_str()); + return false; + } + std::string sizeterm = rawfont.substr(pos + 1); + + pos = sizeterm.find(":"); // find terminating : + if (pos != std::string::npos) { + sizeterm = sizeterm.substr(0, pos); + } + + pos = sizeterm.find("="); + if (pos == std::string::npos) { + syslog(LOG_ERR, "cFontElement::Load(): Invalid size term '%s' (must be 'size=<fontsize>')\n", url.c_str()); + return false; + } + + std::string tmp = sizeterm.substr(pos + 1); + mSize = atoi(tmp.c_str()); + if (mSize <= 0) { + syslog(LOG_ERR, "cFontElement::Load(): Invalid font size in '%s'\n", url.c_str()); + return false; + } + + if (cSkinFont::FcInitCount <= 0) { + FcInit(); + } + cSkinFont::FcInitCount ++; + + FcPattern *pat = FcNameParse((FcChar8 *) rawfont.c_str() ); + rawfont = ""; + FcPatternAddBool(pat, FC_SCALABLE, FcTrue); + FcConfigSubstitute(NULL, pat, FcMatchPattern); + FcDefaultSubstitute(pat); + FcResult result; + FcFontSet *fontset = FcFontSort(NULL, pat, FcFalse, NULL, &result); + if (fontset) { + FcBool scalable; + for (int i = 0; i < fontset->nfont; i++) { + FcPatternGetBool(fontset->fonts[i], FC_SCALABLE, 0, &scalable); + if (scalable) { + FcChar8 *s = NULL; + FcPatternGetString(fontset->fonts[i], FC_FILE, 0, &s); + rawfont = (char *)s; // set font path + break; + } + } + FcFontSetDestroy(fontset); + } + FcPatternDestroy(pat); + + if (rawfont == "") { + syslog(LOG_ERR, "cFontElement::Load(): No usable font found for '%s'\n", url.c_str()); + return false; + } + } +#endif else { syslog(LOG_ERR, "cSkinFont::ParseUrl(): Unknown font type in %s\n", url.c_str()); return false; } - if (url[4] == '/' || url.find("./") == 4 || url.find("../") == 4) - mFile = url.substr(4, count); + if (isFontconfig || (rawfont[0] == '/' || rawfont.find("./") == 0 || rawfont.find("../") == 0)) { + mFile = rawfont; + } else { // first try skin's font dir @@ -57,7 +142,7 @@ bool cSkinFont::ParseUrl(const std::string & url) mFile += '/'; } mFile += "fonts/"; - mFile += url.substr(4, count); + mFile += rawfont; #if (__GNUC__ < 3) std::ifstream f(mFile.c_str(), std::ios::in | std::ios::binary); #else @@ -76,7 +161,7 @@ bool cSkinFont::ParseUrl(const std::string & url) if (mFile[mFile.length() - 1] != '/') mFile += '/'; } - mFile += url.substr(4, count); + mFile += rawfont; } } diff --git a/glcdskin/font.h b/glcdskin/font.h index d11fa50..80e5556 100644 --- a/glcdskin/font.h +++ b/glcdskin/font.h @@ -49,8 +49,11 @@ private: cSkinDisplay mDummyDisplay; cSkinObject mDummyObject; + static int FcInitCount; + public: cSkinFont(cSkin * Parent); + ~cSkinFont(void); bool ParseUrl(const std::string & Text); bool ParseCondition(const std::string & Text); diff --git a/glcdskin/function.c b/glcdskin/function.c index 8582ad5..a2d58ca 100644 --- a/glcdskin/function.c +++ b/glcdskin/function.c @@ -22,7 +22,7 @@ namespace GLCD static const char * Internals[] = { - "not", "and", "or", "equal", "gt", "lt", "ge", "le", "ne", "file", "trans", + "not", "and", "or", "equal", "eq", "gt", "lt", "ge", "le", "ne", "file", "trans", "add", "sub", "mul", "div", "FontTotalWidth", "FontTotalHeight", @@ -33,6 +33,7 @@ static const char * Internals[] = "FontTextHeight", "ImageWidth", "ImageHeight", + "QueryFeature", NULL }; @@ -74,7 +75,7 @@ cSkinFunction::~cSkinFunction() delete mParams[i]; } -bool cSkinFunction::Parse(const std::string & Text) +bool cSkinFunction::Parse(const std::string & Text, bool reparse) { const char *text = Text.c_str(); const char *ptr = text, *last = text; @@ -89,7 +90,8 @@ bool cSkinFunction::Parse(const std::string & Text) || (*ptr == '\'' && *(ptr + strlen(ptr) - 1) != '\'') || (*ptr == '{' && *(ptr + strlen(ptr) - 1) != '}')) { - syslog(LOG_ERR, "ERROR: Unmatched string end\n"); + if (!reparse) // only log this error when not reparsing + syslog(LOG_ERR, "ERROR: graphlcd/skin/function: Unmatched string end\n"); return false; } @@ -113,7 +115,8 @@ bool cSkinFunction::Parse(const std::string & Text) // must be a variable id if (strlen(ptr) < 2) { - syslog(LOG_ERR, "ERROR: No variable id given\n"); + if (!reparse) // only log this error when not reparsing + syslog(LOG_ERR, "ERROR: graphlcd/skin/function: No variable id given\n"); return false; } @@ -127,7 +130,8 @@ bool cSkinFunction::Parse(const std::string & Text) int num = strtol(ptr, &end, 10); if (end == ptr || *end != '\0') { - syslog(LOG_ERR, "ERROR: Invalid numeric value\n"); + // don't log this because when parsing a string starting with a digit (eg: 0%) this may result in a load of false positives + //syslog(LOG_ERR, "ERROR: Invalid numeric value (%s)\n", Text.c_str()); return false; } @@ -136,9 +140,17 @@ bool cSkinFunction::Parse(const std::string & Text) } else { + bool inToken = false; + // expression for (; *ptr; ++ptr) { + + if (*ptr == '{') + inToken = true; + else if (inToken && *ptr == '}') + inToken = false; + if (*ptr == '(') { if (inExpr++ == 0) @@ -156,17 +168,19 @@ bool cSkinFunction::Parse(const std::string & Text) if (Internals[i] == NULL) { - syslog(LOG_ERR, "ERROR: Unknown function %.*s", (int)(ptr - last), last); + if (!reparse) // only log this error when not reparsing + syslog(LOG_ERR, "ERROR: graphlcd/skin/function: Unknown function %.*s", (int)(ptr - last), last); return false; } last = ptr + 1; } } - else if (*ptr == ',' || *ptr == ')') + else if ( ( (!inToken) && (*ptr == ',') ) || *ptr == ')') { if (inExpr == 0) { - syslog(LOG_ERR, "ERROR: Unmatched '%c' in expression", *ptr); + if (!reparse) // only log this error when not reparsing + syslog(LOG_ERR, "ERROR: graphlcd/skin/function: Unmatched '%c' in expression (%s)", *ptr, Text.c_str()); return false; } @@ -181,8 +195,9 @@ bool cSkinFunction::Parse(const std::string & Text) if (mNumParams == MAXPARAMETERS) { - syslog(LOG_ERR, "ERROR: Too many parameters to function, maximum is %d", - MAXPARAMETERS); + if (!reparse) // only log this error when not reparsing + syslog(LOG_ERR, "ERROR: graphlcd/skin/function: Too many parameters to function, maximum is %d", + MAXPARAMETERS); return false; } @@ -204,6 +219,7 @@ bool cSkinFunction::Parse(const std::string & Text) params = -1; break; + case fun_equal: case fun_eq: case fun_ne: case fun_gt: @@ -247,13 +263,17 @@ bool cSkinFunction::Parse(const std::string & Text) params = 1; break; + case funQueryFeature: + params = 1; + break; + default: break; } if (params != -1 && mNumParams != (uint32_t) params) { - syslog(LOG_ERR, "ERROR: Text2Skin: Wrong number of parameters to %s, " + syslog(LOG_ERR, "ERROR: graphlcd/skin/function: Wrong number of parameters to %s, " "expecting %d", Internals[mType - INTERNAL], params); return false; } @@ -266,7 +286,9 @@ bool cSkinFunction::Parse(const std::string & Text) if (inExpr > 0) { - syslog(LOG_ERR, "ERROR: Expecting ')' in expression"); + // only log this error when not reparsing + if (!reparse) + syslog(LOG_ERR, "ERROR: Expecting ')' in expression"); return false; } } @@ -345,8 +367,21 @@ cType cSkinFunction::Evaluate(void) const case variable: { cSkinVariable * variable = mSkin->GetVariable(mVariableId); - if (variable) - return variable->Value(); + if (variable) { + cType rv = variable->Value(); + if (rv.IsString()) { + std::string val = rv; + if (val.find("{") != std::string::npos || val.find("#") != std::string::npos) { + cSkinString *result = new cSkinString(mObject, false); + if (result->Parse(val)) { + val = (std::string) result->Evaluate(); + rv = cType(val); + } + delete result; + } + } + return rv; + } return false; } @@ -369,23 +404,24 @@ cType cSkinFunction::Evaluate(void) const } return false; + case fun_equal: case fun_eq: - return mParams[0]->Evaluate() == mParams[1]->Evaluate(); + return (std::string) mParams[0]->Evaluate() == (std::string) mParams[1]->Evaluate(); case fun_ne: - return mParams[0]->Evaluate() != mParams[1]->Evaluate(); + return (std::string) mParams[0]->Evaluate() != (std::string) mParams[1]->Evaluate(); case fun_gt: - return mParams[0]->Evaluate() > mParams[1]->Evaluate(); + return (int) mParams[0]->Evaluate() > (int) mParams[1]->Evaluate(); case fun_lt: - return mParams[0]->Evaluate() < mParams[1]->Evaluate(); + return (int) mParams[0]->Evaluate() < (int) mParams[1]->Evaluate(); case fun_ge: - return mParams[0]->Evaluate() >= mParams[1]->Evaluate(); + return (int) mParams[0]->Evaluate() >= (int) mParams[1]->Evaluate(); case fun_le: - return mParams[0]->Evaluate() <= mParams[1]->Evaluate(); + return (int) mParams[0]->Evaluate() <= (int) mParams[1]->Evaluate(); case fun_file: return FunFile(mParams[0]->Evaluate()); @@ -443,9 +479,18 @@ cType cSkinFunction::Evaluate(void) const case funImageHeight: return FunImage(mType, mParams[0]->Evaluate()); + case funQueryFeature: { + int value; + if (mSkin->Config().GetDriver()->GetFeature((const std::string)(mParams[0]->Evaluate()), value)) { + return (value) ? true : false; + } else { + return false; + } + } + default: //Dprintf("unknown function code\n"); - syslog(LOG_ERR, "ERROR: Unknown function code called (this shouldn't happen)"); + syslog(LOG_ERR, "ERROR: graphlcd/skin/function: Unknown function code called (this shouldn't happen)"); break; } return false; diff --git a/glcdskin/function.h b/glcdskin/function.h index 4b3732f..16bcfb7 100644 --- a/glcdskin/function.h +++ b/glcdskin/function.h @@ -47,6 +47,7 @@ public: fun_not = INTERNAL, fun_and, fun_or, + fun_equal, fun_eq, fun_gt, fun_lt, @@ -70,7 +71,9 @@ public: funFontTextHeight, funImageWidth, - funImageHeight + funImageHeight, + + funQueryFeature }; private: @@ -94,7 +97,7 @@ public: cSkinFunction(const cSkinFunction &Src); ~cSkinFunction(); - bool Parse(const std::string &Text); + bool Parse(const std::string &Text, bool reparse = false); cType Evaluate(void) const; void SetListIndex(int MaxItems, int Index); diff --git a/glcdskin/object.c b/glcdskin/object.c index ab00a44..796e436 100644 --- a/glcdskin/object.c +++ b/glcdskin/object.c @@ -4,6 +4,8 @@ #include "cache.h" #include "function.h" +#include <typeinfo> + namespace GLCD { @@ -19,18 +21,27 @@ static const std::string ObjectNames[] = "text", "scrolltext", "scrollbar", + "button", "block", "list", - "item" + "item", + "condblock" }; cSkinObject::cSkinObject(cSkinDisplay * Parent) : mDisplay(Parent), mSkin(Parent->Skin()), mType((eType) __COUNT_OBJECT__), - mPos1(0, 0), - mPos2(-1, -1), - mColor(GLCD::clrBlack), + //mPos1(0, 0), + //mPos2(-1, -1), + mX1(this, false), + mY1(this, false), + mX2(this, false), + mY2(this, false), + mWidth(this, false), + mHeight(this, false), + mColor(this, cColor(cColor::Black)), + mBackgroundColor(this, cColor(cColor::Transparent)), mFilled(false), mRadius(0), mArc(0), @@ -41,13 +52,20 @@ cSkinObject::cSkinObject(cSkinDisplay * Parent) mPath(this, false), mCurrent(this, false), mTotal(this, false), + mPeak(this, false), mFont(this, false), mText(this, false), mCondition(NULL), + mEffect(tfxNone), + mEffectColor(this, cColor(cColor::White)), + mPeakGradientColor(this, cColor(cColor::ERRCOL)), // color for peak or gradient; if ERRCOL -> use mColor + mGradient(tgrdNone), // default: no gradient mLastChange(0), mChangeDelay(-1), // delay between two images frames: -1: not animated / don't care mStoredImagePath(""), mImageFrameId(0), // start with 1st frame + mOpacity(255), // default: full opacity + mScale(tscNone), // scale image: default: don't scale mScrollLoopMode(-1), // scroll (text) or loop (image) mode: default (-1) mScrollLoopReached(false), // if scroll/loop == once: already looped once? mScrollSpeed(0), // scroll speed: default (0) @@ -56,17 +74,29 @@ cSkinObject::cSkinObject(cSkinDisplay * Parent) mCurrText(""), // current text (for checks if text has changed) mAltText(""), // alternative text source for text-objects mAltCondition(NULL), // condition when alternative sources are used + mAction(""), // action (e.g. touchscreen action) + mMultilineScrollPosition(0), + mMultilineRelScroll(this, false), mObjects(NULL) { + mColor.SetColor(Parent->Skin()->Config().GetDriver()->GetForegroundColor()); + mBackgroundColor.SetColor(Parent->Skin()->Config().GetDriver()->GetBackgroundColor()); } cSkinObject::cSkinObject(const cSkinObject & Src) : mDisplay(Src.mDisplay), mSkin(Src.mSkin), mType(Src.mType), - mPos1(Src.mPos1), - mPos2(Src.mPos2), + //mPos1(Src.mPos1), + //mPos2(Src.mPos2), + mX1(Src.mX1), + mY1(Src.mY1), + mX2(Src.mX2), + mY2(Src.mY2), + mWidth(Src.mWidth), + mHeight(Src.mHeight), mColor(Src.mColor), + mBackgroundColor(Src.mBackgroundColor), mFilled(Src.mFilled), mRadius(Src.mRadius), mArc(Src.mArc), @@ -77,13 +107,20 @@ cSkinObject::cSkinObject(const cSkinObject & Src) mPath(Src.mPath), mCurrent(Src.mCurrent), mTotal(Src.mTotal), + mPeak(Src.mPeak), mFont(Src.mFont), mText(Src.mText), mCondition(Src.mCondition), + mEffect(Src.mEffect), + mEffectColor(Src.mEffectColor), + mPeakGradientColor(Src.mPeakGradientColor), + mGradient(Src.mGradient), mLastChange(0), mChangeDelay(-1), mStoredImagePath(Src.mStoredImagePath), mImageFrameId(0), + mOpacity(Src.mOpacity), + mScale(Src.mScale), mScrollLoopMode(Src.mScrollLoopMode), mScrollLoopReached(Src.mScrollLoopReached), mScrollSpeed(Src.mScrollSpeed), @@ -92,6 +129,9 @@ cSkinObject::cSkinObject(const cSkinObject & Src) mCurrText(Src.mCurrText), mAltText(Src.mAltText), mAltCondition(Src.mAltCondition), + mAction(Src.mAction), + mMultilineScrollPosition(Src.mMultilineScrollPosition), + mMultilineRelScroll(Src.mMultilineRelScroll), mObjects(NULL) { if (Src.mObjects) @@ -116,14 +156,27 @@ bool cSkinObject::ParseType(const std::string & Text) return false; } -bool cSkinObject::ParseColor(const std::string & Text) +bool cSkinObject::ParseColor(const std::string & Text, cSkinColor & ParamColor) { - if (Text == "white") - mColor = GLCD::clrWhite; - else if (Text == "black") - mColor = GLCD::clrBlack; - else + std::string text = (std::string) Text; + cColor color = cColor::ERRCOL; + if (text[0] == '#') { + cSkinVariable * variable = mSkin->GetVariable(text.substr(1)); + if (variable) { + color = cColor::ParseColor(variable->Value().String()); + if (color == cColor::ERRCOL) { + return false; + } + ParamColor.SetVarId(text.substr(1)); + return true; + } return false; + } + color = cColor::ParseColor(text); + if (color == cColor::ERRCOL) { + return false; + } + ParamColor.SetColor(color); return true; } @@ -165,6 +218,51 @@ bool cSkinObject::ParseVerticalAlignment(const std::string & Text) return true; } +bool cSkinObject::ParseEffect(const std::string & Text) +{ + if (Text == "none") + mEffect = tfxNone; + else if (Text == "shadow") + mEffect = tfxShadow; + else if (Text == "outline") + mEffect = tfxOutline; + else + return false; + return true; +} + +bool cSkinObject::ParseScale(const std::string & Text) +{ + if (Text == "none") + mScale = tscNone; + else if (Text == "auto") + mScale = tscAuto; + else if (Text == "autox") + mScale = tscAutoX; + else if (Text == "autoy") + mScale = tscAutoY; + else if (Text == "fill") + mScale = tscFill; + else + return false; + return true; +} + +bool cSkinObject::ParseGradient(const std::string & Text) +{ + if (Text == "none") + mGradient = tgrdNone; + else if (Text == "total" || Text == "default") + mGradient = tgrdTotal; + else if (Text == "current" || Text == "currentonly") + mGradient = tgrdCurrent; + else if (Text == "vertical") + mGradient = tgrdVertical; + else + return false; + return true; +} + bool cSkinObject::ParseIntParam(const std::string &Text, int & Param) { if (isalpha(Text[0]) || Text[0] == '#') @@ -189,6 +287,7 @@ bool cSkinObject::ParseIntParam(const std::string &Text, int & Param) return true; } +#if 0 bool cSkinObject::ParseWidth(const std::string &Text) { int w; @@ -214,6 +313,7 @@ bool cSkinObject::ParseHeight(const std::string &Text) } return false; } +#endif bool cSkinObject::ParseFontFace(const std::string & Text) { @@ -292,16 +392,33 @@ const std::string & cSkinObject::TypeName(void) const tPoint cSkinObject::Pos(void) const { - return tPoint(mPos1.x < 0 ? mSkin->BaseSize().w + mPos1.x : mPos1.x, - mPos1.y < 0 ? mSkin->BaseSize().h + mPos1.y : mPos1.y); + int x1 = mX1.Evaluate(); + int y1 = mY1.Evaluate(); + return tPoint(x1 < 0 ? mSkin->BaseSize().w + x1 : x1, + y1 < 0 ? mSkin->BaseSize().h + y1 : y1); } tSize cSkinObject::Size(void) const { - tPoint p1(mPos1.x < 0 ? mSkin->BaseSize().w + mPos1.x : mPos1.x, - mPos1.y < 0 ? mSkin->BaseSize().h + mPos1.y : mPos1.y); - tPoint p2(mPos2.x < 0 ? mSkin->BaseSize().w + mPos2.x : mPos2.x, - mPos2.y < 0 ? mSkin->BaseSize().h + mPos2.y : mPos2.y); + int x1 = mX1.Evaluate(); + int y1 = mY1.Evaluate(); + tPoint p1(x1 < 0 ? mSkin->BaseSize().w + x1 : x1, + y1 < 0 ? mSkin->BaseSize().h + y1 : y1); + + int w = mWidth.Evaluate(); + int h = mHeight.Evaluate(); + + int x2 = mX2.Evaluate(); + if (w != 0 && x2 == -1) { + x2 = x1 + w - 1; + } + int y2 = mY2.Evaluate(); + if (h != 0 && y2 == -1) { + y2 = y1 + h - 1; + } + + tPoint p2((x2 < 0) ? mSkin->BaseSize().w + x2 : x2, + (y2 < 0) ? mSkin->BaseSize().h + y2 : y2); return tSize(p2.x - p1.x + 1, p2.y - p1.y + 1); } @@ -332,7 +449,47 @@ void cSkinObject::Render(GLCD::cBitmap * screen) mChangeDelay = -1; } - GLCD::cImage * image = cache->Get(evalPath); + uint16_t scalew = 0; + uint16_t scaleh = 0; + + switch (mScale) { + case tscAuto: + { + uint16_t w_temp = 0; + uint16_t h_temp = 0; + // get dimensions of unscaled image + GLCD::cImage * image = cache->Get(evalPath, w_temp, h_temp); + if (image) { + w_temp = image->Width(); + h_temp = image->Height(); + if (w_temp != Size().w || h_temp != Size().h) { + double fw = (double)Size().w / (double)w_temp; + double fh = (double)Size().h / (double)h_temp; + if (fw < fh) { + scalew = Size().w; + } else { + scaleh = Size().h; + } + } + } + } + break; + case tscAutoX: + scalew = Size().w; + break; + case tscAutoY: + scaleh = Size().h; + break; + case tscFill: + scalew = Size().w; + scaleh = Size().h; + break; + default: + scalew = 0; + scaleh = 0; + } + + GLCD::cImage * image = cache->Get(evalPath, scalew, scaleh); if (image) { int framecount = image->Count(); @@ -341,7 +498,20 @@ void cSkinObject::Render(GLCD::cBitmap * screen) if (bitmap) { - screen->DrawBitmap(Pos().x, Pos().y, *bitmap, mColor); + uint16_t xoff = 0; + uint16_t yoff = 0; + if (scalew || scaleh) { + if (image->Width() < (uint16_t)Size().w) { + xoff = (Size().w - image->Width() ) / 2; + } else if (image->Height() < (uint16_t)Size().h) { + yoff = (Size().h - image->Height() ) / 2; + } + } + + if (mColor == cColor::ERRCOL) + screen->DrawBitmap(Pos().x + xoff, Pos().y + yoff, *bitmap); + else + screen->DrawBitmap(Pos().x + xoff, Pos().y + yoff, *bitmap, mColor, mBackgroundColor, mOpacity); } if (mScrollLoopMode != -1) // if == -1: currScrollLoopMode already contains correct value @@ -350,7 +520,7 @@ void cSkinObject::Render(GLCD::cBitmap * screen) if (framecount > 1 && currScrollLoopMode > 0 && !mScrollLoopReached) { mChangeDelay = image->Delay(); - if ( (int)(timestamp - mLastChange) >= mChangeDelay) { + if ( (uint32_t)(timestamp - mLastChange) >= (uint32_t)mChangeDelay) { if (currScrollLoopMode == 1 && mImageFrameId+1 == framecount) { mScrollLoopReached = true; // stop looping and switch to 1st frame @@ -405,33 +575,135 @@ void cSkinObject::Render(GLCD::cBitmap * screen) { int current = mCurrent.Evaluate(); int total = mTotal.Evaluate(); + int peak = mPeak.Evaluate(); if (total == 0) total = 1; if (current > total) current = total; - if (mDirection == 0) - { - int w = Size().w * current / total; - if (w > 0) - screen->DrawRectangle(Pos().x, Pos().y, Pos().x + w - 1, Pos().y + Size().h - 1, mColor, true); - } - else if (mDirection == 1) - { - int h = Size().h * current / total; - if (h > 0) - screen->DrawRectangle(Pos().x, Pos().y, Pos().x + Size().w - 1, Pos().y + h - 1, mColor, true); - } - else if (mDirection == 2) - { - int w = Size().w * current / total; - if (w > 0) - screen->DrawRectangle(Pos().x + Size().w - w, Pos().y, Pos().x + Size().w - 1, Pos().y + Size().h - 1, mColor, true); + if (peak > total) + peak = total; + + int maxSize = ( (mDirection % 2) == 0 ) ? Size().w : Size().h; + int currSize = maxSize * current / total; + + int peakSize = 0; + int peakBarSize = 2; + uint32_t peakGradientColor = (mPeakGradientColor == cColor::ERRCOL) ? mColor : mPeakGradientColor; + + bool gradient = false; + + if (peakGradientColor != mColor) { + if (mGradient != tgrdNone) { + gradient = true; + } else if (peak > 0) { + peakSize = maxSize * peak / total; + if (mRadius <= 0) { + peakBarSize = maxSize / 20; + if (peakBarSize < 2) + peakBarSize = 2; + } else { + peakBarSize = mRadius; + } + // at least peakBarSize of empty space between normal progress bar and peak marker. if too small: don't show peak marker + if (currSize + peakBarSize + (peakBarSize / 2) >= peakSize) + peakSize = 0; // don't show at all + } } - else if (mDirection == 3) - { - int h = Size().h * current / total; - if (h > 0) - screen->DrawRectangle(Pos().x, Pos().y + Size().h - h, Pos().x + Size().w - 1, Pos().y + Size().h - 1, mColor, true); + + if (! gradient) { + if (mDirection == 0) + { + if (currSize > 0) + screen->DrawRectangle(Pos().x , Pos().y, + Pos().x + currSize - 1, Pos().y + Size().h - 1, mColor, true); + if (peakSize > 0) + screen->DrawRectangle(Pos().x + peakSize-1 , Pos().y, + Pos().x + peakSize-1 + peakBarSize-1, Pos().y + Size().h - 1, peakGradientColor, true); + } + else if (mDirection == 1) + { + if (currSize > 0) + screen->DrawRectangle(Pos().x , Pos().y, + Pos().x + Size().w - 1, Pos().y + currSize - 1, mColor, true); + if (peakSize > 0) + screen->DrawRectangle(Pos().x , Pos().y + peakSize-1, + Pos().x + Size().w - 1, Pos().y + peakSize-1 + peakBarSize-1, peakGradientColor, true); + } + else if (mDirection == 2) + { + if (currSize > 0) + screen->DrawRectangle(Pos().x + Size().w - currSize, Pos().y, + Pos().x + Size().w - 1 , Pos().y + Size().h - 1, mColor, true); + if (peakSize > 0) + screen->DrawRectangle(Pos().x + Size().w + maxSize - peakSize , Pos().y, + Pos().x + maxSize - peakSize + peakBarSize-1, Pos().y + Size().h - 1, peakGradientColor, true); + } + else if (mDirection == 3) + { + if (currSize > 0) + screen->DrawRectangle(Pos().x , Pos().y + Size().h - currSize, + Pos().x + Size().w - 1, Pos().y + Size().h - 1 , mColor, true); + if (peakSize > 0) + screen->DrawRectangle(Pos().x , Pos().y + maxSize - peakSize, + Pos().x + Size().w - 1, Pos().y + maxSize - peakSize + peakBarSize-1, peakGradientColor, true); + } + } else { + if (currSize > 0) { + int s_a = (mColor & 0xFF000000) >> 24; + int s_r = (mColor & 0x00FF0000) >> 16; + int s_g = (mColor & 0x0000FF00) >> 8; + int s_b = (mColor & 0x000000FF) ; + int delta_a = ((peakGradientColor & 0xFF000000) >> 24) - s_a; + int delta_r = ((peakGradientColor & 0x00FF0000) >> 16) - s_r; + int delta_g = ((peakGradientColor & 0x0000FF00) >> 8) - s_g; + int delta_b = ((peakGradientColor & 0x000000FF) ) - s_b; + int c_a, c_r, c_g, c_b; + double fact; + uint32_t currCol; + int gradSize = 0; + switch (mGradient) { + case tgrdCurrent: gradSize = currSize; break; + case tgrdVertical: gradSize = (mDirection % 2 == 0) ? Size().h : Size().w ; break; + default: gradSize = maxSize; break; + } + + for (int i = 0; i < ((mGradient == tgrdVertical) ? gradSize : currSize); i++) { + fact = (double)i / (double)(gradSize - 1); + c_a = s_a + int( double(delta_a) * fact ); + c_r = s_r + int( double(delta_r) * fact ); + c_g = s_g + int( double(delta_g) * fact ); + c_b = s_b + int( double(delta_b) * fact ); + currCol = (c_a << 24) | (c_r << 16) | (c_g << 8) | c_b; + //fprintf(stderr, "i: %d / %08x -> %08x / currCol: %08x\n", i, (uint32_t)mColor, peakGradientColor, currCol); + if (mGradient == tgrdVertical) { + if (mDirection == 0) + screen->DrawLine(Pos().x, Pos().y + i, + Pos().x + currSize - 1, Pos().y + i, currCol); + else if (mDirection == 2) + screen->DrawLine(Pos().x + Size().w - currSize, Pos().y + i, + Pos().x + Size().w - 1, Pos().y + i, currCol); + else if (mDirection == 1) + screen->DrawLine(Pos().x + Size().w - 1 - i, Pos().y, + Pos().x + Size().w - 1 - i, Pos().y + currSize - 1, currCol); + else if (mDirection == 3) + screen->DrawLine(Pos().x + i, Pos().y + Size().h - currSize, + Pos().x + i, Pos().y + Size().h - 1 , currCol); + } else { + if (mDirection == 0) + screen->DrawLine(Pos().x + i, Pos().y, + Pos().x + i, Pos().y + Size().h - 1, currCol); + else if (mDirection == 2) + screen->DrawLine(Pos().x + Size().w - 1 - i, Pos().y, + Pos().x + Size().w - 1 - i, Pos().y + Size().h - 1, currCol); + else if (mDirection == 1) + screen->DrawLine(Pos().x , Pos().y + i, + Pos().x + Size().w - 1, Pos().y + i, currCol); + else if (mDirection == 3) + screen->DrawLine(Pos().x , Pos().y + Size().h - 1 - i, + Pos().x + Size().w - 1, Pos().y + Size().h - 1 - i , currCol); + } + } + } } break; } @@ -462,8 +734,48 @@ void cSkinObject::Render(GLCD::cBitmap * screen) currScrollTime = (int)(t); } + // amount of loops for effects (no effect: 1 loop) + int loop; + int loops = 1; + int varx[6] = {0, 0, 0, 0, 0, 0}; + int vary[6] = {0, 0, 0, 0, 0, 0}; + uint32_t varcol[6] = { mColor, mColor, mColor, mColor, mColor, mColor }; + + int fxOff = 1; + if (mRadius > 1) + fxOff = 2; + + switch (mEffect) { + case tfxShadow: + loops = 1; + for (int fxi = 0; fxi < fxOff; fxi++) { + varx[fxi] = fxi + 1; vary[fxi] = fxi + 1; + varcol[loops-1] = mEffectColor; + loops++; + } + varcol[loops-1] = cColor::Transparent; + loops++; + break; + case tfxOutline: + loops = 6; + varx[0] = -fxOff; vary[0] = 0; + varx[1] = fxOff; vary[1] = 0; + varx[2] = 0; vary[2] = -fxOff; + varx[3] = 0; vary[3] = fxOff; + varcol[0] = varcol[1] = varcol[2] = varcol[3] = mEffectColor; + varcol[4] = cColor::Transparent; + break; + case tfxNone: // no-one gets forgotten here, so make g++ happy + default: + loops = 1; + } + if (skinFont) { + + cBitmap* pane = new cBitmap(Size().w, Size().h, cColor::Transparent); + pane->SetProcessAlpha(false); + const cFont * font = skinFont->Font(); std::string text = ""; @@ -484,6 +796,7 @@ void cSkinObject::Render(GLCD::cBitmap * screen) mCurrText = text; mScrollLoopReached = false; mLastChange = timestamp; + mMultilineScrollPosition = 0; } if (mMultiline) @@ -492,7 +805,24 @@ void cSkinObject::Render(GLCD::cBitmap * screen) mScrollLoopReached = true; // avoid check in NeedsUpdate() std::vector <std::string> lines; - font->WrapText(Size().w, Size().h, text, lines); + font->WrapText(Size().w, 0/*Size().h*/, text, lines); + + size_t amount_lines = Size().h / font->LineHeight(); + + if (amount_lines < lines.size()) { + int multilineRelScroll = mMultilineRelScroll.Evaluate(); + if (multilineRelScroll != 0) { + if (multilineRelScroll < 0) { + mMultilineScrollPosition += multilineRelScroll; + if (mMultilineScrollPosition < 0) + mMultilineScrollPosition = 0; + } else if (multilineRelScroll > 0) { + mMultilineScrollPosition += multilineRelScroll; + if (mMultilineScrollPosition > (int)((lines.size() - amount_lines)) ) + mMultilineScrollPosition = lines.size() - amount_lines; + } + } + } // vertical alignment, calculate y offset int yoff = 0; @@ -507,10 +837,14 @@ void cSkinObject::Render(GLCD::cBitmap * screen) default: yoff = 0; } - for (size_t i = 0; i < lines.size(); i++) + int end_line = amount_lines; + if (amount_lines > lines.size() ) + end_line = lines.size(); + + for (size_t i = 0; i < (size_t)end_line; i++) { - int w = font->Width(lines[i]); - int x = Pos().x; + int w = font->Width(lines[i + mMultilineScrollPosition]); + int x = 0; if (w < Size().w) { if (mAlign == taRight) @@ -522,7 +856,12 @@ void cSkinObject::Render(GLCD::cBitmap * screen) x += (Size().w - w) / 2; } } - screen->DrawText(x, yoff + Pos().y + i * font->LineHeight(), x + Size().w - 1, lines[i], font, mColor); + for (loop = 0; loop < loops; loop++) { + pane->DrawText( + varx[loop] + x, vary[loop] + yoff + i * font->LineHeight(), + x + Size().w - 1, lines[i + mMultilineScrollPosition], font, varcol[loop], mBackgroundColor + ); + } } } else @@ -549,7 +888,7 @@ void cSkinObject::Render(GLCD::cBitmap * screen) std::string::size_type pos1; std::string::size_type pos2; std::string str; - int x = Pos().x; + int x = 0; int w = Size().w; int tab = 0; int tabWidth; @@ -560,7 +899,9 @@ void cSkinObject::Render(GLCD::cBitmap * screen) { str = text.substr(pos1, pos2 - pos1); tabWidth = mSkin->Config().GetTabPosition(tab, Size().w, *font); - screen->DrawText(x, yoff + Pos().y, x + tabWidth - 1, str, font, mColor); + for (loop = 0; loop < loops; loop++) { + pane->DrawText( varx[loop] + x, vary[loop] + yoff, x + tabWidth - 1, str, font, varcol[loop], mBackgroundColor ); + } pos1 = pos2 + 1; pos2 = text.find('\t', pos1); tabWidth += font->Width(' '); @@ -569,12 +910,14 @@ void cSkinObject::Render(GLCD::cBitmap * screen) tab++; } str = text.substr(pos1); - screen->DrawText(x, yoff + Pos().y, x + w - 1, str, font, mColor); + for (loop = 0; loop < loops; loop++) { + pane->DrawText( varx[loop] + x, vary[loop] + yoff, x + w - 1, str, font, varcol[loop], mBackgroundColor ); + } } else { int w = font->Width(text); - int x = Pos().x; + int x = 0; bool updateScroll = false; if (w < Size().w) @@ -601,7 +944,7 @@ void cSkinObject::Render(GLCD::cBitmap * screen) currScrollTime = mScrollTime; if (currScrollLoopMode > 0 && (!mScrollLoopReached || mScrollOffset) && - ((int)(timestamp-mLastChange) >= currScrollTime) + ((uint32_t)(timestamp-mLastChange) >= (uint32_t)currScrollTime) ) { if (mScrollLoopReached) @@ -615,22 +958,31 @@ void cSkinObject::Render(GLCD::cBitmap * screen) if (mScrollOffset) { int corr_scrolloffset = mScrollOffset; /* object update before scrolltime? use previous offset to avoid 'stumbling' scrolling */ - if ((int)(timestamp-mLastChange) < currScrollTime) { + if ((uint32_t)(timestamp-mLastChange) < (uint32_t)currScrollTime) { corr_scrolloffset -= currScrollSpeed; if (corr_scrolloffset < 0) corr_scrolloffset = 0; } w += font->Width(" "); std::string textdoubled = text + " " + text; - screen->DrawText(x, yoff + Pos().y, x + Size().w - 1, textdoubled, font, mColor, true, corr_scrolloffset); + for (loop = 0; loop < loops; loop++) { + pane->DrawText( + varx[loop] + x, vary[loop] + yoff, x + Size().w - 1, textdoubled, font, + varcol[loop], mBackgroundColor, true, corr_scrolloffset + ); + } } else { - screen->DrawText(x, yoff + Pos().y, x + Size().w - 1, text, font, mColor, true, mScrollOffset); + for (loop = 0; loop < loops; loop++) { + pane->DrawText( + varx[loop] + x, vary[loop] + yoff, x + Size().w - 1, text, font, + varcol[loop], mBackgroundColor, true, mScrollOffset + ); + } } if (updateScroll) { - mScrollOffset += currScrollSpeed; - - if ( x + Size().w + mScrollOffset >= (w+Size().w - font->Width(" "))) { + mScrollOffset += currScrollSpeed; + if ( mScrollOffset >= w ) { if (currScrollLoopMode == 1) // reset mScrollOffset in next step (else: string not redrawn when scroll done) mScrollLoopReached = true; @@ -642,6 +994,8 @@ void cSkinObject::Render(GLCD::cBitmap * screen) } } } + screen->DrawBitmap(Pos().x, Pos().y, *pane, cColor::White, cColor::Transparent); + delete pane; } break; } @@ -651,6 +1005,44 @@ void cSkinObject::Render(GLCD::cBitmap * screen) // Object->Align()); // break; + case cSkinObject::button: + { + cSkinFont * skinFont = mSkin->GetFont(mFont.Evaluate()); + + if (mBackgroundColor == mColor || mBackgroundColor == cColor::Transparent) + mBackgroundColor.SetColor( (cColor(mColor).Invert()) ); + + if (mRadius == 0) + screen->DrawRectangle(Pos().x, Pos().y, Pos().x + Size().w - 1, Pos().y + Size().h - 1, mBackgroundColor, true); + else + screen->DrawRoundRectangle(Pos().x, Pos().y, Pos().x + Size().w - 1, Pos().y + Size().h - 1, mBackgroundColor, true, mRadius); + + if (skinFont) + { + const cFont * font = skinFont->Font(); + std::string text = ""; + + text = (std::string) mText.Evaluate(); + + if (! (text == mCurrText) ) { + mCurrText = text; + } + std::vector <std::string> lines; + font->WrapText(Size().w, Size().h, text, lines); + + // always use middle vertical alignment for buttons + int diff = Size().h - lines.size() * font->LineHeight(); + int yoff = (diff > 0) ? diff >> 1 : 0; + + int w = font->Width(text); + int x = Pos().x; + if (w < Size().w) // always center alignment for buttons + x += (Size().w - w) / 2; + screen->DrawText(x, yoff + Pos().y, x + Size().w - 1, text, font, mColor, mBackgroundColor); + } + break; + } + case cSkinObject::scrollbar: //DrawScrollbar(Object->Pos(), Object->Size(), Object->Bg(), Object->Fg()); break; @@ -673,14 +1065,22 @@ void cSkinObject::Render(GLCD::cBitmap * screen) { for (int j = 1; j < (int) NumObjects(); j++) { + int px, py, ph,pw; + char buf[10]; const cSkinObject * o = GetObject(j); cSkinObject obj(*o); obj.SetListIndex(maxitems, i); if (obj.Condition() != NULL && !obj.Condition()->Evaluate()) continue; - obj.mPos1.x += mPos1.x; - obj.mPos1.y += mPos1.y + yoffset; - obj.mPos2.y += mPos1.y + yoffset; + px = obj.Pos().x + Pos().x; // obj.mPos1.x += mPos1.x; + py = obj.Pos().y + Pos().y + yoffset; // obj.mPos1.y += mPos1.y + yoffset; + ph = o->Size().h; // obj.mPos2.y += mPos1.y + yoffset; + pw = o->Size().w; + snprintf(buf, 9, "%d", px); obj.mX1.Parse((const char*)buf); + snprintf(buf, 9, "%d", py); obj.mY1.Parse((const char*)buf); + if (ph > 0) + snprintf(buf, 9, "%d", ph); obj.mHeight.Parse((const char*)buf); + snprintf(buf, 9, "%d", pw); obj.mWidth.Parse((const char*)buf); obj.Render(screen); } yoffset += itemheight; @@ -710,7 +1110,7 @@ bool cSkinObject::NeedsUpdate(uint64_t CurrentTime) currScrollLoopMode = mScrollLoopMode; if ( mChangeDelay > 0 && currScrollLoopMode > 0 && !mScrollLoopReached && - ( (int)(CurrentTime-mLastChange) >= mChangeDelay) + ( (uint32_t)(CurrentTime-mLastChange) >= (uint32_t)mChangeDelay) ) { return true; @@ -720,6 +1120,7 @@ bool cSkinObject::NeedsUpdate(uint64_t CurrentTime) } case cSkinObject::text: case cSkinObject::scrolltext: + //case cSkinObject::button: { int currScrollLoopMode = 1; // default values if no setup default values available int currScrollTime = 500; @@ -759,7 +1160,7 @@ bool cSkinObject::NeedsUpdate(uint64_t CurrentTime) if ( (text != mCurrText) || ( (currScrollLoopMode > 0) && (!mScrollLoopReached || mScrollOffset) && - ((int)(CurrentTime-mLastChange) >= currScrollTime) + ((uint32_t)(CurrentTime-mLastChange) >= (uint32_t)currScrollTime) ) ) { @@ -788,6 +1189,71 @@ bool cSkinObject::NeedsUpdate(uint64_t CurrentTime) } +std::string cSkinObject::CheckAction(cGLCDEvent * ev) +{ + if (mCondition != NULL && !mCondition->Evaluate()) + return ""; + + switch (Type()) + { + case cSkinObject::image: + case cSkinObject::text: + case cSkinObject::scrolltext: + case cSkinObject::progress: + case cSkinObject::rectangle: + case cSkinObject::ellipse: + case cSkinObject::slope: + case cSkinObject::button: + case cSkinObject::item: + { + if (mAction == "") + return ""; + + if (ev && (typeid(*ev) == typeid(cTouchEvent))) { + cTouchEvent * stev = (cTouchEvent*)ev; + // check if touch event is in bounding box of object + // uses > and < -1 instead of >= and < -0 for better results + if ( (stev->x > Pos().x) && (stev->x < (Pos().x+Size().w -1)) && + (stev->y > Pos().y) && (stev->y < (Pos().y+Size().h -1)) + ) + { + return mAction; + } + } + return ""; + break; + } + case cSkinObject::block: + { + std::string rv = ""; + for (uint32_t i = 0; i < NumObjects(); i++) { + if ( (rv = GetObject(i)->CheckAction(ev)) != "" ) { + return rv; + } + } + return ""; + break; + } + default: + return ""; + } + return ""; +} + + + +uint32_t cSkinColor::GetColor(void) { + if (mVarId != "") { + cSkinVariable * variable = mObject->Skin()->GetVariable(mVarId); + if (variable) { + return cColor::ParseColor(variable->Value().String()); + } + return cColor::ERRCOL; + } + return (uint32_t) mColor; +} + + cSkinObjects::cSkinObjects(void) { } diff --git a/glcdskin/object.h b/glcdskin/object.h index 0dd996d..cb2f180 100644 --- a/glcdskin/object.h +++ b/glcdskin/object.h @@ -24,6 +24,8 @@ #include "type.h" #include "string.h" +#include <glcddrivers/driver.h> + namespace GLCD { @@ -58,6 +60,54 @@ enum eTextVerticalAlignment tvaBottom }; +enum eEffect +{ + tfxNone, + tfxShadow, + tfxOutline +}; + +enum eScale +{ + tscNone, + tscAuto, + tscAutoX, + tscAutoY, + tscFill +}; + +enum eGradient +{ + tgrdNone, + tgrdTotal, + tgrdCurrent, + tgrdVertical +}; + + + +class cSkinColor +{ +private: + cSkinObject * mObject; + uint32_t mColor; + std::string mVarId; +public: + cSkinColor(cSkinObject *Parent, uint32_t color):mVarId("") { mObject = Parent; mColor = color; } + cSkinColor(cSkinObject *Parent, cColor color):mVarId("") { mObject = Parent; mColor = (uint32_t)color; } + cSkinColor(cSkinObject *Parent, const std::string varId) { mObject = Parent; mVarId = varId; } + ~cSkinColor() {}; + + void SetColor(uint32_t color) { mVarId = ""; mColor = color; } + void SetColor(cColor color) { mVarId = ""; mColor = (uint32_t)color; } + void SetVarId(const std::string varId) { mVarId = varId; } + + uint32_t GetColor(void); + + operator uint32_t(void) { return GetColor(); } +}; + + class cSkinObject { friend bool StartElem(const std::string & name, std::map<std::string,std::string> & attrs); @@ -77,6 +127,7 @@ public: text, scrolltext, scrollbar, + button, block, list, item, @@ -87,9 +138,16 @@ private: cSkinDisplay * mDisplay; // parent display cSkin * mSkin; eType mType; // type of object, one of enum eType - tPoint mPos1; - tPoint mPos2; - eColor mColor; + //tPoint mPos1; + //tPoint mPos2; + cSkinString mX1; // either mX1 and mWidth or mX1 and mX2 are defined. not all three + cSkinString mY1; + cSkinString mX2; + cSkinString mY2; + cSkinString mWidth; + cSkinString mHeight; + cSkinColor mColor; + cSkinColor mBackgroundColor; bool mFilled; int mRadius; int mArc; @@ -98,11 +156,16 @@ private: eTextVerticalAlignment mVerticalAlign; bool mMultiline; cSkinString mPath; - cSkinString mCurrent; - cSkinString mTotal; + cSkinString mCurrent; // progress bar: current value + cSkinString mTotal; // progress bar: maximum valid value + cSkinString mPeak; // progress bar: peak value (<= 0: disabled) cSkinString mFont; cSkinString mText; cSkinFunction * mCondition; + eEffect mEffect; // effect: none, shadow, or outline + cSkinColor mEffectColor; // effect colour (= shadow colour or colour of outline) + cSkinColor mPeakGradientColor; // colour of peak marker or gradient color (mutual exclusive) + eGradient mGradient; // use gradient effect for progress bar (overrules peak!) uint64_t mLastChange; // timestamp: last change in dynamic object (scroll, frame change, ...) int mChangeDelay; // delay between two changes (frame change, scrolling, ...) @@ -111,6 +174,8 @@ private: std::string mStoredImagePath; // stored image path int mImageFrameId; // frame ID of image + int mOpacity; // opacity of an image ([0, 255], default 255) + eScale mScale; // image scaling (['none', 'autox', 'autoy', 'fill'], default: none) int mScrollLoopMode; // scroll (text) or loop (image) mode: -1: default, 0: never, 1: once, 2: always bool mScrollLoopReached; // if scroll/loop == once: already looped once? @@ -121,6 +186,10 @@ private: std::string mAltText; // alternative text source for text-objects cSkinFunction * mAltCondition; // condition when alternative sources are used + std::string mAction; // action attached to this object + + int mMultilineScrollPosition; // current scolling position of mMultiline + cSkinString mMultilineRelScroll;// relative scrolling amount of mMultiline (default: 0) cSkinObjects * mObjects; // used for block objects such as <list> @@ -130,14 +199,17 @@ public: ~cSkinObject(); bool ParseType(const std::string &Text); - bool ParseColor(const std::string &Text); + bool ParseColor(const std::string &Text, cSkinColor & ParamColor); bool ParseCondition(const std::string &Text); bool ParseAlignment(const std::string &Text); bool ParseVerticalAlignment(const std::string &Text); + bool ParseEffect(const std::string &Text); + bool ParseScale(const std::string &Text); + bool ParseGradient(const std::string &Text); bool ParseFontFace(const std::string &Text); bool ParseIntParam(const std::string &Text, int & Param); - bool ParseWidth(const std::string &Text); - bool ParseHeight(const std::string &Text); + //bool ParseWidth(const std::string &Text); + //bool ParseHeight(const std::string &Text); bool ParseScrollLoopMode(const std::string & Text); // parse scroll mode ([never|once|always]) bool ParseScrollSpeed(const std::string & Text); // parse scroll speed @@ -164,6 +236,8 @@ public: // check if update is required for dynamic objects (image, text, progress, pane) // false: no update required, true: update required bool NeedsUpdate(uint64_t CurrentTime); + + std::string CheckAction(cGLCDEvent * ev); }; class cSkinObjects: public std::vector<cSkinObject *> diff --git a/glcdskin/parser.c b/glcdskin/parser.c index d42cfb8..5aa5515 100644 --- a/glcdskin/parser.c +++ b/glcdskin/parser.c @@ -16,28 +16,33 @@ #include <vector> #include <string> +#include <clocale> + #include "parser.h" #include "xml.h" #include "skin.h" +/* workaround for thread-safe parsing */ +#include <pthread.h> + namespace GLCD { #define TAG_ERR_REMAIN(_context) do { \ - syslog(LOG_ERR, "ERROR: graphlcd/skin: Unexpected tag %s within %s", \ - name.c_str(), _context); \ + errorDetail = "Unexpected tag "+name+" within "+ _context; \ + syslog(LOG_ERR, "ERROR: graphlcd/skin: %s", errorDetail.c_str()); \ return false; \ } while (0) #define TAG_ERR_CHILD(_context) do { \ - syslog(LOG_ERR, "ERROR: graphlcd/skin: No child tag %s expected within %s", \ - name.c_str(), _context); \ + errorDetail = "No child tag "+name+" expected within "+ _context; \ + syslog(LOG_ERR, "ERROR: graphlcd/skin: %s", errorDetail.c_str()); \ return false; \ } while (0) #define TAG_ERR_END(_context) do { \ - syslog(LOG_ERR, "ERROR: graphlcd/skin: Unexpected closing tag for %s within %s", \ - name.c_str(), _context); \ + errorDetail = "Unexpected closing tag for "+name+" within "+ _context; \ + syslog(LOG_ERR, "ERROR: graphlcd/skin: %s", errorDetail.c_str()); \ return false; \ } while (0) @@ -49,8 +54,8 @@ namespace GLCD #define ATTRIB_MAN_STRING(_attr,_target) \ ATTRIB_OPT_STRING(_attr,_target) \ else { \ - syslog(LOG_ERR, "ERROR: graphlcd/skin: Mandatory attribute %s missing in tag %s", \ - _attr, name.c_str()); \ + errorDetail = "Mandatory attribute "+ (std::string)_attr +" missing in tag "+ name; \ + syslog(LOG_ERR, "ERROR: graphlcd/skin: %s", errorDetail.c_str()); \ return false; \ } @@ -59,8 +64,8 @@ namespace GLCD char *_e; const char *_t = attrs[_attr].c_str(); \ long _l = strtol(_t, &_e, 10); \ if (_e ==_t || *_e != '\0') { \ - syslog(LOG_ERR, "ERROR: graphlcd/skin: Invalid numeric value \"%s\" in attribute %s", \ - _t, _attr); \ + errorDetail = "Invalid numeric value \""+ (std::string)_t +"\" in attribute "+ (std::string)_attr; \ + syslog(LOG_ERR, "ERROR: graphlcd/skin: %s", errorDetail.c_str()); \ return false; \ } else \ _target = _l; \ @@ -69,8 +74,8 @@ namespace GLCD #define ATTRIB_MAN_NUMBER(_attr,_target) \ ATTRIB_OPT_NUMBER(_attr,_target) \ else { \ - syslog(LOG_ERR, "ERROR: graphlcd/skin: Mandatory attribute %s missing in tag %s", \ - _attr, name.c_str()); \ + errorDetail = "Mandatory attribute "+ (std::string)_attr +" missing in tag "+name; \ + syslog(LOG_ERR, "ERROR: graphlcd/skin: %s", errorDetail.c_str()); \ return false; \ } @@ -81,8 +86,8 @@ namespace GLCD else if (attrs[_attr] == "no") \ _target = false; \ else { \ - syslog(LOG_ERR, "ERROR: graphlcd/skin: Invalid boolean value \"%s\" in attribute %s", \ - attrs[_attr].c_str(), _attr); \ + errorDetail = "Invalid boolean value \""+ attrs[_attr] +"\" in attribute "+ _attr; \ + syslog(LOG_ERR, "ERROR: graphlcd/skin: %s", errorDetail.c_str()); \ return false; \ } \ } @@ -90,16 +95,16 @@ namespace GLCD #define ATTRIB_MAN_BOOL(_attr,_target) \ ATTRIB_OPT_BOOL(_attr,_target) \ else { \ - syslog(LOG_ERR, "ERROR: graphlcd/skin: Mandatory attribute %s missing in tag %s", \ - _attr, name.c_str()); \ + errorDetail = "Mandatory attribute "+ (std::string)_attr +" missing in tag "+name; \ + syslog(LOG_ERR, "ERROR: graphlcd/skin: %s", errorDetail.c_str()); \ return false; \ } #define ATTRIB_OPT_FUNC(_attr,_func) \ if (attrs.find(_attr) != attrs.end()) { \ if (!_func(attrs[_attr])) { \ - syslog(LOG_ERR, "ERROR: graphlcd/skin: Unexpected value %s for attribute %s", \ - attrs[_attr].c_str(), _attr); \ + errorDetail = "Unexpected value \""+ attrs[_attr] +"\" for attribute "+ (std::string)_attr; \ + syslog(LOG_ERR, "ERROR: graphlcd/skin: %s", errorDetail.c_str()); \ return false; \ } \ } @@ -107,16 +112,16 @@ namespace GLCD #define ATTRIB_MAN_FUNC(_attr,_func) \ ATTRIB_OPT_FUNC(_attr,_func) \ else { \ - syslog(LOG_ERR, "ERROR: graphlcd/skin: Mandatory attribute %s missing in tag %s", \ - _attr, name.c_str()); \ + errorDetail = "Mandatory attribute "+ (std::string)_attr +" missing in tag "+name; \ + syslog(LOG_ERR, "ERROR: graphlcd/skin: %s", errorDetail.c_str()); \ return false; \ } #define ATTRIB_OPT_FUNC_PARAM(_attr,_func,_param) \ if (attrs.find(_attr) != attrs.end()) { \ if (!_func(attrs[_attr],_param)) { \ - syslog(LOG_ERR, "ERROR: graphlcd/skin: Unexpected value %s for attribute %s", \ - attrs[_attr].c_str(), _attr); \ + errorDetail = "Unexpected value "+ attrs[_attr] +" for attribute "+ (std::string)_attr; \ + syslog(LOG_ERR, "ERROR: graphlcd/skin: %s", errorDetail.c_str()); \ return false; \ } \ } @@ -124,8 +129,8 @@ namespace GLCD #define ATTRIB_MAN_FUNC_PARAM(_attr,_func,_param) \ ATTRIB_OPT_FUNC_PARAM(_attr,_func,_param) \ else { \ - syslog(LOG_ERR, "ERROR: graphlcd/skin: Mandatory attribute %s missing in tag %s", \ - _attr, name.c_str()); \ + errorDetail = "Mandatory attribute "+ (std::string)_attr +" missing in tag "+name; \ + syslog(LOG_ERR, "ERROR: graphlcd/skin: %s", errorDetail.c_str()); \ return false; \ } @@ -133,31 +138,140 @@ static std::vector<std::string> context; static cSkin * skin = NULL; static cSkinFont * font = NULL; static cSkinVariable * variable = NULL; +static cSkinVariable * variable_default = NULL; static cSkinDisplay * display = NULL; static std::vector <cSkinObject *> parents; static cSkinObject * object = NULL; static uint32_t oindex = 0; +static std::string errorDetail = ""; +static std::string condblock_cond = ""; + +// support for including files (templates, ...) in the skin definition +// max. depth supported for file inclusion (-> detect recursive inclusions) +#define MAX_INCLUDEDEPTH 5 +static int includeDepth = 0; +static std::string subErrorDetail = ""; + +bool StartElem(const std::string & name, std::map<std::string,std::string> & attrs); +bool CharData(const std::string & text); +bool EndElem(const std::string & name); + + + +static bool CheckSkinVersion(const std::string & version) { + float currv; + char* ecptr = NULL; + const char* verscstr = version.c_str(); + // only accept floating point numbers with '.' as separator, no ',' + char* curr_locale = setlocale(LC_NUMERIC, "C"); + + currv = strtof(verscstr, &ecptr); + setlocale(LC_NUMERIC, curr_locale); + + if ( (*ecptr != '\0') || (ecptr == NULL) /*|| (ecptr != verscstr)*/ || + ((int)(GLCDSKIN_SKIN_VERSION * 100.0) < (int)(currv * 100.0)) + ) + { + return false; + } + return true; +} + + bool StartElem(const std::string & name, std::map<std::string,std::string> & attrs) { //printf("start element: %s\n", name.c_str()); - +// if (context.size() > 0) fprintf(stderr, "context: %s\n", context[context.size() - 1].c_str()); if (context.size() == 0) { if (name == "skin") { ATTRIB_MAN_STRING("version", skin->version); ATTRIB_MAN_STRING("name", skin->title); + ATTRIB_OPT_FUNC("enable", skin->ParseEnable); + + if (! CheckSkinVersion(skin->version) ) { + errorDetail = "skin version '"+ skin->version +"' not supported."; + syslog(LOG_ERR, "ERROR: graphlcd/skin: %s", errorDetail.c_str()); + return false; + } } else TAG_ERR_REMAIN("document"); } + else if (name == "include") + { + if (includeDepth + 1 < MAX_INCLUDEDEPTH) { + cSkinObject* tmpobj = new cSkinObject(new cSkinDisplay(skin)); + cSkinString* path = new cSkinString(tmpobj, false); + ATTRIB_MAN_FUNC("path", path->Parse); + std::string strpath = path->Evaluate(); + // is path relative? -> prepend skinpath + if (strpath[0] != '/') { + strpath = skin->Config().SkinPath() + "/" + strpath; + } + path = NULL; + tmpobj = NULL; + + includeDepth++; + cXML incxml(strpath, skin->Config().CharSet()); + incxml.SetNodeStartCB(StartElem); + incxml.SetNodeEndCB(EndElem); + incxml.SetCDataCB(CharData); + if (incxml.Parse() != 0) { + errorDetail = "error when parsing included xml file '"+strpath+"'"+ ( (subErrorDetail == "") ? "" : " ("+subErrorDetail+")"); + syslog(LOG_ERR, "ERROR: graphlcd/skin: %s", errorDetail.c_str()); + return false; + } + includeDepth--; + } else { + subErrorDetail = "max. include depth reached"; + return false; + } + } + else if (name == "condblock") + { + int i = context.size() - 1; + while (i >= 0) { + if (context[i] == "condblock") { + errorDetail = "'condblock' must not be nested in another 'condblock'."; + syslog(LOG_ERR, "ERROR: graphlcd/skin: %s", errorDetail.c_str()); + return false; + } + i--; + } + ATTRIB_MAN_STRING("condition", condblock_cond); + } else if (name == "variable") { variable = new cSkinVariable(skin); ATTRIB_MAN_STRING("id", variable->mId); + ATTRIB_OPT_FUNC("evaluate", variable->ParseEvalMode); ATTRIB_MAN_FUNC("value", variable->ParseValue); - ATTRIB_OPT_FUNC("condition", variable->ParseCondition); + if (context[context.size() - 1] == "condblock") { + if (attrs.find("condition") != attrs.end()) { + errorDetail = "variable \""+variable->mId+"\" must not contain a condition when context = 'condblock'."; + syslog(LOG_ERR, "ERROR: graphlcd/skin: %s", errorDetail.c_str()); + return false; + } else { + if (! variable->ParseCondition(condblock_cond)) { + errorDetail = "Unexpected value \""+ attrs["condition"] +"\" for attribute "+ (std::string)"condition"; + syslog(LOG_ERR, "ERROR: graphlcd/skin: %s", errorDetail.c_str()); + return false; + } + } + } else { + ATTRIB_OPT_FUNC("condition", variable->ParseCondition); + } + // if a 'default' value is set, create a second variable w/o condition: will be used if condition is not true + // as variables all have global scope (no matter where defined) and will always be sought from the start of the array, + // the default variable will be inserted _after_ the variable containing the condition. + if (attrs.find("default") != attrs.end()) { + variable_default = new cSkinVariable(skin); + ATTRIB_MAN_STRING("id", variable_default->mId); + ATTRIB_MAN_FUNC("default", variable_default->ParseValue); + } } else if (context[context.size() - 1] == "skin") { @@ -187,83 +301,98 @@ bool StartElem(const std::string & name, std::map<std::string,std::string> & att } object = new cSkinObject(display); + + /* default settings */ + object->ParseColor("transparent", object->mBackgroundColor); + if (object->ParseType(name)) { - ATTRIB_OPT_FUNC_PARAM("x1", object->ParseIntParam, object->mPos1.x); - ATTRIB_OPT_FUNC_PARAM("y1", object->ParseIntParam, object->mPos1.y); - ATTRIB_OPT_FUNC_PARAM("x2", object->ParseIntParam, object->mPos2.x); - ATTRIB_OPT_FUNC_PARAM("y2", object->ParseIntParam, object->mPos2.y); - ATTRIB_OPT_FUNC("width", object->ParseWidth); - ATTRIB_OPT_FUNC("height", object->ParseHeight); + object->mX1.Parse("0"); + object->mY1.Parse("0"); + object->mX2.Parse("-1"); + object->mY2.Parse("-1"); + object->mWidth.Parse("0"); + object->mHeight.Parse("0"); + ATTRIB_OPT_FUNC("x", object->mX1.Parse); + ATTRIB_OPT_FUNC("y", object->mY1.Parse); + ATTRIB_OPT_FUNC("x1", object->mX1.Parse); + ATTRIB_OPT_FUNC("y1", object->mY1.Parse); + ATTRIB_OPT_FUNC("x2", object->mX2.Parse); + ATTRIB_OPT_FUNC("y2", object->mY2.Parse); + ATTRIB_OPT_FUNC("width", object->mWidth.Parse); + ATTRIB_OPT_FUNC("height", object->mHeight.Parse); ATTRIB_OPT_FUNC("condition", object->ParseCondition); + ATTRIB_OPT_STRING("action", object->mAction); if (name == "image") { - ATTRIB_OPT_FUNC_PARAM("x", object->ParseIntParam, object->mPos1.x); - ATTRIB_OPT_FUNC_PARAM("y", object->ParseIntParam, object->mPos1.y); - ATTRIB_OPT_FUNC_PARAM("x", object->ParseIntParam, object->mPos2.x); - ATTRIB_OPT_FUNC_PARAM("y", object->ParseIntParam, object->mPos2.y); - ATTRIB_OPT_FUNC("color", object->ParseColor); + //ATTRIB_OPT_FUNC_PARAM("x", object->ParseIntParam, object->mPos2.x); + //ATTRIB_OPT_FUNC_PARAM("y", object->ParseIntParam, object->mPos2.y); + ATTRIB_OPT_FUNC_PARAM("color", object->ParseColor, object->mColor); + ATTRIB_OPT_FUNC_PARAM("bgcolor", object->ParseColor, object->mBackgroundColor); ATTRIB_MAN_FUNC("path", object->mPath.Parse); ATTRIB_OPT_FUNC("loop", object->ParseScrollLoopMode); + ATTRIB_OPT_FUNC_PARAM("opacity", object->ParseIntParam, object->mOpacity); + ATTRIB_OPT_FUNC("scale", object->ParseScale); } else if (name == "text" || name == "scrolltext") { - ATTRIB_OPT_FUNC("color", object->ParseColor); + ATTRIB_OPT_FUNC_PARAM("color", object->ParseColor, object->mColor); + ATTRIB_OPT_FUNC_PARAM("bgcolor", object->ParseColor, object->mBackgroundColor); ATTRIB_OPT_FUNC("align", object->ParseAlignment); ATTRIB_OPT_FUNC("valign", object->ParseVerticalAlignment); - ATTRIB_OPT_FUNC("font", object->ParseFontFace); + ATTRIB_MAN_FUNC("font", object->ParseFontFace); ATTRIB_OPT_BOOL("multiline", object->mMultiline); + ATTRIB_OPT_FUNC("mlrelscroll", object->mMultilineRelScroll.Parse); ATTRIB_OPT_FUNC("scrollmode", object->ParseScrollLoopMode); ATTRIB_OPT_FUNC("scrollspeed", object->ParseScrollSpeed); ATTRIB_OPT_FUNC("scrolltime", object->ParseScrollTime); ATTRIB_OPT_STRING("alttext", object->mAltText); ATTRIB_OPT_FUNC("altcondition", object->ParseAltCondition); -#if 0 - if (name == "blink") - { - ATTRIB_OPT_NUMBER("delay", object->mDelay); - - if (object->mDelay == 0) - object->mDelay = 1000; - } - else if (name == "marquee") - { - ATTRIB_OPT_NUMBER("delay", object->mDelay); - - if (object->mDelay == 0) - object->mDelay = 500; - } -#endif + ATTRIB_OPT_FUNC_PARAM("effectcolor", object->ParseColor, object->mEffectColor); + ATTRIB_OPT_FUNC("effect", object->ParseEffect); + ATTRIB_OPT_NUMBER("radius", object->mRadius); + } + else if (name == "button") + { + ATTRIB_OPT_FUNC_PARAM("labelcolor", object->ParseColor, object->mColor); + ATTRIB_OPT_FUNC_PARAM("color", object->ParseColor, object->mBackgroundColor); + ATTRIB_MAN_FUNC("font", object->ParseFontFace); + ATTRIB_OPT_NUMBER("radius", object->mRadius); } else if (name == "pixel") { - ATTRIB_OPT_FUNC("color", object->ParseColor); + ATTRIB_OPT_FUNC_PARAM("color", object->ParseColor, object->mColor); } else if (name == "line") { - ATTRIB_OPT_FUNC("color", object->ParseColor); + ATTRIB_OPT_FUNC_PARAM("color", object->ParseColor, object->mColor); } else if (name == "rectangle") { - ATTRIB_OPT_FUNC("color", object->ParseColor); + ATTRIB_OPT_FUNC_PARAM("color", object->ParseColor, object->mColor); ATTRIB_OPT_BOOL("filled", object->mFilled); ATTRIB_OPT_NUMBER("radius", object->mRadius); } else if (name == "ellipse" || name == "slope") { - ATTRIB_OPT_FUNC("color", object->ParseColor); + ATTRIB_OPT_FUNC_PARAM("color", object->ParseColor, object->mColor); ATTRIB_OPT_BOOL("filled", object->mFilled); ATTRIB_OPT_NUMBER("arc", object->mArc); } else if (name == "progress" || name == "scrollbar") { - ATTRIB_OPT_FUNC("color", object->ParseColor); - ATTRIB_OPT_NUMBER("direction", object->mDirection); - ATTRIB_OPT_FUNC("current", object->mCurrent.Parse); - ATTRIB_OPT_FUNC("total", object->mTotal.Parse); + ATTRIB_OPT_FUNC_PARAM( "color", object->ParseColor, object->mColor); + ATTRIB_OPT_NUMBER( "direction", object->mDirection); + ATTRIB_OPT_FUNC( "current", object->mCurrent.Parse); + ATTRIB_OPT_FUNC( "total", object->mTotal.Parse); + ATTRIB_OPT_FUNC( "peak", object->mPeak.Parse); + ATTRIB_OPT_FUNC_PARAM( "peakcolor", object->ParseColor, object->mPeakGradientColor); + ATTRIB_OPT_FUNC( "gradient", object->ParseGradient); + ATTRIB_OPT_FUNC_PARAM( "gradientcolor", object->ParseColor, object->mPeakGradientColor); + ATTRIB_OPT_NUMBER( "radius", object->mRadius); } #if 0 else if (name == "item") { @@ -271,6 +400,12 @@ bool StartElem(const std::string & name, std::map<std::string,std::string> & att --object->mPos2.y; } #endif + // range checks + if (object->mOpacity < 0) + object->mOpacity = 0; + else if (object->mOpacity > 255) + object->mOpacity = 255; + } else TAG_ERR_REMAIN(context[context.size() - 1].c_str()); @@ -291,13 +426,14 @@ bool CharData(const std::string & text) //printf("context: %s\n", context[context.size() - 1].c_str()); if (context[context.size() - 1] == "text" - || context[context.size() - 1] == "scrolltext") + || context[context.size() - 1] == "scrolltext" + || context[context.size() - 1] == "button") { if (!object->mText.Parse(text)) return false; } else - syslog(LOG_ERR, "ERROR: Bad character data"); + syslog(LOG_ERR, "ERROR: graphlcd/skin: Bad character data"); return true; } @@ -314,7 +450,13 @@ bool EndElem(const std::string & name) else if (name == "variable") { skin->mVariables.push_back(variable); +//fprintf(stderr, " variable '%s', value: %s\n", variable->mId.c_str(), ((std::string)variable->Value()).c_str()); variable = NULL; + if (variable_default != NULL) { + skin->mVariables.push_back(variable_default); +//fprintf(stderr, " variable default '%s', value: %s\n", variable_default->mId.c_str(), ((std::string)variable_default->Value()).c_str()); + variable_default = NULL; + } } else if (name == "display") { @@ -367,29 +509,57 @@ bool EndElem(const std::string & name) return true; } -cSkin * XmlParse(cSkinConfig & Config, const std::string & Name, const std::string & fileName) +static pthread_mutex_t parse_mutex; // temp. workaround of thread-safe parsing problem + +cSkin * XmlParse(cSkinConfig & Config, const std::string & Name, const std::string & fileName, std::string & errorString) { + pthread_mutex_lock(&parse_mutex); // temp. workaround + //fprintf(stderr, ">>>>> XmlParse, Config: %s, Name: %s\n", Config.GetDriver()->ConfigName().c_str(), Name.c_str()); skin = new cSkin(Config, Name); context.clear(); - cXML xml(fileName); + { // temp. workaround for thread-safe parsing + font = NULL; + variable = NULL; + variable_default = NULL; + display = NULL; + parents.clear(); + object = NULL; + oindex = 0; + errorDetail = ""; + condblock_cond = ""; + includeDepth = 0; + subErrorDetail = ""; + } + cXML xml(fileName, skin->Config().CharSet()); xml.SetNodeStartCB(StartElem); xml.SetNodeEndCB(EndElem); xml.SetCDataCB(CharData); if (xml.Parse() != 0) { + char buff[8]; + snprintf(buff, 7, "%d", xml.LineNr()); syslog(LOG_ERR, "ERROR: graphlcd/skin: Parse error in %s, line %d", fileName.c_str(), xml.LineNr()); + // shorter version outgoing errorString (eg. displaying errorString on the display) + errorString = "Parse error in skin "+Name+", line "+buff; + if (errorDetail != "") + errorString += ":\n"+errorDetail; delete skin; skin = NULL; delete display; display = NULL; delete object; object = NULL; + //fprintf(stderr, "<<<<< XmlParse ERROR, Config: %s, Name: %s\n", Config.GetDriver()->ConfigName().c_str(), Name.c_str()); + pthread_mutex_unlock(&parse_mutex); return NULL; } cSkin * result = skin; skin = NULL; + errorString = ""; + //fprintf(stderr, "<<<<< XmlParse, Config: %s, Name: %s\n", Config.GetDriver()->ConfigName().c_str(), Name.c_str()); + pthread_mutex_unlock(&parse_mutex); return result; } diff --git a/glcdskin/parser.h b/glcdskin/parser.h index d7d7f63..855c626 100644 --- a/glcdskin/parser.h +++ b/glcdskin/parser.h @@ -16,13 +16,23 @@ #include <string> +// max. version of skin definitions supported by the parser +#define GLCDSKIN_SKIN_VERSION 1.2 + + namespace GLCD { class cSkin; class cSkinConfig; -cSkin * XmlParse(cSkinConfig & Config, const std::string & name, const std::string & fileName); +cSkin * XmlParse(cSkinConfig & Config, const std::string & name, const std::string & fileName, std::string & errorString); + +// provide old function for compatibility +cSkin * XmlParse(cSkinConfig & Config, const std::string & name, const std::string & fileName) +{ std::string errorString = ""; + return XmlParse(Config, name, fileName, errorString); +} } // end of namespace diff --git a/glcdskin/skin.c b/glcdskin/skin.c index a7facdd..122b162 100644 --- a/glcdskin/skin.c +++ b/glcdskin/skin.c @@ -21,6 +21,8 @@ cSkin::cSkin(cSkinConfig & Config, const std::string & Name) name(Name) { mImageCache = new cImageCache(this, 100); + tsEvalTick = 0; + tsEvalSwitch = 0; } cSkin::~cSkin(void) @@ -79,4 +81,16 @@ cSkinVariable * cSkin::GetVariable(const std::string & Id) } +bool cSkin::ParseEnable(const std::string & Text) +{ + cDriver * driver = config.GetDriver(); + + if (!driver) + return false; + + driver->SetFeature(Text, 1); + return true; // always return true else loading the skin would fail if touchscreen is not available +} + + } // end of namespace diff --git a/glcdskin/skin.h b/glcdskin/skin.h index b2d8496..048c193 100644 --- a/glcdskin/skin.h +++ b/glcdskin/skin.h @@ -43,6 +43,8 @@ private: cSkinDisplays displays; cSkinVariables mVariables; cImageCache * mImageCache; + uint64_t tsEvalTick; + uint64_t tsEvalSwitch; public: cSkin(cSkinConfig & Config, const std::string & Name); @@ -61,6 +63,16 @@ public: const tSize & BaseSize(void) const { return baseSize; } cImageCache * ImageCache(void) { return mImageCache; } + + bool ParseEnable(const std::string &Text); + + cColor GetBackgroundColor(void) { return config.GetDriver()->GetBackgroundColor(); } + cColor GetForegroundColor(void) { return config.GetDriver()->GetForegroundColor(); } + + void SetTSEvalTick(uint64_t ts) { tsEvalTick = ts; } + void SetTSEvalSwitch(uint64_t ts) { tsEvalSwitch = ts; } + const uint64_t GetTSEvalTick(void) { return tsEvalTick; } + const uint64_t GetTSEvalSwitch(void) { return tsEvalSwitch; } }; } // end of namespace diff --git a/glcdskin/string.c b/glcdskin/string.c index dc9d9dc..3966449 100644 --- a/glcdskin/string.c +++ b/glcdskin/string.c @@ -55,18 +55,23 @@ std::string tSkinToken::Token(const tSkinToken & Token) return result; } +#if 0 cSkinString::tStringList cSkinString::mStrings; +#endif cSkinString::cSkinString(cSkinObject *Parent, bool Translate) : mObject(Parent), mSkin(Parent->Skin()), mTranslate(Translate) { +#if 0 mStrings.push_back(this); +#endif } cSkinString::~cSkinString() { +#if 0 tStringList::iterator it = mStrings.begin(); for (; it != mStrings.end(); ++it) { if ((*it) == this) { @@ -74,8 +79,10 @@ cSkinString::~cSkinString() break; } } +#endif } +#if 0 void cSkinString::Reparse(void) { tStringList::iterator it = mStrings.begin(); @@ -84,6 +91,14 @@ void cSkinString::Reparse(void) (*it)->Parse((*it)->mOriginal, true); } } +#endif + + +// copied from xml.c (should be valid for parsing variable names too ...) +static bool IsTokenChar(bool start, int c) { + return isalpha(c) || c == '_' || (!start && isdigit(c)); +} + bool cSkinString::Parse(const std::string & Text, bool Translate) { @@ -93,16 +108,6 @@ bool cSkinString::Parse(const std::string & Text, bool Translate) bool inAttrib = false; int offset = 0; - if (trans[0] == '#') - { - cSkinVariable * variable = mSkin->GetVariable(trans.substr(1)); - if (variable) - { - trans = (std::string) variable->Value(); - syslog(LOG_ERR, "string variable %s", trans.c_str()); - } - } - //Dprintf("parsing: %s\n", Text.c_str()); mOriginal = Text; mText = ""; @@ -113,16 +118,39 @@ bool cSkinString::Parse(const std::string & Text, bool Translate) for (; *ptr; ++ptr) { if (inToken && *ptr == '\\') { if (*(ptr + 1) == '\0') { - syslog(LOG_ERR, "ERROR: Stray \\ in token attribute\n"); + syslog(LOG_ERR, "ERROR: graphlcd/skin/string: Stray \\ in token attribute\n"); return false; } ++ptr; continue; } + else if (*ptr == '#') { + if (inToken) { + syslog(LOG_ERR, "ERROR: graphlcd/skin/string: Unexpected '#' in token"); + return false; + } + + mText.append(last, ptr - last); + + bool isStartChar = true; + const char * varNameStart = ptr; + ptr++; + while (*ptr && IsTokenChar(isStartChar, *ptr)) { + isStartChar = false; + ptr++; + offset++; + } + // add #VARNAME# + mText.append(varNameStart, (ptr - varNameStart)); + mText.append("#"); + offset +=2; // adds two '#' -> fix offset + ptr--; // we'd be at the correct position now but the for-loop does a ++ptr -> fix it by stepping back one char + last = ptr + 1; + } else if (*ptr == '{') { if (inToken) { - syslog(LOG_ERR, "ERROR: Unexpected '{' in token"); + syslog(LOG_ERR, "ERROR: graphlcd/skin/string: Unexpected '{' in token"); return false; } @@ -132,13 +160,13 @@ bool cSkinString::Parse(const std::string & Text, bool Translate) } else if (*ptr == '}' || (inToken && *ptr == ':')) { if (!inToken) { - syslog(LOG_ERR, "ERROR: Unexpected '}' outside of token"); + syslog(LOG_ERR, "ERROR: graphlcd/skin/string: Unexpected '}' outside of token"); return false; } if (inAttrib) { if (*ptr == ':') { - syslog(LOG_ERR, "ERROR: Unexpected ':' inside of token attribute"); + syslog(LOG_ERR, "ERROR: graphlcd/skin/string: Unexpected ':' inside of token attribute"); return false; } @@ -179,12 +207,14 @@ bool cSkinString::Parse(const std::string & Text, bool Translate) { std::string tmp; tmp.assign(last, ptr - last); - tSkinToken token(mSkin->Config().GetTokenId(tmp), tmp, offset, ""); - mTokens.push_back(token); + if (tmp != "") { // ignore empty token + tSkinToken token(mSkin->Config().GetTokenId(tmp), tmp, offset, ""); + mTokens.push_back(token); + } } else { - syslog(LOG_ERR, "ERROR: Unexpected token {%.*s}", (int)(ptr - last), last); + syslog(LOG_ERR, "ERROR: graphlcd/skin/string: Unexpected token {%.*s}", (int)(ptr - last), last); return false; } @@ -201,7 +231,7 @@ bool cSkinString::Parse(const std::string & Text, bool Translate) } if (inToken) { - syslog(LOG_ERR, "ERROR: Expecting '}' in token"); + syslog(LOG_ERR, "ERROR: graphlcd/skin/string: Expecting '}' in token"); return false; } @@ -214,19 +244,92 @@ bool cSkinString::Parse(const std::string & Text, bool Translate) cType cSkinString::Evaluate(void) const { - std::string result; - int offset = 0; - if (mText.length() == 0 && mTokens.size() == 1) return mSkin->Config().GetToken(mTokens[0]); - for (uint32_t i = 0; i < mTokens.size(); ++i) { - result.append(mText.c_str() + offset, mTokens[i].Offset - offset); - result.append(mSkin->Config().GetToken(mTokens[i])); + std::string result_raw = ""; + int offset = 0; + for (uint32_t i = 0; i < mTokens.size(); i++) { + result_raw.append(mText.substr(offset, mTokens[i].Offset - offset) ); + result_raw.append(mSkin->Config().GetToken(mTokens[i])); offset = mTokens[i].Offset; } - result.append(mText.c_str() + offset); - return result; + result_raw.append(mText.c_str() + offset); + + // replace variable placeholders (#VARNAME#) with corresponding values + std::string result_trans = ""; + size_t idxstart = 0, idxend = 0; + size_t pos = 0; + while ( (idxstart=result_raw.find("#", idxstart)) != std::string::npos ) { + result_trans.append(result_raw.substr(pos, idxstart-pos)); + idxend = result_raw.find("#", idxstart+1); + if (idxend != std::string::npos) { + cSkinVariable * variable = mSkin->GetVariable(result_raw.substr(idxstart+1, idxend-idxstart-1)); + if (variable) { + std::string val = (std::string) variable->Value(); + + // if value of variable contains token definions: reparse value + if (val.find("{") != std::string::npos) { + cSkinString *result = new cSkinString(Object(), false); + if (result->Parse(val)) { + val = (std::string) result->Evaluate(); + } + delete result; + } + result_trans.append (val); + // syslog(LOG_ERR, "string variable %s", trans.c_str()); + } else { + result_trans.append (result_raw.substr(idxstart, idxend-idxstart)); // no variable found: print as raw text + } + idxstart = idxend+1; + pos = idxstart; + } else { + result_trans.append ("#"); // no closing '#' -> print # as raw text + idxstart ++; + pos = idxstart; + } + } + result_trans.append(result_raw.substr(pos)); + + // re-evaluate resulting string + if ((mText.size() > 0) && mText[0] != '#' && mObject != NULL ) { + cSkinFunction *result = new cSkinFunction(mObject); + if (result->Parse(result_trans, true)) { + std::string result_rescan = (std::string)result->Evaluate(); + if (result_rescan != "") + result_trans = result_rescan; + } + delete result; + } + + // look for $(..)$-expressions + result_raw = result_trans; + result_trans = ""; + idxstart = 0; + pos = 0; + bool err = false; + while ( !err && ((idxstart = result_raw.find("$(", idxstart)) != std::string::npos) ) { + result_trans.append (result_raw.substr(pos, idxstart - pos) ); + idxend = result_raw.find(")$", idxstart + 2); + if ( idxend == std::string::npos ) { // no $) found: ignore leading $( and pass rest of whole string unparsed + result_trans.append(result_raw.substr(pos)); + err = true; + } else { + std::string func = result_raw.substr( idxstart + 2, idxend - idxstart - 2 ); + cSkinFunction *result = new cSkinFunction(mObject); + if (result->Parse(func, true)) { + std::string result_rescan = (std::string)result->Evaluate(); + if (result_rescan != "") + result_trans.append( result_rescan ); + } + delete result; + + idxstart = idxend + 2; + pos = idxstart; + } + } + result_trans.append( result_raw.substr(pos) ); + return result_trans; } } // end of namespace diff --git a/glcdskin/string.h b/glcdskin/string.h index b75fa11..20c517c 100644 --- a/glcdskin/string.h +++ b/glcdskin/string.h @@ -86,8 +86,10 @@ class cSkin; class cSkinString { private: +#if 0 typedef std::vector<cSkinString*> tStringList; static tStringList mStrings; +#endif cSkinObject * mObject; cSkin * mSkin; @@ -97,7 +99,9 @@ private: bool mTranslate; public: +#if 0 static void Reparse(void); +#endif cSkinString(cSkinObject *Parent, bool Translate); ~cSkinString(); diff --git a/glcdskin/type.h b/glcdskin/type.h index ad02a15..4d54de8 100644 --- a/glcdskin/type.h +++ b/glcdskin/type.h @@ -54,6 +54,10 @@ public: void SetUpdate(uint32_t UpdateIn) { mUpdateIn = UpdateIn; } uint32_t UpdateIn(void) const { return mUpdateIn; } + bool IsString(void) const { return (mType == string); } + bool IsNumber(void) const { return (mType == number); } + bool IsBoolean(void) const { return (mType == boolean); } + operator std::string () const { return String(); } operator int () const { return Number(); } operator bool () const; diff --git a/glcdskin/variable.c b/glcdskin/variable.c index b07773a..b300afc 100644 --- a/glcdskin/variable.c +++ b/glcdskin/variable.c @@ -11,23 +11,63 @@ cSkinVariable::cSkinVariable(cSkin * Parent) : mSkin(Parent), mValue(0), mCondition(NULL), + mFunction(NULL), mDummyDisplay(mSkin), - mDummyObject(&mDummyDisplay) + mDummyObject(&mDummyDisplay), + mEvalMode(tevmTick), + mEvalInterval(0), + mTimestamp(0) { } +bool cSkinVariable::ParseEvalMode(const std::string & Text) +{ + + if (Text == "always") { + mEvalMode = tevmAlways; + } else if (Text == "tick") { + mEvalMode = tevmTick; + } else if (Text == "switch") { + mEvalMode = tevmSwitch; + } else if (Text == "once") { + mEvalMode = tevmOnce; + } else if (Text.length() > 9 && Text.substr(0,9) == "interval:") { + char * e; + const char * t = Text.substr(9).c_str(); + long l = strtol(t, &e, 10); + if ( ! (e == t || *e != '\0') && (l >= 100)) + { + mEvalInterval = (int) l; + mEvalMode = tevmInterval; + return true; + } + return false; + } else { + return false; + } + return true; +} + + bool cSkinVariable::ParseValue(const std::string & Text) { - if (isalpha(Text[0]) || Text[0] == '#') + if (isalpha(Text[0]) || Text[0] == '#' || Text[0] == '{') { - cSkinFunction * func = new cSkinFunction(&mDummyObject); - if (func->Parse(Text)) + //delete mFunction; + mFunction = new cSkinFunction(&mDummyObject); + if (mFunction->Parse(Text)) { - mValue = func->Evaluate(); - delete func; + if (mEvalMode == tevmOnce) { + mValue = mFunction->Evaluate(); + delete mFunction; + mFunction = NULL; + } + //mValue = func->Evaluate(); + //delete func; return true; } - delete func; + delete mFunction; + mFunction = NULL; } else if (Text[0] == '\'') { @@ -57,6 +97,34 @@ bool cSkinVariable::ParseCondition(const std::string & Text) return false; } + +const cType & cSkinVariable::Value(void) +{ + if ( mTimestamp > 0 && + ( ( mEvalMode == tevmTick && mTimestamp >= mSkin->GetTSEvalTick() ) || + ( mEvalMode == tevmSwitch && mTimestamp >= mSkin->GetTSEvalSwitch() ) || + ( mEvalMode == tevmInterval && (mTimestamp + (uint64_t)mEvalInterval) > mSkin->Config().Now()) + ) + ) + { + return mValue; + } + + if (mFunction != NULL) { + mValue = mFunction->Evaluate(); + // should've been solved in ParseValue already, just to be sure ... + if (mEvalMode == tevmOnce) { + delete mFunction; + mFunction = NULL; + } + } + if (mEvalMode == tevmTick || mEvalMode == tevmSwitch || mEvalMode == tevmInterval) { + mTimestamp = mSkin->Config().Now(); + } + return mValue; +} + + cSkinVariables::cSkinVariables(void) { } diff --git a/glcdskin/variable.h b/glcdskin/variable.h index 449f6b0..8c6ef6f 100644 --- a/glcdskin/variable.h +++ b/glcdskin/variable.h @@ -18,10 +18,21 @@ #include "display.h" #include "object.h" +#include "function.h" namespace GLCD { +enum eEvalMode +{ + tevmAlways, + tevmTick, + tevmSwitch, + tevmOnce, + tevmInterval +}; + + class cSkin; class cSkinVariable @@ -34,18 +45,24 @@ private: std::string mId; cType mValue; cSkinFunction * mCondition; + cSkinFunction * mFunction; cSkinDisplay mDummyDisplay; cSkinObject mDummyObject; + eEvalMode mEvalMode; + int mEvalInterval; + uint64_t mTimestamp; public: cSkinVariable(cSkin * Parent); + bool ParseEvalMode(const std::string & Text); bool ParseValue(const std::string & Text); bool ParseCondition(const std::string & Text); cSkin * Skin(void) const { return mSkin; } const std::string & Id(void) const { return mId; } - const cType & Value(void) const { return mValue; } +// const cType & Value(void) const { return mValue; } + const cType & Value(void); cSkinFunction * Condition(void) const { return mCondition; } }; diff --git a/glcdskin/xml.c b/glcdskin/xml.c index ed29d11..a3ad976 100644 --- a/glcdskin/xml.c +++ b/glcdskin/xml.c @@ -15,32 +15,15 @@ #include <iostream> #include <fstream> +#include <string.h> +#include <stdlib.h> + #include "xml.h" +#include "../glcdgraphics/common.h" namespace GLCD { -std::string trim(const std::string & s) -{ - std::string::size_type start, end; - - start = 0; - while (start < s.length()) - { - if (!isspace(s[start])) - break; - start++; - } - end = s.length() - 1; - while (end >= 0) - { - if (!isspace(s[end])) - break; - end--; - } - return s.substr(start, end - start + 1); -} - enum { LOOK4START, // looking for first element start LOOK4TAG, // looking for element tag @@ -57,7 +40,7 @@ enum { INCLOSETAG, // reading closing tag }; -cXML::cXML(const std::string & file) +cXML::cXML(const std::string & file, const std::string sysCharset) : nodestartcb(NULL), nodeendcb(NULL), cdatacb(NULL), @@ -66,6 +49,18 @@ cXML::cXML(const std::string & file) { char * buffer; long size; + sysEncoding = sysCharset; + sysIsUTF8 = (sysEncoding == "UTF-8"); + if (!sysIsUTF8) { + // convert from utf-8 to system encoding + iconv_cd = iconv_open(sysEncoding.c_str(), "UTF-8"); + if (iconv_cd == (iconv_t) -1) { + syslog(LOG_ERR, "ERROR: system encoding %s is not supported\n", sysEncoding.c_str()); + iconv_cd = NULL; + } + } else { + iconv_cd = NULL; + } #if (__GNUC__ < 3) std::ifstream f(file.c_str(), std::ios::in | std::ios::binary | std::ios::ate); @@ -75,20 +70,24 @@ cXML::cXML(const std::string & file) if (!f.is_open()) { syslog(LOG_ERR, "ERROR: skin file %s not found\n", file.c_str()); - } - size = f.tellg(); + validFile = false; + } else { + validFile = true; + size = f.tellg(); #if (__GNUC__ < 3) - f.seekg(0, std::ios::beg); + f.seekg(0, std::ios::beg); #else - f.seekg(0, std::ios_base::beg); + f.seekg(0, std::ios_base::beg); #endif - buffer = new char[size]; - f.read(buffer, size); - f.close(); - data.assign(buffer, size); - delete[] buffer; + buffer = new char[size]; + f.read(buffer, size); + f.close(); + data.assign(buffer, size); + delete[] buffer; + } } +#if 0 cXML::cXML(const char * mem, unsigned int len) : nodestartcb(NULL), nodeendcb(NULL), @@ -98,6 +97,13 @@ cXML::cXML(const char * mem, unsigned int len) { data.assign(mem, len); } +#endif + +cXML::~cXML() +{ + if (iconv_cd != NULL) + iconv_close(iconv_cd); +} void cXML::SetNodeStartCB(XML_NODE_START_CB(cb)) { @@ -129,14 +135,29 @@ int cXML::Parse(void) int percent = 0; int last = 0; std::string::size_type len; + uint32_t c, c_tmp; + unsigned int i_old; + int l, char_size; + + if (!validFile) + return -1; state = LOOK4START; linenr = 1; skipping = false; len = data.length(); - for (std::string::size_type i = 0; i < len; i++) + + unsigned int i = 0; + while (i < (unsigned int)len) { - if (ReadChar(data[i]) != 0) + i_old = i; + encodedCharAdjustCounter(true, data, c_tmp, i); + char_size = (i - i_old) + 1; + c = 0; + for (l = 0 ; l < char_size; l++) + c += ( (0xFF & data[i_old + l]) << ( l << 3) ); + + if (ReadChar(c /*data[i]*/, char_size) != 0) return -1; if (progresscb) { @@ -147,6 +168,7 @@ int cXML::Parse(void) last = percent; } } + i++; } return 0; } @@ -156,8 +178,15 @@ bool cXML::IsTokenChar(bool start, int c) return isalpha(c) || c == '_' || (!start && isdigit(c)); } -int cXML::ReadChar(int c) +int cXML::ReadChar(unsigned int c, int char_size) { + // buffer for conversions (when conversion from utf8 to system encoding is required) + char convbufin[5]; + char convbufout[5]; + char* convbufinp = convbufin; + char* convbufoutp = convbufout; + size_t bufin_size, bufout_size, bufconverted; + // new line? if (c == '\n') linenr++; @@ -179,6 +208,46 @@ int cXML::ReadChar(int c) cdata.replace(pos, 4, ">"); else if (cdata.substr(pos, 5) == "&") cdata.replace(pos, 5, "&"); + else if (cdata.substr(pos, 2) == "&#") { + bool ishex = ((cdata.substr(pos+2, 1) == "x") || (cdata.substr(pos+2, 1) == "X") ); + size_t startpos = pos+2+((ishex)?1:0); + size_t endpos = cdata.find(';', startpos ); + if (endpos != std::string::npos) { + char* tempptr; + std::string charid = cdata.substr(startpos, endpos-startpos); + long val = strtol(charid.c_str(), &tempptr, (ishex) ? 16 : 10); + + if (tempptr != charid.c_str() && *tempptr == '\0') { + char encbuf[5]; size_t enclen = 0; + + if ( val <= 0x1F ) { + enclen = 0; // ignore control chars + } else if ( val <= 0x007F ) { + enclen = 1; + encbuf[0] = (char)(val & 0x7F); + } else if ( val <= 0x07FF ) { + enclen = 2; + encbuf[1] = (char)(( val & 0x003F) | 0x80); + encbuf[0] = (char)(( (val & 0x07C0) >> 6) | 0xC0); + } else if ( val <= 0xFFFF ) { + enclen = 3; + encbuf[2] = (char)(( val & 0x003F) | 0x80); + encbuf[1] = (char)(( (val & 0x0FC0) >> 6) | 0x80); + encbuf[0] = (char)(( (val & 0xF000) >> 12) | 0xE0); + } else if ( val <= 0x10FFFF ) { + enclen = 4; + encbuf[3] = (char)(( val & 0x003F) | 0x80); + encbuf[2] = (char)(( (val & 0x0FC0) >> 6) | 0x80); + encbuf[1] = (char)(( (val & 0x03F000 ) >> 12) | 0x80); + encbuf[0] = (char)(( (val & 0x1C0000 ) >> 18) | 0xF0); + } + encbuf[enclen] = '\0'; + if (enclen > 0) { + cdata.replace(pos, endpos-pos+1, encbuf); + } + } + } + } pos++; } if (!cdatacb(trim(cdata))) @@ -190,7 +259,29 @@ int cXML::ReadChar(int c) state = LOOK4TAG; } else - cdata += c; + { + int i; + //cdata += c; + // convert text-data on the fly if system encoding != UTF-8 + if (iconv_cd != NULL && char_size > 1 /* ((c & 0x80) == 0x80)*/) { + for (i = 0; i < char_size; i++) + convbufin[i] = ( (char)((c >> ( i << 3) ) & 0xFF) ); + convbufin[char_size] = '\0'; + bufin_size = strlen(convbufin); + bufout_size = bufin_size; + bufconverted = iconv(iconv_cd, &convbufinp, &bufin_size, &convbufoutp, &bufout_size); + + if (bufconverted != (size_t)-1 && strlen(convbufout) != 0) { + for (i = 0; i < (int)strlen(convbufout); i++) + cdata += convbufout[i]; + } else { + cdata += "?"; + } + } else { + for (i = 0; i < char_size; i++) + cdata += ( (unsigned char)((c >> ( i << 3) ) & 0xFF) ); + } + } // silently ignore until resync break; diff --git a/glcdskin/xml.h b/glcdskin/xml.h index 009679c..0978cf4 100644 --- a/glcdskin/xml.h +++ b/glcdskin/xml.h @@ -15,6 +15,7 @@ #include <string> #include <map> +#include <iconv.h> namespace GLCD { @@ -33,10 +34,14 @@ void (*CB)(int percent) class cXML { private: + bool validFile; bool skipping; int state; int linenr; - int delim; + unsigned int delim; + std::string sysEncoding; + bool sysIsUTF8; + iconv_t iconv_cd; std::string data, cdata, tag, attrn, attrv; std::map<std::string, std::string> attr; @@ -49,11 +54,12 @@ private: protected: bool IsTokenChar(bool start, int c); - int ReadChar(int c); + int ReadChar(unsigned int c, int char_size); public: - cXML(const std::string & file); - cXML(const char * mem, unsigned int len); + cXML(const std::string & file, const std::string sysCharset = "UTF-8"); + //cXML(const char * mem, unsigned int len); + ~cXML(); void SetNodeStartCB(XML_NODE_START_CB(cb)); void SetNodeEndCB(XML_NODE_END_CB(cb)); |