| /*************************************************************************** |
| * __________ __ ___. |
| * Open \______ \ ____ ____ | | _\_ |__ _______ ___ |
| * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / |
| * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < |
| * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ |
| * \/ \/ \/ \/ \/ |
| * $Id$ |
| * |
| * Copyright (C) 2010 Robert Bieber |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * |
| * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| * KIND, either express or implied. |
| * |
| ****************************************************************************/ |
| |
| #include "symbols.h" |
| #include "tag_table.h" |
| |
| #include "parsetreenode.h" |
| #include "parsetreemodel.h" |
| |
| #include "rbimage.h" |
| #include "rbprogressbar.h" |
| #include "rbtoucharea.h" |
| |
| #include <iostream> |
| #include <cmath> |
| #include <cassert> |
| |
| #include <QDebug> |
| |
| int ParseTreeNode::openConditionals = 0; |
| bool ParseTreeNode::breakFlag = false; |
| |
| /* Root element constructor */ |
| ParseTreeNode::ParseTreeNode(struct skin_element* data, ParseTreeModel* model) |
| : parent(0), element(0), param(0), children(), model(model) |
| { |
| while(data) |
| { |
| children.append(new ParseTreeNode(data, this, model)); |
| data = data->next; |
| } |
| } |
| |
| /* Normal element constructor */ |
| ParseTreeNode::ParseTreeNode(struct skin_element* data, ParseTreeNode* parent, |
| ParseTreeModel* model) |
| : parent(parent), element(data), param(0), |
| children(), model(model) |
| { |
| switch(element->type) |
| { |
| |
| case TAG: |
| for(int i = 0; i < element->params_count; i++) |
| { |
| if(element->params[i].type == skin_tag_parameter::CODE) |
| children.append(new ParseTreeNode(element->params[i].data.code, |
| this, model)); |
| else |
| children.append(new ParseTreeNode(&element->params[i], this, |
| model)); |
| } |
| break; |
| |
| case CONDITIONAL: |
| for(int i = 0; i < element->params_count; i++) |
| children.append(new ParseTreeNode(&data->params[i], this, model)); |
| for(int i = 0; i < element->children_count; i++) |
| children.append(new ParseTreeNode(data->children[i], this, model)); |
| break; |
| |
| case LINE_ALTERNATOR: |
| for(int i = 0; i < element->children_count; i++) |
| { |
| children.append(new ParseTreeNode(data->children[i], this, model)); |
| } |
| break; |
| |
| case VIEWPORT: |
| for(int i = 0; i < element->params_count; i++) |
| children.append(new ParseTreeNode(&data->params[i], this, model)); |
| /* Deliberate fall-through here */ |
| |
| case LINE: |
| for(int i = 0; i < data->children_count; i++) |
| { |
| for(struct skin_element* current = data->children[i]; current; |
| current = current->next) |
| { |
| children.append(new ParseTreeNode(current, this, model)); |
| } |
| } |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| /* Parameter constructor */ |
| ParseTreeNode::ParseTreeNode(skin_tag_parameter *data, ParseTreeNode *parent, |
| ParseTreeModel *model) |
| : parent(parent), element(0), param(data), |
| children(), model(model) |
| { |
| |
| } |
| |
| ParseTreeNode::~ParseTreeNode() |
| { |
| for(int i = 0; i < children.count(); i++) |
| delete children[i]; |
| } |
| |
| QString ParseTreeNode::genCode() const |
| { |
| QString buffer = ""; |
| |
| if(element) |
| { |
| switch(element->type) |
| { |
| case UNKNOWN: |
| break; |
| case VIEWPORT: |
| /* Generating the Viewport tag, if necessary */ |
| if(element->tag) |
| { |
| buffer.append(TAGSYM); |
| buffer.append(element->tag->name); |
| buffer.append(ARGLISTOPENSYM); |
| for(int i = 0; i < element->params_count; i++) |
| { |
| buffer.append(children[i]->genCode()); |
| if(i != element->params_count - 1) |
| buffer.append(ARGLISTSEPARATESYM); |
| } |
| buffer.append(ARGLISTCLOSESYM); |
| buffer.append('\n'); |
| } |
| |
| for(int i = element->params_count; i < children.count(); i++) |
| buffer.append(children[i]->genCode()); |
| break; |
| |
| case LINE: |
| for(int i = 0; i < children.count(); i++) |
| { |
| buffer.append(children[i]->genCode()); |
| } |
| if(openConditionals == 0 |
| && !(parent && parent->element->type == LINE_ALTERNATOR) |
| && !(children.count() > 0 && |
| children[children.count() - 1]->getElement()->type |
| == COMMENT)) |
| { |
| buffer.append('\n'); |
| } |
| break; |
| |
| case LINE_ALTERNATOR: |
| for(int i = 0; i < children.count(); i++) |
| { |
| buffer.append(children[i]->genCode()); |
| if(i != children.count() - 1) |
| buffer.append(MULTILINESYM); |
| } |
| if(openConditionals == 0) |
| buffer.append('\n'); |
| break; |
| |
| case CONDITIONAL: |
| openConditionals++; |
| |
| /* Inserting the tag part */ |
| buffer.append(TAGSYM); |
| buffer.append(CONDITIONSYM); |
| buffer.append(element->tag->name); |
| if(element->params_count > 0) |
| { |
| buffer.append(ARGLISTOPENSYM); |
| for(int i = 0; i < element->params_count; i++) |
| { |
| buffer.append(children[i]->genCode()); |
| if( i != element->params_count - 1) |
| buffer.append(ARGLISTSEPARATESYM); |
| buffer.append(ARGLISTCLOSESYM); |
| } |
| } |
| |
| /* Inserting the sublines */ |
| buffer.append(ENUMLISTOPENSYM); |
| for(int i = element->params_count; i < children.count(); i++) |
| { |
| buffer.append(children[i]->genCode()); |
| if(i != children.count() - 1) |
| buffer.append(ENUMLISTSEPARATESYM); |
| } |
| buffer.append(ENUMLISTCLOSESYM); |
| openConditionals--; |
| break; |
| |
| case TAG: |
| buffer.append(TAGSYM); |
| buffer.append(element->tag->name); |
| |
| if(element->params_count > 0) |
| { |
| /* Rendering parameters if there are any */ |
| buffer.append(ARGLISTOPENSYM); |
| for(int i = 0; i < children.count(); i++) |
| { |
| buffer.append(children[i]->genCode()); |
| if(i != children.count() - 1) |
| buffer.append(ARGLISTSEPARATESYM); |
| } |
| buffer.append(ARGLISTCLOSESYM); |
| } |
| if(element->tag->params[strlen(element->tag->params) - 1] == '\n') |
| buffer.append('\n'); |
| break; |
| |
| case TEXT: |
| for(char* cursor = (char*)element->data; *cursor; cursor++) |
| { |
| if(find_escape_character(*cursor)) |
| buffer.append(TAGSYM); |
| buffer.append(*cursor); |
| } |
| break; |
| |
| case COMMENT: |
| buffer.append(COMMENTSYM); |
| buffer.append((char*)element->data); |
| buffer.append('\n'); |
| break; |
| } |
| } |
| else if(param) |
| { |
| switch(param->type) |
| { |
| case skin_tag_parameter::STRING: |
| for(char* cursor = param->data.text; *cursor; cursor++) |
| { |
| if(find_escape_character(*cursor)) |
| buffer.append(TAGSYM); |
| buffer.append(*cursor); |
| } |
| break; |
| |
| case skin_tag_parameter::INTEGER: |
| buffer.append(QString::number(param->data.number, 10)); |
| break; |
| |
| case skin_tag_parameter::DECIMAL: |
| buffer.append(QString::number(param->data.number / 10., 'f', 1)); |
| break; |
| |
| case skin_tag_parameter::DEFAULT: |
| buffer.append(DEFAULTSYM); |
| break; |
| |
| case skin_tag_parameter::CODE: |
| buffer.append(QObject::tr("This doesn't belong here")); |
| break; |
| |
| } |
| } |
| else |
| { |
| for(int i = 0; i < children.count(); i++) |
| buffer.append(children[i]->genCode()); |
| } |
| |
| return buffer; |
| } |
| |
| /* A more or less random hashing algorithm */ |
| int ParseTreeNode::genHash() const |
| { |
| int hash = 0; |
| char *text; |
| |
| if(element) |
| { |
| hash += element->type; |
| switch(element->type) |
| { |
| case UNKNOWN: |
| break; |
| case VIEWPORT: |
| case LINE: |
| case LINE_ALTERNATOR: |
| case CONDITIONAL: |
| hash += element->children_count; |
| break; |
| |
| case TAG: |
| for(unsigned int i = 0; i < strlen(element->tag->name); i++) |
| hash += element->tag->name[i]; |
| break; |
| |
| case COMMENT: |
| case TEXT: |
| text = (char*)element->data; |
| for(unsigned int i = 0; i < strlen(text); i++) |
| { |
| if(i % 2) |
| hash += text[i] % element->type; |
| else |
| hash += text[i] % element->type * 2; |
| } |
| break; |
| } |
| |
| } |
| |
| if(param) |
| { |
| hash += param->type; |
| switch(param->type) |
| { |
| case skin_tag_parameter::DEFAULT: |
| case skin_tag_parameter::CODE: |
| break; |
| |
| case skin_tag_parameter::INTEGER: |
| hash += param->data.number * (param->data.number / 4); |
| break; |
| |
| case skin_tag_parameter::STRING: |
| for(unsigned int i = 0; i < strlen(param->data.text); i++) |
| { |
| if(i % 2) |
| hash += param->data.text[i] * 2; |
| else |
| hash += param->data.text[i]; |
| } |
| break; |
| |
| case skin_tag_parameter::DECIMAL: |
| hash += param->data.number; |
| break; |
| } |
| } |
| |
| for(int i = 0; i < children.count(); i++) |
| { |
| hash += children[i]->genHash(); |
| } |
| |
| return hash; |
| } |
| |
| ParseTreeNode* ParseTreeNode::child(int row) |
| { |
| if(row < 0 || row >= children.count()) |
| return 0; |
| |
| return children[row]; |
| } |
| |
| int ParseTreeNode::numChildren() const |
| { |
| return children.count(); |
| } |
| |
| |
| QVariant ParseTreeNode::data(int column) const |
| { |
| switch(column) |
| { |
| case ParseTreeModel::typeColumn: |
| if(element) |
| { |
| switch(element->type) |
| { |
| case UNKNOWN: |
| return QObject::tr("Unknown"); |
| case VIEWPORT: |
| return QObject::tr("Viewport"); |
| |
| case LINE: |
| return QObject::tr("Logical Line"); |
| |
| case LINE_ALTERNATOR: |
| return QObject::tr("Alternator"); |
| |
| case COMMENT: |
| return QObject::tr("Comment"); |
| |
| case CONDITIONAL: |
| return QObject::tr("Conditional Tag"); |
| |
| case TAG: |
| return QObject::tr("Tag"); |
| |
| case TEXT: |
| return QObject::tr("Plaintext"); |
| } |
| } |
| else if(param) |
| { |
| switch(param->type) |
| { |
| case skin_tag_parameter::STRING: |
| return QObject::tr("String"); |
| |
| case skin_tag_parameter::INTEGER: |
| return QObject::tr("Integer"); |
| |
| case skin_tag_parameter::DECIMAL: |
| return QObject::tr("Decimal"); |
| |
| case skin_tag_parameter::DEFAULT: |
| return QObject::tr("Default Argument"); |
| |
| case skin_tag_parameter::CODE: |
| return QObject::tr("This doesn't belong here"); |
| } |
| } |
| else |
| { |
| return QObject::tr("Root"); |
| } |
| |
| break; |
| |
| case ParseTreeModel::valueColumn: |
| if(element) |
| { |
| switch(element->type) |
| { |
| case UNKNOWN: |
| case VIEWPORT: |
| case LINE: |
| case LINE_ALTERNATOR: |
| return QString(); |
| |
| case CONDITIONAL: |
| return QString(element->tag->name); |
| |
| case TEXT: |
| case COMMENT: |
| return QString((char*)element->data); |
| |
| case TAG: |
| return QString(element->tag->name); |
| } |
| } |
| else if(param) |
| { |
| switch(param->type) |
| { |
| case skin_tag_parameter::DEFAULT: |
| return QObject::tr("-"); |
| |
| case skin_tag_parameter::STRING: |
| return QString(param->data.text); |
| |
| case skin_tag_parameter::INTEGER: |
| return QString::number(param->data.number, 10); |
| |
| case skin_tag_parameter::DECIMAL: |
| return QString::number(param->data.number / 10., 'f', 1); |
| |
| case skin_tag_parameter::CODE: |
| return QObject::tr("Seriously, something's wrong here"); |
| |
| } |
| } |
| else |
| { |
| return QString(); |
| } |
| break; |
| |
| case ParseTreeModel::lineColumn: |
| if(element) |
| return QString::number(element->line, 10); |
| else |
| return QString(); |
| break; |
| } |
| |
| return QVariant(); |
| } |
| |
| |
| int ParseTreeNode::getRow() const |
| { |
| if(!parent) |
| return -1; |
| |
| return parent->children.indexOf(const_cast<ParseTreeNode*>(this)); |
| } |
| |
| ParseTreeNode* ParseTreeNode::getParent() const |
| { |
| return parent; |
| } |
| |
| /* This version is called for the root node and for viewports */ |
| void ParseTreeNode::render(const RBRenderInfo& info) |
| { |
| /* Parameters don't get rendered */ |
| if(!element && param) |
| return; |
| |
| /* If we're at the root, we need to render each viewport */ |
| if(!element && !param) |
| { |
| for(int i = 0; i < children.count(); i++) |
| { |
| children[i]->render(info); |
| } |
| |
| return; |
| } |
| |
| if(element->type != VIEWPORT) |
| { |
| std::cerr << QObject::tr("Error in parse tree").toStdString() |
| << std::endl; |
| return; |
| } |
| |
| rendered = new RBViewport(element, info, this); |
| |
| for(int i = element->params_count; i < children.count(); i++) |
| children[i]->render(info, dynamic_cast<RBViewport*>(rendered)); |
| |
| } |
| |
| /* This version is called for logical lines, tags, conditionals and such */ |
| void ParseTreeNode::render(const RBRenderInfo &info, RBViewport* viewport, |
| bool noBreak) |
| { |
| if(!element) |
| return; |
| |
| if(element->type == LINE) |
| { |
| for(int i = 0; i < children.count(); i++) |
| children[i]->render(info, viewport); |
| if(!noBreak && !breakFlag) |
| viewport->newLine(); |
| else |
| viewport->flushText(); |
| |
| if(breakFlag) |
| breakFlag = false; |
| } |
| else if(element->type == TEXT) |
| { |
| viewport->write(QString(static_cast<char*>(element->data))); |
| } |
| else if(element->type == TAG) |
| { |
| if(!execTag(info, viewport)) |
| viewport->write(evalTag(info).toString()); |
| if(element->tag->flags & NOBREAK) |
| breakFlag = true; |
| } |
| else if(element->type == CONDITIONAL) |
| { |
| int child = evalTag(info, true, element->children_count).toInt(); |
| int max = children.count() - element->params_count; |
| if(child < max) |
| { |
| children[element->params_count + child] |
| ->render(info, viewport, true); |
| } |
| } |
| else if(element->type == LINE_ALTERNATOR) |
| { |
| /* First we build a list of the times for each branch */ |
| QList<double> times; |
| for(int i = 0; i < children.count() ; i++) |
| times.append(findBranchTime(children[i], info)); |
| |
| double totalTime = 0; |
| for(int i = 0; i < children.count(); i++) |
| totalTime += times[i]; |
| |
| /* Now we figure out which branch to select */ |
| double timeLeft = info.device()->data(QString("simtime")).toDouble(); |
| |
| /* Skipping any full cycles */ |
| timeLeft -= totalTime * std::floor(timeLeft / totalTime); |
| |
| int branch = 0; |
| while(timeLeft > 0) |
| { |
| timeLeft -= times[branch]; |
| if(timeLeft >= 0) |
| branch++; |
| else |
| break; |
| if(branch >= times.count()) |
| branch = 0; |
| } |
| |
| /* In case we end up on a disabled branch, skip ahead. If we find that |
| * all the branches are disabled, don't render anything |
| */ |
| int originalBranch = branch; |
| while(times[branch] == 0) |
| { |
| branch++; |
| if(branch == originalBranch) |
| { |
| branch = -1; |
| break; |
| } |
| if(branch >= times.count()) |
| branch = 0; |
| } |
| |
| /* ...and finally render the selected branch */ |
| if(branch >= 0) |
| children[branch]->render(info, viewport, true); |
| } |
| } |
| |
| bool ParseTreeNode::execTag(const RBRenderInfo& info, RBViewport* viewport) |
| { |
| |
| QString filename; |
| QString id; |
| int x, y, tiles, tile, maxWidth, maxHeight, width, height; |
| char c, hAlign, vAlign; |
| RBImage* image; |
| QPixmap temp; |
| RBFont* fLoad; |
| |
| /* Two switch statements to narrow down the tag name */ |
| switch(element->tag->name[0]) |
| { |
| |
| case 'a': |
| switch(element->tag->name[1]) |
| { |
| case 'c': |
| /* %ac */ |
| viewport->alignText(RBViewport::Center); |
| return true; |
| |
| case 'l': |
| /* %al */ |
| viewport->alignText(RBViewport::Left); |
| return true; |
| |
| case 'r': |
| /* %ar */ |
| viewport->alignText(RBViewport::Right); |
| return true; |
| |
| case 'x': |
| /* %ax */ |
| info.screen()->RtlMirror(); |
| return true; |
| |
| case 'L': |
| /* %aL */ |
| if(info.device()->data("rtl").toBool()) |
| viewport->alignText(RBViewport::Right); |
| else |
| viewport->alignText(RBViewport::Left); |
| return true; |
| |
| case 'R': |
| /* %aR */ |
| if(info.device()->data("rtl").toBool()) |
| viewport->alignText(RBViewport::Left); |
| else |
| viewport->alignText(RBViewport::Right); |
| return true; |
| } |
| |
| return false; |
| |
| case 'p': |
| switch(element->tag->name[1]) |
| { |
| case 'b': |
| /* %pb */ |
| new RBProgressBar(viewport, info, this); |
| return true; |
| |
| case 'v': |
| /* %pv */ |
| if(element->params_count > 0) |
| { |
| new RBProgressBar(viewport, info, this, true); |
| return true; |
| } |
| else |
| return false; |
| } |
| |
| return false; |
| |
| case 's': |
| switch(element->tag->name[1]) |
| { |
| case '\0': |
| /* %s */ |
| viewport->scrollText(info.device()->data("simtime").toDouble()); |
| return true; |
| } |
| |
| return false; |
| |
| case 'w': |
| switch(element->tag->name[1]) |
| { |
| case 'd': |
| /* %wd */ |
| info.screen()->breakSBS(); |
| return true; |
| |
| case 'e': |
| /* %we */ |
| /* Totally extraneous */ |
| return true; |
| |
| case 'i': |
| /* %wi */ |
| viewport->enableStatusBar(); |
| return true; |
| } |
| |
| return false; |
| |
| case 'x': |
| switch(element->tag->name[1]) |
| { |
| case 'd': |
| /* %xd */ |
| |
| /* Breaking up into cases, getting the id first */ |
| if(element->params_count == 1) |
| { |
| /* The old fashioned style */ |
| id = ""; |
| id.append(element->params[0].data.text[0]); |
| } |
| else |
| { |
| id = QString(element->params[0].data.text); |
| } |
| |
| |
| if(info.screen()->getImage(id)) |
| { |
| /* Fetching the image if available */ |
| image = new RBImage(*(info.screen()->getImage(id)), viewport); |
| } |
| else |
| { |
| image = 0; |
| } |
| |
| /* Now determining the particular tile to load */ |
| if(element->params_count == 1) |
| { |
| c = element->params[0].data.text[1]; |
| |
| if(c == '\0') |
| { |
| tile = 1; |
| } |
| else |
| { |
| if(isupper(c)) |
| tile = c - 'A' + 25; |
| else |
| tile = c - 'a'; |
| } |
| |
| }else{ |
| /* If the next param is just an int, use it as the tile */ |
| if(element->params[1].type == skin_tag_parameter::INTEGER) |
| { |
| tile = element->params[1].data.number - 1; |
| } |
| else |
| { |
| tile = children[1]->evalTag(info, true, |
| image->numTiles()).toInt(); |
| |
| /* Adding the offset if there is one */ |
| if(element->params_count == 3) |
| tile += element->params[2].data.number; |
| if(tile < 0) |
| { |
| /* If there is no image for the current status, then |
| * just refrain from showing anything |
| */ |
| delete image; |
| return true; |
| } |
| } |
| } |
| |
| if(image) |
| { |
| image->setTile(tile); |
| image->show(); |
| image->enableMovement(); |
| } |
| |
| return true; |
| |
| case 'l': |
| /* %xl */ |
| id = element->params[0].data.text; |
| if(element->params[1].data.text == QString("__list_icons__")) |
| { |
| filename = info.settings()->value("iconset", ""); |
| filename.replace(".rockbox", |
| info.settings()->value("themebase")); |
| temp.load(filename); |
| if(!temp.isNull()) |
| { |
| tiles = temp.height() / temp.width(); |
| } |
| } |
| else |
| { |
| filename = info.settings()->value("imagepath", "") + "/" + |
| element->params[1].data.text; |
| tiles = 1; |
| } |
| x = element->params[2].data.number; |
| y = element->params[3].data.number; |
| if(element->params_count > 4) |
| tiles = element->params[4].data.number; |
| |
| info.screen()->loadImage(id, new RBImage(filename, tiles, x, y, |
| this, viewport)); |
| return true; |
| |
| case '\0': |
| /* %x */ |
| id = element->params[0].data.text; |
| filename = info.settings()->value("imagepath", "") + "/" + |
| element->params[1].data.text; |
| x = element->params[2].data.number; |
| y = element->params[3].data.number; |
| image = new RBImage(filename, 1, x, y, this, viewport); |
| info.screen()->loadImage(id, image); |
| image->show(); |
| image->enableMovement(); |
| |
| return true; |
| |
| } |
| |
| return false; |
| |
| case 'C': |
| switch(element->tag->name[1]) |
| { |
| case 'd': |
| /* %Cd */ |
| info.screen()->showAlbumArt(viewport); |
| return true; |
| |
| case 'l': |
| /* %Cl */ |
| x = element->params[0].data.number; |
| y = element->params[1].data.number; |
| maxWidth = element->params[2].data.number; |
| maxHeight = element->params[3].data.number; |
| hAlign = element->params_count > 4 |
| ? element->params[4].data.text[0] : 'c'; |
| vAlign = element->params_count > 5 |
| ? element->params[5].data.text[0] : 'c'; |
| width = info.device()->data("artwidth").toInt(); |
| height = info.device()->data("artheight").toInt(); |
| info.screen()->setAlbumArt(new RBAlbumArt(viewport, x, y, maxWidth, |
| maxHeight, width, height, |
| this, hAlign, vAlign)); |
| return true; |
| } |
| |
| return false; |
| |
| case 'F': |
| |
| switch(element->tag->name[1]) |
| { |
| |
| case 'l': |
| /* %Fl */ |
| x = element->params[0].data.number; |
| filename = info.settings()->value("themebase", "") + "/fonts/" + |
| element->params[1].data.text; |
| fLoad = new RBFont(filename); |
| if(!fLoad->isValid()) |
| dynamic_cast<RBScene*>(info.screen()->scene()) |
| ->addWarning(QObject::tr("Missing font file: ") + filename); |
| info.screen()->loadFont(x, fLoad); |
| return true; |
| |
| } |
| |
| return false; |
| |
| case 'T': |
| switch(element->tag->name[1]) |
| { |
| case '\0': |
| /* %T */ |
| if(element->params_count < 5) |
| return false; |
| int x = element->params[0].data.number; |
| int y = element->params[1].data.number; |
| int width = element->params[2].data.number; |
| int height = element->params[3].data.number; |
| QString action(element->params[4].data.text); |
| RBTouchArea* temp = new RBTouchArea(width, height, action, info, |
| viewport); |
| temp->setPos(x, y); |
| return true; |
| } |
| |
| return false; |
| |
| case 'V': |
| |
| switch(element->tag->name[1]) |
| { |
| |
| case 'b': |
| /* %Vb */ |
| viewport->setBGColor(RBScreen:: |
| stringToColor(QString(element->params[0]. |
| data.text), |
| Qt::white)); |
| return true; |
| |
| case 'd': |
| /* %Vd */ |
| id = element->params[0].data.text; |
| info.screen()->showViewport(id); |
| return true; |
| |
| case 'f': |
| /* %Vf */ |
| viewport->setFGColor(RBScreen:: |
| stringToColor(QString(element->params[0]. |
| data.text), |
| Qt::black)); |
| return true; |
| |
| case 'p': |
| /* %Vp */ |
| viewport->showPlaylist(info, element->params[0].data.number, |
| children[1]); |
| return true; |
| |
| case 'I': |
| /* %VI */ |
| info.screen()->makeCustomUI(element->params[0].data.text); |
| return true; |
| |
| } |
| |
| return false; |
| |
| case 'X': |
| |
| switch(element->tag->name[1]) |
| { |
| case '\0': |
| /* %X */ |
| filename = QString(element->params[0].data.text); |
| info.screen()->setBackdrop(filename); |
| return true; |
| } |
| |
| return false; |
| |
| } |
| |
| return false; |
| |
| } |
| |
| QVariant ParseTreeNode::evalTag(const RBRenderInfo& info, bool conditional, |
| int branches) |
| { |
| if(!conditional) |
| { |
| if(element->tag->name[0] == 'c' && !info.device()->data("cc").toBool()) |
| return QString(); |
| |
| if(QString(element->tag->name) == "Sx") |
| return element->params[0].data.text; |
| return info.device()->data(QString(element->tag->name), |
| element->params_count, element->params); |
| } |
| else |
| { |
| /* If we're evaluating for a conditional, we return the child branch |
| * index that should be selected. For true/false values, this is |
| * 0 for true, 1 for false, and we also have to make sure not to |
| * ever exceed the number of available children |
| */ |
| |
| int child; |
| QVariant val = info.device()->data("?" + QString(element->tag->name)); |
| if(val.isNull()) |
| val = info.device()->data(QString(element->tag->name), |
| element->params_count, element->params); |
| |
| if(val.isNull()) |
| { |
| child = 1; |
| } |
| else if(QString(element->tag->name) == "bl") |
| { |
| /* bl has to be scaled to the number of available children, but it |
| * also has an initial -1 value for an unknown state */ |
| child = val.toInt(); |
| if(child == -1) |
| { |
| child = 0; |
| } |
| else |
| { |
| child = ((branches - 1) * child / 100) + 1; |
| } |
| } |
| else if(QString(element->tag->name) == "pv") |
| { |
| /* ?pv gets scaled to the number of available children, sandwiched |
| * in between mute and 0/>0dB. I assume a floor of -50dB for the |
| * time being |
| */ |
| int dB = val.toInt(); |
| |
| if(dB < -50) |
| child = 0; |
| else if(dB == 0) |
| child = branches - 2; |
| else if(dB > 0) |
| child = branches - 1; |
| else |
| { |
| int options = branches - 3; |
| child = (options * (dB + 50)) / 50; |
| } |
| } |
| else if(QString(element->tag->name) == "px") |
| { |
| child = val.toInt(); |
| child = branches * child / 100; |
| } |
| else if(val.type() == QVariant::Bool) |
| { |
| /* Boolean values have to be reversed, because conditionals are |
| * always of the form %?tag<true|false> |
| */ |
| if(val.toBool()) |
| child = 0; |
| else |
| child = 1; |
| } |
| else if(element->tag->name[0] == 'i' || element->tag->name[0] == 'I' |
| || element->tag->name[0] == 'f' || element->tag->name[0] == 'F') |
| { |
| if(info.device()->data("id3available").toBool()) |
| child = 0; |
| else |
| child = 1; |
| } |
| else if(val.type() == QVariant::String) |
| { |
| if(val.toString().length() > 0) |
| child = 0; |
| else |
| child = 1; |
| } |
| else |
| { |
| child = val.toInt(); |
| } |
| |
| if(child < 0) |
| child = 0; |
| |
| if(child < branches) |
| return child; |
| else if(branches == 1) |
| return 2; |
| else |
| return branches - 1; |
| } |
| } |
| |
| double ParseTreeNode::findBranchTime(ParseTreeNode *branch, |
| const RBRenderInfo& info) |
| { |
| double retval = 2; |
| for(int i = 0; i < branch->children.count(); i++) |
| { |
| ParseTreeNode* current = branch->children[i]; |
| if(current->element->type == TAG) |
| { |
| if(current->element->tag->name[0] == 't' |
| && current->element->tag->name[1] == '\0') |
| { |
| retval = current->element->params[0].data.number / 10.; |
| } |
| } |
| else if(current->element->type == CONDITIONAL) |
| { |
| retval = findConditionalTime(current, info); |
| } |
| } |
| return retval; |
| } |
| |
| double ParseTreeNode::findConditionalTime(ParseTreeNode *conditional, |
| const RBRenderInfo& info) |
| { |
| int child = conditional->evalTag(info, true, |
| conditional->children.count()).toInt(); |
| if(child >= conditional->children.count()) |
| child = conditional->children.count() - 1; |
| |
| return findBranchTime(conditional->children[child], info); |
| } |
| |
| void ParseTreeNode::modParam(QVariant value, int index) |
| { |
| if(element) |
| { |
| if(index < 0) |
| return; |
| while(index >= children.count()) |
| { |
| /* Padding children with defaults until we make the necessary |
| * parameter available |
| */ |
| skin_tag_parameter* newParam = new skin_tag_parameter; |
| newParam->type = skin_tag_parameter::DEFAULT; |
| /* We'll need to manually delete the extra parameters in the |
| * destructor |
| */ |
| extraParams.append(children.count()); |
| |
| children.append(new ParseTreeNode(newParam, this, model)); |
| element->params_count++; |
| } |
| |
| children[index]->modParam(value); |
| } |
| else if(param) |
| { |
| if(value.type() == QVariant::Double) |
| { |
| param->type = skin_tag_parameter::DECIMAL; |
| param->data.number = static_cast<int>(value.toDouble() * 10); |
| } |
| else if(value.type() == QVariant::String) |
| { |
| param->type = skin_tag_parameter::STRING; |
| free(param->data.text); |
| param->data.text = strdup(value.toString().toStdString().c_str()); |
| } |
| else if(value.type() == QVariant::Int) |
| { |
| param->type = skin_tag_parameter::INTEGER; |
| param->data.number = value.toInt(); |
| } |
| |
| model->paramChanged(this); |
| |
| } |
| } |