/* 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; } if (splitwin->_type == wintype_Pair) { if ((method & winmethod_DirMask) != winmethod_Arbitrary) { warning("window_open: Can only add windows to a Pair window in arbitrary mode"); return nullptr; } } else { 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(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(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(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(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(win); TextBufferWindow *bufWindow = dynamic_cast(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(win); if (pairWin) { return pairWin->_backward ? pairWin->_children.back() : pairWin->_children.front(); } else { while (win->_parent) { pairWin = dynamic_cast(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(0), _lineRequestUni(0), _charRequest(0), _charRequestUni(0), _mouseRequest(0), _hyperRequest(0), _moreRequest(0), _scrollRequest(0), _imageLoaded(0), _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(_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(wx); if (pairWin && pairWin->_key == this) { pairWin->_key = nullptr; pairWin->_keyDamage = true; } } PairWindow *pairWin = dynamic_cast(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(this) || dynamic_cast(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) { if (!g_conf->_graphics) return false; TextBufferWindow *bufWin = dynamic_cast(this); GraphicsWindow *graWin = dynamic_cast(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(_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(_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; fgcolor = 0; bgcolor = 0; reverse = false; hyper = 0; style = 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