From d055f50828a0b33b5228171cd7b5f043187954dd Mon Sep 17 00:00:00 2001
From: Shinichi Hanayama <hanatyan@opensphere.co.jp>
Date: Thu, 22 Aug 2024 11:28:49 +0900
Subject: [PATCH] - fix: Changing source name, scene name, or filter name is
 not reflected in the status Dock. - add: "Enable All" and "Disable All"
 button to status dock. - add: "Enable/Disable" checkbox to status dock's
 filter rows individually. - chore: Update translations for ca-ES, zh-CN,
 ko-KR, ja-JP, en-US, ro-RO, fr-FR, de-DE, uk-UA, and ru-RU locales

---
 buildspec.json             |   2 +-
 data/locale/ca-ES.ini      |   2 +
 data/locale/de-DE.ini      |   4 +-
 data/locale/en-US.ini      |   4 +-
 data/locale/fr-FR.ini      |   4 +-
 data/locale/ja-JP.ini      |   4 +-
 data/locale/ko-KR.ini      |   4 +-
 data/locale/ro-RO.ini      |   4 +-
 data/locale/ru-RU.ini      |   4 +-
 data/locale/uk-UA.ini      |   4 +-
 data/locale/zh-CN.ini      |   4 +-
 src/dock/output-status.cpp | 139 +++++++++++++++++++++++++++++++++----
 src/dock/output-status.hpp |  26 ++++++-
 src/plugin-main.cpp        |   4 +-
 14 files changed, 182 insertions(+), 27 deletions(-)

diff --git a/buildspec.json b/buildspec.json
index 49280c7..16de67b 100644
--- a/buildspec.json
+++ b/buildspec.json
@@ -38,7 +38,7 @@
     },
     "name": "osi-branch-output",
     "displayName": "Branch Output Plugin",
-    "version": "0.9.5",
+    "version": "0.9.6",
     "author": "OPENSPHERE Inc.",
     "website": "https://opensphere.co.jp/",
     "email": "info@opensphere.co.jp",
diff --git a/data/locale/ca-ES.ini b/data/locale/ca-ES.ini
index 6a1e536..ee41577 100644
--- a/data/locale/ca-ES.ini
+++ b/data/locale/ca-ES.ini
@@ -27,3 +27,5 @@ Status.Reconnecting="Tornant a connectar"
 Status.Inactive="Inactiu"
 Status.Active="Actiu"
 Reset="Restableix"
+EnableAll="Activa-ho tot"
+DisableAll="Desactiva-ho tot"
diff --git a/data/locale/de-DE.ini b/data/locale/de-DE.ini
index 27b2ede..15e7ccd 100644
--- a/data/locale/de-DE.ini
+++ b/data/locale/de-DE.ini
@@ -26,4 +26,6 @@ Status.Live="Live"
 Status.Reconnecting="Wiederverbindung"
 Status.Inactive="Inaktiv"
 Status.Active="Aktiv"
-Reset="Zurücksetzen"
\ No newline at end of file
+Reset="Zurücksetzen"
+EnableAll="Alle aktivieren"
+DisableAll="Alle deaktivieren"
\ No newline at end of file
diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini
index 0477952..c1e4550 100644
--- a/data/locale/en-US.ini
+++ b/data/locale/en-US.ini
@@ -26,4 +26,6 @@ Status.Live="Live"
 Status.Reconnecting="Reconnecting"
 Status.Inactive="Inactive"
 Status.Active="Active"
-Reset="Reset"
\ No newline at end of file
+Reset="Reset"
+EnableAll="Activate All"
+DisableAll="Deactivate All"
diff --git a/data/locale/fr-FR.ini b/data/locale/fr-FR.ini
index 80d5e52..930488c 100644
--- a/data/locale/fr-FR.ini
+++ b/data/locale/fr-FR.ini
@@ -26,4 +26,6 @@ Status.Live="En direct"
 Status.Reconnecting="Reconnexion"
 Status.Inactive="Inactif"
 Status.Active="Actif"
-Reset="Réinitialiser"
\ No newline at end of file
+Reset="Réinitialiser"
+EnableAll="Activer tout"
+DisableAll="Désactiver tout"
\ No newline at end of file
diff --git a/data/locale/ja-JP.ini b/data/locale/ja-JP.ini
index 045f2a0..3b1f851 100644
--- a/data/locale/ja-JP.ini
+++ b/data/locale/ja-JP.ini
@@ -26,4 +26,6 @@ Status.Live="配信中"
 Status.Reconnecting="再接続中"
 Status.Inactive="非アクティブ"
 Status.Active="アクティブ"
-Reset="リセット"
\ No newline at end of file
+Reset="リセット"
+EnableAll="全て有効化"
+DisableAll="全て無効化"
diff --git a/data/locale/ko-KR.ini b/data/locale/ko-KR.ini
index 90a1e98..04e9228 100644
--- a/data/locale/ko-KR.ini
+++ b/data/locale/ko-KR.ini
@@ -26,4 +26,6 @@ Status.Live="살다"
 Status.Reconnecting="다시 연결"
 Status.Inactive="비활성"
 Status.Active="활동적인"
-Reset="리셋"
\ No newline at end of file
+Reset="리셋"
+EnableAll="모두 활성화"
+DisableAll="모두 무효화"
diff --git a/data/locale/ro-RO.ini b/data/locale/ro-RO.ini
index 52ab61f..8b7e48b 100644
--- a/data/locale/ro-RO.ini
+++ b/data/locale/ro-RO.ini
@@ -26,4 +26,6 @@ Status.Live="Trăi"
 Status.Reconnecting="Reconectare"
 Status.Inactive="Inactiv"
 Status.Active="Activ"
-Reset="Resetați"
\ No newline at end of file
+Reset="Resetați"
+EnableAll="Activați toate"
+DisableAll="Dezactivați toate"
\ No newline at end of file
diff --git a/data/locale/ru-RU.ini b/data/locale/ru-RU.ini
index 5b81ff7..4461079 100644
--- a/data/locale/ru-RU.ini
+++ b/data/locale/ru-RU.ini
@@ -26,4 +26,6 @@ Status.Live="Жить"
 Status.Reconnecting="Повторное подключение"
 Status.Inactive="Неактивный"
 Status.Active="Активный"
-Reset="Перезагрузить"
\ No newline at end of file
+Reset="Перезагрузить"
+EnableAll="Активировать все"
+DisableAll="Деактивировать все"
\ No newline at end of file
diff --git a/data/locale/uk-UA.ini b/data/locale/uk-UA.ini
index 28eb0f2..8eabbce 100644
--- a/data/locale/uk-UA.ini
+++ b/data/locale/uk-UA.ini
@@ -26,4 +26,6 @@ Status.Live="Жити"
 Status.Reconnecting="Повторне підключення"
 Status.Inactive="Неактивний"
 Status.Active="Активний"
-Reset="Скидання"
\ No newline at end of file
+Reset="Скидання"
+EnableAll="Активувати всі"
+DisableAll="Деактивувати всі"
\ No newline at end of file
diff --git a/data/locale/zh-CN.ini b/data/locale/zh-CN.ini
index 6ee6fe8..77afca0 100644
--- a/data/locale/zh-CN.ini
+++ b/data/locale/zh-CN.ini
@@ -26,4 +26,6 @@ Status.Live="活的"
 Status.Reconnecting="重新连接"
 Status.Inactive="不活跃"
 Status.Active="积极的"
-Reset="重置"
\ No newline at end of file
+Reset="重置"
+EnableAll="全部启用"
+DisableAll="全部停用"
diff --git a/src/dock/output-status.cpp b/src/dock/output-status.cpp
index 549e5b5..3643bcd 100644
--- a/src/dock/output-status.cpp
+++ b/src/dock/output-status.cpp
@@ -26,6 +26,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
 #include <QTableWidgetItem>
 #include <QHeaderView>
 #include <QPushButton>
+#include <QHBoxLayout>
+#include <QCheckBox>
 #include <plugin-main.hpp>
 #include "output-status.hpp"
 
@@ -53,13 +55,13 @@ BranchOutputStatus::BranchOutputStatus(QWidget *parent) : QFrame(parent), timer(
     outputTable->setColumnCount(7);
 
     int col = 0;
-    outputTable->setHorizontalHeaderItem(col++, new QTableWidgetItem(QTStr("SourceName")));
     outputTable->setHorizontalHeaderItem(col++, new QTableWidgetItem(QTStr("FilterName")));
+    outputTable->setHorizontalHeaderItem(col++, new QTableWidgetItem(QTStr("SourceName")));
     outputTable->setHorizontalHeaderItem(col++, new QTableWidgetItem(QTStr("Status")));
     outputTable->setHorizontalHeaderItem(col++, new QTableWidgetItem(QTStr("DropFrames")));
     outputTable->setHorizontalHeaderItem(col++, new QTableWidgetItem(QTStr("SentDataSize")));
     outputTable->setHorizontalHeaderItem(col++, new QTableWidgetItem(QTStr("BitRate")));
-    outputTable->setHorizontalHeaderItem(col++, new QTableWidgetItem(QTStr("")));
+    outputTable->setHorizontalHeaderItem(col++, new QTableWidgetItem(QString::fromUtf8("")));
 
     QObject::connect(&timer, &QTimer::timeout, this, &BranchOutputStatus::Update);
 
@@ -68,45 +70,79 @@ BranchOutputStatus::BranchOutputStatus(QWidget *parent) : QFrame(parent), timer(
         timer.start();
     }
 
+    // Tool buttons
+    auto enableAllButton = new QPushButton(QTStr("EnableAll"));
+    connect(enableAllButton, &QPushButton::clicked, [this]() { SetEabnleAll(true); });
+
+    auto disableAllButton = new QPushButton(QTStr("DisableAll"));
+    connect(disableAllButton, &QPushButton::clicked, [this]() { SetEabnleAll(false); });
+
+    auto buttonsContainerLayout = new QHBoxLayout();
+    buttonsContainerLayout->addWidget(enableAllButton);
+    buttonsContainerLayout->addWidget(disableAllButton);
+    buttonsContainerLayout->addStretch();
+
     QVBoxLayout *outputContainerLayout = new QVBoxLayout();
     outputContainerLayout->addWidget(outputTable);
-
+    outputContainerLayout->addLayout(buttonsContainerLayout);
     this->setLayout(outputContainerLayout);
 }
 
 BranchOutputStatus::~BranchOutputStatus() {}
 
-void BranchOutputStatus::AddOutputLabels(QString parentName, filter_t *filter)
+void BranchOutputStatus::AddOutputLabels(filter_t *filter)
 {
+    auto parent = obs_filter_get_parent(filter->source);
     OutputLabels ol;
     ol.filter = filter;
 
-    ol.parentName = new QTableWidgetItem(parentName);
-    ol.name = new QTableWidgetItem(QTStr(obs_source_get_name(filter->source)));
-    ol.status = new QLabel(QTStr("Status.Inactive"));
-    ol.droppedFrames = new QLabel(QTStr(""));
-    ol.megabytesSent = new QLabel(QTStr(""));
-    ol.bitrate = new QLabel(QTStr(""));
+    ol.filterItem = new FilterItem(QString::fromUtf8(obs_source_get_name(filter->source)), filter->source, this);
+    ol.parentName = new QTableWidgetItem(QString::fromUtf8(obs_source_get_name(parent)));
+    ol.status = new QLabel(QTStr("Status.Inactive"), this);
+    ol.droppedFrames = new QLabel(QString::fromUtf8(""), this);
+    ol.megabytesSent = new QLabel(QString::fromUtf8(""), this);
+    ol.bitrate = new QLabel(QString::fromUtf8(""), this);
 
     auto col = 0;
     auto row = (int)outputLabels.size();
 
     outputTable->setRowCount(row + 1);
+    outputTable->setCellWidget(row, col++, ol.filterItem);
     outputTable->setItem(row, col++, ol.parentName);
-    outputTable->setItem(row, col++, ol.name);
     outputTable->setCellWidget(row, col++, ol.status);
     outputTable->setCellWidget(row, col++, ol.droppedFrames);
     outputTable->setCellWidget(row, col++, ol.megabytesSent);
     outputTable->setCellWidget(row, col++, ol.bitrate);
 
+    outputTable->setRowHeight(row, 32);
+
     outputLabels.push_back(ol);
 
     // Setup reset button
+    auto resetButtonContainer = new QWidget(this);
+    auto resetButtonLayout = new QHBoxLayout();
+    resetButtonLayout->setContentsMargins(0, 0, 0, 0);
+    resetButtonContainer->setLayout(resetButtonLayout);
+
     auto resetButton = new QPushButton(QTStr("Reset"), this);
     connect(resetButton, &QPushButton::clicked, [this, row]() { outputLabels[row].Reset(); });
+    resetButton->setProperty("toolButton", true);
     resetButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
-    resetButton->setMinimumHeight(27);
-    outputTable->setCellWidget(row, col, resetButton);
+
+    resetButtonLayout->addWidget(resetButton);
+    outputTable->setCellWidget(row, col, resetButtonContainer);
+
+    // Listen signal for filter update
+    signal_handler_connect(
+        obs_source_get_signal_handler(filter->source), "rename", BranchOutputStatus::OutputLabels::FilterRenamed,
+        outputLabels[row].filterItem
+    );
+
+    // Listen signal for parent update
+    signal_handler_connect(
+        obs_source_get_signal_handler(parent), "rename", BranchOutputStatus::OutputLabels::ParentRenamed,
+        outputLabels[row].parentName
+    );
 }
 
 void BranchOutputStatus::RemoveOutputLabels(filter_t *filter)
@@ -115,6 +151,17 @@ void BranchOutputStatus::RemoveOutputLabels(filter_t *filter)
         if (outputLabels[i].filter == filter) {
             outputLabels.removeAt(i);
             outputTable->removeRow(i);
+
+            signal_handler_disconnect(
+                obs_source_get_signal_handler(filter->source), "rename",
+                BranchOutputStatus::OutputLabels::FilterRenamed, outputLabels[i].filterItem
+            );
+
+            signal_handler_disconnect(
+                obs_source_get_signal_handler(obs_filter_get_parent(filter->source)), "rename",
+                BranchOutputStatus::OutputLabels::ParentRenamed, outputLabels[i].parentName
+            );
+
             break;
         }
     }
@@ -137,6 +184,13 @@ void BranchOutputStatus::hideEvent(QHideEvent *)
     timer.stop();
 }
 
+void BranchOutputStatus::SetEabnleAll(bool enabled)
+{
+    for (int i = 0; i < outputLabels.size(); i++) {
+        obs_source_set_enabled(outputLabels[i].filter->source, enabled);
+    }
+}
+
 // Imitate UI/window-basic-stats.cpp
 void setThemeID(QWidget *widget, const QString &themeID)
 {
@@ -150,6 +204,8 @@ void setThemeID(QWidget *widget, const QString &themeID)
     }
 }
 
+// BranchOutputStatus::OutputLabels structure
+
 // Imitate UI/window-basic-stats.cpp
 void BranchOutputStatus::OutputLabels::Update(bool rec)
 {
@@ -257,3 +313,60 @@ void BranchOutputStatus::OutputLabels::Reset()
     megabytesSent->setText(QString("0 MiB"));
     bitrate->setText(QString("0 kb/s"));
 }
+
+void BranchOutputStatus::OutputLabels::FilterRenamed(void *data, calldata_t *cd)
+{
+    auto item = (FilterItem *)data;
+    auto newName = calldata_string(cd, "new_name");
+    item->SetText(QString::fromUtf8(newName));
+}
+
+void BranchOutputStatus::OutputLabels::ParentRenamed(void *data, calldata_t *cd)
+{
+    auto label = (QTableWidgetItem *)data;
+    auto newName = calldata_string(cd, "new_name");
+    label->setText(QString::fromUtf8(newName));
+}
+
+// FilterItem class
+
+FilterItem::FilterItem(QString text, obs_source_t *_source, QWidget *parent) : QWidget(parent)
+{
+    setMinimumHeight(27);
+    source = _source;
+
+    visibilityCheckbox = new QCheckBox(this);
+    visibilityCheckbox->setProperty("visibilityCheckBox", true);
+    visibilityCheckbox->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
+    visibilityCheckbox->setChecked(obs_source_enabled(source));
+
+    connect(visibilityCheckbox, &QCheckBox::clicked, [this](bool visible) { obs_source_set_enabled(source, visible); });
+
+    name = new QLabel(text, this);
+
+    auto checkboxLayout = new QHBoxLayout();
+    checkboxLayout->setContentsMargins(0, 0, 0, 0);
+    checkboxLayout->addWidget(visibilityCheckbox);
+    checkboxLayout->addWidget(name);
+    setLayout(checkboxLayout);
+
+    // Listen signal for filter enabled/disabled
+    signal_handler_connect(obs_source_get_signal_handler(source), "enable", FilterItem::VisibilityChanged, this);
+}
+
+FilterItem::~FilterItem()
+{
+    signal_handler_disconnect(obs_source_get_signal_handler(source), "enable", FilterItem::VisibilityChanged, this);
+}
+
+void FilterItem::SetText(QString text)
+{
+    name->setText(text);
+}
+
+void FilterItem::VisibilityChanged(void *data, calldata_t *cd)
+{
+    auto item = (FilterItem *)data;
+    auto enabled = calldata_bool(cd, "enabled");
+    item->visibilityCheckbox->setChecked(enabled);
+}
diff --git a/src/dock/output-status.hpp b/src/dock/output-status.hpp
index 30e6580..f94bdff 100644
--- a/src/dock/output-status.hpp
+++ b/src/dock/output-status.hpp
@@ -28,15 +28,33 @@ class QTableWidget;
 class QTableWidgetItem;
 class QLabel;
 class QString;
+class QCheckBox;
 struct filter_t;
 
+class FilterItem : public QWidget {
+    Q_OBJECT
+
+    obs_source_t *source;
+
+    QCheckBox *visibilityCheckbox;
+    QLabel *name;
+
+public:
+    FilterItem(QString text, obs_source_t *_source, QWidget *parent = (QWidget *)nullptr);
+    ~FilterItem();
+
+    void SetText(QString text);
+
+    static void VisibilityChanged(void *data, calldata_t *cd);
+};
+
 class BranchOutputStatus : public QFrame {
     Q_OBJECT
 
     struct OutputLabels {
         filter_t *filter;
+        FilterItem *filterItem;
         QTableWidgetItem *parentName;
-        QTableWidgetItem *name;
         QLabel *status;
         QLabel *droppedFrames;
         QLabel *megabytesSent;
@@ -51,6 +69,9 @@ class BranchOutputStatus : public QFrame {
         void Update(bool rec);
         void Reset();
 
+        static void FilterRenamed(void *data, calldata_t *cd);
+        static void ParentRenamed(void *data, calldata_t *cd);
+
         long double kbps = 0.0l;
     };
 
@@ -64,8 +85,9 @@ class BranchOutputStatus : public QFrame {
     BranchOutputStatus(QWidget *parent = (QWidget *)nullptr);
     ~BranchOutputStatus();
 
-    void AddOutputLabels(QString parentName, filter_t *filter);
+    void AddOutputLabels(filter_t *filter);
     void RemoveOutputLabels(filter_t *filter);
+    void SetEabnleAll(bool enabled);
 
 protected:
     virtual void showEvent(QShowEvent *event) override;
diff --git a/src/plugin-main.cpp b/src/plugin-main.cpp
index 51fb33f..459b41b 100644
--- a/src/plugin-main.cpp
+++ b/src/plugin-main.cpp
@@ -513,11 +513,11 @@ void video_tick(void *data, float)
 
 BranchOutputStatus *status_dock = nullptr;
 
-void filter_add(void *data, obs_source_t *parent)
+void filter_add(void *data, obs_source_t *)
 {
     // Register to output status dock
     auto filter = (filter_t *)data;
-    status_dock->AddOutputLabels(QTStr(obs_source_get_name(parent)), filter);
+    status_dock->AddOutputLabels(filter);
 }
 
 void filter_remove(void *data, obs_source_t *)