scummvm/engines/glk/window_text_buffer.cpp
Paul Gilbert 7637edb5a2 GLK: Change all references to glui32 and glsi32 to uint and int
This should finally fix compilation for the Amiga, which had
difference sized types. And the renamed types are clearer for
ScummVM anyway
2018-12-09 11:36:20 -08:00

1624 lines
38 KiB
C++

/* 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_buffer.h"
#include "glk/conf.h"
#include "glk/glk.h"
#include "glk/screen.h"
#include "glk/selection.h"
#include "glk/unicode.h"
namespace Glk {
/**
*
* How many pixels we add to left/right margins
*/
#define SLOP (2 * GLI_SUBPIX)
TextBufferWindow::TextBufferWindow(Windows *windows, uint32 rock) : Window(windows, rock),
_historyPos(0), _historyFirst(0), _historyPresent(0), _lastSeen(0), _scrollPos(0),
_scrollMax(0), _scrollBack(SCROLLBACK), _width(-1), _height(-1), _inBuf(nullptr),
_lineTerminators(nullptr), _echoLineInput(true), _ladjw(0), _radjw(0), _ladjn(0),
_radjn(0), _numChars(0), _chars(nullptr), _attrs(nullptr),
_spaced(0), _dashed(0), _copyBuf(0), _copyPos(0) {
_type = wintype_TextBuffer;
_history.resize(HISTORYLEN);
_lines.resize(SCROLLBACK);
_chars = _lines[0]._chars;
_attrs = _lines[0]._attrs;
Common::copy(&g_conf->_tStyles[0], &g_conf->_tStyles[style_NUMSTYLES], _styles);
}
TextBufferWindow::~TextBufferWindow() {
if (_inBuf) {
if (g_vm->gli_unregister_arr)
(*g_vm->gli_unregister_arr)(_inBuf, _inMax, "&+#!Cn", _inArrayRock);
_inBuf = nullptr;
}
delete[] _copyBuf;
delete[] _lineTerminators;
for (int i = 0; i < _scrollBack; i++) {
if (_lines[i]._lPic)
_lines[i]._lPic->decrement();
if (_lines[i]._rPic)
_lines[i]._rPic->decrement();
}
}
void TextBufferWindow::rearrange(const Rect &box) {
Window::rearrange(box);
int newwid, newhgt;
int rnd;
newwid = (box.width() - g_conf->_tMarginX * 2 - g_conf->_scrollWidth) / g_conf->_cellW;
newhgt = (box.height() - g_conf->_tMarginY * 2) / g_conf->_cellH;
// align text with bottom
rnd = newhgt * g_conf->_cellH + g_conf->_tMarginY * 2;
_yAdj = (box.height() - rnd);
_bbox.top += (box.height() - rnd);
if (newwid != _width) {
_width = newwid;
reflow();
}
if (newhgt != _height) {
// scroll up if we obscure new lines
if (_lastSeen >= newhgt - 1)
_scrollPos += (_height - newhgt);
_height = newhgt;
// keep window within 'valid' lines
if (_scrollPos > _scrollMax - _height + 1)
_scrollPos = _scrollMax - _height + 1;
if (_scrollPos < 0)
_scrollPos = 0;
touchScroll();
// allocate copy buffer
if (_copyBuf)
delete[] _copyBuf;
_copyBuf = new uint[_height * TBLINELEN];
for (int i = 0; i < (_height * TBLINELEN); i++)
_copyBuf[i] = 0;
_copyPos = 0;
}
}
void TextBufferWindow::reflow() {
int inputbyte = -1;
Attributes curattr, oldattr;
int i, k, p, s;
int x;
if (_height < 4 || _width < 20)
return;
_lines[0]._len = _numChars;
// allocate temp buffers
Attributes *attrbuf = new Attributes[SCROLLBACK * TBLINELEN];
uint *charbuf = new uint[SCROLLBACK * TBLINELEN];
int *alignbuf = new int[SCROLLBACK];
Picture **pictbuf = new Picture *[SCROLLBACK];
uint *hyperbuf = new uint[SCROLLBACK];
int *offsetbuf = new int[SCROLLBACK];
if (!attrbuf || !charbuf || !alignbuf || !pictbuf || !hyperbuf || !offsetbuf) {
delete[] attrbuf;
delete[] charbuf;
delete[] alignbuf;
delete[] pictbuf;
delete[] hyperbuf;
delete[] offsetbuf;
return;
}
// copy text to temp buffers
oldattr = _attr;
curattr.clear();
x = 0;
p = 0;
s = _scrollMax < SCROLLBACK ? _scrollMax : SCROLLBACK - 1;
for (k = s; k >= 0; k--) {
if (k == 0 && _lineRequest)
inputbyte = p + _inFence;
if (_lines[k]._lPic) {
offsetbuf[x] = p;
alignbuf[x] = imagealign_MarginLeft;
pictbuf[x] = _lines[k]._lPic;
if (pictbuf[x]) pictbuf[x]->increment();
hyperbuf[x] = _lines[k]._lHyper;
x++;
}
if (_lines[k]._rPic) {
offsetbuf[x] = p;
alignbuf[x] = imagealign_MarginRight;
pictbuf[x] = _lines[k]._rPic;
if (pictbuf[x]) pictbuf[x]->increment();
hyperbuf[x] = _lines[k]._rHyper;
x++;
}
for (i = 0; i < _lines[k]._len; i++) {
attrbuf[p] = curattr = _lines[k]._attrs[i];
charbuf[p] = _lines[k]._chars[i];
p++;
}
if (_lines[k]._newLine) {
attrbuf[p] = curattr;
charbuf[p] = '\n';
p++;
}
}
offsetbuf[x] = -1;
// clear window
clear();
// and dump text back
x = 0;
for (i = 0; i < p; i++) {
if (i == inputbyte)
break;
_attr = attrbuf[i];
if (offsetbuf[x] == i) {
putPicture(pictbuf[x], alignbuf[x], hyperbuf[x]);
x++;
}
putCharUni(charbuf[i]);
}
// terribly sorry about this...
_lastSeen = 0;
_scrollPos = 0;
if (inputbyte != -1) {
_inFence = _numChars;
putTextUni(charbuf + inputbyte, p - inputbyte, _numChars, 0);
_inCurs = _numChars;
}
// free temp buffers
delete[] attrbuf;
delete[] charbuf;
delete[] alignbuf;
delete[] pictbuf;
delete[] hyperbuf;
delete[] offsetbuf;
_attr = oldattr;
touchScroll();
}
void TextBufferWindow::touchScroll() {
g_vm->_selection->clearSelection();
_windows->repaint(_bbox);
for (int i = 0; i < _scrollMax; i++)
_lines[i]._dirty = true;
}
bool TextBufferWindow::putPicture(Picture *pic, uint align, uint linkval) {
if (align == imagealign_MarginRight) {
if (_lines[0]._rPic || _numChars)
return false;
_radjw = (pic->w + g_conf->_tMarginX) * GLI_SUBPIX;
_radjn = (pic->h + g_conf->_cellH - 1) / g_conf->_cellH;
_lines[0]._rPic = pic;
_lines[0]._rm = _radjw;
_lines[0]._rHyper = linkval;
} else {
if (align != imagealign_MarginLeft && _numChars)
putCharUni('\n');
if (_lines[0]._lPic || _numChars)
return false;
_ladjw = (pic->w + g_conf->_tMarginX) * GLI_SUBPIX;
_ladjn = (pic->h + g_conf->_cellH - 1) / g_conf->_cellH;
_lines[0]._lPic = pic;
_lines[0]._lm = _ladjw;
_lines[0]._lHyper = linkval;
if (align != imagealign_MarginLeft)
flowBreak();
}
return true;
}
uint TextBufferWindow::drawPicture(uint image, uint align, uint scaled, uint width, uint height) {
Picture *pic;
uint hyperlink;
int error;
pic = g_vm->_pictures->load(image);
if (!pic)
return false;
if (!_imageLoaded) {
g_vm->_pictures->increment();
_imageLoaded = true;
}
if (scaled) {
Picture *tmp;
tmp = g_vm->_pictures->scale(pic, width, height);
pic = tmp;
}
hyperlink = _attr.hyper;
pic->increment();
error = putPicture(pic, align, hyperlink);
return error;
}
void TextBufferWindow::putText(const char *buf, int len, int pos, int oldlen) {
int diff = len - oldlen;
if (_numChars + diff >= TBLINELEN)
return;
if (diff != 0 && pos + oldlen < _numChars) {
memmove(_chars + pos + len,
_chars + pos + oldlen,
(_numChars - (pos + oldlen)) * 4);
memmove(_attrs + pos + len,
_attrs + pos + oldlen,
(_numChars - (pos + oldlen)) * sizeof(Attributes));
}
if (len > 0) {
for (int i = 0; i < len; i++) {
_chars[pos + i] = buf[i];
_attrs[pos + i].set(style_Input);
}
}
_numChars += diff;
if (_inBuf) {
if (_inCurs >= pos + oldlen)
_inCurs += diff;
else if (_inCurs >= pos)
_inCurs = pos + len;
}
touch(0);
}
void TextBufferWindow::putTextUni(const uint *buf, int len, int pos, int oldlen) {
int diff = len - oldlen;
if (_numChars + diff >= TBLINELEN)
return;
if (diff != 0 && pos + oldlen < _numChars) {
memmove(_chars + pos + len,
_chars + pos + oldlen,
(_numChars - (pos + oldlen)) * 4);
memmove(_attrs + pos + len,
_attrs + pos + oldlen,
(_numChars - (pos + oldlen)) * sizeof(Attributes));
}
if (len > 0) {
int i;
memmove(_chars + pos, buf, len * 4);
for (i = 0; i < len; i++)
_attrs[pos + i].set(style_Input);
}
_numChars += diff;
if (_inBuf) {
if (_inCurs >= pos + oldlen)
_inCurs += diff;
else if (_inCurs >= pos)
_inCurs = pos + len;
}
touch(0);
}
void TextBufferWindow::touch(int line) {
_lines[line]._dirty = true;
g_vm->_selection->clearSelection();
int y = _bbox.top + g_conf->_tMarginY + (_height - line - 1) * g_conf->_leading;
_windows->repaint(Rect(_bbox.left, y - 2, _bbox.right, y + g_conf->_leading + 2));
}
uint TextBufferWindow::getSplit(uint size, bool vertical) const {
return (vertical) ? size * g_conf->_cellW : size * g_conf->_cellH;
}
void TextBufferWindow::putCharUni(uint ch) {
uint bchars[TBLINELEN];
Attributes battrs[TBLINELEN];
int pw;
int bpoint;
int saved;
int i;
int linelen;
byte *color;
gli_tts_speak(&ch, 1);
pw = (_bbox.right - _bbox.left - g_conf->_tMarginX * 2 - g_conf->_scrollWidth) * GLI_SUBPIX;
pw = pw - 2 * SLOP - _radjw - _ladjw;
color = Windows::_overrideBgSet ? g_conf->_windowColor : _bgColor;
// oops ... overflow
if (_numChars + 1 >= TBLINELEN)
scrollOneLine(0);
if (ch == '\n') {
scrollOneLine(1);
return;
}
if (g_conf->_quotes) {
// fails for 'tis a wonderful day in the '80s
if (g_conf->_quotes > 1 && ch == '\'') {
if (_numChars == 0 || leftquote(_chars[_numChars - 1]))
ch = UNI_LSQUO;
}
if (ch == '`')
ch = UNI_LSQUO;
if (ch == '\'')
ch = UNI_RSQUO;
if (ch == '"') {
if (_numChars == 0 || leftquote(_chars[_numChars - 1]))
ch = UNI_LDQUO;
else
ch = UNI_RDQUO;
}
}
if (g_conf->_dashes && _attr.style != style_Preformatted) {
if (ch == '-') {
_dashed++;
if (_dashed == 2) {
_numChars--;
if (g_conf->_dashes == 2)
ch = UNI_NDASH;
else
ch = UNI_MDASH;
}
if (_dashed == 3) {
_numChars--;
ch = UNI_MDASH;
_dashed = 0;
}
} else {
_dashed = 0;
}
}
if (g_conf->_spaces && _attr.style != style_Preformatted
&& _styles[_attr.style].bg == color
&& !_styles[_attr.style].reverse) {
// turn (period space space) into (period space)
if (g_conf->_spaces == 1) {
if (ch == '.')
_spaced = 1;
else if (ch == ' ' && _spaced == 1)
_spaced = 2;
else if (ch == ' ' && _spaced == 2) {
_spaced = 0;
return;
} else {
_spaced = 0;
}
}
// Turn (per sp x) into (per sp sp x)
if (g_conf->_spaces == 2) {
if (ch == '.')
_spaced = 1;
else if (ch == ' ' && _spaced == 1)
_spaced = 2;
else if (ch != ' ' && _spaced == 2) {
_spaced = 0;
putCharUni(' ');
} else {
_spaced = 0;
}
}
}
_chars[_numChars] = ch;
_attrs[_numChars] = _attr;
_numChars++;
// kill spaces at the end for line width calculation
linelen = _numChars;
while (linelen > 1 && _chars[linelen - 1] == ' '
&& _styles[_attrs[linelen - 1].style].bg == color
&& !_styles[_attrs[linelen - 1].style].reverse)
linelen--;
if (calcWidth(_chars, _attrs, 0, linelen, -1) >= pw) {
bpoint = _numChars;
for (i = _numChars - 1; i > 0; i--) {
if (_chars[i] == ' ') {
bpoint = i + 1; // skip space
break;
}
}
saved = _numChars - bpoint;
memcpy(bchars, _chars + bpoint, saved * 4);
memcpy(battrs, _attrs + bpoint, saved * sizeof(Attributes));
_numChars = bpoint;
scrollOneLine(0);
memcpy(_chars, bchars, saved * 4);
memcpy(_attrs, battrs, saved * sizeof(Attributes));
_numChars = saved;
}
touch(0);
}
bool TextBufferWindow::unputCharUni(uint32 ch) {
if (_numChars > 0 && _chars[_numChars - 1] == ch) {
_numChars--;
touch(0);
return true;
}
return false;
}
void TextBufferWindow::clear() {
int i;
_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;
_ladjw = _radjw = 0;
_ladjn = _radjn = 0;
_spaced = 0;
_dashed = 0;
_numChars = 0;
for (i = 0; i < _scrollBack; i++) {
_lines[i]._len = 0;
if (_lines[i]._lPic) _lines[i]._lPic->decrement();
_lines[i]._lPic = nullptr;
if (_lines[i]._rPic) _lines[i]._rPic->decrement();
_lines[i]._rPic = nullptr;
_lines[i]._lHyper = 0;
_lines[i]._rHyper = 0;
_lines[i]._lm = 0;
_lines[i]._rm = 0;
_lines[i]._newLine = 0;
_lines[i]._dirty = true;
_lines[i]._repaint = false;
}
_lastSeen = 0;
_scrollPos = 0;
_scrollMax = 0;
for (i = 0; i < _height; i++)
touch(i);
}
void TextBufferWindow::click(const Point &newPos) {
int gh = false;
int gs = false;
if (_lineRequest || _charRequest
|| _lineRequestUni || _charRequestUni
|| _moreRequest || _scrollRequest)
_windows->setFocus(this);
if (_hyperRequest) {
uint 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 = 1;
gh = true;
}
}
if (newPos.x > _bbox.right - g_conf->_scrollWidth) {
if (newPos.y < _bbox.top + g_conf->_tMarginY + g_conf->_scrollWidth)
acceptScroll(keycode_Up);
else if (newPos.y > _bbox.bottom - g_conf->_tMarginY - g_conf->_scrollWidth)
acceptScroll(keycode_Down);
else if (newPos.y < (_bbox.top + _bbox.bottom) / 2)
acceptScroll(keycode_PageUp);
else
acceptScroll(keycode_PageDown);
gs = true;
}
if (!gh && !gs) {
g_vm->_copySelect = true;
g_vm->_selection->startSelection(newPos);
}
}
void TextBufferWindow::requestLineEvent(char *buf, uint maxlen, uint initlen) {
if (_charRequest || _lineRequest || _charRequestUni || _lineRequestUni) {
warning("request_line_event: window already has keyboard request");
return;
}
_lineRequest = true;
int pw;
gli_tts_flush();
// because '>' prompt is ugly without extra space
if (_numChars && _chars[_numChars - 1] == '>')
putCharUni(' ');
if (_numChars && _chars[_numChars - 1] == '?')
putCharUni(' ');
// make sure we have some space left for typing...
pw = (_bbox.right - _bbox.left - g_conf->_tMarginX * 2) * GLI_SUBPIX;
pw = pw - 2 * SLOP - _radjw + _ladjw;
if (calcWidth(_chars, _attrs, 0, _numChars, -1) >= pw * 3 / 4)
putCharUni('\n');
_inBuf = buf;
_inMax = maxlen;
_inFence = _numChars;
_inCurs = _numChars;
_origAttr = _attr;
_attr.set(style_Input);
_historyPos = _historyPresent;
if (initlen) {
touch(0);
putText(buf, initlen, _inCurs, 0);
}
// WORKAROUND: Mark bottom line as dirty so caret will be drawn
_lines[0]._dirty = true;
_echoLineInput = _echoLineInputBase;
if (_lineTerminatorsBase && _termCt) {
_lineTerminators = new uint[_termCt + 1];
if (_lineTerminators) {
memcpy(_lineTerminators, _lineTerminatorsBase, _termCt * sizeof(uint));
_lineTerminators[_termCt] = 0;
}
}
if (g_vm->gli_register_arr)
_inArrayRock = (*g_vm->gli_register_arr)(buf, maxlen, "&+#!Cn");
}
void TextBufferWindow::requestLineEventUni(uint *buf, uint maxlen, uint initlen) {
if (_charRequest || _lineRequest || _charRequestUni || _lineRequestUni) {
warning("request_line_event_uni: window already has keyboard request");
return;
}
int pw;
_lineRequestUni = true;
gli_tts_flush();
// because '>' prompt is ugly without extra space
if (_numChars && _chars[_numChars - 1] == '>')
putCharUni(' ');
if (_numChars && _chars[_numChars - 1] == '?')
putCharUni(' ');
// make sure we have some space left for typing...
pw = (_bbox.right - _bbox.left - g_conf->_tMarginX * 2) * GLI_SUBPIX;
pw = pw - 2 * SLOP - _radjw + _ladjw;
if (calcWidth(_chars, _attrs, 0, _numChars, -1) >= pw * 3 / 4)
putCharUni('\n');
//_lastSeen = 0;
_inBuf = buf;
_inMax = maxlen;
_inFence = _numChars;
_inCurs = _numChars;
_origAttr = _attr;
_attr.set(style_Input);
_historyPos = _historyPresent;
if (initlen) {
touch(0);
putTextUni(buf, initlen, _inCurs, 0);
}
_echoLineInput = _echoLineInputBase;
if (_lineTerminatorsBase && _termCt) {
_lineTerminators = new uint[_termCt + 1];
if (_lineTerminators) {
memcpy(_lineTerminators, _lineTerminatorsBase, _termCt * sizeof(uint));
_lineTerminators[_termCt] = 0;
}
}
if (g_vm->gli_register_arr)
_inArrayRock = (*g_vm->gli_register_arr)(buf, maxlen, "&+#!Iu");
}
void TextBufferWindow::cancelLineEvent(Event *ev) {
gidispatch_rock_t inarrayrock;
int ix;
int len;
void *inbuf;
int inmax;
int unicode = _lineRequestUni;
Event dummyEv;
if (!ev)
ev = &dummyEv;
ev->clear();
if (!_lineRequest && !_lineRequestUni)
return;
if (!_inBuf)
return;
inbuf = _inBuf;
inmax = _inMax;
inarrayrock = _inArrayRock;
len = _numChars - _inFence;
if (_echoStream)
_echoStream->echoLineUni(_chars + _inFence, len);
if (len > inmax)
len = inmax;
if (!unicode) {
for (ix = 0; ix < len; ix++) {
uint ch = _chars[_inFence + ix];
if (ch > 0xff)
ch = '?';
((char *)inbuf)[ix] = (char)ch;
}
} else {
for (ix = 0; ix < len; ix++)
((uint *)inbuf)[ix] = _chars[_inFence + ix];
}
_attr = _origAttr;
ev->type = evtype_LineInput;
ev->window = this;
ev->val1 = len;
ev->val2 = 0;
_lineRequest = false;
_lineRequestUni = false;
if (_lineTerminators) {
free(_lineTerminators);
_lineTerminators = nullptr;
}
_inBuf = nullptr;
_inMax = 0;
if (_echoLineInput) {
putCharUni('\n');
} else {
_numChars = _inFence;
touch(0);
}
if (g_vm->gli_unregister_arr)
(*g_vm->gli_unregister_arr)(inbuf, inmax, unicode ? "&+#!Iu" : "&+#!Cn", inarrayrock);
}
void TextBufferWindow::redraw() {
TextBufferRow *ln;
int linelen;
int nsp, spw, pw;
int x0, y0, x1, y1;
int x, y, w;
int a, b;
uint link;
int font;
unsigned char *color;
int i;
int hx0, hx1, hy0, hy1;
int selrow, selchar, sx0, sx1, selleft, selright;
bool selBuf;
int tx, tsc, tsw, lsc, rsc;
Screen &screen = *g_vm->_screen;
Window::redraw();
_lines[0]._len = _numChars;
sx0 = sx1 = selleft = selright = 0;
ln = new TextBufferRow();
if (!ln)
return;
x0 = (_bbox.left + g_conf->_tMarginX) * GLI_SUBPIX;
x1 = (_bbox.right - g_conf->_tMarginX - g_conf->_scrollWidth) * GLI_SUBPIX;
y0 = _bbox.top + g_conf->_tMarginY;
y1 = _bbox.bottom - g_conf->_tMarginY;
pw = x1 - x0 - 2 * GLI_SUBPIX;
// check if any part of buffer is selected
selBuf = g_vm->_selection->checkSelection(Rect(x0 / GLI_SUBPIX, y0, x1 / GLI_SUBPIX, y1));
for (i = _scrollPos + _height - 1; i >= _scrollPos; i--) {
// top of line
y = y0 + (_height - (i - _scrollPos) - 1) * g_conf->_leading;
// check if part of line is selected
if (selBuf) {
selrow = g_vm->_selection->getSelection(Rect(x0 / GLI_SUBPIX, y,
x1 / GLI_SUBPIX, y + g_conf->_leading), &sx0, &sx1);
selleft = (sx0 == x0 / GLI_SUBPIX);
selright = (sx1 == x1 / GLI_SUBPIX);
} else {
selrow = false;
}
// mark selected line dirty
if (selrow)
_lines[i]._dirty = true;
memcpy(ln, &_lines[i], sizeof(TextBufferRow));
// skip if we can
if (!ln->_dirty && !ln->_repaint && !Windows::_forceRedraw && _scrollPos == 0)
continue;
// repaint previously selected lines if needed
if (ln->_repaint && !Windows::_forceRedraw)
_windows->redrawRect(Rect(x0 / GLI_SUBPIX, y,
x1 / GLI_SUBPIX, y + g_conf->_leading));
// keep selected line dirty and flag for repaint
if (!selrow) {
_lines[i]._dirty = false;
_lines[i]._repaint = false;
} else {
_lines[i]._repaint = true;
}
// leave bottom line blank for [more] prompt
if (i == _scrollPos && i > 0)
continue;
linelen = ln->_len;
// kill spaces at the end unless they're a different color
color = Windows::_overrideBgSet ? g_conf->_windowColor : _bgColor;
while (i > 0 && linelen > 1 && ln->_chars[linelen - 1] == ' '
&& _styles[ln->_attrs[linelen - 1].style].bg == color
&& !_styles[ln->_attrs[linelen - 1].style].reverse)
linelen --;
// kill characters that would overwrite the scroll bar
while (linelen > 1 && calcWidth(ln->_chars, ln->_attrs, 0, linelen, -1) >= pw)
linelen --;
/*
* count spaces and width for justification
*/
if (g_conf->_justify && !ln->_newLine && i > 0) {
for (a = 0, nsp = 0; a < linelen; a++)
if (ln->_chars[a] == ' ')
nsp ++;
w = calcWidth(ln->_chars, ln->_attrs, 0, linelen, 0);
if (nsp)
spw = (x1 - x0 - ln->_lm - ln->_rm - 2 * SLOP - w) / nsp;
else
spw = 0;
} else {
spw = -1;
}
// find and highlight selected characters
if (selrow && !Windows::_claimSelect) {
lsc = 0;
rsc = 0;
selchar = false;
// optimized case for all chars selected
if (selleft && selright) {
rsc = linelen > 0 ? linelen - 1 : 0;
selchar = calcWidth(ln->_chars, ln->_attrs, lsc, rsc, spw) / GLI_SUBPIX;
} else {
// optimized case for leftmost char selected
if (selleft) {
tsc = linelen > 0 ? linelen - 1 : 0;
selchar = calcWidth(ln->_chars, ln->_attrs, lsc, tsc, spw) / GLI_SUBPIX;
} else {
// find the substring contained by the selection
tx = (x0 + SLOP + ln->_lm) / GLI_SUBPIX;
// measure string widths until we find left char
for (tsc = 0; tsc < linelen; tsc++) {
tsw = calcWidth(ln->_chars, ln->_attrs, 0, tsc, spw) / GLI_SUBPIX;
if (tsw + tx >= sx0 ||
((tsw + tx + GLI_SUBPIX) >= sx0 && ln->_chars[tsc] != ' ')) {
lsc = tsc;
selchar = true;
break;
}
}
}
if (selchar) {
// optimized case for rightmost char selected
if (selright) {
rsc = linelen > 0 ? linelen - 1 : 0;
} else {
// measure string widths until we find right char
for (tsc = lsc; tsc < linelen; tsc++) {
tsw = calcWidth(ln->_chars, ln->_attrs, lsc, tsc, spw) / GLI_SUBPIX;
if (tsw + sx0 < sx1)
rsc = tsc;
}
if (lsc && !rsc)
rsc = lsc;
}
}
}
// reverse colors for selected chars
if (selchar) {
for (tsc = lsc; tsc <= rsc; tsc++) {
ln->_attrs[tsc].reverse = !ln->_attrs[tsc].reverse;
_copyBuf[_copyPos] = ln->_chars[tsc];
_copyPos++;
}
}
// add newline if we reach the end of the line
if (ln->_len == 0 || ln->_len == (rsc + 1)) {
_copyBuf[_copyPos] = '\n';
_copyPos++;
}
}
// clear any stored hyperlink coordinates
g_vm->_selection->putHyperlink(0, x0 / GLI_SUBPIX, y,
x1 / GLI_SUBPIX, y + g_conf->_leading);
/*
* fill in background colors
*/
color = Windows::_overrideBgSet ? g_conf->_windowColor : _bgColor;
screen.fillRect(Rect::fromXYWH(x0 / GLI_SUBPIX, y, (x1 - x0) / GLI_SUBPIX, g_conf->_leading),
color);
x = x0 + SLOP + ln->_lm;
a = 0;
for (b = 0; b < linelen; b++) {
if (ln->_attrs[a] != ln->_attrs[b]) {
link = ln->_attrs[a].hyper;
font = ln->_attrs[a].attrFont(_styles);
color = ln->_attrs[a].attrBg(_styles);
w = screen.stringWidthUni(font, Common::U32String(ln->_chars + a, b - a), spw);
screen.fillRect(Rect::fromXYWH(x / GLI_SUBPIX, y, w / GLI_SUBPIX, g_conf->_leading),
color);
if (link) {
screen.fillRect(Rect::fromXYWH(x / GLI_SUBPIX + 1, y + g_conf->_baseLine + 1,
w / GLI_SUBPIX + 1, g_conf->_linkStyle), g_conf->_linkColor);
g_vm->_selection->putHyperlink(link, x / GLI_SUBPIX, y,
x / GLI_SUBPIX + w / GLI_SUBPIX,
y + g_conf->_leading);
}
x += w;
a = b;
}
}
link = ln->_attrs[a].hyper;
font = ln->_attrs[a].attrFont(_styles);
color = ln->_attrs[a].attrBg(_styles);
w = screen.stringWidthUni(font, Common::U32String(ln->_chars + a, b - a), spw);
screen.fillRect(Rect::fromXYWH(x / GLI_SUBPIX, y, w / GLI_SUBPIX, g_conf->_leading), color);
if (link) {
screen.fillRect(Rect::fromXYWH(x / GLI_SUBPIX + 1, y + g_conf->_baseLine + 1,
w / GLI_SUBPIX + 1, g_conf->_linkStyle), g_conf->_linkColor);
g_vm->_selection->putHyperlink(link, x / GLI_SUBPIX, y,
x / GLI_SUBPIX + w / GLI_SUBPIX,
y + g_conf->_leading);
}
x += w;
color = Windows::_overrideBgSet ? g_conf->_windowColor : _bgColor;
screen.fillRect(Rect::fromXYWH(x / GLI_SUBPIX, y, x1 / GLI_SUBPIX - x / GLI_SUBPIX, g_conf->_leading), color);
/*
* draw caret
*/
if (_windows->getFocusWindow() == this && i == 0 && (_lineRequest || _lineRequestUni)) {
w = calcWidth(_chars, _attrs, 0, _inCurs, spw);
if (w < pw - g_conf->_caretShape * 2 * GLI_SUBPIX)
screen.drawCaret(Point(x0 + SLOP + ln->_lm + w, y + g_conf->_baseLine));
}
/*
* draw text
*/
x = x0 + SLOP + ln->_lm;
a = 0;
for (b = 0; b < linelen; b++) {
if (ln->_attrs[a] != ln->_attrs[b]) {
link = ln->_attrs[a].hyper;
font = ln->_attrs[a].attrFont(_styles);
color = link ? g_conf->_linkColor : ln->_attrs[a].attrFg(_styles);
x = screen.drawStringUni(Point(x, y + g_conf->_baseLine),
font, color, Common::U32String(ln->_chars + a, b - a), spw);
a = b;
}
}
link = ln->_attrs[a].hyper;
font = ln->_attrs[a].attrFont(_styles);
color = link ? g_conf->_linkColor : ln->_attrs[a].attrFg(_styles);
screen.drawStringUni(Point(x, y + g_conf->_baseLine), font, color, Common::U32String(ln->_chars + a, linelen - a), spw);
}
/*
* draw more prompt
*/
if (_scrollPos && _height > 1) {
x = x0 + SLOP;
y = y0 + (_height - 1) * g_conf->_leading;
g_vm->_selection->putHyperlink(0, x0 / GLI_SUBPIX, y,
x1 / GLI_SUBPIX, y + g_conf->_leading);
color = Windows::_overrideBgSet ? g_conf->_windowColor : _bgColor;
screen.fillRect(Rect::fromXYWH(x / GLI_SUBPIX, y, x1 / GLI_SUBPIX - x / GLI_SUBPIX, g_conf->_leading), color);
w = screen.stringWidth(g_conf->_moreFont, g_conf->_morePrompt);
if (g_conf->_moreAlign == 1)
// center
x = x0 + SLOP + (x1 - x0 - w - SLOP * 2) / 2;
if (g_conf->_moreAlign == 2)
// right
x = x1 - SLOP - w;
color = Windows::_overrideFgSet ? g_conf->_moreColor : _fgColor;
screen.drawString(Point(x, y + g_conf->_baseLine),
g_conf->_moreFont, color, g_conf->_morePrompt);
y1 = y; // don't want pictures overdrawing "[more]"
// try to claim the focus
_moreRequest = true;
Windows::_moreFocus = true;
} else {
_moreRequest = false;
y1 = y0 + _height * g_conf->_leading;
}
/*
* draw the images
*/
for (i = 0; i < _scrollBack; i++) {
memcpy(ln, &_lines[i], sizeof(TextBufferRow));
y = y0 + (_height - (i - _scrollPos) - 1) * g_conf->_leading;
if (ln->_lPic) {
if (y < y1 && y + ln->_lPic->h > y0) {
ln->_lPic->drawPicture(Point(x0 / GLI_SUBPIX, y),
Rect(x0 / GLI_SUBPIX, y0, x1 / GLI_SUBPIX, y1));
link = ln->_lHyper;
hy0 = y > y0 ? y : y0;
hy1 = y + ln->_lPic->h < y1 ? y + ln->_lPic->h : y1;
hx0 = x0 / GLI_SUBPIX;
hx1 = x0 / GLI_SUBPIX + ln->_lPic->w < x1 / GLI_SUBPIX
? x0 / GLI_SUBPIX + ln->_lPic->w
: x1 / GLI_SUBPIX;
g_vm->_selection->putHyperlink(link, hx0, hy0, hx1, hy1);
}
}
if (ln->_rPic) {
if (y < y1 && y + ln->_rPic->h > y0) {
ln->_rPic->drawPicture(Point(x1 / GLI_SUBPIX - ln->_rPic->w, y),
Rect(x0 / GLI_SUBPIX, y0, x1 / GLI_SUBPIX, y1));
link = ln->_rHyper;
hy0 = y > y0 ? y : y0;
hy1 = y + ln->_rPic->h < y1 ? y + ln->_rPic->h : y1;
hx0 = x1 / GLI_SUBPIX - ln->_rPic->w > x0 / GLI_SUBPIX
? x1 / GLI_SUBPIX - ln->_rPic->w
: x0 / GLI_SUBPIX;
hx1 = x1 / GLI_SUBPIX;
g_vm->_selection->putHyperlink(link, hx0, hy0, hx1, hy1);
}
}
}
/*
* Draw the scrollbar
*/
// try to claim scroll keys
_scrollRequest = _scrollMax > _height;
if (_scrollRequest && g_conf->_scrollWidth) {
int t0, t1;
x0 = _bbox.right - g_conf->_scrollWidth;
x1 = _bbox.right;
y0 = _bbox.top + g_conf->_tMarginY;
y1 = _bbox.bottom - g_conf->_tMarginY;
g_vm->_selection->putHyperlink(0, x0, y0, x1, y1);
y0 += g_conf->_scrollWidth / 2;
y1 -= g_conf->_scrollWidth / 2;
// pos = thbot, pos - ht = thtop, max = wtop, 0 = wbot
t0 = (_scrollMax - _scrollPos) - (_height - 1);
t1 = (_scrollMax - _scrollPos);
if (_scrollMax > _height) {
t0 = t0 * (y1 - y0) / _scrollMax + y0;
t1 = t1 * (y1 - y0) / _scrollMax + y0;
} else {
t0 = t1 = y0;
}
screen.fillRect(Rect::fromXYWH(x0 + 1, y0, x1 - x0 - 2, y1 - y0), g_conf->_scrollBg);
screen.fillRect(Rect::fromXYWH(x0 + 1, t0, x1 - x0 - 2, t1 - t0), g_conf->_scrollFg);
for (i = 0; i < g_conf->_scrollWidth / 2 + 1; i++) {
screen.fillRect(Rect::fromXYWH(x0 + g_conf->_scrollWidth / 2 - i,
y0 - g_conf->_scrollWidth / 2 + i, i * 2, 1), g_conf->_scrollFg);
screen.fillRect(Rect::fromXYWH(x0 + g_conf->_scrollWidth / 2 - i,
y1 + g_conf->_scrollWidth / 2 - i, i * 2, 1), g_conf->_scrollFg);
}
}
// Keep track of selected text to be ready when user copies it to the clipboard
if (selBuf && _copyPos) {
Windows::_claimSelect = true;
g_vm->_clipboard->clipboardStore(Common::U32String(_copyBuf, _copyPos));
for (i = 0; i < _copyPos; i++)
_copyBuf[i] = 0;
_copyPos = 0;
}
// no more prompt means all text has been seen
if (!_moreRequest)
_lastSeen = 0;
delete ln;
}
int TextBufferWindow::acceptScroll(uint arg) {
int pageht = _height - 2; // 1 for prompt, 1 for overlap
int startpos = _scrollPos;
switch (arg) {
case keycode_PageUp:
_scrollPos += pageht;
break;
case keycode_End:
_scrollPos = 0;
break;
case keycode_Up:
_scrollPos++;
break;
case keycode_Down:
case keycode_Return:
_scrollPos--;
break;
case keycode_MouseWheelUp:
_scrollPos += 3;
startpos = true;
break;
case keycode_MouseWheelDown:
_scrollPos -= 3;
startpos = true;
break;
case ' ':
case keycode_PageDown:
//default:
if (pageht)
_scrollPos -= pageht;
else
_scrollPos = 0;
break;
}
if (_scrollPos > _scrollMax - _height + 1)
_scrollPos = _scrollMax - _height + 1;
if (_scrollPos < 0)
_scrollPos = 0;
touchScroll();
return (startpos || _scrollPos);
}
void TextBufferWindow::acceptReadChar(uint arg) {
uint key;
if (_height < 2)
_scrollPos = 0;
if (_scrollPos
|| arg == keycode_PageUp
|| arg == keycode_MouseWheelUp) {
acceptScroll(arg);
return;
}
switch (arg) {
case keycode_Erase:
key = keycode_Delete;
break;
case keycode_MouseWheelUp:
case keycode_MouseWheelDown:
return;
default:
key = arg;
break;
}
gli_tts_purge();
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 TextBufferWindow::acceptReadLine(uint arg) {
uint *cx;
Common::U32String s;
int len;
if (_height < 2)
_scrollPos = 0;
if (_scrollPos || arg == keycode_PageUp || arg == keycode_MouseWheelUp) {
acceptScroll(arg);
return;
}
if (!_inBuf)
return;
if (_lineTerminators && checkTerminator(arg)) {
for (cx = _lineTerminators; *cx; cx++) {
if (*cx == arg) {
acceptLine(arg);
return;
}
}
}
switch (arg) {
// History keys (up and down)
case keycode_Up:
if (_historyPos == _historyFirst)
return;
if (_historyPos == _historyPresent) {
len = _numChars - _inFence;
if (len > 0)
s = Common::U32String(&(_chars[_inFence]), len);
_history[_historyPos] = s;
}
_historyPos--;
if (_historyPos < 0)
_historyPos += HISTORYLEN;
s = _history[_historyPos];
putTextUni(s.c_str(), s.size(), _inFence, _numChars - _inFence);
break;
case keycode_Down:
if (_historyPos == _historyPresent)
return;
_historyPos++;
if (_historyPos >= HISTORYLEN)
_historyPos -= HISTORYLEN;
s = _history[_historyPos];
putTextUni(s.c_str(), s.size(), _inFence, _numChars - _inFence);
break;
// Cursor movement keys, during line input.
case keycode_Left:
if (_inCurs <= _inFence)
return;
_inCurs--;
break;
case keycode_Right:
if (_inCurs >= _numChars)
return;
_inCurs++;
break;
case keycode_Home:
if (_inCurs <= _inFence)
return;
_inCurs = _inFence;
break;
case keycode_End:
if (_inCurs >= _numChars)
return;
_inCurs = _numChars;
break;
case keycode_SkipWordLeft:
while (_inCurs > _inFence && _chars[_inCurs - 1] == ' ')
_inCurs--;
while (_inCurs > _inFence && _chars[_inCurs - 1] != ' ')
_inCurs--;
break;
case keycode_SkipWordRight:
while (_inCurs < _numChars && _chars[_inCurs] != ' ')
_inCurs++;
while (_inCurs < _numChars && _chars[_inCurs] == ' ')
_inCurs++;
break;
// Delete keys, during line input.
case keycode_Delete:
if (_inCurs <= _inFence)
return;
putTextUni(nullptr, 0, _inCurs - 1, 1);
break;
case keycode_Erase:
if (_inCurs >= _numChars)
return;
putTextUni(nullptr, 0, _inCurs, 1);
break;
case keycode_Escape:
if (_inFence >= _numChars)
return;
putTextUni(nullptr, 0, _inFence, _numChars - _inFence);
break;
// Regular keys
case keycode_Return:
acceptLine(arg);
break;
default:
if (arg >= 32 && arg <= 0x10FFFF) {
if (g_conf->_caps && (arg > 0x60 && arg < 0x7b))
arg -= 0x20;
putTextUni(&arg, 1, _inCurs, 0);
}
break;
}
touch(0);
}
void TextBufferWindow::acceptLine(uint keycode) {
int ix;
int len, olen;
void *inbuf;
Common::U32String s, o;
int inmax;
gidispatch_rock_t inarrayrock;
int unicode = _lineRequestUni;
if (!_inBuf)
return;
inbuf = _inBuf;
inmax = _inMax;
inarrayrock = _inArrayRock;
len = _numChars - _inFence;
if (_echoStream)
_echoStream->echoLineUni(_chars + _inFence, len);
gli_tts_purge();
if (g_conf->_speakInput) {
const uint32 NEWLINE = '\n';
gli_tts_speak(_chars + _inFence, len);
gli_tts_speak((const uint *)&NEWLINE, 1);
}
/*
* Store in history.
* The history is a ring buffer, with historypresent being the index of the most recent
* element and historyfirst the index of the oldest element.
* A history entry should not repeat the string from the entry before it.
*/
if (len) {
s = Common::U32String(_chars + _inFence, len);
_history[_historyPresent].clear();
o = _history[(_historyPresent == 0 ? HISTORYLEN : _historyPresent) - 1];
olen = o.size();
if (len != olen || !s.equals(o)) {
_history[_historyPresent] = s;
_historyPresent++;
if (_historyPresent == HISTORYLEN)
_historyPresent = 0;
if (_historyPresent == _historyFirst) {
_historyFirst++;
if (_historyFirst == HISTORYLEN)
_historyFirst = 0;
}
}
}
// Store in event buffer.
if (len > inmax)
len = inmax;
if (!unicode) {
for (ix = 0; ix < len; ix++) {
uint ch = _chars[_inFence + ix];
if (ch > 0xff)
ch = '?';
((char *)inbuf)[ix] = (char)ch;
}
} else {
for (ix = 0; ix < len; ix++)
((uint *)inbuf)[ix] = _chars[_inFence + ix];
}
_attr = _origAttr;
if (_lineTerminators) {
uint val2 = keycode;
if (val2 == keycode_Return)
val2 = 0;
g_vm->_events->store(evtype_LineInput, this, len, val2);
free(_lineTerminators);
_lineTerminators = nullptr;
} else {
g_vm->_events->store(evtype_LineInput, this, len, 0);
}
_lineRequest = false;
_lineRequestUni = false;
_inBuf = nullptr;
_inMax = 0;
if (_echoLineInput) {
putCharUni('\n');
} else {
_numChars = _inFence;
touch(0);
}
if (g_vm->gli_unregister_arr)
(*g_vm->gli_unregister_arr)(inbuf, inmax, unicode ? "&+#!Iu" : "&+#!Cn", inarrayrock);
}
bool TextBufferWindow::leftquote(uint c) {
switch (c) {
case '(':
case '[':
// The following are Unicode characters in the "Separator, Space" category.
case 0x0020:
case 0x00a0:
case 0x1680:
case 0x2000:
case 0x2001:
case 0x2002:
case 0x2003:
case 0x2004:
case 0x2005:
case 0x2006:
case 0x2007:
case 0x2008:
case 0x2009:
case 0x200a:
case 0x202f:
case 0x205f:
case 0x3000:
return true;
default:
return false;
}
}
void TextBufferWindow::scrollOneLine(bool forced) {
_lastSeen++;
_scrollMax++;
if (_scrollMax > _scrollBack - 1
|| _lastSeen > _scrollBack - 1)
scrollResize();
if (_lastSeen >= _height)
_scrollPos++;
if (_scrollPos > _scrollMax - _height + 1)
_scrollPos = _scrollMax - _height + 1;
if (_scrollPos < 0)
_scrollPos = 0;
if (forced)
_dashed = 0;
_spaced = 0;
_lines[0]._len = _numChars;
_lines[0]._newLine = forced;
for (int i = _scrollBack - 1; i > 0; i--) {
memcpy(&_lines[i], &_lines[i - 1], sizeof(TextBufferRow));
if (i < _height)
touch(i);
}
if (_radjn)
_radjn--;
if (_radjn == 0)
_radjw = 0;
if (_ladjn)
_ladjn--;
if (_ladjn == 0)
_ladjw = 0;
touch(0);
_lines[0]._len = 0;
_lines[0]._newLine = 0;
_lines[0]._lm = _ladjw;
_lines[0]._rm = _radjw;
_lines[0]._lPic = nullptr;
_lines[0]._rPic = nullptr;
_lines[0]._lHyper = 0;
_lines[0]._rHyper = 0;
memset(_chars, ' ', TBLINELEN * 4);
memset(_attrs, 0, TBLINELEN * sizeof(Attributes));
_numChars = 0;
touchScroll();
}
void TextBufferWindow::scrollResize() {
int i;
_lines.clear();
_lines.resize(_scrollBack + SCROLLBACK);
_chars = _lines[0]._chars;
_attrs = _lines[0]._attrs;
for (i = _scrollBack; i < (_scrollBack + SCROLLBACK); i++) {
_lines[i]._dirty = false;
_lines[i]._repaint = false;
_lines[i]._lm = 0;
_lines[i]._rm = 0;
_lines[i]._lPic = 0;
_lines[i]._rPic = 0;
_lines[i]._lHyper = 0;
_lines[i]._rHyper = 0;
_lines[i]._len = 0;
_lines[i]._newLine = 0;
memset(_lines[i]._chars, ' ', sizeof _lines[i]._chars);
memset(_lines[i]._attrs, 0, sizeof _lines[i]._attrs);
}
_scrollBack += SCROLLBACK;
}
int TextBufferWindow::calcWidth(uint *chars, Attributes *attrs, int startchar,
int numChars, int spw) {
Screen &screen = *g_vm->_screen;
int w = 0;
int a, b;
a = startchar;
for (b = startchar; b < numChars; b++) {
if (attrs[a] != attrs[b]) {
w += screen.stringWidthUni(attrs[a].attrFont(_styles),
Common::U32String(chars + a, b - a), spw);
a = b;
}
}
w += screen.stringWidthUni(attrs[a].attrFont(_styles), Common::U32String(chars + a, b - a), spw);
return w;
}
void TextBufferWindow::getSize(uint *width, uint *height) const {
if (width)
*width = (_bbox.width() - g_conf->_tMarginX * 2) / g_conf->_cellW;
if (height)
*height = (_bbox.height() - g_conf->_tMarginY * 2) / g_conf->_cellH;
}
void TextBufferWindow::flowBreak() {
while (_ladjn || _radjn)
putCharUni('\n');
}
/*--------------------------------------------------------------------------*/
TextBufferWindow::TextBufferRow::TextBufferRow() : _len(0), _newLine(0), _dirty(false),
_repaint(false), _lPic(nullptr), _rPic(nullptr), _lHyper(0), _rHyper(0),
_lm(0), _rm(0) {
Common::fill(&_chars[0], &_chars[TBLINELEN], 0);
}
} // End of namespace Glk