/* 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/enc-internal.h"
#include "common/macresman.h"
#include "graphics/cursorman.h"
#include "graphics/paletteman.h"
#include "graphics/fonts/macfont.h"
#include "graphics/macgui/macwindowmanager.h"
#include "image/pict.h"
#include "scumm/macgui/macgui_impl.h"
#include "scumm/scumm.h"
#include "scumm/scumm_v4.h"
namespace Scumm {
// ===========================================================================
// Base class for Macintosh game user interface. Handles menus, fonts, image
// loading, etc.
// ===========================================================================
MacGuiImpl::MacGuiImpl(ScummEngine *vm, const Common::Path &resourceFile) : _vm(vm), _system(_vm->_system), _surface(_vm->_macScreen), _resourceFile(resourceFile) {
_fonts.clear();
_strsStrings.clear();
// kMacRomanConversionTable is a conversion table from Mac Roman
// 128-255 to unicode. What we need, however, is a mapping from
// unicode 160-255 to Mac Roman.
for (int i = 0; i < ARRAYSIZE(_unicodeToMacRoman); i++)
_unicodeToMacRoman[i] = 0;
for (int i = 0; i < ARRAYSIZE(Common::kMacRomanConversionTable); i++) {
int unicode = Common::kMacRomanConversionTable[i];
if (unicode >= 160 && unicode <= 255)
_unicodeToMacRoman[unicode - 160] = 128 + i;
}
}
MacGuiImpl::~MacGuiImpl() {
delete _bannerWindow;
delete _windowManager;
}
uint32 MacGuiImpl::getBlack() const {
return _windowManager->_colorBlack;
}
uint32 MacGuiImpl::getWhite() const {
return _windowManager->_colorWhite;
}
int MacGuiImpl::toMacRoman(int unicode) const {
if (unicode >= 32 && unicode <= 127)
return unicode;
if (unicode < 160 || unicode > 255)
return 0;
int macRoman = _unicodeToMacRoman[unicode - 160];
// These characters are defined in Mac Roman, but apparently not
// present in older fonts like Chicago?
if (macRoman >= 0xD9 && macRoman != 0xF0)
macRoman = 0;
return macRoman;
}
void MacGuiImpl::setPalette(const byte *palette, uint size) {
_windowManager->passPalette(palette, size);
}
bool MacGuiImpl::handleEvent(Common::Event event) {
// The situation we're trying to avoid here is the user opening e.g.
// the save dialog using keyboard shortcuts while the game is paused.
if (_bannerWindow)
return false;
return _windowManager->processEvent(event);
}
MacGuiImpl::DelayStatus MacGuiImpl::delay(uint32 ms) {
uint32 to;
to = _system->getMillis() + ms;
while (ms == 0 || _system->getMillis() < to) {
Common::Event event;
while (_system->getEventManager()->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_QUIT:
return kDelayAborted;
case Common::EVENT_LBUTTONDOWN:
return kDelayInterrupted;
default:
break;
}
}
uint32 delta = to - _system->getMillis();
if (delta > 0) {
_system->delayMillis(MIN(delta, 10));
_system->updateScreen();
}
}
return kDelayDone;
}
// --------------------------------------------------------------------------
// Menu handling
//
// In the original, the menu was activated by pressing the "Command key".
// This does not seem very friendly to touch devices, so we instead use the
// "mouse over" method in the Mac Window Manager class.
//
// Later games added clicking to open the menu, and even an option to keep the
// menu bar always visible.
//
// TODO: Ideally we should perhaps implement some of these behaviors, but I'm
// not sure which one can sensibly coexist.
// --------------------------------------------------------------------------
void MacGuiImpl::menuCallback(int id, Common::String &name, void *data) {
MacGuiImpl *gui = (MacGuiImpl *)data;
gui->handleMenu(id, name);
if (gui->_forceMenuClosed) {
gui->_windowManager->getMenu()->closeMenu();
gui->_forceMenuClosed = false;
}
}
void MacGuiImpl::initialize() {
uint32 menuMode = Graphics::kWMModeNoDesktop | Graphics::kWMModeAutohideMenu |
Graphics::kWMModalMenuMode | Graphics::kWMModeNoCursorOverride | Graphics::kWMModeForceMacFonts;
// Allow a more modern UX: the menu doesn't close if the mouse accidentally goes outside the menu area
if (_vm->enhancementEnabled(kEnhUIUX))
menuMode |= Graphics::kWMModeWin95 | Graphics::kWMModeForceMacFontsInWin95 | Graphics::kWMModeForceMacBorder;
_windowManager = new Graphics::MacWindowManager(menuMode);
_windowManager->setEngine(_vm);
_windowManager->setScreen(640, _vm->_useMacScreenCorrectHeight ? 480 : 400);
if (_vm->isUsingOriginalGUI()) {
_windowManager->setMenuHotzone(Common::Rect(640, 23));
_windowManager->setMenuDelay(250);
Common::MacResManager resource;
Graphics::MacMenu *menu = _windowManager->addMenu();
resource.open(_resourceFile);
// Add the Apple menu
const Graphics::MacMenuData menuSubItems[] = {
{ 0, nullptr, 0, 0, false }
};
Common::String aboutMenuDef = _strsStrings[kMSIAboutGameName].c_str();
int maxMenu = -1;
switch (_vm->_game.id) {
case GID_INDY3:
case GID_LOOM:
maxMenu = 130;
break;
case GID_MONKEY:
maxMenu = 131;
break;
default:
maxMenu = 132;
}
if (_vm->_game.id == GID_LOOM) {
aboutMenuDef += ";";
if (!_vm->enhancementEnabled(kEnhUIUX))
aboutMenuDef += "(";
aboutMenuDef += "Drafts Inventory";
}
menu->addStaticMenus(menuSubItems);
menu->createSubMenuFromString(0, aboutMenuDef.c_str(), 0);
menu->setCommandsCallback(menuCallback, this);
for (int i = 129; i <= maxMenu; i++) {
Common::SeekableReadStream *res = resource.getResource(MKTAG('M', 'E', 'N', 'U'), i);
if (!res)
continue;
Common::StringArray *menuDef = Graphics::MacMenu::readMenuFromResource(res);
Common::String name = menuDef->operator[](0);
Common::String string = menuDef->operator[](1);
int id = menu->addMenuItem(nullptr, name);
// The CD version of Fate of Atlantis has a menu item
// for toggling graphics smoothing. We retroactively
// add that to the remaining V5 games, but not to
// Loom and Last Crusade.
if (_vm->enhancementEnabled(kEnhUIUX)) {
if ((_vm->_game.id == GID_MONKEY || _vm->_game.id == GID_MONKEY2) && id == 3) {
string += ";(-;Smooth Graphics";
}
// Floppy version
if (_vm->_game.id == GID_INDY4 && !string.contains("Smooth Graphics") && id == 3) {
string += ";(-;Smooth Graphics";
}
}
menu->createSubMenuFromString(id, string.c_str(), 0);
delete menuDef;
delete res;
}
resource.close();
// Assign sensible IDs to the menu items
int numberOfMenus = menu->numberOfMenus();
for (int i = 0; i < numberOfMenus; i++) {
Graphics::MacMenuItem *item = menu->getMenuItem(i);
int numberOfMenuItems = menu->numberOfMenuItems(item);
int id = 100 * (i + 1);
for (int j = 0; j < numberOfMenuItems; j++) {
Graphics::MacMenuItem *subItem = menu->getSubMenuItem(item, j);
Common::String name = menu->getName(subItem);
if (!name.empty())
menu->setAction(subItem, id++);
}
}
}
// Register custom fonts. The font family just happens to match the
// printed name of the game.
const Common::String fontFamily = name();
const Common::Array &fontFamilies = _windowManager->_fontMan->getFontFamilies();
_windowManager->_fontMan->loadFonts(_resourceFile);
for (uint i = 0; i < fontFamilies.size(); i++) {
if (fontFamilies[i]->getName() == fontFamily) {
_gameFontId = fontFamilies[i]->getFontFamilyId();
break;
}
}
}
bool MacGuiImpl::handleMenu(int id, Common::String &name) {
// This menu item (e.g. a menu separator) has no action, so it's
// handled trivially.
if (id == 0)
return true;
// This is how we keep the menu bar visible.
Graphics::MacMenu *menu = _windowManager->getMenu();
// If the menu is opened through a shortcut key, force it to activate
// to avoid screen corruption. In that case, we also force the menu to
// close afterwards, or the game will stay paused. Which is
// particularly bad during a restart.
if (!menu->_active) {
_windowManager->activateMenu();
_forceMenuClosed = true;
}
menu->closeMenu();
menu->setActive(true);
menu->setVisible(true);
updateWindowManager();
int saveSlotToHandle = -1;
Common::String savegameName;
switch (id) {
case 100: // About
runAboutDialog();
return true;
case 200: // Open
if (runOpenDialog(saveSlotToHandle)) {
if (saveSlotToHandle > -1) {
_vm->loadGameState(saveSlotToHandle);
if (_vm->_game.id == GID_INDY3)
((ScummEngine_v4 *)_vm)->updateIQPoints();
}
}
return true;
case 201: // Save
_vm->beginTextInput();
if (runSaveDialog(saveSlotToHandle, savegameName)) {
if (saveSlotToHandle > -1) {
_vm->saveGameState(saveSlotToHandle, savegameName);
}
}
_vm->endTextInput();
return true;
case 202: // Restart
if (runRestartDialog())
_vm->restart();
return true;
case 203: // Pause
if (!_vm->_messageBannerActive) {
menu->closeMenu();
if (_vm->_game.version == 3)
_vm->mac_showOldStyleBannerAndPause(_vm->getGUIString(gsPause), -1);
else
_vm->showBannerAndPause(0, -1, _vm->getGUIString(gsPause));
}
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;
}
return false;
}
void MacGuiImpl::updateWindowManager() {
Graphics::MacMenu *menu = _windowManager->getMenu();
if (!menu)
return;
// We want the arrow cursor for menus. Note that the menu triggers even
// when the mouse is invisible, which may or may not be a bug. But the
// original did allow you to open the menu with Alt even when the
// cursor was visible, so for now it's a feature.
bool isActive = _windowManager->isMenuActive();
bool saveCondition = true;
bool loadCondition = true;
if (_vm->_game.id == GID_INDY3) {
// Taken from Mac disasm...
// The VAR(94) part tells us whether the copy protection has
// failed or not, while the VAR(58) part uses bitmasks to enable
// or disable saving and loading during normal gameplay.
saveCondition = (_vm->VAR(58) & 0x01) && !(_vm->VAR(94) & 0x10);
loadCondition = (_vm->VAR(58) & 0x02) && !(_vm->VAR(94) & 0x10);
} else if (_vm->_game.id == GID_LOOM) {
// TODO: Complete LOOM with the rest of the proper code from disasm,
// for now we only have the copy protection code and a best guess in place...
//
// Details:
// VAR(221) & 0x4000: Copy protection bit (the only thing I could confirm from the disasm)
// VAR(VAR_VERB_SCRIPT) == 5: Best guess... it prevents saving/loading from e.g. difficulty selection screen
// _userPut > 0: Best guess... it prevents saving/loading during cutscenes
saveCondition = loadCondition =
!(_vm->VAR(221) & 0x4000) &&
(_vm->VAR(_vm->VAR_VERB_SCRIPT) == 5) &&
(_vm->_userPut > 0);
} else {
saveCondition = true;
loadCondition = true;
}
bool canLoad = _vm->canLoadGameStateCurrently() && loadCondition;
bool canSave = _vm->canSaveGameStateCurrently() && saveCondition;
Graphics::MacMenuItem *gameMenu = menu->getMenuItem("Game");
Graphics::MacMenuItem *loadMenu = menu->getSubMenuItem(gameMenu, 0);
Graphics::MacMenuItem *saveMenu = menu->getSubMenuItem(gameMenu, 1);
if (loadMenu)
loadMenu->enabled = canLoad;
if (saveMenu)
saveMenu->enabled = canSave;
if (isActive) {
if (!_menuIsActive) {
_cursorWasVisible = CursorMan.showMouse(true);
_windowManager->pushCursor(Graphics::MacGUIConstants::kMacCursorArrow);
}
} else {
if (_menuIsActive) {
if (_windowManager->getCursorType() == Graphics::MacGUIConstants::kMacCursorArrow)
_windowManager->popCursor();
CursorMan.showMouse(_cursorWasVisible);
}
}
if (_vm->_game.version > 3) {
Graphics::MacMenuItem *windowMenu = menu->getMenuItem("Window");
Graphics::MacMenuItem *hideDesktopMenu = menu->getSubMenuItem(windowMenu, 0);
Graphics::MacMenuItem *hideBarMenu = menu->getSubMenuItem(windowMenu, 1);
hideDesktopMenu->enabled = false;
hideBarMenu->enabled = false;
// "Fix color map"
menu->getSubMenuItem(gameMenu, 5)->enabled = false;
// Window mode
menu->getSubMenuItem(windowMenu, 3)->enabled = false;
menu->getSubMenuItem(windowMenu, 4)->enabled = false;
menu->getSubMenuItem(windowMenu, 5)->enabled = false;
if (menu->numberOfMenuItems(windowMenu) >= 8)
menu->getSubMenuItem(windowMenu, 7)->checked = _vm->_useMacGraphicsSmoothing;
Graphics::MacMenuItem *speechMenu = menu->getMenuItem("Speech");
if (speechMenu) {
menu->getSubMenuItem(speechMenu, 0)->checked = false; // Voice Only
menu->getSubMenuItem(speechMenu, 1)->checked = false; // Text Only
menu->getSubMenuItem(speechMenu, 2)->checked = false; // Voice and Text
switch (_vm->_voiceMode) {
case 0: // Voice Only
menu->getSubMenuItem(speechMenu, 0)->checked = true;
break;
case 1: // Voice and Text
menu->getSubMenuItem(speechMenu, 2)->checked = true;
break;
case 2: // Text Only
menu->getSubMenuItem(speechMenu, 1)->checked = true;
break;
default:
warning("MacGuiImpl::updateWindowManager(): Invalid voice mode");
}
}
}
_menuIsActive = isActive;
_windowManager->draw();
}
// ---------------------------------------------------------------------------
// Font handling
// ---------------------------------------------------------------------------
const Graphics::Font *MacGuiImpl::getFont(FontId fontId) {
const Graphics::Font *font = _fonts.getValOrDefault((int)fontId);
if (font)
return font;
int id;
int size;
int slant;
switch (fontId) {
case kSystemFont:
id = Graphics::kMacFontChicago;
size = 12;
slant = Graphics::kMacFontRegular;
break;
default:
getFontParams(fontId, id, size, slant);
break;
}
font = _windowManager->_fontMan->getFont(Graphics::MacFont(id, size, slant));
_fonts[(int)fontId] = font;
return font;
}
bool MacGuiImpl::getFontParams(FontId fontId, int &id, int &size, int &slant) const {
switch (fontId) {
case kAboutFontHeaderOutside:
id = _gameFontId;
size = 12;
slant = Graphics::kMacFontItalic | Graphics::kMacFontBold | Graphics::kMacFontOutline;
return true;
case kAboutFontHeaderInside:
id = _gameFontId;
size = 12;
slant = Graphics::kMacFontItalic | Graphics::kMacFontBold | Graphics::kMacFontExtend;
return true;
case kAboutFontRegular:
id = Graphics::kMacFontGeneva;
size = 9;
slant = Graphics::kMacFontRegular;
return true;
case kAboutFontBold:
id = _gameFontId;
size = 9;
slant = Graphics::kMacFontRegular;
return true;
case kAboutFontExtraBold:
id = _gameFontId;
size = 9;
slant = Graphics::kMacFontBold;
return true;
default:
return false;
}
}
// ---------------------------------------------------------------------------
// PICT loader
// ---------------------------------------------------------------------------
Graphics::Surface *MacGuiImpl::loadPict(int id) {
Common::MacResManager resource;
Graphics::Surface *s = nullptr;
resource.open(_resourceFile);
Common::SeekableReadStream *res = resource.getResource(MKTAG('P', 'I', 'C', 'T'), id);
Image::PICTDecoder pict;
if (pict.loadStream(*res)) {
const Graphics::Surface *s1 = pict.getSurface();
const byte *palette = pict.getPalette();
s = new Graphics::Surface();
s->create(s1->w, s1->h, Graphics::PixelFormat::createFormatCLUT8());
byte paletteMap[256];
memset(paletteMap, 0, ARRAYSIZE(paletteMap));
for (int i = 0; i < pict.getPaletteColorCount(); i++) {
int r = palette[3 * i];
int g = palette[3 * i + 1];
int b = palette[3 * i + 2];
uint32 c = _windowManager->findBestColor(r, g, b);
paletteMap[i] = c;
}
if (!pict.getPaletteColorCount()) {
paletteMap[0] = getWhite();
paletteMap[1] = getBlack();
}
if (palette) {
for (int y = 0; y < s->h; y++) {
for (int x = 0; x < s->w; x++) {
int color = s1->getPixel(x, y);
s->setPixel(x, y, paletteMap[color]);
}
}
} else
s->copyFrom(*s1);
}
delete res;
resource.close();
return s;
}
// ---------------------------------------------------------------------------
// Window handling
// ---------------------------------------------------------------------------
MacGuiImpl::MacDialogWindow *MacGuiImpl::createWindow(Common::Rect bounds, MacDialogWindowStyle windowStyle, MacDialogMenuStyle menuStyle) {
if (bounds.left < 0 || bounds.top < 0 || bounds.right >= 640 || bounds.bottom >= 400) {
// This happens with the Last Crusade file dialogs.
bounds.moveTo((640 - bounds.width()) / 2, 27);
}
// Adjust the dialog to the actual screen size. This is slightly
// wasteful since we've already adjusted the dialog coordinates for
// 640x400 pixels, but that may not be a bad thing if we want to keep
// support for that resolution later.
bounds.translate(0, 2 * _vm->_macScreenDrawOffset);
return new MacDialogWindow(this, _system, _surface, bounds, windowStyle, menuStyle);
}
Common::String MacGuiImpl::getDialogString(Common::SeekableReadStream *res, int len) {
Common::String str;
for (int i = 0; i < len; i++)
str += res->readByte();
return str;
}
MacGuiImpl::MacDialogWindow *MacGuiImpl::createDialog(int dialogId) {
uint32 black = getBlack();
Common::MacResManager resource;
Common::SeekableReadStream *res;
Common::String saveGameFileAsResStr, gameFileResStr;
saveGameFileAsResStr = _strsStrings[kMSISaveGameFileAs].c_str();
gameFileResStr = _strsStrings[kMSIGameFile].c_str();
resource.open(_resourceFile);
Common::Rect bounds;
bool isOpenDialog = dialogId == 4000 || dialogId == 4001;
bool isSaveDialog = dialogId == 3998 || dialogId == 3999;
res = resource.getResource(MKTAG('D', 'L', 'O', 'G'), dialogId);
if (res) {
bounds.top = res->readUint16BE();
bounds.left = res->readUint16BE();
bounds.bottom = res->readUint16BE();
bounds.right = res->readUint16BE();
// Grow the window to include the outer bounds
bounds.grow(8);
// Compensate for the original not drawing the game at the very top of
// the screen.
bounds.translate(0, -40);
} else {
bounds.top = 0;
bounds.left = 0;
bounds.bottom = 86;
bounds.right = 340;
bounds.translate(86, 88);
}
delete res;
MacDialogWindow *window = createWindow(bounds);
res = resource.getResource(MKTAG('D', 'I', 'T', 'L'), dialogId);
if (res) {
int numItems = res->readUint16BE() + 1;
for (int i = 0; i < numItems; i++) {
res->skip(4); // Placeholder for handle or procedure pointer
Common::Rect r;
r.top = res->readUint16BE();
r.left = res->readUint16BE();
r.bottom = res->readUint16BE();
r.right = res->readUint16BE();
// Move to appropriate position on inner surface
r.translate(2, 2);
int type = res->readByte();
int len = res->readByte();
Common::String str;
bool enabled = ((type & 0x80) == 0);
switch (type & 0x7F) {
case 0:
{
// User item
// Skip drive label box and listbox
bool doNotDraw = (isOpenDialog && (i == 6 || i == 7)) || ((isOpenDialog || isSaveDialog) && i == 3);
if (!doNotDraw) {
window->innerSurface()->frameRect(r, black);
} else if (_vm->_game.id == GID_INDY3 && i == 3) {
drawFakeDriveLabel(window, Common::Rect(r.left + 5, r.top, r.right, r.bottom), "ScummVM");
}
break;
}
case 4:
// Button
str = getDialogString(res, len);
if ((isOpenDialog || isSaveDialog) && (i == 4 || i == 5)) // "Eject" and "Drive"
enabled = false;
window->addButton(r, str, enabled);
break;
case 5:
// Checkbox
str = getDialogString(res, len);
window->addCheckbox(r, str, enabled);
break;
case 8:
// Static text
str = getDialogString(res, len);
if (isSaveDialog && i == 2)
str = saveGameFileAsResStr;
window->addStaticText(r, str, enabled);
break;
case 16:
{
// Editable text
// Adjust for pixel accuracy...
r.left -= 1;
MacGuiImpl::MacEditText *editText = window->addEditText(r, gameFileResStr, enabled);
editText->selectAll();
window->innerSurface()->frameRect(Common::Rect(r.left - 2, r.top - 3, r.right + 3, r.bottom + 3), black);
res->skip(len);
break;
}
case 64:
// Picture
window->addPicture(r, res->readUint16BE(), enabled);
break;
default:
warning("MacGuiImpl::createDialog(): Unknown item type %d", type);
res->skip(len);
break;
}
if (len & 1)
res->skip(1);
}
}
delete res;
resource.close();
return window;
}
// ---------------------------------------------------------------------------
// Standard dialogs
// ---------------------------------------------------------------------------
// A standard file picker dialog doesn't really make sense in ScummVM, so we
// make something that just looks similar to one.
bool MacGuiImpl::runOpenDialog(int &saveSlotToHandle) {
Common::Rect bounds(88, 28, 448, 210);
MacDialogWindow *window = createWindow(bounds);
MacButton *buttonOpen = window->addButton(Common::Rect(254, 137, 334, 157), "Open", true);
MacButton *buttonCancel = window->addButton(Common::Rect(254, 106, 334, 126), "Cancel", true);
window->addButton(Common::Rect(254, 62, 334, 82), "Desktop", false);
window->addButton(Common::Rect(254, 34, 334, 54), "Eject", false);
window->drawDottedHLine(253, 93, 334);
bool availSlots[100];
int slotIds[100];
Common::StringArray savegameNames;
prepareSaveLoad(savegameNames, availSlots, slotIds, ARRAYSIZE(availSlots));
drawFakePathList(window, Common::Rect(14, 8, 232, 27), name().c_str());
drawFakeDriveLabel(window, Common::Rect(232, 10, 344, 28), "ScummVM");
MacListBox *listBox = window->addListBox(Common::Rect(14, 31, 232, 161), savegameNames, true);
window->setDefaultWidget(buttonOpen);
// When quitting, the default action is to not open a saved game
bool ret = false;
Common::Array deferredActionsIds;
while (!_vm->shouldQuit()) {
int clicked = window->runDialog(deferredActionsIds);
if (clicked == buttonOpen->getId() || clicked == listBox->getId()) {
saveSlotToHandle =
listBox->getValue() < ARRAYSIZE(slotIds) ?
slotIds[listBox->getValue()] : -1;
ret = true;
break;
}
if (clicked == buttonCancel->getId())
break;
}
delete window;
return ret;
}
bool MacGuiImpl::runSaveDialog(int &saveSlotToHandle, Common::String &saveName) {
uint32 black = getBlack();
Common::Rect bounds(110, 27, 470, 231);
MacDialogWindow *window = createWindow(bounds);
MacButton *buttonSave = window->addButton(Common::Rect(254, 163, 334, 183), "Save", true);
MacButton *buttonCancel = window->addButton(Common::Rect(254, 132, 334, 152), "Cancel", true);
window->addButton(Common::Rect(254, 90, 334, 110), "New", false);
window->addButton(Common::Rect(254, 62, 334, 82), "Desktop", false);
window->addButton(Common::Rect(254, 34, 334, 54), "Eject", false);
bool busySlots[100];
int slotIds[100];
Common::StringArray savegameNames;
prepareSaveLoad(savegameNames, busySlots, slotIds, ARRAYSIZE(busySlots));
Common::String saveGameFileAsResStr = _strsStrings[kMSISaveGameFileAs].c_str();
Common::String gameFileResStr = _strsStrings[kMSIGameFile].c_str();
int firstAvailableSlot = -1;
for (int i = 1; i < ARRAYSIZE(busySlots); i++) { // Skip the autosave slot
if (!busySlots[i]) {
firstAvailableSlot = i;
break;
}
}
drawFakePathList(window, Common::Rect(14, 8, 232, 27), name().c_str());
drawFakeDriveLabel(window, Common::Rect(232, 10, 344, 28), "ScummVM");
window->addListBox(Common::Rect(14, 31, 232, 129), savegameNames, true, true);
MacGuiImpl::MacEditText *editText = window->addEditText(Common::Rect(16, 159, 229, 175), gameFileResStr, true);
Graphics::Surface *s = window->innerSurface();
const Graphics::Font *font = getFont(kSystemFont);
s->frameRect(Common::Rect(14, 156, 232, 178), black);
window->drawDottedHLine(253, 121, 334);
font->drawString(s, saveGameFileAsResStr, 14, 138, 218, black, Graphics::kTextAlignLeft, 4);
window->setDefaultWidget(buttonSave);
editText->selectAll();
// When quitting, the default action is to not open a saved game
bool ret = false;
Common::Array deferredActionsIds;
while (!_vm->shouldQuit()) {
int clicked = window->runDialog(deferredActionsIds);
if (clicked == buttonSave->getId()) {
ret = true;
saveName = editText->getText();
saveSlotToHandle = firstAvailableSlot;
break;
}
if (clicked == buttonCancel->getId())
break;
if (clicked == kDialogWantsAttention) {
// Cycle through deferred actions
for (uint i = 0; i < deferredActionsIds.size(); i++) {
if (deferredActionsIds[i] == editText->getId()) {
// Disable "Save" button when text is empty
buttonSave->setEnabled(!editText->getText().empty());
}
}
}
}
delete window;
return ret;
}
void MacGuiImpl::prepareSaveLoad(Common::StringArray &savegameNames, bool *availSlots, int *slotIds, int size) {
int saveCounter = 0;
for (int i = 0; i < size; i++) {
slotIds[i] = -1;
}
Common::String name;
_vm->listSavegames(availSlots, size);
for (int i = 0; i < size; i++) {
if (availSlots[i]) {
// Save the slot ids for slots which actually contain savegames...
slotIds[saveCounter] = i;
saveCounter++;
if (_vm->getSavegameName(i, name)) {
Common::String temp = Common::U32String(name, _vm->getDialogCodePage()).encode(Common::kMacRoman);
savegameNames.push_back(temp);
} else {
// The original printed "WARNING... old savegame", but we do support old savegames :-)
savegameNames.push_back(Common::String::format("%s", "WARNING: wrong save version"));
}
}
}
}
bool MacGuiImpl::runOkCancelDialog(Common::String text) {
// Widgets:
//
// 0 - Okay button
// 1 - Cancel button
// 2 - "^0" text
MacDialogWindow *window = createDialog(502);
MacButton *buttonOk = (MacButton *)window->getWidget(kWidgetButton, 0);
MacButton *buttonCancel = (MacButton *)window->getWidget(kWidgetButton, 1);
MacStaticText *textWidget = (MacStaticText *)window->getWidget(kWidgetStaticText);
textWidget->setWordWrap(true);
window->setDefaultWidget(buttonOk);
window->addSubstitution(text);
// When quitting, the default action is to quit
bool ret = true;
Common::Array deferredActionsIds;
while (!_vm->shouldQuit()) {
int clicked = window->runDialog(deferredActionsIds);
if (clicked == buttonOk->getId())
break;
if (clicked == buttonCancel->getId()) {
ret = false;
break;
}
}
delete window;
return ret;
}
void MacGuiImpl::drawFakePathList(MacDialogWindow *window, Common::Rect r, const char *text) {
uint32 black = getBlack();
const Graphics::Font *font = getFont(kSystemFont);
// Draw the text...
int x0 = r.left + 23;
int x1 = r.right - 23;
font->drawString(window->innerSurface(), text, x0 + 1, r.top + 2, x1 - x0, black, Graphics::kTextAlignCenter, 0, true);
int width = font->getStringWidth(text);
if (width < x1 - x0) {
int middle = (x0 + x1) / 2;
x0 = middle - width / 2;
x1 = middle + width / 2;
}
Common::Rect iconRect(16, 16);
const uint16 folderIcon[16] = {
0x0000, 0x0000, 0x0000, 0x0000, 0x1E00, 0x21FC, 0x2002, 0xFFE2,
0x8012, 0x4012, 0x400A, 0x200A, 0x2006, 0x1FFE, 0x0000, 0x0000
};
const uint16 arrowDownIcon[16] = {
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x3FF8, 0x1FF0, 0x0FE0,
0x07C0, 0x0380, 0x0100, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
};
// Draw the icon...
iconRect.moveTo(x0 - 19, r.top + 1);
drawBitmap(window->innerSurface(), iconRect, folderIcon, black);
// Draw the arrow...
iconRect.moveTo(x1 + 4, r.top + 1);
drawBitmap(window->innerSurface(), iconRect, arrowDownIcon, black);
// Draw the black frame...
window->innerSurface()->frameRect(Common::Rect(x0 - 23, r.top, x1 + 23, r.bottom - 1), black);
// Draw the shadows...
window->innerSurface()->hLine(x0 - 20, r.bottom - 1, x1 + 23, black);
window->innerSurface()->vLine(x1 + 23, r.top + 3, r.bottom - 1, black);
}
void MacGuiImpl::drawFakeDriveLabel(MacDialogWindow *window, Common::Rect r, const char *text) {
uint32 black = getBlack();
const Graphics::Font *font = getFont(kSystemFont);
font->drawString(window->innerSurface(), text, r.left, r.top, r.width(), black, Graphics::kTextAlignCenter, 9, true);
const uint16 hardDriveIcon[16] = {
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x7FFE, 0x8001, 0x8001,
0xA001, 0x8001, 0x7FFE, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
};
Common::Rect iconRect(16, 16);
int width = font->getStringWidth(text);
if (width < r.width())
iconRect.moveTo((r.left + r.right - width - 20) / 2, r.top);
else
iconRect.moveTo(r.left, r.top);
drawBitmap(window->innerSurface(), iconRect, hardDriveIcon, black);
}
bool MacGuiImpl::runQuitDialog() {
return runOkCancelDialog(_strsStrings[kMSIAreYouSureYouWantToQuit].c_str());
}
bool MacGuiImpl::runRestartDialog() {
return runOkCancelDialog(_strsStrings[kMSIAreYouSureYouWantToRestart].c_str());
}
void MacGuiImpl::drawBanner(char *message) {
if (_bannerWindow)
undrawBanner();
_bannerWindow = createWindow(
Common::Rect(70, 189, 570, 211),
kWindowStyleRounded, kMenuStyleNone);
const Graphics::Font *font = getFont(_vm->_game.id == GID_INDY3 ? kIndy3FontMedium : kLoomFontMedium);
Graphics::Surface *s = _bannerWindow->innerSurface();
font->drawString(s, (char *)message, 0, 0, s->w, getBlack(), Graphics::kTextAlignCenter);
_bannerWindow->show();
}
void MacGuiImpl::undrawBanner() {
if (_bannerWindow) {
delete _bannerWindow;
_bannerWindow = nullptr;
}
}
void MacGuiImpl::drawBitmap(Graphics::Surface *s, Common::Rect r, const uint16 *bitmap, uint32 color) const {
assert(r.width() <= 16);
for (int y = 0; y < r.height(); y++) {
uint16 bit = 0x8000;
for (int x = 0; x < r.width(); x++) {
if (bitmap[y] & bit)
s->setPixel(r.left + x, r.top + y, color);
bit >>= 1;
}
}
}
} // End of namespace Scumm