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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
28 | ListWidget::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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
57 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
78 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
105 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
128 | const string& ListWidget::getSelectedString() const |
129 | { |
130 | return (_selectedItem >= 0 && _selectedItem < int(_list.size())) |
131 | ? _list[_selectedItem] : EmptyString; |
132 | } |
133 | |
134 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
135 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
151 | int ListWidget::getWidth() const |
152 | { |
153 | return _w + kScrollBarWidth; |
154 | } |
155 | |
156 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
157 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
188 | void ListWidget::scrollBarRecalc() |
189 | { |
190 | _scrollBar->_currentPos = _currentPos; |
191 | _scrollBar->recalc(); |
192 | sendCommand(ListWidget::kScrolledCmd, _currentPos, _id); |
193 | } |
194 | |
195 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
196 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
221 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
236 | void ListWidget::handleMouseWheel(int x, int y, int direction) |
237 | { |
238 | _scrollBar->handleMouseWheel(x, y, direction); |
239 | } |
240 | |
241 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
242 | int ListWidget::findItem(int x, int y) const |
243 | { |
244 | return (y - 1) / _fontHeight + _currentPos; |
245 | } |
246 | |
247 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
248 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
255 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
284 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
297 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
307 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
380 | void ListWidget::lostFocusWidget() |
381 | { |
382 | _editMode = false; |
383 | |
384 | // Reset to normal data entry |
385 | abortEditMode(); |
386 | } |
387 | |
388 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
389 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
407 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
437 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
450 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
465 | void ListWidget::abortEditMode() |
466 | { |
467 | // Undo any changes made |
468 | _editMode = false; |
469 | |
470 | // Reset to normal data entry |
471 | EditableWidget::abortEditMode(); |
472 | } |
473 | |