aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engines/agi/graphics.cpp4
-rw-r--r--engines/agi/graphics.h2
-rw-r--r--engines/agi/keyboard.cpp22
-rw-r--r--engines/agi/module.mk1
-rw-r--r--engines/agi/predictive.cpp350
-rw-r--r--engines/agi/text.h30
-rwxr-xr-xtools/construct-pred-dict.pl63
-rwxr-xr-xtools/extract-words-tok.pl73
8 files changed, 542 insertions, 3 deletions
diff --git a/engines/agi/graphics.cpp b/engines/agi/graphics.cpp
index 3b9c754d97..8499e29ea1 100644
--- a/engines/agi/graphics.cpp
+++ b/engines/agi/graphics.cpp
@@ -282,7 +282,7 @@ void print_character(int x, int y, char c, int fg, int bg) {
* @param a set if the button has focus
* @param p set if the button is pressed
*/
-void draw_button(int x, int y, const char *s, int a, int p) {
+void draw_button(int x, int y, const char *s, int a, int p, int fgcolor, int bgcolor) {
int len = strlen(s);
int x1, y1, x2, y2;
@@ -292,7 +292,7 @@ void draw_button(int x, int y, const char *s, int a, int p) {
y2 = y + CHAR_LINES + 2;
while (*s) {
- put_text_character(0, x + (!!p), y + (!!p), *s++, a ? 15 : 0, a ? 0 : 15);
+ put_text_character(0, x + (!!p), y + (!!p), *s++, a ? fgcolor : bgcolor, a ? bgcolor : fgcolor);
x += CHAR_COLS;
}
diff --git a/engines/agi/graphics.h b/engines/agi/graphics.h
index 85be6eb3c3..2f45dcb289 100644
--- a/engines/agi/graphics.h
+++ b/engines/agi/graphics.h
@@ -63,7 +63,7 @@ void flush_screen(void);
void clear_screen(int);
void clear_console_screen(int);
void draw_box(int, int, int, int, int, int, int);
-void draw_button(int, int, const char *, int, int);
+void draw_button(int, int, const char *, int, int, int fgcolor = 0, int bgcolor = 0);
int test_button(int, int, const char *);
void draw_rectangle(int, int, int, int, int);
void save_block(int, int, int, int, uint8 *);
diff --git a/engines/agi/keyboard.cpp b/engines/agi/keyboard.cpp
index a35fe8a547..9c401e067c 100644
--- a/engines/agi/keyboard.cpp
+++ b/engines/agi/keyboard.cpp
@@ -170,6 +170,16 @@ int handle_controller(int key) {
break;
}
}
+
+ if (mouse.y >= game.line_user_input * CHAR_LINES &&
+ mouse.y <= (game.line_user_input + 1) * CHAR_LINES) {
+ if (_text->predictiveDialog()) {
+ strcpy((char *)game.input_buffer, _text->_predictiveResult);
+ handle_keys(KEY_ENTER);
+ }
+ return true;
+ }
+
if (!opt.agimouse) {
/* Handle mouse button events */
if (key == BUTTON_LEFT) {
@@ -201,6 +211,18 @@ void handle_getstring(int key) {
debugC(3, kDebugLevelInput, "handling key: %02x", key);
switch (key) {
+ case BUTTON_LEFT:
+ if (mouse.y >= stringdata.y * CHAR_LINES &&
+ mouse.y <= (stringdata.y + 1) * CHAR_LINES) {
+ if (_text->predictiveDialog()) {
+ strcpy(game.strings[stringdata.str], _text->_predictiveResult);
+ new_input_mode(INPUT_NORMAL);
+ print_character(stringdata.x + strlen(game.strings[stringdata.str]) + 1,
+ stringdata.y, ' ', game.color_fg, game.color_bg);
+ return;
+ }
+ }
+ break;
case KEY_ENTER:
debugC(3, kDebugLevelInput, "KEY_ENTER");
game.has_prompt = 0;
diff --git a/engines/agi/module.mk b/engines/agi/module.mk
index b155a12c9b..f56b910fc4 100644
--- a/engines/agi/module.mk
+++ b/engines/agi/module.mk
@@ -23,6 +23,7 @@ MODULE_OBJS = \
op_test.o \
patches.o \
picture.o \
+ predictive.o \
savegame.o \
sound.o \
sprite.o \
diff --git a/engines/agi/predictive.cpp b/engines/agi/predictive.cpp
new file mode 100644
index 0000000000..2de626d1f4
--- /dev/null
+++ b/engines/agi/predictive.cpp
@@ -0,0 +1,350 @@
+/* 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
diff --git a/engines/agi/text.h b/engines/agi/text.h
index 28324fc005..ede10dbc44 100644
--- a/engines/agi/text.h
+++ b/engines/agi/text.h
@@ -26,9 +26,14 @@
#define AGI_TEXT_H
#include "agi/agi.h"
+#include "common/hash-str.h"
namespace Agi {
+#define MAXWORDLEN 24
+
+typedef Common::String String;
+
class TextMan {
public:
int message_box(const char *);
@@ -44,6 +49,7 @@ public:
void write_prompt(void);
void clear_lines(int, int, int);
void flush_lines(int, int);
+ bool predictiveDialog(void);
private:
void print_status(const char *message, ...);
@@ -51,6 +57,30 @@ private:
void blit_textbox(const char *p, int y, int x, int len);
void erase_textbox();
char *safe_strcat(char *s, const char *t);
+
+ void loadDict(void);
+
+ bool matchWord(void);
+
+private:
+ typedef Common::HashMap<String, String, Common::CaseSensitiveString_Hash, Common::CaseSensitiveString_EqualTo> DictMap;
+
+ DictMap _dict;
+ Common::StringList _dictKeys;
+
+ String _currentCode;
+ String _currentWord;
+ String _matchedWord;
+
+ int _wordNumber;
+
+ int _wordPosition;
+
+ bool _nextIsActive;
+ bool _addIsActive;
+
+public:
+ char _predictiveResult[40];
};
extern TextMan *_text;
diff --git a/tools/construct-pred-dict.pl b/tools/construct-pred-dict.pl
new file mode 100755
index 0000000000..780d8843ab
--- /dev/null
+++ b/tools/construct-pred-dict.pl
@@ -0,0 +1,63 @@
+#!perl
+#
+# 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$
+#
+
+# This script constructs dictionary for use with predictive input
+#
+# Feed it with list of words. One word per line
+
+%words = ();
+
+while(<>) {
+ chomp;
+
+ s/[\x7f-\xff]//g; # remove unprintable characters
+
+ next if /^.$/; # skip one character words
+ next if $_ eq ""; # skip empty words
+
+ $words{$_} = 1;
+}
+
+%list = ();
+
+for $_ (sort keys %words) {
+ $word = $_;
+
+ s/['-.&_!]/1/g;
+ s/[abc]/2/g;
+ s/[def]/3/g;
+ s/[ghi]/4/g;
+ s/[jkl]/5/g;
+ s/[mno]/6/g;
+ s/[pqrs]/7/g;
+ s/[tuv]/8/g;
+ s/[wxyz]/9/g;
+
+ $list{$_} .= "$word ";
+}
+
+for $k (sort keys %list) {
+ chop $list{$k};
+
+ print "$k $list{$k}\n";
+}
diff --git a/tools/extract-words-tok.pl b/tools/extract-words-tok.pl
new file mode 100755
index 0000000000..53c7ae09a9
--- /dev/null
+++ b/tools/extract-words-tok.pl
@@ -0,0 +1,73 @@
+#!perl
+#
+# 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$
+#
+
+# This script extracts AGI words.tok file
+#
+# It produces one word per line. Multiword verbs get splitted
+#
+# Typical usage:
+#
+# for i in agigames/*/words.tok
+# do
+# tools/extract-words-tok.pl "$i"
+# done | tools/construct-pred-dict.pl
+#
+
+local $/;
+local $file = <>;
+
+#$off = ord(substr($file, $i * 2, 1)) * 256 + ord(substr($file, $i * 2 + 1, 1));
+#$offn = ord(substr($file, ($i + 1) * 2, 1)) * 256 + ord(substr($file, ($i + 1) * 2 + 1, 1));
+
+$off = 52;
+
+$word = "";
+$mode = 0;
+
+while ($off < length $file) {
+ $c = (ord(substr($file, $off, 1)));
+ if ($mode == 0) {
+ $word = substr $word, 0, $c;
+ $mode = 1;
+ $off++;
+ next;
+ }
+
+ $r = ($c & 0x7f) ^ 0x7f;
+ $word .= chr($r);
+
+ if ($c & 0x80) {
+ for $w (split ' ', $word) {
+ print "$w\n";
+ }
+ $off += 3;
+ $mode = 0;
+ next;
+ }
+
+ $off++;
+}
+
+for $w (split ' ', $word) {
+ print "$w\n";
+}