/* 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

#include "common/system.h"
#include "common/events.h"

#include "engines/engine.h"

#include "graphics/surface.h"

#include "scumm/macgui/macgui_impl.h"

namespace Scumm {

// ---------------------------------------------------------------------------
// Widgets imitating the look and feel of an 80s Macintosh
// ---------------------------------------------------------------------------

// ---------------------------------------------------------------------------
// Base widget
// ---------------------------------------------------------------------------

MacGuiImpl::MacWidget::MacWidget(MacGuiImpl::MacDialogWindow *window, Common::Rect bounds, Common::String text, bool enabled) : MacGuiObject(bounds, enabled), _window(window), _text(text) {
	_black = _window->_gui->getBlack();
	_white = _window->_gui->getWhite();

	// Widgets are clipped to the inner surface of the dialog. If a widget
	// is clipped out of existence, make it invisible to avoid crashes.

	Graphics::Surface *s = _window->innerSurface();

	_bounds.clip(Common::Rect(s->w, s->h));

	if (_bounds.width() <= 0 || _bounds.height() <= 0)
		_visible = false;
}

Common::String MacGuiImpl::MacWidget::getText() const {
	Common::String temp = Common::U32String(_text, Common::kMacRoman).encode(Common::kUtf8);
	return temp;
}

bool MacGuiImpl::MacWidget::findWidget(int x, int y) const {
	return _enabled && _bounds.contains(x, y);
}

void MacGuiImpl::MacWidget::setRedraw(bool fullRedraw) {
	if (fullRedraw)
		_fullRedraw = true;
	else
		_redraw = true;
}

void MacGuiImpl::MacWidget::setEnabled(bool enabled) {
	_enabled = enabled;
	setRedraw(true);
}

void MacGuiImpl::MacWidget::setValue(int value) {
	_value = value;
	setRedraw();
}

void MacGuiImpl::MacWidget::drawBitmap(Common::Rect r, const uint16 *bitmap, uint32 color) const {
	_window->_gui->drawBitmap(_window->innerSurface(), r, bitmap, color);
}

int MacGuiImpl::MacWidget::drawText(Common::String text, int x, int y, int w, uint32 fg, uint32 bg, Graphics::TextAlign align, bool wordWrap, int deltax) const {
	if (text.empty())
		return 0;

	if (fg == 0 && bg == 0) {
		fg = _black;
		bg = _white;
	}

	const Graphics::Font *font = _window->_gui->getFont(kSystemFont);

	// Apply text substitutions

	for (uint i = 0; i < text.size() - 1; i++) {
		if (text[i] == '^') {
			uint nr = text[i + 1] - '0';
			if (_window->hasSubstitution(nr)) {
				Common::String &subst = _window->getSubstitution(nr);
				text.replace(i, 2, subst);
			}
		}
	}

	// Word-wrap text

	Common::StringArray lines;
	int maxLineWidth = 0;

	if (wordWrap) {
		maxLineWidth = font->wordWrapText(text, w, lines);
	} else {
		lines.push_back(text);
		maxLineWidth = font->getStringWidth(text);
	}

	// Draw the text. Disabled text is implemented as a filter on top of
	// the already drawn text.

	int y0 = y;

	for (uint i = 0; i < lines.size(); i++) {
		font->drawString(_window->innerSurface(), lines[i], x, y0, w, fg, align, deltax);

		if (!_enabled) {
			Common::Rect textBox = font->getBoundingBox(lines[i], x, y0, w, align);

			for (int y1 = textBox.top; y1 < textBox.bottom; y1++) {
				for (int x1 = textBox.left; x1 < textBox.right; x1++) {
					if (((x1 + y1) % 2) == 0)
						_window->innerSurface()->setPixel(x1, y1, bg);
				}
			}
		}

		y0 += font->getFontHeight();
	}

	return maxLineWidth;
}

// ---------------------------------------------------------------------------
// Button widget
// ---------------------------------------------------------------------------

void MacGuiImpl::MacButton::draw(bool drawFocused) {
	if (!_redraw && !_fullRedraw)
		return;

	debug(1, "MacGuiImpl::MacButton: Drawing button %d (_fullRedraw = %d, drawFocused = %d, _value = %d)", _id, _fullRedraw, drawFocused, _value);

	// ScummVM's rounded rectangles aren't a complete match for QuickDraw's
	// rounded rectangles, and for arcs this small they don't look very
	// good. So we draw the corners manually.

	CornerLine buttonCorner[] = { { 0, 0 }, { 1, 2 }, { 1, 1 }, { 0, -1 } };
	CornerLine smallButtonCorner[] = { { 0, 0 }, { 1, 2 }, { 1, 1 }, { 0, -1 } };
	CornerLine frameCorner[] = { { 5, 1 }, { 3, 3 }, { 2, 4 }, { 1, 5 }, { 1, 3 }, { 0, 4 }, { 0, -1 } };

	Graphics::Surface *s = _window->innerSurface();
	uint32 fg, bg;

	if (drawFocused || (_window->getFocusedWidget() == this && _bounds.contains(_window->getMousePos()))) {
		fg = _white;
		bg = _black;
	} else {
		fg = _black;
		bg = _white;
	}

	int x0 = _bounds.left;
	int x1 = _bounds.right - 1;

	int y0 = _bounds.top;
	int y1 = _bounds.bottom - 1;

	CornerLine *corner;
	int cornerSize;

	if (_bounds.height() >= 20) {
		corner = buttonCorner;
		cornerSize = 3;
	} else {
		corner = smallButtonCorner;
		cornerSize = 2;
	}

	s->hLine(x0 + cornerSize, y0, x1 - cornerSize, _black);
	s->hLine(x0 + cornerSize, y1, x1 - cornerSize, _black);
	s->vLine(x0, y0 + cornerSize, y1 - cornerSize, _black);
	s->vLine(x1, y0 + cornerSize, y1 - cornerSize, _black);

	// The way the corners are rounded, we can fill this entire rectangle
	// in one go.

	Common::Rect inside = _bounds;
	inside.grow(-1);
	s->fillRect(inside, bg);

	drawCorners(_bounds, corner, true);

	const Graphics::Font *font = _window->_gui->getFont(kSystemFont);

	drawText(_text, _bounds.left, _bounds.top + (_bounds.height() - font->getFontHeight()) / 2, _bounds.width(), fg, bg, Graphics::kTextAlignCenter);

	Common::Rect bounds = _bounds;

	if (_window->getDefaultWidget() == this && _fullRedraw) {
		bounds.grow(4);

		x0 = bounds.left;
		x1 = bounds.right - 1;

		y0 = bounds.top;
		y1 = bounds.bottom - 1;

		for (int i = 0; i < 3; i++) {
			hLine(x0 + 6, y0 + i, x1 - 6, _enabled);
			hLine(x0 + 6, y1 - i, x1 - 6, _enabled);
			vLine(x0 + i, y0 + 6, y1 - 6, _enabled);
			vLine(x1 - i, y0 + 6, y1 - 6, _enabled);
		}

		drawCorners(bounds, frameCorner, _enabled);
	}

	_redraw = false;
	_fullRedraw = false;

	_window->markRectAsDirty(bounds);
}

void MacGuiImpl::MacButton::hLine(int x0, int y0, int x1, bool enabled) {
	Graphics::Surface *s = _window->innerSurface();

	if (!enabled) {
		if (x0 > x1)
			SWAP(x0, x1);

		for (int x = x0; x <= x1; x++) {
			if (((x + y0) % 2) == 0)
				s->setPixel(x, y0, _black);
			else
				s->setPixel(x, y0, _white);
		}
	} else
		s->hLine(x0, y0, x1, _black);
}

void MacGuiImpl::MacButton::vLine(int x0, int y0, int y1, bool enabled) {
	Graphics::Surface *s = _window->innerSurface();

	if (!enabled) {
		if (y0 > y1)
			SWAP(y0, y1);

		for (int y = y0; y <= y1; y++) {
			if (((x0 + y) % 2) == 0)
				s->setPixel(x0, y, _black);
			else
				s->setPixel(x0, y, _white);
		}
	} else
		s->vLine(x0, y0, y1, _black);
}

void MacGuiImpl::MacButton::drawCorners(Common::Rect r, CornerLine *corner, bool enabled) {
	for (int i = 0; corner[i].length >= 0; i++) {
		if (corner[i].length == 0)
			continue;

		int x0 = r.left + corner[i].start;
		int x1 = r.left + corner[i].start + corner[i].length - 1;
		int x2 = r.right - 1 - corner[i].start;
		int x3 = r.right - 1 - corner[i].start - corner[i].length + 1;

		int y0 = r.top + i;
		int y1 = r.bottom - i - 1;

		hLine(x0, y0, x1, enabled);
		hLine(x2, y0, x3, enabled);
		hLine(x0, y1, x1, enabled);
		hLine(x2, y1, x3, enabled);
	}
}

// ---------------------------------------------------------------------------
// Checkbox widget
// ---------------------------------------------------------------------------

MacGuiImpl::MacCheckbox::MacCheckbox(MacGuiImpl::MacDialogWindow *window, Common::Rect bounds, Common::String text, bool enabled) : MacWidget(window, bounds, text, enabled) {
	// The DITL may define a larger than necessary area for the checkbox,
	// so we need to calculate the hit bounds.

	const Graphics::Font *font = _window->_gui->getFont(kSystemFont);

	_hitBounds.left = _bounds.left;
	_hitBounds.top = _bounds.bottom - _bounds.height() / 2 - 8;
	_hitBounds.bottom = _hitBounds.top + 16;
	_hitBounds.right = _bounds.left + 18 + font->getStringWidth(_text) + 2;
}

bool MacGuiImpl::MacCheckbox::findWidget(int x, int y) const {
	return _hitBounds.contains(x, y);
}

void MacGuiImpl::MacCheckbox::draw(bool drawFocused) {
	if (!_redraw && !_fullRedraw)
		return;

	debug(1, "MacGuiImpl::MacCheckbox: Drawing checkbox %d (_fullRedraw = %d, drawFocused = %d, _value = %d)", _id, _fullRedraw, drawFocused, _value);

	Graphics::Surface *s = _window->innerSurface();

	Common::Rect box(_hitBounds.left + 2, _hitBounds.top + 2, _hitBounds.left + 14, _hitBounds.top + 14);

	if (_fullRedraw) {
		_window->innerSurface()->fillRect(_bounds, _white);

		int x = _hitBounds.left + 18;
		int y = _hitBounds.top;

		drawText(_text, x, y, _hitBounds.right - x);
		_window->markRectAsDirty(_bounds);
	} else
		_window->markRectAsDirty(box);

	s->fillRect(box, _black);
	if (drawFocused || (_window->getFocusedWidget() == this && _hitBounds.contains(_window->getMousePos()))) {
		box.grow(-2);
	} else {
		box.grow(-1);
	}
	s->fillRect(box, _white);

	if (_value && _enabled) {
		s->drawLine(box.left, box.top, box.right - 1, box.bottom - 1, _black);
		s->drawLine(box.left, box.bottom - 1, box.right - 1, box.top, _black);
	}

	_redraw = false;
	_fullRedraw = false;
}

bool MacGuiImpl::MacCheckbox::handleMouseUp(Common::Event &event) {
	_value = _value ? 0 : 1;
	setRedraw();
	return true;
}

// ---------------------------------------------------------------------------
// Static text widget. Text is encoded as MacRoman, so any outside strings
// (e.g.save file names or hard-coded texts) have to be re-encoded.
// ---------------------------------------------------------------------------

void MacGuiImpl::MacStaticText::draw(bool drawFocused) {
	if (!_redraw && !_fullRedraw)
		return;

	debug(1, "MacGuiImpl::MacStaticText: Drawing text %d (_fullRedraw = %d, drawFocused = %d, _value = %d)", _id, _fullRedraw, drawFocused, _value);

	_window->innerSurface()->fillRect(_bounds, _bg);
	drawText(_text, _bounds.left, _bounds.top, _bounds.width(), _fg, _bg, _alignment, _wordWrap, 1);
	_window->markRectAsDirty(_bounds);

	_redraw = false;
	_fullRedraw = false;
}

// ---------------------------------------------------------------------------
// Editable text widget
//
// Text is encoded as MacRoman, and has to be re-encoded before handed over
// to the outside world.
//
// The current edit position is stored in _caretPos. This holds the character
// the caret is placed right after, so 0 is the first character.
//
// The length of the current selection is stored in _selectionLen. Selections
// can extend left or right of the _caretPos. This makes it slightly more
// tricky to handle selections after they've been made, but simplifies the
// actual selection by mouse enormously.
// ---------------------------------------------------------------------------

MacGuiImpl::MacEditText::MacEditText(MacGuiImpl::MacDialogWindow *window, Common::Rect bounds, Common::String text, bool enabled) : MacWidget(window, bounds, text, enabled) {
	_font = _window->_gui->getFont(kSystemFont);

	// This widget gets its own text surface, to ensure that the text is
	// properly clipped to the widget's bounds. Technically, the other
	// widgets that draw text could use this too, but there we assume that
	// the texts are chosen to fit without clipping.

	Common::Rect textBounds = _bounds;
	textBounds.left++;

	_textSurface = _window->innerSurface()->getSubArea(textBounds);
}

bool MacGuiImpl::MacEditText::findWidget(int x, int y) const {
	// Once we start drag-selecting, any mouse position is considered
	// within the widget.

	if (_window->getFocusedWidget() == this)
		return true;

	return _bounds.contains(x, y);
}

int MacGuiImpl::MacEditText::getTextPosFromMouse(int x, int y) {
	if (_text.empty())
		return 0;

	if (y < _bounds.top)
		return 0;

	if (y >= _bounds.bottom)
		return _text.size();

	x -= _bounds.left;

	int textX = _textPos;
	uint i;

	for (i = 0; i < _text.size() && textX <= _bounds.width(); i++) {
		int charWidth = _font->getCharWidth(_text[i]);

		if (x >= textX && x < textX + charWidth) {
			if (x > textX + charWidth / 2)
				return i + 1;
			return i;
		}

		textX += charWidth;
	}

	if (x <= _bounds.left)
		return 0;

	return i;
}

void MacGuiImpl::MacEditText::updateSelection(int x, int y) {
	int oldSelectLen = _selectLen;

	int pos = getTextPosFromMouse(x, y);

	_selectLen = pos - _caretPos;

	if (_selectLen != oldSelectLen)
		setRedraw();
}

void MacGuiImpl::MacEditText::deleteSelection() {
	int startPos;
	int len;

	if (_selectLen < 0) {
		startPos = _caretPos + _selectLen;
		len = -_selectLen;
	} else {
		startPos = _caretPos;
		len = _selectLen;
	}

	_text.erase(startPos, len);
	_caretPos = startPos;
	_selectLen = 0;
	setRedraw();
}

void MacGuiImpl::MacEditText::selectAll() {
	_caretPos = 0;
	_selectLen = _text.size();
	setRedraw();
}

void MacGuiImpl::MacEditText::draw(bool drawFocused) {
	int caretX = 0;

	// Calculate the caret position, and make sure that it will be placed
	// inside the visible area of the widget. This may require scrolling
	// the text, which will trigger a redraw.

	if (_selectLen == 0) {
		caretX = _textPos - 1;

		for (int i = 0; i < _caretPos; i++)
			caretX += _font->getCharWidth((byte)_text[i]);

		int delta = 0;

		if (caretX < 0)
			delta = caretX;
		else if (caretX >= _textSurface.w)
			delta = caretX - _textSurface.w + 1;

		if (delta) {
			_textPos -= delta;
			_caretX -= delta;
			caretX -= delta;
			setRedraw();
		}
	}

	// Redraw the contents of the widget. This redraws the entire text,
	// which is a bit wasteful.

	if (_redraw || _fullRedraw) {
		Graphics::Surface *s = _window->innerSurface();

		s->fillRect(_bounds, _white);

		int selectStart = -1;
		int selectEnd = -1;

		if (_selectLen != 0) {
			if (_selectLen < 0) {
				selectStart = _caretPos + _selectLen;
				selectEnd = _caretPos - 1;
			} else {
				selectStart = _caretPos;
				selectEnd = _caretPos + _selectLen - 1;
			}
		}

		int x = _textPos;
		int y = 0;

		bool firstChar = true;
		bool firstCharSelected = false;
		bool lastCharSelected = false;

		for (int i = 0; i < (int)_text.size() && x < _textSurface.w; i++) {
			uint32 color = _black;
			int charWidth = _font->getCharWidth((byte)_text[i]);

			if (x + charWidth >= 0) {
				if (_selectLen != 0 && i >= selectStart && i <= selectEnd) {
					if (firstChar)
						firstCharSelected = true;
					lastCharSelected = true;

					_textSurface.fillRect(Common::Rect(x, 0, x + charWidth, _textSurface.h), _black);
					color = _white;
				} else
					lastCharSelected = false;

				_font->drawChar(&_textSurface, (byte)_text[i], x, y, color);
				firstChar = false;
			}

			x += charWidth;
		}

		if (firstCharSelected)
			_window->innerSurface()->fillRect(Common::Rect(_bounds.left + 1, _bounds.top, _bounds.left + 2, _bounds.bottom), _black);

		if (lastCharSelected && _bounds.left + x + 1 < _bounds.right)
			_window->innerSurface()->fillRect(Common::Rect(_bounds.left + x + 1, _bounds.top, _bounds.right, _bounds.bottom), _black);

		_window->markRectAsDirty(_bounds);
	}

	// Redraw the caret, if it has changed since the last time.

	if (_selectLen == 0) {
		uint32 now = _window->_system->getMillis();
		bool caretVisible = _caretVisible;

		if (now >= _nextCaretBlink) {
			_caretVisible = !_caretVisible;
			_nextCaretBlink = now + 500;
		}

		if (caretX != _caretX || caretVisible != _caretVisible) {
			if (caretX != _caretX && !_redraw && !_fullRedraw) {
				// Erase the old caret. Not needed if the whole
				// widget was just redrawn.

				_textSurface.vLine(_caretX, 0, _bounds.bottom - 1, _white);
				if (!_redraw && !_fullRedraw)
					_window->markRectAsDirty(Common::Rect(_bounds.left + _caretX + 1, _bounds.top, _bounds.left + _caretX + 2, _bounds.bottom));
			}

			// Draw the new caret

			_textSurface.vLine(caretX, 0, _bounds.bottom - 1, _caretVisible ? _black : _white);

			if (!_redraw && !_fullRedraw)
				_window->markRectAsDirty(Common::Rect(_bounds.left + caretX + 1, _bounds.top, _bounds.left + caretX + 2, _bounds.bottom));

			_caretX = caretX;
		}
	}

	_redraw = false;
	_fullRedraw = false;
}

void MacGuiImpl::MacEditText::handleMouseDown(Common::Event &event) {
	int oldSelectLen = _selectLen;
	int oldCaretPos = _caretPos;

	_caretPos = getTextPosFromMouse(event.mouse.x, event.mouse.y);
	_selectLen = 0;

	if (_selectLen != oldSelectLen || _caretPos != oldCaretPos)
		setRedraw();
}

bool MacGuiImpl::MacEditText::handleDoubleClick(Common::Event &event) {
	if (_text.empty())
		return false;

	_selectLen = 0;

	int startPos = _caretPos;
	int endPos = _caretPos;

	// Check if used clicked past the end of the text
	if (_caretPos >= (int)_text.size())
		startPos = endPos = _text.size() - 1;

	if (_text[startPos] == ' ') {
		while (startPos >= 0) {
			if (_text[startPos] != ' ') {
				startPos++;
				break;
			}
			startPos--;
		}

		while (endPos < (int)_text.size()) {
			if (_text[endPos] != ' ') {
				endPos--;
				break;
			}
			endPos++;
		}
	} else {
		while (startPos >= 0) {
			if (_text[startPos] == ' ') {
				startPos++;
				break;
			}
			startPos--;
		}

		while (endPos < (int)_text.size()) {
			if (_text[endPos] == ' ') {
				endPos--;
				break;
			}
			endPos++;
		}
	}

	if (startPos < 0)
		startPos = 0;

	if (endPos >= (int)_text.size())
		endPos = _text.size() - 1;

	_caretPos = startPos;
	_selectLen = endPos - startPos + 1;
	setRedraw();

	return false;
}

bool MacGuiImpl::MacEditText::handleKeyDown(Common::Event &event) {
	if (event.kbd.flags & (Common::KBD_CTRL | Common::KBD_ALT | Common::KBD_META))
		return false;

	// Stop caret blinking while typing. We do this by making the caret
	// visible, but force the next blink to happen immediately. Otherwise,
	// it may not register that the blink state has changed.

	_caretVisible = false;
	_nextCaretBlink = _window->_system->getMillis();

	switch (event.kbd.keycode) {
	case Common::KEYCODE_LEFT:
		if (_selectLen < 0)
			_caretPos = _caretPos + _selectLen;

		_caretPos--;

		if (_caretPos < 0)
			_caretPos = 0;

		if (_selectLen != 0) {
			_selectLen = 0;
			setRedraw();
		}
		return true;

	case Common::KEYCODE_RIGHT:
		if (_selectLen > 0)
			_caretPos += _selectLen;

		_caretPos++;

		if (_caretPos > (int)_text.size())
			_caretPos = _text.size();

		if (_selectLen != 0) {
			_selectLen = 0;
			setRedraw();
		}
		return true;

	case Common::KEYCODE_HOME:
	case Common::KEYCODE_UP:
		_caretPos = 0;

		if (_selectLen != 0) {
			_selectLen = 0;
			setRedraw();
		}
		return true;

	case Common::KEYCODE_END:
	case Common::KEYCODE_DOWN:
		_caretPos = _text.size();

		if (_selectLen != 0) {
			_selectLen = 0;
			setRedraw();
		}
		return true;

	case Common::KEYCODE_BACKSPACE:
		if (_selectLen != 0) {
			deleteSelection();
		} else if (_caretPos > 0) {
			_caretPos--;
			_text.deleteChar(_caretPos);
			setRedraw();
		}
		return true;

	case Common::KEYCODE_DELETE:
		if (_selectLen != 0) {
			deleteSelection();
		} else if (_caretPos < (int)_text.size()) {
			_text.deleteChar(_caretPos);
			setRedraw();
		}
		return true;

	default:
		break;
	}

	int c = _window->_gui->toMacRoman(event.kbd.ascii);

	if (c > 0) {
		if (_selectLen != 0)
			deleteSelection();
		if (_text.size() < _maxLength) {
			_text.insertChar(c, _caretPos);
			_caretPos++;
			setRedraw();
		}
		return true;
	}

	return false;
}

void MacGuiImpl::MacEditText::handleMouseHeld() {
	if (_text.empty())
		return;

	Common::Point mousePos = _window->getMousePos();

	int oldTextPos = _textPos;

	int minTextPos = MIN(_bounds.width() - _font->getStringWidth(_text) - 1, 1);

	if (mousePos.x < _bounds.left + 1 && mousePos.y < _bounds.bottom && _textPos < 1) {
		_textPos += 8;
		if (_textPos > 1)
			_textPos = 1;
	} else if (mousePos.x >= _bounds.right) {
		_textPos -= 8;
		if (_textPos < minTextPos)
			_textPos = minTextPos;
	}

	if (_textPos != oldTextPos) {
		updateSelection(mousePos.x, mousePos.y);
		setRedraw();
	}
}

void MacGuiImpl::MacEditText::handleMouseMove(Common::Event &event) {
	updateSelection(event.mouse.x, event.mouse.y);
}

// ---------------------------------------------------------------------------
// Image widget
// ---------------------------------------------------------------------------

MacGuiImpl::MacImage::MacImage(MacGuiImpl::MacDialogWindow *window, Common::Rect bounds, Graphics::Surface *surface, Graphics::Surface *mask, bool enabled) : MacWidget(window, bounds, "Picture", enabled) {
	_image = surface;
	_mask = mask;
}

MacGuiImpl::MacImage::~MacImage() {
	if (_image) {
		_image->free();
		delete _image;
	}
	if (_mask) {
		_mask->free();
		delete _mask;
	}
}

void MacGuiImpl::MacImage::draw(bool drawFocused) {
	if (!_redraw && !_fullRedraw)
		return;

	debug(1, "MacGuiImpl::MacImage: Drawing picture %d (_fullRedraw = %d, drawFocused = %d, _value = %d)", _id, _fullRedraw, drawFocused, _value);

	if (_image)
		_window->drawSprite(this, _bounds.left, _bounds.top);

	_redraw = false;
	_fullRedraw = false;
}

// ---------------------------------------------------------------------------
// Slider base class
//
// The idea here is that _maxPos and _minPos correspond to _maxValue and
// _minValue respectively. The position can be calculated from the value and
// vice versa.
//
// For more exact positioning, where you have to match it exactly to a visible
// ruler, you can use addStop() to override the calculated values.
// ---------------------------------------------------------------------------

void MacGuiImpl::MacSliderBase::setValue(int value) {
	int newValue = CLIP(value, _minValue, _maxValue);
	MacWidget::setValue(newValue);
	_handlePos = calculatePosFromValue();
}

int MacGuiImpl::MacSliderBase::calculateValueFromPos() const {
	return calculateValueFromPos(_handlePos);
}

int MacGuiImpl::MacSliderBase::calculateValueFromPos(int pos) const {
	int value;

	if (_posToValue.tryGetVal(pos, value))
		return value;

	int posOffset = pos - _minPos;
	int posRange = _maxPos - _minPos;

	int valueRange = _maxValue - _minValue;
	int valueOffset = (posRange / 2 + valueRange * posOffset) / posRange;

	return _minValue + valueOffset;
}

int MacGuiImpl::MacSliderBase::calculatePosFromValue() const {
	return calculatePosFromValue(_value);
}

int MacGuiImpl::MacSliderBase::calculatePosFromValue(int value) const {
	int pos;

	if (_valueToPos.tryGetVal(value, pos))
		return pos;

	int valueRange = _maxValue - _minValue;
	int valueOffset = value - _minValue;

	int posRange = _maxPos - _minPos;
	int posOffset = (valueRange / 2 + posRange * valueOffset) / valueRange;

	return _minPos + posOffset;
}

// ---------------------------------------------------------------------------
// Standard slider widget
// ---------------------------------------------------------------------------

MacGuiImpl::MacSlider::MacSlider(MacGuiImpl::MacDialogWindow *window, Common::Rect bounds, int minValue, int maxValue, int pageSize, bool enabled)
	: MacSliderBase(window, bounds, minValue, maxValue, 0, 0, enabled),
	_pageSize(pageSize) {
	_boundsButtonUp = Common::Rect(_bounds.left, _bounds.top, _bounds.right, _bounds.top + 16);
	_boundsButtonDown = Common::Rect(_bounds.left, _bounds.bottom - 16, _bounds.right, _bounds.bottom);
	_boundsBody = Common::Rect(_bounds.left, _bounds.top + 16, _bounds.right, _bounds.bottom - 16);

	_minPos = _boundsBody.top;
	_maxPos = _boundsBody.bottom - 16;

	_clickPos.x = -1;
	_clickPos.y = -1;
}

bool MacGuiImpl::MacSlider::findWidget(int x, int y) const {
	if (!isScrollable())
		return false;

	Common::Rect bounds = _bounds;

	// While dragging the handle, you're allowed to go outside the slider.
	// I don't know by how much, though.

	if (_grabOffset >= 0) {
		bounds.left -= 25;
		bounds.right += 25;
		bounds.top -= 50;
		bounds.bottom += 50;
	}

	return bounds.contains(x, y);
}

Common::Rect MacGuiImpl::MacSlider::getHandleRect(int value) {
	int handlePos = value * (_boundsBody.bottom - _boundsBody.top - 16) / (_maxValue - _minValue);

	Common::Rect handleRect;

	handleRect.left = _boundsBody.left + 1;
	handleRect.top = _boundsBody.top + handlePos;
	handleRect.right = _boundsBody.right - 1;
	handleRect.bottom = handleRect.top + 16;

	return handleRect;
}

void MacGuiImpl::MacSlider::fill(Common::Rect r, bool inverted) {
	uint32 pattern[2][4] = {
		{ _white, _white, _black, _white },
		{ _black, _white, _white, _white }
	};

	Graphics::Surface *s = _window->innerSurface();

	for (int y = r.top; y < r.bottom; y++) {
		for (int x = r.left; x < r.right; x++) {
			if (inverted) {
				// The inverted style is used for drawing the "ghost" of the
				// slider handle while dragging. I think this matches the
				// original behavior, though I'm not quite sure.

				bool srcPixel = s->getPixel(x, y) == _black;
				bool dstPixel = pattern[y % 2][x % 4] == _white;

				uint32 color = (srcPixel ^ dstPixel) ? _black : _white;

				s->setPixel(x, y, color);
			} else
				s->setPixel(x, y, pattern[y % 2][x % 4]);
		}
	}
}

void MacGuiImpl::MacSlider::draw(bool drawFocused) {
	if (!_redraw && !_fullRedraw)
		return;

	// There are several things that will trigger a redraw, but unlike
	// other widgets this one only handles full redraws. Everything else
	// is handled outside of draw().

	if (_fullRedraw) {
		debug(1, "MacGuiImpl::MacSlider: Drawing slider (_fullRedraw = %d, drawFocused = %d, _value = %d)", _fullRedraw, drawFocused, _value);

		Graphics::Surface *s = _window->innerSurface();

		s->frameRect(_bounds, _black);
		s->hLine(_bounds.left + 1, _bounds.top + 15, _bounds.right - 2, _black);
		s->hLine(_bounds.left + 1, _bounds.bottom - 16, _bounds.right - 2, _black);

		drawUpArrow(false);
		drawDownArrow(false);

		Common::Rect fillRect(_boundsBody.left + 1, _boundsBody.top, _boundsBody.right - 1, _boundsBody.bottom);

		if (isScrollable()) {
			fill(fillRect);

			Common::Rect handleRect = getHandleRect(_value);
			drawHandle(handleRect);
		} else
			s->fillRect(fillRect, _white);

		_window->markRectAsDirty(_bounds);
	}

	_redraw = false;
	_fullRedraw = false;
}

// There is a narrower version of these arrows, but I'm speculating that
// they're intended for a 9" Mac screen. We go with the slightly wider version
// here to make them easier to hit.

void MacGuiImpl::MacSlider::drawUpArrow(bool markAsDirty) {
	debug(1, "MacGuiImpl::MacSlider: Drawing up arrow (_upArrowPressed = %d, markAsDirty = %d)", _upArrowPressed, markAsDirty);

	const uint16 upArrow[] = {
		0x0600, 0x0900, 0x1080, 0x2040, 0x4020,
		0xF0F0, 0x1080, 0x1080, 0x1080, 0x1F80
	};

	const uint16 upArrowFilled[] = {
		0x0600, 0x0F00, 0x1F80, 0x3FC0, 0x7FE0,
		0xFFF0, 0x1F80, 0x1F80, 0x1F80, 0x1F80
	};

	drawArrow(_boundsButtonUp, (_upArrowPressed ? upArrowFilled : upArrow), markAsDirty);
}

void MacGuiImpl::MacSlider::drawDownArrow(bool markAsDirty) {
	debug(1, "MacGuiImpl::MacSlider: Drawing down arrow (_downArrowPressed = %d, markAsDirty = %d)", _downArrowPressed, markAsDirty);

	const uint16 downArrow[] = {
		0x1F80,	0x1080,	0x1080,	0x1080,	0xF0F0,
		0x4020,	0x2040,	0x1080,	0x0900,	0x0600
	};

	const uint16 downArrowFilled[] = {
		0x1F80, 0x1F80, 0x1F80, 0x1F80, 0xFFF0,
		0x7FE0, 0x3FC0, 0x1F80, 0x0F00, 0x0600
	};

	drawArrow(_boundsButtonDown, (_downArrowPressed ? downArrowFilled : downArrow), markAsDirty);
}

void MacGuiImpl::MacSlider::drawArrow(Common::Rect r, const uint16 *bitmap, bool markAsDirty) {
	Graphics::Surface *s = _window->innerSurface();

	r.grow(-1);

	s->fillRect(r, _white);
	drawBitmap(Common::Rect(r.left + 1, r.top + 2, r.right - 1, r.top + 12), bitmap, _black);

	if (markAsDirty)
		_window->markRectAsDirty(r);
}

void MacGuiImpl::MacSlider::eraseDragHandle() {
	Common::Rect r(_boundsBody.left + 1, _handlePos, _boundsBody.right - 1, _handlePos + 16);
	fill(r);
	_window->markRectAsDirty(r);
}

void MacGuiImpl::MacSlider::drawHandle(Common::Rect r) {
	debug(2, "MacGuiImpl::MacSlider::drawHandle(%d)", r.top);

	Graphics::Surface *s = _window->innerSurface();

	s->frameRect(r, _black);
	r.grow(-1);
	s->fillRect(r, _white);
}

void MacGuiImpl::MacSlider::redrawHandle(int oldValue, int newValue) {
	Common::Rect r = getHandleRect(oldValue);

	fill(r);
	_window->markRectAsDirty(r);

	r = getHandleRect(newValue);
	drawHandle(r);
	_window->markRectAsDirty(r);
}

void MacGuiImpl::MacSlider::handleMouseDown(Common::Event &event) {
	int x = event.mouse.x;
	int y = event.mouse.y;

	_clickPos.x = x;
	_clickPos.y = y;
	_paging = 0;
	_grabOffset = -1;
	_handlePos = -1;

	int oldValue = _value;

	if (_boundsButtonUp.contains(x, y)) {
		_nextRepeat = _window->_system->getMillis() + 200;
		_upArrowPressed = true;
		_value = MAX(_minValue, _value - 1);
		drawUpArrow(true);
	} else if (_boundsButtonDown.contains(x, y)) {
		_nextRepeat = _window->_system->getMillis() + 200;
		_downArrowPressed = true;
		_value = MIN(_maxValue, _value + 1);
		drawDownArrow(true);
	} else {
		Common::Rect handleRect = getHandleRect(_value);

		if (y < handleRect.top) {
			_nextRepeat = _window->_system->getMillis() + 200;
			_paging = -1;
			_value = MAX(_minValue, _value - (_pageSize - 1));
		} else if (y >= handleRect.bottom) {
			_nextRepeat = _window->_system->getMillis() + 200;
			_paging = 1;
			_value = MIN(_maxValue, _value + (_pageSize - 1));
		} else {
			_grabOffset = y - handleRect.top;
			_handlePos = handleRect.top;
		}
	}

	if (_value != oldValue)
		redrawHandle(oldValue, _value);
}

bool MacGuiImpl::MacSlider::handleMouseUp(Common::Event &event) {
	if (_upArrowPressed) {
		_upArrowPressed = false;
		drawUpArrow(true);
	} else if (_downArrowPressed) {
		_downArrowPressed = false;
		drawDownArrow(true);
	} else if (_grabOffset >= 0) {
		// Erase the drag handle, since the handle might not end up in
		// the exact same spot.
		eraseDragHandle();

		// Calculate new value and move the handle there
		int newValue = calculateValueFromPos();

		redrawHandle(_value, newValue);
		_value = newValue;
	}

	_paging = 0;
	_grabOffset = -1;
	_handlePos = -1;
	_clickPos.x = -1;
	_clickPos.y = -1;

	return false;
}

void MacGuiImpl::MacSlider::handleMouseMove(Common::Event &event) {
	int x = event.mouse.x;
	int y = event.mouse.y;

	if (_grabOffset >= 0) {
		if (!findWidget(x, y)) {
			eraseDragHandle();

			Common::Rect handleRect = getHandleRect(_value);

			if (ABS(_handlePos - handleRect.top) <= handleRect.height()) {
				drawHandle(handleRect);
				_window->markRectAsDirty(handleRect);
			}

			return;
		}

		int newHandlePos = CLIP<int>(y - _grabOffset, _boundsBody.top, _boundsBody.bottom - 16);

		// Theoretically, we could end here if the handle position has
		// not changed. However, we currently don't keep track of if
		// the handle is hidden because the mouse has moved out of the
		// widget's control.

		eraseDragHandle();

		Common::Rect handleRect = getHandleRect(_value);

		if (ABS(_handlePos - handleRect.top) <= handleRect.height()) {
			drawHandle(handleRect);
			_window->markRectAsDirty(handleRect);
		}

		_handlePos = newHandlePos;

		int x0 = _boundsBody.left + 1;
		int x1 = _boundsBody.right - 1;
		int y0 = _handlePos;
		int y1 = _handlePos + 16;

		// Drawing a solid rectangle would be easier, and probably look
		// better. But it seems the original Mac widget would draw the
		// frame as an inverted slider background, even when drawing it
		// on top of the slider handle.

		fill(Common::Rect(x0, y0, x1, y0 + 1), true);
		fill(Common::Rect(x0, y1 - 1, x1, y1), true);
		fill(Common::Rect(x0, y0 + 1, x0 + 1, y1 - 1), true);
		fill(Common::Rect(x1 - 1, y0 + 1, x1, y1 - 1), true);

		_window->markRectAsDirty(Common::Rect(x0, y0, x1, y1));
	} else {
		if (!_boundsButtonUp.contains(x, y)) {
			if (_upArrowPressed) {
				_upArrowPressed = false;
				drawUpArrow(true);
			}
		} else {
			if (_boundsButtonUp.contains(_clickPos) && !_upArrowPressed) {
				_nextRepeat = _window->_system->getMillis() + 200;
				_upArrowPressed = true;
				drawUpArrow(true);
			}
		}

		if (!_boundsButtonDown.contains(x, y)) {
			if (_downArrowPressed) {
				_downArrowPressed = false;
				drawDownArrow(true);
			}
		} else {
			if (_boundsButtonDown.contains(_clickPos) && !_downArrowPressed) {
				_nextRepeat = _window->_system->getMillis() + 200;
				_downArrowPressed = true;
				drawDownArrow(true);
			}
		}
	}
}

void MacGuiImpl::MacSlider::handleMouseHeld() {
	uint32 now = _window->_system->getMillis();
	Common::Point p = _window->getMousePos();

	if (now < _nextRepeat || !findWidget(p.x, p.y))
		return;

	int oldValue = _value;

	if (_upArrowPressed) {
		_value = MAX(_minValue, _value - 1);
		_nextRepeat = now + 80;
	}

	if (_downArrowPressed) {
		_value = MIN(_maxValue, _value + 1);
		_nextRepeat = now + 80;
	}

	if (_paging) {
		Common::Rect r = getHandleRect(_value);

		// Keep paging until at least half the scroll handle has gone
		// past the mouse cursor. This may have to be tuned.

		if (_paging == -1) {
			if (p.y < r.top + r.height() / 2 && _value > _minValue) {
				_nextRepeat = now + 100;
				_value = MAX(_minValue, _value - (_pageSize - 1));
			}
		} else if (_paging == 1) {
			if (p.y >= r.bottom - r.height() / 2 && _value < _maxValue) {
				_nextRepeat = now + 100;
				_value = MIN(_maxValue, _value + (_pageSize - 1));
			}
		}
	}

	if (_value != oldValue)
		redrawHandle(oldValue, _value);
}

void MacGuiImpl::MacSlider::handleWheelUp() {
	int oldValue = _value;

	_value = MAX(_minValue, _value - (_pageSize - 1));

	if (_value != oldValue)
		redrawHandle(oldValue, _value);
}

void MacGuiImpl::MacSlider::handleWheelDown() {
	int oldValue = _value;

	_value = MIN(_maxValue, _value + (_pageSize - 1));

	if (_value != oldValue)
		redrawHandle(oldValue, _value);
}

// ---------------------------------------------------------------------------
// Image slider widget. This is the custom slider widget used for the Loom
// and Indy 3 options dialogs. It consists of a background image and a slider
// drag handle.
//
// In addition to the min and max value positions, this one also maintains a
// _minX and _maxX position to which the handle can be dragged. This is only
// used for the older games.
// ---------------------------------------------------------------------------

MacGuiImpl::MacImageSlider::MacImageSlider(MacGuiImpl::MacDialogWindow *window, Common::Rect bounds, MacImage *handle, bool enabled, int minX, int maxX, int minValue, int maxValue)
	: MacSliderBase(window, bounds, minValue, maxValue, minX, maxX, enabled), _handle(handle), _minX(minX), _maxX(maxX) {
	_background = new Graphics::Surface();
	_background->copyFrom(window->innerSurface()->getSubArea(bounds));
	_freeBackground = true;
}

MacGuiImpl::MacImageSlider::~MacImageSlider() {
	if (_freeBackground && _background) {
		_background->free();
		delete _background;
	}
}

void MacGuiImpl::MacImageSlider::setValue(int value) {
	int newValue = CLIP(value, _minValue, _maxValue);
	MacWidget::setValue(newValue);
	eraseHandle();
	_handlePos = calculatePosFromValue();
	drawHandle();
}

bool MacGuiImpl::MacImageSlider::findWidget(int x, int y) const {
	// Once we start dragging the handle, any mouse position is considered
	// within the widget.

	if (_window->getFocusedWidget() == this)
		return true;

	return _bounds.contains(x, y);
}

void MacGuiImpl::MacImageSlider::draw(bool drawFocused) {
	if (!_redraw && !_fullRedraw)
		return;

	debug(1, "MacGuiImpl::MacImageSlider: Drawing slider %d (_fullRedraw = %d, drawFocused = %d, _value = %d)", _id, _fullRedraw, drawFocused, _value);

	if (_fullRedraw) {
		_window->drawSprite(_background, _bounds.left, _bounds.top);
		drawHandle();
	}

	_redraw = false;
	_fullRedraw = false;
}

void MacGuiImpl::MacImageSlider::eraseHandle() {
	if (_handlePos == -1)
		return;

	Common::Rect r = _handle->getBounds();
	int y = r.top - _bounds.top;
	int w = r.width();
	int h = r.height();

	Graphics::Surface sprite = _background->getSubArea(Common::Rect(_handlePos, y, _handlePos + w, y + h));
	_window->drawSprite(&sprite, _bounds.left + _handlePos, r.top);
}

void MacGuiImpl::MacImageSlider::drawHandle() {
	Common::Rect r = _handle->getBounds();
	_window->drawSprite(_handle, _bounds.left + _handlePos, r.top);
}

void MacGuiImpl::MacImageSlider::handleMouseDown(Common::Event &event) {
	int mouseX = event.mouse.x;
	int handleWidth = _handle->getBounds().width();

	if (mouseX >= _handlePos && mouseX < _handlePos + handleWidth)
		_grabOffset = event.mouse.x - _bounds.left - _handlePos;
	else
		_grabOffset = handleWidth / 2;

	handleMouseMove(event);
}

bool MacGuiImpl::MacImageSlider::handleMouseUp(Common::Event &event) {
	// Erase the drag rect, since the handle might not end up in
	// the exact same spot.
	int newValue = calculateValueFromPos();

	// Even if the value doesn't change, we need to reposition the slider
	// handle, or it may be left between two values. This is particularly
	// noticeable for the music quality slider.
	eraseHandle();
	setValue(newValue);
	drawHandle();

	return false;
}

void MacGuiImpl::MacImageSlider::handleMouseMove(Common::Event &event) {
	int newPos;

	if (_snapWhileDragging) {
		// Even when overriding the stops, the calculated one should
		// be close enough here.
		int newValue = calculateValueFromPos(CLIP<int>(event.mouse.x - _bounds.left - _handle->getImage()->w / 2, _minX, _maxX));
		newPos = calculatePosFromValue(newValue);
	} else
		newPos = CLIP<int>(event.mouse.x - _bounds.left - _grabOffset, _minX, _maxX);

	if (newPos != _handlePos) {
		eraseHandle();
		_handlePos = newPos;
		drawHandle();
	}
}

void MacGuiImpl::MacImageSlider::handleWheelUp() {
	int newValue = MAX(_minValue, _value + 1);

	if (_value != newValue) {
		eraseHandle();
		setValue(newValue);
		drawHandle();
	}
}

void MacGuiImpl::MacImageSlider::handleWheelDown() {
	int newValue = MIN(_maxValue, _value - 1);

	if (_value != newValue) {
		eraseHandle();
		setValue(newValue);
		drawHandle();
	}
}

// ---------------------------------------------------------------------------
// List box widget
// ---------------------------------------------------------------------------

MacGuiImpl::MacListBox::MacListBox(MacGuiImpl::MacDialogWindow *window, Common::Rect bounds, Common::StringArray texts, bool enabled, bool contentUntouchable) : MacWidget(window, bounds, "ListBox", enabled), _texts(texts) {
	int pageSize = _bounds.height() / 16;

	int numSlots = MIN<int>(pageSize, texts.size());

	for (int i = 0; i < numSlots; i++) {
		Common::Rect r(_bounds.left + 1, _bounds.top + 1 + 16 * i, _bounds.right - 16, _bounds.top + 1 + 16 * (i + 1));
		MacStaticText *tmp = new MacStaticText(window, r, texts[i], enabled);
		if (contentUntouchable)
			tmp->setEnabled(false);
		_textWidgets.push_back(tmp);
	}

	_slider = new MacSlider(window, Common::Rect(_bounds.right - 16, _bounds.top, _bounds.right, _bounds.bottom), 0, texts.size() - pageSize, pageSize, enabled);

	// The widget value indicates the selected element
	_value = 0;
	updateTexts();
}

MacGuiImpl::MacListBox::~MacListBox() {
	_texts.clear();
	delete _slider;

	for (uint i = 0; i < _textWidgets.size(); i++)
		delete _textWidgets[i];
}

bool MacGuiImpl::MacListBox::findWidget(int x, int y) const {
	return MacWidget::findWidget(x, y) || _slider->findWidget(x, y);
}

void MacGuiImpl::MacListBox::setRedraw(bool fullRedraw) {
	MacWidget::setRedraw(fullRedraw);
	_slider->setRedraw(fullRedraw);

	for (uint i = 0; i < _textWidgets.size(); i++)
		_textWidgets[i]->setRedraw(fullRedraw);
}

void MacGuiImpl::MacListBox::updateTexts() {
	int offset = _slider->getValue();

	for (uint i = 0; i < _textWidgets.size(); i++) {
		_textWidgets[i]->setText(_texts[i + offset]);

		if (_textWidgets[i]->isEnabled() && (int)i + offset == _value)
			_textWidgets[i]->setColor(_white, _black);
		else
			_textWidgets[i]->setColor(_black, _white);
	}
}

void MacGuiImpl::MacListBox::draw(bool drawFocused) {
	for (uint i = 0; i < _textWidgets.size(); i++)
		_textWidgets[i]->draw(drawFocused);

	_slider->draw(drawFocused);

	if (!_redraw && !_fullRedraw)
		return;

	debug(1, "MacGuiImpl::MacListBox: Drawing list box (_fullRedraw = %d, drawFocused = %d)", _fullRedraw, drawFocused);

	Graphics::Surface *s = _window->innerSurface();

	s->hLine(_bounds.left, _bounds.top, _bounds.right - 17, _black);
	s->hLine(_bounds.left, _bounds.bottom - 1, _bounds.right - 17, _black);
	s->vLine(_bounds.left, _bounds.top + 1, _bounds.bottom - 2, _black);

	_redraw = false;
	_fullRedraw = false;

	_window->markRectAsDirty(_bounds);
}

void MacGuiImpl::MacListBox::handleMouseDown(Common::Event &event) {
	if (_slider->findWidget(event.mouse.x, event.mouse.y)) {
		int oldValue = _slider->getValue();

		_sliderFocused = true;
		_slider->handleMouseDown(event);

		if (_slider->getValue() != oldValue)
			updateTexts();

		return;
	}

	int offset = _slider->getValue();

	for (uint i = 0; i < _textWidgets.size(); i++) {
		if (_textWidgets[i]->findWidget(event.mouse.x, event.mouse.y)) {
			setValue(i + offset);
			break;
		}
	}
}

bool MacGuiImpl::MacListBox::handleDoubleClick(Common::Event &event) {
	for (uint i = 0; i < _textWidgets.size(); i++) {
		if (_textWidgets[i]->findWidget(event.mouse.x, event.mouse.y))
			return true;
	}

	return false;
}

bool MacGuiImpl::MacListBox::handleMouseUp(Common::Event &event) {
	if (_sliderFocused) {
		int oldValue = _slider->getValue();

		_sliderFocused = false;
		_slider->handleMouseUp(event);

		if (_slider->getValue() != oldValue)
			updateTexts();
	}

	return false;
}

void MacGuiImpl::MacListBox::handleMouseMove(Common::Event &event) {
	if (_sliderFocused) {
		int oldValue = _slider->getValue();

		_slider->handleMouseMove(event);

		if (_slider->getValue() != oldValue)
			updateTexts();
	}
}

void MacGuiImpl::MacListBox::handleMouseHeld() {
	if (_sliderFocused) {
		int oldValue = _slider->getValue();

		_slider->handleMouseHeld();

		if (_slider->getValue() != oldValue)
			updateTexts();
	}
}

void MacGuiImpl::MacListBox::handleWheelUp() {
	handleWheel(-1);
}

void MacGuiImpl::MacListBox::handleWheelDown() {
	handleWheel(1);
}

void MacGuiImpl::MacListBox::handleWheel(int distance) {
	if (!_slider->isScrollable())
		return;

	Common::Point mousePos = _window->getMousePos();
	int oldValue = _slider->getValue();

	if (_slider->findWidget(mousePos.x, mousePos.y))
		distance *= _slider->getPageSize();

	_slider->setValue(oldValue + distance);

	int newValue = _slider->getValue();

	if (newValue != oldValue) {
		updateTexts();
		_slider->redrawHandle(oldValue, newValue);
	}
}

bool MacGuiImpl::MacListBox::handleKeyDown(Common::Event &event) {
	if (_texts.size() <= 1 || !_textWidgets[0]->isEnabled())
		return false;

	if (event.kbd.flags & (Common::KBD_CTRL | Common::KBD_ALT | Common::KBD_META))
		return false;

	int oldValue = _value;

	switch (event.kbd.keycode) {
	case Common::KEYCODE_UP:
		_value = MAX(_value - 1, 0);
		break;

	case Common::KEYCODE_DOWN:
		_value = MIN<int>(_value + 1, _texts.size() - 1);
		break;

	default:
		break;
	}

	if (_value != oldValue) {
		int sliderValue = _slider->getValue();
		int pageSize = _slider->getPageSize();
		int newSliderValue = sliderValue;

		if (_value < sliderValue)
			newSliderValue = _value;
		else if (_value >= sliderValue + pageSize)
			newSliderValue = _value - pageSize + 1;

		if (sliderValue != newSliderValue) {
			_slider->setValue(newSliderValue);
			_slider->redrawHandle(sliderValue, newSliderValue);
		}

		updateTexts();
	}

	return false;
}

// ---------------------------------------------------------------------------
// Pop-up menu widget
// ---------------------------------------------------------------------------

MacGuiImpl::MacPopUpMenu::MacPopUpMenu(MacGuiImpl::MacDialogWindow *window, Common::Rect bounds, Common::String text, int textWidth, Common::StringArray texts, bool enabled) : MacWidget(window, bounds, text, enabled), _textWidth(textWidth), _texts(texts) {
	_black = _window->_gui->getBlack();
	_white = _window->_gui->getWhite();

	_popUpBounds.left = _bounds.left + _textWidth;
	_popUpBounds.right = _bounds.right;
}

MacGuiImpl::MacPopUpMenu::~MacPopUpMenu() {
	_texts.clear();
	_popUpBackground.free();
}

bool MacGuiImpl::MacPopUpMenu::findWidget(int x, int y) const {
	// Once we have opened the drop down list, any mouse position is
	// considered within the widget.

	if (_window->getFocusedWidget() == this)
		return true;

	return _bounds.contains(x, y);
}

void MacGuiImpl::MacPopUpMenu::draw(bool drawFocused) {
	if (!_redraw && !_fullRedraw)
		return;

	debug(1, "MacGuiImpl::MacPopUpMenu: Drawing list box (_fullRedraw = %d, drawFocused = %d)", _fullRedraw, drawFocused);

	// I don't know how Mac originally drew disabled drop downs lists, or
	// if that was even a thing. For our purposes, the text is still
	// relevant. We just need to make it obvious that you can't change it
	// in any way.

	uint32 fg, bg;
	bool focused = drawFocused || _window->getFocusedWidget() == this;

	if (focused) {
		fg = _white;
		bg = _black;
	} else {
		fg = _black;
		bg = _white;
	}

	Graphics::Surface *s = _window->innerSurface();
	const Graphics::Font *font = _window->_gui->getFont(kSystemFont);

	s->fillRect(Common::Rect(_bounds.left, _bounds.top + 1, _bounds.left + _textWidth, _bounds.bottom - 3), bg);
	font->drawString(s, _text, _bounds.left, _bounds.top + 1, _textWidth, fg, Graphics::kTextAlignLeft, 4);

	if (focused) {
		Common::Rect r = _popUpBounds;
		r.bottom--;
		r.right--;

		s->fillRect(r, _white);
		s->frameRect(r, _black);
		s->hLine(r.left + 3, r.bottom, r.right, _black);
		s->vLine(r.right, r.top + 3, r.bottom - 1, _black);

		Common::Rect textRect(r.left + 1, r.top + 1, r.right - 1, r.top + 17);

		for (int i = 0; i < (int)_texts.size(); i++) {
			if (i == _selected) {
				fg = _white;
				bg = _black;
			} else {
				fg = _black;
				bg = _white;
			}

			s->fillRect(textRect, bg);

			font->drawString(s, _texts[i], textRect.left, textRect.top, textRect.width(), fg, Graphics::kTextAlignLeft, 14);

			if (i == _value)
				font->drawString(s, "\x12", textRect.left + 2, textRect.top, 10, fg);

			textRect.translate(0, 16);
		}
	} else {
		Common::Rect r(_bounds.left + _textWidth, _bounds.top, _bounds.right - 1, _bounds.bottom - 2);

		s->fillRect(r, _white);
		s->frameRect(r, _black);
		s->hLine(r.left + 3, r.bottom, r.right, _black);
		s->vLine(r.right, r.top + 3, r.bottom - 1, _black);

		font->drawString(s, _texts[_value], r.left, r.top + 1, r.width() - 20, _black, Graphics::kTextAlignLeft, 15);

		const uint16 arrowDownIcon[16] = {
			0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x3FF8, 0x1FF0, 0x0FE0,
			0x07C0, 0x0380, 0x0100, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
		};

		const uint16 disabledArrowDownIcon[16] = {
			0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2AA8, 0x1550, 0x0AA0,
			0x0540, 0x0280, 0x0100, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
		};

		Common::Rect iconRect(16, 16);
		iconRect.moveTo(r.right - 19, r.top + 1);

		drawBitmap(iconRect, _enabled ? arrowDownIcon : disabledArrowDownIcon, _black);
	}

	_redraw = false;
	_fullRedraw = false;

	_window->markRectAsDirty(_bounds);
	if (focused)
		_window->markRectAsDirty(_popUpBounds);
}

void MacGuiImpl::MacPopUpMenu::handleMouseDown(Common::Event &event) {
	_popUpBounds.top = _bounds.top - 16 * _value;
	_popUpBounds.bottom = _bounds.bottom - 1 + 16 * (_texts.size() - _value - 1);

	Graphics::Surface background = _window->innerSurface()->getSubArea(_popUpBounds);

	_popUpBackground.free();
	_popUpBackground.copyFrom(background);

	_menuVisible = true;
	_selected = _value;
}

bool MacGuiImpl::MacPopUpMenu::handleMouseUp(Common::Event &event) {
	if (_selected != -1) {
		int selected = _selected;

		for (int i = 0; i < 6; i++) {
			if (_selected == selected)
				_selected = -1;
			else
				_selected = selected;

			// It is a bit wasteful to redraw the entire widget
			// just for this. It's also a lot easier.

			setRedraw();
			_window->update();

			for (int j = 0; j < 3; j++) {
				Common::Event e;
				while (_window->_system->getEventManager()->pollEvent(e))
					;
				_window->_system->delayMillis(10);
				_window->_system->updateScreen();
			}
		}

		setValue(selected);
	}

	_window->drawSprite(&_popUpBackground, _popUpBounds.left, _popUpBounds.top);
	_menuVisible = false;

	return false;
}

void MacGuiImpl::MacPopUpMenu::handleMouseMove(Common::Event &event) {
	if (!_menuVisible)
		return;

	Common::Rect menuBounds(_popUpBounds.left + 1, _popUpBounds.top + 1, _popUpBounds.right - 2, _popUpBounds.bottom - 2);

	int selected = -1;

	if (menuBounds.contains(event.mouse.x, event.mouse.y)) {
		selected = (event.mouse.y - menuBounds.top) / 16;

		int maxValue = _texts.size() - 1;

		if (selected > maxValue) {
			warning("MacGuiImpl::MacPopUpMenu::handleMouseMove: Max selection value exceeded");
			selected = -1;
		}
	}

	if (selected != _selected) {
		_selected = selected;
		setRedraw();
	}
}

} // End of namespace Scumm
