scummvm/engines/wintermute/ad/ad_response_box.cpp
2014-02-18 02:39:40 +01:00

749 lines
19 KiB
C++

/* 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.
*
*/
/*
* This file is based on WME Lite.
* http://dead-code.org/redir.php?target=wmelite
* Copyright (c) 2011 Jan Nedoma
*/
#include "engines/wintermute/ad/ad_game.h"
#include "engines/wintermute/ad/ad_response.h"
#include "engines/wintermute/ad/ad_response_box.h"
#include "engines/wintermute/base/base_dynamic_buffer.h"
#include "engines/wintermute/base/base_file_manager.h"
#include "engines/wintermute/base/base_parser.h"
#include "engines/wintermute/base/base_sprite.h"
#include "engines/wintermute/base/base_surface_storage.h"
#include "engines/wintermute/base/font/base_font_storage.h"
#include "engines/wintermute/base/font/base_font.h"
#include "engines/wintermute/base/gfx/base_renderer.h"
#include "engines/wintermute/base/scriptables/script.h"
#include "engines/wintermute/base/scriptables/script_stack.h"
#include "engines/wintermute/ui/ui_button.h"
#include "engines/wintermute/ui/ui_window.h"
#include "engines/wintermute/utils/utils.h"
#include "engines/wintermute/platform_osystem.h"
#include "engines/wintermute/wintermute.h"
#include "common/str.h"
namespace Wintermute {
IMPLEMENT_PERSISTENT(AdResponseBox, false)
//////////////////////////////////////////////////////////////////////////
AdResponseBox::AdResponseBox(BaseGame *inGame) : BaseObject(inGame) {
_font = _fontHover = nullptr;
_window = nullptr;
_shieldWindow = new UIWindow(_gameRef);
_horizontal = false;
_responseArea.setEmpty();
_scrollOffset = 0;
_spacing = 0;
_waitingScript = nullptr;
_lastResponseText = nullptr;
_lastResponseTextOrig = nullptr;
_verticalAlign = VAL_BOTTOM;
_align = TAL_LEFT;
}
//////////////////////////////////////////////////////////////////////////
AdResponseBox::~AdResponseBox() {
delete _window;
_window = nullptr;
delete _shieldWindow;
_shieldWindow = nullptr;
delete[] _lastResponseText;
_lastResponseText = nullptr;
delete[] _lastResponseTextOrig;
_lastResponseTextOrig = nullptr;
if (_font) {
_gameRef->_fontStorage->removeFont(_font);
}
if (_fontHover) {
_gameRef->_fontStorage->removeFont(_fontHover);
}
clearResponses();
clearButtons();
_waitingScript = nullptr;
}
uint32 AdResponseBox::getNumResponses() const {
return _responses.size();
}
//////////////////////////////////////////////////////////////////////////
void AdResponseBox::clearResponses() {
for (uint32 i = 0; i < _responses.size(); i++) {
delete _responses[i];
}
_responses.clear();
}
//////////////////////////////////////////////////////////////////////////
void AdResponseBox::clearButtons() {
for (uint32 i = 0; i < _respButtons.size(); i++) {
delete _respButtons[i];
}
_respButtons.clear();
}
//////////////////////////////////////////////////////////////////////////
bool AdResponseBox::invalidateButtons() {
for (uint32 i = 0; i < _respButtons.size(); i++) {
_respButtons[i]->setImage(nullptr);
_respButtons[i]->setFont(nullptr);
_respButtons[i]->setText("");
_respButtons[i]->_cursor = nullptr;
_respButtons[i]->setFontHover(nullptr);
_respButtons[i]->setFontPress(nullptr);
}
return STATUS_OK;
}
//////////////////////////////////////////////////////////////////////////
bool AdResponseBox::createButtons() {
clearButtons();
_scrollOffset = 0;
for (uint32 i = 0; i < _responses.size(); i++) {
UIButton *btn = new UIButton(_gameRef);
if (btn) {
btn->_parent = _window;
btn->setSharedFonts(true);
btn->setSharedImages(true);
btn->_sharedCursors = true;
// iconic
if (_responses[i]->getIcon()) {
btn->setImage(_responses[i]->getIcon());
if (_responses[i]->getIconHover()) {
btn->setImageHover(_responses[i]->getIconHover());
}
if (_responses[i]->getIconPressed()) {
btn->setImagePress(_responses[i]->getIconPressed());
}
btn->setCaption(_responses[i]->getText());
if (_cursor) {
btn->_cursor = _cursor;
} else if (_gameRef->_activeCursor) {
btn->_cursor = _gameRef->_activeCursor;
}
}
// textual
else {
btn->setText(_responses[i]->getText());
if (_font == nullptr) {
btn->setFont(_gameRef->getSystemFont());
} else {
btn->setFont(_font);
}
btn->setFontHover((_fontHover == nullptr) ? _gameRef->getSystemFont() : _fontHover);
btn->setFontPress(btn->getFontHover());
btn->setTextAlign(_align);
if (_gameRef->_touchInterface) {
btn->setFontHover(btn->getFont());
}
if (_responses[i]->getFont()) {
btn->setFont(_responses[i]->getFont());
}
int width = _responseArea.right - _responseArea.left;
if (width <= 0) {
btn->setWidth(_gameRef->_renderer->getWidth());
} else {
btn->setWidth(width);
}
}
btn->setName("response");
btn->correctSize();
// make the responses touchable
if (_gameRef->_touchInterface) {
btn->setHeight(MAX<int32>(btn->getHeight(), 50));
}
//btn->SetListener(this, btn, _responses[i]->_iD);
btn->setListener(this, btn, i);
btn->setVisible(false);
_respButtons.add(btn);
if (_responseArea.bottom - _responseArea.top < btn->getHeight()) {
_gameRef->LOG(0, "Warning: Response '%s' is too high to be displayed within response box. Correcting.", _responses[i]->getText());
_responseArea.bottom += (btn->getHeight() - (_responseArea.bottom - _responseArea.top));
}
}
}
_ready = false;
return STATUS_OK;
}
//////////////////////////////////////////////////////////////////////////
bool AdResponseBox::loadFile(const char *filename) {
char *buffer = (char *)BaseFileManager::getEngineInstance()->readWholeFile(filename);
if (buffer == nullptr) {
_gameRef->LOG(0, "AdResponseBox::LoadFile failed for file '%s'", filename);
return STATUS_FAILED;
}
bool ret;
setFilename(filename);
if (DID_FAIL(ret = loadBuffer(buffer, true))) {
_gameRef->LOG(0, "Error parsing RESPONSE_BOX file '%s'", filename);
}
delete[] buffer;
return ret;
}
TOKEN_DEF_START
TOKEN_DEF(RESPONSE_BOX)
TOKEN_DEF(TEMPLATE)
TOKEN_DEF(FONT_HOVER)
TOKEN_DEF(FONT)
TOKEN_DEF(AREA)
TOKEN_DEF(HORIZONTAL)
TOKEN_DEF(SPACING)
TOKEN_DEF(WINDOW)
TOKEN_DEF(CURSOR)
TOKEN_DEF(TEXT_ALIGN)
TOKEN_DEF(VERTICAL_ALIGN)
TOKEN_DEF(EDITOR_PROPERTY)
TOKEN_DEF_END
//////////////////////////////////////////////////////////////////////////
bool AdResponseBox::loadBuffer(char *buffer, bool complete) {
TOKEN_TABLE_START(commands)
TOKEN_TABLE(RESPONSE_BOX)
TOKEN_TABLE(TEMPLATE)
TOKEN_TABLE(FONT_HOVER)
TOKEN_TABLE(FONT)
TOKEN_TABLE(AREA)
TOKEN_TABLE(HORIZONTAL)
TOKEN_TABLE(SPACING)
TOKEN_TABLE(WINDOW)
TOKEN_TABLE(CURSOR)
TOKEN_TABLE(TEXT_ALIGN)
TOKEN_TABLE(VERTICAL_ALIGN)
TOKEN_TABLE(EDITOR_PROPERTY)
TOKEN_TABLE_END
char *params;
int cmd;
BaseParser parser;
if (complete) {
if (parser.getCommand(&buffer, commands, &params) != TOKEN_RESPONSE_BOX) {
_gameRef->LOG(0, "'RESPONSE_BOX' keyword expected.");
return STATUS_FAILED;
}
buffer = params;
}
while ((cmd = parser.getCommand(&buffer, commands, &params)) > 0) {
switch (cmd) {
case TOKEN_TEMPLATE:
if (DID_FAIL(loadFile(params))) {
cmd = PARSERR_GENERIC;
}
break;
case TOKEN_WINDOW:
delete _window;
_window = new UIWindow(_gameRef);
if (!_window || DID_FAIL(_window->loadBuffer(params, false))) {
delete _window;
_window = nullptr;
cmd = PARSERR_GENERIC;
} else if (_shieldWindow) {
_shieldWindow->_parent = _window;
}
break;
case TOKEN_FONT:
if (_font) {
_gameRef->_fontStorage->removeFont(_font);
}
_font = _gameRef->_fontStorage->addFont(params);
if (!_font) {
cmd = PARSERR_GENERIC;
}
break;
case TOKEN_FONT_HOVER:
if (_fontHover) {
_gameRef->_fontStorage->removeFont(_fontHover);
}
_fontHover = _gameRef->_fontStorage->addFont(params);
if (!_fontHover) {
cmd = PARSERR_GENERIC;
}
break;
case TOKEN_AREA:
parser.scanStr(params, "%d,%d,%d,%d", &_responseArea.left, &_responseArea.top, &_responseArea.right, &_responseArea.bottom);
break;
case TOKEN_HORIZONTAL:
parser.scanStr(params, "%b", &_horizontal);
break;
case TOKEN_TEXT_ALIGN:
if (scumm_stricmp(params, "center") == 0) {
_align = TAL_CENTER;
} else if (scumm_stricmp(params, "right") == 0) {
_align = TAL_RIGHT;
} else {
_align = TAL_LEFT;
}
break;
case TOKEN_VERTICAL_ALIGN:
if (scumm_stricmp(params, "top") == 0) {
_verticalAlign = VAL_TOP;
} else if (scumm_stricmp(params, "center") == 0) {
_verticalAlign = VAL_CENTER;
} else {
_verticalAlign = VAL_BOTTOM;
}
break;
case TOKEN_SPACING:
parser.scanStr(params, "%d", &_spacing);
break;
case TOKEN_EDITOR_PROPERTY:
parseEditorProperty(params, false);
break;
case TOKEN_CURSOR:
delete _cursor;
_cursor = new BaseSprite(_gameRef);
if (!_cursor || DID_FAIL(_cursor->loadFile(params))) {
delete _cursor;
_cursor = nullptr;
cmd = PARSERR_GENERIC;
}
break;
}
}
if (cmd == PARSERR_TOKENNOTFOUND) {
_gameRef->LOG(0, "Syntax error in RESPONSE_BOX definition");
return STATUS_FAILED;
}
if (_window) {
for (uint32 i = 0; i < _window->_widgets.size(); i++) {
if (!_window->_widgets[i]->getListener()) {
_window->_widgets[i]->setListener(this, _window->_widgets[i], 0);
}
}
}
return STATUS_OK;
}
//////////////////////////////////////////////////////////////////////////
bool AdResponseBox::saveAsText(BaseDynamicBuffer *buffer, int indent) {
buffer->putTextIndent(indent, "RESPONSE_BOX\n");
buffer->putTextIndent(indent, "{\n");
buffer->putTextIndent(indent + 2, "AREA { %d, %d, %d, %d }\n", _responseArea.left, _responseArea.top, _responseArea.right, _responseArea.bottom);
if (_font && _font->getFilename()) {
buffer->putTextIndent(indent + 2, "FONT=\"%s\"\n", _font->getFilename());
}
if (_fontHover && _fontHover->getFilename()) {
buffer->putTextIndent(indent + 2, "FONT_HOVER=\"%s\"\n", _fontHover->getFilename());
}
if (_cursor && _cursor->getFilename()) {
buffer->putTextIndent(indent + 2, "CURSOR=\"%s\"\n", _cursor->getFilename());
}
buffer->putTextIndent(indent + 2, "HORIZONTAL=%s\n", _horizontal ? "TRUE" : "FALSE");
switch (_align) {
case TAL_LEFT:
buffer->putTextIndent(indent + 2, "TEXT_ALIGN=\"%s\"\n", "left");
break;
case TAL_RIGHT:
buffer->putTextIndent(indent + 2, "TEXT_ALIGN=\"%s\"\n", "right");
break;
case TAL_CENTER:
buffer->putTextIndent(indent + 2, "TEXT_ALIGN=\"%s\"\n", "center");
break;
default:
error("AdResponseBox::SaveAsText - Unhandled enum");
break;
}
switch (_verticalAlign) {
case VAL_TOP:
buffer->putTextIndent(indent + 2, "VERTICAL_ALIGN=\"%s\"\n", "top");
break;
case VAL_BOTTOM:
buffer->putTextIndent(indent + 2, "VERTICAL_ALIGN=\"%s\"\n", "bottom");
break;
case VAL_CENTER:
buffer->putTextIndent(indent + 2, "VERTICAL_ALIGN=\"%s\"\n", "center");
break;
}
buffer->putTextIndent(indent + 2, "SPACING=%d\n", _spacing);
buffer->putTextIndent(indent + 2, "\n");
// window
if (_window) {
_window->saveAsText(buffer, indent + 2);
}
buffer->putTextIndent(indent + 2, "\n");
// editor properties
BaseClass::saveAsText(buffer, indent + 2);
buffer->putTextIndent(indent, "}\n");
return STATUS_OK;
}
//////////////////////////////////////////////////////////////////////////
bool AdResponseBox::display() {
Rect32 rect = _responseArea;
if (_window) {
rect.offsetRect(_window->_posX, _window->_posY);
//_window->display();
}
int xxx, yyy;
uint32 i;
xxx = rect.left;
yyy = rect.top;
// shift down if needed
if (!_horizontal) {
int totalHeight = 0;
for (i = 0; i < _respButtons.size(); i++) {
totalHeight += (_respButtons[i]->getHeight() + _spacing);
}
totalHeight -= _spacing;
switch (_verticalAlign) {
case VAL_BOTTOM:
if (yyy + totalHeight < rect.bottom) {
yyy = rect.bottom - totalHeight;
}
break;
case VAL_CENTER:
if (yyy + totalHeight < rect.bottom) {
yyy += ((rect.bottom - rect.top) - totalHeight) / 2;
}
break;
case VAL_TOP:
// do nothing
break;
}
}
// prepare response buttons
bool scrollNeeded = false;
for (i = _scrollOffset; i < _respButtons.size(); i++) {
if ((_horizontal && xxx + _respButtons[i]->getWidth() > rect.right)
|| (!_horizontal && yyy + _respButtons[i]->getHeight() > rect.bottom)) {
scrollNeeded = true;
_respButtons[i]->setVisible(false);
break;
}
_respButtons[i]->setVisible(true);
_respButtons[i]->_posX = xxx;
_respButtons[i]->_posY = yyy;
if (_horizontal) {
xxx += (_respButtons[i]->getWidth() + _spacing);
} else {
yyy += (_respButtons[i]->getHeight() + _spacing);
}
}
// show appropriate scroll buttons
if (_window) {
_window->showWidget("prev", _scrollOffset > 0);
_window->showWidget("next", scrollNeeded);
}
// go exclusive
if (_shieldWindow) {
_shieldWindow->_posX = _shieldWindow->_posY = 0;
_shieldWindow->setWidth(_gameRef->_renderer->getWidth());
_shieldWindow->setHeight(_gameRef->_renderer->getHeight());
_shieldWindow->display();
}
// display window
if (_window) {
_window->display();
}
// display response buttons
for (i = _scrollOffset; i < _respButtons.size(); i++) {
_respButtons[i]->display();
}
return STATUS_OK;
}
//////////////////////////////////////////////////////////////////////////
bool AdResponseBox::listen(BaseScriptHolder *param1, uint32 param2) {
UIObject *obj = (UIObject *)param1;
switch (obj->_type) {
case UI_BUTTON:
if (scumm_stricmp(obj->getName(), "prev") == 0) {
_scrollOffset--;
} else if (scumm_stricmp(obj->getName(), "next") == 0) {
_scrollOffset++;
} else if (scumm_stricmp(obj->getName(), "response") == 0) {
if (_waitingScript) {
_waitingScript->_stack->pushInt(_responses[param2]->getID());
}
handleResponse(_responses[param2]);
_waitingScript = nullptr;
_gameRef->_state = GAME_RUNNING;
((AdGame *)_gameRef)->_stateEx = GAME_NORMAL;
_ready = true;
invalidateButtons();
clearResponses();
} else {
return BaseObject::listen(param1, param2);
}
break;
default:
error("AdResponseBox::Listen - Unhandled enum");
}
return STATUS_OK;
}
//////////////////////////////////////////////////////////////////////////
bool AdResponseBox::persist(BasePersistenceManager *persistMgr) {
BaseObject::persist(persistMgr);
persistMgr->transferPtr(TMEMBER_PTR(_font));
persistMgr->transferPtr(TMEMBER_PTR(_fontHover));
persistMgr->transferBool(TMEMBER(_horizontal));
persistMgr->transferCharPtr(TMEMBER(_lastResponseText));
persistMgr->transferCharPtr(TMEMBER(_lastResponseTextOrig));
_respButtons.persist(persistMgr);
persistMgr->transferRect32(TMEMBER(_responseArea));
_responses.persist(persistMgr);
persistMgr->transferSint32(TMEMBER(_scrollOffset));
persistMgr->transferPtr(TMEMBER_PTR(_shieldWindow));
persistMgr->transferSint32(TMEMBER(_spacing));
persistMgr->transferPtr(TMEMBER_PTR(_waitingScript));
persistMgr->transferPtr(TMEMBER_PTR(_window));
persistMgr->transferSint32(TMEMBER_INT(_verticalAlign));
persistMgr->transferSint32(TMEMBER_INT(_align));
return STATUS_OK;
}
//////////////////////////////////////////////////////////////////////////
bool AdResponseBox::weedResponses() {
AdGame *adGame = (AdGame *)_gameRef;
for (uint32 i = 0; i < _responses.size(); i++) {
switch (_responses[i]->_responseType) {
case RESPONSE_ONCE:
if (adGame->branchResponseUsed(_responses[i]->getID())) {
delete _responses[i];
_responses.remove_at(i);
i--;
}
break;
case RESPONSE_ONCE_GAME:
if (adGame->gameResponseUsed(_responses[i]->getID())) {
delete _responses[i];
_responses.remove_at(i);
i--;
}
break;
default:
debugC(kWintermuteDebugGeneral, "AdResponseBox::WeedResponses - Unhandled enum");
break;
}
}
return STATUS_OK;
}
//////////////////////////////////////////////////////////////////////////
void AdResponseBox::setLastResponseText(const char *text, const char *textOrig) {
BaseUtils::setString(&_lastResponseText, text);
BaseUtils::setString(&_lastResponseTextOrig, textOrig);
}
const char *AdResponseBox::getLastResponseText() const {
return _lastResponseText;
}
const char *AdResponseBox::getLastResponseTextOrig() const {
return _lastResponseTextOrig;
}
UIWindow *AdResponseBox::getResponseWindow() {
return _window;
}
void AdResponseBox::addResponse(const AdResponse *response) {
_responses.add(response);
}
int32 AdResponseBox::getIdForResponseNum(uint32 num) const {
assert(num < _responses.size());
return _responses[num]->getID();
}
bool AdResponseBox::handleResponseNum(uint32 num) {
return handleResponse(_responses[num]);
}
//////////////////////////////////////////////////////////////////////////
bool AdResponseBox::handleResponse(const AdResponse *response) {
setLastResponseText(response->getText(), response->getTextOrig());
AdGame *adGame = (AdGame *)_gameRef;
switch (response->_responseType) {
case RESPONSE_ONCE:
adGame->addBranchResponse(response->getID());
break;
case RESPONSE_ONCE_GAME:
adGame->addGameResponse(response->getID());
break;
default:
debugC(kWintermuteDebugGeneral, "AdResponseBox::HandleResponse - Unhandled enum");
}
return STATUS_OK;
}
//////////////////////////////////////////////////////////////////////////
BaseObject *AdResponseBox::getNextAccessObject(BaseObject *currObject) {
BaseArray<UIObject *> objects;
getObjects(objects, true);
if (objects.size() == 0) {
return nullptr;
} else {
if (currObject != nullptr) {
for (uint32 i = 0; i < objects.size(); i++) {
if (objects[i] == currObject) {
if (i < objects.size() - 1) {
return objects[i + 1];
} else {
break;
}
}
}
}
return objects[0];
}
return nullptr;
}
//////////////////////////////////////////////////////////////////////////
BaseObject *AdResponseBox::getPrevAccessObject(BaseObject *currObject) {
BaseArray<UIObject *> objects;
getObjects(objects, true);
if (objects.size() == 0) {
return nullptr;
} else {
if (currObject != nullptr) {
for (int i = objects.size() - 1; i >= 0; i--) {
if (objects[i] == currObject) {
if (i > 0) {
return objects[i - 1];
} else {
break;
}
}
}
}
return objects[objects.size() - 1];
}
return nullptr;
}
//////////////////////////////////////////////////////////////////////////
bool AdResponseBox::getObjects(BaseArray<UIObject *> &objects, bool interactiveOnly) {
for (uint32 i = 0; i < _respButtons.size(); i++) {
objects.add(_respButtons[i]);
}
if (_window) {
_window->getWindowObjects(objects, interactiveOnly);
}
return STATUS_OK;
}
} // End of namespace Wintermute