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.

547 lines
18KB

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