@@ -1,8 +1,10 @@
# include "inputMappingDialog.h"
# include "inputMappingDialog.h"
# include "controllerPreview.h"
# include "dualsenseWidget.h"
# include <QCheckBox>
# include <QComboBox>
# include <QDialogButtonBox>
# include <QDoubleSpinBox>
# include <QGridLayout>
@@ -16,56 +18,113 @@
# include <QPushButton>
# include <QScreen>
# include <QScrollArea>
# include <QSet>
# include <QStringList>
# include <QTabWidget>
# include <QVBoxLayout>
namespace {
constexpr auto DEFAULT_MOUSE_SENSITIVITY = 1.0 ;
constexpr char MOUSE_SENSITIVITY [ ] = " MouseSensitivity= " ;
constexpr char TOUCH_PARTICLE_SKIN [ ] = " TouchParticleSkin= " ;
constexpr char TOUCH_CURSOR_OFF [ ] = " TouchCursorImage=Off " ;
constexpr char KEYBOARD_DISABLED [ ] = " Keyboard=Disabled " ;
constexpr char PAD_DISABLED [ ] = " Pad=Disabled " ;
enum Group { Dpad , LeftStick , RightStick , Face , Shoulders , Center , Shortcuts , GroupCount } ;
constexpr char PAD_PREFIX [ ] = " Pad: " ;
enum Section { Face , Dpad , Shoulders , Sticks , System , LeftStick , RightStick , Shortcuts , SectionCount } ;
const char * const SECTION_TITLES [ SectionCount ] = {
QT_TR_NOOP ( " Face buttons " ) , QT_TR_NOOP ( " D-pad " ) ,
QT_TR_NOOP ( " Shoulders && triggers " ) , QT_TR_NOOP ( " Stick clicks " ) ,
QT_TR_NOOP ( " System " ) , QT_TR_NOOP ( " Left stick " ) ,
QT_TR_NOOP ( " Right stick " ) , QT_TR_NOOP ( " In-game shortcuts (cycle) " ) ,
} ;
// pad_default is null for controls that have no gamepad equivalent.
struct PadControl {
const char * id ;
const char * label ;
const char * default_binding ;
Group group ;
int row ;
int column ;
const char * keyboard_default ;
const char * pad_default ;
Section section ;
} ;
constexpr PadControl PAD_CONTROLS [ ] = {
{ " Up " , " Up " , " Up " , Dpad , 0 , 1 } ,
{ " Down " , " Down " , " Down " , Dpad , 2 , 1 } ,
{ " Left " , " Left " , " Left " , Dpad , 1 , 0 } ,
{ " Right " , " Right " , " Right " , Dpad , 1 , 2 } ,
{ " LeftStickUp " , " Up " , " W " , LeftStick , 0 , 1 } ,
{ " LeftStickDown " , " Down " , " S " , LeftStick , 2 , 1 } ,
{ " LeftStickLeft " , " Left " , " A " , LeftStick , 1 , 0 } ,
{ " LeftStickRight " , " Right " , " D " , LeftStick , 1 , 2 } ,
{ " RightStickUp " , " Up " , " T " , RightStick , 0 , 1 } ,
{ " RightStickDown " , " Down " , " G " , RightStick , 2 , 1 } ,
{ " RightStickLeft " , " Left " , " F " , RightStick , 1 , 0 } ,
{ " RightStickRight " , " Right " , " H " , RightStick , 1 , 2 } ,
{ " Triangle " , " Triangle " , " I " , Face , 0 , 1 } ,
{ " Circle " , " Circle " , " L " , Face , 1 , 2 } ,
{ " Cross " , " Cross " , " J " , Face , 2 , 1 } ,
{ " Square " , " Square " , " K " , Face , 1 , 0 } ,
{ " L1 " , " L1 " , " Q " , Shoulders , 0 , 1 } ,
{ " R1 " , " R1 " , " E " , Shoulders , 0 , 2 } ,
{ " L2 " , " L2 " , " Z " , Shoulders , 0 , 0 } ,
{ " R2 " , " R2 " , " C " , Shoulders , 0 , 3 } ,
{ " L3 " , " Press / L3 " , " Left Shift " , LeftStick , 1 , 1 } ,
{ " R3 " , " Press / R3 " , " Left Ctrl " , RightStick , 1 , 1 } ,
{ " Options " , " Options " , " Return " , Center , 0 , 1 } ,
{ " TouchPad " , " Touch pad left " , " Backspace " , Center , 0 , 0 } ,
{ " TouchPadRight " , " Touch pad right " , " Tab " , Center , 0 , 2 } ,
{ " SpeakerVolume " , " Speaker volume " , " 1 " , Shortcuts , 0 , 0 } ,
{ " VibrationIntensity " , " Vibration intensity " , " 2 " , Shortcuts , 0 , 1 } ,
{ " TriggerEffectIntensity " , " Trigger intensity " , " 3 " , Shortcuts , 0 , 2 } ,
{ " Cross " , " Cross " , " J " , " South " , Face } ,
{ " Circle " , " Circle " , " L " , " East " , Face } ,
{ " Square " , " Square " , " K " , " West " , Face } ,
{ " Triangle " , " Triangle " , " I " , " North " , Face } ,
{ " Up " , " Up " , " Up " , " DpadUp " , Dpad } ,
{ " Down " , " Down " , " Down " , " DpadDown " , Dpad } ,
{ " Left " , " Left " , " Left " , " DpadLeft " , Dpad } ,
{ " Right " , " Right " , " Right " , " DpadRight " , Dpad } ,
{ " L1 " , " L1 " , " Q " , " LeftShoulder " , Shoulders } ,
{ " R1 " , " R1 " , " E " , " RightShoulder " , Shoulders } ,
{ " L2 " , " L2 " , " Z " , " LeftTrigger " , Shoulders } ,
{ " R2 " , " R2 " , " C " , " RightTrigger " , Shoulders } ,
{ " L3 " , " L3 (left stick click) " , " Left Shift " , " LeftStick " , Sticks } ,
{ " R3 " , " R3 (right stick click) " , " Left Ctrl " , " RightStick " , Sticks } ,
{ " Options " , " Options " , " Return " , " Start " , System } ,
{ " TouchPad " , " Touch pad click " , " Backspace " , " Touchpad " , System } ,
{ " TouchPadRight " , " Touch pad right " , " Tab " , nullptr , System } ,
{ " GyroToggle " , " Gyro emulation toggle " , " " , " Back " , System } ,
{ " TouchPadMode " , " Emulated touch pad mode " , " F6 " , " " , System } ,
{ " LeftStickUp " , " Up " , " W " , nullptr , LeftStick } ,
{ " LeftStickDown " , " Down " , " S " , nullptr , LeftStick } ,
{ " LeftStickLeft " , " Left " , " A " , nullptr , LeftStick } ,
{ " LeftStickRight " , " Right " , " D " , nullptr , LeftStick } ,
{ " RightStickUp " , " Up " , " T " , nullptr , RightStick } ,
{ " RightStickDown " , " Down " , " G " , nullptr , RightStick } ,
{ " RightStickLeft " , " Left " , " F " , nullptr , RightStick } ,
{ " RightStickRight " , " Right " , " H " , nullptr , RightStick } ,
{ " SpeakerVolume " , " Speaker volume " , " 1 " , nullptr , Shortcuts } ,
{ " VibrationIntensity " , " Vibration intensity " , " 2 " , nullptr , Shortcuts } ,
{ " TriggerEffectIntensity " , " Trigger intensity " , " 3 " , nullptr , Shortcuts } ,
} ;
struct PadSourceName {
const char * id ;
const char * label ;
} ;
constexpr PadSourceName PAD_SOURCES [ ] = {
{ " South " , " A / Cross " } ,
{ " East " , " B / Circle " } ,
{ " West " , " X / Square " } ,
{ " North " , " Y / Triangle " } ,
{ " Back " , " View / Back / Share " } ,
{ " Guide " , " Guide / Home " } ,
{ " Start " , " Menu / Start " } ,
{ " LeftStick " , " Left stick click " } ,
{ " RightStick " , " Right stick click " } ,
{ " LeftShoulder " , " LB / L1 " } ,
{ " RightShoulder " , " RB / R1 " } ,
{ " DpadUp " , " D-pad Up " } ,
{ " DpadDown " , " D-pad Down " } ,
{ " DpadLeft " , " D-pad Left " } ,
{ " DpadRight " , " D-pad Right " } ,
{ " Misc1 " , " Share / Misc " } ,
{ " RightPaddle1 " , " Right paddle 1 " } ,
{ " LeftPaddle1 " , " Left paddle 1 " } ,
{ " RightPaddle2 " , " Right paddle 2 " } ,
{ " LeftPaddle2 " , " Left paddle 2 " } ,
{ " Touchpad " , " Touch pad click " } ,
{ " LeftTrigger " , " LT / L2 " } ,
{ " RightTrigger " , " RT / R2 " } ,
} ;
QString PadSourceLabel ( const QString & id ) {
for ( const auto & source : PAD_SOURCES ) {
if ( id = = QLatin1String ( source . id ) ) {
return QObject : : tr ( source . label ) ;
}
}
return id ;
}
QString KeypadName ( int key ) {
if ( key > = Qt : : Key_0 & & key < = Qt : : Key_9 ) {
return QStringLiteral ( " Keypad %1 " ) . arg ( key - Qt : : Key_0 ) ;
@@ -191,6 +250,54 @@ private:
QString m_binding ;
} ;
class PadCaptureDialog final : public QDialog {
public :
PadCaptureDialog ( ControllerPreview * preview , QWidget * parent ) : QDialog ( parent ) {
setWindowTitle ( tr ( " Set Binding " ) ) ;
setModal ( true ) ;
setMinimumWidth ( 360 ) ;
auto * layout = new QVBoxLayout ( this ) ;
m_label = new QLabel ( this ) ;
m_label - > setAlignment ( Qt : : AlignCenter ) ;
m_label - > setWordWrap ( true ) ;
layout - > addWidget ( m_label ) ;
if ( preview = = nullptr ) {
return ;
}
// Inputs already held when the dialog opens must be released before they count.
m_held = preview - > RawPressed ( ) ;
connect ( preview , & ControllerPreview : : StateChanged , this , [ this , preview ] {
const auto & pressed = preview - > RawPressed ( ) ;
m_held . intersect ( pressed ) ;
for ( const auto & name : pressed ) {
if ( ! m_held . contains ( name ) ) {
m_binding = name ;
accept ( ) ;
return ;
}
}
UpdateText ( preview ) ;
} ) ;
UpdateText ( preview ) ;
}
[[nodiscard]] const QString & Binding ( ) const { return m_binding ; }
private :
void UpdateText ( const ControllerPreview * preview ) {
m_label - > setText ( preview - > DeviceName ( ) . isEmpty ( )
? tr ( " No controller connected. \n Connect one, or press Esc to cancel. " )
: tr ( " Press a button or trigger on %1. \n Esc cancels. " )
. arg ( preview - > DeviceName ( ) ) ) ;
}
QLabel * m_label = nullptr ;
QString m_binding ;
QSet < QString > m_held ;
} ;
void AssignBinding ( QHash < QString , QString > & bindings , const QString & id , const QString & binding ) {
for ( auto item = bindings . begin ( ) ; item ! = bindings . end ( ) ; ) {
if ( item . value ( ) . compare ( binding , Qt : : CaseInsensitive ) = = 0 ) {
@@ -205,7 +312,11 @@ void AssignBinding(QHash<QString, QString>& bindings, const QString& id, const Q
QHash < QString , QString > ParseMapping ( const QStringList & mapping ) {
QHash < QString , QString > result ;
for ( const auto & entry : mapping ) {
if ( entry . startsWith ( QLatin1String ( MOUSE_SENSITIVITY ) ) ) {
if ( entry = = QLatin1String ( KEYBOARD_DISABLED ) | | entry = = QLatin1String ( PAD_DISABLED ) | |
entry . startsWith ( QLatin1String ( MOUSE_SENSITIVITY ) ) | |
entry . startsWith ( QLatin1String ( TOUCH_PARTICLE_SKIN ) ) | |
entry . startsWith ( QLatin1String ( " TouchCursorImage= " ) ) | |
entry . startsWith ( QLatin1String ( PAD_PREFIX ) ) ) {
continue ;
}
const auto separator = entry . indexOf ( QLatin1Char ( ' = ' ) ) ;
@@ -216,6 +327,15 @@ QHash<QString, QString> ParseMapping(const QStringList& mapping) {
return result ;
}
QString ParseTouchParticleSkin ( const QStringList & mapping ) {
for ( const auto & entry : mapping ) {
if ( entry . startsWith ( QLatin1String ( TOUCH_PARTICLE_SKIN ) ) ) {
return entry . mid ( sizeof ( TOUCH_PARTICLE_SKIN ) - 1 ) ;
}
}
return QStringLiteral ( " Classic " ) ;
}
double ParseMouseSensitivity ( const QStringList & mapping ) {
for ( const auto & entry : mapping ) {
if ( entry . startsWith ( QLatin1String ( MOUSE_SENSITIVITY ) ) ) {
@@ -231,106 +351,67 @@ InputMappingDialog::InputMappingDialog(const QStringList& mapping, ControllerPre
QWidget * parent )
: QDialog ( parent ) , m_preview ( preview ) {
setWindowTitle ( tr ( " Controls " ) ) ;
resize ( QSize ( 1100 , 820 ) . boundedTo ( screen ( ) - > availableGeometry ( ) . size ( ) ) ) ;
resize ( QSize ( 1360 , 780 ) . boundedTo ( screen ( ) - > availableGeometry ( ) . size ( ) ) ) ;
m_bindings = ParseMapping ( mapping ) ;
m_custom_bindings = ! m_bindings . isEmpty ( ) | | mapping . contains ( QLatin1String ( KEYBOARD_DISABLED ) ) ;
m_custom_pad = mapping . contains ( QLatin1String ( PAD_DISABLED ) ) ;
for ( const auto & entry : mapping ) {
if ( ! entry . startsWith ( QLatin1String ( PAD_PREFIX ) ) ) {
continue ;
}
const auto body = entry . mid ( static_cast < int > ( sizeof ( PAD_PREFIX ) ) - 1 ) ;
const auto separator = body . indexOf ( QLatin1Char ( ' = ' ) ) ;
if ( separator > 0 & & separator + 1 < body . size ( ) ) {
AssignBinding ( m_pad_bindings , body . left ( separator ) , body . mid ( separator + 1 ) ) ;
m_custom_pad = true ;
}
}
for ( const auto & control : PAD_CONTROLS ) {
const auto id = QString : : fromLatin1 ( control . id ) ;
if ( ! m_custom_bindings & & control . keyboard_default [ 0 ] ! = ' \0 ' ) {
m_bindings . insert ( id , QString : : fromLatin1 ( control . keyboard_default ) ) ;
}
if ( ! m_custom_pad & & control . pad_default ! = nullptr ) {
m_pad_bindings . insert ( id , QString : : fromLatin1 ( control . pad_default ) ) ;
}
}
auto * layout = new QVBoxLayout ( this ) ;
layout - > setSpacing ( 10 ) ;
auto * title = new QLabel ( tr ( " Controller setup " ) , this ) ;
auto font = title - > font ( ) ;
font . setPointSize ( font . pointSize ( ) + 7 ) ;
font . setBold ( true ) ;
title - > setFont ( font ) ;
layout - > addWidget ( title ) ;
auto * hint = new QLabel (
tr ( " Select a button to remap. Press controller buttons to test. " ) , this ) ;
hint - > setWordWrap ( true ) ;
layout - > addWidget ( hint ) ;
m_status = new QLabel ( this ) ;
m_status - > setObjectName ( QStringLiteral ( " controllerStatus " ) ) ;
m_status - > setWordWrap ( true ) ;
layout - > addWidget ( m_status ) ;
auto * scroll = new QScrollArea ( this ) ;
scroll - > setWidgetResizable ( true ) ;
scroll - > setFrameShape ( QFrame : : NoFrame ) ;
auto * content = new QWidget ( scroll ) ;
auto * grid = new QGridLayout ( content ) ;
grid - > setContentsMargins ( 0 , 0 , 0 , 0 ) ;
grid - > setSpacing ( 12 ) ;
QGroupBox * groups [ GroupCount ] ;
QGridLayout * cells [ GroupCount ] ;
const char * titles [ ] = { " D-Pad " ,
" Left stick " ,
" Right stick " ,
" Face buttons " ,
" Shoulders and triggers " ,
" Touch pad and options " ,
" In-game shortcuts (cycle) " } ;
for ( int index = 0 ; index < GroupCount ; + + index ) {
groups [ index ] = new QGroupBox ( tr ( titles [ index ] ) , content ) ;
cells [ index ] = new QGridLayout ( groups [ index ] ) ;
cells [ index ] - > setSpacing ( 5 ) ;
}
m_bindings = ParseMapping ( mapping ) ;
m_custom_bindings = ! m_bindings . isEmpty ( ) ;
for ( const auto & control : PAD_CONTROLS ) {
const auto id = QString : : fromLatin1 ( control . id ) ;
auto * cell = new QGroupBox ( tr ( control . label ) , groups [ control . group ] ) ;
auto * cell_layout = new QVBoxLayout ( cell ) ;
cell_layout - > setContentsMargins ( 3 , 5 , 3 , 3 ) ;
auto * button = new QPushButton ( cell ) ;
button - > setObjectName ( QStringLiteral ( " binding_ " ) + id ) ;
button - > setAutoDefault ( false ) ;
button - > setMinimumWidth ( 64 ) ;
button - > setSizePolicy ( QSizePolicy : : Expanding , QSizePolicy : : Fixed ) ;
button - > setAccessibleName ( tr ( titles [ control . group ] ) + QStringLiteral ( " / " ) +
tr ( control . label ) ) ;
cell_layout - > addWidget ( button ) ;
cells [ control . group ] - > addWidget ( cell , control . row , control . column ) ;
m_buttons . insert ( id , button ) ;
if ( ! m_custom_bindings ) {
m_bindings . insert ( id , QString : : fromLatin1 ( control . default_binding ) ) ;
}
connect ( button , & QPushButton : : clicked , this , [ this , id ] { ChangeBinding ( id ) ; } ) ;
}
auto * left = new QVBoxLayout ;
left - > addWidget ( groups [ Dpad ] ) ;
left - > addWidget ( groups [ LeftStick ] ) ;
left - > addStretch ( ) ;
grid - > addLayout ( left , 0 , 0 ) ;
auto * center = new QVBoxLayout ;
center - > addWidget ( groups [ Shoulders ] ) ;
m_controller = new DualSenseWidget ( content ) ;
m_controller - > setObjectName ( QStringLiteral ( " controllerDiagram " ) ) ;
center - > addWidget ( m_controller , 1 ) ;
center - > addWidget ( groups [ Center ] ) ;
grid - > addLayout ( center , 0 , 1 ) ;
auto * right = new QVBoxLayout ;
right - > addWidget ( groups [ Face ] ) ;
right - > addWidget ( groups [ RightStick ] ) ;
right - > addStretch ( ) ;
grid - > addLayout ( right , 0 , 2 ) ;
grid - > setColumnStretch ( 1 , 1 ) ;
grid - > addWidget ( groups [ Shortcuts ] , 1 , 0 , 1 , 3 ) ;
scroll - > setWidget ( content ) ;
layout - > addWidget ( scroll , 1 ) ;
auto * sensitivity_layout = new QHBoxLayout ;
sensitivity_layout - > addWidget ( new QLabel ( tr ( " Mouse sensitivity " ) , this ) ) ;
m_sensitivity = new QDoubleSpinBox ( this ) ;
m_sensitivity - > setObjectName ( QStringLiteral ( " mouseSensitivity " ) ) ;
m_sensitivity - > setRange ( 0.1 , 5.0 ) ;
m_sensitivity - > setSingleStep ( 0.1 ) ;
m_sensitivity - > setDecimals ( 1 ) ;
m_sensitivity - > setSuffix ( QStringLiteral ( " × " ) ) ;
m_tabs = new QTabWidget ( this ) ;
m_tabs - > setObjectName ( QStringLiteral ( " inputTabs " ) ) ;
m_tabs - > setDocumentMode ( true ) ;
m_tabs - > addTab ( BuildTab ( true ) , tr ( " Gamepad " ) ) ;
m_tabs - > addTab ( BuildTab ( false ) , tr ( " Keyboard && Mouse " ) ) ;
layout - > addWidget ( m_tabs , 1 ) ;
m_sensitivity - > setValue ( ParseMouseSensitivity ( mapping ) ) ;
sensitivity_layout - > addWidget ( m_sensitivity ) ;
sensitivity_layout - > addWidget (
new QLabel ( tr ( " F7 in-game toggles mouse movement on the right stick. " ) , this ) ) ;
sensitivity_layout - > addStretch ( ) ;
layout - > addLayout ( sensitivity_layout ) ;
auto * touch_group = new QGroupBox ( tr ( " Emulated touch pad (shared by both tabs) " ) , this ) ;
auto * touch_layout = new QHBoxLayout ( touch_group ) ;
touch_layout - > addWidget ( new QLabel ( tr ( " Particle skin " ) , touch_group ) ) ;
m_particle_skin = new QComboBox ( touch_group ) ;
m_particle_skin - > setObjectName ( QStringLiteral ( " touchParticleSkin " ) ) ;
m_particle_skin - > addItem ( tr ( " Classic " ) , QStringLiteral ( " Classic " ) ) ;
m_particle_skin - > addItem ( tr ( " Blue neon " ) , QStringLiteral ( " BlueNeon " ) ) ;
m_particle_skin - > addItem ( tr ( " Monochrome " ) , QStringLiteral ( " Monochrome " ) ) ;
m_particle_skin - > addItem ( tr ( " Crimson " ) , QStringLiteral ( " Crimson " ) ) ;
m_particle_skin - > addItem ( tr ( " Red/Blue " ) , QStringLiteral ( " RedBlue " ) ) ;
touch_layout - > addWidget ( m_particle_skin ) ;
m_touch_cursor = new QCheckBox ( tr ( " Show hand cursor and trail " ) , touch_group ) ;
m_touch_cursor - > setObjectName ( QStringLiteral ( " touchCursorImage " ) ) ;
m_touch_cursor - > setToolTip ( tr ( " When off, the emulated touch pad still works but the hand "
" image and drawing trail are not shown. " ) ) ;
m_touch_cursor - > setChecked ( ! mapping . contains ( QLatin1String ( TOUCH_CURSOR_OFF ) ) ) ;
touch_layout - > addWidget ( m_touch_cursor ) ;
touch_layout - > addStretch ( ) ;
touch_layout - > addWidget ( new QLabel (
tr ( " Toggle with the touch pad mode key (F6 by default); draw with left mouse / Cross. " ) ,
touch_group ) ) ;
layout - > addWidget ( touch_group ) ;
const auto skin_index = m_particle_skin - > findData ( ParseTouchParticleSkin ( mapping ) ) ;
m_particle_skin - > setCurrentIndex ( skin_index > = 0 ? skin_index : 0 ) ;
auto * buttons = new QDialogButtonBox ( QDialogButtonBox : : RestoreDefaults |
QDialogButtonBox : : Save | QDialogButtonBox : : Cancel ,
@@ -338,9 +419,14 @@ InputMappingDialog::InputMappingDialog(const QStringList& mapping, ControllerPre
auto * defaults = buttons - > button ( QDialogButtonBox : : RestoreDefaults ) ;
defaults - > setObjectName ( QStringLiteral ( " defaults " ) ) ;
defaults - > setAutoDefault ( false ) ;
layout - > addWidget ( buttons ) ;
connect ( m_controller , & DualSenseWidget : : ControlClicked , this ,
& InputMappingDialog : : ChangeBinding ) ;
defaults - > setToolTip ( tr ( " Restore the defaults of the current tab. " ) ) ;
auto * tip = new QLabel ( tr ( " Tip: right-click a binding to unmap it. " ) , this ) ;
auto * footer = new QHBoxLayout ;
footer - > addWidget ( tip ) ;
footer - > addStretch ( ) ;
footer - > addWidget ( buttons ) ;
layout - > addLayout ( footer ) ;
connect ( defaults , & QPushButton : : clicked , this , & InputMappingDialog : : RestoreDefaults ) ;
connect ( buttons , & QDialogButtonBox : : accepted , this , & QDialog : : accept ) ;
connect ( buttons , & QDialogButtonBox : : rejected , this , & QDialog : : reject ) ;
@@ -359,6 +445,128 @@ InputMappingDialog::~InputMappingDialog() {
}
}
QWidget * InputMappingDialog : : BuildTab ( bool pad ) {
auto * page = new QWidget ( this ) ;
auto * layout = new QVBoxLayout ( page ) ;
layout - > setSpacing ( 10 ) ;
auto * hint = new QLabel (
pad ? tr ( " Choose which button on your gamepad triggers each PS5 control. Click a "
" binding, then press the button or trigger you want to use. " )
: tr ( " Choose which keyboard key or mouse button triggers each PS5 control. Click a "
" binding, then press the key or mouse button. " ) ,
page ) ;
hint - > setWordWrap ( true ) ;
layout - > addWidget ( hint ) ;
if ( pad ) {
m_status = new QLabel ( page ) ;
m_status - > setObjectName ( QStringLiteral ( " controllerStatus " ) ) ;
m_status - > setWordWrap ( true ) ;
layout - > addWidget ( m_status ) ;
}
auto * body = new QHBoxLayout ;
body - > setSpacing ( 14 ) ;
layout - > addLayout ( body , 1 ) ;
auto * scroll = new QScrollArea ( page ) ;
scroll - > setWidgetResizable ( true ) ;
scroll - > setFrameShape ( QFrame : : NoFrame ) ;
auto * content = new QWidget ( scroll ) ;
auto * columns = new QGridLayout ( content ) ;
columns - > setContentsMargins ( 0 , 0 , 8 , 0 ) ;
columns - > setSpacing ( 12 ) ;
columns - > setAlignment ( Qt : : AlignTop ) ;
// Section order and the column each one lives in.
static constexpr Section ORDER [ ] = { Face , Dpad , Shoulders , Sticks , System , LeftStick ,
RightStick , Shortcuts } ;
int column_rows [ 2 ] = { 0 , 0 } ;
for ( const auto section : ORDER ) {
QGroupBox * group = nullptr ;
QGridLayout * rows = nullptr ;
int row = 0 ;
for ( const auto & control : PAD_CONTROLS ) {
if ( control . section ! = section | | ( pad & & control . pad_default = = nullptr ) ) {
continue ;
}
if ( group = = nullptr ) {
group = new QGroupBox ( tr ( SECTION_TITLES [ section ] ) , content ) ;
rows = new QGridLayout ( group ) ;
rows - > setHorizontalSpacing ( 12 ) ;
rows - > setVerticalSpacing ( 6 ) ;
rows - > setColumnStretch ( 0 , 1 ) ;
rows - > setColumnMinimumWidth ( 1 , 190 ) ;
}
const auto id = QString : : fromLatin1 ( control . id ) ;
const auto label = ! pad & & id = = QLatin1String ( " TouchPad " ) ? tr ( " Touch pad left " )
: tr ( control . label ) ;
auto * name = new QLabel ( label , group ) ;
auto * button = new QPushButton ( group ) ;
button - > setObjectName ( ( pad ? QStringLiteral ( " padBinding_ " ) : QStringLiteral ( " binding_ " ) ) +
id ) ;
button - > setAutoDefault ( false ) ;
button - > setMinimumWidth ( 190 ) ;
button - > setContextMenuPolicy ( Qt : : CustomContextMenu ) ;
button - > setAccessibleName ( tr ( SECTION_TITLES [ section ] ) + QStringLiteral ( " / " ) + label ) ;
rows - > addWidget ( name , row , 0 ) ;
rows - > addWidget ( button , row , 1 ) ;
+ + row ;
( pad ? m_pad_buttons : m_buttons ) . insert ( id , button ) ;
connect ( button , & QPushButton : : clicked , this , [ this , pad , id ] {
pad ? ChangePadBinding ( id ) : ChangeKeyboardBinding ( id ) ;
} ) ;
connect ( button , & QPushButton : : customContextMenuRequested , this ,
[ this , pad , id ] { ClearBinding ( pad , id ) ; } ) ;
}
if ( group ! = nullptr ) {
const int target = column_rows [ 0 ] < = column_rows [ 1 ] ? 0 : 1 ;
columns - > addWidget ( group , column_rows [ target ] + + , target ) ;
}
}
columns - > setColumnStretch ( 0 , 1 ) ;
columns - > setColumnStretch ( 1 , 1 ) ;
scroll - > setWidget ( content ) ;
scroll - > setHorizontalScrollBarPolicy ( Qt : : ScrollBarAlwaysOff ) ;
scroll - > setMinimumWidth ( pad ? 640 : 700 ) ;
body - > addWidget ( scroll , 3 ) ;
if ( pad ) {
m_controller = new DualSenseWidget ( page ) ;
m_controller - > setObjectName ( QStringLiteral ( " controllerDiagram " ) ) ;
m_controller - > setMinimumWidth ( 320 ) ;
body - > addWidget ( m_controller , 2 ) ;
connect ( m_controller , & DualSenseWidget : : ControlClicked , this ,
[ this ] ( const QString & id ) {
ChangePadBinding ( id = = QLatin1String ( " TouchPadRight " ) ? QStringLiteral ( " TouchPad " )
: id ) ;
} ) ;
auto * note = new QLabel ( tr ( " The gyro emulation toggle activates tilt mode on gamepads "
" without a gyroscope: the right stick then controls pitch and "
" roll. It defaults to the View/Back button. " ) ,
page ) ;
note - > setWordWrap ( true ) ;
layout - > addWidget ( note ) ;
} else {
auto * sensitivity_layout = new QHBoxLayout ;
sensitivity_layout - > addWidget ( new QLabel ( tr ( " Mouse sensitivity " ) , page ) ) ;
m_sensitivity = new QDoubleSpinBox ( page ) ;
m_sensitivity - > setObjectName ( QStringLiteral ( " mouseSensitivity " ) ) ;
m_sensitivity - > setRange ( 0.1 , 5.0 ) ;
m_sensitivity - > setSingleStep ( 0.1 ) ;
m_sensitivity - > setDecimals ( 1 ) ;
m_sensitivity - > setSuffix ( QStringLiteral ( " × " ) ) ;
m_sensitivity - > setValue ( DEFAULT_MOUSE_SENSITIVITY ) ;
sensitivity_layout - > addWidget ( m_sensitivity ) ;
sensitivity_layout - > addWidget (
new QLabel ( tr ( " F7 in-game toggles mouse movement on the right stick. " ) , page ) ) ;
sensitivity_layout - > addStretch ( ) ;
layout - > addLayout ( sensitivity_layout ) ;
}
return page ;
}
QStringList InputMappingDialog : : Mapping ( ) const {
QStringList result ;
if ( m_custom_bindings ) {
@@ -369,15 +577,38 @@ QStringList InputMappingDialog::Mapping() const {
result . append ( id + QLatin1Char ( ' = ' ) + binding ) ;
}
}
if ( result . isEmpty ( ) ) {
result . append ( QLatin1String ( KEYBOARD_DISABLED ) ) ;
}
}
if ( m_custom_pad ) {
const auto start = result . size ( ) ;
for ( const auto & control : PAD_CONTROLS ) {
const auto id = QString : : fromLatin1 ( control . id ) ;
const auto source = m_pad_bindings . value ( id ) ;
if ( ! source . isEmpty ( ) ) {
result . append ( QLatin1String ( PAD_PREFIX ) + id + QLatin1Char ( ' = ' ) + source ) ;
}
}
if ( result . size ( ) = = start ) {
result . append ( QLatin1String ( PAD_DISABLED ) ) ;
}
}
if ( m_sensitivity - > value ( ) ! = DEFAULT_MOUSE_SENSITIVITY ) {
result . append ( QLatin1String ( MOUSE_SENSITIVITY ) +
QString : : number ( m_sensitivity - > value ( ) , ' f ' , 1 ) ) ;
}
const auto skin = m_particle_skin - > currentData ( ) . toString ( ) ;
if ( skin ! = QLatin1String ( " Classic " ) ) {
result . append ( QLatin1String ( TOUCH_PARTICLE_SKIN ) + skin ) ;
}
if ( ! m_touch_cursor - > isChecked ( ) ) {
result . append ( QLatin1String ( TOUCH_CURSOR_OFF ) ) ;
}
return result ;
}
void InputMappingDialog : : ChangeBinding ( const QString & id ) {
void InputMappingDialog : : ChangeKeyboardBinding ( const QString & id ) {
if ( ! m_buttons . contains ( id ) ) {
return ;
}
@@ -393,14 +624,54 @@ void InputMappingDialog::ChangeBinding(const QString& id) {
UpdateButtons ( ) ;
}
void InputMappingDialog : : RestoreDefaults ( ) {
m_bindings . clear ( ) ;
for ( const auto & control : PAD_CONTROLS ) {
m_bindings . insert ( QString : : fromLatin1 ( control . id ) ,
QString : : fromLatin1 ( control . default_binding ) ) ;
void InputMappingDialog : : ChangePadBinding ( const QString & id ) {
if ( ! m_pad_buttons . contains ( id ) ) {
return ;
}
m_selected = id ;
UpdateHighlights ( ) ;
PadCaptureDialog dialog ( m_preview , this ) ;
dialog . setWindowTitle ( tr ( " Set binding — %1 " ) . arg ( m_pad_buttons . value ( id ) - > accessibleName ( ) ) ) ;
if ( dialog . exec ( ) = = QDialog : : Accepted ) {
AssignBinding ( m_pad_bindings , id , dialog . Binding ( ) ) ;
m_custom_pad = true ;
}
m_selected . clear ( ) ;
UpdateButtons ( ) ;
}
void InputMappingDialog : : ClearBinding ( bool pad , const QString & id ) {
if ( pad ) {
m_pad_bindings . remove ( id ) ;
m_custom_pad = true ;
} else {
m_bindings . remove ( id ) ;
m_custom_bindings = true ;
}
UpdateButtons ( ) ;
}
void InputMappingDialog : : RestoreDefaults ( ) {
if ( m_tabs - > currentIndex ( ) = = 0 ) {
m_pad_bindings . clear ( ) ;
for ( const auto & control : PAD_CONTROLS ) {
if ( control . pad_default ! = nullptr ) {
m_pad_bindings . insert ( QString : : fromLatin1 ( control . id ) ,
QString : : fromLatin1 ( control . pad_default ) ) ;
}
}
m_custom_pad = false ;
} else {
m_bindings . clear ( ) ;
for ( const auto & control : PAD_CONTROLS ) {
if ( control . keyboard_default [ 0 ] ! = ' \0 ' ) {
m_bindings . insert ( QString : : fromLatin1 ( control . id ) ,
QString : : fromLatin1 ( control . keyboard_default ) ) ;
}
}
m_sensitivity - > setValue ( DEFAULT_MOUSE_SENSITIVITY ) ;
m_custom_bindings = false ;
}
m_sensitivity - > setValue ( DEFAULT_MOUSE_SENSITIVITY ) ;
m_custom_bindings = false ;
UpdateButtons ( ) ;
}
@@ -410,14 +681,42 @@ void InputMappingDialog::UpdateButtons() {
const auto binding = m_bindings . value ( item . key ( ) ) ;
const auto label = binding . isEmpty ( ) ? tr ( " None " ) : binding ;
button - > setText ( QString ( label ) . replace ( QLatin1Char ( ' & ' ) , QLatin1String ( " && " ) ) ) ;
button - > setToolTip ( tr ( " %1: %2. Click to change. " ) . arg ( button - > accessibleName ( ) , label ) ) ;
button - > setToolTip (
tr ( " %1: %2. Click to change, right-click to clear. " ) . arg ( button - > accessibleName ( ) , label ) ) ;
}
for ( auto item = m_pad_buttons . begin ( ) ; item ! = m_pad_buttons . end ( ) ; + + item ) {
auto * button = item . value ( ) ;
const auto source = m_pad_bindings . value ( item . key ( ) ) ;
const auto label = source . isEmpty ( ) ? tr ( " None " ) : PadSourceLabel ( source ) ;
button - > setText ( QString ( label ) . replace ( QLatin1Char ( ' & ' ) , QLatin1String ( " && " ) ) ) ;
button - > setToolTip (
tr ( " %1: %2. Click to change, right-click to clear. " ) . arg ( button - > accessibleName ( ) , label ) ) ;
}
UpdateHighlights ( ) ;
}
void InputMappingDialog : : UpdatePreview ( ) {
const auto pressed = m_preview ! = nullptr ? m_preview - > Pressed ( ) : QSet < QString > ( ) ;
m_controller - > SetPressed ( pressed ) ;
m_pad_pressed . clear ( ) ;
if ( m_preview ! = nullptr ) {
const auto & raw = m_preview - > RawPressed ( ) ;
for ( auto item = m_pad_bindings . begin ( ) ; item ! = m_pad_bindings . end ( ) ; + + item ) {
if ( raw . contains ( item . value ( ) ) ) {
m_pad_pressed . insert ( item . key ( ) ) ;
}
}
if ( m_pad_pressed . contains ( QStringLiteral ( " TouchPad " ) ) ) {
m_pad_pressed . insert ( QStringLiteral ( " TouchPadRight " ) ) ;
}
for ( const auto & name : m_preview - > Pressed ( ) ) {
// Stick directions and touch pad fingers are shown as the pad reports them.
if ( name . startsWith ( QLatin1String ( " LeftStick " ) ) | |
name . startsWith ( QLatin1String ( " RightStick " ) ) | |
name . startsWith ( QLatin1String ( " TouchPad " ) ) ) {
m_pad_pressed . insert ( name ) ;
}
}
}
m_controller - > SetPressed ( m_pad_pressed ) ;
m_controller - > SetSticks ( m_preview ! = nullptr ? m_preview - > LeftStick ( ) : QPointF ( ) ,
m_preview ! = nullptr ? m_preview - > RightStick ( ) : QPointF ( ) ) ;
UpdateHighlights ( ) ;
@@ -432,8 +731,8 @@ void InputMappingDialog::UpdatePreview() {
void InputMappingDialog : : UpdateHighlights ( ) {
m_controller - > SetSelected ( m_selected ) ;
for ( auto item = m_buttons . begin ( ) ; item ! = m_buttons . end ( ) ; + + item ) {
const bool down = m_preview ! = nullptr & & m_preview - > Pressed ( ) . contains ( item . key ( ) ) ;
for ( auto item = m_pad_buttons . begin ( ) ; item ! = m_pad_buttons . end ( ) ; + + item ) {
const bool down = m_pad_pressed . contains ( item . key ( ) ) ;
auto colors = palette ( ) ;
if ( down ) {
colors . setColor ( QPalette : : Button , colors . color ( QPalette : : Highlight ) ) ;
@@ -446,4 +745,11 @@ void InputMappingDialog::UpdateHighlights() {
item . value ( ) - > setPalette ( colors ) ;
item . value ( ) - > setFont ( label_font ) ;
}
for ( auto item = m_buttons . begin ( ) ; item ! = m_buttons . end ( ) ; + + item ) {
auto colors = palette ( ) ;
if ( item . key ( ) = = m_selected ) {
colors . setColor ( QPalette : : ButtonText , colors . color ( QPalette : : Highlight ) ) ;
}
item . value ( ) - > setPalette ( colors ) ;
}
}