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.

1144 lines
35KB

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