2009-05-12 22:17:42 +02:00

462 lines
16 KiB
C++
Executable File

void InputCaptureWindow::setup() {
window = new Window;
window->setObjectName("input-capture-window");
window->setWindowTitle("Input Capture");
layout = new QVBoxLayout;
layout->setMargin(Style::WindowMargin);
layout->setSpacing(0);
hlayout = new QHBoxLayout;
hlayout->setSpacing(Style::WidgetSpacing); {
title = new QLabel;
hlayout->addWidget(title, 0, Qt::AlignTop);
mouseAxes = new QPushButton(" Assign Mouse Axis ");
mouseAxes->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
hlayout->addWidget(mouseAxes, 0, Qt::AlignTop);
mouseButtons = new QPushButton(" Assign Mouse Button ");
mouseButtons->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
hlayout->addWidget(mouseButtons, 0, Qt::AlignTop);
}
layout->addLayout(hlayout);
imageSpacer = new QWidget;
imageSpacer->setFixedSize(Style::WidgetSpacing, Style::WidgetSpacing);
layout->addWidget(imageSpacer);
imageWidget = new ImageWidget;
layout->addWidget(imageWidget, 0, Qt::AlignHCenter);
spacer = new QWidget;
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
layout->addWidget(spacer);
window->setLayout(layout);
connect(mouseAxes, SIGNAL(released()), this, SLOT(assignMouseAxis()));
connect(mouseButtons, SIGNAL(released()), this, SLOT(assignMouseButton()));
winInputMouseCapture = new InputMouseCaptureWindow;
winInputMouseCapture->setup();
winInputCalibration = new InputCalibrationWindow;
winInputCalibration->setup();
}
void InputCaptureWindow::activate(InputObject *object) {
if(!activeGroup) window->hide();
utf8 info;
info << "<b>ID:</b> ";
if(object->parent) {
InputDevice *device = dynamic_cast<InputDevice*>(object->parent);
if(device) info << "Controller port " << (int)(device->port + 1) << " <b>::</b> ";
else info << "User interface <b>::</b> ";
info << object->parent->name << " <b>::</b> ";
}
info << object->name << "<br>";
activeObject = object;
if(activeObject->type == InputObject::Button) {
mouseAxes->hide();
mouseButtons->show();
info << "Press any key or button to assign to this ID.";
} else /*(activeObject->type == InputObject::Axis)*/ {
mouseAxes->show();
mouseButtons->hide();
info << "Move any axis to assign to this ID.";
}
if(dynamic_cast<Joypad*>(activeObject->parent)) {
imageSpacer->show();
imageWidget->setFixedSize(480, 210);
imageWidget->show();
} else {
imageSpacer->hide();
imageWidget->hide();
}
title->setText(info);
utility.showCentered(window);
}
void InputCaptureWindow::activate(InputGroup *group) {
activeGroup = group;
groupIndex = 0;
window->hide();
activate((*activeGroup)[groupIndex]);
}
void InputCaptureWindow::inputEvent(uint16_t code, bool forceAssign /* = false */) {
if(!activeObject || inputLock == true) return;
//input polling is global, need to block mouse actions that may be UI interactions.
//custom controls on window allow mouse assignment instead.
if(forceAssign == false) {
if(winInputMouseCapture->window->isActiveWindow()) {
winInputMouseCapture->inputEvent(code);
return;
}
if(!window->isActiveWindow()) return;
//get as much info as possible about this code
InputCode::type_t type = InputCode::type(code);
InputCode::axistype_t axisType = InputCode::axisType(code);
int joypadNumber = InputCode::joypadNumber(code);
int16_t state = inputManager.state(code);
unsigned distance = abs(state - inputManager.lastState(code));
if(type == InputCode::JoypadHat) {
//hats can be in any of nine clock-wise states (4x direct, 4x angled and centered.)
//only map when hat is moved to an explicit direction.
if(state != joypad<>::hat_up && state != joypad<>::hat_down
&& state != joypad<>::hat_left && state != joypad<>::hat_right) return;
}
if(type == InputCode::JoypadAxis) {
//require a degree of resistance to prevent accidental mapping
if(axisType == InputCode::Stick) {
//require axis to be pressed almost completely toward a specific direction
if(state > -28672 && state < +28672) return;
} else if(axisType == InputCode::Trigger) {
//require trigger to be at least 75% pressed down
if(state > -16384) return;
} else {
//invalid axis type: most likely the controller has yet to be calibrated
if(joypadNumber < 0) return; //should never occur
//some analog triggers report phantom motion even when user does not touch gamepad
if(distance < 64) return; //require some degree of force to trigger calibration
if(state > -8192 && state < +8192) return; //ignore center motion
if(inputManager.calibrated(joypadNumber) == false) {
winInputCalibration->activate(joypadNumber);
}
//block assignment until controller is fully calibrated
return;
}
}
if(activeObject->type == InputObject::Axis) {
if(type == InputCode::KeyboardButton
|| type == InputCode::MouseAxis
|| type == InputCode::MouseButton
|| type == InputCode::JoypadHat
|| type == InputCode::JoypadButton
) return;
//uni-directional trigger cannot map to bi-directional axis
if(type == InputCode::JoypadAxis && axisType == InputCode::Trigger) return;
}
if(activeObject->type == InputObject::Button) {
if(type == InputCode::MouseAxis
|| type == InputCode::MouseButton
) return;
//only capture on button press, not release
if(type != InputCode::JoypadAxis && state == false) return;
}
//if assigning a complete controller set, ensure requested key has not been assigned
//to a previous entry in the group already. this prevents slow motion of joypad axes
//from assigning the same value to multiple entries in rapid succession.
if(activeGroup && groupIndex > 0) {
for(unsigned i = 0; i < groupIndex; i++) {
if(code == (*activeGroup)[i]->code) {
//joypad hats and axes have multiple states, and are differentiated by modifier.
//allow mapping only if requested modifier is unique.
if(type == InputCode::JoypadHat) {
if(state == joypad<>::hat_up && (*activeGroup)[i]->modifier == InputObject::Up ) return;
if(state == joypad<>::hat_down && (*activeGroup)[i]->modifier == InputObject::Down ) return;
if(state == joypad<>::hat_left && (*activeGroup)[i]->modifier == InputObject::Left ) return;
if(state == joypad<>::hat_right && (*activeGroup)[i]->modifier == InputObject::Right) return;
} else if(type == InputCode::JoypadAxis) {
if(axisType == InputCode::Stick) {
if(state < 0 && (*activeGroup)[i]->modifier == InputObject::Lo) return;
if(state >= 0 && (*activeGroup)[i]->modifier == InputObject::Hi) return;
} else if(axisType == InputCode::Trigger) {
if((*activeGroup)[i]->modifier == InputObject::Trigger) return;
}
} else {
//this code has already been used, do not map it
return;
}
}
}
}
}
//bind code and update GUI input assignment list
activeObject->bind(code);
winInputSettings->updateList();
activeObject = 0;
//ignore multiple simultaneous state changes.
//this helps with joypads that only activate
//analog inputs after the first button press.
inputLock = true;
for(unsigned i = 0; i < 2; i++) inputManager.refresh();
inputLock = false;
if(!activeGroup) {
window->hide();
winInputMouseCapture->window->hide();
} else {
//try and map the next code in this input group
groupIndex++;
if(groupIndex < activeGroup->size()) {
activate((*activeGroup)[groupIndex]);
} else {
//all group codes mapped
window->hide();
winInputMouseCapture->window->hide();
activeGroup = 0;
}
}
}
void InputCaptureWindow::assignMouseAxis() {
//refresh input state so that mouse release event (from SIGNAL(released())
//is not sent immediately after window is visible.
inputManager.refresh();
winInputMouseCapture->activate(InputMouseCaptureWindow::AxisMode);
}
void InputCaptureWindow::assignMouseButton() {
inputManager.refresh();
winInputMouseCapture->activate(InputMouseCaptureWindow::ButtonMode);
}
InputCaptureWindow::InputCaptureWindow() {
activeObject = 0;
activeGroup = 0;
groupIndex = 0;
inputLock = false;
}
void InputCaptureWindow::Window::closeEvent(QCloseEvent*) {
//window closed by user, cancel key assignment
winInputCapture->activeObject = 0;
winInputCapture->activeGroup = 0;
winInputMouseCapture->window->hide();
winInputCalibration->dismiss();
}
void InputCaptureWindow::ImageWidget::paintEvent(QPaintEvent*) {
//currently, there is only an image available for the joypad.
//in the future, this routine should determine which type of
//image to draw via activeObject->parent's derived class type.
QPainter painter(this);
QPixmap pixmap(":/joypad.png");
painter.drawPixmap(0, 0, pixmap);
}
//=======================
//InputMouseCaptureWindow
//=======================
void InputMouseCaptureWindow::setup() {
window = new QWidget;
window->setObjectName("input-mouse-capture-window");
window->setWindowTitle("Mouse Input Capture");
layout = new QVBoxLayout;
layout->setMargin(Style::WindowMargin);
layout->setSpacing(0);
info = new QLabel;
layout->addWidget(info);
layout->addSpacing(Style::WidgetSpacing);
captureBox = new QLabel("[ capture box ]");
captureBox->setObjectName("mouse-capture-box");
captureBox->setAlignment(Qt::AlignCenter);
captureBox->setFixedHeight(120);
layout->addWidget(captureBox);
buttonLayout = new QHBoxLayout;
buttonLayout->setSpacing(Style::WidgetSpacing); {
xAxis = new QPushButton("X-axis");
buttonLayout->addWidget(xAxis);
yAxis = new QPushButton("Y-axis");
buttonLayout->addWidget(yAxis);
}
layout->addLayout(buttonLayout);
spacer = new QWidget;
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
layout->addWidget(spacer);
window->setLayout(layout);
connect(xAxis, SIGNAL(released()), this, SLOT(assignAxisX()));
connect(yAxis, SIGNAL(released()), this, SLOT(assignAxisY()));
}
void InputMouseCaptureWindow::activate(InputMouseCaptureWindow::Mode mode_) {
window->hide();
activeMode = mode_;
if(activeMode == AxisMode) {
captureBox->hide();
xAxis->show();
yAxis->show();
info->setText(utf8()
<< "To assign a mouse axis to this ID, please click the desired axis button below,<br>"
<< "using the mouse that you want the axis to be assigned to."
);
activeMouse = -1;
} else /*(activeMode == ButtonMode) */ {
captureBox->show();
xAxis->hide();
yAxis->hide();
info->setText(utf8()
<< "To assign a mouse button to this ID, please place the mouse you wish to map<br>"
<< "over the capture box below, and then click any mouse button to set assignment."
);
}
utility.showCentered(window);
}
//this is only called when isActiveWindow() == true
void InputMouseCaptureWindow::inputEvent(uint16_t code) {
InputCode::type_t type = InputCode::type(code);
int16_t state = inputManager.state(code);
if(activeMode == AxisMode) {
//when pressing down mouse button (eg to select "X-axis" or "Y-axis"),
//record mouse index for later assignment
if(type == InputCode::MouseButton && state == true) {
activeMouse = InputCode::mouseNumber(code);
return;
}
} else if(activeMode == ButtonMode) {
//if this is a mouse button that is being released ...
if(type == InputCode::MouseButton && state == false) {
//ensure button was clicked inside active capture box
QRect windowRect = window->geometry();
QRect widgetRect = captureBox->geometry();
unsigned wx = windowRect.left() + widgetRect.left();
unsigned wy = windowRect.top() + widgetRect.top();
unsigned px = QCursor::pos().x();
unsigned py = QCursor::pos().y();
if(px < wx || px >= wx + widgetRect.size().width() ) return;
if(py < wy || py >= wy + widgetRect.size().height()) return;
winInputCapture->inputEvent(code, true);
return;
}
}
}
void InputMouseCaptureWindow::assignAxisX() {
if(activeMouse >= 0) {
winInputCapture->inputEvent(mouse<>::index(activeMouse, mouse<>::x), true);
}
}
void InputMouseCaptureWindow::assignAxisY() {
if(activeMouse >= 0) {
winInputCapture->inputEvent(mouse<>::index(activeMouse, mouse<>::y), true);
}
}
//====================
//InputCalibrateWindow
//====================
//background:
//===========
//HID devices work by sending device state *changes* only. what this means is that when an application is started,
//it does not know the current state of said device. the keyboard and mouse are exceptions, as the OS globally
//tracks their states. but this does apply to joypads. once a button is pressed, or an axis is moved, the entire
//joypad state will be sent in a message, that APIs such as DirectInput and SDL will buffer.
//
//to complicate matters, recent joypads have added pressure-sensitive buttons (triggers), in addition to the
//existing analog sticks. but this functionality was not extended to the USB HID state or to various platform
//input APIs. instead, they are treated exactly like axes.
//
//however, an application needs to treat these two input types distinctly:
//a stick is a bi-directional input. the stick starts off centered, and can be moved left or right, or up or down.
//a trigger is a uni-directional input. it can only be pushed down.
//
//a stick's default, unpressed state is centered (0), whereas a trigger's default state is fully depressed (+32767.)
//but because the default state is not available until the user presses a button on a joypad, it is not possible to
//calibrate a joypad on startup. all axes will report a value of 0, even if buttons are depressed.
//
//thusly, this class is needed. it will spawn a window upon the first attempt to map a joypad axis after startup.
//by the point this window appears, an axis must have been moved, so the joypad state is now valid. but since it's
//not possible to tell which button was pressed or which axis was moved, it's possible the axis that we're trying to
//map was moved. so querying its state now might result in improper mapping. so instead, this window is shown, and
//the user is asked not to press any buttons or move any axes. after hitting okay to confirm the joypad is idle,
//the joypad can finally be calibrated properly.
//
//upon assignment, the calibration data is appended to the input assignment value (eg "joypad00.axis00::trigger"),
//so that calibration is not necessary every time the emulator is run -- only when modifying input mapping on an axis.
void InputCalibrationWindow::activate(unsigned joy) {
//do not override an already active calibration window
if(window->isVisible()) return;
activeJoypad = joy;
info->setText(utf8()
<< "Joypad #" << joy << " needs to be calibrated before it can be mapped. "
<< "Please ensure that<br>no buttons are pressed, "
<< "and all axes are centered before pressing okay."
);
utility.showCentered(window);
ok->setFocus();
}
void InputCalibrationWindow::setup() {
window = new Window;
window->setObjectName("input-calibrate-window");
window->setWindowTitle("Joypad Calibration");
layout = new QVBoxLayout;
layout->setMargin(Style::WindowMargin);
layout->setSpacing(0);
info = new QLabel;
layout->addWidget(info);
layout->addSpacing(Style::WidgetSpacing);
ok = new QPushButton("Ok");
layout->addWidget(ok);
spacer = new QWidget;
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
layout->addWidget(spacer);
window->setLayout(layout);
connect(ok, SIGNAL(released()), this, SLOT(dismiss()));
}
void InputCalibrationWindow::dismiss() {
window->hide();
if(activeJoypad != -1) {
inputManager.calibrate(activeJoypad);
activeJoypad = -1;
}
}
void InputCalibrationWindow::Window::closeEvent(QCloseEvent*) {
winInputCalibration->dismiss();
}
InputCalibrationWindow::InputCalibrationWindow() {
activeJoypad = -1;
}