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 "glk/window_text_grid.h"
24 #include "glk/conf.h"
25 #include "glk/glk.h"
26 #include "glk/selection.h"
27 #include "glk/screen.h"
28
29 namespace Glk {
30
TextGridWindow(Windows * windows,uint rock)31 TextGridWindow::TextGridWindow(Windows *windows, uint rock) : TextWindow(windows, rock),
32 _font(g_conf->_monoInfo) {
33 _type = wintype_TextGrid;
34 _width = _height = 0;
35 _curX = _curY = 0;
36 _inBuf = nullptr;
37 _inOrgX = _inOrgY = 0;
38 _inMax = 0;
39 _inCurs = _inLen = 0;
40 _inArrayRock.num = 0;
41 _lineTerminators = nullptr;
42
43 Common::copy(&g_conf->_gStyles[0], &g_conf->_gStyles[style_NUMSTYLES], _styles);
44
45 if (g_conf->_speak)
46 gli_initialize_tts();
47 }
48
~TextGridWindow()49 TextGridWindow::~TextGridWindow() {
50 if (g_conf->_speak)
51 gli_free_tts();
52
53 if (_inBuf) {
54 if (g_vm->gli_unregister_arr)
55 (*g_vm->gli_unregister_arr)(_inBuf, _inMax, "&+#!Cn", _inArrayRock);
56 _inBuf = nullptr;
57 }
58
59 delete[] _lineTerminators;
60 }
61
rearrange(const Rect & box)62 void TextGridWindow::rearrange(const Rect &box) {
63 Window::rearrange(box);
64 int newwid, newhgt;
65
66 newwid = MAX(box.width() / _font._cellW, 0);
67 newhgt = MAX(box.height() / _font._cellH, 0);
68
69 if (newwid == _width && newhgt == _height)
70 return;
71
72 _lines.resize(newhgt);
73 for (int y = 0; y < newhgt; ++y) {
74 _lines[y].resize(newwid);
75 touch(y);
76 }
77
78 _attr.clear();
79 _width = newwid;
80 _height = newhgt;
81 }
82
touch(int line)83 void TextGridWindow::touch(int line) {
84 int y = _bbox.top + line * _font._leading;
85 _lines[line].dirty = true;
86 _windows->repaint(Rect(_bbox.left, y, _bbox.right, y + _font._leading));
87 }
88
getSplit(uint size,bool vertical) const89 uint TextGridWindow::getSplit(uint size, bool vertical) const {
90 return vertical ? size * _font._cellW : size * _font._cellH;
91 }
92
putCharUni(uint32 ch)93 void TextGridWindow::putCharUni(uint32 ch) {
94 TextGridRow *ln;
95
96 // This may not be the best way to do this, but some games use user styles to
97 // display some gliphs from ASCII characters. Those should not be spoken as
98 // they make no sense.
99 if (_attr.style < style_User1)
100 gli_tts_speak(&ch, 1);
101
102 // Canonicalize the cursor position. That is, the cursor may have been
103 // left outside the window area; wrap it if necessary.
104 if (_curX < 0) {
105 _curX = 0;
106 } else if (_curX >= _width) {
107 _curX = 0;
108 _curY++;
109 }
110 if (_curY < 0)
111 _curY = 0;
112 else if (_curY >= _height)
113 return; // outside the window
114
115 if (ch == '\n') {
116 // a newline just moves the cursor.
117 _curY++;
118 _curX = 0;
119 return;
120 }
121
122 touch(_curY);
123
124 ln = &(_lines[_curY]);
125 ln->_chars[_curX] = ch;
126 ln->_attrs[_curX] = _attr;
127
128 _curX++;
129 // We can leave the cursor outside the window, since it will be
130 // canonicalized next time a character is printed.
131 }
132
unputCharUni(uint32 ch)133 bool TextGridWindow::unputCharUni(uint32 ch) {
134 TextGridRow *ln;
135 int oldx = _curX, oldy = _curY;
136
137 // Move the cursor back.
138 if (_curX >= _width)
139 _curX = _width - 1;
140 else
141 _curX--;
142
143 // Canonicalize the cursor position. That is, the cursor may have been
144 // left outside the window area; wrap it if necessary.
145 if (_curX < 0) {
146 _curX = _width - 1;
147 _curY--;
148 }
149 if (_curY < 0)
150 _curY = 0;
151 else if (_curY >= _height)
152 return false; // outside the window
153
154 if (ch == '\n') {
155 // a newline just moves the cursor.
156 if (_curX == _width - 1)
157 return 1; // deleted a newline
158 _curX = oldx;
159 _curY = oldy;
160 return 0; // it wasn't there
161 }
162
163 ln = &(_lines[_curY]);
164 if (ln->_chars[_curX] == ch) {
165 ln->_chars[_curX] = ' ';
166 ln->_attrs[_curX].clear();
167 touch(_curY);
168 return true; // deleted the char
169 } else {
170 _curX = oldx;
171 _curY = oldy;
172 return false; // it wasn't there
173 }
174 }
175
moveCursor(const Point & pos)176 void TextGridWindow::moveCursor(const Point &pos) {
177 // If the values are negative, they're really huge positive numbers --
178 // remember that they were cast from uint. So set them huge and
179 // let canonicalization take its course.
180 if (_curY >= 0 && _curY < _height && _lines[_curY].dirty) {
181 const uint32 NEWLINE = '\n';
182 gli_tts_speak((const uint32 *)&NEWLINE, 1);
183 }
184
185 _curX = (pos.x < 0) ? 32767 : pos.x;
186 _curY = (pos.y < 0) ? 32767 : pos.y;
187 }
188
clear()189 void TextGridWindow::clear() {
190 _attr.fgset = Windows::_overrideFgSet;
191 _attr.bgset = Windows::_overrideBgSet;
192 _attr.fgcolor = Windows::_overrideFgSet ? Windows::_overrideFgVal : 0;
193 _attr.bgcolor = Windows::_overrideBgSet ? Windows::_overrideBgVal : 0;
194 _attr.reverse = false;
195
196 for (int k = 0; k < _height; k++) {
197 TextGridRow &ln = _lines[k];
198 touch(k);
199 for (uint j = 0; j < ln._attrs.size(); ++j) {
200 ln._chars[j] = ' ';
201 ln._attrs[j].clear();
202 }
203 }
204
205 _curX = 0;
206 _curY = 0;
207 }
208
click(const Point & newPos)209 void TextGridWindow::click(const Point &newPos) {
210 int x = newPos.x - _bbox.left;
211 int y = newPos.y - _bbox.top;
212
213 if (_lineRequest || _charRequest || _lineRequestUni || _charRequestUni
214 || _moreRequest || _scrollRequest)
215 _windows->setFocus(this);
216
217 if (_mouseRequest) {
218 g_vm->_events->store(evtype_MouseInput, this, x / _font._cellW, y / _font._leading);
219 _mouseRequest = false;
220 if (g_conf->_safeClicks)
221 g_vm->_events->_forceClick = true;
222 }
223
224 if (_hyperRequest) {
225 uint linkval = g_vm->_selection->getHyperlink(newPos);
226 if (linkval) {
227 g_vm->_events->store(evtype_Hyperlink, this, linkval, 0);
228 _hyperRequest = false;
229 if (g_conf->_safeClicks)
230 g_vm->_events->_forceClick = true;
231 }
232 }
233 }
234
requestLineEvent(char * buf,uint maxlen,uint initlen)235 void TextGridWindow::requestLineEvent(char *buf, uint maxlen, uint initlen) {
236 if (_charRequest || _lineRequest || _charRequestUni || _lineRequestUni) {
237 warning("request_line_event: window already has keyboard request");
238 return;
239 }
240
241 _lineRequest = true;
242 gli_tts_flush();
243
244 if ((int)maxlen > (_width - _curX))
245 maxlen = (_width - _curX);
246
247 _inBuf = buf;
248 _inMax = maxlen;
249 _inLen = 0;
250 _inCurs = 0;
251 _inOrgX = _curX;
252 _inOrgY = _curY;
253 _origAttr = _attr;
254 _attr.set(style_Input);
255
256 if (initlen > maxlen)
257 initlen = maxlen;
258
259 if (initlen) {
260 TextGridRow *ln = &_lines[_inOrgY];
261
262 for (uint ix = 0; ix < initlen; ix++) {
263 ln->_attrs[_inOrgX + ix].set(style_Input);
264 ln->_chars[_inOrgX + ix] = buf[ix];
265 }
266
267 _inCurs += initlen;
268 _inLen += initlen;
269 _curX = _inOrgX + _inCurs;
270 _curY = _inOrgY;
271
272 touch(_inOrgY);
273 }
274
275 if (_lineTerminatorsBase && _termCt) {
276 _lineTerminators = new uint32[_termCt + 1];
277
278 if (_lineTerminators) {
279 memcpy(_lineTerminators, _lineTerminatorsBase, _termCt * sizeof(uint32));
280 _lineTerminators[_termCt] = 0;
281 }
282 }
283
284 if (g_vm->gli_register_arr)
285 _inArrayRock = (*g_vm->gli_register_arr)(buf, maxlen, "&+#!Cn");
286
287 // Switch focus to the new window
288 _windows->inputGuessFocus();
289 }
290
requestLineEventUni(uint32 * buf,uint maxlen,uint initlen)291 void TextGridWindow::requestLineEventUni(uint32 *buf, uint maxlen, uint initlen) {
292 if (_charRequest || _lineRequest || _charRequestUni || _lineRequestUni) {
293 warning("requestLineEventUni: window already has keyboard request");
294 return;
295 }
296
297 _lineRequestUni = true;
298 gli_tts_flush();
299
300 if ((int)maxlen > (_width - _curX))
301 maxlen = (_width - _curX);
302
303 _inBuf = buf;
304 _inMax = maxlen;
305 _inLen = 0;
306 _inCurs = 0;
307 _inOrgX = _curX;
308 _inOrgY = _curY;
309 _origAttr = _attr;
310 _attr.set(style_Input);
311
312 if (initlen > maxlen)
313 initlen = maxlen;
314
315 if (initlen) {
316 TextGridRow *ln = &(_lines[_inOrgY]);
317
318 for (uint ix = 0; ix < initlen; ix++) {
319 ln->_attrs[_inOrgX + ix].set(style_Input);
320 ln->_chars[_inOrgX + ix] = buf[ix];
321 }
322
323 _inCurs += initlen;
324 _inLen += initlen;
325 _curX = _inOrgX + _inCurs;
326 _curY = _inOrgY;
327
328 touch(_inOrgY);
329 }
330
331 if (_lineTerminatorsBase && _termCt) {
332 _lineTerminators = new uint32[_termCt + 1];
333
334 if (_lineTerminators) {
335 memcpy(_lineTerminators, _lineTerminatorsBase, _termCt * sizeof(uint));
336 _lineTerminators[_termCt] = 0;
337 }
338 }
339
340 if (g_vm->gli_register_arr)
341 _inArrayRock = (*g_vm->gli_register_arr)(buf, maxlen, "&+#!Iu");
342
343 // Switch focus to the new window
344 _windows->inputGuessFocus();
345 }
346
requestCharEvent()347 void TextGridWindow::requestCharEvent() {
348 _charRequest = true;
349
350 // Switch focus to the new window
351 _windows->inputGuessFocus();
352 }
353
requestCharEventUni()354 void TextGridWindow::requestCharEventUni() {
355 _charRequestUni = true;
356
357 // Switch focus to the new window
358 _windows->inputGuessFocus();
359 }
360
cancelLineEvent(Event * ev)361 void TextGridWindow::cancelLineEvent(Event *ev) {
362 int ix;
363 void *inbuf;
364 int inmax;
365 bool unicode = _lineRequestUni;
366 gidispatch_rock_t inarrayrock;
367 TextGridRow *ln = &_lines[_inOrgY];
368 Event dummyEv;
369
370 if (!ev)
371 ev = &dummyEv;
372
373 ev->clear();
374
375 if (!_lineRequest && !_lineRequestUni)
376 return;
377
378
379 inbuf = _inBuf;
380 inmax = _inMax;
381 inarrayrock = _inArrayRock;
382
383 if (!unicode) {
384 for (ix = 0; ix < _inLen; ix++) {
385 uint32 ch = ln->_chars[_inOrgX + ix];
386 if (ch > 0xff)
387 ch = '?';
388 ((char *)inbuf)[ix] = (char)ch;
389 }
390 if (_echoStream)
391 _echoStream->echoLine((char *)_inBuf, _inLen);
392 } else {
393 for (ix = 0; ix < _inLen; ix++)
394 ((uint *)inbuf)[ix] = ln->_chars[_inOrgX + ix];
395 if (_echoStream)
396 _echoStream->echoLineUni((uint32 *)inbuf, _inLen);
397 }
398
399 _curY = _inOrgY + 1;
400 _curX = 0;
401 _attr = _origAttr;
402
403 ev->type = evtype_LineInput;
404 ev->window = this;
405 ev->val1 = _inLen;
406 ev->val2 = 0;
407
408 _lineRequest = false;
409 _lineRequestUni = false;
410
411 if (_lineTerminators) {
412 delete[] _lineTerminators;
413 _lineTerminators = nullptr;
414 }
415
416 _inBuf = nullptr;
417 _inMax = 0;
418 _inOrgX = 0;
419 _inOrgY = 0;
420
421 if (g_vm->gli_unregister_arr)
422 (*g_vm->gli_unregister_arr)(inbuf, inmax, unicode ? "&+#!Iu" : "&+#!Cn", inarrayrock);
423 }
424
acceptReadChar(uint arg)425 void TextGridWindow::acceptReadChar(uint arg) {
426 uint key;
427
428 switch (arg) {
429 case keycode_Erase:
430 key = keycode_Delete;
431 break;
432 case keycode_MouseWheelUp:
433 case keycode_MouseWheelDown:
434 return;
435 default:
436 key = arg;
437 }
438
439 gli_tts_purge();
440
441 if (key > 0xff && key < (0xffffffff - keycode_MAXVAL + 1)) {
442 if (!(_charRequestUni) || key > 0x10ffff)
443 key = keycode_Unknown;
444 }
445
446 _charRequest = false;
447 _charRequestUni = false;
448 g_vm->_events->store(evtype_CharInput, this, key, 0);
449 }
450
acceptLine(uint32 keycode)451 void TextGridWindow::acceptLine(uint32 keycode) {
452 int ix;
453 void *inbuf;
454 int inmax;
455 gidispatch_rock_t inarrayrock;
456 TextGridRow *ln = &(_lines[_inOrgY]);
457 bool unicode = _lineRequestUni;
458
459 if (!_inBuf)
460 return;
461
462 inbuf = _inBuf;
463 inmax = _inMax;
464 inarrayrock = _inArrayRock;
465
466 gli_tts_purge();
467
468 if (!unicode) {
469 for (ix = 0; ix < _inLen; ix++)
470 ((char *)inbuf)[ix] = (char)ln->_chars[_inOrgX + ix];
471 if (_echoStream)
472 _echoStream->echoLine((char *)inbuf, _inLen);
473 if (g_conf->_speakInput) {
474 const char NEWLINE = '\n';
475 gli_tts_speak((const char *)inbuf, _inLen);
476 gli_tts_speak((const char *)&NEWLINE, 1);
477 }
478 } else {
479 for (ix = 0; ix < _inLen; ix++)
480 ((uint *)inbuf)[ix] = ln->_chars[_inOrgX + ix];
481 if (_echoStream)
482 _echoStream->echoLineUni((const uint32 *)inbuf, _inLen);
483 if (g_conf->_speakInput) {
484 const uint32 NEWLINE = '\n';
485 gli_tts_speak((const uint32 *)inbuf, _inLen);
486 gli_tts_speak((const uint32 *)&NEWLINE, 1);
487 }
488 }
489
490 _curY = _inOrgY + 1;
491 _curX = 0;
492 _attr = _origAttr;
493
494 if (_lineTerminators) {
495 uint val2 = keycode;
496 if (val2 == keycode_Return)
497 val2 = 0;
498 g_vm->_events->store(evtype_LineInput, this, _inLen, val2);
499 delete[] _lineTerminators;
500 _lineTerminators = nullptr;
501 } else {
502 g_vm->_events->store(evtype_LineInput, this, _inLen, 0);
503 }
504 _lineRequest = false;
505 _lineRequestUni = false;
506 _inBuf = nullptr;
507 _inMax = 0;
508 _inOrgX = 0;
509 _inOrgY = 0;
510
511 if (g_vm->gli_unregister_arr)
512 (*g_vm->gli_unregister_arr)(inbuf, inmax, unicode ? "&+#!Iu" : "&+#!Cn", inarrayrock);
513 }
514
acceptReadLine(uint32 arg)515 void TextGridWindow::acceptReadLine(uint32 arg) {
516 int ix;
517 TextGridRow *ln = &(_lines[_inOrgY]);
518
519 if (!_inBuf)
520 return;
521
522 if (_lineTerminators && checkTerminators(arg)) {
523 const uint32 *cx;
524 for (cx = _lineTerminators; *cx; cx++) {
525 if (*cx == arg) {
526 acceptLine(arg);
527 return;
528 }
529 }
530 }
531
532 switch (arg) {
533
534 // Delete keys, during line input.
535 case keycode_Delete:
536 if (_inLen <= 0)
537 return;
538 if (_inCurs <= 0)
539 return;
540 for (ix = _inCurs; ix < _inLen; ix++)
541 ln->_chars[_inOrgX + ix - 1] = ln->_chars[_inOrgX + ix];
542 ln->_chars[_inOrgX + _inLen - 1] = ' ';
543 _inCurs--;
544 _inLen--;
545 break;
546
547 case keycode_Erase:
548 if (_inLen <= 0)
549 return;
550 if (_inCurs >= _inLen)
551 return;
552 for (ix = _inCurs; ix < _inLen - 1; ix++)
553 ln->_chars[_inOrgX + ix] = ln->_chars[_inOrgX + ix + 1];
554 ln->_chars[_inOrgX + _inLen - 1] = ' ';
555 _inLen--;
556 break;
557
558 case keycode_Escape:
559 if (_inLen <= 0)
560 return;
561 for (ix = 0; ix < _inLen; ix++)
562 ln->_chars[_inOrgX + ix] = ' ';
563 _inLen = 0;
564 _inCurs = 0;
565 break;
566
567 // Cursor movement keys, during line input.
568 case keycode_Left:
569 if (_inCurs <= 0)
570 return;
571 _inCurs--;
572 break;
573
574 case keycode_Right:
575 if (_inCurs >= _inLen)
576 return;
577 _inCurs++;
578 break;
579
580 case keycode_Home:
581 if (_inCurs <= 0)
582 return;
583 _inCurs = 0;
584 break;
585
586 case keycode_End:
587 if (_inCurs >= _inLen)
588 return;
589 _inCurs = _inLen;
590 break;
591
592 case keycode_Return:
593 acceptLine(arg);
594 break;
595
596 default:
597 if (_inLen >= _inMax)
598 return;
599
600 if (arg < 32 || arg > 0xff)
601 return;
602
603 if (_font._caps && (arg > 0x60 && arg < 0x7b))
604 arg -= 0x20;
605
606 for (ix = _inLen; ix > _inCurs; ix--)
607 ln->_chars[_inOrgX + ix] = ln->_chars[_inOrgX + ix - 1];
608 ln->_attrs[_inOrgX + _inLen].set(style_Input);
609 ln->_chars[_inOrgX + _inCurs] = arg;
610
611 _inCurs++;
612 _inLen++;
613 }
614
615 _curX = _inOrgX + _inCurs;
616 _curY = _inOrgY;
617
618 touch(_inOrgY);
619 }
620
redraw()621 void TextGridWindow::redraw() {
622 TextGridRow *ln;
623 int x0, y0;
624 int x, y, w;
625 int i, a, b, k, o;
626 uint link;
627 int font;
628 uint fgcolor, bgcolor;
629 Screen &screen = *g_vm->_screen;
630
631 gli_tts_flush();
632
633 Window::redraw();
634
635 x0 = _bbox.left;
636 y0 = _bbox.top;
637
638 for (i = 0; i < _height; i++) {
639 ln = &_lines[i];
640 if (ln->dirty || Windows::_forceRedraw) {
641 ln->dirty = false;
642
643 x = x0;
644 y = y0 + i * _font._leading;
645
646 // clear any stored hyperlink coordinates
647 g_vm->_selection->putHyperlink(0, x0, y, x0 + _font._cellW * _width, y + _font._leading);
648
649 a = 0;
650 for (b = 0; b < _width; b++) {
651 if (ln->_attrs[a] != ln->_attrs[b]) {
652 link = ln->_attrs[a].hyper;
653 font = ln->_attrs[a].attrFont(_styles);
654 fgcolor = link ? _font._linkColor : ln->_attrs[a].attrFg(_styles);
655 bgcolor = ln->_attrs[a].attrBg(_styles);
656 w = (b - a) * _font._cellW;
657 screen.fillRect(Rect::fromXYWH(x, y, w, _font._leading), bgcolor);
658 o = x;
659
660 for (k = a, o = x; k < b; k++, o += _font._cellW) {
661 screen.drawStringUni(Point(o * GLI_SUBPIX, y + _font._baseLine), font,
662 fgcolor, Common::U32String(&ln->_chars[k], 1), -1);
663 }
664 if (link) {
665 screen.fillRect(Rect::fromXYWH(x, y + _font._baseLine + 1, w,
666 _font._linkStyle), _font._linkColor);
667 g_vm->_selection->putHyperlink(link, x, y, x + w, y + _font._leading);
668 }
669
670 x += w;
671 a = b;
672 }
673 }
674 link = ln->_attrs[a].hyper;
675 font = ln->_attrs[a].attrFont(_styles);
676 fgcolor = link ? _font._linkColor : ln->_attrs[a].attrFg(_styles);
677 bgcolor = ln->_attrs[a].attrBg(_styles);
678 w = (b - a) * _font._cellW;
679 w += _bbox.right - (x + w);
680 screen.fillRect(Rect::fromXYWH(x, y, w, _font._leading), bgcolor);
681
682 // Draw the caret if necessary
683 if (_windows->getFocusWindow() == this && i == _curY &&
684 (_lineRequest || _lineRequestUni || _charRequest || _charRequestUni)) {
685 _font.drawCaret(Point((x0 + _curX * _font._cellW) * GLI_SUBPIX, y + _font._baseLine));
686 }
687
688 // Write out the text
689 for (k = a, o = x; k < b; k++, o += _font._cellW) {
690 screen.drawStringUni(Point(o * GLI_SUBPIX, y + _font._baseLine), font,
691 fgcolor, Common::U32String(&ln->_chars[k], 1));
692 }
693 if (link) {
694 screen.fillRect(Rect::fromXYWH(x, y + _font._baseLine + 1, w, _font._linkStyle), _font._linkColor);
695 g_vm->_selection->putHyperlink(link, x, y, x + w, y + _font._leading);
696 }
697 }
698 }
699 }
700
getSize(uint * width,uint * height) const701 void TextGridWindow::getSize(uint *width, uint *height) const {
702 if (width)
703 *width = _bbox.width() / _font._cellW;
704 if (height)
705 *height = _bbox.height() / _font._cellH;
706 }
707
708 /*--------------------------------------------------------------------------*/
709
resize(size_t newSize)710 void TextGridWindow::TextGridRow::resize(size_t newSize) {
711 size_t oldSize = _chars.size();
712 if (newSize != oldSize) {
713 _chars.resize(newSize);
714 _attrs.resize(newSize);
715
716 if (newSize > oldSize)
717 Common::fill(&_chars[0] + oldSize, &_chars[0] + newSize, ' ');
718 }
719 }
720
721 } // End of namespace Glk
722