Theme Editor: Made toutouch area coordinates absolute rather than relative
[kugel-rb.git] / utils / themeeditor / models / parsetreenode.cpp
blobf698adcd497ad9ad9a9da30fd2bdf6bc85f18149
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2010 Robert Bieber
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.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
22 #include "symbols.h"
23 #include "tag_table.h"
25 #include "parsetreenode.h"
26 #include "parsetreemodel.h"
28 #include "rbimage.h"
29 #include "rbprogressbar.h"
30 #include "rbtoucharea.h"
32 #include <iostream>
33 #include <cmath>
35 int ParseTreeNode::openConditionals = 0;
36 bool ParseTreeNode::breakFlag = false;
38 /* Root element constructor */
39 ParseTreeNode::ParseTreeNode(struct skin_element* data)
40 : parent(0), element(0), param(0), children()
42 while(data)
44 children.append(new ParseTreeNode(data, this));
45 data = data->next;
49 /* Normal element constructor */
50 ParseTreeNode::ParseTreeNode(struct skin_element* data, ParseTreeNode* parent)
51 : parent(parent), element(data), param(0), children()
53 switch(element->type)
56 case TAG:
57 for(int i = 0; i < element->params_count; i++)
59 if(element->params[i].type == skin_tag_parameter::CODE)
60 children.append(new ParseTreeNode(element->params[i].data.code,
61 this));
62 else
63 children.append(new ParseTreeNode(&element->params[i], this));
65 break;
67 case CONDITIONAL:
68 for(int i = 0; i < element->params_count; i++)
69 children.append(new ParseTreeNode(&data->params[i], this));
70 for(int i = 0; i < element->children_count; i++)
71 children.append(new ParseTreeNode(data->children[i], this));
72 break;
74 case LINE_ALTERNATOR:
75 for(int i = 0; i < element->children_count; i++)
77 children.append(new ParseTreeNode(data->children[i], this));
79 break;
81 case VIEWPORT:
82 for(int i = 0; i < element->params_count; i++)
83 children.append(new ParseTreeNode(&data->params[i], this));
84 /* Deliberate fall-through here */
86 case LINE:
87 for(int i = 0; i < data->children_count; i++)
89 for(struct skin_element* current = data->children[i]; current;
90 current = current->next)
92 children.append(new ParseTreeNode(current, this));
95 break;
97 default:
98 break;
102 /* Parameter constructor */
103 ParseTreeNode::ParseTreeNode(skin_tag_parameter *data, ParseTreeNode *parent)
104 : parent(parent), element(0), param(data), children()
109 ParseTreeNode::~ParseTreeNode()
111 for(int i = 0; i < children.count(); i++)
112 delete children[i];
115 QString ParseTreeNode::genCode() const
117 QString buffer = "";
119 if(element)
121 switch(element->type)
123 case UNKNOWN:
124 break;
125 case VIEWPORT:
126 /* Generating the Viewport tag, if necessary */
127 if(element->tag)
129 buffer.append(TAGSYM);
130 buffer.append(element->tag->name);
131 buffer.append(ARGLISTOPENSYM);
132 for(int i = 0; i < element->params_count; i++)
134 buffer.append(children[i]->genCode());
135 if(i != element->params_count - 1)
136 buffer.append(ARGLISTSEPERATESYM);
138 buffer.append(ARGLISTCLOSESYM);
139 buffer.append('\n');
142 for(int i = element->params_count; i < children.count(); i++)
143 buffer.append(children[i]->genCode());
144 break;
146 case LINE:
147 for(int i = 0; i < children.count(); i++)
149 buffer.append(children[i]->genCode());
151 if(openConditionals == 0
152 && !(parent && parent->element->type == LINE_ALTERNATOR))
154 buffer.append('\n');
156 break;
158 case LINE_ALTERNATOR:
159 for(int i = 0; i < children.count(); i++)
161 buffer.append(children[i]->genCode());
162 if(i != children.count() - 1)
163 buffer.append(MULTILINESYM);
165 if(openConditionals == 0)
166 buffer.append('\n');
167 break;
169 case CONDITIONAL:
170 openConditionals++;
172 /* Inserting the tag part */
173 buffer.append(TAGSYM);
174 buffer.append(CONDITIONSYM);
175 buffer.append(element->tag->name);
176 if(element->params_count > 0)
178 buffer.append(ARGLISTOPENSYM);
179 for(int i = 0; i < element->params_count; i++)
181 buffer.append(children[i]->genCode());
182 if( i != element->params_count - 1)
183 buffer.append(ARGLISTSEPERATESYM);
184 buffer.append(ARGLISTCLOSESYM);
188 /* Inserting the sublines */
189 buffer.append(ENUMLISTOPENSYM);
190 for(int i = element->params_count; i < children.count(); i++)
192 buffer.append(children[i]->genCode());
193 if(i != children.count() - 1)
194 buffer.append(ENUMLISTSEPERATESYM);
196 buffer.append(ENUMLISTCLOSESYM);
197 openConditionals--;
198 break;
200 case TAG:
201 buffer.append(TAGSYM);
202 buffer.append(element->tag->name);
204 if(element->params_count > 0)
206 /* Rendering parameters if there are any */
207 buffer.append(ARGLISTOPENSYM);
208 for(int i = 0; i < children.count(); i++)
210 buffer.append(children[i]->genCode());
211 if(i != children.count() - 1)
212 buffer.append(ARGLISTSEPERATESYM);
214 buffer.append(ARGLISTCLOSESYM);
216 if(element->tag->params[strlen(element->tag->params) - 1] == '\n')
217 buffer.append('\n');
218 break;
220 case TEXT:
221 for(char* cursor = (char*)element->data; *cursor; cursor++)
223 if(find_escape_character(*cursor))
224 buffer.append(TAGSYM);
225 buffer.append(*cursor);
227 break;
229 case COMMENT:
230 buffer.append(COMMENTSYM);
231 buffer.append((char*)element->data);
232 buffer.append('\n');
233 break;
236 else if(param)
238 switch(param->type)
240 case skin_tag_parameter::STRING:
241 for(char* cursor = param->data.text; *cursor; cursor++)
243 if(find_escape_character(*cursor))
244 buffer.append(TAGSYM);
245 buffer.append(*cursor);
247 break;
249 case skin_tag_parameter::INTEGER:
250 buffer.append(QString::number(param->data.number, 10));
251 break;
253 case skin_tag_parameter::DECIMAL:
254 buffer.append(QString::number(param->data.number / 10., 'f', 1));
255 break;
257 case skin_tag_parameter::DEFAULT:
258 buffer.append(DEFAULTSYM);
259 break;
261 case skin_tag_parameter::CODE:
262 buffer.append(QObject::tr("This doesn't belong here"));
263 break;
267 else
269 for(int i = 0; i < children.count(); i++)
270 buffer.append(children[i]->genCode());
273 return buffer;
276 /* A more or less random hashing algorithm */
277 int ParseTreeNode::genHash() const
279 int hash = 0;
280 char *text;
282 if(element)
284 hash += element->type;
285 switch(element->type)
287 case UNKNOWN:
288 break;
289 case VIEWPORT:
290 case LINE:
291 case LINE_ALTERNATOR:
292 case CONDITIONAL:
293 hash += element->children_count;
294 break;
296 case TAG:
297 for(unsigned int i = 0; i < strlen(element->tag->name); i++)
298 hash += element->tag->name[i];
299 break;
301 case COMMENT:
302 case TEXT:
303 text = (char*)element->data;
304 for(unsigned int i = 0; i < strlen(text); i++)
306 if(i % 2)
307 hash += text[i] % element->type;
308 else
309 hash += text[i] % element->type * 2;
311 break;
316 if(param)
318 hash += param->type;
319 switch(param->type)
321 case skin_tag_parameter::DEFAULT:
322 case skin_tag_parameter::CODE:
323 break;
325 case skin_tag_parameter::INTEGER:
326 hash += param->data.number * (param->data.number / 4);
327 break;
329 case skin_tag_parameter::STRING:
330 for(unsigned int i = 0; i < strlen(param->data.text); i++)
332 if(i % 2)
333 hash += param->data.text[i] * 2;
334 else
335 hash += param->data.text[i];
337 break;
339 case skin_tag_parameter::DECIMAL:
340 hash += param->data.number;
341 break;
345 for(int i = 0; i < children.count(); i++)
347 hash += children[i]->genHash();
350 return hash;
353 ParseTreeNode* ParseTreeNode::child(int row)
355 if(row < 0 || row >= children.count())
356 return 0;
358 return children[row];
361 int ParseTreeNode::numChildren() const
363 return children.count();
367 QVariant ParseTreeNode::data(int column) const
369 switch(column)
371 case ParseTreeModel::typeColumn:
372 if(element)
374 switch(element->type)
376 case UNKNOWN:
377 return QObject::tr("Unknown");
378 case VIEWPORT:
379 return QObject::tr("Viewport");
381 case LINE:
382 return QObject::tr("Logical Line");
384 case LINE_ALTERNATOR:
385 return QObject::tr("Alternator");
387 case COMMENT:
388 return QObject::tr("Comment");
390 case CONDITIONAL:
391 return QObject::tr("Conditional Tag");
393 case TAG:
394 return QObject::tr("Tag");
396 case TEXT:
397 return QObject::tr("Plaintext");
400 else if(param)
402 switch(param->type)
404 case skin_tag_parameter::STRING:
405 return QObject::tr("String");
407 case skin_tag_parameter::INTEGER:
408 return QObject::tr("Integer");
410 case skin_tag_parameter::DECIMAL:
411 return QObject::tr("Decimal");
413 case skin_tag_parameter::DEFAULT:
414 return QObject::tr("Default Argument");
416 case skin_tag_parameter::CODE:
417 return QObject::tr("This doesn't belong here");
420 else
422 return QObject::tr("Root");
425 break;
427 case ParseTreeModel::valueColumn:
428 if(element)
430 switch(element->type)
432 case UNKNOWN:
433 case VIEWPORT:
434 case LINE:
435 case LINE_ALTERNATOR:
436 return QString();
438 case CONDITIONAL:
439 return QString(element->tag->name);
441 case TEXT:
442 case COMMENT:
443 return QString((char*)element->data);
445 case TAG:
446 return QString(element->tag->name);
449 else if(param)
451 switch(param->type)
453 case skin_tag_parameter::DEFAULT:
454 return QObject::tr("-");
456 case skin_tag_parameter::STRING:
457 return QString(param->data.text);
459 case skin_tag_parameter::INTEGER:
460 return QString::number(param->data.number, 10);
462 case skin_tag_parameter::DECIMAL:
463 return QString::number(param->data.number / 10., 'f', 1);
465 case skin_tag_parameter::CODE:
466 return QObject::tr("Seriously, something's wrong here");
470 else
472 return QString();
474 break;
476 case ParseTreeModel::lineColumn:
477 if(element)
478 return QString::number(element->line, 10);
479 else
480 return QString();
481 break;
484 return QVariant();
488 int ParseTreeNode::getRow() const
490 if(!parent)
491 return -1;
493 return parent->children.indexOf(const_cast<ParseTreeNode*>(this));
496 ParseTreeNode* ParseTreeNode::getParent() const
498 return parent;
501 /* This version is called for the root node and for viewports */
502 void ParseTreeNode::render(const RBRenderInfo& info)
504 /* Parameters don't get rendered */
505 if(!element && param)
506 return;
508 /* If we're at the root, we need to render each viewport */
509 if(!element && !param)
511 for(int i = 0; i < children.count(); i++)
513 children[i]->render(info);
516 return;
519 if(element->type != VIEWPORT)
521 std::cerr << QObject::tr("Error in parse tree").toStdString()
522 << std::endl;
523 return;
526 rendered = new RBViewport(element, info);
528 for(int i = element->params_count; i < children.count(); i++)
529 children[i]->render(info, dynamic_cast<RBViewport*>(rendered));
533 /* This version is called for logical lines, tags, conditionals and such */
534 void ParseTreeNode::render(const RBRenderInfo &info, RBViewport* viewport,
535 bool noBreak)
537 if(element->type == LINE)
539 for(int i = 0; i < children.count(); i++)
540 children[i]->render(info, viewport);
541 if(!noBreak && !breakFlag)
542 viewport->newLine();
543 else
544 viewport->flushText();
546 if(breakFlag)
547 breakFlag = false;
549 else if(element->type == TEXT)
551 viewport->write(QString(static_cast<char*>(element->data)));
553 else if(element->type == TAG)
555 if(!execTag(info, viewport))
556 viewport->write(evalTag(info).toString());
557 if(element->tag->flags & NOBREAK)
558 breakFlag = true;
560 else if(element->type == CONDITIONAL)
562 int child = evalTag(info, true, element->children_count).toInt();
563 children[element->params_count + child]->render(info, viewport, true);
565 else if(element->type == LINE_ALTERNATOR)
567 /* First we build a list of the times for each branch */
568 QList<double> times;
569 for(int i = 0; i < children.count() ; i++)
570 times.append(findBranchTime(children[i], info));
572 double totalTime = 0;
573 for(int i = 0; i < children.count(); i++)
574 totalTime += times[i];
576 /* Now we figure out which branch to select */
577 double timeLeft = info.device()->data(QString("simtime")).toDouble();
579 /* Skipping any full cycles */
580 timeLeft -= totalTime * std::floor(timeLeft / totalTime);
582 int branch = 0;
583 while(timeLeft > 0)
585 timeLeft -= times[branch];
586 if(timeLeft >= 0)
587 branch++;
588 else
589 break;
590 if(branch >= times.count())
591 branch = 0;
594 /* In case we end up on a disabled branch, skip ahead. If we find that
595 * all the branches are disabled, don't render anything
597 int originalBranch = branch;
598 while(times[branch] == 0)
600 branch++;
601 if(branch == originalBranch)
603 branch = -1;
604 break;
606 if(branch >= times.count())
607 branch = 0;
610 /* ...and finally render the selected branch */
611 if(branch >= 0)
612 children[branch]->render(info, viewport, true);
616 bool ParseTreeNode::execTag(const RBRenderInfo& info, RBViewport* viewport)
619 QString filename;
620 QString id;
621 int x, y, tiles, tile, maxWidth, maxHeight, width, height;
622 char c, hAlign, vAlign;
623 RBImage* image;
625 /* Two switch statements to narrow down the tag name */
626 switch(element->tag->name[0])
629 case 'a':
630 switch(element->tag->name[1])
632 case 'c':
633 /* %ac */
634 viewport->alignText(RBViewport::Center);
635 return true;
637 case 'l':
638 /* %al */
639 viewport->alignText(RBViewport::Left);
640 return true;
642 case 'r':
643 /* %ar */
644 viewport->alignText(RBViewport::Right);
645 return true;
647 case 'x':
648 /* %ax */
649 return true;
651 case 'L':
652 /* %aL */
653 if(info.device()->data("rtl").toBool())
654 viewport->alignText(RBViewport::Right);
655 else
656 viewport->alignText(RBViewport::Left);
657 return true;
659 case 'R':
660 /* %aR */
661 if(info.device()->data("rtl").toBool())
662 viewport->alignText(RBViewport::Left);
663 else
664 viewport->alignText(RBViewport::Right);
665 return true;
668 return false;
670 case 'p':
671 switch(element->tag->name[1])
673 case 'b':
674 /* %pb */
675 new RBProgressBar(viewport, info, element->params_count,
676 element->params);
677 return true;
679 case 'v':
680 /* %pv */
681 if(element->params_count > 0)
683 new RBProgressBar(viewport, info, element->params_count,
684 element->params, true);
685 return true;
687 else
688 return false;
691 return false;
693 case 's':
694 switch(element->tag->name[1])
696 case '\0':
697 /* %s */
698 viewport->scrollText(info.device()->data("simtime").toDouble());
699 return true;
702 return false;
704 case 'w':
705 switch(element->tag->name[1])
707 case 'd':
708 /* %wd */
709 info.screen()->breakSBS();
710 return true;
712 case 'e':
713 /* %we */
714 /* Totally extraneous */
715 return true;
717 case 'i':
718 /* %wi */
719 viewport->enableStatusBar();
720 return true;
723 return false;
725 case 'x':
726 switch(element->tag->name[1])
728 case 'd':
729 /* %xd */
730 id = "";
731 id.append(element->params[0].data.text[0]);
732 c = element->params[0].data.text[1];
734 if(c == '\0')
736 tile = 1;
738 else
740 if(isupper(c))
741 tile = c - 'A' + 25;
742 else
743 tile = c - 'a';
746 if(info.screen()->getImage(id))
748 image = new RBImage(*(info.screen()->getImage(id)), viewport);
749 image->setTile(tile);
750 image->show();
753 return true;
755 case 'l':
756 /* %xl */
757 id = element->params[0].data.text;
758 filename = info.settings()->value("imagepath", "") + "/" +
759 element->params[1].data.text;
760 x = element->params[2].data.number;
761 y = element->params[3].data.number;
762 if(element->params_count > 4)
763 tiles = element->params[4].data.number;
764 else
765 tiles = 1;
767 info.screen()->loadImage(id, new RBImage(filename, tiles, x, y,
768 viewport));
769 return true;
771 case '\0':
772 /* %x */
773 id = element->params[0].data.text;
774 filename = info.settings()->value("imagepath", "") + "/" +
775 element->params[1].data.text;
776 x = element->params[2].data.number;
777 y = element->params[3].data.number;
778 image = new RBImage(filename, 1, x, y, viewport);
779 info.screen()->loadImage(id, new RBImage(filename, 1, x, y,
780 viewport));
781 info.screen()->getImage(id)->show();
782 return true;
786 return false;
788 case 'C':
789 switch(element->tag->name[1])
791 case 'd':
792 /* %Cd */
793 info.screen()->showAlbumArt(viewport);
794 return true;
796 case 'l':
797 /* %Cl */
798 x = element->params[0].data.number;
799 y = element->params[1].data.number;
800 maxWidth = element->params[2].data.number;
801 maxHeight = element->params[3].data.number;
802 hAlign = element->params_count > 4
803 ? element->params[4].data.text[0] : 'c';
804 vAlign = element->params_count > 5
805 ? element->params[5].data.text[0] : 'c';
806 width = info.device()->data("artwidth").toInt();
807 height = info.device()->data("artheight").toInt();
808 info.screen()->setAlbumArt(new RBAlbumArt(viewport, x, y, maxWidth,
809 maxHeight, width, height,
810 hAlign, vAlign));
811 return true;
814 return false;
816 case 'F':
818 switch(element->tag->name[1])
821 case 'l':
822 /* %Fl */
823 x = element->params[0].data.number;
824 filename = info.settings()->value("themebase", "") + "/fonts/" +
825 element->params[1].data.text;
826 info.screen()->loadFont(x, new RBFont(filename));
827 return true;
831 return false;
833 case 'T':
834 switch(element->tag->name[1])
836 case '\0':
837 /* %T */
838 if(element->params_count < 5)
839 return false;
840 int x = element->params[0].data.number;
841 int y = element->params[1].data.number;
842 int width = element->params[2].data.number;
843 int height = element->params[3].data.number;
844 QString action(element->params[4].data.text);
845 RBTouchArea* temp = new RBTouchArea(width, height, action, info);
846 x -= viewport->x();
847 y -= viewport->y();
848 temp->setPos(x, y);
849 return true;
852 return false;
854 case 'V':
856 switch(element->tag->name[1])
859 case 'b':
860 /* %Vb */
861 viewport->setBGColor(RBScreen::
862 stringToColor(QString(element->params[0].
863 data.text),
864 Qt::white));
865 return true;
867 case 'd':
868 /* %Vd */
869 id = element->params[0].data.text;
870 info.screen()->showViewport(id);
871 return true;
873 case 'f':
874 /* %Vf */
875 viewport->setFGColor(RBScreen::
876 stringToColor(QString(element->params[0].
877 data.text),
878 Qt::black));
879 return true;
881 case 'p':
882 /* %Vp */
883 viewport->showPlaylist(info, element->params[0].data.number,
884 element->params[1].data.code,
885 element->params[2].data.code);
886 return true;
888 case 'I':
889 /* %VI */
890 info.screen()->makeCustomUI(element->params[0].data.text);
891 return true;
895 return false;
897 case 'X':
899 switch(element->tag->name[1])
901 case '\0':
902 /* %X */
903 filename = QString(element->params[0].data.text);
904 if(info.sbsScreen() && info.screen()->parentItem())
905 info.sbsScreen()->setBackdrop(filename);
906 else
907 info.screen()->setBackdrop(filename);
908 return true;
911 return false;
915 return false;
919 QVariant ParseTreeNode::evalTag(const RBRenderInfo& info, bool conditional,
920 int branches)
922 if(!conditional)
924 return info.device()->data(QString(element->tag->name),
925 element->params_count, element->params);
927 else
929 /* If we're evaluating for a conditional, we return the child branch
930 * index that should be selected. For true/false values, this is
931 * 0 for true, 1 for false, and we also have to make sure not to
932 * ever exceed the number of available children
935 int child;
936 QVariant val = info.device()->data("?" + QString(element->tag->name));
937 if(val.isNull())
938 val = info.device()->data(QString(element->tag->name),
939 element->params_count, element->params);
941 if(val.isNull())
943 child = 1;
945 else if(QString(element->tag->name) == "bl")
947 /* bl has to be scaled to the number of available children, but it
948 * also has an initial -1 value for an unknown state */
949 child = val.toInt();
950 if(child == -1)
952 child = 0;
954 else
956 child = ((branches - 1) * child / 100) + 1;
959 else if(QString(element->tag->name) == "pv")
961 /* ?pv gets scaled to the number of available children, sandwiched
962 * in between mute and 0/>0dB. I assume a floor of -50dB for the
963 * time being
965 int dB = val.toInt();
967 if(dB < -50)
968 child = 0;
969 else if(dB == 0)
970 child = branches - 2;
971 else if(dB > 0)
972 child = branches - 1;
973 else
975 int options = branches - 3;
976 child = (options * (dB + 50)) / 50;
979 else if(QString(element->tag->name) == "px")
981 child = val.toInt();
982 child = branches * child / 100;
984 else if(val.type() == QVariant::Bool)
986 /* Boolean values have to be reversed, because conditionals are
987 * always of the form %?tag<true|false>
989 if(val.toBool())
990 child = 0;
991 else
992 child = 1;
994 else if(val.type() == QVariant::String)
996 if(val.toString().length() > 0)
997 child = 0;
998 else
999 child = 1;
1001 else if(element->tag->name[0] == 'i' || element->tag->name[0] == 'I'
1002 || element->tag->name[0] == 'f' || element->tag->name[0] == 'F')
1004 if(info.device()->data("id3available").toBool())
1005 child = 0;
1006 else
1007 child = 1;
1009 else
1011 child = val.toInt();
1014 if(child < 0)
1015 child = 0;
1017 if(child < branches)
1018 return child;
1019 else
1020 return branches - 1;
1024 double ParseTreeNode::findBranchTime(ParseTreeNode *branch,
1025 const RBRenderInfo& info)
1027 double retval = 2;
1028 for(int i = 0; i < branch->children.count(); i++)
1030 ParseTreeNode* current = branch->children[i];
1031 if(current->element->type == TAG)
1033 if(current->element->tag->name[0] == 't'
1034 && current->element->tag->name[1] == '\0')
1036 retval = current->element->params[0].data.number / 10.;
1039 else if(current->element->type == CONDITIONAL)
1041 retval = findConditionalTime(current, info);
1044 return retval;
1047 double ParseTreeNode::findConditionalTime(ParseTreeNode *conditional,
1048 const RBRenderInfo& info)
1050 int child = conditional->evalTag(info, true,
1051 conditional->children.count()).toInt();
1052 return findBranchTime(conditional->children[child], info);