/* 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 3 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, see . * */ #include "bagel/spacebar/boflib/gui/window.h" #include "bagel/spacebar/boflib/app.h" #include "bagel/spacebar/boflib/events.h" #include "bagel/boflib/sound.h" #include "bagel/spacebar/boflib/std_keys.h" #include "bagel/metaengine.h" #include "bagel/bagel.h" namespace Bagel { namespace SpaceBar { #define DOUBLE_CLICK_TIME 250 // Static members defined here CBofWindow *CBofWindow::_pWindowList = nullptr; CBofWindow *CBofWindow::_pActiveWindow = nullptr; CBofTimerPacket *CBofWindow::_pTimerList = nullptr; int CBofWindow::_mouseX = 0; int CBofWindow::_mouseY = 0; CBofWindow::CBofWindow() { if (_pActiveWindow == nullptr) _pActiveWindow = this; if (_pWindowList == nullptr) { _pWindowList = this; } else { _pWindowList->Insert(this); } } CBofWindow::CBofWindow(const char *pszName, int x, int y, int nWidth, int nHeight, CBofWindow *pParent) { if (_pWindowList == nullptr) { _pWindowList = this; } else { _pWindowList->Insert(this); } create(pszName, x, y, nWidth, nHeight, pParent); } CBofWindow::~CBofWindow() { assert(isValidObject(this)); delete _surface; _surface = nullptr; killMyTimers(); // Remove it from any parent if (_parent != nullptr) setParent(nullptr); // Remove this window from the list if (_pWindowList == this) { _pWindowList = (CBofWindow *)getNext(); } killBackdrop(); CBofWindow::destroy(); } ErrorCode CBofWindow::initialize() { _pWindowList = nullptr; _pActiveWindow = nullptr; _pTimerList = nullptr; return ERR_NONE; } ErrorCode CBofWindow::shutdown() { return ERR_NONE; } Common::Point CBofWindow::getMousePos() { return Common::Point(_mouseX, _mouseY); } void CBofWindow::destroy() { releaseCapture(); delete _surface; _surface = nullptr; // When gui elements are destroyed, remove them // from the _children array of their parent setParent(nullptr); } void CBofWindow::validateAnscestors(CBofRect *pRect) { assert(isValidObject(this)); // Validate all anscestors CBofWindow *pParent = _parent; while (pParent != nullptr) { pParent->validateRect(pRect); pParent = pParent->getParent(); } } ErrorCode CBofWindow::create(const char *pszName, int x, int y, int nWidth, int nHeight, CBofWindow *pParent, uint32 nControlID) { assert(isValidObject(this)); assert(pszName != nullptr); assert(pParent != this); // Remember who our parent is if (pParent != nullptr) setParent(pParent); _nID = nControlID; // Remember the name of this window Common::strlcpy(_szTitle, pszName, MAX_TITLE); // Retain screen coordinates for this window _cWindowRect.setRect(x, y, x + nWidth - 1, y + nHeight - 1); // Calculate effective bounds Common::Rect stRect(x, y, x + nWidth, y + nHeight); if (pParent != nullptr) stRect.translate(pParent->getWindowRect().left, pParent->getWindowRect().top); delete _surface; _surface = new Graphics::ManagedSurface(*g_engine->getScreen(), stRect); if (!errorOccurred()) { CBofPalette *pPalette = CBofApp::getApp()->getPalette(); if (pPalette != nullptr) { selectPalette(pPalette); } // Retain local coordinates (based on own window) _cRect.setRect(0, 0, _cWindowRect.width() - 1, _cWindowRect.height() - 1); } return _errCode; } void CBofWindow::updateWindow() { if (_visible) { if (isVisible()) onPaint(&_cRect); for (uint i = 0; i < _children.size(); ++i) _children[i]->updateWindow(); } } void CBofWindow::setParent(CBofWindow *parent) { if (_parent != nullptr) _parent->_children.remove(this); _parent = parent; if (parent) parent->_children.push_back(this); } ErrorCode CBofWindow::create(const char *pszName, CBofRect *pRect, CBofWindow *pParent, uint32 nControlID) { assert(isValidObject(this)); assert(pszName != nullptr); int x = 0; int y = 0; int nWidth = USE_DEFAULT; int nHeight = USE_DEFAULT; if (pRect != nullptr) { x = pRect->left; y = pRect->top; nWidth = pRect->width(); nHeight = pRect->height(); } return create(pszName, x, y, nWidth, nHeight, pParent, nControlID); } void CBofWindow::releaseCapture() { _bCaptured = false; if (hasCapture()) CBofApp::getApp()->setCaptureControl(nullptr); } void CBofWindow::setCapture() { _bCaptured = true; CBofApp::getApp()->setCaptureControl(this); } bool CBofWindow::hasCapture() const { return CBofApp::getApp()->getCaptureControl() == this; } void CBofWindow::releaseFocus() { CBofApp::getApp()->setFocusControl(nullptr); } void CBofWindow::setFocus() { CBofApp::getApp()->setFocusControl(this); } bool CBofWindow::hasFocus() const { return CBofApp::getApp()->getFocusControl() == this; } void CBofWindow::center() { assert(isValidObject(this)); CBofWindow *pParent = _parent; int x, y; if (pParent != nullptr) { CBofRect cWindowRect = pParent->getWindowRect(); x = cWindowRect.left + (pParent->width() - width()) / 2; y = cWindowRect.top + (pParent->height() - height()) / 2; } else { x = (CBofApp::getApp()->screenWidth() - width()) / 2; y = (CBofApp::getApp()->screenHeight() - height()) / 2; } move(x, y); } void CBofWindow::move(const int x, const int y, bool bRepaint) { assert(isValidObject(this)); assert(isCreated()); // We now have a new position (in screen coordinates) _cWindowRect.setRect(x, y, x + _cRect.width() - 1, y + _cRect.height() - 1); // Recreate the surface at the new screen position delete _surface; _surface = new Graphics::ManagedSurface(*g_engine->getScreen(), _cWindowRect); } void CBofWindow::reSize(CBofRect *pRect, bool bRepaint) { assert(isValidObject(this)); assert(isCreated()); assert(pRect != nullptr); // We now have a new position (in screen coordinates) _cWindowRect = *pRect; _cRect.setRect(0, 0, _cWindowRect.width() - 1, _cWindowRect.height() - 1); // Recreate the surface at the new screen position delete _surface; _surface = new Graphics::ManagedSurface(*g_engine->getScreen(), _cWindowRect); } void CBofWindow::select() { // No implementation in ScummVM } void CBofWindow::show() { assert(isValidObject(this)); if (!errorOccurred()) { assert(isCreated()); if (isCreated()) { _visible = true; invalidateRect(&_cRect); } } } void CBofWindow::hide() { assert(isValidObject(this)); if (!errorOccurred()) { assert(isCreated()); _visible = false; } } void CBofWindow::postMessage(uint32 nMessage, uint32 lParam1, uint32 lParam2) { assert(isValidObject(this)); assert(isCreated()); } void CBofWindow::setTimer(uint32 nID, uint32 nInterval, BofCallback pCallBack) { assert(isValidObject(this)); assert(isCreated()); // Don't add it if there's already a timer there with the same id. CBofTimerPacket *pPacket = _pTimerList; while (pPacket != nullptr) { if (pPacket->_nID == nID) return; pPacket = (CBofTimerPacket *)pPacket->getNext(); } pPacket = new CBofTimerPacket; pPacket->_nID = nID; pPacket->_nInterval = nInterval; pPacket->_pCallBack = pCallBack; pPacket->_pOwnerWindow = this; // Add this timer to the list of current timers if (_pTimerList != nullptr) { _pTimerList->addToHead(pPacket); } _pTimerList = pPacket; // Add the timer to the window _timers.push_back(WindowTimer(nInterval, nID, pCallBack)); } void CBofWindow::killTimer(uint32 nID) { assert(isValidObject(this)); // Remove the timer from the window timer list for (Common::List::iterator it = _timers.begin(); it != _timers.end(); ++it) { if (it->_id == nID) { _timers.erase(it); break; } } // Find and remove the timer packet for this timer CBofTimerPacket *pPacket = _pTimerList; while (pPacket != nullptr) { if (pPacket->_nID == nID) { if (pPacket == _pTimerList) { _pTimerList = (CBofTimerPacket *)_pTimerList->getNext(); } delete pPacket; break; } pPacket = (CBofTimerPacket *)pPacket->getNext(); } } void CBofWindow::killMyTimers() { assert(isValidObject(this)); CBofTimerPacket *pTimer = _pTimerList; while (pTimer != nullptr) { CBofTimerPacket *pNextTimer = (CBofTimerPacket *)pTimer->getNext(); if (pTimer->_pOwnerWindow == this) { killTimer(pTimer->_nID); } pTimer = pNextTimer; } } void CBofWindow::checkTimers() { for (uint i = 0; i < _children.size(); ++i) _children[i]->checkTimers(); for (bool timersChanged = true; timersChanged;) { timersChanged = false; uint32 currTime = g_system->getMillis(); // Iterate over the timers looking for any that have expired for (auto &timer : _timers) { if (currTime >= (timer._lastExpiryTime + timer._interval)) { // Timer has expired timer._lastExpiryTime = currTime; if (timer._callback) { (timer._callback)(timer._id, this); } else { onTimer(timer._id); } // Flag to restart scanning through the timer list // for any other expired timers, since the timer call // may have modified the existing list timersChanged = true; break; } } } } void CBofWindow::screenToClient(CBofPoint *pPoint) { // Not needed in ScummVM } CBofRect CBofWindow::getClientRect() { assert(isValidObject(this)); CBofRect cRect(0, 0, _cRect.width() - 1, _cRect.height() - 1); return cRect; } void CBofWindow::postUserMessage(uint32 lMessage, uint32 lExtraInfo) { Common::Event e; e.type = (Common::EventType)EVENT_USER; e.mouse.x = lMessage; e.mouse.y = lExtraInfo; g_system->getEventManager()->pushEvent(e); } void CBofWindow::flushAllMessages() { // Make sure this is a valid window assert(isValidObject(this)); assert(isCreated()); } void CBofWindow::validateRect(const CBofRect *pRect) { // No implementation in ScummVM } void CBofWindow::invalidateRect(const CBofRect *pRect) { } ErrorCode CBofWindow::setBackdrop(CBofBitmap *pNewBitmap, bool bRefresh) { assert(isValidObject(this)); assert(pNewBitmap != nullptr); // Destroy old backdrop (if any) killBackdrop(); // We take ownership of this bitmap! _pBackdrop = pNewBitmap; if (bRefresh) { _pBackdrop->paint(this, 0, 0); } return _errCode; } ErrorCode CBofWindow::setBackdrop(const char *pszFileName, bool bRefresh) { assert(isValidObject(this)); assert(pszFileName != nullptr); // Use Application's palette if none supplied CBofPalette *pPalette = CBofApp::getApp()->getPalette(); CBofBitmap *pBmp = new CBofBitmap(pszFileName, pPalette); return setBackdrop(pBmp, bRefresh); } void CBofWindow::killBackdrop() { assert(isValidObject(this)); delete _pBackdrop; _pBackdrop = nullptr; } ErrorCode CBofWindow::paintBackdrop(CBofRect *pRect, int nTransparentColor) { assert(isValidObject(this)); if (_pBackdrop != nullptr) { if (pRect == nullptr) { _errCode = _pBackdrop->paint(this, &_cRect, nullptr, nTransparentColor); } else { _errCode = _pBackdrop->paint(this, pRect, pRect, nTransparentColor); } } return _errCode; } void CBofWindow::selectPalette(CBofPalette *pPal) { assert(isValidObject(this)); assert(isCreated()); } Graphics::ManagedSurface *CBofWindow::getSurface() { return _surface; } // Default version of these virtual functions don't do anything // void CBofWindow::onMouseMove(uint32, CBofPoint *, void *) { } void CBofWindow::onLButtonDown(uint32, CBofPoint *, void *) { } void CBofWindow::onLButtonUp(uint32, CBofPoint *, void *) { } void CBofWindow::onLButtonDblClk(uint32, CBofPoint *) { } void CBofWindow::onRButtonDown(uint32, CBofPoint *) { } void CBofWindow::onRButtonUp(uint32, CBofPoint *) { } void CBofWindow::onRButtonDblClk(uint32, CBofPoint *) { } void CBofWindow::onKeyHit(uint32, uint32) { } void CBofWindow::onReSize(CBofSize *) { } void CBofWindow::onPaint(CBofRect *) { } void CBofWindow::onTimer(uint32) { } void CBofWindow::onClose() { } void CBofWindow::onBofButton(CBofObject *, int) { } void CBofWindow::onBofScrollBar(CBofObject *, int) { } void CBofWindow::onBofListBox(CBofObject *, int) { } void CBofWindow::onUserMessage(uint32, uint32) { } void CBofWindow::onMainLoop() { } void CBofWindow::onSoundNotify(CBofObject *, uint32) { } void CBofWindow::onMovieNotify(uint32, uint32) { } void CBofWindow::onActivate() { } void CBofWindow::onDeActivate() { } void CBofWindow::onMCINotify(uint32 wParam, uint32 lParam) { assert(isValidObject(this)); } void CBofWindow::handleEvents() { Common::Event e; CBofWindow *capture = CBofApp::getApp()->getCaptureControl(); CBofWindow *focus = CBofApp::getApp()->getFocusControl(); bool eventsPresent = false; while (g_system->getEventManager()->pollEvent(e)) { if (capture) capture->handleEvent(e); else if (e.type == Common::EVENT_KEYDOWN && focus) focus->handleEvent(e); else handleEvent(e); if (e.type >= Common::EVENT_MOUSEMOVE && e.type <= Common::EVENT_MBUTTONUP) { _mouseX = e.mouse.x; _mouseY = e.mouse.y; } if (e.type != Common::EVENT_MOUSEMOVE) { eventsPresent = true; break; } } // Only do timer checks when not processing other pending events. // This simulates Windows behaviour, where the WM_TIMER events // would be added at the end of the event queue if (!eventsPresent) // Check for expired timers checkTimers(); } void CBofWindow::handleEvent(const Common::Event &event) { assert(isValidObject(this)); if (!_enabled || !_visible) // Window is disabled or hidden return; CBofPoint mousePos(event.mouse.x - _cWindowRect.left, event.mouse.y - _cWindowRect.top); for (auto parent = _parent; parent; parent = parent->_parent) { mousePos.x -= parent->_cWindowRect.left; mousePos.y -= parent->_cWindowRect.top; } switch (event.type) { case Common::EVENT_MOUSEMOVE: case Common::EVENT_LBUTTONDOWN: case Common::EVENT_LBUTTONUP: case Common::EVENT_RBUTTONDOWN: case Common::EVENT_RBUTTONUP: { // Check if the mouse is within the area of a child control for (uint i = 0; i < _children.size(); ++i) { auto &child = *_children[i]; if (child.isVisible() && child.isEnabled() && child.getWindowRect().ptInRect(mousePos)) { child.handleEvent(event); return; } } break; } default: break; } uint32 currTime = g_system->getMillis(); switch ((int)event.type) { case Common::EVENT_MOUSEMOVE: onMouseMove(0, &mousePos); break; case Common::EVENT_LBUTTONDOWN: if ((currTime - _lastLButtonTime) <= DOUBLE_CLICK_TIME) { _lastLButtonTime = 0; onLButtonDblClk(1, &mousePos); } else { onLButtonDown(1, &mousePos); _lastLButtonTime = currTime; } break; case Common::EVENT_LBUTTONUP: onLButtonUp(0, &mousePos); break; case Common::EVENT_RBUTTONDOWN: if ((currTime - _lastRButtonTime) <= DOUBLE_CLICK_TIME) { _lastRButtonTime = 0; onRButtonDblClk(2, &mousePos); } else { onRButtonDown(2, &mousePos); _lastRButtonTime = currTime; } break; case Common::EVENT_RBUTTONUP: onRButtonUp(0, &mousePos); break; case Common::EVENT_KEYDOWN: uint32 lNewKey; if ((lNewKey = translateKey(event)) != BKEY_UNKNOWN) { onKeyHit(lNewKey, event.kbdRepeat ? 1 : 0); } break; case Common::EVENT_CUSTOM_ENGINE_ACTION_START: if (event.customType != KEYBIND_NONE) onKeyHit((event.customType == KEYBIND_WAIT) ? BKEY_SPACE : BKEY_SCRL_LOCK, 0); break; case EVENT_USER: // Message type and param are stored in mouse x/y onUserMessage(event.mouse.x, event.mouse.y); break; case Common::EVENT_QUIT: onClose(); break; default: break; } } uint32 CBofWindow::translateKey(const Common::Event &event) const { uint32 nCode = BKEY_UNKNOWN; switch (event.kbd.keycode) { case Common::KEYCODE_F1: nCode = BKEY_F1; break; case Common::KEYCODE_F2: nCode = BKEY_SAVE; break; case Common::KEYCODE_F3: nCode = BKEY_RESTORE; break; case Common::KEYCODE_F4: nCode = BKEY_F4; break; case Common::KEYCODE_F5: nCode = BKEY_SAVE; break; case Common::KEYCODE_F6: nCode = BKEY_F6; break; case Common::KEYCODE_F7: nCode = BKEY_RESTORE; break; case Common::KEYCODE_F8: nCode = BKEY_F8; break; case Common::KEYCODE_F9: nCode = BKEY_F9; break; case Common::KEYCODE_F10: nCode = BKEY_F10; break; case Common::KEYCODE_F11: nCode = BKEY_F11; break; case Common::KEYCODE_F12: nCode = BKEY_F12; break; case Common::KEYCODE_END: nCode = BKEY_END; break; case Common::KEYCODE_HOME: nCode = BKEY_HOME; break; case Common::KEYCODE_LEFT: nCode = BKEY_LEFT; break; case Common::KEYCODE_RIGHT: nCode = BKEY_RIGHT; break; case Common::KEYCODE_UP: nCode = BKEY_UP; break; case Common::KEYCODE_DOWN: nCode = BKEY_DOWN; break; case Common::KEYCODE_RETURN: nCode = BKEY_ENTER; break; case Common::KEYCODE_INSERT: nCode = BKEY_INS; break; case Common::KEYCODE_BACKSPACE: nCode = BKEY_BACK; break; case Common::KEYCODE_DELETE: nCode = BKEY_DEL; break; case Common::KEYCODE_SCROLLOCK: nCode = BKEY_SCRL_LOCK; break; case Common::KEYCODE_PAGEUP: nCode = BKEY_PAGEUP; break; case Common::KEYCODE_PAGEDOWN: nCode = BKEY_PAGEDOWN; break; case Common::KEYCODE_ESCAPE: nCode = BKEY_ESC; break; default: // No translation for this key if (event.kbd.ascii >= 32 && event.kbd.ascii <= 127) nCode = event.kbd.ascii; break; } if (nCode != BKEY_UNKNOWN) { if (event.kbd.flags & Common::KBD_ALT) { nCode = tolower(nCode) | BKF_ALT; } } return nCode; } void CBofWindow::fillWindow(byte iColor) { fillRect(nullptr, iColor); } void CBofWindow::fillRect(CBofRect *pRect, byte iColor) { CBofBitmap cBmp(width(), height(), CBofApp::getApp()->getPalette()); cBmp.fillRect(pRect, iColor); cBmp.paint(this, 0, 0); } ErrorCode CBofWindow::paintBeveledText(CBofRect *rect, const CBofString &cString, const int size, const int weight, const COLORREF color, int justify, uint32 format) { assert(rect != nullptr); CBofBitmap bmp(rect->width(), rect->height(), nullptr, false); // Assume no error ErrorCode errorCode = ERR_NONE; CBofRect r = bmp.getRect(); CBofPalette *palette = nullptr; CBofApp *app = CBofApp::getApp(); if (app != nullptr) { palette = app->getPalette(); } if (palette != nullptr) { bmp.fillRect(nullptr, palette->getNearestIndex(RGB(92, 92, 92))); bmp.drawRect(&r, palette->getNearestIndex(RGB(0, 0, 0))); } else { bmp.fillRect(nullptr, COLOR_BLACK); } byte c1 = 3; byte c2 = 9; CBofRect cBevel = r; int left = cBevel.left; int top = cBevel.top; int right = cBevel.right; int bottom = cBevel.bottom; r.left += 6; r.top += 3; r.right -= 5; r.bottom -= 5; for (int i = 1; i <= 3; i++) { bmp.line(left + i, bottom - i, right - i, bottom - i, c1); bmp.line(right - i, bottom - i, right - i, top + i - 1, c1); } for (int i = 1; i <= 3; i++) { bmp.line(left + i, bottom - i, left + i, top + i - 1, c2); bmp.line(left + i, top + i - 1, right - i, top + i - 1, c2); } paintText(&bmp, &r, cString, size, weight, color, justify, format, FONT_DEFAULT); bmp.paint(this, rect); return errorCode; } } // namespace SpaceBar } // namespace Bagel