Audio plugin host https://kx.studio/carla
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.

1128 lines
34KB

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