1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "common/config-manager.h"
24 #include "common/translation.h"
25 
26 #include "audio/mixer.h"
27 
28 #include "gui/saveload.h"
29 
30 #include "neverhood/menumodule.h"
31 #include "neverhood/gamemodule.h"
32 
33 #include "engines/savestate.h"
34 
35 namespace Neverhood {
36 
37 enum {
38 	MAIN_MENU			= 0,
39 	CREDITS_SCENE		= 1,
40 	MAKING_OF			= 2,
41 	LOAD_GAME_MENU		= 3,
42 	SAVE_GAME_MENU		= 4,
43 	DELETE_GAME_MENU	= 5,
44 	QUERY_OVR_MENU		= 6
45 };
46 
47 enum {
48 	kMainMenuRestartGame	= 0,
49 	kMainMenuLoadGame		= 1,
50 	kMainMenuSaveGame		= 2,
51 	kMainMenuResumeGame		= 3,
52 	kMainMenuQuitGame		= 4,
53 	kMainMenuCredits		= 5,
54 	kMainMenuMakingOf		= 6,
55 	kMainMenuToggleMusic	= 7,
56 	kMainMenuDeleteGame		= 8
57 };
58 
59 static const uint32 kMakingOfSmackerFileHashList[] = {
60 	0x21082409,
61 	0x21082809,
62 	0x21083009,
63 	0x21080009,
64 	0x21086009,
65 	0x2108A009,
66 	0x21092009,
67 	0x210A2009,
68 	0x210C2009,
69 	0x21082411,
70 	0x21082811,
71 	0x21083011,
72 	0x21080011,
73 	0
74 };
75 
MenuModule(NeverhoodEngine * vm,Module * parentModule,int which)76 MenuModule::MenuModule(NeverhoodEngine *vm, Module *parentModule, int which)
77 	: Module(vm, parentModule), _savegameList(NULL) {
78 
79 	SetMessageHandler(&MenuModule::handleMessage);
80 
81 	_savedPaletteData = _vm->_screen->getPaletteData();
82 	_vm->_mixer->pauseAll(true);
83 	_vm->toggleSoundUpdate(false);
84 
85 	createScene(MAIN_MENU, -1);
86 }
87 
~MenuModule()88 MenuModule::~MenuModule() {
89 	_vm->_mixer->pauseAll(false);
90 	_vm->toggleSoundUpdate(true);
91 	_vm->_screen->setPaletteData(_savedPaletteData);
92 }
93 
setLoadgameInfo(uint index)94 void MenuModule::setLoadgameInfo(uint index) {
95 	_savegameSlot = (*_savegameList)[index].slotNum;
96 }
97 
setLoadgameSlot(int slot)98 void MenuModule::setLoadgameSlot(int slot) {
99 	_savegameSlot = slot;
100 }
101 
setSavegameInfo(const Common::String & description,uint index,bool newSavegame)102 void MenuModule::setSavegameInfo(const Common::String &description, uint index, bool newSavegame) {
103 	_savegameDescription = description;
104 	_savegameSlot = newSavegame ? -1 : (*_savegameList)[index].slotNum;
105 }
106 
setDeletegameInfo(uint index)107 void MenuModule::setDeletegameInfo(uint index) {
108 	_savegameSlot = (*_savegameList)[index].slotNum;
109 }
110 
createScene(int sceneNum,int which)111 void MenuModule::createScene(int sceneNum, int which) {
112 	_sceneNum = sceneNum;
113 	switch (_sceneNum) {
114 	case MAIN_MENU:
115 		_childObject = new MainMenu(_vm, this);
116 		break;
117 	case CREDITS_SCENE:
118 		_childObject = new CreditsScene(_vm, this, true);
119 		break;
120 	case MAKING_OF:
121 		createSmackerScene(kMakingOfSmackerFileHashList, ConfMan.getBool("scalemakingofvideos"), true, true);
122 		break;
123 	case LOAD_GAME_MENU:
124 		createLoadGameMenu();
125 		break;
126 	case SAVE_GAME_MENU:
127 		createSaveGameMenu();
128 		break;
129 	case DELETE_GAME_MENU:
130 		createDeleteGameMenu();
131 		break;
132 	case QUERY_OVR_MENU:
133 		_childObject = new QueryOverwriteMenu(_vm, this, _savegameDescription);
134 		break;
135 	default:
136 		break;
137 	}
138 	SetUpdateHandler(&MenuModule::updateScene);
139 	_childObject->handleUpdate();
140 }
141 
updateScene()142 void MenuModule::updateScene() {
143 	if (!updateChild()) {
144 		switch (_sceneNum) {
145 		case MAIN_MENU:
146 			switch (_moduleResult) {
147 			case kMainMenuRestartGame:
148 				_vm->_gameModule->requestRestartGame(false);
149 				leaveModule(0);
150 				break;
151 			case kMainMenuLoadGame:
152 				createScene(LOAD_GAME_MENU, -1);
153 				break;
154 			case kMainMenuSaveGame:
155 				createScene(SAVE_GAME_MENU, -1);
156 				break;
157 			case kMainMenuResumeGame:
158 				leaveModule(0);
159 				break;
160 			case kMainMenuQuitGame:
161 				_vm->quitGame();
162 				break;
163 			case kMainMenuCredits:
164 				createScene(CREDITS_SCENE, -1);
165 				break;
166 			case kMainMenuMakingOf:
167 				createScene(MAKING_OF, -1);
168 				break;
169 			case kMainMenuToggleMusic:
170 				_vm->toggleMusic(!_vm->musicIsEnabled());
171 				_vm->_mixer->muteSoundType(Audio::Mixer::kMusicSoundType, !_vm->musicIsEnabled());
172 				createScene(MAIN_MENU, -1);
173 				break;
174 			case kMainMenuDeleteGame:
175 				createScene(DELETE_GAME_MENU, -1);
176 				break;
177 			default:
178 				createScene(MAIN_MENU, -1);
179 				break;
180 			}
181 			break;
182 		case CREDITS_SCENE:
183 		case MAKING_OF:
184 			createScene(MAIN_MENU, -1);
185 			break;
186 		case LOAD_GAME_MENU:
187 			handleLoadGameMenuAction(_moduleResult != 1);
188 			break;
189 		case SAVE_GAME_MENU:
190 			handleSaveGameMenuAction(_moduleResult != 1, true);
191 			break;
192 		case DELETE_GAME_MENU:
193 			handleDeleteGameMenuAction(_moduleResult != 1);
194 			break;
195 		case QUERY_OVR_MENU:
196 			handleSaveGameMenuAction(_moduleResult != 1, false);
197 			break;
198 		default:
199 			break;
200 		}
201 	}
202 }
203 
handleMessage(int messageNum,const MessageParam & param,Entity * sender)204 uint32 MenuModule::handleMessage(int messageNum, const MessageParam &param, Entity *sender) {
205 	switch(messageNum) {
206 	case NM_KEYPRESS_ESC:
207 		leaveModule(0);
208 		break;
209 	default:
210 		break;
211 	}
212 
213 	return Module::handleMessage(messageNum, param, sender);
214 }
215 
createLoadGameMenu()216 void MenuModule::createLoadGameMenu() {
217 	refreshSaveGameList();
218 	_childObject = new LoadGameMenu(_vm, this, _savegameList);
219 }
220 
createSaveGameMenu()221 void MenuModule::createSaveGameMenu() {
222 	refreshSaveGameList();
223 	_childObject = new SaveGameMenu(_vm, this, _savegameList);
224 }
225 
createDeleteGameMenu()226 void MenuModule::createDeleteGameMenu() {
227 	refreshSaveGameList();
228 	_childObject = new DeleteGameMenu(_vm, this, _savegameList);
229 }
230 
refreshSaveGameList()231 void MenuModule::refreshSaveGameList() {
232 	_savegameSlot = -1;
233 	delete _savegameList;
234 	_savegameList = NULL;
235 	_savegameList = new SavegameList();
236 	loadSavegameList();
237 }
238 
handleLoadGameMenuAction(bool doLoad)239 void MenuModule::handleLoadGameMenuAction(bool doLoad) {
240 	createScene(MAIN_MENU, -1);
241 	if (doLoad && _savegameSlot >= 0) {
242 		_vm->loadGameState(_savegameSlot);
243 		leaveModule(0);
244 	}
245 	delete _savegameList;
246 	_savegameList = NULL;
247 }
248 
handleSaveGameMenuAction(bool doSave,bool doQuery)249 void MenuModule::handleSaveGameMenuAction(bool doSave, bool doQuery) {
250 	if (doSave && doQuery && _savegameSlot >= 0) {
251 		createScene(QUERY_OVR_MENU, -1);
252 	} else if (doSave) {
253 		// Get a new slot number if it's a new savegame
254 		if (_savegameSlot < 0)
255 			_savegameSlot = _savegameList->size() > 0 ? _savegameList->back().slotNum + 1 : 0;
256 		// Restore the scene palette and background so that the correct thumbnail is saved
257 		byte *menuPaletteData = _vm->_screen->getPaletteData();
258 		_vm->_screen->setPaletteData(_savedPaletteData);
259 		_vm->_gameModule->redrawPrevChildObject();
260 		_vm->saveGameState(_savegameSlot, _savegameDescription);
261 		_vm->_screen->setPaletteData(menuPaletteData);
262 		createScene(MAIN_MENU, -1);
263 	} else {
264 		createScene(MAIN_MENU, -1);
265 	}
266 	delete _savegameList;
267 	_savegameList = NULL;
268 }
269 
handleDeleteGameMenuAction(bool doDelete)270 void MenuModule::handleDeleteGameMenuAction(bool doDelete) {
271 	createScene(MAIN_MENU, -1);
272 	if (doDelete && _savegameSlot >= 0) {
273 		_vm->removeGameState(_savegameSlot);
274 	}
275 	delete _savegameList;
276 	_savegameList = NULL;
277 }
278 
loadSavegameList()279 void MenuModule::loadSavegameList() {
280 
281 	Common::SaveFileManager *saveFileMan = _vm->_system->getSavefileManager();
282 	Neverhood::NeverhoodEngine::SaveHeader header;
283 	Common::String pattern = _vm->getTargetName();
284 	pattern += ".???";
285 
286 	Common::StringArray filenames;
287 	filenames = saveFileMan->listSavefiles(pattern.c_str());
288 	Common::sort(filenames.begin(), filenames.end());
289 
290 	SaveStateList saveList;
291 	for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); file++) {
292 		int slotNum = atoi(file->c_str() + file->size() - 3);
293 		if (slotNum >= 0 && slotNum <= 999) {
294 			Common::InSaveFile *in = saveFileMan->openForLoading(file->c_str());
295 			if (in) {
296 				if (Neverhood::NeverhoodEngine::readSaveHeader(in, header) == Neverhood::NeverhoodEngine::kRSHENoError) {
297 					SavegameItem savegameItem;
298 					savegameItem.slotNum = slotNum;
299 					savegameItem.description = header.description;
300 					_savegameList->push_back(savegameItem);
301 				}
302 				delete in;
303 			}
304 		}
305 	}
306 
307 }
308 
MenuButton(NeverhoodEngine * vm,Scene * parentScene,uint buttonIndex,uint32 fileHash,const NRect & collisionBounds)309 MenuButton::MenuButton(NeverhoodEngine *vm, Scene *parentScene, uint buttonIndex, uint32 fileHash, const NRect &collisionBounds)
310 	: StaticSprite(vm, 900), _parentScene(parentScene), _buttonIndex(buttonIndex), _countdown(0) {
311 
312 	loadSprite(fileHash, kSLFDefDrawOffset | kSLFDefPosition, 100);
313 	_collisionBounds = collisionBounds;
314 	setVisible(false);
315 	SetUpdateHandler(&MenuButton::update);
316 	SetMessageHandler(&MenuButton::handleMessage);
317 }
318 
update()319 void MenuButton::update() {
320 	updatePosition();
321 	if (_countdown != 0 && (--_countdown) == 0) {
322 		setVisible(false);
323 		sendMessage(_parentScene, 0x2000, _buttonIndex);
324 	}
325 }
326 
handleMessage(int messageNum,const MessageParam & param,Entity * sender)327 uint32 MenuButton::handleMessage(int messageNum, const MessageParam &param, Entity *sender) {
328 	uint32 messageResult = Sprite::handleMessage(messageNum, param, sender);
329 	switch (messageNum) {
330 	case 0x1011:
331 		if (_countdown == 0) {
332 			setVisible(true);
333 			_countdown = 4;
334 		}
335 		messageResult = 1;
336 		break;
337 	default:
338 		break;
339 	}
340 	return messageResult;
341 }
342 
MainMenu(NeverhoodEngine * vm,Module * parentModule)343 MainMenu::MainMenu(NeverhoodEngine *vm, Module *parentModule)
344 	: Scene(vm, parentModule) {
345 
346 	static const uint32 kMenuButtonFileHashes[] = {
347 		0x36C62120,
348 		0x56C62120,
349 		0x96C62120,
350 		0x16C62121,
351 		0x16C62122,
352 		0x16C62124,
353 		0x16C62128,
354 		0x16C62130,
355 		0x16C62100
356 	};
357 
358 	static const NRect kMenuButtonCollisionBounds[] = {
359 		{  52, 121, 110, 156 },
360 		{  52, 192, 109, 222 },
361 		{  60, 257, 119, 286 },
362 		{  67, 326, 120, 354 },
363 		{  70, 389, 128, 416 },
364 		{ 523, 113, 580, 144 },
365 		{ 525, 176, 577, 206 },
366 		{ 527, 384, 580, 412 },
367 		{ 522, 255, 580, 289 }
368 	};
369 
370 	setBackground(0x08C0020C);
371 	setPalette(0x08C0020C);
372 	insertScreenMouse(0x00208084);
373 
374 	insertStaticSprite(0x41137051, 100);	// "Options" header text
375 	insertStaticSprite(0xC10B2015, 100);	// Button texts
376 
377 	if (!_vm->musicIsEnabled())
378 		insertStaticSprite(0x0C24C0EE, 100);	// "Music is off" button
379 
380 	for (uint buttonIndex = 0; buttonIndex < 9; ++buttonIndex) {
381 		Sprite *menuButton = insertSprite<MenuButton>(this, buttonIndex,
382 			kMenuButtonFileHashes[buttonIndex], kMenuButtonCollisionBounds[buttonIndex]);
383 		addCollisionSprite(menuButton);
384 	}
385 
386 	SetUpdateHandler(&Scene::update);
387 	SetMessageHandler(&MainMenu::handleMessage);
388 
389 }
390 
handleMessage(int messageNum,const MessageParam & param,Entity * sender)391 uint32 MainMenu::handleMessage(int messageNum, const MessageParam &param, Entity *sender) {
392 	Scene::handleMessage(messageNum, param, sender);
393 	switch (messageNum) {
394 	case NM_ANIMATION_UPDATE:
395 		leaveScene(param.asInteger());
396 		break;
397 	default:
398 		break;
399 	}
400 	return 0;
401 }
402 
403 static const uint32 kCreditsSceneFileHashes[] = {
404 	0x6081128C, 0x608112BC, 0x608112DC,
405 	0x6081121C, 0x6081139C, 0x6081109C,
406 	0x6081169C, 0x60811A9C, 0x6081029C,
407 	0x0081128C, 0x008112BC, 0x008012BC,
408 	0x008112DC, 0x0081121C, 0x0081139C,
409 	0x0081109C, 0x0081169C, 0x00811A9C,
410 	0x0081029C, 0x0081329C, 0xC08112BC,
411 	0xC08112DC, 0xC081121C, 0xC081139C,
412 	0
413 };
414 
CreditsScene(NeverhoodEngine * vm,Module * parentModule,bool canAbort)415 CreditsScene::CreditsScene(NeverhoodEngine *vm, Module *parentModule, bool canAbort)
416 	: Scene(vm, parentModule), _canAbort(canAbort), _screenIndex(0), _ticksDuration(0),
417 	_countdown(216) {
418 
419 	SetUpdateHandler(&CreditsScene::update);
420 	SetMessageHandler(&CreditsScene::handleMessage);
421 
422 	setBackground(0x6081128C);
423 	setPalette(0x6081128C);
424 
425 	_ticksTime = _vm->_system->getMillis() + 202100;
426 
427 	_musicResource = new MusicResource(_vm);
428 	_musicResource->load(0x30812225);
429 	_musicResource->play(0);
430 
431 }
432 
~CreditsScene()433 CreditsScene::~CreditsScene() {
434 	_musicResource->unload();
435 	delete _musicResource;
436 }
437 
update()438 void CreditsScene::update() {
439 	Scene::update();
440 	if (_countdown != 0) {
441 		if (_screenIndex == 23 && _vm->_system->getMillis() > _ticksTime)
442 			leaveScene(0);
443 		else if ((--_countdown) == 0) {
444 			++_screenIndex;
445 			if (kCreditsSceneFileHashes[_screenIndex] == 0)
446 				leaveScene(0);
447 			else {
448 				_background->load(kCreditsSceneFileHashes[_screenIndex]);
449 				_palette->addPalette(kCreditsSceneFileHashes[_screenIndex], 0, 256, 0);
450 				if (_screenIndex < 5)
451 					_countdown = 192;
452 				else if (_screenIndex < 15)
453 					_countdown = 144;
454 				else if (_screenIndex < 16)
455 					_countdown = 216;
456 				else if (_screenIndex < 23)
457 					_countdown = 144;
458 				else
459 					_countdown = 1224;
460 			}
461 		}
462 	}
463 }
464 
handleMessage(int messageNum,const MessageParam & param,Entity * sender)465 uint32 CreditsScene::handleMessage(int messageNum, const MessageParam &param, Entity *sender) {
466 	Scene::handleMessage(messageNum, param, sender);
467 	switch (messageNum) {
468 	case NM_KEYPRESS_SPACE:
469 		leaveScene(0);
470 		break;
471 	case 0x000B:
472 		if (param.asInteger() == Common::KEYCODE_ESCAPE && _canAbort)
473 			leaveScene(0);
474 		break;
475 	case NM_MOUSE_HIDE:
476 		_ticksDuration = _ticksTime - _vm->_system->getMillis();
477 		break;
478 	case NM_MOUSE_SHOW:
479 		_ticksTime = _ticksDuration + _vm->_system->getMillis();
480 		break;
481 	default:
482 		break;
483 	}
484 	return 0;
485 }
486 
Widget(NeverhoodEngine * vm,int16 x,int16 y,GameStateMenu * parentScene,int baseObjectPriority,int baseSurfacePriority)487 Widget::Widget(NeverhoodEngine *vm, int16 x, int16 y, GameStateMenu *parentScene,
488 	int baseObjectPriority, int baseSurfacePriority)
489 	: StaticSprite(vm, baseObjectPriority), _parentScene(parentScene),
490 	_baseObjectPriority(baseObjectPriority), _baseSurfacePriority(baseSurfacePriority) {
491 
492 	SetUpdateHandler(&Widget::update);
493 	SetMessageHandler(&Widget::handleMessage);
494 
495 	setPosition(x, y);
496 }
497 
onClick()498 void Widget::onClick() {
499 	_parentScene->setCurrWidget(this);
500 }
501 
setPosition(int16 x,int16 y)502 void Widget::setPosition(int16 x, int16 y) {
503 	_x = x;
504 	_y = y;
505 	updateBounds();
506 }
507 
refreshPosition()508 void Widget::refreshPosition() {
509 	_needRefresh = true;
510 	StaticSprite::updatePosition();
511 	_collisionBoundsOffset.set(0, 0,
512 		_spriteResource.getDimensions().width, _spriteResource.getDimensions().height);
513 	updateBounds();
514 }
515 
initialize()516 void Widget::initialize() {
517 	// Empty
518 }
519 
getWidth()520 int16 Widget::getWidth() {
521 	return _spriteResource.getDimensions().width;
522 }
523 
getHeight()524 int16 Widget::getHeight() {
525 	return _spriteResource.getDimensions().height;
526 }
527 
enterWidget()528 void Widget::enterWidget() {
529 	// Empty
530 }
531 
exitWidget()532 void Widget::exitWidget() {
533 	// Empty
534 }
535 
update()536 void Widget::update() {
537 	handleSpriteUpdate();
538 	StaticSprite::updatePosition();
539 }
540 
handleMessage(int messageNum,const MessageParam & param,Entity * sender)541 uint32 Widget::handleMessage(int messageNum, const MessageParam &param, Entity *sender) {
542 	uint32 messageResult = Sprite::handleMessage(messageNum, param, sender);
543 	switch (messageNum) {
544 	case 0x1011:
545 		onClick();
546 		messageResult = 1;
547 		break;
548 	default:
549 		break;
550 	}
551 	return messageResult;
552 }
553 
TextLabelWidget(NeverhoodEngine * vm,int16 x,int16 y,GameStateMenu * parentScene,int baseObjectPriority,int baseSurfacePriority,const byte * string,int stringLen,BaseSurface * drawSurface,int16 tx,int16 ty,FontSurface * fontSurface)554 TextLabelWidget::TextLabelWidget(NeverhoodEngine *vm, int16 x, int16 y, GameStateMenu *parentScene,
555 	int baseObjectPriority, int baseSurfacePriority,
556 	const byte *string, int stringLen, BaseSurface *drawSurface, int16 tx, int16 ty, FontSurface *fontSurface)
557 	: Widget(vm, x, y, parentScene,	baseObjectPriority, baseSurfacePriority),
558 	_string(string), _stringLen(stringLen), _drawSurface(drawSurface), _tx(tx), _ty(ty), _fontSurface(fontSurface) {
559 
560 }
561 
initialize()562 void TextLabelWidget::initialize() {
563 	_parentScene->addSprite(this);
564 	_parentScene->addCollisionSprite(this);
565 }
566 
getWidth()567 int16 TextLabelWidget::getWidth() {
568 	return _fontSurface->getStringWidth(_string, _stringLen);
569 }
570 
getHeight()571 int16 TextLabelWidget::getHeight() {
572 	return _fontSurface->getCharHeight();
573 }
574 
drawString(int maxStringLength)575 void TextLabelWidget::drawString(int maxStringLength) {
576 	_fontSurface->drawString(_drawSurface, _x, _y, _string, MIN(_stringLen, maxStringLength));
577 	_collisionBoundsOffset.set(_tx, _ty, getWidth(), getHeight());
578 	updateBounds();
579 }
580 
clear()581 void TextLabelWidget::clear() {
582 	_collisionBoundsOffset.set(0, 0, 0, 0);
583 	updateBounds();
584 }
585 
setString(const byte * string,int stringLen)586 void TextLabelWidget::setString(const byte *string, int stringLen) {
587 	_string = string;
588 	_stringLen = stringLen;
589 }
590 
TextEditWidget(NeverhoodEngine * vm,int16 x,int16 y,GameStateMenu * parentScene,int maxStringLength,FontSurface * fontSurface,uint32 fileHash,const NRect & rect)591 TextEditWidget::TextEditWidget(NeverhoodEngine *vm, int16 x, int16 y, GameStateMenu *parentScene,
592 	int maxStringLength, FontSurface *fontSurface, uint32 fileHash, const NRect &rect)
593 	: Widget(vm, x, y, parentScene,	1000, 1000),
594 	_maxStringLength(maxStringLength), _fontSurface(fontSurface), _fileHash(fileHash), _rect(rect),
595 	_cursorSurface(NULL), _cursorTicks(0), _cursorPos(0), _cursorFileHash(0), _cursorWidth(0), _cursorHeight(0),
596 	_modified(false), _readOnly(false) {
597 
598 	_maxVisibleChars = (_rect.x2 - _rect.x1) / _fontSurface->getCharWidth();
599 	_cursorPos = 0;
600 	_textLabelWidget = NULL;
601 
602 	SetUpdateHandler(&TextEditWidget::update);
603 	SetMessageHandler(&TextEditWidget::handleMessage);
604 }
605 
~TextEditWidget()606 TextEditWidget::~TextEditWidget() {
607 	delete _cursorSurface;
608 }
609 
onClick()610 void TextEditWidget::onClick() {
611 	NPoint mousePos = _parentScene->getMousePos();
612 	mousePos.x -= _x + _rect.x1;
613 	mousePos.y -= _y + _rect.y1;
614 	if (mousePos.x >= 0 && mousePos.x <= _rect.x2 - _rect.x1 &&
615 		mousePos.y >= 0 && mousePos.y <= _rect.y2 - _rect.y1) {
616 		if (_entryString.size() == 1)
617 			_cursorPos = 0;
618 		else {
619 			int newCursorPos = mousePos.x / _fontSurface->getCharWidth();
620 			if (mousePos.x % _fontSurface->getCharWidth() > _fontSurface->getCharWidth() / 2 && newCursorPos <= (int)_entryString.size())
621 				++newCursorPos;
622 			_cursorPos = MIN((int)_entryString.size(), newCursorPos);
623 		}
624 		if (!_readOnly)
625 			_cursorSurface->setVisible(true);
626 		refresh();
627 	}
628 	Widget::onClick();
629 }
630 
initialize()631 void TextEditWidget::initialize() {
632 	SpriteResource cursorSpriteResource(_vm);
633 
634 	_spriteResource.load(_fileHash, true);
635 	createSurface(_baseSurfacePriority, _spriteResource.getDimensions().width, _spriteResource.getDimensions().height);
636 	refreshPosition();
637 	_parentScene->addSprite(this);
638 	_parentScene->addCollisionSprite(this);
639 	_surface->setVisible(true);
640 	_textLabelWidget = new TextLabelWidget(_vm, _rect.x1, _rect.y1 + (_rect.y2 - _rect.y1 + 1 - _fontSurface->getCharHeight()) / 2,
641 		_parentScene, _baseObjectPriority + 1, _baseSurfacePriority + 1,
642 		(const byte*)_entryString.c_str(), _entryString.size(), _surface, _x, _y, _fontSurface);
643 	_textLabelWidget->initialize();
644 	if (_cursorFileHash != 0) {
645 		cursorSpriteResource.load(_cursorFileHash, true);
646 		_cursorSurface = new BaseSurface(_vm, 0, cursorSpriteResource.getDimensions().width, cursorSpriteResource.getDimensions().height, "cursor");
647 		_cursorSurface->drawSpriteResourceEx(cursorSpriteResource, false, false, cursorSpriteResource.getDimensions().width, cursorSpriteResource.getDimensions().height);
648 		_cursorSurface->setVisible(!_readOnly);
649 	}
650 	refresh();
651 }
652 
enterWidget()653 void TextEditWidget::enterWidget() {
654 	if (!_readOnly) {
655 		_cursorSurface->setVisible(true);
656 		_vm->_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, true);
657 	}
658 	refresh();
659 }
660 
exitWidget()661 void TextEditWidget::exitWidget() {
662 	if (!_readOnly) {
663 		_cursorSurface->setVisible(false);
664 		_vm->_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);
665 	}
666 	refresh();
667 }
668 
setCursor(uint32 cursorFileHash,int16 cursorWidth,int16 cursorHeight)669 void TextEditWidget::setCursor(uint32 cursorFileHash, int16 cursorWidth, int16 cursorHeight) {
670 	_cursorFileHash = cursorFileHash;
671 	_cursorWidth = cursorWidth;
672 	_cursorHeight = cursorHeight;
673 }
674 
drawCursor()675 void TextEditWidget::drawCursor() {
676 	if (_cursorSurface->getVisible() && _cursorPos >= 0 && _cursorPos <= _maxVisibleChars) {
677 		NDrawRect sourceRect(0, 0, _cursorWidth, _cursorHeight);
678 		_surface->copyFrom(_cursorSurface->getSurface(), _rect.x1 + _cursorPos * _fontSurface->getCharWidth(),
679 			_rect.y1 + (_rect.y2 - _cursorHeight - _rect.y1 + 1) / 2, sourceRect);
680 	} else if (!_readOnly)
681 		_cursorSurface->setVisible(false);
682 }
683 
updateString()684 void TextEditWidget::updateString() {
685 	_textLabelWidget->setString((const byte *)_entryString.c_str(), _entryString.size());
686 	_textLabelWidget->drawString(_maxVisibleChars);
687 }
688 
getString()689 Common::String& TextEditWidget::getString() {
690 	return _entryString;
691 }
692 
setString(const Common::String & string)693 void TextEditWidget::setString(const Common::String &string) {
694 	_entryString = string;
695 	_cursorPos = _entryString.size();
696 	_modified = false;
697 	refresh();
698 }
699 
handleAsciiKey(char ch)700 void TextEditWidget::handleAsciiKey(char ch) {
701 	if ((int)_entryString.size() < _maxStringLength &&
702 		((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == ' ')) {
703 		_entryString.insertChar(ch, _cursorPos);
704 		++_cursorPos;
705 		_modified = true;
706 		refresh();
707 	}
708 }
709 
handleKeyDown(Common::KeyCode keyCode)710 void TextEditWidget::handleKeyDown(Common::KeyCode keyCode) {
711 	bool doRefresh = true;
712 	switch (keyCode) {
713 	case Common::KEYCODE_HOME:
714 		_cursorPos = 0;
715 		break;
716 	case Common::KEYCODE_END:
717 		_cursorPos = _entryString.size();
718 		break;
719 	case Common::KEYCODE_LEFT:
720 		if (_entryString.size() > 0 && _cursorPos > 0)
721 			--_cursorPos;
722 		break;
723 	case Common::KEYCODE_RIGHT:
724 		if (_cursorPos < (int)_entryString.size())
725 			++_cursorPos;
726 		break;
727 	case Common::KEYCODE_DELETE:
728 		if (_entryString.size() > 0 && _cursorPos < (int)_entryString.size()) {
729 			_entryString.deleteChar(_cursorPos);
730 			_modified = true;
731 		}
732 		break;
733 	case Common::KEYCODE_BACKSPACE:
734 		if (_entryString.size() > 0 && _cursorPos > 0) {
735 			_entryString.deleteChar(--_cursorPos);
736 			_modified = true;
737 		}
738 		break;
739 	default:
740 		doRefresh = false;
741 		break;
742 	}
743 	if (doRefresh) {
744 		_cursorSurface->setVisible(!_readOnly);
745 		_cursorTicks = 0;
746 		refresh();
747 	}
748 }
749 
refresh()750 void TextEditWidget::refresh() {
751 	refreshPosition();
752 	updateString();
753 	if (_cursorFileHash != 0)
754 		drawCursor();
755 }
756 
update()757 void TextEditWidget::update() {
758 	Widget::update();
759 	if (!_readOnly && _parentScene->getCurrWidget() == this && _cursorTicks++ == 10) {
760 		_cursorSurface->setVisible(!_cursorSurface->getVisible());
761 		refresh();
762 		_cursorTicks = 0;
763 	}
764 }
765 
handleMessage(int messageNum,const MessageParam & param,Entity * sender)766 uint32 TextEditWidget::handleMessage(int messageNum, const MessageParam &param, Entity *sender) {
767 	uint32 messageResult = Widget::handleMessage(messageNum, param, sender);
768 	switch (messageNum) {
769 	case 0x000A:
770 		handleAsciiKey(param.asInteger());
771 		break;
772 	case 0x000B:
773 		handleKeyDown((Common::KeyCode)param.asInteger());
774 		break;
775 	default:
776 		break;
777 	}
778 	return messageResult;
779 }
780 
SavegameListBox(NeverhoodEngine * vm,int16 x,int16 y,GameStateMenu * parentScene,SavegameList * savegameList,FontSurface * fontSurface,uint32 bgFileHash,const NRect & rect)781 SavegameListBox::SavegameListBox(NeverhoodEngine *vm, int16 x, int16 y, GameStateMenu *parentScene,
782 	SavegameList *savegameList, FontSurface *fontSurface, uint32 bgFileHash, const NRect &rect)
783 	: Widget(vm, x, y, parentScene,	1000, 1000),
784 	_savegameList(savegameList), _fontSurface(fontSurface), _bgFileHash(bgFileHash), _rect(rect),
785 	_maxStringLength(0), _firstVisibleItem(0), _lastVisibleItem(0), _currIndex(0) {
786 
787 	_maxVisibleItemsCount = (_rect.y2 - _rect.y1) / _fontSurface->getCharHeight();
788 	_maxStringLength = (_rect.x2 - _rect.x1) / _fontSurface->getCharWidth();
789 }
790 
onClick()791 void SavegameListBox::onClick() {
792 	NPoint mousePos = _parentScene->getMousePos();
793 	mousePos.x -= _x + _rect.x1;
794 	mousePos.y -= _y + _rect.y1;
795 	if (mousePos.x >= 0 && mousePos.x <= _rect.x2 - _rect.x1 &&
796 		mousePos.y >= 0 && mousePos.y <= _rect.y2 - _rect.y1) {
797 		int newIndex = _firstVisibleItem + mousePos.y / _fontSurface->getCharHeight();
798 		if (newIndex <= _lastVisibleItem) {
799 			_currIndex = newIndex;
800 			refresh();
801 			_parentScene->setCurrWidget(this);
802 			_parentScene->refreshDescriptionEdit();
803 		}
804 	}
805 }
806 
initialize()807 void SavegameListBox::initialize() {
808 	_spriteResource.load(_bgFileHash, true);
809 	createSurface(_baseSurfacePriority, _spriteResource.getDimensions().width, _spriteResource.getDimensions().height);
810 	refreshPosition();
811 	_parentScene->addSprite(this);
812 	_parentScene->addCollisionSprite(this);
813 	_surface->setVisible(true);
814 	buildItems();
815 	_firstVisibleItem = 0;
816 	_lastVisibleItem = MIN(_maxVisibleItemsCount, (int)_textLabelItems.size()) - 1;
817 	refresh();
818 }
819 
buildItems()820 void SavegameListBox::buildItems() {
821 	SavegameList &savegameList = *_savegameList;
822 	int16 itemX = _rect.x1, itemY = 0;
823 	for (uint i = 0; i < savegameList.size(); ++i) {
824 		const byte *string = (const byte*)savegameList[i].description.c_str();
825 		int stringLen = (int)savegameList[i].description.size();
826 		TextLabelWidget *label = new TextLabelWidget(_vm, itemX, itemY, _parentScene, _baseObjectPriority + 1,
827 			_baseSurfacePriority + 1, string, MIN(stringLen, _maxStringLength), _surface, _x, _y, _fontSurface);
828 		label->initialize();
829 		_textLabelItems.push_back(label);
830 	}
831 }
832 
drawItems()833 void SavegameListBox::drawItems() {
834 	for (int i = 0; i < (int)_textLabelItems.size(); ++i) {
835 		TextLabelWidget *label = _textLabelItems[i];
836 		if (i >= _firstVisibleItem && i <= _lastVisibleItem) {
837 			label->setY(_rect.y1 + (i - _firstVisibleItem) * _fontSurface->getCharHeight());
838 			label->updateBounds();
839 			label->drawString(_maxStringLength);
840 		} else
841 			label->clear();
842 	}
843 }
844 
refresh()845 void SavegameListBox::refresh() {
846 	refreshPosition();
847 	drawItems();
848 }
849 
scrollUp()850 void SavegameListBox::scrollUp() {
851 	if (_firstVisibleItem > 0) {
852 		--_firstVisibleItem;
853 		--_lastVisibleItem;
854 		refresh();
855 	}
856 }
857 
scrollDown()858 void SavegameListBox::scrollDown() {
859 	if (_lastVisibleItem < (int)_textLabelItems.size() - 1) {
860 		++_firstVisibleItem;
861 		++_lastVisibleItem;
862 		refresh();
863 	}
864 }
865 
pageUp()866 void SavegameListBox::pageUp() {
867 	int amount = MIN(_firstVisibleItem, _maxVisibleItemsCount);
868 	if (amount > 0) {
869 		_firstVisibleItem -= amount;
870 		_lastVisibleItem -= amount;
871 		refresh();
872 	}
873 }
874 
pageDown()875 void SavegameListBox::pageDown() {
876 	int amount = MIN((int)_textLabelItems.size() - _lastVisibleItem - 1, _maxVisibleItemsCount);
877 	if (amount > 0) {
878 		_firstVisibleItem += amount;
879 		_lastVisibleItem += amount;
880 		refresh();
881 	}
882 }
883 
scummVMSaveLoadDialog(bool isSave,Common::String & saveDesc)884 int GameStateMenu::scummVMSaveLoadDialog(bool isSave, Common::String &saveDesc) {
885 	GUI::SaveLoadChooser *dialog;
886 	Common::String desc;
887 	int slot;
888 
889 	if (isSave) {
890 		dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
891 
892 		slot = dialog->runModalWithCurrentTarget();
893 		desc = dialog->getResultString();
894 
895 		if (desc.empty())
896 			desc = dialog->createDefaultSaveDescription(slot);
897 
898 		if (desc.size() > 29)
899 			desc = Common::String(desc.c_str(), 29);
900 
901 		saveDesc = desc;
902 	} else {
903 		dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false);
904 		slot = dialog->runModalWithCurrentTarget();
905 	}
906 
907 	delete dialog;
908 
909 	return slot;
910 }
911 
GameStateMenu(NeverhoodEngine * vm,Module * parentModule,SavegameList * savegameList,const uint32 * buttonFileHashes,const NRect * buttonCollisionBounds,uint32 backgroundFileHash,uint32 fontFileHash,uint32 mouseFileHash,const NRect * mouseRect,uint32 listBoxBackgroundFileHash,int16 listBoxX,int16 listBoxY,const NRect & listBoxRect,uint32 textEditBackgroundFileHash,uint32 textEditCursorFileHash,int16 textEditX,int16 textEditY,const NRect & textEditRect,uint32 textFileHash1,uint32 textFileHash2)912 GameStateMenu::GameStateMenu(NeverhoodEngine *vm, Module *parentModule, SavegameList *savegameList,
913 	const uint32 *buttonFileHashes, const NRect *buttonCollisionBounds,
914 	uint32 backgroundFileHash, uint32 fontFileHash,
915 	uint32 mouseFileHash, const NRect *mouseRect,
916 	uint32 listBoxBackgroundFileHash, int16 listBoxX, int16 listBoxY, const NRect &listBoxRect,
917 	uint32 textEditBackgroundFileHash, uint32 textEditCursorFileHash, int16 textEditX, int16 textEditY, const NRect &textEditRect,
918 	uint32 textFileHash1, uint32 textFileHash2)
919 	: Scene(vm, parentModule), _currWidget(NULL), _savegameList(savegameList) {
920 
921 	bool isSave = (textEditCursorFileHash != 0);
922 
923 	_fontSurface = new FontSurface(_vm, fontFileHash, 32, 7, 32, 11, 17);
924 
925 	if (!ConfMan.getBool("originalsaveload")) {
926 		Common::String saveDesc;
927 		int saveCount = savegameList->size();
928 		int slot = scummVMSaveLoadDialog(isSave, saveDesc);
929 
930 		if (slot >= 0) {
931 			if (!isSave) {
932 				((MenuModule*)_parentModule)->setLoadgameSlot(slot);
933 			} else {
934 				((MenuModule*)_parentModule)->setSavegameInfo(saveDesc,
935 					slot, slot >= saveCount);
936 			}
937 			leaveScene(0);
938 		} else {
939 			leaveScene(1);
940 		}
941 		return;
942 	}
943 
944 	setBackground(backgroundFileHash);
945 	setPalette(backgroundFileHash);
946 	insertScreenMouse(mouseFileHash, mouseRect);
947 	insertStaticSprite(textFileHash1, 200);
948 	insertStaticSprite(textFileHash2, 200);
949 
950 	_listBox = new SavegameListBox(_vm, listBoxX, listBoxY, this,
951 		_savegameList, _fontSurface, listBoxBackgroundFileHash, listBoxRect);
952 	_listBox->initialize();
953 
954 	_textEditWidget = new TextEditWidget(_vm, textEditX, textEditY, this, 29,
955 		_fontSurface, textEditBackgroundFileHash, textEditRect);
956 	if (isSave)
957 		_textEditWidget->setCursor(textEditCursorFileHash, 2, 13);
958 	else
959 		_textEditWidget->setReadOnly(true);
960 	_textEditWidget->initialize();
961 	setCurrWidget(_textEditWidget);
962 
963 	for (uint buttonIndex = 0; buttonIndex < 6; ++buttonIndex) {
964 		Sprite *menuButton = insertSprite<MenuButton>(this, buttonIndex,
965 			buttonFileHashes[buttonIndex], buttonCollisionBounds[buttonIndex]);
966 		addCollisionSprite(menuButton);
967 	}
968 
969 	SetUpdateHandler(&Scene::update);
970 	SetMessageHandler(&GameStateMenu::handleMessage);
971 }
972 
~GameStateMenu()973 GameStateMenu::~GameStateMenu() {
974 	delete _fontSurface;
975 }
976 
getMousePos()977 NPoint GameStateMenu::getMousePos() {
978 	NPoint pt;
979 	pt.x = _mouseCursor->getX();
980 	pt.y = _mouseCursor->getY();
981 	return pt;
982 }
983 
setCurrWidget(Widget * newWidget)984 void GameStateMenu::setCurrWidget(Widget *newWidget) {
985 	if (newWidget && newWidget != _currWidget) {
986 		if (_currWidget)
987 			_currWidget->exitWidget();
988 		newWidget->enterWidget();
989 		_currWidget = newWidget;
990 	}
991 }
992 
refreshDescriptionEdit()993 void GameStateMenu::refreshDescriptionEdit() {
994 	uint currIndex = _listBox->getCurrIndex();
995 	_textEditWidget->setString((*_savegameList)[currIndex].description);
996 	setCurrWidget(_textEditWidget);
997 }
998 
performAction()999 void GameStateMenu::performAction() {
1000 	// Empty
1001 }
1002 
handleMessage(int messageNum,const MessageParam & param,Entity * sender)1003 uint32 GameStateMenu::handleMessage(int messageNum, const MessageParam &param, Entity *sender) {
1004 	Scene::handleMessage(messageNum, param, sender);
1005 	switch (messageNum) {
1006 	case 0x000A:
1007 		if (!_textEditWidget->isReadOnly()) {
1008 			sendMessage(_textEditWidget, 0x000A, param.asInteger());
1009 			setCurrWidget(_textEditWidget);
1010 		}
1011 		break;
1012 	case 0x000B:
1013 		if (param.asInteger() == Common::KEYCODE_RETURN)
1014 			performAction();
1015 		else if (param.asInteger() == Common::KEYCODE_ESCAPE)
1016 			leaveScene(1);
1017 		else if (!_textEditWidget->isReadOnly()) {
1018 			sendMessage(_textEditWidget, 0x000B, param.asInteger());
1019 			setCurrWidget(_textEditWidget);
1020 		}
1021 		break;
1022 	case NM_ANIMATION_UPDATE:
1023 		// Handle menu button click
1024 		switch (param.asInteger()) {
1025 		case 0:
1026 			performAction();
1027 			break;
1028 		case 1:
1029 			leaveScene(1);
1030 			break;
1031 		case 2:
1032 			_listBox->pageUp();
1033 			break;
1034 		case 3:
1035 			_listBox->scrollUp();
1036 			break;
1037 		case 4:
1038 			_listBox->scrollDown();
1039 			break;
1040 		case 5:
1041 			_listBox->pageDown();
1042 			break;
1043 		default:
1044 			break;
1045 		}
1046 		break;
1047 	case NM_MOUSE_WHEELUP:
1048 		_listBox->scrollUp();
1049 		break;
1050 	case NM_MOUSE_WHEELDOWN:
1051 		_listBox->scrollDown();
1052 		break;
1053 	default:
1054 		break;
1055 	}
1056 	return 0;
1057 }
1058 
1059 static const uint32 kSaveGameMenuButtonFileHashes[] = {
1060 	0x8359A824, 0x0690E260, 0x0352B050,
1061 	0x1392A223, 0x13802260, 0x0B32B200
1062 };
1063 
1064 static const NRect kSaveGameMenuButtonCollisionBounds[] = {
1065 	{ 518, 106, 602, 160 },
1066 	{ 516, 378, 596, 434 },
1067 	{ 394, 108, 458, 206 },
1068 	{ 400, 204, 458, 276 },
1069 	{ 398, 292, 456, 352 },
1070 	{ 396, 352, 460, 444 }
1071 };
1072 
1073 static const NRect kSaveGameMenuListBoxRect = { 0, 0, 320, 272 };
1074 static const NRect kSaveGameMenuTextEditRect = { 0, 0, 377, 17 };
1075 static const NRect kSaveGameMenuMouseRect = { 50, 47, 427, 64 };
1076 
SaveGameMenu(NeverhoodEngine * vm,Module * parentModule,SavegameList * savegameList)1077 SaveGameMenu::SaveGameMenu(NeverhoodEngine *vm, Module *parentModule, SavegameList *savegameList)
1078 	: GameStateMenu(vm, parentModule, savegameList, kSaveGameMenuButtonFileHashes, kSaveGameMenuButtonCollisionBounds,
1079 		0x30084E25, 0x2328121A,
1080 		0x84E21308, &kSaveGameMenuMouseRect,
1081 		0x1115A223, 60, 142, kSaveGameMenuListBoxRect,
1082 		0x3510A868, 0x8290AC20, 50, 47, kSaveGameMenuTextEditRect,
1083 		0x1340A5C2, 0x1301A7EA) {
1084 
1085 }
1086 
performAction()1087 void SaveGameMenu::performAction() {
1088 	if (!_textEditWidget->getString().empty()) {
1089 		((MenuModule*)_parentModule)->setSavegameInfo(_textEditWidget->getString(),
1090 			_listBox->getCurrIndex(), _textEditWidget->isModified());
1091 		leaveScene(0);
1092 	}
1093 }
1094 
1095 static const uint32 kLoadGameMenuButtonFileHashes[] = {
1096 	0x100B2091, 0x84822B03, 0x20E22087,
1097 	0x04040107, 0x04820122, 0x24423047
1098 };
1099 
1100 static const NRect kLoadGameMenuButtonCollisionBounds[] = {
1101 	{  44, 115, 108, 147 },
1102 	{  52, 396, 112, 426 },
1103 	{ 188, 116, 245, 196 },
1104 	{ 189, 209, 239, 269 },
1105 	{ 187, 301, 233, 349 },
1106 	{ 182, 358, 241, 433 }
1107 };
1108 
1109 static const NRect kLoadGameMenuListBoxRect = { 0, 0, 320, 272 };
1110 static const NRect kLoadGameMenuTextEditRect = { 0, 0, 320, 17 };
1111 
1112 #if 0
1113 // Unlike the original game, the text widget in our load dialog is read-only so
1114 // don't change the mouse cursor to indicate that you can type the name of the
1115 // game to load.
1116 //
1117 // Since we allow multiple saved games to have the same name, it's probably
1118 // better this way.
1119 static const NRect kLoadGameMenuMouseRect = { 263, 48, 583, 65 };
1120 #endif
1121 
LoadGameMenu(NeverhoodEngine * vm,Module * parentModule,SavegameList * savegameList)1122 LoadGameMenu::LoadGameMenu(NeverhoodEngine *vm, Module *parentModule, SavegameList *savegameList)
1123 	: GameStateMenu(vm, parentModule, savegameList, kLoadGameMenuButtonFileHashes, kLoadGameMenuButtonCollisionBounds,
1124 		0x98620234, 0x201C2474,
1125 		0x2023098E, NULL /* &kLoadGameMenuMouseRect */,
1126 		0x04040409, 263, 142, kLoadGameMenuListBoxRect,
1127 		0x10924C03, 0, 263, 48, kLoadGameMenuTextEditRect,
1128 		0x0BC600A3, 0x0F960021) {
1129 
1130 }
1131 
performAction()1132 void LoadGameMenu::performAction() {
1133 	// TODO: The original would display a message here if nothing was selected.
1134 	if (!_textEditWidget->getString().empty()) {
1135 		((MenuModule*)_parentModule)->setLoadgameInfo(_listBox->getCurrIndex());
1136 		leaveScene(0);
1137 	}
1138 }
1139 
1140 static const uint32 kDeleteGameMenuButtonFileHashes[] = {
1141 	0x8198E268, 0xDD0C4620, 0x81296520,
1142 	0x8D284211, 0x8C004621, 0x07294020
1143 };
1144 
1145 static const NRect kDeleteGameMenuButtonCollisionBounds[] = {
1146 	{ 518,  46, 595,  91 },
1147 	{ 524, 322, 599, 369 },
1148 	{ 395,  40, 462, 127 },
1149 	{ 405, 126, 460, 185 },
1150 	{ 397, 205, 456, 273 },
1151 	{ 395, 278, 452, 372 }
1152 };
1153 
1154 static const NRect kDeleteGameMenuListBoxRect = { 0, 0, 320, 272 };
1155 static const NRect kDeleteGameMenuTextEditRect = { 0, 0, 320, 17 };
1156 
DeleteGameMenu(NeverhoodEngine * vm,Module * parentModule,SavegameList * savegameList)1157 DeleteGameMenu::DeleteGameMenu(NeverhoodEngine *vm, Module *parentModule, SavegameList *savegameList)
1158 	: GameStateMenu(vm, parentModule, savegameList, kDeleteGameMenuButtonFileHashes, kDeleteGameMenuButtonCollisionBounds,
1159 		0x4080E01C, 0x728523ED,
1160 		0x0E018400, NULL,
1161 		0xA5584211, 61, 64, kDeleteGameMenuListBoxRect,
1162 		0x250A3060, 0, 49, 414, kDeleteGameMenuTextEditRect,
1163 		0x80083C01, 0x84181E81) {
1164 
1165 }
1166 
performAction()1167 void DeleteGameMenu::performAction() {
1168 	// TODO: The original would display a message here if no game was selected.
1169 	if (!_textEditWidget->getString().empty()) {
1170 		((MenuModule*)_parentModule)->setDeletegameInfo(_listBox->getCurrIndex());
1171 		leaveScene(0);
1172 	}
1173 }
1174 
QueryOverwriteMenu(NeverhoodEngine * vm,Module * parentModule,const Common::String & description)1175 QueryOverwriteMenu::QueryOverwriteMenu(NeverhoodEngine *vm, Module *parentModule, const Common::String &description)
1176 	: Scene(vm, parentModule) {
1177 
1178 	static const uint32 kQueryOverwriteMenuButtonFileHashes[] = {
1179 		0x90312400,
1180 		0x94C22A22
1181 	};
1182 
1183 	static const NRect kQueryOverwriteMenuCollisionBounds[] = {
1184 		{ 145, 334, 260, 385 },
1185 		{ 365, 340, 477, 388 }
1186 	};
1187 
1188 	setBackground(0x043692C4);
1189 	setPalette(0x043692C4);
1190 	insertScreenMouse(0x692C004B);
1191 	insertStaticSprite(0x08C0AC24, 200);
1192 
1193 	for (uint buttonIndex = 0; buttonIndex < 2; ++buttonIndex) {
1194 		Sprite *menuButton = insertSprite<MenuButton>(this, buttonIndex,
1195 			kQueryOverwriteMenuButtonFileHashes[buttonIndex], kQueryOverwriteMenuCollisionBounds[buttonIndex]);
1196 		addCollisionSprite(menuButton);
1197 	}
1198 
1199 	// Draw the query text to the background, each text line is centered
1200 	// NOTE The original had this text in its own class
1201 	FontSurface *fontSurface = new FontSurface(_vm, 0x94188D4D, 32, 7, 32, 11, 17);
1202 	Common::StringArray textLines;
1203 	textLines.push_back(description);
1204 	textLines.push_back("Game exists.");
1205 	textLines.push_back("Overwrite it?");
1206 	for (uint i = 0; i < textLines.size(); ++i)
1207 		fontSurface->drawString(_background->getSurface(), 106 + (423 - textLines[i].size() * 11) / 2,
1208 			127 + 31 + i * 17, (const byte*)textLines[i].c_str());
1209 	delete fontSurface;
1210 
1211 	SetUpdateHandler(&Scene::update);
1212 	SetMessageHandler(&QueryOverwriteMenu::handleMessage);
1213 }
1214 
handleMessage(int messageNum,const MessageParam & param,Entity * sender)1215 uint32 QueryOverwriteMenu::handleMessage(int messageNum, const MessageParam &param, Entity *sender) {
1216 	Scene::handleMessage(messageNum, param, sender);
1217 	switch (messageNum) {
1218 	case NM_ANIMATION_UPDATE:
1219 		// Handle menu button click
1220 		leaveScene(param.asInteger());
1221 		break;
1222 	default:
1223 		break;
1224 	}
1225 	return 0;
1226 }
1227 
1228 } // End of namespace Neverhood
1229