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 "base/version.h"
24 
25 #include "common/config-manager.h"
26 #include "common/events.h"
27 #include "common/str.h"
28 #include "common/system.h"
29 #include "common/translation.h"
30 
31 #include "gui/about.h"
32 #include "gui/gui-manager.h"
33 #include "gui/message.h"
34 #include "gui/options.h"
35 #include "gui/saveload.h"
36 #include "gui/ThemeEngine.h"
37 #include "gui/ThemeEval.h"
38 #include "gui/widget.h"
39 #include "gui/widgets/tab.h"
40 
41 #include "graphics/font.h"
42 
43 #include "engines/dialogs.h"
44 #include "engines/engine.h"
45 #include "engines/metaengine.h"
46 
MainMenuDialog(Engine * engine)47 MainMenuDialog::MainMenuDialog(Engine *engine)
48 	: GUI::Dialog("GlobalMenu"), _engine(engine) {
49 	_backgroundType = GUI::ThemeEngine::kDialogBackgroundSpecial;
50 
51 #ifndef DISABLE_FANCY_THEMES
52 	_logo = 0;
53 	if (g_gui.xmlEval()->getVar("Globals.ShowGlobalMenuLogo", 0) == 1 && g_gui.theme()->supportsImages()) {
54 		_logo = new GUI::GraphicsWidget(this, "GlobalMenu.Logo");
55 		_logo->useThemeTransparency(true);
56 		_logo->setGfx(g_gui.theme()->getImageSurface(GUI::ThemeEngine::kImageLogoSmall));
57 	} else {
58 		GUI::StaticTextWidget *title = new GUI::StaticTextWidget(this, "GlobalMenu.Title", Common::U32String("ScummVM"));
59 		title->setAlign(Graphics::kTextAlignCenter);
60 	}
61 #else
62 	GUI::StaticTextWidget *title = new GUI::StaticTextWidget(this, "GlobalMenu.Title", Common::U32String("ScummVM"));
63 	title->setAlign(Graphics::kTextAlignCenter);
64 #endif
65 
66 	GUI::StaticTextWidget *version = new GUI::StaticTextWidget(this, "GlobalMenu.Version", Common::U32String(gScummVMVersionDate));
67 	version->setAlign(Graphics::kTextAlignCenter);
68 
69 	new GUI::ButtonWidget(this, "GlobalMenu.Resume", _("~R~esume"), Common::U32String(), kPlayCmd, 'P');
70 
71 	_loadButton = new GUI::ButtonWidget(this, "GlobalMenu.Load", _("~L~oad"), Common::U32String(), kLoadCmd);
72 	_loadButton->setVisible(_engine->hasFeature(Engine::kSupportsLoadingDuringRuntime));
73 	_loadButton->setEnabled(_engine->hasFeature(Engine::kSupportsLoadingDuringRuntime));
74 
75 	_saveButton = new GUI::ButtonWidget(this, "GlobalMenu.Save", _("~S~ave"), Common::U32String(), kSaveCmd);
76 	_saveButton->setVisible(_engine->hasFeature(Engine::kSupportsSavingDuringRuntime));
77 	_saveButton->setEnabled(_engine->hasFeature(Engine::kSupportsSavingDuringRuntime));
78 
79 	new GUI::ButtonWidget(this, "GlobalMenu.Options", _("~O~ptions"), Common::U32String(), kOptionsCmd);
80 
81 	// The help button is disabled by default.
82 	// To enable "Help", an engine needs to use a subclass of MainMenuDialog
83 	// (at least for now, we might change how this works in the future).
84 	_helpButton = new GUI::ButtonWidget(this, "GlobalMenu.Help", _("~H~elp"), Common::U32String(), kHelpCmd);
85 
86 	new GUI::ButtonWidget(this, "GlobalMenu.About", _("~A~bout"), Common::U32String(), kAboutCmd);
87 
88 	if (g_gui.getGUIWidth() > 320)
89 		_returnToLauncherButton = new GUI::ButtonWidget(this, "GlobalMenu.ReturnToLauncher", _("~R~eturn to Launcher"), Common::U32String(), kLauncherCmd);
90 	else
91 		_returnToLauncherButton = new GUI::ButtonWidget(this, "GlobalMenu.ReturnToLauncher", _c("~R~eturn to Launcher", "lowres"), Common::U32String(), kLauncherCmd);
92 	_returnToLauncherButton->setEnabled(_engine->hasFeature(Engine::kSupportsReturnToLauncher));
93 
94 	if (!g_system->hasFeature(OSystem::kFeatureNoQuit) && (!(ConfMan.getBool("gui_return_to_launcher_at_exit")) || !_engine->hasFeature(Engine::kSupportsReturnToLauncher)))
95 		new GUI::ButtonWidget(this, "GlobalMenu.Quit", _("~Q~uit"), Common::U32String(), kQuitCmd);
96 
97 	_aboutDialog = new GUI::AboutDialog();
98 	_loadDialog = new GUI::SaveLoadChooser(_("Load game:"), _("Load"), false);
99 	_saveDialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
100 }
101 
~MainMenuDialog()102 MainMenuDialog::~MainMenuDialog() {
103 	delete _aboutDialog;
104 	delete _loadDialog;
105 	delete _saveDialog;
106 }
107 
handleCommand(GUI::CommandSender * sender,uint32 cmd,uint32 data)108 void MainMenuDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) {
109 	switch (cmd) {
110 	case kPlayCmd:
111 		close();
112 		break;
113 	case kLoadCmd:
114 		load();
115 		break;
116 	case kSaveCmd:
117 		save();
118 		break;
119 	case kOptionsCmd: {
120 		GUI::ConfigDialog configDialog;
121 		configDialog.runModal();
122 		break;
123 	}
124 	case kAboutCmd:
125 		_aboutDialog->runModal();
126 		break;
127 	case kHelpCmd: {
128 		GUI::MessageDialog dialog(
129 					_("Sorry, this engine does not currently provide in-game help. "
130 					"Please consult the README for basic information, and for "
131 					"instructions on how to obtain further assistance."));
132 		dialog.runModal();
133 		}
134 		break;
135 	case kLauncherCmd: {
136 		Common::Event eventReturnToLauncher;
137 		eventReturnToLauncher.type = Common::EVENT_RETURN_TO_LAUNCHER;
138 		g_system->getEventManager()->pushEvent(eventReturnToLauncher);
139 		close();
140 		}
141 		break;
142 	case kQuitCmd: {
143 		Common::Event eventQ;
144 		eventQ.type = Common::EVENT_QUIT;
145 		g_system->getEventManager()->pushEvent(eventQ);
146 		close();
147 		}
148 		break;
149 	default:
150 		GUI::Dialog::handleCommand(sender, cmd, data);
151 	}
152 }
153 
reflowLayout()154 void MainMenuDialog::reflowLayout() {
155 	if (_engine->hasFeature(Engine::kSupportsLoadingDuringRuntime))
156 		_loadButton->setEnabled(_engine->canLoadGameStateCurrently());
157 	if (_engine->hasFeature(Engine::kSupportsSavingDuringRuntime))
158 		_saveButton->setEnabled(_engine->canSaveGameStateCurrently());
159 
160 	// Overlay size might have changed since the construction of the dialog.
161 	// Update labels when it might be needed
162 	// FIXME: it might be better to declare GUI::StaticTextWidget::setLabel() virtual
163 	// and to reimplement it in GUI::ButtonWidget to handle the hotkey.
164 	if (g_gui.getGUIWidth() > 320)
165 		_returnToLauncherButton->setLabel(_returnToLauncherButton->cleanupHotkey(_("~R~eturn to Launcher")));
166 	else
167 		_returnToLauncherButton->setLabel(_returnToLauncherButton->cleanupHotkey(_c("~R~eturn to Launcher", "lowres")));
168 
169 #ifndef DISABLE_FANCY_THEMES
170 	if (g_gui.xmlEval()->getVar("Globals.ShowGlobalMenuLogo", 0) == 1 && g_gui.theme()->supportsImages()) {
171 		if (!_logo)
172 			_logo = new GUI::GraphicsWidget(this, "GlobalMenu.Logo");
173 		_logo->useThemeTransparency(true);
174 		_logo->setGfx(g_gui.theme()->getImageSurface(GUI::ThemeEngine::kImageLogoSmall));
175 
176 		GUI::StaticTextWidget *title = (GUI::StaticTextWidget *)findWidget("GlobalMenu.Title");
177 		if (title) {
178 			removeWidget(title);
179 			title->setNext(0);
180 			delete title;
181 		}
182 	} else {
183 		GUI::StaticTextWidget *title = (GUI::StaticTextWidget *)findWidget("GlobalMenu.Title");
184 		if (!title) {
185 			title = new GUI::StaticTextWidget(this, "GlobalMenu.Title", Common::U32String("ScummVM"));
186 			title->setAlign(Graphics::kTextAlignCenter);
187 		}
188 
189 		if (_logo) {
190 			removeWidget(_logo);
191 			_logo->setNext(0);
192 			delete _logo;
193 			_logo = 0;
194 		}
195 	}
196 #endif
197 
198 	Dialog::reflowLayout();
199 }
200 
save()201 void MainMenuDialog::save() {
202 	int slot = _saveDialog->runModalWithCurrentTarget();
203 
204 	if (slot >= 0) {
205 		Common::String result(_saveDialog->getResultString());
206 		if (result.empty()) {
207 			// If the user was lazy and entered no save name, come up with a default name.
208 			result = _saveDialog->createDefaultSaveDescription(slot);
209 		}
210 
211 		Common::Error status = _engine->saveGameState(slot, result);
212 		if (status.getCode() != Common::kNoError) {
213 			Common::U32String failMessage = Common::U32String::format(_("Failed to save game (%s)! "
214 				  "Please consult the README for basic information, and for "
215 				  "instructions on how to obtain further assistance."), status.getDesc().c_str());
216 			GUI::MessageDialog dialog(failMessage);
217 			dialog.runModal();
218 		}
219 
220 		close();
221 	}
222 }
223 
load()224 void MainMenuDialog::load() {
225 	int slot = _loadDialog->runModalWithCurrentTarget();
226 
227 	_engine->setGameToLoadSlot(slot);
228 
229 	if (slot >= 0)
230 		close();
231 }
232 
233 namespace GUI {
234 
235 // FIXME: We use the empty string as domain name here. This tells the
236 // ConfigManager to use the 'default' domain for all its actions. We do that
237 // to get as close as possible to editing the 'active' settings.
238 //
239 // However, that requires bad & evil hacks in the ConfigManager code,
240 // and even then still doesn't work quite correctly.
241 // For example, if the transient domain contains 'false' for the 'fullscreen'
242 // flag, but the user used a hotkey to switch to windowed mode, then the dialog
243 // will display the wrong value anyway.
244 //
245 // Proposed solution consisting of multiple steps:
246 // 1) Add special code to the open() code that reads out everything stored
247 //    in the transient domain that is controlled by this dialog, and updates
248 //    the dialog accordingly.
249 // 2) Even more code is added to query the backend for current settings, like
250 //    the fullscreen mode flag etc., and also updates the dialog accordingly.
251 // 3) The domain being edited is set to the active game domain.
252 // 4) If the dialog is closed with the "OK" button, then we remove everything
253 //    stored in the transient domain (or at least everything corresponding to
254 //    switches in this dialog.
255 //    If OTOH the dialog is closed with "Cancel" we do no such thing.
256 //
257 // These changes will achieve two things at once: Allow us to get rid of using
258 //  "" as value for the domain, and in fact provide a somewhat better user
259 // experience at the same time.
ConfigDialog()260 ConfigDialog::ConfigDialog() :
261 		GUI::OptionsDialog("", "GlobalConfig"),
262 		_engineOptions(nullptr) {
263 	assert(g_engine);
264 
265 	const Common::String &gameDomain = ConfMan.getActiveDomainName();
266 	const MetaEngine *metaEngine = g_engine->getMetaEngine();
267 	const MetaEngineDetection &metaEngineDetection = g_engine->getMetaEngineDetection();
268 
269 	// GUI:  Add tab widget
270 	GUI::TabWidget *tab = new GUI::TabWidget(this, "GlobalConfig.TabWidget");
271 
272 	//
273 	// The game specific options tab
274 	//
275 
276 	int tabId = tab->addTab(_("Game"), "GlobalConfig_Engine");
277 
278 	if (g_engine->hasFeature(Engine::kSupportsChangingOptionsDuringRuntime)) {
279 		_engineOptions = metaEngine->buildEngineOptionsWidgetDynamic(tab, "GlobalConfig_Engine.Container", gameDomain);
280 		if (!_engineOptions)
281 			_engineOptions = metaEngineDetection.buildEngineOptionsWidgetStatic(tab, "GlobalConfig_Engine.Container", gameDomain);
282 	}
283 
284 	if (_engineOptions) {
285 		_engineOptions->setParentDialog(this);
286 	} else {
287 		tab->removeTab(tabId);
288 	}
289 
290 	//
291 	// The Audio / Subtitles tab
292 	//
293 
294 	tab->addTab(_("Audio"), "GlobalConfig_Audio");
295 
296 	//
297 	// Sound controllers
298 	//
299 
300 	addVolumeControls(tab, "GlobalConfig_Audio.");
301 	setVolumeSettingsState(true); // could disable controls by GUI options
302 
303 	//
304 	// Subtitle speed and toggle controllers
305 	//
306 
307 	if (g_engine->hasFeature(Engine::kSupportsSubtitleOptions)) {
308 		// Global talkspeed range of 0-255
309 		addSubtitleControls(tab, "GlobalConfig_Audio.", 255);
310 		setSubtitleSettingsState(true); // could disable controls by GUI options
311 	}
312 
313 	//
314 	// The Keymap tab
315 	//
316 
317 	Common::KeymapArray keymaps = metaEngine->initKeymaps(gameDomain.c_str());
318 	if (!keymaps.empty()) {
319 		tab->addTab(_("Keymaps"), "GlobalConfig_KeyMapper");
320 		addKeyMapperControls(tab, "GlobalConfig_KeyMapper.", keymaps, gameDomain);
321 	}
322 
323 	//
324 	// The backend tab (shown only if the backend implements one)
325 	//
326 	int backendTabId = tab->addTab(_("Backend"), "GlobalConfig_Backend");
327 
328 	_backendOptions = g_system->buildBackendOptionsWidget(tab, "GlobalConfig_Backend.Container", _domain);
329 
330 	if (_backendOptions) {
331 		_backendOptions->setParentDialog(this);
332 	} else {
333 		tab->removeTab(backendTabId);
334 	}
335 
336 	//
337 	// The Achievements & The Statistics tabs
338 	//
339 	AchMan.setActiveDomain(metaEngine->getAchievementsInfo(gameDomain));
340 	if (AchMan.getAchievementCount()) {
341 		tab->addTab(_("Achievements"), "GlobalConfig_Achievements");
342 		addAchievementsControls(tab, "GlobalConfig_Achievements.");
343 	}
344 	if (AchMan.getStatCount()) {
345 		tab->addTab(_("Statistics"), "GlobalConfig_Achievements");
346 		addStatisticsControls(tab, "GlobalConfig_Achievements.");
347 	}
348 
349 	// Activate the first tab
350 	tab->setActiveTab(0);
351 
352 	//
353 	// Add the buttons
354 	//
355 
356 	new GUI::ButtonWidget(this, "GlobalConfig.Ok", _("~O~K"), Common::U32String(), GUI::kOKCmd);
357 	new GUI::ButtonWidget(this, "GlobalConfig.Cancel", _("~C~ancel"), Common::U32String(), GUI::kCloseCmd);
358 }
359 
~ConfigDialog()360 ConfigDialog::~ConfigDialog() {
361 }
362 
build()363 void ConfigDialog::build() {
364 	OptionsDialog::build();
365 
366 	// Engine options
367 	if (_engineOptions) {
368 		_engineOptions->load();
369 	}
370 }
371 
apply()372 void ConfigDialog::apply() {
373 	if (_engineOptions) {
374 		_engineOptions->save();
375 	}
376 
377 	OptionsDialog::apply();
378 }
379 
ExtraGuiOptionsWidget(GuiObject * containerBoss,const Common::String & name,const Common::String & domain,const ExtraGuiOptions & options)380 ExtraGuiOptionsWidget::ExtraGuiOptionsWidget(GuiObject *containerBoss, const Common::String &name, const Common::String &domain, const ExtraGuiOptions &options) :
381 		OptionsContainerWidget(containerBoss, name, dialogLayout(domain), true, domain),
382 		_options(options) {
383 
384 	for (uint i = 0; i < _options.size(); i++) {
385 		Common::String id = Common::String::format("%d", i + 1);
386 		_checkboxes.push_back(new CheckboxWidget(widgetsBoss(),
387 			_dialogLayout + ".customOption" + id + "Checkbox", _(_options[i].label), _(_options[i].tooltip)));
388 	}
389 }
390 
~ExtraGuiOptionsWidget()391 ExtraGuiOptionsWidget::~ExtraGuiOptionsWidget() {
392 }
393 
dialogLayout(const Common::String & domain)394 Common::String ExtraGuiOptionsWidget::dialogLayout(const Common::String &domain) {
395 	if (ConfMan.getActiveDomainName().equals(domain)) {
396 		return "GlobalConfig_Engine_Container";
397 	} else {
398 		return "GameOptions_Engine_Container";
399 	}
400 }
401 
load()402 void ExtraGuiOptionsWidget::load() {
403 	// Set the state of engine-specific checkboxes
404 	for (uint j = 0; j < _options.size() && j < _checkboxes.size(); ++j) {
405 		// The default values for engine-specific checkboxes are not set when
406 		// ScummVM starts, as this would require us to load and poll all of the
407 		// engine plugins on startup. Thus, we set the state of each custom
408 		// option checkbox to what is specified by the engine plugin, and
409 		// update it only if a value has been set in the configuration of the
410 		// currently selected game.
411 		bool isChecked = _options[j].defaultState;
412 		if (ConfMan.hasKey(_options[j].configOption, _domain))
413 			isChecked = ConfMan.getBool(_options[j].configOption, _domain);
414 		_checkboxes[j]->setState(isChecked);
415 	}
416 }
417 
save()418 bool ExtraGuiOptionsWidget::save() {
419 	// Set the state of engine-specific checkboxes
420 	for (uint i = 0; i < _options.size() && i < _checkboxes.size(); i++) {
421 		ConfMan.setBool(_options[i].configOption, _checkboxes[i]->getState(), _domain);
422 	}
423 
424 	return true;
425 }
426 
defineLayout(ThemeEval & layouts,const Common::String & layoutName,const Common::String & overlayedLayout) const427 void ExtraGuiOptionsWidget::defineLayout(ThemeEval& layouts, const Common::String& layoutName, const Common::String& overlayedLayout) const {
428 	layouts.addDialog(layoutName, overlayedLayout);
429 	layouts.addLayout(GUI::ThemeLayout::kLayoutVertical).addPadding(8, 8, 8, 8);
430 
431 	for (uint i = 0; i < _options.size(); i++) {
432 		Common::String id = Common::String::format("%d", i + 1);
433 		layouts.addWidget("customOption" + id + "Checkbox", "Checkbox");
434 	}
435 
436 	layouts.closeLayout().closeDialog();
437 }
438 
439 } // End of namespace GUI
440