From 12bde373b1d8c30d2747a5fb07ff109c57d8a949 Mon Sep 17 00:00:00 2001 From: Celtic Minstrel Date: Thu, 20 Feb 2020 23:41:40 -0500 Subject: [PATCH] Refactor dialog parsing to reduce code duplication - Refactor dialog format parameter management to use a single protected control function - Format parameters "frame" and "frame style" merged - Colour is now a first-class format parameter (though it still has a separate setter) --- src/dialogxml/dialogs/dialog.hpp | 10 +- src/dialogxml/widgets/button.cpp | 462 ++++++++------------------- src/dialogxml/widgets/button.hpp | 24 +- src/dialogxml/widgets/control.cpp | 187 ++++++++++- src/dialogxml/widgets/control.hpp | 73 ++++- src/dialogxml/widgets/field.cpp | 103 +++--- src/dialogxml/widgets/field.hpp | 8 +- src/dialogxml/widgets/message.cpp | 196 ++++-------- src/dialogxml/widgets/message.hpp | 10 +- src/dialogxml/widgets/pict.cpp | 273 +++++++--------- src/dialogxml/widgets/pict.hpp | 12 +- src/dialogxml/widgets/scrollbar.cpp | 94 +++--- src/dialogxml/widgets/scrollbar.hpp | 4 +- src/dialogxml/widgets/scrollpane.cpp | 165 ++++------ src/dialogxml/widgets/scrollpane.hpp | 9 +- src/dialogxml/widgets/stack.cpp | 140 ++++---- src/dialogxml/widgets/stack.hpp | 9 +- src/fileio/fileio_scen.cpp | 2 +- src/game/boe.party.cpp | 2 +- src/game/boe.town.cpp | 2 +- 20 files changed, 805 insertions(+), 980 deletions(-) diff --git a/src/dialogxml/dialogs/dialog.hpp b/src/dialogxml/dialogs/dialog.hpp index e21ad60c..6d549189 100644 --- a/src/dialogxml/dialogs/dialog.hpp +++ b/src/dialogxml/dialogs/dialog.hpp @@ -226,17 +226,17 @@ private: friend class cControl; }; -/// Thrown when an invalid element is found while parsing an XML dialog definition. +/// Thrown when an invalid node (element or text/cdata) is found while parsing an XML dialog definition. class xBadNode : public std::exception { std::string type, dlg; int row, col; mutable const char* msg; public: /// Construct a new exception. - /// @param t The tag name of the invalid element. - /// @param r The line number of the element in the source. - /// @param c The column number of the element in the source. - /// @param dlg The name of the file in which the element occurred. + /// @param t The tag name of the invalid element, or empty if the invalid node was a text node. + /// @param r The line number of the node in the source. + /// @param c The column number of the node in the source. + /// @param dlg The name of the file in which the node occurred. xBadNode(std::string t, int r, int c, std::string dlg) throw(); ~xBadNode() throw(); /// @return The error message. diff --git a/src/dialogxml/widgets/button.cpp b/src/dialogxml/widgets/button.cpp index 3a8c35f3..677e4365 100644 --- a/src/dialogxml/widgets/button.cpp +++ b/src/dialogxml/widgets/button.cpp @@ -90,161 +90,89 @@ void cButton::draw(){ } } -void cButton::setFormat(eFormat prop, short val){ - if(prop == TXT_WRAP) wrapLabel = val; - else if(prop == TXT_FRAMESTYLE) frameStyle = eFrameStyle(val); - else throw xUnsupportedProp(prop); -} - -short cButton::getFormat(eFormat prop){ - if(prop == TXT_WRAP) return wrapLabel; - else if(prop == TXT_FRAMESTYLE) return frameStyle; - else throw xUnsupportedProp(prop); -} - -void cButton::setColour(sf::Color clr) { - textClr = clr; -} - -sf::Color cButton::getColour() { - return textClr; -} - -std::string cButton::parse(ticpp::Element& who, std::string fname) { - using namespace ticpp; - Iterator attr; - Iterator node; - std::string name, id; - int width = 0, height = 0; - bool foundType = false, foundTop = false, foundLeft = false; // required attributes - rectangle frame; - for(attr = attr.begin(&who); attr != attr.end(); attr++){ - attr->GetName(&name); - if(name == "name") - attr->GetValue(&id); - else if(name == "wrap"){ - std::string val; - attr->GetValue(&val); - if(val == "true") setFormat(TXT_WRAP, true); - }else if(name == "type"){ - std::string val; - foundType = true; - attr->GetValue(&val); - if(val == "small") - setBtnType(BTN_SM); - else if(val == "regular") - setBtnType(BTN_REG); - else if(val == "large") - setBtnType(BTN_LG); - else if(val == "help") - setBtnType(BTN_HELP); - else if(val == "left") - setBtnType(BTN_LEFT); - else if(val == "right") - setBtnType(BTN_RIGHT); - else if(val == "up") - setBtnType(BTN_UP); - else if(val == "down") - setBtnType(BTN_DOWN); - else if(val == "tiny") - setBtnType(BTN_TINY); - else if(val == "done") - setBtnType(BTN_DONE); - else if(val == "tall") - setBtnType(BTN_TALL); - else if(val == "trait") - setBtnType(BTN_TRAIT); - else if(val == "push") - setBtnType(BTN_PUSH); - }else if(name == "color" || name == "colour"){ - std::string val; - attr->GetValue(&val); - sf::Color clr; - try{ - clr = parseColor(val); - }catch(int){ - throw xBadVal("button",name,val,attr->Row(),attr->Column(),fname); +bool cButton::manageFormat(eFormat prop, bool set, boost::any* val) { + switch(prop) { + case TXT_FRAME: + if(val) { + if(set) frameStyle = boost::any_cast(*val); + else *val = frameStyle; } - setColour(clr); - }else if(name == "def-key"){ - std::string val; - attr->GetValue(&val); - try{ - attachKey(parseKey(val)); - }catch(int){ - throw xBadVal("button",name,val,attr->Row(),attr->Column(),fname); + break; + case TXT_WRAP: + if(val) { + if(set) wrapLabel = boost::any_cast(*val); + else *val = wrapLabel; } -// }else if(name == "fromlist"){ -// attr->GetValue(&fromList); - }else if(name == "top"){ - attr->GetValue(&frame.top), foundTop = true; - }else if(name == "left"){ - attr->GetValue(&frame.left), foundLeft = true; - }else if(name == "width"){ - attr->GetValue(&width); - }else if(name == "height"){ - attr->GetValue(&height); - }else throw xBadAttr("button",name,attr->Row(),attr->Column(),fname); + break; + case TXT_COLOUR: + if(val) { + if(set) textClr = boost::any_cast(*val); + else *val = textClr; + } + break; + default: return false; } - if(parent->getBg() == cDialog::BG_DARK && (getBtnType() == BTN_TINY || getBtnType() == BTN_PUSH)) - setColour(sf::Color::White); - if(!foundType) throw xMissingAttr("button","type",who.Row(),who.Column(),fname); - if(!foundTop) throw xMissingAttr("button","top",who.Row(),who.Column(),fname); - if(!foundLeft) throw xMissingAttr("button","left",who.Row(),who.Column(),fname); - switch(getBtnType()){ - case BTN_SM: - frame.right = frame.left + 23; - frame.bottom = frame.top + 23; - break; - case BTN_LG: - frame.right = frame.left + 102; - frame.bottom = frame.top + 23; - break; - case BTN_HELP: - frame.right = frame.left + 16; - frame.bottom = frame.top + 13; - break; - case BTN_TINY: - case BTN_LED: // this should never happen - frame.right = frame.left + 14; - frame.bottom = frame.top + 10; - break; - case BTN_TALL: - case BTN_TRAIT: - frame.right = frame.left + 63; - frame.bottom = frame.top + 40; - break; - case BTN_PUSH: - frame.right = frame.left + 30; - frame.bottom = frame.top + 30; - break; - default: - frame.right = frame.left + 63; - frame.bottom = frame.top + 23; - } - if(width > 0) - frame.right = frame.left + width; - if(height > 0) - frame.bottom = frame.top + height; - setBounds(frame); - std::string content; - for(node = node.begin(&who); node != node.end(); node++){ - std::string val; - int type = node->Type(); - node->GetValue(&val); - if(type == TiXmlNode::ELEMENT && val == "key"){ - // TODO: There's surely a better way to do this - if(content.length() > 0) throw xBadVal("button",xBadVal::CONTENT,content + val,node->Row(),node->Column(),fname); -// labelWithKey = true; - }else if(type == TiXmlNode::TEXT) - content += dlogStringFilter(val); - else if(type != TiXmlNode::COMMENT) { - val = '<' + val + '>'; - throw xBadVal("button",xBadVal::CONTENT,val,node->Row(),node->Column(),fname); + return true; +} + +bool cButton::parseAttribute(ticpp::Attribute& attr, std::string tagName, std::string fname) { + std::string name = attr.Name(); + if(name == "type") { + std::string val = attr.Value(); + if(val == "small") setBtnType(BTN_SM); + else if(val == "regular") setBtnType(BTN_REG); + else if(val == "large") setBtnType(BTN_LG); + else if(val == "help") setBtnType(BTN_HELP); + else if(val == "left") setBtnType(BTN_LEFT); + else if(val == "right") setBtnType(BTN_RIGHT); + else if(val == "up") setBtnType(BTN_UP); + else if(val == "down") setBtnType(BTN_DOWN); + else if(val == "tiny") setBtnType(BTN_DONE); + else if(val == "done") setBtnType(BTN_DONE); + else if(val == "tall") setBtnType(BTN_TALL); + else if(val == "trait") setBtnType(BTN_TRAIT); + else if(val == "push") setBtnType(BTN_PUSH); + else throw xBadVal(tagName, name, val, attr.Row(), attr.Column(), fname); + return true; + } else if(name == "def-key") { + std::string val = attr.Value(); + try { + attachKey(parseKey(val)); + } catch(int) { + throw xBadVal(tagName, name, val, attr.Row(), attr.Column(), fname); } + return true; } - setText(content); - return id; + return cControl::parseAttribute(attr, tagName, fname); +} + +bool cButton::parseContent(ticpp::Node& content, int n, std::string tagName, std::string fname, std::string& text) { + if(content.Type() == TiXmlNode::TEXT) { + text += dlogStringFilter(content.Value()); + return true; + } else if(content.Value() == "key") { + // TODO: There's surely a better way to do this + if(text.length() > 0) throw xBadVal("button", xBadVal::CONTENT, text + "", content.Row(), content.Column(), fname); + labelWithKey = true; + return true; + } + return cControl::parseContent(content, n, tagName, fname, text); +} + +void cButton::validatePostParse(ticpp::Element& elem, std::string fname, const std::set& attrs, const std::multiset& elems) { + cControl::validatePostParse(elem, fname, attrs, elems); + if(getType() == CTRL_BTN && !attrs.count("type")) throw xMissingAttr(elem.Value(), "type", elem.Row(), elem.Column(), fname); + static const std::set labelledButtons{BTN_TINY, BTN_LED, BTN_PUSH}; + if(labelledButtons.count(type)) { + if(!attrs.count("color") && !attrs.count("colour") && parent->getBg() == cDialog::BG_DARK) + setColour(sf::Color::White); + if(!attrs.count("width")) + throw xMissingAttr(elem.Value(), "width", elem.Row(), elem.Column(), fname); + } +} + +location cButton::getPreferredSize() { + return {btnRects[type][0].width(), btnRects[type][0].height()}; } // Indices within the buttons array. @@ -348,18 +276,35 @@ void cLed::callHandler(event_fcn::type onClick, cDialog& me, std::str } } -void cLed::setFormat(eFormat prop, short val){ - if(prop == TXT_FONT) textFont = (eFont) val; - else if(prop == TXT_SIZE) textSize = val; - else if(prop == TXT_WRAP) wrapLabel = val; - else throw xUnsupportedProp(prop); -} - -short cLed::getFormat(eFormat prop){ - if(prop == TXT_FONT) return textFont; - else if(prop == TXT_SIZE) return textSize; - else if(prop == TXT_WRAP) return wrapLabel; - else throw xUnsupportedProp(prop); +bool cLed::manageFormat(eFormat prop, bool set, boost::any* val) { + switch(prop) { + case TXT_FONT: + if(val) { + if(set) textFont = boost::any_cast(*val); + else *val = textFont; + } + break; + case TXT_SIZE: + if(val) { + if(set) textSize = boost::any_cast(*val); + else *val = textSize; + } + break; + case TXT_WRAP: + if(val) { + if(set) wrapLabel = boost::any_cast(*val); + else *val = wrapLabel; + } + break; + case TXT_COLOUR: + if(val) { + if(set) textClr = boost::any_cast(*val); + else *val = textClr; + } + break; + default: return false; + } + return true; } void cLed::draw(){ @@ -439,104 +384,22 @@ eLedState cLed::getState(){ return state; } -std::string cLed::parse(ticpp::Element& who, std::string fname) { - using namespace ticpp; - Iterator attr; - Iterator node; - std::string name, id; - int width = 0, height = 0; - bool foundTop = false, foundLeft = false; // requireds - rectangle frame; - if(parent->getBg() == cDialog::BG_DARK) - setColour(sf::Color::White); - for(attr = attr.begin(&who); attr != attr.end(); attr++){ - attr->GetName(&name); - if(name == "name") - attr->GetValue(&id); - else if(name == "state"){ - std::string val; - attr->GetValue(&val); - if(val == "red") setState(led_red); - else if(val == "green") setState(led_green); - else if(val == "off") setState(led_off); - else throw xBadVal("led",name,val,attr->Row(),attr->Column(),fname); -// }else if(name == "fromlist"){ -// attr->GetValue(&fromList); - }else if(name == "font"){ - std::string val; - attr->GetValue(&val); - if(val == "dungeon") - setFormat(TXT_FONT, FONT_DUNGEON); - else if(val == "plain") - setFormat(TXT_FONT, FONT_PLAIN); - else if(val == "bold") - setFormat(TXT_FONT, FONT_BOLD); - else if(val == "maidenword") - setFormat(TXT_FONT, FONT_MAIDWORD); - else throw xBadVal("led",name,val,attr->Row(),attr->Column(),fname); - }else if(name == "size"){ - std::string val; - attr->GetValue(&val); - if(val == "large") - setFormat(TXT_SIZE, 12); - else if(val == "small") - setFormat(TXT_SIZE, 10); - else if(val == "title") - setFormat(TXT_SIZE, 18); - else throw xBadVal("led",name,val,attr->Row(),attr->Column(),fname); - }else if(name == "color" || name == "colour"){ - std::string val; - attr->GetValue(&val); - sf::Color clr; - try{ - clr = parseColor(val); - }catch(int){ - throw xBadVal("led",name,val,attr->Row(),attr->Column(),fname); - } - setColour(clr); - } else if(name == "wrap") { - std::string val; - attr->GetValue(&val); - if(val == "true") - setFormat(TXT_WRAP, true); - else setFormat(TXT_WRAP, false); - }else if(name == "top"){ - attr->GetValue(&frame.top), foundTop = true; - }else if(name == "left"){ - attr->GetValue(&frame.left), foundLeft = true; - }else if(name == "width"){ - attr->GetValue(&width); - }else if(name == "height"){ - attr->GetValue(&height); - }else throw xBadAttr("led",name,attr->Row(),attr->Column(),fname); +bool cLed::parseAttribute(ticpp::Attribute& attr, std::string tagName, std::string fname) { + if(attr.Name() == "state") { + std::string val = attr.Value(); + if(val == "red") setState(led_red); + else if(val == "green") setState(led_green); + else if(val == "off") setState(led_off); + else throw xBadVal(tagName, attr.Name(), val, attr.Row(), attr.Column(), fname); + return true; } - if(!foundTop) throw xMissingAttr("led","top",who.Row(),who.Column(),fname); - if(!foundLeft) throw xMissingAttr("led","left",who.Row(),who.Column(),fname); - if(width > 0) { - frame.right = frame.left + width; - }else{ - frame.right = frame.left + 14; - } - if(height > 0) { - frame.bottom = frame.top + height; - }else{ - frame.bottom = frame.top + 10; - } - setBounds(frame); - std::string content; - for(node = node.begin(&who); node != node.end(); node++){ - std::string val; - int type = node->Type(); - node->GetValue(&val); - if(type == TiXmlNode::TEXT) - content += dlogStringFilter(val); - else if(type != TiXmlNode::COMMENT) { - val = '<' + val + '>'; - throw xBadVal("led",xBadVal::CONTENT,content + val,node->Row(),node->Column(),fname); - } - } - setText(content); - return id; + return cButton::parseAttribute(attr, tagName, fname); +} + +bool cLed::parseContent(ticpp::Node& content, int n, std::string tagName, std::string fname, std::string& text) { + if(content.Type() == TiXmlNode::ELEMENT && content.Value() == "key") + return false; + return cButton::parseContent(content, n, tagName, fname, text); } void cLedGroup::addChoice(cLed* ctrl, std::string key) { @@ -627,27 +490,6 @@ void cLedGroup::hide(std::string id){ choices[id]->hide(); } -void cLedGroup::setFormat(eFormat prop, short val) { - if(prop == TXT_FRAME) drawFramed = val; - else if(prop == TXT_FRAMESTYLE) frameStyle = eFrameStyle(val); - else throw xUnsupportedProp(prop); -} - -short cLedGroup::getFormat(eFormat prop) { - if(prop == TXT_FRAME) return drawFramed; - else if(prop == TXT_FRAMESTYLE) return frameStyle; - throw xUnsupportedProp(prop); -} - -void cLedGroup::setColour(sf::Color) { - // TODO: Colour is not supported -} - -sf::Color cLedGroup::getColour() { - // TODO: Colour is not supported - return sf::Color(); -} - bool cLedGroup::isClickable(){ return true; } @@ -725,7 +567,7 @@ void cLedGroup::draw(){ iter->second->draw(); iter++; } - if(drawFramed) drawFrame(2, frameStyle); + drawFrame(2, frameStyle); } void cButton::setBtnType(eBtnType newType){ @@ -756,8 +598,8 @@ void cButton::setBtnType(eBtnType newType){ frame.height() = 40; break; case BTN_PUSH: - frame.width() = 30; - frame.height() = 30; + frame.width() = std::min(frame.width(), 30); + frame.height() = std::min(frame.height(), 30); break; case BTN_TINY: case BTN_LED: @@ -789,42 +631,22 @@ void cLedGroup::forEach(std::function callback) { callback(ctrl.first, *ctrl.second); } -std::string cLedGroup::parse(ticpp::Element& who, std::string fname) { - using namespace ticpp; - Iterator attr; - Iterator node; - std::string name, id; - for(attr = attr.begin(&who); attr != attr.end(); attr++){ - attr->GetName(&name); - if(name == "name") - attr->GetValue(&id); -// else if(name == "fromlist") -// attr->GetValue(&fromList); - else if(name == "framed"){ - std::string val; - attr->GetValue(&val); - if(val == "true") setFormat(TXT_FRAME, true); - }else if(name == "outline") { - std::string val; - attr->GetValue(&val); - if(val == "solid") setFormat(TXT_FRAMESTYLE, FRM_SOLID); - else if(val == "inset") setFormat(TXT_FRAMESTYLE, FRM_INSET); - else if(val == "outset") setFormat(TXT_FRAMESTYLE, FRM_OUTSET); - else if(val == "double") setFormat(TXT_FRAMESTYLE, FRM_DOUBLE); - }else throw xBadAttr("group",name,attr->Row(),attr->Column(),fname); +bool cLedGroup::parseContent(ticpp::Node& content, int n, std::string tagName, std::string fname, std::string& text) { + std::string val = content.Value(); + int type = content.Type(); + if(type == TiXmlNode::ELEMENT && val == "led"){ + auto led = parent->parse(dynamic_cast(content)); + addChoice(led.second, led.first); + return true; + } else if(type == TiXmlNode::TEXT) { + return false; } - for(node = node.begin(&who); node != node.end(); node++){ - std::string val; - int type = node->Type(); - node->GetValue(&val); - if(type == TiXmlNode::ELEMENT && val == "led"){ - auto led = parent->parse(*node); - addChoice(led.second, led.first); - }else if(type != TiXmlNode::COMMENT) { - val = '<' + val + '>'; - throw xBadVal("group",xBadVal::CONTENT,val,node->Row(),node->Column(),fname); - } - } - recalcRect(); - return id; + return cContainer::parseContent(content, n, tagName, fname, text); +} + +void cLedGroup::validatePostParse(ticpp::Element& who, std::string fname, const std::set& attrs, const std::multiset& nodes) { + // Don't defer to super-class; groups are an abstract container that doesn't require a position. + //cControl::validatePostParse(who, fname, attrs, nodes); + recalcRect(); + frameStyle = FRM_NONE; } diff --git a/src/dialogxml/widgets/button.hpp b/src/dialogxml/widgets/button.hpp index 66eab34c..10fc9fa0 100644 --- a/src/dialogxml/widgets/button.hpp +++ b/src/dialogxml/widgets/button.hpp @@ -50,11 +50,10 @@ class cButton : public cControl { public: /// @copydoc cDialog::init() static void init(); - std::string parse(ticpp::Element& who, std::string fname); - void setFormat(eFormat prop, short val); - short getFormat(eFormat prop); - void setColour(sf::Color clr); - sf::Color getColour(); + bool parseAttribute(ticpp::Attribute& attr, std::string tagName, std::string fname) override; + bool parseContent(ticpp::Node& content, int n, std::string tagName, std::string fname, std::string& text) override; + void validatePostParse(ticpp::Element& elem, std::string fname, const std::set& attrs, const std::multiset& elems) override; + location getPreferredSize() override; /// Set the type of this button. /// @param newType The desired button type. void setBtnType(eBtnType newType); @@ -88,6 +87,7 @@ protected: /// @param t The type of control. Should be either CTRL_LED or CTRL_BTN. cButton(cDialog& parent,eControlType t); private: + bool manageFormat(eFormat prop, bool set, boost::any* val) override; bool labelWithKey; std::string fromList; static rectangle btnRects[13][2]; @@ -114,9 +114,8 @@ public: /// default toggle-selected action of an LED. /// @return true to indicate the event should continue. static bool noAction(cDialog&,std::string,eKeyMod) {return true;} - std::string parse(ticpp::Element& who, std::string fname) override; - void setFormat(eFormat prop, short val) override; - short getFormat(eFormat prop) override; + bool parseAttribute(ticpp::Attribute& attr, std::string tagName, std::string fname) override; + bool parseContent(ticpp::Node& content, int n, std::string tagName, std::string fname, std::string& text) override; storage_t store() override; void restore(storage_t to) override; /// Create a new LED button. @@ -141,6 +140,7 @@ public: private: void defaultClickHandler(cDialog&, std::string, eKeyMod); void callHandler(event_fcn::type onClick, cDialog& me, std::string id, eKeyMod mods) override; + bool manageFormat(eFormat prop, bool set, boost::any* val) override; eLedState state; eFont textFont; short textSize; @@ -166,7 +166,6 @@ private: /// However, when the focus handler of the LED group is called, the selection _has_ been updated., /// so getSelected() will return the new selection. (This is the reason for the getPreviousSelection() method.) class cLedGroup : public cContainer { - bool drawFramed = false; std::map choices; std::string fromList; std::string curSelect, prevSelect; @@ -177,7 +176,8 @@ class cLedGroup : public cContainer { public: /// @deprecated in favour of @ref attachEventHandler void attachFocusHandler(std::function f) override; - std::string parse(ticpp::Element& who, std::string fname) override; + bool parseContent(ticpp::Node& content, int n, std::string tagName, std::string fname, std::string& text) override; + void validatePostParse(ticpp::Element& elem, std::string fname, const std::set& attrs, const std::multiset& elems) override; /// @copydoc cControl::attachClickHandler() /// /// The click handler is called whenever an LED in the group is clicked, even if it's the currently selected LED. @@ -210,10 +210,6 @@ public: /// Show one of the choices in this group. /// @param id The unique key of the choice. void show(std::string id); - void setFormat(eFormat prop, short val) override; - short getFormat(eFormat prop) override; - void setColour(sf::Color clr) override; - sf::Color getColour() override; /// Create a new LED group. /// @param parent The parent dialog. explicit cLedGroup(cDialog& parent); diff --git a/src/dialogxml/widgets/control.cpp b/src/dialogxml/widgets/control.cpp index 07a38094..9fd8c759 100644 --- a/src/dialogxml/widgets/control.cpp +++ b/src/dialogxml/widgets/control.cpp @@ -67,9 +67,6 @@ const char* xUnsupportedProp::what() const throw(){ case TXT_FRAME: s = "TXT_FRAME"; break; - case TXT_FRAMESTYLE: - s = "TXT_FRAMESTYLE"; - break; case TXT_FONT: s = "TXT_FONT"; break; @@ -79,8 +76,10 @@ const char* xUnsupportedProp::what() const throw(){ case TXT_WRAP: s = "TXT_WRAP"; break; + case TXT_COLOUR: + s = "TXT_COLOUR"; + break; } - // TODO: Support colour, which doesn't use the setFormat function sprintf(msg,"Format property %s not valid for this control.\n",s.c_str()); } return msg; @@ -188,6 +187,69 @@ void cControl::setActive(bool active) { depressed = active; } +void cControl::setFormat(eFormat prop, short val) { + boost::any newVal; + switch(prop) { + case TXT_WRAP: + newVal = bool(val); + break; + case TXT_FONT: + newVal = eFont(val); + break; + case TXT_SIZE: + newVal = val; + break; + case TXT_FRAME: + newVal = eFrameStyle(val); + break; + case TXT_COLOUR: // Interpret as a shade of grey + newVal = sf::Color{val, val, val}; + break; + } + if(!manageFormat(prop, true, &newVal)) + throw xUnsupportedProp(prop); +} + +short cControl::getFormat(eFormat prop) { + boost::any curVal; + if(!manageFormat(prop, false, &curVal)) + throw xUnsupportedProp(prop); + switch(prop) { + case TXT_WRAP: + return boost::any_cast(curVal); + case TXT_FONT: + return boost::any_cast(curVal); + case TXT_SIZE: + return boost::any_cast(curVal); + case TXT_FRAME: + return boost::any_cast(curVal); + case TXT_COLOUR: // Interpret as a shade of grey + return boost::any_cast(curVal).toInteger(); + } + return 0; +} + +bool cControl::canFormat(eFormat prop) { + return manageFormat(prop, false, nullptr); +} + +void cControl::setColour(sf::Color clr) { + boost::any newVal = clr; + if(!manageFormat(TXT_COLOUR, true, &newVal)) + throw xUnsupportedProp(TXT_COLOUR); +} + +sf::Color cControl::getColour() { + boost::any curVal; + if(!manageFormat(TXT_COLOUR, false, &curVal)) + throw xUnsupportedProp(TXT_COLOUR); + return boost::any_cast(curVal); +} + +bool cControl::manageFormat(eFormat, bool, boost::any*) { + return false; +} + void cControl::redraw() { // If there's no parent dialog, we're not responsible for redrawing if(parent) parent->draw(); @@ -312,6 +374,7 @@ bool cControl::triggerFocusHandler(cDialog& dlg, std::string id, bool losing){ } void cControl::drawFrame(short amt, eFrameStyle frameStyle){ + if(frameStyle == FRM_NONE) return; // dk_gray had a 0..65535 component of 12287, and med_gray had a 0..65535 component of 24574 static sf::Color lt_gray = {224,224,224},dk_gray = {48,48,48}; rectangle rect = frame, ul_rect; @@ -340,6 +403,122 @@ void cControl::drawFrame(short amt, eFrameStyle frameStyle){ } } +std::string cControl::parse(ticpp::Element& who, std::string fname) { + using namespace ticpp; + std::string tagName, id; + who.GetValue(&tagName); + Iterator attr; + Iterator node; + std::set foundAttrs; + std::multiset foundNodes; + int width = 0, height = 0; + rectangle frame; + for(attr = attr.begin(&who); attr != attr.end(); attr++){ + std::string attrName = attr->Name(); + foundAttrs.insert(attrName); + if(attrName == "name") attr->GetValue(&id); + else if(attrName == "top") attr->GetValue(&frame.top); + else if(attrName == "left") attr->GetValue(&frame.left); + else if(attrName == "width") attr->GetValue(&width); + else if(attrName == "height") attr->GetValue(&height); + else if(!parseAttribute(*attr, tagName, fname)) + throw xBadAttr(tagName, attrName, attr->Row(), attr->Column(), fname); + } + std::string text; + for(node = node.begin(&who); node != node.end(); node++){ + int type = node->Type(); + std::string nodeTag; + if(type == TiXmlNode::ELEMENT) + nodeTag = node->Value(); + if(type == TiXmlNode::COMMENT) continue; + else if(!parseContent(*node, foundNodes.count(nodeTag), tagName, fname, text)) { + std::string val = nodeTag.empty() ? nodeTag : xBadVal::CONTENT; + throw xBadVal(tagName, xBadVal::CONTENT, val, node->Row(), node->Column(), fname); + } + foundNodes.insert(nodeTag); + } + setText(text); + location bestSz = getPreferredSize(); + frame.width() = width > 0 ? width : bestSz.x; + frame.height() = height > 0 ? height : bestSz.y; + setBounds(frame); + validatePostParse(who, fname, foundAttrs, foundNodes); + return id; +} + +bool cControl::parseAttribute(ticpp::Attribute& attr, std::string tagName, std::string fname) { + std::string name; + attr.GetName(&name); + // Colour and formatting, if supported + if(name == "framed" && canFormat(TXT_FRAME)) { + std::string val; + attr.GetValue(&val); + if(val == "true") setFormat(TXT_FRAME, FRM_SOLID); + else if(val == "false") setFormat(TXT_FRAME, FRM_NONE); + else throw xBadVal(tagName, name, val, attr.Row(), attr.Column(), fname); + return true; + } + if(name == "outline" && canFormat(TXT_FRAME)) { + std::string val; + attr.GetValue(&val); + if(val == "none") setFormat(TXT_FRAME, FRM_NONE); + else if(val == "solid") setFormat(TXT_FRAME, FRM_SOLID); + else if(val == "inset") setFormat(TXT_FRAME, FRM_INSET); + else if(val == "outset") setFormat(TXT_FRAME, FRM_OUTSET); + else if(val == "double") setFormat(TXT_FRAME, FRM_DOUBLE); + else throw xBadVal(tagName, name, val, attr.Row(), attr.Column(), fname); + return true; + } + if(name == "font" && canFormat(TXT_FONT)) { + std::string val; + attr.GetValue(&val); + if(val == "plain") setFormat(TXT_FONT, FONT_PLAIN); + else if(val == "bold") setFormat(TXT_FONT, FONT_BOLD); + else if(val == "dungeon") setFormat(TXT_FONT, FONT_DUNGEON); + else if(val == "maidenword") setFormat(TXT_FONT, FONT_MAIDWORD); + else throw xBadVal(tagName, name, val, attr.Row(), attr.Column(), fname); + return true; + } + if(name == "size" && canFormat(TXT_SIZE)) { + std::string val; + attr.GetValue(&val); + if(val == "small") setFormat(TXT_SIZE, 10); + else if(val == "large") setFormat(TXT_SIZE, 12); + else if(val == "title") setFormat(TXT_SIZE, 18); + else throw xBadVal(tagName, name, val, attr.Row(), attr.Column(), fname); + return true; + } + if(name == "wrap" && canFormat(TXT_WRAP)) { + std::string val; + attr.GetValue(&val); + if(val == "true") setFormat(TXT_WRAP, true); + else if(val == "false") setFormat(TXT_WRAP, false); + else throw xBadVal(tagName, name, val, attr.Row(), attr.Column(), fname); + return true; + } + if((name == "color" || name == "colour") && canFormat(TXT_COLOUR)) { + std::string val; + try{ + sf::Color clr = parseColor(val); + setColour(clr); + } catch(int) { + throw xBadVal(tagName, name, val, attr.Row(), attr.Column(), fname); + } + return true; + } + return false; +} + + +bool cControl::parseContent(ticpp::Node&, int, std::string, std::string, std::string&) { + return false; +} + +void cControl::validatePostParse(ticpp::Element& elem, std::string fname, const std::set& attrs, const std::multiset&) { + if(!attrs.count("left")) throw xMissingAttr(elem.Value(), "left", elem.Row(), elem.Column(), fname); + if(!attrs.count("top")) throw xMissingAttr(elem.Value(), "top", elem.Row(), elem.Column(), fname); +} + cControl::~cControl() {} eControlType cControl::getType(){ diff --git a/src/dialogxml/widgets/control.hpp b/src/dialogxml/widgets/control.hpp index 2fa53c8e..72acca81 100644 --- a/src/dialogxml/widgets/control.hpp +++ b/src/dialogxml/widgets/control.hpp @@ -25,15 +25,16 @@ /// Formatting properties enum eFormat { - TXT_FRAME, ///< Whether to draw a frame around the control. Should be a boolean (true or false). + TXT_FRAME, ///< The control's frame style. Should be an enum from @ref eFrameStyle. TXT_FONT, ///< The control's text font. Should be one of the constants FONT_PLAIN, FONT_BOLD, FONT_DUNGEON, FONT_MAIDEN. TXT_SIZE, ///< The control's text size. Should be an integer indicating point size. TXT_WRAP, ///< Whether the control should wrap. Should be a boolean (true or false). - TXT_FRAMESTYLE, ///< The control's frame style. Should be an enum from @ref eFrameStyle. + TXT_COLOUR, ///< The control's text colour. Should be an sf::Color. Use the separate set/getColour functions to set this. }; /// Frame styles enum eFrameStyle { + FRM_NONE, ///< No frame. FRM_INSET, ///< An outline style that makes it look like the interior is slightly depressed. FRM_OUTSET, ///< An outline style that makes it look like the interior is slightly raise. FRM_SOLID, ///< A solid outline. @@ -91,8 +92,46 @@ public: class cControl { public: using storage_t = std::map; +protected: /// Parses the control from an XML element - virtual std::string parse(ticpp::Element& who, std::string fname) = 0; + /// Most controls probably don't need to override this. Override parseAttribute() and parseContent() instead. + /// @param who A reference to the XML element being parsed. + /// @param fname The file being parsed, for error messages. + /// @return The unique ID of the widget. + /// @throw xBadNode if an unsupported sub-element is detected, or if text is found in an element that does not support it + virtual std::string parse(ticpp::Element& who, std::string fname); + /// Parses an attribute on the XML element representing this control. + /// All controls should defer to cControl::parseAttribute if they don't recognize the attribute. + /// @param attr A reference to the XML attribute being parsed. + /// @param tagName The name of the XML element, for error messages. + /// @param fname The file being parsed, for error messages. + /// @return true if the attribute is valid and was successfully parsed, false if it is unrecognized. + /// @throw xBadVal if the attribute is allowed but its content is invalid. + virtual bool parseAttribute(ticpp::Attribute& attr, std::string tagName, std::string fname); + /// Parses content of the XML element representing this control, either text or a nested element. + /// Comments will not be passed to this function. + /// @param content A reference to the XML node being parsed; it will be either an element node or a text node. + /// @param tagName The name of the XML parent element, for error messages. + /// @param counter The number of previously-parsed nodes of the same tag name (or previously-parsed text nodes), excluding the current one. + /// @param[out] text If non-empty, this becomes the widget's text content. + /// @return true if the node is recognized and allowed in this location, false otherwise. + /// @throw xBadAttr if the node is an element and one of its attributes is not allowed. + /// @throw xMissingAttr if the node is an element and is missing a required attribute. + /// @throw xMissingElem if the node is an element and is missing a mandatory sub-element. + /// @throw xBadVal if the value of the node or (if the node is an element) an attribute is invalid. + /// The third argument can be used to accumulate the control's text. + virtual bool parseContent(ticpp::Node& content, int counter, std::string tagName, std::string fname, std::string& text); + /// Performs final validation of a parsed widget. + /// This can be used to throw errors for missing required attributes or elements, + /// or set defaults for optional values that were not found while parsing. + /// @param who A reference to the XML element being validated. + /// @param fname The file being parsed, for error messages + /// @param attrs A set containing all the attributes parsed from the widget's root element. + /// @param nodes A multiset containing all the sub-elements parsed from the widget's root element. Text nodes are also included in this set under the key "". + /// @throw xMissingAttr if a required attribute is missing + /// @throw xMissingElem if a required attribute is either missing or present in insufficient quantity + virtual void validatePostParse(ticpp::Element& who, std::string fname, const std::set& attrs, const std::multiset& nodes); +public: /// Attach a keyboard shortcut to a control. Pressing the keyboard shortcut is equivalent to clicking the control. /// @param key The desired keyboard shortcut. void attachKey(cKey key); @@ -203,6 +242,11 @@ public: /// Set the bounding rect of this control. /// @param newBounds The new bounding rect. void setBounds(rectangle newBounds); + /// Calculate a preferred size for this control. + /// This can specify an exact preferred size or just a preferred width or height. + /// The preferred size is only used if the size is not specified in the XML. + /// @return The preferred size, or (0,0) if there is no preferred size. + virtual location getPreferredSize() {return {0,0};} /// Set the position of this control. /// @param to The new position. void relocate(location to); @@ -216,20 +260,27 @@ public: /// @param prop The parameter to set. /// @param val The desired value of the parameter. /// @throw xUnsupportedProp if this control doesn't support the given parameter. - virtual void setFormat(eFormat prop, short val) = 0; + void setFormat(eFormat prop, short val); /// Get one of the control's formatting parameters. /// @param prop The parameter to retrieve. /// @return The value of the parameter. /// @throw xUnsupportedProp if this control doesn't support the given parameter. - virtual short getFormat(eFormat prop) = 0; + short getFormat(eFormat prop); + /// Test if the control supports a given formatting parameter + /// @param prop The parameter to check. + /// @return true if supported, false if not. + bool canFormat(eFormat prop); /// Set the control's colour (usually text colour). /// @param clr The desired colour. /// @throw xUnsupportedProp if this control does not support colour. - virtual void setColour(sf::Color clr) = 0; + void setColour(sf::Color clr); /// Get the control's colour. /// @return The current colour. /// @throw xUnsupportedProp if this control does not support colour. - virtual sf::Color getColour() = 0; + sf::Color getColour(); + /// Test if the control supports colour + /// @return true if supported, false if not. + bool canColour(); /// Check if the control is clickable. /// @return true if it's clickable. /// @note This does not indicate whether the control supports click handlers. @@ -326,6 +377,14 @@ protected: if(handler) return handler(dlg, id, newVal); return true; } + /// Called to manage a format setting on this control. + /// No action is taken if the parameter is unsupported. + /// @param prop The parameter to manage. + /// @param set Whether to set the value. + /// @param val If @a set is true, set to this value, otherwise store the value here. Ignored if null. + /// The real type of this value depends on @a prop. + /// @return true if the parameter is supported, false otherwise. + virtual bool manageFormat(eFormat prop, bool set, boost::any* val); /// Parses an HTML colour code. /// Recognizes three-digit hex, six-digit hex, and HTML colour names. /// @param code The colour code to parse. diff --git a/src/dialogxml/widgets/field.cpp b/src/dialogxml/widgets/field.cpp index 88aae4f9..4e3ee3fc 100644 --- a/src/dialogxml/widgets/field.cpp +++ b/src/dialogxml/widgets/field.cpp @@ -165,20 +165,17 @@ bool cTextField::handleClick(location clickLoc) { return true; } -void cTextField::setFormat(eFormat prop, short){ - throw xUnsupportedProp(prop); -} - -short cTextField::getFormat(eFormat prop){ - throw xUnsupportedProp(prop); -} - -void cTextField::setColour(sf::Color clr) { - color = clr; -} - -sf::Color cTextField::getColour() { - return color; +bool cTextField::manageFormat(eFormat prop, bool set, boost::any* val) { + switch(prop) { + case TXT_COLOUR: + if(val) { + if(set) color = boost::any_cast(*val); + else *val = color; + } + break; + default: return false; + } + return true; } eFldType cTextField::getInputType() { @@ -638,61 +635,33 @@ void cTextField::handleInput(cKey key) { selectionPoint = sp; } -std::string cTextField::parse(ticpp::Element& who, std::string fname) { - using namespace ticpp; - Iterator attr; - Iterator node; - std::string name, id; - int width = 0, height = 0; - bool foundTop = false, foundLeft = false; // requireds - rectangle frame; - for(attr = attr.begin(&who); attr != attr.end(); attr++){ - attr->GetName(&name); - if(name == "name") - attr->GetValue(&id); - else if(name == "type"){ - std::string val; - attr->GetValue(&val); - if(val == "int") - setInputType(FLD_INT); - else if(val == "uint") - setInputType(FLD_UINT); - else if(val == "real") - setInputType(FLD_REAL); - else if(val == "text") - setInputType(FLD_TEXT); - else throw xBadVal("field",name,val,attr->Row(),attr->Column(),fname); - }else if(name == "top"){ - attr->GetValue(&frame.top), foundTop = true; - }else if(name == "left"){ - attr->GetValue(&frame.left), foundLeft = true; - }else if(name == "width"){ - attr->GetValue(&width); - }else if(name == "height"){ - attr->GetValue(&height); - }else if(name == "tab-order"){ - attr->GetValue(&tabOrder); - }else throw xBadAttr("field",name,attr->Row(),attr->Column(),fname); +bool cTextField::parseAttribute(ticpp::Attribute& attr, std::string tagName, std::string fname) { + std::string name = attr.Name(); + if(name == "type"){ + std::string val = attr.Value(); + if(val == "int") + setInputType(FLD_INT); + else if(val == "uint") + setInputType(FLD_UINT); + else if(val == "real") + setInputType(FLD_REAL); + else if(val == "text") + setInputType(FLD_TEXT); + else throw xBadVal(tagName, name, val, attr.Row(), attr.Column(), fname); + return true; + } else if(name == "tab-order"){ + attr.GetValue(&tabOrder); + return true; } - if(!foundTop) throw xMissingAttr("field","top",attr->Row(),attr->Column(),fname); - if(!foundLeft) throw xMissingAttr("field","left",attr->Row(),attr->Column(),fname); - frame.right = frame.left + width; - frame.bottom = frame.top + height; - setBounds(frame); - std::string content; - for(node = node.begin(&who); node != node.end(); node++){ - std::string val; - int type = node->Type(); - node->GetValue(&val); - if(type == TiXmlNode::TEXT) - content += dlogStringFilter(val); - else if(type != TiXmlNode::COMMENT) { - val = '<' + val + '>'; - throw xBadVal("field",xBadVal::CONTENT,val,node->Row(),node->Column(),fname); - } + return cControl::parseAttribute(attr, tagName, fname); +} + +bool cTextField::parseContent(ticpp::Node& content, int n, std::string tagName, std::string fname, std::string& text) { + if(content.Type() == TiXmlNode::TEXT) { + text += dlogStringFilter(content.Value()); + return true; } - setText(content); - return id; + return cControl::parseContent(content, n, tagName, fname, text); } aTextInsert::aTextInsert(cTextField& in, int at, std::string text) : cAction("insert text"), in(in), at(at), text(text) {} diff --git a/src/dialogxml/widgets/field.hpp b/src/dialogxml/widgets/field.hpp index 1fd562ea..517f5e1c 100644 --- a/src/dialogxml/widgets/field.hpp +++ b/src/dialogxml/widgets/field.hpp @@ -33,7 +33,8 @@ enum eFldType { /// There is no Unicode support. class cTextField : public cControl { public: - std::string parse(ticpp::Element& who, std::string fname) override; + bool parseAttribute(ticpp::Attribute& attr, std::string tagName, std::string fname) override; + bool parseContent(ticpp::Node& content, int n, std::string tagName, std::string fname, std::string& text) override; /// For text fields, this is triggered when it loses or gains the input focus. /// @copydoc cControl::getSupportedHandlers /// @@ -42,9 +43,6 @@ public: return {EVT_FOCUS, EVT_DEFOCUS}; } bool handleClick(location where) override; - void setFormat(eFormat prop, short val) override; - short getFormat(eFormat prop) override; - void setColour(sf::Color clr) override; void setText(std::string to) override; storage_t store() override; void restore(storage_t to) override; @@ -54,7 +52,6 @@ public: /// Set the input type of the field. /// @param newType The new input type. void setInputType(eFldType newType); - sf::Color getColour() override; /// Create a new editable text field. /// @param parent The parent dialog. explicit cTextField(cDialog& parent); @@ -76,6 +73,7 @@ public: private: void callHandler(event_fcn::type onFocus, cDialog& me, std::string id) override; bool callHandler(event_fcn::type onFocus, cDialog& me, std::string id) override; + bool manageFormat(eFormat prop, bool set, boost::any* val) override; void set_ip(location clickLoc, int cTextField::* insertionPoint); cUndoList history; action_ptr current_action; diff --git a/src/dialogxml/widgets/message.cpp b/src/dialogxml/widgets/message.cpp index 558352ff..e0f4388a 100644 --- a/src/dialogxml/widgets/message.cpp +++ b/src/dialogxml/widgets/message.cpp @@ -12,167 +12,85 @@ extern sf::Texture bg_gworld; -void cTextMsg::setColour(sf::Color clr) { - color = clr; -} - -void cTextMsg::setFormat(eFormat prop, short val){ - switch(prop){ +bool cTextMsg::manageFormat(eFormat prop, bool set, boost::any* val) { + switch(prop) { case TXT_FRAME: - drawFramed = val; - break; - case TXT_FRAMESTYLE: - frameStyle = eFrameStyle(val); + if(val) { + if(set) frameStyle = boost::any_cast(*val); + else *val = frameStyle; + } break; case TXT_SIZE: - textSize = val; + if(val) { + if(set) textSize = boost::any_cast(*val); + else *val = textSize; + } break; case TXT_FONT: - if(val == FONT_DUNGEON) textFont = FONT_DUNGEON; - else if(val == FONT_PLAIN) textFont = FONT_PLAIN; - else if(val == FONT_MAIDWORD) textFont = FONT_MAIDWORD; - else textFont = FONT_BOLD; // Defaults to bold if an invalid value is provided. + if(val) { + if(set) textFont = boost::any_cast(*val); + else *val = textFont; + } break; - case TXT_WRAP: - throw xUnsupportedProp(prop); - } -} - -sf::Color cTextMsg::getColour() { - return color; -} - -short cTextMsg::getFormat(eFormat prop){ - switch(prop){ - case TXT_FRAME: - return drawFramed; - case TXT_FRAMESTYLE: - return frameStyle; - case TXT_SIZE: - return textSize; - case TXT_FONT: - return textFont; - case TXT_WRAP: - throw xUnsupportedProp(prop); - } - return 0; -} - -std::string cTextMsg::parse(ticpp::Element& who, std::string fname) { - using namespace ticpp; - Iterator attr; - Iterator node; - std::string name, id; - int width = 0, height = 0; - bool foundTop = false, foundLeft = false; // top and left are required attributes - rectangle frame; - if(parent->getBg() == cDialog::BG_DARK) - setColour(sf::Color::White); - for(attr = attr.begin(&who); attr != attr.end(); attr++){ - attr->GetName(&name); - if(name == "name") - attr->GetValue(&id); - else if(name == "framed"){ - std::string val; - attr->GetValue(&val); - if(val == "true") setFormat(TXT_FRAME, true); - }else if(name == "outline") { - std::string val; - attr->GetValue(&val); - if(val == "solid") setFormat(TXT_FRAMESTYLE, FRM_SOLID); - else if(val == "inset") setFormat(TXT_FRAMESTYLE, FRM_INSET); - else if(val == "outset") setFormat(TXT_FRAMESTYLE, FRM_OUTSET); - else if(val == "double") setFormat(TXT_FRAMESTYLE, FRM_DOUBLE); - }else if(name == "font"){ - std::string val; - attr->GetValue(&val); - if(val == "dungeon") - setFormat(TXT_FONT, FONT_DUNGEON); - else if(val == "plain") - setFormat(TXT_FONT, FONT_PLAIN); - else if(val == "bold") - setFormat(TXT_FONT, FONT_BOLD); - else if(val == "maidenword") - setFormat(TXT_FONT, FONT_MAIDWORD); - else throw xBadVal("text",name,val,attr->Row(),attr->Column(),fname); - }else if(name == "size"){ - std::string val; - attr->GetValue(&val); - if(val == "large") - setFormat(TXT_SIZE, 12); - else if(val == "small") - setFormat(TXT_SIZE, 10); - else if(val == "title") - setFormat(TXT_SIZE, 18); - else throw xBadVal("text",name,val,attr->Row(),attr->Column(),fname); - }else if(name == "color" || name == "colour"){ - std::string val; - attr->GetValue(&val); - sf::Color clr; - try{ - clr = parseColor(val); - }catch(int){ - throw xBadVal("text",name,val,attr->Row(),attr->Column(),fname); + case TXT_COLOUR: + if(val) { + if(set) color = boost::any_cast(*val); + else *val = color; } - setColour(clr); - }else if(name == "def-key"){ - std::string val; - attr->GetValue(&val); - try{ - attachKey(parseKey(val)); - }catch(int){ - throw xBadVal("text",name,val,attr->Row(),attr->Column(),fname); - } - }else if(name == "top"){ - attr->GetValue(&frame.top), foundTop = true; - }else if(name == "left"){ - attr->GetValue(&frame.left), foundLeft = true; - }else if(name == "width"){ - attr->GetValue(&width); - }else if(name == "height"){ - attr->GetValue(&height); -// }else if(name == "fromlist"){ -// attr->GetValue(&p.second->fromList); - }else throw xBadAttr("text",name,attr->Row(),attr->Column(),fname); + break; + default: return false; } - if(!foundTop) throw xMissingAttr("text","top",who.Row(),who.Column(),fname); - if(!foundLeft) throw xMissingAttr("text","left",who.Row(),who.Column(),fname); - frame.right = frame.left + width; - frame.bottom = frame.top + height; - setBounds(frame); - std::string content; - for(node = node.begin(&who); node != node.end(); node++){ - std::string val; - int type = node->Type(); - node->GetValue(&val); - // TODO: De-magic the | character - if(type == TiXmlNode::ELEMENT && val == "br") content += '|'; // because vertical bar is replaced by a newline when drawing strings - else if(type == TiXmlNode::TEXT) - content += dlogStringFilter(val); - else if(type != TiXmlNode::COMMENT) { - val = '<' + val + '>'; - throw xBadVal("text",xBadVal::CONTENT,content + val,node->Row(),node->Column(),fname); + return true; +} + +bool cTextMsg::parseAttribute(ticpp::Attribute& attr, std::string tagName, std::string fname) { + if(attr.Name() == "def-key"){ + std::string val = attr.Value(); + try { + attachKey(parseKey(val)); + } catch(int){ + throw xBadVal(tagName, attr.Name(), val, attr.Row(), attr.Column(), fname); } + return true; } - setText(content); - return id; + return cControl::parseAttribute(attr, tagName, fname); +} + +bool cTextMsg::parseContent(ticpp::Node& content, int n, std::string tagName, std::string fname, std::string& text) { + if(content.Type() == TiXmlNode::TEXT) { + text += dlogStringFilter(content.Value()); + return true; + } else if(content.Value() == "br") { + // TODO: De-magic the | character + text += '|'; // because vertical bar is replaced by a newline when drawing strings + return true; + } + return cControl::parseContent(content, n, tagName, fname, text); +} + +void cTextMsg::validatePostParse(ticpp::Element& who, std::string fname, const std::set& attrs, const std::multiset& nodes) { + cControl::validatePostParse(who, fname, attrs, nodes); + if(!attrs.count("color") && !attrs.count("colour") && parent->getBg() == cDialog::BG_DARK) + setColour(sf::Color::White); } cTextMsg::cTextMsg(cDialog& parent) : cControl(CTRL_TEXT,parent), - drawFramed(false), textFont(FONT_BOLD), textSize(10), color(parent.getDefTextClr()), - fromList("none") {} + fromList("none") { + setFormat(TXT_FRAME, FRM_NONE); +} cTextMsg::cTextMsg(sf::RenderWindow& parent) : cControl(CTRL_TEXT,parent), - drawFramed(false), textFont(FONT_BOLD), textSize(10), color(cDialog::defaultBackground == cDialog::BG_DARK ? sf::Color::White : sf::Color::Black), - fromList("none") {} + fromList("none") { + setFormat(TXT_FRAME, FRM_NONE); +} bool cTextMsg::isClickable(){ return haveHandler(EVT_CLICK); @@ -195,7 +113,7 @@ void cTextMsg::draw(){ TextStyle style; style.font = textFont; style.pointSize = textSize; - if(drawFramed) drawFrame(2,frameStyle); + drawFrame(2, frameStyle); sf::Color draw_color = color; if(depressed){ draw_color.r = 256 - draw_color.r; diff --git a/src/dialogxml/widgets/message.hpp b/src/dialogxml/widgets/message.hpp index 931759c4..114e88cf 100644 --- a/src/dialogxml/widgets/message.hpp +++ b/src/dialogxml/widgets/message.hpp @@ -22,11 +22,10 @@ /// This class can also create a frame for grouping controls or a clickable area. class cTextMsg : public cControl { public: - std::string parse(ticpp::Element& who, std::string fname); - void setFormat(eFormat prop, short val); - short getFormat(eFormat prop); - void setColour(sf::Color clr); - sf::Color getColour(); + bool parseAttribute(ticpp::Attribute& attr, std::string tagName, std::string fname) override; + bool parseContent(ticpp::Node& content, int n, std::string tagName, std::string fname, std::string& text) override; + void validatePostParse(ticpp::Element& who, std::string fname, const std::set& attrs, const std::multiset& nodes) override; + bool manageFormat(eFormat prop, bool set, boost::any* val); /// Create a new text message. /// @param parent The parent dialog. explicit cTextMsg(cDialog& parent); @@ -47,7 +46,6 @@ public: cTextMsg& operator=(cTextMsg& other) = delete; cTextMsg(cTextMsg& other) = delete; private: - bool drawFramed; short textSize; eFont textFont; sf::Color color; diff --git a/src/dialogxml/widgets/pict.cpp b/src/dialogxml/widgets/pict.cpp index 84f41662..886cb6f3 100644 --- a/src/dialogxml/widgets/pict.cpp +++ b/src/dialogxml/widgets/pict.cpp @@ -75,27 +75,24 @@ std::map& cPict::drawPict(){ return f; } -void cPict::setFormat(eFormat prop, short val){ - if(prop == TXT_FRAME) drawFramed = val; - else if(prop == TXT_FRAMESTYLE) frameStyle = eFrameStyle(val); - else if(prop == TXT_WRAP) drawScaled = !val; - else throw xUnsupportedProp(prop); -} - -short cPict::getFormat(eFormat prop){ - if(prop == TXT_FRAME) return drawFramed; - else if(prop == TXT_FRAMESTYLE) return frameStyle; - else if(prop == TXT_WRAP) return !drawScaled; - else throw xUnsupportedProp(prop); -} - -void cPict::setColour(sf::Color) { - // TODO: Colour is not supported -} - -sf::Color cPict::getColour() { - // TODO: Colour is not supported - return sf::Color(); +bool cPict::manageFormat(eFormat prop, bool set, boost::any* val) { + switch(prop) { + case TXT_FRAME: + if(val) { + if(set) frameStyle = boost::any_cast(*val); + else *val = frameStyle; + } + break; + case TXT_WRAP: + if(val) { + if(set) drawScaled = !boost::any_cast(*val); + else *val = !drawScaled; + } + break; + // TODO: Color is not supported + default: return false; + } + return true; } void cPict::setPict(pic_num_t num, ePicType type){ @@ -133,12 +130,14 @@ ePicType cPict::getPicType(){ } cPict::cPict(cDialog& parent) : - cControl(CTRL_PICT,parent), - drawFramed(true) {} + cControl(CTRL_PICT,parent) { + setFormat(TXT_FRAME, FRM_SOLID); +} cPict::cPict(sf::RenderWindow& parent) : - cControl(CTRL_PICT, parent), - drawFramed(true) {} + cControl(CTRL_PICT, parent) { + setFormat(TXT_FRAME, FRM_SOLID); +} bool cPict::isClickable(){ return haveHandler(EVT_CLICK); @@ -435,141 +434,95 @@ void cPict::advanceAnim() { if(animFrame >= 256) animFrame = 0; } -std::string cPict::parse(ticpp::Element& who, std::string fname) { - using namespace ticpp; - Iterator attr; - std::string name, id; - ePicType type; - bool wide = false, tall = false, custom = false, tiny = false; - bool foundTop = false, foundLeft = false, foundType = false, foundNum = false; // required attributes - rectangle frame; - int width = 0, height = 0; - for(attr = attr.begin(&who); attr != attr.end(); attr++){ - attr->GetName(&name); - if(name == "name") - attr->GetValue(&id); - else if(name == "type"){ - std::string val; - foundType = true; - attr->GetValue(&val); - if(val == "blank"){ - setPict(cPict::BLANK, PIC_TER); - foundNum = true; - continue; - }else if(val == "ter") - type = PIC_TER; - else if(val == "teranim") - type = PIC_TER_ANIM; - else if(val == "monst") - type = PIC_MONST; - else if(val == "dlog") - type = PIC_DLOG; - else if(val == "talk") - type = PIC_TALK; - else if(val == "scen") - type = PIC_SCEN; - else if(val == "item") - type = PIC_ITEM; - else if(val == "pc") - type = PIC_PC; - else if(val == "field") - type = PIC_FIELD; - else if(val == "boom") - type = PIC_BOOM; - else if(val == "missile") - type = PIC_MISSILE; - else if(val == "full") - type = PIC_FULL; - else if(val == "map") - type = PIC_TER_MAP; - else if(val == "status") - type = PIC_STATUS; - else throw xBadVal("pict",name,val,attr->Row(),attr->Column(),fname); - if(foundNum) { - pic_num_t wasPic = getPicNum(); - setPict(wasPic, type); - } - }else if(name == "custom"){ - std::string val; - attr->GetValue(&val); - if(val == "true") custom = true; - }else if(name == "scaled"){ - std::string val; - attr->GetValue(&val); - if(val == "true") setFormat(TXT_WRAP, false); - }else if(name == "size"){ - std::string val; - attr->GetValue(&val); - if(val == "wide") wide = true; - else if(val == "tall") tall = true; - else if(val == "large") wide = tall = true; - else if(val == "small") tiny = true; - else throw xBadVal("pict",name,val,attr->Row(),attr->Column(),fname); - }else if(name == "def-key"){ - std::string val; - attr->GetValue(&val); - try{ - attachKey(parseKey(val)); - }catch(int){ - throw xBadVal("pict",name,val,attr->Row(),attr->Column(),fname); - } - }else if(name == "num"){ - pic_num_t newPic; - attr->GetValue(&newPic), foundNum = true; - if(foundType) setPict(newPic, type); - else setPict(newPic); - }else if(name == "top"){ - attr->GetValue(&frame.top), foundTop = true; - }else if(name == "left"){ - attr->GetValue(&frame.left), foundLeft = true; - }else if(name == "width"){ - attr->GetValue(&width); - }else if(name == "height"){ - attr->GetValue(&height); - }else if(name == "framed"){ - std::string val; - attr->GetValue(&val); - if(val == "true") setFormat(TXT_FRAME, true); - else setFormat(TXT_FRAME, false); - }else if(name == "outline") { - std::string val; - attr->GetValue(&val); - if(val == "solid") setFormat(TXT_FRAMESTYLE, FRM_SOLID); - else if(val == "inset") setFormat(TXT_FRAMESTYLE, FRM_INSET); - else if(val == "outset") setFormat(TXT_FRAMESTYLE, FRM_OUTSET); - else if(val == "double") setFormat(TXT_FRAMESTYLE, FRM_DOUBLE); - }else throw xBadAttr("pict",name,attr->Row(),attr->Column(),fname); - } - if(!foundType) throw xMissingAttr("pict","type",who.Row(),who.Column(),fname); - if(!foundNum) throw xMissingAttr("pict","num",who.Row(),who.Column(),fname); - if(!foundTop) throw xMissingAttr("pict","top",who.Row(),who.Column(),fname); - if(!foundLeft) throw xMissingAttr("pict","left",who.Row(),who.Column(),fname); - if(wide || tall) { - pic_num_t wasPic = getPicNum(); - if(wide && !tall && getPicType() == PIC_MONST) setPict(wasPic, PIC_MONST_WIDE); - else if(!wide && tall && getPicType() == PIC_MONST) setPict(wasPic, PIC_MONST_TALL); - else if(wide && tall){ - if(getPicType() == PIC_MONST) setPict(wasPic, PIC_MONST_LG); - else if(getPicType() == PIC_SCEN) setPict(wasPic, PIC_SCEN_LG); - else if(getPicType() == PIC_DLOG) setPict(wasPic, PIC_DLOG_LG); + +bool cPict::parseAttribute(ticpp::Attribute& attr, std::string tagName, std::string fname) { + std::string name = attr.Name(); + if(name == "type") { + std::string val = attr.Value(); + if(val == "blank") + blank = true; + else if(val == "ter") + picType = PIC_TER; + else if(val == "teranim") + picType = PIC_TER_ANIM; + else if(val == "monst") + picType = PIC_MONST; + else if(val == "dlog") + picType = PIC_DLOG; + else if(val == "talk") + picType = PIC_TALK; + else if(val == "scen") + picType = PIC_SCEN; + else if(val == "item") + picType = PIC_ITEM; + else if(val == "pc") + picType = PIC_PC; + else if(val == "field") + picType = PIC_FIELD; + else if(val == "boom") + picType = PIC_BOOM; + else if(val == "missile") + picType = PIC_MISSILE; + else if(val == "full") + picType = PIC_FULL; + else if(val == "map") + picType = PIC_TER_MAP; + else if(val == "status") + picType = PIC_STATUS; + else throw xBadVal(tagName, name, val, attr.Row(), attr.Column(), fname); + return true; + } else if(name == "num") { + try { + attr.GetValue(&picNum); + } catch(ticpp::Exception&) { + throw xBadVal(tagName, name, attr.Value(), attr.Row(), attr.Column(), fname); } - } else if(tiny && type == PIC_ITEM) { - pic_num_t wasPic = getPicNum(); - setPict(wasPic, PIC_TINY_ITEM); + return true; + } else if(name == "custom") { + custom = true; + return true; + } else if(name == "size") { + std::string val = attr.Value(); + if(val == "wide") wide = true; + else if(val == "tall") tall = true; + else if(val == "large") wide = tall = true; + else if(val == "small") tiny = true; + else throw xBadVal(tagName, name, val, attr.Row(), attr.Column(), fname); + return true; + } else if(name == "scaled") { + std::string val = attr.Value(); + if(val == "true") setFormat(TXT_WRAP, false); + else if(val == "false") setFormat(TXT_WRAP, true); + else throw xBadVal(tagName, name, val, attr.Row(), attr.Column(), fname); + return true; + } else if(name == "def-key") { + try { + attachKey(parseKey(attr.Value())); + } catch(int) { + throw xBadVal(tagName, name, attr.Value(), attr.Row(), attr.Column(), fname); + } + return true; + } else if(name == "wrap") { + return false; } - frame.right = frame.left; - frame.bottom = frame.top; - if(width > 0 || height > 0 || getPicType() == PIC_FULL){ - frame.right = frame.left + width; - frame.bottom = frame.top + height; - } - setBounds(frame); - pic_num_t wasPic = getPicNum(); - if(custom) { - setPict(wasPic, getPicType() + PIC_CUSTOM); - } else setPict(wasPic, getPicType()); - // The above line also sets the graphic's bounding rect, if necessary - return id; + return cControl::parseAttribute(attr, tagName, fname); +} + +void cPict::validatePostParse(ticpp::Element& who, std::string fname, const std::set& attrs, const std::multiset& nodes) { + if(!attrs.count("type")) throw xMissingAttr(who.Value(), "type", who.Row(), who.Column(), fname); + if(blank && attrs.count("num")) throw xBadAttr(who.Value(), "num", who.Row(), who.Column(), fname); + else if(!blank && !attrs.count("num")) throw xMissingAttr(who.Value(), "num", who.Row(), who.Column(), fname); + + if(blank) picType = PIC_MONST, picNum = BLANK; + else if(tiny && picType == PIC_ITEM) picType = PIC_TINY_ITEM; + else if(custom) picType += PIC_CUSTOM; + + if(wide && tall) picType += PIC_LARGE; + else if(wide) picType += PIC_WIDE; + else if(tall) picType += PIC_TALL; + + setPict(picNum, picType); + return cControl::validatePostParse(who, fname, attrs, nodes); } void cPict::recalcRect() { @@ -733,7 +686,7 @@ void cPict::draw(){ fill_rect(*inWindow, rect, sf::Color::Black); else (this->*drawPict()[picType])(picNum,rect); - if(drawFramed) drawFrame(2,frameStyle); + drawFrame(2, frameStyle); } void cPict::drawPresetTer(short num, rectangle to_rect){ @@ -1325,7 +1278,7 @@ void cPict::drawAt(sf::RenderWindow& win, rectangle dest, pic_num_t which_g, ePi cPict pic(win); pic.frame = dest; pic.setPict(which_g, type_g); - pic.setFormat(TXT_FRAME, framed); + pic.setFormat(TXT_FRAME, framed ? FRM_SOLID : FRM_NONE); pic.draw(); } diff --git a/src/dialogxml/widgets/pict.hpp b/src/dialogxml/widgets/pict.hpp index 57c69dcf..377ca631 100644 --- a/src/dialogxml/widgets/pict.hpp +++ b/src/dialogxml/widgets/pict.hpp @@ -25,11 +25,9 @@ class cPict : public cControl { public: /// @copydoc cDialog::init() static void init(); - std::string parse(ticpp::Element& who, std::string fname); - void setFormat(eFormat prop, short val); - short getFormat(eFormat prop); - void setColour(sf::Color clr); - sf::Color getColour(); + bool parseAttribute(ticpp::Attribute& attr, std::string tagName, std::string fname) override; + void validatePostParse(ticpp::Element& who, std::string fname, const std::set& attrs, const std::multiset& nodes) override; + bool manageFormat(eFormat prop, bool set, boost::any* val); storage_t store(); void restore(storage_t to); /// @copydoc setPict(pic_num_t) @@ -93,7 +91,9 @@ private: static short animFrame; pic_num_t picNum; ePicType picType; - bool drawFramed, drawScaled; + bool drawScaled; + // Transient parse flags + bool wide = false, tall = false, tiny = false, custom = false, blank = false; void drawPresetTer(short num, rectangle to_rect); void drawPresetTerAnim(short num, rectangle to_rect); void drawPresetMonstSm(short num, rectangle to_rect); diff --git a/src/dialogxml/widgets/scrollbar.cpp b/src/dialogxml/widgets/scrollbar.cpp index aa1be492..16a12acd 100644 --- a/src/dialogxml/widgets/scrollbar.cpp +++ b/src/dialogxml/widgets/scrollbar.cpp @@ -519,54 +519,46 @@ void cScrollbar::draw() { else draw_horizontal(); } -std::string cScrollbar::parse(ticpp::Element& who, std::string fname) { - using namespace ticpp; - Iterator attr; - std::string name, id; - bool foundTop = false, foundLeft = false, foundMax = false; // required attributes - bool foundVertical = false; - rectangle frame; - int width = 0, height = 0; - style = SCROLL_LED; // Dialog scrollbars have a different default. - for(attr = attr.begin(&who); attr != attr.end(); attr++){ - attr->GetName(&name); - if(name == "name") - attr->GetValue(&id); - else if(name == "vertical"){ - std::string val; - attr->GetValue(&val); - if(val == "true") vert = true; - else vert = false; - foundVertical = true; - }else if(name == "style"){ - std::string val; - attr->GetValue(&val); - if(val == "white") style = SCROLL_WHITE; - else if(val == "led") style = SCROLL_LED; - else throw xBadVal("slider", name, val, attr->Row(), attr->Column(), fname); - }else if(name == "link"){ - attr->GetValue(&link); - }else if(name == "initial"){ - attr->GetValue(&pos); - }else if(name == "max"){ - attr->GetValue(&max); - foundMax = true; - }else if(name == "page-size"){ - attr->GetValue(&pgsz); - }else if(name == "top"){ - attr->GetValue(&frame.top), foundTop = true; - }else if(name == "left"){ - attr->GetValue(&frame.left), foundLeft = true; - }else if(name == "width"){ - attr->GetValue(&width); - }else if(name == "height"){ - attr->GetValue(&height); - }else throw xBadAttr("slider",name,attr->Row(),attr->Column(),fname); +bool cScrollbar::parseAttribute(ticpp::Attribute& attr, std::string tagName, std::string fname) { + std::string name = attr.Name(); + if(name == "vertical"){ + std::string val; + attr.GetValue(&val); + if(val == "true") vert = true; + else vert = false; + }else if(name == "style"){ + std::string val; + attr.GetValue(&val); + if(val == "white") style = SCROLL_WHITE; + else if(val == "led") style = SCROLL_LED; + else throw xBadVal("slider", name, val, attr.Row(), attr.Column(), fname); + }else if(name == "link"){ + attr.GetValue(&link); + }else if(name == "initial"){ + attr.GetValue(&pos); + }else if(name == "max"){ + attr.GetValue(&max); + }else if(name == "page-size"){ + attr.GetValue(&pgsz); } - if(!foundMax) throw xMissingAttr("slider","num",who.Row(),who.Column(),fname); - if(!foundTop) throw xMissingAttr("slider","top",who.Row(),who.Column(),fname); - if(!foundLeft) throw xMissingAttr("slider","left",who.Row(),who.Column(),fname); - if(pos > max) throw xBadAttr("slider","initial",who.Row(),who.Column(),fname); + return cControl::parseAttribute(attr, tagName, fname); +} + + +void cScrollbar::validatePostParse(ticpp::Element& who, std::string fname, const std::set& attrs, const std::multiset& nodes) { + cControl::validatePostParse(who, fname, attrs, nodes); + if(!attrs.count("max")) throw xMissingAttr(who.Value(), "max", who.Row(), who.Column(), fname); + if(pos > max) throw xBadVal(who.Value(), "initial", std::to_string(pos), who.Row(), who.Column(), fname); + if(!attrs.count("vertical")) { + // Try to guess whether it's horizontal or vertical based on its size + int width, height; + who.GetAttributeOrDefault("width", &width, 0); + who.GetAttributeOrDefault("height", &height, 0); + if(height > width) vert = true; + else if(width > height) vert = false; + else throw xMissingAttr(who.Value(), "vertical", who.Row(), who.Column(), fname); + } + /* int thickness = vert ? up_rect[style][VERT].width() : up_rect[style][HORZ].height(); if(width > 0) { frame.right = frame.left + width; @@ -585,10 +577,14 @@ std::string cScrollbar::parse(ticpp::Element& who, std::string fname) { if(vert) frame.height() = 25; else frame.height() = thickness; } - setBounds(frame); + */ if(parent->hasControl(link)) parent->getControl(link).setTextToNum(pos); - return id; +} + +location cScrollbar::getPreferredSize() { + if(vert) return {up_rect[style][VERT].width(), 0}; + else return {0, up_rect[style][HORZ].height()}; } cControl::storage_t cScrollbar::store() { diff --git a/src/dialogxml/widgets/scrollbar.hpp b/src/dialogxml/widgets/scrollbar.hpp index 23c9c7f9..6ea6f912 100644 --- a/src/dialogxml/widgets/scrollbar.hpp +++ b/src/dialogxml/widgets/scrollbar.hpp @@ -64,7 +64,9 @@ class cScrollbar : public cControl, public iEventListener, public iDrawable { public: /// @copydoc cDialog::init() static void init(); - std::string parse(ticpp::Element& who, std::string fname); + bool parseAttribute(ticpp::Attribute& attr, std::string tagName, std::string fname) override; + void validatePostParse(ticpp::Element& who, std::string fname, const std::set& attrs, const std::multiset& nodes) override; + location getPreferredSize() override; /// Create a new scrollbar without a parent dialog. /// @param parent The parent window. explicit cScrollbar(sf::RenderWindow& parent); diff --git a/src/dialogxml/widgets/scrollpane.cpp b/src/dialogxml/widgets/scrollpane.cpp index 3786861e..10e483f4 100644 --- a/src/dialogxml/widgets/scrollpane.cpp +++ b/src/dialogxml/widgets/scrollpane.cpp @@ -70,25 +70,18 @@ void cScrollPane::recalcRect() { frame.right += scrollFrame.width(); } -void cScrollPane::setFormat(eFormat prop, short val) { - if(prop == TXT_FRAME) framed = val; - else if(prop == TXT_FRAMESTYLE) frameStyle = eFrameStyle(val); - else throw xUnsupportedProp(prop); -} - -short cScrollPane::getFormat(eFormat prop) { - if(prop == TXT_FRAME) return framed; - else if(prop == TXT_FRAMESTYLE) return frameStyle; - else throw xUnsupportedProp(prop); -} - -void cScrollPane::setColour(sf::Color) { - // TODO: Colour is not supported -} - -sf::Color cScrollPane::getColour() { - // TODO: Colour is not supported - return sf::Color(); +bool cScrollPane::manageFormat(eFormat prop, bool set, boost::any* val) { + switch(prop) { + case TXT_FRAME: + if(val) { + if(set) frameStyle = boost::any_cast(*val); + else *val = frameStyle; + } + break; + // TODO: Colour is not supported + default: return false; + } + return true; } cControl::storage_t cScrollPane::store() { @@ -179,92 +172,52 @@ void cScrollPane::forEach(std::function callback) { callback(ctrl.first, *ctrl.second); } -std::string cScrollPane::parse(ticpp::Element& who, std::string fname) { - using namespace ticpp; - Iterator attr; - Iterator node; - std::string name, id; - rectangle frame; - int width = 0, height = 0; - bool foundTop = false, foundLeft = false; - scroll.setStyle(SCROLL_LED); - for(attr = attr.begin(&who); attr != attr.end(); attr++) { - attr->GetName(&name); - if(name == "name") - attr->GetValue(&id); - else if(name == "top") - attr->GetValue(&frame.top), foundTop = true; - else if(name == "left") - attr->GetValue(&frame.left), foundLeft = true; - else if(name == "width") - attr->GetValue(&width); - else if(name == "height") - attr->GetValue(&height); - else if(name == "framed") { - std::string val; - attr->GetValue(&val); - if(val == "true") setFormat(TXT_FRAME, true); - else setFormat(TXT_FRAME, false); - }else if(name == "outline") { - std::string val; - attr->GetValue(&val); - if(val == "solid") setFormat(TXT_FRAMESTYLE, FRM_SOLID); - else if(val == "inset") setFormat(TXT_FRAMESTYLE, FRM_INSET); - else if(val == "outset") setFormat(TXT_FRAMESTYLE, FRM_OUTSET); - else if(val == "double") setFormat(TXT_FRAMESTYLE, FRM_DOUBLE); - }else if(name == "style"){ - std::string val; - attr->GetValue(&val); - if(val == "white") setStyle(SCROLL_WHITE); - else if(val == "led") setStyle(SCROLL_LED); - else throw xBadVal("slider", name, val, attr->Row(), attr->Column(), fname); - } else throw xBadAttr("pane",name,attr->Row(),attr->Column(),fname); +bool cScrollPane::parseAttribute(ticpp::Attribute& attr, std::string tagName, std::string fname) { + if(attr.Name() == "style"){ + std::string val = attr.Value(); + if(val == "white") setStyle(SCROLL_WHITE); + else if(val == "led") setStyle(SCROLL_LED); + else throw xBadVal(tagName, attr.Name(), val, attr.Row(), attr.Column(), fname); + return true; } - if(!foundTop) throw xMissingAttr("pane","top",who.Row(),who.Column(),fname); - if(!foundLeft) throw xMissingAttr("pane","left",who.Row(),who.Column(),fname); - if(width > 0) { - frame.width() = width; - }else{ - throw xMissingAttr("pane","width",who.Row(),who.Column(),fname); - } - if(height > 0) { - frame.height() = height; - }else{ - frame.height() = 10; - } - setBounds(frame); - for(node = node.begin(&who); node != node.end(); node++) { - std::string val; - int type = node->Type(); - node->GetValue(&val); - if(type == TiXmlNode::ELEMENT) { - if(val == "field") { - auto field = parent->parse(*node); - contents.insert(field); - // TODO: Add field to tab order - //tabOrder.push_back(field); - } else if(val == "text") { - auto text = parent->parse(*node); - contents.insert(text); - } else if(val == "pict") { - auto pict = parent->parse(*node); - contents.insert(pict); - } else if(val == "slider") { - auto slide = parent->parse(*node); - contents.insert(slide); - } else if(val == "button") { - auto button = parent->parse(*node); - contents.insert(button); - } else if(val == "led") { - auto led = parent->parse(*node); - contents.insert(led); - } else if(val == "group") { - auto group = parent->parse(*node); - contents.insert(group); - } else throw xBadNode(val,node->Row(),node->Column(),fname); - } else if(type != TiXmlNode::COMMENT) - throw xBadVal("pane",xBadVal::CONTENT,val,node->Row(),node->Column(),fname); - } - recalcRect(); - return id; + return cContainer::parseAttribute(attr, tagName, fname); +} + +bool cScrollPane::parseContent(ticpp::Node& content, int n, std::string tagName, std::string fname, std::string& text) { + if(content.Type() == TiXmlNode::ELEMENT) { + std::string tag = content.Value(); + auto& elem = dynamic_cast(content); + if(tag == "field") { + auto field = parent->parse(elem); + contents.insert(field); + // TODO: Add field to tab order + //tabOrder.push_back(field); + } else if(tag == "text") { + auto text = parent->parse(elem); + contents.insert(text); + } else if(tag == "pict") { + auto pict = parent->parse(elem); + contents.insert(pict); + } else if(tag == "slider") { + auto slide = parent->parse(elem); + contents.insert(slide); + } else if(tag == "button") { + auto button = parent->parse(elem); + contents.insert(button); + } else if(tag == "led") { + auto led = parent->parse(elem); + contents.insert(led); + } else if(tag == "group") { + auto group = parent->parse(elem); + contents.insert(group); + } else throw xBadNode(tag, content.Row(), content.Column(), fname); + return true; + } + return cContainer::parseContent(content, n, tagName, fname, text); +} + +void cScrollPane::validatePostParse(ticpp::Element& who, std::string fname, const std::set& attrs, const std::multiset& nodes) { + cContainer::validatePostParse(who, fname, attrs, nodes); + if(!attrs.count("style")) setStyle(SCROLL_LED); + recalcRect(); } diff --git a/src/dialogxml/widgets/scrollpane.hpp b/src/dialogxml/widgets/scrollpane.hpp index faad0e51..187783b4 100644 --- a/src/dialogxml/widgets/scrollpane.hpp +++ b/src/dialogxml/widgets/scrollpane.hpp @@ -20,15 +20,14 @@ class cScrollPane : public cContainer { std::map contents; rectangle globalFrame; bool framed = false; + bool manageFormat(eFormat prop, bool set, boost::any* val) override; public: /// Create a new scroll pane explicit cScrollPane(cDialog& parent); - std::string parse(ticpp::Element& who, std::string fname) override; + bool parseAttribute(ticpp::Attribute& attr, std::string tagName, std::string fname) override; + bool parseContent(ticpp::Node& content, int n, std::string tagName, std::string fname, std::string& text) override; + void validatePostParse(ticpp::Element& who, std::string fname, const std::set& attrs, const std::multiset& nodes) override; bool handleClick(location where) override; - void setFormat(eFormat prop, short val) override; - short getFormat(eFormat prop) override; - void setColour(sf::Color clr) override; - sf::Color getColour() override; bool hasChild(std::string id) override; cControl& getChild(std::string id) override; storage_t store() override; diff --git a/src/dialogxml/widgets/stack.cpp b/src/dialogxml/widgets/stack.cpp index 5bc60171..bf1af069 100644 --- a/src/dialogxml/widgets/stack.cpp +++ b/src/dialogxml/widgets/stack.cpp @@ -32,25 +32,18 @@ void cContainer::callHandler(event_fcn::type onClick, cDialog& me, st getChild(which_clicked).triggerClickHandler(me, which_clicked, mods); } -void cStack::setFormat(eFormat prop, short val) { - if(prop == TXT_FRAME) drawFramed = val; - else if(prop == TXT_FRAMESTYLE) frameStyle = eFrameStyle(val); - else throw xUnsupportedProp(prop); -} - -short cStack::getFormat(eFormat prop) { - if(prop == TXT_FRAME) return drawFramed; - else if(prop == TXT_FRAMESTYLE) return frameStyle; - throw xUnsupportedProp(prop); -} - -void cStack::setColour(sf::Color) { - // TODO: Colour is not supported -} - -sf::Color cStack::getColour() { - // TODO: Colour is not supported - return sf::Color(); +bool cStack::manageFormat(eFormat prop, bool set, boost::any* val) { + switch(prop) { + case TXT_FRAME: + if(val) { + if(set) frameStyle = boost::any_cast(*val); + else *val = frameStyle; + } + break; + // TODO: Colour is not supported + default: return false; + } + return true; } bool cStack::isClickable() { @@ -158,65 +151,56 @@ void cStack::forEach(std::function callback) { callback(ctrl.first, *ctrl.second); } -std::string cStack::parse(ticpp::Element& who, std::string fname) { - using namespace ticpp; - Iterator attr; - Iterator node; - std::string name, id; - for(attr = attr.begin(&who); attr != attr.end(); attr++) { - attr->GetName(&name); - if(name == "name") - attr->GetValue(&id); - else if(name == "pages") { - size_t val; - attr->GetValue(&val); - setPageCount(val); - }else if(name == "framed"){ - std::string val; - attr->GetValue(&val); - if(val == "true") setFormat(TXT_FRAME, true); - }else if(name == "outline") { - std::string val; - attr->GetValue(&val); - if(val == "solid") setFormat(TXT_FRAMESTYLE, FRM_SOLID); - else if(val == "inset") setFormat(TXT_FRAMESTYLE, FRM_INSET); - else if(val == "outset") setFormat(TXT_FRAMESTYLE, FRM_OUTSET); - else if(val == "double") setFormat(TXT_FRAMESTYLE, FRM_DOUBLE); - } else throw xBadAttr("stack",name,attr->Row(),attr->Column(),fname); + +bool cStack::parseAttribute(ticpp::Attribute& attr, std::string tagName, std::string fname) { + if(attr.Name() == "pages") { + try { + size_t n; + attr.GetValue(&n); + setPageCount(n); + } catch(ticpp::Exception&) { + throw xBadVal(tagName, attr.Name(), attr.Value(), attr.Row(), attr.Column(), fname); + } + return true; } - for(node = node.begin(&who); node != node.end(); node++) { - std::string val; - int type = node->Type(); - node->GetValue(&val); - if(type == TiXmlNode::ELEMENT) { - if(val == "field") { - auto field = parent->parse(*node); - controls.insert(field); - // TODO: Add field to tab order - //tabOrder.push_back(field); - } else if(val == "text") { - auto text = parent->parse(*node); - controls.insert(text); - } else if(val == "pict") { - auto pict = parent->parse(*node); - controls.insert(pict); - } else if(val == "slider") { - auto slide = parent->parse(*node); - controls.insert(slide); - } else if(val == "button") { - auto button = parent->parse(*node); - controls.insert(button); - } else if(val == "led") { - auto led = parent->parse(*node); - controls.insert(led); - } else if(val == "group") { - auto group = parent->parse(*node); - controls.insert(group); - } else throw xBadNode(val,node->Row(),node->Column(),fname); - } else if(type != TiXmlNode::COMMENT) - throw xBadVal("stack",xBadVal::CONTENT,val,node->Row(),node->Column(),fname); - } - recalcRect(); - return id; + return cContainer::parseAttribute(attr, tagName, fname); +} + +bool cStack::parseContent(ticpp::Node& content, int n, std::string tagName, std::string fname, std::string& text) { + if(content.Type() == TiXmlNode::ELEMENT) { + std::string tag = content.Value(); + auto& elem = dynamic_cast(content); + if(tag == "field") { + auto field = parent->parse(elem); + controls.insert(field); + // TODO: Add field to tab order + //tabOrder.push_back(field); + } else if(tag == "text") { + auto text = parent->parse(elem); + controls.insert(text); + } else if(tag == "pict") { + auto pict = parent->parse(elem); + controls.insert(pict); + } else if(tag == "slider") { + auto slide = parent->parse(elem); + controls.insert(slide); + } else if(tag == "button") { + auto button = parent->parse(elem); + controls.insert(button); + } else if(tag == "led") { + auto led = parent->parse(elem); + controls.insert(led); + } else if(tag == "group") { + auto group = parent->parse(elem); + controls.insert(group); + } else throw xBadNode(tag, content.Row(), content.Column(), fname); + return true; + } + return cContainer::parseContent(content, n, tagName, fname, text); +} + +void cStack::validatePostParse(ticpp::Element& who, std::string fname, const std::set& attrs, const std::multiset& nodes) { + validatePostParse(who, fname, attrs, nodes); + recalcRect(); } diff --git a/src/dialogxml/widgets/stack.hpp b/src/dialogxml/widgets/stack.hpp index 640eb74f..10dbf537 100644 --- a/src/dialogxml/widgets/stack.hpp +++ b/src/dialogxml/widgets/stack.hpp @@ -37,12 +37,11 @@ class cStack : public cContainer { std::vector> storage; std::map controls; bool drawFramed = false; + bool manageFormat(eFormat prop, bool set, boost::any* val) override; public: - std::string parse(ticpp::Element& who, std::string fname) override; - void setFormat(eFormat prop, short val) override; - short getFormat(eFormat prop) override; - void setColour(sf::Color clr) override; - sf::Color getColour() override; + bool parseAttribute(ticpp::Attribute& attr, std::string tagName, std::string fname) override; + bool parseContent(ticpp::Node& content, int n, std::string tagName, std::string fname, std::string& text) override; + void validatePostParse(ticpp::Element& who, std::string fname, const std::set& attrs, const std::multiset& nodes) override; bool isClickable() override; bool isFocusable() override; bool isScrollable() override; diff --git a/src/fileio/fileio_scen.cpp b/src/fileio/fileio_scen.cpp index 03a19d67..99255e4c 100644 --- a/src/fileio/fileio_scen.cpp +++ b/src/fileio/fileio_scen.cpp @@ -520,7 +520,7 @@ static void readQuestFromXml(ticpp::Element& data, cQuest& quest) { elem->GetText(&quest.bank1); else if(banks_found == 1) elem->GetText(&quest.bank2); - else throw xBadNode(name, elem->Row(), elem->Column(), fname); + else throw xBadNode(type, elem->Row(), elem->Column(), fname); banks_found++; } else if(type == "name") { elem->GetText(&quest.name, false); diff --git a/src/game/boe.party.cpp b/src/game/boe.party.cpp index 567886ca..fac701aa 100644 --- a/src/game/boe.party.cpp +++ b/src/game/boe.party.cpp @@ -1620,7 +1620,7 @@ static void put_target_status_graphics(cDialog& me, short for_pc) { for(auto next : univ.party[for_pc].status) { std::string id2 = id + "-stat" + std::to_string(slot + 1); cPict& pic = dynamic_cast(me[id2]); - pic.setFormat(TXT_FRAME, false); + pic.setFormat(TXT_FRAME, FRM_NONE); if(isAlive) { short placedIcon = -1; if(next.first == eStatus::POISON && next.second > 4) placedIcon = 1; diff --git a/src/game/boe.town.cpp b/src/game/boe.town.cpp index 61887627..4d3354b5 100644 --- a/src/game/boe.town.cpp +++ b/src/game/boe.town.cpp @@ -1486,7 +1486,7 @@ void draw_map(bool need_refresh) { cPict theGraphic(mini_map); theGraphic.setBounds(dlogpicrect); theGraphic.setPict(21, PIC_DLOG); - theGraphic.setFormat(TXT_FRAME, false); + theGraphic.setFormat(TXT_FRAME, FRM_NONE); theGraphic.draw(); style.colour = sf::Color::White; style.lineHeight = 12;