stella/src/gui/EventMappingWidget.cxx

401 lines
12 KiB
C++

//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include <sstream>
#include "bspf.hxx"
#include "OSystem.hxx"
#include "GuiObject.hxx"
#include "FrameBuffer.hxx"
#include "EventHandler.hxx"
#include "Event.hxx"
#include "OSystem.hxx"
#include "EditTextWidget.hxx"
#include "StringListWidget.hxx"
#include "Widget.hxx"
#include "Font.hxx"
#include "ComboDialog.hxx"
#include "Variant.hxx"
#include "EventMappingWidget.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
EventMappingWidget::EventMappingWidget(GuiObject* boss, const GUI::Font& font,
int x, int y, int w, int h,
const StringList& actions, EventMode mode)
: Widget(boss, font, x, y, w, h),
CommandSender(boss),
myComboDialog(nullptr),
myEventMode(mode),
myActionSelected(-1),
myRemapStatus(false),
myLastStick(0),
myLastAxis(0),
myLastHat(0),
myLastValue(0),
myFirstTime(true)
{
const GUI::Font& ifont = boss->instance().frameBuffer().infoFont();
const int fontHeight = font.getFontHeight(),
lineHeight = font.getLineHeight(),
buttonWidth = font.getStringWidth("Defaults") + 10,
buttonHeight = font.getLineHeight() + 4;
const int HBORDER = 8;
const int VBORDER = 8;
int xpos = HBORDER, ypos = VBORDER;
myActionsList = new StringListWidget(boss, font, xpos, ypos,
_w - buttonWidth - HBORDER * 2 - 8, _h - 4*lineHeight - VBORDER);
myActionsList->setTarget(this);
myActionsList->setEditable(false);
myActionsList->setList(actions);
addFocusWidget(myActionsList);
// Add remap, erase, cancel and default buttons
xpos = _w - HBORDER - buttonWidth;
myMapButton = new ButtonWidget(boss, font, xpos, ypos,
buttonWidth, buttonHeight,
"Map" + ELLIPSIS, kStartMapCmd);
myMapButton->setTarget(this);
addFocusWidget(myMapButton);
ypos += lineHeight + 10;
myCancelMapButton = new ButtonWidget(boss, font, xpos, ypos,
buttonWidth, buttonHeight,
"Cancel", kStopMapCmd);
myCancelMapButton->setTarget(this);
myCancelMapButton->clearFlags(Widget::FLAG_ENABLED);
addFocusWidget(myCancelMapButton);
ypos += lineHeight + 20;
myEraseButton = new ButtonWidget(boss, font, xpos, ypos,
buttonWidth, buttonHeight,
"Erase", kEraseCmd);
myEraseButton->setTarget(this);
addFocusWidget(myEraseButton);
ypos += lineHeight + 10;
myResetButton = new ButtonWidget(boss, font, xpos, ypos,
buttonWidth, buttonHeight,
"Reset", kResetCmd);
myResetButton->setTarget(this);
addFocusWidget(myResetButton);
if(mode == kEmulationMode)
{
ypos += lineHeight + 20;
myComboButton = new ButtonWidget(boss, font, xpos, ypos,
buttonWidth, buttonHeight,
"Combo" + ELLIPSIS, kComboCmd);
myComboButton->setTarget(this);
addFocusWidget(myComboButton);
VariantList combolist = instance().eventHandler().getComboList(mode);
myComboDialog = new ComboDialog(boss, font, combolist);
}
else
myComboButton = nullptr;
// Show message for currently selected event
xpos = HBORDER; ypos = VBORDER + myActionsList->getHeight() + 8;
StaticTextWidget* t;
t = new StaticTextWidget(boss, font, xpos, ypos+2, font.getStringWidth("Action"),
fontHeight, "Action", TextAlign::Left);
myKeyMapping = new EditTextWidget(boss, font, xpos + t->getWidth() + 8, ypos,
_w - xpos - t->getWidth() - 8 - HBORDER, lineHeight, "");
myKeyMapping->setEditable(false, true);
myKeyMapping->clearFlags(Widget::FLAG_RETAIN_FOCUS);
// Add information for hardcoded keys
ypos += lineHeight + 8;
new StaticTextWidget(boss, ifont, xpos, ypos, "(*) Hardcoded action, cannot be erased");
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EventMappingWidget::loadConfig()
{
if(myFirstTime)
{
myActionsList->setSelected(0);
myFirstTime = false;
}
// Make sure remapping is turned off, just in case the user didn't properly
// exit last time
if(myRemapStatus)
stopRemapping();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EventMappingWidget::saveConfig()
{
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EventMappingWidget::setDefaults()
{
instance().eventHandler().setDefaultMapping(Event::NoType, myEventMode);
drawKeyMapping();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EventMappingWidget::startRemapping()
{
if(myActionSelected < 0 || myRemapStatus)
return;
// Set the flags for the next event that arrives
myRemapStatus = true;
// Reset all previous events for determining correct axis/hat values
myLastStick = myLastAxis = myLastHat = myLastValue = -1;
// Disable all other widgets while in remap mode, except enable 'Cancel'
enableButtons(false);
// And show a message indicating which key is being remapped
ostringstream buf;
buf << "Select action for '"
<< instance().eventHandler().actionAtIndex(myActionSelected, myEventMode)
<< "' event";
myKeyMapping->setTextColor(kTextColorEm);
myKeyMapping->setText(buf.str());
// Make sure that this widget receives all raw data, before any
// pre-processing occurs
myActionsList->setFlags(Widget::FLAG_WANTS_RAWDATA);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EventMappingWidget::eraseRemapping()
{
if(myActionSelected < 0)
return;
Event::Type event =
instance().eventHandler().eventAtIndex(myActionSelected, myEventMode);
instance().eventHandler().eraseMapping(event, myEventMode);
drawKeyMapping();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EventMappingWidget::resetRemapping()
{
if(myActionSelected < 0)
return;
Event::Type event =
instance().eventHandler().eventAtIndex(myActionSelected, myEventMode);
instance().eventHandler().setDefaultMapping(event, myEventMode);
drawKeyMapping();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EventMappingWidget::stopRemapping()
{
// Turn off remap mode
myRemapStatus = false;
// Reset all previous events for determining correct axis/hat values
myLastStick = myLastAxis = myLastHat = myLastValue = -1;
// And re-enable all the widgets
enableButtons(true);
// Make sure the list widget is in a known state
drawKeyMapping();
// Widget is now free to process events normally
myActionsList->clearFlags(Widget::FLAG_WANTS_RAWDATA);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EventMappingWidget::drawKeyMapping()
{
if(myActionSelected >= 0)
{
myKeyMapping->setTextColor(kTextColor);
myKeyMapping->setText(instance().eventHandler().keyAtIndex(myActionSelected, myEventMode));
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EventMappingWidget::enableButtons(bool state)
{
myActionsList->setEnabled(state);
myMapButton->setEnabled(state);
myCancelMapButton->setEnabled(!state);
myEraseButton->setEnabled(state);
myResetButton->setEnabled(state);
if(myComboButton)
{
Event::Type e =
instance().eventHandler().eventAtIndex(myActionSelected, myEventMode);
myComboButton->setEnabled(state && e >= Event::Combo1 && e <= Event::Combo16);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool EventMappingWidget::handleKeyUp(StellaKey key, StellaMod mod)
{
// Remap keys in remap mode
if (myRemapStatus && myActionSelected >= 0)
{
Event::Type event =
instance().eventHandler().eventAtIndex(myActionSelected, myEventMode);
if (instance().eventHandler().addKeyMapping(event, myEventMode, key, mod))
stopRemapping();
}
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EventMappingWidget::handleJoyDown(int stick, int button)
{
// Remap joystick buttons in remap mode
if(myRemapStatus && myActionSelected >= 0)
{
Event::Type event =
instance().eventHandler().eventAtIndex(myActionSelected, myEventMode);
if(instance().eventHandler().addJoyButtonMapping(event, myEventMode, stick, button))
stopRemapping();
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EventMappingWidget::handleJoyAxis(int stick, int axis, int value)
{
// Remap joystick axes in remap mode
// There are two phases to detection:
// First, detect an axis 'on' event
// Then, detect the same axis 'off' event
if(myRemapStatus && myActionSelected >= 0)
{
// Detect the first axis event that represents 'on'
if(myLastStick == -1 && myLastAxis == -1 && value != 0)
{
myLastStick = stick;
myLastAxis = axis;
myLastValue = value;
}
// Detect the first axis event that matches a previously set
// stick and axis, but turns the axis 'off'
else if(myLastStick == stick && axis == myLastAxis && value == 0)
{
value = myLastValue;
Event::Type event =
instance().eventHandler().eventAtIndex(myActionSelected, myEventMode);
if(instance().eventHandler().addJoyAxisMapping(event, myEventMode,
stick, axis, value))
stopRemapping();
}
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool EventMappingWidget::handleJoyHat(int stick, int hat, JoyHat value)
{
// Remap joystick hats in remap mode
// There are two phases to detection:
// First, detect a hat direction event
// Then, detect the same hat 'center' event
if(myRemapStatus && myActionSelected >= 0)
{
// Detect the first hat event that represents a valid direction
if(myLastStick == -1 && myLastHat == -1 && value != JoyHat::CENTER)
{
myLastStick = stick;
myLastHat = hat;
myLastValue = int(value);
return true;
}
// Detect the first hat event that matches a previously set
// stick and hat, but centers the hat
else if(myLastStick == stick && hat == myLastHat && value == JoyHat::CENTER)
{
value = JoyHat(myLastValue);
Event::Type event =
instance().eventHandler().eventAtIndex(myActionSelected, myEventMode);
if(instance().eventHandler().addJoyHatMapping(event, myEventMode,
stick, hat, value))
{
stopRemapping();
return true;
}
}
}
return false;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EventMappingWidget::handleCommand(CommandSender* sender, int cmd,
int data, int id)
{
switch(cmd)
{
case ListWidget::kSelectionChangedCmd:
if(myActionsList->getSelected() >= 0)
{
myActionSelected = myActionsList->getSelected();
drawKeyMapping();
enableButtons(true);
}
break;
/*
case ListWidget::kDoubleClickedCmd:
if(myActionsList->getSelected() >= 0)
{
myActionSelected = myActionsList->getSelected();
startRemapping();
}
break;
*/
case kStartMapCmd:
startRemapping();
break;
case kStopMapCmd:
stopRemapping();
break;
case kEraseCmd:
eraseRemapping();
break;
case kResetCmd:
resetRemapping();
break;
case kComboCmd:
if(myComboDialog)
myComboDialog->show(
instance().eventHandler().eventAtIndex(myActionSelected, myEventMode),
instance().eventHandler().actionAtIndex(myActionSelected, myEventMode));
break;
}
}