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