1 /* ScummVM - Graphic Adventure Engine
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.
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.
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.
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.
25 #include "base/version.h"
27 #include "common/config-manager.h"
28 #include "common/events.h"
29 #include "common/fs.h"
30 #include "common/util.h"
31 #include "common/savefile.h"
32 #include "common/system.h"
34 #include "gui/about.h"
35 #include "gui/browser.h"
36 #include "gui/chooser.h"
37 #include "gui/launcher.h"
38 #include "gui/massadd.h"
39 #include "gui/message.h"
40 #include "gui/GuiManager.h"
41 #include "gui/options.h"
42 #include "gui/saveload.h"
43 #include "gui/EditTextWidget.h"
44 #include "gui/ListWidget.h"
45 #include "gui/TabWidget.h"
46 #include "gui/PopUpWidget.h"
47 #include "gui/ThemeEval.h"
49 #include "graphics/cursorman.h"
51 #include "sound/mididrv.h"
54 using Common::ConfigManager
;
63 kEditGameCmd
= 'EDTG',
64 kRemoveGameCmd
= 'REMG',
65 kLoadGameCmd
= 'LOAD',
68 kListSearchCmd
= 'LSSR',
69 kSearchClearCmd
= 'SRCL',
71 kCmdGlobalGraphicsOverride
= 'OGFX',
72 kCmdGlobalAudioOverride
= 'OSFX',
73 kCmdGlobalMIDIOverride
= 'OMID',
74 kCmdGlobalVolumeOverride
= 'OVOL',
76 kCmdChooseSoundFontCmd
= 'chsf',
78 kCmdExtraBrowser
= 'PEXT',
79 kCmdGameBrowser
= 'PGME',
80 kCmdSaveBrowser
= 'PSAV'
84 * TODO: Clean up this ugly design: we subclass EditTextWidget to perform
85 * input validation. It would be much more elegant to use a decorator pattern,
86 * or a validation callback, or something like that.
88 class DomainEditTextWidget
: public EditTextWidget
{
90 DomainEditTextWidget(GuiObject
*boss
, const String
&name
, const String
&text
)
91 : EditTextWidget(boss
, name
, text
) {
95 bool tryInsertChar(byte c
, int pos
) {
96 if (isalnum(c
) || c
== '-' || c
== '_') {
97 _editString
.insertChar(c
, pos
);
105 * A dialog that allows the user to edit a config game entry.
106 * TODO: add widgets for some/all of the following
107 * - Maybe scaler/graphics mode. But there are two problems:
108 * 1) Different backends can have different scalers with different names,
109 * so we first have to add a way to query those... no Ender, I don't
110 * think a bitmasked property() value is nice for this, because we would
111 * have to add to the bitmask values whenever a backends adds a new scaler).
112 * 2) At the time the launcher is running, the GFX backend is already setup.
113 * So when a game is run via the launcher, the custom scaler setting for it won't be
114 * used. So we'd also have to add an API to change the scaler during runtime
115 * (the SDL backend can already do that based on user input, but there is no API
117 * If the APIs for 1&2 are in place, we can think about adding this to the Edit&Option dialogs
120 class EditGameDialog
: public OptionsDialog
{
121 typedef Common::String String
;
122 typedef Common::StringList StringList
;
124 EditGameDialog(const String
&domain
, const String
&desc
);
128 virtual void handleCommand(CommandSender
*sender
, uint32 cmd
, uint32 data
);
131 EditTextWidget
*_descriptionWidget
;
132 DomainEditTextWidget
*_domainWidget
;
134 StaticTextWidget
*_gamePathWidget
;
135 StaticTextWidget
*_extraPathWidget
;
136 StaticTextWidget
*_savePathWidget
;
138 StaticTextWidget
*_langPopUpDesc
;
139 PopUpWidget
*_langPopUp
;
140 StaticTextWidget
*_platformPopUpDesc
;
141 PopUpWidget
*_platformPopUp
;
143 CheckboxWidget
*_globalGraphicsOverride
;
144 CheckboxWidget
*_globalAudioOverride
;
145 CheckboxWidget
*_globalMIDIOverride
;
146 CheckboxWidget
*_globalVolumeOverride
;
149 EditGameDialog::EditGameDialog(const String
&domain
, const String
&desc
)
150 : OptionsDialog(domain
, "GameOptions") {
152 // GAME: Path to game data (r/o), extra data (r/o), and save data (r/w)
153 String
gamePath(ConfMan
.get("path", _domain
));
154 String
extraPath(ConfMan
.get("extrapath", _domain
));
155 String
savePath(ConfMan
.get("savepath", _domain
));
157 // GAME: Determine the description string
158 String
description(ConfMan
.get("description", domain
));
159 if (description
.empty() && !desc
.empty()) {
163 // GUI: Add tab widget
164 TabWidget
*tab
= new TabWidget(this, "GameOptions.TabWidget");
171 // GUI: Label & edit widget for the game ID
172 new StaticTextWidget(tab
, "GameOptions_Game.Id", "ID:");
173 _domainWidget
= new DomainEditTextWidget(tab
, "GameOptions_Game.Domain", _domain
);
175 // GUI: Label & edit widget for the description
176 new StaticTextWidget(tab
, "GameOptions_Game.Name", "Name:");
177 _descriptionWidget
= new EditTextWidget(tab
, "GameOptions_Game.Desc", description
);
180 _langPopUpDesc
= new StaticTextWidget(tab
, "GameOptions_Game.LangPopupDesc", "Language:");
181 _langPopUp
= new PopUpWidget(tab
, "GameOptions_Game.LangPopup");
182 _langPopUp
->appendEntry("<default>");
183 _langPopUp
->appendEntry("");
184 const Common::LanguageDescription
*l
= Common::g_languages
;
185 for (; l
->code
; ++l
) {
186 _langPopUp
->appendEntry(l
->description
, l
->id
);
190 _platformPopUpDesc
= new StaticTextWidget(tab
, "GameOptions_Game.PlatformPopupDesc", "Platform:");
191 _platformPopUp
= new PopUpWidget(tab
, "GameOptions_Game.PlatformPopup");
192 _platformPopUp
->appendEntry("<default>");
193 _platformPopUp
->appendEntry("");
194 const Common::PlatformDescription
*p
= Common::g_platforms
;
195 for (; p
->code
; ++p
) {
196 _platformPopUp
->appendEntry(p
->description
, p
->id
);
200 // 3) The graphics tab
202 _graphicsTabId
= tab
->addTab(g_system
->getOverlayWidth() > 320 ? "Graphics" : "GFX");
204 _globalGraphicsOverride
= new CheckboxWidget(tab
, "GameOptions_Graphics.EnableTabCheckbox", "Override global graphic settings", kCmdGlobalGraphicsOverride
, 0);
206 addGraphicControls(tab
, "GameOptions_Graphics.");
211 tab
->addTab("Audio");
213 _globalAudioOverride
= new CheckboxWidget(tab
, "GameOptions_Audio.EnableTabCheckbox", "Override global audio settings", kCmdGlobalAudioOverride
, 0);
215 addAudioControls(tab
, "GameOptions_Audio.");
216 addSubtitleControls(tab
, "GameOptions_Audio.");
221 tab
->addTab("Volume");
223 _globalVolumeOverride
= new CheckboxWidget(tab
, "GameOptions_Volume.EnableTabCheckbox", "Override global volume settings", kCmdGlobalVolumeOverride
, 0);
225 addVolumeControls(tab
, "GameOptions_Volume.");
232 _globalMIDIOverride
= new CheckboxWidget(tab
, "GameOptions_MIDI.EnableTabCheckbox", "Override global MIDI settings", kCmdGlobalMIDIOverride
, 0);
234 if (_guioptions
& Common::GUIO_NOMIDI
)
235 _globalMIDIOverride
->setEnabled(false);
237 addMIDIControls(tab
, "GameOptions_MIDI.");
242 tab
->addTab("Paths");
244 // These buttons have to be extra wide, or the text will be truncated
245 // in the small version of the GUI.
247 // GUI: Button + Label for the game path
248 new ButtonWidget(tab
, "GameOptions_Paths.Gamepath", "Game Path:", kCmdGameBrowser
, 0);
249 _gamePathWidget
= new StaticTextWidget(tab
, "GameOptions_Paths.GamepathText", gamePath
);
251 // GUI: Button + Label for the additional path
252 new ButtonWidget(tab
, "GameOptions_Paths.Extrapath", "Extra Path:", kCmdExtraBrowser
, 0);
253 _extraPathWidget
= new StaticTextWidget(tab
, "GameOptions_Paths.ExtrapathText", extraPath
);
255 // GUI: Button + Label for the save path
256 new ButtonWidget(tab
, "GameOptions_Paths.Savepath", "Save Path:", kCmdSaveBrowser
, 0);
257 _savePathWidget
= new StaticTextWidget(tab
, "GameOptions_Paths.SavepathText", savePath
);
259 // Activate the first tab
260 tab
->setActiveTab(0);
263 // Add OK & Cancel buttons
264 new ButtonWidget(this, "GameOptions.Cancel", "Cancel", kCloseCmd
, 0);
265 new ButtonWidget(this, "GameOptions.Ok", "OK", kOKCmd
, 0);
268 void EditGameDialog::open() {
269 OptionsDialog::open();
271 String
extraPath(ConfMan
.get("extrapath", _domain
));
272 if (extraPath
.empty() || !ConfMan
.hasKey("extrapath", _domain
)) {
273 _extraPathWidget
->setLabel("None");
276 String
savePath(ConfMan
.get("savepath", _domain
));
277 if (savePath
.empty() || !ConfMan
.hasKey("savepath", _domain
)) {
278 _savePathWidget
->setLabel("Default");
284 // En-/disable dialog items depending on whether overrides are active or not.
286 e
= ConfMan
.hasKey("gfx_mode", _domain
) ||
287 ConfMan
.hasKey("render_mode", _domain
) ||
288 ConfMan
.hasKey("fullscreen", _domain
) ||
289 ConfMan
.hasKey("aspect_ratio", _domain
);
290 _globalGraphicsOverride
->setState(e
);
292 e
= ConfMan
.hasKey("music_driver", _domain
) ||
293 ConfMan
.hasKey("output_rate", _domain
) ||
294 ConfMan
.hasKey("opl_driver", _domain
) ||
295 ConfMan
.hasKey("subtitles", _domain
) ||
296 ConfMan
.hasKey("talkspeed", _domain
);
297 _globalAudioOverride
->setState(e
);
299 e
= ConfMan
.hasKey("music_volume", _domain
) ||
300 ConfMan
.hasKey("sfx_volume", _domain
) ||
301 ConfMan
.hasKey("speech_volume", _domain
);
302 _globalVolumeOverride
->setState(e
);
304 e
= ConfMan
.hasKey("soundfont", _domain
) ||
305 ConfMan
.hasKey("multi_midi", _domain
) ||
306 ConfMan
.hasKey("native_mt32", _domain
) ||
307 ConfMan
.hasKey("enable_gs", _domain
) ||
308 ConfMan
.hasKey("midi_gain", _domain
);
309 _globalMIDIOverride
->setState(e
);
313 const Common::LanguageDescription
*l
= Common::g_languages
;
314 const Common::Language lang
= Common::parseLanguage(ConfMan
.get("language", _domain
));
317 if (ConfMan
.hasKey("language", _domain
)) {
318 for (i
= 0; l
->code
; ++l
, ++i
) {
323 _langPopUp
->setSelected(sel
);
326 const Common::PlatformDescription
*p
= Common::g_platforms
;
327 const Common::Platform platform
= Common::parsePlatform(ConfMan
.get("platform", _domain
));
329 for (i
= 0; p
->code
; ++p
, ++i
) {
330 if (platform
== p
->id
)
333 _platformPopUp
->setSelected(sel
);
337 void EditGameDialog::close() {
339 ConfMan
.set("description", _descriptionWidget
->getEditString(), _domain
);
341 Common::Language lang
= (Common::Language
)_langPopUp
->getSelectedTag();
343 ConfMan
.removeKey("language", _domain
);
345 ConfMan
.set("language", Common::getLanguageCode(lang
), _domain
);
347 String
gamePath(_gamePathWidget
->getLabel());
348 if (!gamePath
.empty())
349 ConfMan
.set("path", gamePath
, _domain
);
351 String
extraPath(_extraPathWidget
->getLabel());
352 if (!extraPath
.empty() && (extraPath
!= "None"))
353 ConfMan
.set("extrapath", extraPath
, _domain
);
355 String
savePath(_savePathWidget
->getLabel());
356 if (!savePath
.empty() && (savePath
!= "Default"))
357 ConfMan
.set("savepath", savePath
, _domain
);
359 Common::Platform platform
= (Common::Platform
)_platformPopUp
->getSelectedTag();
361 ConfMan
.removeKey("platform", _domain
);
363 ConfMan
.set("platform", Common::getPlatformCode(platform
), _domain
);
365 OptionsDialog::close();
368 void EditGameDialog::handleCommand(CommandSender
*sender
, uint32 cmd
, uint32 data
) {
370 case kCmdGlobalGraphicsOverride
:
371 setGraphicSettingsState(data
!= 0);
374 case kCmdGlobalAudioOverride
:
375 setAudioSettingsState(data
!= 0);
376 setSubtitleSettingsState(data
!= 0);
377 if (_globalVolumeOverride
== NULL
)
378 setVolumeSettingsState(data
!= 0);
381 case kCmdGlobalMIDIOverride
:
382 setMIDISettingsState(data
!= 0);
385 case kCmdGlobalVolumeOverride
:
386 setVolumeSettingsState(data
!= 0);
389 case kCmdChooseSoundFontCmd
: {
390 BrowserDialog
browser("Select SoundFont", false);
392 if (browser
.runModal() > 0) {
393 // User made this choice...
394 Common::FSNode
file(browser
.getResult());
395 _soundFont
->setLabel(file
.getPath());
397 if (!file
.getPath().empty() && (file
.getPath() != "None"))
398 _soundFontClearButton
->setEnabled(true);
400 _soundFontClearButton
->setEnabled(false);
407 // Change path for the game
408 case kCmdGameBrowser
: {
409 BrowserDialog
browser("Select directory with game data", true);
410 if (browser
.runModal() > 0) {
411 // User made his choice...
412 Common::FSNode
dir(browser
.getResult());
414 // TODO: Verify the game can be found in the new directory... Best
415 // done with optional specific gameid to pluginmgr detectgames?
416 // FSList files = dir.listDir(FSNode::kListFilesOnly);
418 _gamePathWidget
->setLabel(dir
.getPath());
425 // Change path for extra game data (eg, using sword cutscenes when playing via CD)
426 case kCmdExtraBrowser
: {
427 BrowserDialog
browser("Select additional game directory", true);
428 if (browser
.runModal() > 0) {
429 // User made his choice...
430 Common::FSNode
dir(browser
.getResult());
431 _extraPathWidget
->setLabel(dir
.getPath());
437 // Change path for stored save game (perm and temp) data
438 case kCmdSaveBrowser
: {
439 BrowserDialog
browser("Select directory for saved games", true);
440 if (browser
.runModal() > 0) {
441 // User made his choice...
442 Common::FSNode
dir(browser
.getResult());
443 _savePathWidget
->setLabel(dir
.getPath());
451 // Write back changes made to config object
452 String
newDomain(_domainWidget
->getEditString());
453 if (newDomain
!= _domain
) {
454 if (newDomain
.empty()
455 || newDomain
.hasPrefix("_")
456 || newDomain
== ConfigManager::kApplicationDomain
457 || ConfMan
.hasGameDomain(newDomain
)) {
458 MessageDialog
alert("This game ID is already taken. Please choose another one.");
462 ConfMan
.renameGameDomain(_domain
, newDomain
);
466 // FALL THROUGH to default case
468 OptionsDialog::handleCommand(sender
, cmd
, data
);
474 LauncherDialog::LauncherDialog()
475 : Dialog(0, 0, 320, 200) {
476 _backgroundType
= GUI::ThemeEngine::kDialogBackgroundMain
;
478 const int screenW
= g_system
->getOverlayWidth();
479 const int screenH
= g_system
->getOverlayHeight();
484 #ifndef DISABLE_FANCY_THEMES
486 if (g_gui
.xmlEval()->getVar("Globals.ShowLauncherLogo") == 1 && g_gui
.theme()->supportsImages()) {
487 _logo
= new GraphicsWidget(this, "Launcher.Logo");
488 _logo
->useThemeTransparency(true);
489 _logo
->setGfx(g_gui
.theme()->getImageSurface(ThemeEngine::kImageLogo
));
491 new StaticTextWidget(this, "Launcher.Version", gScummVMVersionDate
);
493 new StaticTextWidget(this, "Launcher.Version", gScummVMFullVersion
);
495 // Show ScummVM version
496 new StaticTextWidget(this, "Launcher.Version", gScummVMFullVersion
);
499 new ButtonWidget(this, "Launcher.QuitButton", "Quit", kQuitCmd
, 'Q');
500 new ButtonWidget(this, "Launcher.AboutButton", "About", kAboutCmd
, 'B');
501 new ButtonWidget(this, "Launcher.OptionsButton", "Options", kOptionsCmd
, 'O');
503 new ButtonWidget(this, "Launcher.StartButton", "Start", kStartCmd
, 'S');
506 new ButtonWidget(this, "Launcher.LoadGameButton", "Load", kLoadGameCmd
, 'L');
508 // Above the lowest button rows: two more buttons (directly below the list box)
510 new ButtonWidget(this, "Launcher.AddGameButton", "Add Game", kAddGameCmd
, 'A');
512 new ButtonWidget(this, "Launcher.EditGameButton", "Edit Game", kEditGameCmd
, 'E');
514 new ButtonWidget(this, "Launcher.RemoveGameButton", "Remove Game", kRemoveGameCmd
, 'R');
518 #ifndef DISABLE_FANCY_THEMES
520 if (g_gui
.xmlEval()->getVar("Globals.ShowSearchPic") == 1 && g_gui
.theme()->supportsImages()) {
521 _searchPic
= new GraphicsWidget(this, "Launcher.SearchPic");
522 _searchPic
->setGfx(g_gui
.theme()->getImageSurface(ThemeEngine::kImageSearch
));
525 _searchDesc
= new StaticTextWidget(this, "Launcher.SearchDesc", "Search:");
527 _searchWidget
= new EditTextWidget(this, "Launcher.Search", _search
, kSearchCmd
);
528 _searchClearButton
= new ButtonWidget(this, "Launcher.SearchClearButton", "C", kSearchClearCmd
, 0);
530 // Add list with game titles
531 _list
= new ListWidget(this, "Launcher.GameList", kListSearchCmd
);
532 _list
->setEditable(false);
533 _list
->setNumberingMode(kListNumberingOff
);
539 // Restore last selection
540 String
last(ConfMan
.get("lastselectedgame", ConfigManager::kApplicationDomain
));
543 // En-/disable the buttons depending on the list selection
546 // Create file browser dialog
547 _browser
= new BrowserDialog("Select directory with game data", true);
549 // Create Load dialog
550 _loadDialog
= new SaveLoadChooser("Load game:", "Load");
553 void LauncherDialog::selectGame(const String
&name
) {
555 int itemToSelect
= 0;
556 StringList::const_iterator iter
;
557 for (iter
= _domains
.begin(); iter
!= _domains
.end(); ++iter
, ++itemToSelect
) {
559 _list
->setSelected(itemToSelect
);
566 LauncherDialog::~LauncherDialog() {
571 void LauncherDialog::open() {
572 // Clear the active domain, in case we return to the dialog from a
573 // failure to launch a game. Otherwise, pressing ESC will attempt to
574 // re-launch the same game again.
575 ConfMan
.setActiveDomain("");
577 CursorMan
.popAllCursors();
583 void LauncherDialog::close() {
584 // Save last selection
585 const int sel
= _list
->getSelected();
587 ConfMan
.set("lastselectedgame", _domains
[sel
], ConfigManager::kApplicationDomain
);
589 ConfMan
.removeKey("lastselectedgame", ConfigManager::kApplicationDomain
);
591 ConfMan
.flushToDisk();
595 void LauncherDialog::updateListing() {
596 Common::StringList l
;
598 // Retrieve a list of all games defined in the config file
600 const ConfigManager::DomainMap
&domains
= ConfMan
.getGameDomains();
601 ConfigManager::DomainMap::const_iterator iter
;
602 for (iter
= domains
.begin(); iter
!= domains
.end(); ++iter
) {
604 // DS port uses an extra section called 'ds'. This prevents the section from being
605 // detected as a game.
606 if (iter
->_key
== "ds") {
611 String
gameid(iter
->_value
.get("gameid"));
612 String
description(iter
->_value
.get("description"));
616 if (description
.empty()) {
617 GameDescriptor g
= EngineMan
.findGame(gameid
);
618 if (g
.contains("description"))
619 description
= g
.description();
622 if (description
.empty())
623 description
= "Unknown (target " + iter
->_key
+ ", gameid " + gameid
+ ")";
625 if (!gameid
.empty() && !description
.empty()) {
626 // Insert the game into the launcher list
627 int pos
= 0, size
= l
.size();
629 while (pos
< size
&& (scumm_stricmp(description
.c_str(), l
[pos
].c_str()) > 0))
631 l
.insert_at(pos
, description
);
632 _domains
.insert_at(pos
, iter
->_key
);
636 const int oldSel
= _list
->getSelected();
638 if (oldSel
< (int)l
.size())
639 _list
->setSelected(oldSel
); // Restore the old selection
640 else if (oldSel
!= -1)
641 // Select the last entry if the list has been reduced
642 _list
->setSelected(_list
->getList().size() - 1);
645 // Update the filter settings, those are lost when "setList"
647 _list
->setFilter(_searchWidget
->getEditString());
650 void LauncherDialog::addGame() {
651 int modifiers
= g_system
->getEventManager()->getModifierState();
652 bool massAdd
= (modifiers
& Common::KBD_SHIFT
) != 0;
655 MessageDialog
alert("Do you really want to run the mass game detector? "
656 "This could potentially add a huge number of games.", "Yes", "No");
657 if (alert
.runModal() == GUI::kMessageOK
&& _browser
->runModal() > 0) {
658 MassAddDialog
massAddDlg(_browser
->getResult());
660 if (_list
->getSelected() != -1) {
661 // Save current game position, so on cancel cursor will move back
662 ConfMan
.set("temp_selection", _domains
[_list
->getSelected()], ConfigManager::kApplicationDomain
);
665 massAddDlg
.runModal();
667 // Update the ListWidget and force a redraw
670 // Set cursor to first detected game
671 selectGame(ConfMan
.get("temp_selection", ConfigManager::kApplicationDomain
));
672 ConfMan
.removeKey("temp_selection", ConfigManager::kApplicationDomain
);
677 // We need to update the buttons here, so "Mass add" will revert to "Add game"
678 // without any additional event.
683 // Allow user to add a new game to the list.
684 // 1) show a dir selection dialog which lets the user pick the directory
685 // the game data resides in.
686 // 2) try to auto detect which game is in the directory, if we cannot
687 // determine it uniquely present a list of candidates to the user
689 // 3) Display the 'Edit' dialog for that item, letting the user specify
690 // an alternate description (to distinguish multiple versions of the
691 // game, e.g. 'Monkey German' and 'Monkey English') and set default
692 // options for that game
693 // 4) If no game is found in the specified directory, return to the
700 if (_browser
->runModal() > 0) {
701 // User made his choice...
702 Common::FSNode
dir(_browser
->getResult());
703 Common::FSList files
;
704 if (!dir
.getChildren(files
, Common::FSNode::kListAll
)) {
705 MessageDialog
alert("ScummVM couldn't open the specified directory!");
710 // ...so let's determine a list of candidates, games that
711 // could be contained in the specified directory.
712 GameList
candidates(EngineMan
.detectGames(files
));
715 if (candidates
.empty()) {
716 // No game was found in the specified directory
717 MessageDialog
alert("ScummVM could not find any game in the specified directory!");
722 } else if (candidates
.size() == 1) {
726 // Display the candidates to the user and let her/him pick one
728 for (idx
= 0; idx
< (int)candidates
.size(); idx
++)
729 list
.push_back(candidates
[idx
].description());
731 ChooserDialog
dialog("Pick the game:");
732 dialog
.setList(list
);
733 idx
= dialog
.runModal();
735 if (0 <= idx
&& idx
< (int)candidates
.size()) {
736 GameDescriptor result
= candidates
[idx
];
738 // TODO: Change the detectors to set "path" !
739 result
["path"] = dir
.getPath();
741 Common::String domain
= addGameToConf(result
);
743 // Display edit dialog for the new entry
744 EditGameDialog
editDialog(domain
, result
.description());
745 if (editDialog
.runModal() > 0) {
746 // User pressed OK, so make changes permanent
748 // Write config to disk
749 ConfMan
.flushToDisk();
751 // Update the ListWidget, select the new item, and force a redraw
753 selectGame(editDialog
.getDomain());
756 // User aborted, remove the the new domain again
757 ConfMan
.removeGameDomain(domain
);
765 Common::String
addGameToConf(const GameDescriptor
&result
) {
766 // The auto detector or the user made a choice.
767 // Pick a domain name which does not yet exist (after all, we
768 // are *adding* a game to the config, not replacing).
769 Common::String domain
= result
.preferredtarget();
771 assert(!domain
.empty());
772 if (ConfMan
.hasGameDomain(domain
)) {
775 Common::String
gameid(domain
);
777 while (ConfMan
.hasGameDomain(domain
)) {
778 snprintf(suffix
, 16, "-%d", suffixN
);
779 domain
= gameid
+ suffix
;
784 // Add the name domain
785 ConfMan
.addGameDomain(domain
);
787 // Copy all non-empty key/value pairs into the new domain
788 for (GameDescriptor::const_iterator iter
= result
.begin(); iter
!= result
.end(); ++iter
) {
789 if (!iter
->_value
.empty() && iter
->_key
!= "preferredtarget")
790 ConfMan
.set(iter
->_key
, iter
->_value
, domain
);
793 // TODO: Setting the description field here has the drawback
794 // that the user does never notice when we upgrade our descriptions.
795 // It might be nice ot leave this field empty, and only set it to
796 // a value when the user edits the description string.
797 // However, at this point, that's impractical. Once we have a method
798 // to query all backends for the proper & full description of a given
799 // game target, we can change this (currently, you can only query
800 // for the generic gameid description; it's not possible to obtain
801 // a description which contains extended information like language, etc.).
806 void LauncherDialog::removeGame(int item
) {
807 MessageDialog
alert("Do you really want to remove this game configuration?", "Yes", "No");
809 if (alert
.runModal() == GUI::kMessageOK
) {
810 // Remove the currently selected game from the list
812 ConfMan
.removeGameDomain(_domains
[item
]);
814 // Write config to disk
815 ConfMan
.flushToDisk();
817 // Update the ListWidget and force a redraw
823 void LauncherDialog::editGame(int item
) {
824 // Set game specific options. Most of these should be "optional", i.e. by
825 // default set nothing and use the global ScummVM settings. E.g. the user
826 // can set here an optional alternate music volume, or for specific games
827 // a different music driver etc.
828 // This is useful because e.g. MonkeyVGA needs Adlib music to have decent
829 // music support etc.
831 String
gameId(ConfMan
.get("gameid", _domains
[item
]));
833 gameId
= _domains
[item
];
834 EditGameDialog
editDialog(_domains
[item
], EngineMan
.findGame(gameId
).description());
835 if (editDialog
.runModal() > 0) {
836 // User pressed OK, so make changes permanent
838 // Write config to disk
839 ConfMan
.flushToDisk();
841 // Update the ListWidget, reselect the edited game and force a redraw
843 selectGame(editDialog
.getDomain());
848 void LauncherDialog::loadGame(int item
) {
849 String gameId
= ConfMan
.get("gameid", _domains
[item
]);
851 gameId
= _domains
[item
];
853 const EnginePlugin
*plugin
= 0;
854 EngineMan
.findGame(gameId
, &plugin
);
856 String target
= _domains
[item
];
857 target
.toLowercase();
860 if ((*plugin
)->hasFeature(MetaEngine::kSupportsListSaves
) &&
861 (*plugin
)->hasFeature(MetaEngine::kSupportsLoadingDuringStartup
)) {
862 int slot
= _loadDialog
->runModal(plugin
, target
);
864 ConfMan
.setActiveDomain(_domains
[item
]);
865 ConfMan
.setInt("save_slot", slot
, Common::ConfigManager::kTransientDomain
);
870 ("This game does not support loading games from the launcher.", "OK");
874 MessageDialog
dialog("ScummVM could not find any engine capable of running the selected game!", "OK");
879 void LauncherDialog::handleKeyDown(Common::KeyState state
) {
880 if (state
.keycode
== Common::KEYCODE_TAB
) {
881 // Toggle between the game list and the quick search field.
882 if (getFocusWidget() == _searchWidget
) {
883 setFocusWidget(_list
);
884 } else if (getFocusWidget() == _list
) {
885 setFocusWidget(_searchWidget
);
888 Dialog::handleKeyDown(state
);
892 void LauncherDialog::handleKeyUp(Common::KeyState state
) {
893 Dialog::handleKeyUp(state
);
897 void LauncherDialog::handleCommand(CommandSender
*sender
, uint32 cmd
, uint32 data
) {
898 int item
= _list
->getSelected();
914 GlobalOptionsDialog options
;
924 case kListItemActivatedCmd
:
925 case kListItemDoubleClickedCmd
:
926 // Print out what was selected
928 ConfMan
.setActiveDomain(_domains
[item
]);
931 case kListItemRemovalRequestCmd
:
934 case kListSelectionChangedCmd
:
938 ConfMan
.setActiveDomain("");
943 _list
->setFilter(_searchWidget
->getEditString());
945 case kSearchClearCmd
:
946 _searchWidget
->setEditString("");
947 _list
->setFilter("");
950 Dialog::handleCommand(sender
, cmd
, data
);
954 void LauncherDialog::updateButtons() {
955 bool enable
= (_list
->getSelected() >= 0);
956 if (enable
!= _startButton
->isEnabled()) {
957 _startButton
->setEnabled(enable
);
958 _startButton
->draw();
960 if (enable
!= _editButton
->isEnabled()) {
961 _editButton
->setEnabled(enable
);
964 if (enable
!= _removeButton
->isEnabled()) {
965 _removeButton
->setEnabled(enable
);
966 _removeButton
->draw();
969 int item
= _list
->getSelected();
973 en
= !(Common::checkGameGUIOption(Common::GUIO_NOLAUNCHLOAD
, ConfMan
.get("guioptions", _domains
[item
])));
975 if (en
!= _loadButton
->isEnabled()) {
976 _loadButton
->setEnabled(en
);
980 // Update the label of the "Add" button depending on whether shift is pressed or not
981 int modifiers
= g_system
->getEventManager()->getModifierState();
982 const char *newAddButtonLabel
= ((modifiers
& Common::KBD_SHIFT
) != 0)
986 if (_addButton
->getLabel() != newAddButtonLabel
)
987 _addButton
->setLabel(newAddButtonLabel
);
990 void LauncherDialog::reflowLayout() {
991 #ifndef DISABLE_FANCY_THEMES
992 if (g_gui
.xmlEval()->getVar("Globals.ShowLauncherLogo") == 1 && g_gui
.theme()->supportsImages()) {
993 StaticTextWidget
*ver
= (StaticTextWidget
*)findWidget("Launcher.Version");
995 ver
->setAlign((Graphics::TextAlign
)g_gui
.xmlEval()->getVar("Launcher.Version.Align", Graphics::kTextAlignCenter
));
996 ver
->setLabel(gScummVMVersionDate
);
1000 _logo
= new GraphicsWidget(this, "Launcher.Logo");
1001 _logo
->useThemeTransparency(true);
1002 _logo
->setGfx(g_gui
.theme()->getImageSurface(ThemeEngine::kImageLogo
));
1004 StaticTextWidget
*ver
= (StaticTextWidget
*)findWidget("Launcher.Version");
1006 ver
->setAlign((Graphics::TextAlign
)g_gui
.xmlEval()->getVar("Launcher.Version.Align", Graphics::kTextAlignCenter
));
1007 ver
->setLabel(gScummVMFullVersion
);
1011 removeWidget(_logo
);
1018 if (g_gui
.xmlEval()->getVar("Globals.ShowSearchPic") == 1 && g_gui
.theme()->supportsImages()) {
1020 _searchPic
= new GraphicsWidget(this, "Launcher.SearchPic");
1021 _searchPic
->setGfx(g_gui
.theme()->getImageSurface(ThemeEngine::kImageSearch
));
1024 removeWidget(_searchDesc
);
1025 _searchDesc
->setNext(0);
1031 _searchDesc
= new StaticTextWidget(this, "Launcher.SearchDesc", "Search:");
1034 removeWidget(_searchPic
);
1035 _searchPic
->setNext(0);
1042 _w
= g_system
->getOverlayWidth();
1043 _h
= g_system
->getOverlayHeight();
1045 Dialog::reflowLayout();
1048 } // End of namespace GUI