Qt: Add stick multipliers to the pad dialog

This commit is contained in:
Megamouse 2020-07-03 21:16:01 +02:00
parent f8920edb2e
commit 99be645fcc
5 changed files with 179 additions and 110 deletions

View file

@ -135,7 +135,7 @@ u16 PadHandlerBase::NormalizeTriggerInput(u16 value, int threshold)
// normalizes a directed input, meaning it will correspond to a single "button" and not an axis with two directions // normalizes a directed input, meaning it will correspond to a single "button" and not an axis with two directions
// the input values must lie in 0+ // the input values must lie in 0+
u16 PadHandlerBase::NormalizeDirectedInput(s32 raw_value, s32 threshold, s32 maximum) u16 PadHandlerBase::NormalizeDirectedInput(s32 raw_value, s32 threshold, s32 maximum) const
{ {
if (threshold >= maximum || maximum <= 0) if (threshold >= maximum || maximum <= 0)
{ {
@ -155,7 +155,7 @@ u16 PadHandlerBase::NormalizeDirectedInput(s32 raw_value, s32 threshold, s32 max
} }
} }
u16 PadHandlerBase::NormalizeStickInput(u16 raw_value, int threshold, int multiplier, bool ignore_threshold) u16 PadHandlerBase::NormalizeStickInput(u16 raw_value, int threshold, int multiplier, bool ignore_threshold) const
{ {
const s32 scaled_value = (multiplier * raw_value) / 100; const s32 scaled_value = (multiplier * raw_value) / 100;

View file

@ -99,9 +99,9 @@ protected:
// normalizes a directed input, meaning it will correspond to a single "button" and not an axis with two directions // normalizes a directed input, meaning it will correspond to a single "button" and not an axis with two directions
// the input values must lie in 0+ // the input values must lie in 0+
u16 NormalizeDirectedInput(s32 raw_value, s32 threshold, s32 maximum); u16 NormalizeDirectedInput(s32 raw_value, s32 threshold, s32 maximum) const;
u16 NormalizeStickInput(u16 raw_value, int threshold, int multiplier, bool ignore_threshold = false); u16 NormalizeStickInput(u16 raw_value, int threshold, int multiplier, bool ignore_threshold = false) const;
// This function normalizes stick deadzone based on the DS3's deadzone, which is ~13% // This function normalizes stick deadzone based on the DS3's deadzone, which is ~13%
// X and Y is expected to be in (-255) to 255 range, deadzone should be in terms of thumb stick range // X and Y is expected to be in (-255) to 255 range, deadzone should be in terms of thumb stick range

View file

@ -202,8 +202,8 @@ pad_settings_dialog::pad_settings_dialog(QWidget *parent, const GameInfo *game)
show(); show();
RepaintPreviewLabel(ui->preview_stick_left, ui->slider_stick_left->value(), ui->slider_stick_left->size().width(), 0, 0); RepaintPreviewLabel(ui->preview_stick_left, ui->slider_stick_left->value(), ui->slider_stick_left->size().width(), 0, 0, 0);
RepaintPreviewLabel(ui->preview_stick_right, ui->slider_stick_right->value(), ui->slider_stick_right->size().width(), 0, 0); RepaintPreviewLabel(ui->preview_stick_right, ui->slider_stick_right->value(), ui->slider_stick_right->size().width(), 0, 0, 0);
} }
pad_settings_dialog::~pad_settings_dialog() pad_settings_dialog::~pad_settings_dialog()
@ -331,12 +331,12 @@ void pad_settings_dialog::InitButtons()
connect(ui->slider_stick_left, &QSlider::valueChanged, [&](int value) connect(ui->slider_stick_left, &QSlider::valueChanged, [&](int value)
{ {
RepaintPreviewLabel(ui->preview_stick_left, value, ui->slider_stick_left->size().width(), lx, ly); RepaintPreviewLabel(ui->preview_stick_left, value, ui->slider_stick_left->size().width(), lx, ly, ui->stick_multi_left->value());
}); });
connect(ui->slider_stick_right, &QSlider::valueChanged, [&](int value) connect(ui->slider_stick_right, &QSlider::valueChanged, [&](int value)
{ {
RepaintPreviewLabel(ui->preview_stick_right, value, ui->slider_stick_right->size().width(), rx, ry); RepaintPreviewLabel(ui->preview_stick_right, value, ui->slider_stick_right->size().width(), rx, ry, ui->stick_multi_right->value());
}); });
// Open LED settings // Open LED settings
@ -368,12 +368,12 @@ void pad_settings_dialog::InitButtons()
if (lx != preview_values[2] || ly != preview_values[3]) if (lx != preview_values[2] || ly != preview_values[3])
{ {
lx = preview_values[2], ly = preview_values[3]; lx = preview_values[2], ly = preview_values[3];
RepaintPreviewLabel(ui->preview_stick_left, ui->slider_stick_left->value(), ui->slider_stick_left->size().width(), lx, ly); RepaintPreviewLabel(ui->preview_stick_left, ui->slider_stick_left->value(), ui->slider_stick_left->size().width(), lx, ly, ui->stick_multi_left->value());
} }
if (rx != preview_values[4] || ry != preview_values[5]) if (rx != preview_values[4] || ry != preview_values[5])
{ {
rx = preview_values[4], ry = preview_values[5]; rx = preview_values[4], ry = preview_values[5];
RepaintPreviewLabel(ui->preview_stick_right, ui->slider_stick_right->value(), ui->slider_stick_right->size().width(), rx, ry); RepaintPreviewLabel(ui->preview_stick_right, ui->slider_stick_right->value(), ui->slider_stick_right->size().width(), rx, ry, ui->stick_multi_right->value());
} }
} }
@ -524,89 +524,16 @@ void pad_settings_dialog::ReloadButtons()
updateButton(button_ids::id_pad_rstick_right, ui->b_rstick_right, &m_handler_cfg.rs_right); updateButton(button_ids::id_pad_rstick_right, ui->b_rstick_right, &m_handler_cfg.rs_right);
updateButton(button_ids::id_pad_rstick_up, ui->b_rstick_up, &m_handler_cfg.rs_up); updateButton(button_ids::id_pad_rstick_up, ui->b_rstick_up, &m_handler_cfg.rs_up);
ui->chb_vibration_large->setChecked(static_cast<bool>(m_handler_cfg.enable_vibration_motor_large));
ui->chb_vibration_small->setChecked(static_cast<bool>(m_handler_cfg.enable_vibration_motor_small));
ui->chb_vibration_switch->setChecked(static_cast<bool>(m_handler_cfg.switch_vibration_motors));
m_min_force = m_handler->vibration_min; m_min_force = m_handler->vibration_min;
m_max_force = m_handler->vibration_max; m_max_force = m_handler->vibration_max;
ui->chooseClass->setCurrentIndex(m_handler_cfg.device_class_type);
// Trigger the change manually in case that the class dropdown didn't fire an event
HandleDeviceClassChange(ui->chooseClass->currentIndex());
const auto products = input::get_products_by_class(m_handler_cfg.device_class_type);
for (size_t i = 0; i < products.size(); i++)
{
if (products[i].vendor_id == m_handler_cfg.vendor_id && products[i].product_id == m_handler_cfg.product_id)
{
ui->chooseProduct->setCurrentIndex(static_cast<int>(i));
break;
}
}
// Enable Mouse Deadzones
std::vector<std::string> mouse_dz_range_x = m_handler_cfg.mouse_deadzone_x.to_list();
ui->mouse_dz_x->setRange(std::stoi(mouse_dz_range_x.front()), std::stoi(mouse_dz_range_x.back()));
ui->mouse_dz_x->setValue(m_handler_cfg.mouse_deadzone_x);
std::vector<std::string> mouse_dz_range_y = m_handler_cfg.mouse_deadzone_y.to_list();
ui->mouse_dz_y->setRange(std::stoi(mouse_dz_range_y.front()), std::stoi(mouse_dz_range_y.back()));
ui->mouse_dz_y->setValue(m_handler_cfg.mouse_deadzone_y);
// Enable Mouse Acceleration
std::vector<std::string> mouse_accel_range_x = m_handler_cfg.mouse_acceleration_x.to_list();
ui->mouse_accel_x->setRange(std::stod(mouse_accel_range_x.front()) / 100.0, std::stod(mouse_accel_range_x.back()) / 100.0);
ui->mouse_accel_x->setValue(m_handler_cfg.mouse_acceleration_x / 100.0);
std::vector<std::string> mouse_accel_range_y = m_handler_cfg.mouse_acceleration_y.to_list();
ui->mouse_accel_y->setRange(std::stod(mouse_accel_range_y.front()) / 100.0, std::stod(mouse_accel_range_y.back()) / 100.0);
ui->mouse_accel_y->setValue(m_handler_cfg.mouse_acceleration_y / 100.0);
// Enable Stick Lerp Factors
std::vector<std::string> left_stick_lerp_range = m_handler_cfg.l_stick_lerp_factor.to_list();
ui->left_stick_lerp->setRange(std::stod(left_stick_lerp_range.front()) / 100.0, std::stod(left_stick_lerp_range.back()) / 100.0);
ui->left_stick_lerp->setValue(m_handler_cfg.l_stick_lerp_factor / 100.0);
std::vector<std::string> right_stick_lerp_range = m_handler_cfg.r_stick_lerp_factor.to_list();
ui->right_stick_lerp->setRange(std::stod(right_stick_lerp_range.front()) / 100.0, std::stod(right_stick_lerp_range.back()) / 100.0);
ui->right_stick_lerp->setValue(m_handler_cfg.r_stick_lerp_factor / 100.0);
// Enable Vibration Checkboxes // Enable Vibration Checkboxes
m_enable_rumble = m_handler->has_rumble(); m_enable_rumble = m_handler->has_rumble();
// Enable Deadzone Settings // Enable Deadzone Settings
m_enable_deadzones = m_handler->has_deadzones(); m_enable_deadzones = m_handler->has_deadzones();
// Enable Trigger Thresholds UpdateLabels(true);
ui->slider_trigger_left->setRange(0, m_handler->trigger_max);
ui->slider_trigger_left->setValue(m_handler_cfg.ltriggerthreshold);
ui->slider_trigger_right->setRange(0, m_handler->trigger_max);
ui->slider_trigger_right->setValue(m_handler_cfg.rtriggerthreshold);
ui->preview_trigger_left->setRange(0, m_handler->trigger_max);
ui->preview_trigger_right->setRange(0, m_handler->trigger_max);
// Enable Stick Deadzones
ui->slider_stick_left->setRange(0, m_handler->thumb_max);
ui->slider_stick_left->setValue(m_handler_cfg.lstickdeadzone);
ui->slider_stick_right->setRange(0, m_handler->thumb_max);
ui->slider_stick_right->setValue(m_handler_cfg.rstickdeadzone);
RepaintPreviewLabel(ui->preview_stick_left, ui->slider_stick_left->value(), ui->slider_stick_left->size().width(), lx, ly);
RepaintPreviewLabel(ui->preview_stick_right, ui->slider_stick_right->value(), ui->slider_stick_right->size().width(), rx, ry);
// Apply stored/default LED settings to the device
m_enable_led = m_handler->has_led();
m_handler->SetPadData(m_device_name, 0, 0, m_handler_cfg.colorR, m_handler_cfg.colorG, m_handler_cfg.colorB, false, m_handler_cfg.led_battery_indicator_brightness);
// Enable battery and LED group box
m_enable_battery = m_handler->has_battery();
ui->gb_battery->setVisible(m_enable_battery || m_enable_led);
} }
void pad_settings_dialog::ReactivateButtons() void pad_settings_dialog::ReactivateButtons()
@ -626,7 +553,7 @@ void pad_settings_dialog::ReactivateButtons()
} }
m_button_id = button_ids::id_pad_begin; m_button_id = button_ids::id_pad_begin;
UpdateLabel(); UpdateLabels();
SwitchButtons(true); SwitchButtons(true);
for (auto but : m_padButtons->buttons()) for (auto but : m_padButtons->buttons())
@ -643,7 +570,7 @@ void pad_settings_dialog::ReactivateButtons()
ui->chooseProduct->setFocusPolicy(Qt::WheelFocus); ui->chooseProduct->setFocusPolicy(Qt::WheelFocus);
} }
void pad_settings_dialog::RepaintPreviewLabel(QLabel* l, int deadzone, int desired_width, int x, int y) void pad_settings_dialog::RepaintPreviewLabel(QLabel* l, int deadzone, int desired_width, int x, int y, double multiplier)
{ {
const int deadzone_max = m_handler->thumb_max; const int deadzone_max = m_handler->thumb_max;
const qreal device_pixel_ratio = devicePixelRatioF(); const qreal device_pixel_ratio = devicePixelRatioF();
@ -653,8 +580,8 @@ void pad_settings_dialog::RepaintPreviewLabel(QLabel* l, int deadzone, int desir
const qreal outer_circle_diameter = relative_size * desired_width; const qreal outer_circle_diameter = relative_size * desired_width;
const qreal inner_circle_diameter = outer_circle_diameter * deadzone / deadzone_max; const qreal inner_circle_diameter = outer_circle_diameter * deadzone / deadzone_max;
const qreal outer_circle_radius = outer_circle_diameter / 2.0; const qreal outer_circle_radius = outer_circle_diameter / 2.0;
const qreal stick_x = outer_circle_radius * x / deadzone_max; const qreal stick_x = outer_circle_radius * x * multiplier / deadzone_max;
const qreal stick_y = outer_circle_radius * -y / deadzone_max; const qreal stick_y = outer_circle_radius * -y * multiplier / deadzone_max;
// Set up the canvas for our work of art // Set up the canvas for our work of art
QPixmap pixmap(scaled_width, scaled_width); QPixmap pixmap(scaled_width, scaled_width);
@ -852,29 +779,95 @@ bool pad_settings_dialog::eventFilter(QObject* object, QEvent* event)
return QDialog::eventFilter(object, event); return QDialog::eventFilter(object, event);
} }
void pad_settings_dialog::UpdateLabel(bool is_reset) void pad_settings_dialog::UpdateLabels(bool is_reset)
{ {
if (is_reset) if (is_reset)
{ {
if (m_handler->has_rumble()) // Update device class
ui->chooseClass->setCurrentIndex(m_handler_cfg.device_class_type);
// Trigger the change manually in case that the class dropdown didn't fire an event
HandleDeviceClassChange(ui->chooseClass->currentIndex());
const auto products = input::get_products_by_class(m_handler_cfg.device_class_type);
for (size_t i = 0; i < products.size(); i++)
{ {
ui->chb_vibration_large->setChecked(static_cast<bool>(m_handler_cfg.enable_vibration_motor_large)); if (products[i].vendor_id == m_handler_cfg.vendor_id && products[i].product_id == m_handler_cfg.product_id)
ui->chb_vibration_small->setChecked(static_cast<bool>(m_handler_cfg.enable_vibration_motor_small)); {
ui->chb_vibration_switch->setChecked(static_cast<bool>(m_handler_cfg.switch_vibration_motors)); ui->chooseProduct->setCurrentIndex(static_cast<int>(i));
break;
}
} }
if (m_handler->has_deadzones()) ui->chb_vibration_large->setChecked(static_cast<bool>(m_handler_cfg.enable_vibration_motor_large));
{ ui->chb_vibration_small->setChecked(static_cast<bool>(m_handler_cfg.enable_vibration_motor_small));
ui->slider_trigger_left->setValue(m_handler_cfg.ltriggerthreshold); ui->chb_vibration_switch->setChecked(static_cast<bool>(m_handler_cfg.switch_vibration_motors));
ui->slider_trigger_right->setValue(m_handler_cfg.rtriggerthreshold);
ui->slider_stick_left->setValue(m_handler_cfg.lstickdeadzone);
ui->slider_stick_right->setValue(m_handler_cfg.rstickdeadzone);
}
if (m_handler->has_led()) // Update Trigger Thresholds
{ ui->preview_trigger_left->setRange(0, m_handler->trigger_max);
m_handler->SetPadData(m_device_name, 0, 0, m_handler_cfg.colorR, m_handler_cfg.colorG, m_handler_cfg.colorB, false, m_handler_cfg.led_battery_indicator_brightness); ui->slider_trigger_left->setRange(0, m_handler->trigger_max);
} ui->slider_trigger_left->setValue(m_handler_cfg.ltriggerthreshold);
ui->preview_trigger_right->setRange(0, m_handler->trigger_max);
ui->slider_trigger_right->setRange(0, m_handler->trigger_max);
ui->slider_trigger_right->setValue(m_handler_cfg.rtriggerthreshold);
// Update Stick Deadzones
ui->slider_stick_left->setRange(0, m_handler->thumb_max);
ui->slider_stick_left->setValue(m_handler_cfg.lstickdeadzone);
ui->slider_stick_right->setRange(0, m_handler->thumb_max);
ui->slider_stick_right->setValue(m_handler_cfg.rstickdeadzone);
m_handler->SetPadData(m_device_name, 0, 0, m_handler_cfg.colorR, m_handler_cfg.colorG, m_handler_cfg.colorB, false, m_handler_cfg.led_battery_indicator_brightness);
// Update Mouse Deadzones
std::vector<std::string> mouse_dz_range_x = m_handler_cfg.mouse_deadzone_x.to_list();
ui->mouse_dz_x->setRange(std::stoi(mouse_dz_range_x.front()), std::stoi(mouse_dz_range_x.back()));
ui->mouse_dz_x->setValue(m_handler_cfg.mouse_deadzone_x);
std::vector<std::string> mouse_dz_range_y = m_handler_cfg.mouse_deadzone_y.to_list();
ui->mouse_dz_y->setRange(std::stoi(mouse_dz_range_y.front()), std::stoi(mouse_dz_range_y.back()));
ui->mouse_dz_y->setValue(m_handler_cfg.mouse_deadzone_y);
// Update Mouse Acceleration
std::vector<std::string> mouse_accel_range_x = m_handler_cfg.mouse_acceleration_x.to_list();
ui->mouse_accel_x->setRange(std::stod(mouse_accel_range_x.front()) / 100.0, std::stod(mouse_accel_range_x.back()) / 100.0);
ui->mouse_accel_x->setValue(m_handler_cfg.mouse_acceleration_x / 100.0);
std::vector<std::string> mouse_accel_range_y = m_handler_cfg.mouse_acceleration_y.to_list();
ui->mouse_accel_y->setRange(std::stod(mouse_accel_range_y.front()) / 100.0, std::stod(mouse_accel_range_y.back()) / 100.0);
ui->mouse_accel_y->setValue(m_handler_cfg.mouse_acceleration_y / 100.0);
// Update Stick Lerp Factors
std::vector<std::string> left_stick_lerp_range = m_handler_cfg.l_stick_lerp_factor.to_list();
ui->left_stick_lerp->setRange(std::stod(left_stick_lerp_range.front()) / 100.0, std::stod(left_stick_lerp_range.back()) / 100.0);
ui->left_stick_lerp->setValue(m_handler_cfg.l_stick_lerp_factor / 100.0);
std::vector<std::string> right_stick_lerp_range = m_handler_cfg.r_stick_lerp_factor.to_list();
ui->right_stick_lerp->setRange(std::stod(right_stick_lerp_range.front()) / 100.0, std::stod(right_stick_lerp_range.back()) / 100.0);
ui->right_stick_lerp->setValue(m_handler_cfg.r_stick_lerp_factor / 100.0);
// Update Stick Multipliers
std::vector<std::string> stick_multi_range_left = m_handler_cfg.lstickmultiplier.to_list();
ui->stick_multi_left->setRange(std::stod(stick_multi_range_left.front()) / 100.0, std::stod(stick_multi_range_left.back()) / 100.0);
ui->stick_multi_left->setValue(m_handler_cfg.lstickmultiplier / 100.0);
std::vector<std::string> stick_multi_range_right = m_handler_cfg.rstickmultiplier.to_list();
ui->stick_multi_right->setRange(std::stod(stick_multi_range_right.front()) / 100.0, std::stod(stick_multi_range_right.back()) / 100.0);
ui->stick_multi_right->setValue(m_handler_cfg.rstickmultiplier / 100.0);
RepaintPreviewLabel(ui->preview_stick_left, ui->slider_stick_left->value(), ui->slider_stick_left->size().width(), lx, ly, m_handler_cfg.lstickmultiplier / 100.0);
RepaintPreviewLabel(ui->preview_stick_right, ui->slider_stick_right->value(), ui->slider_stick_right->size().width(), rx, ry, m_handler_cfg.rstickmultiplier / 100.0);
// Apply stored/default LED settings to the device
m_enable_led = m_handler->has_led();
m_handler->SetPadData(m_device_name, 0, 0, m_handler_cfg.colorR, m_handler_cfg.colorG, m_handler_cfg.colorB, false, m_handler_cfg.led_battery_indicator_brightness);
// Enable battery and LED group box
m_enable_battery = m_handler->has_battery();
ui->gb_battery->setVisible(m_enable_battery || m_enable_led);
} }
for (auto& entry : m_cfg_entries) for (auto& entry : m_cfg_entries)
@ -887,8 +880,6 @@ void pad_settings_dialog::UpdateLabel(bool is_reset)
m_padButtons->button(entry.first)->setText(entry.second.text); m_padButtons->button(entry.first)->setText(entry.second.text);
} }
ui->chooseClass->setCurrentIndex(m_handler_cfg.device_class_type);
} }
void pad_settings_dialog::SwitchButtons(bool is_enabled) void pad_settings_dialog::SwitchButtons(bool is_enabled)
@ -927,7 +918,7 @@ void pad_settings_dialog::OnPadButtonClicked(int id)
case button_ids::id_reset_parameters: case button_ids::id_reset_parameters:
ReactivateButtons(); ReactivateButtons();
m_handler_cfg.from_default(); m_handler_cfg.from_default();
UpdateLabel(true); UpdateLabels(true);
return; return;
case button_ids::id_blacklist: case button_ids::id_blacklist:
m_handler->get_next_button_press(m_device_name, nullptr, nullptr, true); m_handler->get_next_button_press(m_device_name, nullptr, nullptr, true);
@ -1369,6 +1360,9 @@ void pad_settings_dialog::SaveProfile()
entry.second.cfg_name->from_string(entry.second.key); entry.second.cfg_name->from_string(entry.second.key);
} }
m_handler_cfg.lstickmultiplier.set(ui->stick_multi_left->value() * 100);
m_handler_cfg.rstickmultiplier.set(ui->stick_multi_right->value() * 100);
if (m_handler->has_rumble()) if (m_handler->has_rumble())
{ {
m_handler_cfg.enable_vibration_motor_large.set(ui->chb_vibration_large->isChecked()); m_handler_cfg.enable_vibration_motor_large.set(ui->chb_vibration_large->isChecked());

View file

@ -158,7 +158,7 @@ private:
void SetPadData(u32 large_motor, u32 small_motor); void SetPadData(u32 large_motor, u32 small_motor);
/** Update all the Button Labels with current button mapping */ /** Update all the Button Labels with current button mapping */
void UpdateLabel(bool is_reset = false); void UpdateLabels(bool is_reset = false);
void SwitchPadInfo(const std::string& name, bool is_connected); void SwitchPadInfo(const std::string& name, bool is_connected);
/** Enable/Disable Buttons while trying to remap an other */ /** Enable/Disable Buttons while trying to remap an other */
@ -173,7 +173,7 @@ private:
void ChangeProfile(); void ChangeProfile();
/** Repaints a stick deadzone preview label */ /** Repaints a stick deadzone preview label */
void RepaintPreviewLabel(QLabel* l, int deadzone, int desired_width, int x, int y); void RepaintPreviewLabel(QLabel* l, int deadzone, int desired_width, int x, int y, double multiplier);
std::shared_ptr<PadHandlerBase> GetHandler(pad_handler type); std::shared_ptr<PadHandlerBase> GetHandler(pad_handler type);

View file

@ -1469,7 +1469,7 @@
<item> <item>
<widget class="QGroupBox" name="gb_choose_class"> <widget class="QGroupBox" name="gb_choose_class">
<property name="title"> <property name="title">
<string>Device Class:</string> <string>Device Class</string>
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin"> <property name="leftMargin">
@ -2098,6 +2098,81 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item>
<widget class="QGroupBox" name="gb_stick_multi">
<property name="title">
<string>Stick Multipliers</string>
</property>
<layout class="QHBoxLayout" name="gb_stick_multi_layout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QLabel" name="label_stick_multi_left">
<property name="text">
<string>Left</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="stick_multi_left">
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item>
<spacer name="spacer_stick_multi_left">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_stick_multi_right">
<property name="text">
<string>Right</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="stick_multi_right">
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item>
<spacer name="spacer_stick_multi_right">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="gb_sticks"> <widget class="QGroupBox" name="gb_sticks">
<property name="title"> <property name="title">