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.

542 lines
17KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 7 technical preview.
  4. Copyright (c) 2022 - 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 the technical preview this file cannot be licensed commercially.
  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 Button::Listener,
  131. private NumberedBoxes::Listener
  132. {
  133. public:
  134. InputOutputConfig (IOConfigurationWindow& parent, bool direction)
  135. : owner (parent),
  136. ioTitle ("ioLabel", direction ? "Input Configuration" : "Output Configuration"),
  137. ioBuses (*this, false, false),
  138. isInput (direction)
  139. {
  140. ioTitle.setFont (ioTitle.getFont().withStyle (Font::bold));
  141. nameLabel.setFont (nameLabel.getFont().withStyle (Font::bold));
  142. layoutLabel.setFont (layoutLabel.getFont().withStyle (Font::bold));
  143. enabledToggle.setClickingTogglesState (true);
  144. enabledToggle.addListener (this);
  145. addAndMakeVisible (layoutLabel);
  146. addAndMakeVisible (layouts);
  147. addAndMakeVisible (enabledToggle);
  148. addAndMakeVisible (ioTitle);
  149. addAndMakeVisible (nameLabel);
  150. addAndMakeVisible (name);
  151. addAndMakeVisible (ioBuses);
  152. updateBusButtons();
  153. updateBusLayout();
  154. }
  155. void paint (Graphics& g) override
  156. {
  157. g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
  158. }
  159. void resized() override
  160. {
  161. auto r = getLocalBounds().reduced (10);
  162. ioTitle.setBounds (r.removeFromTop (14));
  163. r.reduce (10, 0);
  164. r.removeFromTop (16);
  165. ioBuses.setBounds (r.removeFromTop (60));
  166. {
  167. auto label = r.removeFromTop (24);
  168. nameLabel.setBounds (label.removeFromLeft (100));
  169. enabledToggle.setBounds (label.removeFromRight (80));
  170. name.setBounds (label);
  171. }
  172. {
  173. auto label = r.removeFromTop (24);
  174. layoutLabel.setBounds (label.removeFromLeft (100));
  175. layouts.setBounds (label);
  176. }
  177. }
  178. private:
  179. void updateBusButtons()
  180. {
  181. if (auto* plugin = owner.getAudioProcessor())
  182. {
  183. auto& header = ioBuses.getHeader();
  184. header.removeAllColumns();
  185. const int n = plugin->getBusCount (isInput);
  186. for (int i = 0; i < n; ++i)
  187. header.addColumn ("", i + 1, 40);
  188. header.addColumn ("+", NumberedBoxes::plusButtonColumnId, 20);
  189. header.addColumn ("-", NumberedBoxes::minusButtonColumnId, 20);
  190. ioBuses.setCanAddColumn (plugin->canAddBus (isInput));
  191. ioBuses.setCanRemoveColumn (plugin->canRemoveBus (isInput));
  192. }
  193. ioBuses.setSelected (currentBus + 1);
  194. }
  195. void updateBusLayout()
  196. {
  197. if (auto* plugin = owner.getAudioProcessor())
  198. {
  199. if (auto* bus = plugin->getBus (isInput, currentBus))
  200. {
  201. name.setText (bus->getName(), NotificationType::dontSendNotification);
  202. // supported layouts have changed
  203. layouts.clear (dontSendNotification);
  204. auto* menu = layouts.getRootMenu();
  205. auto itemId = 1;
  206. auto selectedId = -1;
  207. for (auto i = 1; i < AudioChannelSet::maxChannelsOfNamedLayout; ++i)
  208. {
  209. for (const auto& set : AudioChannelSet::channelSetsWithNumberOfChannels (i))
  210. {
  211. if (bus->isLayoutSupported (set))
  212. {
  213. menu->addItem (PopupMenu::Item { set.getDescription() }
  214. .setAction ([this, set] { applyBusLayout (set); })
  215. .setID (itemId));
  216. }
  217. if (bus->getCurrentLayout() == set)
  218. selectedId = itemId;
  219. ++itemId;
  220. }
  221. }
  222. layouts.setSelectedId (selectedId);
  223. const bool canBeDisabled = bus->isNumberOfChannelsSupported (0);
  224. if (canBeDisabled != enabledToggle.isEnabled())
  225. enabledToggle.setEnabled (canBeDisabled);
  226. enabledToggle.setToggleState (bus->isEnabled(), NotificationType::dontSendNotification);
  227. }
  228. }
  229. }
  230. //==============================================================================
  231. void applyBusLayout (const AudioChannelSet& set)
  232. {
  233. if (auto* p = owner.getAudioProcessor())
  234. {
  235. if (auto* bus = p->getBus (isInput, currentBus))
  236. {
  237. if (bus->setCurrentLayoutWithoutEnabling (set))
  238. {
  239. if (auto* config = owner.getConfig (! isInput))
  240. config->updateBusLayout();
  241. owner.update();
  242. }
  243. }
  244. }
  245. }
  246. void buttonClicked (Button*) override {}
  247. void buttonStateChanged (Button* btn) override
  248. {
  249. if (btn == &enabledToggle && enabledToggle.isEnabled())
  250. {
  251. if (auto* p = owner.getAudioProcessor())
  252. {
  253. if (auto* bus = p->getBus (isInput, currentBus))
  254. {
  255. if (bus->isEnabled() != enabledToggle.getToggleState())
  256. {
  257. bool success = enabledToggle.getToggleState() ? bus->enable()
  258. : bus->setCurrentLayout (AudioChannelSet::disabled());
  259. if (success)
  260. {
  261. updateBusLayout();
  262. if (auto* config = owner.getConfig (! isInput))
  263. config->updateBusLayout();
  264. owner.update();
  265. }
  266. else
  267. {
  268. enabledToggle.setToggleState (! enabledToggle.getToggleState(),
  269. NotificationType::dontSendNotification);
  270. }
  271. }
  272. }
  273. }
  274. }
  275. }
  276. //==============================================================================
  277. void addColumn() override
  278. {
  279. if (auto* p = owner.getAudioProcessor())
  280. {
  281. if (p->canAddBus (isInput))
  282. {
  283. if (p->addBus (isInput))
  284. {
  285. updateBusButtons();
  286. updateBusLayout();
  287. if (auto* config = owner.getConfig (! isInput))
  288. {
  289. config->updateBusButtons();
  290. config->updateBusLayout();
  291. }
  292. }
  293. owner.update();
  294. }
  295. }
  296. }
  297. void removeColumn() override
  298. {
  299. if (auto* p = owner.getAudioProcessor())
  300. {
  301. if (p->getBusCount (isInput) > 1 && p->canRemoveBus (isInput))
  302. {
  303. if (p->removeBus (isInput))
  304. {
  305. currentBus = jmin (p->getBusCount (isInput) - 1, currentBus);
  306. updateBusButtons();
  307. updateBusLayout();
  308. if (auto* config = owner.getConfig (! isInput))
  309. {
  310. config->updateBusButtons();
  311. config->updateBusLayout();
  312. }
  313. owner.update();
  314. }
  315. }
  316. }
  317. }
  318. void columnSelected (int columnId) override
  319. {
  320. const int newBus = columnId - 1;
  321. if (currentBus != newBus)
  322. {
  323. currentBus = newBus;
  324. ioBuses.setSelected (currentBus + 1);
  325. updateBusLayout();
  326. }
  327. }
  328. //==============================================================================
  329. IOConfigurationWindow& owner;
  330. Label ioTitle, name;
  331. Label nameLabel { "nameLabel", "Bus Name:" };
  332. Label layoutLabel { "layoutLabel", "Channel Layout:" };
  333. ToggleButton enabledToggle { "Enabled" };
  334. ComboBox layouts;
  335. NumberedBoxes ioBuses;
  336. bool isInput;
  337. int currentBus = 0;
  338. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InputOutputConfig)
  339. };
  340. IOConfigurationWindow::IOConfigurationWindow (AudioProcessor& p)
  341. : AudioProcessorEditor (&p),
  342. title ("title", p.getName())
  343. {
  344. setOpaque (true);
  345. title.setFont (title.getFont().withStyle (Font::bold));
  346. addAndMakeVisible (title);
  347. {
  348. ScopedLock renderLock (p.getCallbackLock());
  349. p.suspendProcessing (true);
  350. p.releaseResources();
  351. }
  352. if (p.getBusCount (true) > 0 || p.canAddBus (true))
  353. {
  354. inConfig.reset (new InputOutputConfig (*this, true));
  355. addAndMakeVisible (inConfig.get());
  356. }
  357. if (p.getBusCount (false) > 0 || p.canAddBus (false))
  358. {
  359. outConfig.reset (new InputOutputConfig (*this, false));
  360. addAndMakeVisible (outConfig.get());
  361. }
  362. currentLayout = p.getBusesLayout();
  363. setSize (400, (inConfig != nullptr && outConfig != nullptr ? 160 : 0) + 200);
  364. }
  365. IOConfigurationWindow::~IOConfigurationWindow()
  366. {
  367. if (auto* graph = getGraph())
  368. {
  369. if (auto* p = getAudioProcessor())
  370. {
  371. ScopedLock renderLock (graph->getCallbackLock());
  372. graph->suspendProcessing (true);
  373. graph->releaseResources();
  374. p->prepareToPlay (graph->getSampleRate(), graph->getBlockSize());
  375. p->suspendProcessing (false);
  376. graph->prepareToPlay (graph->getSampleRate(), graph->getBlockSize());
  377. graph->suspendProcessing (false);
  378. }
  379. }
  380. }
  381. void IOConfigurationWindow::paint (Graphics& g)
  382. {
  383. g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
  384. }
  385. void IOConfigurationWindow::resized()
  386. {
  387. auto r = getLocalBounds().reduced (10);
  388. title.setBounds (r.removeFromTop (14));
  389. r.reduce (10, 0);
  390. if (inConfig != nullptr)
  391. inConfig->setBounds (r.removeFromTop (160));
  392. if (outConfig != nullptr)
  393. outConfig->setBounds (r.removeFromTop (160));
  394. }
  395. void IOConfigurationWindow::update()
  396. {
  397. auto nodeID = getNodeID();
  398. if (auto* graph = getGraph())
  399. if (nodeID != AudioProcessorGraph::NodeID())
  400. graph->disconnectNode (nodeID);
  401. if (auto* graphEditor = getGraphEditor())
  402. if (auto* panel = graphEditor->graphPanel.get())
  403. panel->updateComponents();
  404. }
  405. AudioProcessorGraph::NodeID IOConfigurationWindow::getNodeID() const
  406. {
  407. if (auto* graph = getGraph())
  408. for (auto* node : graph->getNodes())
  409. if (node->getProcessor() == getAudioProcessor())
  410. return node->nodeID;
  411. return {};
  412. }
  413. MainHostWindow* IOConfigurationWindow::getMainWindow() const
  414. {
  415. auto& desktop = Desktop::getInstance();
  416. for (int i = desktop.getNumComponents(); --i >= 0;)
  417. if (auto* mainWindow = dynamic_cast<MainHostWindow*> (desktop.getComponent(i)))
  418. return mainWindow;
  419. return nullptr;
  420. }
  421. GraphDocumentComponent* IOConfigurationWindow::getGraphEditor() const
  422. {
  423. if (auto* mainWindow = getMainWindow())
  424. return mainWindow->graphHolder.get();
  425. return nullptr;
  426. }
  427. AudioProcessorGraph* IOConfigurationWindow::getGraph() const
  428. {
  429. if (auto* graphEditor = getGraphEditor())
  430. if (auto* panel = graphEditor->graph.get())
  431. return &panel->graph;
  432. return nullptr;
  433. }