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.

554 lines
18KB

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