commit d9d31b5f3ca45d67dfedb1c3f8c374d014b84c74
Author: Willem Jan Palenstijn <wjp@usecode.org>
Date:   Sun Oct 3 16:50:16 2010 +0200

    SCI: Support alternative inputs from vocab 913

diff --git a/engines/sci/graphics/controls.cpp b/engines/sci/graphics/controls.cpp
index 70ff70d..e4e7bc0 100644
--- a/engines/sci/graphics/controls.cpp
+++ b/engines/sci/graphics/controls.cpp
@@ -159,6 +159,8 @@ void GfxControls::kernelTexteditChange(reg_t controlObject, reg_t eventObject) {
 		error("kEditControl called on object that doesnt have a text reference");
 	text = _segMan->getString(textReference);
 
+	uint16 oldCursorPos = cursorPos;
+
 	if (!eventObject.isNull()) {
 		textSize = text.size();
 		eventType = readSelectorValue(_segMan, eventObject, SELECTOR(type));
@@ -223,6 +225,11 @@ void GfxControls::kernelTexteditChange(reg_t controlObject, reg_t eventObject) {
 		}
 	}
 
+	if (!textChanged && oldCursorPos != cursorPos) {
+		assert(!textAddChar);
+		textChanged = g_sci->getVocabulary()->checkAltInput(text, cursorPos);
+	}
+
 	if (textChanged) {
 		GuiResourceId oldFontId = _text16->GetFontId();
 		GuiResourceId fontId = readSelectorValue(_segMan, controlObject, SELECTOR(font));
@@ -230,18 +237,27 @@ void GfxControls::kernelTexteditChange(reg_t controlObject, reg_t eventObject) {
 							  readSelectorValue(_segMan, controlObject, SELECTOR(nsRight)), readSelectorValue(_segMan, controlObject, SELECTOR(nsBottom)));
 		_text16->SetFont(fontId);
 		if (textAddChar) {
-			// We check, if we are really able to add the new char
-			uint16 textWidth = 0;
+
 			const char *textPtr = text.c_str();
+
+			// We check if we are really able to add the new char
+			uint16 textWidth = 0;
 			while (*textPtr)
 				textWidth += _text16->_font->getCharWidth((byte)*textPtr++);
 			textWidth += _text16->_font->getCharWidth(eventKey);
+
+			// Does it fit?
 			if (textWidth >= rect.width()) {
 				_text16->SetFont(oldFontId);
 				return;
 			}
+
 			text.insertChar(eventKey, cursorPos++);
+
+			// Note: the following checkAltInput call might make the text
+			// too wide to fit, but SSCI fails to check that too.
 		}
+		g_sci->getVocabulary()->checkAltInput(text, cursorPos);
 		texteditCursorErase();
 		_paint16->eraseRect(rect);
 		_text16->Box(text.c_str(), 0, rect, SCI_TEXT16_ALIGNMENT_LEFT, -1);
diff --git a/engines/sci/parser/vocabulary.cpp b/engines/sci/parser/vocabulary.cpp
index a6de611..f9989b2 100644
--- a/engines/sci/parser/vocabulary.cpp
+++ b/engines/sci/parser/vocabulary.cpp
@@ -73,6 +73,8 @@ Vocabulary::Vocabulary(ResourceManager *resMan, bool foreign) : _resMan(resMan),
 		_parserRules = NULL;
 	}
 
+	loadAltInputs();
+
 	parser_base = NULL_REG;
 	parser_event = NULL_REG;
 	parserIsValid = false;
@@ -81,6 +83,7 @@ Vocabulary::Vocabulary(ResourceManager *resMan, bool foreign) : _resMan(resMan),
 Vocabulary::~Vocabulary() {
 	freeRuleList(_parserRules);
 	freeSuffixes();
+	freeAltInputs();
 }
 
 void Vocabulary::reset() {
@@ -272,6 +275,104 @@ bool Vocabulary::loadBranches() {
 	return true;
 }
 
+bool Vocabulary::loadAltInputs() {
+	Resource *resource = _resMan->findResource(ResourceId(kResourceTypeVocab, VOCAB_RESOURCE_ALT_INPUTS), 1);
+
+	if (!resource)
+		return true; // it's not a problem if this resource doesn't exist
+
+	const char *data = (const char*)resource->data;
+	const char *data_end = data + resource->size;
+
+	_altInputs.clear();
+	_altInputs.resize(256);
+
+	while (data < data_end && *data) {
+		AltInput t;
+		t._input = data;
+
+		unsigned int l = strlen(data);
+		t._inputLength = l;
+		data += l + 1;
+
+		t._replacement = data;
+		l = strlen(data);
+		data += l + 1;
+
+		if (data < data_end && strncmp(data, t._input, t._inputLength) == 0)
+			t._prefix = true;
+		else
+			t._prefix = false;
+
+		unsigned char firstChar = t._input[0];
+		_altInputs[firstChar].push_front(t);
+	}
+
+	return true;
+}
+
+void Vocabulary::freeAltInputs() {
+	Resource *resource = _resMan->findResource(ResourceId(kResourceTypeVocab, VOCAB_RESOURCE_ALT_INPUTS), 0);
+	if (resource)
+		_resMan->unlockResource(resource);
+
+	_altInputs.clear();
+}
+
+bool Vocabulary::checkAltInput(Common::String& text, uint16& cursorPos) {
+	if (_altInputs.empty())
+		return false;
+	if (SELECTOR(parseLang) == -1)
+		return false;
+	if (readSelectorValue(g_sci->getEngineState()->_segMan, g_sci->getGameObject(), SELECTOR(parseLang)) == 1)
+		return false;
+
+	bool ret = false;
+	unsigned int loopCount = 0;
+	bool changed;
+	do {
+		changed = false;
+
+		const char* t = text.c_str();
+		unsigned int tlen = text.size();
+
+		for (unsigned int p = 0; p < tlen && !changed; ++p) {
+			unsigned char s = t[p];
+			if (s >= _altInputs.size() || _altInputs[s].empty())
+				continue;
+			Common::List<AltInput>::iterator i;
+			for (i = _altInputs[s].begin(); i != _altInputs[s].end(); ++i) {
+				if (p + i->_inputLength > tlen)
+					continue;
+				if (i->_prefix && cursorPos > p && cursorPos <= p + i->_inputLength)
+					continue;
+				if (strncmp(i->_input, t+p, i->_inputLength) == 0) {
+					// replace
+					if (cursorPos > p + i->_inputLength) {
+						cursorPos += strlen(i->_replacement) - i->_inputLength;
+					} else if (cursorPos > p) {
+						cursorPos = p + strlen(i->_replacement);
+					}
+
+					for (unsigned int j = 0; j < i->_inputLength; ++j)
+						text.deleteChar(p);
+					const char *r = i->_replacement;
+					while (*r)
+						text.insertChar(*r++, p++);
+
+					assert(cursorPos <= text.size());
+
+					changed = true;
+					ret = true;
+					break;
+				}
+			}
+		}
+	} while (changed && loopCount < 10);
+
+	return ret;
+}
+
 // we assume that *word points to an already lowercased word
 void Vocabulary::lookupWord(ResultWordList& retval, const char *word, int word_len) {
 	retval.clear();
diff --git a/engines/sci/parser/vocabulary.h b/engines/sci/parser/vocabulary.h
index 1b37219..620d50c 100644
--- a/engines/sci/parser/vocabulary.h
+++ b/engines/sci/parser/vocabulary.h
@@ -49,7 +49,9 @@ enum {
 
 	VOCAB_RESOURCE_SCI1_MAIN_VOCAB = 900,
 	VOCAB_RESOURCE_SCI1_PARSE_TREE_BRANCHES = 901,
-	VOCAB_RESOURCE_SCI1_SUFFIX_VOCAB = 902
+	VOCAB_RESOURCE_SCI1_SUFFIX_VOCAB = 902,
+
+	VOCAB_RESOURCE_ALT_INPUTS = 913
 };
 
 
@@ -147,6 +149,15 @@ struct synonym_t {
 
 typedef Common::List<synonym_t> SynonymList;
 
+
+struct AltInput {
+	const char *_input;
+	const char *_replacement;
+	unsigned int _inputLength;
+	bool _prefix;
+};
+
+
 struct parse_tree_branch_t {
 	int id;
 	int data[10];
@@ -273,6 +284,14 @@ public:
 
 	int parseNodes(int *i, int *pos, int type, int nr, int argc, const char **argv);
 
+	/**
+	 * Check text input against alternative inputs.
+	 * @param text The text to process. It will be modified in-place
+	 * @param cursorPos The cursor position
+	 * @return true if anything changed
+	 */
+	bool checkAltInput(Common::String& text, uint16& cursorPos);
+
 private:
 	/**
 	 * Loads all words from the main vocabulary.
@@ -305,6 +324,20 @@ private:
 	 */
 	void freeRuleList(ParseRuleList *rule_list);
 
+
+	/**
+	 * Retrieves all alternative input combinations from vocab 913.
+	 * @return true on success, false on error
+	 */
+	bool loadAltInputs();
+
+	/**
+	 * Frees all alternative input combinations.
+	 */
+	void freeAltInputs();
+
+
+
 	ResourceManager *_resMan;
 	VocabularyVersions _vocabVersion;
 
@@ -319,6 +352,7 @@ private:
 	Common::Array<parse_tree_branch_t> _parserBranches;
 	WordMap _parserWords;
 	SynonymList _synonyms; /**< The list of synonyms */
+	Common::Array<Common::List<AltInput> > _altInputs;
 
 public:
 	// Accessed by said()
