scummvm/engines/agi/text.cpp
2016-02-03 08:27:44 +01:00

1085 lines
28 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.
*
*/
#include "agi/agi.h"
#include "agi/sprite.h" // for commit_both()
#include "agi/graphics.h"
#include "agi/keyboard.h"
#include "agi/text.h"
#include "agi/systemui.h"
#include "agi/words.h"
#ifdef __DS__
#include "wordcompletion.h"
#endif
namespace Agi {
TextMgr::TextMgr(AgiEngine *vm, Words *words, GfxMgr *gfx) {
_vm = vm;
_words = words;
_gfx = gfx;
memset(&_messageState, 0, sizeof(_messageState));
_textPos.row = 0;
_textPos.column = 0;
_reset_Column = 0;
charAttrib_Set(15, 0);
_messageState.wanted_TextPos.row = -1;
_messageState.wanted_TextPos.column = -1;
_messageState.wanted_Text_Width = -1;
_textPosArrayCount = 0;
memset(&_textPosArray, 0, sizeof(_textPosArray));
_textAttribArrayCount = 0;
memset(&_textAttribArray, 0, sizeof(_textAttribArray));
_inputEditEnabled = false;
_inputCursorChar = 0;
_statusEnabled = false;
_statusRow = 0;
_promptRow = 0;
promptDisable();
promptReset();
_inputStringRow = 0;
_inputStringColumn = 0;
_inputStringMaxLen = 0;
_inputStringCursorPos = 0;
_inputString[0] = 0;
configureScreen(2);
_messageBoxCancelled = false;
}
TextMgr::~TextMgr() {
}
void TextMgr::init(SystemUI *systemUI) {
_systemUI = systemUI;
}
void TextMgr::configureScreen(uint16 row_Min) {
_window_Row_Min = row_Min;
_window_Row_Max = row_Min + 21;
// forward data to GfxMgr as well
_gfx->setRenderStartOffset(row_Min * FONT_DISPLAY_HEIGHT);
}
uint16 TextMgr::getWindowRowMin() {
return _window_Row_Min;
}
void TextMgr::dialogueOpen() {
_messageState.dialogue_Open = true;
}
void TextMgr::dialogueClose() {
_messageState.dialogue_Open = false;
}
void TextMgr::charPos_Clip(int16 &row, int16 &column) {
row = CLIP<int16>(row, 0, FONT_ROW_CHARACTERS - 1);
column = CLIP<int16>(column, 0, FONT_COLUMN_CHARACTERS - 1);
}
void TextMgr::charPos_Set(int16 row, int16 column) {
_textPos.row = row;
_textPos.column = column;
}
void TextMgr::charPos_Set(TextPos_Struct *posPtr) {
_textPos.row = posPtr->row;
_textPos.column = posPtr->column;
}
void TextMgr::charPos_Get(int16 &row, int16 &column) {
row = _textPos.row;
column = _textPos.column;
}
void TextMgr::charPos_Get(TextPos_Struct *posPtr) {
posPtr->row = _textPos.row;
posPtr->column = _textPos.column;
}
void TextMgr::charPos_Push() {
if (_textPosArrayCount < TEXTPOSARRAY_MAX) {
charPos_Get(&_textPosArray[_textPosArrayCount]);
_textPosArrayCount++;
}
}
void TextMgr::charPos_Pop() {
if (_textPosArrayCount > 0) {
_textPosArrayCount--;
charPos_Set(&_textPosArray[_textPosArrayCount]);
}
}
void TextMgr::charPos_SetInsideWindow(int16 windowRow, int16 windowColumn) {
if (!_messageState.window_Active)
return;
_textPos.row = _messageState.textPos.row + windowRow;
_textPos.column = _messageState.textPos.column + windowColumn;
}
static byte charAttrib_CGA_Conversion[] = {
0, 1, 1, 1, 2, 2, 2, 3, 3, 1, 1, 1, 2, 2, 2
};
void TextMgr::charAttrib_Set(byte foreground, byte background) {
_textAttrib.foreground = foreground;
_textAttrib.background = calculateTextBackground(background);
if (!_vm->_game.gfxMode) {
// Text-mode:
// just use the given colors directly
_textAttrib.combinedForeground = foreground;
_textAttrib.combinedBackground = background;
} else {
// Graphics-mode:
switch (_vm->_renderMode) {
case Common::kRenderCGA:
// CGA
if (background) {
_textAttrib.combinedForeground = 3;
_textAttrib.combinedBackground = 8; // enable invert of colors
} else if (foreground > 14) {
if (foreground > 14) {
_textAttrib.combinedForeground = 3;
} else {
_textAttrib.combinedForeground = charAttrib_CGA_Conversion[foreground & 0x0F];
}
_textAttrib.combinedBackground = 0;
}
break;
default:
// EGA-handling:
if (background) {
_textAttrib.combinedForeground = 15;
_textAttrib.combinedBackground = 8; // enable invert of colors
} else {
_textAttrib.combinedForeground = foreground;
_textAttrib.combinedBackground = 0;
}
break;
}
}
}
byte TextMgr::charAttrib_GetForeground() {
return _textAttrib.foreground;
}
byte TextMgr::charAttrib_GetBackground() {
return _textAttrib.background;
}
void TextMgr::charAttrib_Push() {
if (_textAttribArrayCount < TEXTATTRIBARRAY_MAX) {
memcpy(&_textAttribArray[_textAttribArrayCount], &_textAttrib, sizeof(_textAttrib));
_textAttribArrayCount++;
}
}
void TextMgr::charAttrib_Pop() {
if (_textAttribArrayCount > 0) {
_textAttribArrayCount--;
memcpy(&_textAttrib, &_textAttribArray[_textAttribArrayCount], sizeof(_textAttrib));
}
}
byte TextMgr::calculateTextBackground(byte background) {
if ((_vm->_game.gfxMode) && (background)) {
return 15; // interpreter sets 0xFF, but drawClearCharacter() would use upper 4 bits by shift
}
return 0;
}
void TextMgr::display(int16 textNr, int16 textRow, int16 textColumn) {
const char *logicTextPtr = NULL;
char *processedTextPtr = NULL;
charPos_Push();
charPos_Set(textRow, textColumn);
if (textNr >= 1 && textNr <= _vm->_game._curLogic->numTexts) {
logicTextPtr = _vm->_game._curLogic->texts[textNr - 1];
processedTextPtr = stringPrintf(logicTextPtr);
processedTextPtr = stringWordWrap(processedTextPtr, 40);
displayText(processedTextPtr);
// Signal, that non-blocking text is shown at the moment
if (textRow > 0) {
// only signal, when it's not the status line (kq3)
_vm->nonBlockingText_IsShown();
}
}
charPos_Pop();
}
void TextMgr::displayTextInsideWindow(const char *textPtr, int16 windowRow, int16 windowColumn) {
int16 textRow = 0;
int16 textColumn = 0;
if (!_messageState.window_Active)
return;
charPos_Push();
textRow = _messageState.textPos.row + windowRow;
textColumn = _messageState.textPos.column + windowColumn;
charPos_Set(textRow, textColumn);
displayText(textPtr);
charPos_Pop();
}
void TextMgr::displayText(const char *textPtr, bool disabledLook) {
const char *curTextPtr = textPtr;
byte curCharacter = 0;
while (1) {
curCharacter = *curTextPtr;
if (!curCharacter)
break;
curTextPtr++;
displayCharacter(curCharacter, disabledLook);
}
}
void TextMgr::displayCharacter(byte character, bool disabledLook) {
TextPos_Struct charCurPos;
charPos_Get(&charCurPos);
switch (character) {
case 0x08: // backspace
if (charCurPos.column) {
charCurPos.column--;
} else if (charCurPos.row > 21) {
charCurPos.column = (FONT_COLUMN_CHARACTERS - 1);
charCurPos.row--;
}
clearBlock(charCurPos.row, charCurPos.column, charCurPos.row, charCurPos.column, _textAttrib.background);
charPos_Set(&charCurPos);
break;
case 0x0D:
case 0x0A: // CR/LF
if (charCurPos.row < (FONT_ROW_CHARACTERS - 1))
charCurPos.row++;
charCurPos.column = _reset_Column;
charPos_Set(&charCurPos);
break;
default:
// ch_attrib(state.text_comb, conversion);
_gfx->drawCharacter(charCurPos.row, charCurPos.column, character, _textAttrib.combinedForeground, _textAttrib.combinedBackground, disabledLook);
charCurPos.column++;
if (charCurPos.column <= (FONT_COLUMN_CHARACTERS - 1)) {
charPos_Set(&charCurPos);
} else {
displayCharacter(0x0D); // go to next line
}
}
}
void TextMgr::print(int16 textNr) {
const char *logicTextPtr = NULL;
if (textNr >= 1 && textNr <= _vm->_game._curLogic->numTexts) {
logicTextPtr = _vm->_game._curLogic->texts[textNr - 1];
messageBox(logicTextPtr);
}
}
void TextMgr::printAt(int16 textNr, int16 textPos_Row, int16 textPos_Column, int16 text_Width) {
// Sierra didn't do clipping, we do it for security
charPos_Clip(textPos_Row, textPos_Column);
_messageState.wanted_TextPos.row = textPos_Row;
_messageState.wanted_TextPos.column = textPos_Column;
_messageState.wanted_Text_Width = text_Width;
if (_messageState.wanted_Text_Width == 0) {
_messageState.wanted_Text_Width = 30;
}
print(textNr);
_messageState.wanted_TextPos.row = -1;
_messageState.wanted_TextPos.column = -1;
_messageState.wanted_Text_Width = -1;
}
bool TextMgr::messageBox(const char *textPtr) {
drawMessageBox(textPtr);
if (_vm->getFlag(VM_FLAG_OUTPUT_MODE)) {
// non-blocking window
_vm->setFlag(VM_FLAG_OUTPUT_MODE, false);
// Signal, that non-blocking text is shown at the moment
_vm->nonBlockingText_IsShown();
return true;
}
// blocking window
_vm->_noSaveLoadAllowed = true;
_vm->nonBlockingText_Forget();
// timed window
uint32 windowTimer = _vm->getVar(VM_VAR_WINDOW_AUTO_CLOSE_TIMER);
debugC(3, kDebugLevelText, "blocking window v21=%d", windowTimer);
windowTimer = windowTimer * 10; // 1 = 0.5 seconds
_messageBoxCancelled = false;
_vm->inGameTimerResetPassedCycles();
_vm->cycleInnerLoopActive(CYCLE_INNERLOOP_MESSAGEBOX);
do {
_vm->processAGIEvents();
_vm->inGameTimerUpdate();
if (windowTimer > 0) {
if (_vm->inGameTimerGetPassedCycles() >= windowTimer) {
// Timer reached, close automatically
_vm->cycleInnerLoopInactive();
}
}
} while (_vm->cycleInnerLoopIsActive() && !(_vm->shouldQuit() || _vm->_restartGame));
_vm->inGameTimerResetPassedCycles();
_vm->setVar(VM_VAR_WINDOW_AUTO_CLOSE_TIMER, 0);
closeWindow();
_vm->_noSaveLoadAllowed = false;
if (_messageBoxCancelled)
return false;
return true;
}
void TextMgr::messageBox_KeyPress(uint16 newKey) {
switch (newKey) {
case AGI_KEY_ENTER:
_vm->cycleInnerLoopInactive(); // exit messagebox-loop
break;
case AGI_KEY_ESCAPE:
_messageBoxCancelled = true;
_vm->cycleInnerLoopInactive(); // exit messagebox-loop
break;
case AGI_MOUSE_BUTTON_LEFT: {
// Find out, where current mouse cursor actually is
int16 mouseY = _vm->_mouse.pos.y;
int16 mouseX = _vm->_mouse.pos.x;
_vm->adjustPosToGameScreen(mouseX, mouseY);
// Check, if mouse cursor is within message box
// If it is, take the click as ENTER.
// That's what AGI on Amiga + Apple IIgs did.
// On Atari ST at least via emulator it seems that the mouse cursor froze when messageboxes were diplayed.
if ((mouseX >= _messageState.backgroundPos_x) && (mouseX <= (_messageState.backgroundPos_x + _messageState.backgroundSize_Width))) {
if ((mouseY >= _messageState.backgroundPos_y - _messageState.backgroundSize_Height) && (mouseY <= (_messageState.backgroundPos_y))) {
_vm->cycleInnerLoopInactive(); // exit messagebox-loop
}
}
break;
}
default:
break;
}
}
void TextMgr::drawMessageBox(const char *textPtr, int16 wantedHeight, int16 wantedWidth, bool wantedForced) {
int16 maxWidth = wantedWidth;
int16 startingRow = 0;
char *processedTextPtr;
if (_messageState.window_Active) {
closeWindow();
}
charAttrib_Push();
charPos_Push();
charAttrib_Set(0, 15);
if ((_messageState.wanted_Text_Width == -1) && (maxWidth == 0)) {
maxWidth = 30;
} else if (_messageState.wanted_Text_Width != -1) {
maxWidth = _messageState.wanted_Text_Width;
}
processedTextPtr = stringPrintf(textPtr);
int16 calculatedWidth = 0;
int16 calculatedHeight = 0;
processedTextPtr = stringWordWrap(processedTextPtr, maxWidth, &calculatedWidth, &calculatedHeight);
_messageState.textSize_Width = calculatedWidth;
_messageState.textSize_Height = calculatedHeight;
_messageState.printed_Height = _messageState.textSize_Height;
// Caller wants to force specified width/height? set it
if (wantedForced) {
if (wantedHeight)
_messageState.textSize_Height = wantedHeight;
if (wantedWidth)
_messageState.textSize_Width = wantedWidth;
}
if (_messageState.wanted_TextPos.row == -1) {
startingRow = ((HEIGHT_MAX - _messageState.textSize_Height - 1) / 2) + 1;
} else {
startingRow = _messageState.wanted_TextPos.row;
}
_messageState.textPos.row = startingRow + _window_Row_Min;
_messageState.textPos_Edge.row = _messageState.textSize_Height + _messageState.textPos.row - 1;
if (_messageState.wanted_TextPos.column == -1) {
_messageState.textPos.column = (FONT_COLUMN_CHARACTERS - _messageState.textSize_Width) / 2;
} else {
_messageState.textPos.column = _messageState.wanted_TextPos.column;
}
_messageState.textPos_Edge.column = _messageState.textPos.column + _messageState.textSize_Width;
charPos_Set(_messageState.textPos.row, _messageState.textPos.column);
_messageState.backgroundSize_Width = (_messageState.textSize_Width * FONT_VISUAL_WIDTH) + 10;
_messageState.backgroundSize_Height = (_messageState.textSize_Height * FONT_VISUAL_HEIGHT) + 10;
_messageState.backgroundPos_x = (_messageState.textPos.column * FONT_VISUAL_WIDTH) - 5;
_messageState.backgroundPos_y = (_messageState.textPos_Edge.row - _window_Row_Min + 1) * FONT_VISUAL_HEIGHT + 4;
// Hardcoded colors: white background and red lines
_gfx->drawBox(_messageState.backgroundPos_x, _messageState.backgroundPos_y, _messageState.backgroundSize_Width, _messageState.backgroundSize_Height, 15, 4);
_messageState.window_Active = true;
_reset_Column = _messageState.textPos.column;
displayText(processedTextPtr);
_reset_Column = 0;
charPos_Pop();
charAttrib_Pop();
_messageState.dialogue_Open = true;
}
void TextMgr::closeWindow() {
if (_messageState.window_Active) {
_gfx->render_Block(_messageState.backgroundPos_x, _messageState.backgroundPos_y, _messageState.backgroundSize_Width, _messageState.backgroundSize_Height);
}
_messageState.dialogue_Open = false;
_messageState.window_Active = false;
}
void TextMgr::statusRow_Set(int16 row) {
_statusRow = row;
}
int16 TextMgr::statusRow_Get() {
return _statusRow;
}
void TextMgr::statusEnable() {
_statusEnabled = true;
}
void TextMgr::statusDisable() {
_statusEnabled = false;
}
bool TextMgr::statusEnabled() {
return _statusEnabled;
}
void TextMgr::statusDraw() {
char *statusTextPtr = NULL;
charAttrib_Push();
charPos_Push();
if (_statusEnabled) {
clearLine(_statusRow, 15);
charAttrib_Set(0, 15);
charPos_Set(_statusRow, 1);
statusTextPtr = stringPrintf(_systemUI->getStatusTextScore());
displayText(statusTextPtr);
charPos_Set(_statusRow, 30);
if (_vm->getFlag(VM_FLAG_SOUND_ON)) {
statusTextPtr = stringPrintf(_systemUI->getStatusTextSoundOn());
} else {
statusTextPtr = stringPrintf(_systemUI->getStatusTextSoundOff());
}
displayText(statusTextPtr);
}
charPos_Pop();
charAttrib_Pop();
}
void TextMgr::statusClear() {
clearLine(_statusRow, 0);
}
void TextMgr::clearLine(int16 row, byte color) {
clearLines(row, row, color);
}
void TextMgr::clearLines(int16 row_Upper, int16 row_Lower, byte color) {
clearBlock(row_Upper, 0, row_Lower, FONT_COLUMN_CHARACTERS - 1, color);
}
void TextMgr::clearBlock(int16 row_Upper, int16 column_Upper, int16 row_Lower, int16 column_Lower, byte color) {
// Sierra didn't do clipping of the coordinates, we do it for security
// and b/c there actually are some games, that call commands with invalid coordinates
// see cmdClearLines() comments.
charPos_Clip(row_Upper, column_Upper);
charPos_Clip(row_Lower, column_Lower);
int16 x = column_Upper * FONT_DISPLAY_WIDTH;
int16 y = row_Upper * FONT_DISPLAY_HEIGHT;
int16 width = (column_Lower + 1 - column_Upper) * FONT_DISPLAY_WIDTH;
int16 height = (row_Lower + 1 - row_Upper) * FONT_DISPLAY_HEIGHT;
y = y + height - 1; // drawDisplayRect wants lower Y-coordinate
_gfx->drawDisplayRect(x, y, width, height, color);
}
void TextMgr::clearBlockInsideWindow(int16 windowRow, int16 windowColumn, int16 width, byte color) {
int16 row;
int16 column;
if (!_messageState.window_Active)
return;
row = _messageState.textPos.row + windowRow;
column = _messageState.textPos.column + windowColumn;
clearBlock(row, column, row, column + width - 1, color);
}
bool TextMgr::inputGetEditStatus() {
return _inputEditEnabled;
}
void TextMgr::inputEditOn() {
if (!_inputEditEnabled) {
_inputEditEnabled = true;
if (_inputCursorChar) {
displayCharacter(0x08); // backspace
}
}
}
void TextMgr::inputEditOff() {
if (_inputEditEnabled) {
_inputEditEnabled = false;
if (_inputCursorChar) {
displayCharacter(_inputCursorChar);
}
}
}
void TextMgr::inputSetCursorChar(int16 cursorChar) {
_inputCursorChar = cursorChar;
}
byte TextMgr::inputGetCursorChar() {
return _inputCursorChar;
}
void TextMgr::promptRow_Set(int16 row) {
_promptRow = row;
}
int16 TextMgr::promptRow_Get() {
return _promptRow;
}
void TextMgr::promptReset() {
_promptCursorPos = 0;
memset(_prompt, 0, sizeof(_prompt));
memset(_promptPrevious, 0, sizeof(_promptPrevious));
}
void TextMgr::promptEnable() {
_promptEnabled = true;
}
void TextMgr::promptDisable() {
_promptEnabled = false;
}
bool TextMgr::promptIsEnabled() {
return _promptEnabled;
}
void TextMgr::promptKeyPress(uint16 newKey) {
int16 maxChars = 0;
int16 scriptsInputLen = _vm->getVar(VM_VAR_MAX_INPUT_CHARACTERS);
if (_messageState.dialogue_Open) {
maxChars = TEXT_STRING_MAX_SIZE - 4;
} else {
maxChars = TEXT_STRING_MAX_SIZE - strlen(_vm->_game.strings[0]); // string 0 is the prompt string prefix
}
if (_promptCursorPos)
maxChars--;
if (scriptsInputLen < maxChars)
maxChars = scriptsInputLen;
inputEditOn();
switch (newKey) {
case AGI_KEY_BACKSPACE: {
if (_promptCursorPos) {
_promptCursorPos--;
_prompt[_promptCursorPos] = 0;
displayCharacter(newKey);
promptRememberForAutoComplete();
}
break;
}
case 0x0A: // LF
break;
case AGI_KEY_ENTER: {
if (_promptCursorPos) {
// something got entered? -> process it and pass it to the scripts
promptRememberForAutoComplete(true);
memcpy(&_promptPrevious, &_prompt, sizeof(_prompt));
// parse text
_vm->_words->parseUsingDictionary((char *)&_prompt);
_promptCursorPos = 0;
_prompt[0] = 0;
promptRedraw();
}
break;
}
default:
if (maxChars > _promptCursorPos) {
bool acceptableInput = false;
// FEATURE: Sierra didn't check for valid characters (filtered out umlauts etc.)
// In text-mode this sort of worked at least with the DOS interpreter
// but as soon as invalid characters were used in graphics mode they weren't properly shown
switch (_vm->getLanguage()) {
case Common::RU_RUS:
if (newKey >= 0x20)
acceptableInput = true;
break;
default:
if ((newKey >= 0x20) && (newKey <= 0x7f))
acceptableInput = true;
break;
}
if (acceptableInput) {
_prompt[_promptCursorPos] = newKey;
_promptCursorPos++;
_prompt[_promptCursorPos] = 0;
displayCharacter(newKey);
promptRememberForAutoComplete();
}
}
break;
}
inputEditOff();
}
void TextMgr::promptCancelLine() {
while (_promptCursorPos) {
promptKeyPress(0x08); // Backspace until prompt is empty
}
}
void TextMgr::promptEchoLine() {
int16 previousLen = strlen((char *)_promptPrevious);
if (_promptCursorPos < previousLen) {
inputEditOn();
while (_promptPrevious[_promptCursorPos]) {
promptKeyPress(_promptPrevious[_promptCursorPos]);
}
promptRememberForAutoComplete();
inputEditOff();
}
}
void TextMgr::promptRedraw() {
char *textPtr = nullptr;
if (_promptEnabled) {
inputEditOn();
clearLine(_promptRow, _textAttrib.background);
charPos_Set(_promptRow, 0);
// agi_printf(str_wordwrap(msg, state.string[0], 40) );
textPtr = _vm->_game.strings[0];
textPtr = stringPrintf(textPtr);
textPtr = stringWordWrap(textPtr, 40);
displayText(textPtr);
displayText((char *)&_prompt);
inputEditOff();
}
}
// for AGI1
void TextMgr::promptClear() {
clearLine(_promptRow, _textAttrib.background);
}
void TextMgr::promptRememberForAutoComplete(bool entered) {
#ifdef __DS__
DS::findWordCompletions((char *)_prompt);
#endif
}
bool TextMgr::stringWasEntered() {
return _inputStringEntered;
}
void TextMgr::stringSet(const char *text) {
strncpy((char *)_inputString, text, sizeof(_inputString));
_inputString[sizeof(_inputString) - 1] = 0; // terminator
}
void TextMgr::stringPos_Get(int16 &row, int16 &column) {
row = _inputStringRow;
column = _inputStringColumn;
}
int16 TextMgr::stringGetMaxLen() {
return _inputStringMaxLen;
}
void TextMgr::stringEdit(int16 stringMaxLen) {
int16 inputStringLen = strlen((const char *)_inputString);
// Remember current position for predictive dialog
_inputStringRow = _textPos.row;
_inputStringColumn = _textPos.column;
// Caller can set the input string
_inputStringCursorPos = 0;
while (_inputStringCursorPos < inputStringLen) {
displayCharacter(_inputString[_inputStringCursorPos]);
_inputStringCursorPos++;
}
// should never happen unless there is a coding glitch
assert(_inputStringCursorPos <= stringMaxLen);
_inputStringMaxLen = stringMaxLen;
_inputStringEntered = false;
inputEditOff();
do {
_vm->processAGIEvents();
} while (_vm->cycleInnerLoopIsActive() && !(_vm->shouldQuit() || _vm->_restartGame));
inputEditOn();
// Forget non-blocking text, user was asked to enter something
_vm->nonBlockingText_Forget();
}
void TextMgr::stringKeyPress(uint16 newKey) {
inputEditOn();
switch (newKey) {
case 0x3: // ctrl-c
case 0x18: { // ctrl-x
// clear string
while (_inputStringCursorPos) {
_inputStringCursorPos--;
_inputString[_inputStringCursorPos] = 0;
displayCharacter(0x08);
}
break;
}
case AGI_KEY_BACKSPACE: {
if (_inputStringCursorPos) {
_inputStringCursorPos--;
_inputString[_inputStringCursorPos] = 0;
displayCharacter(newKey);
stringRememberForAutoComplete();
}
break;
}
case AGI_KEY_ENTER: {
stringRememberForAutoComplete(true);
_inputStringEntered = true;
_vm->cycleInnerLoopInactive(); // exit GetString-loop
break;
}
case AGI_KEY_ESCAPE: {
_inputString[0] = 0;
_inputStringCursorPos = 0;
_inputStringEntered = false;
_vm->cycleInnerLoopInactive(); // exit GetString-loop
break;
}
default:
if (_inputStringMaxLen > _inputStringCursorPos) {
bool acceptableInput = false;
// FEATURE: Sierra didn't check for valid characters (filtered out umlauts etc.)
// In text-mode this sort of worked at least with the DOS interpreter
// but as soon as invalid characters were used in graphics mode they weren't properly shown
switch (_vm->getLanguage()) {
case Common::RU_RUS:
if (newKey >= 0x20)
acceptableInput = true;
break;
default:
if ((newKey >= 0x20) && (newKey <= 0x7f))
acceptableInput = true;
break;
}
if (acceptableInput) {
if ((_vm->_game.cycleInnerLoopType == CYCLE_INNERLOOP_GETSTRING) || ((newKey >= '0') && (newKey <= '9'))) {
// Additionally check for GETNUMBER-mode, if character is a number
// Sierra also did not do this
_inputString[_inputStringCursorPos] = newKey;
_inputStringCursorPos++;
_inputString[_inputStringCursorPos] = 0;
displayCharacter(newKey);
stringRememberForAutoComplete();
}
}
}
break;
}
inputEditOff();
}
void TextMgr::stringRememberForAutoComplete(bool entered) {
#ifdef __DS__
DS::findWordCompletions((char *)_inputString);
#endif
}
/**
* Wrap text line to the specified width.
* @param str String to wrap.
* @param len Length of line.
*
* Based on GBAGI implementation with permission from the author
*/
char *TextMgr::stringWordWrap(const char *originalText, int16 maxWidth, int16 *calculatedWidthPtr, int16 *calculatedHeightPtr) {
static char resultWrapBuffer[2000];
char *outStr = resultWrapBuffer;
const char *wordStartPtr;
int16 lineLen = 0;
int16 wordLen = 0;
int curMaxWidth = 0;
int curHeight = 0;
assert(maxWidth > 0); // this routine would create heap corruption in case maxWidth <= 0
while (*originalText) {
wordStartPtr = originalText;
while (*originalText != '\0' && *originalText != ' ' && *originalText != '\n' && *originalText != '\r')
originalText++;
wordLen = originalText - wordStartPtr;
if (wordLen && *originalText == '\n' && originalText[-1] == ' ')
wordLen--;
if (wordLen + lineLen >= maxWidth) {
// Check if outStr isn't msgBuf. If this is the case, outStr hasn't advanced
// yet, so no output has been written yet
if (outStr != resultWrapBuffer) {
if (outStr[-1] == ' ')
outStr[-1] = '\n';
else
*outStr++ = '\n';
}
curHeight++;
lineLen = 0;
while (wordLen >= maxWidth) {
curMaxWidth = maxWidth;
memcpy(outStr, wordStartPtr, maxWidth);
wordLen -= maxWidth;
outStr += maxWidth;
wordStartPtr += maxWidth;
*outStr++ = '\n';
curHeight++;
}
}
if (wordLen) {
memcpy(outStr, wordStartPtr, wordLen);
outStr += wordLen;
}
lineLen += wordLen + 1;
if (lineLen > curMaxWidth) {
curMaxWidth = lineLen;
if (*originalText == '\0' || *originalText == ' ' || *originalText == '\n' || *originalText == '\r')
curMaxWidth--;
}
if (*originalText == '\n') {
lineLen = 0;
curHeight++;
}
if (*originalText)
*outStr++ = *originalText++;
}
*outStr = '\0';
curHeight++;
if (calculatedWidthPtr) {
*calculatedWidthPtr = curMaxWidth;
}
if (calculatedHeightPtr) {
*calculatedHeightPtr = curHeight;
}
return resultWrapBuffer;
}
// ===============================================================
static void safeStrcat(Common::String &p, const char *t) {
if (t != NULL)
p += t;
}
/**
* Formats AGI string.
* This function turns a AGI string into a real string expanding values
* according to the AGI format specifiers.
* @param s string containing the format specifier
* @param n logic number
*/
char *TextMgr::stringPrintf(const char *originalText) {
static char resultPrintfBuffer[2000];
Common::String resultString;
char z[16];
debugC(3, kDebugLevelText, "logic %d, '%s'", _vm->_game.curLogicNr, originalText);
while (*originalText) {
switch (*originalText) {
case '%':
originalText++;
switch (*originalText++) {
int i;
case 'v':
i = strtoul(originalText, NULL, 10);
while (*originalText >= '0' && *originalText <= '9')
originalText++;
sprintf(z, "%015i", _vm->getVar(i));
i = 99;
if (*originalText == '|') {
originalText++;
i = strtoul(originalText, NULL, 10);
while (*originalText >= '0' && *originalText <= '9')
originalText++;
}
if (i == 99) {
// remove all leading 0
// don't remove the 3rd zero if 000
for (i = 0; z[i] == '0' && i < 14; i++)
;
} else {
i = 15 - i;
}
safeStrcat(resultString, z + i);
break;
case '0':
i = strtoul(originalText, NULL, 10) - 1;
safeStrcat(resultString, _vm->objectName(i));
break;
case 'g':
i = strtoul(originalText, NULL, 10) - 1;
safeStrcat(resultString, _vm->_game.logics[0].texts[i]);
break;
case 'w':
i = strtoul(originalText, NULL, 10) - 1;
safeStrcat(resultString, _vm->_words->getEgoWord(i));
break;
case 's':
i = strtoul(originalText, NULL, 10);
safeStrcat(resultString, stringPrintf(_vm->_game.strings[i]));
break;
case 'm':
i = strtoul(originalText, NULL, 10) - 1;
if (_vm->_game.logics[_vm->_game.curLogicNr].numTexts > i)
safeStrcat(resultString, stringPrintf(_vm->_game.logics[_vm->_game.curLogicNr].texts[i]));
break;
}
while (*originalText >= '0' && *originalText <= '9')
originalText++;
break;
case '\\':
originalText++;
// FALL THROUGH
default:
resultString += *originalText++;
break;
}
}
assert(resultString.size() < sizeof(resultPrintfBuffer));
strcpy(resultPrintfBuffer, resultString.c_str());
return resultPrintfBuffer;
}
} // End of namespace Agi