scummvm/engines/dialogs.cpp

472 lines
16 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "base/version.h"
#include "common/config-manager.h"
#include "common/events.h"
#include "common/str.h"
#include "common/system.h"
#include "common/translation.h"
#include "gui/about.h"
#include "gui/gui-manager.h"
#include "gui/message.h"
#include "gui/options.h"
#include "gui/saveload.h"
#include "gui/ThemeEngine.h"
#include "gui/ThemeEval.h"
#include "gui/widget.h"
#include "gui/widgets/tab.h"
#include "graphics/font.h"
#include "engines/dialogs.h"
#include "engines/engine.h"
#include "engines/metaengine.h"
MainMenuDialog::MainMenuDialog(Engine *engine)
: GUI::Dialog("GlobalMenu"), _engine(engine) {
_backgroundType = GUI::ThemeEngine::kDialogBackgroundSpecial;
#ifndef DISABLE_FANCY_THEMES
_logo = 0;
if (g_gui.xmlEval()->getVar("Globals.ShowGlobalMenuLogo", 0) == 1 && g_gui.theme()->supportsImages()) {
_logo = new GUI::GraphicsWidget(this, "GlobalMenu.Logo");
_logo->useThemeTransparency(true);
_logo->setGfx(g_gui.theme()->getImageSurface(GUI::ThemeEngine::kImageLogoSmall));
} else {
GUI::StaticTextWidget *title = new GUI::StaticTextWidget(this, "GlobalMenu.Title", Common::U32String("ScummVM"));
title->setAlign(Graphics::kTextAlignCenter);
}
#else
GUI::StaticTextWidget *title = new GUI::StaticTextWidget(this, "GlobalMenu.Title", Common::U32String("ScummVM"));
title->setAlign(Graphics::kTextAlignCenter);
#endif
GUI::StaticTextWidget *version = new GUI::StaticTextWidget(this, "GlobalMenu.Version", Common::U32String(gScummVMVersionDate));
version->setAlign(Graphics::kTextAlignCenter);
new GUI::ButtonWidget(this, "GlobalMenu.Resume", _("~R~esume"), Common::U32String(), kPlayCmd, 'P');
_loadButton = new GUI::ButtonWidget(this, "GlobalMenu.Load", _("~L~oad"), Common::U32String(), kLoadCmd);
_loadButton->setVisible(_engine->hasFeature(Engine::kSupportsLoadingDuringRuntime));
_loadButton->setEnabled(_engine->hasFeature(Engine::kSupportsLoadingDuringRuntime));
_saveButton = new GUI::ButtonWidget(this, "GlobalMenu.Save", _("~S~ave"), Common::U32String(), kSaveCmd);
_saveButton->setVisible(_engine->hasFeature(Engine::kSupportsSavingDuringRuntime));
_saveButton->setEnabled(_engine->hasFeature(Engine::kSupportsSavingDuringRuntime));
new GUI::ButtonWidget(this, "GlobalMenu.Options", _("~O~ptions"), Common::U32String(), kOptionsCmd);
// The help button is disabled by default.
// To enable "Help", an engine needs to use a subclass of MainMenuDialog
// (at least for now, we might change how this works in the future).
_helpButton = new GUI::ButtonWidget(this, "GlobalMenu.Help", _("~H~elp"), Common::U32String(), kHelpCmd);
_helpButton->setVisible(_engine->hasFeature(Engine::kSupportsHelp));
_helpButton->setEnabled(_engine->hasFeature(Engine::kSupportsHelp));
new GUI::ButtonWidget(this, "GlobalMenu.About", _("~A~bout"), Common::U32String(), kAboutCmd);
if (g_gui.getGUIWidth() > 320)
_returnToLauncherButton = new GUI::ButtonWidget(this, "GlobalMenu.ReturnToLauncher", _("~R~eturn to Launcher"), Common::U32String(), kLauncherCmd);
else
_returnToLauncherButton = new GUI::ButtonWidget(this, "GlobalMenu.ReturnToLauncher", _c("~R~eturn to Launcher", "lowres"), Common::U32String(), kLauncherCmd);
_returnToLauncherButton->setEnabled(_engine->hasFeature(Engine::kSupportsReturnToLauncher));
if (!g_system->hasFeature(OSystem::kFeatureNoQuit) && (!(ConfMan.getBool("gui_return_to_launcher_at_exit")) || !_engine->hasFeature(Engine::kSupportsReturnToLauncher)))
new GUI::ButtonWidget(this, "GlobalMenu.Quit", _("~Q~uit"), Common::U32String(), kQuitCmd);
_aboutDialog = new GUI::AboutDialog();
_loadDialog = new GUI::SaveLoadChooser(_("Load game:"), _("Load"), false);
_saveDialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
}
MainMenuDialog::~MainMenuDialog() {
delete _aboutDialog;
delete _loadDialog;
delete _saveDialog;
}
void MainMenuDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd) {
case kPlayCmd:
close();
break;
case kLoadCmd:
load();
break;
case kSaveCmd:
save();
break;
case kOptionsCmd: {
GUI::ConfigDialog configDialog;
configDialog.runModal();
break;
}
case kAboutCmd:
_aboutDialog->runModal();
break;
case kHelpCmd: {
GUI::MessageDialog dialog(
_("Sorry, this engine does not currently provide in-game help. "
"Please consult the README for basic information, and for "
"instructions on how to obtain further assistance."));
dialog.runModal();
}
break;
case kLauncherCmd: {
Common::Event eventReturnToLauncher;
eventReturnToLauncher.type = Common::EVENT_RETURN_TO_LAUNCHER;
g_system->getEventManager()->pushEvent(eventReturnToLauncher);
close();
}
break;
case kQuitCmd: {
Common::Event eventQ;
eventQ.type = Common::EVENT_QUIT;
g_system->getEventManager()->pushEvent(eventQ);
close();
}
break;
default:
GUI::Dialog::handleCommand(sender, cmd, data);
}
}
void MainMenuDialog::reflowLayout() {
if (_engine->hasFeature(Engine::kSupportsLoadingDuringRuntime))
_loadButton->setEnabled(_engine->canLoadGameStateCurrently());
if (_engine->hasFeature(Engine::kSupportsSavingDuringRuntime))
_saveButton->setEnabled(_engine->canSaveGameStateCurrently());
// Overlay size might have changed since the construction of the dialog.
// Update labels when it might be needed
// FIXME: it might be better to declare GUI::StaticTextWidget::setLabel() virtual
// and to reimplement it in GUI::ButtonWidget to handle the hotkey.
if (g_gui.getGUIWidth() > 320)
_returnToLauncherButton->setLabel(_returnToLauncherButton->cleanupHotkey(_("~R~eturn to Launcher")));
else
_returnToLauncherButton->setLabel(_returnToLauncherButton->cleanupHotkey(_c("~R~eturn to Launcher", "lowres")));
#ifndef DISABLE_FANCY_THEMES
if (g_gui.xmlEval()->getVar("Globals.ShowGlobalMenuLogo", 0) == 1 && g_gui.theme()->supportsImages()) {
if (!_logo)
_logo = new GUI::GraphicsWidget(this, "GlobalMenu.Logo");
_logo->useThemeTransparency(true);
_logo->setGfx(g_gui.theme()->getImageSurface(GUI::ThemeEngine::kImageLogoSmall));
GUI::StaticTextWidget *title = (GUI::StaticTextWidget *)findWidget("GlobalMenu.Title");
if (title) {
removeWidget(title);
title->setNext(0);
delete title;
}
} else {
GUI::StaticTextWidget *title = (GUI::StaticTextWidget *)findWidget("GlobalMenu.Title");
if (!title) {
title = new GUI::StaticTextWidget(this, "GlobalMenu.Title", Common::U32String("ScummVM"));
title->setAlign(Graphics::kTextAlignCenter);
}
if (_logo) {
removeWidget(_logo);
_logo->setNext(0);
delete _logo;
_logo = 0;
}
}
#endif
Dialog::reflowLayout();
}
void MainMenuDialog::save() {
int slot = _saveDialog->runModalWithCurrentTarget();
if (slot >= 0) {
Common::String result(_saveDialog->getResultString());
if (result.empty()) {
// If the user was lazy and entered no save name, come up with a default name.
result = _saveDialog->createDefaultSaveDescription(slot);
}
Common::Error status = _engine->saveGameState(slot, result);
if (status.getCode() != Common::kNoError) {
Common::U32String failMessage = Common::U32String::format(_("Failed to save game (%s)! "
"Please consult the README for basic information, and for "
"instructions on how to obtain further assistance."), status.getDesc().c_str());
GUI::MessageDialog dialog(failMessage);
dialog.runModal();
}
close();
}
}
void MainMenuDialog::load() {
int slot = _loadDialog->runModalWithCurrentTarget();
_engine->setGameToLoadSlot(slot);
if (slot >= 0)
close();
}
namespace GUI {
// FIXME: We use the empty string as domain name here. This tells the
// ConfigManager to use the 'default' domain for all its actions. We do that
// to get as close as possible to editing the 'active' settings.
//
// However, that requires bad & evil hacks in the ConfigManager code,
// and even then still doesn't work quite correctly.
// For example, if the transient domain contains 'false' for the 'fullscreen'
// flag, but the user used a hotkey to switch to windowed mode, then the dialog
// will display the wrong value anyway.
//
// Proposed solution consisting of multiple steps:
// 1) Add special code to the open() code that reads out everything stored
// in the transient domain that is controlled by this dialog, and updates
// the dialog accordingly.
// 2) Even more code is added to query the backend for current settings, like
// the fullscreen mode flag etc., and also updates the dialog accordingly.
// 3) The domain being edited is set to the active game domain.
// 4) If the dialog is closed with the "OK" button, then we remove everything
// stored in the transient domain (or at least everything corresponding to
// switches in this dialog.
// If OTOH the dialog is closed with "Cancel" we do no such thing.
//
// These changes will achieve two things at once: Allow us to get rid of using
// "" as value for the domain, and in fact provide a somewhat better user
// experience at the same time.
ConfigDialog::ConfigDialog() :
GUI::OptionsDialog("", "GlobalConfig"),
_engineOptions(nullptr) {
assert(g_engine);
const Common::String &gameDomain = ConfMan.getActiveDomainName();
const MetaEngine *metaEngine = g_engine->getMetaEngine();
// GUI: Add tab widget
GUI::TabWidget *tab = new GUI::TabWidget(this, "GlobalConfig.TabWidget");
//
// The game specific options tab
//
int tabId = tab->addTab(_("Game"), "GlobalConfig_Engine");
if (g_engine->hasFeature(Engine::kSupportsChangingOptionsDuringRuntime)) {
_engineOptions = metaEngine->buildEngineOptionsWidget(tab, "GlobalConfig_Engine.Container", gameDomain);
}
if (_engineOptions) {
_engineOptions->setParentDialog(this);
} else {
tab->removeTab(tabId);
}
//
// The Audio / Subtitles tab
//
tab->addTab(_("Audio"), "GlobalConfig_Audio");
//
// Sound controllers
//
addVolumeControls(tab, "GlobalConfig_Audio.");
setVolumeSettingsState(true); // could disable controls by GUI options
//
// Subtitle speed and toggle controllers
//
if (g_engine->hasFeature(Engine::kSupportsSubtitleOptions)) {
// Global talkspeed range of 0-255
addSubtitleControls(tab, "GlobalConfig_Audio.", 255);
setSubtitleSettingsState(true); // could disable controls by GUI options
}
//
// The Keymap tab
//
Common::KeymapArray keymaps = metaEngine->initKeymaps(gameDomain.c_str());
if (!keymaps.empty()) {
tab->addTab(_("Keymaps"), "GlobalConfig_KeyMapper", false);
addKeyMapperControls(tab, "GlobalConfig_KeyMapper.", keymaps, gameDomain);
}
//
// The backend tab (shown only if the backend implements one)
//
int backendTabId = tab->addTab(_("Backend"), "GlobalConfig_Backend", false);
_backendOptions = g_system->buildBackendOptionsWidget(tab, "GlobalConfig_Backend.Container", _domain);
if (_backendOptions) {
_backendOptions->setParentDialog(this);
} else {
tab->removeTab(backendTabId);
}
//
// The Achievements & The Statistics tabs
//
AchMan.setActiveDomain(metaEngine->getAchievementsInfo(gameDomain));
if (AchMan.getAchievementCount()) {
tab->addTab(_("Achievements"), "GlobalConfig_Achievements", false);
addAchievementsControls(tab, "GlobalConfig_Achievements.");
}
if (AchMan.getStatCount()) {
tab->addTab(_("Statistics"), "GlobalConfig_Achievements", false);
addStatisticsControls(tab, "GlobalConfig_Achievements.");
}
// Activate the first tab
tab->setActiveTab(0);
//
// Add the buttons
//
new GUI::ButtonWidget(this, "GlobalConfig.Ok", _("~O~K"), Common::U32String(), GUI::kOKCmd);
new GUI::ButtonWidget(this, "GlobalConfig.Cancel", _("~C~ancel"), Common::U32String(), GUI::kCloseCmd);
}
ConfigDialog::~ConfigDialog() {
}
void ConfigDialog::build() {
OptionsDialog::build();
// Engine options
if (_engineOptions) {
_engineOptions->load();
}
}
void ConfigDialog::apply() {
if (_engineOptions) {
_engineOptions->save();
}
OptionsDialog::apply();
}
ExtraGuiOptionsWidget::ExtraGuiOptionsWidget(GuiObject *containerBoss, const Common::String &name, const Common::String &domain, const ExtraGuiOptions &options) :
OptionsContainerWidget(containerBoss, name, dialogLayout(domain), false, domain),
_options(options) {
for (uint i = 0; i < _options.size(); i++) {
Common::String id = Common::String::format("%d", i + 1);
uint32 cmd = _options[i].groupLeaderId ? kClickGroupLeaderCmd : 0;
_checkboxes.push_back(new CheckboxWidget(widgetsBoss(),
_dialogLayout + ".customOption" + id + "Checkbox", _(_options[i].label), _(_options[i].tooltip), cmd));
}
}
ExtraGuiOptionsWidget::~ExtraGuiOptionsWidget() {
}
void ExtraGuiOptionsWidget::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd) {
case kClickGroupLeaderCmd: {
byte groupLeaderId = 0;
for (uint i = 0; i < _checkboxes.size(); i++) {
if (_checkboxes[i] == (CheckboxWidget *)sender) {
groupLeaderId = _options[i].groupLeaderId;
break;
}
}
if (!groupLeaderId)
break;
// We have found the "group leader" checkbox. Enable or disable
// all checkboxes in the group. Theoretically, this could mean
// that we disable another group leader, so its group should
// also be disabled. But that seems overkill for now.
for (uint i = 0; i < _options.size(); i++) {
if (_options[i].groupId == groupLeaderId) {
_checkboxes[i]->setEnabled(data != 0);
}
}
break;
}
default:
OptionsContainerWidget::handleCommand(sender, cmd, data);
break;
}
}
Common::String ExtraGuiOptionsWidget::dialogLayout(const Common::String &domain) {
if (ConfMan.getActiveDomainName().equals(domain)) {
return "GlobalConfig_Engine_Container";
} else {
return "GameOptions_Game_Container";
}
}
void ExtraGuiOptionsWidget::load() {
// Set the state of engine-specific checkboxes
for (uint j = 0; j < _options.size() && j < _checkboxes.size(); ++j) {
// The default values for engine-specific checkboxes are not set when
// ScummVM starts, as this would require us to load and poll all of the
// engine plugins on startup. Thus, we set the state of each custom
// option checkbox to what is specified by the engine plugin, and
// update it only if a value has been set in the configuration of the
// currently selected game.
bool isChecked = _options[j].defaultState;
if (ConfMan.hasKey(_options[j].configOption, _domain))
isChecked = ConfMan.getBool(_options[j].configOption, _domain);
_checkboxes[j]->setState(isChecked);
}
}
bool ExtraGuiOptionsWidget::save() {
// Set the state of engine-specific checkboxes
for (uint i = 0; i < _options.size() && i < _checkboxes.size(); i++) {
ConfMan.setBool(_options[i].configOption, _checkboxes[i]->isEnabled() && _checkboxes[i]->getState(), _domain);
}
return true;
}
void ExtraGuiOptionsWidget::defineLayout(ThemeEval& layouts, const Common::String& layoutName, const Common::String& overlayedLayout) const {
layouts.addDialog(layoutName, overlayedLayout);
layouts.addLayout(GUI::ThemeLayout::kLayoutVertical).addPadding(8, 8, 8, 8);
for (uint i = 0; i < _options.size(); i++) {
Common::String id = Common::String::format("%d", i + 1);
layouts.addWidget("customOption" + id + "Checkbox", "Checkbox");
}
layouts.closeLayout().closeDialog();
}
} // End of namespace GUI