From dcd28b363bffe302acf7f932662f2b893eee2924 Mon Sep 17 00:00:00 2001 From: Celtic Minstrel Date: Mon, 22 Dec 2014 13:22:06 -0500 Subject: [PATCH] Finally implemented the stack control, and used it for town comments in the town details dialog --- osx/BoE.xcodeproj/project.pbxproj | 10 ++ osx/dialogxml/button.cpp | 26 +++++ osx/dialogxml/button.hpp | 4 + osx/dialogxml/control.cpp | 11 +++ osx/dialogxml/control.hpp | 10 +- osx/dialogxml/dialog.cpp | 85 +++++++++++++++-- osx/dialogxml/dialog.hpp | 5 + osx/dialogxml/field.cpp | 15 +++ osx/dialogxml/field.hpp | 2 + osx/dialogxml/pict.cpp | 15 +++ osx/dialogxml/pict.hpp | 2 + osx/dialogxml/scrollbar.cpp | 12 +++ osx/dialogxml/scrollbar.hpp | 2 + osx/dialogxml/stack.cpp | 146 +++++++++++++++++++++++++++++ osx/dialogxml/stack.hpp | 85 +++++++++++++++++ osx/doxy/mainpage.md | 9 +- osx/scenedit/scen.townout.cpp | 17 ++++ rsrc/dialogs/dialog.xsl | 12 +-- rsrc/dialogs/edit-town-details.xml | 17 +++- rsrc/schemas/dialog.xsd | 1 + 20 files changed, 469 insertions(+), 17 deletions(-) create mode 100644 osx/dialogxml/stack.cpp create mode 100644 osx/dialogxml/stack.hpp diff --git a/osx/BoE.xcodeproj/project.pbxproj b/osx/BoE.xcodeproj/project.pbxproj index af77981b..c4f1531c 100644 --- a/osx/BoE.xcodeproj/project.pbxproj +++ b/osx/BoE.xcodeproj/project.pbxproj @@ -145,6 +145,9 @@ 915E090C1A317E2E008BDF00 /* map_parse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 915E09081A316D89008BDF00 /* map_parse.cpp */; }; 9179A4601A42988500FEF872 /* sounds.exa in Copy Sounds and Graphics */ = {isa = PBXBuildFile; fileRef = 9179A45F1A42988200FEF872 /* sounds.exa */; }; 9179A4611A42988800FEF872 /* graphics.exd in Copy Sounds and Graphics */ = {isa = PBXBuildFile; fileRef = 9179A45E1A42986200FEF872 /* graphics.exd */; }; + 9179A4651A48683100FEF872 /* stack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9179A4641A48681800FEF872 /* stack.cpp */; }; + 9179A4661A48683100FEF872 /* stack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9179A4641A48681800FEF872 /* stack.cpp */; }; + 9179A4671A48683100FEF872 /* stack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9179A4641A48681800FEF872 /* stack.cpp */; }; 91870F81190C8C1C0081C150 /* winutil.mac.mm in Sources */ = {isa = PBXBuildFile; fileRef = 919145FF18E63B70005CF3A4 /* winutil.mac.mm */; }; 91870F83190C8C1F0081C150 /* tarball.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91BFA3D81902AD78001686E4 /* tarball.cpp */; }; 91870F84190C90980081C150 /* scen.menu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 914CA49F190C4E9200B6ADD1 /* scen.menu.xib */; }; @@ -669,6 +672,8 @@ 9179A45E1A42986200FEF872 /* graphics.exd */ = {isa = PBXFileReference; lastKnownFileType = folder; path = graphics.exd; sourceTree = ""; }; 9179A45F1A42988200FEF872 /* sounds.exa */ = {isa = PBXFileReference; lastKnownFileType = folder; path = sounds.exa; sourceTree = ""; }; 9179A4621A47D4E200FEF872 /* vector2d.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = vector2d.hpp; sourceTree = ""; }; + 9179A4631A4867E200FEF872 /* stack.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = stack.hpp; sourceTree = ""; }; + 9179A4641A48681800FEF872 /* stack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = stack.cpp; sourceTree = ""; }; 917B573F100B956C0096C978 /* undo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = undo.h; sourceTree = ""; }; 918D59A718EA513900735B66 /* dialog.keys.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = dialog.keys.hpp; sourceTree = ""; }; 919145FB18E3A32F005CF3A4 /* boe.appleevents.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = boe.appleevents.mm; sourceTree = ""; }; @@ -872,6 +877,7 @@ 910BBAB40FB91A26001E34EA /* field.hpp */, 910BBAB80FB91ADB001E34EA /* message.hpp */, 910BBAA80FB8F733001E34EA /* pict.hpp */, + 9179A4631A4867E200FEF872 /* stack.hpp */, 919145FD18E3C750005CF3A4 /* scrollbar.hpp */, ); name = headers; @@ -887,6 +893,7 @@ 910BBAB50FB91A26001E34EA /* field.cpp */, 910BBAB90FB91ADB001E34EA /* message.cpp */, 910BBAA90FB8F733001E34EA /* pict.cpp */, + 9179A4641A48681800FEF872 /* stack.cpp */, 9191460018E63D8E005CF3A4 /* scrollbar.cpp */, ); name = src; @@ -1598,6 +1605,7 @@ 9153253B1A2E5F37000A9A1C /* specials_parse.cpp in Sources */, 915E090A1A316EE3008BDF00 /* map_parse.cpp in Sources */, 91597A6F1A3C021400BE7BF9 /* spell.cpp in Sources */, + 9179A4671A48683100FEF872 /* stack.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1652,6 +1660,7 @@ 91BFA3DB1902B13F001686E4 /* tarball.cpp in Sources */, 91BFA3EA19033E01001686E4 /* gzstream.cpp in Sources */, 915E090C1A317E2E008BDF00 /* map_parse.cpp in Sources */, + 9179A4661A48683100FEF872 /* stack.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1711,6 +1720,7 @@ 915325391A2E5F36000A9A1C /* specials_parse.cpp in Sources */, 915E090B1A316EE4008BDF00 /* map_parse.cpp in Sources */, 91597A701A3C021600BE7BF9 /* spell.cpp in Sources */, + 9179A4651A48683100FEF872 /* stack.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/osx/dialogxml/button.cpp b/osx/dialogxml/button.cpp index f4285115..250030af 100644 --- a/osx/dialogxml/button.cpp +++ b/osx/dialogxml/button.cpp @@ -255,6 +255,18 @@ void cLed::draw(){ } } +cControl::storage_t cLed::store() { + storage_t storage = cButton::store(); + storage["led-state"] = getState(); + return storage; +} + +void cLed::restore(storage_t to) { + cButton::restore(to); + if(to.find("led-state") != to.end()) + setState(boost::any_cast(to["led-state"])); +} + cLedGroup::cLedGroup(cDialog* parent) : cControl(CTRL_GROUP,*parent), fromList("none") {} @@ -307,6 +319,8 @@ eLedState cLed::getState(){ void cLedGroup::addChoice(cLed* ctrl, std::string key) { choices[key] = ctrl; + if(ctrl->getState() != led_off) + setSelected(key); } bool cLedGroup::handleClick(location where) { @@ -476,3 +490,15 @@ void cButton::setBtnType(eBtnType newType){ eBtnType cButton::getBtnType(){ return type; } + +cControl::storage_t cLedGroup::store() { + storage_t storage = cControl::store(); + storage["led-select"] = getSelected(); + return storage; +} + +void cLedGroup::restore(storage_t to) { + cControl::restore(to); + if(to.find("led-select") != to.end()) + setSelected(boost::any_cast(to["led-select"])); +} diff --git a/osx/dialogxml/button.hpp b/osx/dialogxml/button.hpp index a672cc1c..7572c827 100644 --- a/osx/dialogxml/button.hpp +++ b/osx/dialogxml/button.hpp @@ -111,6 +111,8 @@ public: bool triggerFocusHandler(cDialog& me, std::string id, bool losingFocus); void setFormat(eFormat prop, short val) throw(xUnsupportedProp); short getFormat(eFormat prop) throw(xUnsupportedProp); + storage_t store(); + void restore(storage_t to); /// Create a new LED button. /// @param parent The parent dialog. explicit cLed(cDialog* parent); @@ -175,6 +177,8 @@ public: void attachFocusHandler(focus_callback_t f) throw(); bool triggerClickHandler(cDialog& me, std::string id, eKeyMod mods); bool triggerFocusHandler(cDialog& me, std::string id, bool losingFocus); + storage_t store(); + void restore(storage_t to); /// Add a new LED to this group. /// @param ctrl A pointer to the LED, which should already be constructed. /// @param key A key to be used to look up the LED later. diff --git a/osx/dialogxml/control.cpp b/osx/dialogxml/control.cpp index 0f6e2e9f..0af6c043 100644 --- a/osx/dialogxml/control.cpp +++ b/osx/dialogxml/control.cpp @@ -315,3 +315,14 @@ bool cControl::hasKey(){ if(key.spec) return true; return key.c != 0; } + +cControl::storage_t cControl::store() { + storage_t storage; + storage["text"] = lbl; + return storage; +} + +void cControl::restore(storage_t to) { + if(to.find("text") != to.end()) + lbl = boost::any_cast(to["text"]); +} diff --git a/osx/dialogxml/control.hpp b/osx/dialogxml/control.hpp index 262ab18a..626e972f 100644 --- a/osx/dialogxml/control.hpp +++ b/osx/dialogxml/control.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include "dialog.hpp" #include "location.h" @@ -44,7 +45,7 @@ enum eControlType { CTRL_FIELD, ///< An edit text field CTRL_TEXT, ///< A static text object CTRL_GROUP, ///< A LED radiobutton-like group - CTRL_STACK, ///< A group of controls that display pages (not implemented yet) + CTRL_STACK, ///< A group of controls that represents one element in an array CTRL_SCROLL,///< A scrollbar }; @@ -87,6 +88,7 @@ public: /// a keyboard event is received that should trigger the control. class cControl { public: + using storage_t = std::map; /// 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); @@ -204,6 +206,12 @@ public: /// The practical effect of this is that hiding or showing this control automatically hides or shows the label as well. /// @param label A pointer to the control that acts as a label. void setLabelCtrl(cControl* label); + /// Get a view of the control's current state. + /// @return A map of string keys to boost::any values, representing the control's state. + virtual storage_t store(); + /// Restore the control to a previous state. + /// @param to A state previously returned from store() + virtual void restore(storage_t to); /// Create a new control attached to an arbitrary window, rather than a dialog. /// @param t The type of the control. /// @param p The parent window. diff --git a/osx/dialogxml/dialog.cpp b/osx/dialogxml/dialog.cpp index 3c260c9b..4c1da308 100644 --- a/osx/dialogxml/dialog.cpp +++ b/osx/dialogxml/dialog.cpp @@ -20,6 +20,7 @@ using namespace ticpp; #include "field.hpp" #include "message.hpp" #include "scrollbar.hpp" +#include "stack.hpp" #include "winutil.h" #include "mathutil.h" #include "cursors.h" @@ -754,6 +755,68 @@ template<> pair cDialog::parse(Element& who /*field*/){ return p; } +// Note: This specialization must come last because it requires the other specializations +template<> pair cDialog::parse(Element& who /*stack*/) { + pair p; + Iterator attr; + Iterator node; + string name; + p.second = new cStack(*this); + for(attr = attr.begin(&who); attr != attr.end(); attr++) { + attr->GetName(&name); + if(name == "name") + attr->GetValue(&p.first); + else if(name == "pages") { + size_t val; + attr->GetValue(&val); + p.second->setPageCount(val); + } else throw xBadAttr("stack",name,attr->Row(),attr->Column(),fname); + } + vector stack; + for(node = node.begin(&who); node != node.end(); node++) { + string val; + int type = node->Type(); + node->GetValue(&val); + if(type == TiXmlNode::ELEMENT) { + if(val == "field") { + auto field = parse(*node); + controls.insert(field); + stack.push_back(field.first); + tabOrder.push_back(field); + } else if(val == "text") { + auto text = parse(*node); + controls.insert(text); + stack.push_back(text.first); + } else if(val == "pict") { + auto pict = parse(*node); + controls.insert(pict); + stack.push_back(pict.first); + } else if(val == "button") { + auto button = parse(*node); + controls.insert(button); + stack.push_back(button.first); + } else if(val == "led") { + auto led = parse(*node); + controls.insert(led); + stack.push_back(led.first); + } else if(val == "group") { + auto group = parse(*node); + controls.insert(group); + stack.push_back(group.first); + } 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); + } + p.second->controls = stack; + p.second->recalcRect(); + if(p.first == ""){ + do{ + p.first = generateRandomString(); + }while(controls.find(p.first) != controls.end()); + } + return p; +} + cDialog::cDialog(cDialog* p) : parent(p) {} cDialog::cDialog(std::string path, cDialog* p) : parent(p) { @@ -826,7 +889,12 @@ void cDialog::loadFromFile(std::string path){ controls.insert(parse(*node)); else if(type == "group") controls.insert(parse(*node)); - else throw xBadNode(type,node->Row(),node->Column(),fname); + else if(type == "stack") { + auto parsed = parse(*node); + controls.insert(parsed); + // Now, if it contains any fields, their tab order must be accounted for + parsed.second->fillTabOrder(specificTabs, reverseTabs); + } else throw xBadNode(type,node->Row(),node->Column(),fname); } // Sort by tab order // First, fill any gaps that might have been left, using ones that had no specific tab order @@ -1192,6 +1260,10 @@ bool cDialog::setFocus(cTextField* newFocus, bool force) { if(!force) { if(!this->getControl(currentFocus).triggerFocusHandler(*this, currentFocus, true)) return false; } + if(newFocus == nullptr) { + currentFocus = ""; + return true; + } auto iter = find_if(controls.begin(), controls.end(), [newFocus](std::pair p){ return p.second == newFocus; }); @@ -1201,6 +1273,12 @@ bool cDialog::setFocus(cTextField* newFocus, bool force) { return true; } +cTextField* cDialog::getFocus() { + auto iter = controls.find(currentFocus); + if(iter == controls.end()) return nullptr; + return dynamic_cast(iter->second); +} + void cDialog::attachClickHandlers(click_callback_t handler, std::vector controls) { cDialog& me = *this; for(std::string control : controls) { @@ -1437,11 +1515,6 @@ cControl& cDialog::getControl(std::string id) { cLedGroup* tmp = (cLedGroup*) (iter->second); return tmp->operator[](id); }catch(std::invalid_argument) {} - }else if(iter->second->getType() == CTRL_STACK){ // TODO: Implement stacks -// try{ -// cStack* tmp = (cStack*) (iter->second); -// return tmp->operator[](id); -// }catch(std::invalid_argument) {} } iter++; } diff --git a/osx/dialogxml/dialog.hpp b/osx/dialogxml/dialog.hpp index 0cd846ca..188b9c9b 100644 --- a/osx/dialogxml/dialog.hpp +++ b/osx/dialogxml/dialog.hpp @@ -131,7 +131,12 @@ public: /// @return true if the focus changed; if it returns false, it could mean either that the control did not exist in the dialog /// or that one of the focus handlers prevented the focus change. /// @note This function is intended for internal use, which is why it takes a control pointer instead of a unique key. + /// @note If a null pointer is passed, the focus will be cleared. This means that tabbing between fields will no longer work, + /// though clicking a field to focus it should still work. bool setFocus(cTextField* newFocus, bool force = false); + /// Get the currently focused text field. + /// @return A pointer to the currently focused field. + cTextField* getFocus(); /// Close the dialog. /// @param triggerFocus true to allow the focus handler of the currently focused text field to prevent the dialog from closing /// @return true unless the currently focused field prevented the dialog from closing diff --git a/osx/dialogxml/field.cpp b/osx/dialogxml/field.cpp index 21b623d5..c76701b5 100644 --- a/osx/dialogxml/field.cpp +++ b/osx/dialogxml/field.cpp @@ -413,3 +413,18 @@ void cTextField::handleInput(cKey key) { } setText(contents); } + +cControl::storage_t cTextField::store() { + storage_t storage = cControl::store(); + storage["fld-ip"] = insertionPoint; + storage["fld-sp"] = selectionPoint; + return storage; +} + +void cTextField::restore(storage_t to) { + cControl::restore(to); + if(to.find("fld-ip") != to.end()) + insertionPoint = boost::any_cast(to["fld-ip"]); + if(to.find("fld-sp") != to.end()) + selectionPoint = boost::any_cast(to["fld-sp"]); +} diff --git a/osx/dialogxml/field.hpp b/osx/dialogxml/field.hpp index 20d4f137..b3efb82b 100644 --- a/osx/dialogxml/field.hpp +++ b/osx/dialogxml/field.hpp @@ -40,6 +40,8 @@ public: void setFormat(eFormat prop, short val) throw(xUnsupportedProp); short getFormat(eFormat prop) throw(xUnsupportedProp); void setColour(sf::Color clr) throw(xUnsupportedProp); + storage_t store(); + void restore(storage_t to); /// Get the current input type of the field. /// @return The input type. eFldType getInputType(); diff --git a/osx/dialogxml/pict.cpp b/osx/dialogxml/pict.cpp index d4eb0bf8..36917282 100644 --- a/osx/dialogxml/pict.cpp +++ b/osx/dialogxml/pict.cpp @@ -1119,3 +1119,18 @@ void cPict::drawAt(sf::RenderWindow& win, rectangle dest, pic_num_t which_g, ePi pic.setFormat(TXT_FRAME, framed); pic.draw(); } + +cControl::storage_t cPict::store() { + storage_t storage = cControl::store(); + storage["pic-num"] = picNum; + storage["pic-type"] = picType; + return storage; +} + +void cPict::restore(storage_t to) { + cControl::restore(to); + if(to.find("pic-num") != to.end()) + picNum = boost::any_cast(to["pic-num"]); + if(to.find("pic-type") != to.end()) + picType = boost::any_cast(to["pic-type"]); +} diff --git a/osx/dialogxml/pict.hpp b/osx/dialogxml/pict.hpp index 4ebc438f..adc512d4 100644 --- a/osx/dialogxml/pict.hpp +++ b/osx/dialogxml/pict.hpp @@ -115,6 +115,8 @@ public: short getFormat(eFormat prop) throw(xUnsupportedProp); void setColour(sf::Color clr) throw(xUnsupportedProp); sf::Color getColour() throw(xUnsupportedProp); + storage_t store(); + void restore(storage_t to); /// @copydoc setPict(pic_num_t) /// @param type The type of the new icon /// @note Calling this function automatically adjusts the bounding rect so that the picture fits perfectly. diff --git a/osx/dialogxml/scrollbar.cpp b/osx/dialogxml/scrollbar.cpp index f9547ab4..7815b8f3 100644 --- a/osx/dialogxml/scrollbar.cpp +++ b/osx/dialogxml/scrollbar.cpp @@ -187,3 +187,15 @@ void cScrollbar::draw() { from_rect.offset(0,16); rect_draw_some_item(scroll_gw, from_rect, *inWindow, draw_rect); } + +cControl::storage_t cScrollbar::store() { + storage_t storage = cControl::store(); + storage["scroll-pos"] = pos; + return storage; +} + +void cScrollbar::restore(storage_t to) { + cControl::restore(to); + if(to.find("scroll-pos") != to.end()) + pos = boost::any_cast(to["scroll-pos"]); +} diff --git a/osx/dialogxml/scrollbar.hpp b/osx/dialogxml/scrollbar.hpp index e8d77f66..86ade382 100644 --- a/osx/dialogxml/scrollbar.hpp +++ b/osx/dialogxml/scrollbar.hpp @@ -47,6 +47,8 @@ public: short getFormat(eFormat prop) throw(xUnsupportedProp); void setColour(sf::Color clr) throw(xUnsupportedProp); sf::Color getColour() throw(xUnsupportedProp); + storage_t store(); + void restore(storage_t to); bool isClickable(); /// Get the scrollbar thumb's current position. /// @return The current position. diff --git a/osx/dialogxml/stack.cpp b/osx/dialogxml/stack.cpp new file mode 100644 index 00000000..23aae675 --- /dev/null +++ b/osx/dialogxml/stack.cpp @@ -0,0 +1,146 @@ +// +// stack.cpp +// BoE +// +// Created by Celtic Minstrel on 14-12-22. +// +// + +#include "stack.hpp" +#include "field.hpp" + +void cStack::attachClickHandler(click_callback_t f) throw(xHandlerNotSupported) { + onClick = f; +} + +void cStack::attachFocusHandler(focus_callback_t f) throw(xHandlerNotSupported) { + throw xHandlerNotSupported(true); +} + +// TODO: The only reason the handleClick and delegation here is needed is because the engine currently has no concept of layering. +// This means a stack hides any of its controls that happen to end up underneath it. +bool cStack::triggerClickHandler(cDialog& me, std::string id, eKeyMod mods) { + std::string which_clicked = clicking; + clicking = ""; + + if(onClick) onClick(me, id, mods); + return parent->getControl(which_clicked).triggerClickHandler(me, id, mods); +} + +bool cStack::handleClick(location where) { + std::string which_clicked; + auto iter = controls.begin(); + while(iter != controls.end()){ + if(parent->getControl(*iter).isVisible() && where.in(parent->getControl(*iter).getBounds())){ + if(parent->getControl(*iter).handleClick(where)) { + which_clicked = *iter; + break; + } + } + iter++; + } + + if(which_clicked == "") return false; + + clicking = which_clicked; + return true; +} + +void cStack::setFormat(eFormat prop, short) throw(xUnsupportedProp) { + throw xUnsupportedProp(prop); +} + +short cStack::getFormat(eFormat prop) throw(xUnsupportedProp) { + throw xUnsupportedProp(prop); +} + +void cStack::setColour(sf::Color) throw(xUnsupportedProp) { + // TODO: Colour is not supported +} + +sf::Color cStack::getColour() throw(xUnsupportedProp) { + // TODO: Colour is not supported + return sf::Color(); +} + +bool cStack::isClickable() { + return true; +} + +void cStack::draw() {} + +bool cStack::setPage(size_t n) { + if(n >= nPages) return false; + if(n == curPage) return true; + cTextField* focus = parent->getFocus(); + bool failed = false; + for(auto id : controls) { + cControl& ctrl = parent->getControl(id); + storage[curPage][id] = ctrl.store(); + if(!ctrl.triggerFocusHandler(*parent, id, true)) + failed = true; + if(!failed) { + ctrl.restore(storage[n][id]); + if(focus == &ctrl && !ctrl.triggerFocusHandler(*parent, id, false)) { + failed = true; + ctrl.restore(storage[curPage][id]); + } + } + } + if(!failed) curPage = n; + return !failed; +} + +size_t cStack::getPage() { + return curPage; +} + +void cStack::setPageCount(size_t n) { + nPages = n; + storage.resize(nPages); +} + +size_t cStack::getPageCount() { + return nPages; +} + +void cStack::recalcRect() { + auto iter = controls.begin(); + frame = {INT_MAX, INT_MAX, 0, 0}; + while(iter != controls.end()){ + cControl& ctrl = parent->getControl(*iter); + rectangle otherFrame = ctrl.getBounds(); + if(otherFrame.right > frame.right) + frame.right = otherFrame.right; + if(otherFrame.bottom > frame.bottom) + frame.bottom = otherFrame.bottom; + if(otherFrame.left < frame.left) + frame.left = otherFrame.left; + if(otherFrame.top < frame.top) + frame.top = otherFrame.top; + iter++; + } + frame.inset(-6,-6); +} + +cControl& cStack::operator[](std::string id) { + auto iter = std::find(controls.begin(), controls.end(), id); + if(iter == controls.end()) throw std::invalid_argument(id + " does not exist in the stack."); + return parent->getControl(id); +} + +void cStack::fillTabOrder(std::vector& specificTabs, std::vector& reverseTabs) { + for(auto id : controls) { + cControl& ctrl = parent->getControl(id); + if(ctrl.getType() == CTRL_FIELD) { + cTextField& field = dynamic_cast(ctrl); + if(field.tabOrder > 0) + specificTabs.push_back(field.tabOrder); + else if(field.tabOrder < 0) + reverseTabs.push_back(field.tabOrder); + } + } +} + +cStack::cStack(cDialog& parent) : cControl(CTRL_STACK, parent) {} + diff --git a/osx/dialogxml/stack.hpp b/osx/dialogxml/stack.hpp new file mode 100644 index 00000000..fb264fa9 --- /dev/null +++ b/osx/dialogxml/stack.hpp @@ -0,0 +1,85 @@ +// +// stack.hpp +// BoE +// +// Created by Celtic Minstrel on 14-12-22. +// +// + +#ifndef BoE_DIALOG_STACK_HPP +#define BoE_DIALOG_STACK_HPP + +#include +#include +#include +#include "control.hpp" + +/// A stack groups several controls that represent an array of data. +/// Generally, each control would represent some aspect of a single element of the data. +/// The stack handles updating those controls to displaying different elements of the data, +/// and stores the hidden portion of the array within itself. +/// @note The stack itself provides no mechanism for switching pages. You will need +/// other controls, not within the stack, to trigger the switch. +/// @note Unlike an LED group, a stack does not have ownership of its contained controls. +/// It merely keeps track of a reference to the controls, which are in the parent dialog's +/// dictionary. Thus, a stack requires a parent dialog. +/// +/// A stack supports a click handler, which is triggered prior to passing it on to the +/// clicked control, though at present this should not be relied on due to the lack of +/// any layering concept in the engine. +/// +/// When the stack's page is changed, the focus handlers for any edit text fields in +/// the stack are triggered with the third parameter set to true to indicate they are +/// losing focus. If any of them return false, the page change is cancelled. +/// In addition, if one of the fields in the stack previously held the focus, its +/// focus handler is called with the third parameter set to false, to indicate that +/// it is gaining focus. +class cStack : public cControl { + friend class cDialog; // So it can insert controls + size_t nPages, curPage = 0; + std::string clicking; + std::vector> storage; + std::vector controls; + click_callback_t onClick; +public: + void attachClickHandler(click_callback_t f) throw(xHandlerNotSupported); + void attachFocusHandler(focus_callback_t f) throw(xHandlerNotSupported); + bool triggerClickHandler(cDialog& me, std::string id, eKeyMod mods); + bool handleClick(location where); + void setFormat(eFormat prop, short val) throw(xUnsupportedProp); + short getFormat(eFormat prop) throw(xUnsupportedProp); + void setColour(sf::Color clr) throw(xUnsupportedProp); + sf::Color getColour() throw(xUnsupportedProp); + bool isClickable(); + void draw(); + /// Switch the stack to a particular page. + /// You need to do this before retrieving data from that page. + /// @param The new page number + /// @return false if the page could not be changed, usually due to a focus handler + bool setPage(size_t n); + /// Get the current page the stack is displaying. + /// @return The current page number + size_t getPage(); + /// Set the number of pages in the stack. + /// @param n The new number of pages + /// @note If you reduce the number of pages, some data will be destroyed. + void setPageCount(size_t n); + // Get the number of pages in the stack. + /// @return The number of pages + size_t getPageCount(); + /// Recalculate the stack's bounding rect based on its contained controls. + void recalcRect(); + /// Retrieve a control reference from the stack. + /// @param id The control's unique ID + cControl& operator[](std::string id); + /// Adds any fields in this stack to the tab order building arrays. + /// Meant for internal use. + void fillTabOrder(std::vector& specificTabs, std::vector& reverseTabs); + /// Create a new stack + /// @param parent The parent dialog. + cStack(cDialog& parent); + cStack& operator=(cStack& other) = delete; + cStack(cStack& other) = delete; +}; + +#endif diff --git a/osx/doxy/mainpage.md b/osx/doxy/mainpage.md index 0bc4ab75..a5c06e27 100644 --- a/osx/doxy/mainpage.md +++ b/osx/doxy/mainpage.md @@ -209,8 +209,13 @@ integer that is unique in the dialog. The `` tag ----------------- -The `` tag is currently unimplemented. Trying to load a dialog -that contains it will crash the game. +The `` tag groups elements that represent a single entry in an array. +It can contain any elements except for nested `` elements. + +The `` tag accepts the following attributes: + +* `pages` - The initial number of pages in the stack. This can also be +set programmatically. Keyboard Shortcuts ------------------ diff --git a/osx/scenedit/scen.townout.cpp b/osx/scenedit/scen.townout.cpp index 2b50e601..5679d01e 100644 --- a/osx/scenedit/scen.townout.cpp +++ b/osx/scenedit/scen.townout.cpp @@ -13,6 +13,7 @@ #include "button.hpp" #include "dlogutil.hpp" #include "winutil.h" +#include "stack.hpp" extern short cen_x, cen_y, overall_mode;//,user_given_password; extern bool mouse_button_held,editing_town; @@ -575,6 +576,11 @@ static bool save_town_details(cDialog& me, std::string, eKeyMod) { else if(lighting == "dark") town->lighting_type = LIGHT_DARK; else if(lighting == "drains") town->lighting_type = LIGHT_DRAINS; else if(lighting == "no-light") town->lighting_type = LIGHT_NONE; + cStack& comments = dynamic_cast(me["cmt"]); + for(int i = 0; i < 3; i++) { + comments.setPage(i); + town->comment[i] = comments["comment"].getText(); + } return true; } @@ -591,6 +597,11 @@ static void put_town_details_in_dlog(cDialog& me) { case LIGHT_DRAINS: lighting.setSelected("drains"); break; case LIGHT_NONE: lighting.setSelected("no-light"); break; } + cStack& comments = dynamic_cast(me["cmt"]); + for(int i = 2; i >= 0; i--) { + comments.setPage(i); + comments["comment"].setText(town->comment[i]); + } } void edit_town_details() { @@ -600,6 +611,12 @@ void edit_town_details() { town_dlg["chop"].attachFocusHandler(std::bind(check_range_msg, _1, _2, _3, -1, 10000, "The day the town becomes abandoned", "-1 if it doesn't")); town_dlg["key"].attachFocusHandler(std::bind(check_range_msg, _1, _2, _3, -1, 10, "The event which prevents the town from becoming abandoned", "-1 or 0 for none")); town_dlg["difficulty"].attachFocusHandler(std::bind(check_range_msg, _1, _2, _3, 0, 10, "The town difficulty", "0 - easiest, 10 - hardest")); + town_dlg["pick-cmt"].attachFocusHandler([](cDialog& me, std::string id, bool losing) -> bool { + if(losing) return true; + int i = dynamic_cast(me[id]).getSelected()[3] - '0' - 1; + dynamic_cast(me["cmt"]).setPage(i); + return true; + }); put_town_details_in_dlog(town_dlg); diff --git a/rsrc/dialogs/dialog.xsl b/rsrc/dialogs/dialog.xsl index 8c1b4db9..71700e97 100644 --- a/rsrc/dialogs/dialog.xsl +++ b/rsrc/dialogs/dialog.xsl @@ -23,7 +23,7 @@ dark
- +
pict @@ -66,7 +66,7 @@
- +
button @@ -88,7 +88,7 @@
- +
led @@ -117,7 +117,7 @@
- +
led @@ -147,7 +147,7 @@
- +
text @@ -180,7 +180,7 @@
- +
left: px; top: px; diff --git a/rsrc/dialogs/edit-town-details.xml b/rsrc/dialogs/edit-town-details.xml index 8004183d..d8944445 100644 --- a/rsrc/dialogs/edit-town-details.xml +++ b/rsrc/dialogs/edit-town-details.xml @@ -7,7 +7,7 @@ - + Town Details Town name: @@ -16,7 +16,7 @@ Number of event which prevents town death (if -1 or 0, none) - see chapter in documentation on time for more details. Lighting: Fully Lit Dark @@ -36,4 +36,17 @@ Town difficulty (0-10): (Determines how fast wandering monsters appear, how nasty traps are, and how hard it is to unlock doors.) + Comments:
+ You can add up to three comments for yourself. + These are not used by the game. + Use the LEDs to the left to switch between them. +
+ + + + + + + + \ No newline at end of file diff --git a/rsrc/schemas/dialog.xsd b/rsrc/schemas/dialog.xsd index 8257bbbb..788d982c 100644 --- a/rsrc/schemas/dialog.xsd +++ b/rsrc/schemas/dialog.xsd @@ -266,6 +266,7 @@ +