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.

549 lines
17KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - 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 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-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 Button::Listener,
  136. private NumberedBoxes::Listener
  137. {
  138. public:
  139. InputOutputConfig (IOConfigurationWindow& parent, bool direction)
  140. : owner (parent),
  141. ioTitle ("ioLabel", direction ? "Input Configuration" : "Output Configuration"),
  142. ioBuses (*this, false, false),
  143. isInput (direction)
  144. {
  145. ioTitle.setFont (ioTitle.getFont().withStyle (Font::bold));
  146. nameLabel.setFont (nameLabel.getFont().withStyle (Font::bold));
  147. layoutLabel.setFont (layoutLabel.getFont().withStyle (Font::bold));
  148. enabledToggle.setClickingTogglesState (true);
  149. enabledToggle.addListener (this);
  150. addAndMakeVisible (layoutLabel);
  151. addAndMakeVisible (layouts);
  152. addAndMakeVisible (enabledToggle);
  153. addAndMakeVisible (ioTitle);
  154. addAndMakeVisible (nameLabel);
  155. addAndMakeVisible (name);
  156. addAndMakeVisible (ioBuses);
  157. updateBusButtons();
  158. updateBusLayout();
  159. }
  160. void paint (Graphics& g) override
  161. {
  162. g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
  163. }
  164. void resized() override
  165. {
  166. auto r = getLocalBounds().reduced (10);
  167. ioTitle.setBounds (r.removeFromTop (14));
  168. r.reduce (10, 0);
  169. r.removeFromTop (16);
  170. ioBuses.setBounds (r.removeFromTop (60));
  171. {
  172. auto label = r.removeFromTop (24);
  173. nameLabel.setBounds (label.removeFromLeft (100));
  174. enabledToggle.setBounds (label.removeFromRight (80));
  175. name.setBounds (label);
  176. }
  177. {
  178. auto label = r.removeFromTop (24);
  179. layoutLabel.setBounds (label.removeFromLeft (100));
  180. layouts.setBounds (label);
  181. }
  182. }
  183. private:
  184. void updateBusButtons()
  185. {
  186. if (auto* plugin = owner.getAudioProcessor())
  187. {
  188. auto& header = ioBuses.getHeader();
  189. header.removeAllColumns();
  190. const int n = plugin->getBusCount (isInput);
  191. for (int i = 0; i < n; ++i)
  192. header.addColumn ("", i + 1, 40);
  193. header.addColumn ("+", NumberedBoxes::plusButtonColumnId, 20);
  194. header.addColumn ("-", NumberedBoxes::minusButtonColumnId, 20);
  195. ioBuses.setCanAddColumn (plugin->canAddBus (isInput));
  196. ioBuses.setCanRemoveColumn (plugin->canRemoveBus (isInput));
  197. }
  198. ioBuses.setSelected (currentBus + 1);
  199. }
  200. void updateBusLayout()
  201. {
  202. if (auto* plugin = owner.getAudioProcessor())
  203. {
  204. if (auto* bus = plugin->getBus (isInput, currentBus))
  205. {
  206. name.setText (bus->getName(), NotificationType::dontSendNotification);
  207. // supported layouts have changed
  208. layouts.clear (dontSendNotification);
  209. auto* menu = layouts.getRootMenu();
  210. auto itemId = 1;
  211. auto selectedId = -1;
  212. for (auto i = 1; i < AudioChannelSet::maxChannelsOfNamedLayout; ++i)
  213. {
  214. for (const auto& set : AudioChannelSet::channelSetsWithNumberOfChannels (i))
  215. {
  216. if (bus->isLayoutSupported (set))
  217. {
  218. menu->addItem (PopupMenu::Item { set.getDescription() }
  219. .setAction ([this, set] { applyBusLayout (set); })
  220. .setID (itemId));
  221. }
  222. if (bus->getCurrentLayout() == set)
  223. selectedId = itemId;
  224. ++itemId;
  225. }
  226. }
  227. layouts.setSelectedId (selectedId);
  228. const bool canBeDisabled = bus->isNumberOfChannelsSupported (0);
  229. if (canBeDisabled != enabledToggle.isEnabled())
  230. enabledToggle.setEnabled (canBeDisabled);
  231. enabledToggle.setToggleState (bus->isEnabled(), NotificationType::dontSendNotification);
  232. }
  233. }
  234. }
  235. //==============================================================================
  236. void applyBusLayout (const AudioChannelSet& set)
  237. {
  238. if (auto* p = owner.getAudioProcessor())
  239. {
  240. if (auto* bus = p->getBus (isInput, currentBus))
  241. {
  242. if (bus->setCurrentLayoutWithoutEnabling (set))
  243. {
  244. if (auto* config = owner.getConfig (! isInput))
  245. config->updateBusLayout();
  246. owner.update();
  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. }