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.

1135 lines
34KB

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