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.

1061 lines
31KB

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