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.

1139 lines
34KB

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