/* 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/system.h" #include "common/config-manager.h" #include "common/macresman.h" #include "audio/mixer.h" #include "engines/engine.h" #include "engines/metaengine.h" #include "engines/savestate.h" #include "graphics/color_quantizer.h" #include "graphics/macgamma.h" #include "graphics/palette.h" #include "graphics/paletteman.h" #include "graphics/macgui/macwindowmanager.h" #include "graphics/surface.h" #include "scumm/scumm.h" #include "scumm/detection.h" #include "scumm/file.h" #include "scumm/imuse_digi/dimuse_engine.h" #include "scumm/macgui/macgui_impl.h" #include "scumm/macgui/macgui_v6.h" namespace Scumm { // =========================================================================== // The Maniac Mansion + Mac SCUMM v6 (and later) GUI // // Note that the original Maniac Mansion GUI would not open the Open and Save // dialogs directly. Instead it would trigger the built-in save/load screen, // and trigger the dialogs from there. At least for now, that's more work than // it is worth. // =========================================================================== MacV6Gui::MacV6Gui(ScummEngine *vm, const Common::Path &resourceFile) : MacGuiImpl(vm, resourceFile) { _backupScreen = nullptr; _backupPalette = nullptr; if (_vm->_game.id == GID_TENTACLE) _gameName = "Day of the Tentacle"; else if (_vm->_game.id == GID_SAMNMAX) _gameName = "Sam & Max"; #ifdef ENABLE_SCUMM_7_8 else if (_vm->_game.id == GID_DIG) _gameName = "The Dig"; else if (_vm->_game.id == GID_FT) _gameName = "Full Throttle"; #endif else if (_vm->_game.id == GID_MANIAC) _gameName = "Maniac Mansion"; else if (_vm->_game.id == GID_INDY4) _gameName = "Fate of Atlantis PowerPC"; else _gameName = "Some Game I Do Not Know"; if (_vm->_game.features & GF_DEMO) _gameName += " Demo"; } MacV6Gui::~MacV6Gui() { if (_backupScreen) { _backupScreen->free(); delete _backupScreen; } delete[] _backupPalette; } bool MacV6Gui::initialize() { if (!MacGuiImpl::initialize()) return false; // We can't use the name of the menus here, because there are // non-English versions. Let's hope the menu positions are always the // same, at least! Graphics::MacMenu *menu = _windowManager->getMenu(); Graphics::MacMenuItem *editMenu = menu->getMenuItem(2); Graphics::MacMenuItem *videoMenu = menu->getMenuItem(3); editMenu->enabled = false; menu->getSubMenuItem(videoMenu, 0)->enabled = false; // Small menu->getSubMenuItem(videoMenu, 1)->enabled = false; // Interlaced if (_vm->_game.id == GID_MANIAC) { Graphics::MacMenuItem *soundMenu = menu->getMenuItem(4); soundMenu->enabled = false; } return true; } bool MacV6Gui::readStrings() { _strsStrings.clear(); _strsStrings.reserve(128); for (int i = 0; i < 128; i++) _strsStrings.emplace_back(""); return true; } const Graphics::Font *MacV6Gui::getFontByScummId(int32 id) { // V6 and V7 games (and Maniac Mansion) do not use CharsetRendererMac return nullptr; } bool MacV6Gui::getFontParams(FontId fontId, int &id, int &size, int &slant) const { return false; } void MacV6Gui::setupCursor(int &width, int &height, int &hotspotX, int &hotspotY, int &animate) { if (_vm->_game.id == GID_INDY4) { byte cross[15 * 15]; memset(cross, 3, sizeof(cross)); for (int i = 0; i < 15; i++) { if (i < 6 || i > 8) { cross[i * 15 + 7] = 1; cross[7 * 15 + i] = 1; } } width = height = 15; hotspotX = hotspotY = 7; animate = false; _windowManager->replaceCustomCursor(cross, width, height, hotspotX, hotspotY, 3); } else if (_vm->_game.id == GID_MANIAC) { byte invertedMacArrow[11 * 16] = { 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 0, 1, 1, 0, 3, 3, 3, 3, 3, 3, 3, 0, 1, 1, 1, 0, 3, 3, 3, 3, 3, 3, 0, 1, 1, 1, 1, 0, 3, 3, 3, 3, 3, 0, 1, 1, 1, 1, 1, 0, 3, 3, 3, 3, 0, 1, 1, 1, 1, 1, 1, 0, 3, 3, 3, 0, 1, 1, 1, 1, 1, 1, 1, 0, 3, 3, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 3, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 3, 3, 3, 3, 0, 1, 0, 3, 0, 1, 1, 0, 3, 3, 3, 0, 0, 3, 3, 0, 1, 1, 0, 3, 3, 3, 0, 3, 3, 3, 3, 0, 1, 1, 0, 3, 3, 3, 3, 3, 3, 3, 0, 1, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 3, 3 }; width = 11; height = 16; hotspotX = 1; hotspotY = 1; animate = false; _windowManager->replaceCustomCursor(invertedMacArrow, width, height, hotspotX, hotspotY, 3); } } void MacV6Gui::updateMenus() { MacGuiImpl::updateMenus(); #if ENABLE_SCUMM_7_8 // Remember the old music and sfx volume so that we can toggle back and // forth between that and 0. If we don't have an old volume, use the // default value. We could save it to the config file, but... Nah. if (_vm->_imuseDigital && _oldMusicVolume == -1 && _oldSfxVolume == -1) { _oldMusicVolume = _vm->_imuseDigital->diMUSEGetMusicGroupVol(); if (_oldMusicVolume == 0) _oldMusicVolume = 81; _oldSfxVolume = _vm->_imuseDigital->diMUSEGetSFXGroupVol(); if (_oldSfxVolume == 0) _oldSfxVolume = 81; } #endif Graphics::MacMenu *menu = _windowManager->getMenu(); Graphics::MacMenuItem *videoMenu = menu->getMenuItem(3); menu->getSubMenuItem(videoMenu, 2)->checked = true; if (_vm->_game.id != GID_MANIAC) menu->getSubMenuItem(videoMenu, 3)->checked = _vm->_useMacGraphicsSmoothing; Graphics::MacMenuItem *soundMenu = menu->getMenuItem(4); int voiceMenuIndex = 5; #if ENABLE_SCUMM_7_8 if (_vm->_game.version >= 7) { if (_vm->_imuseDigital) { menu->getSubMenuItem(soundMenu, 0)->checked = (_vm->_imuseDigital->diMUSEGetMusicGroupVol() > 0); menu->getSubMenuItem(soundMenu, 1)->checked = (_vm->_imuseDigital->diMUSEGetSFXGroupVol() > 0); } } else #endif if (_vm->_game.id == GID_INDY4) { menu->getSubMenuItem(soundMenu, 0)->checked = (_vm->_soundEnabled & 2); // Music voiceMenuIndex = 4; } else { menu->getSubMenuItem(soundMenu, 0)->checked = (_vm->_soundEnabled & 2); // Music menu->getSubMenuItem(soundMenu, 1)->checked = (_vm->_soundEnabled & 1); // Effects } for (int i = 0; i < 3; i++) menu->getSubMenuItem(soundMenu, i + voiceMenuIndex)->checked = false; switch (_vm->_voiceMode) { case 0: // Voice Only menu->getSubMenuItem(soundMenu, voiceMenuIndex + 1)->checked = true; break; case 1: // Voice and Text menu->getSubMenuItem(soundMenu, voiceMenuIndex + 2)->checked = true; break; case 2: // Text Only menu->getSubMenuItem(soundMenu, voiceMenuIndex)->checked = true; break; default: warning("MacV6Gui::updateMenus(): Invalid voice mode %d", _vm->_voiceMode); break; } } bool MacV6Gui::handleMenu(int id, Common::String &name) { // Don't call the original method. The menus are too different. int saveSlotToHandle = -1; bool syncSoundSettings = false; Common::String savegameName; // The Dig and Full Throttle don't have a Restart menu entry if (_vm->_game.version > 6 && id >= 204 && id < 300) id++; // Fate of Atlantis doesn't have an Effects menu entry if (_vm->_game.id == GID_INDY4 && id >= 501 && id < 600) id++; switch (id) { case 100: // About runAboutDialog(); return true; case 200: // Open if (runOpenDialog(saveSlotToHandle)) { if (saveSlotToHandle > -1) { _vm->loadGameState(saveSlotToHandle); } } return true; case 201: // Save _vm->beginTextInput(); if (runSaveDialog(saveSlotToHandle, savegameName)) { if (saveSlotToHandle > -1) { _vm->saveGameState(saveSlotToHandle, savegameName); } } _vm->endTextInput(); return true; case 202: _skipScene = true; return true; case 203: // Resume is supposed to simply close the Mac menu, and that // happens automatically when selecting any menu item. return true; case 204: // Restart if (runRestartDialog()) _vm->restart(); return true; case 205: runOptionsDialog(); return true; case 206: if (runQuitDialog()) _vm->quitGame(); return true; // In the original, the Edit menu is active during save dialogs, though // only Cut, Copy and Paste. case 300: // Undo case 301: // Cut case 302: // Copy case 303: // Paste case 304: // Clear return true; case 403: // Graphics Smoothing if (_vm->_game.id != GID_MANIAC) _vm->mac_toggleSmoothing(); return true; case 500: // Music #if ENABLE_SCUMM_7_8 if (_vm->_game.version >= 7) { int musicVolume = _vm->_imuseDigital->diMUSEGetMusicGroupVol(); if (musicVolume == 0) { musicVolume = _oldMusicVolume; } else { _oldMusicVolume = musicVolume; musicVolume = 0; } setVolume(0, musicVolume / 8); } else #endif { _vm->_soundEnabled = (_vm->_soundEnabled & ~8) ^ 2; ConfMan.setBool("music_mute", !(_vm->_soundEnabled & 2)); ConfMan.setBool("mute", (_vm->_soundEnabled == 0 && _vm->_voiceMode == 2)); } syncSoundSettings = true; break; case 501: // Effects #if ENABLE_SCUMM_7_8 if (_vm->_game.version >= 7) { int sfxVolume = _vm->_imuseDigital->diMUSEGetSFXGroupVol(); if (sfxVolume == 0) { sfxVolume = _oldSfxVolume; } else { _oldSfxVolume = sfxVolume; sfxVolume = 0; } setVolume(1, sfxVolume / 8); } else #endif { _vm->_soundEnabled = (_vm->_soundEnabled & ~8) ^ 1; ConfMan.setBool("sfx_mute", !(_vm->_soundEnabled & 1)); ConfMan.setBool("mute", (_vm->_soundEnabled == 0 && _vm->_voiceMode == 2)); } syncSoundSettings = true; break; case 502: // Toggle Text & Voice switch (_vm->_voiceMode) { case 0: // Voice Only -> Text & Voice ConfMan.setBool("subtitles", true); ConfMan.setBool("speech_mute", false); break; case 1: // Text & Voice -> Text Only ConfMan.setBool("subtitles", true); ConfMan.setBool("speech_mute", true); break; case 2: // Text Only -> Voice Only ConfMan.setBool("subtitles", false); ConfMan.setBool("speech_mute", false); break; default: warning("Invalid voice mode %d", _vm->_voiceMode); return true; } syncSoundSettings = true; break; case 503: // Text Only ConfMan.setBool("subtitles", true); ConfMan.setBool("speech_mute", true); syncSoundSettings = true; break; case 504: // Voice Only ConfMan.setBool("subtitles", false); ConfMan.setBool("speech_mute", false); syncSoundSettings = true; break; case 505: // Text & Voice ConfMan.setBool("subtitles", true); ConfMan.setBool("speech_mute", false); syncSoundSettings = true; break; default: warning("Unknown menu command: %d", id); break; } if (syncSoundSettings) { ConfMan.flushToDisk(); _vm->syncSoundSettings(); return true; } return false; } void MacV6Gui::saveScreen() { if (_screenSaveLevel++ == 0) { _suspendPaletteUpdates = true; Graphics::Surface *screen = _vm->_macScreen; _backupScreen = new Graphics::Surface(); _backupScreen->copyFrom(*screen); // We have to grab the actual palette, becaues the engine // palette may not be what's on screen, e.g. during SMUSH // movies. _backupPalette = new byte[256 * 3]; _system->getPaletteManager()->grabPalette(_backupPalette, 0, 256); Graphics::Palette palette(256); setMacGuiColors(palette); _windowManager->passPalette(palette.data(), 256); if (_vm->_useGammaCorrection) { for (int i = 0; i < 256; i++) { byte r, g, b; palette.get(i, r, g, b); r = Graphics::macGammaCorrectionLookUp[r]; g = Graphics::macGammaCorrectionLookUp[g]; b = Graphics::macGammaCorrectionLookUp[b]; palette.set(i, r, g, b); } } screen->fillRect(Common::Rect(screen->w, screen->h), getBlack()); _system->copyRectToScreen(screen->getBasePtr(0, 0), screen->pitch, 0, 0, screen->w, screen->h); _system->getPaletteManager()->setPalette(palette); if (_windowManager->_screenCopy) _windowManager->_screenCopy->copyFrom(*_vm->_macScreen); _system->updateScreen(); } } void MacV6Gui::restoreScreen() { if (--_screenSaveLevel == 0) { _suspendPaletteUpdates = false; Graphics::Surface *screen = _vm->_macScreen; screen->copyFrom(*_backupScreen); _system->copyRectToScreen(screen->getBasePtr(0, 0), screen->pitch, 0, 0, screen->w, screen->h); _system->getPaletteManager()->setPalette(_backupPalette, 0, 256); _backupScreen->free(); delete _backupScreen; _backupScreen = nullptr; delete[] _backupPalette; _backupPalette = nullptr; } } void MacV6Gui::onMenuOpen() { MacGuiImpl::onMenuOpen(); saveScreen(); } void MacV6Gui::onMenuClose() { MacGuiImpl::onMenuClose(); restoreScreen(); } void MacV6Gui::drawDottedFrame(MacDialogWindow *window, Common::Rect bounds, int x1, int x2) { Graphics::Surface *s = window->innerSurface(); uint32 black = getBlack(); for (int x = bounds.left; x < bounds.right; x++) { if (((x + bounds.bottom - 1) & 1) == 0) s->setPixel(x, bounds.bottom - 1, black); if ((x <= x1 || x >= x2) && ((x + bounds.top) & 1) == 0) s->setPixel(x, bounds.top, black); } for (int y = bounds.top; y < bounds.bottom; y++) { if (((bounds.left + y) & 1) == 0) s->setPixel(bounds.left, y, black); if (((bounds.right - 1 + y) & 1) == 0) s->setPixel(bounds.right - 1, y, black); } } MacGuiImpl::MacImageSlider *MacV6Gui::addSlider(MacDialogWindow *window, int x, int y, int width, int minValue, int maxValue, int primaryMarkings) { int numMarkings = (maxValue - minValue) + 1; Graphics::Surface *s = window->innerSurface(); uint32 gray = _windowManager->findBestColor(0xCD, 0xCD, 0xCD); uint32 black = getBlack(); Common::Rect r(width, 12); r.moveTo(x, y); s->fillRect(r, gray); s->frameRect(r, black); int yt = y + 14; int *positions = new int[numMarkings]; for (int i = 0; i < numMarkings; i++) { int ht = ((i % primaryMarkings) == 0) ? 4 : 2; int xt = x + (i * (width - 1)) / (numMarkings - 1); s->vLine(xt, yt, yt + ht, black); positions[i] = xt - x; } MacImage *handle = window->addIcon(x - 6, y - 4, 300, true); MacImageSlider *slider = window->addImageSlider(Common::Rect(x - 6, y - 4, x + width + 7, y + 16), handle, true, 0, width - 1, minValue, maxValue); for (int i = 0; i < numMarkings; i++) slider->addStop(positions[i], minValue + i); slider->setSnapWhileDragging(true); slider->setValue(minValue); delete[] positions; return slider; } void MacV6Gui::runAboutDialog() { // While there is a menu item for a Maniac Mansion credits screen, it // doesn't do anything. // // "I was probably exhausted from hand-placing all the letters on the // DOTT credits screen to make one for Maniac." -- Aaron Giles, // December 30, 2024. if (_vm->_game.id == GID_MANIAC) return; if (_vm->_game.features & GF_DEMO) { // HACK: Use the largest bounds as default for unknown demos // It would be nice if we could figure these out automatically Common::Rect bounds(117, 5, 523, 384); if (_vm->_game.id == GID_TENTACLE || _vm->_game.id == GID_SAMNMAX) { bounds.left = 117; bounds.top = 5; bounds.right = 523; bounds.bottom = 384; #ifdef ENABLE_SCUMM_7_8 } else if (_vm->_game.id == GID_DIG || _vm->_game.id == GID_FT) { bounds.left = 121; bounds.top = 15; bounds.right = 519; bounds.bottom = 364; #endif } MacDialogWindow *window = createDialog(136, bounds); MacButton *buttonOk = (MacButton *)window->getWidget(kWidgetButton, 0); window->setDefaultWidget(buttonOk); while (!_vm->shouldQuit()) { MacDialogEvent event; while (window->runDialog(event)) { switch (event.type) { case kDialogClick: if (event.widget == buttonOk) { delete window; return; } break; default: break; } } window->delayAndUpdate(); } delete window; return; } ScummFile aboutFile(_vm); if (!_vm->openFile(aboutFile, "ABOUT")) return; PauseToken token = _vm->pauseEngine(); const int aboutW = 480; const int aboutH = 299; Graphics::Surface *screen = _vm->_macScreen; Common::Rect aboutArea(aboutW, aboutH); const int aboutX = (screen->w - aboutW) / 2; const int aboutY = (screen->h - aboutH) / 2; aboutArea.moveTo(aboutX, aboutY); Graphics::Surface aboutImage = screen->getSubArea(aboutArea); uint black = 0; Graphics::Palette palette(256); for (uint i = 0; i < 256; i++) { byte r = aboutFile.readByte(); byte g = aboutFile.readByte(); byte b = aboutFile.readByte(); if (r == 0 && g == 0 && b == 0) black = i; if (_vm->_useGammaCorrection) { r = Graphics::macGammaCorrectionLookUp[r]; g = Graphics::macGammaCorrectionLookUp[g]; b = Graphics::macGammaCorrectionLookUp[b]; } palette.set(i, r, g, b); } // The screen is already black, but what's black in the palette may // have changed. Also, we want to clear the menu area. screen->fillRect(Common::Rect(screen->w, screen->h), black); for (int y = 0; y < aboutH; y++) { for (int x = 0; x < aboutW; x++) { byte *dst = (byte *)aboutImage.getBasePtr(x, y); *dst = aboutFile.readByte(); } } aboutFile.close(); _system->copyRectToScreen(screen->getBasePtr(0, 0), screen->pitch, 0, 0, screen->w, screen->h); _system->getPaletteManager()->setPalette(palette); bool done = false; while (!_vm->shouldQuit() && !done) { Common::Event event; while (_system->getEventManager()->pollEvent(event)) { switch (event.type) { case Common::EVENT_LBUTTONDOWN: done = true; break; case Common::EVENT_KEYDOWN: if (event.kbd.keycode == Common::KEYCODE_ESCAPE) done = true; break; default: break; } } _system->delayMillis(10); _system->updateScreen(); } token.clear(); } void MacV6Gui::updateThumbnail(MacDialogWindow *window, Common::Rect drawArea, int saveSlot) { if (_vm->_game.id == GID_MANIAC) return; if (saveSlot < 0) return; SaveStateDescriptor desc = _vm->getMetaEngine()->querySaveMetaInfos(_vm->_targetName.c_str(), saveSlot); const Graphics::Surface *thumbnail = desc.getThumbnail(); Common::HashMap paletteMap; int diff = thumbnail->h - drawArea.height(); int yMin = diff / 2; int yMax = thumbnail->h - (diff / 2); assert(drawArea.width() == thumbnail->w); assert(drawArea.height() == yMax - yMin); Common::Rect thumbnailRect(0, yMin, drawArea.width(), yMax); // We don't know in advance how many colors the thumbnail is going to // use. Reduce the image to a smaller palette. We reserve 10 colors for // the Mac GUI itself. Graphics::ColorQuantizer quantizer(245); for (int y = yMin; y < yMax; y++) { for (int x = 0; x < thumbnail->w; x++) { uint32 color = thumbnail->getPixel(x, y); byte r, g, b; thumbnail->format.colorToRGB(color, r, g, b); quantizer.addColor(r, g, b); } } Graphics::Palette *palette = quantizer.getPalette(); _system->getPaletteManager()->setPalette(*palette); delete palette; Graphics::Palette fullPalette = _system->getPaletteManager()->grabPalette(0, 256); Graphics::Surface *palettedThumbnail = thumbnail->convertTo(window->innerSurface()->format, nullptr, 0, fullPalette.data(), fullPalette.size()); window->innerSurface()->copyRectToSurface(*palettedThumbnail, drawArea.left, drawArea.top, thumbnailRect); window->markRectAsDirty(drawArea); palettedThumbnail->free(); delete palettedThumbnail; } bool MacV6Gui::runOpenDialog(int &saveSlotToHandle) { // Widgets: // // 0 - Open button // 1 - Cancel button // 2 - Unknown item type 129 // 3 - User item // 4 - Eject button // 5 - Desktop button // 6 - User item // 7 - User item // 8 - Picture (thumbnail?) // // Not in Maniac Mansion: // // 9 - User item // 10 - "Where you were:" text int dialogId = (_vm->_game.id == GID_MANIAC) ? 384 : 256; MacDialogWindow *window = createDialog(dialogId); MacButton *buttonSave = (MacButton *)window->getWidget(kWidgetButton, 0); MacButton *buttonCancel = (MacButton *)window->getWidget(kWidgetButton, 1); MacButton *buttonEject = (MacButton *)window->getWidget(kWidgetButton, 2); MacButton *buttonDesktop = (MacButton *)window->getWidget(kWidgetButton, 3); window->setDefaultWidget(buttonSave); buttonEject->setEnabled(false); buttonDesktop->setEnabled(false); bool availSlots[100]; int slotIds[100]; Common::StringArray savegameNames; prepareSaveLoad(savegameNames, availSlots, slotIds, ARRAYSIZE(availSlots)); Common::Rect thumbnailRect(12, 32, 172, 132); MacListBox *listBox; if (_vm->_game.id == GID_MANIAC) { listBox = window->addListBox(Common::Rect(10, 31, 228, 161), savegameNames, true); drawFakePathList(window, Common::Rect(10, 8, 228, 27), _gameName.c_str()); drawFakeDriveLabel(window, Common::Rect(238, 10, 335, 26), "ScummVM"); } else { listBox = window->addListBox(Common::Rect(184, 31, 402, 161), savegameNames, true); drawFakePathList(window, Common::Rect(184, 8, 402, 27), _gameName.c_str()); drawFakeDriveLabel(window, Common::Rect(412, 10, 509, 26), "ScummVM"); window->innerSurface()->frameRect(Common::Rect(thumbnailRect.left - 1, thumbnailRect.top - 1, thumbnailRect.right + 1, thumbnailRect.bottom + 1), getBlack()); } int saveSlot = listBox->getValue() < ARRAYSIZE(slotIds) ? slotIds[listBox->getValue()] : -1; updateThumbnail(window, thumbnailRect, saveSlot); while (!_vm->shouldQuit()) { MacDialogEvent event; while (window->runDialog(event)) { switch (event.type) { case kDialogClick: if (event.widget == buttonSave || event.widget == listBox) { saveSlotToHandle = listBox->getValue() < ARRAYSIZE(slotIds) ? slotIds[listBox->getValue()] : -1; delete window; return true; } else if (event.widget == buttonCancel) { delete window; return false; } break; case kDialogValueChange: if (event.widget == listBox) { saveSlot = listBox->getValue() < ARRAYSIZE(slotIds) ? slotIds[listBox->getValue()] : -1; updateThumbnail(window, thumbnailRect, saveSlot); } break; default: break; } } window->delayAndUpdate(); } // When quitting, do not load the saved game delete window; return false; } bool MacV6Gui::runSaveDialog(int &saveSlotToHandle, Common::String &saveName) { // Widgets: // // 0 - Save button // 1 - Cancel button // 2 - Unknown item type 129 // 3 - User item // 4 - Eject button // 5 - Desktop button // 6 - User item // 7 - User item // 8 - Picture // 9 - "Save as:" text // 10 - User item int dialogId = (_vm->_game.id == GID_MANIAC) ? 386 : 258; MacDialogWindow *window = createDialog(dialogId); MacButton *buttonSave = (MacButton *)window->getWidget(kWidgetButton, 0); MacButton *buttonCancel = (MacButton *)window->getWidget(kWidgetButton, 1); MacButton *buttonEject = (MacButton *)window->getWidget(kWidgetButton, 2); MacButton *buttonDrive = (MacButton *)window->getWidget(kWidgetButton, 3); MacEditText *editText = (MacEditText *)window->getWidget(kWidgetEditText); window->setDefaultWidget(buttonSave); buttonEject->setEnabled(false); buttonDrive->setEnabled(false); bool busySlots[100]; int slotIds[100]; Common::StringArray savegameNames; prepareSaveLoad(savegameNames, busySlots, slotIds, ARRAYSIZE(busySlots)); drawFakePathList(window, Common::Rect(14, 8, 232, 27), _gameName.c_str()); drawFakeDriveLabel(window, Common::Rect(242, 10, 339, 26), "ScummVM"); int firstAvailableSlot = -1; for (int i = 0; i < ARRAYSIZE(busySlots); i++) { if (!busySlots[i]) { firstAvailableSlot = i; break; } } window->addListBox(Common::Rect(14, 31, 232, 129), savegameNames, true, true); while (!_vm->shouldQuit()) { MacDialogEvent event; while (window->runDialog(event)) { switch (event.type) { case kDialogClick: if (event.widget == buttonSave) { saveName = editText->getText(); saveSlotToHandle = firstAvailableSlot; delete window; return true; } else if (event.widget == buttonCancel) { delete window; return false; } break; case kDialogValueChange: if (event.widget == editText) { buttonSave->setEnabled(!editText->getText().empty()); } break; default: break; } } window->delayAndUpdate(); } // When quitting, do not save the game delete window; return false; } void MacV6Gui::setVolume(int type, int volume) { const char *keys[] = { "music_volume", "sfx_volume", "speech_volume" }; const Audio::Mixer::SoundType soundTypes[] = { Audio::Mixer::kMusicSoundType, Audio::Mixer::kSFXSoundType, Audio::Mixer::kSpeechSoundType }; // Input volume comes from the options dialog slider. It's 0-16. // The regular mixer uses 0-256 while Digital iMUSE uses 0-127. int mixerVolume = CLIP(16 * volume, 0, 256); #ifdef ENABLE_SCUMM_7_8 if (_vm->_game.version >= 7) { int dimuseVolume = CLIP(8 * volume, 0, 127); switch (type) { case 0: _vm->_imuseDigital->diMUSESetMusicGroupVol(dimuseVolume); break; case 1: _vm->_imuseDigital->diMUSESetSFXGroupVol(dimuseVolume); break; case 2: _vm->_imuseDigital->diMUSESetVoiceGroupVol(dimuseVolume); break; } } else #endif { _vm->_mixer->setVolumeForSoundType(soundTypes[type], mixerVolume); } ConfMan.setInt(keys[type], mixerVolume); } bool MacV6Gui::runOptionsDialog() { // There are too many different variations to list all widgets here. // The important thing that they share are that the first three buttons // are OK, Cancel, and Defaults, and that with the exception of Maniac // Mansion they expect the first text to contain the name of the game // as "^3". int dialogId = (_vm->_game.id == GID_MANIAC) ? 385 : 257; MacDialogWindow *window = createDialog(dialogId); MacButton *buttonOk = (MacButton *)window->getWidget(kWidgetButton, 0); MacButton *buttonCancel = (MacButton *)window->getWidget(kWidgetButton, 1); MacButton *buttonDefaults = (MacButton *)window->getWidget(kWidgetButton, 2); MacImageSlider *sliderMusicVolume = nullptr; MacImageSlider *sliderEffectVolume = nullptr; MacImageSlider *sliderVoiceVolume = nullptr; MacImageSlider *sliderTextSpeed = nullptr; #ifdef ENABLE_SCUMM_7_8 MacCheckbox *checkboxSpoolMusic = nullptr; #endif MacPopUpMenu *popUpInteraction = nullptr; MacPopUpMenu *popUpVideoQuality = nullptr; if (_vm->_game.id != GID_MANIAC) { window->addSubstitution(""); window->addSubstitution(""); window->addSubstitution(""); window->addSubstitution(_gameName); popUpInteraction = (MacPopUpMenu *)window->getWidget(kWidgetPopUpMenu, 0); popUpVideoQuality = (MacPopUpMenu *)window->getWidget(kWidgetPopUpMenu, 1); } else { popUpVideoQuality = (MacPopUpMenu *)window->getWidget(kWidgetPopUpMenu, 0); } // Note: The video quality menu contains an additional "Graphics // Smoothing" entry. I don't know why it doesn't show up in the // original, but as long as we disabled the pop-up that's not a // problem. My future self can thank me later. popUpVideoQuality->setValue(1); popUpVideoQuality->setEnabled(false); window->setDefaultWidget(buttonOk); if (_vm->_game.id == GID_TENTACLE || _vm->_game.id == GID_INDY4) { // Yes, the frames really are supposed to be slightly // misaligned to match the original appearance. drawDottedFrame(window, Common::Rect(12, 41, 337, 113), 21, 137); drawDottedFrame(window, Common::Rect(11, 130, 336, 203), 20, 168); sliderMusicVolume = addSlider(window, 152, 63, 147, 0, 16); sliderVoiceVolume = addSlider(window, 152, 87, 147, 0, 16); sliderTextSpeed = addSlider(window, 151, 177, 147, 1, 9); } else if (_vm->_game.id == GID_MANIAC) { sliderMusicVolume = addSlider(window, 152, 41, 147, 0, 16); sliderTextSpeed = addSlider(window, 152, 72, 147, 0, 9, 5); } else if (_vm->_game.id == GID_SAMNMAX || _vm->_game.id == GID_DIG) { drawDottedFrame(window, Common::Rect(12, 41, 337, 136), 21, 137); drawDottedFrame(window, Common::Rect(12, 156, 337, 229), 20, 168); sliderMusicVolume = addSlider(window, 152, 63, 147, 0, 16); sliderEffectVolume = addSlider(window, 152, 87, 147, 0, 16); sliderVoiceVolume = addSlider(window, 152, 111, 147, 0, 16); sliderTextSpeed = addSlider(window, 152, 203, 147, 1, 9); #ifdef ENABLE_SCUMM_7_8 } else if (_vm->_game.id == GID_FT) { drawDottedFrame(window, Common::Rect(12, 41, 337, 164), 21, 137); drawDottedFrame(window, Common::Rect(12, 184, 337, 257), 20, 168); sliderMusicVolume = addSlider(window, 152, 63, 147, 0, 16); sliderEffectVolume = addSlider(window, 152, 87, 147, 0, 16); sliderVoiceVolume = addSlider(window, 152, 111, 147, 0, 16); sliderTextSpeed = addSlider(window, 152, 231, 147, 1, 9); checkboxSpoolMusic = (MacCheckbox *)window->getWidget(kWidgetCheckbox, 0); #endif } else error("MacV6Gui::runOptionsDialog: Unknown game"); if (popUpInteraction) { switch (_vm->_voiceMode) { case 0: // Voice Only popUpInteraction->setValue(1); break; case 1: // Voice And Text popUpInteraction->setValue(2); break; case 2: // Text Only popUpInteraction->setValue(0); break; default: warning("MacGuiImpl::MacV6Gui::runOptionsDialog(): Invalid voice mode %d", _vm->_voiceMode); break; } } int musicVolume = 0; int effectVolume = 0; int voiceVolume = 0; // There is always a music volume slider. The rest are not guaranteed. // If there is a voice volume slider but no effect volume slider, the // voice volume slider cover both. musicVolume = ConfMan.getInt("music_volume") / 16; sliderMusicVolume->setValue(musicVolume); if (sliderEffectVolume) { effectVolume = ConfMan.getInt("sfx_volume") / 16; sliderEffectVolume->setValue(effectVolume); } if (sliderVoiceVolume) { voiceVolume = ConfMan.getInt("speech_volume") / 16; sliderVoiceVolume->setValue(voiceVolume); } #ifdef ENABLE_SCUMM_7_8 if (checkboxSpoolMusic) checkboxSpoolMusic->setValue(_vm->_spooledMusicIsToBeEnabled); #endif if (sliderTextSpeed) sliderTextSpeed->setValue(_vm->_defaultTextSpeed); while (!_vm->shouldQuit()) { MacDialogEvent event; while (window->runDialog(event)) { switch (event.type) { case kDialogClick: if (event.widget == buttonOk) { musicVolume = sliderMusicVolume->getValue(); if (_vm->_game.id == GID_MANIAC) { effectVolume = musicVolume; } else if (_vm->_game.id == GID_TENTACLE || _vm->_game.id == GID_INDY4) { musicVolume = sliderMusicVolume->getValue(); voiceVolume = sliderVoiceVolume->getValue(); effectVolume = voiceVolume; } else { musicVolume = sliderMusicVolume->getValue(); effectVolume = sliderEffectVolume->getValue(); voiceVolume = sliderVoiceVolume->getValue(); } setVolume(0, musicVolume); setVolume(1, effectVolume); if (sliderVoiceVolume) { voiceVolume = sliderVoiceVolume->getValue(); setVolume(2, voiceVolume); } // The DOS version uses 0-9. Apparently the Mac version // uses 1-9 instead, except for Maniac Mansion. _vm->_defaultTextSpeed = sliderTextSpeed->getValue(); ConfMan.setInt("original_gui_text_speed", _vm->_defaultTextSpeed); _vm->setTalkSpeed(_vm->_defaultTextSpeed); #ifdef ENABLE_SCUMM_7_8 if (checkboxSpoolMusic) { bool spoolMusic = (checkboxSpoolMusic->getValue() != 0); _vm->_spooledMusicIsToBeEnabled = spoolMusic; if (_vm->_imuseDigital) { if (spoolMusic) { _vm->_imuseDigital->diMUSEEnableSpooledMusic(); } else { _vm->_imuseDigital->diMUSEDisableSpooledMusic(); } } } #endif if (popUpInteraction) { switch (popUpInteraction->getValue()) { case 0: // Text Only ConfMan.setBool("subtitles", true); ConfMan.setBool("speech_mute", true); break; case 1: // Voice Only ConfMan.setBool("subtitles", false); ConfMan.setBool("speech_mute", false); break; case 2: // Text & Voice ConfMan.setBool("subtitles", true); ConfMan.setBool("speech_mute", false); break; } } ConfMan.flushToDisk(); _vm->syncSoundSettings(); delete window; return true; } else if (event.widget == buttonCancel) { delete window; return false; } else if (event.widget == buttonDefaults) { if (sliderMusicVolume) sliderMusicVolume->setValue(16); if (sliderEffectVolume) sliderEffectVolume->setValue(12); if (sliderVoiceVolume) sliderVoiceVolume->setValue(16); if (popUpInteraction) popUpInteraction->setValue(1); if (sliderTextSpeed) sliderTextSpeed->setValue((_vm->_game.id == GID_MANIAC) ? 4 : 5); #ifdef ENABLE_SCUMM_7_8 if (checkboxSpoolMusic) checkboxSpoolMusic->setValue(1); #endif } break; default: break; } } window->delayAndUpdate(); } delete window; return false; } bool MacV6Gui::runQuitDialog() { int dialogId = (_vm->_game.id == GID_MANIAC) ? 192 : 128; MacDialogWindow *window = createDialog(dialogId); MacButton *buttonOk = (MacButton *)window->getWidget(kWidgetButton, 0); MacButton *buttonCancel = (MacButton *)window->getWidget(kWidgetButton, 1); window->setDefaultWidget(buttonOk); while (!_vm->shouldQuit()) { MacDialogEvent event; while (window->runDialog(event)) { switch (event.type) { case kDialogClick: if (event.widget == buttonOk) { delete window; return true; } else if (event.widget == buttonCancel) { delete window; return false; } break; default: break; } } window->delayAndUpdate(); } // When quitting, quit delete window; return true; } bool MacV6Gui::runRestartDialog() { int dialogId = (_vm->_game.id == GID_MANIAC) ? 193 : 137; MacDialogWindow *window = createDialog(dialogId); // If there is no dialog, just restart without asking. if (!window) return true; MacButton *buttonOk = (MacButton *)window->getWidget(kWidgetButton, 0); MacButton *buttonCancel = (MacButton *)window->getWidget(kWidgetButton, 1); window->setDefaultWidget(buttonOk); window->addSubstitution(""); window->addSubstitution(""); window->addSubstitution(""); window->addSubstitution(_gameName); while (!_vm->shouldQuit()) { MacDialogEvent event; while (window->runDialog(event)) { switch (event.type) { case kDialogClick: if (event.widget == buttonOk) { delete window; return true; } else if (event.widget == buttonCancel) { delete window; return false; } break; default: break; } } window->delayAndUpdate(); } // When quitting, do not restart delete window; return false; } void MacV6Gui::resetAfterLoad() { reset(); } void MacV6Gui::updateWindowManager() { MacGuiImpl::updateWindowManager(); if (_skipScene && _screenSaveLevel == 0) { _skipScene = false; _vm->processKeyboard(Common::KEYCODE_ESCAPE); } } } // End of namespace Scumm