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