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.

1183 lines
36KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #include "../JuceLibraryCode/JuceHeader.h"
  18. #include "GraphEditorPanel.h"
  19. #include "InternalFilters.h"
  20. #include "MainHostWindow.h"
  21. #include "FilterIOConfiguration.h"
  22. //==============================================================================
  23. class PluginWindow;
  24. static Array <PluginWindow*> activePluginWindows;
  25. PluginWindow::PluginWindow (Component* const pluginEditor,
  26. AudioProcessorGraph::Node* const o,
  27. WindowFormatType t,
  28. AudioProcessorGraph& audioGraph)
  29. : DocumentWindow (pluginEditor->getName(), Colours::lightblue,
  30. DocumentWindow::minimiseButton | DocumentWindow::closeButton),
  31. graph (audioGraph),
  32. owner (o),
  33. type (t)
  34. {
  35. setSize (400, 300);
  36. setContentOwned (pluginEditor, true);
  37. setTopLeftPosition (owner->properties.getWithDefault (getLastXProp (type), Random::getSystemRandom().nextInt (500)),
  38. owner->properties.getWithDefault (getLastYProp (type), Random::getSystemRandom().nextInt (500)));
  39. owner->properties.set (getOpenProp (type), true);
  40. setVisible (true);
  41. activePluginWindows.add (this);
  42. }
  43. void PluginWindow::closeCurrentlyOpenWindowsFor (const uint32 nodeId)
  44. {
  45. for (int i = activePluginWindows.size(); --i >= 0;)
  46. if (activePluginWindows.getUnchecked(i)->owner->nodeId == nodeId)
  47. delete activePluginWindows.getUnchecked (i);
  48. }
  49. void PluginWindow::closeAllCurrentlyOpenWindows()
  50. {
  51. if (activePluginWindows.size() > 0)
  52. {
  53. for (int i = activePluginWindows.size(); --i >= 0;)
  54. delete activePluginWindows.getUnchecked (i);
  55. Component dummyModalComp;
  56. dummyModalComp.enterModalState();
  57. MessageManager::getInstance()->runDispatchLoopUntil (50);
  58. }
  59. }
  60. //==============================================================================
  61. class ProcessorProgramPropertyComp : public PropertyComponent,
  62. private AudioProcessorListener
  63. {
  64. public:
  65. ProcessorProgramPropertyComp (const String& name, AudioProcessor& p, int index_)
  66. : PropertyComponent (name),
  67. owner (p),
  68. index (index_)
  69. {
  70. owner.addListener (this);
  71. }
  72. ~ProcessorProgramPropertyComp()
  73. {
  74. owner.removeListener (this);
  75. }
  76. void refresh() { }
  77. void audioProcessorChanged (AudioProcessor*) { }
  78. void audioProcessorParameterChanged (AudioProcessor*, int, float) { }
  79. private:
  80. AudioProcessor& owner;
  81. const int index;
  82. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProcessorProgramPropertyComp)
  83. };
  84. class ProgramAudioProcessorEditor : public AudioProcessorEditor
  85. {
  86. public:
  87. ProgramAudioProcessorEditor (AudioProcessor* const p)
  88. : AudioProcessorEditor (p)
  89. {
  90. jassert (p != nullptr);
  91. setOpaque (true);
  92. addAndMakeVisible (panel);
  93. Array<PropertyComponent*> programs;
  94. const int numPrograms = p->getNumPrograms();
  95. int totalHeight = 0;
  96. for (int i = 0; i < numPrograms; ++i)
  97. {
  98. String name (p->getProgramName (i).trim());
  99. if (name.isEmpty())
  100. name = "Unnamed";
  101. ProcessorProgramPropertyComp* const pc = new ProcessorProgramPropertyComp (name, *p, i);
  102. programs.add (pc);
  103. totalHeight += pc->getPreferredHeight();
  104. }
  105. panel.addProperties (programs);
  106. setSize (400, jlimit (25, 400, totalHeight));
  107. }
  108. void paint (Graphics& g) override
  109. {
  110. g.fillAll (Colours::grey);
  111. }
  112. void resized() override
  113. {
  114. panel.setBounds (getLocalBounds());
  115. }
  116. private:
  117. PropertyPanel panel;
  118. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProgramAudioProcessorEditor)
  119. };
  120. //==============================================================================
  121. PluginWindow* PluginWindow::getWindowFor (AudioProcessorGraph::Node* const node,
  122. WindowFormatType type,
  123. AudioProcessorGraph& audioGraph)
  124. {
  125. jassert (node != nullptr);
  126. for (int i = activePluginWindows.size(); --i >= 0;)
  127. if (activePluginWindows.getUnchecked(i)->owner == node
  128. && activePluginWindows.getUnchecked(i)->type == type)
  129. return activePluginWindows.getUnchecked(i);
  130. AudioProcessor* processor = node->getProcessor();
  131. AudioProcessorEditor* ui = nullptr;
  132. if (type == Normal)
  133. {
  134. ui = processor->createEditorIfNeeded();
  135. if (ui == nullptr)
  136. type = Generic;
  137. }
  138. if (ui == nullptr)
  139. {
  140. if (type == Generic || type == Parameters)
  141. ui = new GenericAudioProcessorEditor (processor);
  142. else if (type == Programs)
  143. ui = new ProgramAudioProcessorEditor (processor);
  144. else if (type == AudioIO)
  145. ui = new FilterIOConfigurationWindow (processor);
  146. }
  147. if (ui != nullptr)
  148. {
  149. if (AudioPluginInstance* const plugin = dynamic_cast<AudioPluginInstance*> (processor))
  150. ui->setName (plugin->getName());
  151. return new PluginWindow (ui, node, type, audioGraph);
  152. }
  153. return nullptr;
  154. }
  155. PluginWindow::~PluginWindow()
  156. {
  157. activePluginWindows.removeFirstMatchingValue (this);
  158. clearContentComponent();
  159. }
  160. void PluginWindow::moved()
  161. {
  162. owner->properties.set (getLastXProp (type), getX());
  163. owner->properties.set (getLastYProp (type), getY());
  164. }
  165. void PluginWindow::closeButtonPressed()
  166. {
  167. owner->properties.set (getOpenProp (type), false);
  168. delete this;
  169. }
  170. //==============================================================================
  171. class PinComponent : public Component,
  172. public SettableTooltipClient
  173. {
  174. public:
  175. PinComponent (FilterGraph& graph_,
  176. const uint32 filterID_, const int index_, const bool isInput_)
  177. : filterID (filterID_),
  178. index (index_),
  179. isInput (isInput_),
  180. busIdx (0),
  181. graph (graph_)
  182. {
  183. if (const AudioProcessorGraph::Node::Ptr node = graph.getNodeForId (filterID_))
  184. {
  185. String tip;
  186. if (index == FilterGraph::midiChannelNumber)
  187. {
  188. tip = isInput ? "MIDI Input"
  189. : "MIDI Output";
  190. }
  191. else
  192. {
  193. const AudioProcessor& processor = *node->getProcessor();
  194. int channel;
  195. channel = processor.getOffsetInBusBufferForAbsoluteChannelIndex (isInput, index, busIdx);
  196. if (const AudioProcessor::Bus* bus = processor.getBus (isInput, busIdx))
  197. tip = bus->getName() + String (": ")
  198. + AudioChannelSet::getAbbreviatedChannelTypeName (bus->getCurrentLayout().getTypeOfChannel (channel));
  199. else
  200. tip = (isInput ? "Main Input: "
  201. : "Main Output: ") + String (index + 1);
  202. }
  203. setTooltip (tip);
  204. }
  205. setSize (16, 16);
  206. }
  207. void paint (Graphics& g) override
  208. {
  209. const float w = (float) getWidth();
  210. const float h = (float) getHeight();
  211. Path p;
  212. p.addEllipse (w * 0.25f, h * 0.25f, w * 0.5f, h * 0.5f);
  213. p.addRectangle (w * 0.4f, isInput ? (0.5f * h) : 0.0f, w * 0.2f, h * 0.5f);
  214. Colour colour = (index == FilterGraph::midiChannelNumber ? Colours::red : Colours::green);
  215. g.setColour (colour.withRotatedHue (static_cast<float> (busIdx) / 5.0f));
  216. g.fillPath (p);
  217. }
  218. void mouseDown (const MouseEvent& e) override
  219. {
  220. getGraphPanel()->beginConnectorDrag (isInput ? 0 : filterID,
  221. index,
  222. isInput ? filterID : 0,
  223. index,
  224. e);
  225. }
  226. void mouseDrag (const MouseEvent& e) override
  227. {
  228. getGraphPanel()->dragConnector (e);
  229. }
  230. void mouseUp (const MouseEvent& e) override
  231. {
  232. getGraphPanel()->endDraggingConnector (e);
  233. }
  234. const uint32 filterID;
  235. const int index;
  236. const bool isInput;
  237. int busIdx;
  238. private:
  239. FilterGraph& graph;
  240. GraphEditorPanel* getGraphPanel() const noexcept
  241. {
  242. return findParentComponentOfClass<GraphEditorPanel>();
  243. }
  244. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PinComponent)
  245. };
  246. //==============================================================================
  247. class FilterComponent : public Component
  248. {
  249. public:
  250. FilterComponent (FilterGraph& graph_,
  251. const uint32 filterID_)
  252. : graph (graph_),
  253. filterID (filterID_),
  254. numInputs (0),
  255. numOutputs (0),
  256. pinSize (16),
  257. font (13.0f, Font::bold),
  258. numIns (0),
  259. numOuts (0)
  260. {
  261. shadow.setShadowProperties (DropShadow (Colours::black.withAlpha (0.5f), 3, Point<int> (0, 1)));
  262. setComponentEffect (&shadow);
  263. setSize (150, 60);
  264. }
  265. ~FilterComponent()
  266. {
  267. deleteAllChildren();
  268. }
  269. void mouseDown (const MouseEvent& e) override
  270. {
  271. originalPos = localPointToGlobal (Point<int>());
  272. toFront (true);
  273. if (e.mods.isPopupMenu())
  274. {
  275. PopupMenu m;
  276. m.addItem (1, "Delete this filter");
  277. m.addItem (2, "Disconnect all pins");
  278. m.addSeparator();
  279. m.addItem (3, "Show plugin UI");
  280. m.addItem (4, "Show all programs");
  281. m.addItem (5, "Show all parameters");
  282. m.addSeparator();
  283. m.addItem (6, "Configure Audio I/O");
  284. m.addItem (7, "Test state save/load");
  285. const int r = m.show();
  286. if (r == 1)
  287. {
  288. graph.removeFilter (filterID);
  289. return;
  290. }
  291. else if (r == 2)
  292. {
  293. graph.disconnectFilter (filterID);
  294. }
  295. else
  296. {
  297. if (AudioProcessorGraph::Node::Ptr f = graph.getNodeForId (filterID))
  298. {
  299. AudioProcessor* const processor = f->getProcessor();
  300. jassert (processor != nullptr);
  301. if (r == 7)
  302. {
  303. MemoryBlock state;
  304. processor->getStateInformation (state);
  305. processor->setStateInformation (state.getData(), (int) state.getSize());
  306. }
  307. else
  308. {
  309. PluginWindow::WindowFormatType type = processor->hasEditor() ? PluginWindow::Normal
  310. : PluginWindow::Generic;
  311. switch (r)
  312. {
  313. case 4: type = PluginWindow::Programs; break;
  314. case 5: type = PluginWindow::Parameters; break;
  315. case 6: type = PluginWindow::AudioIO; break;
  316. default: break;
  317. };
  318. if (PluginWindow* const w = PluginWindow::getWindowFor (f, type, graph.getGraph()))
  319. w->toFront (true);
  320. }
  321. }
  322. }
  323. }
  324. }
  325. void mouseDrag (const MouseEvent& e) override
  326. {
  327. if (! e.mods.isPopupMenu())
  328. {
  329. Point<int> pos (originalPos + Point<int> (e.getDistanceFromDragStartX(), e.getDistanceFromDragStartY()));
  330. if (getParentComponent() != nullptr)
  331. pos = getParentComponent()->getLocalPoint (nullptr, pos);
  332. graph.setNodePosition (filterID,
  333. (pos.getX() + getWidth() / 2) / (double) getParentWidth(),
  334. (pos.getY() + getHeight() / 2) / (double) getParentHeight());
  335. getGraphPanel()->updateComponents();
  336. }
  337. }
  338. void mouseUp (const MouseEvent& e) override
  339. {
  340. if (e.mouseWasDraggedSinceMouseDown())
  341. {
  342. graph.setChangedFlag (true);
  343. }
  344. else if (e.getNumberOfClicks() == 2)
  345. {
  346. if (const AudioProcessorGraph::Node::Ptr f = graph.getNodeForId (filterID))
  347. if (PluginWindow* const w = PluginWindow::getWindowFor (f, PluginWindow::Normal, graph.getGraph()))
  348. w->toFront (true);
  349. }
  350. }
  351. bool hitTest (int x, int y) override
  352. {
  353. for (int i = getNumChildComponents(); --i >= 0;)
  354. if (getChildComponent(i)->getBounds().contains (x, y))
  355. return true;
  356. return x >= 3 && x < getWidth() - 6 && y >= pinSize && y < getHeight() - pinSize;
  357. }
  358. void paint (Graphics& g) override
  359. {
  360. g.setColour (Colours::lightgrey);
  361. const int x = 4;
  362. const int y = pinSize;
  363. const int w = getWidth() - x * 2;
  364. const int h = getHeight() - pinSize * 2;
  365. g.fillRect (x, y, w, h);
  366. g.setColour (Colours::black);
  367. g.setFont (font);
  368. g.drawFittedText (getName(), getLocalBounds().reduced (4, 2), Justification::centred, 2);
  369. g.setColour (Colours::grey);
  370. g.drawRect (x, y, w, h);
  371. }
  372. void resized() override
  373. {
  374. if (AudioProcessorGraph::Node::Ptr f = graph.getNodeForId (filterID))
  375. {
  376. if (AudioProcessor* const processor = f->getProcessor())
  377. {
  378. for (int i = 0; i < getNumChildComponents(); ++i)
  379. {
  380. if (PinComponent* const pc = dynamic_cast<PinComponent*> (getChildComponent(i)))
  381. {
  382. const bool isInput = pc->isInput;
  383. int busIdx, channelIdx;
  384. channelIdx =
  385. processor->getOffsetInBusBufferForAbsoluteChannelIndex (isInput, pc->index, busIdx);
  386. const int total = isInput ? numIns : numOuts;
  387. const int index = pc->index == FilterGraph::midiChannelNumber ? (total - 1) : pc->index;
  388. const float totalSpaces = static_cast<float> (total) + (static_cast<float> (jmax (0, processor->getBusCount (isInput) - 1)) * 0.5f);
  389. const float indexPos = static_cast<float> (index) + (static_cast<float> (busIdx) * 0.5f);
  390. pc->setBounds (proportionOfWidth ((1.0f + indexPos) / (totalSpaces + 1.0f)) - pinSize / 2,
  391. pc->isInput ? 0 : (getHeight() - pinSize),
  392. pinSize, pinSize);
  393. }
  394. }
  395. }
  396. }
  397. }
  398. void getPinPos (const int index, const bool isInput, float& x, float& y)
  399. {
  400. for (int i = 0; i < getNumChildComponents(); ++i)
  401. {
  402. if (PinComponent* const pc = dynamic_cast<PinComponent*> (getChildComponent(i)))
  403. {
  404. if (pc->index == index && isInput == pc->isInput)
  405. {
  406. x = getX() + pc->getX() + pc->getWidth() * 0.5f;
  407. y = getY() + pc->getY() + pc->getHeight() * 0.5f;
  408. break;
  409. }
  410. }
  411. }
  412. }
  413. void update()
  414. {
  415. const AudioProcessorGraph::Node::Ptr f (graph.getNodeForId (filterID));
  416. if (f == nullptr)
  417. {
  418. delete this;
  419. return;
  420. }
  421. numIns = f->getProcessor()->getTotalNumInputChannels();
  422. if (f->getProcessor()->acceptsMidi())
  423. ++numIns;
  424. numOuts = f->getProcessor()->getTotalNumOutputChannels();
  425. if (f->getProcessor()->producesMidi())
  426. ++numOuts;
  427. int w = 100;
  428. int h = 60;
  429. w = jmax (w, (jmax (numIns, numOuts) + 1) * 20);
  430. const int textWidth = font.getStringWidth (f->getProcessor()->getName());
  431. w = jmax (w, 16 + jmin (textWidth, 300));
  432. if (textWidth > 300)
  433. h = 100;
  434. setSize (w, h);
  435. setName (f->getProcessor()->getName());
  436. {
  437. Point<double> p = graph.getNodePosition (filterID);
  438. setCentreRelative ((float) p.x, (float) p.y);
  439. }
  440. if (numIns != numInputs || numOuts != numOutputs)
  441. {
  442. numInputs = numIns;
  443. numOutputs = numOuts;
  444. deleteAllChildren();
  445. int i;
  446. for (i = 0; i < f->getProcessor()->getTotalNumInputChannels(); ++i)
  447. addAndMakeVisible (new PinComponent (graph, filterID, i, true));
  448. if (f->getProcessor()->acceptsMidi())
  449. addAndMakeVisible (new PinComponent (graph, filterID, FilterGraph::midiChannelNumber, true));
  450. for (i = 0; i < f->getProcessor()->getTotalNumOutputChannels(); ++i)
  451. addAndMakeVisible (new PinComponent (graph, filterID, i, false));
  452. if (f->getProcessor()->producesMidi())
  453. addAndMakeVisible (new PinComponent (graph, filterID, FilterGraph::midiChannelNumber, false));
  454. resized();
  455. }
  456. }
  457. FilterGraph& graph;
  458. const uint32 filterID;
  459. int numInputs, numOutputs;
  460. private:
  461. int pinSize;
  462. Point<int> originalPos;
  463. Font font;
  464. int numIns, numOuts;
  465. DropShadowEffect shadow;
  466. GraphEditorPanel* getGraphPanel() const noexcept
  467. {
  468. return findParentComponentOfClass<GraphEditorPanel>();
  469. }
  470. FilterComponent (const FilterComponent&);
  471. FilterComponent& operator= (const FilterComponent&);
  472. };
  473. //==============================================================================
  474. class ConnectorComponent : public Component,
  475. public SettableTooltipClient
  476. {
  477. public:
  478. ConnectorComponent (FilterGraph& graph_)
  479. : sourceFilterID (0),
  480. destFilterID (0),
  481. sourceFilterChannel (0),
  482. destFilterChannel (0),
  483. graph (graph_),
  484. lastInputX (0),
  485. lastInputY (0),
  486. lastOutputX (0),
  487. lastOutputY (0)
  488. {
  489. setAlwaysOnTop (true);
  490. }
  491. void setInput (const uint32 sourceFilterID_, const int sourceFilterChannel_)
  492. {
  493. if (sourceFilterID != sourceFilterID_ || sourceFilterChannel != sourceFilterChannel_)
  494. {
  495. sourceFilterID = sourceFilterID_;
  496. sourceFilterChannel = sourceFilterChannel_;
  497. update();
  498. }
  499. }
  500. void setOutput (const uint32 destFilterID_, const int destFilterChannel_)
  501. {
  502. if (destFilterID != destFilterID_ || destFilterChannel != destFilterChannel_)
  503. {
  504. destFilterID = destFilterID_;
  505. destFilterChannel = destFilterChannel_;
  506. update();
  507. }
  508. }
  509. void dragStart (int x, int y)
  510. {
  511. lastInputX = (float) x;
  512. lastInputY = (float) y;
  513. resizeToFit();
  514. }
  515. void dragEnd (int x, int y)
  516. {
  517. lastOutputX = (float) x;
  518. lastOutputY = (float) y;
  519. resizeToFit();
  520. }
  521. void update()
  522. {
  523. float x1, y1, x2, y2;
  524. getPoints (x1, y1, x2, y2);
  525. if (lastInputX != x1
  526. || lastInputY != y1
  527. || lastOutputX != x2
  528. || lastOutputY != y2)
  529. {
  530. resizeToFit();
  531. }
  532. }
  533. void resizeToFit()
  534. {
  535. float x1, y1, x2, y2;
  536. getPoints (x1, y1, x2, y2);
  537. const Rectangle<int> newBounds ((int) jmin (x1, x2) - 4,
  538. (int) jmin (y1, y2) - 4,
  539. (int) std::abs (x1 - x2) + 8,
  540. (int) std::abs (y1 - y2) + 8);
  541. if (newBounds != getBounds())
  542. setBounds (newBounds);
  543. else
  544. resized();
  545. repaint();
  546. }
  547. void getPoints (float& x1, float& y1, float& x2, float& y2) const
  548. {
  549. x1 = lastInputX;
  550. y1 = lastInputY;
  551. x2 = lastOutputX;
  552. y2 = lastOutputY;
  553. if (GraphEditorPanel* const hostPanel = getGraphPanel())
  554. {
  555. if (FilterComponent* srcFilterComp = hostPanel->getComponentForFilter (sourceFilterID))
  556. srcFilterComp->getPinPos (sourceFilterChannel, false, x1, y1);
  557. if (FilterComponent* dstFilterComp = hostPanel->getComponentForFilter (destFilterID))
  558. dstFilterComp->getPinPos (destFilterChannel, true, x2, y2);
  559. }
  560. }
  561. void paint (Graphics& g) override
  562. {
  563. if (sourceFilterChannel == FilterGraph::midiChannelNumber
  564. || destFilterChannel == FilterGraph::midiChannelNumber)
  565. {
  566. g.setColour (Colours::red);
  567. }
  568. else
  569. {
  570. g.setColour (Colours::green);
  571. }
  572. g.fillPath (linePath);
  573. }
  574. bool hitTest (int x, int y) override
  575. {
  576. if (hitPath.contains ((float) x, (float) y))
  577. {
  578. double distanceFromStart, distanceFromEnd;
  579. getDistancesFromEnds (x, y, distanceFromStart, distanceFromEnd);
  580. // avoid clicking the connector when over a pin
  581. return distanceFromStart > 7.0 && distanceFromEnd > 7.0;
  582. }
  583. return false;
  584. }
  585. void mouseDown (const MouseEvent&) override
  586. {
  587. dragging = false;
  588. }
  589. void mouseDrag (const MouseEvent& e) override
  590. {
  591. if (dragging)
  592. {
  593. getGraphPanel()->dragConnector (e);
  594. }
  595. else if (e.mouseWasDraggedSinceMouseDown())
  596. {
  597. dragging = true;
  598. graph.removeConnection (sourceFilterID, sourceFilterChannel, destFilterID, destFilterChannel);
  599. double distanceFromStart, distanceFromEnd;
  600. getDistancesFromEnds (e.x, e.y, distanceFromStart, distanceFromEnd);
  601. const bool isNearerSource = (distanceFromStart < distanceFromEnd);
  602. getGraphPanel()->beginConnectorDrag (isNearerSource ? 0 : sourceFilterID,
  603. sourceFilterChannel,
  604. isNearerSource ? destFilterID : 0,
  605. destFilterChannel,
  606. e);
  607. }
  608. }
  609. void mouseUp (const MouseEvent& e) override
  610. {
  611. if (dragging)
  612. getGraphPanel()->endDraggingConnector (e);
  613. }
  614. void resized() override
  615. {
  616. float x1, y1, x2, y2;
  617. getPoints (x1, y1, x2, y2);
  618. lastInputX = x1;
  619. lastInputY = y1;
  620. lastOutputX = x2;
  621. lastOutputY = y2;
  622. x1 -= getX();
  623. y1 -= getY();
  624. x2 -= getX();
  625. y2 -= getY();
  626. linePath.clear();
  627. linePath.startNewSubPath (x1, y1);
  628. linePath.cubicTo (x1, y1 + (y2 - y1) * 0.33f,
  629. x2, y1 + (y2 - y1) * 0.66f,
  630. x2, y2);
  631. PathStrokeType wideStroke (8.0f);
  632. wideStroke.createStrokedPath (hitPath, linePath);
  633. PathStrokeType stroke (2.5f);
  634. stroke.createStrokedPath (linePath, linePath);
  635. const float arrowW = 5.0f;
  636. const float arrowL = 4.0f;
  637. Path arrow;
  638. arrow.addTriangle (-arrowL, arrowW,
  639. -arrowL, -arrowW,
  640. arrowL, 0.0f);
  641. arrow.applyTransform (AffineTransform()
  642. .rotated (float_Pi * 0.5f - (float) atan2 (x2 - x1, y2 - y1))
  643. .translated ((x1 + x2) * 0.5f,
  644. (y1 + y2) * 0.5f));
  645. linePath.addPath (arrow);
  646. linePath.setUsingNonZeroWinding (true);
  647. }
  648. uint32 sourceFilterID, destFilterID;
  649. int sourceFilterChannel, destFilterChannel;
  650. private:
  651. FilterGraph& graph;
  652. float lastInputX, lastInputY, lastOutputX, lastOutputY;
  653. Path linePath, hitPath;
  654. bool dragging;
  655. GraphEditorPanel* getGraphPanel() const noexcept
  656. {
  657. return findParentComponentOfClass<GraphEditorPanel>();
  658. }
  659. void getDistancesFromEnds (int x, int y, double& distanceFromStart, double& distanceFromEnd) const
  660. {
  661. float x1, y1, x2, y2;
  662. getPoints (x1, y1, x2, y2);
  663. distanceFromStart = juce_hypot (x - (x1 - getX()), y - (y1 - getY()));
  664. distanceFromEnd = juce_hypot (x - (x2 - getX()), y - (y2 - getY()));
  665. }
  666. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectorComponent)
  667. };
  668. //==============================================================================
  669. GraphEditorPanel::GraphEditorPanel (FilterGraph& graph_)
  670. : graph (graph_)
  671. {
  672. graph.addChangeListener (this);
  673. setOpaque (true);
  674. }
  675. GraphEditorPanel::~GraphEditorPanel()
  676. {
  677. graph.removeChangeListener (this);
  678. draggingConnector = nullptr;
  679. deleteAllChildren();
  680. }
  681. void GraphEditorPanel::paint (Graphics& g)
  682. {
  683. g.fillAll (Colours::white);
  684. }
  685. void GraphEditorPanel::mouseDown (const MouseEvent& e)
  686. {
  687. if (e.mods.isPopupMenu())
  688. {
  689. PopupMenu m;
  690. if (MainHostWindow* const mainWindow = findParentComponentOfClass<MainHostWindow>())
  691. {
  692. mainWindow->addPluginsToMenu (m);
  693. const int r = m.show();
  694. createNewPlugin (mainWindow->getChosenType (r), e.x, e.y);
  695. }
  696. }
  697. }
  698. void GraphEditorPanel::createNewPlugin (const PluginDescription* desc, int x, int y)
  699. {
  700. graph.addFilter (desc, x / (double) getWidth(), y / (double) getHeight());
  701. }
  702. FilterComponent* GraphEditorPanel::getComponentForFilter (const uint32 filterID) const
  703. {
  704. for (int i = getNumChildComponents(); --i >= 0;)
  705. {
  706. if (FilterComponent* const fc = dynamic_cast<FilterComponent*> (getChildComponent (i)))
  707. if (fc->filterID == filterID)
  708. return fc;
  709. }
  710. return nullptr;
  711. }
  712. ConnectorComponent* GraphEditorPanel::getComponentForConnection (const AudioProcessorGraph::Connection& conn) const
  713. {
  714. for (int i = getNumChildComponents(); --i >= 0;)
  715. {
  716. if (ConnectorComponent* const c = dynamic_cast<ConnectorComponent*> (getChildComponent (i)))
  717. if (c->sourceFilterID == conn.sourceNodeId
  718. && c->destFilterID == conn.destNodeId
  719. && c->sourceFilterChannel == conn.sourceChannelIndex
  720. && c->destFilterChannel == conn.destChannelIndex)
  721. return c;
  722. }
  723. return nullptr;
  724. }
  725. PinComponent* GraphEditorPanel::findPinAt (const int x, const int y) const
  726. {
  727. for (int i = getNumChildComponents(); --i >= 0;)
  728. {
  729. if (FilterComponent* fc = dynamic_cast<FilterComponent*> (getChildComponent (i)))
  730. {
  731. if (PinComponent* pin = dynamic_cast<PinComponent*> (fc->getComponentAt (x - fc->getX(),
  732. y - fc->getY())))
  733. return pin;
  734. }
  735. }
  736. return nullptr;
  737. }
  738. void GraphEditorPanel::resized()
  739. {
  740. updateComponents();
  741. }
  742. void GraphEditorPanel::changeListenerCallback (ChangeBroadcaster*)
  743. {
  744. updateComponents();
  745. }
  746. void GraphEditorPanel::updateComponents()
  747. {
  748. for (int i = getNumChildComponents(); --i >= 0;)
  749. {
  750. if (FilterComponent* const fc = dynamic_cast<FilterComponent*> (getChildComponent (i)))
  751. fc->update();
  752. }
  753. for (int i = getNumChildComponents(); --i >= 0;)
  754. {
  755. ConnectorComponent* const cc = dynamic_cast<ConnectorComponent*> (getChildComponent (i));
  756. if (cc != nullptr && cc != draggingConnector)
  757. {
  758. if (graph.getConnectionBetween (cc->sourceFilterID, cc->sourceFilterChannel,
  759. cc->destFilterID, cc->destFilterChannel) == nullptr)
  760. {
  761. delete cc;
  762. }
  763. else
  764. {
  765. cc->update();
  766. }
  767. }
  768. }
  769. for (int i = graph.getNumFilters(); --i >= 0;)
  770. {
  771. const AudioProcessorGraph::Node::Ptr f (graph.getNode (i));
  772. if (getComponentForFilter (f->nodeId) == 0)
  773. {
  774. FilterComponent* const comp = new FilterComponent (graph, f->nodeId);
  775. addAndMakeVisible (comp);
  776. comp->update();
  777. }
  778. }
  779. for (int i = graph.getNumConnections(); --i >= 0;)
  780. {
  781. const AudioProcessorGraph::Connection* const c = graph.getConnection (i);
  782. if (getComponentForConnection (*c) == 0)
  783. {
  784. ConnectorComponent* const comp = new ConnectorComponent (graph);
  785. addAndMakeVisible (comp);
  786. comp->setInput (c->sourceNodeId, c->sourceChannelIndex);
  787. comp->setOutput (c->destNodeId, c->destChannelIndex);
  788. }
  789. }
  790. }
  791. void GraphEditorPanel::beginConnectorDrag (const uint32 sourceFilterID, const int sourceFilterChannel,
  792. const uint32 destFilterID, const int destFilterChannel,
  793. const MouseEvent& e)
  794. {
  795. draggingConnector = dynamic_cast<ConnectorComponent*> (e.originalComponent);
  796. if (draggingConnector == nullptr)
  797. draggingConnector = new ConnectorComponent (graph);
  798. draggingConnector->setInput (sourceFilterID, sourceFilterChannel);
  799. draggingConnector->setOutput (destFilterID, destFilterChannel);
  800. addAndMakeVisible (draggingConnector);
  801. draggingConnector->toFront (false);
  802. dragConnector (e);
  803. }
  804. void GraphEditorPanel::dragConnector (const MouseEvent& e)
  805. {
  806. const MouseEvent e2 (e.getEventRelativeTo (this));
  807. if (draggingConnector != nullptr)
  808. {
  809. draggingConnector->setTooltip (String());
  810. int x = e2.x;
  811. int y = e2.y;
  812. if (PinComponent* const pin = findPinAt (x, y))
  813. {
  814. uint32 srcFilter = draggingConnector->sourceFilterID;
  815. int srcChannel = draggingConnector->sourceFilterChannel;
  816. uint32 dstFilter = draggingConnector->destFilterID;
  817. int dstChannel = draggingConnector->destFilterChannel;
  818. if (srcFilter == 0 && ! pin->isInput)
  819. {
  820. srcFilter = pin->filterID;
  821. srcChannel = pin->index;
  822. }
  823. else if (dstFilter == 0 && pin->isInput)
  824. {
  825. dstFilter = pin->filterID;
  826. dstChannel = pin->index;
  827. }
  828. if (graph.canConnect (srcFilter, srcChannel, dstFilter, dstChannel))
  829. {
  830. x = pin->getParentComponent()->getX() + pin->getX() + pin->getWidth() / 2;
  831. y = pin->getParentComponent()->getY() + pin->getY() + pin->getHeight() / 2;
  832. draggingConnector->setTooltip (pin->getTooltip());
  833. }
  834. }
  835. if (draggingConnector->sourceFilterID == 0)
  836. draggingConnector->dragStart (x, y);
  837. else
  838. draggingConnector->dragEnd (x, y);
  839. }
  840. }
  841. void GraphEditorPanel::endDraggingConnector (const MouseEvent& e)
  842. {
  843. if (draggingConnector == nullptr)
  844. return;
  845. draggingConnector->setTooltip (String());
  846. const MouseEvent e2 (e.getEventRelativeTo (this));
  847. uint32 srcFilter = draggingConnector->sourceFilterID;
  848. int srcChannel = draggingConnector->sourceFilterChannel;
  849. uint32 dstFilter = draggingConnector->destFilterID;
  850. int dstChannel = draggingConnector->destFilterChannel;
  851. draggingConnector = nullptr;
  852. if (PinComponent* const pin = findPinAt (e2.x, e2.y))
  853. {
  854. if (srcFilter == 0)
  855. {
  856. if (pin->isInput)
  857. return;
  858. srcFilter = pin->filterID;
  859. srcChannel = pin->index;
  860. }
  861. else
  862. {
  863. if (! pin->isInput)
  864. return;
  865. dstFilter = pin->filterID;
  866. dstChannel = pin->index;
  867. }
  868. graph.addConnection (srcFilter, srcChannel, dstFilter, dstChannel);
  869. }
  870. }
  871. //==============================================================================
  872. class TooltipBar : public Component,
  873. private Timer
  874. {
  875. public:
  876. TooltipBar()
  877. {
  878. startTimer (100);
  879. }
  880. void paint (Graphics& g) override
  881. {
  882. g.setFont (Font (getHeight() * 0.7f, Font::bold));
  883. g.setColour (Colours::black);
  884. g.drawFittedText (tip, 10, 0, getWidth() - 12, getHeight(), Justification::centredLeft, 1);
  885. }
  886. void timerCallback() override
  887. {
  888. Component* const underMouse = Desktop::getInstance().getMainMouseSource().getComponentUnderMouse();
  889. TooltipClient* const ttc = dynamic_cast<TooltipClient*> (underMouse);
  890. String newTip;
  891. if (ttc != nullptr && ! (underMouse->isMouseButtonDown() || underMouse->isCurrentlyBlockedByAnotherModalComponent()))
  892. newTip = ttc->getTooltip();
  893. if (newTip != tip)
  894. {
  895. tip = newTip;
  896. repaint();
  897. }
  898. }
  899. private:
  900. String tip;
  901. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TooltipBar)
  902. };
  903. //==============================================================================
  904. GraphDocumentComponent::GraphDocumentComponent (AudioPluginFormatManager& formatManager,
  905. AudioDeviceManager* deviceManager_)
  906. : graph (new FilterGraph (formatManager)), deviceManager (deviceManager_),
  907. graphPlayer (getAppProperties().getUserSettings()->getBoolValue ("doublePrecisionProcessing", false))
  908. {
  909. addAndMakeVisible (graphPanel = new GraphEditorPanel (*graph));
  910. deviceManager->addChangeListener (graphPanel);
  911. graphPlayer.setProcessor (&graph->getGraph());
  912. keyState.addListener (&graphPlayer.getMidiMessageCollector());
  913. addAndMakeVisible (keyboardComp = new MidiKeyboardComponent (keyState,
  914. MidiKeyboardComponent::horizontalKeyboard));
  915. addAndMakeVisible (statusBar = new TooltipBar());
  916. deviceManager->addAudioCallback (&graphPlayer);
  917. deviceManager->addMidiInputCallback (String(), &graphPlayer.getMidiMessageCollector());
  918. graphPanel->updateComponents();
  919. }
  920. GraphDocumentComponent::~GraphDocumentComponent()
  921. {
  922. releaseGraph();
  923. keyState.removeListener (&graphPlayer.getMidiMessageCollector());
  924. }
  925. void GraphDocumentComponent::resized()
  926. {
  927. const int keysHeight = 60;
  928. const int statusHeight = 20;
  929. graphPanel->setBounds (0, 0, getWidth(), getHeight() - keysHeight);
  930. statusBar->setBounds (0, getHeight() - keysHeight - statusHeight, getWidth(), statusHeight);
  931. keyboardComp->setBounds (0, getHeight() - keysHeight, getWidth(), keysHeight);
  932. }
  933. void GraphDocumentComponent::createNewPlugin (const PluginDescription* desc, int x, int y)
  934. {
  935. graphPanel->createNewPlugin (desc, x, y);
  936. }
  937. void GraphDocumentComponent::unfocusKeyboardComponent()
  938. {
  939. keyboardComp->unfocusAllComponents();
  940. }
  941. void GraphDocumentComponent::releaseGraph()
  942. {
  943. deviceManager->removeAudioCallback (&graphPlayer);
  944. deviceManager->removeMidiInputCallback (String(), &graphPlayer.getMidiMessageCollector());
  945. deviceManager->removeChangeListener (graphPanel);
  946. deleteAllChildren();
  947. graphPlayer.setProcessor (nullptr);
  948. graph = nullptr;
  949. }