aboutsummaryrefslogtreecommitdiff
path: root/gui/widgets/list.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gui/widgets/list.cpp')
-rw-r--r--gui/widgets/list.cpp734
1 files changed, 734 insertions, 0 deletions
diff --git a/gui/widgets/list.cpp b/gui/widgets/list.cpp
new file mode 100644
index 0000000000..1e13226d61
--- /dev/null
+++ b/gui/widgets/list.cpp
@@ -0,0 +1,734 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/system.h"
+#include "common/events.h"
+#include "common/frac.h"
+#include "common/tokenizer.h"
+
+#include "gui/widgets/list.h"
+#include "gui/widgets/scrollbar.h"
+#include "gui/dialog.h"
+#include "gui/gui-manager.h"
+
+#include "gui/ThemeEval.h"
+
+namespace GUI {
+
+ListWidget::ListWidget(Dialog *boss, const String &name, const char *tooltip, uint32 cmd)
+ : EditableWidget(boss, name, tooltip), _cmd(cmd) {
+
+ _scrollBar = NULL;
+ _textWidth = NULL;
+
+ // This ensures that _entriesPerPage is properly initialised.
+ reflowLayout();
+
+ _scrollBar = new ScrollBarWidget(this, _w - _scrollBarWidth + 1, 0, _scrollBarWidth, _h);
+ _scrollBar->setTarget(this);
+
+ setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_WANT_TICKLE);
+ _type = kListWidget;
+ _editMode = false;
+ _numberingMode = kListNumberingOne;
+ _currentPos = 0;
+ _selectedItem = -1;
+ _currentKeyDown = 0;
+
+ _quickSelectTime = 0;
+
+ // The item is selected, thus _bgcolor is used to draw the caret and _textcolorhi to erase it
+ _caretInverse = true;
+
+ // FIXME: This flag should come from widget definition
+ _editable = true;
+
+ _quickSelect = true;
+ _editColor = ThemeEngine::kFontColorNormal;
+}
+
+ListWidget::ListWidget(Dialog *boss, int x, int y, int w, int h, const char *tooltip, uint32 cmd)
+ : EditableWidget(boss, x, y, w, h, tooltip), _cmd(cmd) {
+
+ _scrollBar = NULL;
+ _textWidth = NULL;
+
+ // This ensures that _entriesPerPage is properly initialised.
+ reflowLayout();
+
+ _scrollBar = new ScrollBarWidget(this, _w - _scrollBarWidth + 1, 0, _scrollBarWidth, _h);
+ _scrollBar->setTarget(this);
+
+ setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_WANT_TICKLE);
+ _type = kListWidget;
+ _editMode = false;
+ _numberingMode = kListNumberingOne;
+ _currentPos = 0;
+ _selectedItem = -1;
+ _currentKeyDown = 0;
+
+ _quickSelectTime = 0;
+
+ // The item is selected, thus _bgcolor is used to draw the caret and _textcolorhi to erase it
+ _caretInverse = true;
+
+ // FIXME: This flag should come from widget definition
+ _editable = true;
+}
+
+ListWidget::~ListWidget() {
+ delete[] _textWidth;
+}
+
+Widget *ListWidget::findWidget(int x, int y) {
+ if (x >= _w - _scrollBarWidth)
+ return _scrollBar;
+
+ return this;
+}
+
+void ListWidget::setSelected(int item) {
+ // HACK/FIXME: If our _listIndex has a non zero size,
+ // we will need to look up, whether the user selected
+ // item is present in that list
+ if (_listIndex.size()) {
+ int filteredItem = -1;
+
+ for (uint i = 0; i < _listIndex.size(); ++i) {
+ if (_listIndex[i] == item) {
+ filteredItem = i;
+ break;
+ }
+ }
+
+ item = filteredItem;
+ }
+
+ assert(item >= -1 && item < (int)_list.size());
+
+ // We only have to do something if the widget is enabled and the selection actually changes
+ if (isEnabled() && _selectedItem != item) {
+ if (_editMode)
+ abortEditMode();
+
+ _selectedItem = item;
+
+ // Notify clients that the selection changed.
+ sendCommand(kListSelectionChangedCmd, _selectedItem);
+
+ _currentPos = _selectedItem - _entriesPerPage / 2;
+ scrollToCurrent();
+ draw();
+ }
+}
+
+ThemeEngine::FontColor ListWidget::getSelectionColor() const {
+ if (_listColors.empty())
+ return ThemeEngine::kFontColorNormal;
+
+ if (_filter.empty())
+ return _listColors[_selectedItem];
+ else
+ return _listColors[_listIndex[_selectedItem]];
+}
+
+void ListWidget::setList(const StringArray &list, const ColorList *colors) {
+ if (_editMode && _caretVisible)
+ drawCaret(true);
+
+ // Copy everything
+ _dataList = list;
+ _list = list;
+ _filter.clear();
+ _listIndex.clear();
+ _listColors.clear();
+
+ if (colors) {
+ _listColors = *colors;
+ assert(_listColors.size() == _dataList.size());
+ }
+
+ int size = list.size();
+ if (_currentPos >= size)
+ _currentPos = size - 1;
+ if (_currentPos < 0)
+ _currentPos = 0;
+ _selectedItem = -1;
+ _editMode = false;
+ g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);
+ scrollBarRecalc();
+}
+
+void ListWidget::append(const String &s, ThemeEngine::FontColor color) {
+ if (_dataList.size() == _listColors.size()) {
+ // If the color list has the size of the data list, we append the color.
+ _listColors.push_back(color);
+ } else if (!_listColors.size() && color != ThemeEngine::kFontColorNormal) {
+ // If it's the first entry to use a non default color, we will fill
+ // up all other entries of the color list with the default color and
+ // add the requested color for the new entry.
+ for (uint i = 0; i < _dataList.size(); ++i)
+ _listColors.push_back(ThemeEngine::kFontColorNormal);
+ _listColors.push_back(color);
+ }
+
+ _dataList.push_back(s);
+ _list.push_back(s);
+
+ setFilter(_filter, false);
+
+ scrollBarRecalc();
+}
+
+void ListWidget::scrollTo(int item) {
+ int size = _list.size();
+ if (item >= size)
+ item = size - 1;
+ if (item < 0)
+ item = 0;
+
+ if (_currentPos != item) {
+ _currentPos = item;
+ scrollBarRecalc();
+ }
+}
+
+void ListWidget::scrollBarRecalc() {
+ _scrollBar->_numEntries = _list.size();
+ _scrollBar->_entriesPerPage = _entriesPerPage;
+ _scrollBar->_currentPos = _currentPos;
+ _scrollBar->recalc();
+}
+
+void ListWidget::handleTickle() {
+ if (_editMode)
+ EditableWidget::handleTickle();
+}
+
+void ListWidget::handleMouseDown(int x, int y, int button, int clickCount) {
+ if (!isEnabled())
+ return;
+
+ // First check whether the selection changed
+ int newSelectedItem = findItem(x, y);
+
+ if (_selectedItem != newSelectedItem && newSelectedItem != -1) {
+ if (_editMode)
+ abortEditMode();
+ _selectedItem = newSelectedItem;
+ sendCommand(kListSelectionChangedCmd, _selectedItem);
+ }
+
+ // TODO: Determine where inside the string the user clicked and place the
+ // caret accordingly.
+ // See _editScrollOffset and EditTextWidget::handleMouseDown.
+ draw();
+
+}
+
+void ListWidget::handleMouseUp(int x, int y, int button, int clickCount) {
+ // If this was a double click and the mouse is still over
+ // the selected item, send the double click command
+ if (clickCount == 2 && (_selectedItem == findItem(x, y)) &&
+ _selectedItem >= 0) {
+ sendCommand(kListItemDoubleClickedCmd, _selectedItem);
+ }
+}
+
+void ListWidget::handleMouseWheel(int x, int y, int direction) {
+ _scrollBar->handleMouseWheel(x, y, direction);
+}
+
+
+int ListWidget::findItem(int x, int y) const {
+ if (y < _topPadding) return -1;
+ int item = (y - _topPadding) / kLineHeight + _currentPos;
+ if (item >= _currentPos && item < _currentPos + _entriesPerPage &&
+ item < (int)_list.size())
+ return item;
+ else
+ return -1;
+}
+
+static int matchingCharsIgnoringCase(const char *x, const char *y, bool &stop) {
+ int match = 0;
+ while (*x && *y && tolower(*x) == tolower(*y)) {
+ ++x;
+ ++y;
+ ++match;
+ }
+ stop = !*y || (*x && (tolower(*x) >= tolower(*y)));
+ return match;
+}
+
+bool ListWidget::handleKeyDown(Common::KeyState state) {
+ bool handled = true;
+ bool dirty = false;
+ int oldSelectedItem = _selectedItem;
+
+ if (!_editMode && state.keycode <= Common::KEYCODE_z && isprint((unsigned char)state.ascii)) {
+ // Quick selection mode: Go to first list item starting with this key
+ // (or a substring accumulated from the last couple key presses).
+ // Only works in a useful fashion if the list entries are sorted.
+ uint32 time = g_system->getMillis();
+ if (_quickSelectTime < time) {
+ _quickSelectStr = (char)state.ascii;
+ } else {
+ _quickSelectStr += (char)state.ascii;
+ }
+ _quickSelectTime = time + 300; // TODO: Turn this into a proper constant (kQuickSelectDelay ?)
+
+ if (_quickSelect) {
+ // FIXME: This is bad slow code (it scans the list linearly each time a
+ // key is pressed); it could be much faster. Only of importance if we have
+ // quite big lists to deal with -- so for now we can live with this lazy
+ // implementation :-)
+ int newSelectedItem = 0;
+ int bestMatch = 0;
+ bool stop;
+ for (StringArray::const_iterator i = _list.begin(); i != _list.end(); ++i) {
+ const int match = matchingCharsIgnoringCase(i->c_str(), _quickSelectStr.c_str(), stop);
+ if (match > bestMatch || stop) {
+ _selectedItem = newSelectedItem;
+ bestMatch = match;
+ if (stop)
+ break;
+ }
+ newSelectedItem++;
+ }
+
+ scrollToCurrent();
+ } else {
+ sendCommand(_cmd, 0);
+ }
+ } else if (_editMode) {
+ // Class EditableWidget handles all text editing related key presses for us
+ handled = EditableWidget::handleKeyDown(state);
+ } else {
+ // not editmode
+
+ switch (state.keycode) {
+ case Common::KEYCODE_RETURN:
+ case Common::KEYCODE_KP_ENTER:
+ if (_selectedItem >= 0) {
+ // override continuous enter keydown
+ if (_editable && (_currentKeyDown != Common::KEYCODE_RETURN && _currentKeyDown != Common::KEYCODE_KP_ENTER)) {
+ dirty = true;
+ startEditMode();
+ } else
+ sendCommand(kListItemActivatedCmd, _selectedItem);
+ }
+ break;
+
+ // Keypad & special keys
+ // - if num lock is set, we do not handle the keypress
+ // - if num lock is not set, we either fall down to the special key case
+ // or ignore the key press for 0, 4, 5 and 6
+
+ case Common::KEYCODE_KP_PERIOD:
+ if (state.flags & Common::KBD_NUM) {
+ handled = false;
+ break;
+ }
+ case Common::KEYCODE_BACKSPACE:
+ case Common::KEYCODE_DELETE:
+ if (_selectedItem >= 0) {
+ if (_editable) {
+ // Ignore delete and backspace when the list item is editable
+ } else {
+ sendCommand(kListItemRemovalRequestCmd, _selectedItem);
+ }
+ }
+ break;
+
+ case Common::KEYCODE_KP1:
+ if (state.flags & Common::KBD_NUM) {
+ handled = false;
+ break;
+ }
+ case Common::KEYCODE_END:
+ _selectedItem = _list.size() - 1;
+ break;
+
+
+ case Common::KEYCODE_KP2:
+ if (state.flags & Common::KBD_NUM) {
+ handled = false;
+ break;
+ }
+ case Common::KEYCODE_DOWN:
+ if (_selectedItem < (int)_list.size() - 1)
+ _selectedItem++;
+ break;
+
+ case Common::KEYCODE_KP3:
+ if (state.flags & Common::KBD_NUM) {
+ handled = false;
+ break;
+ }
+ case Common::KEYCODE_PAGEDOWN:
+ _selectedItem += _entriesPerPage - 1;
+ if (_selectedItem >= (int)_list.size() )
+ _selectedItem = _list.size() - 1;
+ break;
+
+ case Common::KEYCODE_KP7:
+ if (state.flags & Common::KBD_NUM) {
+ handled = false;
+ break;
+ }
+ case Common::KEYCODE_HOME:
+ _selectedItem = 0;
+ break;
+
+ case Common::KEYCODE_KP8:
+ if (state.flags & Common::KBD_NUM) {
+ handled = false;
+ break;
+ }
+ case Common::KEYCODE_UP:
+ if (_selectedItem > 0)
+ _selectedItem--;
+ break;
+
+ case Common::KEYCODE_KP9:
+ if (state.flags & Common::KBD_NUM) {
+ handled = false;
+ break;
+ }
+ case Common::KEYCODE_PAGEUP:
+ _selectedItem -= _entriesPerPage - 1;
+ if (_selectedItem < 0)
+ _selectedItem = 0;
+ break;
+
+ default:
+ handled = false;
+ }
+
+ scrollToCurrent();
+ }
+
+ if (dirty || _selectedItem != oldSelectedItem)
+ draw();
+
+ if (_selectedItem != oldSelectedItem) {
+ sendCommand(kListSelectionChangedCmd, _selectedItem);
+ // also draw scrollbar
+ _scrollBar->draw();
+ }
+
+ return handled;
+}
+
+bool ListWidget::handleKeyUp(Common::KeyState state) {
+ if (state.keycode == _currentKeyDown)
+ _currentKeyDown = 0;
+ return true;
+}
+
+void ListWidget::receivedFocusWidget() {
+ _inversion = ThemeEngine::kTextInversionFocus;
+
+ // Redraw the widget so the selection color will change
+ draw();
+}
+
+void ListWidget::lostFocusWidget() {
+ _inversion = ThemeEngine::kTextInversion;
+
+ // If we lose focus, we simply forget the user changes
+ _editMode = false;
+ g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);
+ drawCaret(true);
+ draw();
+}
+
+void ListWidget::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
+ switch (cmd) {
+ case kSetPositionCmd:
+ if (_currentPos != (int)data) {
+ _currentPos = data;
+ draw();
+
+ // Scrollbar actions cause list focus (which triggers a redraw)
+ // NOTE: ListWidget's boss is always GUI::Dialog
+ ((GUI::Dialog *)_boss)->setFocusWidget(this);
+ }
+ break;
+ }
+}
+
+void ListWidget::drawWidget() {
+ int i, pos, len = _list.size();
+ Common::String buffer;
+
+ // Draw a thin frame around the list.
+ g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x + _w, _y + _h), 0, ThemeEngine::kWidgetBackgroundBorder);
+ const int scrollbarW = (_scrollBar && _scrollBar->isVisible()) ? _scrollBarWidth : 0;
+
+ // Draw the list items
+ for (i = 0, pos = _currentPos; i < _entriesPerPage && pos < len; i++, pos++) {
+ const int y = _y + _topPadding + kLineHeight * i;
+ const int fontHeight = kLineHeight;
+ ThemeEngine::TextInversionState inverted = ThemeEngine::kTextInversionNone;
+
+ // Draw the selected item inverted, on a highlighted background.
+ if (_selectedItem == pos)
+ inverted = _inversion;
+
+ Common::Rect r(getEditRect());
+ int pad = _leftPadding;
+
+ // If in numbering mode, we first print a number prefix
+ if (_numberingMode != kListNumberingOff) {
+ char temp[10];
+ sprintf(temp, "%2d. ", (pos + _numberingMode));
+ buffer = temp;
+ g_gui.theme()->drawText(Common::Rect(_x, y, _x + r.left + _leftPadding, y + fontHeight - 2),
+ buffer, _state, Graphics::kTextAlignLeft, inverted, _leftPadding, true);
+ pad = 0;
+ }
+
+ int width;
+
+ ThemeEngine::FontColor color = ThemeEngine::kFontColorNormal;
+
+ if (!_listColors.empty()) {
+ if (_filter.empty() || _selectedItem == -1)
+ color = _listColors[pos];
+ else
+ color = _listColors[_listIndex[pos]];
+ }
+
+ if (_selectedItem == pos && _editMode) {
+ buffer = _editString;
+ color = _editColor;
+ adjustOffset();
+ width = _w - r.left - _hlRightPadding - _leftPadding - scrollbarW;
+ g_gui.theme()->drawText(Common::Rect(_x + r.left, y, _x + r.left + width, y + fontHeight - 2), buffer, _state,
+ Graphics::kTextAlignLeft, inverted, pad, true, ThemeEngine::kFontStyleBold, color);
+ } else {
+ buffer = _list[pos];
+ width = _w - r.left - scrollbarW;
+ g_gui.theme()->drawText(Common::Rect(_x + r.left, y, _x + r.left + width, y + fontHeight - 2), buffer, _state,
+ Graphics::kTextAlignLeft, inverted, pad, true, ThemeEngine::kFontStyleBold, color);
+ }
+
+ _textWidth[i] = width;
+ }
+}
+
+Common::Rect ListWidget::getEditRect() const {
+ Common::Rect r(_hlLeftPadding, 0, _w - _hlLeftPadding - _hlRightPadding, kLineHeight - 1);
+ const int offset = (_selectedItem - _currentPos) * kLineHeight + _topPadding;
+ r.top += offset;
+ r.bottom += offset;
+
+ if (_numberingMode != kListNumberingOff) {
+ char temp[10];
+ // FIXME: Assumes that all digits have the same width.
+ sprintf(temp, "%2d. ", (_list.size() - 1 + _numberingMode));
+ r.left += g_gui.getStringWidth(temp) + _leftPadding;
+ }
+
+ return r;
+}
+
+void ListWidget::scrollToCurrent() {
+ // Only do something if the current item is not in our view port
+ if (_selectedItem < _currentPos) {
+ // it's above our view
+ _currentPos = _selectedItem;
+ } else if (_selectedItem >= _currentPos + _entriesPerPage ) {
+ // it's below our view
+ _currentPos = _selectedItem - _entriesPerPage + 1;
+ }
+
+ if (_currentPos < 0 || _entriesPerPage > (int)_list.size())
+ _currentPos = 0;
+ else if (_currentPos + _entriesPerPage > (int)_list.size())
+ _currentPos = _list.size() - _entriesPerPage;
+
+ _scrollBar->_currentPos = _currentPos;
+ _scrollBar->recalc();
+}
+
+void ListWidget::scrollToEnd() {
+ if (_currentPos + _entriesPerPage < (int)_list.size()) {
+ _currentPos = _list.size() - _entriesPerPage;
+ } else {
+ return;
+ }
+
+ _scrollBar->_currentPos = _currentPos;
+ _scrollBar->recalc();
+ _scrollBar->draw();
+}
+
+void ListWidget::startEditMode() {
+ if (_editable && !_editMode && _selectedItem >= 0) {
+ _editMode = true;
+ setEditString(_list[_selectedItem]);
+ if (_listColors.empty()) {
+ _editColor = ThemeEngine::kFontColorNormal;
+ } else {
+ if (_filter.empty())
+ _editColor = _listColors[_selectedItem];
+ else
+ _editColor = _listColors[_listIndex[_selectedItem]];
+ }
+ draw();
+ g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, true);
+ }
+}
+
+void ListWidget::endEditMode() {
+ if (!_editMode)
+ return;
+ // send a message that editing finished with a return/enter key press
+ _editMode = false;
+ _list[_selectedItem] = _editString;
+ g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);
+ sendCommand(kListItemActivatedCmd, _selectedItem);
+}
+
+void ListWidget::abortEditMode() {
+ // undo any changes made
+ assert(_selectedItem >= 0);
+ _editMode = false;
+ //drawCaret(true);
+ //draw();
+ g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);
+}
+
+void ListWidget::reflowLayout() {
+ Widget::reflowLayout();
+
+ _leftPadding = g_gui.xmlEval()->getVar("Globals.ListWidget.Padding.Left", 0);
+ _rightPadding = g_gui.xmlEval()->getVar("Globals.ListWidget.Padding.Right", 0);
+ _topPadding = g_gui.xmlEval()->getVar("Globals.ListWidget.Padding.Top", 0);
+ _bottomPadding = g_gui.xmlEval()->getVar("Globals.ListWidget.Padding.Bottom", 0);
+ _hlLeftPadding = g_gui.xmlEval()->getVar("Globals.ListWidget.hlLeftPadding", 0);
+ _hlRightPadding = g_gui.xmlEval()->getVar("Globals.ListWidget.hlRightPadding", 0);
+
+ _scrollBarWidth = g_gui.xmlEval()->getVar("Globals.Scrollbar.Width", 0);
+
+ // HACK: Once we take padding into account, there are times where
+ // integer rounding leaves a big chunk of white space in the bottom
+ // of the list.
+ // We do a rough rounding on the decimal places of Entries Per Page,
+ // to add another entry even if it goes a tad over the padding.
+ frac_t entriesPerPage = intToFrac(_h - _topPadding - _bottomPadding) / kLineHeight;
+
+ // Our threshold before we add another entry is 0.9375 (0xF000 with FRAC_BITS being 16).
+ const frac_t threshold = intToFrac(15) / 16;
+
+ if ((frac_t)(entriesPerPage & FRAC_LO_MASK) >= threshold)
+ entriesPerPage += FRAC_ONE;
+
+ _entriesPerPage = fracToInt(entriesPerPage);
+ assert(_entriesPerPage > 0);
+
+ delete[] _textWidth;
+ _textWidth = new int[_entriesPerPage];
+
+ for (int i = 0; i < _entriesPerPage; i++)
+ _textWidth[i] = 0;
+
+ if (_scrollBar) {
+ _scrollBar->resize(_w - _scrollBarWidth + 1, 0, _scrollBarWidth, _h);
+ scrollBarRecalc();
+ scrollToCurrent();
+ }
+}
+
+void ListWidget::setFilter(const String &filter, bool redraw) {
+ // FIXME: This method does not deal correctly with edit mode!
+ // Until we fix that, let's make sure it isn't called while editing takes place
+ assert(!_editMode);
+
+ String filt = filter;
+ filt.toLowercase();
+
+ if (_filter == filt) // Filter was not changed
+ return;
+
+ _filter = filt;
+
+ if (_filter.empty()) {
+ // No filter -> display everything
+ _list = _dataList;
+ _listIndex.clear();
+ } else {
+ // Restrict the list to everything which contains all words in _filter
+ // as substrings, ignoring case.
+
+ Common::StringTokenizer tok(_filter);
+ String tmp;
+ int n = 0;
+
+ _list.clear();
+ _listIndex.clear();
+
+ for (StringArray::iterator i = _dataList.begin(); i != _dataList.end(); ++i, ++n) {
+ tmp = *i;
+ tmp.toLowercase();
+ bool matches = true;
+ tok.reset();
+ while (!tok.empty()) {
+ if (!tmp.contains(tok.nextToken())) {
+ matches = false;
+ break;
+ }
+ }
+
+ if (matches) {
+ _list.push_back(*i);
+ _listIndex.push_back(n);
+ }
+ }
+ }
+
+ _currentPos = 0;
+ _selectedItem = -1;
+
+ if (redraw) {
+ scrollBarRecalc();
+ // Redraw the whole dialog. This is annoying, as this might be rather
+ // expensive when really only the list widget and its scroll bar area
+ // to be redrawn. However, since the scrollbar might change its
+ // visibility status, and the list its width, we cannot just redraw
+ // the two.
+ // TODO: A more efficient (and elegant?) way to handle this would be to
+ // introduce a kind of "BoxWidget" or "GroupWidget" which defines a
+ // rectangular region and subwidgets can be placed within it.
+ // Such a widget could also (optionally) draw a border (or even different
+ // kinds of borders) around the objects it groups; and also a 'title'
+ // (I am borrowing these "ideas" from the NSBox class in Cocoa :).
+ _boss->draw();
+ }
+}
+
+} // End of namespace GUI