stk-code_catmod/src/states_screens/options_screen_device.cpp

644 lines
23 KiB
C++

// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2009-2015 Marianne Gagnon
//
// 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, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "states_screens/options_screen_device.hpp"
#include "config/user_config.hpp"
#include "guiengine/CGUISpriteBank.hpp"
#include "guiengine/scalable_font.hpp"
#include "guiengine/screen.hpp"
#include "guiengine/widget.hpp"
#include "guiengine/widgets/button_widget.hpp"
#include "guiengine/widgets/label_widget.hpp"
#include "guiengine/widgets/list_widget.hpp"
#include "guiengine/widgets/ribbon_widget.hpp"
#include "input/input_manager.hpp"
#include "input/device_manager.hpp"
#include "input/gamepad_device.hpp"
#include "io/file_manager.hpp"
#include "states_screens/dialogs/press_a_key_dialog.hpp"
#include "states_screens/options_screen_audio.hpp"
#include "states_screens/options_screen_input.hpp"
#include "states_screens/options_screen_video.hpp"
#include "states_screens/options_screen_ui.hpp"
#include "states_screens/state_manager.hpp"
#include "states_screens/user_screen.hpp"
#include "utils/translation.hpp"
#include <iostream>
#include <sstream>
#include <set>
using namespace GUIEngine;
DEFINE_SCREEN_SINGLETON( OptionsScreenDevice );
// ----------------------------------------------------------------------------
OptionsScreenDevice::OptionsScreenDevice() : Screen("options_device.stkgui")
{
m_config = NULL;
} // OptionsScreenDevice
// ----------------------------------------------------------------------------
void OptionsScreenDevice::loadedFromFile()
{
} // loadedFromFile
// ----------------------------------------------------------------------------
void OptionsScreenDevice::beforeAddingWidget()
{
GUIEngine::ListWidget* w_list =
getWidget<GUIEngine::ListWidget>("actions");
assert(w_list != NULL);
w_list->clearColumns();
w_list->addColumn(_("Action"), 1);
w_list->addColumn(_("Key binding"), 1);
w_list->setSortable(false);
}
// ----------------------------------------------------------------------------
void OptionsScreenDevice::init()
{
Screen::init();
RibbonWidget* tabBar = getWidget<RibbonWidget>("options_choice");
assert(tabBar != NULL);
tabBar->select( "tab_controls", PLAYER_ID_GAME_MASTER );
tabBar->getRibbonChildren()[0].setTooltip( _("Graphics") );
tabBar->getRibbonChildren()[1].setTooltip( _("Audio") );
tabBar->getRibbonChildren()[2].setTooltip( _("User Interface") );
tabBar->getRibbonChildren()[3].setTooltip( _("Players") );
ButtonWidget* delete_button = getWidget<ButtonWidget>("delete");
if (!m_config->isKeyboard())
{
core::stringw label = (m_config->isEnabled()
? //I18N: button to disable a gamepad configuration
_("Disable Device")
: //I18N: button to enable a gamepad configuration
_("Enable Device"));
// Make sure button is wide enough as the text is being changed away
// from the original value
core::dimension2d<u32> size =
GUIEngine::getFont()->getDimension(label.c_str());
const int needed = size.Width + delete_button->getWidthNeededAroundLabel();
if (delete_button->m_w < needed) delete_button->m_w = needed;
delete_button->setLabel(label);
}
else
{
delete_button->setLabel(_("Delete Configuration"));
// Don't allow deleting the last config
delete_button->setActive(
input_manager->getDeviceManager()->getKeyboardAmount() > 1);
}
// Make the two buttons the same length, not strictly needed but will
// look nicer...
ButtonWidget* backBtn = getWidget<ButtonWidget>("back_to_device_list");
if (backBtn->m_w < delete_button->m_w) backBtn->m_w = delete_button->m_w;
else delete_button->m_w = backBtn->m_w;
backBtn->moveIrrlichtElement();
delete_button->moveIrrlichtElement();
LabelWidget* label = getWidget<LabelWidget>("title");
label->setText( m_config->getName().c_str(), false );
GUIEngine::ListWidget* actions =
getWidget<GUIEngine::ListWidget>("actions");
assert( actions != NULL );
// ---- create list skeleton (right number of items, right internal names)
// their actualy contents will be adapted as needed after
//I18N: Key binding section
addListItemSubheader(actions, "game_keys_section", _("Game Keys"));
addListItem(actions, PA_STEER_LEFT);
addListItem(actions, PA_STEER_RIGHT);
addListItem(actions, PA_ACCEL);
addListItem(actions, PA_BRAKE);
addListItem(actions, PA_FIRE);
addListItem(actions, PA_NITRO);
addListItem(actions, PA_DRIFT);
addListItem(actions, PA_LOOK_BACK);
addListItem(actions, PA_RESCUE);
addListItem(actions, PA_PAUSE_RACE);
//I18N: Key binding section
addListItemSubheader(actions, "menu_keys_section", _("Menu Keys"));
addListItem(actions, PA_MENU_UP);
addListItem(actions, PA_MENU_DOWN);
addListItem(actions, PA_MENU_LEFT);
addListItem(actions, PA_MENU_RIGHT);
addListItem(actions, PA_MENU_SELECT);
addListItem(actions, PA_MENU_CANCEL);
updateInputButtons();
// Disable deletion keyboard configurations
bool in_game = StateManager::get()->getGameState() == GUIEngine::INGAME_MENU;
getWidget<ButtonWidget>("delete")->setActive(!in_game);
} // init
// -----------------------------------------------------------------------------
void OptionsScreenDevice::addListItemSubheader(GUIEngine::ListWidget* actions,
const char* id,
const core::stringw& text)
{
std::vector<GUIEngine::ListWidget::ListCell> row;
row.push_back(GUIEngine::ListWidget::ListCell(text, -1, 1, false));
row.push_back(GUIEngine::ListWidget::ListCell(L"", -1, 1, false));
actions->addItem(id, row);
} // addListItemSubheader
// -----------------------------------------------------------------------------
void OptionsScreenDevice::addListItem(GUIEngine::ListWidget* actions,
PlayerAction pa)
{
std::vector<GUIEngine::ListWidget::ListCell> row;
core::stringw s(KartActionStrings[pa].c_str());
row.push_back(GUIEngine::ListWidget::ListCell(s, -1, 1, false));
row.push_back(GUIEngine::ListWidget::ListCell(L"", -1, 1, false));
actions->addItem(KartActionStrings[pa], row);
} // addListItem
// -----------------------------------------------------------------------------
void OptionsScreenDevice::renameRow(GUIEngine::ListWidget* actions,
int idRow,
const irr::core::stringw &translatedName,
PlayerAction action) const
{
actions->renameCell(idRow, 0, core::stringw(" ") + translatedName);
actions->renameCell(idRow, 1, m_config->getBindingAsString(action));
} // makeLabel
// -----------------------------------------------------------------------------
void OptionsScreenDevice::updateInputButtons()
{
assert(m_config != NULL);
//TODO: detect duplicates
GUIEngine::ListWidget* actions =
getWidget<GUIEngine::ListWidget>("actions");
assert( actions != NULL );
int i = 0;
i++; // section header
//I18N: Key binding name
renameRow(actions, i++, _("Steer Left"), PA_STEER_LEFT);
//I18N: Key binding name
renameRow(actions, i++, _("Steer Right"), PA_STEER_RIGHT);
//I18N: Key binding name
renameRow(actions, i++, _("Accelerate"), PA_ACCEL);
//I18N: Key binding name
renameRow(actions, i++, _("Brake"), PA_BRAKE);
//I18N: Key binding name
renameRow(actions, i++, _("Fire"), PA_FIRE);
//I18N: Key binding name
renameRow(actions, i++, _("Nitro"), PA_NITRO);
//I18N: Key binding name
renameRow(actions, i++, _("Skidding"), PA_DRIFT);
//I18N: Key binding name
renameRow(actions, i++, _("Look Back"), PA_LOOK_BACK);
//I18N: Key binding name
renameRow(actions, i++, _("Rescue"), PA_RESCUE);
//I18N: Key binding name
renameRow(actions, i++, _("Pause Game"), PA_PAUSE_RACE);
i++; // section header
//I18N: Key binding name
renameRow(actions, i++, _("Up"), PA_MENU_UP);
//I18N: Key binding name
renameRow(actions, i++, _("Down"), PA_MENU_DOWN);
//I18N: Key binding name
renameRow(actions, i++, _("Left"), PA_MENU_LEFT);
//I18N: Key binding name
renameRow(actions, i++, _("Right"), PA_MENU_RIGHT);
//I18N: Key binding name
renameRow(actions, i++, _("Select"), PA_MENU_SELECT);
//I18N: Key binding name
renameRow(actions, i++, _("Cancel/Back"), PA_MENU_CANCEL);
bool conflicts_between = false;
bool conflicts_inside = false;
// ---- make sure there are no binding conflicts
// (same key used for two actions)
std::set<irr::core::stringw> currently_used_keys;
for (PlayerAction action = PA_FIRST_GAME_ACTION;
action <= PA_LAST_GAME_ACTION;
action=PlayerAction(action+1))
{
const irr::core::stringw item = m_config->getMappingIdString(action);
if (currently_used_keys.find(item) == currently_used_keys.end())
{
currently_used_keys.insert( item );
if (m_config->isKeyboard()
&& conflictsBetweenKbdConfig(action, PA_FIRST_GAME_ACTION,
PA_LAST_GAME_ACTION))
{
conflicts_between = true;
actions->markItemBlue (KartActionStrings[action]);
}
}
else
{
// binding conflict!
actions->markItemRed( KartActionStrings[action] );
// also mark others
for (PlayerAction others = PA_FIRST_GAME_ACTION;
others < action; others=PlayerAction(others+1))
{
const irr::core::stringw others_item =
m_config->getMappingIdString(others);
if (others_item == item)
{
conflicts_inside = true;
actions->markItemRed( KartActionStrings[others] );
}
}
//actions->renameItem( KartActionStrings[action],
// _("Binding Conflict!") );
}
}
// menu keys and game keys can overlap, no problem, so forget game keys
// before checking menu keys
currently_used_keys.clear();
for (PlayerAction action = PA_FIRST_MENU_ACTION;
action <= PA_LAST_MENU_ACTION;
action=PlayerAction(action+1))
{
const irr::core::stringw item = m_config->getBindingAsString(action);
if (currently_used_keys.find(item) == currently_used_keys.end())
{
currently_used_keys.insert( item );
if (m_config->isKeyboard()
&& conflictsBetweenKbdConfig(action, PA_FIRST_MENU_ACTION,
PA_LAST_MENU_ACTION))
{
conflicts_between = true;
actions->markItemBlue (KartActionStrings[action]);
}
}
else // existing key
{
// binding conflict!
actions->markItemRed( KartActionStrings[action] );
// also mark others
for (PlayerAction others = PA_FIRST_MENU_ACTION;
others < action; others=PlayerAction(others+1))
{
const irr::core::stringw others_item =
m_config->getBindingAsString(others);
if (others_item == item)
{
conflicts_inside = true;
actions->markItemRed( KartActionStrings[others] );
}
} // for others < action
} // if existing key
} // for action <= PA_LAST_MENU_ACTION;
GUIEngine::Widget* conflict_label =
getWidget<GUIEngine::LabelWidget>("conflict");
std::wostringstream oss;
if (conflicts_between)
{
oss << _("* A blue item means a conflict with another configuration");
if (conflicts_inside)
oss << "\n";
}
if (conflicts_inside)
oss << _("* A red item means a conflict in the current configuration");
conflict_label->setText(oss.str().c_str());
} // updateInputButtons
// -----------------------------------------------------------------------------
static PlayerAction binding_to_set;
static std::string binding_to_set_button;
void OptionsScreenDevice::gotSensedInput(const Input& sensed_input)
{
const bool keyboard = (m_config->isKeyboard() &&
sensed_input.m_type == Input::IT_KEYBOARD);
const bool gamepad = (sensed_input.m_type == Input::IT_STICKMOTION ||
sensed_input.m_type == Input::IT_STICKBUTTON) &&
m_config->isGamePad();
if (keyboard)
{
if (UserConfigParams::logMisc())
{
Log::info("OptionsScreenDevice", "Binding %s: setting to keyboard key %d",
KartActionStrings[binding_to_set].c_str(), sensed_input.m_button_id);
}
KeyboardConfig* keyboard = (KeyboardConfig*)m_config;
keyboard->setBinding(binding_to_set, Input::IT_KEYBOARD,
sensed_input.m_button_id, Input::AD_NEUTRAL,
Input::AR_HALF,
sensed_input.m_character);
// refresh display
updateInputButtons();
}
else if (gamepad)
{
if (UserConfigParams::logMisc())
{
Log::info("OptionsScreenDevice", "Binding %s: setting to gamepad #%d",
KartActionStrings[binding_to_set].c_str(), sensed_input.m_device_id);
if (sensed_input.m_type == Input::IT_STICKMOTION)
{
Log::info("OptionsScreenDevice", "Axis %d; direction %s", sensed_input.m_button_id,
sensed_input.m_axis_direction == Input::AD_NEGATIVE ? "-" : "+");
}
else if (sensed_input.m_type == Input::IT_STICKBUTTON)
{
Log::info("OptionsScreenDevice", "Button %d", sensed_input.m_button_id);
}
else
{
Log::info("OptionsScreenDevice", "Sensed unknown gamepad event type??");
}
}
GamePadDevice *gpad = input_manager->getDeviceManager()
->getGamePadFromIrrID(sensed_input.m_device_id);
std::string gamepad_name = gpad ? gpad->getName() : "UNKNOWN DEVICE";
if (m_config->getName() == gamepad_name)
{
GamepadConfig* config = (GamepadConfig*)m_config;
config->setBinding(binding_to_set, sensed_input.m_type,
sensed_input.m_button_id,
(Input::AxisDirection)sensed_input.m_axis_direction,
(Input::AxisRange)sensed_input.m_axis_range);
// refresh display
updateInputButtons();
}
else
{
return;
}
}
else if (sensed_input.m_type == Input::IT_NONE)
{
if (UserConfigParams::logMisc())
{
Log::info("OptionsScreenDevice", "Binding %s: setting to keyboard key NONE",
KartActionStrings[binding_to_set].c_str());
}
KeyboardConfig* keyboard = (KeyboardConfig*)m_config;
keyboard->setBinding(binding_to_set, Input::IT_NONE,
sensed_input.m_button_id, Input::AD_NEUTRAL,
Input::AR_HALF,
sensed_input.m_character);
// refresh display
updateInputButtons();
}
else
{
return;
}
ModalDialog::dismiss();
input_manager->setMode(InputManager::MENU);
if (keyboard && (sensed_input.m_button_id == irr::IRR_KEY_SHIFT ||
sensed_input.m_button_id == irr::IRR_KEY_LSHIFT ||
sensed_input.m_button_id == irr::IRR_KEY_RSHIFT))
{
new MessageDialog(_("Warning: The 'Shift' is not a recommended key. When "
"'Shift' is pressed down, all keys that contain a "
"character that is different in upper-case will "
"stop working."));
}
// re-select the previous button (TODO!)
//ButtonWidget* btn =
// getWidget<ButtonWidget>(binding_to_set_button.c_str());
//if(btn != NULL) btn->setFocusForPlayer(PLAYER_ID_GAME_MASTER);
// save new binding to file
input_manager->getDeviceManager()->save();
} // gotSensedInput
// ----------------------------------------------------------------------------
void OptionsScreenDevice::eventCallback(Widget* widget,
const std::string& name,
const int playerID)
{
//const std::string& screen_name = getName();
StateManager *sm = StateManager::get();
if (name == "options_choice")
{
std::string selection = ((RibbonWidget*)widget)->getSelectionIDString(PLAYER_ID_GAME_MASTER);
Screen *screen = NULL;
if (selection == "tab_audio")
screen = OptionsScreenAudio::getInstance();
//else if (selection == "tab_video")
// screen = OptionsScreenVideo::getInstance();
else if (selection == "tab_players")
screen = TabbedUserScreen::getInstance();
//else if (selection == "tab_controls")
// screen = OptionsScreenInput::getInstance();
else if (selection == "tab_ui")
screen = OptionsScreenUI::getInstance();
if(screen)
StateManager::get()->replaceTopMostScreen(screen);
}
else if (name == "back_to_device_list")
{
sm->replaceTopMostScreen(OptionsScreenInput::getInstance());
}
else if (name == "back")
{
sm->replaceTopMostScreen(OptionsScreenInput::getInstance());
}
else if (name == "actions")
{
GUIEngine::ListWidget* actions =
getWidget<GUIEngine::ListWidget>("actions");
assert( actions != NULL );
// a player action in the list was clicked. find which one
const std::string& clicked = actions->getSelectionInternalName();
for (int n=PA_BEFORE_FIRST+1; n<PA_COUNT; n++)
{
if (KartActionStrings[n] == clicked)
{
// we found which one. show the "press a key" dialog.
if (UserConfigParams::logMisc())
{
Log::info("OptionsScreenDevice", "Entering sensing mode for %s",
m_config->getName().c_str());
}
binding_to_set = (PlayerAction)n;
new PressAKeyDialog(0.4f, 0.4f);
if (m_config->isKeyboard())
{
input_manager->setMode(InputManager::INPUT_SENSE_KEYBOARD);
}
else if (m_config->isGamePad())
{
input_manager->setMode(InputManager::INPUT_SENSE_GAMEPAD);
}
else
{
Log::error("OptionsScreenDevice", "Unknown selection device in options: %s",
m_config->getName().c_str());
}
break;
}
}
}
else if (name == "delete")
{
if (m_config->isKeyboard())
{
// keyboard configs may be deleted
//I18N: shown before deleting an input configuration
new MessageDialog( _("Are you sure you want to permanently delete "
"this configuration?"),
MessageDialog::MESSAGE_DIALOG_CONFIRM, this, false );
}
else
{
// gamepad configs may be disabled
if (m_config->isEnabled()) m_config->setEnabled(false);
else m_config->setEnabled(true);
// update widget label
ButtonWidget* delete_button = getWidget<ButtonWidget>("delete");
delete_button->setLabel(m_config->isEnabled() ? _("Disable Device")
: _("Enable Device") );
input_manager->getDeviceManager()->save();
}
}
} // eventCallback
// -----------------------------------------------------------------------------
void OptionsScreenDevice::unloaded()
{
} // unloaded
// -----------------------------------------------------------------------------
bool OptionsScreenDevice::onEscapePressed()
{
StateManager::get()
->replaceTopMostScreen(OptionsScreenInput::getInstance());
return false; // don't use standard escape key handler,
// we handled it differently
} // onEscapePressed
// -----------------------------------------------------------------------------
void OptionsScreenDevice::onConfirm()
{
const bool success =
input_manager->getDeviceManager()->deleteConfig(m_config);
assert(success);
if (!success)
Log::error("OptionsScreenDevice", "Failed to delete config!");
m_config = NULL;
input_manager->getDeviceManager()->save();
ModalDialog::dismiss();
StateManager::get()
->replaceTopMostScreen(OptionsScreenInput::getInstance());
} // onConfirm
// -----------------------------------------------------------------------------
bool OptionsScreenDevice::conflictsBetweenKbdConfig(PlayerAction action,
PlayerAction from,
PlayerAction to)
{
int id = m_config->getBinding(action).getId();
for (int i=0; i < input_manager->getDeviceManager()->getKeyboardAmount(); i++)
{
KeyboardConfig* other_kbd_config =
input_manager->getDeviceManager()->getKeyboardConfig(i);
if (m_config != other_kbd_config &&
other_kbd_config->hasBindingFor(id, from, to)
&& (other_kbd_config->getBinding(action).getId() != id ||
action == PA_FIRE) )
{
return true;
}
}
return false;
} // conflictsBetweenKbdConfig