457 lines
12 KiB
C++
457 lines
12 KiB
C++
#include "axis.h"
|
|
#include "event.h"
|
|
#include "time.h"
|
|
|
|
#define sqr(a) ((a)*(a))
|
|
#define cub(a) ((a)*(a)*(a))
|
|
#define clamp(a, a_low, a_high) \
|
|
((a) < (a_low) ? (a_low) : (a) > (a_high) ? (a_high) : (a))
|
|
|
|
|
|
Axis::Axis( int i, QObject *parent ) : QObject(parent) {
|
|
index = i;
|
|
isOn = false;
|
|
isDown = false;
|
|
state = 0;
|
|
interpretation = ZeroOne;
|
|
gradient = false;
|
|
absolute = false;
|
|
toDefault();
|
|
tick = 0;
|
|
}
|
|
|
|
|
|
Axis::~Axis() {
|
|
release();
|
|
}
|
|
|
|
bool Axis::read( QTextStream &stream ) {
|
|
// At this point, toDefault has just been called.
|
|
|
|
//read in a line from the stream, and split it up into individual words
|
|
QString input = stream.readLine().toLower();
|
|
QRegExp regex("[\\s,]+");
|
|
QStringList words = input.split(regex);
|
|
|
|
//used to assure QString->int conversions worked
|
|
bool ok;
|
|
//int to store values derived from strings
|
|
int val;
|
|
float fval;
|
|
//step through each word, check if it's a token we recognize
|
|
for ( QStringList::Iterator it = words.begin(); it != words.end(); ++it ) {
|
|
if (*it == "maxspeed") {
|
|
++it; //move to the next word, which should be the maximum speed.
|
|
//if no value was given, there's an error in the file, stop reading.
|
|
if (it == words.end()) return false;
|
|
//try to convert the value.
|
|
val = (*it).toInt(&ok);
|
|
//if that worked and the maximum speed is in range, set it.
|
|
if (ok && val >= 0 && val <= MAXMOUSESPEED) maxSpeed = val;
|
|
//otherwise, faulty input, give up.
|
|
else return false;
|
|
}
|
|
//pretty much the same process for getting the dead zone
|
|
else if (*it == "dzone") {
|
|
++it;
|
|
if (it == words.end()) return false;
|
|
val = (*it).toInt(&ok);
|
|
if (ok && val >= 0 && val <= JOYMAX) dZone = val;
|
|
else return false;
|
|
}
|
|
//and again for the extreme zone,
|
|
else if (*it == "xzone") {
|
|
++it;
|
|
if (it == words.end()) return false;
|
|
val = (*it).toInt(&ok);
|
|
if (ok && val >= 0 && val <= JOYMAX) xZone = val;
|
|
else return false;
|
|
}
|
|
else if (*it == "tcurve") {
|
|
++it;
|
|
if (it == words.end()) return false;
|
|
val = (*it).toInt(&ok);
|
|
if (ok && val >= 0 && val <= PowerFunction) transferCurve = val;
|
|
else return false;
|
|
}
|
|
else if (*it == "sens") {
|
|
++it;
|
|
if (it == words.end()) return false;
|
|
fval = (*it).toFloat(&ok);
|
|
if (ok && fval >= SENSITIVITY_MIN && fval <= SENSITIVITY_MAX)
|
|
sensitivity = fval;
|
|
else return false;
|
|
}
|
|
//and for the positive keycode,
|
|
else if (*it == "+key") {
|
|
++it;
|
|
if (it == words.end()) return false;
|
|
val = (*it).toInt(&ok);
|
|
if (ok && val >= 0 && val <= MAXKEY) pkeycode = val;
|
|
else return false;
|
|
}
|
|
//and finally for the negative keycode.
|
|
else if (*it == "-key") {
|
|
++it;
|
|
if (it == words.end()) return false;
|
|
val = (*it).toInt(&ok);
|
|
if (ok && val >= 0 && val <= MAXKEY) nkeycode = val;
|
|
else return false;
|
|
}
|
|
else if (*it == "+mouse") {
|
|
++it;
|
|
if (it == words.end()) return false;
|
|
val = (*it).toInt(&ok);
|
|
if (ok && val >= 0 && val <= MAXKEY) {
|
|
puseMouse = true;
|
|
pkeycode = val;
|
|
}
|
|
else return false;
|
|
}
|
|
else if (*it == "-mouse") {
|
|
++it;
|
|
if (it == words.end()) return false;
|
|
val = (*it).toInt(&ok);
|
|
if (ok && val >= 0 && val <= MAXKEY) {
|
|
nuseMouse = true;
|
|
nkeycode = val;
|
|
}
|
|
else return false;
|
|
}
|
|
//the rest of the options are keywords without integers
|
|
else if (*it == "zeroone") {
|
|
interpretation = ZeroOne;
|
|
gradient = false;
|
|
absolute = false;
|
|
}
|
|
else if (*it == "absolute") {
|
|
interpretation = Absolute2; // to avoid name collision with a #define Absolute
|
|
gradient = true;
|
|
absolute = true;
|
|
}
|
|
else if (*it == "gradient") {
|
|
interpretation = Gradient;
|
|
gradient = true;
|
|
absolute = false;
|
|
}
|
|
else if (*it == "throttle+") {
|
|
throttle = 1;
|
|
}
|
|
else if (*it == "throttle-") {
|
|
throttle = -1;
|
|
}
|
|
else if (*it == "mouse+v") {
|
|
mode = MousePosVert;
|
|
}
|
|
else if (*it == "mouse-v") {
|
|
mode = MouseNegVert;
|
|
}
|
|
else if (*it == "mouse+h") {
|
|
mode = MousePosHor;
|
|
}
|
|
else if (*it == "mouse-h") {
|
|
mode = MouseNegHor;
|
|
}
|
|
//we ignore unrecognized words to be friendly and allow for additions to
|
|
//the format in later versions. Note, this means that typos will not get
|
|
//the desired effect OR produce an error message.
|
|
}
|
|
|
|
//assume that xZone, dZone, or maxSpeed has changed, for simplicity.
|
|
//do a few floating point calculations.
|
|
adjustGradient();
|
|
|
|
//if we parsed through all of the words, yay! All done.
|
|
return true;
|
|
}
|
|
|
|
void Axis::timerCalled() {
|
|
timerTick(++tick);
|
|
}
|
|
|
|
void Axis::write( QTextStream &stream ) {
|
|
stream << "\tAxis " << (index+1) << ": ";
|
|
stream << ((interpretation == ZeroOne)?"ZeroOne":
|
|
(interpretation == Gradient)?"Gradient":"Absolute") << ", ";
|
|
if (throttle > 0) stream << "throttle+, ";
|
|
else if (throttle < 0) stream << "throttle-, ";
|
|
if (dZone != DZONE) stream << "dZone " << dZone << ", ";
|
|
if (xZone != XZONE) stream << "xZone " << xZone << ", ";
|
|
if (mode == Keyboard) {
|
|
stream
|
|
<< (puseMouse ? "+mouse " : "+key ") << pkeycode << ", "
|
|
<< (nuseMouse ? "-mouse " : "-key ") << nkeycode << "\n";
|
|
}
|
|
else {
|
|
if (gradient) stream << "maxSpeed " << maxSpeed << ", ";
|
|
if (transferCurve != Quadratic)
|
|
stream << "tCurve " << transferCurve << ", ";
|
|
if (sensitivity != 1.0F)
|
|
stream << "sens " << sensitivity << ", ";
|
|
stream << "mouse";
|
|
if (mode == MousePosVert)
|
|
stream << "+v\n";
|
|
else if (mode == MouseNegVert)
|
|
stream << "-v\n";
|
|
else if (mode == MousePosHor)
|
|
stream << "+h\n";
|
|
else if (mode == MouseNegHor)
|
|
stream << "-h\n";
|
|
}
|
|
|
|
}
|
|
|
|
void Axis::release() {
|
|
//if we're pressing a key, let it go.
|
|
if (isDown) {
|
|
move(false);
|
|
isDown = false;
|
|
}
|
|
}
|
|
|
|
void Axis::jsevent( int value ) {
|
|
//adjust real value to throttle value
|
|
if (throttle == 0)
|
|
state = value;
|
|
else if (throttle == -1)
|
|
state = (value + JOYMIN) / 2;
|
|
else
|
|
state = (value + JOYMAX) / 2;
|
|
//set isOn, deal with state changing.
|
|
//if was on but now should be off:
|
|
if (isOn && abs(state) <= dZone) {
|
|
isOn = false;
|
|
if (gradient) {
|
|
duration = 0;
|
|
release();
|
|
timer.stop();
|
|
disconnect(&timer, SIGNAL(timeout()), 0, 0);
|
|
tick = 0;
|
|
}
|
|
}
|
|
//if was off but now should be on:
|
|
else if (!isOn && abs(state) >= dZone) {
|
|
isOn = true;
|
|
if (gradient) {
|
|
duration = (abs(state) * FREQ) / JOYMAX;
|
|
connect(&timer, SIGNAL(timeout()), this, SLOT(timerCalled()));
|
|
timer.start(MSEC);
|
|
}
|
|
}
|
|
//otherwise, state doesn't change! Don't touch it.
|
|
else return;
|
|
|
|
//gradient will trigger movement on its own via timer().
|
|
//non-gradient needs to be told to move.
|
|
if (!gradient) {
|
|
move(isOn);
|
|
}
|
|
}
|
|
|
|
void Axis::toDefault() {
|
|
release();
|
|
interpretation = ZeroOne;
|
|
gradient = false;
|
|
absolute = false;
|
|
throttle = 0;
|
|
maxSpeed = 100;
|
|
transferCurve = Quadratic;
|
|
sensitivity = 1.0F;
|
|
dZone = DZONE;
|
|
tick = 0;
|
|
xZone = XZONE;
|
|
mode = Keyboard;
|
|
pkeycode = 0;
|
|
nkeycode = 0;
|
|
puseMouse = false;
|
|
nuseMouse = false;
|
|
downkey = 0;
|
|
state = 0;
|
|
adjustGradient();
|
|
}
|
|
|
|
bool Axis::isDefault() {
|
|
return (interpretation == ZeroOne) &&
|
|
(gradient == false) &&
|
|
(absolute == false) &&
|
|
(throttle == 0) &&
|
|
(maxSpeed == 100) &&
|
|
(dZone == DZONE) &&
|
|
(xZone == XZONE) &&
|
|
(mode == Keyboard) &&
|
|
(pkeycode == 0) &&
|
|
(nkeycode == 0) &&
|
|
(puseMouse == false) &&
|
|
(nuseMouse == false) ;
|
|
}
|
|
|
|
QString Axis::getName() {
|
|
return tr("Axis %1").arg(index+1);
|
|
}
|
|
|
|
bool Axis::inDeadZone( int val ) {
|
|
int value;
|
|
if (throttle == 0)
|
|
value = val;
|
|
else if (throttle == -1)
|
|
value = (val + JOYMIN) / 2;
|
|
else
|
|
value = (val + JOYMAX) / 2;
|
|
return (abs(value) < dZone);
|
|
}
|
|
|
|
QString Axis::status() {
|
|
QString label;
|
|
if (mode == Keyboard) {
|
|
if (throttle == 0) {
|
|
if (puseMouse != nuseMouse) {
|
|
label = tr("KEYBOARD/MOUSE");
|
|
}
|
|
else if (puseMouse) {
|
|
label = tr("MOUSE");
|
|
}
|
|
else {
|
|
label = tr("KEYBOARD");
|
|
}
|
|
}
|
|
else {
|
|
label = tr("THROTTLE");
|
|
}
|
|
}
|
|
else {
|
|
label = tr("MOUSE");
|
|
}
|
|
return QString("%1 : [%2]").arg(getName(), label);
|
|
}
|
|
|
|
void Axis::setKey(bool positive, int value) {
|
|
setKey(false, positive, value);
|
|
}
|
|
|
|
void Axis::setKey(bool useMouse, bool positive, int value) {
|
|
if (positive) {
|
|
pkeycode = value;
|
|
puseMouse = useMouse;
|
|
}
|
|
else {
|
|
nkeycode = value;
|
|
nuseMouse = useMouse;
|
|
}
|
|
}
|
|
|
|
void Axis::timerTick( int tick ) {
|
|
if (isOn) {
|
|
if (mode == Keyboard) {
|
|
if (tick % FREQ == 0)
|
|
{
|
|
if (duration == FREQ)
|
|
{
|
|
if (!isDown) move(true);
|
|
duration = (abs(state) * FREQ) / JOYMAX;
|
|
return;
|
|
}
|
|
move(true);
|
|
}
|
|
if (tick % FREQ == duration) {
|
|
move(false);
|
|
duration = (abs(state) * FREQ) / JOYMAX;
|
|
}
|
|
}
|
|
else {
|
|
move(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Axis::adjustGradient() {
|
|
inverseRange = 1.0F / (xZone - dZone);
|
|
// This is also the convenient spot to initialize the dithering
|
|
// accmulator.
|
|
sumDist = 0;
|
|
}
|
|
|
|
void Axis::move( bool press ) {
|
|
FakeEvent e;
|
|
if (mode == Keyboard) {
|
|
//prevent KeyPress-KeyPress and KeyRelease-KeyRelease pairs.
|
|
//this would only happen in odd circumstances involving the setup
|
|
//dialog being open and blocking events from happening.
|
|
if (isDown == press) return;
|
|
isDown = press;
|
|
bool useMouse = (state > 0)?puseMouse:nuseMouse;
|
|
if (press) {
|
|
e.type = useMouse ? FakeEvent::MouseDown : FakeEvent::KeyDown;
|
|
downkey = (state > 0)?pkeycode:nkeycode;
|
|
}
|
|
else {
|
|
e.type = useMouse ? FakeEvent::MouseUp : FakeEvent::KeyUp;
|
|
}
|
|
e.keycode = downkey;
|
|
}
|
|
//if using the mouse
|
|
else if (press) {
|
|
int dist;
|
|
|
|
if (gradient) {
|
|
const int absState = abs(state);
|
|
float fdist; // Floating point movement distance
|
|
|
|
if (absState >= xZone) fdist = 1.0F;
|
|
else if (absState <= dZone) fdist = 0.0F;
|
|
else {
|
|
const float u = inverseRange * (absState - dZone);
|
|
|
|
switch(transferCurve) {
|
|
case Quadratic:
|
|
fdist = sqr(u);
|
|
break;
|
|
case Cubic:
|
|
fdist = cub(u);
|
|
break;
|
|
case QuadraticExtreme:
|
|
fdist = sqr(u);
|
|
if(u >= 0.95F) {
|
|
fdist *= 1.5F;
|
|
}
|
|
break;
|
|
case PowerFunction:
|
|
fdist = clamp(powf(u, 1.0F / clamp(
|
|
sensitivity, 1e-8F, 1e+3F)), 0.0F, 1.0F);
|
|
break;
|
|
default:
|
|
fdist = u;
|
|
}
|
|
}
|
|
fdist *= maxSpeed;
|
|
if (state < 0) fdist = -fdist;
|
|
// Accumulate the floating point distance and shift the
|
|
// mouse by the rounded magnitude
|
|
sumDist += fdist;
|
|
dist = static_cast<int>(rint(sumDist));
|
|
sumDist -= dist;
|
|
}
|
|
//if not gradient, always go full speed.
|
|
else dist = maxSpeed;
|
|
|
|
e.type = absolute ? FakeEvent::MouseMoveAbsolute : FakeEvent::MouseMove;
|
|
if (mode == MousePosVert) {
|
|
e.move.x = 0;
|
|
e.move.y = dist;
|
|
}
|
|
else if (mode == MouseNegVert) {
|
|
e.move.x = 0;
|
|
e.move.y = -dist;
|
|
}
|
|
else if (mode == MousePosHor) {
|
|
e.move.x = dist;
|
|
e.move.y = 0;
|
|
}
|
|
else if (mode == MouseNegHor) {
|
|
e.move.x = -dist;
|
|
e.move.y = 0;
|
|
}
|
|
}
|
|
//actually create the event
|
|
sendevent(e);
|
|
}
|