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.

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