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