UI: Don't recreate entire Hotkey Settings tab

This replaces the programmatic generation of the hotkeys tab with XML.
Also fixes a memory leak. Reuses concepts from LoadAudioSources().
master
Matt Gajownik 2022-01-03 17:41:28 +11:00 committed by Jim
parent a46f56a3e9
commit e00195ad6f
5 changed files with 207 additions and 134 deletions

View File

@ -4690,31 +4690,87 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QScrollArea" name="hotkeyPage"> <widget class="QWidget" name="hotkeyPage">
<property name="widgetResizable"> <layout class="QVBoxLayout" name="verticalLayout_25">
<bool>true</bool> <item>
</property> <layout class="QGridLayout" name="hotkeySearchLayout">
<widget class="QWidget" name="hotkeyWidget"> <item row="0" column="0">
<property name="geometry"> <widget class="QLabel" name="hotkeySearchLabel">
<rect> <property name="text">
<x>0</x> <string>Basic.Settings.Hotkeys.Filter</string>
<y>0</y> </property>
<width>98</width> </widget>
<height>28</height> </item>
</rect> <item row="0" column="1">
</property> <widget class="QLineEdit" name="hotkeyFilterSearch"/>
<layout class="QFormLayout" name="hotkeyLayout"> </item>
<property name="fieldGrowthPolicy"> <item row="0" column="2">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum> <widget class="QLabel" name="hotkeyFilterLabel">
</property> <property name="text">
<property name="labelAlignment"> <string>Basic.Settings.Hotkeys.FilterByHotkey</string>
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> </property>
</property> </widget>
<property name="verticalSpacing"> </item>
<number>0</number> <item row="0" column="3">
</property> <widget class="OBSHotkeyEdit" name="hotkeyFilterInput"/>
</layout> </item>
</widget> <item row="0" column="4">
<widget class="QPushButton" name="hotkeyFilterReset">
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="baseSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Clear</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string>trashIcon</string>
</property>
<property name="fixedSize" stdset="0">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QScrollArea" name="hotkeyScrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="hotkeyScrollContents">
<layout class="QFormLayout" name="hotkeyFormLayout">
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="verticalSpacing">
<number>0</number>
</property>
</layout>
</widget>
</widget>
</item>
</layout>
</widget> </widget>
<widget class="QWidget" name="advancedPage"> <widget class="QWidget" name="advancedPage">
<layout class="QVBoxLayout" name="verticalLayout_16"> <layout class="QVBoxLayout" name="verticalLayout_16">
@ -5572,6 +5628,11 @@
<extends>QPushButton</extends> <extends>QPushButton</extends>
<header>url-push-button.hpp</header> <header>url-push-button.hpp</header>
</customwidget> </customwidget>
<customwidget>
<class>OBSHotkeyEdit</class>
<extends>QLineEdit</extends>
<header>hotkey-edit.hpp</header>
</customwidget>
</customwidgets> </customwidgets>
<tabstops> <tabstops>
<tabstop>listWidget</tabstop> <tabstop>listWidget</tabstop>

View File

@ -175,7 +175,7 @@ void OBSHotkeyEdit::ClearKey()
void OBSHotkeyEdit::UpdateDuplicationState() void OBSHotkeyEdit::UpdateDuplicationState()
{ {
if (dupeIcon->isVisible() != hasDuplicate) { if (dupeIcon && dupeIcon->isVisible() != hasDuplicate) {
dupeIcon->setVisible(hasDuplicate); dupeIcon->setVisible(hasDuplicate);
update(); update();
} }
@ -194,8 +194,8 @@ void OBSHotkeyEdit::InitSignalHandler()
void OBSHotkeyEdit::CreateDupeIcon() void OBSHotkeyEdit::CreateDupeIcon()
{ {
dupeIcon = this->addAction(settings->GetHotkeyConflictIcon(), dupeIcon = addAction(settings->GetHotkeyConflictIcon(),
ActionPosition::TrailingPosition); ActionPosition::TrailingPosition);
dupeIcon->setToolTip(QTStr("Basic.Settings.Hotkeys.DuplicateWarning")); dupeIcon->setToolTip(QTStr("Basic.Settings.Hotkeys.DuplicateWarning"));
QObject::connect(dupeIcon, &QAction::triggered, QObject::connect(dupeIcon, &QAction::triggered,
[=] { emit SearchKey(key); }); [=] { emit SearchKey(key); });
@ -274,7 +274,7 @@ void OBSHotkeyWidget::Save(std::vector<obs_key_combination_t> &combinations)
void OBSHotkeyWidget::AddEdit(obs_key_combination combo, int idx) void OBSHotkeyWidget::AddEdit(obs_key_combination combo, int idx)
{ {
auto edit = new OBSHotkeyEdit(combo, settings); auto edit = new OBSHotkeyEdit(parentWidget(), combo, settings);
edit->setToolTip(toolTip); edit->setToolTip(toolTip);
auto revert = new QPushButton; auto revert = new QPushButton;

View File

@ -62,9 +62,9 @@ class OBSHotkeyEdit : public QLineEdit {
Q_OBJECT; Q_OBJECT;
public: public:
OBSHotkeyEdit(obs_key_combination_t original, OBSHotkeyEdit(QWidget *parent, obs_key_combination_t original,
OBSBasicSettings *settings) OBSBasicSettings *settings)
: QLineEdit(nullptr), original(original), settings(settings) : QLineEdit(parent), original(original), settings(settings)
{ {
#ifdef __APPLE__ #ifdef __APPLE__
// disable the input cursor on OSX, focus should be clear // disable the input cursor on OSX, focus should be clear
@ -77,6 +77,19 @@ public:
CreateDupeIcon(); CreateDupeIcon();
ResetKey(); ResetKey();
} }
OBSHotkeyEdit(QWidget *parent = nullptr)
: QLineEdit(parent), original({}), settings(nullptr)
{
#ifdef __APPLE__
// disable the input cursor on OSX, focus should be clear
// enough with the default focus frame
setReadOnly(true);
#endif
setAttribute(Qt::WA_InputMethodEnabled, false);
setAttribute(Qt::WA_MacShowFocusRect, true);
InitSignalHandler();
ResetKey();
}
obs_key_combination_t original; obs_key_combination_t original;
obs_key_combination_t key; obs_key_combination_t key;
@ -116,10 +129,10 @@ class OBSHotkeyWidget : public QWidget {
Q_OBJECT; Q_OBJECT;
public: public:
OBSHotkeyWidget(obs_hotkey_id id, std::string name, OBSHotkeyWidget(QWidget *parent, obs_hotkey_id id, std::string name,
OBSBasicSettings *settings, OBSBasicSettings *settings,
const std::vector<obs_key_combination_t> &combos = {}) const std::vector<obs_key_combination_t> &combos = {})
: QWidget(nullptr), : QWidget(parent),
id(id), id(id),
name(name), name(name),
bindingsChanged(obs_get_signal_handler(), bindingsChanged(obs_get_signal_handler(),

View File

@ -2620,11 +2620,11 @@ LayoutHotkey(OBSBasicSettings *settings, obs_hotkey_id id, obs_hotkey_t *key,
auto combos = keys.find(id); auto combos = keys.find(id);
if (combos == std::end(keys)) if (combos == std::end(keys))
hw = new OBSHotkeyWidget(id, obs_hotkey_get_name(key), hw = new OBSHotkeyWidget(settings, id, obs_hotkey_get_name(key),
settings); settings);
else else
hw = new OBSHotkeyWidget(id, obs_hotkey_get_name(key), settings, hw = new OBSHotkeyWidget(settings, id, obs_hotkey_get_name(key),
combos->second); settings, combos->second);
hw->label = label; hw->label = label;
label->widget = hw; label->widget = hw;
@ -2700,7 +2700,17 @@ static inline void AddHotkeys(
void OBSBasicSettings::LoadHotkeySettings(obs_hotkey_id ignoreKey) void OBSBasicSettings::LoadHotkeySettings(obs_hotkey_id ignoreKey)
{ {
hotkeys.clear(); hotkeys.clear();
ui->hotkeyPage->takeWidget()->deleteLater(); if (ui->hotkeyFormLayout->rowCount() > 0) {
QLayoutItem *forDeletion = ui->hotkeyFormLayout->takeAt(0);
forDeletion->widget()->deleteLater();
delete forDeletion;
}
ui->hotkeyFilterSearch->blockSignals(true);
ui->hotkeyFilterInput->blockSignals(true);
ui->hotkeyFilterSearch->setText("");
ui->hotkeyFilterInput->ResetKey();
ui->hotkeyFilterSearch->blockSignals(false);
ui->hotkeyFilterInput->blockSignals(false);
using keys_t = map<obs_hotkey_id, vector<obs_key_combination_t>>; using keys_t = map<obs_hotkey_id, vector<obs_key_combination_t>>;
keys_t keys; keys_t keys;
@ -2717,104 +2727,14 @@ void OBSBasicSettings::LoadHotkeySettings(obs_hotkey_id ignoreKey)
}, },
&keys); &keys);
auto layout = new QVBoxLayout(); QFormLayout *hotkeysLayout = new QFormLayout();
ui->hotkeyPage->setLayout(layout);
auto widget = new QWidget();
auto scrollArea = new QScrollArea();
scrollArea->setWidgetResizable(true);
scrollArea->setWidget(widget);
auto hotkeysLayout = new QFormLayout();
hotkeysLayout->setVerticalSpacing(0); hotkeysLayout->setVerticalSpacing(0);
hotkeysLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); hotkeysLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
hotkeysLayout->setLabelAlignment(Qt::AlignRight | Qt::AlignTrailing | hotkeysLayout->setLabelAlignment(Qt::AlignRight | Qt::AlignTrailing |
Qt::AlignVCenter); Qt::AlignVCenter);
widget->setLayout(hotkeysLayout); auto hotkeyChildWidget = new QWidget();
hotkeyChildWidget->setLayout(hotkeysLayout);
auto filterLayout = new QGridLayout(); ui->hotkeyFormLayout->addRow(hotkeyChildWidget);
auto filterLabel = new QLabel(QTStr("Basic.Settings.Hotkeys.Filter"));
auto filter = new QLineEdit();
auto filterHotkeyLabel =
new QLabel(QTStr("Basic.Settings.Hotkeys.FilterByHotkey"));
auto filterHotkeyInput = new OBSHotkeyEdit({}, this);
auto filterReset = new QPushButton;
filterReset->setProperty("themeID", "trashIcon");
filterReset->setToolTip(QTStr("Clear"));
filterReset->setFixedSize(24, 24);
filterReset->setFlat(true);
auto setRowVisible = [=](int row, bool visible, QLayoutItem *label) {
label->widget()->setVisible(visible);
auto field = hotkeysLayout->itemAt(row, QFormLayout::FieldRole);
if (field)
field->widget()->setVisible(visible);
};
auto searchFunction = [=](const QString &text,
obs_key_combination_t filterCombo) {
std::vector<obs_key_combination_t> combos;
bool showHotkey;
scrollArea->ensureVisible(0, 0);
scrollArea->setUpdatesEnabled(false);
for (int i = 0; i < hotkeysLayout->rowCount(); i++) {
auto label = hotkeysLayout->itemAt(
i, QFormLayout::LabelRole);
if (!label)
continue;
OBSHotkeyLabel *item =
qobject_cast<OBSHotkeyLabel *>(label->widget());
if (!item)
continue;
item->widget->GetCombinations(combos);
QString fullname =
item->property("fullName").value<QString>();
showHotkey =
text.isEmpty() ||
fullname.toLower().contains(text.toLower());
if (showHotkey &&
!obs_key_combination_is_empty(filterCombo)) {
showHotkey = false;
for (auto combo : combos) {
if (combo == filterCombo) {
showHotkey = true;
continue;
}
}
}
setRowVisible(i, showHotkey, label);
}
scrollArea->setUpdatesEnabled(true);
};
connect(filter, &QLineEdit::textChanged, this, [=](const QString text) {
searchFunction(text, filterHotkeyInput->key);
});
connect(filterHotkeyInput, &OBSHotkeyEdit::KeyChanged, this,
[=](obs_key_combination_t combo) {
searchFunction(filter->text(), combo);
});
connect(filterReset, &QPushButton::clicked, this, [=]() {
filter->setText("");
filterHotkeyInput->ResetKey();
});
filterLayout->addWidget(filterLabel, 0, 0);
filterLayout->addWidget(filter, 0, 1);
filterLayout->addWidget(filterHotkeyLabel, 0, 2);
filterLayout->addWidget(filterHotkeyInput, 0, 3);
filterLayout->addWidget(filterReset, 0, 4);
layout->addLayout(filterLayout);
layout->addWidget(scrollArea);
using namespace std; using namespace std;
using encoders_elem_t = using encoders_elem_t =
@ -2938,9 +2858,9 @@ void OBSBasicSettings::LoadHotkeySettings(obs_hotkey_id ignoreKey)
}); });
connect(hw, &OBSHotkeyWidget::SearchKey, connect(hw, &OBSHotkeyWidget::SearchKey,
[=](obs_key_combination_t combo) { [=](obs_key_combination_t combo) {
filter->setText(""); ui->hotkeyFilterSearch->setText("");
filterHotkeyInput->HandleNewKey(combo); ui->hotkeyFilterInput->HandleNewKey(combo);
filterHotkeyInput->KeyChanged(combo); ui->hotkeyFilterInput->KeyChanged(combo);
}); });
}; };
@ -4343,6 +4263,78 @@ void OBSBasicSettings::HotkeysChanged()
EnableApplyButton(true); EnableApplyButton(true);
} }
void OBSBasicSettings::SearchHotkeys(const QString &text,
obs_key_combination_t filterCombo)
{
if (ui->hotkeyFormLayout->rowCount() == 0)
return;
std::vector<obs_key_combination_t> combos;
bool showHotkey;
ui->hotkeyScrollArea->ensureVisible(0, 0);
ui->hotkeyScrollArea->setUpdatesEnabled(false);
QLayoutItem *hotkeysItem = ui->hotkeyFormLayout->itemAt(0);
QWidget *hotkeys = hotkeysItem->widget();
if (!hotkeys)
return;
QFormLayout *hotkeysLayout =
qobject_cast<QFormLayout *>(hotkeys->layout());
for (int i = 0; i < hotkeysLayout->rowCount(); i++) {
auto label = hotkeysLayout->itemAt(i, QFormLayout::LabelRole);
if (!label)
continue;
OBSHotkeyLabel *item =
qobject_cast<OBSHotkeyLabel *>(label->widget());
if (!item)
continue;
item->widget->GetCombinations(combos);
QString fullname = item->property("fullName").value<QString>();
showHotkey = text.isEmpty() ||
fullname.toLower().contains(text.toLower());
if (showHotkey && !obs_key_combination_is_empty(filterCombo)) {
showHotkey = false;
for (auto combo : combos) {
if (combo == filterCombo) {
showHotkey = true;
continue;
}
}
}
label->widget()->setVisible(showHotkey);
auto field = hotkeysLayout->itemAt(i, QFormLayout::FieldRole);
if (field)
field->widget()->setVisible(showHotkey);
}
ui->hotkeyScrollArea->setUpdatesEnabled(true);
}
void OBSBasicSettings::on_hotkeyFilterReset_clicked()
{
ui->hotkeyFilterSearch->setText("");
ui->hotkeyFilterInput->ResetKey();
}
void OBSBasicSettings::on_hotkeyFilterSearch_textChanged(const QString text)
{
SearchHotkeys(text, ui->hotkeyFilterInput->key);
}
void OBSBasicSettings::on_hotkeyFilterInput_KeyChanged(
obs_key_combination_t combo)
{
SearchHotkeys(ui->hotkeyFilterSearch->text(), combo);
}
static bool MarkHotkeyConflicts(OBSHotkeyLabel *item1, OBSHotkeyLabel *item2) static bool MarkHotkeyConflicts(OBSHotkeyLabel *item1, OBSHotkeyLabel *item2)
{ {
if (item1->pairPartner == item2) if (item1->pairPartner == item2)

View File

@ -268,6 +268,10 @@ private slots:
void on_useStreamKey_clicked(); void on_useStreamKey_clicked();
void on_useAuth_toggled(); void on_useAuth_toggled();
void on_hotkeyFilterReset_clicked();
void on_hotkeyFilterSearch_textChanged(const QString text);
void on_hotkeyFilterInput_KeyChanged(obs_key_combination_t combo);
private: private:
/* output */ /* output */
void LoadSimpleOutputSettings(); void LoadSimpleOutputSettings();
@ -303,6 +307,9 @@ private:
void SaveAdvancedSettings(); void SaveAdvancedSettings();
void SaveSettings(); void SaveSettings();
void SearchHotkeys(const QString &text,
obs_key_combination_t filterCombo);
void UpdateSimpleOutStreamDelayEstimate(); void UpdateSimpleOutStreamDelayEstimate();
void UpdateAdvOutStreamDelayEstimate(); void UpdateAdvOutStreamDelayEstimate();