Theme Editor: Fixed line numbering bug in parser. Implemented playlist display in...
[kugel-rb.git] / utils / themeeditor / models / parsetreenode.cpp
blobe61c6552e28d160d9a9fb1e341ef3d2d4569cd28
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"
30 #include <iostream>
32 int ParseTreeNode::openConditionals = 0;
34 /* Root element constructor */
35 ParseTreeNode::ParseTreeNode(struct skin_element* data)
36 : parent(0), element(0), param(0), children()
38 while(data)
40 children.append(new ParseTreeNode(data, this));
41 data = data->next;
45 /* Normal element constructor */
46 ParseTreeNode::ParseTreeNode(struct skin_element* data, ParseTreeNode* parent)
47 : parent(parent), element(data), param(0), children()
49 switch(element->type)
52 case TAG:
53 for(int i = 0; i < element->params_count; i++)
55 if(element->params[i].type == skin_tag_parameter::CODE)
56 children.append(new ParseTreeNode(element->params[i].data.code,
57 this));
58 else
59 children.append(new ParseTreeNode(&element->params[i], this));
61 break;
63 case CONDITIONAL:
64 for(int i = 0; i < element->params_count; i++)
65 children.append(new ParseTreeNode(&data->params[i], this));
66 for(int i = 0; i < element->children_count; i++)
67 children.append(new ParseTreeNode(data->children[i], this));
68 break;
70 case SUBLINES:
71 for(int i = 0; i < element->children_count; i++)
73 children.append(new ParseTreeNode(data->children[i], this));
75 break;
77 case VIEWPORT:
78 for(int i = 0; i < element->params_count; i++)
79 children.append(new ParseTreeNode(&data->params[i], this));
80 /* Deliberate fall-through here */
82 case LINE:
83 for(int i = 0; i < data->children_count; i++)
85 for(struct skin_element* current = data->children[i]; current;
86 current = current->next)
88 children.append(new ParseTreeNode(current, this));
91 break;
93 default:
94 break;
98 /* Parameter constructor */
99 ParseTreeNode::ParseTreeNode(skin_tag_parameter *data, ParseTreeNode *parent)
100 : parent(parent), element(0), param(data), children()
105 ParseTreeNode::~ParseTreeNode()
107 for(int i = 0; i < children.count(); i++)
108 delete children[i];
111 QString ParseTreeNode::genCode() const
113 QString buffer = "";
115 if(element)
117 switch(element->type)
119 case UNKNOWN:
120 break;
121 case VIEWPORT:
122 /* Generating the Viewport tag, if necessary */
123 if(element->tag)
125 buffer.append(TAGSYM);
126 buffer.append(element->tag->name);
127 buffer.append(ARGLISTOPENSYM);
128 for(int i = 0; i < element->params_count; i++)
130 buffer.append(children[i]->genCode());
131 if(i != element->params_count - 1)
132 buffer.append(ARGLISTSEPERATESYM);
134 buffer.append(ARGLISTCLOSESYM);
135 buffer.append('\n');
138 for(int i = element->params_count; i < children.count(); i++)
139 buffer.append(children[i]->genCode());
140 break;
142 case LINE:
143 for(int i = 0; i < children.count(); i++)
145 buffer.append(children[i]->genCode());
147 if(openConditionals == 0
148 && !(parent && parent->element->type == SUBLINES))
150 buffer.append('\n');
152 break;
154 case SUBLINES:
155 for(int i = 0; i < children.count(); i++)
157 buffer.append(children[i]->genCode());
158 if(i != children.count() - 1)
159 buffer.append(MULTILINESYM);
161 if(openConditionals == 0)
162 buffer.append('\n');
163 break;
165 case CONDITIONAL:
166 openConditionals++;
168 /* Inserting the tag part */
169 buffer.append(TAGSYM);
170 buffer.append(CONDITIONSYM);
171 buffer.append(element->tag->name);
172 if(element->params_count > 0)
174 buffer.append(ARGLISTOPENSYM);
175 for(int i = 0; i < element->params_count; i++)
177 buffer.append(children[i]->genCode());
178 if( i != element->params_count - 1)
179 buffer.append(ARGLISTSEPERATESYM);
180 buffer.append(ARGLISTCLOSESYM);
184 /* Inserting the sublines */
185 buffer.append(ENUMLISTOPENSYM);
186 for(int i = element->params_count; i < children.count(); i++)
188 buffer.append(children[i]->genCode());
189 if(i != children.count() - 1)
190 buffer.append(ENUMLISTSEPERATESYM);
192 buffer.append(ENUMLISTCLOSESYM);
193 openConditionals--;
194 break;
196 case TAG:
197 buffer.append(TAGSYM);
198 buffer.append(element->tag->name);
200 if(element->params_count > 0)
202 /* Rendering parameters if there are any */
203 buffer.append(ARGLISTOPENSYM);
204 for(int i = 0; i < children.count(); i++)
206 buffer.append(children[i]->genCode());
207 if(i != children.count() - 1)
208 buffer.append(ARGLISTSEPERATESYM);
210 buffer.append(ARGLISTCLOSESYM);
212 break;
214 case TEXT:
215 for(char* cursor = (char*)element->data; *cursor; cursor++)
217 if(find_escape_character(*cursor))
218 buffer.append(TAGSYM);
219 buffer.append(*cursor);
221 break;
223 case COMMENT:
224 buffer.append(COMMENTSYM);
225 buffer.append((char*)element->data);
226 buffer.append('\n');
227 break;
230 else if(param)
232 switch(param->type)
234 case skin_tag_parameter::STRING:
235 for(char* cursor = param->data.text; *cursor; cursor++)
237 if(find_escape_character(*cursor))
238 buffer.append(TAGSYM);
239 buffer.append(*cursor);
241 break;
243 case skin_tag_parameter::NUMERIC:
244 buffer.append(QString::number(param->data.numeric, 10));
245 break;
247 case skin_tag_parameter::DEFAULT:
248 buffer.append(DEFAULTSYM);
249 break;
251 case skin_tag_parameter::CODE:
252 buffer.append(QObject::tr("This doesn't belong here"));
253 break;
257 else
259 for(int i = 0; i < children.count(); i++)
260 buffer.append(children[i]->genCode());
263 return buffer;
266 /* A more or less random hashing algorithm */
267 int ParseTreeNode::genHash() const
269 int hash = 0;
270 char *text;
272 if(element)
274 hash += element->type;
275 switch(element->type)
277 case UNKNOWN:
278 break;
279 case VIEWPORT:
280 case LINE:
281 case SUBLINES:
282 case CONDITIONAL:
283 hash += element->children_count;
284 break;
286 case TAG:
287 for(unsigned int i = 0; i < strlen(element->tag->name); i++)
288 hash += element->tag->name[i];
289 break;
291 case COMMENT:
292 case TEXT:
293 text = (char*)element->data;
294 for(unsigned int i = 0; i < strlen(text); i++)
296 if(i % 2)
297 hash += text[i] % element->type;
298 else
299 hash += text[i] % element->type * 2;
301 break;
306 if(param)
308 hash += param->type;
309 switch(param->type)
311 case skin_tag_parameter::DEFAULT:
312 case skin_tag_parameter::CODE:
313 break;
315 case skin_tag_parameter::NUMERIC:
316 hash += param->data.numeric * (param->data.numeric / 4);
317 break;
319 case skin_tag_parameter::STRING:
320 for(unsigned int i = 0; i < strlen(param->data.text); i++)
322 if(i % 2)
323 hash += param->data.text[i] * 2;
324 else
325 hash += param->data.text[i];
327 break;
331 for(int i = 0; i < children.count(); i++)
333 hash += children[i]->genHash();
336 return hash;
339 ParseTreeNode* ParseTreeNode::child(int row)
341 if(row < 0 || row >= children.count())
342 return 0;
344 return children[row];
347 int ParseTreeNode::numChildren() const
349 return children.count();
353 QVariant ParseTreeNode::data(int column) const
355 switch(column)
357 case ParseTreeModel::typeColumn:
358 if(element)
360 switch(element->type)
362 case UNKNOWN:
363 return QObject::tr("Unknown");
364 case VIEWPORT:
365 return QObject::tr("Viewport");
367 case LINE:
368 return QObject::tr("Logical Line");
370 case SUBLINES:
371 return QObject::tr("Alternator");
373 case COMMENT:
374 return QObject::tr("Comment");
376 case CONDITIONAL:
377 return QObject::tr("Conditional Tag");
379 case TAG:
380 return QObject::tr("Tag");
382 case TEXT:
383 return QObject::tr("Plaintext");
386 else if(param)
388 switch(param->type)
390 case skin_tag_parameter::STRING:
391 return QObject::tr("String");
393 case skin_tag_parameter::NUMERIC:
394 return QObject::tr("Number");
396 case skin_tag_parameter::DEFAULT:
397 return QObject::tr("Default Argument");
399 case skin_tag_parameter::CODE:
400 return QObject::tr("This doesn't belong here");
403 else
405 return QObject::tr("Root");
408 break;
410 case ParseTreeModel::valueColumn:
411 if(element)
413 switch(element->type)
415 case UNKNOWN:
416 case VIEWPORT:
417 case LINE:
418 case SUBLINES:
419 return QString();
421 case CONDITIONAL:
422 return QString(element->tag->name);
424 case TEXT:
425 case COMMENT:
426 return QString((char*)element->data);
428 case TAG:
429 return QString(element->tag->name);
432 else if(param)
434 switch(param->type)
436 case skin_tag_parameter::DEFAULT:
437 return QObject::tr("-");
439 case skin_tag_parameter::STRING:
440 return QString(param->data.text);
442 case skin_tag_parameter::NUMERIC:
443 return QString::number(param->data.numeric, 10);
445 case skin_tag_parameter::CODE:
446 return QObject::tr("Seriously, something's wrong here");
449 else
451 return QString();
453 break;
455 case ParseTreeModel::lineColumn:
456 if(element)
457 return QString::number(element->line, 10);
458 else
459 return QString();
460 break;
463 return QVariant();
467 int ParseTreeNode::getRow() const
469 if(!parent)
470 return -1;
472 return parent->children.indexOf(const_cast<ParseTreeNode*>(this));
475 ParseTreeNode* ParseTreeNode::getParent() const
477 return parent;
480 /* This version is called for the root node and for viewports */
481 void ParseTreeNode::render(const RBRenderInfo& info)
483 /* Parameters don't get rendered */
484 if(!element && param)
485 return;
487 /* If we're at the root, we need to render each viewport */
488 if(!element && !param)
490 for(int i = 0; i < children.count(); i++)
492 children[i]->render(info);
495 return;
498 if(element->type != VIEWPORT)
500 std::cerr << QObject::tr("Error in parse tree").toStdString()
501 << std::endl;
502 return;
505 rendered = new RBViewport(element, info);
507 for(int i = element->params_count; i < children.count(); i++)
508 children[i]->render(info, dynamic_cast<RBViewport*>(rendered));
512 /* This version is called for logical lines, tags, conditionals and such */
513 void ParseTreeNode::render(const RBRenderInfo &info, RBViewport* viewport,
514 bool noBreak)
516 if(element->type == LINE)
518 for(int i = 0; i < children.count(); i++)
519 children[i]->render(info, viewport);
520 if(!noBreak)
521 viewport->newLine();
523 else if(element->type == TEXT)
525 viewport->write(QString(static_cast<char*>(element->data)));
527 else if(element->type == TAG)
529 if(!execTag(info, viewport))
530 viewport->write(evalTag(info).toString());
532 else if(element->type == CONDITIONAL)
534 int child = evalTag(info, true, element->children_count).toInt();
535 children[element->params_count + child]->render(info, viewport, true);
537 else if(element->type == SUBLINES)
539 /* First we build a list of the times for each branch */
540 QList<double> times;
541 for(int i = 0; i < children.count() ; i++)
542 times.append(findBranchTime(children[i], info));
544 /* Now we figure out which branch to select */
545 double timeLeft = info.device()->data(QString("?pc")).toDouble();
546 int branch = 0;
547 while(timeLeft > 0)
549 timeLeft -= times[branch];
550 if(timeLeft >= 0)
551 branch++;
552 else
553 break;
554 if(branch >= times.count())
555 branch = 0;
558 /* In case we end up on a disabled branch, skip ahead. If we find that
559 * all the branches are disabled, don't render anything
561 int originalBranch = branch;
562 while(times[branch] == 0)
564 branch++;
565 if(branch == originalBranch)
567 branch = -1;
568 break;
570 if(branch >= times.count())
571 branch = 0;
574 /* ...and finally render the selected branch */
575 if(branch >= 0)
576 children[branch]->render(info, viewport, true);
580 bool ParseTreeNode::execTag(const RBRenderInfo& info, RBViewport* viewport)
583 QString filename;
584 QString id;
585 int x, y, tiles, tile, maxWidth, maxHeight, width, height;
586 char c, hAlign, vAlign;
587 RBImage* image;
589 /* Two switch statements to narrow down the tag name */
590 switch(element->tag->name[0])
593 case 'a':
594 switch(element->tag->name[1])
596 case 'c':
597 /* %ac */
598 viewport->alignText(RBViewport::Center);
599 return true;
601 case 'l':
602 /* %al */
603 viewport->alignText(RBViewport::Left);
604 return true;
606 case 'r':
607 /* %ar */
608 viewport->alignText(RBViewport::Right);
609 return true;
612 return false;
614 case 'w':
615 switch(element->tag->name[1])
617 case 'd':
618 info.screen()->disableStatusBar();
619 return true;
621 case 'e':
622 info.screen()->enableStatusBar();
623 return true;
625 case 'i':
626 info.screen()->disableStatusBar();
627 viewport->enableStatusBar();
628 return true;
631 return false;
633 case 'x':
634 switch(element->tag->name[1])
636 case 'd':
637 /* %xd */
638 id = "";
639 id.append(element->params[0].data.text[0]);
640 c = element->params[0].data.text[1];
642 if(c == '\0')
644 tile = 1;
646 else
648 if(isupper(c))
649 tile = c - 'A' + 25;
650 else
651 tile = c - 'a';
654 if(info.screen()->getImage(id))
656 image = new RBImage(*(info.screen()->getImage(id)), viewport);
657 image->setTile(tile);
658 image->show();
661 return true;
663 case 'l':
664 /* %xl */
665 id = element->params[0].data.text;
666 filename = info.settings()->value("imagepath", "") + "/" +
667 element->params[1].data.text;
668 x = element->params[2].data.numeric;
669 y = element->params[3].data.numeric;
670 if(element->params_count > 4)
671 tiles = element->params[4].data.numeric;
672 else
673 tiles = 1;
675 info.screen()->loadImage(id, new RBImage(filename, tiles, x, y,
676 viewport));
677 return true;
679 case '\0':
680 /* %x */
681 id = element->params[0].data.text;
682 filename = info.settings()->value("imagepath", "") + "/" +
683 element->params[1].data.text;
684 x = element->params[2].data.numeric;
685 y = element->params[3].data.numeric;
686 image = new RBImage(filename, 1, x, y, viewport);
687 info.screen()->loadImage(id, new RBImage(filename, 1, x, y,
688 viewport));
689 info.screen()->getImage(id)->show();
690 return true;
694 return false;
696 case 'C':
697 switch(element->tag->name[1])
699 case 'd':
700 /* %Cd */
701 info.screen()->showAlbumArt(viewport);
702 return true;
704 case 'l':
705 /* %Cl */
706 x = element->params[0].data.numeric;
707 y = element->params[1].data.numeric;
708 maxWidth = element->params[2].data.numeric;
709 maxHeight = element->params[3].data.numeric;
710 hAlign = element->params_count > 4
711 ? element->params[4].data.text[0] : 'c';
712 vAlign = element->params_count > 5
713 ? element->params[5].data.text[0] : 'c';
714 width = info.device()->data("artwidth").toInt();
715 height = info.device()->data("artheight").toInt();
716 info.screen()->setAlbumArt(new RBAlbumArt(viewport, x, y, maxWidth,
717 maxHeight, width, height,
718 hAlign, vAlign));
719 return true;
722 return false;
724 case 'F':
726 switch(element->tag->name[1])
729 case 'l':
730 /* %Fl */
731 x = element->params[0].data.numeric;
732 filename = info.settings()->value("themebase", "") + "/fonts/" +
733 element->params[1].data.text;
734 info.screen()->loadFont(x, new RBFont(filename));
735 return true;
739 return false;
741 case 'V':
743 switch(element->tag->name[1])
746 case 'b':
747 /* %Vb */
748 viewport->setBGColor(RBScreen::
749 stringToColor(QString(element->params[0].
750 data.text),
751 Qt::white));
752 return true;
754 case 'd':
755 /* %Vd */
756 id = element->params[0].data.text;
757 info.screen()->showViewport(id);
758 return true;
760 case 'f':
761 /* %Vf */
762 viewport->setFGColor(RBScreen::
763 stringToColor(QString(element->params[0].
764 data.text),
765 Qt::black));
766 return true;
768 case 'p':
769 /* %Vp */
770 viewport->showPlaylist(info, element->params[0].data.numeric,
771 element->params[1].data.code,
772 element->params[2].data.code);
773 return true;
775 case 'I':
776 /* %VI */
777 info.screen()->makeCustomUI(element->params[0].data.text);
778 return true;
782 return false;
784 case 'X':
786 switch(element->tag->name[1])
788 case '\0':
789 /* %X */
790 filename = QString(element->params[0].data.text);
791 info.screen()->setBackdrop(filename);
792 return true;
795 return false;
799 return false;
803 QVariant ParseTreeNode::evalTag(const RBRenderInfo& info, bool conditional,
804 int branches)
806 if(!conditional)
808 return info.device()->data(QString(element->tag->name));
810 else
812 /* If we're evaluating for a conditional, we return the child branch
813 * index that should be selected. For true/false values, this is
814 * 0 for true, 1 for false, and we also have to make sure not to
815 * ever exceed the number of available children
818 int child;
819 QVariant val = info.device()->data("?" + QString(element->tag->name));
820 if(val.isNull())
821 val = info.device()->data(QString(element->tag->name));
823 if(val.isNull())
825 child = 1;
827 else if(QString(element->tag->name) == "bl")
829 /* bl has to be scaled to the number of available children, but it
830 * also has an initial -1 value for an unknown state */
831 child = val.toInt();
832 if(child == -1)
834 child = 0;
836 else
838 child = ((branches - 1) * child / 100) + 1;
841 else if(QString(element->tag->name) == "px")
843 child = val.toInt();
844 child = branches * child / 100;
846 else if(val.type() == QVariant::Bool)
848 /* Boolean values have to be reversed, because conditionals are
849 * always of the form %?tag<true|false>
851 if(val.toBool())
852 child = 0;
853 else
854 child = 1;
856 else if(val.type() == QVariant::String)
858 if(val.toString().length() > 0)
859 child = 0;
860 else
861 child = 1;
863 else
865 child = val.toInt();
868 if(child < 0)
869 child = 0;
871 if(child < branches)
872 return child;
873 else
874 return branches - 1;
878 double ParseTreeNode::findBranchTime(ParseTreeNode *branch,
879 const RBRenderInfo& info)
881 double retval = 2;
882 for(int i = 0; i < branch->children.count(); i++)
884 ParseTreeNode* current = branch->children[i];
885 if(current->element->type == TAG)
887 if(current->element->tag->name[0] == 't'
888 && current->element->tag->name[1] == '\0')
890 retval = atof(current->element->params[0].data.text);
893 else if(current->element->type == CONDITIONAL)
895 retval = findConditionalTime(current, info);
898 return retval;
901 double ParseTreeNode::findConditionalTime(ParseTreeNode *conditional,
902 const RBRenderInfo& info)
904 int child = conditional->evalTag(info, true,
905 conditional->children.count()).toInt();
906 return findBranchTime(conditional->children[child], info);