diff --git a/osx/dialogxml/dialog.cpp b/osx/dialogxml/dialog.cpp index 29019537..6b0c752f 100644 --- a/osx/dialogxml/dialog.cpp +++ b/osx/dialogxml/dialog.cpp @@ -689,6 +689,8 @@ template<> pair 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 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(*node)); - else if(type == "text") + if(type == "field") { + auto field = parse(*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(*node)); else if(type == "pict") controls.insert(parse(*node)); @@ -775,6 +784,41 @@ void cDialog::loadFromFile(std::string path){ controls.insert(parse(*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& 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(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? diff --git a/osx/dialogxml/dialog.h b/osx/dialogxml/dialog.h index a7328699..9e76f6ae 100644 --- a/osx/dialogxml/dialog.h +++ b/osx/dialogxml/dialog.h @@ -13,6 +13,7 @@ #include #include +#include #include #include "ticpp.h" @@ -39,6 +40,7 @@ class cDialog { cTextField* currentFocus; cDialog* parent; void loadFromFile(std::string path); + std::vector> tabOrder; public: static void init(); static bool noAction(cDialog&,std::string,eKeyMod) {return true;} diff --git a/osx/dialogxml/field.h b/osx/dialogxml/field.h index 119f5327..2e8d5936 100644 --- a/osx/dialogxml/field.h +++ b/osx/dialogxml/field.h @@ -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; diff --git a/rsrc/dialogs/dialog.xsd b/rsrc/dialogs/dialog.xsd index 6ef28b12..6a26c172 100644 --- a/rsrc/dialogs/dialog.xsd +++ b/rsrc/dialogs/dialog.xsd @@ -163,6 +163,7 @@ + @@ -284,5 +285,9 @@ + + + +