/* 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 "common/debug.h" #include "common/file.h" #include "common/system.h" #include "common/macresman.h" #include "graphics/macgui/macwindowmanager.h" #include "director/director.h" #include "director/archive.h" #include "director/cast.h" #include "director/debugger.h" #include "director/lingo/lingo.h" #include "director/movie.h" #include "director/window.h" #include "director/score.h" #include "director/channel.h" #include "director/sound.h" #include "director/sprite.h" #include "director/castmember/castmember.h" #include "director/debugger/debugtools.h" #include "graphics/managed_surface.h" namespace Director { bool commandsWindowCallback(Graphics::WindowClick click, Common::Event &event, void *window) { Window *w = (Window*)window; return w->processWMEvent(click, event); } Window::Window(int id, bool scrollable, bool resizable, bool editable, Graphics::MacWindowManager *wm, DirectorEngine *vm, bool isStage) : Object("Window") { _vm = vm; _wm = wm; _isStage = isStage; _stageColor = _wm->_colorBlack; _puppetTransition = nullptr; _soundManager = new DirectorSound(this); _lingoState = new LingoState; _lingoPlayState = nullptr; _currentMovie = nullptr; _nextMovie.frameI = -1; _newMovieStarted = true; _objType = kWindowObj; _startFrame = _vm->getStartMovie().startFrame; _windowType = -1; _isModal = false; _skipFrameAdvance = false; // Owned by the window manager _window = new Graphics::MacWindow(id, scrollable, resizable, editable, wm); _window->setDraggable(!_isStage); _window->setCallback(commandsWindowCallback, this); updateBorderType(); } Window::~Window() { delete _lingoState; if (_lingoPlayState) delete _lingoPlayState; delete _soundManager; delete _currentMovie; for (uint i = 0; i < _frozenLingoStates.size(); i++) delete _frozenLingoStates[i]; if (_puppetTransition) delete _puppetTransition; g_director->_wm->removeWindow(_window); g_director->_wm->removeMarked(); _window = nullptr; } void Window::invertChannel(Channel *channel, const Common::Rect &destRect) { const Graphics::Surface *mask; // in D3, we have inverted QDshape if (channel->_sprite->isQDShape() && channel->_sprite->_ink == kInkTypeMatte) mask = channel->_sprite->getQDMatte(); else mask = channel->getMask(true); Common::Rect srcRect = channel->getBbox(); srcRect.clip(destRect); // let compiler to optimize it int xoff = srcRect.left - channel->getBbox().left; int yoff = srcRect.top - channel->getBbox().top; Graphics::ManagedSurface *composeSurface = _window->getSurface(); if (_wm->_pixelformat.bytesPerPixel == 1) { for (int i = 0; i < srcRect.height(); i++) { byte *src = (byte *)composeSurface->getBasePtr(srcRect.left, srcRect.top + i); const byte *msk = mask ? (const byte *)mask->getBasePtr(xoff, yoff + i) : nullptr; for (int j = 0; j < srcRect.width(); j++, src++) if (!mask || (msk && (*msk++))) *src = _wm->inverter(*src); } } else { for (int i = 0; i < srcRect.height(); i++) { uint32 *src = (uint32 *)composeSurface->getBasePtr(srcRect.left, srcRect.top + i); const byte *msk = mask ? (const byte *)mask->getBasePtr(xoff, yoff + i) : nullptr; for (int j = 0; j < srcRect.width(); j++, src++) if (!mask || (msk && (*msk++))) *src = _wm->inverter(*src); } } } void Window::drawFrameCounter(Graphics::ManagedSurface *blitTo) { const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kConsoleFont); Common::String msg = Common::String::format("Frame: %d", g_director->getCurrentMovie()->getScore()->getCurrentFrameNum()); uint32 width = font->getStringWidth(msg); blitTo->fillRect(Common::Rect(blitTo->w - 3 - width, 1, blitTo->w - 1, font->getFontHeight() + 1), _wm->_colorBlack); font->drawString(blitTo, msg, blitTo->w - 1 - width, 3, width, _wm->_colorBlack); font->drawString(blitTo, msg, blitTo->w - 2 - width, 2, width, _wm->_colorWhite); } void Window::drawChannelBox(Director::Movie *currentMovie, Graphics::ManagedSurface *blitTo, int selectedChannel) { const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kConsoleFont); Channel *channel = currentMovie->getScore()->_channels[selectedChannel]; if (!channel->isEmpty()) { Common::Rect bbox = channel->getBbox(); blitTo->frameRect(bbox, g_director->_wm->_colorWhite); font->drawString(blitTo, Common::String::format("m: %d, ch: %d, fr: %d", channel->_sprite->_castId.member, selectedChannel, channel->_filmLoopFrame ? channel->_filmLoopFrame : channel->_movieTime), bbox.left + 3, bbox.top + 3, 128, g_director->_wm->_colorBlack); font->drawString(blitTo, Common::String::format("m: %d, ch: %d, fr: %d", channel->_sprite->_castId.member, selectedChannel, channel->_filmLoopFrame ? channel->_filmLoopFrame : channel->_movieTime), bbox.left + 2, bbox.top + 2, 128, g_director->_wm->_colorWhite); } } bool Window::render(bool forceRedraw, Graphics::ManagedSurface *blitTo) { if (!_currentMovie) return false; if (!blitTo) blitTo = _window->getSurface(); Common::List &dirtyRects = _window->getDirtyRectList(); if (forceRedraw) { blitTo->clear(_stageColor); _window->markAllDirty(); } else { if (dirtyRects.size() == 0 && _currentMovie->_videoPlayback == false) { if (g_director->_debugDraw & kDebugDrawFrame) { drawFrameCounter(blitTo); _window->setContentDirty(true); } return false; } _window->mergeDirtyRects(); } Channel *hiliteChannel = _currentMovie->getScore()->getChannelById(_currentMovie->_currentHiliteChannelId); uint32 renderStartTime = g_system->getMillis(); debugC(7, kDebugImages, "Window::render(): Updating %d rects", dirtyRects.size()); for (auto &i : dirtyRects) { Common::Rect r = i; // The inner dimensions are relative to the virtual desktop while // r isn't, so we need to move the window to be relative to the // same sapce. Common::Rect windowRect = _window->getInnerDimensions(); windowRect.moveTo(r.left, r.top); r.clip(windowRect); _dirtyChannels = _currentMovie->getScore()->getSpriteIntersections(r); bool shouldClear = true; Channel *trailChannel = nullptr; for (auto &j : _dirtyChannels) { if (j->_visible && r == j->getBbox() && j->isTrail()) { shouldClear = false; trailChannel = j; break; } } if (shouldClear) { blitTo->fillRect(r, _stageColor); } else if (trailChannel) { // Trail rendering mode; do not re-render the background and sprites underneath. _dirtyChannels.clear(); _dirtyChannels.push_back(trailChannel); } for (int pass = 0; pass < 2; pass++) { for (auto &j : _dirtyChannels) { if (j->isActiveVideo() && j->isVideoDirectToStage()) { if (pass == 0) continue; } else { if (pass == 1) continue; } if (j->_visible) { if (j->hasSubChannels()) { Common::Array *list = j->getSubChannels(); for (auto &k : *list) { inkBlitFrom(&k, r, blitTo); } } else { inkBlitFrom(j, r, blitTo); if (j == hiliteChannel) invertChannel(hiliteChannel, r); } } } } } #ifdef USE_IMGUI int selectedChannel = DT::getSelectedChannel(); if (selectedChannel > 0) Window::drawChannelBox(_currentMovie, blitTo, selectedChannel); #endif if (g_director->_debugDraw & kDebugDrawCast) { const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kConsoleFont); for (uint i = 0; i < _currentMovie->getScore()->_channels.size(); i++) { Channel *channel = _currentMovie->getScore()->_channels[i]; if (!channel->isEmpty()) { Common::Rect bbox = channel->getBbox(); blitTo->frameRect(bbox, g_director->_wm->_colorWhite); font->drawString(blitTo, Common::String::format("m: %d, ch: %d, fr: %d", channel->_sprite->_castId.member, i, channel->_filmLoopFrame ? channel->_filmLoopFrame : channel->_movieTime), bbox.left + 3, bbox.top + 3, 128, g_director->_wm->_colorBlack); font->drawString(blitTo, Common::String::format("m: %d, ch: %d, fr: %d", channel->_sprite->_castId.member, i, channel->_filmLoopFrame ? channel->_filmLoopFrame : channel->_movieTime), bbox.left + 2, bbox.top + 2, 128, g_director->_wm->_colorWhite); } } } if (g_director->_debugDraw & kDebugDrawFrame) drawFrameCounter(blitTo); dirtyRects.clear(); _window->setContentDirty(true); debugC(7, kDebugImages, "Window::render(): Draw finished in %d ms", g_system->getMillis() - renderStartTime); return true; } void Window::setStageColor(uint32 stageColor, bool forceReset) { if (stageColor != _stageColor || forceReset) { _stageColor = stageColor; reset(); _window->markAllDirty(); } } void Window::setTitleVisible(bool titleVisible) { _window->setTitleVisible(titleVisible); updateBorderType(); } Graphics::ManagedSurface *Window::getSurface() { return _window->getSurface(); } void Window::addDirtyRect(const Common::Rect &r) { _window->addDirtyRect(r); } void Window::resizeInner(int w, int h) { _window->resizeInner(w, h); } int Window::getId() { return _window->getId(); } void Window::setDirty(bool dirty) { _window->setDirty(dirty); } void Window::disableBorder() { _window->disableBorder(); } void Window::center(bool toCenter) { _window->center(toCenter); } Common::Point Window::getAbsolutePos() { return _window->getAbsolutePos(); } void Window::setTitle(const Common::String &title) { _window->setTitle(title); } void Window::move(int x, int y) { _window->move(x, y); } Datum Window::getStageRect() { ensureMovieIsLoaded(); Common::Rect rect = _window->getInnerDimensions(); Datum d; d.type = RECT; d.u.farr = new FArray; d.u.farr->arr.push_back(rect.left); d.u.farr->arr.push_back(rect.top); d.u.farr->arr.push_back(rect.right); d.u.farr->arr.push_back(rect.bottom); return d; } void Window::setStageRect(Datum datum) { if (datum.type != RECT) { warning("Window::setStageRect(): bad argument passed to rect field"); return; } // Unpack rect from datum Common::Rect rect = Common::Rect(datum.u.farr->arr[0].asInt(), datum.u.farr->arr[1].asInt(), datum.u.farr->arr[2].asInt(), datum.u.farr->arr[3].asInt()); _window->setInnerDimensions(rect); } void Window::setModal(bool modal) { if (_isModal && !modal) { _wm->setLockedWidget(nullptr); _isModal = false; } else if (!_isModal && modal) { _wm->setLockedWidget(this->_window); _isModal = true; } } void Window::setFileName(Common::String filename) { setNextMovie(filename); ensureMovieIsLoaded(); } void Window::reset() { Graphics::ManagedSurface *composeSurface = _window->getSurface(); resizeInner(composeSurface->w, composeSurface->h); _window->setContentDirty(true); } void Window::inkBlitFrom(Channel *channel, Common::Rect destRect, Graphics::ManagedSurface *blitTo) { Common::Rect srcRect = channel->getBbox(); destRect.clip(srcRect); DirectorPlotData pd = channel->getPlotData(); pd.destRect = destRect; pd.dst = blitTo; uint32 renderStartTime = 0; if (debugChannelSet(8, kDebugImages)) { CastType castType = channel->_sprite->_cast ? channel->_sprite->_cast->_type : kCastTypeNull; debugC(8, kDebugImages, "Window::inkBlitFrom(): updating %dx%d @ %d,%d -> %dx%d @ %d,%d, type: %s, cast: %s, ink: %d", srcRect.width(), srcRect.height(), srcRect.left, srcRect.top, destRect.width(), destRect.height(), destRect.left, destRect.top, castType2str(castType), channel->_sprite->_castId.asString().c_str(), channel->_sprite->_ink); renderStartTime = g_system->getMillis(); } if (pd.ms) { pd.inkBlitShape(srcRect); } else if (pd.srf) { pd.inkBlitSurface(srcRect, channel->getMask()); } else { if (debugChannelSet(4, kDebugImages)) { CastType castType = channel->_sprite->_cast ? channel->_sprite->_cast->_type : kCastTypeNull; warning("Window::inkBlitFrom(): No source surface: spriteType: %d (%s), castType: %d (%s), castId: %s", channel->_sprite->_spriteType, spriteType2str(channel->_sprite->_spriteType), castType, castType2str(castType), channel->_sprite->_castId.asString().c_str()); } } if (debugChannelSet(8, kDebugImages)) { debugC(8, kDebugImages, "Window::inkBlitFrom(): Draw finished in %d ms", g_system->getMillis() - renderStartTime); } } Common::Point Window::getMousePos() { Common::Rect innerDims = _window->getInnerDimensions(); return g_system->getEventManager()->getMousePos() - Common::Point(innerDims.left, innerDims.top); } void Window::setVisible(bool visible, bool silent) { // setting visible triggers movie load if (!_currentMovie && !silent) ensureMovieIsLoaded(); _window->setVisible(visible); if (visible) _wm->setActiveWindow(getId()); } void Window::ensureMovieIsLoaded() { if (!_currentMovie) { if (_fileName.empty()) { Common::String movieName = getName(); setNextMovie(movieName); } } else if (_nextMovie.movie.empty()) { // The movie is loaded and no next movie to load return; } if (_nextMovie.movie.empty()) { warning("Window::ensureMovieIsLoaded(): No movie to load"); return; } if (_currentMovie) { if (!_lingoState->callstack.empty()) freezeLingoState(); _currentMovie->getScore()->stopPlay(); } loadNextMovie(); if (_currentMovie->getScore()->_playState == kPlayNotStarted) step(); // we will load it here and move to kPlayLoaded state } bool Window::setNextMovie(Common::String &movieFilenameRaw) { _fileName = findMoviePath(movieFilenameRaw); bool fileExists = false; Common::File file; if (!_fileName.empty() && file.open(_fileName)) { fileExists = true; file.close(); } debug(1, "Window::setNextMovie: '%s' -> '%s' -> '%s'", movieFilenameRaw.c_str(), convertPath(movieFilenameRaw).c_str(), _fileName.toString(Common::Path::kNativeSeparator).c_str()); if (!fileExists) { warning("Movie %s does not exist", _fileName.toString(Common::Path::kNativeSeparator).c_str()); _fileName.clear(); return false; } _nextMovie.movie = _fileName.toString(g_director->_dirSeparator); return true; } void Window::updateBorderType() { if (_isStage) { _window->setBorderType(3); } else if (!_window->isTitleVisible()) { _window->setBorderType(2); } else { _window->setBorderType(MAX(0, MIN(_windowType, 16))); } } void Window::loadNewSharedCast(Cast *previousSharedCast) { if (g_director->getVersion() >= 500) return; Common::Path previousSharedCastPath; Common::Path newSharedCastPath = getSharedCastPath(); if (previousSharedCast && previousSharedCast->getArchive()) { previousSharedCastPath = previousSharedCast->getArchive()->getPathName(); } // Check if previous and new sharedCasts are the same if (!previousSharedCastPath.empty() && previousSharedCastPath == newSharedCastPath) { // Clear those previous widget pointers previousSharedCast->releaseCastMemberWidget(); _currentMovie->_sharedCast = previousSharedCast; debugC(1, kDebugLoading, "Skipping loading already loaded shared cast, path: %s", previousSharedCastPath.toString(Common::Path::kNativeSeparator).c_str()); return; } // Clean up the previous sharedCast if (previousSharedCast) { debug(0, "@@ Clearing shared cast '%s'", previousSharedCastPath.toString().c_str()); g_director->_allSeenResFiles.erase(previousSharedCastPath); g_director->_allOpenResFiles.remove(previousSharedCastPath); delete previousSharedCast->_castArchive; delete previousSharedCast; } else { debug(0, "@@ No previous shared cast"); } // Load the new sharedCast if (!newSharedCastPath.empty()) { _currentMovie->loadSharedCastsFrom(newSharedCastPath); } } bool Window::loadNextMovie() { _soundManager->changingMovie(); _newMovieStarted = true; _currentPath = Common::firstPathComponents(_nextMovie.movie, g_director->_dirSeparator); Cast *previousSharedCast = nullptr; if (_currentMovie) { previousSharedCast = _currentMovie->getSharedCast(); _currentMovie->_sharedCast = nullptr; } delete _currentMovie; _currentMovie = nullptr; Common::Path archivePath = Common::Path(_currentPath, g_director->_dirSeparator); archivePath.appendInPlace(Common::lastPathComponent(_nextMovie.movie, g_director->_dirSeparator)); Archive *mov = g_director->openArchive(archivePath); _nextMovie.movie.clear(); // Clearing it, so we will not attempt to load again if (!mov) return false; probeResources(mov); // Artificial delay for games that expect slow media, e.g. Spaceship Warlock if (g_director->_loadSlowdownFactor && !debugChannelSet(-1, kDebugFast)) { // Check that we're not cooling down from skipping a delay. if (g_system->getMillis() > g_director->_loadSlowdownCooldownTime) { uint32 delay = mov->getFileSize() * 1000 / g_director->_loadSlowdownFactor; debugC(5, kDebugLoading, "Slowing load of next movie by %d ms", delay); while (delay != 0) { uint32 dec = MIN((uint32)10, delay); // Skip delay if mouse is clicked if (g_director->processEvents(true, true)) { g_director->loadSlowdownCooloff(); break; } g_director->_wm->replaceCursor(Graphics::kMacCursorWatch); g_director->draw(); g_system->delayMillis(dec); delay -= dec; } } // If this movie switch is within the cooldown time, // don't add a delay. This is to allow for rapid navigation. // User input events will call loadSlowdownCooloff() and // extend the cooldown time. } _currentMovie = new Movie(this); _currentMovie->setArchive(mov); debug(0, "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); debug(0, "@@@@ Switching to movie '%s' in '%s'", utf8ToPrintable(_currentMovie->getMacName()).c_str(), _currentPath.c_str()); debug(0, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"); if (g_director->getVersion() < 500) loadNewSharedCast(previousSharedCast); return true; } bool Window::step() { // finish last movie if (_currentMovie && _currentMovie->getScore()->_playState == kPlayStopped) { // attempt to thaw the lingo play state, if required // For movie switches, we want to run it in the context of the new movie. if (_nextMovie.movie.empty()) _currentMovie->getScore()->processFrozenPlayScript(); debugC(5, kDebugEvents, "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); debugC(5, kDebugEvents, "@@@@ Finishing movie '%s' in '%s'", utf8ToPrintable(_currentMovie->getMacName()).c_str(), _currentPath.c_str()); debugC(5, kDebugEvents, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"); _currentMovie->getScore()->stopPlay(); debugC(1, kDebugEvents, "Finished playback of movie '%s'", utf8ToPrintable(_currentMovie->getMacName()).c_str()); if (_vm->getGameGID() == GID_TESTALL) { _nextMovie = getNextMovieFromQueue(); } } if (debugChannelSet(-1, kDebugFewFramesOnly) && g_director->_framesRan > kFewFamesMaxCounter) return false; // prepare next movie if (!_nextMovie.movie.empty()) { if (!loadNextMovie()) return (_vm->getGameGID() == GID_TESTALL); g_lingo->resetLingo(); g_director->_lastPalette = CastMemberID(); } // play current movie if (_currentMovie) { switch (_currentMovie->getScore()->_playState) { case kPlayNotStarted: { debug(0, "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); debug(0, "@@@@ Loading movie '%s' in '%s'", utf8ToPrintable(_currentMovie->getMacName()).c_str(), _currentPath.c_str()); debug(0, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"); bool goodMovie = _currentMovie->loadArchive(); // If we've just started, switch to the default palette if (g_director->_firstMovie) g_director->setPalette(_currentMovie->getCast()->_defaultPalette); // If we came in a loop, then skip as requested if (!_nextMovie.frameS.empty()) { _currentMovie->getScore()->setStartToLabel(_nextMovie.frameS); _nextMovie.frameS.clear(); } if (_nextMovie.frameI != -1) { _currentMovie->getScore()->setCurrentFrame(_nextMovie.frameI); _nextMovie.frameI = -1; } if (debugChannelSet(-1, kDebugPauseOnLoad) || (g_director->_firstMovie && debugChannelSet(-1, kDebugPaused))) { _currentMovie->getScore()->_playState = kPlayPausedAfterLoading; debug(0, "Window::step(): Putting score in paused state as requested"); g_system->displayMessageOnOSD(Common::U32String("Paused")); g_director->_firstMovie = false; return true; } g_director->_firstMovie = false; if (!goodMovie) return false; _currentMovie->getScore()->_playState = kPlayLoaded; return true; } case kPlayLoaded: if (!debugChannelSet(-1, kDebugCompileOnly)) { debugC(1, kDebugEvents, "Starting playback of movie '%s'", _currentMovie->getMacName().c_str()); if (_vm->getVersion() >= 600) { // We need to call this before behavior scripts are instantiated // or cast loaded _currentMovie->getScore()->_disableGoPlayUpdateStage = true; _currentMovie->processEvent(kEventPrepareMovie); _currentMovie->getScore()->_disableGoPlayUpdateStage = false; } _currentMovie->getScore()->startPlay(); if (_startFrame != -1) { _currentMovie->getScore()->setCurrentFrame(_startFrame); _startFrame = -1; } g_debugger->movieHook(); } else { return false; } // fall through case kPlayStarted: debugC(5, kDebugEvents, "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); debugC(5, kDebugEvents, "@@@@ Stepping movie '%s' in '%s'", utf8ToPrintable(_currentMovie->getMacName()).c_str(), _currentPath.c_str()); debugC(5, kDebugEvents, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"); _currentMovie->getScore()->step(); return true; case kPlayPausedAfterLoading: case kPlayPaused: return true; default: return false; } } return false; } Common::Path Window::getSharedCastPath() { Common::Array namesToTry; if (_vm->getVersion() < 400) { if (!_sharedCastFilenameHint.empty()) { namesToTry.push_back(_sharedCastFilenameHint); } else if (g_director->getPlatform() == Common::kPlatformWindows) { namesToTry.push_back("SHARDCST.MMM"); } else { namesToTry.push_back("Shared Cast"); } } else if (_vm->getVersion() < 500) { namesToTry.push_back("Shared.dir"); if (!_sharedCastFilenameHint.empty()) { namesToTry.push_back(_sharedCastFilenameHint); } } Common::Path result; for (uint i = 0; i < namesToTry.size(); i++) { result = findMoviePath(namesToTry[i]); if (!result.empty()) return result; } return result; } void Window::freezeLingoState() { _frozenLingoStates.push_back(_lingoState); _lingoState = new LingoState; debugC(3, kDebugLingoExec, "Freezing Lingo state, depth %d", _frozenLingoStates.size()); } void Window::thawLingoState() { if (_frozenLingoStates.empty()) { warning("Tried to thaw when there's no frozen state, ignoring"); return; } if (!_lingoState->callstack.empty()) { warning("Can't thaw a Lingo state in mid-execution, ignoring"); return; } delete _lingoState; debugC(3, kDebugLingoExec, "Thawing Lingo state, depth %d", _frozenLingoStates.size()); _lingoState = _frozenLingoStates.back(); _frozenLingoStates.pop_back(); } void Window::freezeLingoPlayState() { if (_lingoPlayState) { warning("FIXME: Just clobbered the play state"); delete _lingoPlayState; } _lingoPlayState = _lingoState; _lingoState = new LingoState; debugC(3, kDebugLingoExec, "Freezing Lingo play state"); } bool Window::thawLingoPlayState() { if (!_lingoPlayState) { warning("Tried to thaw when there's no frozen play state, ignoring"); return false; } if (!_lingoState->callstack.empty()) { warning("Can't thaw a Lingo state in mid-execution, ignoring"); return false; } delete _lingoState; debugC(3, kDebugLingoExec, "Thawing Lingo play state"); _lingoState = _lingoPlayState; _lingoPlayState = nullptr; return true; } void Window::moveLingoState(Window *target) { if (target == this) return; if (!target->_lingoState->callstack.empty()) target->freezeLingoState(); delete target->_lingoState; target->_lingoState = _lingoState; _lingoState = new LingoState(); } // Check how many times enterFrame/stepMovie have been called recursively. // When Lingo encounters a go() call, it freezes the execution state and starts // processing the next frame. In the case of enterFrame/stepMovie, it is possible // to keep recursing without reaching a point where the frozen contexts are finished. // D4 and higher will only process recursive handlers to a depth of 2. // e.g. in a movie: // - frame 1 has an enterFrame handler that calls go(2) // - frame 2 has an enterFrame handler that calls go(3) // - frame 3 has an enterFrame handler that calls go(4) // The third enterFrame handler will be eaten and not called. // We can count the number of frozen states which started from enterFrame/stepMovie. uint32 Window::frozenLingoRecursionCount() { uint32 count = 0; bool stepFrameCanRecurse = _vm->getVersion() < 500; for (int i = (int)_frozenLingoStates.size() - 1; i >= 0; i--) { LingoState *state = _frozenLingoStates[i]; if (state->callstack.empty()) continue; CFrame *frame = state->callstack.front(); if (frame->sp.name->equalsIgnoreCase("enterFrame") || frame->sp.name->equalsIgnoreCase("stepMovie") || (!stepFrameCanRecurse && frame->sp.name->equalsIgnoreCase("stepFrame"))) { count++; } else { break; } } return count; } Common::String Window::formatWindowInfo() { Common::Rect dims = _window->getDimensions(); Common::Rect innerDims = _window->getInnerDimensions(); return Common::String::format( "name: \"%s\", movie: \"%s\", currentPath: \"%s\", dims: (%d,%d) %dx%d, innerDims: (%d, %d) %dx%d, visible: %d", _name.c_str(), _currentMovie->getMacName().c_str(), _currentPath.c_str(), dims.left, dims.top, dims.width(), dims.height(), innerDims.left, innerDims.top, innerDims.width(), innerDims.height(), _window->isVisible() ); } } // End of namespace Director