/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2020 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 6 End-User License Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). End User License Agreement: www.juce.com/juce-6-licence Privacy Policy: www.juce.com/juce-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ #include #include "../UI/GraphEditorPanel.h" #include "InternalPlugins.h" #include "../UI/MainHostWindow.h" #include "IOConfigurationWindow.h" //============================================================================== struct NumberedBoxes : public TableListBox, private TableListBoxModel, private Button::Listener { struct Listener { virtual ~Listener() {} virtual void addColumn() = 0; virtual void removeColumn() = 0; virtual void columnSelected (int columnId) = 0; }; enum { plusButtonColumnId = 128, minusButtonColumnId = 129 }; //============================================================================== NumberedBoxes (Listener& listenerToUse, bool canCurrentlyAddColumn, bool canCurrentlyRemoveColumn) : TableListBox ("NumberedBoxes", this), listener (listenerToUse), canAddColumn (canCurrentlyAddColumn), canRemoveColumn (canCurrentlyRemoveColumn) { auto& tableHeader = getHeader(); for (int i = 0; i < 16; ++i) tableHeader.addColumn (String (i + 1), i + 1, 40); setHeaderHeight (0); setRowHeight (40); getHorizontalScrollBar().setAutoHide (false); } void setSelected (int columnId) { if (auto* button = dynamic_cast (getCellComponent (columnId, 0))) button->setToggleState (true, NotificationType::dontSendNotification); } void setCanAddColumn (bool canCurrentlyAdd) { if (canCurrentlyAdd != canAddColumn) { canAddColumn = canCurrentlyAdd; if (auto* button = dynamic_cast (getCellComponent (plusButtonColumnId, 0))) button->setEnabled (true); } } void setCanRemoveColumn (bool canCurrentlyRemove) { if (canCurrentlyRemove != canRemoveColumn) { canRemoveColumn = canCurrentlyRemove; if (auto* button = dynamic_cast (getCellComponent (minusButtonColumnId, 0))) button->setEnabled (true); } } private: //============================================================================== Listener& listener; bool canAddColumn, canRemoveColumn; //============================================================================== int getNumRows() override { return 1; } void paintCell (Graphics&, int, int, int, int, bool) override {} void paintRowBackground (Graphics& g, int, int, int, bool) override { g.fillAll (Colours::grey); } Component* refreshComponentForCell (int, int columnId, bool, Component* existingComponentToUpdate) override { auto* textButton = dynamic_cast (existingComponentToUpdate); if (textButton == nullptr) textButton = new TextButton(); textButton->setButtonText (getButtonName (columnId)); textButton->setConnectedEdges (Button::ConnectedOnLeft | Button::ConnectedOnRight | Button::ConnectedOnTop | Button::ConnectedOnBottom); const bool isPlusMinusButton = (columnId == plusButtonColumnId || columnId == minusButtonColumnId); if (isPlusMinusButton) { textButton->setEnabled (columnId == plusButtonColumnId ? canAddColumn : canRemoveColumn); } else { textButton->setRadioGroupId (1, NotificationType::dontSendNotification); textButton->setClickingTogglesState (true); auto busColour = Colours::green.withRotatedHue (static_cast (columnId) / 5.0f); textButton->setColour (TextButton::buttonColourId, busColour); textButton->setColour (TextButton::buttonOnColourId, busColour.withMultipliedBrightness (2.0f)); } textButton->addListener (this); return textButton; } //============================================================================== String getButtonName (int columnId) { if (columnId == plusButtonColumnId) return "+"; if (columnId == minusButtonColumnId) return "-"; return String (columnId); } void buttonClicked (Button* btn) override { auto text = btn->getButtonText(); if (text == "+") listener.addColumn(); if (text == "-") listener.removeColumn(); } void buttonStateChanged (Button* btn) override { auto text = btn->getButtonText(); if (text == "+" || text == "-") return; if (btn->getToggleState()) listener.columnSelected (text.getIntValue()); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NumberedBoxes) }; //============================================================================== class IOConfigurationWindow::InputOutputConfig : public Component, private ComboBox::Listener, private Button::Listener, private NumberedBoxes::Listener { public: InputOutputConfig (IOConfigurationWindow& parent, bool direction) : owner (parent), ioTitle ("ioLabel", direction ? "Input Configuration" : "Output Configuration"), ioBuses (*this, false, false), isInput (direction) { ioTitle.setFont (ioTitle.getFont().withStyle (Font::bold)); nameLabel.setFont (nameLabel.getFont().withStyle (Font::bold)); layoutLabel.setFont (layoutLabel.getFont().withStyle (Font::bold)); enabledToggle.setClickingTogglesState (true); layouts.addListener (this); enabledToggle.addListener (this); addAndMakeVisible (layoutLabel); addAndMakeVisible (layouts); addAndMakeVisible (enabledToggle); addAndMakeVisible (ioTitle); addAndMakeVisible (nameLabel); addAndMakeVisible (name); addAndMakeVisible (ioBuses); updateBusButtons(); updateBusLayout(); } void paint (Graphics& g) override { g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId)); } void resized() override { auto r = getLocalBounds().reduced (10); ioTitle.setBounds (r.removeFromTop (14)); r.reduce (10, 0); r.removeFromTop (16); ioBuses.setBounds (r.removeFromTop (60)); { auto label = r.removeFromTop (24); nameLabel.setBounds (label.removeFromLeft (100)); enabledToggle.setBounds (label.removeFromRight (80)); name.setBounds (label); } { auto label = r.removeFromTop (24); layoutLabel.setBounds (label.removeFromLeft (100)); layouts.setBounds (label); } } private: void updateBusButtons() { if (auto* plugin = owner.getAudioProcessor()) { auto& header = ioBuses.getHeader(); header.removeAllColumns(); const int n = plugin->getBusCount (isInput); for (int i = 0; i < n; ++i) header.addColumn ("", i + 1, 40); header.addColumn ("+", NumberedBoxes::plusButtonColumnId, 20); header.addColumn ("-", NumberedBoxes::minusButtonColumnId, 20); ioBuses.setCanAddColumn (plugin->canAddBus (isInput)); ioBuses.setCanRemoveColumn (plugin->canRemoveBus (isInput)); } ioBuses.setSelected (currentBus + 1); } void updateBusLayout() { if (auto* plugin = owner.getAudioProcessor()) { if (auto* bus = plugin->getBus (isInput, currentBus)) { name.setText (bus->getName(), NotificationType::dontSendNotification); int i; for (i = 1; i < AudioChannelSet::maxChannelsOfNamedLayout; ++i) if ((layouts.indexOfItemId(i) == -1) != bus->supportedLayoutWithChannels (i).isDisabled()) break; // supported layouts have changed if (i < AudioChannelSet::maxChannelsOfNamedLayout) { layouts.clear(); for (i = 1; i < AudioChannelSet::maxChannelsOfNamedLayout; ++i) { auto set = bus->supportedLayoutWithChannels (i); if (! set.isDisabled()) layouts.addItem (set.getDescription(), i); } } layouts.setSelectedId (bus->getLastEnabledLayout().size()); const bool canBeDisabled = bus->isNumberOfChannelsSupported (0); if (canBeDisabled != enabledToggle.isEnabled()) enabledToggle.setEnabled (canBeDisabled); enabledToggle.setToggleState (bus->isEnabled(), NotificationType::dontSendNotification); } } } //============================================================================== void comboBoxChanged (ComboBox* combo) override { if (combo == &layouts) { if (auto* p = owner.getAudioProcessor()) { if (auto* bus = p->getBus (isInput, currentBus)) { auto selectedNumChannels = layouts.getSelectedId(); if (selectedNumChannels != bus->getLastEnabledLayout().size()) { if (isPositiveAndBelow (selectedNumChannels, AudioChannelSet::maxChannelsOfNamedLayout) && bus->setCurrentLayoutWithoutEnabling (bus->supportedLayoutWithChannels (selectedNumChannels))) { if (auto* config = owner.getConfig (! isInput)) config->updateBusLayout(); owner.update(); } } } } } } void buttonClicked (Button*) override {} void buttonStateChanged (Button* btn) override { if (btn == &enabledToggle && enabledToggle.isEnabled()) { if (auto* p = owner.getAudioProcessor()) { if (auto* bus = p->getBus (isInput, currentBus)) { if (bus->isEnabled() != enabledToggle.getToggleState()) { bool success = enabledToggle.getToggleState() ? bus->enable() : bus->setCurrentLayout (AudioChannelSet::disabled()); if (success) { updateBusLayout(); if (auto* config = owner.getConfig (! isInput)) config->updateBusLayout(); owner.update(); } else { enabledToggle.setToggleState (! enabledToggle.getToggleState(), NotificationType::dontSendNotification); } } } } } } //============================================================================== void addColumn() override { if (auto* p = owner.getAudioProcessor()) { if (p->canAddBus (isInput)) { if (p->addBus (isInput)) { updateBusButtons(); updateBusLayout(); if (auto* config = owner.getConfig (! isInput)) { config->updateBusButtons(); config->updateBusLayout(); } } owner.update(); } } } void removeColumn() override { if (auto* p = owner.getAudioProcessor()) { if (p->getBusCount (isInput) > 1 && p->canRemoveBus (isInput)) { if (p->removeBus (isInput)) { currentBus = jmin (p->getBusCount (isInput) - 1, currentBus); updateBusButtons(); updateBusLayout(); if (auto* config = owner.getConfig (! isInput)) { config->updateBusButtons(); config->updateBusLayout(); } owner.update(); } } } } void columnSelected (int columnId) override { const int newBus = columnId - 1; if (currentBus != newBus) { currentBus = newBus; ioBuses.setSelected (currentBus + 1); updateBusLayout(); } } //============================================================================== IOConfigurationWindow& owner; Label ioTitle, name; Label nameLabel { "nameLabel", "Bus Name:" }; Label layoutLabel { "layoutLabel", "Channel Layout:" }; ToggleButton enabledToggle { "Enabled" }; ComboBox layouts; NumberedBoxes ioBuses; bool isInput; int currentBus = 0; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InputOutputConfig) }; IOConfigurationWindow::IOConfigurationWindow (AudioProcessor& p) : AudioProcessorEditor (&p), title ("title", p.getName()) { setOpaque (true); title.setFont (title.getFont().withStyle (Font::bold)); addAndMakeVisible (title); { ScopedLock renderLock (p.getCallbackLock()); p.suspendProcessing (true); p.releaseResources(); } if (p.getBusCount (true) > 0 || p.canAddBus (true)) { inConfig.reset (new InputOutputConfig (*this, true)); addAndMakeVisible (inConfig.get()); } if (p.getBusCount (false) > 0 || p.canAddBus (false)) { outConfig.reset (new InputOutputConfig (*this, false)); addAndMakeVisible (outConfig.get()); } currentLayout = p.getBusesLayout(); setSize (400, (inConfig != nullptr && outConfig != nullptr ? 160 : 0) + 200); } IOConfigurationWindow::~IOConfigurationWindow() { if (auto* graph = getGraph()) { if (auto* p = getAudioProcessor()) { ScopedLock renderLock (graph->getCallbackLock()); graph->suspendProcessing (true); graph->releaseResources(); p->prepareToPlay (graph->getSampleRate(), graph->getBlockSize()); p->suspendProcessing (false); graph->prepareToPlay (graph->getSampleRate(), graph->getBlockSize()); graph->suspendProcessing (false); } } } void IOConfigurationWindow::paint (Graphics& g) { g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId)); } void IOConfigurationWindow::resized() { auto r = getLocalBounds().reduced (10); title.setBounds (r.removeFromTop (14)); r.reduce (10, 0); if (inConfig != nullptr) inConfig->setBounds (r.removeFromTop (160)); if (outConfig != nullptr) outConfig->setBounds (r.removeFromTop (160)); } void IOConfigurationWindow::update() { auto nodeID = getNodeID(); if (auto* graph = getGraph()) if (nodeID != AudioProcessorGraph::NodeID()) graph->disconnectNode (nodeID); if (auto* graphEditor = getGraphEditor()) if (auto* panel = graphEditor->graphPanel.get()) panel->updateComponents(); } AudioProcessorGraph::NodeID IOConfigurationWindow::getNodeID() const { if (auto* graph = getGraph()) for (auto* node : graph->getNodes()) if (node->getProcessor() == getAudioProcessor()) return node->nodeID; return {}; } MainHostWindow* IOConfigurationWindow::getMainWindow() const { auto& desktop = Desktop::getInstance(); for (int i = desktop.getNumComponents(); --i >= 0;) if (auto* mainWindow = dynamic_cast (desktop.getComponent(i))) return mainWindow; return nullptr; } GraphDocumentComponent* IOConfigurationWindow::getGraphEditor() const { if (auto* mainWindow = getMainWindow()) return mainWindow->graphHolder.get(); return nullptr; } AudioProcessorGraph* IOConfigurationWindow::getGraph() const { if (auto* graphEditor = getGraphEditor()) if (auto* panel = graphEditor->graph.get()) return &panel->graph; return nullptr; }