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.

1063 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. 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. PluginWindow* PluginWindow::getWindowFor (AudioProcessorGraph::Node* node,
  58. bool useGenericView)
  59. {
  60. for (int i = activePluginWindows.size(); --i >= 0;)
  61. if (activePluginWindows.getUnchecked(i)->owner == node
  62. && activePluginWindows.getUnchecked(i)->isGeneric == useGenericView)
  63. return activePluginWindows.getUnchecked(i);
  64. AudioProcessorEditor* ui = nullptr;
  65. if (! useGenericView)
  66. {
  67. ui = node->getProcessor()->createEditorIfNeeded();
  68. if (ui == nullptr)
  69. useGenericView = true;
  70. }
  71. if (useGenericView)
  72. ui = new GenericAudioProcessorEditor (node->getProcessor());
  73. if (ui != nullptr)
  74. {
  75. AudioPluginInstance* const plugin = dynamic_cast <AudioPluginInstance*> (node->getProcessor());
  76. if (plugin != nullptr)
  77. ui->setName (plugin->getName());
  78. return new PluginWindow (ui, node, useGenericView);
  79. }
  80. return nullptr;
  81. }
  82. PluginWindow::~PluginWindow()
  83. {
  84. activePluginWindows.removeFirstMatchingValue (this);
  85. clearContentComponent();
  86. }
  87. void PluginWindow::moved()
  88. {
  89. owner->properties.set ("uiLastX", getX());
  90. owner->properties.set ("uiLastY", getY());
  91. }
  92. void PluginWindow::closeButtonPressed()
  93. {
  94. delete this;
  95. }
  96. //==============================================================================
  97. class PinComponent : public Component,
  98. public SettableTooltipClient
  99. {
  100. public:
  101. PinComponent (FilterGraph& graph_,
  102. const uint32 filterID_, const int index_, const bool isInput_)
  103. : filterID (filterID_),
  104. index (index_),
  105. isInput (isInput_),
  106. graph (graph_)
  107. {
  108. const AudioProcessorGraph::Node::Ptr node (graph.getNodeForId (filterID_));
  109. if (node != nullptr)
  110. {
  111. String tip;
  112. if (isInput)
  113. tip = node->getProcessor()->getInputChannelName (index_);
  114. else
  115. tip = node->getProcessor()->getOutputChannelName (index_);
  116. if (tip.isEmpty())
  117. {
  118. if (index_ == FilterGraph::midiChannelNumber)
  119. tip = isInput ? "Midi Input" : "Midi Output";
  120. else
  121. tip = (isInput ? "Input " : "Output ") + String (index_ + 1);
  122. }
  123. setTooltip (tip);
  124. }
  125. setSize (16, 16);
  126. }
  127. void paint (Graphics& g)
  128. {
  129. const float w = (float) getWidth();
  130. const float h = (float) getHeight();
  131. Path p;
  132. p.addEllipse (w * 0.25f, h * 0.25f, w * 0.5f, h * 0.5f);
  133. p.addRectangle (w * 0.4f, isInput ? (0.5f * h) : 0.0f, w * 0.2f, h * 0.5f);
  134. g.setColour (index == FilterGraph::midiChannelNumber ? Colours::red : Colours::green);
  135. g.fillPath (p);
  136. }
  137. void mouseDown (const MouseEvent& e)
  138. {
  139. getGraphPanel()->beginConnectorDrag (isInput ? 0 : filterID,
  140. index,
  141. isInput ? filterID : 0,
  142. index,
  143. e);
  144. }
  145. void mouseDrag (const MouseEvent& e)
  146. {
  147. getGraphPanel()->dragConnector (e);
  148. }
  149. void mouseUp (const MouseEvent& e)
  150. {
  151. getGraphPanel()->endDraggingConnector (e);
  152. }
  153. const uint32 filterID;
  154. const int index;
  155. const bool isInput;
  156. private:
  157. FilterGraph& graph;
  158. GraphEditorPanel* getGraphPanel() const noexcept
  159. {
  160. return findParentComponentOfClass<GraphEditorPanel>();
  161. }
  162. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PinComponent);
  163. };
  164. //==============================================================================
  165. class FilterComponent : public Component
  166. {
  167. public:
  168. FilterComponent (FilterGraph& graph_,
  169. const uint32 filterID_)
  170. : graph (graph_),
  171. filterID (filterID_),
  172. numInputs (0),
  173. numOutputs (0),
  174. pinSize (16),
  175. font (13.0f, Font::bold),
  176. numIns (0),
  177. numOuts (0)
  178. {
  179. shadow.setShadowProperties (DropShadow (Colours::black.withAlpha (0.5f), 3, Point<int> (0, 1)));
  180. setComponentEffect (&shadow);
  181. setSize (150, 60);
  182. }
  183. ~FilterComponent()
  184. {
  185. deleteAllChildren();
  186. }
  187. void mouseDown (const MouseEvent& e)
  188. {
  189. originalPos = localPointToGlobal (Point<int>());
  190. toFront (true);
  191. if (e.mods.isPopupMenu())
  192. {
  193. PopupMenu m;
  194. m.addItem (1, "Delete this filter");
  195. m.addItem (2, "Disconnect all pins");
  196. m.addSeparator();
  197. m.addItem (3, "Show plugin UI");
  198. m.addItem (4, "Show all parameters");
  199. const int r = m.show();
  200. if (r == 1)
  201. {
  202. graph.removeFilter (filterID);
  203. return;
  204. }
  205. else if (r == 2)
  206. {
  207. graph.disconnectFilter (filterID);
  208. }
  209. else if (r == 3 || r == 4)
  210. {
  211. AudioProcessorGraph::Node::Ptr f (graph.getNodeForId (filterID));
  212. if (f != nullptr)
  213. {
  214. PluginWindow* const w = PluginWindow::getWindowFor (f, r == 4);
  215. if (w != nullptr)
  216. w->toFront (true);
  217. }
  218. }
  219. }
  220. }
  221. void mouseDrag (const MouseEvent& e)
  222. {
  223. if (! e.mods.isPopupMenu())
  224. {
  225. Point<int> pos (originalPos + Point<int> (e.getDistanceFromDragStartX(), e.getDistanceFromDragStartY()));
  226. if (getParentComponent() != nullptr)
  227. pos = getParentComponent()->getLocalPoint (nullptr, pos);
  228. graph.setNodePosition (filterID,
  229. (pos.getX() + getWidth() / 2) / (double) getParentWidth(),
  230. (pos.getY() + getHeight() / 2) / (double) getParentHeight());
  231. getGraphPanel()->updateComponents();
  232. }
  233. }
  234. void mouseUp (const MouseEvent& e)
  235. {
  236. if (e.mouseWasClicked() && e.getNumberOfClicks() == 2)
  237. {
  238. const AudioProcessorGraph::Node::Ptr f (graph.getNodeForId (filterID));
  239. if (f != nullptr)
  240. {
  241. PluginWindow* const w = PluginWindow::getWindowFor (f, false);
  242. if (w != nullptr)
  243. w->toFront (true);
  244. }
  245. }
  246. else if (! e.mouseWasClicked())
  247. {
  248. graph.setChangedFlag (true);
  249. }
  250. }
  251. bool hitTest (int x, int y)
  252. {
  253. for (int i = getNumChildComponents(); --i >= 0;)
  254. if (getChildComponent(i)->getBounds().contains (x, y))
  255. return true;
  256. return x >= 3 && x < getWidth() - 6 && y >= pinSize && y < getHeight() - pinSize;
  257. }
  258. void paint (Graphics& g)
  259. {
  260. g.setColour (Colours::lightgrey);
  261. const int x = 4;
  262. const int y = pinSize;
  263. const int w = getWidth() - x * 2;
  264. const int h = getHeight() - pinSize * 2;
  265. g.fillRect (x, y, w, h);
  266. g.setColour (Colours::black);
  267. g.setFont (font);
  268. g.drawFittedText (getName(), getLocalBounds().reduced (4, 2), Justification::centred, 2);
  269. g.setColour (Colours::grey);
  270. g.drawRect (x, y, w, h);
  271. }
  272. void resized()
  273. {
  274. for (int i = 0; i < getNumChildComponents(); ++i)
  275. {
  276. PinComponent* const pc = dynamic_cast <PinComponent*> (getChildComponent(i));
  277. if (pc != nullptr)
  278. {
  279. const int total = pc->isInput ? numIns : numOuts;
  280. const int index = pc->index == FilterGraph::midiChannelNumber ? (total - 1) : pc->index;
  281. pc->setBounds (proportionOfWidth ((1 + index) / (total + 1.0f)) - pinSize / 2,
  282. pc->isInput ? 0 : (getHeight() - pinSize),
  283. pinSize, pinSize);
  284. }
  285. }
  286. }
  287. void getPinPos (const int index, const bool isInput, float& x, float& y)
  288. {
  289. for (int i = 0; i < getNumChildComponents(); ++i)
  290. {
  291. PinComponent* const pc = dynamic_cast <PinComponent*> (getChildComponent(i));
  292. if (pc != nullptr && pc->index == index && isInput == pc->isInput)
  293. {
  294. x = getX() + pc->getX() + pc->getWidth() * 0.5f;
  295. y = getY() + pc->getY() + pc->getHeight() * 0.5f;
  296. break;
  297. }
  298. }
  299. }
  300. void update()
  301. {
  302. const AudioProcessorGraph::Node::Ptr f (graph.getNodeForId (filterID));
  303. if (f == nullptr)
  304. {
  305. delete this;
  306. return;
  307. }
  308. numIns = f->getProcessor()->getNumInputChannels();
  309. if (f->getProcessor()->acceptsMidi())
  310. ++numIns;
  311. numOuts = f->getProcessor()->getNumOutputChannels();
  312. if (f->getProcessor()->producesMidi())
  313. ++numOuts;
  314. int w = 100;
  315. int h = 60;
  316. w = jmax (w, (jmax (numIns, numOuts) + 1) * 20);
  317. const int textWidth = font.getStringWidth (f->getProcessor()->getName());
  318. w = jmax (w, 16 + jmin (textWidth, 300));
  319. if (textWidth > 300)
  320. h = 100;
  321. setSize (w, h);
  322. setName (f->getProcessor()->getName());
  323. {
  324. double x, y;
  325. graph.getNodePosition (filterID, x, y);
  326. setCentreRelative ((float) x, (float) y);
  327. }
  328. if (numIns != numInputs || numOuts != numOutputs)
  329. {
  330. numInputs = numIns;
  331. numOutputs = numOuts;
  332. deleteAllChildren();
  333. int i;
  334. for (i = 0; i < f->getProcessor()->getNumInputChannels(); ++i)
  335. addAndMakeVisible (new PinComponent (graph, filterID, i, true));
  336. if (f->getProcessor()->acceptsMidi())
  337. addAndMakeVisible (new PinComponent (graph, filterID, FilterGraph::midiChannelNumber, true));
  338. for (i = 0; i < f->getProcessor()->getNumOutputChannels(); ++i)
  339. addAndMakeVisible (new PinComponent (graph, filterID, i, false));
  340. if (f->getProcessor()->producesMidi())
  341. addAndMakeVisible (new PinComponent (graph, filterID, FilterGraph::midiChannelNumber, false));
  342. resized();
  343. }
  344. }
  345. FilterGraph& graph;
  346. const uint32 filterID;
  347. int numInputs, numOutputs;
  348. private:
  349. int pinSize;
  350. Point<int> originalPos;
  351. Font font;
  352. int numIns, numOuts;
  353. DropShadowEffect shadow;
  354. GraphEditorPanel* getGraphPanel() const noexcept
  355. {
  356. return findParentComponentOfClass<GraphEditorPanel>();
  357. }
  358. FilterComponent (const FilterComponent&);
  359. FilterComponent& operator= (const FilterComponent&);
  360. };
  361. //==============================================================================
  362. class ConnectorComponent : public Component,
  363. public SettableTooltipClient
  364. {
  365. public:
  366. ConnectorComponent (FilterGraph& graph_)
  367. : sourceFilterID (0),
  368. destFilterID (0),
  369. sourceFilterChannel (0),
  370. destFilterChannel (0),
  371. graph (graph_),
  372. lastInputX (0),
  373. lastInputY (0),
  374. lastOutputX (0),
  375. lastOutputY (0)
  376. {
  377. setAlwaysOnTop (true);
  378. }
  379. ~ConnectorComponent()
  380. {
  381. }
  382. void setInput (const uint32 sourceFilterID_, const int sourceFilterChannel_)
  383. {
  384. if (sourceFilterID != sourceFilterID_ || sourceFilterChannel != sourceFilterChannel_)
  385. {
  386. sourceFilterID = sourceFilterID_;
  387. sourceFilterChannel = sourceFilterChannel_;
  388. update();
  389. }
  390. }
  391. void setOutput (const uint32 destFilterID_, const int destFilterChannel_)
  392. {
  393. if (destFilterID != destFilterID_ || destFilterChannel != destFilterChannel_)
  394. {
  395. destFilterID = destFilterID_;
  396. destFilterChannel = destFilterChannel_;
  397. update();
  398. }
  399. }
  400. void dragStart (int x, int y)
  401. {
  402. lastInputX = (float) x;
  403. lastInputY = (float) y;
  404. resizeToFit();
  405. }
  406. void dragEnd (int x, int y)
  407. {
  408. lastOutputX = (float) x;
  409. lastOutputY = (float) y;
  410. resizeToFit();
  411. }
  412. void update()
  413. {
  414. float x1, y1, x2, y2;
  415. getPoints (x1, y1, x2, y2);
  416. if (lastInputX != x1
  417. || lastInputY != y1
  418. || lastOutputX != x2
  419. || lastOutputY != y2)
  420. {
  421. resizeToFit();
  422. }
  423. }
  424. void resizeToFit()
  425. {
  426. float x1, y1, x2, y2;
  427. getPoints (x1, y1, x2, y2);
  428. const Rectangle<int> newBounds ((int) jmin (x1, x2) - 4,
  429. (int) jmin (y1, y2) - 4,
  430. (int) fabsf (x1 - x2) + 8,
  431. (int) fabsf (y1 - y2) + 8);
  432. if (newBounds != getBounds())
  433. setBounds (newBounds);
  434. else
  435. resized();
  436. repaint();
  437. }
  438. void getPoints (float& x1, float& y1, float& x2, float& y2) const
  439. {
  440. x1 = lastInputX;
  441. y1 = lastInputY;
  442. x2 = lastOutputX;
  443. y2 = lastOutputY;
  444. GraphEditorPanel* const hostPanel = getGraphPanel();
  445. if (hostPanel != nullptr)
  446. {
  447. FilterComponent* srcFilterComp = hostPanel->getComponentForFilter (sourceFilterID);
  448. if (srcFilterComp != nullptr)
  449. srcFilterComp->getPinPos (sourceFilterChannel, false, x1, y1);
  450. FilterComponent* dstFilterComp = hostPanel->getComponentForFilter (destFilterID);
  451. if (dstFilterComp != nullptr)
  452. dstFilterComp->getPinPos (destFilterChannel, true, x2, y2);
  453. }
  454. }
  455. void paint (Graphics& g)
  456. {
  457. if (sourceFilterChannel == FilterGraph::midiChannelNumber
  458. || destFilterChannel == FilterGraph::midiChannelNumber)
  459. {
  460. g.setColour (Colours::red);
  461. }
  462. else
  463. {
  464. g.setColour (Colours::green);
  465. }
  466. g.fillPath (linePath);
  467. }
  468. bool hitTest (int x, int y)
  469. {
  470. if (hitPath.contains ((float) x, (float) y))
  471. {
  472. double distanceFromStart, distanceFromEnd;
  473. getDistancesFromEnds (x, y, distanceFromStart, distanceFromEnd);
  474. // avoid clicking the connector when over a pin
  475. return distanceFromStart > 7.0 && distanceFromEnd > 7.0;
  476. }
  477. return false;
  478. }
  479. void mouseDown (const MouseEvent&)
  480. {
  481. dragging = false;
  482. }
  483. void mouseDrag (const MouseEvent& e)
  484. {
  485. if ((! dragging) && ! e.mouseWasClicked())
  486. {
  487. dragging = true;
  488. graph.removeConnection (sourceFilterID, sourceFilterChannel, destFilterID, destFilterChannel);
  489. double distanceFromStart, distanceFromEnd;
  490. getDistancesFromEnds (e.x, e.y, distanceFromStart, distanceFromEnd);
  491. const bool isNearerSource = (distanceFromStart < distanceFromEnd);
  492. getGraphPanel()->beginConnectorDrag (isNearerSource ? 0 : sourceFilterID,
  493. sourceFilterChannel,
  494. isNearerSource ? destFilterID : 0,
  495. destFilterChannel,
  496. e);
  497. }
  498. else if (dragging)
  499. {
  500. getGraphPanel()->dragConnector (e);
  501. }
  502. }
  503. void mouseUp (const MouseEvent& e)
  504. {
  505. if (dragging)
  506. getGraphPanel()->endDraggingConnector (e);
  507. }
  508. void resized()
  509. {
  510. float x1, y1, x2, y2;
  511. getPoints (x1, y1, x2, y2);
  512. lastInputX = x1;
  513. lastInputY = y1;
  514. lastOutputX = x2;
  515. lastOutputY = y2;
  516. x1 -= getX();
  517. y1 -= getY();
  518. x2 -= getX();
  519. y2 -= getY();
  520. linePath.clear();
  521. linePath.startNewSubPath (x1, y1);
  522. linePath.cubicTo (x1, y1 + (y2 - y1) * 0.33f,
  523. x2, y1 + (y2 - y1) * 0.66f,
  524. x2, y2);
  525. PathStrokeType wideStroke (8.0f);
  526. wideStroke.createStrokedPath (hitPath, linePath);
  527. PathStrokeType stroke (2.5f);
  528. stroke.createStrokedPath (linePath, linePath);
  529. const float arrowW = 5.0f;
  530. const float arrowL = 4.0f;
  531. Path arrow;
  532. arrow.addTriangle (-arrowL, arrowW,
  533. -arrowL, -arrowW,
  534. arrowL, 0.0f);
  535. arrow.applyTransform (AffineTransform::identity
  536. .rotated (float_Pi * 0.5f - (float) atan2 (x2 - x1, y2 - y1))
  537. .translated ((x1 + x2) * 0.5f,
  538. (y1 + y2) * 0.5f));
  539. linePath.addPath (arrow);
  540. linePath.setUsingNonZeroWinding (true);
  541. }
  542. uint32 sourceFilterID, destFilterID;
  543. int sourceFilterChannel, destFilterChannel;
  544. private:
  545. FilterGraph& graph;
  546. float lastInputX, lastInputY, lastOutputX, lastOutputY;
  547. Path linePath, hitPath;
  548. bool dragging;
  549. GraphEditorPanel* getGraphPanel() const noexcept
  550. {
  551. return findParentComponentOfClass<GraphEditorPanel>();
  552. }
  553. void getDistancesFromEnds (int x, int y, double& distanceFromStart, double& distanceFromEnd) const
  554. {
  555. float x1, y1, x2, y2;
  556. getPoints (x1, y1, x2, y2);
  557. distanceFromStart = juce_hypot (x - (x1 - getX()), y - (y1 - getY()));
  558. distanceFromEnd = juce_hypot (x - (x2 - getX()), y - (y2 - getY()));
  559. }
  560. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectorComponent);
  561. };
  562. //==============================================================================
  563. GraphEditorPanel::GraphEditorPanel (FilterGraph& graph_)
  564. : graph (graph_)
  565. {
  566. graph.addChangeListener (this);
  567. setOpaque (true);
  568. }
  569. GraphEditorPanel::~GraphEditorPanel()
  570. {
  571. graph.removeChangeListener (this);
  572. draggingConnector = nullptr;
  573. deleteAllChildren();
  574. }
  575. void GraphEditorPanel::paint (Graphics& g)
  576. {
  577. g.fillAll (Colours::white);
  578. }
  579. void GraphEditorPanel::mouseDown (const MouseEvent& e)
  580. {
  581. if (e.mods.isPopupMenu())
  582. {
  583. PopupMenu m;
  584. MainHostWindow* const mainWindow = findParentComponentOfClass<MainHostWindow>();
  585. if (mainWindow != nullptr)
  586. {
  587. mainWindow->addPluginsToMenu (m);
  588. const int r = m.show();
  589. createNewPlugin (mainWindow->getChosenType (r), e.x, e.y);
  590. }
  591. }
  592. }
  593. void GraphEditorPanel::createNewPlugin (const PluginDescription* desc, int x, int y)
  594. {
  595. graph.addFilter (desc, x / (double) getWidth(), y / (double) getHeight());
  596. }
  597. FilterComponent* GraphEditorPanel::getComponentForFilter (const uint32 filterID) const
  598. {
  599. for (int i = getNumChildComponents(); --i >= 0;)
  600. {
  601. FilterComponent* const fc = dynamic_cast <FilterComponent*> (getChildComponent (i));
  602. if (fc != nullptr && fc->filterID == filterID)
  603. return fc;
  604. }
  605. return nullptr;
  606. }
  607. ConnectorComponent* GraphEditorPanel::getComponentForConnection (const AudioProcessorGraph::Connection& conn) const
  608. {
  609. for (int i = getNumChildComponents(); --i >= 0;)
  610. {
  611. ConnectorComponent* const c = dynamic_cast <ConnectorComponent*> (getChildComponent (i));
  612. if (c != nullptr
  613. && c->sourceFilterID == conn.sourceNodeId
  614. && c->destFilterID == conn.destNodeId
  615. && c->sourceFilterChannel == conn.sourceChannelIndex
  616. && c->destFilterChannel == conn.destChannelIndex)
  617. {
  618. return c;
  619. }
  620. }
  621. return nullptr;
  622. }
  623. PinComponent* GraphEditorPanel::findPinAt (const int x, const int y) const
  624. {
  625. for (int i = getNumChildComponents(); --i >= 0;)
  626. {
  627. FilterComponent* const fc = dynamic_cast <FilterComponent*> (getChildComponent (i));
  628. if (fc != nullptr)
  629. {
  630. PinComponent* const pin
  631. = dynamic_cast <PinComponent*> (fc->getComponentAt (x - fc->getX(),
  632. y - fc->getY()));
  633. if (pin != nullptr)
  634. return pin;
  635. }
  636. }
  637. return nullptr;
  638. }
  639. void GraphEditorPanel::resized()
  640. {
  641. updateComponents();
  642. }
  643. void GraphEditorPanel::changeListenerCallback (ChangeBroadcaster*)
  644. {
  645. updateComponents();
  646. }
  647. void GraphEditorPanel::updateComponents()
  648. {
  649. int i;
  650. for (i = getNumChildComponents(); --i >= 0;)
  651. {
  652. FilterComponent* const fc = dynamic_cast <FilterComponent*> (getChildComponent (i));
  653. if (fc != nullptr)
  654. fc->update();
  655. }
  656. for (i = getNumChildComponents(); --i >= 0;)
  657. {
  658. ConnectorComponent* const cc = dynamic_cast <ConnectorComponent*> (getChildComponent (i));
  659. if (cc != nullptr && cc != draggingConnector)
  660. {
  661. if (graph.getConnectionBetween (cc->sourceFilterID, cc->sourceFilterChannel,
  662. cc->destFilterID, cc->destFilterChannel) == nullptr)
  663. {
  664. delete cc;
  665. }
  666. else
  667. {
  668. cc->update();
  669. }
  670. }
  671. }
  672. for (i = graph.getNumFilters(); --i >= 0;)
  673. {
  674. const AudioProcessorGraph::Node::Ptr f (graph.getNode (i));
  675. if (getComponentForFilter (f->nodeId) == 0)
  676. {
  677. FilterComponent* const comp = new FilterComponent (graph, f->nodeId);
  678. addAndMakeVisible (comp);
  679. comp->update();
  680. }
  681. }
  682. for (i = graph.getNumConnections(); --i >= 0;)
  683. {
  684. const AudioProcessorGraph::Connection* const c = graph.getConnection (i);
  685. if (getComponentForConnection (*c) == 0)
  686. {
  687. ConnectorComponent* const comp = new ConnectorComponent (graph);
  688. addAndMakeVisible (comp);
  689. comp->setInput (c->sourceNodeId, c->sourceChannelIndex);
  690. comp->setOutput (c->destNodeId, c->destChannelIndex);
  691. }
  692. }
  693. }
  694. void GraphEditorPanel::beginConnectorDrag (const uint32 sourceFilterID, const int sourceFilterChannel,
  695. const uint32 destFilterID, const int destFilterChannel,
  696. const MouseEvent& e)
  697. {
  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. draggingConnector = nullptr;
  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 (Font (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 (AudioPluginFormatManager& formatManager,
  810. AudioDeviceManager* deviceManager_)
  811. : graph (formatManager), deviceManager (deviceManager_)
  812. {
  813. addAndMakeVisible (graphPanel = new GraphEditorPanel (graph));
  814. graphPlayer.setProcessor (&graph.getGraph());
  815. keyState.addListener (&graphPlayer.getMidiMessageCollector());
  816. addAndMakeVisible (keyboardComp = new MidiKeyboardComponent (keyState,
  817. MidiKeyboardComponent::horizontalKeyboard));
  818. addAndMakeVisible (statusBar = new TooltipBar());
  819. deviceManager->addAudioCallback (&graphPlayer);
  820. deviceManager->addMidiInputCallback (String::empty, &graphPlayer.getMidiMessageCollector());
  821. graphPanel->updateComponents();
  822. }
  823. GraphDocumentComponent::~GraphDocumentComponent()
  824. {
  825. deviceManager->removeAudioCallback (&graphPlayer);
  826. deviceManager->removeMidiInputCallback (String::empty, &graphPlayer.getMidiMessageCollector());
  827. deleteAllChildren();
  828. graphPlayer.setProcessor (nullptr);
  829. keyState.removeListener (&graphPlayer.getMidiMessageCollector());
  830. graph.clear();
  831. }
  832. void GraphDocumentComponent::resized()
  833. {
  834. const int keysHeight = 60;
  835. const int statusHeight = 20;
  836. graphPanel->setBounds (0, 0, getWidth(), getHeight() - keysHeight);
  837. statusBar->setBounds (0, getHeight() - keysHeight - statusHeight, getWidth(), statusHeight);
  838. keyboardComp->setBounds (0, getHeight() - keysHeight, getWidth(), keysHeight);
  839. }
  840. void GraphDocumentComponent::createNewPlugin (const PluginDescription* desc, int x, int y)
  841. {
  842. graphPanel->createNewPlugin (desc, x, y);
  843. }