Add page definition facility to the DialogXML <stack> widget

- New <page> element in <stack>
- Visibility of elements is now remembered when switching pages
- Unstoring a <pict> on a stack page no longer resets default values
- Documentation updated for new features
This commit is contained in:
2020-02-25 21:05:59 -05:00
parent f0f789913f
commit 49d88c76e2
6 changed files with 123 additions and 5 deletions

View File

@@ -251,6 +251,22 @@
<xs:element name="led" type="led"/>
<xs:element name="group" type="ledGroup"/>
<xs:element name="slider" type="slider"/>
<xs:element name="page">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="field" type="field"/>
<xs:element name="text" type="message"/>
<xs:element name="pict" type="pict"/>
<xs:element name="button" type="button"/>
<xs:element name="led" type="led"/>
<xs:element name="group" type="ledGroup"/>
<xs:element name="slider" type="slider"/>
</xs:choice>
<xs:attribute name="span" type="xs:integer"/>
<xs:attribute name="default" type="bool"/>
<xs:attribute name="template" type="xs:token"/>
</xs:complexType>
</xs:element>
</xs:choice>
<xs:attribute name="name" type="xs:token"/>
<xs:attributeGroup ref="frame"/>
@@ -282,7 +298,16 @@
<xs:element name="led" type="led"/>
<xs:element name="group" type="ledGroup"/>
<xs:element name="slider" type="slider"/>
<xs:element name="stack" type="stack"/>
<xs:element name="stack" type="stack">
<xs:unique name="pageName">
<xs:selector xpath="page"/>
<xs:field xpath="@template"/>
</xs:unique>
<xs:unique name="onlyOneDefault">
<xs:selector xpath="page"/>
<xs:field xpath="@default"/>
</xs:unique>
</xs:element>
<xs:element name="pane" type="scrollPane"/>
</xs:choice>
<xs:attribute name="skin" default="dark">

View File

@@ -663,6 +663,7 @@ bool cControl::hasKey(){
cControl::storage_t cControl::store() {
storage_t storage;
storage["text"] = lbl;
storage["visible"] = visible;
return storage;
}
@@ -670,4 +671,6 @@ void cControl::restore(storage_t to) {
if(to.find("text") != to.end())
lbl = boost::any_cast<std::string>(to["text"]);
else lbl = "";
if(to.find("visible") != to.end())
boost::any_cast<bool>(to["visible"]) ? show() : hide();
}

View File

@@ -1293,8 +1293,6 @@ 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"]);
else picNum = -1;
if(to.find("pic-type") != to.end())
picType = boost::any_cast<ePicType>(to["pic-type"]);
else picNum = PIC_DLOG;
}

View File

@@ -99,9 +99,18 @@ size_t cStack::getPage() {
void cStack::setPageCount(size_t n) {
if(curPage >= n && n > 0)
setPage(nPages - 1);
auto added = n - nPages;
if(n == 0) curPage = 0;
nPages = n;
storage.resize(nPages);
if(added > 0 && defaultTemplate < storage.size()) {
auto saveCur = curPage;
for(int i = n - added; i < nPages; i++) {
setPage(i);
applyTemplate(defaultTemplate);
}
setPage(saveCur);
}
}
void cStack::addPage() {
@@ -167,15 +176,74 @@ bool cStack::parseAttribute(ticpp::Attribute& attr, std::string tagName, std::st
}
bool cStack::parseContent(ticpp::Node& content, int n, std::string tagName, std::string fname, std::string& text) {
using namespace ticpp;
if(content.Type() == TiXmlNode::ELEMENT) {
auto& elem = dynamic_cast<Element&>(content);
std::string id;
return parseChildControl(dynamic_cast<ticpp::Element&>(content), controls, id);
if(elem.Value() == "page") {
templates.emplace_back();
Iterator<Element> iter;
for(iter = iter.begin(&elem); iter != iter.end(); iter++) {
if(iter->Type() == TiXmlNode::ELEMENT) {
if(!parseChildControl(*iter, controls, id)) return false;
templates.back().push_back(id);
}
}
Iterator<Attribute> attr;
for(attr = attr.begin(&elem); attr != attr.end(); attr++) {
std::string name = attr->Name();
if(name == "default") {
std::string val = attr->Value();
if(val == "true") {
if(defaultTemplate != std::numeric_limits<size_t>::max())
throw xBadAttr("page", "multiple default", attr->Row(), attr->Column(), fname);
defaultTemplate = templates.size() - 1;
} else if(val != "false") throw xBadVal("page", name, val, attr->Row(), attr->Column(), fname);
} else if(name == "span") {
int span;
try {
attr->GetValue(&span);
} catch(Exception&) {
throw xBadVal("page", name, attr->Value(), attr->Row(), attr->Column(), fname);
}
while(0 <-- span) {
templates.push_back(templates.back());
}
} else if(name == "template") {
templateNames[attr->Value()] = templates.size() - 1;
} else throw xBadAttr("page", name, attr->Row(), attr->Column(), fname);
}
return true;
} else return parseChildControl(elem, controls, id);
}
return cContainer::parseContent(content, n, tagName, fname, text);
}
void cStack::validatePostParse(ticpp::Element& who, std::string fname, const std::set<std::string>& attrs, const std::multiset<std::string>& nodes) {
void cStack::validatePostParse(ticpp::Element&, std::string, const std::set<std::string>&, const std::multiset<std::string>&) {
// Don't defer to the superclass, since this is an abstract widget with no position
//cControl::validatePostParse(who, fname, attrs, nodes);
// Actual number of pages is the larger of pages= and count of <page>
setPageCount(std::max(nPages, templates.size()));
for(size_t i = 0; i < nPages; i++) {
setPage(i);
applyTemplate(i);
}
setPage(0);
}
void cStack::applyTemplate(size_t n) {
if(n >= templates.size()) return;
for(size_t i = 0; i < templates.size(); i++) {
for(size_t j = 0; j < templates[i].size(); j++) {
auto ctrl = controls[templates[i][j]];
if(n == i) ctrl->show(); else ctrl->hide();
}
}
}
void cStack::applyTemplate(const std::string& name) {
auto iter = templateNames.find(name);
if(iter != templateNames.end()) applyTemplate(iter->second);
}

View File

@@ -38,6 +38,10 @@ class cStack : public cContainer {
std::map<std::string,cControl*> controls;
bool drawFramed = false;
bool manageFormat(eFormat prop, bool set, boost::any* val) override;
std::vector<std::vector<std::string>> templates;
std::map<std::string, size_t> templateNames;
size_t defaultTemplate = std::numeric_limits<size_t>::max();
void applyTemplate(size_t n);
public:
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;
@@ -64,6 +68,9 @@ public:
/// Add a new page to the end of the stack.
/// This is equivalent to calling setPageCount(getPageCount() + 1).
void addPage();
/// Apply a named template to the current page.
/// @param name The name of the template to apply
void applyTemplate(const std::string& name);
// Get the number of pages in the stack.
/// @return The number of pages
size_t getPageCount();

View File

@@ -262,6 +262,23 @@ The `<stack>` tag accepts the following attributes:
* `pages` - The initial number of pages in the stack. This can also be
set programmatically.
In addition to nested widgets, the `<stack>` tag can contain `<page>` elements.
Each `<page>` element contains a group of widgets that will be visible on a
particular page. Unless changed programmatically, any widgets in a `<page>`
element will be visible if and if only the corresponding page is active.
Widgets not assigned to a `<page>` will always be visible.
A `<page>` can contain any elements except for nested `<stack>` or `<pane>` elements.
The `<page>` tag accepts the following attributes:
* `span` - If set to an integer greater than 1, the same page definition will be
used for a span of this many pages, as if the entire element had been duplicated
that many times.
* `default` - If set to true, this page will be used as the default definition for
any new pages added programmatically. Only one page may be the default.
* `template` - If set to a string, this can be used to apply the page definition to
a page programmatically.
The `<pane>` tag
----------------