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