1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 #include <JuceHeader.h>
27 #include "../UI/GraphEditorPanel.h"
28 #include "InternalPlugins.h"
29 #include "../UI/MainHostWindow.h"
30 #include "IOConfigurationWindow.h"
31 
32 
33 //==============================================================================
34 struct NumberedBoxes  : public TableListBox,
35                         private TableListBoxModel,
36                         private Button::Listener
37 {
38     struct Listener
39     {
~ListenerNumberedBoxes::Listener40         virtual ~Listener() {}
41 
42         virtual void addColumn()    = 0;
43         virtual void removeColumn() = 0;
44         virtual void columnSelected (int columnId) = 0;
45     };
46 
47     enum
48     {
49         plusButtonColumnId  = 128,
50         minusButtonColumnId = 129
51     };
52 
53     //==============================================================================
NumberedBoxesNumberedBoxes54     NumberedBoxes (Listener& listenerToUse, bool canCurrentlyAddColumn, bool canCurrentlyRemoveColumn)
55         : TableListBox ("NumberedBoxes", this),
56           listener (listenerToUse),
57           canAddColumn (canCurrentlyAddColumn),
58           canRemoveColumn (canCurrentlyRemoveColumn)
59     {
60         auto& tableHeader = getHeader();
61 
62         for (int i = 0; i < 16; ++i)
63             tableHeader.addColumn (String (i + 1), i + 1, 40);
64 
65         setHeaderHeight (0);
66         setRowHeight (40);
67         getHorizontalScrollBar().setAutoHide (false);
68     }
69 
setSelectedNumberedBoxes70     void setSelected (int columnId)
71     {
72         if (auto* button = dynamic_cast<TextButton*> (getCellComponent (columnId, 0)))
73             button->setToggleState (true, NotificationType::dontSendNotification);
74     }
75 
setCanAddColumnNumberedBoxes76     void setCanAddColumn (bool canCurrentlyAdd)
77     {
78         if (canCurrentlyAdd != canAddColumn)
79         {
80             canAddColumn = canCurrentlyAdd;
81 
82             if (auto* button = dynamic_cast<TextButton*> (getCellComponent (plusButtonColumnId, 0)))
83                 button->setEnabled (true);
84         }
85     }
86 
setCanRemoveColumnNumberedBoxes87     void setCanRemoveColumn (bool canCurrentlyRemove)
88     {
89         if (canCurrentlyRemove != canRemoveColumn)
90         {
91             canRemoveColumn = canCurrentlyRemove;
92 
93             if (auto* button = dynamic_cast<TextButton*> (getCellComponent (minusButtonColumnId, 0)))
94                 button->setEnabled (true);
95         }
96     }
97 
98 private:
99     //==============================================================================
100     Listener& listener;
101     bool canAddColumn, canRemoveColumn;
102 
103     //==============================================================================
getNumRowsNumberedBoxes104     int getNumRows() override                                             { return 1; }
paintCellNumberedBoxes105     void paintCell (Graphics&, int, int, int, int, bool) override         {}
paintRowBackgroundNumberedBoxes106     void paintRowBackground (Graphics& g, int, int, int, bool) override   { g.fillAll (Colours::grey); }
107 
refreshComponentForCellNumberedBoxes108     Component* refreshComponentForCell (int, int columnId, bool,
109                                         Component* existingComponentToUpdate) override
110     {
111         auto* textButton = dynamic_cast<TextButton*> (existingComponentToUpdate);
112 
113         if (textButton == nullptr)
114             textButton = new TextButton();
115 
116         textButton->setButtonText (getButtonName (columnId));
117         textButton->setConnectedEdges (Button::ConnectedOnLeft | Button::ConnectedOnRight |
118                                        Button::ConnectedOnTop  | Button::ConnectedOnBottom);
119 
120         const bool isPlusMinusButton = (columnId == plusButtonColumnId || columnId == minusButtonColumnId);
121 
122         if (isPlusMinusButton)
123         {
124             textButton->setEnabled (columnId == plusButtonColumnId ? canAddColumn : canRemoveColumn);
125         }
126         else
127         {
128             textButton->setRadioGroupId (1, NotificationType::dontSendNotification);
129             textButton->setClickingTogglesState (true);
130 
131             auto busColour = Colours::green.withRotatedHue (static_cast<float> (columnId) / 5.0f);
132             textButton->setColour (TextButton::buttonColourId, busColour);
133             textButton->setColour (TextButton::buttonOnColourId, busColour.withMultipliedBrightness (2.0f));
134         }
135 
136         textButton->addListener (this);
137 
138         return textButton;
139     }
140 
141     //==============================================================================
getButtonNameNumberedBoxes142     String getButtonName (int columnId)
143     {
144         if (columnId == plusButtonColumnId)  return "+";
145         if (columnId == minusButtonColumnId) return "-";
146 
147         return String (columnId);
148     }
149 
buttonClickedNumberedBoxes150     void buttonClicked (Button* btn) override
151     {
152         auto text = btn->getButtonText();
153 
154         if (text == "+") listener.addColumn();
155         if (text == "-") listener.removeColumn();
156     }
157 
buttonStateChangedNumberedBoxes158     void buttonStateChanged (Button* btn) override
159     {
160         auto text = btn->getButtonText();
161 
162         if (text == "+" || text == "-")
163             return;
164 
165         if (btn->getToggleState())
166             listener.columnSelected (text.getIntValue());
167     }
168 
169     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NumberedBoxes)
170 };
171 
172 //==============================================================================
173 class IOConfigurationWindow::InputOutputConfig  : public Component,
174                                                   private ComboBox::Listener,
175                                                   private Button::Listener,
176                                                   private NumberedBoxes::Listener
177 {
178 public:
InputOutputConfig(IOConfigurationWindow & parent,bool direction)179     InputOutputConfig (IOConfigurationWindow& parent, bool direction)
180         : owner (parent),
181           ioTitle ("ioLabel", direction ? "Input Configuration" : "Output Configuration"),
182           ioBuses (*this, false, false),
183           isInput (direction)
184     {
185         ioTitle.setFont (ioTitle.getFont().withStyle (Font::bold));
186         nameLabel.setFont (nameLabel.getFont().withStyle (Font::bold));
187         layoutLabel.setFont (layoutLabel.getFont().withStyle (Font::bold));
188         enabledToggle.setClickingTogglesState (true);
189 
190         layouts.addListener (this);
191         enabledToggle.addListener (this);
192 
193         addAndMakeVisible (layoutLabel);
194         addAndMakeVisible (layouts);
195         addAndMakeVisible (enabledToggle);
196         addAndMakeVisible (ioTitle);
197         addAndMakeVisible (nameLabel);
198         addAndMakeVisible (name);
199         addAndMakeVisible (ioBuses);
200 
201         updateBusButtons();
202         updateBusLayout();
203     }
204 
paint(Graphics & g)205     void paint (Graphics& g) override
206     {
207         g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
208     }
209 
resized()210     void resized() override
211     {
212         auto r = getLocalBounds().reduced (10);
213 
214         ioTitle.setBounds (r.removeFromTop (14));
215         r.reduce (10, 0);
216         r.removeFromTop (16);
217 
218         ioBuses.setBounds (r.removeFromTop (60));
219 
220         {
221             auto label = r.removeFromTop (24);
222             nameLabel.setBounds (label.removeFromLeft (100));
223             enabledToggle.setBounds (label.removeFromRight (80));
224             name.setBounds (label);
225         }
226 
227         {
228             auto label = r.removeFromTop (24);
229             layoutLabel.setBounds (label.removeFromLeft (100));
230             layouts.setBounds (label);
231         }
232     }
233 
234 private:
updateBusButtons()235     void updateBusButtons()
236     {
237         if (auto* plugin = owner.getAudioProcessor())
238         {
239             auto& header = ioBuses.getHeader();
240             header.removeAllColumns();
241 
242             const int n = plugin->getBusCount (isInput);
243 
244             for (int i = 0; i < n; ++i)
245                 header.addColumn ("", i + 1, 40);
246 
247             header.addColumn ("+", NumberedBoxes::plusButtonColumnId,  20);
248             header.addColumn ("-", NumberedBoxes::minusButtonColumnId, 20);
249 
250             ioBuses.setCanAddColumn    (plugin->canAddBus    (isInput));
251             ioBuses.setCanRemoveColumn (plugin->canRemoveBus (isInput));
252         }
253 
254         ioBuses.setSelected (currentBus + 1);
255     }
256 
updateBusLayout()257     void updateBusLayout()
258     {
259         if (auto* plugin = owner.getAudioProcessor())
260         {
261             if (auto* bus = plugin->getBus (isInput, currentBus))
262             {
263                 name.setText (bus->getName(), NotificationType::dontSendNotification);
264 
265                 int i;
266                 for (i = 1; i < AudioChannelSet::maxChannelsOfNamedLayout; ++i)
267                     if ((layouts.indexOfItemId(i) == -1) != bus->supportedLayoutWithChannels (i).isDisabled())
268                         break;
269 
270                 // supported layouts have changed
271                 if (i < AudioChannelSet::maxChannelsOfNamedLayout)
272                 {
273                     layouts.clear();
274 
275                     for (i = 1; i < AudioChannelSet::maxChannelsOfNamedLayout; ++i)
276                     {
277                         auto set = bus->supportedLayoutWithChannels (i);
278 
279                         if (! set.isDisabled())
280                             layouts.addItem (set.getDescription(), i);
281                     }
282                 }
283 
284                 layouts.setSelectedId (bus->getLastEnabledLayout().size());
285 
286                 const bool canBeDisabled = bus->isNumberOfChannelsSupported (0);
287 
288                 if (canBeDisabled != enabledToggle.isEnabled())
289                     enabledToggle.setEnabled (canBeDisabled);
290 
291                 enabledToggle.setToggleState (bus->isEnabled(), NotificationType::dontSendNotification);
292             }
293         }
294     }
295 
296     //==============================================================================
comboBoxChanged(ComboBox * combo)297     void comboBoxChanged (ComboBox* combo) override
298     {
299         if (combo == &layouts)
300         {
301             if (auto* p = owner.getAudioProcessor())
302             {
303                 if (auto* bus = p->getBus (isInput, currentBus))
304                 {
305                     auto selectedNumChannels = layouts.getSelectedId();
306 
307                     if (selectedNumChannels != bus->getLastEnabledLayout().size())
308                     {
309                         if (isPositiveAndBelow (selectedNumChannels, AudioChannelSet::maxChannelsOfNamedLayout)
310                              && bus->setCurrentLayoutWithoutEnabling (bus->supportedLayoutWithChannels (selectedNumChannels)))
311                         {
312                             if (auto* config = owner.getConfig (! isInput))
313                                 config->updateBusLayout();
314 
315                             owner.update();
316                         }
317                     }
318                 }
319             }
320         }
321     }
322 
buttonClicked(Button *)323     void buttonClicked (Button*) override {}
324 
buttonStateChanged(Button * btn)325     void buttonStateChanged (Button* btn) override
326     {
327         if (btn == &enabledToggle && enabledToggle.isEnabled())
328         {
329             if (auto* p = owner.getAudioProcessor())
330             {
331                 if (auto* bus = p->getBus (isInput, currentBus))
332                 {
333                     if (bus->isEnabled() != enabledToggle.getToggleState())
334                     {
335                         bool success = enabledToggle.getToggleState() ? bus->enable()
336                                                                       : bus->setCurrentLayout (AudioChannelSet::disabled());
337 
338                         if (success)
339                         {
340                             updateBusLayout();
341 
342                             if (auto* config = owner.getConfig (! isInput))
343                                 config->updateBusLayout();
344 
345                             owner.update();
346                         }
347                         else
348                         {
349                             enabledToggle.setToggleState (! enabledToggle.getToggleState(),
350                                                           NotificationType::dontSendNotification);
351                         }
352                     }
353                 }
354             }
355         }
356     }
357 
358     //==============================================================================
addColumn()359     void addColumn() override
360     {
361         if (auto* p = owner.getAudioProcessor())
362         {
363             if (p->canAddBus (isInput))
364             {
365                 if (p->addBus (isInput))
366                 {
367                     updateBusButtons();
368                     updateBusLayout();
369 
370                     if (auto* config = owner.getConfig (! isInput))
371                     {
372                         config->updateBusButtons();
373                         config->updateBusLayout();
374                     }
375                 }
376 
377                 owner.update();
378             }
379         }
380     }
381 
removeColumn()382     void removeColumn() override
383     {
384         if (auto* p = owner.getAudioProcessor())
385         {
386             if (p->getBusCount (isInput) > 1 && p->canRemoveBus (isInput))
387             {
388                 if (p->removeBus (isInput))
389                 {
390                     currentBus = jmin (p->getBusCount (isInput) - 1, currentBus);
391 
392                     updateBusButtons();
393                     updateBusLayout();
394 
395                     if (auto* config = owner.getConfig (! isInput))
396                     {
397                         config->updateBusButtons();
398                         config->updateBusLayout();
399                     }
400 
401                     owner.update();
402                 }
403             }
404         }
405     }
406 
columnSelected(int columnId)407     void columnSelected (int columnId) override
408     {
409         const int newBus = columnId - 1;
410 
411         if (currentBus != newBus)
412         {
413             currentBus = newBus;
414             ioBuses.setSelected (currentBus + 1);
415             updateBusLayout();
416         }
417     }
418 
419     //==============================================================================
420     IOConfigurationWindow& owner;
421     Label ioTitle, name;
422     Label nameLabel { "nameLabel", "Bus Name:" };
423     Label layoutLabel { "layoutLabel", "Channel Layout:" };
424     ToggleButton enabledToggle { "Enabled" };
425     ComboBox layouts;
426     NumberedBoxes ioBuses;
427     bool isInput;
428     int currentBus = 0;
429 
430     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InputOutputConfig)
431 };
432 
433 
IOConfigurationWindow(AudioProcessor & p)434 IOConfigurationWindow::IOConfigurationWindow (AudioProcessor& p)
435    : AudioProcessorEditor (&p),
436      title ("title", p.getName())
437 {
438     setOpaque (true);
439 
440     title.setFont (title.getFont().withStyle (Font::bold));
441     addAndMakeVisible (title);
442 
443     {
444         ScopedLock renderLock (p.getCallbackLock());
445         p.suspendProcessing (true);
446         p.releaseResources();
447     }
448 
449     if (p.getBusCount (true)  > 0 || p.canAddBus (true))
450     {
451         inConfig.reset (new InputOutputConfig (*this, true));
452         addAndMakeVisible (inConfig.get());
453     }
454 
455     if (p.getBusCount (false) > 0 || p.canAddBus (false))
456     {
457         outConfig.reset (new InputOutputConfig (*this, false));
458         addAndMakeVisible (outConfig.get());
459     }
460 
461     currentLayout = p.getBusesLayout();
462     setSize (400, (inConfig != nullptr && outConfig != nullptr ? 160 : 0) + 200);
463 }
464 
~IOConfigurationWindow()465 IOConfigurationWindow::~IOConfigurationWindow()
466 {
467     if (auto* graph = getGraph())
468     {
469         if (auto* p = getAudioProcessor())
470         {
471             ScopedLock renderLock (graph->getCallbackLock());
472 
473             graph->suspendProcessing (true);
474             graph->releaseResources();
475 
476             p->prepareToPlay (graph->getSampleRate(), graph->getBlockSize());
477             p->suspendProcessing (false);
478 
479             graph->prepareToPlay (graph->getSampleRate(), graph->getBlockSize());
480             graph->suspendProcessing (false);
481         }
482     }
483 }
484 
paint(Graphics & g)485 void IOConfigurationWindow::paint (Graphics& g)
486 {
487      g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
488 }
489 
resized()490 void IOConfigurationWindow::resized()
491 {
492     auto r = getLocalBounds().reduced (10);
493 
494     title.setBounds (r.removeFromTop (14));
495     r.reduce (10, 0);
496 
497     if (inConfig != nullptr)
498         inConfig->setBounds (r.removeFromTop (160));
499 
500     if (outConfig != nullptr)
501         outConfig->setBounds (r.removeFromTop (160));
502 }
503 
update()504 void IOConfigurationWindow::update()
505 {
506     auto nodeID = getNodeID();
507 
508     if (auto* graph = getGraph())
509         if (nodeID != AudioProcessorGraph::NodeID())
510             graph->disconnectNode (nodeID);
511 
512     if (auto* graphEditor = getGraphEditor())
513         if (auto* panel = graphEditor->graphPanel.get())
514             panel->updateComponents();
515 }
516 
getNodeID() const517 AudioProcessorGraph::NodeID IOConfigurationWindow::getNodeID() const
518 {
519     if (auto* graph = getGraph())
520         for (auto* node : graph->getNodes())
521             if (node->getProcessor() == getAudioProcessor())
522                 return node->nodeID;
523 
524     return {};
525 }
526 
getMainWindow() const527 MainHostWindow* IOConfigurationWindow::getMainWindow() const
528 {
529     auto& desktop = Desktop::getInstance();
530 
531     for (int i = desktop.getNumComponents(); --i >= 0;)
532         if (auto* mainWindow = dynamic_cast<MainHostWindow*> (desktop.getComponent(i)))
533             return mainWindow;
534 
535     return nullptr;
536 }
537 
getGraphEditor() const538 GraphDocumentComponent* IOConfigurationWindow::getGraphEditor() const
539 {
540     if (auto* mainWindow = getMainWindow())
541         return mainWindow->graphHolder.get();
542 
543     return nullptr;
544 }
545 
getGraph() const546 AudioProcessorGraph* IOConfigurationWindow::getGraph() const
547 {
548     if (auto* graphEditor = getGraphEditor())
549         if (auto* panel = graphEditor->graph.get())
550             return &panel->graph;
551 
552     return nullptr;
553 }
554