aboutsummaryrefslogtreecommitdiff
path: root/gui/widgets
diff options
context:
space:
mode:
Diffstat (limited to 'gui/widgets')
-rw-r--r--gui/widgets/editable.cpp304
-rw-r--r--gui/widgets/editable.h95
-rw-r--r--gui/widgets/edittext.cpp118
-rw-r--r--gui/widgets/edittext.h69
-rw-r--r--gui/widgets/list.cpp734
-rw-r--r--gui/widgets/list.h157
-rw-r--r--gui/widgets/popup.cpp463
-rw-r--r--gui/widgets/popup.h91
-rw-r--r--gui/widgets/scrollbar.cpp233
-rw-r--r--gui/widgets/scrollbar.h84
-rw-r--r--gui/widgets/tab.cpp320
-rw-r--r--gui/widgets/tab.h118
12 files changed, 2786 insertions, 0 deletions
diff --git a/gui/widgets/editable.cpp b/gui/widgets/editable.cpp
new file mode 100644
index 0000000000..9817f562ad
--- /dev/null
+++ b/gui/widgets/editable.cpp
@@ -0,0 +1,304 @@
+/* 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/events.h"
+#include "gui/widgets/editable.h"
+#include "gui/GuiManager.h"
+
+namespace GUI {
+
+EditableWidget::EditableWidget(GuiObject *boss, int x, int y, int w, int h, const char *tooltip, uint32 cmd)
+ : Widget(boss, x, y, w, h, tooltip), CommandSender(boss), _cmd(cmd) {
+ init();
+}
+
+EditableWidget::EditableWidget(GuiObject *boss, const String &name, const char *tooltip, uint32 cmd)
+ : Widget(boss, name, tooltip), CommandSender(boss), _cmd(cmd) {
+ init();
+}
+
+void EditableWidget::init() {
+ _caretVisible = false;
+ _caretTime = 0;
+ _caretPos = 0;
+
+ _caretInverse = false;
+
+ _editScrollOffset = 0;
+
+ _font = ThemeEngine::kFontStyleBold;
+ _inversion = ThemeEngine::kTextInversionNone;
+}
+
+EditableWidget::~EditableWidget() {
+}
+
+void EditableWidget::reflowLayout() {
+ Widget::reflowLayout();
+
+ _editScrollOffset = g_gui.getStringWidth(_editString, _font) - getEditRect().width();
+ if (_editScrollOffset < 0)
+ _editScrollOffset = 0;
+}
+
+void EditableWidget::setEditString(const String &str) {
+ // TODO: We probably should filter the input string here,
+ // e.g. using tryInsertChar.
+ _editString = str;
+ _caretPos = 0;
+}
+
+bool EditableWidget::tryInsertChar(byte c, int pos) {
+ if ((c >= 32 && c <= 127) || c >= 160) {
+ _editString.insertChar(c, pos);
+ return true;
+ }
+ return false;
+}
+
+void EditableWidget::handleTickle() {
+ uint32 time = g_system->getMillis();
+ if (_caretTime < time) {
+ _caretTime = time + kCaretBlinkTime;
+ drawCaret(_caretVisible);
+ }
+}
+
+bool EditableWidget::handleKeyDown(Common::KeyState state) {
+ bool handled = true;
+ bool dirty = false;
+ bool forcecaret = false;
+
+ // First remove caret
+ if (_caretVisible)
+ drawCaret(true);
+
+ switch (state.keycode) {
+ case Common::KEYCODE_RETURN:
+ case Common::KEYCODE_KP_ENTER:
+ // confirm edit and exit editmode
+ endEditMode();
+ dirty = true;
+ break;
+
+ case Common::KEYCODE_ESCAPE:
+ abortEditMode();
+ dirty = true;
+ break;
+
+ case Common::KEYCODE_BACKSPACE:
+ if (_caretPos > 0) {
+ _caretPos--;
+ _editString.deleteChar(_caretPos);
+ dirty = true;
+
+ sendCommand(_cmd, 0);
+ }
+ forcecaret = true;
+ break;
+
+ // Keypad & special keys
+ // - if num lock is set, we always go to the default case
+ // - if num lock is not set, we either fall down to the special key case
+ // or ignore the key press in case of 0 (INSERT), 2 (DOWN), 3 (PGDWN)
+ // 5, 8 (UP) and 9 (PGUP)
+
+ case Common::KEYCODE_KP0:
+ case Common::KEYCODE_KP2:
+ case Common::KEYCODE_KP3:
+ case Common::KEYCODE_KP5:
+ case Common::KEYCODE_KP8:
+ case Common::KEYCODE_KP9:
+ if (state.flags & Common::KBD_NUM)
+ defaultKeyDownHandler(state, dirty, forcecaret, handled);
+ break;
+
+ case Common::KEYCODE_KP_PERIOD:
+ if (state.flags & Common::KBD_NUM) {
+ defaultKeyDownHandler(state, dirty, forcecaret, handled);
+ break;
+ }
+ case Common::KEYCODE_DELETE:
+ if (_caretPos < (int)_editString.size()) {
+ _editString.deleteChar(_caretPos);
+ dirty = true;
+
+ sendCommand(_cmd, 0);
+ }
+ forcecaret = true;
+ break;
+
+ case Common::KEYCODE_KP1:
+ if (state.flags & Common::KBD_NUM) {
+ defaultKeyDownHandler(state, dirty, forcecaret, handled);
+ break;
+ }
+ case Common::KEYCODE_END:
+ dirty = setCaretPos(_editString.size());
+ forcecaret = true;
+ break;
+
+ case Common::KEYCODE_KP4:
+ if (state.flags & Common::KBD_NUM) {
+ defaultKeyDownHandler(state, dirty, forcecaret, handled);
+ break;
+ }
+ case Common::KEYCODE_LEFT:
+ if (_caretPos > 0) {
+ dirty = setCaretPos(_caretPos - 1);
+ }
+ forcecaret = true;
+ dirty = true;
+ break;
+
+ case Common::KEYCODE_KP6:
+ if (state.flags & Common::KBD_NUM) {
+ defaultKeyDownHandler(state, dirty, forcecaret, handled);
+ break;
+ }
+ case Common::KEYCODE_RIGHT:
+ if (_caretPos < (int)_editString.size()) {
+ dirty = setCaretPos(_caretPos + 1);
+ }
+ forcecaret = true;
+ dirty = true;
+ break;
+
+ case Common::KEYCODE_KP7:
+ if (state.flags & Common::KBD_NUM) {
+ defaultKeyDownHandler(state, dirty, forcecaret, handled);
+ break;
+ }
+ case Common::KEYCODE_HOME:
+ dirty = setCaretPos(0);
+ forcecaret = true;
+ break;
+
+ default:
+ defaultKeyDownHandler(state, dirty, forcecaret, handled);
+ }
+
+ if (dirty)
+ draw();
+
+ if (forcecaret)
+ makeCaretVisible();
+
+ return handled;
+}
+
+void EditableWidget::defaultKeyDownHandler(Common::KeyState &state, bool &dirty, bool &forcecaret, bool &handled) {
+ if (state.ascii < 256 && tryInsertChar((byte)state.ascii, _caretPos)) {
+ _caretPos++;
+ dirty = true;
+ forcecaret = true;
+
+ sendCommand(_cmd, 0);
+ } else {
+ handled = false;
+ }
+}
+
+int EditableWidget::getCaretOffset() const {
+ int caretpos = 0;
+ for (int i = 0; i < _caretPos; i++)
+ caretpos += g_gui.getCharWidth(_editString[i], _font);
+
+ caretpos -= _editScrollOffset;
+
+ return caretpos;
+}
+
+void EditableWidget::drawCaret(bool erase) {
+ // Only draw if item is visible
+ if (!isVisible() || !_boss->isVisible())
+ return;
+
+ Common::Rect editRect = getEditRect();
+
+ int x = editRect.left;
+ int y = editRect.top;
+
+ x += getCaretOffset();
+
+ if (y < 0 || y + editRect.height() - 2 >= _h)
+ return;
+
+ x += getAbsX();
+ y += getAbsY();
+
+ g_gui.theme()->drawCaret(Common::Rect(x, y, x + 1, y + editRect.height() - 2), erase);
+
+ if (erase) {
+ if ((uint)_caretPos < _editString.size()) {
+ GUI::EditableWidget::String chr(_editString[_caretPos]);
+ int chrWidth = g_gui.getCharWidth(_editString[_caretPos], _font);
+ g_gui.theme()->drawText(Common::Rect(x, y, x + chrWidth, y + editRect.height() - 2), chr, _state, Graphics::kTextAlignLeft, _inversion, 0, false, _font);
+ }
+ }
+
+ _caretVisible = !erase;
+}
+
+bool EditableWidget::setCaretPos(int newPos) {
+ assert(newPos >= 0 && newPos <= (int)_editString.size());
+ _caretPos = newPos;
+ return adjustOffset();
+}
+
+bool EditableWidget::adjustOffset() {
+ // check if the caret is still within the textbox; if it isn't,
+ // adjust _editScrollOffset
+
+ int caretpos = getCaretOffset();
+ const int editWidth = getEditRect().width();
+
+ if (caretpos < 0) {
+ // scroll left
+ _editScrollOffset += caretpos;
+ return true;
+ } else if (caretpos >= editWidth) {
+ // scroll right
+ _editScrollOffset -= (editWidth - caretpos);
+ return true;
+ } else if (_editScrollOffset > 0) {
+ const int strWidth = g_gui.getStringWidth(_editString, _font);
+ if (strWidth - _editScrollOffset < editWidth) {
+ // scroll right
+ _editScrollOffset = (strWidth - editWidth);
+ if (_editScrollOffset < 0)
+ _editScrollOffset = 0;
+ }
+ }
+
+ return false;
+}
+
+void EditableWidget::makeCaretVisible() {
+ _caretTime = g_system->getMillis() + kCaretBlinkTime;
+ _caretVisible = true;
+ drawCaret(false);
+}
+
+} // End of namespace GUI
diff --git a/gui/widgets/editable.h b/gui/widgets/editable.h
new file mode 100644
index 0000000000..0cd1f2721c
--- /dev/null
+++ b/gui/widgets/editable.h
@@ -0,0 +1,95 @@
+/* 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$
+ */
+
+#ifndef GUI_WIDGETS_EDITABLE_H
+#define GUI_WIDGETS_EDITABLE_H
+
+#include "common/str.h"
+#include "common/rect.h"
+#include "gui/widget.h"
+#include "gui/GuiManager.h"
+
+namespace GUI {
+
+/**
+ * Base class for widgets which need to edit text, like ListWidget and
+ * EditTextWidget.
+ */
+class EditableWidget : public Widget, public CommandSender {
+public:
+ typedef Common::String String;
+protected:
+ String _editString;
+
+ uint32 _cmd;
+
+ bool _caretVisible;
+ uint32 _caretTime;
+ int _caretPos;
+
+ bool _caretInverse;
+
+ int _editScrollOffset;
+
+ ThemeEngine::FontStyle _font;
+
+ ThemeEngine::TextInversionState _inversion;
+
+public:
+ EditableWidget(GuiObject *boss, int x, int y, int w, int h, const char *tooltip = 0, uint32 cmd = 0);
+ EditableWidget(GuiObject *boss, const String &name, const char *tooltip = 0, uint32 cmd = 0);
+ virtual ~EditableWidget();
+
+ void init();
+
+ virtual void setEditString(const String &str);
+ virtual const String &getEditString() const { return _editString; }
+
+ virtual void handleTickle();
+ virtual bool handleKeyDown(Common::KeyState state);
+
+ virtual void reflowLayout();
+
+protected:
+ virtual void startEditMode() = 0;
+ virtual void endEditMode() = 0;
+ virtual void abortEditMode() = 0;
+
+ virtual Common::Rect getEditRect() const = 0;
+ virtual int getCaretOffset() const;
+ void drawCaret(bool erase);
+ bool setCaretPos(int newPos);
+ bool adjustOffset();
+ void makeCaretVisible();
+
+ void defaultKeyDownHandler(Common::KeyState &state, bool &dirty, bool &forcecaret, bool &handled);
+
+ void setFontStyle(ThemeEngine::FontStyle font) { _font = font; }
+
+ virtual bool tryInsertChar(byte c, int pos);
+};
+
+} // End of namespace GUI
+
+#endif
diff --git a/gui/widgets/edittext.cpp b/gui/widgets/edittext.cpp
new file mode 100644
index 0000000000..1e2ba69478
--- /dev/null
+++ b/gui/widgets/edittext.cpp
@@ -0,0 +1,118 @@
+/* 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 "gui/widgets/edittext.h"
+#include "gui/dialog.h"
+#include "gui/GuiManager.h"
+
+#include "gui/ThemeEval.h"
+
+namespace GUI {
+
+EditTextWidget::EditTextWidget(GuiObject *boss, int x, int y, int w, int h, const String &text, const char *tooltip, uint32 cmd)
+ : EditableWidget(boss, x, y - 1, w, h + 2, tooltip, cmd) {
+ setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_WANT_TICKLE);
+ _type = kEditTextWidget;
+
+ setEditString(text);
+}
+
+EditTextWidget::EditTextWidget(GuiObject *boss, const String &name, const String &text, const char *tooltip, uint32 cmd)
+ : EditableWidget(boss, name, tooltip, cmd) {
+ setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_WANT_TICKLE);
+ _type = kEditTextWidget;
+
+ setEditString(text);
+}
+
+void EditTextWidget::setEditString(const String &str) {
+ EditableWidget::setEditString(str);
+ _backupString = str;
+}
+
+void EditTextWidget::reflowLayout() {
+ _leftPadding = g_gui.xmlEval()->getVar("Globals.EditTextWidget.Padding.Left", 0);
+ _rightPadding = g_gui.xmlEval()->getVar("Globals.EditTextWidget.Padding.Right", 0);
+
+ EditableWidget::reflowLayout();
+}
+
+
+void EditTextWidget::handleMouseDown(int x, int y, int button, int clickCount) {
+ // First remove caret
+ if (_caretVisible)
+ drawCaret(true);
+
+ x += _editScrollOffset;
+
+ int width = 0;
+ uint i;
+
+ for (i = 0; i < _editString.size(); ++i) {
+ width += g_gui.theme()->getCharWidth(_editString[i], _font);
+ if (width >= x)
+ break;
+ }
+ if (setCaretPos(i))
+ draw();
+}
+
+
+void EditTextWidget::drawWidget() {
+ g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x+_w, _y+_h), 0, ThemeEngine::kWidgetBackgroundEditText);
+
+ // Draw the text
+ adjustOffset();
+ g_gui.theme()->drawText(Common::Rect(_x+2+ _leftPadding,_y+2, _x+_leftPadding+getEditRect().width()+2, _y+_h-2), _editString, _state, Graphics::kTextAlignLeft, ThemeEngine::kTextInversionNone, -_editScrollOffset, false, _font);
+}
+
+Common::Rect EditTextWidget::getEditRect() const {
+ Common::Rect r(2 + _leftPadding, 2, _w - 2 - _leftPadding - _rightPadding, _h-1);
+
+ return r;
+}
+
+void EditTextWidget::receivedFocusWidget() {
+}
+
+void EditTextWidget::lostFocusWidget() {
+ // If we loose focus, 'commit' the user changes
+ _backupString = _editString;
+ drawCaret(true);
+}
+
+void EditTextWidget::startEditMode() {
+}
+
+void EditTextWidget::endEditMode() {
+ releaseFocus();
+}
+
+void EditTextWidget::abortEditMode() {
+ setEditString(_backupString);
+ sendCommand(_cmd, 0);
+ releaseFocus();
+}
+
+} // End of namespace GUI
diff --git a/gui/widgets/edittext.h b/gui/widgets/edittext.h
new file mode 100644
index 0000000000..a2549882ca
--- /dev/null
+++ b/gui/widgets/edittext.h
@@ -0,0 +1,69 @@
+/* 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$
+ */
+
+#ifndef GUI_WIDGETS_EDITTEXT_H
+#define GUI_WIDGETS_EDITTEXT_H
+
+#include "gui/widgets/editable.h"
+#include "common/str.h"
+
+namespace GUI {
+
+/* EditTextWidget */
+class EditTextWidget : public EditableWidget {
+protected:
+ typedef Common::String String;
+
+ String _backupString;
+
+ int _leftPadding;
+ int _rightPadding;
+
+public:
+ EditTextWidget(GuiObject *boss, int x, int y, int w, int h, const String &text, const char *tooltip = 0, uint32 cmd = 0);
+ EditTextWidget(GuiObject *boss, const String &name, const String &text, const char *tooltp = 0, uint32 cmd = 0);
+
+ void setEditString(const String &str);
+
+ virtual void handleMouseDown(int x, int y, int button, int clickCount);
+
+ virtual bool wantsFocus() { return true; }
+
+ virtual void reflowLayout();
+
+protected:
+ void drawWidget();
+ void receivedFocusWidget();
+ void lostFocusWidget();
+
+ void startEditMode();
+ void endEditMode();
+ void abortEditMode();
+
+ Common::Rect getEditRect() const;
+};
+
+} // End of namespace GUI
+
+#endif
diff --git a/gui/widgets/list.cpp b/gui/widgets/list.cpp
new file mode 100644
index 0000000000..bcbe0b9cbf
--- /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/GuiManager.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
diff --git a/gui/widgets/list.h b/gui/widgets/list.h
new file mode 100644
index 0000000000..4bc9adc5ef
--- /dev/null
+++ b/gui/widgets/list.h
@@ -0,0 +1,157 @@
+/* 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$
+ */
+
+#ifndef GUI_WIDGETS_LIST_H
+#define GUI_WIDGETS_LIST_H
+
+#include "gui/widgets/editable.h"
+#include "common/str.h"
+
+#include "gui/ThemeEngine.h"
+
+namespace GUI {
+
+class ScrollBarWidget;
+
+enum NumberingMode {
+ kListNumberingOff = -1,
+ kListNumberingZero = 0,
+ kListNumberingOne = 1
+};
+
+/// Some special commands
+enum {
+ kListItemDoubleClickedCmd = 'LIdb', ///< double click on item - 'data' will be item index
+ kListItemActivatedCmd = 'LIac', ///< item activated by return/enter - 'data' will be item index
+ kListItemRemovalRequestCmd = 'LIrm', ///< request to remove the item with the delete/backspace keys - 'data' will be item index
+ kListSelectionChangedCmd = 'Lsch' ///< selection changed - 'data' will be item index
+};
+
+/* ListWidget */
+class ListWidget : public EditableWidget {
+public:
+ typedef Common::String String;
+ typedef Common::Array<Common::String> StringArray;
+ typedef Common::Array<ThemeEngine::FontColor> ColorList;
+protected:
+ StringArray _list;
+ StringArray _dataList;
+ ColorList _listColors;
+ Common::Array<int> _listIndex;
+ bool _editable;
+ bool _editMode;
+ NumberingMode _numberingMode;
+ int _currentPos;
+ int _entriesPerPage;
+ int _selectedItem;
+ ScrollBarWidget *_scrollBar;
+ int _currentKeyDown;
+
+ String _quickSelectStr;
+ uint32 _quickSelectTime;
+
+ int _hlLeftPadding;
+ int _hlRightPadding;
+ int _leftPadding;
+ int _rightPadding;
+ int _topPadding;
+ int _bottomPadding;
+ int _scrollBarWidth;
+
+ String _filter;
+ bool _quickSelect;
+
+ uint32 _cmd;
+
+ ThemeEngine::FontColor _editColor;
+
+public:
+ ListWidget(Dialog *boss, const String &name, const char *tooltip = 0, uint32 cmd = 0);
+ ListWidget(Dialog *boss, int x, int y, int w, int h, const char *tooltip = 0, uint32 cmd = 0);
+ virtual ~ListWidget();
+
+ virtual Widget *findWidget(int x, int y);
+
+ void setList(const StringArray &list, const ColorList *colors = 0);
+ const StringArray &getList() const { return _dataList; }
+
+ void append(const String &s, ThemeEngine::FontColor color = ThemeEngine::kFontColorNormal);
+
+ void setSelected(int item);
+ int getSelected() const { return (_filter.empty() || _selectedItem == -1) ? _selectedItem : _listIndex[_selectedItem]; }
+
+ const String &getSelectedString() const { return _list[_selectedItem]; }
+ ThemeEngine::FontColor getSelectionColor() const;
+
+ void setNumberingMode(NumberingMode numberingMode) { _numberingMode = numberingMode; }
+
+ void scrollTo(int item);
+ void scrollToEnd();
+
+ void enableQuickSelect(bool enable) { _quickSelect = enable; }
+ String getQuickSelectString() const { return _quickSelectStr; }
+
+ bool isEditable() const { return _editable; }
+ void setEditable(bool editable) { _editable = editable; }
+ void setEditColor(ThemeEngine::FontColor color) { _editColor = color; }
+
+ // Made startEditMode/endEditMode for SaveLoadChooser
+ void startEditMode();
+ void endEditMode();
+
+ void setFilter(const String &filter, bool redraw = true);
+
+ virtual void handleTickle();
+ virtual void handleMouseDown(int x, int y, int button, int clickCount);
+ virtual void handleMouseUp(int x, int y, int button, int clickCount);
+ virtual void handleMouseWheel(int x, int y, int direction);
+ virtual bool handleKeyDown(Common::KeyState state);
+ virtual bool handleKeyUp(Common::KeyState state);
+ virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
+
+ virtual void reflowLayout();
+
+ virtual bool wantsFocus() { return true; }
+
+protected:
+ void drawWidget();
+
+ /// Finds the item at position (x,y). Returns -1 if there is no item there.
+ int findItem(int x, int y) const;
+ void scrollBarRecalc();
+
+ void abortEditMode();
+
+ Common::Rect getEditRect() const;
+
+ void receivedFocusWidget();
+ void lostFocusWidget();
+ void scrollToCurrent();
+
+ int *_textWidth;
+};
+
+} // End of namespace GUI
+
+#endif
diff --git a/gui/widgets/popup.cpp b/gui/widgets/popup.cpp
new file mode 100644
index 0000000000..fb3fee2926
--- /dev/null
+++ b/gui/widgets/popup.cpp
@@ -0,0 +1,463 @@
+/* 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 "gui/dialog.h"
+#include "gui/GuiManager.h"
+#include "gui/widgets/popup.h"
+#include "engines/engine.h"
+
+#include "gui/ThemeEval.h"
+
+namespace GUI {
+
+//
+// PopUpDialog
+//
+
+class PopUpDialog : public Dialog {
+protected:
+ PopUpWidget *_popUpBoss;
+ int _clickX, _clickY;
+ byte *_buffer;
+ int _selection;
+ uint32 _openTime;
+ bool _twoColumns;
+ int _entriesPerColumn;
+
+ int _leftPadding;
+ int _rightPadding;
+
+public:
+ PopUpDialog(PopUpWidget *boss, int clickX, int clickY);
+
+ void drawDialog();
+
+ void handleMouseUp(int x, int y, int button, int clickCount);
+ void handleMouseWheel(int x, int y, int direction); // Scroll through entries with scroll wheel
+ void handleMouseMoved(int x, int y, int button); // Redraw selections depending on mouse position
+ void handleKeyDown(Common::KeyState state); // Scroll through entries with arrow keys etc.
+
+protected:
+ void drawMenuEntry(int entry, bool hilite);
+
+ int findItem(int x, int y) const;
+ void setSelection(int item);
+ bool isMouseDown();
+
+ void moveUp();
+ void moveDown();
+};
+
+PopUpDialog::PopUpDialog(PopUpWidget *boss, int clickX, int clickY)
+ : Dialog(0, 0, 16, 16),
+ _popUpBoss(boss) {
+
+ // Copy the selection index
+ _selection = _popUpBoss->_selectedItem;
+
+ // Calculate real popup dimensions
+ _x = _popUpBoss->getAbsX();
+ _y = _popUpBoss->getAbsY() - _popUpBoss->_selectedItem * kLineHeight;
+ _h = _popUpBoss->_entries.size() * kLineHeight + 2;
+ _w = _popUpBoss->_w - kLineHeight + 2;
+
+ _leftPadding = _popUpBoss->_leftPadding;
+ _rightPadding = _popUpBoss->_rightPadding;
+
+ // Perform clipping / switch to scrolling mode if we don't fit on the screen
+ // FIXME - OSystem should send out notification messages when the screen
+ // resolution changes... we could generalize CommandReceiver and CommandSender.
+
+ const int screenH = g_system->getOverlayHeight();
+
+ // HACK: For now, we do not do scrolling. Instead, we draw the dialog
+ // in two columns if it's too tall.
+
+ if (_h >= screenH) {
+ const int screenW = g_system->getOverlayWidth();
+
+ _twoColumns = true;
+ _entriesPerColumn = _popUpBoss->_entries.size() / 2;
+
+ if (_popUpBoss->_entries.size() & 1)
+ _entriesPerColumn++;
+
+ _h = _entriesPerColumn * kLineHeight + 2;
+ _w = 0;
+
+ for (uint i = 0; i < _popUpBoss->_entries.size(); i++) {
+ int width = g_gui.getStringWidth(_popUpBoss->_entries[i].name);
+
+ if (width > _w)
+ _w = width;
+ }
+
+ _w = 2 * _w + 10;
+
+ if (!(_w & 1))
+ _w++;
+
+ if (_popUpBoss->_selectedItem >= _entriesPerColumn) {
+ _x -= _w / 2;
+ _y = _popUpBoss->getAbsY() - (_popUpBoss->_selectedItem - _entriesPerColumn) * kLineHeight;
+ }
+
+ if (_w >= screenW)
+ _w = screenW - 1;
+ if (_x < 0)
+ _x = 0;
+ if (_x + _w >= screenW)
+ _x = screenW - 1 - _w;
+ } else
+ _twoColumns = false;
+
+ if (_h >= screenH)
+ _h = screenH - 1;
+ if (_y < 0)
+ _y = 0;
+ else if (_y + _h >= screenH)
+ _y = screenH - 1 - _h;
+
+ // TODO - implement scrolling if we had to move the menu, or if there are too many entries
+
+ // Remember original mouse position
+ _clickX = clickX - _x;
+ _clickY = clickY - _y;
+
+ _openTime = 0;
+}
+
+void PopUpDialog::drawDialog() {
+ // Draw the menu border
+ g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x+_w, _y+_h), 0);
+
+ /*if (_twoColumns)
+ g_gui.vLine(_x + _w / 2, _y, _y + _h - 2, g_gui._color);*/
+
+ // Draw the entries
+ int count = _popUpBoss->_entries.size();
+ for (int i = 0; i < count; i++) {
+ drawMenuEntry(i, i == _selection);
+ }
+
+ // The last entry may be empty. Fill it with black.
+ /*if (_twoColumns && (count & 1)) {
+ g_gui.fillRect(_x + 1 + _w / 2, _y + 1 + kLineHeight * (_entriesPerColumn - 1), _w / 2 - 1, kLineHeight, g_gui._bgcolor);
+ }*/
+
+ if (_openTime == 0) {
+ // Time the popup was opened
+ _openTime = g_system->getMillis();
+ }
+}
+
+void PopUpDialog::handleMouseUp(int x, int y, int button, int clickCount) {
+ // Mouse was released. If it wasn't moved much since the original mouse down,
+ // let the popup stay open. If it did move, assume the user made his selection.
+ int dist = (_clickX - x) * (_clickX - x) + (_clickY - y) * (_clickY - y);
+ if (dist > 3 * 3 || g_system->getMillis() - _openTime > 300) {
+ setResult(_selection);
+ close();
+ }
+ _clickX = -1;
+ _clickY = -1;
+ _openTime = (uint32)-1;
+}
+
+void PopUpDialog::handleMouseWheel(int x, int y, int direction) {
+ if (direction < 0)
+ moveUp();
+ else if (direction > 0)
+ moveDown();
+}
+
+void PopUpDialog::handleMouseMoved(int x, int y, int button) {
+ // Compute over which item the mouse is...
+ int item = findItem(x, y);
+
+ if (item >= 0 && _popUpBoss->_entries[item].name.size() == 0)
+ item = -1;
+
+ if (item == -1 && !isMouseDown()) {
+ setSelection(_popUpBoss->_selectedItem);
+ return;
+ }
+
+ // ...and update the selection accordingly
+ setSelection(item);
+}
+
+void PopUpDialog::handleKeyDown(Common::KeyState state) {
+ if (state.keycode == Common::KEYCODE_ESCAPE) {
+ // Don't change the previous selection
+ setResult(-1);
+ close();
+ return;
+ }
+
+ if (isMouseDown())
+ return;
+
+ switch (state.keycode) {
+
+ case Common::KEYCODE_RETURN:
+ case Common::KEYCODE_KP_ENTER:
+ setResult(_selection);
+ close();
+ break;
+
+ // Keypad & special keys
+ // - if num lock is set, we ignore the keypress
+ // - if num lock is not set, we fall down to the special key case
+
+ case Common::KEYCODE_KP1:
+ if (state.flags & Common::KBD_NUM)
+ break;
+ case Common::KEYCODE_END:
+ setSelection(_popUpBoss->_entries.size()-1);
+ break;
+
+ case Common::KEYCODE_KP2:
+ if (state.flags & Common::KBD_NUM)
+ break;
+ case Common::KEYCODE_DOWN:
+ moveDown();
+ break;
+
+ case Common::KEYCODE_KP7:
+ if (state.flags & Common::KBD_NUM)
+ break;
+ case Common::KEYCODE_HOME:
+ setSelection(0);
+ break;
+
+ case Common::KEYCODE_KP8:
+ if (state.flags & Common::KBD_NUM)
+ break;
+ case Common::KEYCODE_UP:
+ moveUp();
+ break;
+
+ default:
+ break;
+ }
+}
+
+int PopUpDialog::findItem(int x, int y) const {
+ if (x >= 0 && x < _w && y >= 0 && y < _h) {
+ if (_twoColumns) {
+ uint entry = (y - 2) / kLineHeight;
+ if (x > _w / 2) {
+ entry += _entriesPerColumn;
+
+ if (entry >= _popUpBoss->_entries.size())
+ return -1;
+ }
+ return entry;
+ }
+ return (y - 2) / kLineHeight;
+ }
+ return -1;
+}
+
+void PopUpDialog::setSelection(int item) {
+ if (item != _selection) {
+ // Undraw old selection
+ if (_selection >= 0)
+ drawMenuEntry(_selection, false);
+
+ // Change selection
+ _selection = item;
+
+ // Draw new selection
+ if (item >= 0)
+ drawMenuEntry(item, true);
+ }
+}
+
+bool PopUpDialog::isMouseDown() {
+ // TODO/FIXME - need a way to determine whether any mouse buttons are pressed or not.
+ // Sure, we could just count mouse button up/down events, but that is cumbersome and
+ // error prone. Would be much nicer to add an API to OSystem for this...
+
+ return false;
+}
+
+void PopUpDialog::moveUp() {
+ if (_selection < 0) {
+ setSelection(_popUpBoss->_entries.size() - 1);
+ } else if (_selection > 0) {
+ int item = _selection;
+ do {
+ item--;
+ } while (item >= 0 && _popUpBoss->_entries[item].name.size() == 0);
+ if (item >= 0)
+ setSelection(item);
+ }
+}
+
+void PopUpDialog::moveDown() {
+ int lastItem = _popUpBoss->_entries.size() - 1;
+
+ if (_selection < 0) {
+ setSelection(0);
+ } else if (_selection < lastItem) {
+ int item = _selection;
+ do {
+ item++;
+ } while (item <= lastItem && _popUpBoss->_entries[item].name.size() == 0);
+ if (item <= lastItem)
+ setSelection(item);
+ }
+}
+
+void PopUpDialog::drawMenuEntry(int entry, bool hilite) {
+ // Draw one entry of the popup menu, including selection
+ assert(entry >= 0);
+ int x, y, w;
+
+ if (_twoColumns) {
+ int n = _popUpBoss->_entries.size() / 2;
+
+ if (_popUpBoss->_entries.size() & 1)
+ n++;
+
+ if (entry >= n) {
+ x = _x + 1 + _w / 2;
+ y = _y + 1 + kLineHeight * (entry - n);
+ } else {
+ x = _x + 1;
+ y = _y + 1 + kLineHeight * entry;
+ }
+
+ w = _w / 2 - 1;
+ } else {
+ x = _x + 1;
+ y = _y + 1 + kLineHeight * entry;
+ w = _w - 2;
+ }
+
+ Common::String &name(_popUpBoss->_entries[entry].name);
+
+ if (name.size() == 0) {
+ // Draw a separator
+ g_gui.theme()->drawLineSeparator(Common::Rect(x, y, x+w, y+kLineHeight));
+ } else {
+ g_gui.theme()->drawText(Common::Rect(x+1, y+2, x+w, y+2+kLineHeight), name, hilite ? ThemeEngine::kStateHighlight : ThemeEngine::kStateEnabled,
+ Graphics::kTextAlignLeft, ThemeEngine::kTextInversionNone, _leftPadding);
+ }
+}
+
+
+#pragma mark -
+
+//
+// PopUpWidget
+//
+
+PopUpWidget::PopUpWidget(GuiObject *boss, const String &name, const char *tooltip)
+ : Widget(boss, name, tooltip), CommandSender(boss) {
+ setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_IGNORE_DRAG);
+ _type = kPopUpWidget;
+
+ _selectedItem = -1;
+}
+
+void PopUpWidget::handleMouseDown(int x, int y, int button, int clickCount) {
+ if (isEnabled()) {
+ PopUpDialog popupDialog(this, x + getAbsX(), y + getAbsY());
+ int newSel = popupDialog.runModal();
+ if (newSel != -1 && _selectedItem != newSel) {
+ _selectedItem = newSel;
+ sendCommand(kPopUpItemSelectedCmd, _entries[_selectedItem].tag);
+ }
+ }
+}
+
+void PopUpWidget::handleMouseWheel(int x, int y, int direction) {
+ int newSelection = _selectedItem + direction;
+
+ // Skip separator entries
+ while ((newSelection >= 0) && (newSelection < (int)_entries.size()) &&
+ _entries[newSelection].name.equals("")) {
+ newSelection += direction;
+ }
+
+ // Just update the selected item when we're in range
+ if ((newSelection >= 0) && (newSelection < (int)_entries.size()) &&
+ (newSelection != _selectedItem)) {
+ _selectedItem = newSelection;
+ draw();
+ }
+}
+
+void PopUpWidget::reflowLayout() {
+ _leftPadding = g_gui.xmlEval()->getVar("Globals.PopUpWidget.Padding.Left", 0);
+ _rightPadding = g_gui.xmlEval()->getVar("Globals.PopUpWidget.Padding.Right", 0);
+
+ Widget::reflowLayout();
+}
+
+void PopUpWidget::appendEntry(const String &entry, uint32 tag) {
+ Entry e;
+ e.name = entry;
+ e.tag = tag;
+ _entries.push_back(e);
+}
+
+void PopUpWidget::clearEntries() {
+ _entries.clear();
+ _selectedItem = -1;
+}
+
+void PopUpWidget::setSelected(int item) {
+ if (item != _selectedItem) {
+ if (item >= 0 && item < (int)_entries.size()) {
+ _selectedItem = item;
+ } else {
+ _selectedItem = -1;
+ }
+ }
+}
+
+void PopUpWidget::setSelectedTag(uint32 tag) {
+ uint item;
+ for (item = 0; item < _entries.size(); ++item) {
+ if (_entries[item].tag == tag) {
+ setSelected(item);
+ return;
+ }
+ }
+}
+
+void PopUpWidget::drawWidget() {
+ Common::String sel;
+ if (_selectedItem >= 0)
+ sel = _entries[_selectedItem].name;
+ g_gui.theme()->drawPopUpWidget(Common::Rect(_x, _y, _x + _w, _y + _h), sel, _leftPadding, _state, Graphics::kTextAlignLeft);
+}
+
+} // End of namespace GUI
diff --git a/gui/widgets/popup.h b/gui/widgets/popup.h
new file mode 100644
index 0000000000..b3b3e30837
--- /dev/null
+++ b/gui/widgets/popup.h
@@ -0,0 +1,91 @@
+/* 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$
+ */
+
+#ifndef GUI_WIDGETS_POPUP_H
+#define GUI_WIDGETS_POPUP_H
+
+#include "gui/widget.h"
+#include "common/str.h"
+#include "common/array.h"
+
+namespace GUI {
+
+enum {
+ kPopUpItemSelectedCmd = 'POPs'
+};
+
+/**
+ * Popup or dropdown widget which, when clicked, "pop up" a list of items and
+ * lets the user pick on of them.
+ *
+ * Implementation wise, when the user selects an item, then a kPopUpItemSelectedCmd
+ * is broadcast, with data being equal to the tag value of the selected entry.
+ */
+class PopUpWidget : public Widget, public CommandSender {
+ friend class PopUpDialog;
+ typedef Common::String String;
+
+ struct Entry {
+ String name;
+ uint32 tag;
+ };
+ typedef Common::Array<Entry> EntryList;
+protected:
+ EntryList _entries;
+ int _selectedItem;
+
+ int _leftPadding;
+ int _rightPadding;
+
+public:
+ PopUpWidget(GuiObject *boss, const String &name, const char *tooltip = 0);
+
+ void handleMouseDown(int x, int y, int button, int clickCount);
+ void handleMouseWheel(int x, int y, int direction);
+
+ void appendEntry(const String &entry, uint32 tag = (uint32)-1);
+ void clearEntries();
+ int numEntries() { return _entries.size(); }
+
+ /** Select the entry at the given index. */
+ void setSelected(int item);
+
+ /** Select the first entry matching the given tag. */
+ void setSelectedTag(uint32 tag);
+
+ int getSelected() const { return _selectedItem; }
+ uint32 getSelectedTag() const { return (_selectedItem >= 0) ? _entries[_selectedItem].tag : (uint32)-1; }
+// const String& getSelectedString() const { return (_selectedItem >= 0) ? _entries[_selectedItem].name : String::emptyString; }
+
+ void handleMouseEntered(int button) { setFlags(WIDGET_HILITED); draw(); }
+ void handleMouseLeft(int button) { clearFlags(WIDGET_HILITED); draw(); }
+
+ virtual void reflowLayout();
+protected:
+ void drawWidget();
+};
+
+} // End of namespace GUI
+
+#endif
diff --git a/gui/widgets/scrollbar.cpp b/gui/widgets/scrollbar.cpp
new file mode 100644
index 0000000000..2671fc7d61
--- /dev/null
+++ b/gui/widgets/scrollbar.cpp
@@ -0,0 +1,233 @@
+/* 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 "gui/widgets/scrollbar.h"
+#include "gui/dialog.h"
+#include "gui/GuiManager.h"
+
+#include "common/timer.h"
+
+namespace GUI {
+
+#define UP_DOWN_BOX_HEIGHT (_w+1)
+
+ScrollBarWidget::ScrollBarWidget(GuiObject *boss, int x, int y, int w, int h)
+ : Widget (boss, x, y, w, h), CommandSender(boss) {
+ setFlags(WIDGET_ENABLED | WIDGET_TRACK_MOUSE | WIDGET_CLEARBG | WIDGET_WANT_TICKLE);
+ _type = kScrollBarWidget;
+
+ _part = kNoPart;
+ _sliderHeight = 0;
+ _sliderPos = 0;
+
+ _draggingPart = kNoPart;
+ _sliderDeltaMouseDownPos = 0;
+
+ _numEntries = 0;
+ _entriesPerPage = 0;
+ _currentPos = 0;
+}
+
+static void upArrowRepeater(void *ref) {
+ ScrollBarWidget *sb = (ScrollBarWidget *)ref;
+ int old_pos = sb->_currentPos;
+
+ sb->_currentPos -= 3;
+ sb->checkBounds(old_pos);
+
+ g_system->getTimerManager()->removeTimerProc(&upArrowRepeater);
+ g_system->getTimerManager()->installTimerProc(&upArrowRepeater, 1000000/10, ref);
+}
+
+static void downArrowRepeater(void *ref) {
+ ScrollBarWidget *sb = (ScrollBarWidget *)ref;
+ int old_pos = sb->_currentPos;
+
+ sb->_currentPos += 3;
+ sb->checkBounds(old_pos);
+
+ g_system->getTimerManager()->removeTimerProc(&downArrowRepeater);
+ g_system->getTimerManager()->installTimerProc(&downArrowRepeater, 1000000/10, ref);
+}
+
+void ScrollBarWidget::handleMouseDown(int x, int y, int button, int clickCount) {
+ int old_pos = _currentPos;
+
+ // Do nothing if there are less items than fit on one page
+ if (_numEntries <= _entriesPerPage)
+ return;
+
+ if (y <= UP_DOWN_BOX_HEIGHT) {
+ // Up arrow
+ _currentPos--;
+ _draggingPart = kUpArrowPart;
+ g_system->getTimerManager()->installTimerProc(&upArrowRepeater, 1000000/2, this);
+ } else if (y >= _h - UP_DOWN_BOX_HEIGHT) {
+ // Down arrow
+ _currentPos++;
+ _draggingPart = kDownArrowPart;
+ g_system->getTimerManager()->installTimerProc(&downArrowRepeater, 1000000/2, this);
+ } else if (y < _sliderPos) {
+ _currentPos -= _entriesPerPage - 1;
+ } else if (y >= _sliderPos + _sliderHeight) {
+ _currentPos += _entriesPerPage - 1;
+ } else {
+ _draggingPart = kSliderPart;
+ _sliderDeltaMouseDownPos = y - _sliderPos;
+ }
+
+ // Make sure that _currentPos is still inside the bounds
+ checkBounds(old_pos);
+}
+
+void ScrollBarWidget::handleMouseUp(int x, int y, int button, int clickCount) {
+ _draggingPart = kNoPart;
+
+ g_system->getTimerManager()->removeTimerProc(&upArrowRepeater);
+ g_system->getTimerManager()->removeTimerProc(&downArrowRepeater);
+}
+
+void ScrollBarWidget::handleMouseWheel(int x, int y, int direction) {
+ int old_pos = _currentPos;
+
+ if (_numEntries < _entriesPerPage)
+ return;
+
+ if (direction < 0) {
+ _currentPos--;
+ } else {
+ _currentPos++;
+ }
+
+ // Make sure that _currentPos is still inside the bounds
+ checkBounds(old_pos);
+}
+
+void ScrollBarWidget::handleMouseMoved(int x, int y, int button) {
+ // Do nothing if there are less items than fit on one page
+ if (_numEntries <= _entriesPerPage)
+ return;
+
+ if (_draggingPart == kSliderPart) {
+ int old_pos = _currentPos;
+ _sliderPos = y - _sliderDeltaMouseDownPos;
+
+ if (_sliderPos < UP_DOWN_BOX_HEIGHT)
+ _sliderPos = UP_DOWN_BOX_HEIGHT;
+
+ if (_sliderPos > _h - UP_DOWN_BOX_HEIGHT - _sliderHeight)
+ _sliderPos = _h - UP_DOWN_BOX_HEIGHT - _sliderHeight;
+
+ _currentPos =
+ (_sliderPos - UP_DOWN_BOX_HEIGHT) * (_numEntries - _entriesPerPage) / (_h - 2 * UP_DOWN_BOX_HEIGHT - _sliderHeight);
+ checkBounds(old_pos);
+ } else {
+ int old_part = _part;
+
+ if (y <= UP_DOWN_BOX_HEIGHT) // Up arrow
+ _part = kUpArrowPart;
+ else if (y >= _h - UP_DOWN_BOX_HEIGHT) // Down arrow
+ _part = kDownArrowPart;
+ else if (y < _sliderPos)
+ _part = kPageUpPart;
+ else if (y >= _sliderPos + _sliderHeight)
+ _part = kPageDownPart;
+ else
+ _part = kSliderPart;
+
+ if (old_part != _part)
+ draw();
+ }
+}
+
+void ScrollBarWidget::handleTickle() {
+/*
+ // FIXME/TODO - this code is supposed to allow for "click-repeat" (like key repeat),
+ // i.e. if you click on one of the arrows and keep clicked, it will scroll
+ // continuously. However, just like key repeat, this requires two delays:
+ // First an "initial" delay that has to pass before repeating starts (otherwise
+ // it is near to impossible to achieve single clicks). Secondly, a repeat delay
+ // that determines how often per second a click is simulated.
+ int old_pos = _currentPos;
+
+ if (_draggingPart == kUpArrowPart)
+ _currentPos--;
+ else if (_draggingPart == kDownArrowPart)
+ _currentPos++;
+
+ // Make sure that _currentPos is still inside the bounds
+ checkBounds(old_pos);
+*/
+}
+
+void ScrollBarWidget::checkBounds(int old_pos) {
+ if (_numEntries <= _entriesPerPage || _currentPos < 0)
+ _currentPos = 0;
+ else if (_currentPos > _numEntries - _entriesPerPage)
+ _currentPos = _numEntries - _entriesPerPage;
+
+ if (old_pos != _currentPos) {
+ recalc();
+ draw();
+ sendCommand(kSetPositionCmd, _currentPos);
+ }
+}
+
+void ScrollBarWidget::recalc() {
+ if (_numEntries > _entriesPerPage) {
+ _sliderHeight = (_h - 2 * UP_DOWN_BOX_HEIGHT) * _entriesPerPage / _numEntries;
+ if (_sliderHeight < UP_DOWN_BOX_HEIGHT)
+ _sliderHeight = UP_DOWN_BOX_HEIGHT;
+
+ _sliderPos =
+ UP_DOWN_BOX_HEIGHT + (_h - 2 * UP_DOWN_BOX_HEIGHT - _sliderHeight) * _currentPos / (_numEntries - _entriesPerPage);
+ if (_sliderPos < 0)
+ _sliderPos = 0;
+ setVisible(true);
+ } else {
+ _sliderHeight = _h - 2 * UP_DOWN_BOX_HEIGHT;
+ _sliderPos = UP_DOWN_BOX_HEIGHT;
+ setVisible(false);
+ }
+}
+
+void ScrollBarWidget::drawWidget() {
+ if (_draggingPart != kNoPart)
+ _part = _draggingPart;
+
+ ThemeEngine::ScrollbarState state = ThemeEngine::kScrollbarStateNo;
+ if (_numEntries <= _entriesPerPage) {
+ state = ThemeEngine::kScrollbarStateSinglePage;
+ } else if (_part == kUpArrowPart) {
+ state = ThemeEngine::kScrollbarStateUp;
+ } else if (_part == kDownArrowPart) {
+ state = ThemeEngine::kScrollbarStateDown;
+ } else if (_part == kSliderPart) {
+ state = ThemeEngine::kScrollbarStateSlider;
+ }
+
+ g_gui.theme()->drawScrollbar(Common::Rect(_x, _y, _x+_w, _y+_h), _sliderPos, _sliderHeight, state, _state);
+}
+
+} // End of namespace GUI
diff --git a/gui/widgets/scrollbar.h b/gui/widgets/scrollbar.h
new file mode 100644
index 0000000000..53a3369fb4
--- /dev/null
+++ b/gui/widgets/scrollbar.h
@@ -0,0 +1,84 @@
+/* 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$
+ */
+
+#ifndef GUI_WIDGETS_SCROLLBAR_H
+#define GUI_WIDGETS_SCROLLBAR_H
+
+#include "gui/widget.h"
+
+namespace GUI {
+
+enum {
+ kSetPositionCmd = 'SETP'
+};
+
+
+class ScrollBarWidget : public Widget, public CommandSender {
+protected:
+ typedef enum {
+ kNoPart,
+ kUpArrowPart,
+ kDownArrowPart,
+ kSliderPart,
+ kPageUpPart,
+ kPageDownPart
+ } Part;
+
+ Part _part;
+ int _sliderHeight;
+ int _sliderPos;
+
+ Part _draggingPart;
+ int _sliderDeltaMouseDownPos;
+
+public:
+ int _numEntries;
+ int _entriesPerPage;
+ int _currentPos;
+
+public:
+ ScrollBarWidget(GuiObject *boss, int x, int y, int w, int h);
+
+ void handleMouseDown(int x, int y, int button, int clickCount);
+ void handleMouseUp(int x, int y, int button, int clickCount);
+ void handleMouseWheel(int x, int y, int direction);
+ void handleMouseMoved(int x, int y, int button);
+ void handleMouseEntered(int button) { setFlags(WIDGET_HILITED); }
+ void handleMouseLeft(int button) { clearFlags(WIDGET_HILITED); _part = kNoPart; draw(); }
+ void handleTickle();
+
+ // FIXME - this should be private, but then we also have to add accessors
+ // for _numEntries, _entriesPerPage and _currentPos. This again leads to the question:
+ // should these accessors force a redraw?
+ void recalc();
+
+ void checkBounds(int old_pos);
+
+protected:
+ void drawWidget();
+};
+
+} // End of namespace GUI
+
+#endif
diff --git a/gui/widgets/tab.cpp b/gui/widgets/tab.cpp
new file mode 100644
index 0000000000..48b4dab430
--- /dev/null
+++ b/gui/widgets/tab.cpp
@@ -0,0 +1,320 @@
+/* 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/util.h"
+#include "gui/widgets/tab.h"
+#include "gui/dialog.h"
+#include "gui/GuiManager.h"
+
+#include "gui/ThemeEval.h"
+
+namespace GUI {
+
+enum {
+ kCmdLeft = 'LEFT',
+ kCmdRight = 'RGHT'
+};
+
+TabWidget::TabWidget(GuiObject *boss, int x, int y, int w, int h)
+ : Widget(boss, x, y, w, h), _bodyBackgroundType(GUI::ThemeEngine::kDialogBackgroundDefault) {
+ init();
+}
+
+TabWidget::TabWidget(GuiObject *boss, const String &name)
+ : Widget(boss, name), _bodyBackgroundType(GUI::ThemeEngine::kDialogBackgroundDefault) {
+ init();
+}
+
+void TabWidget::init() {
+ setFlags(WIDGET_ENABLED);
+ _type = kTabWidget;
+ _activeTab = -1;
+ _firstVisibleTab = 0;
+
+ _tabWidth = g_gui.xmlEval()->getVar("Globals.TabWidget.Tab.Width");
+ _tabHeight = g_gui.xmlEval()->getVar("Globals.TabWidget.Tab.Height");
+ _titleVPad = g_gui.xmlEval()->getVar("Globals.TabWidget.Tab.Padding.Top");
+
+ _bodyTP = g_gui.xmlEval()->getVar("Globals.TabWidget.Body.Padding.Top");
+ _bodyBP = g_gui.xmlEval()->getVar("Globals.TabWidget.Body.Padding.Bottom");
+ _bodyLP = g_gui.xmlEval()->getVar("Globals.TabWidget.Body.Padding.Left");
+ _bodyRP = g_gui.xmlEval()->getVar("Globals.TabWidget.Body.Padding.Right");
+
+ _butRP = g_gui.xmlEval()->getVar("Globals.TabWidget.NavButtonPadding.Right", 0);
+ _butTP = g_gui.xmlEval()->getVar("Globals.TabWidget.NavButton.Padding.Top", 0);
+ _butW = g_gui.xmlEval()->getVar("Globals.TabWidget.NavButton.Width", 10);
+ _butH = g_gui.xmlEval()->getVar("Globals.TabWidget.NavButton.Height", 10);
+
+ int x = _w - _butRP - _butW * 2 - 2;
+ int y = _butTP - _tabHeight;
+ _navLeft = new ButtonWidget(this, x, y, _butW, _butH, "<", 0, kCmdLeft);
+ _navRight = new ButtonWidget(this, x + _butW + 2, y, _butW, _butH, ">", 0, kCmdRight);
+}
+
+TabWidget::~TabWidget() {
+ _firstWidget = 0;
+ for (uint i = 0; i < _tabs.size(); ++i) {
+ delete _tabs[i].firstWidget;
+ _tabs[i].firstWidget = 0;
+ }
+ _tabs.clear();
+ delete _navRight;
+}
+
+int16 TabWidget::getChildY() const {
+ return getAbsY() + _tabHeight;
+}
+
+int TabWidget::addTab(const String &title) {
+ // Add a new tab page
+ Tab newTab;
+ newTab.title = title;
+ newTab.firstWidget = 0;
+
+ _tabs.push_back(newTab);
+
+ int numTabs = _tabs.size();
+
+ // HACK: Nintendo DS uses a custom config dialog. This dialog does not work with
+ // our default "Globals.TabWidget.Tab.Width" setting.
+ //
+ // TODO: Add proper handling in the theme layout for such cases.
+ //
+ // There are different solutions to this problem:
+ // - offer a "Tab.Width" setting per tab widget and thus let the Ninteno DS
+ // backend set a default value for its special dialog.
+ //
+ // - change our themes to use auto width calculaction by default
+ //
+ // - change "Globals.TabWidget.Tab.Width" to be the minimal tab width setting and
+ // rename it accordingly.
+ // Actually this solution is pretty similar to our HACK for the Nintendo DS
+ // backend. This hack enables auto width calculation by default with the
+ // "Globals.TabWidget.Tab.Width" value as minimal width for the tab buttons.
+ //
+ // - we might also consider letting every tab button having its own width.
+ //
+ // - other solutions you can think of, which are hopefully less evil ;-).
+ //
+ // Of course also the Ninteno DS' dialog should be in our layouting engine, instead
+ // of being hard coded like it is right now.
+ //
+ // There are checks for __DS__ all over this source file to take care of the
+ // aforemnetioned problem.
+#ifdef __DS__
+ if (true) {
+#else
+ if (g_gui.xmlEval()->getVar("Globals.TabWidget.Tab.Width") == 0) {
+#endif
+ if (_tabWidth == 0)
+ _tabWidth = 40;
+ // Determine the new tab width
+ int newWidth = g_gui.getStringWidth(title) + 2 * 3;
+ if (_tabWidth < newWidth)
+ _tabWidth = newWidth;
+ int maxWidth = _w / numTabs;
+ if (_tabWidth > maxWidth)
+ _tabWidth = maxWidth;
+ }
+
+ // Activate the new tab
+ setActiveTab(numTabs - 1);
+
+ return _activeTab;
+}
+
+void TabWidget::removeTab(int tabID) {
+ assert(0 <= tabID && tabID < (int)_tabs.size());
+
+ // Deactive the tab if it's currently the active one
+ if (tabID == _activeTab) {
+ _tabs[tabID].firstWidget = _firstWidget;
+ releaseFocus();
+ _firstWidget = 0;
+ }
+
+ // Dispose the widgets in that tab and then the tab itself
+ delete _tabs[tabID].firstWidget;
+ _tabs.remove_at(tabID);
+
+ // Adjust _firstVisibleTab if necessary
+ if (_firstVisibleTab >= (int)_tabs.size()) {
+ _firstVisibleTab = MAX(0, (int)_tabs.size() - 1);
+ }
+
+ // The active tab was removed, so select a new active one (if any remains)
+ if (tabID == _activeTab) {
+ _activeTab = -1;
+ if (tabID >= (int)_tabs.size())
+ tabID = _tabs.size() - 1;
+ if (tabID >= 0)
+ setActiveTab(tabID);
+ }
+
+ // Finally trigger a redraw
+ _boss->draw();
+}
+
+void TabWidget::setActiveTab(int tabID) {
+ assert(0 <= tabID && tabID < (int)_tabs.size());
+ if (_activeTab != tabID) {
+ // Exchange the widget lists, and switch to the new tab
+ if (_activeTab != -1) {
+ _tabs[_activeTab].firstWidget = _firstWidget;
+ releaseFocus();
+ }
+ _activeTab = tabID;
+ _firstWidget = _tabs[tabID].firstWidget;
+ _boss->draw();
+ }
+}
+
+
+void TabWidget::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
+ Widget::handleCommand(sender, cmd, data);
+
+ switch (cmd) {
+ case kCmdLeft:
+ if (_firstVisibleTab) {
+ _firstVisibleTab--;
+ draw();
+ }
+ break;
+
+ case kCmdRight:
+ if (_firstVisibleTab + _w / _tabWidth < (int)_tabs.size()) {
+ _firstVisibleTab++;
+ draw();
+ }
+ break;
+ }
+}
+
+void TabWidget::handleMouseDown(int x, int y, int button, int clickCount) {
+ assert(y < _tabHeight);
+
+ // Determine which tab was clicked
+ int tabID = -1;
+ if (x >= 0 && (x % _tabWidth) < _tabWidth) {
+ tabID = x / _tabWidth;
+ if (tabID >= (int)_tabs.size())
+ tabID = -1;
+ }
+
+ // If a tab was clicked, switch to that pane
+ if (tabID >= 0 && tabID + _firstVisibleTab < (int)_tabs.size()) {
+ setActiveTab(tabID + _firstVisibleTab);
+ }
+}
+
+bool TabWidget::handleKeyDown(Common::KeyState state) {
+ // TODO: maybe there should be a way to switch between tabs
+ // using the keyboard? E.g. Alt-Shift-Left/Right-Arrow or something
+ // like that.
+ return Widget::handleKeyDown(state);
+}
+
+void TabWidget::reflowLayout() {
+ Widget::reflowLayout();
+
+ for (uint i = 0; i < _tabs.size(); ++i) {
+ Widget *w = _tabs[i].firstWidget;
+ while (w) {
+ w->reflowLayout();
+ w = w->next();
+ }
+ }
+
+ _tabHeight = g_gui.xmlEval()->getVar("Globals.TabWidget.Tab.Height");
+ _tabWidth = g_gui.xmlEval()->getVar("Globals.TabWidget.Tab.Width");
+ _titleVPad = g_gui.xmlEval()->getVar("Globals.TabWidget.Tab.Padding.Top");
+
+ if (_tabWidth == 0) {
+ _tabWidth = 40;
+#ifdef __DS__
+ }
+ if (true) {
+#endif
+ int maxWidth = _w / _tabs.size();
+
+ for (uint i = 0; i < _tabs.size(); ++i) {
+ // Determine the new tab width
+ int newWidth = g_gui.getStringWidth(_tabs[i].title) + 2 * 3;
+ if (_tabWidth < newWidth)
+ _tabWidth = newWidth;
+ if (_tabWidth > maxWidth)
+ _tabWidth = maxWidth;
+ }
+ }
+
+ _butRP = g_gui.xmlEval()->getVar("Globals.TabWidget.NavButton.PaddingRight", 0);
+ _butTP = g_gui.xmlEval()->getVar("Globals.TabWidget.NavButton.Padding.Top", 0);
+ _butW = g_gui.xmlEval()->getVar("GlobalsTabWidget.NavButton.Width", 10);
+ _butH = g_gui.xmlEval()->getVar("Globals.TabWidget.NavButton.Height", 10);
+
+ int x = _w - _butRP - _butW * 2 - 2;
+ int y = _butTP - _tabHeight;
+ _navLeft->resize(x, y, _butW, _butH);
+ _navRight->resize(x + _butW + 2, y, _butW, _butH);
+}
+
+void TabWidget::drawWidget() {
+ Common::Array<Common::String> tabs;
+ for (int i = _firstVisibleTab; i < (int)_tabs.size(); ++i) {
+ tabs.push_back(_tabs[i].title);
+ }
+ g_gui.theme()->drawDialogBackground(Common::Rect(_x + _bodyLP, _y + _bodyTP, _x+_w-_bodyRP, _y+_h-_bodyBP), _bodyBackgroundType);
+
+ g_gui.theme()->drawTab(Common::Rect(_x, _y, _x+_w, _y+_h), _tabHeight, _tabWidth, tabs, _activeTab - _firstVisibleTab, 0, _titleVPad);
+}
+
+void TabWidget::draw() {
+ Widget::draw();
+
+ if (_tabWidth * _tabs.size() > _w) {
+ _navLeft->draw();
+ _navRight->draw();
+ }
+}
+
+Widget *TabWidget::findWidget(int x, int y) {
+ if (y < _tabHeight) {
+ if (_tabWidth * _tabs.size() > _w) {
+ if (y >= _butTP && y < _butTP + _butH) {
+ if (x >= _w - _butRP - _butW * 2 - 2 && x < _w - _butRP - _butW - 2)
+ return _navLeft;
+ if (x >= _w - _butRP - _butW && x < _w - _butRP)
+ return _navRight;
+ }
+ }
+
+ // Click was in the tab area
+ return this;
+ } else {
+ // Iterate over all child widgets and find the one which was clicked
+ return Widget::findWidgetInChain(_firstWidget, x, y - _tabHeight);
+ }
+}
+
+} // End of namespace GUI
diff --git a/gui/widgets/tab.h b/gui/widgets/tab.h
new file mode 100644
index 0000000000..aec68cca0a
--- /dev/null
+++ b/gui/widgets/tab.h
@@ -0,0 +1,118 @@
+/* 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$
+ */
+
+#ifndef GUI_WIDGETS_TAB_H
+#define GUI_WIDGETS_TAB_H
+
+#include "gui/widget.h"
+#include "common/str.h"
+#include "common/array.h"
+
+namespace GUI {
+
+class TabWidget : public Widget {
+ typedef Common::String String;
+ struct Tab {
+ String title;
+ Widget *firstWidget;
+ };
+ typedef Common::Array<Tab> TabList;
+
+protected:
+ int _activeTab;
+ int _firstVisibleTab;
+ TabList _tabs;
+ int _tabWidth;
+ int _tabHeight;
+
+ int _bodyRP, _bodyTP, _bodyLP, _bodyBP;
+ ThemeEngine::DialogBackground _bodyBackgroundType;
+
+ int _titleVPad;
+
+ int _butRP, _butTP, _butW, _butH;
+
+ ButtonWidget *_navLeft, *_navRight;
+
+public:
+ TabWidget(GuiObject *boss, int x, int y, int w, int h);
+ TabWidget(GuiObject *boss, const String &name);
+ ~TabWidget();
+
+ void init();
+
+ /**
+ * Add a new tab with the given title. Returns a unique ID which can be used
+ * to identify the tab (to remove it / activate it etc.).
+ */
+ int addTab(const String &title);
+
+ /**
+ * Remove the tab with the given tab ID. Disposes all child widgets of that tab.
+ * TODO: This code is *unfinished*. In particular, it changes the
+ * tabIDs, so that they are not unique anymore! This is bad.
+ * If we need to, we could fix this by changing the tab IDs from being an index
+ * into the _tabs array to a real "unique" ID, which gets stored in the Tab struct.
+ * It won't be difficult to implement this, but since currently no code seems to make
+ * use of the feature...
+ */
+ void removeTab(int tabID);
+
+ int getActiveTab() {
+ return _activeTab;
+ }
+
+ /**
+ * Set the active tab by specifying a valid tab ID.
+ * setActiveTab changes the value of _firstWidget. This means new
+ * Widgets are always added to the active tab.
+ */
+ void setActiveTab(int tabID);
+
+ void setTabTitle(int tabID, const String &title) {
+ assert(0 <= tabID && tabID < (int)_tabs.size());
+ _tabs[tabID].title = title;
+ }
+
+ virtual void handleMouseDown(int x, int y, int button, int clickCount);
+ virtual bool handleKeyDown(Common::KeyState state);
+ virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
+
+ virtual void reflowLayout();
+
+ virtual void draw();
+
+protected:
+ // We overload getChildY to make sure child widgets are positioned correctly.
+ // Essentially this compensates for the space taken up by the tab title header.
+ virtual int16 getChildY() const;
+
+ virtual void drawWidget();
+
+ virtual Widget *findWidget(int x, int y);
+};
+
+} // End of namespace GUI
+
+#endif