scummvm/engines/agi/predictive.cpp
Eugene Sandulenko da3e724a99 Predictive input for AGI engine. To Do:
- Multitap
- scummvm.ini-based dictionary path
- speedup dictionary loading

svn-id: r24635
2006-11-06 13:19:12 +00:00

351 lines
7.5 KiB
C++

/* ScummVM - Scumm Interpreter
* Copyright (C) 2006 The ScummVM project
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*
*/
#include "agi/agi.h"
#include "agi/graphics.h"
#include "agi/keyboard.h"
#include "agi/text.h"
#include "common/func.h"
namespace Agi {
#define kModePre 0
#define kModeNum 1
#define kModeAbc 2
bool TextMan::predictiveDialog(void) {
int key, active = 0;
bool rc = false;
int x;
int y;
int bx[17], by[17];
String prefix = "";
char temp[MAXWORDLEN + 1];
const char *buttonStr[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" };
const char *buttons[] = {
"(1)'-.&", "(2)abc", "(3)def",
"(4)ghi", "(5)jkl", "(6)mno",
"(7)pqrs", "(8)tuv", "(9)wxyz",
"next", "add",
"<",
"Cancel", "OK",
"Pre", "(0) ", NULL
};
const int colors[] = {
15, 0, 15, 0, 15, 0,
15, 0, 15, 0, 15, 0,
15, 0, 15, 0, 15, 0,
15, 12, 15, 12,
15, 0,
15, 0, 15, 0,
14, 0, 15, 0, 0, 0
};
const char *modes[] = { "Pre", "123", "Abc" };
if (!_dict.size())
loadDict();
draw_window(50, 40, 269, 159);
draw_rectangle(62, 54, 249, 66, MSG_BOX_TEXT);
flush_block(62, 54, 249, 66);
print_character(3, 11, game.cursor_char, MSG_BOX_COLOUR, MSG_BOX_TEXT);
bx[15] = 73; // Zero/space
by[15] = 120;
bx[9] = 120; // next
by[9] = 120;
bx[10] = 160; // add
by[10] = 120;
bx[14] = 200; // Mode
by[14] = 120;
bx[11] = 252; // Backspace
by[11] = 57;
bx[12] = 180; // Cancel
by[12] = 140;
bx[13] = 240; // OK
by[13] = 140;
x = 73;
y = 75;
for (int i = 0; i < 9; i++) {
bx[i] = x;
by[i] = y;
x += 60;
if (i % 3 == 2) {
y += 15;
x = 73;
}
}
/* clear key queue */
while (keypress()) {
get_key();
}
_wordPosition = 0;
_currentCode = "";
_currentWord = "";
_matchedWord = "";
_wordNumber = 0;
_nextIsActive = _addIsActive = false;
int mode = kModePre;
bool needRefresh = true;
while (42) {
if (needRefresh) {
for (int i = 0; buttons[i]; i++) {
int color1 = colors[i * 2];
int color2 = colors[i * 2 + 1];
if (i == 9 && !_nextIsActive) { // Next
color2 = 7;
}
if (i == 10 && !_addIsActive) { // Add
color2 = 7;
}
if (i == 14) {
draw_button(bx[i], by[i], modes[mode], i == active, 0, color1, color2);
} else {
draw_button(bx[i], by[i], buttons[i], i == active, 0, color1, color2);
}
}
if (_currentWord != "") {
temp[MAXWORDLEN] = 0;
strncpy(temp, prefix.c_str(), MAXWORDLEN);
strncat(temp, _currentWord.c_str(), MAXWORDLEN);
for (int i = prefix.size() + _currentCode.size(); i < MAXWORDLEN; i++)
temp[i] = ' ';
print_text(temp, 0, 8, 7, MAXWORDLEN, 15, 0);
flush_block(62, 54, 249, 66);
}
}
poll_timer(); /* msdos driver -> does nothing */
key = do_poll_keyboard();
switch (key) {
case KEY_ENTER:
rc = true;
goto press;
case KEY_ESCAPE:
rc = false;
goto getout;
case BUTTON_LEFT:
for (int i = 0; buttons[i]; i++) {
if (test_button(bx[i], by[i], buttons[i])) {
needRefresh = true;
active = i;
if (active == 15 && mode != kModeNum) { // Space
strncpy(temp, _currentWord.c_str(), _currentCode.size());
temp[_currentCode.size()] = 0;
prefix += temp;
prefix += " ";
_wordPosition = 0;
_currentCode = "";
} if (active < 9 || active == 11 || active == 15) { // number or backspace
if (active == 11) { // backspace
if (_currentCode.size()) {
_currentCode.deleteLastChar();
_wordPosition--;
} else {
if (prefix.size())
prefix.deleteLastChar();
}
} else if (active == 15) { // zero
_currentCode += buttonStr[9];
_wordPosition++;
} else {
_currentCode += buttonStr[active];
_wordPosition++;
}
if (mode == kModeNum) {
_currentWord = _currentCode;
} else if (mode == kModePre) {
if (!matchWord() && _currentCode.size()) {
_currentCode.deleteLastChar();
_wordPosition--;
matchWord();
}
}
} else if (active == 9) { // next
if (_nextIsActive) {
int wordsNumber = (_matchedWord.size() + 1) / _currentCode.size();
int start;
_wordNumber = (_wordNumber + 1) % wordsNumber;
start = _wordNumber * (_currentCode.size() + 1);
strncpy(temp, _matchedWord.c_str() + start, _currentCode.size());
temp[_matchedWord.size() + 1] = 0;
_currentWord = temp;
}
} else if (active == 10) { // add
debug(0, "add");
} else if (active == 13) { // Ok
rc = true;
goto press;
} else if (active == 14) { // Mode
mode++;
if (mode > kModeAbc)
mode = kModePre;
} else {
goto press;
}
}
}
break;
case 0x09: /* Tab */
debugC(3, kDebugLevelText, "Focus change");
active++;
active %= ARRAYSIZE(buttons) - 1;
needRefresh = true;
break;
}
do_update();
}
press:
strncpy(_predictiveResult, prefix.c_str(), 40);
strncat(_predictiveResult, _currentWord.c_str(), 40);
_predictiveResult[prefix.size() + _currentCode.size() + 1] = 0;
getout:
close_window();
return rc;
}
static char *ltrim(char *t) {
while (isspace(*t))
t++;
return t;
}
static char *rtrim(char *t) {
int l = strlen(t) - 1;
while (l >= 0 && isspace(t[l]))
t[l--] = 0;
return t;
}
#define MAXLINELEN 80
void TextMan::loadDict(void) {
Common::File in;
char buf[MAXLINELEN];
in.open("pred.txt");
while (!in.eos()) {
if (!in.readLine(buf, MAXLINELEN))
break;
// Skip leading & trailing whitespaces
char *t = rtrim(ltrim(buf));
char *k = t;
int len = 0;
char key[30];
// Skip empty lines
if (*t == 0)
continue;
while (!isspace(*t)) {
len++;
t++;
}
while (isspace(*t))
t++;
strncpy(key, k, len);
key[len] = 0;
_dict[String(key)] = String(t);
_dictKeys.push_back(String(key));
}
Common::sort(_dictKeys.begin(), _dictKeys.end());
debug(0, "Loaded %d keys", _dict.size());
}
bool TextMan::matchWord(void) {
_addIsActive = false;
if (!_currentCode.size()) {
return false;
}
if (_dict.contains(_currentCode)) {
_currentWord = _matchedWord = _dict[_currentCode];
_nextIsActive = ((_matchedWord.size() + 1) / _currentCode.size() > 1);
return true;
}
// Else search first partial match
for (uint i = 0; i < _dictKeys.size(); i++) {
bool matched = true;
if (_dictKeys[i].size() < _wordPosition)
continue;
for (uint j = 0; j < _dictKeys[i].size() && j < _wordPosition; j++) {
if (_currentCode[j] != _dictKeys[i][j]) {
matched = false;
break;
}
}
if (matched && _dictKeys[i].size() >= _wordPosition) {
_currentWord = _matchedWord = _dict[_dictKeys[i]];
_nextIsActive = ((_matchedWord.size() + 1) / _currentCode.size() > 1);
return true;
}
}
_currentWord = _matchedWord = "";
_nextIsActive = false;
_addIsActive = true;
return false;
}
} // End of namespace Agi