A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
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}