Finally implemented the stack control, and used it for town comments in the town details dialog

This commit is contained in:
2014-12-22 13:22:06 -05:00
parent 5e762147bd
commit dcd28b363b
20 changed files with 469 additions and 17 deletions

View File

@@ -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 = "<group>"; };
9179A45F1A42988200FEF872 /* sounds.exa */ = {isa = PBXFileReference; lastKnownFileType = folder; path = sounds.exa; sourceTree = "<group>"; };
9179A4621A47D4E200FEF872 /* vector2d.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = vector2d.hpp; sourceTree = "<group>"; };
9179A4631A4867E200FEF872 /* stack.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = stack.hpp; sourceTree = "<group>"; };
9179A4641A48681800FEF872 /* stack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = stack.cpp; sourceTree = "<group>"; };
917B573F100B956C0096C978 /* undo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = undo.h; sourceTree = "<group>"; };
918D59A718EA513900735B66 /* dialog.keys.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = dialog.keys.hpp; sourceTree = "<group>"; };
919145FB18E3A32F005CF3A4 /* boe.appleevents.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = boe.appleevents.mm; sourceTree = "<group>"; };
@@ -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;
};

View File

@@ -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<eLedState>(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<std::string>(to["led-select"]));
}

View File

@@ -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.

View File

@@ -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<std::string>(to["text"]);
}

View File

@@ -17,6 +17,7 @@
#include <string>
#include <exception>
#include <functional>
#include <boost/any.hpp>
#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<std::string, boost::any>;
/// 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.

View File

@@ -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<string,cTextField*> cDialog::parse(Element& who /*field*/){
return p;
}
// Note: This specialization must come last because it requires the other specializations
template<> pair<string,cStack*> cDialog::parse(Element& who /*stack*/) {
pair<string, cStack*> p;
Iterator<Attribute> attr;
Iterator<Element> 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<string> 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<cTextField>(*node);
controls.insert(field);
stack.push_back(field.first);
tabOrder.push_back(field);
} else if(val == "text") {
auto text = parse<cTextMsg>(*node);
controls.insert(text);
stack.push_back(text.first);
} else if(val == "pict") {
auto pict = parse<cPict>(*node);
controls.insert(pict);
stack.push_back(pict.first);
} else if(val == "button") {
auto button = parse<cButton>(*node);
controls.insert(button);
stack.push_back(button.first);
} else if(val == "led") {
auto led = parse<cLed>(*node);
controls.insert(led);
stack.push_back(led.first);
} else if(val == "group") {
auto group = parse<cLedGroup>(*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<cLed>(*node));
else if(type == "group")
controls.insert(parse<cLedGroup>(*node));
else throw xBadNode(type,node->Row(),node->Column(),fname);
else if(type == "stack") {
auto parsed = parse<cStack>(*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<const std::string, cControl*> 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<cTextField*>(iter->second);
}
void cDialog::attachClickHandlers(click_callback_t handler, std::vector<std::string> 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++;
}

View File

@@ -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

View File

@@ -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<int>(to["fld-ip"]);
if(to.find("fld-sp") != to.end())
selectionPoint = boost::any_cast<int>(to["fld-sp"]);
}

View File

@@ -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();

View File

@@ -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<pic_num_t>(to["pic-num"]);
if(to.find("pic-type") != to.end())
picType = boost::any_cast<ePicType>(to["pic-type"]);
}

View File

@@ -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.

View File

@@ -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<int>(to["scroll-pos"]);
}

View File

@@ -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.

146
osx/dialogxml/stack.cpp Normal file
View File

@@ -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<int>& specificTabs, std::vector<int>& reverseTabs) {
for(auto id : controls) {
cControl& ctrl = parent->getControl(id);
if(ctrl.getType() == CTRL_FIELD) {
cTextField& field = dynamic_cast<cTextField&>(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) {}

85
osx/dialogxml/stack.hpp Normal file
View File

@@ -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 <vector>
#include <map>
#include <string>
#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<std::map<std::string,storage_t>> storage;
std::vector<std::string> 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<int>& specificTabs, std::vector<int>& reverseTabs);
/// Create a new stack
/// @param parent The parent dialog.
cStack(cDialog& parent);
cStack& operator=(cStack& other) = delete;
cStack(cStack& other) = delete;
};
#endif

View File

@@ -209,8 +209,13 @@ integer that is unique in the dialog.
The `<stack>` tag
-----------------
The `<stack>` tag is currently unimplemented. Trying to load a dialog
that contains it will crash the game.
The `<stack>` tag groups elements that represent a single entry in an array.
It can contain any elements except for nested `<stack>` elements.
The `<stack>` tag accepts the following attributes:
* `pages` - The initial number of pages in the stack. This can also be
set programmatically.
Keyboard Shortcuts
------------------

View File

@@ -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<cStack&>(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<cStack&>(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<cLedGroup&>(me[id]).getSelected()[3] - '0' - 1;
dynamic_cast<cStack&>(me["cmt"]).setPage(i);
return true;
});
put_town_details_in_dlog(town_dlg);

View File

@@ -23,7 +23,7 @@
<xsl:attribute name='class'>dark</xsl:attribute>
</xsl:if>
<div class='dialog'>
<xsl:for-each select='dialog/pict'>
<xsl:for-each select='dialog/pict | dialog/stack/pict'>
<div>
<xsl:attribute name='class'>
pict
@@ -66,7 +66,7 @@
</div>
</xsl:for-each>
<xsl:for-each select='dialog/button'>
<xsl:for-each select='dialog/button | dialog/stack/button'>
<div>
<xsl:attribute name='class'>
button <xsl:value-of select='./@type'/>
@@ -88,7 +88,7 @@
</div>
</xsl:for-each>
<xsl:for-each select='dialog/led'>
<xsl:for-each select='dialog/led | dialog/stack/led'>
<div>
<xsl:attribute name='class'>
led
@@ -117,7 +117,7 @@
</div>
</xsl:for-each>
<xsl:for-each select='dialog/group/led'>
<xsl:for-each select='dialog/group/led | dialog/stack/group/led'>
<div>
<xsl:attribute name='class'>
led
@@ -147,7 +147,7 @@
</div>
</xsl:for-each>
<xsl:for-each select='dialog/text'>
<xsl:for-each select='dialog/text | dialog/stack/text'>
<div>
<xsl:attribute name='class'>
text
@@ -180,7 +180,7 @@
</div>
</xsl:for-each>
<xsl:for-each select='dialog/field'>
<xsl:for-each select='dialog/field | dialog/stack/field'>
<div class='tfield'>
<xsl:attribute name='style'>
left: <xsl:value-of select='./@left'/>px; top: <xsl:value-of select='./@top'/>px;

View File

@@ -7,7 +7,7 @@
<field name='key' top='101' left='327' width='43' height='16'/>
<field name='population' top='219' left='294' width='54' height='16'/>
<field name='difficulty' top='265' left='294' width='54' height='16'/>
<button name='okay' type='regular' top='317' left='309'>OK</button>
<button name='okay' type='regular' top='410' left='349'>OK</button>
<pict type='dlog' num='16' top='8' left='8'/>
<text size='large' top='6' left='50' width='256' height='17'>Town Details</text>
<text top='30' left='50' width='130' height='14'>Town name:</text>
@@ -16,7 +16,7 @@
<text top='98' left='59' width='261' height='41'>Number of event which prevents town death (if -1 or 0, none) - see chapter in documentation on time for more details.</text>
<text top='143' left='50' width='63' height='14'>Lighting:</text>
<!--
TODO: Put these LED labels in the LED elementes
TODO: Put these LED labels in the LED elements
-->
<text top='143' left='122' width='164' height='14'>Fully Lit</text>
<text top='160' left='122' width='164' height='14'>Dark</text>
@@ -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.)
</text>
<text top='317' left='50' width='101' height='110'>Comments:<br/>
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.
</text>
<group name='pick-cmt'>
<led name='cmt1' top='327' left='30' state='red'/>
<led name='cmt2' top='347' left='30'/>
<led name='cmt3' top='367' left='30'/>
</group>
<stack name='cmt' pages='3'>
<field name='comment' top='317' left='162' width='251' height='80'/>
</stack>
</dialog>

View File

@@ -266,6 +266,7 @@
<xs:element ref="group"/>
</xs:choice>
<xs:attribute name="name" type="xs:ID"/>
<xs:attribute name="pages" type="xs:integer"/>
</xs:complexType>
</xs:element>
<xs:element name="dialog">