paulxstretch/deps/juce/extras/AudioPluginHost/Source/Plugins/IOConfigurationWindow.cpp

554 lines
18 KiB
C++
Raw Permalink Normal View History

/*
==============================================================================
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 <JuceHeader.h>
#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<TextButton*> (getCellComponent (columnId, 0)))
button->setToggleState (true, NotificationType::dontSendNotification);
}
void setCanAddColumn (bool canCurrentlyAdd)
{
if (canCurrentlyAdd != canAddColumn)
{
canAddColumn = canCurrentlyAdd;
if (auto* button = dynamic_cast<TextButton*> (getCellComponent (plusButtonColumnId, 0)))
button->setEnabled (true);
}
}
void setCanRemoveColumn (bool canCurrentlyRemove)
{
if (canCurrentlyRemove != canRemoveColumn)
{
canRemoveColumn = canCurrentlyRemove;
if (auto* button = dynamic_cast<TextButton*> (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<TextButton*> (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<float> (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<MainHostWindow*> (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;
}