1 // Written in the D programming language.
2
3 /**
4 This module contains list widgets implementation.
5
6 Similar to lists implementation in Android UI API.
7
8 Synopsis:
9
10 ----
11 import dlangui.widgets.lists;
12
13 ----
14
15 Copyright: Vadim Lopatin, 2014
16 License: Boost License 1.0
17 Authors: Vadim Lopatin, coolreader.org@gmail.com
18 */
19 module dlangui.widgets.lists;
20
21 import dlangui.widgets.widget;
22 import dlangui.widgets.controls;
23 import dlangui.widgets.scrollbar;
24 import dlangui.widgets.layouts;
25 import dlangui.core.signals;
26
27
28 /** interface - slot for onAdapterChangeListener */
29 interface OnAdapterChangeHandler {
30 void onAdapterChange(ListAdapter source);
31 }
32
33
34 /// list widget adapter provides items for list widgets
35 interface ListAdapter {
36 /// returns number of widgets in list
37 @property int itemCount() const;
38 /// return list item widget by item index
39 Widget itemWidget(int index);
40 /// return list item's state flags
41 uint itemState(int index) const;
42 /// set one or more list item's state flags, returns updated state
43 uint setItemState(int index, uint flags);
44 /// reset one or more list item's state flags, returns updated state
45 uint resetItemState(int index, uint flags);
46 /// returns integer item id by index (if supported)
47 int itemId(int index) const;
48 /// returns string item id by index (if supported)
49 string itemStringId(int index) const;
50
51 /// remove all items
52 void clear();
53
54 /// connect adapter change handler
55 ListAdapter connect(OnAdapterChangeHandler handler);
56 /// disconnect adapter change handler
57 ListAdapter disconnect(OnAdapterChangeHandler handler);
58
59 /// called when theme is changed
60 void onThemeChanged();
61
62 /// return true to receive mouse events
63 @property bool wantMouseEvents();
64 /// return true to receive keyboard events
65 @property bool wantKeyEvents();
66 }
67
68 /// List adapter for simple list of widget instances
69 class ListAdapterBase : ListAdapter {
70 /** Handle items change */
71 protected Signal!OnAdapterChangeHandler adapterChanged;
72
73 /// connect adapter change handler
74 override ListAdapter connect(OnAdapterChangeHandler handler) {
75 adapterChanged.connect(handler);
76 return this;
77 }
78 /// disconnect adapter change handler
79 override ListAdapter disconnect(OnAdapterChangeHandler handler) {
80 adapterChanged.disconnect(handler);
81 return this;
82 }
83 /// returns integer item id by index (if supported)
84 override int itemId(int index) const {
85 return 0;
86 }
87 /// returns string item id by index (if supported)
88 override string itemStringId(int index) const {
89 return null;
90 }
91
92 /// returns number of widgets in list
93 override @property int itemCount() const {
94 // override it
95 return 0;
96 }
97
98 /// return list item widget by item index
99 override Widget itemWidget(int index) {
100 // override it
101 return null;
102 }
103
104 /// return list item's state flags
105 override uint itemState(int index) const {
106 // override it
107 return State.Enabled;
108 }
109 /// set one or more list item's state flags, returns updated state
110 override uint setItemState(int index, uint flags) {
111 return 0;
112 }
113 /// reset one or more list item's state flags, returns updated state
114 override uint resetItemState(int index, uint flags) {
115 return 0;
116 }
117
118 /// remove all items
119 override void clear() {
120 }
121
122 /// notify listeners about list items changes
123 void updateViews() {
124 if (adapterChanged.assigned)
125 adapterChanged.emit(this);
126 }
127
128 /// called when theme is changed
129 void onThemeChanged() {
130 }
131
132 /// return true to receive mouse events
133 override @property bool wantMouseEvents() {
134 return false;
135 }
136
137 /// return true to receive keyboard events
138 override @property bool wantKeyEvents() {
139 return false;
140 }
141 }
142
143 /// List adapter for simple list of widget instances
144 class WidgetListAdapter : ListAdapterBase {
145 private WidgetList _widgets;
146 /// list of widgets to display
147 @property ref const(WidgetList) widgets() { return _widgets; }
148 /// returns number of widgets in list
149 @property override int itemCount() const {
150 return _widgets.count;
151 }
152 /// return list item widget by item index
153 override Widget itemWidget(int index) {
154 return _widgets.get(index);
155 }
156 /// return list item's state flags
157 override uint itemState(int index) const {
158 return _widgets.get(index).state;
159 }
160 /// set one or more list item's state flags, returns updated state
161 override uint setItemState(int index, uint flags) {
162 return _widgets.get(index).setState(flags).state;
163 }
164 /// reset one or more list item's state flags, returns updated state
165 override uint resetItemState(int index, uint flags) {
166 return _widgets.get(index).resetState(flags).state;
167 }
168 /// add item
169 WidgetListAdapter add(Widget item, int index = -1) {
170 _widgets.insert(item, index);
171 updateViews();
172 return this;
173 }
174 /// remove item
175 WidgetListAdapter remove(int index) {
176 auto item = _widgets.remove(index);
177 destroy(item);
178 updateViews();
179 return this;
180 }
181 /// remove all items
182 override void clear() {
183 _widgets.clear();
184 updateViews();
185 }
186 /// called when theme is changed
187 override void onThemeChanged() {
188 super.onThemeChanged();
189 foreach(w; _widgets)
190 w.onThemeChanged();
191 }
192 ~this() {
193 //Log.d("Destroying WidgetListAdapter");
194 }
195
196 /// return true to receive mouse events
197 override @property bool wantMouseEvents() {
198 return true;
199 }
200 }
201
202 /** List adapter providing strings only. */
203 class StringListAdapterBase : ListAdapterBase {
204 protected UIStringCollection _items;
205 protected uint[] _states;
206 protected int[] _intIds;
207 protected string[] _stringIds;
208 protected string[] _iconIds;
209 protected int _lastItemIndex;
210
211 /** create empty string list adapter. */
212 this() {
213 _lastItemIndex = -1;
214 }
215
216 /** Init with array of string resource IDs. */
217 this(string[] items) {
218 _items.addAll(items);
219 _intIds.length = items.length;
220 _stringIds.length = items.length;
221 _iconIds.length = items.length;
222 _lastItemIndex = -1;
223 updateStatesLength();
224 }
225
226 /** Init with array of unicode strings. */
227 this(dstring[] items) {
228 _items.addAll(items);
229 _intIds.length = items.length;
230 _stringIds.length = items.length;
231 _iconIds.length = items.length;
232 _lastItemIndex = -1;
233 updateStatesLength();
234 }
235
236 /** Init with array of StringListValue. */
237 this(StringListValue[] items) {
238 _intIds.length = items.length;
239 _stringIds.length = items.length;
240 _iconIds.length = items.length;
241 for (int i = 0; i < items.length; i++) {
242 _items.add(items[i].label);
243 _intIds[i] = items[i].intId;
244 _stringIds[i] = items[i].stringId;
245 _iconIds[i] = items[i].iconId;
246 }
247 _lastItemIndex = -1;
248 updateStatesLength();
249 }
250
251 /// remove all items
252 override void clear() {
253 _items.clear();
254 updateStatesLength();
255 updateViews();
256 }
257
258 /// remove item by index
259 StringListAdapterBase remove(int index) {
260 if (index < 0 || index >= _items.length)
261 return this;
262 for (int i = 0; i < _items.length - 1; i++) {
263 _intIds[i] = _intIds[i + 1];
264 _stringIds[i] = _stringIds[i + 1];
265 _iconIds[i] = _iconIds[i + 1];
266 _states[i] = _states[i + 1];
267 }
268 _items.remove(index);
269 _intIds.length = items.length;
270 _states.length = _items.length;
271 _stringIds.length = items.length;
272 _iconIds.length = items.length;
273 updateViews();
274 return this;
275 }
276
277 /// add new item
278 StringListAdapterBase add(UIString item, int index = -1) {
279 if (index < 0 || index > _items.length)
280 index = _items.length;
281 _items.add(item, index);
282 _intIds.length = items.length;
283 _states.length = _items.length;
284 _stringIds.length = items.length;
285 _iconIds.length = items.length;
286 for (int i = _items.length - 1; i > index; i--) {
287 _intIds[i] = _intIds[i - 1];
288 _stringIds[i] = _stringIds[i - 1];
289 _iconIds[i] = _iconIds[i - 1];
290 _states[i] = _states[i - 1];
291 }
292 _intIds[index] = 0;
293 _stringIds[index] = null;
294 _iconIds[index] = null;
295 _states[index] = State.Enabled;
296 updateViews();
297 return this;
298 }
299 /// add new string resource item
300 StringListAdapterBase add(string item, int index = -1) {
301 return add(UIString.fromId(item), index);
302 }
303 /// add new raw dstring item
304 StringListAdapterBase add(dstring item, int index = -1) {
305 return add(UIString.fromRaw(item), index);
306 }
307
308 /** Access to items collection. */
309 @property ref const(UIStringCollection) items() { return _items; }
310
311 /** Replace items collection. */
312 @property StringListAdapterBase items(dstring[] values) {
313 _items = values;
314 _intIds.length = items.length;
315 _states.length = _items.length;
316 _stringIds.length = items.length;
317 _iconIds.length = items.length;
318 for (int i = 0; i < _items.length; i++) {
319 _intIds[i] = 0;
320 _stringIds[i] = null;
321 _iconIds[i] = null;
322 _states[i] = State.Enabled;
323 }
324 updateViews();
325 return this;
326 }
327
328 /** Replace items collection. */
329 @property StringListAdapterBase items(UIString[] values) {
330 _items = values;
331 _intIds.length = items.length;
332 _states.length = _items.length;
333 _stringIds.length = items.length;
334 _iconIds.length = items.length;
335 for (int i = 0; i < _items.length; i++) {
336 _intIds[i] = 0;
337 _stringIds[i] = null;
338 _iconIds[i] = null;
339 _states[i] = State.Enabled;
340 }
341 updateViews();
342 return this;
343 }
344
345 /** Replace items collection. */
346 @property StringListAdapterBase items(StringListValue[] values) {
347 _items = values;
348 _intIds.length = items.length;
349 _states.length = _items.length;
350 _stringIds.length = items.length;
351 _iconIds.length = items.length;
352 for (int i = 0; i < _items.length; i++) {
353 _intIds[i] = values[i].intId;
354 _stringIds[i] = values[i].stringId;
355 _iconIds[i] = values[i].iconId;
356 _states[i] = State.Enabled;
357 }
358 updateViews();
359 return this;
360 }
361
362 /// returns number of widgets in list
363 @property override int itemCount() const {
364 return _items.length;
365 }
366
367 /// returns integer item id by index (if supported)
368 override int itemId(int index) const {
369 return index >= 0 && index < _intIds.length ? _intIds[index] : 0;
370 }
371
372 /// returns string item id by index (if supported)
373 override string itemStringId(int index) const {
374 return index >= 0 && index < _stringIds.length ? _stringIds[index] : null;
375 }
376
377 protected void updateStatesLength() {
378 if (_states.length < _items.length) {
379 int oldlen = cast(int)_states.length;
380 _states.length = _items.length;
381 for (int i = oldlen; i < _items.length; i++)
382 _states[i] = State.Enabled;
383 }
384 if (_intIds.length < items.length)
385 _intIds.length = items.length;
386 if (_stringIds.length < items.length)
387 _stringIds.length = items.length;
388 if (_iconIds.length < items.length)
389 _iconIds.length = items.length;
390 }
391
392 /// return list item's state flags
393 override uint itemState(int index) const {
394 if (index < 0 || index >= _items.length)
395 return 0;
396 return _states[index];
397 }
398
399 /// set one or more list item's state flags, returns updated state
400 override uint setItemState(int index, uint flags) {
401 updateStatesLength();
402 _states[index] |= flags;
403 return _states[index];
404 }
405 /// reset one or more list item's state flags, returns updated state
406 override uint resetItemState(int index, uint flags) {
407 updateStatesLength();
408 _states[index] &= ~flags;
409 return _states[index];
410 }
411
412 ~this() {
413 }
414 }
415
416 /** List adapter providing strings only. */
417 class StringListAdapter : StringListAdapterBase {
418 protected TextWidget _widget;
419
420 /** create empty string list adapter. */
421 this() {
422 super();
423 }
424
425 /** Init with array of string resource IDs. */
426 this(string[] items) {
427 super(items);
428 }
429
430 /** Init with array of unicode strings. */
431 this(dstring[] items) {
432 super(items);
433 }
434
435 /** Init with array of StringListValue. */
436 this(StringListValue[] items) {
437 super(items);
438 }
439
440 /// return list item widget by item index
441 override Widget itemWidget(int index) {
442 updateStatesLength();
443 if (_widget is null) {
444 _widget = new TextWidget("STRING_LIST_ITEM");
445 _widget.styleId = STYLE_LIST_ITEM;
446 } else {
447 if (index == _lastItemIndex)
448 return _widget;
449 }
450 // update widget
451 _widget.text = _items.get(index);
452 _widget.state = _states[index];
453 _lastItemIndex = index;
454 return _widget;
455 }
456
457 /// called when theme is changed
458 override void onThemeChanged() {
459 super.onThemeChanged();
460 if (_widget)
461 _widget.onThemeChanged();
462 }
463
464 /// set one or more list item's state flags, returns updated state
465 override uint setItemState(int index, uint flags) {
466 uint res = super.setItemState(index, flags);
467 if (_widget !is null && _lastItemIndex == index)
468 _widget.state = res;
469 return res;
470 }
471
472
473
474 /// reset one or more list item's state flags, returns updated state
475 override uint resetItemState(int index, uint flags) {
476 uint res = super.resetItemState(index, flags);
477 if (_widget !is null && _lastItemIndex == index)
478 _widget.state = res;
479 return res;
480 }
481
482 ~this() {
483 if (_widget)
484 destroy(_widget);
485 }
486 }
487
488 /** List adapter providing strings with icons. */
489 class IconStringListAdapter : StringListAdapterBase {
490 protected HorizontalLayout _widget;
491 protected TextWidget _textWidget;
492 protected ImageWidget _iconWidget;
493
494 /** create empty string list adapter. */
495 this() {
496 super();
497 }
498
499 /** Init with array of StringListValue. */
500 this(StringListValue[] items) {
501 super(items);
502 }
503
504 /// return list item widget by item index
505 override Widget itemWidget(int index) {
506 updateStatesLength();
507 if (_widget is null) {
508 _widget = new HorizontalLayout("ICON_STRING_LIST_ITEM");
509 _widget.styleId = STYLE_LIST_ITEM;
510 _textWidget = new TextWidget("label");
511 _iconWidget = new ImageWidget("icon");
512 _widget.addChild(_iconWidget);
513 _widget.addChild(_textWidget);
514 } else {
515 if (index == _lastItemIndex)
516 return _widget;
517 }
518 // update widget
519 _textWidget.text = _items.get(index);
520 _textWidget.state = _states[index];
521 if (_iconIds[index]) {
522 _iconWidget.visibility = Visibility.Visible;
523 _iconWidget.drawableId = _iconIds[index];
524 } else {
525 _iconWidget.visibility = Visibility.Gone;
526 }
527 _lastItemIndex = index;
528 return _widget;
529 }
530
531 /// called when theme is changed
532 override void onThemeChanged() {
533 super.onThemeChanged();
534 if (_widget)
535 _widget.onThemeChanged();
536 }
537
538 /// set one or more list item's state flags, returns updated state
539 override uint setItemState(int index, uint flags) {
540 uint res = super.setItemState(index, flags);
541 if (_widget !is null && _lastItemIndex == index) {
542 _widget.state = res;
543 _textWidget.state = res;
544 }
545 return res;
546 }
547
548 /// reset one or more list item's state flags, returns updated state
549 override uint resetItemState(int index, uint flags) {
550 uint res = super.resetItemState(index, flags);
551 if (_widget !is null && _lastItemIndex == index) {
552 _widget.state = res;
553 _textWidget.state = res;
554 }
555 return res;
556 }
557
558 ~this() {
559 if (_widget)
560 destroy(_widget);
561 }
562 }
563
564 /** interface - slot for onItemSelectedListener */
565 interface OnItemSelectedHandler {
566 bool onItemSelected(Widget source, int itemIndex);
567 }
568
569 /** interface - slot for onItemClickListener */
570 interface OnItemClickHandler {
571 bool onItemClick(Widget source, int itemIndex);
572 }
573
574
575 /** List widget - shows content as hori*/
576 class ListWidget : WidgetGroup, OnScrollHandler, OnAdapterChangeHandler {
577
578 /** Handle selection change. */
579 Signal!OnItemSelectedHandler itemSelected;
580 /** Handle item click / activation (e.g. Space or Enter key press and mouse double click) */
581 Signal!OnItemClickHandler itemClick;
582
583 protected Orientation _orientation = Orientation.Vertical;
584 /// returns linear layout orientation (Vertical, Horizontal)
585 @property Orientation orientation() { return _orientation; }
586 /// sets linear layout orientation
587 @property ListWidget orientation(Orientation value) {
588 _orientation = value;
589 _scrollbar.orientation = value;
590 requestLayout();
591 return this;
592 }
593
594 protected Rect[] _itemRects;
595 protected Point[] _itemSizes;
596 protected bool _needScrollbar;
597 protected Point _sbsz; // scrollbar size
598 protected ScrollBar _scrollbar;
599 protected int _lastMeasureWidth;
600 protected int _lastMeasureHeight;
601
602 /// first visible item index
603 protected int _firstVisibleItem;
604 /// scroll position - offset of scroll area
605 protected int _scrollPosition;
606 /// maximum scroll position
607 protected int _maxScrollPosition;
608 /// client area rectangle (counting padding, margins, and scrollbar)
609 protected Rect _clientRc;
610 /// total height of all items for Vertical orientation, or width for Horizontal
611 protected int _totalSize;
612 /// item with Hover state, -1 if no such item
613 protected int _hoverItemIndex;
614 /// item with Selected state, -1 if no such item
615 protected int _selectedItemIndex;
616
617 /// when true, mouse hover selects underlying item
618 protected bool _selectOnHover;
619 /// when true, mouse hover selects underlying item
620 @property bool selectOnHover() { return _selectOnHover; }
621 /// when true, mouse hover selects underlying item
622 @property ListWidget selectOnHover(bool select) { _selectOnHover = select; return this; }
623
624 /// if true, generate itemClicked on mouse down instead mouse up event
625 protected bool _clickOnButtonDown;
626
627 /// returns rectangle for item (not scrolled, first item starts at 0,0)
628 Rect itemRectNoScroll(int index) {
629 if (index < 0 || index >= _itemRects.length)
630 return Rect.init;
631 Rect res;
632 res = _itemRects[index];
633 return res;
634 }
635
636 /// returns rectangle for item (scrolled)
637 Rect itemRect(int index) {
638 if (index < 0 || index >= _itemRects.length)
639 return Rect.init;
640 Rect res = itemRectNoScroll(index);
641 if (_orientation == Orientation.Horizontal) {
642 res.left -= _scrollPosition;
643 res.right -= _scrollPosition;
644 } else {
645 res.top -= _scrollPosition;
646 res.bottom -= _scrollPosition;
647 }
648 return res;
649 }
650
651 /// returns item index by 0-based offset from top/left of list content
652 int itemByPosition(int pos) {
653 return 0;
654 }
655
656 protected ListAdapter _adapter;
657 /// when true, need to destroy adapter on list destroy
658 protected bool _ownAdapter;
659
660 /// get adapter
661 @property ListAdapter adapter() { return _adapter; }
662 /// set adapter
663 @property ListWidget adapter(ListAdapter adapter) {
664 if (_adapter is adapter)
665 return this; // no changes
666 if (_adapter)
667 _adapter.disconnect(this);
668 if (_adapter !is null && _ownAdapter)
669 destroy(_adapter);
670 _adapter = adapter;
671 if (_adapter)
672 _adapter.connect(this);
673 _ownAdapter = false;
674 onAdapterChange(_adapter);
675 return this;
676 }
677 /// set adapter, which will be owned by list (destroy will be called for adapter on widget destroy)
678 @property ListWidget ownAdapter(ListAdapter adapter) {
679 if (_adapter is adapter)
680 return this; // no changes
681 if (_adapter)
682 _adapter.disconnect(this);
683 if (_adapter !is null && _ownAdapter)
684 destroy(_adapter);
685 _adapter = adapter;
686 if (_adapter)
687 _adapter.connect(this);
688 _ownAdapter = true;
689 onAdapterChange(_adapter);
690 return this;
691 }
692
693 /// returns number of widgets in list
694 @property int itemCount() {
695 if (_adapter !is null)
696 return _adapter.itemCount;
697 return 0;
698 }
699
700 /// return list item widget by item index
701 Widget itemWidget(int index) {
702 if (_adapter !is null)
703 return _adapter.itemWidget(index);
704 return null;
705 }
706
707 /// returns true if item with corresponding index is enabled
708 bool itemEnabled(int index) {
709 if (_adapter !is null && index >= 0 && index < itemCount)
710 return (_adapter.itemState(index) & State.Enabled) != 0;
711 return false;
712 }
713
714 /// empty parameter list constructor - for usage by factory
715 this() {
716 this(null);
717 }
718 /// create with ID parameter
719 this(string ID, Orientation orientation = Orientation.Vertical) {
720 super(ID);
721 _orientation = orientation;
722 focusable = true;
723 _hoverItemIndex = -1;
724 _selectedItemIndex = -1;
725 _scrollbar = new ScrollBar("listscroll", orientation);
726 _scrollbar.visibility = Visibility.Gone;
727 _scrollbar.scrollEvent = &onScrollEvent;
728 addChild(_scrollbar);
729 }
730
731 protected void setHoverItem(int index) {
732 if (_hoverItemIndex == index)
733 return;
734 if (_hoverItemIndex != -1) {
735 _adapter.resetItemState(_hoverItemIndex, State.Hovered);
736 invalidate();
737 }
738 _hoverItemIndex = index;
739 if (_hoverItemIndex != -1) {
740 _adapter.setItemState(_hoverItemIndex, State.Hovered);
741 invalidate();
742 }
743 }
744
745 /// item list is changed
746 override void onAdapterChange(ListAdapter source) {
747 requestLayout();
748 }
749
750 /// override to handle change of selection
751 protected void selectionChanged(int index, int previouslySelectedItem = -1) {
752 if (itemSelected.assigned)
753 itemSelected(this, index);
754 }
755
756 /// override to handle mouse up on item
757 protected void itemClicked(int index) {
758 if (itemClick.assigned)
759 itemClick(this, index);
760 }
761
762 /// allow to override state for updating of items
763 // currently used to treat main menu items with opened submenu as focused
764 @property protected uint overrideStateForItem() {
765 return state;
766 }
767
768 protected void updateSelectedItemFocus() {
769 if (_selectedItemIndex != -1) {
770 if ((_adapter.itemState(_selectedItemIndex) & State.Focused) != (overrideStateForItem & State.Focused)) {
771 if (overrideStateForItem & State.Focused)
772 _adapter.setItemState(_selectedItemIndex, State.Focused);
773 else
774 _adapter.resetItemState(_selectedItemIndex, State.Focused);
775 invalidate();
776 }
777 }
778 }
779
780 /// override to handle focus changes
781 override protected void handleFocusChange(bool focused, bool receivedFocusFromKeyboard = false) {
782 updateSelectedItemFocus();
783 }
784
785 /// ensure selected item is visible (scroll if necessary)
786 void makeSelectionVisible() {
787 if (_selectedItemIndex < 0)
788 return; // no selection
789 if (needLayout) {
790 _makeSelectionVisibleOnNextLayout = true;
791 return;
792 }
793 makeItemVisible(_selectedItemIndex);
794 }
795
796 protected bool _makeSelectionVisibleOnNextLayout;
797 /// ensure item is visible
798 void makeItemVisible(int itemIndex) {
799 if (itemIndex < 0 || itemIndex >= itemCount)
800 return; // no selection
801
802 Rect viewrc = Rect(0, 0, _clientRc.width, _clientRc.height);
803 Rect scrolledrc = itemRect(itemIndex);
804 if (scrolledrc.isInsideOf(viewrc)) // completely visible
805 return;
806 int delta = 0;
807 if (_orientation == Orientation.Vertical) {
808 if (scrolledrc.top < viewrc.top)
809 delta = scrolledrc.top - viewrc.top;
810 else if (scrolledrc.bottom > viewrc.bottom)
811 delta = scrolledrc.bottom - viewrc.bottom;
812 } else {
813 if (scrolledrc.left < viewrc.left)
814 delta = scrolledrc.left - viewrc.left;
815 else if (scrolledrc.right > viewrc.right)
816 delta = scrolledrc.right - viewrc.right;
817 }
818 int newPosition = _scrollPosition + delta;
819 _scrollbar.position = newPosition;
820 _scrollPosition = newPosition;
821 invalidate();
822 }
823
824 /// move selection
825 bool moveSelection(int direction, bool wrapAround = true) {
826 if (itemCount <= 0)
827 return false;
828 int maxAttempts = itemCount - 1;
829 int index = _selectedItemIndex;
830 for (int i = 0; i < maxAttempts; i++) {
831 int newIndex = 0;
832 if (index < 0) {
833 // no previous selection
834 if (direction > 0)
835 newIndex = wrapAround ? 0 : itemCount - 1;
836 else
837 newIndex = wrapAround ? itemCount - 1 : 0;
838 } else {
839 // step
840 newIndex = index + direction;
841 }
842 if (newIndex < 0)
843 newIndex = wrapAround ? itemCount - 1 : 0;
844 else if (newIndex >= itemCount)
845 newIndex = wrapAround ? 0 : itemCount - 1;
846 if (newIndex != index) {
847 if (selectItem(newIndex)) {
848 selectionChanged(_selectedItemIndex, index);
849 return true;
850 }
851 index = newIndex;
852 }
853 }
854 return true;
855 }
856
857 bool selectItem(int index, int disabledItemsSkipDirection) {
858 //debug Log.d("selectItem ", index, " skipDirection=", disabledItemsSkipDirection);
859 if (index == -1 || disabledItemsSkipDirection == 0)
860 return selectItem(index);
861 int maxAttempts = itemCount;
862 for (int i = 0; i < maxAttempts; i++) {
863 if (selectItem(index))
864 return true;
865 index += disabledItemsSkipDirection > 0 ? 1 : -1;
866 if (index < 0)
867 index = itemCount - 1;
868 if (index >= itemCount)
869 index = 0;
870 }
871 return false;
872 }
873
874 /** Selected item index. */
875 @property int selectedItemIndex() {
876 return _selectedItemIndex;
877 }
878
879 @property void selectedItemIndex(int index) {
880 selectItem(index);
881 }
882
883 bool selectItem(int index) {
884 //debug Log.d("selectItem ", index);
885 if (_selectedItemIndex == index) {
886 updateSelectedItemFocus();
887 makeSelectionVisible();
888 return true;
889 }
890 if (index != -1 && !itemEnabled(index))
891 return false;
892 if (_selectedItemIndex != -1) {
893 _adapter.resetItemState(_selectedItemIndex, State.Selected | State.Focused);
894 invalidate();
895 }
896 _selectedItemIndex = index;
897 if (_selectedItemIndex != -1) {
898 makeSelectionVisible();
899 _adapter.setItemState(_selectedItemIndex, State.Selected | (overrideStateForItem & State.Focused));
900 invalidate();
901 }
902 return true;
903 }
904
905 ~this() {
906 if (_adapter)
907 _adapter.disconnect(this);
908 //Log.d("Destroying List ", _id);
909 if (_adapter !is null && _ownAdapter)
910 destroy(_adapter);
911 _adapter = null;
912 }
913
914 /// handle scroll event
915 override bool onScrollEvent(AbstractSlider source, ScrollEvent event) {
916 int newPosition = _scrollPosition;
917 if (event.action == ScrollAction.SliderMoved) {
918 // scroll
919 newPosition = event.position;
920 } else {
921 // use default handler for page/line up/down events
922 newPosition = event.defaultUpdatePosition();
923 }
924 if (_scrollPosition != newPosition) {
925 _scrollPosition = newPosition;
926 if (_scrollPosition > _maxScrollPosition)
927 _scrollPosition = _maxScrollPosition;
928 if (_scrollPosition < 0)
929 _scrollPosition = 0;
930 invalidate();
931 }
932 return true;
933 }
934
935 /// handle theme change: e.g. reload some themed resources
936 override void onThemeChanged() {
937 super.onThemeChanged();
938 _scrollbar.onThemeChanged();
939 for (int i = 0; i < itemCount; i++) {
940 Widget w = itemWidget(i);
941 w.onThemeChanged();
942 }
943 if (_adapter)
944 _adapter.onThemeChanged();
945 }
946
947 /// sets minimum size for the list, override to change
948 Point minimumVisibleContentSize() {
949 if (_orientation == Orientation.Vertical)
950 return Point(measureMinChildrenSize().x, 100);
951 else
952 return Point(100, measureMinChildrenSize().y);
953 }
954
955 protected Point measureMinChildrenSize() {
956 // measure children
957 Point sz;
958 for (int i = 0; i < itemCount; i++) {
959 Widget w = itemWidget(i);
960 if (w is null || w.visibility == Visibility.Gone)
961 continue;
962
963 w.measure(SIZE_UNSPECIFIED, SIZE_UNSPECIFIED);
964 if (_orientation == Orientation.Vertical) {
965 // Vertical
966 if (sz.x < w.measuredWidth)
967 sz.x = w.measuredWidth;
968 sz.y += w.measuredHeight;
969 } else {
970 // Horizontal
971 if (sz.y < w.measuredHeight)
972 sz.y = w.measuredHeight;
973 sz.x += w.measuredWidth;
974 }
975 }
976 return sz;
977 }
978
979 /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout).
980 override void measure(int parentWidth, int parentHeight) {
981 if (visibility == Visibility.Gone) {
982 _measuredWidth = _measuredHeight = 0;
983 return;
984 }
985 if (_itemSizes.length < itemCount)
986 _itemSizes.length = itemCount;
987 Rect m = margins;
988 Rect p = padding;
989
990 // set widget area to small when first measure
991 if (parentWidth == SIZE_UNSPECIFIED && parentHeight == SIZE_UNSPECIFIED)
992 {
993 Point sz = minimumVisibleContentSize;
994 measuredContent(parentWidth, parentHeight, sz.x, sz.y);
995 return;
996 }
997
998 // calc size constraints for children
999 int pwidth = parentWidth;
1000 int pheight = parentHeight;
1001 if (parentWidth != SIZE_UNSPECIFIED)
1002 pwidth -= m.left + m.right + p.left + p.right;
1003 if (parentHeight != SIZE_UNSPECIFIED)
1004 pheight -= m.top + m.bottom + p.top + p.bottom;
1005
1006 bool oldNeedLayout = _needLayout;
1007 Visibility oldScrollbarVisibility = _scrollbar.visibility;
1008
1009 _scrollbar.visibility = Visibility.Visible;
1010 _scrollbar.measure(pwidth, pheight);
1011
1012 _lastMeasureWidth = pwidth;
1013 _lastMeasureHeight = pheight;
1014
1015 int sbsize = _orientation == Orientation.Vertical ? _scrollbar.measuredWidth : _scrollbar.measuredHeight;
1016 // measure children
1017 Point sz;
1018 _sbsz.destroy();
1019 for (int i = 0; i < itemCount; i++) {
1020 Widget w = itemWidget(i);
1021 if (w is null || w.visibility == Visibility.Gone) {
1022 _itemSizes[i].x = _itemSizes[i].y = 0;
1023 continue;
1024 }
1025 w.measure(pwidth, pheight);
1026 _itemSizes[i].x = w.measuredWidth;
1027 _itemSizes[i].y = w.measuredHeight;
1028 if (_orientation == Orientation.Vertical) {
1029 // Vertical
1030 if (sz.x < w.measuredWidth)
1031 sz.x = w.measuredWidth;
1032 sz.y += w.measuredHeight;
1033 } else {
1034 // Horizontal
1035 if (sz.y < w.measuredHeight)
1036 sz.y = w.measuredHeight;
1037 sz.x += w.measuredWidth;
1038 }
1039 }
1040 _needScrollbar = false;
1041 if (_orientation == Orientation.Vertical) {
1042 if (pheight != SIZE_UNSPECIFIED && sz.y > pheight) {
1043 // need scrollbar
1044 if (pwidth != SIZE_UNSPECIFIED) {
1045 pwidth -= sbsize;
1046 _sbsz.x = sbsize;
1047 _needScrollbar = true;
1048 }
1049 }
1050 } else {
1051 if (pwidth != SIZE_UNSPECIFIED && sz.x > pwidth) {
1052 // need scrollbar
1053 if (pheight != SIZE_UNSPECIFIED) {
1054 pheight -= sbsize;
1055 _sbsz.y = sbsize;
1056 _needScrollbar = true;
1057 }
1058 }
1059 }
1060 if (_needScrollbar) {
1061 // recalculate with scrollbar
1062 sz.x = sz.y = 0;
1063 for (int i = 0; i < itemCount; i++) {
1064 Widget w = itemWidget(i);
1065 if (w is null || w.visibility == Visibility.Gone)
1066 continue;
1067 w.measure(pwidth, pheight);
1068 _itemSizes[i].x = w.measuredWidth;
1069 _itemSizes[i].y = w.measuredHeight;
1070 if (_orientation == Orientation.Vertical) {
1071 // Vertical
1072 if (sz.x < w.measuredWidth)
1073 sz.x = w.measuredWidth;
1074 sz.y += w.measuredHeight;
1075 } else {
1076 // Horizontal
1077 w.measure(pwidth, pheight);
1078 if (sz.y < w.measuredHeight)
1079 sz.y = w.measuredHeight;
1080 sz.x += w.measuredWidth;
1081 }
1082 }
1083 }
1084 measuredContent(parentWidth, parentHeight, sz.x + _sbsz.x, sz.y + _sbsz.y);
1085
1086 if (_scrollbar.visibility == oldScrollbarVisibility) {
1087 _needLayout = oldNeedLayout;
1088 _scrollbar.cancelLayout();
1089 }
1090 }
1091
1092
1093 protected void updateItemPositions() {
1094 Rect r;
1095 int p = 0;
1096 for (int i = 0; i < itemCount; i++) {
1097 if (_itemSizes[i].x == 0 && _itemSizes[i].y == 0)
1098 continue;
1099 if (_orientation == Orientation.Vertical) {
1100 // Vertical
1101 int w = _clientRc.width;
1102 int h = _itemSizes[i].y;
1103 r.top = p;
1104 r.bottom = p + h;
1105 r.left = 0;
1106 r.right = w;
1107 _itemRects[i] = r;
1108 p += h;
1109 } else {
1110 // Horizontal
1111 int h = _clientRc.height;
1112 int w = _itemSizes[i].x;
1113 r.top = 0;
1114 r.bottom = h;
1115 r.left = p;
1116 r.right = p + w;
1117 _itemRects[i] = r;
1118 p += w;
1119 }
1120 }
1121 _totalSize = p;
1122 if (_needScrollbar) {
1123 if (_orientation == Orientation.Vertical) {
1124 _scrollbar.setRange(0, p);
1125 _scrollbar.pageSize = _clientRc.height;
1126 _scrollbar.position = _scrollPosition;
1127 } else {
1128 _scrollbar.setRange(0, p);
1129 _scrollbar.pageSize = _clientRc.width;
1130 _scrollbar.position = _scrollPosition;
1131 }
1132 }
1133 /// maximum scroll position
1134 if (_orientation == Orientation.Vertical) {
1135 _maxScrollPosition = _totalSize - _clientRc.height;
1136 if (_maxScrollPosition < 0)
1137 _maxScrollPosition = 0;
1138 } else {
1139 _maxScrollPosition = _totalSize - _clientRc.width;
1140 if (_maxScrollPosition < 0)
1141 _maxScrollPosition = 0;
1142 }
1143 if (_scrollPosition > _maxScrollPosition)
1144 _scrollPosition = _maxScrollPosition;
1145 if (_scrollPosition < 0)
1146 _scrollPosition = 0;
1147 if (_needScrollbar) {
1148 if (_orientation == Orientation.Vertical) { // FIXME:
1149 _scrollbar.position = _scrollPosition;
1150 } else {
1151 _scrollbar.position = _scrollPosition;
1152 }
1153 }
1154 }
1155
1156 /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout).
1157 override void layout(Rect rc) {
1158 _needLayout = false;
1159 if (visibility == Visibility.Gone) {
1160 return;
1161 }
1162 _pos = rc;
1163
1164 Rect parentrc = rc;
1165 applyMargins(rc);
1166 applyPadding(rc);
1167
1168 if (_itemRects.length < itemCount)
1169 _itemRects.length = itemCount;
1170
1171 // measure again if client size has been changed
1172 if (_lastMeasureWidth != rc.width || _lastMeasureHeight != rc.height)
1173 measure(parentrc.width, parentrc.height);
1174
1175 // hide scrollbar or update rc for scrollbar
1176 Rect sbrect = rc;
1177 // layout scrollbar
1178 if (_needScrollbar) {
1179 rc.right -= _sbsz.x;
1180 rc.bottom -= _sbsz.y;
1181 } else {
1182 _scrollbar.visibility = Visibility.Gone;
1183 }
1184
1185 _clientRc = rc;
1186
1187 // calc item rectangles
1188 updateItemPositions();
1189
1190 // layout scrollbar - must be under updateItemPositions()
1191 if (_needScrollbar) {
1192 _scrollbar.visibility = Visibility.Visible;
1193 if (_orientation == Orientation.Vertical)
1194 sbrect.left = sbrect.right - _sbsz.x;
1195 else
1196 sbrect.top = sbrect.bottom - _sbsz.y;
1197 _scrollbar.layout(sbrect);
1198 }
1199
1200 if (_makeSelectionVisibleOnNextLayout) {
1201 makeSelectionVisible();
1202 _makeSelectionVisibleOnNextLayout = false;
1203 }
1204 _needLayout = false;
1205 _scrollbar.cancelLayout();
1206 }
1207
1208 /// Draw widget at its position to buffer
1209 override void onDraw(DrawBuf buf) {
1210 if (visibility != Visibility.Visible)
1211 return;
1212 super.onDraw(buf);
1213 Rect rc = _pos;
1214 applyMargins(rc);
1215 applyPadding(rc);
1216 auto saver = ClipRectSaver(buf, rc, alpha);
1217 // draw scrollbar
1218 if (_needScrollbar)
1219 _scrollbar.onDraw(buf);
1220
1221 Point scrollOffset;
1222 if (_orientation == Orientation.Vertical) {
1223 scrollOffset.y = _scrollPosition;
1224 } else {
1225 scrollOffset.x = _scrollPosition;
1226 }
1227 // draw items
1228 for (int i = 0; i < itemCount; i++) {
1229 Rect itemrc = _itemRects[i];
1230 itemrc.left += rc.left - scrollOffset.x;
1231 itemrc.right += rc.left - scrollOffset.x;
1232 itemrc.top += rc.top - scrollOffset.y;
1233 itemrc.bottom += rc.top - scrollOffset.y;
1234 if (itemrc.intersects(rc)) {
1235 Widget w = itemWidget(i);
1236 if (w is null || w.visibility != Visibility.Visible)
1237 continue;
1238 w.layout(itemrc);
1239 w.onDraw(buf);
1240 }
1241 }
1242 }
1243
1244 /// list navigation using keys
1245 override bool onKeyEvent(KeyEvent event) {
1246 if (itemCount == 0)
1247 return false;
1248 int navigationDelta = 0;
1249 if (event.action == KeyAction.KeyDown) {
1250 if (orientation == Orientation.Vertical) {
1251 if (event.keyCode == KeyCode.DOWN)
1252 navigationDelta = 1;
1253 else if (event.keyCode == KeyCode.UP)
1254 navigationDelta = -1;
1255 } else {
1256 if (event.keyCode == KeyCode.RIGHT)
1257 navigationDelta = 1;
1258 else if (event.keyCode == KeyCode.LEFT)
1259 navigationDelta = -1;
1260 }
1261 }
1262 if (navigationDelta != 0) {
1263 moveSelection(navigationDelta);
1264 return true;
1265 }
1266 if (event.action == KeyAction.KeyDown) {
1267 if (event.keyCode == KeyCode.HOME) {
1268 // select first enabled item on HOME key
1269 selectItem(0, 1);
1270 return true;
1271 } else if (event.keyCode == KeyCode.END) {
1272 // select last enabled item on END key
1273 selectItem(itemCount - 1, -1);
1274 return true;
1275 } else if (event.keyCode == KeyCode.PAGEDOWN) {
1276 // TODO
1277 } else if (event.keyCode == KeyCode.PAGEUP) {
1278 // TODO
1279 }
1280 }
1281 if ((event.keyCode == KeyCode.SPACE || event.keyCode == KeyCode.RETURN)) {
1282 if (event.action == KeyAction.KeyDown && enabled) {
1283 if (itemEnabled(_selectedItemIndex)) {
1284 itemClicked(_selectedItemIndex);
1285 }
1286 }
1287 return true;
1288 }
1289 return super.onKeyEvent(event);
1290 //if (_selectedItemIndex != -1 && event.action == KeyAction.KeyUp && (event.keyCode == KeyCode.SPACE || event.keyCode == KeyCode.RETURN)) {
1291 // itemClicked(_selectedItemIndex);
1292 // return true;
1293 //}
1294 //if (navigationDelta != 0) {
1295 // int p = _selectedItemIndex;
1296 // if (p < 0) {
1297 // if (navigationDelta < 0)
1298 // p = itemCount - 1;
1299 // else
1300 // p = 0;
1301 // } else {
1302 // p += navigationDelta;
1303 // if (p < 0)
1304 // p = itemCount - 1;
1305 // else if (p >= itemCount)
1306 // p = 0;
1307 // }
1308 // setHoverItem(-1);
1309 // selectItem(p);
1310 // return true;
1311 //}
1312 //return false;
1313 }
1314
1315 /// process mouse event; return true if event is processed by widget.
1316 override bool onMouseEvent(MouseEvent event) {
1317 //Log.d("onMouseEvent ", id, " ", event.action, " (", event.x, ",", event.y, ")");
1318 super.onMouseEvent(event);
1319 if (event.action == MouseAction.Leave || event.action == MouseAction.Cancel) {
1320 setHoverItem(-1);
1321 return true;
1322 }
1323 // delegate processing of mouse wheel to scrollbar widget
1324 if (event.action == MouseAction.Wheel && _needScrollbar) {
1325 return _scrollbar.onMouseEvent(event);
1326 }
1327 // support onClick
1328 Rect rc = _pos;
1329 applyMargins(rc);
1330 applyPadding(rc);
1331 Point scrollOffset;
1332 if (_orientation == Orientation.Vertical) {
1333 scrollOffset.y = _scrollPosition;
1334 } else {
1335 scrollOffset.x = _scrollPosition;
1336 }
1337 if (event.action == MouseAction.Wheel) {
1338 if (_scrollbar)
1339 _scrollbar.sendScrollEvent(event.wheelDelta > 0 ? ScrollAction.LineUp : ScrollAction.LineDown);
1340 return true;
1341 }
1342 if (event.action == MouseAction.ButtonDown && (event.flags & (MouseFlag.LButton || MouseFlag.RButton)))
1343 setFocus();
1344 if (itemCount > _itemRects.length)
1345 return true; // layout not yet called
1346 for (int i = 0; i < itemCount; i++) {
1347 Rect itemrc = _itemRects[i];
1348 itemrc.left += rc.left - scrollOffset.x;
1349 itemrc.right += rc.left - scrollOffset.x;
1350 itemrc.top += rc.top - scrollOffset.y;
1351 itemrc.bottom += rc.top - scrollOffset.y;
1352 if (itemrc.isPointInside(Point(event.x, event.y))) {
1353 if (_adapter && _adapter.wantMouseEvents) {
1354 auto itemWidget = _adapter.itemWidget(i);
1355 if (itemWidget) {
1356 Widget oldParent = itemWidget.parent;
1357 itemWidget.parent = this;
1358 if (event.action == MouseAction.Move && event.noModifiers && itemWidget.hasTooltip) {
1359 itemWidget.scheduleTooltip(200);
1360 }
1361 //itemWidget.onMouseEvent(event);
1362 itemWidget.parent = oldParent;
1363 }
1364 }
1365 //Log.d("mouse event action=", event.action, " button=", event.button, " flags=", event.flags);
1366 if ((event.flags & (MouseFlag.LButton || MouseFlag.RButton)) || _selectOnHover) {
1367 if (_selectedItemIndex != i && itemEnabled(i)) {
1368 int prevSelection = _selectedItemIndex;
1369 selectItem(i);
1370 setHoverItem(-1);
1371 selectionChanged(_selectedItemIndex, prevSelection);
1372 }
1373 } else {
1374 if (itemEnabled(i))
1375 setHoverItem(i);
1376 }
1377 if (event.button == MouseButton.Left || event.button == MouseButton.Right) {
1378 if ((_clickOnButtonDown && event.action == MouseAction.ButtonDown) || (!_clickOnButtonDown && event.action == MouseAction.ButtonUp)) {
1379 if (itemEnabled(i)) {
1380 itemClicked(i);
1381 if (_clickOnButtonDown)
1382 event.doNotTrackButtonDown = true;
1383 }
1384 }
1385 }
1386 return true;
1387 }
1388 }
1389 return true;
1390 }
1391 /// returns true if item is child of this widget (when deepSearch == true - returns true if item is this widget or one of children inside children tree).
1392 override bool isChild(Widget item, bool deepSearch = true) {
1393 if (_adapter && _adapter.wantMouseEvents) {
1394 for (int i = 0; i < itemCount; i++) {
1395 auto itemWidget = _adapter.itemWidget(i);
1396 if (itemWidget is item)
1397 return true;
1398 }
1399 }
1400 return super.isChild(item, deepSearch);
1401 }
1402 }
1403
1404 class StringListWidget : ListWidget {
1405 import std.conv : to;
1406 import std.datetime.stopwatch : StopWatch;
1407 import core.time : dur;
1408 private dstring _searchString;
1409 private StopWatch _stopWatch;
1410
1411 this(string ID = null) {
1412 super(ID);
1413 styleId = STYLE_EDIT_BOX;
1414 clickable = true;
1415 }
1416
1417 this(string ID, string[] items) {
1418 super(ID);
1419 styleId = STYLE_EDIT_BOX;
1420 ownAdapter = new StringListAdapter(items);
1421 clickable = true;
1422 }
1423
1424 this(string ID, dstring[] items) {
1425 super(ID);
1426 styleId = STYLE_EDIT_BOX;
1427 ownAdapter = new StringListAdapter(items);
1428 clickable = true;
1429 }
1430
1431 this(string ID, StringListValue[] items) {
1432 super(ID);
1433 styleId = STYLE_EDIT_BOX;
1434 ownAdapter = new StringListAdapter(items);
1435 clickable = true;
1436 }
1437
1438 @property void items(string[] itemResourceIds) {
1439 _selectedItemIndex = -1;
1440 ownAdapter = new StringListAdapter(itemResourceIds);
1441 if(itemResourceIds.length > 0) {
1442 selectedItemIndex = 0;
1443 }
1444 requestLayout();
1445 }
1446
1447 @property void items(dstring[] items) {
1448 _selectedItemIndex = -1;
1449 ownAdapter = new StringListAdapter(items);
1450 if(items.length > 0) {
1451 selectedItemIndex = 0;
1452 }
1453 requestLayout();
1454 }
1455
1456 @property void items(StringListValue[] items) {
1457 _selectedItemIndex = -1;
1458 ownAdapter = new StringListAdapter(items);
1459 if(items.length > 0) {
1460 selectedItemIndex = 0;
1461 }
1462 requestLayout();
1463 }
1464
1465 /// StringListValue list values
1466 override bool setStringListValueListProperty(string propName, StringListValue[] values) {
1467 if (propName == "items") {
1468 items = values;
1469 return true;
1470 }
1471 return false;
1472 }
1473
1474 /// get selected item as text
1475 @property dstring selectedItem() {
1476 if (_selectedItemIndex < 0 || _selectedItemIndex >= _adapter.itemCount)
1477 return "";
1478 return (cast(StringListAdapter)adapter).items.get(_selectedItemIndex);
1479 }
1480
1481 override bool onKeyEvent(KeyEvent event) {
1482 if (itemCount == 0) return false;
1483
1484 // Accept user input and try to find a match in the list.
1485 if (event.action == KeyAction.Text) {
1486 if ( !_stopWatch.running) { _stopWatch.start; }
1487
1488 version(DigitalMars) {
1489 auto timePassed = _stopWatch.peek; //.to!("seconds", float)(); // dtop is std.datetime.to
1490
1491 if (timePassed > dur!"msecs"(500)) _searchString = ""d;
1492 } else {
1493 auto timePassed = (cast(float)_stopWatch.peek.total!"msecs")/1000.0f;
1494
1495 if (timePassed > 0.5) _searchString = ""d; // TODO_GRIM: make everything look-alike
1496 }
1497 _searchString ~= to!dchar(event.text.toUTF8);
1498 _stopWatch.reset;
1499
1500 if ( selectClosestMatch(_searchString) ) {
1501 invalidate();
1502 return true;
1503 }
1504 }
1505
1506 return super.onKeyEvent(event);
1507 }
1508
1509
1510 private bool selectClosestMatch(dstring term) {
1511 import std.uni : toLower;
1512 if (term.length == 0) return false;
1513 auto myItems = (cast(StringListAdapter)adapter).items;
1514
1515 // Perfect match or best match
1516 int[] indexes;
1517 foreach(int itemIndex; 0 .. myItems.length) {
1518 dstring item = myItems.get(itemIndex);
1519
1520 if (item == term) {
1521 // Perfect match
1522 indexes ~= itemIndex;
1523 break;
1524 } else {
1525 // Term approximate to something
1526 bool addItem = true;
1527 foreach(int termIndex; 0 .. cast(int)term.length) {
1528 if (termIndex < item.length) {
1529 if ( toLower(term[termIndex]) != toLower(item[termIndex]) ) {
1530 addItem = false;
1531 break;
1532 }
1533 }
1534 }
1535
1536 if (addItem) { indexes ~= itemIndex; }
1537
1538 }
1539 }
1540
1541 // Return best match
1542 if (indexes.length > 0) {
1543 selectItem(indexes[0]);
1544 itemSelected(this, indexes[0]);
1545 return true;
1546 }
1547
1548 return false; // Did not find term
1549
1550 }
1551
1552
1553
1554 }
1555
1556 //import dlangui.widgets.metadata;
1557 //mixin(registerWidgets!(ListWidget, StringListWidget)());