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.

1021 lines
31KB

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