DUDS
Distributed Update of Data from Something
MenuView.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of the DUDS project. It is subject to the BSD-style
3  * license terms in the LICENSE file found in the top-level directory of this
4  * distribution and at https://github.com/jjackowski/duds/blob/master/LICENSE.
5  * No part of DUDS, including this file, may be copied, modified, propagated,
6  * or distributed except according to the terms contained in the LICENSE file.
7  *
8  * Copyright (C) 2019 Jeff Jackowski
9  */
13 #include <duds/general/Errors.hpp>
14 
15 namespace duds { namespace ui { namespace menu {
16 
18 currSel(0), nextSel(0), nextSelOff(0), updateIdx(-1), outvUsers(0),
19 choseItem(false) { }
20 
21 void MenuView::attach(const std::shared_ptr<Menu> &menu) {
22  // already attached?
23  if (parent) {
24  // cannot be attached a second time
26  }
27  parent = menu;
28  parent->addView(shared_from_this());
29  title(parent->title());
30 }
31 
32 int MenuView::adv(int pos) {
33  int off = pos;
34  // find next selectable item
35  Menu::ItemVec::const_iterator mi = parent->iterator(off);
36  while ((mi != parent->items.end()) && !(*mi)->isSelectable()) {
37  // advanace toward end of menu
38  ++mi;
39  ++off;
40  }
41  // no selectable item?
42  if (mi == parent->items.end()) {
43  // look in the opposite direction
44  off = pos;
45  mi = parent->iterator(off);
46  while ((mi != parent->items.begin()) && !(*mi)->isSelectable()) {
47  // advanace toward start of menu
48  --mi;
49  --off;
50  }
51  }
52  // mi is now selectable or the start of the menu
53  assert((mi == parent->items.begin()) || (*mi)->isSelectable());
54  return off;
55 }
56 
57 int MenuView::retr(int pos) {
58  int off = pos;
59  // find next selectable item
60  Menu::ItemVec::const_iterator mi = parent->iterator(off);
61  while ((mi != parent->items.begin()) && !(*mi)->isSelectable()) {
62  // advanace toward start of menu
63  --mi;
64  --off;
65  }
66  // no selectable item?
67  if (mi == parent->items.begin() && !(*mi)->isSelectable()) {
68  // look in the opposite direction
69  off = pos;
70  mi = parent->iterator(off);
71  while ((mi != parent->items.end()) && !(*mi)->isSelectable()) {
72  // advanace toward end of menu
73  ++mi;
74  ++off;
75  }
76  // no selectable item?
77  if (mi == parent->items.end()) {
78  // stay in same bad spot
79  return pos;
80  }
81  }
82  // mi is now selectable or the start of the menu
83  assert((mi == parent->items.begin()) || (*mi)->isSelectable());
84  return off;
85 }
86 
88  std::lock_guard<duds::general::Spinlock> lock(block);
89  // only proceed if there is no other thread using this menu view
90  if (!outvUsers++) {
91  // nothing on the menu?
92  if (parent->empty()) {
93  // cancel any selection change request
94  nextSel = currSel = nextSelOff = 0;
95  choseItem = false;
96  // all done
97  --outvUsers;
98  return true;
99  }
100  // check for no need to update the current selection
101  int uidx = parent->updateIndex();
102  if (
103  // menu change
104  (uidx == updateIdx) &&
105  // item selection change
106  !choseItem &&
107  !nextSelOff &&
108  (nextSel == currSel)
109  ) {
110  // all done
111  --outvUsers;
112  return true;
113  }
114  // prepare to update the view; requires exclusive menu lock
115  MenuAccess ma(parent);
116  // record new update index; may have changed
117  updateIdx = parent->updateIndex();
118  // the new proposed position starts where indicated, even if the option
119  // cannot be selected
120  int prop = nextSel;
121  // adjust negative positions to be from the end of the menu
122  if (prop < 0) {
123  prop += parent->size();
124  // could have been given a very bad number
125  if (prop < 0) {
126  prop = 0;
127  }
128  }
129  // adjust too large a position to the start of the menu
130  if (prop >= parent->size()) {
131  prop -= parent->size();
132  // much too far?
133  if (prop >= parent->size()) {
134  prop = parent->size() - 1;
135  }
136  }
137  // no offset?
138  if (!nextSelOff) {
139  // item to select was within menu bounds?
140  if (prop == nextSel) {
141  Menu::ItemVec::const_iterator mi = parent->iterator(prop);
142  // not selectable?
143  if (!(*mi)->isSelectable()) {
144  // no change to selected item
145  prop = currSel;
146  }
147  }
148  // use the item if selectable, or find the next selectable item
149  prop = adv(prop);
150  } else {
151  // modify proposed item by the offset
152  // advance toward end of menu
153  if (nextSelOff > 0) {
154  // wrap check
155  if ((prop == parent->size() - 1) || (adv(prop + 1) == prop)) {
156  // select the first item
157  prop = adv(0);
158  } else {
159  // start at current location
160  Menu::ItemVec::const_iterator mi = parent->iterator(prop);
161  int off = prop;
162  // advance!
163  for (; nextSelOff && (mi != parent->items.end()); --nextSelOff) {
164  ++mi;
165  ++off;
166  // move past and do not count invisible items
167  while ((mi != parent->items.end()) && (*mi)->isInvisible()) {
168  ++mi;
169  ++off;
170  }
171  }
172  // not at end?
173  if (mi != parent->items.end()) {
174  // use the found item
175  prop = adv(off);
176  } else {
177  // use the last item
178  prop = retr(parent->size() - 1);
179  }
180  }
181  }
182  // advance toward start of menu
183  else {
184  // wrap check
185  if ((prop == 0) || (retr(prop - 1) == prop)) {
186  // select the last item
187  prop = retr(parent->size() - 1);
188  } else {
189  // start at current location
190  Menu::ItemVec::const_iterator mi = parent->iterator(prop);
191  int off = prop;
192  // advance backwards!
193  for (; nextSelOff && off; ++nextSelOff) {
194  --mi;
195  --off;
196  // move past and do not count invisible items
197  while (off && (*mi)->isInvisible()) {
198  --mi;
199  --off;
200  }
201  }
202  // not at start?
203  if (off) {
204  // use the found item
205  prop = retr(off);
206  } else {
207  // use the first item
208  prop = adv(0);
209  }
210  }
211  }
212  }
213  if (prop != currSel) {
214  try {
215  parent->iterator(currSel)->get()->deselect(*this, ma);
216  } catch (...) { }
217  try {
218  parent->iterator(prop)->get()->select(*this, ma);
219  } catch (...) { }
220  }
221  // prepare the next selection values to move from the current
222  // selection
223  nextSel = currSel = prop;
224  nextSelOff = 0;
225  // chose the item?
226  if (choseItem) {
227  // do not continue chosing
228  choseItem = false;
229  // invoke the menu item's chose function
230  try {
231  parent->iterator(currSel)->get()->chose(*this, ma);
232  } catch (...) {
233  // still must decrement user count
234  --outvUsers;
235  throw;
236  }
237  }
238  }
239  // updating is delayed if outvUsers is not zero after decrement
240  return !--outvUsers;
241 }
242 
243 void MenuView::insertion(std::size_t idx) {
244  // Really should lock block, but if a menu item attempts to remove or insert
245  // another menu item when invoked from update() above, then a lock will
246  // deadlock the thread. Trying to lock for a short time will mitigate the
247  // problem.
249  duds::general::UniqueYieldingSpinLock lock(syw, std::chrono::milliseconds(4));
250  // insertion after or at current selection?
251  if (currSel >= idx) {
252  // modify to track the same menu item
253  ++currSel;
254  }
255  // insertion after or at next selection?
256  if (nextSel >= idx) {
257  // modify to track the same menu item
258  ++nextSel;
259  }
260 }
261 
262 void MenuView::removal(std::size_t idx) {
263  // Really should lock block, but if a menu item attempts to remove or insert
264  // another menu item when invoked from update() above, then a lock will
265  // deadlock the thread. Trying to lock for a short time will mitigate the
266  // problem.
268  duds::general::UniqueYieldingSpinLock lock(syw, std::chrono::milliseconds(4));
269  // removal after current selection or both at end?
270  if (currSel && ((currSel > idx) || (parent->size() == currSel))) {
271  // modify to track the same menu item
272  --currSel;
273  }
274  // removal after next selection or both at end?
275  if (nextSel && ((nextSel > idx) || (parent->size() == nextSel))) {
276  // modify to track the same menu item
277  --nextSel;
278  }
279 }
280 
282  std::lock_guard<duds::general::Spinlock> lock(block);
283  ++outvUsers;
284 }
285 
287  std::lock_guard<duds::general::Spinlock> lock(block);
288  --outvUsers;
289 }
290 
291 void MenuView::backward(int dist) {
292  std::lock_guard<duds::general::Spinlock> lock(block);
293  // do not change selection further if an item is to be chosen
294  if (!choseItem) {
295  nextSelOff += dist;
296  }
297 }
298 
299 void MenuView::jump(int pos) {
300  std::lock_guard<duds::general::Spinlock> lock(block);
301  // do not change selection further if an item is to be chosen
302  if (!choseItem) {
303  // set the next selection to the indicated one
304  nextSel = pos;
305  // clear out relative selection change
306  nextSelOff = 0;
307  }
308 }
309 
311  std::lock_guard<duds::general::Spinlock> lock(block);
312  choseItem = true;
313 }
314 
316  std::lock_guard<duds::general::Spinlock> lock(block);
317  return choseItem || nextSelOff || (nextSel != currSel);
318 }
319 
320 } } }
void decUser()
Decrements the internal count of subviews currently accessing this menu view.
Definition: MenuView.cpp:286
void incUser()
Increments the internal count of subviews currently accessing this menu view.
Definition: MenuView.cpp:281
MenuView()
Constructs a new MenuView without attaching it to a Menu.
Definition: MenuView.cpp:17
void chose()
Queues a request to chose what will be the currently selected menu item during input processing when ...
Definition: MenuView.cpp:310
int updateIdx
The menu&#39;s update index value when this subview was last rendered.
Definition: MenuView.hpp:80
void attach(const std::shared_ptr< Menu > &menu)
Attaches the view to a menu so that the view can operate on the menu&#39;s data, and the menu can inform ...
Definition: MenuView.cpp:21
void insertion(std::size_t idx)
Responds to the menu inserting a menu item to a spot other than the end of the menu.
Definition: MenuView.cpp:243
A simple wrapper around a Spinlock object that implements the timed lockable concept such that attemp...
Definition: Spinlock.hpp:245
void removal(std::size_t idx)
Responds to the menu removing a menu item.
Definition: MenuView.cpp:262
int currSel
The index of the currently selected menu item.
Definition: MenuView.hpp:64
int adv(int pos)
Find the first menu item that is selectable, starting at and including pos, and advancing toward the ...
Definition: MenuView.cpp:32
std::unique_lock< duds::general::SpinlockYieldingWrapper > UniqueYieldingSpinLock
A convenience typedef for a std::unique_lock using the Spinlock yielding wrapper. ...
Definition: Spinlock.hpp:280
const std::shared_ptr< Menu > & menu() const
Returns the Menu used by this view.
Definition: MenuView.hpp:168
int retr(int pos)
Find the first menu item that is selectable, starting at and including pos, and advancing toward the ...
Definition: MenuView.cpp:57
std::shared_ptr< Menu > parent
The parent menu that supplies the MenuItems.
Definition: MenuView.hpp:55
bool update()
Updates the view&#39;s selected and chosen menu item if there are no MenuOutput objects currently renderi...
Definition: MenuView.cpp:87
int nextSel
The index of the next menu item to select.
Definition: MenuView.hpp:68
int nextSelOff
Offset from the next selection.
Definition: MenuView.hpp:76
duds::general::Spinlock block
Protects this object&#39;s data from inappropriate modification when used in a multithreaded manner...
Definition: MenuView.hpp:60
Provides an exclusive lock on a Menu to allow the menu to be changed.
Definition: MenuAccess.hpp:32
bool choseItem
Flag used to queue an input request to chose the selected item.
Definition: MenuView.hpp:84
const std::string & title() const
Returns the name or title of the page.
Definition: Page.hpp:50
int outvUsers
The number of MenuOutput objects currently using this MenuView.
Definition: MenuView.hpp:72
void backward(int dist=1)
Changes the selection toward the back (last item) of the menu.
Definition: MenuView.cpp:291
std::shared_ptr< MenuView > shared_from_this()
Helper function that returns a shared pointer to this object from the base class Page.
Definition: MenuView.hpp:382
General errors.
#define DUDS_THROW_EXCEPTION(x)
Works like BOOST_THROW_EXCEPTION, but includes a stack trace if DUDS_ERRORS_VERBOSE is defined...
Definition: Errors.hpp:48
bool queuedInput()
Returns true if any input for the menu view has been queued and is awaiting processing.
Definition: MenuView.cpp:315
void jump(int pos)
Jump to a particular option by position index.
Definition: MenuView.cpp:299
MenuView::attach() was called more than once on a MenuView object.
Definition: MenuErrors.hpp:67