Dialog tab order implementation

- Defaults to order of definition in file
- tab-order attribtue can be set to a postive number to force towards the start, or a negative number to force towards the end
This commit is contained in:
2014-11-29 16:03:54 -05:00
parent 6f9b5c8f69
commit 84f53a8a7d
4 changed files with 67 additions and 13 deletions

View File

@@ -689,6 +689,8 @@ template<> pair<string,cTextField*> cDialog::parse(Element& who /*field*/){
attr->GetValue(&width);
}else if(name == "height"){
attr->GetValue(&height);
}else if(name == "tab-order"){
attr->GetValue(&p.second->tabOrder);
}else throw xBadAttr("button",name,attr->Row(),attr->Column(),fname);
}
if(!foundTop) throw xMissingAttr("field","top",attr->Row(),attr->Column(),fname);
@@ -756,14 +758,21 @@ void cDialog::loadFromFile(std::string path){
throw xBadAttr(type,name,attr->Row(),attr->Column(),fname);
}
vector<int> specificTabs, reverseTabs;
for(node = node.begin(xml.FirstChildElement()); node != node.end(); node++){
node->GetValue(&type);
// Yes, I'm using insert instead of [] to add elements to the map.
// In this situation, it's actually easier that way; the reason being, the
// map key is obtained from the name attribute of each element.
if(type == "field")
controls.insert(parse<cTextField>(*node));
else if(type == "text")
if(type == "field") {
auto field = parse<cTextField>(*node);
controls.insert(field);
tabOrder.push_back(field);
if(field.second->tabOrder > 0)
specificTabs.push_back(field.second->tabOrder);
else if(field.second->tabOrder < 0)
reverseTabs.push_back(field.second->tabOrder);
} else if(type == "text")
controls.insert(parse<cTextMsg>(*node));
else if(type == "pict")
controls.insert(parse<cPict>(*node));
@@ -775,6 +784,41 @@ void cDialog::loadFromFile(std::string path){
controls.insert(parse<cLedGroup>(*node));
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
// Of course, if there are not enough without a specific tab order, there could still be gaps
using fld_t = decltype(tabOrder)::value_type;
auto noTabOrder = [](fld_t x) {return x.second->tabOrder == 0;};
if(!specificTabs.empty()) {
int max = *max_element(specificTabs.begin(), specificTabs.end());
for(int i = 1; i < max; i++) {
auto check = find(specificTabs.begin(), specificTabs.end(), i);
if(check != specificTabs.end()) continue;
auto change = find_if(tabOrder.begin(), tabOrder.end(), noTabOrder);
if(change == tabOrder.end()) break;
change->second->tabOrder = i;
}
}
if(!reverseTabs.empty()) {
int max = -*min_element(reverseTabs.begin(), reverseTabs.end());
for(int i = 1; i < max; i++) {
auto check = find(reverseTabs.begin(), reverseTabs.end(), -i);
if(check != reverseTabs.end()) continue;
auto change = find_if(tabOrder.begin(), tabOrder.end(), noTabOrder);
if(change == tabOrder.end()) break;
change->second->tabOrder = -i;
}
}
// Then, sort - first, positive tab order ascending; then zeros; then negative tab order descending.
stable_sort(tabOrder.begin(), tabOrder.end(), [](fld_t a, fld_t b) -> bool {
bool a_neg = a.second->tabOrder < 0, b_neg = b.second->tabOrder < 0;
if(a_neg && !b_neg) return false;
else if(!a_neg && b_neg) return true;
bool a_pos = a.second->tabOrder > 0, b_pos = b.second->tabOrder > 0;
if(a_pos && !b_pos) return true;
else if(!a_pos && b_pos) return false;
return a.second->tabOrder < b.second->tabOrder;
});
} catch(Exception& x){ // XML processing exception
printf("%s",x.what());
exit(1);
@@ -865,12 +909,8 @@ void cDialog::run(){
std::string itemHit = "";
dialogNotToast = true;
// Focus the first text field, if there is one
for(auto ctrl : controls) {
if(ctrl.second->getType() == CTRL_FIELD) {
ctrl.second->triggerFocusHandler(*this, ctrl.first, false);
break;
}
}
if(!tabOrder.empty())
tabOrder[0].second->triggerFocusHandler(*this, tabOrder[0].first, false);
win.create(sf::VideoMode(winRect.width(), winRect.height()), "Dialog", sf::Style::Titlebar);
win.setActive();
win.setVisible(true);
@@ -994,19 +1034,25 @@ void cDialog::run(){
if(controls[itemHit]->getType() == CTRL_FIELD){
if(key.spec && key.k == key_tab){
// TODO: Tabbing through fields, and trigger focus events.
ctrlIter cur = controls.find(itemHit), iter;
auto cur = find_if(tabOrder.begin(), tabOrder.end(), [&itemHit](pair<string,cTextField*>& a) {
return a.first == itemHit;
});
if(cur == tabOrder.end()) break; // Unlikely, but let's be safe
if(!cur->second->triggerFocusHandler(*this,itemHit,true)) break;
cTextField* wasFocus = currentFocus;
iter = std::next(cur);
auto iter = std::next(cur);
if(iter == tabOrder.end()) iter = tabOrder.begin();
while(iter != cur){
// If tab order is explicitly specified for all fields, gaps are possible
if(iter->second == nullptr) continue;
if((currentFocus = dynamic_cast<cTextField*>(iter->second))){
if(currentFocus->triggerFocusHandler(*this,iter->first,false)){
itemHit = "";
break;
}
}
if(iter == controls.end()) iter = controls.begin();
else iter++;
iter++;
if(iter == tabOrder.end()) iter = tabOrder.begin();
}
if(iter == cur) // no focus change occured!
currentFocus = wasFocus; // TODO: Surely something should happen here?

View File

@@ -13,6 +13,7 @@
#include <string>
#include <map>
#include <vector>
#include <exception>
#include "ticpp.h"
@@ -39,6 +40,7 @@ class cDialog {
cTextField* currentFocus;
cDialog* parent;
void loadFromFile(std::string path);
std::vector<std::pair<std::string,cTextField*>> tabOrder;
public:
static void init();
static bool noAction(cDialog&,std::string,eKeyMod) {return true;}

View File

@@ -36,6 +36,7 @@ public:
void handleInput(cKey key);
cTextField& operator=(cTextField& other) = delete;
cTextField(cTextField& other) = delete;
long tabOrder = 0;
private:
bool isNumericField;
focus_callback_t onFocus;

View File

@@ -163,6 +163,7 @@
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="tab-order" type="xs:integer"/>
<xs:attributeGroup ref="rect"/>
</xs:complexType>
</xs:element>
@@ -284,5 +285,9 @@
<xs:attribute name="fore" default="black"/>
<xs:attribute name="defbtn" type="xs:IDREF"/>
</xs:complexType>
<xs:unique name="FieldTabOrder">
<xs:selector xpath="*" />
<xs:field xpath="@tab-order" />
</xs:unique>
</xs:element>
</xs:schema>