1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "ultima/shared/std/string.h"
24 #include "ultima/nuvie/core/nuvie_defs.h"
25 #include "ultima/nuvie/conf/configuration.h"
26 #include "ultima/nuvie/misc/u6_misc.h"
27 #include "ultima/nuvie/fonts/font_manager.h"
28 #include "ultima/nuvie/fonts/font.h"
29 #include "ultima/nuvie/screen/game_palette.h"
30 #include "ultima/nuvie/gui/gui.h"
31 #include "ultima/nuvie/gui/widgets/msg_scroll.h"
32 #include "ultima/nuvie/core/events.h"
33 #include "ultima/nuvie/core/game.h"
34 #include "ultima/nuvie/core/effect.h"
35 #include "ultima/nuvie/keybinding/keys.h"
36 
37 namespace Ultima {
38 namespace Nuvie {
39 
40 // MsgText Class
MsgText()41 MsgText::MsgText() {
42 	font = NULL;
43 	color = 0;
44 }
45 
MsgText(Std::string new_string,Font * f)46 MsgText::MsgText(Std::string new_string, Font *f) {
47 	s.assign(new_string);
48 	font = f;
49 	color = 0;
50 	if (font) {
51 		color = font->getDefaultColor();
52 	}
53 }
54 
~MsgText()55 MsgText::~MsgText() {
56 }
57 
append(Std::string new_string)58 void MsgText::append(Std::string new_string) {
59 	s.append(new_string);
60 }
61 
copy(MsgText * msg_text)62 void MsgText::copy(MsgText *msg_text) {
63 	s.assign(msg_text->s);
64 	font = msg_text->font;
65 	color = msg_text->color;
66 }
67 
length()68 uint32 MsgText::length() {
69 	return (uint32)s.length();
70 }
71 
getDisplayWidth()72 uint16 MsgText::getDisplayWidth() {
73 	return font->getStringWidth(s.c_str());
74 }
75 
~MsgLine()76 MsgLine::~MsgLine() {
77 	Std::list<MsgText *>::iterator iter;
78 
79 	for (iter = text.begin(); iter != text.end(); iter++) {
80 		delete *iter;
81 	}
82 }
83 
append(MsgText * new_text)84 void MsgLine::append(MsgText *new_text) {
85 	MsgText *msg_text = NULL;
86 
87 	if (text.size() > 0)
88 		msg_text = text.back();
89 
90 	if (msg_text && msg_text->font == new_text->font && msg_text->color == new_text->color && new_text->s.length() == 1 && new_text->s[0] != ' ')
91 		msg_text->s.append(new_text->s);
92 	else {
93 		msg_text = new MsgText();
94 		msg_text->copy(new_text);
95 		text.push_back(msg_text);
96 	}
97 
98 	total_length += new_text->s.length();
99 
100 	return;
101 }
102 
103 // remove the last char in the line.
remove_char()104 void MsgLine::remove_char() {
105 	MsgText *msg_text;
106 
107 	if (total_length == 0)
108 		return;
109 
110 	msg_text = text.back();
111 	msg_text->s.erase(msg_text->s.length() - 1, 1);
112 
113 	if (msg_text->s.length() == 0) {
114 		text.pop_back();
115 		delete msg_text;
116 	}
117 
118 	total_length--;
119 
120 	return;
121 }
122 
length()123 uint32 MsgLine::length() {
124 	return total_length;
125 }
126 
127 // gets the MsgText object that contains the text at line position pos
get_text_at_pos(uint16 pos)128 MsgText *MsgLine::get_text_at_pos(uint16 pos) {
129 	uint16 i;
130 	Std::list<MsgText *>::iterator iter;
131 
132 	if (pos > total_length)
133 		return NULL;
134 
135 	for (i = 0, iter = text.begin(); iter != text.end(); iter++) {
136 		if (i + (*iter)->s.length() >= pos)
137 			return (*iter);
138 
139 		i += (*iter)->s.length();
140 	}
141 
142 	return NULL;
143 }
144 
get_display_width()145 uint16 MsgLine::get_display_width() {
146 	uint16 len = 0;
147 	Std::list<MsgText *>::iterator iter;
148 	for (iter = text.begin(); iter != text.end() ; iter++) {
149 		MsgText *token = *iter;
150 
151 		len += token->font->getStringWidth(token->s.c_str());
152 	}
153 	return len;
154 }
155 
156 // MsgScroll Class
157 
init(Configuration * cfg,Font * f)158 void MsgScroll::init(Configuration *cfg, Font *f) {
159 	font = f;
160 
161 	config = cfg;
162 	config->value("config/GameType", game_type);
163 
164 	input_char = 0;
165 	yes_no_only = false;
166 	aye_nay_only = false;
167 	numbers_only = false;
168 	scroll_updated = false;
169 	discard_whitespace = true;
170 	page_break = false;
171 	show_cursor = true;
172 	talking = false;
173 	autobreak = false;
174 	just_finished_page_break = false;
175 	using_target_cursor = false;
176 
177 	callback_target = NULL;
178 	callback_user_data = NULL;
179 
180 	scrollback_height = MSGSCROLL_SCROLLBACK_HEIGHT;
181 	capitalise_next_letter = false;
182 
183 	font_color = FONT_COLOR_U6_NORMAL;
184 	font_highlight_color = FONT_COLOR_U6_HIGHLIGHT;
185 	if (game_type != NUVIE_GAME_U6) {
186 		font_color = FONT_COLOR_WOU_NORMAL;
187 		font_highlight_color = FONT_COLOR_WOU_HIGHLIGHT;
188 	}
189 }
190 
MsgScroll(Configuration * cfg,Font * f)191 MsgScroll::MsgScroll(Configuration *cfg, Font *f) : GUI_Widget(NULL, 0, 0, 0, 0) {
192 	uint16 x, y;
193 
194 	init(cfg, f);
195 
196 	switch (game_type) {
197 	case NUVIE_GAME_MD :
198 		scroll_width = MSGSCROLL_MD_WIDTH;
199 		scroll_height = MSGSCROLL_MD_HEIGHT;
200 		x = 184;
201 		y = 128;
202 		break;
203 
204 	case NUVIE_GAME_SE :
205 		scroll_width = MSGSCROLL_SE_WIDTH;
206 		scroll_height = MSGSCROLL_SE_HEIGHT;
207 		x = 184;
208 		y = 128;
209 		break;
210 	case NUVIE_GAME_U6 :
211 	default :
212 		scroll_width = MSGSCROLL_U6_WIDTH;
213 		scroll_height = MSGSCROLL_U6_HEIGHT;
214 		x = 176;
215 		y = 112;
216 		break;
217 	}
218 
219 	if (Game::get_game()->is_original_plus())
220 		x += Game::get_game()->get_game_width() - 320;
221 
222 	uint16 x_off = Game::get_game()->get_game_x_offset();
223 	uint16 y_off = Game::get_game()->get_game_y_offset();
224 
225 	GUI_Widget::Init(NULL, x + x_off, y + y_off, scroll_width * 8, scroll_height * 8);
226 
227 	cursor_char = 0;
228 	cursor_x = 0;
229 	cursor_y = scroll_height - 1;
230 	line_count = 0;
231 
232 	cursor_wait = 0;
233 
234 	display_pos = 0;
235 
236 	bg_color = Game::get_game()->get_palette()->get_bg_color();
237 
238 	capitalise_next_letter = false;
239 
240 	left_margin = 0;
241 
242 	add_new_line();
243 }
244 
~MsgScroll()245 MsgScroll::~MsgScroll() {
246 	Std::list<MsgLine *>::iterator msg_line;
247 	Std::list<MsgText *>::iterator msg_text;
248 
249 // delete the scroll buffer
250 	for (msg_line = msg_buf.begin(); msg_line != msg_buf.end(); msg_line++)
251 		delete *msg_line;
252 
253 // delete the holding buffer
254 	for (msg_text = holding_buffer.begin(); msg_text != holding_buffer.end(); msg_text++)
255 		delete *msg_text;
256 
257 }
258 
init(const char * player_name)259 bool MsgScroll::init(const char *player_name) {
260 	Std::string prompt_string;
261 
262 	prompt_string.append(player_name);
263 	if (game_type == NUVIE_GAME_U6) {
264 		prompt_string.append(":\n");
265 	}
266 
267 	prompt_string.append(">");
268 
269 	if (set_prompt(prompt_string.c_str(), font) == false)
270 		return false;
271 
272 	set_input_mode(false);
273 
274 	return true;
275 }
276 
set_scroll_dimensions(uint16 w,uint16 h)277 void MsgScroll::set_scroll_dimensions(uint16 w, uint16 h) {
278 	scroll_width = w;
279 	scroll_height = h;
280 
281 	cursor_char = 0;
282 	cursor_x = 0;
283 	cursor_y = scroll_height - 1;
284 	line_count = 0;
285 
286 	cursor_wait = 0;
287 
288 	display_pos = 0;
289 }
290 
print(Std::string format,...)291 int MsgScroll::print(Std::string format, ...) {
292 
293 	va_list ap;
294 	int printed = 0;
295 	static size_t bufsize = 128; // will be resized if needed
296 	static char *buffer = (char *) malloc(bufsize); // static so we don't have to reallocate all the time.
297 
298 	while (1) {
299 		if (buffer == NULL) {
300 			DEBUG(0, LEVEL_ALERT, "MsgScroll::printf: Couldn't allocate %d bytes for buffer\n", bufsize);
301 			/* try to shrink the buffer to at least have a change next time,
302 			 * but if we're low on memory probably have worse issues...
303 			 */
304 			bufsize >>= 1;
305 			buffer = (char *) malloc(bufsize); // no need to check now.
306 			/*
307 			 * We don't retry, or if we need e.g. 4 bytes for the format and
308 			 * there's only 3 available, we'd grow and shrink forever.
309 			 */
310 			return printed;
311 		}
312 
313 		/* try formatting */
314 		va_start(ap, format);
315 		printed = vsnprintf(buffer, bufsize, format.c_str(), ap);
316 		va_end(ap);
317 
318 		if (printed < 0) {
319 			DEBUG(0, LEVEL_ERROR, "MsgScroll::printf: vsnprintf returned < 0: either output error or glibc < 2.1\n");
320 			free(buffer);
321 			bufsize *= 2; // In case of an output error, we'll just keep doubling until the malloc fails.
322 			buffer = (char *) malloc(bufsize); // if this fails, will be caught later on
323 			/* try again */
324 			continue;
325 		} else if ((size_t)printed >= bufsize) {
326 			DEBUG(0, LEVEL_DEBUGGING, "MsgScroll::printf: needed buffer of %d bytes, only had %d bytes.\n", printed + 1, bufsize);
327 			bufsize = printed + 1; //this should be enough
328 			free(buffer);
329 			buffer = (char *) malloc(bufsize); // if this fails, will be caught later on
330 			/* try again */
331 			continue;
332 		}
333 		/* if we're here, formatting probably worked. We can stop looping */
334 		break;
335 	}
336 	/* use the string */
337 	display_string(buffer);
338 
339 	return printed;
340 }
341 
display_fmt_string(const char * format,...)342 void MsgScroll::display_fmt_string(const char *format, ...) {
343 	char buf[1024];
344 	memset(buf, 0, 1024);
345 	va_list args;
346 	va_start(args, format);
347 	vsnprintf(buf, 1024, format, args);
348 	va_end(args);
349 
350 	display_string(buf);
351 }
352 
display_string(Std::string s,uint16 length,uint8 lang_num)353 void MsgScroll::display_string(Std::string s, uint16 length, uint8 lang_num) {
354 
355 }
356 
display_string(Std::string s,bool include_on_map_window)357 void MsgScroll::display_string(Std::string s, bool include_on_map_window) {
358 	display_string(s, font, include_on_map_window);
359 }
360 
display_string(Std::string s,uint8 color,bool include_on_map_window)361 void MsgScroll::display_string(Std::string s, uint8 color, bool include_on_map_window) {
362 	display_string(s, font, color, include_on_map_window);
363 }
364 
display_string(Std::string s,Font * f,bool include_on_map_window)365 void MsgScroll::display_string(Std::string s, Font *f, bool include_on_map_window) {
366 	display_string(s, f, font_color, include_on_map_window);
367 }
368 
display_string(Std::string s,Font * f,uint8 color,bool include_on_map_window)369 void MsgScroll::display_string(Std::string s, Font *f, uint8 color, bool include_on_map_window) {
370 	MsgText *msg_text;
371 
372 	if (s.empty())
373 		return;
374 
375 	if (f == NULL)
376 		f = font;
377 
378 	msg_text = new MsgText(s, f);
379 	msg_text->color = color;
380 	//::debug(1, "%s", msg_text->s.c_str());
381 
382 	holding_buffer.push_back(msg_text);
383 
384 	process_holding_buffer();
385 
386 }
387 
388 // process text tokens till we either run out or hit a page break.
389 
process_holding_buffer()390 void MsgScroll::process_holding_buffer() {
391 	MsgText *token;
392 
393 	if (!page_break) {
394 		token = holding_buffer_get_token();
395 
396 		for (; token != NULL && !page_break;) {
397 			parse_token(token);
398 			delete token;
399 			scroll_updated = true;
400 
401 			if (!page_break)
402 				token = holding_buffer_get_token();
403 		}
404 	}
405 }
406 
holding_buffer_get_token()407 MsgText *MsgScroll::holding_buffer_get_token() {
408 	MsgText *input, *token;
409 	int i;
410 
411 	if (holding_buffer.empty())
412 		return NULL;
413 
414 	input = holding_buffer.front();
415 
416 	if (input->font == NULL) {
417 		line_count = 0;
418 		holding_buffer.pop_front();
419 		delete input;
420 		return NULL;
421 	}
422 
423 	i = input->s.findFirstOf(" \t\n*<>`", 0);
424 	if (i == 0) i++;
425 
426 	if (i == -1)
427 		i = input->s.length();
428 
429 	if (i > 0) {
430 		token = new MsgText(input->s.substr(0, i), font); // FIX maybe a format flag. // input->font);
431 		token->color = input->color;
432 		input->s.erase(0, i);
433 		if (input->s.length() == 0) {
434 			holding_buffer.pop_front();
435 			delete input;
436 		}
437 		return token;
438 	}
439 
440 	return NULL;
441 }
442 
can_fit_token_on_msgline(MsgLine * msg_line,MsgText * token)443 bool MsgScroll::can_fit_token_on_msgline(MsgLine *msg_line, MsgText *token) {
444 	if (msg_line->total_length + token->length() > scroll_width) {
445 		return false; //token doesn't fit on the current line.
446 	}
447 
448 	return true;
449 }
450 
parse_token(MsgText * token)451 bool MsgScroll::parse_token(MsgText *token) {
452 	MsgLine *msg_line = NULL;
453 
454 	if (!msg_buf.empty())
455 		msg_line = msg_buf.back(); // retrieve the last line from the scroll buffer.
456 
457 	switch (token->s[0]) {
458 	case '\n' :
459 		add_new_line();
460 		break;
461 
462 	case '\t' :
463 		set_autobreak(false);
464 		break;
465 
466 	case '`'  :
467 		capitalise_next_letter = true;
468 		break;
469 
470 	case '<'  :
471 		set_font(NUVIE_FONT_GARG); // runic / gargoyle font
472 		break;
473 
474 	case '>'  :
475 		if (is_garg_font()) {
476 			set_font(NUVIE_FONT_NORMAL); // english font
477 			break;
478 		}
479 		// falls through
480 		// ...if we haven't already seen a '<' char
481 
482 	default   :
483 		if (msg_line) {
484 			if (can_fit_token_on_msgline(msg_line, token) == false) { // the token is to big for the current line
485 				msg_line = add_new_line();
486 			}
487 			// This adds extra newlines. (SB-X)
488 			//                 if(msg_line->total_length + token->length() == scroll_width) //we add a new line but write to the old line.
489 			//                    add_new_line();
490 
491 			if (msg_line->total_length == 0 && token->s[0] == ' ' && discard_whitespace) // discard whitespace at the start of a line.
492 				return true;
493 		}
494 		if (token->s[0] == '*') {
495 			if (just_finished_page_break == false) //we don't want to add another break if we've only just completed an autobreak
496 				set_page_break();
497 		} else {
498 			if (capitalise_next_letter) {
499 				token->s[0] = toupper(token->s[0]);
500 				capitalise_next_letter = false;
501 			}
502 
503 			if (msg_line == NULL) {
504 				msg_line = add_new_line();
505 			}
506 
507 			add_token(token);
508 			if (msg_line->total_length == scroll_width // add another line for cursor.
509 			        && (talking || Game::get_game()->get_event()->get_mode() == INPUT_MODE
510 			            || Game::get_game()->get_event()->get_mode() == KEYINPUT_MODE)) {
511 				msg_line = add_new_line();
512 			}
513 		}
514 		break;
515 	}
516 
517 	if (msg_buf.size() > scroll_height)
518 		display_pos = msg_buf.size() - scroll_height;
519 	just_finished_page_break = false;
520 	just_displayed_prompt = false;
521 	return true;
522 }
523 
add_token(MsgText * token)524 void MsgScroll::add_token(MsgText *token) {
525 	msg_buf.back()->append(token);
526 }
527 
remove_char()528 bool MsgScroll::remove_char() {
529 	MsgLine *msg_line;
530 
531 	msg_line = msg_buf.back(); // retrieve the last line from the scroll buffer.
532 	msg_line->remove_char();
533 
534 	if (msg_line->total_length == 0) { // remove empty line from scroll buffer
535 		msg_buf.pop_back();
536 		delete msg_line;
537 	}
538 
539 	return true;
540 }
541 
set_font(uint8 font_type)542 void MsgScroll::set_font(uint8 font_type) {
543 	font = Game::get_game()->get_font_manager()->get_font(font_type); // 0 = normal english; 1 = runic / gargoyle font
544 }
545 
is_garg_font()546 bool MsgScroll::is_garg_font() {
547 	return (font == Game::get_game()->get_font_manager()->get_font(NUVIE_FONT_GARG));
548 }
549 
clear_scroll()550 void MsgScroll::clear_scroll() {
551 	Std::list<MsgLine *>::iterator iter;
552 
553 	for (iter = msg_buf.begin(); iter != msg_buf.end(); iter++) {
554 		MsgLine *line = *iter;
555 		delete line;
556 	}
557 
558 	msg_buf.clear();
559 	line_count = 0;
560 	display_pos = 0;
561 	scroll_updated = true;
562 	add_new_line();
563 }
564 
delete_front_line()565 void MsgScroll::delete_front_line() {
566 	MsgLine *msg_line_front = msg_buf.front();
567 	msg_buf.pop_front();
568 	delete msg_line_front;
569 }
570 
add_new_line()571 MsgLine *MsgScroll::add_new_line() {
572 	MsgLine *msg_line = new MsgLine();
573 	msg_buf.push_back(msg_line);
574 	line_count++;
575 
576 	if (msg_buf.size() > scrollback_height) {
577 		delete_front_line();
578 	}
579 
580 	if (autobreak && line_count > scroll_height - 1)
581 		set_page_break();
582 
583 	return msg_line;
584 }
585 
set_prompt(const char * new_prompt,Font * f)586 bool MsgScroll::set_prompt(const char *new_prompt, Font *f) {
587 
588 	prompt.s.assign(new_prompt);
589 	prompt.font = f;
590 
591 	return true;
592 }
593 
display_prompt()594 void MsgScroll::display_prompt() {
595 	if (!talking && !just_displayed_prompt) {
596 //line_count = 0;
597 		display_string(prompt.s, prompt.font, MSGSCROLL_NO_MAP_DISPLAY);
598 //line_count = 0;
599 
600 		clear_page_break();
601 		just_displayed_prompt = true;
602 	}
603 }
604 
display_converse_prompt()605 void MsgScroll::display_converse_prompt() {
606 	display_string("\nyou say:", MSGSCROLL_NO_MAP_DISPLAY);
607 }
608 
set_keyword_highlight(bool state)609 void MsgScroll::set_keyword_highlight(bool state) {
610 	keyword_highlight = state;
611 }
612 
set_permitted_input(const char * allowed)613 void MsgScroll::set_permitted_input(const char *allowed) {
614 	permit_input = allowed;
615 	if (permit_input) {
616 		if (strcmp(permit_input, "yn") == 0)
617 			yes_no_only = true;
618 		else if (strncmp(permit_input, "0123456789", strlen(permit_input)) == 0)
619 			numbers_only = true;
620 		else if (game_type == NUVIE_GAME_U6 && strcmp(permit_input, "ayn") == 0) // Heftimus npc 47
621 			aye_nay_only = true;
622 	}
623 }
624 
clear_permitted_input()625 void MsgScroll::clear_permitted_input() {
626 	permit_input = NULL;
627 	yes_no_only = false;
628 	numbers_only = false;
629 	aye_nay_only = false;
630 }
631 
set_input_mode(bool state,const char * allowed,bool can_escape,bool use_target_cursor,bool set_numbers_only_to_true)632 void MsgScroll::set_input_mode(bool state, const char *allowed, bool can_escape, bool use_target_cursor, bool set_numbers_only_to_true) {
633 	bool do_callback = false;
634 
635 	input_mode = state;
636 	clear_permitted_input();
637 	permit_inputescape = can_escape;
638 	using_target_cursor = use_target_cursor;
639 	if (set_numbers_only_to_true)
640 		numbers_only = true;
641 
642 	line_count = 0;
643 
644 	clear_page_break();
645 
646 	if (input_mode == true) {
647 		if (allowed && strlen(allowed))
648 			set_permitted_input(allowed);
649 		//FIXME SDL2 SDL_EnableUNICODE(1); // allow character translation
650 		input_buf.erase(0, input_buf.length());
651 	} else {
652 		//FIXME SDL2 SDL_EnableUNICODE(0); // reduce translation overhead when not needed
653 		if (callback_target)
654 			do_callback = true; // **DELAY until end-of-method so callback can set_input_mode() again**
655 	}
656 	Game::get_game()->get_gui()->lock_input((input_mode && !using_target_cursor) ? this : NULL);
657 
658 // send whatever input was collected to target that requested it
659 	if (do_callback) {
660 		CallBack *requestor = callback_target; // copy to temp
661 		char *user_data = callback_user_data;
662 		cancel_input_request(); // clear originals (callback may request again)
663 
664 		Std::string input_str = input_buf;
665 		requestor->set_user_data(user_data); // use temp requestor/user_data
666 		requestor->callback(MSGSCROLL_CB_TEXT_READY, this, &input_str);
667 	}
668 }
669 
move_scroll_down()670 void MsgScroll::move_scroll_down() {
671 	if (msg_buf.size() > scroll_height && display_pos < msg_buf.size() - scroll_height) {
672 		display_pos++;
673 		scroll_updated = true;
674 	}
675 }
676 
move_scroll_up()677 void MsgScroll::move_scroll_up() {
678 	if (display_pos > 0) {
679 		display_pos--;
680 		scroll_updated = true;
681 	}
682 }
683 
page_up()684 void MsgScroll::page_up() {
685 	uint8 i = 0;
686 	for (; display_pos > 0 && i < scroll_height; i++)
687 		display_pos--;
688 	if (i > 0)
689 		scroll_updated = true;
690 }
691 
page_down()692 void MsgScroll::page_down() {
693 	uint8 i = 0;
694 	for (; msg_buf.size() > scroll_height && i < scroll_height
695 	        && display_pos < msg_buf.size() - scroll_height ; i++)
696 		display_pos++;
697 	if (i > 0)
698 		scroll_updated = true;
699 }
700 
process_page_break()701 void MsgScroll::process_page_break() {
702 	page_break = false;
703 	just_finished_page_break = true;
704 	if (!input_mode)
705 		Game::get_game()->get_gui()->unlock_input();
706 	process_holding_buffer(); // Process any text in the holding buffer.
707 	just_displayed_prompt = true;
708 }
709 
710 /* Take input from the main event handler and do something with it
711  * if necessary.
712  */
KeyDown(const Common::KeyState & keyState)713 GUI_status MsgScroll::KeyDown(const Common::KeyState &keyState) {
714 	Common::KeyState key = keyState;
715 	char ascii = get_ascii_char_from_keysym(key);
716 
717 	if (page_break == false && input_mode == false)
718 		return (GUI_PASS);
719 
720 	bool is_printable = Common::isPrint(ascii);
721 	KeyBinder *keybinder = Game::get_game()->get_keybinder();
722 	ActionType a = keybinder->get_ActionType(key);
723 	ActionKeyType action_key_type = keybinder->GetActionKeyType(a);
724 
725 	if (using_target_cursor && !is_printable && action_key_type <= DO_ACTION_KEY) // directional keys, toggle_cursor, and do_action
726 		return GUI_PASS;
727 
728 	if (!input_mode || !is_printable) {
729 		switch (action_key_type) {
730 		case WEST_KEY:
731 			key.keycode = Common::KEYCODE_LEFT;
732 			break;
733 		case EAST_KEY:
734 			key.keycode = Common::KEYCODE_RIGHT;
735 			break;
736 		case SOUTH_KEY:
737 			key.keycode = Common::KEYCODE_DOWN;
738 			break;
739 		case NORTH_KEY:
740 			key.keycode = Common::KEYCODE_UP;
741 			break;
742 		case CANCEL_ACTION_KEY:
743 			key.keycode = Common::KEYCODE_ESCAPE;
744 			break;
745 		case DO_ACTION_KEY:
746 			key.keycode = Common::KEYCODE_RETURN;
747 			break;
748 		case MSGSCROLL_UP_KEY:
749 			key.keycode = Common::KEYCODE_PAGEUP;
750 			break;
751 		case MSGSCROLL_DOWN_KEY:
752 			key.keycode = Common::KEYCODE_PAGEDOWN;
753 			break;
754 		default:
755 			if (keybinder->handle_always_available_keys(a)) return GUI_YUM;
756 			break;
757 		}
758 	}
759 	switch (key.keycode) {
760 	case Common::KEYCODE_UP :
761 		if (input_mode) break; //will select printable ascii
762 		move_scroll_up();
763 		return (GUI_YUM);
764 
765 	case Common::KEYCODE_DOWN:
766 		if (input_mode) break; //will select printable ascii
767 		move_scroll_down();
768 		return (GUI_YUM);
769 	case Common::KEYCODE_PAGEUP:
770 		if (Game::get_game()->is_new_style())
771 			move_scroll_up();
772 		else page_up();
773 		return (GUI_YUM);
774 	case Common::KEYCODE_PAGEDOWN:
775 		if (Game::get_game()->is_new_style())
776 			move_scroll_down();
777 		else page_down();
778 		return (GUI_YUM);
779 	default :
780 		break;
781 	}
782 
783 	if (page_break) {
784 		process_page_break();
785 		return (GUI_YUM);
786 	}
787 
788 	switch (key.keycode) {
789 	case Common::KEYCODE_ESCAPE:
790 		if (permit_inputescape) {
791 			// reset input buffer
792 			permit_input = NULL;
793 			if (input_mode)
794 				set_input_mode(false);
795 		}
796 		return (GUI_YUM);
797 	case Common::KEYCODE_KP_ENTER:
798 	case Common::KEYCODE_RETURN:
799 		if (permit_inputescape || input_char != 0) { // input_char should only be permit_input
800 			if (input_char != 0)
801 				input_buf_add_char(get_char_from_input_char());
802 			if (input_mode)
803 				set_input_mode(false);
804 		}
805 		return (GUI_YUM);
806 	case Common::KEYCODE_RIGHT:
807 		if (input_char != 0 && permit_input == NULL)
808 			input_buf_add_char(get_char_from_input_char());
809 		break;
810 	case Common::KEYCODE_DOWN:
811 		increase_input_char();
812 		break;
813 	case Common::KEYCODE_UP:
814 		decrease_input_char();
815 		break;
816 	case Common::KEYCODE_LEFT :
817 	case Common::KEYCODE_BACKSPACE :
818 		if (input_mode) {
819 			if (input_char != 0)
820 				input_char = 0;
821 			else
822 				input_buf_remove_char();
823 		}
824 		break;
825 	case Common::KEYCODE_LSHIFT :
826 		return (GUI_YUM);
827 	case Common::KEYCODE_RSHIFT :
828 		return (GUI_YUM);
829 	default: // alphanumeric characters
830 		if (input_mode && is_printable) {
831 			if (permit_input == NULL) {
832 				if (!numbers_only || Common::isDigit(ascii)) {
833 					if (input_char != 0)
834 						input_buf_add_char(get_char_from_input_char());
835 					input_buf_add_char(ascii);
836 				}
837 			} else if (strchr(permit_input, ascii) || strchr(permit_input, tolower(ascii))) {
838 				input_buf_add_char(toupper(ascii));
839 				set_input_mode(false);
840 			}
841 		}
842 		break;
843 	}
844 
845 	return (GUI_YUM);
846 }
847 
848 
MouseWheel(sint32 x,sint32 y)849 GUI_status MsgScroll::MouseWheel(sint32 x, sint32 y) {
850 	if (page_break) { // any click == scroll-to-end
851 		process_page_break();
852 		return (GUI_YUM);
853 	}
854 
855 	Game *game = Game::get_game();
856 
857 	if (game->is_new_style()) {
858 		if (!input_mode)
859 			return GUI_PASS;
860 		if (y > 0)
861 			move_scroll_up();
862 		if (y < 0)
863 			move_scroll_down();
864 	} else {
865 		if (y > 0)
866 			page_up();
867 		if (y < 0)
868 			page_down();
869 	}
870 	return GUI_YUM;
871 }
872 
MouseUp(int x,int y,Shared::MouseButton button)873 GUI_status MsgScroll::MouseUp(int x, int y, Shared::MouseButton button) {
874 	uint16 i;
875 	Std::string token_str;
876 
877 	if (page_break) { // any click == scroll-to-end
878 		process_page_break();
879 		return (GUI_YUM);
880 	}
881 
882 	if (button == 1) { // left click == select word
883 		if (input_mode) {
884 			token_str = get_token_string_at_pos(x, y);
885 			if (permit_input != NULL) {
886 				if (strchr(permit_input, token_str[0])
887 				        || strchr(permit_input, tolower(token_str[0]))) {
888 					input_buf_add_char(token_str[0]);
889 					set_input_mode(false);
890 				}
891 				return (GUI_YUM);
892 			}
893 
894 			for (i = 0; i < token_str.length(); i++) {
895 				if (Common::isAlnum(token_str[i]))
896 					input_buf_add_char(token_str[i]);
897 			}
898 
899 		} else if (!Game::get_game()->is_new_style())
900 			Game::get_game()->get_event()->cancelAction();
901 	} else if (button == 3) { // right click == send input
902 		if (input_mode) {
903 			if (permit_inputescape) {
904 				set_input_mode(false);
905 				return (GUI_YUM);
906 			}
907 		} else if (!Game::get_game()->is_new_style())
908 			Game::get_game()->get_event()->cancelAction();
909 	}
910 	return (GUI_PASS);
911 }
912 
get_token_string_at_pos(uint16 x,uint16 y)913 Std::string MsgScroll::get_token_string_at_pos(uint16 x, uint16 y) {
914 	uint16 i;
915 	sint32 buf_x, buf_y;
916 	MsgText *token = NULL;
917 	Std::list<MsgLine *>::iterator iter;
918 
919 	buf_x = (x - area.left) / 8;
920 	buf_y = (y - area.top) / 8;
921 
922 	if (buf_x < 0 || buf_x >= scroll_width || // click not in MsgScroll area.
923 	        buf_y < 0 || buf_y >= scroll_height)
924 		return "";
925 
926 	if (msg_buf.size() <= scroll_height) {
927 		if ((sint32)msg_buf.size() < buf_y + 1)
928 			return "";
929 	} else {
930 		buf_y = display_pos + buf_y;
931 	}
932 
933 	for (i = 0, iter = msg_buf.begin(); i < buf_y && iter != msg_buf.end();) {
934 		iter++;
935 		i++;
936 	}
937 
938 	if (iter != msg_buf.end()) {
939 		token = (*iter)->get_text_at_pos(buf_x);
940 		if (token) {
941 			DEBUG(0, LEVEL_DEBUGGING, "Token at (%d,%d) = %s\n", buf_x, buf_y, token->s.c_str());
942 			return token->s;
943 		}
944 	}
945 
946 	return "";
947 }
948 
Display(bool full_redraw)949 void MsgScroll::Display(bool full_redraw) {
950 	uint16 i;
951 	Std::list<MsgLine *>::iterator iter;
952 	MsgLine *msg_line = NULL;
953 
954 
955 
956 	if (scroll_updated || full_redraw || Game::get_game()->is_original_plus_full_map()) {
957 		screen->fill(bg_color, area.left, area.top, area.width(), area.height()); //clear whole scroll
958 
959 		iter = msg_buf.begin();
960 		for (i = 0; i < display_pos; i++)
961 			iter++;
962 
963 		for (i = 0; i < scroll_height && iter != msg_buf.end(); i++, iter++) {
964 			msg_line = *iter;
965 			drawLine(screen, msg_line, i);
966 		}
967 		scroll_updated = false;
968 
969 		screen->update(area.left, area.top, area.width(), area.height());
970 
971 		cursor_y = i - 1;
972 		if (msg_line) {
973 			cursor_x = msg_line->total_length;
974 			if (cursor_x == scroll_width) { // don't draw the cursor outside the scroll (SB-X)
975 				if (cursor_y + 1 < scroll_height)
976 					cursor_y++;
977 				cursor_x = 0;
978 			}
979 		} else
980 			cursor_x = area.left;
981 	} else {
982 		clearCursor(area.left + 8 * cursor_x, area.top + cursor_y * 8);
983 	}
984 
985 	if (show_cursor && (msg_buf.size() <= scroll_height || display_pos == msg_buf.size() - scroll_height)) {
986 		drawCursor(area.left + left_margin + 8 * cursor_x, area.top + cursor_y * 8);
987 	}
988 
989 }
990 
drawLine(Screen * theScreen,MsgLine * msg_line,uint16 line_y)991 inline void MsgScroll::drawLine(Screen *theScreen, MsgLine *msg_line, uint16 line_y) {
992 	MsgText *token;
993 	Std::list<MsgText *>::iterator iter;
994 	uint16 total_length = 0;
995 
996 	for (iter = msg_line->text.begin(); iter != msg_line->text.end() ; iter++) {
997 		token = *iter;
998 		token->font->drawString(theScreen, token->s.c_str(), area.left + left_margin + total_length * 8, area.top + line_y * 8, token->color, font_highlight_color); //FIX for hardcoded font height
999 		total_length += token->s.length();
1000 	}
1001 }
1002 
clearCursor(uint16 x,uint16 y)1003 void MsgScroll::clearCursor(uint16 x, uint16 y) {
1004 	screen->fill(bg_color, x, y, 8, 8);
1005 }
1006 
drawCursor(uint16 x,uint16 y)1007 void MsgScroll::drawCursor(uint16 x, uint16 y) {
1008 	uint8 cursor_color = input_mode ? get_input_font_color() : font_color;
1009 
1010 	if (input_char != 0) { // show letter selected by arrow keys
1011 		font->drawChar(screen, get_char_from_input_char(), x, y, cursor_color);
1012 		screen->update(x, y, 8, 8);
1013 		return;
1014 	}
1015 	if (page_break) {
1016 		if (cursor_wait <= 2) // flash arrow
1017 			font->drawChar(screen, 1, x, y, cursor_color); // down arrow
1018 	} else
1019 		font->drawChar(screen, cursor_char + 5, x, y, cursor_color); //spinning ankh
1020 
1021 	screen->update(x, y, 8, 8);
1022 	if (cursor_wait == MSGSCROLL_CURSOR_DELAY) {
1023 		cursor_char = (cursor_char + 1) % 4;
1024 		cursor_wait = 0;
1025 	} else
1026 		cursor_wait++;
1027 }
1028 
1029 
set_page_break()1030 void MsgScroll::set_page_break() {
1031 	line_count = 1;
1032 	page_break = true;
1033 
1034 	if (!input_mode) {
1035 		Game::get_game()->get_gui()->lock_input(this);
1036 	}
1037 
1038 	return;
1039 }
1040 
input_buf_add_char(char c)1041 bool MsgScroll::input_buf_add_char(char c) {
1042 	MsgText token;
1043 	input_char = 0;
1044 	if (permit_input != NULL)
1045 		input_buf_remove_char();
1046 	input_buf.push_back(c);
1047 	scroll_updated = true;
1048 
1049 // Add char to scroll buffer
1050 
1051 	token.s.assign(&c, 1);
1052 	token.font = font;
1053 	token.color = get_input_font_color();
1054 
1055 	parse_token(&token);
1056 
1057 	return true;
1058 }
1059 
input_buf_remove_char()1060 bool MsgScroll::input_buf_remove_char() {
1061 	if (input_buf.length()) {
1062 		input_buf.erase(input_buf.length() - 1, 1);
1063 		scroll_updated = true;
1064 		remove_char();
1065 
1066 		return true;
1067 	}
1068 
1069 	return false;
1070 }
1071 
has_input()1072 bool MsgScroll::has_input() {
1073 	if (input_mode == false) //we only have input ready after the user presses enter.
1074 		return true;
1075 
1076 	return false;
1077 }
1078 
get_input()1079 Std::string MsgScroll::get_input() {
1080 // MsgScroll sets input_mode to false when it receives Common::KEYCODE_ENTER
1081 	Std::string s;
1082 
1083 	if (input_mode == false) {
1084 		s.assign(input_buf);
1085 	}
1086 	//::debug(1, "%s", s.c_str());
1087 
1088 	return s;
1089 }
1090 
clear_page_break()1091 void MsgScroll::clear_page_break() {
1092 	MsgText *msg_text = new MsgText("", NULL);
1093 	holding_buffer.push_back(msg_text);
1094 
1095 	process_holding_buffer();
1096 }
1097 
1098 /* Set callback & callback_user_data so that a message will be sent to the
1099  * caller when input has been gathered. */
request_input(CallBack * caller,void * user_data)1100 void MsgScroll::request_input(CallBack *caller, void *user_data) {
1101 	callback_target = caller;
1102 	callback_user_data = (char *)user_data;
1103 }
1104 
1105 // 0 is no char, 1 - 26 is alpha, 27 is space, 28 - 37 is numbers
increase_input_char()1106 void MsgScroll::increase_input_char() {
1107 	if (permit_input != NULL && strcmp(permit_input, "\n") == 0) // blame hacky PauseEffect
1108 		return;
1109 	if (yes_no_only)
1110 		input_char = input_char == 25 ? 14 : 25;
1111 	else if (aye_nay_only)
1112 		input_char = input_char == 1 ? 14 : 1;
1113 	else if (numbers_only)
1114 		input_char = (input_char == 0 || input_char == 37) ? 28 : input_char + 1;
1115 	else
1116 		input_char = (input_char + 1) % 38;
1117 	if (permit_input != NULL && !strchr(permit_input, get_char_from_input_char())) // might only be needed for the teleport cheat menu
1118 		increase_input_char();
1119 }
1120 
decrease_input_char()1121 void MsgScroll::decrease_input_char() {
1122 	if (permit_input != NULL && strcmp(permit_input, "\n") == 0) // blame hacky PauseEffect
1123 		return;
1124 	if (yes_no_only)
1125 		input_char = input_char == 25 ? 14 : 25;
1126 	else if (numbers_only)
1127 		input_char = (input_char == 0 || input_char == 28) ? 37 : input_char - 1;
1128 	else if (aye_nay_only)
1129 		input_char = input_char == 1 ? 14 : 1;
1130 	else
1131 		input_char = input_char == 0 ? 37 : input_char - 1;
1132 	if (permit_input != NULL && !strchr(permit_input, get_char_from_input_char())) // might only be needed for the teleport cheat menu
1133 		decrease_input_char();
1134 }
1135 
get_char_from_input_char()1136 uint8 MsgScroll::get_char_from_input_char() {
1137 
1138 	if (input_char > 27)
1139 		return (input_char - 28 + Common::KEYCODE_0);
1140 	else if (input_char == 27)
1141 		return Common::KEYCODE_SPACE;
1142 	else
1143 		return (input_char + Common::KEYCODE_a - 1);
1144 }
1145 
1146 } // End of namespace Nuvie
1147 } // End of namespace Ultima
1148