diff options
Diffstat (limited to 'dspitems.c')
-rw-r--r-- | dspitems.c | 2614 |
1 files changed, 2614 insertions, 0 deletions
diff --git a/dspitems.c b/dspitems.c new file mode 100644 index 0000000..29c4b95 --- /dev/null +++ b/dspitems.c @@ -0,0 +1,2614 @@ +/* + * GraphTFT plugin for the Video Disk Recorder + * + * dspitems.c + * + * (c) 2007-2015 Jörg Wendel + * + * This code is distributed under the terms and conditions of the + * GNU GENERAL PUBLIC LICENSE. See the file COPYING for details. + */ + +//*************************************************************************** +// Includes +//*************************************************************************** + +#include <sstream> + +#include <sysinfo.h> +#include <theme.h> +#include <display.h> +#include <scan.h> +#include <span.h> +#include <setup.h> + +#include <libexif/exif-data.h> + +const int maxGranularity = 100; + +//*************************************************************************** +// Init Statics +//*************************************************************************** + +Renderer* cDisplayItem::render = 0; +cGraphTFTDisplay* cDisplayItem::vdrStatus = 0; +int cDisplayItem::forceDraw = yes; +uint64_t cDisplayItem::nextForce = 0; +cDisplayItem* cDisplayItem::selectedItem = 0; + +//*************************************************************************** +// cDisplayItem +//*************************************************************************** + +void cDisplayItem::scheduleForce(uint64_t aTime) +{ + if (!nextForce || nextForce > aTime) + { + nextForce = aTime; + + tell(1, "schedule force in (%ldms)", + nextForce - msNow()); + } +} + +void cDisplayItem::scheduleDrawAt(uint64_t aTime) +{ + // if (aTime < nextDraw || nextDraw < msNow()) + { + nextDraw = aTime; + + tell(2, "schedule next draw of '%s'[%s] in (%ldms)", + nameOf(), Debug().c_str(), + nextDraw - msNow()); + } +} + +void cDisplayItem::scheduleDrawIn(int aTime) +{ + uint64_t at = aTime + msNow(); + + // the maximal redraw granularity is 500ms (maxGranularity), due to this + // adjust to next full step + + at = round((double)((double)at / maxGranularity)) * maxGranularity; + + scheduleDrawAt(at); +} + +void cDisplayItem::scheduleDrawNextFullMinute() +{ + uint64_t ms = SECONDS(((time(0)/60 +1) * 60) - time(0)); + + scheduleDrawAt(ms+msNow()); +} + +//*************************************************************************** +// Object +//*************************************************************************** + +cDisplayItem::cDisplayItem() + : cThemeItem() +{ + changed = no; + nextDraw = 0; + section = 0; + marquee_active = no; + backgroundItem = 0; + visible = yes; + nextAnimationAt = msNow(); + actLineCount = na; + lastConditionState = true; + + lastX = 0; + lastY = 0; + lastWidth = 0; + lastHeight = 0; +} + +cDisplayItem::~cDisplayItem() +{ +} + +//*************************************************************************** +// Evaluate Color +//*************************************************************************** + +p_rgba cDisplayItem::evaluateColor(const char* var, p_rgba rgba) +{ + string p = ""; + + if (evaluate(p, var) != success) + memset(rgba, 255, sizeof(t_rgba)); // fallback white + else + str2rgba(p.c_str(), rgba); + + tell(3, "evaluated color '%s' to %d/%d/%d/%d (%s)", + var, rgba[0], rgba[1], rgba[2], rgba[3], p.c_str()); + + return rgba; +} + +//*************************************************************************** +// Evaluate Path +//*************************************************************************** + +string cDisplayItem::evaluatePath() +{ + string p = ""; + + // iterate over path + + for (int i = 0; i < pathCount; i++) + { + if (evaluate(p, pathList[i].configured.c_str()) != success) + continue; + + tell(5, "check path '%s'", p.c_str()); + + if (p == "") + { + tell(1, "path '%s' empty, skipping", + pathList[i].configured.c_str()); + + continue; + } + + // append plugin config path + + if (p[0] != '/') + { + p = string(GraphTFTSetup.themesPath) + + string(Thms::theTheme->getDir()) + + "/" + p; + + tell(4, "%d path [%s] converted to '%s'", i, + pathList[i].configured.c_str(), p.c_str()); + } + + if (fileExists(p.c_str())) + return p; + + // time for next image? + // -> else we don't need to check for range + + if (nextAnimationAt > msNow()) + return pathList[i].last; + + // check for range + + unsigned int s = p.find_last_of('('); + unsigned int e = p.find_last_of(')'); + int rangeSize = e-s-1; + int cur; + + if (s != string::npos && e != string::npos && rangeSize >= 3) + { + Scan scan(p.substr(s+1, rangeSize).c_str(), no); + int minNum, maxNum; + string path = ""; + + scan.eat(); + + if (!scan.isNum()) + continue; // range parsing error + + minNum = scan.lastInt(); + scan.eat(); + + if (!scan.isOperator() || *scan.lastIdent() != '-') + continue; // range parsing error + + scan.eat(); + + if (!scan.isNum()) + continue; // range parsing error + + maxNum = scan.lastInt(); + + if (scan.eat() == success) + continue; // range parsing error (waste behind second int) + + // get actual number + + cur = pathList[i].curNum; + + // reset ... + + if (cur < minNum) + { + pathList[i].curNum = minNum; + cur = minNum-1; + } + + do + { + if (++cur > maxNum) + cur = minNum; + + path = p.substr(0, s) + Str::toStr(cur) + p.substr(e+1); + tell(2, "Checking for '%s'", path.c_str()); + + } while (!fileExists(path.c_str()) && cur != pathList[i].curNum); + + if (fileExists(path.c_str())) + { + tell(4, "Animated image '%s'", path.c_str()); + pathList[i].last = path; + pathList[i].curNum = cur; + nextAnimationAt = msNow() + _delay; + + return pathList[i].last; + } + } + } + + tell(2, "Image for '%s' with %d elements not found :(", _path.c_str(), pathCount); + + return ""; +} + +//*************************************************************************** +// Replay Mode Value +//*************************************************************************** + +int cDisplayItem::replayModeValue(ReplayMode rm) +{ + bool play, forward; + int speed; + + if (!vdrStatus->_replay.control + || !vdrStatus->_replay.control->GetReplayMode(play, forward, speed)) + return -1; + + switch (rm) + { + case rmSpeed: return speed; + case rmForward: return forward; + case rmPlay: return play; + default: return na; + } + + return na; +} + +//*************************************************************************** +// Evaluate Condition +//*************************************************************************** + +int cDisplayItem::evaluateCondition(int recurse) +{ + static Scan* scan = 0; + + int result; + int state; + + int rightType = catUnknown; + int leftType = catUnknown; + int leftInt = 0; + int rightInt = 0; + string leftStr = ""; + string rightStr = ""; + + char op[100]; *op = 0; + char logicalOp[100]; *logicalOp = 0; + string expression; + + if (!recurse || !scan) + { + if (_condition.size() <= 0) + return yes; + + // beim Fehler erst mal 'no' ... ? + + if (evaluate(expression, _condition.c_str()) != success) + return no; + + tell(3, "evaluating condition '%s' with expression '%s'", + _condition.c_str(), expression.c_str()); + + // ... + + if (scan) delete scan; + scan = new Scan(expression.c_str()); + } + + // left expression + + scan->eat(); + + if (scan->isNum()) + { + leftInt = scan->lastInt(); + leftType = catInteger; + } + else if (scan->isString()) + { + leftStr = scan->lastString(); + leftType = catString; + } + else + { + tell(0, "Error: Invalid left '%s' expression in '%s'", + scan->lastIdent(), expression.c_str()); + return no; + } + + // operator ? + + if ((state = scan->eat()) == success && scan->isOperator() && !scan->isLogical()) + { + strcpy(op, scan->lastIdent()); + + // right expression + + scan->eat(); + + if (scan->isNum()) + { + rightInt = scan->lastInt(); + rightType = catInteger; + } + else if (scan->isString()) + { + rightStr = scan->lastString(); + rightType = catString; + } + else + { + tell(0, "Error: Invalid right '%s' expression in '%s'", + scan->lastIdent(), expression.c_str()); + return no; + } + + // check the condition + + if (leftType != rightType) + { + tell(0, "Error: Argument types of left and right " + "agrument don't match in (%d/%d) '%s'", + leftType, rightType, expression.c_str()); + return no; + } + + if (leftType == catInteger) + result = condition(leftInt, rightInt, op); + else + result = condition(&leftStr, &rightStr, op); + + state = scan->eat(); + } + else if (leftType == catInteger) + { + result = leftInt ? true : false; + } + else + { + result = leftStr != "" ? true : false; + } + + // any more expressions in here? + + tell(4, "check for further condition at '%s'", Str::notNull(scan->next())); + + if (state == success) + { + tell(4, "further condition found"); + + if (!scan->isLogical()) + { + tell(0, "Error: Invalid logical operator '%s' expression in '%s'", + scan->lastIdent(), expression.c_str()); + return no; + } + + strcpy(logicalOp, scan->lastIdent()); + + // start a recursion ... + + if (strncmp(logicalOp, "&", 1) == 0) + result = result && evaluateCondition(yes); + else if (strncmp(logicalOp, "|", 1) == 0) + result = result || evaluateCondition(yes); + } + + tell(3, "condition is '%s'; evaluated condition is '%s'; result is '%s'", + _condition.c_str(), expression.c_str(), result ? "match" : "don't match"); + + return result; +} + +//*************************************************************************** +// evaluate the condition +//*************************************************************************** + +int cDisplayItem::condition(int left, int right, const char* op) +{ + tell(4, "evaluate condition '%d' '%s' '%d'", left, op, right); + + if (strcmp(op, ">") == 0) + return left > right; + + if (strcmp(op, "<") == 0) + return left < right; + + if (strcmp(op, ">=") == 0) + return left >= right; + + if (strcmp(op, "<=") == 0) + return left <= right; + + if (strcmp(op, "=") == 0 || strcmp(op, "==") == 0) + return left == right; + + if (strcmp(op, "!=") == 0 || strcmp(op, "<>") == 0) + return left != right; + + tell(0, "Unexpected operator '%s'", op); + + return no; +} + +int cDisplayItem::condition(string* left, string* right, const char* op) +{ + tell(4, "evaluate condition '%s' '%s' '%s'", + left->c_str(), op, right->c_str()); + + if (strcmp(op, ">") == 0) + return *left > *right; + + if (strcmp(op, "<") == 0) + return *left < *right; + + if (strcmp(op, ">=") == 0) + return *left >= *right; + + if (strcmp(op, "<=") == 0) + return *left <= *right; + + if (strcmp(op, "=") == 0 || strcmp(op, "==") == 0) + return *left == *right; + + if (strcmp(op, "!=") == 0 || strcmp(op, "<>") == 0) + return *left != *right; + + tell(0, "Unexpected operator '%s'", op); + + return no; +} + +//*************************************************************************** +// Interface +//*************************************************************************** + +int cDisplayItem::reset() +{ + if (_scroll) + { + marquee_active = yes; + marquee_left = no; + marquee_idx = na; + marquee_count = 0; + marquee_strip = 0; + // scheduleDrawIn(0); + } + + nextAnimationAt = msNow(); + lastWidth = 0; + + return done; +} + +int cDisplayItem::draw() +{ + int status = success; + int cond = true; + + // check condition + + if (!isOfGroup(groupMenu) && !isOfGroup(groupTextList)) + { + cond = evaluateCondition(); + + if (!cond) + { + tell(4, "Ignore drawing of '%s' due to condition '%s'", + nameOf(), _condition.c_str()); + + status = ignore; + } + } + + // schedule due to the configured delay .. + + if (cond && _delay > 0 && visible && msNow() > nextDraw && !_scroll) + scheduleDrawIn(_delay); + + // condition state changed force immediate redraw ! + + if (lastConditionState != cond) + { + tell(4, "Condition '%s' of '%s' [%s] changed from (%d) to (%d), force draw", + _condition.c_str(), nameOf(), + Text() != "" ? Text().c_str() : Path().c_str(), + lastConditionState, cond); + + lastConditionState = cond; + scheduleForce(msNow() + 10); + } + + return status; +} + +int cDisplayItem::refresh() +{ + tell(6, "timeMs::Now() (%ldms);", msNow()); + tell(6, "nextForce at (%ldms)", nextForce); + tell(6, "forceDraw '%s', nextDraw (%ldms), isForegroundItem(%d), Foreground(%d)", + forceDraw ? "yes" : "no", nextDraw, isForegroundItem(), Foreground()); + + changed = no; + + // LogDuration ld("cDisplayItem::refresh()"); + + // force required ? (volume, animating, osd-message, ...) + + if (nextForce && msNow() >= nextForce) + { + forceDraw = yes; + nextForce = 0; + } + + // respect the maximal redraw granularity + + if ((nextDraw && (msNow() >= nextDraw-(maxGranularity/2-1))) + || isForegroundItem() || forceDraw || Foreground()) + { + nextDraw = 0; + int res = draw() == success ? 1 : 0; + + changed = res > 0; + + tell(2, "draw '%s', %s", nameOf(), res ? "done" : "skipped due to condition"); + + if (res > 0 && logLevel >= 3) + { + if (isForegroundItem() || forceDraw || Foreground()) + { + tell(3, "forceDraw(%d), isForegroundItem(%d), Foreground(%d)", + forceDraw, isForegroundItem(), Foreground()); + tell(3, "'%s' - '%s'", nameOf(), + Text().size() ? Text().c_str() : Path().c_str()); + } + } + + return res; + } + + return 0; +} + +//*************************************************************************** +// Painters +//*************************************************************************** + +int cDisplayItem::drawText(const char* text, int y, + int height, int clear, int skipLines) +{ + int width; + unsigned int viewLength = clen(text); + unsigned int textLen = clen(text); // character count (real chars) + int lineHeight = 0; + + y = y ? y : Y(); + width = Width() ? Width() : Thms::theTheme->getWidth() - X(); + height = height != na? height : Height(); + height = height != na ? height : Thms::theTheme->getHeight() - y; + skipLines = skipLines ? skipLines : StartLine(); + + if (!height) + return done; + + // draw background + + if (clear) + drawBackRect(y, _bg_height ? _bg_height : height); + + if (!textLen || Str::isEmpty(text)) + return done; + + // text width in pixel + + int textWidth = render->textWidthOf(text, _font.c_str(), _size, lineHeight); + lineHeight = !lineHeight ? 1 :lineHeight; + + // respect max height for line count + + int lines = height / lineHeight > 0 ? height / lineHeight : 1; + + if (_lines > 0) + lines = min(lines, _lines); + + int visibleWidth = width*lines; + + if (textWidth <= 0) + { + textWidth = 22 * textLen; + + tell(1, "Info: Can't detect text with of '%s'[%s](%d) witch font %s/%d. Assuming %dpx", + text, _debug.c_str(), textLen, _font.c_str(), _size, textWidth); + } + + int charWidth = textWidth / textLen > 0 ? textWidth / textLen : 1; // at least one pixel :p + viewLength = visibleWidth / charWidth; // calc max visible chars + + tell(4, "[%s] drawing text '%s' at %d/%d (%d/%d), '%s'(%d) lines (%d)!", + _debug.c_str(), text, X(), y, width, height, _font.c_str(), _size, lines); + + tell(3, "[%s] textLen %d, viewLength = %d, textWidth = %dpx, visibleWidth = %dpx, lines = %d, font %s/%d", + _debug.c_str(), textLen, viewLength, textWidth, visibleWidth, lines, _font.c_str(), _size); + + tell(5, "[%s] scroll is (%d) and marquee_active is (%d) for text '%s'", + _debug.c_str(), _scroll, marquee_active, text); + + // get line count of the actual text + + actLineCount = render->lineCount(text, _font.c_str(), _size, width); + + // ... + + // exclude item fom scrolling if more than one line displayed, + // multiline srcolle is prepared but dont work cause of the word warp featute in ImlibRenderer::text(...) + // -> to hard to calculate this here ... + + if (!_scroll || textWidth < visibleWidth || lines > 1) + { + t_rgba rgba; + + // normal and 'dots' mode + + render->text(text, + _font.c_str(), _size, _align, + X(), y, + evaluateColor(_color.c_str(), rgba), + width, height, lines, + _dots, skipLines); + } + + else + { + // marquee and ticker mode + + // viewLength -= 3; + + if (msNow() > nextAnimationAt) + { + if (_scroll == 1 && marquee_idx + viewLength > textLen) + marquee_left = yes; + + else if (_scroll == 2 && marquee_idx + viewLength > textLen) + marquee_idx = na; + + if (marquee_left) + marquee_idx--; + else + marquee_idx++; + + if (marquee_idx == 0) + { + marquee_left = no; + marquee_count++; + } + + if (_scroll_count && marquee_count > _scroll_count) + { + marquee_active = no; + marquee_idx = 0; + } + + if (marquee_active) + { + if (_delay < 200) + _delay = 200; + + if (marquee_idx == 0 || marquee_idx > (int)(textLen - viewLength)) + scheduleDrawIn(_delay*3); + else + scheduleDrawIn(_delay); + + nextAnimationAt = nextDraw; + } + } + + int blen = strlen(text); + int cs, ps; + int i = 0; + t_rgba rgba; + + for (ps = 0; ps < blen; ps += cs) + { + i++; + cs = max(mblen(&text[ps], blen-ps), 1); + + if (i >= marquee_idx) + break; + } + + tell(3, "drawing text in scroll mode '%s' (%d/%d), nextDraw is %ld; idx is %d/%d", + text, textLen, viewLength, nextDraw/1000, marquee_idx, ps); + + render->text(text + ps, + _font.c_str(), _size, _align, + X(), y, + evaluateColor(_color.c_str(), rgba), + width, height, + lines, marquee_active ? no : _dots); + } + + return done; +} + +int cDisplayItem::drawRectangle() +{ + t_rgba rgba; + + render->rectangle(X(), Y(), Width(), Height(), + evaluateColor(_color.c_str(), rgba)); + + return done; +} + +int cDisplayItem::drawBackRect(int y, int height) +{ + int x = _bg_x != na ? _bg_x : X(); + int width = _bg_width > 0 ? _bg_width : Width(); + + y = y ? y : _bg_y != na ? _bg_y : Y(); + + if (!height) + height = _bg_height > 0 ? _bg_height : Height(); + + if (!Overlay()) + { + t_rgba bg_rgba; + + evaluateColor(_bg_color.c_str(), bg_rgba); + + if (haveBackgroundItem()) + { + string p; + + // fill with part of the backround image + + evaluate(p, backgroundItem->Path().c_str()); + + tell(3, "Drawing backround area of '%s' for '%s'", + p.c_str(), _text.c_str()); + + render->imagePart(p.c_str(), x, y, width, height); + } + + if (bg_rgba[rgbA]) + { + // fill with solid color, respect alpha channel + + render->rectangle(x, y, width, height, bg_rgba); + } + } + + return done; +} + +//*************************************************************************** +// Get Jpeg Orientation +//*************************************************************************** + +int getJpegOrientation(const char* file) +{ + int orientation = 1; // 1 => 'normal' + ExifData* exifData = exif_data_new_from_file(file); + + if (exifData) + { + ExifByteOrder byteOrder = exif_data_get_byte_order(exifData); + ExifEntry* exifEntry = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION); + + if (exifEntry) + orientation = exif_get_short(exifEntry->data, byteOrder); + + exif_data_free(exifData); + } + + return orientation; +} + +int cDisplayItem::drawImage(const char* path, int fit, int aspectRatio, int noBack) +{ + int orientation = 1; // 1 => 'normal' + + if (!path) path = _path.c_str(); + if (fit == na) fit = _fit; + if (aspectRatio == na) aspectRatio = _aspect_ratio; + + tell(3, "drawing image '%s' at %d/%d (%d/%d); fit = %d; aspectRatio = %d)", + path, X(), Y(), Width(), Height(), _fit, aspectRatio); + + if (BgWidth() && !noBack) + drawBackRect(); + + if (strcasestr(path, "JPEG") || strcasestr(path, "JPG")) + orientation = getJpegOrientation(path); + + render->image(path, + X(), Y(), + Width(), Height(), + fit, aspectRatio, orientation); + + return done; +} + +//*************************************************************************** +// Format String +//*************************************************************************** + +const char* cDisplayItem::formatString(const char* str, const char* fmt, + char* buffer, int len) +{ + if (Str::isEmpty(fmt) || Str::isEmpty(str)) + return str; + + sprintf(buffer, "%.*s", len, str); + + if (strcasecmp(fmt, "upper") == 0) + Str::toCase(Str::cUpper, buffer); + if (strcasecmp(fmt, "lower") == 0) + Str::toCase(Str::cLower, buffer); + + return buffer; +} + +//*************************************************************************** +// Format Date Time +//*************************************************************************** + +const char* cDisplayItem::formatDateTime(time_t theTime, const char* fmt, + char* date, int len, int absolut) +{ + struct tm tim = {0}; + tm* tmp; + int res; + + *date = 0; + + string format = fmt && *fmt ? fmt : + (_format.length() ? _format : "%a %d.%m %H:%M"); + + // %s seems to be absolut as default ... + + if (absolut && format.find("%s") == string::npos) + { + localtime_r(&theTime, &tim); + theTime += timezone; + } + + tmp = localtime_r(&theTime, &tim); + + if (!tmp) + { + tell(0, "Error: Can't get localtime!"); + return 0; + } + + res = strftime(date, len, format.c_str(), tmp); + + if (!res) + { + tell(0, "Error: Can't convert time, maybe " + "invalid format string '%s'!", format.c_str()); + + return 0; + } + + if (format.find("%s") != string::npos + || format.find("%S") != string::npos + || format.find("%T") != string::npos) + { + // refresh in 1 second + + if (!_delay) + scheduleDrawIn(1000); + } + else + { + // refresh at next full minute + + scheduleDrawNextFullMinute(); + } + + return date; +} + +//*************************************************************************** +// Draw Image on Background Coordinates +//*************************************************************************** + +int cDisplayItem::drawImageOnBack(const char* path, int fit, int height) +{ + if (!path) + return done; + + int x = _bg_x != na ? _bg_x : X(); + int width = _bg_width > 0 ? _bg_width : Width(); + int y = _bg_y != na ? _bg_y : Y(); + + if (height == na) + height = _bg_height > 0 ? _bg_height : Height(); + + tell(0, "drawing image '%s' at %d/%d (%d/%d)", path, x, y, width, height); + + render->image(path, x, y, width, height, fit); + + return done; +} + +int cDisplayItem::drawProgressBar(double current, double total, + string path, int y, int height, + int withFrame, int clear) +{ + t_rgba rgba; + int xDone; + char tmp[50]; + + int bgX = _bg_x != na ? _bg_x : X(); + int bgWidth = _bg_width ? _bg_width : Width(); + + int bgY = y != na ? y : _bg_y != na ? _bg_y : Y(); + int bgHeight = height != na ? height : _bg_height ? _bg_height : Height(); + + int red, green, blue, alpha; + + rgba2int(str2rgba(_color.c_str(), rgba), red, green, blue, alpha); + + current = current < 0 ? 0 : current ; + bgHeight = bgHeight ? bgHeight : Height(); + height = height == na ? Height() : height; + y = y == na ? Y() : y; + + if (!total) total = 1; + xDone = (int)((current/total) * (float)Width()); + + tell(4, "bar, %f/%f xDone=%d", current, total, xDone); + + // background + + if (clear) + drawBackRect(y, height); + + if (_bg_x && withFrame) + render->rectangle(bgX, bgY, + bgWidth, bgHeight, + evaluateColor(_bg_color.c_str(), rgba)); + + if (path != "") + render->image(path.c_str(), + X(), y, + Width(), height, + true); + else // if (_bg_x <= 0) + render->rectangle(X(), y, Width(), height, + evaluateColor(_bg_color.c_str(), rgba)); + + // foreground + + if (path != "") + { + // with image + + render->rectangle(X() + xDone, y, + Width() - xDone, height, + evaluateColor(_bg_color.c_str(), rgba)); + } + else + { + // without image + + if (_switch) + { + // colorchanging bar + + red = green = 255; + blue = 0; + + double percent = current / total; + + if (percent < 0.5f) + red = (int)(255.0f * percent * 2.0f); + else + green = (int)(255.0f * (1-percent) * 2.0f); + } + + render->rectangle(X(), y, xDone, height, + int2rgba(red, green, blue, alpha, rgba)); + } + + // draw optional text + + if (_text == "percent") + { + int textHeight = _size * 5/3; + t_rgba rgba; + sprintf(tmp, "%3.0f%%", current / (total/100)); + + render->text(tmp, + _font.c_str(), _size, _align, + X(), + y + (height-textHeight)/2, + int2rgba(255-red, 255-green, 255-blue, 0, rgba), + bgWidth, height, 1, no); + } + else if (_text == "value") + { + int textHeight = _size * 5/3; + t_rgba rgba; + + sprintf(tmp, "%d%s / %d%s", + (int)current, _unit.c_str(), + (int)total, _unit.c_str()); + + render->text(tmp, + _font.c_str(), _size, _align, + X(), + y + (height-textHeight)/2, + int2rgba(255-red, 255-green, 255-blue, 0, rgba), + bgWidth, height, 1, no); + } + + return success; +} + +int cDisplayItem::drawPartingLine(string text, int y, int height) +{ + string p; + int barHeight = BarHeight(); + + if (BarHeightUnit() == iuPercent) // BarHeight in % of row + barHeight = (int)(((double)height/100) * (double)BarHeight()); + + int offset = (height - barHeight) / 2; + + tell(5, "drawing parting line at %d/%d (%d/%d)", + X(), y+offset, Width(), barHeight); + + if (_path != "" && text != "") + { + evaluate(p, _path.c_str()); + render->image(p.c_str(), + X(), y+offset, + Width(), barHeight, + Fit(), AspectRatio()); + } + + if (_path2 != "" && text == "") + { + evaluate(p, _path2.c_str()); + render->image(p.c_str(), + X(), y+offset, + Width(), barHeight, + Fit(), AspectRatio()); + } + + if (text != "") + { + t_rgba rgba; + int alignOff = 0; + int textOff = 0; + + if (AlignV()) + alignOff = (barHeight-(_size * 5/3))/2; + + if (strncmp(text.c_str(), "->", 2) == 0) + textOff = 2; + + render->text(text.c_str() + textOff, + _font.c_str(), _size, _align, + X(), y + offset + alignOff, + evaluateColor(_color.c_str(), rgba), + Width(), barHeight, 1); + } + + return done; +} + +//*************************************************************************** +// Item Classes +//*************************************************************************** + +int cDisplayText::draw() +{ + string p; + + evaluate(p, _text.c_str()); + + int vLines = Height() / (_size * 5/3); + int width = Width() ? Width() : Thms::theTheme->getWidth() - X(); + + // trim + + p = trim(p); + + // user scrolling + + if (p != lastText || StartLine() < 0) + { + lastText = p; + setStartLine(0); + } + + if (StartLine() >= lineCount() - vLines) + setStartLine(lineCount() - vLines); + + // first ... calc text width and real needed height + + int lineHeight = 0; + lastHeight = 0; + lastWidth = render->textWidthOf(p.c_str(), _font.c_str(), _size, lineHeight); + lastY = Y(); + lastX = X(); + + int neededLines = 0; + + if (lastWidth && p.length()) + { + neededLines = render->lineCount(p.c_str(), _font.c_str(), _size, width); + lastHeight = neededLines * lineHeight; + lastHeight = min(lastHeight, Height()); // respect configured max height + } + + tell(3, "Dimension of '%s' %d/%d now %d/%d; position is %d/%d, need %d lines [%d,%d,%d]", + _id.c_str(), Width(), Height(), + lastWidth, lastHeight, + X(), Y(), neededLines, + lastWidth, width, lineHeight); + + // second ... check condition + + if (cDisplayItem::draw() != success) + return fail; + + // third ... draw text + + return drawText(p.c_str(), lastY, lastHeight); +} + +int cDisplayRectangle::draw() +{ + if (cDisplayItem::draw() != success) + return fail; + + return drawRectangle(); +} + +int cDisplayImage::draw() +{ + if (cDisplayItem::draw() != success) + return fail; + + string path = evaluatePath().c_str(); + + tell(3, "Looking for file '%s'", path.c_str()); + + if (!Str::isEmpty(path.c_str())) + { + if (_path2 != "") + { + string p; + evaluate(p, _path2.c_str()); + drawImageOnBack(p.c_str(), _fit); + } + + return drawImage(path.c_str(), na, na, _path2 != ""); + } + + drawBackRect(); + + return done; +} + +int cDisplayImageFile::draw() +{ + if (cDisplayItem::draw() != success) + return fail; + + FILE* fp; + char line[1000+TB]; *line = 0; + char* c; + + fp = fopen(_path.c_str(), "r"); + + if (fp) + { + c = fgets(line, 1000, fp); + + if (c) line[strlen(line)] = 0; + Str::allTrim(line); + + fclose(fp); + } + + if (!_fit || _aspect_ratio) + drawBackRect(); + + // info + + tell(5, "Looking for file '%s'", line); + + if (!Str::isEmpty(line) && fileExists(line)) + { + drawImage(line); + } + else + { + tell(4, "Info: Image '%s' not found, falling back to '%s'", + line, _path2.c_str()); + + drawImage(_path2.c_str()); + } + + return success; +} + +//*************************************************************************** +// cDisplayImageDir +//*************************************************************************** + +int cDisplayImageDir::init() +{ + if (cDisplayItem::init() != success) + return fail; + + images.clear(); + + scanDir(_path.c_str()); + + tell(0, "Info: Added %ld images of path '%s'", images.size(), _path.c_str()); + current = images.end(); + + return success; +} + +int cDisplayImageDir::scanDir(const char* path, int level) +{ + const char* extensions = "jpeg:jpg"; + const char* ext; + DIR* dir; + struct dirent *entry, *res; + int nameMax, direntSize; + + // calculate dirent size + + nameMax = pathconf(path, _PC_NAME_MAX); + nameMax = nameMax > 0 ? nameMax : 256; + direntSize = offsetof(struct dirent, d_name) + nameMax + TB; + + // open directory + + if (!(dir = opendir(path))) + { + tell(1, "Can't open directory '%s', '%s'", path, strerror(errno)); + return done; + } + + entry = (struct dirent*)malloc(direntSize); + + // iterate .. + + tell(0, "Info: Scanning %sdirectory '%s' for images", level ? "sub-" : "", path); + + while (readdir_r(dir, entry, &res) == 0 && res) + { + ImageFile f; + + if (entry->d_type == DT_DIR) + { + char* subPath; + + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + asprintf(&subPath, "%s/%s", path, entry->d_name); + scanDir(subPath, level+1); + + free(subPath); + } + + // check extension + + if ((ext = strrchr(res->d_name, '.'))) + ext++; + + if (Str::isEmpty(ext)) + { + tell(0, "skipping file '%s' without extension", res->d_name); + continue; + } + + if (!strcasestr(extensions, ext)) + { + tell(0, "skipping file '%s' with extension '%s'", res->d_name, ext); + continue; + } + + // fill image infos + + f.path = path + string("/") + res->d_name; + f.initialized = no; + f.orientation = 1; // 1 => 'normal' + f.landscape = yes; + f.width = 0; + f.height = 0; + + images.push_back(f); + + tell(3, "Info: Added '%s'", f.path.c_str()); + } + + free(entry); + closedir(dir); + + return success; +} + +int cDisplayImageDir::getNext(std::vector<ImageFile>::iterator& it, ImageFile*& file) +{ + if (it != images.end()) + it++; + + if (it == images.end()) + it = images.begin(); + + if (it == images.end()) + return fail; + + file = &(*it); + + if (!file->initialized) + { + file->orientation = getJpegOrientation(file->path.c_str()); + jpegDimensions(file->path.c_str(), file->width, file->height); + + file->landscape = (file->orientation < 5 && file->width < file->height) || (file->orientation >= 5 && file->width > file->height) ? no : yes; + } + + return success; +} + +int cDisplayImageDir::draw() +{ + ImageFile* file; + + if (cDisplayItem::draw() != success) + return fail; + + if (!_fit || _aspect_ratio) + drawBackRect(); + + if (getNext(current, file) != success) + return done; + + if (!file->landscape) + { + static int n = 0; + + unsigned int width = Width() / 2; + std::vector<ImageFile>::iterator next = current; + + // hochkant -> show one image at the left side of the screen + + render->image(file->path.c_str(), 0, Y(), width, Height(), yes, yes, _rotate ? file->orientation : 1); + + // now fill the right halfe of the screen + + do + { + getNext(next, file); + + } while ((n%2 && file->landscape) || (!(n%2) && !file->landscape)); + + n++; + + if (!file->landscape) + { + // one 'hochkant' image at the right side of the screen + + render->image(file->path.c_str(), width, Y(), width, Height(), yes, yes, _rotate ? file->orientation : 1); + } + else + { + const unsigned int offset = 20; + unsigned int height = (Height()-30) / 2; + + // two 'quer' images on the right side of the screen + + render->image(file->path.c_str(), width, 10, width, height, yes, yes, _rotate ? file->orientation : 1); + + do + { + getNext(next, file); + + } while (!file->landscape); + + render->image(file->path.c_str(), width+offset, height+20, width-offset, height, yes, yes, _rotate ? file->orientation : 1); + } + + current++; + } + else + { + // quer -> show one image on screen + + drawImage(file->path.c_str()); + } + + current++; + + return success; +} + +//*************************************************************************** +// cDisplayCalibrationCursor +//*************************************************************************** + +int cDisplayCalibrationCursor::draw() +{ + if (cDisplayItem::draw() != success) + return fail; + + t_rgba rgba; + int width = Width() ? Width() : 20; + int height = Height() ? Height() : 20; + + if (_path != "") + render->image(_path.c_str(), + vdrStatus->calibration.cursorX - width/2, + vdrStatus->calibration.cursorY - height/2, + width, height, yes); + else + render->rectangle(vdrStatus->calibration.cursorX - width/2, + vdrStatus->calibration.cursorY - height/2, + width, height, + evaluateColor(_color.c_str(), rgba)); + + return success; +} + +int cDisplayMenuButton::draw() +{ + if (cDisplayItem::draw() != success) + return fail; + + int index = _item - itemMenuButton; + + if (index >= 0 && index <= 3) + return drawText(vdrStatus->_menu.buttons[index].c_str(), + 0, na, no /*clear*/); + + return fail; +} + +int cDisplayMenuButtonBackground::draw() +{ + string p; + + if (cDisplayItem::draw() != success) + return fail; + + int index = _item - itemMenuButtonBackground; + + if (index < 0 || index > 3) + return fail; + + if (vdrStatus->_menu.buttons[index].length()) + { + if (_path != "") + { + evaluate(p, _path.c_str()); + return drawImage(p.c_str()); + } + } + else if (_path2 != "") + { + evaluate(p, _path2.c_str()); + return drawImage(p.c_str()); + } + + return done; +} + +int cDisplayMessage::draw() +{ + if (cDisplayItem::draw() != success) + return fail; + + if (vdrStatus->_message != "") + { + drawBackRect(); + + if (_path != "") + { + string p; + evaluate(p, _path.c_str()); + drawImageOnBack(p.c_str(), _fit); + } + + tell(3, "draw message '%s'", vdrStatus->_message.c_str()); + drawText(vdrStatus->_message.c_str(), 0, na, no /*clear*/); + + return success; + } + + return ignore; +} + +int cDisplayVolumeMuteSymbol::draw() +{ + static int lastMute = vdrStatus->_mute; + static uint64_t showUntil = msNow(); + + if (cDisplayItem::draw() != success) + return fail; + + visible = no; + + if (!_permanent && lastMute != vdrStatus->_mute) + { + lastMute = vdrStatus->_mute; + showUntil = msNow() + _delay; + } + + if (_permanent || msNow() < showUntil) + { + string p; + + visible = yes; + + if (!_permanent) + scheduleForce(showUntil); + + if (vdrStatus->_mute) + { + evaluate(p, _path.c_str()); + return drawImage(p.c_str()); // is muted + } + else if (_path2 != "") + { + evaluate(p, _path2.c_str()); + return drawImage(p.c_str()); + } + } + + return ignore; +} + +int cDisplayVolumebar::draw() +{ + static int lastVolume = vdrStatus->_volume; + static uint64_t showUntil = msNow(); + + if (cDisplayItem::draw() != success) + return fail; + + visible = no; + + if (!_permanent && lastVolume != vdrStatus->_volume) + { + lastVolume = vdrStatus->_volume; + showUntil = msNow() + _delay; + } + + if (_permanent || msNow() < showUntil) + { + string p; + + if (!_permanent) + scheduleForce(showUntil); + + visible = yes; + + if (_path2 != "") + { + evaluate(p, _path2.c_str()); + drawImageOnBack(p.c_str()); + } + + evaluate(p, _path.c_str()); + + return drawProgressBar(vdrStatus->_volume, 255, p, + Y(), Height(), no /*withFrame*/, no /*clear*/); + } + + return ignore; +} + +int cDisplayTimebar::draw() +{ + if (cDisplayItem::draw() != success) + return fail; + + if (!vdrStatus->_presentEvent.isEmpty() && !vdrStatus->_followingEvent.isEmpty()) + { + string path = evaluatePath().c_str(); + + drawProgressBar(time(0) - vdrStatus->_presentEvent.StartTime(), + vdrStatus->_followingEvent.StartTime() + - vdrStatus->_presentEvent.StartTime(), + path); + } + else + drawBackRect(); + + if (!_delay) + scheduleDrawIn(SECONDS(30)); + + return done; +} + +int cDisplayProgressBar::draw() +{ + if (cDisplayItem::draw() != success) + return fail; + + string cur; + string tot; + + if (evaluate(cur, _value.c_str()) == success + && evaluate(tot, _total.c_str()) == success) + { + tell(3, "Progress: '%s'/'%s' %d/%d", + cur.c_str(), tot.c_str(), + atoi(cur.c_str()), atoi(tot.c_str())); + + string path = evaluatePath().c_str(); + + return drawProgressBar(atoi(cur.c_str()), atoi(tot.c_str()), path); + } + + return ignore; +} + +int cDisplaySysinfo::draw() +{ + int status = ignore; + + if (cDisplayItem::draw() != success) + return fail; + + string path = evaluatePath().c_str(); + + if (_type.find("cpu") == 0) + { + int load = Sysinfo::cpuLoad(); + + // if (forceDraw || abs(load - lastCpuLoad) > 2) + { + lastCpuLoad = load; + + if (_type == "cpuload") + status = drawProgressBar(load, 100, path); + else if (_type == "cpuidle") + status = drawProgressBar(100-load, 100, path); + } + } + + else if (_type.find("mem") == 0) + { + unsigned long total, used, free, cached; + + Sysinfo::memInfoMb(total, used, free, cached); + + if (forceDraw || used != lastUsedMem) + { + int f = _factor / (1024*1024); // due to memInfoMb already return MB + + lastUsedMem = used; + + // scale to given factor + + total /= f; + used /= f; + free /= f; + + if (_type == "memused") + status = drawProgressBar(used, total, path); + else if (_type == "memfree") + status = drawProgressBar(free, total, path); + else if (_type == "memcached") + status = drawProgressBar(cached, total, path); + } + } + + else if (_type == "disk" && _reference != "") + { + unsigned long freeM = 0, usedM = 0; + char* dir = strdup(_reference.c_str()); + char* c; + + if ((c = strchr(dir, '?'))) + { + int u; + + for (int i = 0; i < 10; i++) + { + *c = '0' + i; + + if (fileExists(dir)) + { + tell(6, "adding size of '%s'", dir); + freeM += FreeDiskSpaceMB(dir, &u); + usedM += u; + } + } + } + else + { + freeM = FreeDiskSpaceMB(dir, (int*)&usedM); + } + + free(dir); + + // trick, at least 1 due to divide by zero error + + usedM = usedM ? usedM : 1; + freeM = freeM ? freeM : 1; + + if (forceDraw || usedM != lastUsedDisk) + { + int f = _factor / (1024*1024); // due to FreeDiskSpaceMB return MB + + lastUsedDisk = usedM; + + // scale to given factor + + usedM /= f; + freeM /= f; + + status = drawProgressBar(usedM, freeM + usedM, path); + } + } + else + { + // return without scheduling next draw ! + + tell(0, "Ignoring sysinfo item of unexpected type '%s'", + _type.c_str()); + + return fail; + } + + return status; +} + +int cDisplayBackground::draw() +{ + if (cDisplayItem::draw() != success) + return fail; + + // #TODO - implement a force of all other items? + + if (_path != "") + return cDisplayImage::draw(); + + return drawRectangle(); +} + +int cDisplayTextList::draw() +{ + cGraphTFTDisplay::cTextList* list = 0; + + if (!_lines) + _lines = 1; + + string p; + int visible = 0; + int y = 0; + int lineHeight = _size * 5/3; + int maxRows = Height() / (lineHeight * _lines); + + cDisplayItem::draw(); + + // music or timer list ? + + if (strstr(_text.c_str(), "actTimers") || strstr(_text.c_str(), "actRunning") || strstr(_text.c_str(), "actPending")) + list = &vdrStatus->_timers; + else + list = &vdrStatus->_music; + + // first count visible rows + + lastHeight = 0; + list->reset(); + + // first clear background, event if path is set (image may be transparent!) + + drawBackRect(); + + // draw optional background image + + if (_path != "") + drawImageOnBack(_path.c_str(), yes); + + while (visible < maxRows && list->isValid()) + { + if (evaluateCondition()) + visible++; + + list->inc(); + } + + if (visible == 0) + return success; + + tell(3, "draw list with %d visible items, top pos %d", visible, Y()); + + // draw list + + list->reset(); + + for (int r = 0; r < visible && list->isValid(); list->inc()) + { + if (!evaluateCondition()) + continue; + + evaluate(p, _text.c_str()); + + y = Y() + lineHeight * r * _lines; + + tell(4, "TextList, row %d '%s' at (%d/%d) with (%d) lines [%s]", + r, p.c_str(), X(), y, _lines, _text.c_str()); + + drawText(p.c_str(), y, lineHeight * _lines, no); + + lastHeight += lineHeight * _lines; + r++; + } + + return success; +} + +int cDisplayMenuSelected::draw() +{ + if (cDisplayItem::draw() != success) + return fail; + + selectedItem = this; + + return done; +} + +int cDisplayMenuColumnSelected::draw() +{ + if (cDisplayItem::draw() != success) + return fail; + + selectedItem = this; + + return done; +} + +int cDisplayMenuEventColumnSelected::draw() +{ + if (cDisplayItem::draw() != success) + return fail; + + selectedItem = this; + + return done; +} + +//*************************************************************************** +// Menu - old style +//*************************************************************************** + +int cDisplayMenu::draw() +{ + if (cDisplayItem::draw() != success) + return fail; + + if (!selectedItem) + return fail; + + int total; + int tabPercent[cGraphTFTService::MaxTabs] = {0, 0, 0, 0, 0, 0}; + int afterSelect = 0; + int lineHeight = _size * 5/3; + int lineHeightSelect = selectedItem->Size() * 5/3; + int count = ((Height()-lineHeightSelect) / lineHeight) + 1; + int step = vdrStatus->_menu.currentRow - vdrStatus->_menu.currentRowLast; // step since last refresh + + vdrStatus->_menu.visibleRows = count; + vdrStatus->_menu.lineHeight = lineHeight; + vdrStatus->_menu.lineHeightSelected = lineHeightSelect; + + if (vdrStatus->_menu.topRow < 0) + vdrStatus->_menu.topRow = max(0, vdrStatus->_menu.currentRow - count/2); // initial + else if (vdrStatus->_menu.currentRow == vdrStatus->_menu.topRow-1) + vdrStatus->_menu.topRow = vdrStatus->_menu.currentRow; // up + else if (vdrStatus->_menu.currentRow == vdrStatus->_menu.topRow+count) + vdrStatus->_menu.topRow++; // down + else if (vdrStatus->_menu.currentRow < vdrStatus->_menu.topRow + || vdrStatus->_menu.currentRow > vdrStatus->_menu.topRow+count) + vdrStatus->_menu.topRow += step; // page up / page down + + if (vdrStatus->_menu.topRow > (int)vdrStatus->_menu.items.size()-count) + vdrStatus->_menu.topRow = vdrStatus->_menu.items.size()-count; + + if (vdrStatus->_menu.topRow < 0) + vdrStatus->_menu.topRow = 0; + + vdrStatus->_menu.currentRowLast = vdrStatus->_menu.currentRow; + + // calculate column width + + total = vdrStatus->_menu.charInTabs[0] + vdrStatus->_menu.charInTabs[1] + vdrStatus->_menu.charInTabs[2] + + vdrStatus->_menu.charInTabs[3] + vdrStatus->_menu.charInTabs[4] + vdrStatus->_menu.charInTabs[5]; + + if (!total) + return done; + + for (int i = 0; vdrStatus->_menu.charInTabs[i] != 0; i++) + tabPercent[i] = (int)((100L / (double)total) * (double)vdrStatus->_menu.charInTabs[i]); + + tell(4, "Debug: tabs set to - %d:%d:%d:%d:%d", + tabPercent[0], tabPercent[1], tabPercent[2], + tabPercent[3], tabPercent[4]); + + // loop over visible rows ... + + for (int i = vdrStatus->_menu.topRow; i < min((int)vdrStatus->_menu.items.size(), + vdrStatus->_menu.topRow + count); ++i) + { + cDisplayItem* p = i == vdrStatus->_menu.currentRow ? selectedItem : this; + int y = p->Y() + ((i - vdrStatus->_menu.topRow) * lineHeight); + + if (i == vdrStatus->_menu.currentRow) + { + afterSelect = lineHeightSelect - lineHeight; + + // draw the selected backround + + if (selectedItem->Focus() != "") + { + string p; + + evaluate(p, selectedItem->Focus().c_str()); + render->image(p.c_str(), X(), + Y() + (i - vdrStatus->_menu.topRow) * lineHeight, + Width(), Height()); + } + + // draw the columns + + int x = selectedItem->X(); + + for (int t = 0; t < vdrStatus->_menu.items[i].tabCount; ++t) + { + if (vdrStatus->_menu.items[i].tabs[t] != "") + { + int width = (Width() * tabPercent[t]) / 100; + t_rgba rgba; + + render->text(vdrStatus->_menu.items[i].tabs[t].c_str(), + selectedItem->Font().c_str(), selectedItem->Size(), selectedItem->Align(), + x, + Y() + ((i - vdrStatus->_menu.topRow) * lineHeight), + evaluateColor(selectedItem->Color().c_str(), rgba), + width, + lineHeightSelect, 1); + + x += width; + } + } + + if (selectedItem->Path() != "") + render->image(selectedItem->Path().c_str(), + X(), Y() + (i - vdrStatus->_menu.topRow) * lineHeight, + Width(), Height()); + + if (p->StaticPicture()) + { + // for the selected item we optionaly draw a additional picture (ImageMap) + // as image name use the text after the number in the last column ... + + string path = Thms::theTheme->getPathFromImageMap(vdrStatus->_menu.items[i].tabs[vdrStatus->_menu.items[i].tabCount-1].c_str()); + + if (path != "") + { + if (p->StaticX() && p->StaticY()) + render->image(path.c_str(), p->StaticX(), p->StaticY(), + p->StaticWidth(), p->StaticHeight(), yes, yes); + else if (p->StaticX()) + render->image(path.c_str(), + p->StaticX(), + Y() + (i - vdrStatus->_menu.topRow) * lineHeight + - ((p->StaticHeight() - lineHeightSelect) /2), + p->StaticWidth(), p->StaticHeight(), yes, yes); + } + } + } + + else + { + if (_path != "") + render->image(_path.c_str(), X(), y + afterSelect, Width(), Height()); + + int x = X(); + + for (int t = 0; t < vdrStatus->_menu.items[i].tabCount; ++t) + { + if (vdrStatus->_menu.items[i].tabs[t] != "") + { + int width = (Width() * tabPercent[t]) / 100; + t_rgba rgba; + + render->text(vdrStatus->_menu.items[i].tabs[t].c_str(), // test, + _font.c_str(), _size, _align, // font, font-size, align + x, y + afterSelect, + evaluateColor(_color.c_str(), rgba), + width, lineHeight, 1); + + x += width; + } + } + } + } + + return done; +} + +//*************************************************************************** +// Menu Column - new 'column' style +//*************************************************************************** + +int cDisplayMenuColumn::draw() +{ + if (cDisplayItem::draw() != success) + return fail; + + cGraphTFTDisplay::MenuInfo* _menu = &vdrStatus->_menu; + int count; + int lineHeight; + int lineHeightSelect; + + int step = _menu->currentRow - _menu->currentRowLast; // step since last refresh + int afterSelect = 0; + + if (!_menu->items.size()) + return done; + + // calc height and row count ... + + lineHeight = Height() ? Height() : Size() * 5/3; + + if (selectedItem) + lineHeightSelect = selectedItem->Height() ? selectedItem->Height() : selectedItem->Size() * 5/3; + else + lineHeightSelect = lineHeight; + + count = ((_menu_height-lineHeightSelect) / lineHeight) + 1; + + // tell(0, "_menu->items.size(%d), _menu->topRow(%d), _menu->currentRow(%d)," + // "_menu->currentRowLast(%d), count(%d)", + // _menu->items.size(), _menu->topRow, _menu->currentRow, _menu->currentRowLast, count); + + _menu->visibleRows = count; + _menu->lineHeight = lineHeight; + _menu->lineHeightSelected = lineHeightSelect; + + if (_menu->topRow < 0) + _menu->topRow = max(0, _menu->currentRow - count/2); // initial + else if (_menu->currentRow == _menu->topRow-1) + _menu->topRow = _menu->currentRow; // up + else if (_menu->currentRow == _menu->topRow+count) + _menu->topRow++; // down + else if (_menu->currentRow < _menu->topRow + || _menu->currentRow > _menu->topRow+count) + _menu->topRow += step; // page up / page down + + if (_menu->topRow > (int)_menu->items.size()-count) + _menu->topRow = _menu->items.size()-count; + + if (_menu->topRow < _menu->currentRow-count) + _menu->topRow = _menu->currentRow-count; + + if (_menu->topRow < 0) + _menu->topRow = 0; + + if (step) + marquee_active = no; + + _menu->currentRowLast = _menu->currentRow; + + // paint the visible rows for this column + + for (int i = _menu->topRow; i < min((int)_menu->items.size(), _menu->topRow + count); ++i) + { + cDisplayItem* p = this; + + if (i == _menu->currentRow) + { + afterSelect = lineHeightSelect - lineHeight; + + if (!selectedItem || selectedItem->Number() == na) + continue; + + p = selectedItem; + } + + int y = p->MenuY() + ((i - _menu->topRow) * lineHeight) + (p == selectedItem ? 0 : afterSelect); + _menu->drawingRow = i; // the row + + tell(4, "colcount of row (%d) is (%d)", i, _menu->items[i].tabCount); + + if (p->Number() > vdrStatus->_menu.items[i].tabCount) + { + tell(0, "Warning: Skipping column number %d, only (%d) columns for row (%d) send by vdr", + Number(), vdrStatus->_menu.items[vdrStatus->_menu.drawingRow].tabCount, vdrStatus->_menu.drawingRow); + + continue; + } + + if (!p->evaluateCondition()) + continue; + + // calc pos an width ... + + if (p->X() == na) + p->setX(_menu->items[i].nextX); + + if (!p->Width()) + p->setWidth(Thms::theTheme->getWidth()-p->X()); + + _menu->items[i].nextX = p->X() + p->Width() + p->Spacing(); + + if (i == _menu->currentRow) + { + if (p->ImageMap() && Number()) + { + // for the selected item we optionaly draw a additional picture (ImageMap) + // as image name use the text of the current column ... + + string path = Thms::theTheme->getPathFromImageMap(_menu->items[i].tabs[p->Number()-1].c_str()); + + if (path != "" && p->StaticX()) + render->image(path.c_str(), p->StaticX(), + p->StaticY() ? p->StaticY() : y - ((p->StaticHeight() - lineHeightSelect) /2), + p->StaticWidth(), p->StaticHeight(), yes, yes); + } + + if (p->StaticPicture() && Number()) + { + // Image with static position for this column + + if (_menu->items[i].tabs[p->Number()-1].c_str()[0] && p->StaticX()) + render->image(p->channelLogoPath(_menu->items[i].tabs[p->Number()-1].c_str(), Format().c_str()).c_str(), + p->StaticX(), p->StaticY() ? p->StaticY() : y, p->StaticWidth(), p->StaticHeight(), yes, yes); + } + + if (p->StaticText() && Number()) + { + t_rgba rgba; + + // Text with static position for this column + + if (_menu->items[i].tabs[p->Number()-1].c_str()[0]) + { + render->text(_menu->items[i].tabs[p->Number()-1].c_str(), // text + p->Font().c_str(), p->Size(), p->Align(), // font, font-size, align + p->StaticX(), // x-pos + p->StaticY(), // y-pos + evaluateColor(p->Color().c_str(), rgba), // color + p->StaticWidth(), // width + lineHeight, 1); // height, line-count + } + } + } + + if (p->Width() == na) + continue; + + if (_menu->items[i].type == itPartingLine) + { + cDisplayItem* partingLine = section->getItemByKind(itemPartingLine); + + if (partingLine) + partingLine->drawPartingLine(_menu->items[i].tabs[0], y, lineHeight); + + continue; + } + + // draw image + + if (p->Focus() != "") + { + string f; + evaluate(f, p->Focus().c_str()); + render->image(f.c_str(), + p->X(), y, + p->Width(), lineHeight); + } + + if (p->Type() == "progress") + { + int current = 0; + int n = 0; + int barHeight = p->BarHeight(); + + if (p->BarHeightUnit() == iuPercent) // BarHeight in % of row + barHeight = (int)(((double)lineHeight/100) * (double)p->BarHeight()); + + // calc progress + + while (_menu->items[i].tabs[p->Number()-1][n]) + { + if (_menu->items[i].tabs[p->Number()-1][n] == '|') + current++; + n++; + } + + // draw column's progress-bar + + tell(5, "progress [%d] of (%d%%) at position y = %d [%s]", + i, current, y, _menu->items[i].tabs[p->Number()-1].c_str()); + + if (current && p->Path() != "") + { + string path = evaluatePath().c_str(); + + p->drawProgressBar(current, 8, path, y + (lineHeight-barHeight)/2, barHeight); + } + } + + else if (p->Type() == "image") + { + string path; + + if (p->Path() != "") + { + if (p->Path() == "{imageMap}") + path = Thms::theTheme->getPathFromImageMap(_menu->items[i].tabs[p->Number()-1].c_str()); + else + path = p->evaluatePath(); + } + else + path = p->channelLogoPath(_menu->items[i].tabs[p->Number()-1].c_str(), Format().c_str()); + + // draw column's image + + if (path.length()) + { + int barHeight = p->BarHeight(); + + if (p->BarHeightUnit() == iuPercent) // BarHeight in % of row + barHeight = (int)(((double)lineHeight/100) * (double)p->BarHeight()); + + int offset = (lineHeight - barHeight) / 2; + + render->image(path.c_str(), p->X(), y+offset, + p->Width(), barHeight, + p->Fit(), p->AspectRatio()); + } + } + + else + { + // draw column's text + + string text; + + if (p->Text() != "") + { + if (evaluate(text, p->Text().c_str()) != success) + text = ""; + } + else + text = _menu->items[i].tabs[p->Number()-1].c_str(); + + tell(5, "draw '%s'", text.c_str()); + + p->drawText(text.c_str(), y, lineHeight, no); + } + } + + selectedItem = 0; + + return done; +} + +//*************************************************************************** +// Menu Event Column +//*************************************************************************** + +int cDisplayMenuEventColumn::draw() +{ + // static int selectedOffset = 0; + + if (cDisplayItem::draw() != success) + return fail; + + if (!vdrStatus->_eventsReady) + return fail; + + cGraphTFTDisplay::MenuInfo* _menu = &vdrStatus->_menu; + + string path; + int count; + int lineHeight; + int lineHeightSelect = 0; + + int step = _menu->currentRow - _menu->currentRowLast; // step since last refresh + int afterSelect = 0; + + // calc height and row count ... + + lineHeight = Height() ? Height() : Size() * 5/3; + + if (selectedItem) + lineHeightSelect = selectedItem->Height() ? selectedItem->Height() : selectedItem->Size() * 5/3; + else + lineHeightSelect = lineHeight; // assume normal height this time + + count = ((_menu_height-lineHeightSelect) / lineHeight) + 1; + + _menu->visibleRows = count; + _menu->lineHeight = lineHeight; + _menu->lineHeightSelected = lineHeightSelect; + + if (_menu->topRow < 0) + _menu->topRow = max(0, _menu->currentRow - count/2); // initial + else if (_menu->currentRow == _menu->topRow-1) + _menu->topRow = _menu->currentRow; // up + else if (_menu->currentRow == _menu->topRow+count) + _menu->topRow++; // down + else if (_menu->currentRow < _menu->topRow + || _menu->currentRow > _menu->topRow+count) + _menu->topRow += step; // page up / page down + + if (_menu->topRow > (int)_menu->items.size()-count) + _menu->topRow = _menu->items.size()-count; + + if (_menu->topRow < _menu->currentRow-count) + _menu->topRow = _menu->currentRow-count; + + if (_menu->topRow < 0) + _menu->topRow = 0; + + if (step) + marquee_active = no; + + _menu->currentRowLast = _menu->currentRow; + + // paint the visible rows for this column + + for (int i = _menu->topRow; vdrStatus->_eventsReady && i < min((int)_menu->items.size(), _menu->topRow + count); ++i) + { + if (i == na) + tell(0, "XXXXXXXXXXXXXXXXXXXXXXXXX"); + + _menu->drawingRow = i; // the row + + cDisplayItem* p = i == _menu->currentRow ? selectedItem : this; + + if (!p) + continue; + + int y = p->MenuY() + ((i - _menu->topRow) * lineHeight) + afterSelect; + + tell(4, "colcount of row (%d) is (%d)", i, _menu->items[i].tabCount); + + if (i == _menu->currentRow) + afterSelect = lineHeightSelect - lineHeight; + + if (_menu->items[i].type == itPartingLine) + { + cDisplayItem* partingLine = section->getItemByKind(itemPartingLine); + + // nur für die erste Spalte zeichnen, partingLine + // soll alle Spalten abdecken! + + if (partingLine && Number() == 0) + partingLine->drawPartingLine(_menu->items[i].tabs[0], y, lineHeight); + + continue; + } + + // don't check condition for itPartingLine + + if (!p->evaluateCondition()) + continue; + + // draw focus image + + if (i == _menu->currentRow && p->Focus() != "") + { + string f; + evaluate(f, p->Focus().c_str()); + render->image(f.c_str(), + p->X(), y, // + selectedOffset, + p->Width(), lineHeight); + } + + if (p->Type() == "progress") + { + int barHeight = p->BarHeight(); + string value; + int current; + string path = ""; + + lookupVariable("rowEventProgress", value); + + current = atoi(value.c_str()); + + if (p->BarHeightUnit() == iuPercent) // BarHeight in % of row + barHeight = (int)(((double)lineHeight/100) * (double)p->BarHeight()); + + // draw column's progress-bar + + tell(5, "progress [%d] of (%d%%)", i, current); + + if (current && p->Path() != "") + { + string path = evaluatePath().c_str(); + p->drawProgressBar(current, 100, path, y + (lineHeight-barHeight)/2, barHeight); + } + } + + else if (p->Type() == "image") + { + path = p->evaluatePath(); + + // draw column's image + + if (path.length()) + { + int barHeight = p->BarHeight(); + + if (p->BarHeightUnit() == iuPercent) // BarHeight in % of row + barHeight = (int)(((double)lineHeight/100) * (double)p->BarHeight()); + + int offset = (lineHeight - barHeight) / 2; + + render->image(path.c_str(), p->X(), y+offset, + p->Width(), barHeight, + p->Fit(), p->AspectRatio()); + } + } + + else + { + // draw column's text + + string text; + int textHeight = p->Size() * 5/3; + textHeight -= textHeight/10; // 10% weniger + + if (evaluate(text, p->Text().c_str()) != success) + text = ""; + + tell(5, "draw '%s'", text.c_str()); + + if (!p->Line()) + p->drawText(text.c_str(), + y + (p->AlignV() ? (lineHeight-textHeight)/2 : 0), + lineHeight, no); + else + p->drawText(text.c_str(), + y + ((p->Line()-1)*textHeight), + textHeight, no); + } + } + + selectedItem = 0; + + return done; +} + +//*************************************************************************** +// Spectrum Analyzer +//*************************************************************************** + +int cDisplaySpectrumAnalyzer::draw() +{ + if (cDisplayItem::draw() != success) + return fail; + + if (cPluginManager::CallFirstService(SPAN_GET_BAR_HEIGHTS_ID, 0)) + { + // tell(0, "draw SpectrumAnalyzer II"); + + cSpanService::Span_GetBarHeights_v1_0 GetBarHeights; + + int bandsSA = 20; + int falloffSA = 8; + + unsigned int* barHeights = new unsigned int[bandsSA]; + unsigned int* barHeightsLeftChannel = new unsigned int[bandsSA]; + unsigned int* barHeightsRightChannel = new unsigned int[bandsSA]; + unsigned int* barPeaksBothChannels = new unsigned int[bandsSA]; + unsigned int* barPeaksLeftChannel = new unsigned int[bandsSA]; + unsigned int* barPeaksRightChannel = new unsigned int[bandsSA]; + unsigned int volumeLeftChannel; + unsigned int volumeRightChannel; + unsigned int volumeBothChannels; + + GetBarHeights.bands = bandsSA; + GetBarHeights.barHeights = barHeights; + GetBarHeights.barHeightsLeftChannel = barHeightsLeftChannel; + GetBarHeights.barHeightsRightChannel = barHeightsRightChannel; + GetBarHeights.volumeLeftChannel = &volumeLeftChannel; + GetBarHeights.volumeRightChannel = &volumeRightChannel; + GetBarHeights.volumeBothChannels = &volumeBothChannels; + + GetBarHeights.name = "graphtft"; + GetBarHeights.falloff = falloffSA; + GetBarHeights.barPeaksBothChannels = barPeaksBothChannels; + GetBarHeights.barPeaksLeftChannel = barPeaksLeftChannel; + GetBarHeights.barPeaksRightChannel = barPeaksRightChannel; + + if (cPluginManager::CallFirstService(SPAN_GET_BAR_HEIGHTS_ID, &GetBarHeights)) + { + int i; + t_rgba rgba; + int barWidth = Width() / (2 * bandsSA); + int width = barWidth * 2 * bandsSA; + + tell(4, "width = %d; barWidth = %d; ", width, barWidth); + + if (_path != "") + render->image(_path.c_str(), + X(), Y(), width, Height(), true); + else + render->rectangle(X(), Y(), + width, Height(), + evaluateColor(_color.c_str(), rgba)); + + for (i = 0; i < bandsSA; i++) + { + int2rgba(0, 0, 0, 255, rgba); + + render->rectangle(X() + 2 * barWidth * i, + Y(), + barWidth, + Height() - (barHeightsLeftChannel[i] * Height() / 100), + rgba); + + + render->rectangle(X() + 2 * barWidth * i + barWidth, + Y(), + barWidth, + Height() - (barHeightsRightChannel[i] * Height() / 100), + rgba); + + // the peak + + // height = barPeaksBothChannels[i] * item->Height() / 100; + + // if (height > 0) + // { + // render->rectangle(item->X() + barWidth*2*i+ barWidth + 1, + // item->Y(), + // item->X() + barWidth*2*i + barWidth+ barWidth + 1, + // height, + // item->Red(), item->Green(), item->Blue(), + // item->Transparent()); + // } + } + } + + delete[] barHeights; + delete[] barHeightsLeftChannel; + delete[] barHeightsRightChannel; + delete[] barPeaksBothChannels; + delete[] barPeaksLeftChannel; + delete[] barPeaksRightChannel; + } + + return done; +} |