00001 #include <qapplication.h>
00002 #include <qimage.h>
00003 #include <qdir.h>
00004
00005 #include <iostream>
00006 #include <cmath>
00007 #include <cstdlib>
00008 using namespace std;
00009
00010 #include "exitcodes.h"
00011 #include "myththemedmenu.h"
00012 #include "mythcontext.h"
00013 #include "util.h"
00014 #include "mythmainwindow.h"
00015 #include "mythfontproperties.h"
00016 #include "mythimage.h"
00017 #include "mythdialogbox.h"
00018
00019 #include "lcddevice.h"
00020 #include "mythplugin.h"
00021
00022 #include "mythgesture.h"
00023 #include "mythuiimage.h"
00024 #include "mythuitext.h"
00025 #include "mythuistatetype.h"
00026 #include "xmlparsebase.h"
00027
00028
00029 #include "mythdialogs.h"
00030
00031 struct TextAttributes
00032 {
00033 QRect textRect;
00034 MythFontProperties font;
00035 int textflags;
00036 };
00037
00038 struct ButtonIcon
00039 {
00040 QString name;
00041 MythImage *icon;
00042 MythImage *activeicon;
00043 MythImage *watermark;
00044 QPoint offset;
00045 };
00046
00047 class ThemedButton : public MythUIType
00048 {
00049 public:
00050 ThemedButton(MythUIType *parent, const char *name)
00051 : MythUIType(parent, name),
00052 background(NULL), icon(NULL), text(NULL),
00053 row(0), col(0)
00054 {
00055 }
00056
00057 void SetActive(bool active)
00058 {
00059 MythUIStateType::StateType state = MythUIStateType::None;
00060 if (active)
00061 state = MythUIStateType::Full;
00062
00063 if (background)
00064 background->DisplayState(state);
00065 if (icon)
00066 icon->DisplayState(state);
00067 if (text)
00068 text->DisplayState(state);
00069 }
00070
00071 MythUIStateType *background;
00072 MythUIStateType *icon;
00073 MythUIStateType *text;
00074
00075 QStringList action;
00076 QString message;
00077 QString type;
00078
00079 int row;
00080 int col;
00081 };
00082
00083 struct MenuRow
00084 {
00085 int numitems;
00086 bool visible;
00087 vector<ThemedButton *> buttons;
00088 };
00089
00090 class MythThemedMenuPrivate;
00091
00096 class MythThemedMenuState: public XMLParseBase
00097 {
00098 public:
00099 MythThemedMenuState();
00100 ~MythThemedMenuState();
00101
00102 bool parseSettings(const QString &dir, const QString &menuname);
00103
00104 void parseBackground(const QString &dir, QDomElement &element);
00105 void parseLogo(const QString &dir, QDomElement &element);
00106 void parseArrow(const QString &dir, QDomElement &element, bool up);
00107 void parseTitle(const QString &dir, QDomElement &element);
00108 void parseButtonDefinition(const QString &dir, QDomElement &element);
00109 void parseButton(const QString &dir, QDomElement &element);
00110
00111 void parseText(TextAttributes &attributes, QDomElement &element);
00112 void parseOutline(TextAttributes &attributes, QDomElement &element);
00113 void parseShadow(TextAttributes &attributes, QDomElement &element);
00114
00115 void Reset(void);
00116 void setDefaults(void);
00117
00118 ButtonIcon *getButtonIcon(const QString &type);
00119
00120 QRect buttonArea;
00121
00122 QRect logoRect;
00123 MythImage *logo;
00124
00125 MythImage *buttonnormal;
00126 MythImage *buttonactive;
00127
00128 QMap<QString, ButtonIcon> allButtonIcons;
00129
00130 TextAttributes normalAttributes;
00131 TextAttributes activeAttributes;
00132
00133 void (*callback)(void *, QString &);
00134 void *callbackdata;
00135
00136 bool killable;
00137
00138 bool balancerows;
00139 bool spreadbuttons;
00140 bool buttoncenter;
00141
00142 QMap<QString, MythImage *> titleIcons;
00143 QMap<QString, MythImage *> m_loadedImages;
00144 QString titleText;
00145 QPoint titlePos;
00146
00147 MythImage *buttonBackground;
00148
00149 MythImage *uparrow;
00150 QRect uparrowRect;
00151 MythImage *downarrow;
00152 QRect downarrowRect;
00153
00154 QPoint watermarkPos;
00155 QRect watermarkRect;
00156
00157 bool allowreorder;
00158 int maxColumns;
00159
00160 int visiblerowlimit;
00161
00162 bool loaded;
00163
00164 QString themeDir;
00165
00166
00167 static bool parseFonts;
00168 };
00169
00170 bool MythThemedMenuState::parseFonts = true;
00171
00172 class MythThemedMenuPrivate: public XMLParseBase
00173 {
00174 public:
00175 MythThemedMenuPrivate(MythThemedMenu *lparent, const char *cdir,
00176 MythThemedMenuState *lstate);
00177 ~MythThemedMenuPrivate();
00178
00179 bool keyPressHandler(QKeyEvent *e);
00180 bool keyHandler(QStringList &actions, bool fullexit);
00181
00182 bool ReloadTheme(void);
00183
00184 bool parseMenu(const QString &menuname);
00185
00186 void parseThemeButton(QDomElement &element);
00187
00188 void addButton(const QString &type, const QString &text,
00189 const QString &alttext, const QStringList &action);
00190 bool layoutButtons(void);
00191 void positionButtons(bool resetpos);
00192 bool makeRowVisible(int newrow, int oldrow);
00193
00194 bool handleAction(const QString &action);
00195 bool findDepends(const QString &fileList);
00196 QString findMenuFile(const QString &menuname);
00197
00198 void checkScrollArrows(void);
00199
00200 bool checkPinCode(const QString ×tamp_setting,
00201 const QString &password_setting,
00202 const QString &text);
00203
00204 void SetupBackground();
00205 void SetupUITypes();
00206
00207 bool gestureEvent(MythUIType *origtype, MythGestureEvent *ge);
00208
00209 void updateLCD(void);
00210
00211 MythThemedMenu *parent;
00212
00213 MythThemedMenuState *m_state;
00214 bool allocedstate;
00215
00216 vector<ThemedButton *> buttonList;
00217 ThemedButton *activebutton;
00218 int currentrow;
00219 int currentcolumn;
00220
00221 vector<MenuRow> buttonRows;
00222
00223 QString selection;
00224 bool foundtheme;
00225
00226 int exitModifier;
00227
00228 bool ignorekeys;
00229
00230 int maxrows;
00231 int visiblerows;
00232 int columns;
00233
00234 bool wantpop;
00235
00236 QString titleText;
00237 QString menumode;
00238
00239 MythUIStateType *watermark;
00240 MythUIImage *uparrow;
00241 MythUIImage *downarrow;
00242 };
00243
00245
00246 MythThemedMenuState::MythThemedMenuState()
00247 {
00248 allowreorder = true;
00249 balancerows = true;
00250
00251 logo = NULL;
00252 buttonnormal = NULL;
00253 buttonactive = NULL;
00254 uparrow = NULL;
00255 downarrow = NULL;
00256 buttonBackground = NULL;
00257
00258 loaded = false;
00259
00260 callback = NULL;
00261
00262 killable = false;
00263 }
00264
00265 MythThemedMenuState::~MythThemedMenuState()
00266 {
00267 Reset();
00268 }
00269
00270 void MythThemedMenuState::Reset(void)
00271 {
00272 if (logo)
00273 logo->DownRef();
00274 if (buttonnormal)
00275 buttonnormal->DownRef();
00276 if (buttonactive)
00277 buttonactive->DownRef();
00278 if (uparrow)
00279 uparrow->DownRef();
00280 if (downarrow)
00281 downarrow->DownRef();
00282 if (buttonBackground)
00283 buttonBackground->DownRef();
00284
00285 logo = NULL;
00286 buttonnormal = NULL;
00287 buttonactive = NULL;
00288 uparrow = NULL;
00289 downarrow = NULL;
00290 buttonBackground = NULL;
00291
00292 QMap<QString, ButtonIcon>::Iterator it;
00293 for (it = allButtonIcons.begin(); it != allButtonIcons.end(); ++it)
00294 {
00295 if (it.data().icon)
00296 it.data().icon->DownRef();
00297 if (it.data().activeicon)
00298 it.data().activeicon->DownRef();
00299 if (it.data().watermark)
00300 it.data().watermark->DownRef();
00301 }
00302 allButtonIcons.clear();
00303
00304 QMap<QString, MythImage *>::Iterator jt;
00305 for (jt = titleIcons.begin(); jt != titleIcons.end(); ++jt)
00306 {
00307 jt.data()->DownRef();
00308 }
00309 titleIcons.clear();
00310 m_loadedImages.clear();
00311
00312 normalAttributes = activeAttributes = TextAttributes();
00313 setDefaults();
00314
00315 loaded = false;
00316 }
00317
00318 ButtonIcon *MythThemedMenuState::getButtonIcon(const QString &type)
00319 {
00320 if (allButtonIcons.find(type) != allButtonIcons.end())
00321 return &(allButtonIcons[type]);
00322 return NULL;
00323 }
00324
00330 void MythThemedMenuState::parseBackground(
00331 const QString &dir, QDomElement &element)
00332 {
00333 QString path;
00334
00335 bool hasarea = false;
00336
00337 buttoncenter = true;
00338 spreadbuttons = true;
00339 maxColumns = 20;
00340 visiblerowlimit = 6;
00341
00342 for (QDomNode child = element.firstChild(); !child.isNull();
00343 child = child.nextSibling())
00344 {
00345 QDomElement info = child.toElement();
00346 if (!info.isNull())
00347 {
00348 if (info.tagName() == "buttonarea")
00349 {
00350 buttonArea = parseRect(info);
00351 hasarea = true;
00352
00353 if (info.hasAttribute("background"))
00354 {
00355 QString bPath = dir + info.attribute("background");
00356 QImage *image = gContext->LoadScaleImage(bPath);
00357 buttonBackground = MythImage::FromQImage(&image);
00358 }
00359 }
00360 else if (info.tagName() == "buttonspread")
00361 {
00362 QString val = getFirstText(info);
00363 if (val == "no")
00364 spreadbuttons = false;
00365 }
00366 else if (info.tagName() == "buttoncenter")
00367 {
00368 QString val = getFirstText(info);
00369 if (val == "no")
00370 buttoncenter = false;
00371 }
00372 else if (info.tagName() == "balancerows")
00373 {
00374 QString val = getFirstText(info);
00375 if (val == "no")
00376 balancerows = false;
00377 }
00378 else if (info.tagName() == "columns")
00379 {
00380 QString val = getFirstText(info);
00381 maxColumns = val.toInt();
00382 }
00383 else if (info.tagName() == "visiblerowlimit")
00384 {
00385 visiblerowlimit = atoi(getFirstText(info).ascii());
00386 }
00387 else
00388 {
00389 VERBOSE(VB_GENERAL, QString("MythThemedMenuPrivate: Unknown tag %1 in "
00390 "background").arg(info.tagName()));
00391 }
00392 }
00393 }
00394
00395 if (!hasarea)
00396 {
00397 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: Missing button area in background");
00398 return;
00399 }
00400 }
00401
00407 void MythThemedMenuState::parseShadow(TextAttributes &attributes,
00408 QDomElement &element)
00409 {
00410 QPoint offset;
00411 QColor color;
00412 int alpha;
00413
00414 bool hascolor = false;
00415 bool hasoffset = false;
00416 bool hasalpha = false;
00417
00418 for (QDomNode child = element.firstChild(); !child.isNull();
00419 child = child.nextSibling())
00420 {
00421 QDomElement info = child.toElement();
00422 if (!info.isNull())
00423 {
00424 if (info.tagName() == "color")
00425 {
00426
00427 QColor temp(getFirstText(info));
00428 color = QColor(temp.name());
00429 hascolor = true;
00430 }
00431 else if (info.tagName() == "offset")
00432 {
00433 offset = parsePoint(info);
00434 hasoffset = true;
00435 }
00436 else if (info.tagName() == "alpha")
00437 {
00438 alpha = atoi(getFirstText(info).ascii());
00439 hasalpha = true;
00440 }
00441 else
00442 {
00443 VERBOSE(VB_GENERAL, QString("MythThemedMenuPrivate: Unknown tag %1 in "
00444 "text/shadow").arg(info.tagName()));
00445 }
00446 }
00447 }
00448
00449 if (!hascolor)
00450 {
00451 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: Missing color tag in shadow");
00452 return;
00453 }
00454
00455 if (!hasalpha)
00456 {
00457 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: Missing alpha tag in shadow");
00458 return;
00459 }
00460
00461 if (!hasoffset)
00462 {
00463 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: Missing offset tag in shadow");
00464 return;
00465 }
00466
00467 attributes.font.SetShadow(true, offset, color, alpha);
00468 }
00469
00476 void MythThemedMenuState::parseOutline(TextAttributes &attributes,
00477 QDomElement &element)
00478 {
00479 QColor color;
00480 int size = 0, alpha = 255;
00481
00482 bool hascolor = false;
00483 bool hassize = false;
00484
00485 for (QDomNode child = element.firstChild(); !child.isNull();
00486 child = child.nextSibling())
00487 {
00488 QDomElement info = child.toElement();
00489 if (!info.isNull())
00490 {
00491 if (info.tagName() == "color")
00492 {
00493
00494 QColor temp(getFirstText(info));
00495 color = QColor(temp.name());
00496 hascolor = true;
00497 }
00498 else if (info.tagName() == "size")
00499 {
00500 int lsize = atoi(getFirstText(info).ascii());
00501 size = GetMythMainWindow()->NormY(lsize);
00502 hassize = true;
00503 }
00504 else
00505 {
00506 VERBOSE(VB_GENERAL, QString("MythThemedMenuPrivate: Unknown tag %1 in "
00507 "text/shadow").arg(info.tagName()));
00508 }
00509 }
00510 }
00511
00512 if (!hassize)
00513 {
00514 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: Missing size in outline");
00515 return;
00516 }
00517
00518 if (!hascolor)
00519 {
00520 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: Missing color in outline");
00521 return;
00522 }
00523
00524 attributes.font.SetOutline(true, color, size, alpha);
00525 }
00526
00533 void MythThemedMenuState::parseText(TextAttributes &attributes,
00534 QDomElement &element)
00535 {
00536 bool hasarea = false;
00537
00538 int weight = QFont::Normal;
00539 int fontsize = 14;
00540 QString fontname = "Arial";
00541 bool italic = false;
00542
00543 attributes.textflags = Qt::WordBreak;
00544 for (QDomNode child = element.firstChild(); !child.isNull();
00545 child = child.nextSibling())
00546 {
00547 QDomElement info = child.toElement();
00548 if (!info.isNull())
00549 {
00550 if (info.tagName() == "area")
00551 {
00552 hasarea = true;
00553 attributes.textRect = parseRect(info);
00554 attributes.textRect = QRect(attributes.textRect.x(),
00555 attributes.textRect.y(),
00556 buttonnormal->width() -
00557 attributes.textRect.width() -
00558 attributes.textRect.x(),
00559 buttonnormal->height() -
00560 attributes.textRect.height() -
00561 attributes.textRect.y());
00562 }
00563 else if (info.tagName() == "fontsize")
00564 {
00565 fontsize = atoi(getFirstText(info).ascii());
00566 }
00567 else if (info.tagName() == "fontname")
00568 {
00569 fontname = getFirstText(info);
00570 }
00571 else if (info.tagName() == "bold")
00572 {
00573 if (getFirstText(info) == "yes")
00574 weight = QFont::Bold;
00575 }
00576 else if (info.tagName() == "italics")
00577 {
00578 if (getFirstText(info) == "yes")
00579 italic = true;
00580 }
00581 else if (info.tagName() == "color")
00582 {
00583
00584 QColor temp(getFirstText(info));
00585 attributes.font.SetColor(QColor(temp.name()));
00586 }
00587 else if (info.tagName() == "centered")
00588 {
00589 if (getFirstText(info) == "yes")
00590 {
00591 if (gContext->GetLanguage() == "ja")
00592 {
00593 attributes.textflags = Qt::AlignVCenter |
00594 Qt::AlignHCenter |
00595 Qt::WordBreak;
00596 }
00597 else
00598 {
00599 attributes.textflags = Qt::AlignTop |
00600 Qt::AlignHCenter |
00601 Qt::WordBreak;
00602 }
00603 }
00604 }
00605
00606 else if (info.tagName() == "halign")
00607 {
00608 if (getFirstText(info) == "center")
00609 {
00610
00611 if (gContext->GetLanguage() == "ja")
00612 {
00613 attributes.textflags = attributes.textflags &
00614 ~Qt::AlignHorizontal_Mask |
00615 Qt::AlignCenter;
00616 }
00617 else
00618 {
00619 attributes.textflags = attributes.textflags &
00620 ~Qt::AlignHorizontal_Mask |
00621 Qt::AlignHCenter;
00622 }
00623 }
00624 else if (getFirstText(info) == "left")
00625 {
00626 attributes.textflags = attributes.textflags &
00627 ~Qt::AlignHorizontal_Mask |
00628 Qt::AlignLeft |
00629 Qt::WordBreak;
00630 }
00631 else if (getFirstText(info) == "right")
00632 {
00633 attributes.textflags = attributes.textflags &
00634 ~Qt::AlignHorizontal_Mask |
00635 Qt::AlignRight |
00636 Qt::WordBreak;
00637 }
00638 else
00639 {
00640 VERBOSE(VB_GENERAL,
00641 QString("MythThemedMenuPrivate: Unknown value %1 "
00642 "for halign").arg(getFirstText(info)));
00643 }
00644 }
00645
00646 else if (info.tagName() == "valign")
00647 {
00648 if (getFirstText(info) == "center")
00649 {
00650 attributes.textflags = attributes.textflags &
00651 ~Qt::AlignVertical_Mask |
00652 Qt::AlignVCenter;
00653 }
00654 else if (getFirstText(info) == "top")
00655 {
00656 attributes.textflags = attributes.textflags &
00657 ~Qt::AlignVertical_Mask |
00658 Qt::AlignTop;
00659 }
00660 else if (getFirstText(info) == "bottom")
00661 {
00662 attributes.textflags = attributes.textflags &
00663 ~Qt::AlignVertical_Mask |
00664 Qt::AlignBottom;
00665 }
00666 else
00667 {
00668 VERBOSE(VB_GENERAL,
00669 QString("MythThemedMenuPrivate: Unknown value %1 "
00670 "for valign").arg(getFirstText(info)));
00671 }
00672 }
00673 else if (info.tagName() == "outline")
00674 {
00675 parseOutline(attributes, info);
00676 }
00677 else if (info.tagName() == "shadow")
00678 {
00679 parseShadow(attributes, info);
00680 }
00681 else
00682 {
00683 VERBOSE(VB_GENERAL, QString("MythThemedMenuPrivate: Unknown "
00684 "tag %1 in text").arg(info.tagName()));
00685 return;
00686 }
00687 }
00688 }
00689
00690 QFont font = GetMythMainWindow()->CreateQFont(
00691 fontname, fontsize, weight, italic);
00692
00693 attributes.font.SetFace(font);
00694
00695 if (!hasarea)
00696 {
00697 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: Missing 'area' "
00698 "tag in 'text' element of 'genericbutton'");
00699 return;
00700 }
00701 }
00702
00703 void MythThemedMenuState::parseButtonDefinition(const QString &dir,
00704 QDomElement &element)
00705 {
00706 bool hasnormal = false;
00707 bool hasactive = false;
00708 bool hasactivetext = false;
00709
00710 QString setting;
00711
00712 QImage *tmp;
00713
00714 for (QDomNode child = element.firstChild(); !child.isNull();
00715 child = child.nextSibling())
00716 {
00717 QDomElement info = child.toElement();
00718 if (!info.isNull())
00719 {
00720 if (info.tagName() == "normal")
00721 {
00722 setting = dir + getFirstText(info);
00723 tmp = gContext->LoadScaleImage(setting);
00724 if (tmp)
00725 {
00726 buttonnormal = MythImage::FromQImage(&tmp);
00727 hasnormal = true;
00728 }
00729 }
00730 else if (info.tagName() == "active")
00731 {
00732 setting = dir + getFirstText(info);
00733 tmp = gContext->LoadScaleImage(setting);
00734 if (tmp)
00735 {
00736 buttonactive = MythImage::FromQImage(&tmp);
00737 hasactive = true;
00738 }
00739 }
00740 else if (info.tagName() == "text")
00741 {
00742 if (!hasnormal)
00743 {
00744 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: The 'normal' "
00745 "tag needs to come before the 'text' tag");
00746 return;
00747 }
00748 parseText(normalAttributes, info);
00749 }
00750 else if (info.tagName() == "activetext")
00751 {
00752 if (!hasactive)
00753 {
00754 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: The 'active' "
00755 "tag needs to come before the 'activetext' tag");
00756 return;
00757 }
00758 parseText(activeAttributes, info);
00759 hasactivetext = true;
00760 }
00761 else if (info.tagName() == "watermarkposition")
00762 {
00763 watermarkPos = parsePoint(info);
00764 }
00765 else
00766 {
00767 VERBOSE(VB_GENERAL,
00768 QString("MythThemedMenuPrivate: Unknown tag %1 in "
00769 "genericbutton").arg(info.tagName()));
00770 }
00771 }
00772 }
00773
00774 if (!hasnormal)
00775 {
00776 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: No normal button image defined");
00777 return;
00778 }
00779
00780 if (!hasactive)
00781 {
00782 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: No active button image defined");
00783 return;
00784 }
00785
00786 if (!hasactivetext)
00787 {
00788 activeAttributes = normalAttributes;
00789 }
00790
00791 watermarkRect = QRect(watermarkPos, QSize(0, 0));
00792 }
00793
00800 void MythThemedMenuState::parseLogo(const QString &dir, QDomElement &element)
00801 {
00802 bool hasimage = false;
00803 bool hasposition = false;
00804
00805 QPoint logopos;
00806
00807 for (QDomNode child = element.firstChild(); !child.isNull();
00808 child = child.nextSibling())
00809 {
00810 QDomElement info = child.toElement();
00811 if (!info.isNull())
00812 {
00813 if (info.tagName() == "image")
00814 {
00815 QString logopath = dir + getFirstText(info);
00816 QImage *tmp = gContext->LoadScaleImage(logopath);
00817 if (tmp)
00818 {
00819 logo = MythImage::FromQImage(&tmp);
00820 hasimage = true;
00821 }
00822 }
00823 else if (info.tagName() == "position")
00824 {
00825 logopos = parsePoint(info);
00826 hasposition = true;
00827 }
00828 else
00829 {
00830 VERBOSE(VB_GENERAL, QString("MythThemedMenuPrivate: Unknown tag %1 "
00831 "in logo").arg(info.tagName()));
00832 }
00833 }
00834 }
00835
00836 if (!hasimage)
00837 {
00838 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: Missing image tag in logo");
00839 return;
00840 }
00841
00842 if (!hasposition)
00843 {
00844 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: Missing position tag in logo");
00845 return;
00846 }
00847
00848 logoRect = QRect(logopos.x(), logopos.y(), logo->width(),
00849 logo->height());
00850 }
00851
00858 void MythThemedMenuState::parseTitle(const QString &dir, QDomElement &element)
00859 {
00860 bool hasimage = false;
00861 bool hasposition = false;
00862
00863 for (QDomNode child = element.firstChild(); !child.isNull();
00864 child = child.nextSibling())
00865 {
00866 QDomElement info = child.toElement();
00867 if (!info.isNull())
00868 {
00869 if (info.tagName() == "image")
00870 {
00871
00872 QString titlepath = dir + getFirstText(info);
00873
00874 QString name = info.attribute("mode", "");
00875 if (name != "")
00876 {
00877 MythImage *icon;
00878 QImage *tmppix;
00879
00880 if (m_loadedImages[titlepath])
00881 {
00882 icon = m_loadedImages[titlepath];
00883 icon->UpRef();
00884 }
00885 else
00886 {
00887 tmppix = gContext->LoadScaleImage(titlepath);
00888
00889 if (!tmppix)
00890 continue;
00891
00892 icon = MythImage::FromQImage(&tmppix);
00893 m_loadedImages.insert(titlepath, icon);
00894 }
00895
00896 titleIcons[name] = icon;
00897 }
00898 else
00899 {
00900 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: "
00901 "Missing mode in titles/image");
00902 return;
00903 }
00904
00905 hasimage = true;
00906 }
00907 else if (info.tagName() == "position")
00908 {
00909 titlePos = parsePoint(info);
00910 hasposition = true;
00911 }
00912 else
00913 {
00914 VERBOSE(VB_GENERAL, QString("MythThemedMenuPrivate: Unknown tag %1 "
00915 "in logo").arg(info.tagName()));
00916 }
00917 }
00918 }
00919
00920 if (!hasimage)
00921 {
00922 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: Missing image tag in titles");
00923 return;
00924 }
00925
00926 if (!hasposition)
00927 {
00928 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: Missing position tag in titles");
00929 return;
00930 }
00931 }
00932
00939 void MythThemedMenuState::parseArrow(const QString &dir, QDomElement &element,
00940 bool up)
00941 {
00942 QRect arrowrect;
00943 QPoint arrowpos;
00944 QImage *pix = NULL;
00945
00946 bool hasimage = false;
00947 bool hasposition = false;
00948
00949 for (QDomNode child = element.firstChild(); !child.isNull();
00950 child = child.nextSibling())
00951 {
00952 QDomElement info = child.toElement();
00953 if (!info.isNull())
00954 {
00955 if (info.tagName() == "image")
00956 {
00957 QString arrowpath = dir + getFirstText(info);
00958 pix = gContext->LoadScaleImage(arrowpath);
00959 if (pix)
00960 hasimage = true;
00961 }
00962 else if (info.tagName() == "position")
00963 {
00964 arrowpos = parsePoint(info);
00965 hasposition = true;
00966 }
00967 else
00968 {
00969 VERBOSE(VB_GENERAL, QString("MythThemedMenuPrivate: Unknown tag %1 "
00970 "in arrow").arg(info.tagName()));
00971 }
00972 }
00973 }
00974
00975 if (!hasimage)
00976 {
00977 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: Missing image tag in arrow");
00978 return;
00979 }
00980
00981 if (!hasposition)
00982 {
00983 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: Missing position tag in arrow");
00984 return;
00985 }
00986
00987 arrowrect = QRect(arrowpos.x(), arrowpos.y(), pix->width(),
00988 pix->height());
00989
00990 if (up)
00991 {
00992 uparrow = MythImage::FromQImage(&pix);
00993 uparrowRect = arrowrect;
00994 }
00995 else
00996 {
00997 downarrow = MythImage::FromQImage(&pix);
00998 downarrowRect = arrowrect;
00999 }
01000 }
01001
01008 void MythThemedMenuState::parseButton(const QString &dir, QDomElement &element)
01009 {
01010 bool hasname = false;
01011 bool hasoffset = false;
01012 bool hasicon = false;
01013
01014 QString name = "";
01015 QImage *tmpimg = NULL;
01016 MythImage *image = NULL;
01017 MythImage *activeimage = NULL;
01018 MythImage *watermark = NULL;
01019 QPoint offset;
01020
01021 name = element.attribute("name", "");
01022 if (name != "")
01023 hasname = true;
01024
01025 for (QDomNode child = element.firstChild(); !child.isNull();
01026 child = child.nextSibling())
01027 {
01028 QDomElement info = child.toElement();
01029 if (!info.isNull())
01030 {
01031 if (info.tagName() == "image")
01032 {
01033 QString imagepath = dir + getFirstText(info);
01034
01035 if (m_loadedImages[imagepath])
01036 {
01037 image = m_loadedImages[imagepath];
01038 image->UpRef();
01039 }
01040 else
01041 {
01042 tmpimg = gContext->LoadScaleImage(imagepath);
01043 image = MythImage::FromQImage(&tmpimg);
01044 m_loadedImages.insert(imagepath, image);
01045 }
01046
01047 if (image)
01048 hasicon = true;
01049 }
01050 else if (info.tagName() == "activeimage")
01051 {
01052 QString imagepath = dir + getFirstText(info);
01053
01054 if (m_loadedImages[imagepath])
01055 {
01056 activeimage = m_loadedImages[imagepath];
01057 activeimage->UpRef();
01058 }
01059 else
01060 {
01061 tmpimg = gContext->LoadScaleImage(imagepath);
01062 activeimage = MythImage::FromQImage(&tmpimg);
01063 m_loadedImages.insert(imagepath, activeimage);
01064 }
01065 }
01066 else if (info.tagName() == "offset")
01067 {
01068 offset = parsePoint(info);
01069 hasoffset = true;
01070 }
01071 else if (info.tagName() == "watermarkimage")
01072 {
01073 QString imagepath = dir + getFirstText(info);
01074
01075 if (m_loadedImages[imagepath])
01076 {
01077 watermark = m_loadedImages[imagepath];
01078 watermark->UpRef();
01079 }
01080 else
01081 {
01082 tmpimg = gContext->LoadScaleImage(imagepath);
01083 watermark = MythImage::FromQImage(&tmpimg);
01084 m_loadedImages.insert(imagepath, watermark);
01085 }
01086 }
01087 else
01088 {
01089 VERBOSE(VB_GENERAL, QString("MythThemedMenuPrivate: Unknown tag %1 "
01090 "in buttondef").arg(info.tagName()));
01091 }
01092 }
01093 }
01094
01095 if (!hasname)
01096 {
01097 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: Missing name in button");
01098 return;
01099 }
01100
01101 if (!hasoffset)
01102 {
01103 VERBOSE(VB_IMPORTANT, QString("MythThemedMenuPrivate: Missing offset "
01104 "in buttondef %1").arg(name));
01105 return;
01106 }
01107
01108 if (!hasicon)
01109 {
01110 VERBOSE(VB_IMPORTANT, QString("MythThemedMenuPrivate: Missing image "
01111 "in buttondef %1").arg(name));
01112 return;
01113 }
01114
01115 ButtonIcon newbutton;
01116
01117 newbutton.name = name;
01118 newbutton.icon = image;
01119 newbutton.offset = offset;
01120 newbutton.activeicon = activeimage;
01121
01122 if (watermark)
01123 {
01124 if (watermark->width() > watermarkRect.width())
01125 watermarkRect.setWidth(watermark->width());
01126
01127 if (watermark->height() > watermarkRect.height())
01128 watermarkRect.setHeight(watermark->height());
01129 }
01130
01131 newbutton.watermark = watermark;
01132
01133 allButtonIcons[name] = newbutton;
01134
01135 if (tmpimg)
01136 delete tmpimg;
01137 }
01138
01142 void MythThemedMenuState::setDefaults(void)
01143 {
01144 logo = NULL;
01145 buttonnormal = buttonactive = NULL;
01146 balancerows = true;
01147
01148 normalAttributes.textflags = Qt::AlignTop | Qt::AlignLeft | Qt::WordBreak;
01149 activeAttributes.textflags = Qt::AlignTop | Qt::AlignLeft | Qt::WordBreak;
01150
01151 titleIcons.clear();
01152 titleText = "";
01153 uparrow = NULL;
01154 downarrow = NULL;
01155 watermarkPos = QPoint(0, 0);
01156 watermarkRect = QRect(0, 0, 0, 0);
01157 }
01158
01165 bool MythThemedMenuState::parseSettings(
01166 const QString &dir, const QString &menuname)
01167 {
01168 QString filename = dir + menuname;
01169
01170 QDomDocument doc;
01171 QFile f(filename);
01172
01173 if (!f.open(IO_ReadOnly))
01174 {
01175 VERBOSE(VB_IMPORTANT, QString("MythThemedMenuPrivate::parseSettings(): "
01176 "Can't open: %1").arg(filename));
01177 return false;
01178 }
01179
01180 QString errorMsg;
01181 int errorLine = 0;
01182 int errorColumn = 0;
01183
01184 if (!doc.setContent(&f, false, &errorMsg, &errorLine, &errorColumn))
01185 {
01186 VERBOSE(VB_IMPORTANT, QString("MythThemedMenuPrivate: Error, parsing %1\n"
01187 "at line: %2 column: %3 msg: %4").
01188 arg(filename).arg(errorLine).arg(errorColumn).arg(errorMsg));
01189 f.close();
01190 return false;
01191 }
01192
01193 f.close();
01194
01195 bool setbackground = false;
01196 bool setbuttondef = false;
01197
01198 setDefaults();
01199
01200 QDomElement docElem = doc.documentElement();
01201 QDomNode n = docElem.firstChild();
01202 while (!n.isNull())
01203 {
01204 QDomElement e = n.toElement();
01205 if (!e.isNull())
01206 {
01207 if (e.tagName() == "background")
01208 {
01209 parseBackground(dir, e);
01210 setbackground = true;
01211 }
01212 else if (e.tagName() == "genericbutton")
01213 {
01214 parseButtonDefinition(dir, e);
01215 setbuttondef = true;
01216 }
01217 else if (e.tagName() == "logo")
01218 {
01219 parseLogo(dir, e);
01220 }
01221 else if (e.tagName() == "buttondef")
01222 {
01223 parseButton(dir, e);
01224 }
01225 else if (e.tagName() == "titles")
01226 {
01227 parseTitle(dir, e);
01228 }
01229 else if (e.tagName() == "uparrow")
01230 {
01231 parseArrow(dir, e, true);
01232 }
01233 else if (e.tagName() == "downarrow")
01234 {
01235 parseArrow(dir, e, false);
01236 }
01237 else if (e.tagName() == "font")
01238 {
01239 if (parseFonts)
01240 {
01241 MythFontProperties *font;
01242 font = MythFontProperties::ParseFromXml(e, true);
01243 delete font;
01244 }
01245 }
01246 else
01247 {
01248 VERBOSE(VB_GENERAL, QString("MythThemedMenuPrivate: Unknown "
01249 "element %1").arg(e.tagName()));
01250 }
01251 }
01252 n = n.nextSibling();
01253 }
01254
01255 if (!setbackground)
01256 {
01257 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: Missing background element");
01258 return false;
01259 }
01260
01261 if (!setbuttondef)
01262 {
01263 VERBOSE(VB_IMPORTANT,
01264 "MythThemedMenuPrivate: Missing genericbutton definition");
01265 return false;
01266 }
01267
01268 parseFonts = false;
01269 loaded = true;
01270 return true;
01271 }
01272
01274
01281 MythThemedMenuPrivate::MythThemedMenuPrivate(MythThemedMenu *lparent,
01282 const char *cdir,
01283 MythThemedMenuState *lstate)
01284 {
01285 if (!lstate)
01286 {
01287 m_state = new MythThemedMenuState();
01288 allocedstate = true;
01289 }
01290 else
01291 {
01292 m_state = lstate;
01293 allocedstate = false;
01294 }
01295
01296 parent = lparent;
01297 ignorekeys = false;
01298 wantpop = false;
01299 exitModifier = -1;
01300
01301 m_state->themeDir = cdir;
01302 }
01303
01304 MythThemedMenuPrivate::~MythThemedMenuPrivate()
01305 {
01306 if (allocedstate)
01307 delete m_state;
01308 }
01309
01315 void MythThemedMenuPrivate::parseThemeButton(QDomElement &element)
01316 {
01317 QString type = "";
01318 QString text = "";
01319 QStringList action;
01320 QString alttext = "";
01321
01322 bool addit = true;
01323
01324 for (QDomNode child = element.firstChild(); !child.isNull();
01325 child = child.nextSibling())
01326 {
01327 QDomElement info = child.toElement();
01328 if (!info.isNull())
01329 {
01330 if (info.tagName() == "type")
01331 {
01332 type = getFirstText(info);
01333 }
01334 else if (info.tagName() == "text")
01335 {
01336 if ((text.isNull() || text.isEmpty()) &&
01337 info.attribute("lang","") == "")
01338 {
01339 text = getFirstText(info);
01340 }
01341 else if (info.attribute("lang","").lower() ==
01342 gContext->GetLanguageAndVariant())
01343 {
01344 text = getFirstText(info);
01345 }
01346 else if (info.attribute("lang","").lower() ==
01347 gContext->GetLanguage())
01348 {
01349 text = getFirstText(info);
01350 }
01351 }
01352 else if (info.tagName() == "alttext")
01353 {
01354 if ((alttext.isNull() || alttext.isEmpty()) &&
01355 info.attribute("lang","") == "")
01356 {
01357 alttext = getFirstText(info);
01358 }
01359 else if (info.attribute("lang","").lower() ==
01360 gContext->GetLanguageAndVariant())
01361 {
01362 alttext = getFirstText(info);
01363 }
01364 else if (info.attribute("lang","").lower() ==
01365 gContext->GetLanguage())
01366 {
01367 alttext = getFirstText(info);
01368 }
01369 }
01370 else if (info.tagName() == "action")
01371 {
01372 action += getFirstText(info);
01373 }
01374 else if (info.tagName() == "depends")
01375 {
01376 addit = findDepends(getFirstText(info));
01377 }
01378 else if (info.tagName() == "dependssetting")
01379 {
01380 addit = gContext->GetNumSetting(getFirstText(info));
01381 }
01382 else if (info.tagName() == "dependjumppoint")
01383 {
01384 addit = GetMythMainWindow()->DestinationExists(getFirstText(info));
01385 }
01386 else
01387 {
01388 VERBOSE(VB_GENERAL, QString("MythThemedMenuPrivate: Unknown tag %1 "
01389 "in button").arg(info.tagName()));
01390 }
01391 }
01392 }
01393
01394 if (text == "")
01395 {
01396 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: Missing 'text' in button");
01397 return;
01398 }
01399
01400 if (action.empty())
01401 {
01402 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: Missing 'action' in button");
01403 return;
01404 }
01405
01406 if (addit)
01407 addButton(type, text, alttext, action);
01408 }
01409
01423 bool MythThemedMenuPrivate::parseMenu(const QString &menuname)
01424 {
01425 QString filename = findMenuFile(menuname);
01426
01427 QDomDocument doc;
01428 QFile f(filename);
01429
01430 if (!f.open(IO_ReadOnly))
01431 {
01432 VERBOSE(VB_IMPORTANT, QString("MythThemedMenuPrivate: Couldn't read "
01433 "menu file %1").arg(menuname));
01434 if (menuname == "mainmenu.xml" )
01435 {
01436 return false;
01437 }
01438 else
01439 {
01440 parent->GetScreenStack()->PopScreen();
01441 #if 0
01442 MythPopupBox::showOkPopup(gContext->GetMainWindow(), QObject::tr("No Menu File"),
01443 QObject::tr(QString("Myth could not locate the menu file %1.\n\n"
01444 "We will now return to the main menu.").arg(menuname)));
01445 #endif
01446 return false;
01447 }
01448
01449
01450 }
01451
01452 QString errorMsg;
01453 int errorLine = 0;
01454 int errorColumn = 0;
01455
01456 if (!doc.setContent(&f, false, &errorMsg, &errorLine, &errorColumn))
01457 {
01458 VERBOSE(VB_IMPORTANT,
01459 QString("Error parsing: %1\nat line: %2 column: %3 msg: %4").
01460 arg(filename).arg(errorLine).arg(errorColumn).arg(errorMsg));
01461 f.close();
01462
01463 if (menuname == "mainmenu.xml" )
01464 {
01465 return false;
01466 }
01467
01468 parent->GetScreenStack()->PopScreen();
01469 #if 0
01470 MythPopupBox::showOkPopup(gContext->GetMainWindow(),
01471 QObject::tr("Bad Menu File"),
01472 QObject::tr(QString("The menu file %1 is "
01473 "incomplete.\n\nWe will now "
01474 "return to the main menu.")
01475 .arg(menuname)));
01476 #endif
01477 return false;
01478 }
01479
01480 f.close();
01481
01482 buttonList.clear();
01483 buttonRows.clear();
01484
01485 SetupBackground();
01486
01487 QDomElement docElem = doc.documentElement();
01488
01489 menumode = docElem.attribute("name", "");
01490
01491 QDomNode n = docElem.firstChild();
01492 while (!n.isNull())
01493 {
01494 QDomElement e = n.toElement();
01495 if (!e.isNull())
01496 {
01497 if (e.tagName() == "button")
01498 {
01499 parseThemeButton(e);
01500 }
01501 else
01502 {
01503 VERBOSE(VB_IMPORTANT, QString("MythThemedMenuPrivate: Unknown "
01504 "element %1").arg(e.tagName()));
01505 return false;
01506 }
01507 }
01508 n = n.nextSibling();
01509 }
01510
01511 if (buttonList.size() == 0)
01512 {
01513 VERBOSE(VB_IMPORTANT, QString("MythThemedMenuPrivate: No buttons "
01514 "for menu %1").arg(menuname));
01515 return false;
01516 }
01517
01518 if (!layoutButtons())
01519 return false;
01520 activebutton = NULL;
01521 positionButtons(true);
01522
01523 SetupUITypes();
01524
01525 if (LCD::Get())
01526 {
01527 titleText = "MYTH-";
01528 titleText += menumode;
01529 }
01530
01531 selection = "";
01532 return true;
01533 }
01534
01536 void MythThemedMenuPrivate::SetupBackground(void)
01537 {
01538
01539 if (m_state->buttonBackground)
01540 {
01541 MythUIImage *buttonBackground;
01542 buttonBackground = new MythUIImage(parent, "menu button background");
01543 buttonBackground->SetImage(m_state->buttonBackground);
01544 buttonBackground->SetPosition(m_state->buttonArea.topLeft());
01545 }
01546
01547 }
01548
01550 void MythThemedMenuPrivate::SetupUITypes(void)
01551 {
01552 if (m_state->titleIcons.contains(menumode))
01553 {
01554 MythUIImage *curTitle;
01555 curTitle = new MythUIImage(parent, "menu title image");
01556 curTitle->SetImage(m_state->titleIcons[menumode]);
01557 curTitle->SetPosition(m_state->titlePos);
01558 }
01559
01560 if (m_state->logo)
01561 {
01562 MythUIImage *logo;
01563 logo = new MythUIImage(parent, "menu logo");
01564 logo->SetImage(m_state->logo);
01565 logo->SetPosition(m_state->logoRect.topLeft());
01566 }
01567
01568 watermark = new MythUIStateType(parent, "menu watermarks");
01569 watermark->SetArea(m_state->watermarkRect);
01570 watermark->SetShowEmpty(true);
01571 QMap<QString, ButtonIcon>::Iterator it = m_state->allButtonIcons.begin();
01572 for (; it != m_state->allButtonIcons.end(); ++it)
01573 {
01574 ButtonIcon *icon = &(it.data());
01575 if (icon->watermark)
01576 watermark->AddImage(icon->name, icon->watermark);
01577 }
01578
01579 watermark->DisplayState(activebutton->type);
01580
01581 uparrow = new MythUIImage(parent, "menu up arrow");
01582 if (m_state->uparrow)
01583 {
01584 uparrow->SetArea(m_state->uparrowRect);
01585 uparrow->SetImage(m_state->uparrow);
01586 }
01587 uparrow->SetVisible(false);
01588 uparrow->SetCanTakeFocus(true);
01589
01590 downarrow = new MythUIImage(parent, "menu down arrow");
01591 if (m_state->downarrow)
01592 {
01593 downarrow->SetArea(m_state->downarrowRect);
01594 downarrow->SetImage(m_state->downarrow);
01595 }
01596 downarrow->SetVisible(false);
01597 downarrow->SetCanTakeFocus(true);
01598
01599 checkScrollArrows();
01600 }
01601
01602 void MythThemedMenuPrivate::updateLCD(void)
01603 {
01604 LCD *lcddev = LCD::Get();
01605 if (lcddev == NULL)
01606 return;
01607
01608
01609 QPtrList<LCDMenuItem> menuItems;
01610 menuItems.setAutoDelete(true);
01611 bool selected;
01612
01613 for (int r = 0; r < (int)buttonRows.size(); r++)
01614 {
01615 if (r == currentrow)
01616 selected = true;
01617 else
01618 selected = false;
01619
01620 if (currentcolumn < buttonRows[r].numitems)
01621 menuItems.append(new LCDMenuItem(selected, NOTCHECKABLE,
01622 buttonRows[r].buttons[currentcolumn]->message));
01623 }
01624
01625 if (!menuItems.isEmpty())
01626 lcddev->switchToMenu(&menuItems, titleText);
01627 }
01628
01638 void MythThemedMenuPrivate::addButton(const QString &type, const QString &text,
01639 const QString &alttext,
01640 const QStringList &action)
01641 {
01642 ThemedButton *newbutton = new ThemedButton(parent, type.ascii());
01643
01644 newbutton->type = type;
01645 newbutton->action = action;
01646 newbutton->message = text;
01647
01648 newbutton->SetCanTakeFocus(true);
01649 newbutton->background = new MythUIStateType(newbutton, "button background");
01650 if (m_state->buttonnormal)
01651 newbutton->background->AddImage(MythUIStateType::None,
01652 m_state->buttonnormal);
01653 if (m_state->buttonactive)
01654 newbutton->background->AddImage(MythUIStateType::Full,
01655 m_state->buttonactive);
01656 newbutton->background->DisplayState(MythUIStateType::None);
01657
01658 newbutton->SetArea(QRect(0, 0, m_state->buttonnormal->width(),
01659 m_state->buttonnormal->height()));
01660
01661 newbutton->icon = NULL;
01662 ButtonIcon *buttonicon = m_state->getButtonIcon(type);
01663 if (buttonicon)
01664 {
01665 newbutton->icon = new MythUIStateType(newbutton, "button icon");
01666 newbutton->icon->SetPosition(buttonicon->offset);
01667 if (buttonicon->icon)
01668 newbutton->icon->AddImage(MythUIStateType::None,
01669 buttonicon->icon);
01670 if (buttonicon->activeicon)
01671 newbutton->icon->AddImage(MythUIStateType::Full,
01672 buttonicon->activeicon);
01673 newbutton->icon->DisplayState(MythUIStateType::None);
01674 }
01675
01676 newbutton->text = new MythUIStateType(newbutton, "button text state");
01677 {
01678 QString msg = text;
01679 QRect buttonTextRect = m_state->normalAttributes.textRect;
01680 QRect testBoundRect = buttonTextRect;
01681 testBoundRect.setWidth(testBoundRect.width() + 40);
01682
01683 QFontMetrics tmp(m_state->normalAttributes.font.face());
01684 QRect testBound = tmp.boundingRect(testBoundRect.x(), testBoundRect.y(),
01685 testBoundRect.width(),
01686 testBoundRect.height(),
01687 m_state->normalAttributes.textflags,
01688 text);
01689 if (testBound.height() > buttonTextRect.height() && alttext != "")
01690 msg = alttext;
01691
01692 MythUIText *txt = new MythUIText(msg, m_state->normalAttributes.font,
01693 buttonTextRect, buttonTextRect,
01694 newbutton->text, "button normal text");
01695 txt->SetJustification(m_state->normalAttributes.textflags);
01696 newbutton->text->AddObject(MythUIStateType::None, txt);
01697 }
01698 {
01699 QString msg = text;
01700 QRect buttonTextRect = m_state->activeAttributes.textRect;
01701 QRect testBoundRect = buttonTextRect;
01702 testBoundRect.setWidth(testBoundRect.width() + 40);
01703
01704 QFontMetrics tmp(m_state->activeAttributes.font.face());
01705 QRect testBound = tmp.boundingRect(testBoundRect.x(), testBoundRect.y(),
01706 testBoundRect.width(),
01707 testBoundRect.height(),
01708 m_state->activeAttributes.textflags,
01709 text);
01710 if (testBound.height() > buttonTextRect.height() && alttext != "")
01711 msg = alttext;
01712
01713 MythUIText *txt = new MythUIText(msg, m_state->activeAttributes.font,
01714 buttonTextRect, buttonTextRect,
01715 newbutton->text, "button normal text");
01716 txt->SetJustification(m_state->activeAttributes.textflags);
01717 newbutton->text->AddObject(MythUIStateType::Full, txt);
01718 }
01719 newbutton->text->DisplayState(MythUIStateType::None);
01720
01721 newbutton->SetVisible(false);
01722
01723 buttonList.push_back(newbutton);
01724 }
01725
01729 bool MythThemedMenuPrivate::layoutButtons(void)
01730 {
01731 int numbuttons = buttonList.size();
01732
01733 columns = m_state->buttonArea.width() / m_state->buttonnormal->width();
01734 columns = columns > m_state->maxColumns ? m_state->maxColumns : columns;
01735
01736 maxrows = m_state->buttonArea.height() / m_state->buttonnormal->height();
01737
01738 if (maxrows < 1)
01739 {
01740 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: Must have "
01741 "room for at least 1 row of buttons");
01742 return false;
01743 }
01744
01745 if (columns < 1)
01746 {
01747 VERBOSE(VB_IMPORTANT, "MythThemedMenuPrivate: Must have "
01748 "room for at least 1 column of buttons");
01749 return false;
01750 }
01751
01752 if (m_state->balancerows)
01753 {
01754
01755 if (numbuttons <= 4)
01756 {
01757 if (columns > 2)
01758 columns = 2;
01759 }
01760 else
01761 {
01762 if (columns > 3)
01763 columns = 3;
01764 }
01765 }
01766
01767
01768 if (columns * maxrows > m_state->visiblerowlimit)
01769 {
01770 maxrows = m_state->visiblerowlimit / columns;
01771 }
01772
01773 vector<ThemedButton *>::iterator iter = buttonList.begin();
01774
01775 int rows = numbuttons / columns;
01776 rows++;
01777
01778 visiblerows = 0;
01779
01780 for (int i = 0; i < rows; i++)
01781 {
01782 MenuRow newrow;
01783 newrow.numitems = 0;
01784
01785 for (int j = 0; j < columns && iter != buttonList.end();
01786 j++, iter++)
01787 {
01788 if (columns == 3 && j == 1 && m_state->allowreorder)
01789 newrow.buttons.insert(newrow.buttons.begin(), *iter);
01790 else
01791 newrow.buttons.push_back(*iter);
01792 newrow.numitems++;
01793 }
01794
01795 if (i < maxrows && newrow.numitems > 0)
01796 {
01797 newrow.visible = true;
01798 visiblerows++;
01799 }
01800 else
01801 newrow.visible = false;
01802
01803 if (newrow.numitems > 0)
01804 buttonRows.push_back(newrow);
01805 }
01806
01807 return true;
01808 }
01809
01815 void MythThemedMenuPrivate::positionButtons(bool resetpos)
01816 {
01817 QRect buttonArea = m_state->buttonArea;
01818 int buttonHeight = m_state->buttonnormal->height();
01819 int buttonWidth = m_state->buttonnormal->width();
01820
01821 int rows = visiblerows;
01822 int yspacing = (buttonArea.height() - buttonHeight * rows) /
01823 (rows + 1);
01824 int ystart = 0;
01825
01826 if (!m_state->spreadbuttons)
01827 {
01828 yspacing = 0;
01829 if (m_state->buttoncenter)
01830 ystart = (buttonArea.height() - buttonHeight * rows) / 2;
01831 }
01832
01833 int row = 1;
01834
01835 vector<MenuRow>::iterator menuiter = buttonRows.begin();
01836 for (; menuiter != buttonRows.end(); menuiter++)
01837 {
01838 if (!(*menuiter).visible)
01839 {
01840 vector<ThemedButton *>::iterator biter;
01841 biter = (*menuiter).buttons.begin();
01842 for (; biter != (*menuiter).buttons.end(); biter++)
01843 {
01844 ThemedButton *tbutton = (*biter);
01845 tbutton->SetVisible(false);
01846 tbutton->SetActive(false);
01847 }
01848 continue;
01849 }
01850
01851 int ypos = yspacing * row + (buttonHeight * (row - 1));
01852 ypos += buttonArea.y() + ystart;
01853
01854 int xspacing = (buttonArea.width() - buttonWidth *
01855 (*menuiter).numitems) / ((*menuiter).numitems + 1);
01856 int col = 1;
01857 vector<ThemedButton *>::iterator biter = (*menuiter).buttons.begin();
01858 for (; biter != (*menuiter).buttons.end(); biter++)
01859 {
01860 int xpos = xspacing * col + (buttonWidth * (col - 1));
01861 xpos = (m_state->maxColumns == 1) ? 0 : xpos;
01862 xpos += buttonArea.x();
01863
01864 ThemedButton *tbutton = (*biter);
01865
01866 tbutton->SetVisible(true);
01867 tbutton->SetActive(false);
01868 tbutton->row = row;
01869 tbutton->col = col;
01870 tbutton->SetPosition(xpos, ypos);
01871
01872 col++;
01873 }
01874
01875 row++;
01876 }
01877
01878 if (resetpos)
01879 {
01880 ThemedButton *old = activebutton;
01881 activebutton = (*(buttonList.begin()));
01882
01883 if (activebutton != old && old)
01884 old->SetActive(false);
01885
01886 activebutton->SetActive(true);
01887
01888 currentrow = activebutton->row - 1;
01889 currentcolumn = activebutton->col - 1;
01890 }
01891 }
01892
01893 bool MythThemedMenuPrivate::makeRowVisible(int newrow, int oldrow)
01894 {
01895 if (buttonRows[newrow].visible)
01896 return true;
01897
01898 if (newrow > oldrow)
01899 {
01900 int row;
01901 for (row = newrow; row >= 0; row--)
01902 {
01903 if (row > newrow - visiblerows)
01904 buttonRows[row].visible = true;
01905 else
01906 buttonRows[row].visible = false;
01907 }
01908 }
01909 else
01910 {
01911 int row;
01912 for (row = newrow; row < (int)buttonRows.size(); row++)
01913 {
01914 if (row < newrow + visiblerows)
01915 buttonRows[row].visible = true;
01916 else
01917 buttonRows[row].visible = false;
01918 }
01919 }
01920
01921 positionButtons(false);
01922
01923 checkScrollArrows();
01924
01925 return true;
01926 }
01927
01929 void MythThemedMenuPrivate::checkScrollArrows(void)
01930 {
01931 bool needup = false;
01932 bool needdown = false;
01933
01934 if (!buttonRows.front().visible)
01935 needup = true;
01936 if (!buttonRows.back().visible)
01937 needdown = true;
01938
01939 uparrow->SetVisible(needup);
01940 downarrow->SetVisible(needdown);
01941 }
01942
01948 bool MythThemedMenuPrivate::ReloadTheme(void)
01949 {
01950 GetGlobalFontMap()->Clear();
01951 m_state->parseFonts = true;
01952
01953 buttonList.clear();
01954 buttonRows.clear();
01955
01956 parent->ReloadExitKey();
01957
01958 m_state->Reset();
01959
01960 parent->DeleteAllChildren();
01961
01962 QString themedir = gContext->GetThemeDir();
01963 bool ok = m_state->parseSettings(themedir, "theme.xml");
01964 if (!ok)
01965 return ok;
01966
01967 return parseMenu("mainmenu.xml");
01968 }
01969
01974 bool MythThemedMenuPrivate::keyPressHandler(QKeyEvent *e)
01975 {
01976 QStringList actions;
01977 GetMythMainWindow()->TranslateKeyPress("menu", e, actions);
01978
01979 return keyHandler(actions, e->state() == exitModifier);
01980 }
01981
01986 bool MythThemedMenuPrivate::keyHandler(QStringList &actions,
01987 bool fullexit)
01988 {
01989 ThemedButton *lastbutton = activebutton;
01990 int oldrow = currentrow;
01991 int oldcolumn = currentcolumn;
01992 bool handled = false;
01993
01994 for (unsigned int i = 0; i < actions.size() && !handled; i++)
01995 {
01996 QString action = actions[i];
01997 handled = true;
01998
01999 if (columns == 1)
02000 {
02001 if (action == "LEFT")
02002 action = "ESCAPE";
02003 else if (action == "RIGHT")
02004 action = "SELECT";
02005 }
02006
02007 if (action == "UP")
02008 {
02009 if (currentrow > 0)
02010 currentrow--;
02011 else if (columns == 1)
02012 currentrow = buttonRows.size() - 1;
02013
02014 if (currentcolumn >= buttonRows[currentrow].numitems)
02015 currentcolumn = buttonRows[currentrow].numitems - 1;
02016
02017 if (currentrow == oldrow && currentcolumn == oldcolumn)
02018 {
02019 handled = false;
02020 }
02021 }
02022 else if (action == "PAGEUP")
02023 {
02024 currentrow = max(currentrow - m_state->visiblerowlimit, 0);
02025
02026 if (currentcolumn >= buttonRows[currentrow].numitems)
02027 currentcolumn = buttonRows[currentrow].numitems - 1;
02028 }
02029 else if (action == "LEFT")
02030 {
02031 if (currentcolumn > 0)
02032 currentcolumn--;
02033 else
02034 currentcolumn = buttonRows[currentrow].numitems - 1;
02035 }
02036 else if (action == "DOWN")
02037 {
02038 if (currentrow < (int)buttonRows.size() - 1)
02039 currentrow++;
02040 else if (columns == 1)
02041 currentrow = 0;
02042
02043 if (currentcolumn >= buttonRows[currentrow].numitems)
02044 currentcolumn = buttonRows[currentrow].numitems - 1;
02045
02046 if (currentrow == oldrow && currentcolumn == oldcolumn)
02047 {
02048 handled = false;
02049 }
02050 }
02051 else if (action == "PAGEDOWN")
02052 {
02053 currentrow = min(currentrow + m_state->visiblerowlimit,
02054 (int)buttonRows.size() - 1);
02055
02056 if (currentcolumn >= buttonRows[currentrow].numitems)
02057 currentcolumn = buttonRows[currentrow].numitems - 1;
02058 }
02059 else if (action == "RIGHT")
02060 {
02061 if (currentcolumn < buttonRows[currentrow].numitems - 1)
02062 currentcolumn++;
02063 else
02064 currentcolumn = 0;
02065 }
02066 else if (action == "SELECT")
02067 {
02068 lastbutton = activebutton;
02069 activebutton = NULL;
02070
02071 QStringList::Iterator it = lastbutton->action.begin();
02072 for (; it != lastbutton->action.end(); it++)
02073 {
02074 if (handleAction(*it))
02075 break;
02076 }
02077
02078 lastbutton = NULL;
02079 }
02080 else if (action == "ESCAPE")
02081 {
02082 QString action = "UPMENU";
02083 if (!allocedstate)
02084 handleAction(action);
02085 else if (m_state->killable)
02086 {
02087 wantpop = true;
02088 if (m_state->callback != NULL)
02089 {
02090 QString sel = "EXITING_MENU";
02091 m_state->callback(m_state->callbackdata, sel);
02092 }
02093
02094 if (GetMythMainWindow()->GetMainStack()->TotalScreens() == 1)
02095 QApplication::exit();
02096 }
02097 else if (exitModifier >= 0 && fullexit &&
02098 GetMythMainWindow()->GetMainStack()->TotalScreens() == 1)
02099 {
02100 QApplication::exit();
02101 wantpop = true;
02102 }
02103 lastbutton = NULL;
02104 }
02105 else if (action == "EJECT")
02106 {
02107 myth_eject();
02108 }
02109 else
02110 handled = false;
02111 }
02112
02113 if (!handled)
02114 return false;
02115
02116 if (!buttonRows[currentrow].visible)
02117 {
02118 makeRowVisible(currentrow, oldrow);
02119 }
02120
02121 activebutton = buttonRows[currentrow].buttons[currentcolumn];
02122 watermark->DisplayState(activebutton->type);
02123
02124 if (lastbutton != activebutton && lastbutton && activebutton)
02125 {
02126 lastbutton->SetActive(false);
02127 activebutton->SetActive(true);
02128 }
02129
02130
02131 if (parent->GetScreenStack()->GetTopScreen() == (MythScreenType*) parent)
02132 updateLCD();
02133
02134 return true;
02135 }
02136
02143 bool MythThemedMenuPrivate::gestureEvent(MythUIType *origtype,
02144 MythGestureEvent *ge)
02145 {
02146 if (ge->gesture() == MythGestureEvent::Click)
02147 {
02148 if (origtype == uparrow)
02149 {
02150 QStringList action = "PAGEUP";
02151 keyHandler(action, false);
02152 }
02153 else if (origtype == downarrow)
02154 {
02155 QStringList action = "PAGEDOWN";
02156 keyHandler(action, false);
02157 }
02158 else
02159 {
02160 ThemedButton *button = dynamic_cast<ThemedButton*>(origtype);
02161 if (button)
02162 {
02163 ThemedButton *lastbutton = activebutton;
02164 activebutton = button;
02165
02166 if (LCD *lcddev = LCD::Get())
02167 lcddev->switchToTime();
02168
02169 QStringList::Iterator it = button->action.begin();
02170 for (; it != button->action.end(); it++)
02171 {
02172 if (handleAction(*it))
02173 break;
02174 }
02175
02176 watermark->DisplayState(activebutton->type);
02177
02178 if (lastbutton != activebutton && lastbutton && activebutton)
02179 {
02180 lastbutton->SetActive(false);
02181 activebutton->SetActive(true);
02182 }
02183 }
02184 }
02185
02186 return true;
02187 }
02188
02189 if (ge->gesture() == MythGestureEvent::Left)
02190 {
02191 QStringList action = "ESCAPE";
02192 keyHandler(action, true);
02193 return true;
02194 }
02195
02196 return false;
02197 }
02198
02204 QString MythThemedMenuPrivate::findMenuFile(const QString &menuname)
02205 {
02206 QString testdir = MythContext::GetConfDir() + "/" + menuname;
02207 QFile file(testdir);
02208 if (file.exists())
02209 return testdir;
02210
02211 testdir = gContext->GetMenuThemeDir() + "/" + menuname;
02212 file.setName(testdir);
02213 if (file.exists())
02214 return testdir;
02215
02216
02217 testdir = gContext->GetThemeDir() + "/" + menuname;
02218 file.setName(testdir);
02219 if (file.exists())
02220 return testdir;
02221
02222 testdir = gContext->GetShareDir() + menuname;
02223 file.setName(testdir);
02224 if (file.exists())
02225 return testdir;
02226
02227 testdir = "../mythfrontend/" + menuname;
02228 file.setName(testdir);
02229 if (file.exists())
02230 return testdir;
02231
02232 return "";
02233 }
02234
02240 bool MythThemedMenuPrivate::handleAction(const QString &action)
02241 {
02242 if (action.left(5) == "EXEC ")
02243 {
02244 QString rest = action.right(action.length() - 5);
02245 myth_system(rest);
02246
02247 return false;
02248 }
02249 else if (action.left(7) == "EXECTV ")
02250 {
02251 QString rest = action.right(action.length() - 7).stripWhiteSpace();
02252 QStringList strlist = QString("LOCK_TUNER");
02253 gContext->SendReceiveStringList(strlist);
02254 int cardid = strlist[0].toInt();
02255
02256 if (cardid >= 0)
02257 {
02258 rest = rest.sprintf(rest,
02259 (const char*)strlist[1],
02260 (const char*)strlist[2],
02261 (const char*)strlist[3]);
02262
02263 myth_system(rest);
02264
02265 strlist = QString("FREE_TUNER %1").arg(cardid);
02266 gContext->SendReceiveStringList(strlist);
02267 QString ret = strlist[0];
02268 }
02269 else
02270 {
02271 if (cardid == -2)
02272 VERBOSE(VB_IMPORTANT, QString("MythThemedMenuPrivate: Card %1 is "
02273 "already locked").arg(cardid));
02274
02275 #if 0
02276 DialogBox *error_dialog = new DialogBox(gContext->GetMainWindow(),
02277 "\n\nAll tuners are currently in use. If you want to watch "
02278 "TV, you can cancel one of the in-progress recordings from "
02279 "the delete menu");
02280 error_dialog->AddButton("OK");
02281 error_dialog->exec();
02282 delete error_dialog;
02283 #endif
02284 }
02285 }
02286 else if (action.left(5) == "MENU ")
02287 {
02288 QString rest = action.right(action.length() - 5);
02289
02290 if (rest == "main_settings.xml" &&
02291 gContext->GetNumSetting("SetupPinCodeRequired", 0) &&
02292 !checkPinCode("SetupPinCodeTime", "SetupPinCode", "Setup Pin:"))
02293 {
02294 return true;
02295 }
02296
02297 MythScreenStack *stack = parent->GetScreenStack();
02298
02299 MythThemedMenu *newmenu = new MythThemedMenu(m_state->themeDir.local8Bit(),
02300 rest, stack, rest,
02301 m_state->allowreorder,
02302 m_state);
02303 stack->AddScreen(newmenu);
02304 }
02305 else if (action.left(6) == "UPMENU")
02306 {
02307 wantpop = true;
02308 }
02309 else if (action.left(12) == "CONFIGPLUGIN")
02310 {
02311 QString rest = action.right(action.length() - 13);
02312 MythPluginManager *pmanager = gContext->getPluginManager();
02313 if (pmanager)
02314 pmanager->config_plugin(rest.stripWhiteSpace());
02315 }
02316 else if (action.left(6) == "PLUGIN")
02317 {
02318 QString rest = action.right(action.length() - 7);
02319 MythPluginManager *pmanager = gContext->getPluginManager();
02320 if (pmanager)
02321 pmanager->run_plugin(rest.stripWhiteSpace());
02322 }
02323 else if (action.left(8) == "SHUTDOWN")
02324 {
02325 if (allocedstate)
02326 {
02327 wantpop = true;
02328 }
02329 }
02330 else if (action.left(5) == "EJECT")
02331 {
02332 myth_eject();
02333 }
02334 else if (action.left(5) == "JUMP ")
02335 {
02336 QString rest = action.right(action.length() - 5);
02337 GetMythMainWindow()->JumpTo(rest, false);
02338 }
02339 else
02340 {
02341 selection = action;
02342 if (m_state->callback != NULL)
02343 m_state->callback(m_state->callbackdata, selection);
02344 }
02345
02346 return true;
02347 }
02348
02349
02350 bool MythThemedMenuPrivate::findDepends(const QString &fileList)
02351 {
02352 QStringList files = QStringList::split(" ", fileList);
02353 QString filename;
02354
02355 for ( QStringList::Iterator it = files.begin(); it != files.end(); ++it )
02356 {
02357 QString filename = findMenuFile(*it);
02358 if (filename != "" && filename.endsWith(".xml"))
02359 return true;
02360
02361 QString newname = gContext->FindPlugin(*it);
02362
02363 QFile checkFile(newname);
02364 if (checkFile.exists())
02365 return true;
02366 }
02367
02368 return false;
02369 }
02370
02379 bool MythThemedMenuPrivate::checkPinCode(const QString ×tamp_setting,
02380 const QString &password_setting,
02381 const QString &text)
02382 {
02383 QDateTime curr_time = QDateTime::currentDateTime();
02384 QString last_time_stamp = gContext->GetSetting(timestamp_setting);
02385 QString password = gContext->GetSetting(password_setting);
02386
02387 if (password.length() < 1)
02388 return true;
02389
02390 if (last_time_stamp.length() < 1)
02391 {
02392 VERBOSE(VB_IMPORTANT,
02393 "MythThemedMenuPrivate: Could not read password/pin time stamp.\n"
02394 "This is only an issue if it happens repeatedly.");
02395 }
02396 else
02397 {
02398 QDateTime last_time = QDateTime::fromString(last_time_stamp,
02399 Qt::TextDate);
02400 if (last_time.secsTo(curr_time) < 120)
02401 {
02402 last_time_stamp = curr_time.toString(Qt::TextDate);
02403 gContext->SetSetting(timestamp_setting, last_time_stamp);
02404 gContext->SaveSetting(timestamp_setting, last_time_stamp);
02405 return true;
02406 }
02407 }
02408
02409 if (password.length() > 0)
02410 {
02411 bool ok = false;
02412 MythPasswordDialog *pwd = new MythPasswordDialog(text, &ok, password,
02413 gContext->GetMainWindow());
02414 pwd->exec();
02415 pwd->deleteLater();
02416
02417 if (ok)
02418 {
02419 last_time_stamp = curr_time.toString(Qt::TextDate);
02420 gContext->SetSetting(timestamp_setting, last_time_stamp);
02421 gContext->SaveSetting(timestamp_setting, last_time_stamp);
02422 return true;
02423 }
02424 }
02425 else
02426 {
02427 return true;
02428 }
02429
02430 return false;
02431 }
02432
02434
02444 MythThemedMenu::MythThemedMenu(const char *cdir, const char *menufile,
02445 MythScreenStack *parent, const char *name,
02446 bool allowreorder, MythThemedMenuState *state)
02447 : MythScreenType(parent, name)
02448 {
02449 d = new MythThemedMenuPrivate(this, cdir, state);
02450 d->m_state->allowreorder = allowreorder;
02451
02452 Init(cdir, menufile);
02453 }
02454
02466 void MythThemedMenu::Init(const char *cdir, const char *menufile)
02467 {
02468 QString dir = QString(cdir) + "/";
02469 QString filename = dir + "theme.xml";
02470
02471 d->foundtheme = true;
02472 QFile filetest(filename);
02473 if (!filetest.exists())
02474 {
02475 d->foundtheme = false;
02476 return;
02477 }
02478
02479 ReloadExitKey();
02480
02481 if (!d->m_state->loaded)
02482 d->foundtheme = d->m_state->parseSettings(dir, "theme.xml");
02483
02484 if (d->foundtheme)
02485 d->parseMenu(menufile);
02486 }
02487
02488 MythThemedMenu::~MythThemedMenu(void)
02489 {
02490 if (d)
02491 delete d;
02492 }
02493
02496 bool MythThemedMenu::foundTheme(void)
02497 {
02498 return d->foundtheme;
02499 }
02500
02502 void MythThemedMenu::setCallback(void (*lcallback)(void *, QString &), void *data)
02503 {
02504 d->m_state->callback = lcallback;
02505 d->m_state->callbackdata = data;
02506 }
02507
02508 void MythThemedMenu::setKillable(void)
02509 {
02510 d->m_state->killable = true;
02511 }
02512
02513 QString MythThemedMenu::getSelection(void)
02514 {
02515 return d->selection;
02516 }
02517
02521 void MythThemedMenu::ReloadExitKey(void)
02522 {
02523 int allowsd = gContext->GetNumSetting("AllowQuitShutdown");
02524
02525 if (allowsd == 1)
02526 d->exitModifier = Qt::ControlButton;
02527 else if (allowsd == 2)
02528 d->exitModifier = Qt::MetaButton;
02529 else if (allowsd == 3)
02530 d->exitModifier = Qt::AltButton;
02531 else if (allowsd == 4)
02532 d->exitModifier = 0;
02533 else
02534 d->exitModifier = -1;
02535 }
02536
02542 void MythThemedMenu::ReloadTheme(void)
02543 {
02544 if (!d->ReloadTheme())
02545 d->foundtheme = false;
02546 }
02547
02553 bool MythThemedMenu::keyPressEvent(QKeyEvent *e)
02554 {
02555 if (d->ignorekeys)
02556 return false;
02557
02558 d->ignorekeys = true;
02559
02560 bool ret = true;
02561 if (!d->keyPressHandler(e))
02562 {
02563 ret = MythScreenType::keyPressEvent(e);
02564 }
02565
02566 d->ignorekeys = false;
02567
02568
02569 if (d->wantpop)
02570 m_ScreenStack->PopScreen();
02571
02572 return ret;
02573 }
02574
02580 void MythThemedMenu::gestureEvent(MythUIType *origtype, MythGestureEvent *ge)
02581 {
02582 if (d->gestureEvent(origtype, ge))
02583 {
02584 if (d->wantpop)
02585 m_ScreenStack->PopScreen();
02586 }
02587 else
02588 MythScreenType::gestureEvent(origtype, ge);
02589 }
02590
02591 void MythThemedMenu::aboutToShow()
02592 {
02593 MythScreenType::aboutToShow();
02594 d->updateLCD();
02595 }