diff --git a/CMakeLists.txt b/CMakeLists.txt index 703bd7c0..b0af8775 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -238,6 +238,8 @@ target_sources( src/utils/curl-helper.hpp src/utils/duration-control.cpp src/utils/duration-control.hpp + src/utils/item-selection-helpers.cpp + src/utils/item-selection-helpers.hpp src/utils/file-selection.cpp src/utils/file-selection.hpp src/utils/macro-list.cpp diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 00519b2d..38e1fac9 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -769,6 +769,16 @@ AdvSceneSwitcher.askForMacro="Select macro {{macroSelection}}" AdvSceneSwitcher.close="Close" AdvSceneSwitcher.browse="Browse" +AdvSceneSwitcher.item.select="--select item--" +AdvSceneSwitcher.item.add="Add new item" +AdvSceneSwitcher.item.rename="Rename" +AdvSceneSwitcher.item.remove="Remove" +AdvSceneSwitcher.item.properties="Properties" +AdvSceneSwitcher.item.newName="New name:" +AdvSceneSwitcher.item.emptyName="Empty name not allowed!" +AdvSceneSwitcher.item.nameNotAvailable="Name not available!" +AdvSceneSwitcher.item.nameReserved="Name reserved!" + AdvSceneSwitcher.connection.select="--select connection--" AdvSceneSwitcher.connection.add="Add new connection" AdvSceneSwitcher.connection.rename="Rename" diff --git a/src/utils/item-selection-helpers.cpp b/src/utils/item-selection-helpers.cpp new file mode 100644 index 00000000..769c66a2 --- /dev/null +++ b/src/utils/item-selection-helpers.cpp @@ -0,0 +1,317 @@ +#include "item-selection-helpers.hpp" +#include "utility.hpp" +#include "name-dialog.hpp" + +#include +#include +#include +#include +#include + +Q_DECLARE_METATYPE(Item *); + +Item::Item(std::string name) : _name(name) {} + +static Item *GetItemByName(const std::string &name, + std::deque> &items) +{ + for (auto &item : items) { + if (item->Name() == name) { + return item.get(); + } + } + return nullptr; +} + +static Item *GetItemByName(const QString &name, + std::deque> &items) +{ + return GetItemByName(name.toStdString(), items); +} + +bool ItemNameAvailable(const QString &name, + std::deque> &items) +{ + return !GetItemByName(name, items); +} + +bool ItemNameAvailable(const std::string &name, + std::deque> &items) +{ + return ItemNameAvailable(QString::fromStdString(name), items); +} + +ItemSelection::ItemSelection(std::deque> &items, + CreateItemFunc create, SettingsCallback callback, + std::string_view select, std::string_view add, + QWidget *parent) + : QWidget(parent), + _selection(new QComboBox), + _modify(new QPushButton), + _create(create), + _askForSettings(callback), + _items(items), + _selectStr(select), + _addStr(add) +{ + _modify->setMaximumWidth(22); + setButtonIcon(_modify, ":/settings/images/settings/general.svg"); + _modify->setFlat(true); + + // Connect to slots + QWidget::connect(_selection, + SIGNAL(currentTextChanged(const QString &)), this, + SLOT(ChangeSelection(const QString &))); + QWidget::connect(_modify, SIGNAL(clicked()), this, + SLOT(ModifyButtonClicked())); + + QHBoxLayout *layout = new QHBoxLayout; + layout->addWidget(_selection); + layout->addWidget(_modify); + layout->setContentsMargins(0, 0, 0, 0); + setLayout(layout); + + for (const auto &i : items) { + _selection->addItem(QString::fromStdString(i->_name)); + } + _selection->model()->sort(0); + addSelectionEntry(_selection, obs_module_text(_selectStr.data())); + _selection->insertSeparator(_selection->count()); + _selection->addItem(obs_module_text(_addStr.data())); +} + +void ItemSelection::SetItem(const std::string &item) +{ + const QSignalBlocker blocker(_selection); + if (!!GetItemByName(item, _items)) { + _selection->setCurrentText(QString::fromStdString(item)); + } else { + _selection->setCurrentIndex(0); + } +} + +void ItemSelection::ChangeSelection(const QString &sel) +{ + if (sel == obs_module_text(_addStr.data())) { + auto item = _create(); + bool accepted = _askForSettings(this, *item.get()); + if (!accepted) { + _selection->setCurrentIndex(0); + return; + } + _items.emplace_back(item); + const QSignalBlocker b(_selection); + const QString name = QString::fromStdString(item->_name); + AddItem(name); + _selection->setCurrentText(name); + emit ItemAdded(name); + emit SelectionChanged(name); + return; + } + auto item = GetCurrentItem(); + if (item) { + emit SelectionChanged(QString::fromStdString(item->_name)); + } else { + emit SelectionChanged(""); + } +} + +void ItemSelection::ModifyButtonClicked() +{ + auto item = GetCurrentItem(); + if (!item) { + return; + } + auto properties = [&]() { + const auto oldName = item->_name; + bool accepted = _askForSettings(this, *item); + if (!accepted) { + return; + } + if (oldName != item->_name) { + emit ItemRenamed(QString::fromStdString(oldName), + QString::fromStdString(item->_name)); + } + }; + + QMenu menu(this); + + QAction *action = new QAction( + obs_module_text("AdvSceneSwitcher.item.rename"), &menu); + connect(action, SIGNAL(triggered()), this, SLOT(RenameItem())); + action->setProperty("connetion", QVariant::fromValue(item)); + menu.addAction(action); + + action = new QAction(obs_module_text("AdvSceneSwitcher.item.remove"), + &menu); + connect(action, SIGNAL(triggered()), this, SLOT(RemoveItem())); + menu.addAction(action); + + action = new QAction( + obs_module_text("AdvSceneSwitcher.item.properties"), &menu); + connect(action, &QAction::triggered, properties); + menu.addAction(action); + + menu.exec(QCursor::pos()); +} + +void ItemSelection::RenameItem() +{ + QAction *action = reinterpret_cast(sender()); + QVariant variant = action->property("connetion"); + Item *item = variant.value(); + + std::string name; + bool accepted = AdvSSNameDialog::AskForName( + this, obs_module_text("AdvSceneSwitcher.windowTitle"), + obs_module_text("AdvSceneSwitcher.item.newName"), name, + QString::fromStdString(name)); + if (!accepted) { + return; + } + if (name.empty()) { + DisplayMessage("AdvSceneSwitcher.item.emptyName"); + return; + } + if (_selection->currentText().toStdString() != name && + !ItemNameAvailable(name, _items)) { + DisplayMessage("AdvSceneSwitcher.item.nameNotAvailable"); + return; + } + + const auto oldName = item->_name; + item->_name = name; + emit ItemRenamed(QString::fromStdString(oldName), + QString::fromStdString(name)); +} + +void ItemSelection::RenameItem(const QString &oldName, const QString &name) +{ + int idx = _selection->findText(oldName); + if (idx == -1) { + return; + } + _selection->setItemText(idx, name); +} + +void ItemSelection::AddItem(const QString &name) +{ + if (_selection->findText(name) == -1) { + _selection->insertItem(1, name); + } +} + +void ItemSelection::RemoveItem() +{ + auto item = GetCurrentItem(); + if (!item) { + return; + } + + int idx = _selection->findText(QString::fromStdString(item->_name)); + if (idx == -1 || idx == _selection->count()) { + return; + } + + auto name = item->_name; + for (auto it = _items.begin(); it != _items.end(); ++it) { + if (it->get()->_name == item->_name) { + _items.erase(it); + break; + } + } + + emit ItemRemoved(QString::fromStdString(name)); +} + +void ItemSelection::RemoveItem(const QString &name) +{ + const int idx = _selection->findText(name); + if (idx == _selection->currentIndex()) { + _selection->setCurrentIndex(0); + } + _selection->removeItem(idx); +} + +Item *ItemSelection::GetCurrentItem() +{ + return GetItemByName(_selection->currentText(), _items); +} + +ItemSettingsDialog::ItemSettingsDialog(const Item &settings, + std::deque> &items, + std::string_view select, + std::string_view add, QWidget *parent) + : QDialog(parent), + _name(new QLineEdit()), + _nameHint(new QLabel), + _buttonbox(new QDialogButtonBox(QDialogButtonBox::Ok | + QDialogButtonBox::Cancel)), + _items(items), + _selectStr(select), + _addStr(add) +{ + setModal(true); + setWindowModality(Qt::WindowModality::WindowModal); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + setFixedWidth(555); + setMinimumHeight(100); + + _buttonbox->setCenterButtons(true); + _buttonbox->button(QDialogButtonBox::Ok)->setDisabled(true); + + _name->setText(QString::fromStdString(settings._name)); + + QWidget::connect(_name, SIGNAL(textEdited(const QString &)), this, + SLOT(NameChanged(const QString &))); + QWidget::connect(_buttonbox, &QDialogButtonBox::accepted, this, + &QDialog::accept); + QWidget::connect(_buttonbox, &QDialogButtonBox::rejected, this, + &QDialog::reject); + + NameChanged(_name->text()); +} + +void ItemSettingsDialog::NameChanged(const QString &text) +{ + + if (text != _name->text() && !ItemNameAvailable(text, _items)) { + SetNameWarning(obs_module_text( + "AdvSceneSwitcher.item.nameNotAvailable")); + return; + } + if (text.isEmpty()) { + SetNameWarning( + obs_module_text("AdvSceneSwitcher.item.emptyName")); + return; + } + if (text == obs_module_text(_selectStr.data()) || + text == obs_module_text(_addStr.data())) { + SetNameWarning( + obs_module_text("AdvSceneSwitcher.item.nameReserved")); + return; + } + SetNameWarning(""); +} + +void ItemSettingsDialog::SetNameWarning(const QString warn) +{ + if (warn.isEmpty()) { + _nameHint->hide(); + _buttonbox->button(QDialogButtonBox::Ok)->setDisabled(false); + return; + } + _nameHint->setText(warn); + _nameHint->show(); + _buttonbox->button(QDialogButtonBox::Ok)->setDisabled(true); +} + +void Item::Load(obs_data_t *obj) +{ + _name = obs_data_get_string(obj, "name"); +} + +void Item::Save(obs_data_t *obj) const +{ + obs_data_set_string(obj, "name", _name.c_str()); +} diff --git a/src/utils/item-selection-helpers.hpp b/src/utils/item-selection-helpers.hpp new file mode 100644 index 00000000..a106e1f1 --- /dev/null +++ b/src/utils/item-selection-helpers.hpp @@ -0,0 +1,101 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class ItemSelection; +class ItemSettingsDialog; + +class Item { +public: + Item(std::string name); + Item() = default; + virtual ~Item() = default; + + virtual void Load(obs_data_t *obj); + virtual void Save(obs_data_t *obj) const; + std::string Name() const { return _name; } + +protected: + std::string _name = ""; + + friend ItemSelection; + friend ItemSettingsDialog; +}; + +class ItemSettingsDialog : public QDialog { + Q_OBJECT + +public: + ItemSettingsDialog(const Item &, std::deque> &, + std::string_view = "AdvSceneSwitcher.item.select", + std::string_view = "AdvSceneSwitcher.item.select", + QWidget *parent = 0); + virtual ~ItemSettingsDialog() = default; + +private slots: + void NameChanged(const QString &); + +protected: + void SetNameWarning(const QString); + + QLineEdit *_name; + QLabel *_nameHint; + QDialogButtonBox *_buttonbox; + std::deque> &_items; + std::string_view _selectStr; + std::string_view _addStr; +}; + +typedef bool (*SettingsCallback)(QWidget *, Item &); +typedef std::shared_ptr (*CreateItemFunc)(); + +class ItemSelection : public QWidget { + Q_OBJECT + +public: + ItemSelection(std::deque> &, CreateItemFunc, + SettingsCallback, + std::string_view = "AdvSceneSwitcher.item.select", + std::string_view = "AdvSceneSwitcher.item.select", + QWidget *parent = 0); + virtual ~ItemSelection() = default; + void SetItem(const std::string &); + +private slots: + void ModifyButtonClicked(); + void ChangeSelection(const QString &); + void RenameItem(); + void RenameItem(const QString &oldName, const QString &name); + void AddItem(const QString &); + void RemoveItem(); + void RemoveItem(const QString &); +signals: + void SelectionChanged(const QString &); + void ItemAdded(const QString &); + void ItemRemoved(const QString &); + void ItemRenamed(const QString &oldName, const QString &name); + +protected: + Item *GetCurrentItem(); + + QComboBox *_selection; + QPushButton *_modify; + CreateItemFunc _create; + SettingsCallback _askForSettings; + std::deque> &_items; + std::string_view _selectStr; + std::string_view _addStr; +};