The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

555 lines
18KB

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