1 //============================================================================
2 //
3 //   SSSS    tt          lll  lll
4 //  SS  SS   tt           ll   ll
5 //  SS     tttttt  eeee   ll   ll   aaaa
6 //   SSSS    tt   ee  ee  ll   ll      aa
7 //      SS   tt   eeeeee  ll   ll   aaaaa  --  "An Atari 2600 VCS Emulator"
8 //  SS  SS   tt   ee      ll   ll  aa  aa
9 //   SSSS     ttt  eeeee llll llll  aaaaa
10 //
11 // Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
12 // and the Stella Team
13 //
14 // See the file "License.txt" for information on usage and redistribution of
15 // this file, and for a DISCLAIMER OF ALL WARRANTIES.
16 //============================================================================
17 
18 #include "OSystem.hxx"
19 #include "Widget.hxx"
20 #include "ScrollBarWidget.hxx"
21 #include "Dialog.hxx"
22 #include "FrameBuffer.hxx"
23 #include "StellaKeys.hxx"
24 #include "EventHandler.hxx"
25 #include "ListWidget.hxx"
26 
27 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ListWidget(GuiObject * boss,const GUI::Font & font,int x,int y,int w,int h,bool useScrollbar)28 ListWidget::ListWidget(GuiObject* boss, const GUI::Font& font,
29                        int x, int y, int w, int h, bool useScrollbar)
30   : EditableWidget(boss, font, x, y, 16, 16),
31     _useScrollbar{useScrollbar}
32 {
33   _flags = Widget::FLAG_ENABLED | Widget::FLAG_CLEARBG | Widget::FLAG_RETAIN_FOCUS;
34   _bgcolor = kWidColor;
35   _bgcolorhi = kWidColor;
36   _textcolor = kTextColor;
37   _textcolorhi = kTextColor;
38 
39   _editMode = false;
40 
41   _cols = w / _fontWidth;
42   _rows = h / _lineHeight;
43 
44   // Set real dimensions
45   _h = h + 2;
46 
47   // Create scrollbar and attach to the list
48   if(_useScrollbar)
49   {
50     _w = w - ScrollBarWidget::scrollBarWidth(_font);
51     _scrollBar = new ScrollBarWidget(boss, font, _x + _w, _y,
52                                      ScrollBarWidget::scrollBarWidth(_font), _h);
53     _scrollBar->setTarget(this);
54   }
55   else
56     _w = w - 1;
57 }
58 
59 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setHeight(int h)60 void ListWidget::setHeight(int h)
61 {
62   Widget::setHeight(h);
63   if(_useScrollbar)
64     _scrollBar->setHeight(h);
65 
66   _rows = (h - 2) / _lineHeight;
67   recalc();
68 }
69 
70 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setSelected(int item)71 void ListWidget::setSelected(int item)
72 {
73   setDirty();
74 
75   if(item < 0 || item >= int(_list.size()))
76     return;
77 
78   if(isEnabled())
79   {
80     if(_editMode)
81       abortEditMode();
82 
83     _selectedItem = item;
84     sendCommand(ListWidget::kSelectionChangedCmd, _selectedItem, _id);
85 
86     _currentPos = _selectedItem - _rows / 2;
87     scrollToSelected();
88   }
89 }
90 
91 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setSelected(const string & item)92 void ListWidget::setSelected(const string& item)
93 {
94   int selected = -1;
95   if(!_list.empty())
96   {
97     if(item == "")
98       selected = 0;
99     else
100     {
101       uInt32 itemToSelect = 0;
102       for(const auto& iter: _list)
103       {
104         if(item == iter)
105         {
106           selected = itemToSelect;
107           break;
108         }
109         ++itemToSelect;
110       }
111       if(itemToSelect > _list.size() || selected == -1)
112         selected = 0;
113     }
114   }
115   setSelected(selected);
116 }
117 
118 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setHighlighted(int item)119 void ListWidget::setHighlighted(int item)
120 {
121   if(item < -1 || item >= int(_list.size()))
122     return;
123 
124   if(isEnabled())
125   {
126     if(_editMode)
127       abortEditMode();
128 
129     _highlightedItem = item;
130 
131     // Only scroll the list if we're about to pass the page boundary
132     if(_currentPos == 0)
133       _currentPos = _highlightedItem;
134     else if(_highlightedItem == _currentPos + _rows)
135       _currentPos += _rows;
136 
137     scrollToHighlighted();
138   }
139 }
140 
141 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
getSelectedString() const142 const string& ListWidget::getSelectedString() const
143 {
144   return (_selectedItem >= 0 && _selectedItem < int(_list.size()))
145             ? _list[_selectedItem] : EmptyString;
146 }
147 
148 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
scrollTo(int item)149 void ListWidget::scrollTo(int item)
150 {
151   int size = int(_list.size());
152   if (item >= size)
153     item = size - 1;
154   if (item < 0)
155     item = 0;
156 
157   if(_currentPos != item)
158   {
159     _currentPos = item;
160     scrollBarRecalc();
161   }
162 }
163 
164 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
getWidth() const165 int ListWidget::getWidth() const
166 {
167   return _w + ScrollBarWidget::scrollBarWidth(_font);
168 }
169 
170 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
recalc()171 void ListWidget::recalc()
172 {
173   int size = int(_list.size());
174 
175   if(_currentPos >= size - _rows)
176   {
177     if(size <= _rows)
178       _currentPos = 0;
179     else
180       _currentPos = size - _rows;
181   }
182   if (_currentPos < 0)
183     _currentPos = 0;
184 
185   if(_selectedItem >= size)
186     _selectedItem = size - 1;
187   if(_selectedItem < 0)
188     _selectedItem = 0;
189 
190   _editMode = false;
191 
192   if(_useScrollbar)
193   {
194     _scrollBar->_numEntries = int(_list.size());
195     _scrollBar->_entriesPerPage = _rows;
196     // disable scrollbar if no longer necessary
197     scrollBarRecalc();
198   }
199 
200   // Reset to normal data entry
201   abortEditMode();
202 
203   setDirty();
204 }
205 
206 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
scrollBarRecalc()207 void ListWidget::scrollBarRecalc()
208 {
209   if(_useScrollbar)
210   {
211     _scrollBar->_currentPos = _currentPos;
212     _scrollBar->recalc();
213     sendCommand(ListWidget::kScrolledCmd, _currentPos, _id);
214   }
215 }
216 
217 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
handleMouseDown(int x,int y,MouseButton b,int clickCount)218 void ListWidget::handleMouseDown(int x, int y, MouseButton b, int clickCount)
219 {
220   if (!isEnabled())
221     return;
222 
223   resetSelection();
224   // First check whether the selection changed
225   int newSelectedItem;
226   newSelectedItem = findItem(x, y);
227   if (newSelectedItem >= int(_list.size()))
228     return;
229 
230   if (_selectedItem != newSelectedItem)
231   {
232     if (_editMode)
233       abortEditMode();
234     _selectedItem = newSelectedItem;
235     sendCommand(ListWidget::kSelectionChangedCmd, _selectedItem, _id);
236     setDirty();
237   }
238 
239   // TODO: Determine where inside the string the user clicked and place the
240   // caret accordingly. See _editScrollOffset and EditTextWidget::handleMouseDown.
241 }
242 
243 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
handleMouseUp(int x,int y,MouseButton b,int clickCount)244 void ListWidget::handleMouseUp(int x, int y, MouseButton b, int clickCount)
245 {
246   // If this was a double click and the mouse is still over the selected item,
247   // send the double click command
248   if (clickCount == 2 && (_selectedItem == findItem(x, y)))
249   {
250     sendCommand(ListWidget::kDoubleClickedCmd, _selectedItem, _id);
251 
252     // Start edit mode
253     if(isEditable() && !_editMode)
254       startEditMode();
255   }
256 }
257 
258 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
handleMouseWheel(int x,int y,int direction)259 void ListWidget::handleMouseWheel(int x, int y, int direction)
260 {
261   if(_useScrollbar)
262     _scrollBar->handleMouseWheel(x, y, direction);
263 }
264 
265 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
findItem(int x,int y) const266 int ListWidget::findItem(int x, int y) const
267 {
268   return (y - 1) / _lineHeight + _currentPos;
269 }
270 
271 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
handleText(char text)272 bool ListWidget::handleText(char text)
273 {
274   // Class EditableWidget handles all text editing related key presses for us
275   return _editMode ? EditableWidget::handleText(text) : true;
276 }
277 
278 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
handleKeyDown(StellaKey key,StellaMod mod)279 bool ListWidget::handleKeyDown(StellaKey key, StellaMod mod)
280 {
281   // Ignore all Alt-mod keys
282   if(StellaModTest::isAlt(mod))
283     return false;
284 
285   bool handled = true;
286   if (!_editMode)
287   {
288     switch(key)
289     {
290       case KBDK_SPACE:
291         // Snap list back to currently highlighted line
292         if(_highlightedItem >= 0)
293         {
294           _currentPos = _highlightedItem;
295           scrollToHighlighted();
296         }
297         break;
298 
299       default:
300         handled = false;
301     }
302   }
303 
304   return handled;
305 }
306 
307 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
handleJoyDown(int stick,int button,bool longPress)308 void ListWidget::handleJoyDown(int stick, int button, bool longPress)
309 {
310   if (longPress)
311     sendCommand(ListWidget::kLongButtonPressCmd, _selectedItem, _id);
312 }
313 
314 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
handleJoyUp(int stick,int button)315 void ListWidget::handleJoyUp(int stick, int button)
316 {
317   Event::Type e = _boss->instance().eventHandler().eventForJoyButton(EventMode::kMenuMode, stick, button);
318 
319   handleEvent(e);
320 }
321 
322 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
handleEvent(Event::Type e)323 bool ListWidget::handleEvent(Event::Type e)
324 {
325   if(!isEnabled() || _editMode)
326     return false;
327 
328   bool handled = true;
329   int oldSelectedItem = _selectedItem;
330   int size = int(_list.size());
331 
332   switch(e)
333   {
334     case Event::UISelect:
335       if (_selectedItem >= 0)
336       {
337         if (isEditable())
338           startEditMode();
339         else
340           sendCommand(ListWidget::kActivatedCmd, _selectedItem, _id);
341       }
342       break;
343 
344     case Event::UIUp:
345       if (_selectedItem > 0)
346         _selectedItem--;
347       break;
348 
349     case Event::UIDown:
350       if (_selectedItem < size - 1)
351         _selectedItem++;
352       break;
353 
354     case Event::UIPgUp:
355     case Event::UILeft:
356       _selectedItem -= _rows - 1;
357       if (_selectedItem < 0)
358         _selectedItem = 0;
359       break;
360 
361     case Event::UIPgDown:
362     case Event::UIRight:
363       _selectedItem += _rows - 1;
364       if (_selectedItem >= size)
365         _selectedItem = size - 1;
366       break;
367 
368     case Event::UIHome:
369       _selectedItem = 0;
370       break;
371 
372     case Event::UIEnd:
373       _selectedItem = size - 1;
374       break;
375 
376     case Event::UIPrevDir:
377       sendCommand(ListWidget::kPrevDirCmd, _selectedItem, _id);
378       break;
379 
380     default:
381       handled = false;
382   }
383 
384   if (_selectedItem != oldSelectedItem)
385   {
386     if(_useScrollbar)
387     {
388       _scrollBar->draw();
389       scrollToSelected();
390     }
391 
392     sendCommand(ListWidget::kSelectionChangedCmd, _selectedItem, _id);
393   }
394 
395   return handled;
396 }
397 
398 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
lostFocusWidget()399 void ListWidget::lostFocusWidget()
400 {
401   _editMode = false;
402 
403   // Reset to normal data entry
404   abortEditMode();
405 }
406 
407 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
handleCommand(CommandSender * sender,int cmd,int data,int id)408 void ListWidget::handleCommand(CommandSender* sender, int cmd, int data, int id)
409 {
410   if (cmd == GuiObject::kSetPositionCmd)
411   {
412     if (_currentPos != data)
413     {
414       _currentPos = data;
415       setDirty();
416 
417       // Let boss know the list has scrolled
418       sendCommand(ListWidget::kScrolledCmd, _currentPos, _id);
419     }
420   }
421 }
422 
423 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
scrollToCurrent(int item)424 void ListWidget::scrollToCurrent(int item)
425 {
426   // Only do something if the current item is not in our view port
427   if (item < _currentPos)
428   {
429     // it's above our view
430     _currentPos = item;
431   }
432   else if (item >= _currentPos + _rows )
433   {
434     // it's below our view
435     _currentPos = item - _rows + 1;
436   }
437 
438   if (_currentPos < 0 || _rows > int(_list.size()))
439     _currentPos = 0;
440   else if (_currentPos + _rows > int(_list.size()))
441     _currentPos = int(_list.size()) - _rows;
442 
443   if(_useScrollbar)
444   {
445     int oldScrollPos = _scrollBar->_currentPos;
446     _scrollBar->_currentPos = _currentPos;
447     _scrollBar->recalc();
448 
449     setDirty();
450 
451     if(oldScrollPos != _currentPos)
452       sendCommand(ListWidget::kScrolledCmd, _currentPos, _id);
453   }
454 }
455 
456 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
startEditMode()457 void ListWidget::startEditMode()
458 {
459   if (isEditable() && !_editMode && _selectedItem >= 0)
460   {
461     _editMode = true;
462     setText(_list[_selectedItem]);
463 
464     // Widget gets raw data while editing
465     EditableWidget::startEditMode();
466   }
467 }
468 
469 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
endEditMode()470 void ListWidget::endEditMode()
471 {
472   if (!_editMode)
473     return;
474 
475   // Send a message that editing finished with a return/enter key press
476   _editMode = false;
477   _list[_selectedItem] = editString();
478   sendCommand(ListWidget::kDataChangedCmd, _selectedItem, _id);
479 
480   // Reset to normal data entry
481   EditableWidget::endEditMode();
482 }
483 
484 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
abortEditMode()485 void ListWidget::abortEditMode()
486 {
487   // Undo any changes made
488   _editMode = false;
489 
490   // Reset to normal data entry
491   EditableWidget::abortEditMode();
492 }
493