A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 1204 lines 34 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * Copyright (C) 2010 Robert Bieber 11 * 12 * This program is free software; you can redistribute it and/or 13 * modify it under the terms of the GNU General Public License 14 * as published by the Free Software Foundation; either version 2 15 * of the License, or (at your option) any later version. 16 * 17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 * KIND, either express or implied. 19 * 20 ****************************************************************************/ 21 22#include "symbols.h" 23#include "tag_table.h" 24 25#include "parsetreenode.h" 26#include "parsetreemodel.h" 27 28#include "rbimage.h" 29#include "rbprogressbar.h" 30#include "rbtoucharea.h" 31 32#include <iostream> 33#include <cmath> 34#include <cassert> 35 36#include <QDebug> 37 38int ParseTreeNode::openConditionals = 0; 39bool ParseTreeNode::breakFlag = false; 40 41/* Root element constructor */ 42ParseTreeNode::ParseTreeNode(struct skin_element* data, ParseTreeModel* model) 43 : parent(0), element(0), param(0), children(), model(model) 44{ 45 while(data) 46 { 47 children.append(new ParseTreeNode(data, this, model)); 48 data = data->next; 49 } 50} 51 52/* Normal element constructor */ 53ParseTreeNode::ParseTreeNode(struct skin_element* data, ParseTreeNode* parent, 54 ParseTreeModel* model) 55 : parent(parent), element(data), param(0), 56 children(), model(model) 57{ 58 switch(element->type) 59 { 60 61 case TAG: 62 for(int i = 0; i < element->params_count; i++) 63 { 64 if(element->params[i].type == skin_tag_parameter::CODE) 65 children.append(new ParseTreeNode(element->params[i].data.code, 66 this, model)); 67 else 68 children.append(new ParseTreeNode(&element->params[i], this, 69 model)); 70 } 71 break; 72 73 case CONDITIONAL: 74 for(int i = 0; i < element->params_count; i++) 75 children.append(new ParseTreeNode(&data->params[i], this, model)); 76 for(int i = 0; i < element->children_count; i++) 77 children.append(new ParseTreeNode(data->children[i], this, model)); 78 break; 79 80 case LINE_ALTERNATOR: 81 for(int i = 0; i < element->children_count; i++) 82 { 83 children.append(new ParseTreeNode(data->children[i], this, model)); 84 } 85 break; 86 87case VIEWPORT: 88 for(int i = 0; i < element->params_count; i++) 89 children.append(new ParseTreeNode(&data->params[i], this, model)); 90 /* Intentional fallthrough */ 91 case LINE: 92 for(int i = 0; i < data->children_count; i++) 93 { 94 for(struct skin_element* current = data->children[i]; current; 95 current = current->next) 96 { 97 children.append(new ParseTreeNode(current, this, model)); 98 } 99 } 100 break; 101 102 default: 103 break; 104 } 105} 106 107/* Parameter constructor */ 108ParseTreeNode::ParseTreeNode(skin_tag_parameter *data, ParseTreeNode *parent, 109 ParseTreeModel *model) 110 : parent(parent), element(0), param(data), 111 children(), model(model) 112{ 113 114} 115 116ParseTreeNode::~ParseTreeNode() 117{ 118 for(int i = 0; i < children.count(); i++) 119 delete children[i]; 120} 121 122QString ParseTreeNode::genCode() const 123{ 124 QString buffer = ""; 125 126 if(element) 127 { 128 switch(element->type) 129 { 130 case UNKNOWN: 131 break; 132 case VIEWPORT: 133 /* Generating the Viewport tag, if necessary */ 134 if(element->tag) 135 { 136 buffer.append(TAGSYM); 137 buffer.append(element->tag->name); 138 buffer.append(ARGLISTOPENSYM); 139 for(int i = 0; i < element->params_count; i++) 140 { 141 buffer.append(children[i]->genCode()); 142 if(i != element->params_count - 1) 143 buffer.append(ARGLISTSEPARATESYM); 144 } 145 buffer.append(ARGLISTCLOSESYM); 146 buffer.append('\n'); 147 } 148 149 for(int i = element->params_count; i < children.count(); i++) 150 buffer.append(children[i]->genCode()); 151 break; 152 153 case LINE: 154 for(int i = 0; i < children.count(); i++) 155 { 156 buffer.append(children[i]->genCode()); 157 } 158 if(openConditionals == 0 159 && !(parent && parent->element->type == LINE_ALTERNATOR) 160 && !(children.count() > 0 && 161 children[children.count() - 1]->getElement()->type 162 == COMMENT)) 163 { 164 buffer.append('\n'); 165 } 166 break; 167 168 case LINE_ALTERNATOR: 169 for(int i = 0; i < children.count(); i++) 170 { 171 buffer.append(children[i]->genCode()); 172 if(i != children.count() - 1) 173 buffer.append(MULTILINESYM); 174 } 175 if(openConditionals == 0) 176 buffer.append('\n'); 177 break; 178 179 case CONDITIONAL: 180 openConditionals++; 181 182 /* Inserting the tag part */ 183 buffer.append(TAGSYM); 184 buffer.append(CONDITIONSYM); 185 buffer.append(element->tag->name); 186 if(element->params_count > 0) 187 { 188 buffer.append(ARGLISTOPENSYM); 189 for(int i = 0; i < element->params_count; i++) 190 { 191 buffer.append(children[i]->genCode()); 192 if( i != element->params_count - 1) 193 buffer.append(ARGLISTSEPARATESYM); 194 buffer.append(ARGLISTCLOSESYM); 195 } 196 } 197 198 /* Inserting the sublines */ 199 buffer.append(ENUMLISTOPENSYM); 200 for(int i = element->params_count; i < children.count(); i++) 201 { 202 buffer.append(children[i]->genCode()); 203 if(i != children.count() - 1) 204 buffer.append(ENUMLISTSEPARATESYM); 205 } 206 buffer.append(ENUMLISTCLOSESYM); 207 openConditionals--; 208 break; 209 210 case TAG: 211 buffer.append(TAGSYM); 212 buffer.append(element->tag->name); 213 214 if(element->params_count > 0) 215 { 216 /* Rendering parameters if there are any */ 217 buffer.append(ARGLISTOPENSYM); 218 for(int i = 0; i < children.count(); i++) 219 { 220 buffer.append(children[i]->genCode()); 221 if(i != children.count() - 1) 222 buffer.append(ARGLISTSEPARATESYM); 223 } 224 buffer.append(ARGLISTCLOSESYM); 225 } 226 if (element->tag->param_pos > 1) 227 { 228 const char *param = element->tag->name + element->tag->param_pos; 229 if(param[strlen(param) - 1] == '\n') 230 buffer.append('\n'); 231 } 232 break; 233 234 case TEXT: 235 for(char* cursor = (char*)element->data; *cursor; cursor++) 236 { 237 if(find_escape_character(*cursor)) 238 buffer.append(TAGSYM); 239 buffer.append(*cursor); 240 } 241 break; 242 243 case COMMENT: 244 buffer.append(COMMENTSYM); 245 buffer.append((char*)element->data); 246 buffer.append('\n'); 247 break; 248 } 249 } 250 else if(param) 251 { 252 switch(param->type) 253 { 254 case skin_tag_parameter::STRING: 255 for(char* cursor = param->data.text; *cursor; cursor++) 256 { 257 if(find_escape_character(*cursor)) 258 buffer.append(TAGSYM); 259 buffer.append(*cursor); 260 } 261 break; 262 263 case skin_tag_parameter::INTEGER: 264 buffer.append(QString::number(param->data.number, 10)); 265 break; 266 267 case skin_tag_parameter::DECIMAL: 268 case skin_tag_parameter::PERCENT: 269 buffer.append(QString::number(param->data.number / 10., 'f', 1)); 270 break; 271 272 case skin_tag_parameter::DEFAULT: 273 buffer.append(DEFAULTSYM); 274 break; 275 276 case skin_tag_parameter::CODE: 277 buffer.append(QObject::tr("This doesn't belong here")); 278 break; 279 280 } 281 } 282 else 283 { 284 for(int i = 0; i < children.count(); i++) 285 buffer.append(children[i]->genCode()); 286 } 287 288 return buffer; 289} 290 291/* A more or less random hashing algorithm */ 292int ParseTreeNode::genHash() const 293{ 294 int hash = 0; 295 char *text; 296 297 if(element) 298 { 299 hash += element->type; 300 switch(element->type) 301 { 302 case UNKNOWN: 303 break; 304 case VIEWPORT: 305 case LINE: 306 case LINE_ALTERNATOR: 307 case CONDITIONAL: 308 hash += element->children_count; 309 break; 310 311 case TAG: 312 for(unsigned int i = 0; i < strlen(element->tag->name); i++) 313 hash += element->tag->name[i]; 314 break; 315 316 case COMMENT: 317 case TEXT: 318 text = (char*)element->data; 319 for(unsigned int i = 0; i < strlen(text); i++) 320 { 321 if(i % 2) 322 hash += text[i] % element->type; 323 else 324 hash += text[i] % element->type * 2; 325 } 326 break; 327 } 328 329 } 330 331 if(param) 332 { 333 hash += param->type; 334 switch(param->type) 335 { 336 case skin_tag_parameter::DEFAULT: 337 case skin_tag_parameter::CODE: 338 break; 339 340 case skin_tag_parameter::INTEGER: 341 hash += param->data.number * (param->data.number / 4); 342 break; 343 344 case skin_tag_parameter::STRING: 345 for(unsigned int i = 0; i < strlen(param->data.text); i++) 346 { 347 if(i % 2) 348 hash += param->data.text[i] * 2; 349 else 350 hash += param->data.text[i]; 351 } 352 break; 353 354 case skin_tag_parameter::DECIMAL: 355 case skin_tag_parameter::PERCENT: 356 hash += param->data.number; 357 break; 358 } 359 } 360 361 for(int i = 0; i < children.count(); i++) 362 { 363 hash += children[i]->genHash(); 364 } 365 366 return hash; 367} 368 369ParseTreeNode* ParseTreeNode::child(int row) 370{ 371 if(row < 0 || row >= children.count()) 372 return 0; 373 374 return children[row]; 375} 376 377int ParseTreeNode::numChildren() const 378{ 379 return children.count(); 380} 381 382 383QVariant ParseTreeNode::data(int column) const 384{ 385 switch(column) 386 { 387 case ParseTreeModel::typeColumn: 388 if(element) 389 { 390 switch(element->type) 391 { 392 case UNKNOWN: 393 return QObject::tr("Unknown"); 394 case VIEWPORT: 395 return QObject::tr("Viewport"); 396 397 case LINE: 398 return QObject::tr("Logical Line"); 399 400 case LINE_ALTERNATOR: 401 return QObject::tr("Alternator"); 402 403 case COMMENT: 404 return QObject::tr("Comment"); 405 406 case CONDITIONAL: 407 return QObject::tr("Conditional Tag"); 408 409 case TAG: 410 return QObject::tr("Tag"); 411 412 case TEXT: 413 return QObject::tr("Plaintext"); 414 } 415 } 416 else if(param) 417 { 418 switch(param->type) 419 { 420 case skin_tag_parameter::STRING: 421 return QObject::tr("String"); 422 423 case skin_tag_parameter::INTEGER: 424 return QObject::tr("Integer"); 425 426 case skin_tag_parameter::DECIMAL: 427 return QObject::tr("Decimal"); 428 429 case skin_tag_parameter::PERCENT: 430 return QObject::tr("Percent"); 431 432 case skin_tag_parameter::DEFAULT: 433 return QObject::tr("Default Argument"); 434 435 case skin_tag_parameter::CODE: 436 return QObject::tr("This doesn't belong here"); 437 } 438 } 439 else 440 { 441 return QObject::tr("Root"); 442 } 443 444 break; 445 446 case ParseTreeModel::valueColumn: 447 if(element) 448 { 449 switch(element->type) 450 { 451 case UNKNOWN: 452 case VIEWPORT: 453 case LINE: 454 case LINE_ALTERNATOR: 455 return QString(); 456 457 case CONDITIONAL: 458 return QString(element->tag->name); 459 460 case TEXT: 461 case COMMENT: 462 return QString((char*)element->data); 463 464 case TAG: 465 return QString(element->tag->name); 466 } 467 } 468 else if(param) 469 { 470 switch(param->type) 471 { 472 case skin_tag_parameter::DEFAULT: 473 return QObject::tr("-"); 474 475 case skin_tag_parameter::STRING: 476 return QString(param->data.text); 477 478 case skin_tag_parameter::INTEGER: 479 return QString::number(param->data.number, 10); 480 481 case skin_tag_parameter::DECIMAL: 482 case skin_tag_parameter::PERCENT: 483 return QString::number(param->data.number / 10., 'f', 1); 484 485 case skin_tag_parameter::CODE: 486 return QObject::tr("Seriously, something's wrong here"); 487 488 } 489 } 490 else 491 { 492 return QString(); 493 } 494 break; 495 496 case ParseTreeModel::lineColumn: 497 if(element) 498 return QString::number(element->line, 10); 499 else 500 return QString(); 501 break; 502 } 503 504 return QVariant(); 505} 506 507 508int ParseTreeNode::getRow() const 509{ 510 if(!parent) 511 return -1; 512 513 return parent->children.indexOf(const_cast<ParseTreeNode*>(this)); 514} 515 516ParseTreeNode* ParseTreeNode::getParent() const 517{ 518 return parent; 519} 520 521/* This version is called for the root node and for viewports */ 522void ParseTreeNode::render(const RBRenderInfo& info) 523{ 524 /* Parameters don't get rendered */ 525 if(!element && param) 526 return; 527 528 /* If we're at the root, we need to render each viewport */ 529 if(!element && !param) 530 { 531 for(int i = 0; i < children.count(); i++) 532 { 533 children[i]->render(info); 534 } 535 536 return; 537 } 538 539 if(element->type != VIEWPORT) 540 { 541 std::cerr << QObject::tr("Error in parse tree").toStdString() 542 << std::endl; 543 return; 544 } 545 546 rendered = new RBViewport(element, info, this); 547 548 for(int i = element->params_count; i < children.count(); i++) 549 children[i]->render(info, dynamic_cast<RBViewport*>(rendered)); 550 551} 552 553/* This version is called for logical lines, tags, conditionals and such */ 554void ParseTreeNode::render(const RBRenderInfo &info, RBViewport* viewport, 555 bool noBreak) 556{ 557 if(!element) 558 return; 559 560 if(element->type == LINE) 561 { 562 for(int i = 0; i < children.count(); i++) 563 children[i]->render(info, viewport); 564 if(!noBreak && !breakFlag) 565 viewport->newLine(); 566 else 567 viewport->flushText(); 568 569 if(breakFlag) 570 breakFlag = false; 571 } 572 else if(element->type == TEXT) 573 { 574 viewport->write(QString(static_cast<char*>(element->data))); 575 } 576 else if(element->type == TAG) 577 { 578 if(!execTag(info, viewport)) 579 viewport->write(evalTag(info).toString()); 580 if(element->tag->flags & NOBREAK) 581 breakFlag = true; 582 } 583 else if(element->type == CONDITIONAL) 584 { 585 int child = evalTag(info, true, element->children_count).toInt(); 586 int max = children.count() - element->params_count; 587 if(child < max) 588 { 589 children[element->params_count + child] 590 ->render(info, viewport, true); 591 } 592 } 593 else if(element->type == LINE_ALTERNATOR) 594 { 595 /* First we build a list of the times for each branch */ 596 QList<double> times; 597 for(int i = 0; i < children.count() ; i++) 598 times.append(findBranchTime(children[i], info)); 599 600 double totalTime = 0; 601 for(int i = 0; i < children.count(); i++) 602 totalTime += times[i]; 603 604 /* Now we figure out which branch to select */ 605 double timeLeft = info.device()->data(QString("simtime")).toDouble(); 606 607 /* Skipping any full cycles */ 608 timeLeft -= totalTime * std::floor(timeLeft / totalTime); 609 610 int branch = 0; 611 while(timeLeft > 0) 612 { 613 timeLeft -= times[branch]; 614 if(timeLeft >= 0) 615 branch++; 616 else 617 break; 618 if(branch >= times.count()) 619 branch = 0; 620 } 621 622 /* In case we end up on a disabled branch, skip ahead. If we find that 623 * all the branches are disabled, don't render anything 624 */ 625 int originalBranch = branch; 626 while(times[branch] == 0) 627 { 628 branch++; 629 if(branch == originalBranch) 630 { 631 branch = -1; 632 break; 633 } 634 if(branch >= times.count()) 635 branch = 0; 636 } 637 638 /* ...and finally render the selected branch */ 639 if(branch >= 0) 640 children[branch]->render(info, viewport, true); 641 } 642} 643 644bool ParseTreeNode::execTag(const RBRenderInfo& info, RBViewport* viewport) 645{ 646 647 QString filename; 648 QString id; 649 int x, y, tiles, tile, maxWidth, maxHeight, width, height; 650 char c, hAlign, vAlign; 651 RBImage* image; 652 QPixmap temp; 653 RBFont* fLoad; 654 655 /* Two switch statements to narrow down the tag name */ 656 switch(element->tag->name[0]) 657 { 658 659 case 'a': 660 switch(element->tag->name[1]) 661 { 662 case 'c': 663 /* %ac */ 664 viewport->alignText(RBViewport::Center); 665 return true; 666 667 case 'l': 668 /* %al */ 669 viewport->alignText(RBViewport::Left); 670 return true; 671 672 case 'r': 673 /* %ar */ 674 viewport->alignText(RBViewport::Right); 675 return true; 676 677 case 'x': 678 /* %ax */ 679 info.screen()->RtlMirror(); 680 return true; 681 682 case 'L': 683 /* %aL */ 684 if(info.device()->data("rtl").toBool()) 685 viewport->alignText(RBViewport::Right); 686 else 687 viewport->alignText(RBViewport::Left); 688 return true; 689 690 case 'R': 691 /* %aR */ 692 if(info.device()->data("rtl").toBool()) 693 viewport->alignText(RBViewport::Left); 694 else 695 viewport->alignText(RBViewport::Right); 696 return true; 697 } 698 699 return false; 700 701 case 'p': 702 switch(element->tag->name[1]) 703 { 704 case 'b': 705 /* %pb */ 706 new RBProgressBar(viewport, info, this); 707 return true; 708 709 case 'v': 710 /* %pv */ 711 if(element->params_count > 0) 712 { 713 new RBProgressBar(viewport, info, this, true); 714 return true; 715 } 716 else 717 return false; 718 } 719 720 return false; 721 722 case 's': 723 switch(element->tag->name[1]) 724 { 725 case '\0': 726 /* %s */ 727 viewport->scrollText(info.device()->data("simtime").toDouble()); 728 return true; 729 } 730 731 return false; 732 733 case 'w': 734 switch(element->tag->name[1]) 735 { 736 case 'd': 737 /* %wd */ 738 info.screen()->breakSBS(); 739 return true; 740 741 case 'e': 742 /* %we */ 743 /* Totally extraneous */ 744 return true; 745 746 case 'i': 747 /* %wi */ 748 viewport->enableStatusBar(); 749 return true; 750 } 751 752 return false; 753 754 case 'x': 755 switch(element->tag->name[1]) 756 { 757 case 'd': 758 /* %xd */ 759 760 /* Breaking up into cases, getting the id first */ 761 if(element->params_count == 1) 762 { 763 /* The old fashioned style */ 764 id = ""; 765 id.append(element->params[0].data.text[0]); 766 } 767 else 768 { 769 id = QString(element->params[0].data.text); 770 } 771 772 773 if(info.screen()->getImage(id)) 774 { 775 /* Fetching the image if available */ 776 image = new RBImage(*(info.screen()->getImage(id)), viewport); 777 } 778 else 779 { 780 image = 0; 781 } 782 783 /* Now determining the particular tile to load */ 784 if(element->params_count == 1) 785 { 786 c = element->params[0].data.text[1]; 787 788 if(c == '\0') 789 { 790 tile = 1; 791 } 792 else 793 { 794 if(isupper(c)) 795 tile = c - 'A' + 25; 796 else 797 tile = c - 'a'; 798 } 799 800 }else{ 801 /* If the next param is just an int, use it as the tile */ 802 if(element->params[1].type == skin_tag_parameter::INTEGER) 803 { 804 tile = element->params[1].data.number - 1; 805 } 806 else 807 { 808 tile = children[1]->evalTag(info, true, 809 image->numTiles()).toInt(); 810 811 /* Adding the offset if there is one */ 812 if(element->params_count == 3) 813 tile += element->params[2].data.number; 814 if(tile < 0) 815 { 816 /* If there is no image for the current status, then 817 * just refrain from showing anything 818 */ 819 delete image; 820 return true; 821 } 822 } 823 } 824 825 if(image) 826 { 827 image->setTile(tile); 828 image->show(); 829 image->enableMovement(); 830 } 831 832 return true; 833 834 case 'l': 835 /* %xl */ 836 id = element->params[0].data.text; 837 tiles = 0; 838 if(element->params[1].data.text == QString("__list_icons__")) 839 { 840 filename = info.settings()->value("iconset", ""); 841 filename.replace(".rockbox", 842 info.settings()->value("themebase")); 843 temp.load(filename); 844 if(!temp.isNull()) 845 { 846 tiles = temp.height() / temp.width(); 847 } 848 } 849 else 850 { 851 filename = info.settings()->value("imagepath", "") + "/" + 852 element->params[1].data.text; 853 tiles = 1; 854 } 855 x = element->params[2].data.number; 856 y = element->params[3].data.number; 857 if(element->params_count > 4) 858 tiles = element->params[4].data.number; 859 860 info.screen()->loadImage(id, new RBImage(filename, tiles, x, y, 861 this, viewport)); 862 return true; 863 864 case '\0': 865 /* %x */ 866 id = element->params[0].data.text; 867 filename = info.settings()->value("imagepath", "") + "/" + 868 element->params[1].data.text; 869 x = element->params[2].data.number; 870 y = element->params[3].data.number; 871 image = new RBImage(filename, 1, x, y, this, viewport); 872 info.screen()->loadImage(id, image); 873 image->show(); 874 image->enableMovement(); 875 876 return true; 877 878 } 879 880 return false; 881 882 case 'C': 883 switch(element->tag->name[1]) 884 { 885 case 'd': 886 /* %Cd */ 887 info.screen()->showAlbumArt(viewport); 888 return true; 889 890 case 'l': 891 /* %Cl */ 892 x = element->params[0].data.number; 893 y = element->params[1].data.number; 894 maxWidth = element->params[2].data.number; 895 maxHeight = element->params[3].data.number; 896 hAlign = element->params_count > 4 897 ? element->params[4].data.text[0] : 'c'; 898 vAlign = element->params_count > 5 899 ? element->params[5].data.text[0] : 'c'; 900 width = info.device()->data("artwidth").toInt(); 901 height = info.device()->data("artheight").toInt(); 902 info.screen()->setAlbumArt(new RBAlbumArt(viewport, x, y, maxWidth, 903 maxHeight, width, height, 904 this, hAlign, vAlign)); 905 return true; 906 } 907 908 return false; 909 910 case 'F': 911 912 switch(element->tag->name[1]) 913 { 914 915 case 'l': 916 /* %Fl */ 917 x = element->params[0].data.number; 918 filename = info.settings()->value("themebase", "") + "/fonts/" + 919 element->params[1].data.text; 920 fLoad = new RBFont(filename); 921 if(!fLoad->isValid()) 922 dynamic_cast<RBScene*>(info.screen()->scene()) 923 ->addWarning(QObject::tr("Missing font file: ") + filename); 924 info.screen()->loadFont(x, fLoad); 925 return true; 926 927 } 928 929 return false; 930 931 case 'T': 932 switch(element->tag->name[1]) 933 { 934 case '\0': 935 /* %T */ 936 if(element->params_count < 5) 937 return false; 938 int x = element->params[0].data.number; 939 int y = element->params[1].data.number; 940 int width = element->params[2].data.number; 941 int height = element->params[3].data.number; 942 QString action(element->params[4].data.text); 943 RBTouchArea* temp = new RBTouchArea(width, height, action, info, 944 viewport); 945 temp->setPos(x, y); 946 return true; 947 } 948 949 return false; 950 951 case 'V': 952 953 switch(element->tag->name[1]) 954 { 955 956 case 'b': 957 /* %Vb */ 958 viewport->setBGColor(RBScreen:: 959 stringToColor(QString(element->params[0]. 960 data.text), 961 Qt::white)); 962 return true; 963 964 case 'd': 965 /* %Vd */ 966 id = element->params[0].data.text; 967 info.screen()->showViewport(id); 968 return true; 969 970 case 'f': 971 /* %Vf */ 972 viewport->setFGColor(RBScreen:: 973 stringToColor(QString(element->params[0]. 974 data.text), 975 Qt::black)); 976 return true; 977 978 case 'p': 979 /* %Vp */ 980 viewport->showPlaylist(info, element->params[0].data.number, 981 children[1]); 982 return true; 983 984 case 'I': 985 /* %VI */ 986 info.screen()->makeCustomUI(element->params[0].data.text); 987 return true; 988 989 } 990 991 return false; 992 993 case 'X': 994 995 switch(element->tag->name[1]) 996 { 997 case '\0': 998 /* %X */ 999 filename = QString(element->params[0].data.text); 1000 info.screen()->setBackdrop(filename); 1001 return true; 1002 } 1003 1004 return false; 1005 1006 } 1007 1008 return false; 1009 1010} 1011 1012QVariant ParseTreeNode::evalTag(const RBRenderInfo& info, bool conditional, 1013 int branches) 1014{ 1015 if(!conditional) 1016 { 1017 if(element->tag->name[0] == 'c' && !info.device()->data("cc").toBool()) 1018 return QString(); 1019 1020 if(QString(element->tag->name) == "Sx") 1021 return QString(element->params[0].data.text); 1022 return info.device()->data(QString(element->tag->name), 1023 element->params_count, element->params); 1024 } 1025 else 1026 { 1027 /* If we're evaluating for a conditional, we return the child branch 1028 * index that should be selected. For true/false values, this is 1029 * 0 for true, 1 for false, and we also have to make sure not to 1030 * ever exceed the number of available children 1031 */ 1032 1033 int child; 1034 QVariant val = info.device()->data("?" + QString(element->tag->name)); 1035 if(val.isNull()) 1036 val = info.device()->data(QString(element->tag->name), 1037 element->params_count, element->params); 1038 1039 if(val.isNull()) 1040 { 1041 child = 1; 1042 } 1043 else if(QString(element->tag->name) == "bl") 1044 { 1045 /* bl has to be scaled to the number of available children, but it 1046 * also has an initial -1 value for an unknown state */ 1047 child = val.toInt(); 1048 if(child == -1) 1049 { 1050 child = 0; 1051 } 1052 else 1053 { 1054 child = ((branches - 1) * child / 100) + 1; 1055 } 1056 } 1057 else if(QString(element->tag->name) == "pv") 1058 { 1059 /* ?pv gets scaled to the number of available children, sandwiched 1060 * in between mute and 0/>0dB. I assume a floor of -50dB for the 1061 * time being 1062 */ 1063 int dB = val.toInt(); 1064 1065 if(dB < -50) 1066 child = 0; 1067 else if(dB == 0) 1068 child = branches - 2; 1069 else if(dB > 0) 1070 child = branches - 1; 1071 else 1072 { 1073 int options = branches - 3; 1074 child = (options * (dB + 50)) / 50; 1075 } 1076 } 1077 else if(QString(element->tag->name) == "px") 1078 { 1079 child = val.toInt(); 1080 child = branches * child / 100; 1081 } 1082 else if(val.type() == QVariant::Bool) 1083 { 1084 /* Boolean values have to be reversed, because conditionals are 1085 * always of the form %?tag<true|false> 1086 */ 1087 if(val.toBool()) 1088 child = 0; 1089 else 1090 child = 1; 1091 } 1092 else if(element->tag->name[0] == 'i' || element->tag->name[0] == 'I' 1093 || element->tag->name[0] == 'f' || element->tag->name[0] == 'F') 1094 { 1095 if(info.device()->data("id3available").toBool()) 1096 child = 0; 1097 else 1098 child = 1; 1099 } 1100 else if(val.type() == QVariant::String) 1101 { 1102 if(val.toString().length() > 0) 1103 child = 0; 1104 else 1105 child = 1; 1106 } 1107 else 1108 { 1109 child = val.toInt(); 1110 } 1111 1112 if(child < 0) 1113 child = 0; 1114 1115 if(child < branches) 1116 return child; 1117 else if(branches == 1) 1118 return 2; 1119 else 1120 return branches - 1; 1121 } 1122} 1123 1124double ParseTreeNode::findBranchTime(ParseTreeNode *branch, 1125 const RBRenderInfo& info) 1126{ 1127 double retval = 2; 1128 for(int i = 0; i < branch->children.count(); i++) 1129 { 1130 ParseTreeNode* current = branch->children[i]; 1131 if(current->element->type == TAG) 1132 { 1133 if(current->element->tag->name[0] == 't' 1134 && current->element->tag->name[1] == '\0') 1135 { 1136 retval = current->element->params[0].data.number / 10.; 1137 } 1138 } 1139 else if(current->element->type == CONDITIONAL) 1140 { 1141 retval = findConditionalTime(current, info); 1142 } 1143 } 1144 return retval; 1145} 1146 1147double ParseTreeNode::findConditionalTime(ParseTreeNode *conditional, 1148 const RBRenderInfo& info) 1149{ 1150 int child = conditional->evalTag(info, true, 1151 conditional->children.count()).toInt(); 1152 if(child >= conditional->children.count()) 1153 child = conditional->children.count() - 1; 1154 1155 return findBranchTime(conditional->children[child], info); 1156} 1157 1158void ParseTreeNode::modParam(QVariant value, int index) 1159{ 1160 if(element) 1161 { 1162 if(index < 0) 1163 return; 1164 while(index >= children.count()) 1165 { 1166 /* Padding children with defaults until we make the necessary 1167 * parameter available 1168 */ 1169 skin_tag_parameter* newParam = new skin_tag_parameter; 1170 newParam->type = skin_tag_parameter::DEFAULT; 1171 /* We'll need to manually delete the extra parameters in the 1172 * destructor 1173 */ 1174 extraParams.append(children.count()); 1175 1176 children.append(new ParseTreeNode(newParam, this, model)); 1177 element->params_count++; 1178 } 1179 1180 children[index]->modParam(value); 1181 } 1182 else if(param) 1183 { 1184 if(value.type() == QVariant::Double) 1185 { 1186 param->type = skin_tag_parameter::DECIMAL; 1187 param->data.number = static_cast<int>(value.toDouble() * 10); 1188 } 1189 else if(value.type() == QVariant::String) 1190 { 1191 param->type = skin_tag_parameter::STRING; 1192 free(param->data.text); 1193 param->data.text = strdup(value.toString().toStdString().c_str()); 1194 } 1195 else if(value.type() == QVariant::Int) 1196 { 1197 param->type = skin_tag_parameter::INTEGER; 1198 param->data.number = value.toInt(); 1199 } 1200 1201 model->paramChanged(this); 1202 1203 } 1204}