diff --git a/damc_gui/BalanceController.cpp b/damc_gui/BalanceController.cpp index 1184123..605451b 100644 --- a/damc_gui/BalanceController.cpp +++ b/damc_gui/BalanceController.cpp @@ -5,7 +5,7 @@ #include BalanceController::BalanceController(OutputController* parent, OscContainer* oscParent) - : QDialog(parent), ui(new Ui::BalanceController), oscBalances(oscParent, "balance") { + : ManagedVisibilityWidget(parent), ui(new Ui::BalanceController), oscBalances(oscParent, "balance") { ui->setupUi(this); oscBalances.setWidget( this, ui->verticalLayout, [parent](QWidget* parentWidget, OscContainer* oscParent, int name) -> QWidget* { @@ -13,6 +13,7 @@ BalanceController::BalanceController(OutputController* parent, OscContainer* osc balance->addChangeCallback([parent](bool) { parent->updateBalanceEnable(); }); return balance; }); + manageWidgetsVisiblity(); } BalanceController::~BalanceController() { diff --git a/damc_gui/BalanceController.h b/damc_gui/BalanceController.h index b79792a..9bbb5bb 100644 --- a/damc_gui/BalanceController.h +++ b/damc_gui/BalanceController.h @@ -2,6 +2,7 @@ #include "OscWidgetArray.h" #include +#include "ManagedVisibilityWidget.h" namespace Ui { class BalanceController; @@ -11,7 +12,7 @@ class Balance; class Balance; class OutputController; -class BalanceController : public QDialog { +class BalanceController : public ManagedVisibilityWidget { Q_OBJECT public: diff --git a/damc_gui/CompressorController.cpp b/damc_gui/CompressorController.cpp index 039a7f5..99f7079 100644 --- a/damc_gui/CompressorController.cpp +++ b/damc_gui/CompressorController.cpp @@ -3,7 +3,7 @@ #include CompressorController::CompressorController(QWidget* parent, OscContainer* oscParent, const std::string& name) - : QDialog(parent), + : ManagedVisibilityWidget(parent), OscContainer(oscParent, name), ui(new Ui::CompressorController), oscEnablePeak(this, "enable"), @@ -38,6 +38,8 @@ CompressorController::CompressorController(QWidget* parent, OscContainer* oscPar oscReleaseTime.setScale(1000); oscAttackTime.setScale(1000); oscLufsIntegrationTime.setScale(1000); + + manageWidgetsVisiblity(); } CompressorController::~CompressorController() { diff --git a/damc_gui/CompressorController.h b/damc_gui/CompressorController.h index d6d0e3d..70e9359 100644 --- a/damc_gui/CompressorController.h +++ b/damc_gui/CompressorController.h @@ -1,6 +1,8 @@ #pragma once +#include "ManagedVisibilityWidget.h" #include "OscWidgetMapper.h" +#include "WidgetAutoHidden.h" #include #include @@ -8,7 +10,7 @@ namespace Ui { class CompressorController; } -class CompressorController : public QDialog, public OscContainer { +class CompressorController : public ManagedVisibilityWidget, public OscContainer { public: explicit CompressorController(QWidget* parent, OscContainer* oscParent, const std::string& name); ~CompressorController(); diff --git a/damc_gui/GlobalConfigDialog.cpp b/damc_gui/GlobalConfigDialog.cpp index 9e3666f..06acede 100644 --- a/damc_gui/GlobalConfigDialog.cpp +++ b/damc_gui/GlobalConfigDialog.cpp @@ -3,7 +3,7 @@ #include GlobalConfigDialog::GlobalConfigDialog(QWidget* parent, OscContainer* oscParent) - : QDialog(parent), + : ManagedVisibilityWidget(parent), ui(new Ui::GlobalConfigDialog), oscEnableAutoConnect(oscParent, "enableAutoConnect"), oscMonitorConnections(oscParent, "enableConnectionMonitoring"), @@ -59,6 +59,8 @@ GlobalConfigDialog::GlobalConfigDialog(QWidget* parent, OscContainer* oscParent) memoryAvailable.setWidget(ui->availableMemorySpinBox, false); memoryAvailable.addChangeCallback([this](int32_t value) { updateMemoryUsagePercent(); }); + + manageWidgetsVisiblity(); } GlobalConfigDialog::~GlobalConfigDialog() { diff --git a/damc_gui/GlobalConfigDialog.h b/damc_gui/GlobalConfigDialog.h index 289dd79..8204857 100644 --- a/damc_gui/GlobalConfigDialog.h +++ b/damc_gui/GlobalConfigDialog.h @@ -1,13 +1,15 @@ #pragma once +#include "ManagedVisibilityWidget.h" #include "OscWidgetMapper.h" +#include "WidgetAutoHidden.h" #include namespace Ui { class GlobalConfigDialog; } -class GlobalConfigDialog : public QDialog { +class GlobalConfigDialog : public ManagedVisibilityWidget { Q_OBJECT public: diff --git a/damc_gui/MainWindow.cpp b/damc_gui/MainWindow.cpp index 6b56643..103c459 100644 --- a/damc_gui/MainWindow.cpp +++ b/damc_gui/MainWindow.cpp @@ -4,16 +4,23 @@ #include "ui_MainWindow.h" #include -MainWindow::MainWindow(OscRoot* oscRoot, QWidget* parent) +MainWindow::MainWindow(OscRoot* oscRoot, bool isMicrocontrollerDamc, QWidget* parent) : QWidget(parent), ui(new Ui::MainWindow), oscRoot(oscRoot), + isMicrocontrollerDamc(isMicrocontrollerDamc), outputInterfaces(oscRoot, "strip"), oscTypeArray(oscRoot, "type_list"), oscPortaudioDeviceArray(oscRoot, "device_list"), oscWasapiDeviceArray(oscRoot, "device_list_wasapi") { ui->setupUi(this); + if(isMicrocontrollerDamc) { + ui->showDisabledCheckBox->hide(); + ui->addButton->hide(); + ui->removeButton->hide(); + } + globalConfigDialog = new GlobalConfigDialog(this, oscRoot); connect(globalConfigDialog, &GlobalConfigDialog::showStateChanged, [this](bool shown) { ui->globalConfigButton->setChecked(shown); @@ -21,7 +28,7 @@ MainWindow::MainWindow(OscRoot* oscRoot, QWidget* parent) outputInterfaces.setWidget( this, ui->horizontalLayout, [this](QWidget*, OscContainer* oscParent, int name) -> QWidget* { - return new OutputController(this, oscParent, std::to_string(name)); + return new OutputController(this, oscParent, std::to_string(name), this->isMicrocontrollerDamc); }); connect(ui->globalConfigButton, &QAbstractButton::clicked, this, &MainWindow::onShowGlobalConfig); diff --git a/damc_gui/MainWindow.h b/damc_gui/MainWindow.h index 11aa334..a0aa0b7 100644 --- a/damc_gui/MainWindow.h +++ b/damc_gui/MainWindow.h @@ -15,7 +15,7 @@ class MainWindow : public QWidget { Q_OBJECT public: - explicit MainWindow(OscRoot* oscRoot, QWidget* parent = 0); + explicit MainWindow(OscRoot* oscRoot, bool isMicrocontrollerDamc, QWidget* parent = 0); ~MainWindow(); void removeInstance(int key); @@ -41,6 +41,8 @@ protected slots: Ui::MainWindow* ui; OscRoot* oscRoot; + bool isMicrocontrollerDamc; + OscWidgetArray outputInterfaces; GlobalConfigDialog* globalConfigDialog; diff --git a/damc_gui/MainWindow.ui b/damc_gui/MainWindow.ui index e39ffd9..a0df61f 100644 --- a/damc_gui/MainWindow.ui +++ b/damc_gui/MainWindow.ui @@ -6,8 +6,8 @@ 0 0 - 271 - 409 + 410 + 451 @@ -15,13 +15,27 @@ - + - Remove + Add + + + + + + + Show disabled + + + + + + + Config - + @@ -42,26 +56,25 @@ - - - - Add - - - - - + + - Show disabled + Remove - - - Config + + + Qt::Horizontal - + + + 40 + 20 + + + diff --git a/damc_gui/ManagedVisibilityWidget.h b/damc_gui/ManagedVisibilityWidget.h new file mode 100644 index 0000000..2695bf2 --- /dev/null +++ b/damc_gui/ManagedVisibilityWidget.h @@ -0,0 +1,12 @@ +#pragma once + +#include "WidgetAutoHidden.h" + +template class ManagedVisibilityWidget : public T { +public: + using T::T; + void manageWidgetsVisiblity() { widgetAutoHidden.addContainerWidgets({this}); } + +private: + WidgetAutoHidden widgetAutoHidden; +}; diff --git a/damc_gui/OscWidgetMapper.cpp b/damc_gui/OscWidgetMapper.cpp index 71a1370..5713aa8 100644 --- a/damc_gui/OscWidgetMapper.cpp +++ b/damc_gui/OscWidgetMapper.cpp @@ -12,6 +12,8 @@ template void OscWidgetMapper::setWidget(T* widget, bool updateOnChange) { this->widgets.push_back(widget); + widget->setVisible(false); + updateWidget(widget); if(updateOnChange) { @@ -22,6 +24,8 @@ void OscWidgetMapper::setWidget(T* widget, bool updateOnChang notifyChanged(widget); }); } else { + // Stateless buttons won't receive any value, so never hide them + widget->setVisible(true); connect(widget, &T::clicked, [this, widget](bool value) { this->value = 1; notifyChanged(widget); @@ -80,6 +84,12 @@ void OscWidgetMapper::execute(const std::vector& if(!getArgumentAs(arguments[0], value)) return; + for(T* widget : widgets) { + if(widget->isHidden()) { + widget->setVisible(true); + } + } + this->defaultValue = false; if(this->value == value) diff --git a/damc_gui/OscWidgetMapper.h b/damc_gui/OscWidgetMapper.h index 8f6ad0e..2a15bdf 100644 --- a/damc_gui/OscWidgetMapper.h +++ b/damc_gui/OscWidgetMapper.h @@ -14,13 +14,27 @@ template struct OscWidgetMapperType {}; -template<> struct OscWidgetMapperType { using type = int32_t; }; -template<> struct OscWidgetMapperType { using type = bool; }; -template<> struct OscWidgetMapperType { using type = float; }; -template<> struct OscWidgetMapperType { using type = int32_t; }; -template<> struct OscWidgetMapperType { using type = int32_t; }; -template<> struct OscWidgetMapperType { using type = bool; }; -template<> struct OscWidgetMapperType { using type = std::string; }; +template<> struct OscWidgetMapperType { + using type = int32_t; +}; +template<> struct OscWidgetMapperType { + using type = bool; +}; +template<> struct OscWidgetMapperType { + using type = float; +}; +template<> struct OscWidgetMapperType { + using type = int32_t; +}; +template<> struct OscWidgetMapperType { + using type = int32_t; +}; +template<> struct OscWidgetMapperType { + using type = bool; +}; +template<> struct OscWidgetMapperType { + using type = std::string; +}; template::type> class OscWidgetMapper : public QObject, public OscContainer { diff --git a/damc_gui/OutputController.cpp b/damc_gui/OutputController.cpp index 0c1c475..d78bb0e 100644 --- a/damc_gui/OutputController.cpp +++ b/damc_gui/OutputController.cpp @@ -16,7 +16,10 @@ #define DRAG_FORMAT "application/x-dndwaveoutputinstancewidget" -OutputController::OutputController(MainWindow* parent, OscContainer* oscParent, const std::string& name) +OutputController::OutputController(MainWindow* parent, + OscContainer* oscParent, + const std::string& name, + bool isMicrocontrollerDamc) : QWidget(parent), OscContainer(oscParent, name), ui(new Ui::OutputController), @@ -38,6 +41,11 @@ OutputController::OutputController(MainWindow* parent, OscContainer* oscParent, // By default hide, it will be shown if enabled hide(); + if(isMicrocontrollerDamc) { + // No add/remove with microcontroller DAMC + ui->removeButton->hide(); + } + oscEnable.setWidget(ui->enableCheckBox); oscMute.setWidget(ui->muteButton); oscDelay.setWidget(ui->delaySpinBox); diff --git a/damc_gui/OutputController.h b/damc_gui/OutputController.h index 3bf635f..39cbdc8 100644 --- a/damc_gui/OutputController.h +++ b/damc_gui/OutputController.h @@ -22,7 +22,10 @@ class LevelMeterWidget; class OutputController : public QWidget, public OscContainer { Q_OBJECT public: - explicit OutputController(MainWindow* parent, OscContainer* oscParent, const std::string& name); + explicit OutputController(MainWindow* parent, + OscContainer* oscParent, + const std::string& name, + bool isMicrocontrollerDamc); ~OutputController(); void showConfigDialog(); diff --git a/damc_gui/OutputController.ui b/damc_gui/OutputController.ui index 7802c81..101b570 100644 --- a/damc_gui/OutputController.ui +++ b/damc_gui/OutputController.ui @@ -6,7 +6,7 @@ 0 0 - 70 + 77 391 @@ -50,7 +50,7 @@ 40 - 20 + 0 @@ -95,7 +95,7 @@ 0 - + Qt::Horizontal @@ -217,7 +217,7 @@ - + Qt::Horizontal diff --git a/damc_gui/OutputInstanceConfigDialog.cpp b/damc_gui/OutputInstanceConfigDialog.cpp index 2686b0e..cf30445 100644 --- a/damc_gui/OutputInstanceConfigDialog.cpp +++ b/damc_gui/OutputInstanceConfigDialog.cpp @@ -5,7 +5,7 @@ #include "ui_OutputInstanceConfigDialog.h" OutputInstanceConfigDialog::OutputInstanceConfigDialog(MainWindow* mainWindow, OutputController* parent) - : QDialog(parent), + : ManagedVisibilityWidget(parent), OscContainer(parent, "device"), ui(new Ui::OutputInstanceConfigDialog), mainWindow(mainWindow), @@ -62,6 +62,8 @@ OutputInstanceConfigDialog::OutputInstanceConfigDialog(MainWindow* mainWindow, O oscDeviceRealSampleRate.setWidget(ui->measuredDeviceSampleRateSpinBox); oscType.addChangeCallback([this](int) { updateGroupBoxes(); }); + + manageWidgetsVisiblity(); } OutputInstanceConfigDialog::~OutputInstanceConfigDialog() { diff --git a/damc_gui/OutputInstanceConfigDialog.h b/damc_gui/OutputInstanceConfigDialog.h index fc8ced3..330fc52 100644 --- a/damc_gui/OutputInstanceConfigDialog.h +++ b/damc_gui/OutputInstanceConfigDialog.h @@ -1,5 +1,6 @@ #pragma once +#include "ManagedVisibilityWidget.h" #include "OscWidgetMapper.h" #include #include @@ -12,7 +13,7 @@ class OutputInstanceConfigDialog; class MainWindow; class OutputController; -class OutputInstanceConfigDialog : public QDialog, public OscContainer { +class OutputInstanceConfigDialog : public ManagedVisibilityWidget, public OscContainer { Q_OBJECT public: diff --git a/damc_gui/SerialPortInterface.cpp b/damc_gui/SerialPortInterface.cpp index 9878ca3..3b81b91 100644 --- a/damc_gui/SerialPortInterface.cpp +++ b/damc_gui/SerialPortInterface.cpp @@ -25,7 +25,7 @@ void SerialPortInterface::onOscDataReceived() { receivedData = oscSerialPort.readAll(); // printf("data: %s\n", receivedData.constData()); - SPDLOG_DEBUG("Received OSC data: {:a}", spdlog::to_hex(receivedData)); + SPDLOG_TRACE("Received OSC data: {:a}", spdlog::to_hex(receivedData)); OscConnector::onOscDataReceived((const uint8_t*) receivedData.data(), receivedData.size()); } while(oscSerialPort.bytesAvailable()); } @@ -58,6 +58,7 @@ void SerialPortInterface::onOscReconnect() { if(portFound) { if(oscSerialPort.open(QIODevice::ReadWrite)) { SPDLOG_INFO("Opened: {}", oscSerialPort.portName().toStdString()); + oscSerialPort.clear(); updateOscVariables(); } else { SPDLOG_ERROR("Open failed: {}", oscSerialPort.errorString().toStdString()); diff --git a/damc_gui/WidgetAutoHidden.cpp b/damc_gui/WidgetAutoHidden.cpp new file mode 100644 index 0000000..e351a02 --- /dev/null +++ b/damc_gui/WidgetAutoHidden.cpp @@ -0,0 +1,164 @@ +#include "WidgetAutoHidden.h" +#include +#include +#include +#include +#include +#include +#include + +WidgetAutoHidden::WidgetAutoHidden() noexcept : widgetsShown(false) {} + +void WidgetAutoHidden::addContainerWidgets(std::initializer_list containerWidgets) { + for(QWidget* containerWidget : containerWidgets) { + SPDLOG_DEBUG("Object {} contains:", containerWidget->objectName().toStdString()); + + QList labelWidgets = containerWidget->findChildren(Qt::FindDirectChildrenOnly); + bool isGroupBox = qobject_cast(containerWidget) != nullptr; + + for(QObject* child : containerWidget->children()) { + QWidget* childAsWidget = qobject_cast(child); + if(!childAsWidget) + continue; + + if(qobject_cast(child)) + continue; + + SPDLOG_DEBUG( + "- {} (class {})", childAsWidget->objectName().toStdString(), childAsWidget->metaObject()->className()); + + // Recurse in QGroupBox and QWidget + if(qobject_cast(child) || + strcmp(child->metaObject()->className(), QWidget::staticMetaObject.className()) == 0) { + addContainerWidgets({childAsWidget}); + } + + // Add matching label to labels map + // When a widget is hidden/shown, its matching label is also hidden/shown + qsizetype longestMatchLength = 0; + QLabel* longestMatch = nullptr; + QString childObjectName = child->objectName(); + for(QLabel* label : labelWidgets) { + QString baseName = label->objectName().replace("Label", ""); + if((longestMatch == nullptr || baseName.size() > longestMatchLength) && + childObjectName.startsWith(baseName)) { + longestMatch = label; + longestMatchLength = baseName.size(); + } + } + + if(longestMatch) { + SPDLOG_DEBUG("Found matching QLabel {} for {}", + longestMatch->objectName().toStdString(), + child->objectName().toStdString()); + this->labelWidgetsByWidgetMap.emplace(child, longestMatch); + longestMatch->setVisible(!childAsWidget->isHidden()); + } + + // Only handle visibility of GroupBoxes + if(isGroupBox) { + this->containerWidgetsByChildMap.emplace(child, containerWidget); + this->childByContainerWidgetMap.emplace(containerWidget, childAsWidget); + } + + childAsWidget->installEventFilter(this); + } + + if(isGroupBox) { + updateContainerVisibility(containerWidget); + } + containerWidget->adjustSize(); + } +} + +void WidgetAutoHidden::updateContainerVisibility(QWidget* containerWidget) { + SPDLOG_DEBUG("Updating visibility of {}", containerWidget->objectName().toStdString()); + + bool allHidden = true; + auto childrenRange = childByContainerWidgetMap.equal_range(containerWidget); + for(auto itChildInContainer = childrenRange.first; itChildInContainer != childrenRange.second; + ++itChildInContainer) { + if(!itChildInContainer->second->isHidden()) { + allHidden = false; + SPDLOG_DEBUG("- {} shown", itChildInContainer->second->objectName().toStdString()); + break; + } else { + SPDLOG_DEBUG("- {} hidden", itChildInContainer->second->objectName().toStdString()); + } + } + + if(allHidden) { + SPDLOG_DEBUG("all widget in container are hidden, hidding container widget {}", + containerWidget->objectName().toStdString()); + if(!containerWidget->isHidden()) { + containerWidget->setVisible(false); + adjustParentDialog(containerWidget); + } + } else { + SPDLOG_DEBUG("some widgets are shown, showing container widget {}", + containerWidget->objectName().toStdString()); + containerWidget->setVisible(true); + } +} + +void WidgetAutoHidden::adjustParentDialog(QWidget* widget) { + QWidget* parentWidget = widget; + + while(parentWidget && !qobject_cast(parentWidget)) + parentWidget = qobject_cast(widget->parent()); + + if(parentWidget) { + parentWidget->adjustSize(); + } +} + +bool WidgetAutoHidden::eventFilter(QObject* watched, QEvent* event) { + /*static int eventEnumIndex = QEvent::staticMetaObject.indexOfEnumerator("Type"); + + QString name = QEvent::staticMetaObject.enumerator(eventEnumIndex).valueToKey(event->type()); + SPDLOG_DEBUG("MainWindow: Event {} on {}", name.toStdString(), watched->objectName().toStdString()); +*/ + if(event->type() == QEvent::ShowToParent) { + auto itLabel = labelWidgetsByWidgetMap.find(watched); + if(itLabel != labelWidgetsByWidgetMap.end()) { + itLabel->second->setVisible(true); + } + + auto it = containerWidgetsByChildMap.find(watched); + if(it != containerWidgetsByChildMap.end()) { + QWidget* containerWidget = it->second; + + if(!containerWidget->isHidden()) + return false; + + SPDLOG_DEBUG("widget {} shown, showing container widget {}", + watched->objectName().toStdString(), + containerWidget->objectName().toStdString()); + + containerWidget->setVisible(true); + } + } else if(event->type() == QEvent::HideToParent) { + auto itLabel = labelWidgetsByWidgetMap.find(watched); + if(itLabel != labelWidgetsByWidgetMap.end()) { + itLabel->second->setVisible(false); + } + + auto it = containerWidgetsByChildMap.find(watched); + if(it != containerWidgetsByChildMap.end()) { + QWidget* containerWidget = it->second; + + if(containerWidget->isHidden()) + return false; + + SPDLOG_DEBUG("widget {} hidden, updating {} state", + watched->objectName().toStdString(), + containerWidget->objectName().toStdString()); + + updateContainerVisibility(containerWidget); + } else { + adjustParentDialog(qobject_cast(watched)); + } + } + + return false; +} diff --git a/damc_gui/WidgetAutoHidden.h b/damc_gui/WidgetAutoHidden.h new file mode 100644 index 0000000..720ec69 --- /dev/null +++ b/damc_gui/WidgetAutoHidden.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include +#include + +class WidgetAutoHidden : public QObject { +public: + WidgetAutoHidden() noexcept; + + void addContainerWidgets(std::initializer_list containerWidgets); + +protected: + virtual bool eventFilter(QObject* watched, QEvent* event) override; + + void updateContainerVisibility(QWidget* containerWidget); + void adjustParentDialog(QWidget* widget); + +private: + std::unordered_map containerWidgetsByChildMap; + std::unordered_multimap childByContainerWidgetMap; + std::unordered_map labelWidgetsByWidgetMap; + bool widgetsShown; +}; diff --git a/damc_gui/main.cpp b/damc_gui/main.cpp index 046aa44..90da04a 100644 --- a/damc_gui/main.cpp +++ b/damc_gui/main.cpp @@ -54,6 +54,7 @@ int main(int argc, char* argv[]) { initializeSpdLog(); OscRoot oscRoot(false); + bool isMicrocontrollerDamc; std::unique_ptr oscConnector; @@ -73,10 +74,14 @@ int main(int argc, char* argv[]) { if(parser.isSet(serialOption)) { oscConnector.reset(new SerialPortInterface(&oscRoot)); + isMicrocontrollerDamc = true; } else { QString ip = parser.value(ipOption); QString portStr = parser.value(portOption); uint32_t port = portStr.toInt(); + + isMicrocontrollerDamc = false; + if(port == 0) { SPDLOG_ERROR("Invalid port {}", portStr.toStdString()); exitCode = 2; @@ -87,7 +92,7 @@ int main(int argc, char* argv[]) { } if(oscConnector) { - MainWindow w(&oscRoot); + MainWindow w(&oscRoot, isMicrocontrollerDamc); w.show(); exitCode = a.exec();