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