scummvm/engines/glk/windows.cpp

859 lines
22 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/windows.h"
#include "glk/window_graphics.h"
#include "glk/window_pair.h"
#include "glk/window_text_buffer.h"
#include "glk/window_text_grid.h"
#include "glk/conf.h"
#include "glk/glk.h"
#include "glk/screen.h"
#include "glk/streams.h"
#include "common/algorithm.h"
#include "common/textconsole.h"
namespace Glk {
bool Windows::_overrideReverse;
bool Windows::_overrideFgSet;
bool Windows::_overrideBgSet;
bool Windows::_forceRedraw;
bool Windows::_claimSelect;
bool Windows::_moreFocus;
uint Windows::_overrideFgVal;
uint Windows::_overrideBgVal;
uint Windows::_zcolor_fg;
uint Windows::_zcolor_bg;
uint Windows::_zcolor_LightGrey;
uint Windows::_zcolor_Foreground;
uint Windows::_zcolor_Background;
uint Windows::_zcolor_Bright;
/*--------------------------------------------------------------------------*/
Windows::Windows(Graphics::Screen *screen) : _screen(screen), _windowList(nullptr),
_rootWin(nullptr), _focusWin(nullptr) {
_overrideReverse = false;
_overrideFgSet = false;
_overrideBgSet = false;
_forceRedraw = true;
_claimSelect = false;
_moreFocus = false;
_overrideFgVal = 0;
_overrideBgVal = 0;
_zcolor_fg = _zcolor_bg = 0;
_drawSelect = false;
_zcolor_LightGrey = g_system->getScreenFormat().RGBToColor(181, 181, 181);
_zcolor_Foreground = _zcolor_Background = 0;
_zcolor_Bright = 0;
}
Windows::~Windows() {
delete _rootWin;
}
Window *Windows::windowOpen(Window *splitwin, uint method, uint size,
uint wintype, uint rock) {
Window *newwin, *oldparent = nullptr;
PairWindow *pairWin;
uint val;
_forceRedraw = true;
if (!_rootWin) {
if (splitwin) {
warning("window_open: ref must be nullptr");
return nullptr;
}
// ignore method and size now
oldparent = nullptr;
} else {
if (!splitwin) {
warning("window_open: ref must not be nullptr");
return nullptr;
}
val = (method & winmethod_DivisionMask);
if (val != winmethod_Fixed && val != winmethod_Proportional) {
warning("window_open: invalid method (not fixed or proportional)");
return nullptr;
}
val = (method & winmethod_DirMask);
if (val != winmethod_Above && val != winmethod_Below && val != winmethod_Left
&& val != winmethod_Right && val != winmethod_Arbitrary) {
warning("window_open: invalid method (bad direction)");
return nullptr;
}
oldparent = splitwin->_parent;
if (oldparent && oldparent->_type != wintype_Pair) {
warning("window_open: parent window is not Pair");
return nullptr;
}
}
assert(wintype != wintype_Pair);
newwin = newWindow(wintype, rock);
if (!newwin) {
warning("window_open: unable to create window");
return nullptr;
}
if (!splitwin) {
_rootWin = newwin;
} else if (splitwin->_type == wintype_Pair) {
pairWin = static_cast<PairWindow *>(splitwin);
pairWin->_dir = winmethod_Arbitrary;
pairWin->_children.push_back(newwin);
newwin->_parent = pairWin;
} else {
// create pairWin, with newwin as the key
pairWin = newPairWindow(method, newwin, size);
pairWin->_children.push_back(splitwin);
pairWin->_children.push_back(newwin);
splitwin->_parent = pairWin;
newwin->_parent = pairWin;
pairWin->_parent = oldparent;
if (oldparent) {
PairWindow *parentWin = dynamic_cast<PairWindow *>(oldparent);
assert(parentWin);
for (uint idx = 0; idx < parentWin->_children.size(); ++idx) {
if (parentWin->_children[idx] == splitwin)
parentWin->_children[idx] = pairWin;
}
} else {
_rootWin = pairWin;
}
}
rearrange();
return newwin;
}
void Windows::windowClose(Window *win, StreamResult *result) {
_forceRedraw = true;
if (win == _rootWin || win->_parent == nullptr) {
// Close the root window, which means all windows.
_rootWin = nullptr;
// Begin (simpler) closation
win->_stream->fillResult(result);
win->close(true);
} else {
// Have to jigger parent
Window *sibWin;
PairWindow *pairWin = dynamic_cast<PairWindow *>(win->_parent);
PairWindow *grandparWin;
if (pairWin) {
int index = pairWin->_children.indexOf(win);
if (index == -1) {
warning("windowClose: window tree is corrupted");
return;
}
// Detach window being closed from parent pair window
pairWin->_children.remove_at(index);
win->_parent = nullptr;
if (!(pairWin->_dir & winmethod_Arbitrary)) {
// Get the remaining child window
assert(pairWin->_children.size() == 1);
sibWin = pairWin->_children.front();
// Detach it from the pair window
index = pairWin->_children.indexOf(sibWin);
assert(index >= 0);
pairWin->_children.remove_at(index);
// Set up window as either the singular root, or grandparent pair window if one exists
grandparWin = dynamic_cast<PairWindow *>(pairWin->_parent);
if (!grandparWin) {
_rootWin = sibWin;
sibWin->_parent = nullptr;
} else {
index = grandparWin->_children.indexOf(pairWin);
grandparWin->_children[index] = sibWin;
sibWin->_parent = grandparWin;
}
}
}
// Begin closation
win->_stream->fillResult(result);
// Close the child window (and descendants), so that key-deletion can
// crawl up the tree to the root window.
win->close(true);
if (pairWin && !(pairWin->_dir & winmethod_Arbitrary))
// Now we can delete the parent pair.
pairWin->close(false);
// Sort out the arrangements
rearrange();
}
}
Window *Windows::newWindow(uint type, uint rock) {
Window *win;
switch (type) {
case wintype_Blank:
win = new BlankWindow(this, rock);
break;
case wintype_TextGrid:
win = new TextGridWindow(this, rock);
break;
case wintype_TextBuffer:
win = new TextBufferWindow(this, rock);
break;
case wintype_Graphics:
win = new GraphicsWindow(this, rock);
break;
case wintype_Pair:
error("Pair windows cannot be created directly");
default:
error("Unknown window type");
}
win->_next = _windowList;
_windowList = win;
if (win->_next)
win->_next->_prev = win;
return win;
}
PairWindow *Windows::newPairWindow(uint method, Window *key, uint size) {
PairWindow *pwin = new PairWindow(this, method, key, size);
pwin->_next = _windowList;
_windowList = pwin;
if (pwin->_next)
pwin->_next->_prev = pwin;
return pwin;
}
void Windows::rearrange() {
if (_rootWin) {
Rect box;
Point cell(g_conf->_monoInfo._cellW, g_conf->_monoInfo._cellH);
if (g_conf->_lockCols) {
int desired_width = g_conf->_wMarginSaveX * 2 + cell.x * g_conf->_cols;
if (desired_width > g_conf->_imageW)
g_conf->_wMarginX = g_conf->_wMarginSaveX;
else
g_conf->_wMarginX = (g_conf->_imageW - cell.x * g_conf->_cols) / 2;
}
if (g_conf->_lockRows) {
int desired_height = g_conf->_wMarginSaveY * 2 + cell.y * g_conf->_rows;
if (desired_height > g_conf->_imageH)
g_conf->_wMarginY = g_conf->_wMarginSaveY;
else
g_conf->_wMarginY = (g_conf->_imageH - cell.y * g_conf->_rows) / 2;
}
box.left = g_conf->_wMarginX;
box.top = g_conf->_wMarginY;
box.right = g_conf->_imageW - g_conf->_wMarginX;
box.bottom = g_conf->_imageH - g_conf->_wMarginY;
_rootWin->rearrange(box);
}
}
void Windows::inputGuessFocus() {
Window *altWin = _focusWin;
do {
if (altWin
&& (altWin->_lineRequest || altWin->_charRequest ||
altWin->_lineRequestUni || altWin->_charRequestUni))
break;
altWin = iterateTreeOrder(altWin);
} while (altWin != _focusWin);
if (_focusWin != altWin) {
_focusWin = altWin;
_forceRedraw = true;
redraw();
}
}
void Windows::inputMoreFocus() {
Window *altWin = _focusWin;
do {
if (altWin && altWin->_moreRequest)
break;
altWin = iterateTreeOrder(altWin);
} while (altWin != _focusWin);
_focusWin = altWin;
}
void Windows::inputNextFocus() {
Window *altWin = _focusWin;
do {
altWin = iterateTreeOrder(altWin);
if (altWin
&& (altWin->_lineRequest || altWin->_charRequest ||
altWin->_lineRequestUni || altWin->_charRequestUni))
break;
} while (altWin != _focusWin);
if (_focusWin != altWin) {
_focusWin = altWin;
_forceRedraw = true;
redraw();
}
}
void Windows::inputScrollFocus() {
Window *altWin = _focusWin;
do {
if (altWin && altWin->_scrollRequest)
break;
altWin = iterateTreeOrder(altWin);
} while (altWin != _focusWin);
_focusWin = altWin;
}
void Windows::inputHandleKey(uint key) {
if (_moreFocus) {
inputMoreFocus();
} else if (_focusWin && (_focusWin->_lineRequest || _focusWin->_lineRequestUni) &&
_focusWin->checkTerminators(key)) {
// WORKAROUND: Do line terminators checking first. This was first needed for Beyond Zork,
// since it needs the Page Up/Down keys to scroll the description area rathern than the buffer area
} else {
switch (key) {
case keycode_Tab:
inputNextFocus();
return;
case keycode_PageUp:
case keycode_PageDown:
case keycode_MouseWheelUp:
case keycode_MouseWheelDown:
inputScrollFocus();
break;
default:
inputGuessFocus();
break;
}
}
Window *win = _focusWin;
if (!win)
return;
bool deferExit = false;
TextGridWindow *gridWindow = dynamic_cast<TextGridWindow *>(win);
TextBufferWindow *bufWindow = dynamic_cast<TextBufferWindow *>(win);
if (gridWindow) {
if (gridWindow->_charRequest || gridWindow->_charRequestUni)
gridWindow->acceptReadChar(key);
else if (gridWindow->_lineRequest || gridWindow->_lineRequestUni)
gridWindow->acceptReadLine(key);
} else if (bufWindow) {
if (bufWindow->_charRequest || bufWindow->_charRequestUni)
bufWindow->acceptReadChar(key);
else if (bufWindow->_lineRequest || bufWindow->_lineRequestUni)
bufWindow->acceptReadLine(key);
else if (bufWindow->_moreRequest || bufWindow->_scrollRequest)
deferExit = bufWindow->acceptScroll(key);
}
if (!deferExit && g_vm->_terminated)
g_vm->quitGame();
}
void Windows::inputHandleClick(const Point &pos) {
if (_rootWin)
_rootWin->click(pos);
}
void Windows::selectionChanged() {
_claimSelect = false;
_forceRedraw = true;
redraw();
}
void Windows::redraw() {
_claimSelect = false;
if (_forceRedraw) {
repaint(Rect(0, 0, g_conf->_imageW, g_conf->_imageH));
g_vm->_screen->fill(g_conf->_windowColor);
}
if (_rootWin)
_rootWin->redraw();
if (_moreFocus)
refocus(_focusWin);
_forceRedraw = false;
}
void Windows::redrawRect(const Rect &r) {
_drawSelect = true;
repaint(r);
}
void Windows::repaint(const Rect &box) {
g_vm->_events->redraw();
}
uint Windows::rgbShift(uint color) {
uint8 r, g, b;
Graphics::PixelFormat pf = g_system->getScreenFormat();
pf.colorToRGB(color, r, g, b);
r = ((uint)r + 0x30) < 0xff ? ((uint)r + 0x30) : 0xff;
g = ((uint)g + 0x30) < 0xff ? ((uint)g + 0x30) : 0xff;
b = ((uint)b + 0x30) < 0xff ? ((uint)b + 0x30) : 0xff;
_zcolor_Bright = pf.RGBToColor(r, g, b);
return _zcolor_Bright;
}
/*--------------------------------------------------------------------------*/
Windows::iterator &Windows::iterator::operator++() {
_current = _current->_next;
return *this;
}
Windows::iterator &Windows::iterator::operator--() {
_current = _current->_prev;
return *this;
}
void Windows::refocus(Window *win) {
Window *focus = win;
do {
if (focus && focus->_moreRequest) {
_focusWin = focus;
return;
}
focus = iterateTreeOrder(focus);
} while (focus != win);
_moreFocus = false;
}
Window *Windows::iterateTreeOrder(Window *win) {
if (!win)
return _rootWin;
PairWindow *pairWin = dynamic_cast<PairWindow *>(win);
if (pairWin) {
return pairWin->_backward ? pairWin->_children.back() : pairWin->_children.front();
} else {
while (win->_parent) {
pairWin = dynamic_cast<PairWindow *>(win->_parent);
assert(pairWin);
int index = pairWin->_children.indexOf(win);
assert(index != -1);
if (!pairWin->_backward) {
if (index < ((int)pairWin->_children.size() - 1))
return pairWin->_children[index + 1];
} else {
if (index > 0)
return pairWin->_children[index - 1];
}
win = pairWin;
}
return nullptr;
}
}
/*--------------------------------------------------------------------------*/
Window::Window(Windows *windows, uint rock) : _windows(windows), _rock(rock),
_type(0), _parent(nullptr), _next(nullptr), _prev(nullptr), _yAdj(0),
_lineRequest(false), _lineRequestUni(false), _charRequest(false),
_charRequestUni(false), _mouseRequest(false), _hyperRequest(false),
_moreRequest(false), _scrollRequest(false), _imageLoaded(false),
_echoLineInputBase(true), _lineTerminatorsBase(nullptr), _termCt(0), _echoStream(nullptr) {
_attr.fgset = false;
_attr.bgset = false;
_attr.reverse = false;
_attr.style = 0;
_attr.fgcolor = 0;
_attr.bgcolor = 0;
_attr.hyper = 0;
_bgColor = g_conf->_windowColor;
_fgColor = g_conf->_propInfo._moreColor;
Streams &streams = *g_vm->_streams;
_stream = streams.openWindowStream(this);
if (g_vm->gli_register_obj)
_dispRock = (*g_vm->gli_register_obj)(this, gidisp_Class_Window);
}
Window::~Window() {
if (g_vm->gli_unregister_obj)
(*g_vm->gli_unregister_obj)(this, gidisp_Class_Window, _dispRock);
// Remove the window from the parent's children list
PairWindow *parent = dynamic_cast<PairWindow *>(_parent);
if (parent) {
int index = parent->_children.indexOf(this);
if (index != -1)
parent->_children.remove_at(index);
}
delete[] _lineTerminatorsBase;
// Remove the window from the master list of windows
Window *prev = _prev;
Window *next = _next;
if (prev)
prev->_next = next;
else
_windows->_windowList = next;
if (next)
next->_prev = prev;
// Delete any attached window stream
_echoStream = nullptr;
delete _stream;
}
void Window::close(bool recurse) {
if (_windows->getFocusWindow() == this)
// Focused window is being removed
_windows->setFocus(nullptr);
for (Window *wx = _parent; wx; wx = wx->_parent) {
PairWindow *pairWin = dynamic_cast<PairWindow *>(wx);
if (pairWin && pairWin->_key == this) {
pairWin->_key = nullptr;
pairWin->_keyDamage = true;
}
}
PairWindow *pairWin = dynamic_cast<PairWindow *>(this);
if (pairWin) {
for (uint idx = 0; idx < pairWin->_children.size(); ++idx)
pairWin->_children[idx]->close();
}
// Finally, delete the window
delete this;
}
FontInfo *Window::getFontInfo() {
error("Tried to get font info for a non-text window");
}
void Window::cancelLineEvent(Event *ev) {
Event dummyEv;
if (!ev)
ev = &dummyEv;
ev->clear();
}
void Window::moveCursor(const Point &newPos) {
warning("moveCursor: not a TextGrid window");
}
void Window::requestLineEvent(char *buf, uint maxlen, uint initlen) {
warning("requestLineEvent: window does not support keyboard input");
}
void Window::requestLineEventUni(uint32 *buf, uint maxlen, uint initlen) {
warning("requestLineEventUni: window does not support keyboard input");
}
void Window::redraw() {
if (Windows::_forceRedraw) {
uint color = Windows::_overrideBgSet ? g_conf->_windowColor : _bgColor;
int y0 = _yAdj ? _bbox.top - _yAdj : _bbox.top;
g_vm->_screen->fillRect(Rect(_bbox.left, y0, _bbox.right, _bbox.bottom), color);
}
}
void Window::acceptReadLine(uint32 arg) {
warning("acceptReadLine:: window does not support keyboard input");
}
void Window::acceptReadChar(uint arg) {
warning("acceptReadChar:: window does not support keyboard input");
}
void Window::getArrangement(uint *method, uint *size, Window **keyWin) {
warning("getArrangement: not a Pair window");
}
void Window::setArrangement(uint method, uint size, Window *keyWin) {
warning("setArrangement: not a Pair window");
}
void Window::requestCharEvent() {
warning("requestCharEvent: window does not support keyboard input");
}
void Window::requestCharEventUni() {
warning("requestCharEventUni: window does not support keyboard input");
}
void Window::flowBreak() {
warning("flowBreak: not a text buffer window");
}
void Window::eraseRect(bool whole, const Rect &box) {
warning("eraseRect: not a graphics window");
}
void Window::fillRect(uint color, const Rect &box) {
warning("fillRect: not a graphics window");
}
void Window::setBackgroundColor(uint color) {
warning("setBackgroundColor: not a graphics window");
}
const WindowStyle *Window::getStyles() const {
warning("getStyles: not a text window");
return nullptr;
}
void Window::setTerminatorsLineEvent(const uint32 *keycodes, uint count) {
if (dynamic_cast<TextBufferWindow *>(this) || dynamic_cast<TextGridWindow *>(this)) {
delete[] _lineTerminatorsBase;
_lineTerminatorsBase = nullptr;
if (!keycodes || count == 0) {
_termCt = 0;
} else {
_lineTerminatorsBase = new uint[count + 1];
if (_lineTerminatorsBase) {
memcpy(_lineTerminatorsBase, keycodes, count * sizeof(uint));
_lineTerminatorsBase[count] = 0;
_termCt = count;
}
}
} else {
warning("setTerminatorsLineEvent: window does not support keyboard input");
}
}
bool Window::checkBasicTerminators(uint32 ch) {
if (ch == keycode_Escape)
return true;
else if (ch >= keycode_Func12 && ch <= keycode_Func1)
return true;
else
return false;
}
bool Window::checkTerminators(uint32 ch) {
if (checkBasicTerminators(ch))
return true;
for (uint idx = 0; idx < _termCt; ++idx) {
if (_lineTerminatorsBase[idx] == ch)
return true;
}
return false;
}
bool Window::imageDraw(uint image, uint align, int val1, int val2) {
return imageDraw(Common::String::format("%u", image),
align, val1, val2);
}
bool Window::imageDraw(const Common::String & image, uint align, int val1, int val2) {
if (!g_conf->_graphics)
return false;
TextBufferWindow *bufWin = dynamic_cast<TextBufferWindow *>(this);
GraphicsWindow *graWin = dynamic_cast<GraphicsWindow *>(this);
if (bufWin)
return bufWin->drawPicture(image, val1, false, 0, 0);
if (graWin)
return graWin->drawPicture(image, val1, val2, false, 0, 0);
return false;
}
void Window::getSize(uint *width, uint *height) const {
if (width)
*width = 0;
if (height)
*height = 0;
}
void Window::bringToFront() {
PairWindow *pairWin = dynamic_cast<PairWindow *>(_parent);
if (pairWin && pairWin->_dir == winmethod_Arbitrary && pairWin->_children.back() != this) {
pairWin->_children.remove(this);
pairWin->_children.push_back(this);
Windows::_forceRedraw = true;
}
}
void Window::sendToBack() {
PairWindow *pairWin = dynamic_cast<PairWindow *>(_parent);
if (pairWin && pairWin->_dir == winmethod_Arbitrary && pairWin->_children.front() != this) {
pairWin->_children.remove(this);
pairWin->_children.insert_at(0, this);
Windows::_forceRedraw = true;
}
}
/*--------------------------------------------------------------------------*/
BlankWindow::BlankWindow(Windows *windows, uint rock) : Window(windows, rock) {
_type = wintype_Blank;
}
/*--------------------------------------------------------------------------*/
WindowStyle::WindowStyle(const WindowStyleStatic &src) : font(src.font), reverse(src.reverse) {
Graphics::PixelFormat pf = g_system->getScreenFormat();
fg = pf.RGBToColor(src.fg[0], src.fg[1], src.fg[2]);
bg = pf.RGBToColor(src.bg[0], src.bg[1], src.bg[1]);
}
/*--------------------------------------------------------------------------*/
void Attributes::clear() {
fgset = false;
bgset = false;
reverse = false;
unused = false;
style = 0;
fgcolor = 0;
bgcolor = 0;
hyper = 0;
}
uint Attributes::attrBg(const WindowStyle *styles) {
int revset = reverse || (styles[style].reverse && !Windows::_overrideReverse);
bool zfset = fgset ? fgset : Windows::_overrideFgSet;
bool zbset = bgset ? bgset : Windows::_overrideBgSet;
uint zfore = fgset ? fgcolor : Windows::_overrideFgVal;
uint zback = bgset ? bgcolor : Windows::_overrideBgVal;
if (zfset && zfore != Windows::_zcolor_fg) {
Windows::_zcolor_Foreground = zfore;
Windows::_zcolor_fg = zfore;
}
if (zbset && zback != Windows::_zcolor_bg) {
Windows::_zcolor_Background = zback;
Windows::_zcolor_bg = zback;
}
if (!revset) {
if (zbset)
return Windows::_zcolor_Background;
else
return styles[style].bg;
} else {
if (zfset)
if (zfore == zback)
return Windows::rgbShift(Windows::_zcolor_Foreground);
else
return Windows::_zcolor_Foreground;
else if (zbset && styles[style].fg == Windows::_zcolor_Background)
return Windows::_zcolor_LightGrey;
else
return styles[style].fg;
}
}
uint Attributes::attrFg(const WindowStyle *styles) {
int revset = reverse || (styles[style].reverse && !Windows::_overrideReverse);
bool zfset = fgset ? fgset : Windows::_overrideFgSet;
bool zbset = bgset ? bgset : Windows::_overrideBgSet;
uint zfore = fgset ? fgcolor : Windows::_overrideFgVal;
uint zback = bgset ? bgcolor : Windows::_overrideBgVal;
if (zfset && zfore != Windows::_zcolor_fg) {
Windows::_zcolor_Foreground = zfore;
Windows::_zcolor_fg = zfore;
}
if (zbset && zback != Windows::_zcolor_bg) {
Windows::_zcolor_Background = zback;
Windows::_zcolor_bg = zback;
}
if (!revset) {
if (zfset)
if (zfore == zback)
return Windows::rgbShift(Windows::_zcolor_Foreground);
else
return Windows::_zcolor_Foreground;
else if (zbset && styles[style].fg == Windows::_zcolor_Background)
return Windows::_zcolor_LightGrey;
else
return styles[style].fg;
} else {
if (zbset)
return Windows::_zcolor_Background;
else
return styles[style].bg;
}
}
} // End of namespace Glk