diff --git a/doc/type/field.txt b/doc/type/field.txt index 79755f98..4ea22878 100644 --- a/doc/type/field.txt +++ b/doc/type/field.txt @@ -41,7 +41,6 @@ Fields are part of the [[file:style triangle]]: | @card list name@ [[type:string]] field name Alternate name to use for the card list, for example an abbreviation. | @card list alignment@ [[type:alignment]] @left@ Alignment of the card list column. | @sort script@ [[type:script]] Alternate way to sort the card list when using this column to sort the list. -| @tab index@ [[type:int]] @0@ Index for moving through the fields with the tab key. The default is from left to right and then top to bottom. The @type@ determines what values of this field contain: ! Type Values contain Displayed as diff --git a/doc/type/style.txt b/doc/type/style.txt index 2541d4d7..f42459d7 100644 --- a/doc/type/style.txt +++ b/doc/type/style.txt @@ -68,6 +68,7 @@ Here are some examples: --Properties-- ! Property Type Default Description | @z index@ [[type:int]] @0@ Stacking of this box, fields with a higher @z index@ are placed on top of those with a lower index. +| @tab index@ [[type:int]] @0@ Index for moving through the fields with the tab key, fields with a lower tab index come first. Otherwise the order is from top to bottom and then left to right. | @left@ [[type:scriptable]] [[type:double]] ''Required'' Distance between left edge of the box and the left of the card in pixels. | @width@ [[type:scriptable]] [[type:double]] ''Required'' Width of the box in pixels. | @right@ [[type:scriptable]] [[type:double]] ''Required'' Distance between right edge of the box and the ''left'' of the card in pixels. diff --git a/src/data/field.cpp b/src/data/field.cpp index 96422e87..330abb4f 100644 --- a/src/data/field.cpp +++ b/src/data/field.cpp @@ -33,7 +33,6 @@ Field::Field() , card_list_visible(false) , card_list_allow (true) , card_list_align (ALIGN_LEFT) - , tab_index (0) {} Field::~Field() {} @@ -64,7 +63,6 @@ IMPLEMENT_REFLECTION(Field) { REFLECT(card_list_name); REFLECT(sort_script); REFLECT_N("card_list_alignment", card_list_align); - REFLECT(tab_index); REFLECT_IF_READING if(caption.empty()) caption = name_to_caption(name); REFLECT_IF_READING if(card_list_name.empty()) card_list_name = capitalize(caption); } @@ -97,6 +95,7 @@ intrusive_ptr read_new(Reader& reader) { Style::Style(const FieldP& field) : fieldP(field) , z_index(0) + , tab_index(0) , left (1000000), top (1000000) , width(0), height(0) , right(1000000), bottom(1000000) @@ -110,6 +109,7 @@ Style::~Style() {} IMPLEMENT_REFLECTION(Style) { REFLECT(z_index); + REFLECT(tab_index); REFLECT(left); REFLECT(width); REFLECT(right); diff --git a/src/data/field.hpp b/src/data/field.hpp index 17f513f5..e104fef5 100644 --- a/src/data/field.hpp +++ b/src/data/field.hpp @@ -58,7 +58,6 @@ class Field : public IntrusivePtrVirtualBase { String card_list_name; ///< Alternate name to use in card list. Alignment card_list_align; ///< Alignment of the card list colummn. OptionalScript sort_script; ///< The script to use when sorting this, if not the value. - int tab_index; ///< Tab index in editor Dependencies dependent_scripts; ///< Scripts that depend on values of this field /// Creates a new Value corresponding to this Field @@ -98,6 +97,7 @@ class Style : public IntrusivePtrVirtualBase { const FieldP fieldP; ///< Field this style is for, should have the right type! int z_index; ///< Stacking of values of this field, higher = on top + int tab_index; ///< Tab index in editor Scriptable left, top; ///< Position of this field Scriptable width, height; ///< Position of this field Scriptable right, bottom; ///< Position of this field diff --git a/src/gui/control/card_editor.cpp b/src/gui/control/card_editor.cpp index 7627d171..ababd8a7 100644 --- a/src/gui/control/card_editor.cpp +++ b/src/gui/control/card_editor.cpp @@ -16,6 +16,7 @@ #include #include #include +#include // ----------------------------------------------------------------------------- : DataEditor @@ -55,21 +56,57 @@ bool DataEditor::viewerIsCurrent(const ValueViewer* viewer) const { return viewer == current_viewer && FindFocus() == this; } - void DataEditor::addAction(unique_ptr action) { set->actions.addAction(move(action)); } +// ----------------------------------------------------------------------------- : Algorithms + +/// Swap the order of comparison, i.e. greater-than instead of less-than +template +struct SwapCompare { + Comp comp; + SwapCompare(Comp comp) : comp(comp) {} + template inline bool operator () (T x, U y) { + return comp(y,x); + } +}; + +// Return the next element in a collection after x, when using comp as ordering +template +It next_element(It first, It last, V const& x, Comp comp) { + It best = last; + for (It it = first ; it != last ; ++it) { + if (comp(x, *it)) { + // this is a candidate + if (best == last || comp(*it, *best)) { + best = it; + } + } + } + return best; +} + +template +It prev_element(It first, It last, V const& x, Comp comp) { + return next_element(first, last, x, SwapCompare(comp)); +} + // ----------------------------------------------------------------------------- : Selection bool DataEditor::AcceptsFocus() const { return wxWindow::AcceptsFocus(); } -void DataEditor::select(ValueViewer* v) { +void DataEditor::select(ValueViewer* new_viewer) { ValueEditor* old_editor = current_editor; - current_viewer = v; - current_editor = v->getEditor(); + if (new_viewer) { + current_viewer = new_viewer; + current_editor = new_viewer->getEditor(); + } else { + current_viewer = nullptr; + current_editor = nullptr; + } if (current_editor != old_editor) { // selection has changed if (old_editor) old_editor->onLoseFocus(); @@ -78,54 +115,19 @@ void DataEditor::select(ValueViewer* v) { } } -void DataEditor::selectFirst() { - selectByTabPos(0, true); -} -void DataEditor::selectLast() { - selectByTabPos((int)by_tab_index.size() - 1, false); -} -bool DataEditor::selectNext() { - return selectByTabPos(currentTabPos() + 1, true); -} -bool DataEditor::selectPrevious() { - return selectByTabPos(currentTabPos() - 1, false); -} - -bool DataEditor::selectByTabPos(int tab_pos, bool forward) { - while (tab_pos >= 0 && (size_t)tab_pos < by_tab_index.size()) { - ValueViewer* v = by_tab_index[tab_pos]; - if (v->getField()->editable && v->getStyle()->isVisible()) { - select(v); - return true; - } - // not enabled, maybe the next one? - tab_pos += forward ? 1 : -1; - } - // deselect - if (current_editor) { - current_editor->onLoseFocus(); - onChange(); - } - current_viewer = nullptr; - current_editor = nullptr; - return false; -} -int DataEditor::currentTabPos() const { - int i = 0; - FOR_EACH_CONST(v, by_tab_index) { - if (v == current_viewer) return i; - ++i; - } - return -1; -} - -struct CompareTabIndex { +struct CompareTabOrder { bool operator() (ValueViewer* a, ValueViewer* b) { + assert(a && b); Style& as = *a->getStyle(), &bs = *b->getStyle(); - Field& af = *as.fieldP, &bf = *bs.fieldP; - if (af.tab_index < bf.tab_index) return true; - if (af.tab_index > bf.tab_index) return false; - if (fabs(as.top - bs.top) < 15) { + // if tab_index differs, use that + if (as.tab_index < as.tab_index) return true; + if (as.tab_index > as.tab_index) return false; + // otherwise look at the positions + // TODO: does this actually give a total order? + double vertical_overlap = min(bs.bottom - as.top, as.bottom - bs.top); + double horizontal_overlap = min(bs.right - as.left, as.right - bs.left); + if (vertical_overlap > 0 && vertical_overlap > horizontal_overlap) { + // fields overlap (mostly) vertically // the fields are almost on the same 'row' // compare horizontally first if (as.left < bs.left) return true; // horizontal sorting @@ -137,21 +139,65 @@ struct CompareTabIndex { if (as.top > bs.top) return false; if (as.left < bs.left) return true; // horizontal sorting } - return false; + // arbitrary order otherwise + return a < b; + } + bool operator() (ValueViewerP const& a, ValueViewer* b) { + return operator () (a.get(), b); + } + bool operator() (ValueViewer* a, ValueViewerP const& b) { + return operator () (a, b.get()); + } + bool operator() (ValueViewerP const& a, ValueViewerP const& b) { + return operator () (a.get(), b.get()); } }; -void DataEditor::createTabIndex() { - by_tab_index.clear(); - FOR_EACH(v, viewers) { - ValueEditor* e = v->getEditor(); - if (e) { - by_tab_index.push_back(v.get()); - } - } - stable_sort(by_tab_index.begin(), by_tab_index.end(), CompareTabIndex()); + +bool is_enabled(ValueViewerP const& v) { + return v->getField()->editable && v->getStyle()->isVisible(); } + +bool DataEditor::selectWithTab(vector::iterator const& it) { + if (it != viewers.end()) { + select(it->get()); + return true; + } else { + select(nullptr); + return false; + } +} + +bool DataEditor::selectFirst() { + // This would be nicer with boost::range, but filtered adaptor was only introduced in boost 1.42(?) + return selectWithTab(std::min_element( + boost::make_filter_iterator(is_enabled, viewers.begin(), viewers.end()), + boost::make_filter_iterator(is_enabled, viewers.end(), viewers.end()), + CompareTabOrder()).base()); +} +bool DataEditor::selectLast() { + return selectWithTab(std::max_element( + boost::make_filter_iterator(is_enabled, viewers.begin(), viewers.end()), + boost::make_filter_iterator(is_enabled, viewers.end(), viewers.end()), + CompareTabOrder()).base()); +} +bool DataEditor::selectNext() { + if (!current_viewer) return selectFirst(); + return selectWithTab(next_element( + boost::make_filter_iterator(is_enabled, viewers.begin(), viewers.end()), + boost::make_filter_iterator(is_enabled, viewers.end(), viewers.end()), + current_viewer, + CompareTabOrder()).base()); +} +bool DataEditor::selectPrevious() { + if (!current_viewer) return selectLast(); + return selectWithTab(prev_element( + boost::make_filter_iterator(is_enabled, viewers.begin(), viewers.end()), + boost::make_filter_iterator(is_enabled, viewers.end(), viewers.end()), + current_viewer, + CompareTabOrder()).base()); +} + void DataEditor::onInit() { - createTabIndex(); current_viewer = nullptr; current_editor = nullptr; hovered_viewer = nullptr; diff --git a/src/gui/control/card_editor.hpp b/src/gui/control/card_editor.hpp index 4f43a9a0..30498c74 100644 --- a/src/gui/control/card_editor.hpp +++ b/src/gui/control/card_editor.hpp @@ -34,9 +34,9 @@ class DataEditor : public CardViewer { /// Select the given viewer, sends focus events void select(ValueViewer* v); /// Select the first editable and visible editor (by tab index) - void selectFirst(); + bool selectFirst(); /// Select the last editable and visible editor (by tab index) - void selectLast(); + bool selectLast(); /// Select the next editable editor, returns false if the current editor is the last one bool selectNext(); /// Select the previous editable editor, returns false if the current editor is the first one @@ -127,13 +127,8 @@ class DataEditor : public CardViewer { /// Convert mouse coordinates to internal coordinates RealPoint mousePoint(const wxMouseEvent&, const ValueViewer& viewer); - // Create tab index ordering of the (editable) viewers - void createTabIndex(); - /// Select the field with the given position in the by_tab_index list - /** Returns success */ - bool selectByTabPos(int tab_pos, bool forward = true); - /// Find the tab pos of the current viewer, returns -1 if not found - int currentTabPos() const; + /// Select a field found by tab order, can be viewers.end() + bool selectWithTab(vector::iterator const&); }; /// By default a DataEditor edits cards