aboutsummaryrefslogtreecommitdiff
path: root/engines/glk/window_text_grid.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/glk/window_text_grid.cpp')
-rw-r--r--engines/glk/window_text_grid.cpp654
1 files changed, 654 insertions, 0 deletions
diff --git a/engines/glk/window_text_grid.cpp b/engines/glk/window_text_grid.cpp
new file mode 100644
index 0000000000..963083659f
--- /dev/null
+++ b/engines/glk/window_text_grid.cpp
@@ -0,0 +1,654 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/window_text_grid.h"
+#include "glk/conf.h"
+#include "glk/gargoyle.h"
+#include "glk/selection.h"
+#include "glk/screen.h"
+
+namespace Gargoyle {
+
+TextGridWindow::TextGridWindow(Windows *windows, uint32 rock) : Window(windows, rock) {
+ _type = wintype_TextGrid;
+ _width = _height = 0;
+ _curX = _curY = 0;
+ _inBuf = nullptr;
+ _inOrgX = _inOrgY = 0;
+ _inMax = 0;
+ _inCurs = _inLen = 0;
+ _inArrayRock.num = 0;
+ _lineTerminators = nullptr;
+
+ Common::copy(&g_conf->_gStyles[0], &g_conf->_gStyles[style_NUMSTYLES], _styles);
+}
+
+TextGridWindow::~TextGridWindow() {
+ if (_inBuf) {
+ if (g_vm->gli_unregister_arr)
+ (*g_vm->gli_unregister_arr)(_inBuf, _inMax, "&+#!Cn", _inArrayRock);
+ _inBuf = nullptr;
+ }
+
+ delete[] _lineTerminators;
+}
+
+void TextGridWindow::rearrange(const Rect &box) {
+ Window::rearrange(box);
+ int newwid, newhgt;
+
+ newwid = box.width() / g_conf->_cellW;
+ newhgt = box.height() / g_conf->_cellH;
+
+ if (newwid == _width && newhgt == _height)
+ return;
+
+ _lines.resize(newhgt);
+ for (int y = 0; y < newhgt; ++y) {
+ _lines[y].resize(newwid);
+ touch(y);
+ }
+
+ _attr.clear();
+ _width = newwid;
+ _height = newhgt;
+}
+
+void TextGridWindow::touch(int line) {
+ int y = _bbox.top + line * g_conf->_leading;
+ _lines[line].dirty = true;
+ _windows->repaint(Rect(_bbox.left, y, _bbox.right, y + g_conf->_leading));
+}
+
+glui32 TextGridWindow::getSplit(glui32 size, bool vertical) const {
+ return vertical ? size * g_conf->_cellW : size * g_conf->_cellH;
+}
+
+void TextGridWindow::putCharUni(uint32 ch) {
+ TextGridRow *ln;
+
+ // Canonicalize the cursor position. That is, the cursor may have been
+ // left outside the window area; wrap it if necessary.
+ if (_curX < 0) {
+ _curX = 0;
+ } else if (_curX >= _width) {
+ _curX = 0;
+ _curY++;
+ }
+ if (_curY < 0)
+ _curY = 0;
+ else if (_curY >= _height)
+ return; // outside the window
+
+ if (ch == '\n') {
+ // a newline just moves the cursor.
+ _curY++;
+ _curX = 0;
+ return;
+ }
+
+ touch(_curY);
+
+ ln = &(_lines[_curY]);
+ ln->_chars[_curX] = ch;
+ ln->_attrs[_curX] = _attr;
+
+ _curX++;
+ // We can leave the cursor outside the window, since it will be
+ // canonicalized next time a character is printed.
+}
+
+bool TextGridWindow::unputCharUni(uint32 ch) {
+ TextGridRow *ln;
+ int oldx = _curX, oldy = _curY;
+
+ // Move the cursor back.
+ if (_curX >= _width)
+ _curX = _width - 1;
+ else
+ _curX--;
+
+ // Canonicalize the cursor position. That is, the cursor may have been
+ // left outside the window area; wrap it if necessary.
+ if (_curX < 0) {
+ _curX = _width - 1;
+ _curY--;
+ }
+ if (_curY < 0)
+ _curY = 0;
+ else if (_curY >= _height)
+ return false; // outside the window
+
+ if (ch == '\n') {
+ // a newline just moves the cursor.
+ if (_curX == _width - 1)
+ return 1; // deleted a newline
+ _curX = oldx;
+ _curY = oldy;
+ return 0; // it wasn't there
+ }
+
+ ln = &(_lines[_curY]);
+ if (ln->_chars[_curX] == ch) {
+ ln->_chars[_curX] = ' ';
+ ln->_attrs[_curX].clear();
+ touch(_curY);
+ return true; // deleted the char
+ } else {
+ _curX = oldx;
+ _curY = oldy;
+ return false; // it wasn't there
+ }
+}
+
+void TextGridWindow::moveCursor(const Point &pos) {
+ // If the values are negative, they're really huge positive numbers --
+ // remember that they were cast from glui32. So set them huge and
+ // let canonicalization take its course.
+ _curX = (pos.x < 0) ? 32767 : pos.x;
+ _curY = (pos.y < 0) ? 32767 : pos.y;
+}
+
+void TextGridWindow::clear() {
+ _attr.fgset = Windows::_overrideFgSet;
+ _attr.bgset = Windows::_overrideBgSet;
+ _attr.fgcolor = Windows::_overrideFgSet ? Windows::_overrideFgVal : 0;
+ _attr.bgcolor = Windows::_overrideBgSet ? Windows::_overrideBgVal : 0;
+ _attr.reverse = false;
+
+ for (int k = 0; k < _height; k++) {
+ TextGridRow &ln = _lines[k];
+ touch(k);
+ for (uint j = 0; j < ln._attrs.size(); ++j) {
+ ln._chars[j] = ' ';
+ ln._attrs[j].clear();
+ }
+ }
+
+ _curX = 0;
+ _curY = 0;
+}
+
+void TextGridWindow::click(const Point &newPos) {
+ int x = newPos.x - _bbox.left;
+ int y = newPos.y - _bbox.top;
+
+ if (_lineRequest || _charRequest || _lineRequestUni || _charRequestUni
+ || _moreRequest || _scrollRequest)
+ _windows->setFocus(this);
+
+ if (_mouseRequest) {
+ g_vm->_events->store(evtype_MouseInput, this, x / g_conf->_cellW, y / g_conf->_leading);
+ _mouseRequest = false;
+ if (g_conf->_safeClicks)
+ g_vm->_events->_forceClick = true;
+ }
+
+ if (_hyperRequest) {
+ glui32 linkval = g_vm->_selection->getHyperlink(newPos);
+ if (linkval) {
+ g_vm->_events->store(evtype_Hyperlink, this, linkval, 0);
+ _hyperRequest = false;
+ if (g_conf->_safeClicks)
+ g_vm->_events->_forceClick = true;
+ }
+ }
+}
+
+void TextGridWindow::requestLineEvent(char *buf, glui32 maxlen, glui32 initlen) {
+ if (_charRequest || _lineRequest || _charRequestUni || _lineRequestUni) {
+ warning("request_line_event: window already has keyboard request");
+ return;
+ }
+
+ _lineRequest = true;
+
+ if ((int)maxlen > (_width - _curX))
+ maxlen = (_width - _curX);
+
+ _inBuf = buf;
+ _inMax = maxlen;
+ _inLen = 0;
+ _inCurs = 0;
+ _inOrgX = _curX;
+ _inOrgY = _curY;
+ _origAttr = _attr;
+ _attr.set(style_Input);
+
+ if (initlen > maxlen)
+ initlen = maxlen;
+
+ if (initlen) {
+ TextGridRow *ln = &_lines[_inOrgY];
+
+ for (glui32 ix = 0; ix < initlen; ix++) {
+ ln->_attrs[_inOrgX + ix].set(style_Input);
+ ln->_chars[_inOrgX + ix] = buf[ix];
+ }
+
+ _inCurs += initlen;
+ _inLen += initlen;
+ _curX = _inOrgX + _inCurs;
+ _curY = _inOrgY;
+
+ touch(_inOrgY);
+ }
+
+ if (_lineTerminatorsBase && _termCt) {
+ _lineTerminators = new glui32[_termCt + 1];
+
+ if (_lineTerminators) {
+ memcpy(_lineTerminators, _lineTerminatorsBase, _termCt * sizeof(glui32));
+ _lineTerminators[_termCt] = 0;
+ }
+ }
+
+ if (g_vm->gli_register_arr)
+ _inArrayRock = (*g_vm->gli_register_arr)(buf, maxlen, "&+#!Cn");
+}
+
+void TextGridWindow::requestLineEventUni(glui32 *buf, glui32 maxlen, glui32 initlen) {
+ if (_charRequest || _lineRequest || _charRequestUni || _lineRequestUni) {
+ warning("requestLineEventUni: window already has keyboard request");
+ return;
+ }
+
+ if ((int)maxlen > (_width - _curX))
+ maxlen = (_width - _curX);
+
+ _inBuf = buf;
+ _inMax = maxlen;
+ _inLen = 0;
+ _inCurs = 0;
+ _inOrgX = _curX;
+ _inOrgY = _curY;
+ _origAttr = _attr;
+ _attr.set(style_Input);
+
+ if (initlen > maxlen)
+ initlen = maxlen;
+
+ if (initlen) {
+ TextGridRow *ln = &(_lines[_inOrgY]);
+
+ for (glui32 ix = 0; ix < initlen; ix++) {
+ ln->_attrs[_inOrgX + ix].set(style_Input);
+ ln->_chars[_inOrgX + ix] = buf[ix];
+ }
+
+ _inCurs += initlen;
+ _inLen += initlen;
+ _curX = _inOrgX + _inCurs;
+ _curY = _inOrgY;
+
+ touch(_inOrgY);
+ }
+
+ if (_lineTerminatorsBase && _termCt) {
+ _lineTerminators = new glui32[_termCt + 1];
+
+ if (_lineTerminators) {
+ memcpy(_lineTerminators, _lineTerminatorsBase, _termCt * sizeof(glui32));
+ _lineTerminators[_termCt] = 0;
+ }
+ }
+
+ if (g_vm->gli_register_arr)
+ _inArrayRock = (*g_vm->gli_register_arr)(buf, maxlen, "&+#!Iu");
+}
+
+void TextGridWindow::cancelLineEvent(Event *ev) {
+ int ix;
+ void *inbuf;
+ int inmax;
+ int unicode = _lineRequestUni;
+ gidispatch_rock_t inarrayrock;
+ TextGridRow *ln = &_lines[_inOrgY];
+ Event dummyEv;
+
+ if (!ev)
+ ev = &dummyEv;
+
+ ev->clear();
+
+ if (!_lineRequest && !_lineRequestUni)
+ return;
+
+
+ inbuf = _inBuf;
+ inmax = _inMax;
+ inarrayrock = _inArrayRock;
+
+ if (!unicode) {
+ for (ix = 0; ix < _inLen; ix++) {
+ glui32 ch = ln->_chars[_inOrgX + ix];
+ if (ch > 0xff)
+ ch = '?';
+ ((char *)inbuf)[ix] = (char)ch;
+ }
+ if (_echoStream)
+ _echoStream->echoLine((char *)_inBuf, _inLen);
+ } else {
+ for (ix = 0; ix < _inLen; ix++)
+ ((glui32 *)inbuf)[ix] = ln->_chars[_inOrgX + ix];
+ if (_echoStream)
+ _echoStream->echoLineUni((glui32 *)inbuf, _inLen);
+ }
+
+ _curY = _inOrgY + 1;
+ _curX = 0;
+ _attr = _origAttr;
+
+ ev->type = evtype_LineInput;
+ ev->window = this;
+ ev->val1 = _inLen;
+ ev->val2 = 0;
+
+ _lineRequest = false;
+ _lineRequestUni = false;
+
+ if (_lineTerminators) {
+ free(_lineTerminators);
+ _lineTerminators = nullptr;
+ }
+
+ _inBuf = nullptr;
+ _inMax = 0;
+ _inOrgX = 0;
+ _inOrgY = 0;
+
+ if (g_vm->gli_unregister_arr)
+ (*g_vm->gli_unregister_arr)(inbuf, inmax, unicode ? "&+#!Iu" : "&+#!Cn", inarrayrock);
+}
+
+void TextGridWindow::acceptReadChar(glui32 arg) {
+ glui32 key;
+
+ switch (arg) {
+ case keycode_Erase:
+ key = keycode_Delete;
+ break;
+ case keycode_MouseWheelUp:
+ case keycode_MouseWheelDown:
+ return;
+ default:
+ key = arg;
+ }
+
+ if (key > 0xff && key < (0xffffffff - keycode_MAXVAL + 1)) {
+ if (!(_charRequestUni) || key > 0x10ffff)
+ key = keycode_Unknown;
+ }
+
+ _charRequest = false;
+ _charRequestUni = false;
+ g_vm->_events->store(evtype_CharInput, this, key, 0);
+}
+
+void TextGridWindow::acceptLine(glui32 keycode) {
+ int ix;
+ void *inbuf;
+ int inmax;
+ gidispatch_rock_t inarrayrock;
+ TextGridRow *ln = &(_lines[_inOrgY]);
+ int unicode = _lineRequestUni;
+
+ if (!_inBuf)
+ return;
+
+ inbuf = _inBuf;
+ inmax = _inMax;
+ inarrayrock = _inArrayRock;
+
+ if (!unicode) {
+ for (ix = 0; ix < _inLen; ix++)
+ ((char *)inbuf)[ix] = (char)ln->_chars[_inOrgX + ix];
+ if (_echoStream)
+ _echoStream->echoLine((char *)inbuf, _inLen);
+ } else {
+ for (ix = 0; ix < _inLen; ix++)
+ ((glui32 *)inbuf)[ix] = ln->_chars[_inOrgX + ix];
+ if (_echoStream)
+ _echoStream->echoLineUni((glui32 *)inbuf, _inLen);
+ }
+
+ _curY = _inOrgY + 1;
+ _curX = 0;
+ _attr = _origAttr;
+
+ if (_lineTerminators) {
+ glui32 val2 = keycode;
+ if (val2 == keycode_Return)
+ val2 = 0;
+ g_vm->_events->store(evtype_LineInput, this, _inLen, val2);
+ free(_lineTerminators);
+ _lineTerminators = nullptr;
+ } else {
+ g_vm->_events->store(evtype_LineInput, this, _inLen, 0);
+ }
+ _lineRequest = false;
+ _lineRequestUni = false;
+ _inBuf = nullptr;
+ _inMax = 0;
+ _inOrgX = 0;
+ _inOrgY = 0;
+
+ if (g_vm->gli_unregister_arr)
+ (*g_vm->gli_unregister_arr)(inbuf, inmax, unicode ? "&+#!Iu" : "&+#!Cn", inarrayrock);
+}
+
+void TextGridWindow::acceptReadLine(glui32 arg) {
+ int ix;
+ TextGridRow *ln = &(_lines[_inOrgY]);
+
+ if (!_inBuf)
+ return;
+
+ if (_lineTerminators && checkTerminator(arg)) {
+ glui32 *cx;
+ for (cx = _lineTerminators; *cx; cx++) {
+ if (*cx == arg) {
+ acceptLine(arg);
+ return;
+ }
+ }
+ }
+
+ switch (arg) {
+
+ // Delete keys, during line input.
+ case keycode_Delete:
+ if (_inLen <= 0)
+ return;
+ if (_inCurs <= 0)
+ return;
+ for (ix = _inCurs; ix < _inLen; ix++)
+ ln->_chars[_inOrgX + ix - 1] = ln->_chars[_inOrgX + ix];
+ ln->_chars[_inOrgX + _inLen - 1] = ' ';
+ _inCurs--;
+ _inLen--;
+ break;
+
+ case keycode_Erase:
+ if (_inLen <= 0)
+ return;
+ if (_inCurs >= _inLen)
+ return;
+ for (ix = _inCurs; ix < _inLen - 1; ix++)
+ ln->_chars[_inOrgX + ix] = ln->_chars[_inOrgX + ix + 1];
+ ln->_chars[_inOrgX + _inLen - 1] = ' ';
+ _inLen--;
+ break;
+
+ case keycode_Escape:
+ if (_inLen <= 0)
+ return;
+ for (ix = 0; ix < _inLen; ix++)
+ ln->_chars[_inOrgX + ix] = ' ';
+ _inLen = 0;
+ _inCurs = 0;
+ break;
+
+ // Cursor movement keys, during line input.
+ case keycode_Left:
+ if (_inCurs <= 0)
+ return;
+ _inCurs--;
+ break;
+
+ case keycode_Right:
+ if (_inCurs >= _inLen)
+ return;
+ _inCurs++;
+ break;
+
+ case keycode_Home:
+ if (_inCurs <= 0)
+ return;
+ _inCurs = 0;
+ break;
+
+ case keycode_End:
+ if (_inCurs >= _inLen)
+ return;
+ _inCurs = _inLen;
+ break;
+
+ case keycode_Return:
+ acceptLine(arg);
+ break;
+
+ default:
+ if (_inLen >= _inMax)
+ return;
+
+ if (arg < 32 || arg > 0xff)
+ return;
+
+ if (g_conf->_caps && (arg > 0x60 && arg < 0x7b))
+ arg -= 0x20;
+
+ for (ix = _inLen; ix > _inCurs; ix--)
+ ln->_chars[_inOrgX + ix] = ln->_chars[_inOrgX + ix - 1];
+ ln->_attrs[_inOrgX + _inLen].set(style_Input);
+ ln->_chars[_inOrgX + _inCurs] = arg;
+
+ _inCurs++;
+ _inLen++;
+ }
+
+ _curX = _inOrgX + _inCurs;
+ _curY = _inOrgY;
+
+ touch(_inOrgY);
+}
+
+void TextGridWindow::redraw() {
+ TextGridRow *ln;
+ int x0, y0;
+ int x, y, w;
+ int i, a, b, k, o;
+ glui32 link;
+ int font;
+ byte *fgcolor, *bgcolor;
+ Screen &screen = *g_vm->_screen;
+
+ Window::redraw();
+
+ x0 = _bbox.left;
+ y0 = _bbox.top;
+
+ for (i = 0; i < _height; i++) {
+ ln = &_lines[i];
+ if (ln->dirty || Windows::_forceRedraw) {
+ ln->dirty = false;
+
+ x = x0;
+ y = y0 + i * g_conf->_leading;
+
+ // clear any stored hyperlink coordinates
+ g_vm->_selection->putHyperlink(0, x0, y, x0 + g_conf->_cellW * _width, y + g_conf->_leading);
+
+ a = 0;
+ for (b = 0; b < _width; b++) {
+ if (ln->_attrs[a] != ln->_attrs[b]) {
+ link = ln->_attrs[a].hyper;
+ font = ln->_attrs[a].attrFont(_styles);
+ fgcolor = link ? g_conf->_linkColor : ln->_attrs[a].attrFg(_styles);
+ bgcolor = ln->_attrs[a].attrBg(_styles);
+ w = (b - a) * g_conf->_cellW;
+ screen.fillRect(Rect::fromXYWH(x, y, w, g_conf->_leading), bgcolor);
+ o = x;
+
+ for (k = a, o = x; k < b; k++, o += g_conf->_cellW) {
+ screen.drawStringUni(Point(o * GLI_SUBPIX, y + g_conf->_baseLine), font,
+ fgcolor, Common::U32String(&ln->_chars[k], 1), -1);
+ }
+ if (link) {
+ screen.fillRect(Rect::fromXYWH(x, y + g_conf->_baseLine + 1, w,
+ g_conf->_linkStyle), g_conf->_linkColor);
+ g_vm->_selection->putHyperlink(link, x, y, x + w, y + g_conf->_leading);
+ }
+
+ x += w;
+ a = b;
+ }
+ }
+ link = ln->_attrs[a].hyper;
+ font = ln->_attrs[a].attrFont(_styles);
+ fgcolor = link ? g_conf->_linkColor : ln->_attrs[a].attrFg(_styles);
+ bgcolor = ln->_attrs[a].attrBg(_styles);
+ w = (b - a) * g_conf->_cellW;
+ w += _bbox.right - (x + w);
+ screen.fillRect(Rect::fromXYWH(x, y, w, g_conf->_leading), bgcolor);
+
+ for (k = a, o = x; k < b; k++, o += g_conf->_cellW) {
+ screen.drawStringUni(Point(o * GLI_SUBPIX, y + g_conf->_baseLine), font,
+ fgcolor, Common::U32String(&ln->_chars[k], 1));
+ }
+ if (link) {
+ screen.fillRect(Rect::fromXYWH(x, y + g_conf->_baseLine + 1, w, g_conf->_linkStyle),
+ g_conf->_linkColor);
+ g_vm->_selection->putHyperlink(link, x, y, x + w, y + g_conf->_leading);
+ }
+ }
+ }
+}
+
+void TextGridWindow::getSize(glui32 *width, glui32 *height) const {
+ if (width)
+ *width = _bbox.width() / g_conf->_cellW;
+ if (height)
+ *height = _bbox.height() / g_conf->_cellH;
+}
+
+/*--------------------------------------------------------------------------*/
+
+void TextGridWindow::TextGridRow::resize(size_t newSize) {
+ _chars.clear();
+ _attrs.clear();
+ _chars.resize(newSize);
+ _attrs.resize(newSize);
+ Common::fill(&_chars[0], &_chars[0] + newSize, ' ');
+}
+
+} // End of namespace Gargoyle