Implement opcode 0x3d set animation skip point.
[scummvm-innocent.git] / gui / launcher.cpp
blob1ab4728072711e37f89326ce2bca5e61b25d9a9d
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.
21 * $URL$
22 * $Id$
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;
56 namespace GUI {
58 enum {
59 kStartCmd = 'STRT',
60 kAboutCmd = 'ABOU',
61 kOptionsCmd = 'OPTN',
62 kAddGameCmd = 'ADDG',
63 kEditGameCmd = 'EDTG',
64 kRemoveGameCmd = 'REMG',
65 kLoadGameCmd = 'LOAD',
66 kQuitCmd = 'QUIT',
67 kSearchCmd = 'SRCH',
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 {
89 public:
90 DomainEditTextWidget(GuiObject *boss, const String &name, const String &text)
91 : EditTextWidget(boss, name, text) {
94 protected:
95 bool tryInsertChar(byte c, int pos) {
96 if (isalnum(c) || c == '-' || c == '_') {
97 _editString.insertChar(c, pos);
98 return true;
100 return false;
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
116 * to achieve it)
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;
123 public:
124 EditGameDialog(const String &domain, const String &desc);
126 void open();
127 void close();
128 virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
130 protected:
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()) {
160 description = desc;
163 // GUI: Add tab widget
164 TabWidget *tab = new TabWidget(this, "GameOptions.TabWidget");
167 // 1) The game tab
169 tab->addTab("Game");
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);
179 // Language popup
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);
189 // Platform popup
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.");
209 // 4) The audio tab
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.");
219 // 5) The volume tab
221 tab->addTab("Volume");
223 _globalVolumeOverride = new CheckboxWidget(tab, "GameOptions_Volume.EnableTabCheckbox", "Override global volume settings", kCmdGlobalVolumeOverride, 0);
225 addVolumeControls(tab, "GameOptions_Volume.");
228 // 6) The MIDI tab
230 tab->addTab("MIDI");
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.");
240 // 2) The 'Path' tab
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);
261 _tabWidget = tab;
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");
281 int sel, i;
282 bool e;
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);
311 // TODO: game path
313 const Common::LanguageDescription *l = Common::g_languages;
314 const Common::Language lang = Common::parseLanguage(ConfMan.get("language", _domain));
316 sel = 0;
317 if (ConfMan.hasKey("language", _domain)) {
318 for (i = 0; l->code; ++l, ++i) {
319 if (lang == l->id)
320 sel = i + 2;
323 _langPopUp->setSelected(sel);
326 const Common::PlatformDescription *p = Common::g_platforms;
327 const Common::Platform platform = Common::parsePlatform(ConfMan.get("platform", _domain));
328 sel = 0;
329 for (i = 0; p->code; ++p, ++i) {
330 if (platform == p->id)
331 sel = i + 2;
333 _platformPopUp->setSelected(sel);
337 void EditGameDialog::close() {
338 if (getResult()) {
339 ConfMan.set("description", _descriptionWidget->getEditString(), _domain);
341 Common::Language lang = (Common::Language)_langPopUp->getSelectedTag();
342 if (lang < 0)
343 ConfMan.removeKey("language", _domain);
344 else
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();
360 if (platform < 0)
361 ConfMan.removeKey("platform", _domain);
362 else
363 ConfMan.set("platform", Common::getPlatformCode(platform), _domain);
365 OptionsDialog::close();
368 void EditGameDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
369 switch (cmd) {
370 case kCmdGlobalGraphicsOverride:
371 setGraphicSettingsState(data != 0);
372 draw();
373 break;
374 case kCmdGlobalAudioOverride:
375 setAudioSettingsState(data != 0);
376 setSubtitleSettingsState(data != 0);
377 if (_globalVolumeOverride == NULL)
378 setVolumeSettingsState(data != 0);
379 draw();
380 break;
381 case kCmdGlobalMIDIOverride:
382 setMIDISettingsState(data != 0);
383 draw();
384 break;
385 case kCmdGlobalVolumeOverride:
386 setVolumeSettingsState(data != 0);
387 draw();
388 break;
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);
399 else
400 _soundFontClearButton->setEnabled(false);
402 draw();
404 break;
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());
419 draw();
421 draw();
422 break;
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());
432 draw();
434 draw();
435 break;
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());
444 draw();
446 draw();
447 break;
450 case kOKCmd: {
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.");
459 alert.runModal();
460 return;
462 ConfMan.renameGameDomain(_domain, newDomain);
463 _domain = newDomain;
466 // FALL THROUGH to default case
467 default:
468 OptionsDialog::handleCommand(sender, cmd, data);
472 #pragma mark -
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();
481 _w = screenW;
482 _h = screenH;
484 #ifndef DISABLE_FANCY_THEMES
485 _logo = 0;
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);
492 } else
493 new StaticTextWidget(this, "Launcher.Version", gScummVMFullVersion);
494 #else
495 // Show ScummVM version
496 new StaticTextWidget(this, "Launcher.Version", gScummVMFullVersion);
497 #endif
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');
502 _startButton =
503 new ButtonWidget(this, "Launcher.StartButton", "Start", kStartCmd, 'S');
505 _loadButton =
506 new ButtonWidget(this, "Launcher.LoadGameButton", "Load", kLoadGameCmd, 'L');
508 // Above the lowest button rows: two more buttons (directly below the list box)
509 _addButton =
510 new ButtonWidget(this, "Launcher.AddGameButton", "Add Game", kAddGameCmd, 'A');
511 _editButton =
512 new ButtonWidget(this, "Launcher.EditGameButton", "Edit Game", kEditGameCmd, 'E');
513 _removeButton =
514 new ButtonWidget(this, "Launcher.RemoveGameButton", "Remove Game", kRemoveGameCmd, 'R');
516 // Search box
517 _searchDesc = 0;
518 #ifndef DISABLE_FANCY_THEMES
519 _searchPic = 0;
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));
523 } else
524 #endif
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);
536 // Populate the list
537 updateListing();
539 // Restore last selection
540 String last(ConfMan.get("lastselectedgame", ConfigManager::kApplicationDomain));
541 selectGame(last);
543 // En-/disable the buttons depending on the list selection
544 updateButtons();
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) {
554 if (!name.empty()) {
555 int itemToSelect = 0;
556 StringList::const_iterator iter;
557 for (iter = _domains.begin(); iter != _domains.end(); ++iter, ++itemToSelect) {
558 if (name == *iter) {
559 _list->setSelected(itemToSelect);
560 break;
566 LauncherDialog::~LauncherDialog() {
567 delete _browser;
568 delete _loadDialog;
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();
578 Dialog::open();
580 updateButtons();
583 void LauncherDialog::close() {
584 // Save last selection
585 const int sel = _list->getSelected();
586 if (sel >= 0)
587 ConfMan.set("lastselectedgame", _domains[sel], ConfigManager::kApplicationDomain);
588 else
589 ConfMan.removeKey("lastselectedgame", ConfigManager::kApplicationDomain);
591 ConfMan.flushToDisk();
592 Dialog::close();
595 void LauncherDialog::updateListing() {
596 Common::StringList l;
598 // Retrieve a list of all games defined in the config file
599 _domains.clear();
600 const ConfigManager::DomainMap &domains = ConfMan.getGameDomains();
601 ConfigManager::DomainMap::const_iterator iter;
602 for (iter = domains.begin(); iter != domains.end(); ++iter) {
603 #ifdef __DS__
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") {
607 continue;
609 #endif
611 String gameid(iter->_value.get("gameid"));
612 String description(iter->_value.get("description"));
614 if (gameid.empty())
615 gameid = iter->_key;
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))
630 pos++;
631 l.insert_at(pos, description);
632 _domains.insert_at(pos, iter->_key);
636 const int oldSel = _list->getSelected();
637 _list->setList(l);
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);
643 updateButtons();
645 // Update the filter settings, those are lost when "setList"
646 // is called.
647 _list->setFilter(_searchWidget->getEditString());
650 void LauncherDialog::addGame() {
651 int modifiers = g_system->getEventManager()->getModifierState();
652 bool massAdd = (modifiers & Common::KBD_SHIFT) != 0;
654 if (massAdd) {
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
668 updateListing();
670 // Set cursor to first detected game
671 selectGame(ConfMan.get("temp_selection", ConfigManager::kApplicationDomain));
672 ConfMan.removeKey("temp_selection", ConfigManager::kApplicationDomain);
674 draw();
677 // We need to update the buttons here, so "Mass add" will revert to "Add game"
678 // without any additional event.
679 updateButtons();
680 return;
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
688 // to pick from
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
694 // dialog.
696 bool looping;
697 do {
698 looping = false;
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!");
706 alert.runModal();
707 return;
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));
714 int idx;
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!");
718 alert.runModal();
719 idx = -1;
721 looping = true;
722 } else if (candidates.size() == 1) {
723 // Exact match
724 idx = 0;
725 } else {
726 // Display the candidates to the user and let her/him pick one
727 StringList list;
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
752 updateListing();
753 selectGame(editDialog.getDomain());
754 draw();
755 } else {
756 // User aborted, remove the the new domain again
757 ConfMan.removeGameDomain(domain);
762 } while (looping);
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)) {
773 int suffixN = 1;
774 char suffix[16];
775 Common::String gameid(domain);
777 while (ConfMan.hasGameDomain(domain)) {
778 snprintf(suffix, 16, "-%d", suffixN);
779 domain = gameid + suffix;
780 suffixN++;
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.).
803 return domain;
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
811 assert(item >= 0);
812 ConfMan.removeGameDomain(_domains[item]);
814 // Write config to disk
815 ConfMan.flushToDisk();
817 // Update the ListWidget and force a redraw
818 updateListing();
819 draw();
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.
830 assert(item >= 0);
831 String gameId(ConfMan.get("gameid", _domains[item]));
832 if (gameId.empty())
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
842 updateListing();
843 selectGame(editDialog.getDomain());
844 draw();
848 void LauncherDialog::loadGame(int item) {
849 String gameId = ConfMan.get("gameid", _domains[item]);
850 if (gameId.empty())
851 gameId = _domains[item];
853 const EnginePlugin *plugin = 0;
854 EngineMan.findGame(gameId, &plugin);
856 String target = _domains[item];
857 target.toLowercase();
859 if (plugin) {
860 if ((*plugin)->hasFeature(MetaEngine::kSupportsListSaves) &&
861 (*plugin)->hasFeature(MetaEngine::kSupportsLoadingDuringStartup)) {
862 int slot = _loadDialog->runModal(plugin, target);
863 if (slot >= 0) {
864 ConfMan.setActiveDomain(_domains[item]);
865 ConfMan.setInt("save_slot", slot, Common::ConfigManager::kTransientDomain);
866 close();
868 } else {
869 MessageDialog dialog
870 ("This game does not support loading games from the launcher.", "OK");
871 dialog.runModal();
873 } else {
874 MessageDialog dialog("ScummVM could not find any engine capable of running the selected game!", "OK");
875 dialog.runModal();
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);
889 updateButtons();
892 void LauncherDialog::handleKeyUp(Common::KeyState state) {
893 Dialog::handleKeyUp(state);
894 updateButtons();
897 void LauncherDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
898 int item = _list->getSelected();
900 switch (cmd) {
901 case kAddGameCmd:
902 addGame();
903 break;
904 case kRemoveGameCmd:
905 removeGame(item);
906 break;
907 case kEditGameCmd:
908 editGame(item);
909 break;
910 case kLoadGameCmd:
911 loadGame(item);
912 break;
913 case kOptionsCmd: {
914 GlobalOptionsDialog options;
915 options.runModal();
917 break;
918 case kAboutCmd: {
919 AboutDialog about;
920 about.runModal();
922 break;
923 case kStartCmd:
924 case kListItemActivatedCmd:
925 case kListItemDoubleClickedCmd:
926 // Print out what was selected
927 assert(item >= 0);
928 ConfMan.setActiveDomain(_domains[item]);
929 close();
930 break;
931 case kListItemRemovalRequestCmd:
932 removeGame(item);
933 break;
934 case kListSelectionChangedCmd:
935 updateButtons();
936 break;
937 case kQuitCmd:
938 ConfMan.setActiveDomain("");
939 setResult(-1);
940 close();
941 break;
942 case kSearchCmd:
943 _list->setFilter(_searchWidget->getEditString());
944 break;
945 case kSearchClearCmd:
946 _searchWidget->setEditString("");
947 _list->setFilter("");
948 break;
949 default:
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);
962 _editButton->draw();
964 if (enable != _removeButton->isEnabled()) {
965 _removeButton->setEnabled(enable);
966 _removeButton->draw();
969 int item = _list->getSelected();
970 bool en = enable;
972 if (item >= 0)
973 en = !(Common::checkGameGUIOption(Common::GUIO_NOLAUNCHLOAD, ConfMan.get("guioptions", _domains[item])));
975 if (en != _loadButton->isEnabled()) {
976 _loadButton->setEnabled(en);
977 _loadButton->draw();
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)
983 ? "Mass Add"
984 : "Add Game";
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");
994 if (ver) {
995 ver->setAlign((Graphics::TextAlign)g_gui.xmlEval()->getVar("Launcher.Version.Align", Graphics::kTextAlignCenter));
996 ver->setLabel(gScummVMVersionDate);
999 if (!_logo)
1000 _logo = new GraphicsWidget(this, "Launcher.Logo");
1001 _logo->useThemeTransparency(true);
1002 _logo->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageLogo));
1003 } else {
1004 StaticTextWidget *ver = (StaticTextWidget*)findWidget("Launcher.Version");
1005 if (ver) {
1006 ver->setAlign((Graphics::TextAlign)g_gui.xmlEval()->getVar("Launcher.Version.Align", Graphics::kTextAlignCenter));
1007 ver->setLabel(gScummVMFullVersion);
1010 if (_logo) {
1011 removeWidget(_logo);
1012 _logo->setNext(0);
1013 delete _logo;
1014 _logo = 0;
1018 if (g_gui.xmlEval()->getVar("Globals.ShowSearchPic") == 1 && g_gui.theme()->supportsImages()) {
1019 if (!_searchPic)
1020 _searchPic = new GraphicsWidget(this, "Launcher.SearchPic");
1021 _searchPic->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageSearch));
1023 if (_searchDesc) {
1024 removeWidget(_searchDesc);
1025 _searchDesc->setNext(0);
1026 delete _searchDesc;
1027 _searchDesc = 0;
1029 } else {
1030 if (!_searchDesc)
1031 _searchDesc = new StaticTextWidget(this, "Launcher.SearchDesc", "Search:");
1033 if (_searchPic) {
1034 removeWidget(_searchPic);
1035 _searchPic->setNext(0);
1036 delete _searchPic;
1037 _searchPic = 0;
1040 #endif
1042 _w = g_system->getOverlayWidth();
1043 _h = g_system->getOverlayHeight();
1045 Dialog::reflowLayout();
1048 } // End of namespace GUI