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.

1065 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(),
  269. x + 4, y + 2, w - 8, h - 4,
  270. Justification::centred, 2);
  271. g.setColour (Colours::grey);
  272. g.drawRect (x, y, w, h);
  273. }
  274. void resized()
  275. {
  276. for (int i = 0; i < getNumChildComponents(); ++i)
  277. {
  278. PinComponent* const pc = dynamic_cast <PinComponent*> (getChildComponent(i));
  279. if (pc != nullptr)
  280. {
  281. const int total = pc->isInput ? numIns : numOuts;
  282. const int index = pc->index == FilterGraph::midiChannelNumber ? (total - 1) : pc->index;
  283. pc->setBounds (proportionOfWidth ((1 + index) / (total + 1.0f)) - pinSize / 2,
  284. pc->isInput ? 0 : (getHeight() - pinSize),
  285. pinSize, pinSize);
  286. }
  287. }
  288. }
  289. void getPinPos (const int index, const bool isInput, float& x, float& y)
  290. {
  291. for (int i = 0; i < getNumChildComponents(); ++i)
  292. {
  293. PinComponent* const pc = dynamic_cast <PinComponent*> (getChildComponent(i));
  294. if (pc != nullptr && pc->index == index && isInput == pc->isInput)
  295. {
  296. x = getX() + pc->getX() + pc->getWidth() * 0.5f;
  297. y = getY() + pc->getY() + pc->getHeight() * 0.5f;
  298. break;
  299. }
  300. }
  301. }
  302. void update()
  303. {
  304. const AudioProcessorGraph::Node::Ptr f (graph.getNodeForId (filterID));
  305. if (f == nullptr)
  306. {
  307. delete this;
  308. return;
  309. }
  310. numIns = f->getProcessor()->getNumInputChannels();
  311. if (f->getProcessor()->acceptsMidi())
  312. ++numIns;
  313. numOuts = f->getProcessor()->getNumOutputChannels();
  314. if (f->getProcessor()->producesMidi())
  315. ++numOuts;
  316. int w = 100;
  317. int h = 60;
  318. w = jmax (w, (jmax (numIns, numOuts) + 1) * 20);
  319. const int textWidth = font.getStringWidth (f->getProcessor()->getName());
  320. w = jmax (w, 16 + jmin (textWidth, 300));
  321. if (textWidth > 300)
  322. h = 100;
  323. setSize (w, h);
  324. setName (f->getProcessor()->getName());
  325. {
  326. double x, y;
  327. graph.getNodePosition (filterID, x, y);
  328. setCentreRelative ((float) x, (float) y);
  329. }
  330. if (numIns != numInputs || numOuts != numOutputs)
  331. {
  332. numInputs = numIns;
  333. numOutputs = numOuts;
  334. deleteAllChildren();
  335. int i;
  336. for (i = 0; i < f->getProcessor()->getNumInputChannels(); ++i)
  337. addAndMakeVisible (new PinComponent (graph, filterID, i, true));
  338. if (f->getProcessor()->acceptsMidi())
  339. addAndMakeVisible (new PinComponent (graph, filterID, FilterGraph::midiChannelNumber, true));
  340. for (i = 0; i < f->getProcessor()->getNumOutputChannels(); ++i)
  341. addAndMakeVisible (new PinComponent (graph, filterID, i, false));
  342. if (f->getProcessor()->producesMidi())
  343. addAndMakeVisible (new PinComponent (graph, filterID, FilterGraph::midiChannelNumber, false));
  344. resized();
  345. }
  346. }
  347. FilterGraph& graph;
  348. const uint32 filterID;
  349. int numInputs, numOutputs;
  350. private:
  351. int pinSize;
  352. Point<int> originalPos;
  353. Font font;
  354. int numIns, numOuts;
  355. DropShadowEffect shadow;
  356. GraphEditorPanel* getGraphPanel() const noexcept
  357. {
  358. return findParentComponentOfClass<GraphEditorPanel>();
  359. }
  360. FilterComponent (const FilterComponent&);
  361. FilterComponent& operator= (const FilterComponent&);
  362. };
  363. //==============================================================================
  364. class ConnectorComponent : public Component,
  365. public SettableTooltipClient
  366. {
  367. public:
  368. ConnectorComponent (FilterGraph& graph_)
  369. : sourceFilterID (0),
  370. destFilterID (0),
  371. sourceFilterChannel (0),
  372. destFilterChannel (0),
  373. graph (graph_),
  374. lastInputX (0),
  375. lastInputY (0),
  376. lastOutputX (0),
  377. lastOutputY (0)
  378. {
  379. setAlwaysOnTop (true);
  380. }
  381. ~ConnectorComponent()
  382. {
  383. }
  384. void setInput (const uint32 sourceFilterID_, const int sourceFilterChannel_)
  385. {
  386. if (sourceFilterID != sourceFilterID_ || sourceFilterChannel != sourceFilterChannel_)
  387. {
  388. sourceFilterID = sourceFilterID_;
  389. sourceFilterChannel = sourceFilterChannel_;
  390. update();
  391. }
  392. }
  393. void setOutput (const uint32 destFilterID_, const int destFilterChannel_)
  394. {
  395. if (destFilterID != destFilterID_ || destFilterChannel != destFilterChannel_)
  396. {
  397. destFilterID = destFilterID_;
  398. destFilterChannel = destFilterChannel_;
  399. update();
  400. }
  401. }
  402. void dragStart (int x, int y)
  403. {
  404. lastInputX = (float) x;
  405. lastInputY = (float) y;
  406. resizeToFit();
  407. }
  408. void dragEnd (int x, int y)
  409. {
  410. lastOutputX = (float) x;
  411. lastOutputY = (float) y;
  412. resizeToFit();
  413. }
  414. void update()
  415. {
  416. float x1, y1, x2, y2;
  417. getPoints (x1, y1, x2, y2);
  418. if (lastInputX != x1
  419. || lastInputY != y1
  420. || lastOutputX != x2
  421. || lastOutputY != y2)
  422. {
  423. resizeToFit();
  424. }
  425. }
  426. void resizeToFit()
  427. {
  428. float x1, y1, x2, y2;
  429. getPoints (x1, y1, x2, y2);
  430. const Rectangle<int> newBounds ((int) jmin (x1, x2) - 4,
  431. (int) jmin (y1, y2) - 4,
  432. (int) fabsf (x1 - x2) + 8,
  433. (int) fabsf (y1 - y2) + 8);
  434. if (newBounds != getBounds())
  435. setBounds (newBounds);
  436. else
  437. resized();
  438. repaint();
  439. }
  440. void getPoints (float& x1, float& y1, float& x2, float& y2) const
  441. {
  442. x1 = lastInputX;
  443. y1 = lastInputY;
  444. x2 = lastOutputX;
  445. y2 = lastOutputY;
  446. GraphEditorPanel* const hostPanel = getGraphPanel();
  447. if (hostPanel != nullptr)
  448. {
  449. FilterComponent* srcFilterComp = hostPanel->getComponentForFilter (sourceFilterID);
  450. if (srcFilterComp != nullptr)
  451. srcFilterComp->getPinPos (sourceFilterChannel, false, x1, y1);
  452. FilterComponent* dstFilterComp = hostPanel->getComponentForFilter (destFilterID);
  453. if (dstFilterComp != nullptr)
  454. dstFilterComp->getPinPos (destFilterChannel, true, x2, y2);
  455. }
  456. }
  457. void paint (Graphics& g)
  458. {
  459. if (sourceFilterChannel == FilterGraph::midiChannelNumber
  460. || destFilterChannel == FilterGraph::midiChannelNumber)
  461. {
  462. g.setColour (Colours::red);
  463. }
  464. else
  465. {
  466. g.setColour (Colours::green);
  467. }
  468. g.fillPath (linePath);
  469. }
  470. bool hitTest (int x, int y)
  471. {
  472. if (hitPath.contains ((float) x, (float) y))
  473. {
  474. double distanceFromStart, distanceFromEnd;
  475. getDistancesFromEnds (x, y, distanceFromStart, distanceFromEnd);
  476. // avoid clicking the connector when over a pin
  477. return distanceFromStart > 7.0 && distanceFromEnd > 7.0;
  478. }
  479. return false;
  480. }
  481. void mouseDown (const MouseEvent&)
  482. {
  483. dragging = false;
  484. }
  485. void mouseDrag (const MouseEvent& e)
  486. {
  487. if ((! dragging) && ! e.mouseWasClicked())
  488. {
  489. dragging = true;
  490. graph.removeConnection (sourceFilterID, sourceFilterChannel, destFilterID, destFilterChannel);
  491. double distanceFromStart, distanceFromEnd;
  492. getDistancesFromEnds (e.x, e.y, distanceFromStart, distanceFromEnd);
  493. const bool isNearerSource = (distanceFromStart < distanceFromEnd);
  494. getGraphPanel()->beginConnectorDrag (isNearerSource ? 0 : sourceFilterID,
  495. sourceFilterChannel,
  496. isNearerSource ? destFilterID : 0,
  497. destFilterChannel,
  498. e);
  499. }
  500. else if (dragging)
  501. {
  502. getGraphPanel()->dragConnector (e);
  503. }
  504. }
  505. void mouseUp (const MouseEvent& e)
  506. {
  507. if (dragging)
  508. getGraphPanel()->endDraggingConnector (e);
  509. }
  510. void resized()
  511. {
  512. float x1, y1, x2, y2;
  513. getPoints (x1, y1, x2, y2);
  514. lastInputX = x1;
  515. lastInputY = y1;
  516. lastOutputX = x2;
  517. lastOutputY = y2;
  518. x1 -= getX();
  519. y1 -= getY();
  520. x2 -= getX();
  521. y2 -= getY();
  522. linePath.clear();
  523. linePath.startNewSubPath (x1, y1);
  524. linePath.cubicTo (x1, y1 + (y2 - y1) * 0.33f,
  525. x2, y1 + (y2 - y1) * 0.66f,
  526. x2, y2);
  527. PathStrokeType wideStroke (8.0f);
  528. wideStroke.createStrokedPath (hitPath, linePath);
  529. PathStrokeType stroke (2.5f);
  530. stroke.createStrokedPath (linePath, linePath);
  531. const float arrowW = 5.0f;
  532. const float arrowL = 4.0f;
  533. Path arrow;
  534. arrow.addTriangle (-arrowL, arrowW,
  535. -arrowL, -arrowW,
  536. arrowL, 0.0f);
  537. arrow.applyTransform (AffineTransform::identity
  538. .rotated (float_Pi * 0.5f - (float) atan2 (x2 - x1, y2 - y1))
  539. .translated ((x1 + x2) * 0.5f,
  540. (y1 + y2) * 0.5f));
  541. linePath.addPath (arrow);
  542. linePath.setUsingNonZeroWinding (true);
  543. }
  544. uint32 sourceFilterID, destFilterID;
  545. int sourceFilterChannel, destFilterChannel;
  546. private:
  547. FilterGraph& graph;
  548. float lastInputX, lastInputY, lastOutputX, lastOutputY;
  549. Path linePath, hitPath;
  550. bool dragging;
  551. GraphEditorPanel* getGraphPanel() const noexcept
  552. {
  553. return findParentComponentOfClass<GraphEditorPanel>();
  554. }
  555. void getDistancesFromEnds (int x, int y, double& distanceFromStart, double& distanceFromEnd) const
  556. {
  557. float x1, y1, x2, y2;
  558. getPoints (x1, y1, x2, y2);
  559. distanceFromStart = juce_hypot (x - (x1 - getX()), y - (y1 - getY()));
  560. distanceFromEnd = juce_hypot (x - (x2 - getX()), y - (y2 - getY()));
  561. }
  562. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectorComponent);
  563. };
  564. //==============================================================================
  565. GraphEditorPanel::GraphEditorPanel (FilterGraph& graph_)
  566. : graph (graph_)
  567. {
  568. graph.addChangeListener (this);
  569. setOpaque (true);
  570. }
  571. GraphEditorPanel::~GraphEditorPanel()
  572. {
  573. graph.removeChangeListener (this);
  574. draggingConnector = nullptr;
  575. deleteAllChildren();
  576. }
  577. void GraphEditorPanel::paint (Graphics& g)
  578. {
  579. g.fillAll (Colours::white);
  580. }
  581. void GraphEditorPanel::mouseDown (const MouseEvent& e)
  582. {
  583. if (e.mods.isPopupMenu())
  584. {
  585. PopupMenu m;
  586. MainHostWindow* const mainWindow = findParentComponentOfClass<MainHostWindow>();
  587. if (mainWindow != nullptr)
  588. {
  589. mainWindow->addPluginsToMenu (m);
  590. const int r = m.show();
  591. createNewPlugin (mainWindow->getChosenType (r), e.x, e.y);
  592. }
  593. }
  594. }
  595. void GraphEditorPanel::createNewPlugin (const PluginDescription* desc, int x, int y)
  596. {
  597. graph.addFilter (desc, x / (double) getWidth(), y / (double) getHeight());
  598. }
  599. FilterComponent* GraphEditorPanel::getComponentForFilter (const uint32 filterID) const
  600. {
  601. for (int i = getNumChildComponents(); --i >= 0;)
  602. {
  603. FilterComponent* const fc = dynamic_cast <FilterComponent*> (getChildComponent (i));
  604. if (fc != nullptr && fc->filterID == filterID)
  605. return fc;
  606. }
  607. return nullptr;
  608. }
  609. ConnectorComponent* GraphEditorPanel::getComponentForConnection (const AudioProcessorGraph::Connection& conn) const
  610. {
  611. for (int i = getNumChildComponents(); --i >= 0;)
  612. {
  613. ConnectorComponent* const c = dynamic_cast <ConnectorComponent*> (getChildComponent (i));
  614. if (c != nullptr
  615. && c->sourceFilterID == conn.sourceNodeId
  616. && c->destFilterID == conn.destNodeId
  617. && c->sourceFilterChannel == conn.sourceChannelIndex
  618. && c->destFilterChannel == conn.destChannelIndex)
  619. {
  620. return c;
  621. }
  622. }
  623. return nullptr;
  624. }
  625. PinComponent* GraphEditorPanel::findPinAt (const int x, const int y) const
  626. {
  627. for (int i = getNumChildComponents(); --i >= 0;)
  628. {
  629. FilterComponent* const fc = dynamic_cast <FilterComponent*> (getChildComponent (i));
  630. if (fc != nullptr)
  631. {
  632. PinComponent* const pin
  633. = dynamic_cast <PinComponent*> (fc->getComponentAt (x - fc->getX(),
  634. y - fc->getY()));
  635. if (pin != nullptr)
  636. return pin;
  637. }
  638. }
  639. return nullptr;
  640. }
  641. void GraphEditorPanel::resized()
  642. {
  643. updateComponents();
  644. }
  645. void GraphEditorPanel::changeListenerCallback (ChangeBroadcaster*)
  646. {
  647. updateComponents();
  648. }
  649. void GraphEditorPanel::updateComponents()
  650. {
  651. int i;
  652. for (i = getNumChildComponents(); --i >= 0;)
  653. {
  654. FilterComponent* const fc = dynamic_cast <FilterComponent*> (getChildComponent (i));
  655. if (fc != nullptr)
  656. fc->update();
  657. }
  658. for (i = getNumChildComponents(); --i >= 0;)
  659. {
  660. ConnectorComponent* const cc = dynamic_cast <ConnectorComponent*> (getChildComponent (i));
  661. if (cc != nullptr && cc != draggingConnector)
  662. {
  663. if (graph.getConnectionBetween (cc->sourceFilterID, cc->sourceFilterChannel,
  664. cc->destFilterID, cc->destFilterChannel) == nullptr)
  665. {
  666. delete cc;
  667. }
  668. else
  669. {
  670. cc->update();
  671. }
  672. }
  673. }
  674. for (i = graph.getNumFilters(); --i >= 0;)
  675. {
  676. const AudioProcessorGraph::Node::Ptr f (graph.getNode (i));
  677. if (getComponentForFilter (f->nodeId) == 0)
  678. {
  679. FilterComponent* const comp = new FilterComponent (graph, f->nodeId);
  680. addAndMakeVisible (comp);
  681. comp->update();
  682. }
  683. }
  684. for (i = graph.getNumConnections(); --i >= 0;)
  685. {
  686. const AudioProcessorGraph::Connection* const c = graph.getConnection (i);
  687. if (getComponentForConnection (*c) == 0)
  688. {
  689. ConnectorComponent* const comp = new ConnectorComponent (graph);
  690. addAndMakeVisible (comp);
  691. comp->setInput (c->sourceNodeId, c->sourceChannelIndex);
  692. comp->setOutput (c->destNodeId, c->destChannelIndex);
  693. }
  694. }
  695. }
  696. void GraphEditorPanel::beginConnectorDrag (const uint32 sourceFilterID, const int sourceFilterChannel,
  697. const uint32 destFilterID, const int destFilterChannel,
  698. const MouseEvent& e)
  699. {
  700. draggingConnector = dynamic_cast <ConnectorComponent*> (e.originalComponent);
  701. if (draggingConnector == nullptr)
  702. draggingConnector = new ConnectorComponent (graph);
  703. draggingConnector->setInput (sourceFilterID, sourceFilterChannel);
  704. draggingConnector->setOutput (destFilterID, destFilterChannel);
  705. addAndMakeVisible (draggingConnector);
  706. draggingConnector->toFront (false);
  707. dragConnector (e);
  708. }
  709. void GraphEditorPanel::dragConnector (const MouseEvent& e)
  710. {
  711. const MouseEvent e2 (e.getEventRelativeTo (this));
  712. if (draggingConnector != nullptr)
  713. {
  714. draggingConnector->setTooltip (String::empty);
  715. int x = e2.x;
  716. int y = e2.y;
  717. PinComponent* const pin = findPinAt (x, y);
  718. if (pin != nullptr)
  719. {
  720. uint32 srcFilter = draggingConnector->sourceFilterID;
  721. int srcChannel = draggingConnector->sourceFilterChannel;
  722. uint32 dstFilter = draggingConnector->destFilterID;
  723. int dstChannel = draggingConnector->destFilterChannel;
  724. if (srcFilter == 0 && ! pin->isInput)
  725. {
  726. srcFilter = pin->filterID;
  727. srcChannel = pin->index;
  728. }
  729. else if (dstFilter == 0 && pin->isInput)
  730. {
  731. dstFilter = pin->filterID;
  732. dstChannel = pin->index;
  733. }
  734. if (graph.canConnect (srcFilter, srcChannel, dstFilter, dstChannel))
  735. {
  736. x = pin->getParentComponent()->getX() + pin->getX() + pin->getWidth() / 2;
  737. y = pin->getParentComponent()->getY() + pin->getY() + pin->getHeight() / 2;
  738. draggingConnector->setTooltip (pin->getTooltip());
  739. }
  740. }
  741. if (draggingConnector->sourceFilterID == 0)
  742. draggingConnector->dragStart (x, y);
  743. else
  744. draggingConnector->dragEnd (x, y);
  745. }
  746. }
  747. void GraphEditorPanel::endDraggingConnector (const MouseEvent& e)
  748. {
  749. if (draggingConnector == nullptr)
  750. return;
  751. draggingConnector->setTooltip (String::empty);
  752. const MouseEvent e2 (e.getEventRelativeTo (this));
  753. uint32 srcFilter = draggingConnector->sourceFilterID;
  754. int srcChannel = draggingConnector->sourceFilterChannel;
  755. uint32 dstFilter = draggingConnector->destFilterID;
  756. int dstChannel = draggingConnector->destFilterChannel;
  757. draggingConnector = nullptr;
  758. PinComponent* const pin = findPinAt (e2.x, e2.y);
  759. if (pin != nullptr)
  760. {
  761. if (srcFilter == 0)
  762. {
  763. if (pin->isInput)
  764. return;
  765. srcFilter = pin->filterID;
  766. srcChannel = pin->index;
  767. }
  768. else
  769. {
  770. if (! pin->isInput)
  771. return;
  772. dstFilter = pin->filterID;
  773. dstChannel = pin->index;
  774. }
  775. graph.addConnection (srcFilter, srcChannel, dstFilter, dstChannel);
  776. }
  777. }
  778. //==============================================================================
  779. class TooltipBar : public Component,
  780. private Timer
  781. {
  782. public:
  783. TooltipBar()
  784. {
  785. startTimer (100);
  786. }
  787. void paint (Graphics& g)
  788. {
  789. g.setFont (Font (getHeight() * 0.7f, Font::bold));
  790. g.setColour (Colours::black);
  791. g.drawFittedText (tip, 10, 0, getWidth() - 12, getHeight(), Justification::centredLeft, 1);
  792. }
  793. void timerCallback()
  794. {
  795. Component* const underMouse = Desktop::getInstance().getMainMouseSource().getComponentUnderMouse();
  796. TooltipClient* const ttc = dynamic_cast <TooltipClient*> (underMouse);
  797. String newTip;
  798. if (ttc != nullptr && ! (underMouse->isMouseButtonDown() || underMouse->isCurrentlyBlockedByAnotherModalComponent()))
  799. newTip = ttc->getTooltip();
  800. if (newTip != tip)
  801. {
  802. tip = newTip;
  803. repaint();
  804. }
  805. }
  806. private:
  807. String tip;
  808. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TooltipBar);
  809. };
  810. //==============================================================================
  811. GraphDocumentComponent::GraphDocumentComponent (AudioPluginFormatManager& formatManager,
  812. AudioDeviceManager* deviceManager_)
  813. : graph (formatManager), deviceManager (deviceManager_)
  814. {
  815. addAndMakeVisible (graphPanel = new GraphEditorPanel (graph));
  816. graphPlayer.setProcessor (&graph.getGraph());
  817. keyState.addListener (&graphPlayer.getMidiMessageCollector());
  818. addAndMakeVisible (keyboardComp = new MidiKeyboardComponent (keyState,
  819. MidiKeyboardComponent::horizontalKeyboard));
  820. addAndMakeVisible (statusBar = new TooltipBar());
  821. deviceManager->addAudioCallback (&graphPlayer);
  822. deviceManager->addMidiInputCallback (String::empty, &graphPlayer.getMidiMessageCollector());
  823. graphPanel->updateComponents();
  824. }
  825. GraphDocumentComponent::~GraphDocumentComponent()
  826. {
  827. deviceManager->removeAudioCallback (&graphPlayer);
  828. deviceManager->removeMidiInputCallback (String::empty, &graphPlayer.getMidiMessageCollector());
  829. deleteAllChildren();
  830. graphPlayer.setProcessor (nullptr);
  831. keyState.removeListener (&graphPlayer.getMidiMessageCollector());
  832. graph.clear();
  833. }
  834. void GraphDocumentComponent::resized()
  835. {
  836. const int keysHeight = 60;
  837. const int statusHeight = 20;
  838. graphPanel->setBounds (0, 0, getWidth(), getHeight() - keysHeight);
  839. statusBar->setBounds (0, getHeight() - keysHeight - statusHeight, getWidth(), statusHeight);
  840. keyboardComp->setBounds (0, getHeight() - keysHeight, getWidth(), keysHeight);
  841. }
  842. void GraphDocumentComponent::createNewPlugin (const PluginDescription* desc, int x, int y)
  843. {
  844. graphPanel->createNewPlugin (desc, x, y);
  845. }