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.

915 lines
27KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #include "../JuceLibraryCode/JuceHeader.h"
  20. #include "GraphEditorPanel.h"
  21. #include "InternalFilters.h"
  22. #include "MainHostWindow.h"
  23. //==============================================================================
  24. struct GraphEditorPanel::PinComponent : public Component,
  25. public SettableTooltipClient
  26. {
  27. PinComponent (GraphEditorPanel& p, AudioProcessorGraph::NodeAndChannel pinToUse, bool isIn)
  28. : panel (p), graph (p.graph), pin (pinToUse), isInput (isIn)
  29. {
  30. if (auto node = graph.graph.getNodeForId (pin.nodeID))
  31. {
  32. String tip;
  33. if (pin.isMIDI())
  34. {
  35. tip = isInput ? "MIDI Input"
  36. : "MIDI Output";
  37. }
  38. else
  39. {
  40. auto& processor = *node->getProcessor();
  41. auto channel = processor.getOffsetInBusBufferForAbsoluteChannelIndex (isInput, pin.channelIndex, busIdx);
  42. if (auto* bus = processor.getBus (isInput, busIdx))
  43. tip = bus->getName() + ": " + AudioChannelSet::getAbbreviatedChannelTypeName (bus->getCurrentLayout().getTypeOfChannel (channel));
  44. else
  45. tip = (isInput ? "Main Input: "
  46. : "Main Output: ") + String (pin.channelIndex + 1);
  47. }
  48. setTooltip (tip);
  49. }
  50. setSize (16, 16);
  51. }
  52. void paint (Graphics& g) override
  53. {
  54. auto w = (float) getWidth();
  55. auto h = (float) getHeight();
  56. Path p;
  57. p.addEllipse (w * 0.25f, h * 0.25f, w * 0.5f, h * 0.5f);
  58. p.addRectangle (w * 0.4f, isInput ? (0.5f * h) : 0.0f, w * 0.2f, h * 0.5f);
  59. auto colour = (pin.isMIDI() ? Colours::red : Colours::green);
  60. g.setColour (colour.withRotatedHue (busIdx / 5.0f));
  61. g.fillPath (p);
  62. }
  63. void mouseDown (const MouseEvent& e) override
  64. {
  65. AudioProcessorGraph::NodeAndChannel dummy { 0, 0 };
  66. panel.beginConnectorDrag (isInput ? dummy : pin,
  67. isInput ? pin : dummy,
  68. e);
  69. }
  70. void mouseDrag (const MouseEvent& e) override
  71. {
  72. panel.dragConnector (e);
  73. }
  74. void mouseUp (const MouseEvent& e) override
  75. {
  76. panel.endDraggingConnector (e);
  77. }
  78. GraphEditorPanel& panel;
  79. FilterGraph& graph;
  80. AudioProcessorGraph::NodeAndChannel pin;
  81. const bool isInput;
  82. int busIdx = 0;
  83. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PinComponent)
  84. };
  85. //==============================================================================
  86. struct GraphEditorPanel::FilterComponent : public Component, private AudioProcessorParameter::Listener
  87. {
  88. FilterComponent (GraphEditorPanel& p, uint32 id) : panel (p), graph (p.graph), pluginID (id)
  89. {
  90. shadow.setShadowProperties (DropShadow (Colours::black.withAlpha (0.5f), 3, { 0, 1 }));
  91. setComponentEffect (&shadow);
  92. if (auto f = graph.graph.getNodeForId (pluginID))
  93. {
  94. if (auto* processor = f->getProcessor())
  95. {
  96. if (auto* bypassParam = processor->getBypassParameter())
  97. bypassParam->addListener (this);
  98. }
  99. }
  100. setSize (150, 60);
  101. }
  102. FilterComponent (const FilterComponent&) = delete;
  103. FilterComponent& operator= (const FilterComponent&) = delete;
  104. ~FilterComponent()
  105. {
  106. if (auto f = graph.graph.getNodeForId (pluginID))
  107. {
  108. if (auto* processor = f->getProcessor())
  109. {
  110. if (auto* bypassParam = processor->getBypassParameter())
  111. bypassParam->removeListener (this);
  112. }
  113. }
  114. }
  115. void mouseDown (const MouseEvent& e) override
  116. {
  117. originalPos = localPointToGlobal (Point<int>());
  118. toFront (true);
  119. if (e.mods.isPopupMenu())
  120. showPopupMenu();
  121. }
  122. void mouseDrag (const MouseEvent& e) override
  123. {
  124. if (! e.mods.isPopupMenu())
  125. {
  126. auto pos = originalPos + e.getOffsetFromDragStart();
  127. if (getParentComponent() != nullptr)
  128. pos = getParentComponent()->getLocalPoint (nullptr, pos);
  129. pos += getLocalBounds().getCentre();
  130. graph.setNodePosition (pluginID,
  131. { pos.x / (double) getParentWidth(),
  132. pos.y / (double) getParentHeight() });
  133. panel.updateComponents();
  134. }
  135. }
  136. void mouseUp (const MouseEvent& e) override
  137. {
  138. if (e.mouseWasDraggedSinceMouseDown())
  139. {
  140. graph.setChangedFlag (true);
  141. }
  142. else if (e.getNumberOfClicks() == 2)
  143. {
  144. if (auto f = graph.graph.getNodeForId (pluginID))
  145. if (auto* w = graph.getOrCreateWindowFor (f, PluginWindow::Type::normal))
  146. w->toFront (true);
  147. }
  148. }
  149. bool hitTest (int x, int y) override
  150. {
  151. for (auto* child : getChildren())
  152. if (child->getBounds().contains (x, y))
  153. return true;
  154. return x >= 3 && x < getWidth() - 6 && y >= pinSize && y < getHeight() - pinSize;
  155. }
  156. void paint (Graphics& g) override
  157. {
  158. auto boxArea = getLocalBounds().reduced (4, pinSize);
  159. bool isBypassed = false;
  160. if (auto* f = graph.graph.getNodeForId (pluginID))
  161. isBypassed = f->isBypassed();
  162. auto boxColour = findColour (TextEditor::backgroundColourId);
  163. if (isBypassed)
  164. boxColour = boxColour.brighter();
  165. g.setColour (boxColour);
  166. g.fillRect (boxArea.toFloat());
  167. g.setColour (findColour (TextEditor::textColourId));
  168. g.setFont (font);
  169. g.drawFittedText (getName(), boxArea, Justification::centred, 2);
  170. }
  171. void resized() override
  172. {
  173. if (auto f = graph.graph.getNodeForId (pluginID))
  174. {
  175. if (auto* processor = f->getProcessor())
  176. {
  177. for (auto* pin : pins)
  178. {
  179. const bool isInput = pin->isInput;
  180. auto channelIndex = pin->pin.channelIndex;
  181. int busIdx = 0;
  182. processor->getOffsetInBusBufferForAbsoluteChannelIndex (isInput, channelIndex, busIdx);
  183. const int total = isInput ? numIns : numOuts;
  184. const int index = pin->pin.isMIDI() ? (total - 1) : channelIndex;
  185. auto totalSpaces = static_cast<float> (total) + (static_cast<float> (jmax (0, processor->getBusCount (isInput) - 1)) * 0.5f);
  186. auto indexPos = static_cast<float> (index) + (static_cast<float> (busIdx) * 0.5f);
  187. pin->setBounds (proportionOfWidth ((1.0f + indexPos) / (totalSpaces + 1.0f)) - pinSize / 2,
  188. pin->isInput ? 0 : (getHeight() - pinSize),
  189. pinSize, pinSize);
  190. }
  191. }
  192. }
  193. }
  194. Point<float> getPinPos (int index, bool isInput) const
  195. {
  196. for (auto* pin : pins)
  197. if (pin->pin.channelIndex == index && isInput == pin->isInput)
  198. return getPosition().toFloat() + pin->getBounds().getCentre().toFloat();
  199. return {};
  200. }
  201. void update()
  202. {
  203. const AudioProcessorGraph::Node::Ptr f (graph.graph.getNodeForId (pluginID));
  204. jassert (f != nullptr);
  205. numIns = f->getProcessor()->getTotalNumInputChannels();
  206. if (f->getProcessor()->acceptsMidi())
  207. ++numIns;
  208. numOuts = f->getProcessor()->getTotalNumOutputChannels();
  209. if (f->getProcessor()->producesMidi())
  210. ++numOuts;
  211. int w = 100;
  212. int h = 60;
  213. w = jmax (w, (jmax (numIns, numOuts) + 1) * 20);
  214. const int textWidth = font.getStringWidth (f->getProcessor()->getName());
  215. w = jmax (w, 16 + jmin (textWidth, 300));
  216. if (textWidth > 300)
  217. h = 100;
  218. setSize (w, h);
  219. setName (f->getProcessor()->getName());
  220. {
  221. auto p = graph.getNodePosition (pluginID);
  222. setCentreRelative ((float) p.x, (float) p.y);
  223. }
  224. if (numIns != numInputs || numOuts != numOutputs)
  225. {
  226. numInputs = numIns;
  227. numOutputs = numOuts;
  228. pins.clear();
  229. for (int i = 0; i < f->getProcessor()->getTotalNumInputChannels(); ++i)
  230. addAndMakeVisible (pins.add (new PinComponent (panel, { pluginID, i }, true)));
  231. if (f->getProcessor()->acceptsMidi())
  232. addAndMakeVisible (pins.add (new PinComponent (panel, { pluginID, AudioProcessorGraph::midiChannelIndex }, true)));
  233. for (int i = 0; i < f->getProcessor()->getTotalNumOutputChannels(); ++i)
  234. addAndMakeVisible (pins.add (new PinComponent (panel, { pluginID, i }, false)));
  235. if (f->getProcessor()->producesMidi())
  236. addAndMakeVisible (pins.add (new PinComponent (panel, { pluginID, AudioProcessorGraph::midiChannelIndex }, false)));
  237. resized();
  238. }
  239. }
  240. AudioProcessor* getProcessor() const
  241. {
  242. if (auto node = graph.graph.getNodeForId (pluginID))
  243. return node->getProcessor();
  244. return {};
  245. }
  246. void showPopupMenu()
  247. {
  248. PopupMenu m;
  249. m.addItem (1, "Delete this filter");
  250. m.addItem (2, "Disconnect all pins");
  251. m.addItem (3, "Toggle Bypass");
  252. m.addSeparator();
  253. m.addItem (10, "Show plugin GUI");
  254. m.addItem (11, "Show all programs");
  255. m.addItem (12, "Show all parameters");
  256. m.addSeparator();
  257. m.addItem (20, "Configure Audio I/O");
  258. m.addItem (21, "Test state save/load");
  259. switch (m.show())
  260. {
  261. case 1: graph.graph.removeNode (pluginID); break;
  262. case 2: graph.graph.disconnectNode (pluginID); break;
  263. case 3:
  264. {
  265. if (auto* node = graph.graph.getNodeForId (pluginID))
  266. node->setBypassed (! node->isBypassed());
  267. repaint();
  268. break;
  269. }
  270. case 10: showWindow (PluginWindow::Type::normal); break;
  271. case 11: showWindow (PluginWindow::Type::programs); break;
  272. case 12: showWindow (PluginWindow::Type::generic); break;
  273. case 20: showWindow (PluginWindow::Type::audioIO); break;
  274. case 21: testStateSaveLoad(); break;
  275. default: break;
  276. }
  277. }
  278. void testStateSaveLoad()
  279. {
  280. if (auto* processor = getProcessor())
  281. {
  282. MemoryBlock state;
  283. processor->getStateInformation (state);
  284. processor->setStateInformation (state.getData(), (int) state.getSize());
  285. }
  286. }
  287. void showWindow (PluginWindow::Type type)
  288. {
  289. if (auto node = graph.graph.getNodeForId (pluginID))
  290. if (auto* w = graph.getOrCreateWindowFor (node, type))
  291. w->toFront (true);
  292. }
  293. void parameterValueChanged (int, float) override
  294. {
  295. repaint();
  296. }
  297. void parameterGestureChanged (int, bool) override {}
  298. GraphEditorPanel& panel;
  299. FilterGraph& graph;
  300. const AudioProcessorGraph::NodeID pluginID;
  301. OwnedArray<PinComponent> pins;
  302. int numInputs = 0, numOutputs = 0;
  303. int pinSize = 16;
  304. Point<int> originalPos;
  305. Font font { 13.0f, Font::bold };
  306. int numIns = 0, numOuts = 0;
  307. DropShadowEffect shadow;
  308. };
  309. //==============================================================================
  310. struct GraphEditorPanel::ConnectorComponent : public Component,
  311. public SettableTooltipClient
  312. {
  313. ConnectorComponent (GraphEditorPanel& p) : panel (p), graph (p.graph)
  314. {
  315. setAlwaysOnTop (true);
  316. }
  317. void setInput (AudioProcessorGraph::NodeAndChannel newSource)
  318. {
  319. if (connection.source != newSource)
  320. {
  321. connection.source = newSource;
  322. update();
  323. }
  324. }
  325. void setOutput (AudioProcessorGraph::NodeAndChannel newDest)
  326. {
  327. if (connection.destination != newDest)
  328. {
  329. connection.destination = newDest;
  330. update();
  331. }
  332. }
  333. void dragStart (Point<float> pos)
  334. {
  335. lastInputPos = pos;
  336. resizeToFit();
  337. }
  338. void dragEnd (Point<float> pos)
  339. {
  340. lastOutputPos = pos;
  341. resizeToFit();
  342. }
  343. void update()
  344. {
  345. Point<float> p1, p2;
  346. getPoints (p1, p2);
  347. if (lastInputPos != p1 || lastOutputPos != p2)
  348. resizeToFit();
  349. }
  350. void resizeToFit()
  351. {
  352. Point<float> p1, p2;
  353. getPoints (p1, p2);
  354. auto newBounds = Rectangle<float> (p1, p2).expanded (4.0f).getSmallestIntegerContainer();
  355. if (newBounds != getBounds())
  356. setBounds (newBounds);
  357. else
  358. resized();
  359. repaint();
  360. }
  361. void getPoints (Point<float>& p1, Point<float>& p2) const
  362. {
  363. p1 = lastInputPos;
  364. p2 = lastOutputPos;
  365. if (auto* src = panel.getComponentForFilter (connection.source.nodeID))
  366. p1 = src->getPinPos (connection.source.channelIndex, false);
  367. if (auto* dest = panel.getComponentForFilter (connection.destination.nodeID))
  368. p2 = dest->getPinPos (connection.destination.channelIndex, true);
  369. }
  370. void paint (Graphics& g) override
  371. {
  372. if (connection.source.isMIDI() || connection.destination.isMIDI())
  373. g.setColour (Colours::red);
  374. else
  375. g.setColour (Colours::green);
  376. g.fillPath (linePath);
  377. }
  378. bool hitTest (int x, int y) override
  379. {
  380. auto pos = Point<int> (x, y).toFloat();
  381. if (hitPath.contains (pos))
  382. {
  383. double distanceFromStart, distanceFromEnd;
  384. getDistancesFromEnds (pos, distanceFromStart, distanceFromEnd);
  385. // avoid clicking the connector when over a pin
  386. return distanceFromStart > 7.0 && distanceFromEnd > 7.0;
  387. }
  388. return false;
  389. }
  390. void mouseDown (const MouseEvent&) override
  391. {
  392. dragging = false;
  393. }
  394. void mouseDrag (const MouseEvent& e) override
  395. {
  396. if (dragging)
  397. {
  398. panel.dragConnector (e);
  399. }
  400. else if (e.mouseWasDraggedSinceMouseDown())
  401. {
  402. dragging = true;
  403. graph.graph.removeConnection (connection);
  404. double distanceFromStart, distanceFromEnd;
  405. getDistancesFromEnds (getPosition().toFloat() + e.position, distanceFromStart, distanceFromEnd);
  406. const bool isNearerSource = (distanceFromStart < distanceFromEnd);
  407. AudioProcessorGraph::NodeAndChannel dummy { 0, 0 };
  408. panel.beginConnectorDrag (isNearerSource ? dummy : connection.source,
  409. isNearerSource ? connection.destination : dummy,
  410. e);
  411. }
  412. }
  413. void mouseUp (const MouseEvent& e) override
  414. {
  415. if (dragging)
  416. panel.endDraggingConnector (e);
  417. }
  418. void resized() override
  419. {
  420. Point<float> p1, p2;
  421. getPoints (p1, p2);
  422. lastInputPos = p1;
  423. lastOutputPos = p2;
  424. p1 -= getPosition().toFloat();
  425. p2 -= getPosition().toFloat();
  426. linePath.clear();
  427. linePath.startNewSubPath (p1);
  428. linePath.cubicTo (p1.x, p1.y + (p2.y - p1.y) * 0.33f,
  429. p2.x, p1.y + (p2.y - p1.y) * 0.66f,
  430. p2.x, p2.y);
  431. PathStrokeType wideStroke (8.0f);
  432. wideStroke.createStrokedPath (hitPath, linePath);
  433. PathStrokeType stroke (2.5f);
  434. stroke.createStrokedPath (linePath, linePath);
  435. auto arrowW = 5.0f;
  436. auto arrowL = 4.0f;
  437. Path arrow;
  438. arrow.addTriangle (-arrowL, arrowW,
  439. -arrowL, -arrowW,
  440. arrowL, 0.0f);
  441. arrow.applyTransform (AffineTransform()
  442. .rotated (MathConstants<float>::halfPi - (float) atan2 (p2.x - p1.x, p2.y - p1.y))
  443. .translated ((p1 + p2) * 0.5f));
  444. linePath.addPath (arrow);
  445. linePath.setUsingNonZeroWinding (true);
  446. }
  447. void getDistancesFromEnds (Point<float> p, double& distanceFromStart, double& distanceFromEnd) const
  448. {
  449. Point<float> p1, p2;
  450. getPoints (p1, p2);
  451. distanceFromStart = p1.getDistanceFrom (p);
  452. distanceFromEnd = p2.getDistanceFrom (p);
  453. }
  454. GraphEditorPanel& panel;
  455. FilterGraph& graph;
  456. AudioProcessorGraph::Connection connection { { 0, 0 }, { 0, 0 } };
  457. Point<float> lastInputPos, lastOutputPos;
  458. Path linePath, hitPath;
  459. bool dragging = false;
  460. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectorComponent)
  461. };
  462. //==============================================================================
  463. GraphEditorPanel::GraphEditorPanel (FilterGraph& g) : graph (g)
  464. {
  465. graph.addChangeListener (this);
  466. setOpaque (true);
  467. }
  468. GraphEditorPanel::~GraphEditorPanel()
  469. {
  470. graph.removeChangeListener (this);
  471. draggingConnector = nullptr;
  472. nodes.clear();
  473. connectors.clear();
  474. }
  475. void GraphEditorPanel::paint (Graphics& g)
  476. {
  477. g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
  478. }
  479. void GraphEditorPanel::mouseDown (const MouseEvent& e)
  480. {
  481. if (e.mods.isPopupMenu())
  482. {
  483. PopupMenu m;
  484. if (auto* mainWindow = findParentComponentOfClass<MainHostWindow>())
  485. {
  486. mainWindow->addPluginsToMenu (m);
  487. auto r = m.show();
  488. if (auto* desc = mainWindow->getChosenType (r))
  489. createNewPlugin (*desc, e.position.toInt());
  490. }
  491. }
  492. }
  493. void GraphEditorPanel::createNewPlugin (const PluginDescription& desc, Point<int> position)
  494. {
  495. graph.addPlugin (desc, position.toDouble() / Point<double> ((double) getWidth(), (double) getHeight()));
  496. }
  497. GraphEditorPanel::FilterComponent* GraphEditorPanel::getComponentForFilter (const uint32 filterID) const
  498. {
  499. for (auto* fc : nodes)
  500. if (fc->pluginID == filterID)
  501. return fc;
  502. return nullptr;
  503. }
  504. GraphEditorPanel::ConnectorComponent* GraphEditorPanel::getComponentForConnection (const AudioProcessorGraph::Connection& conn) const
  505. {
  506. for (auto* cc : connectors)
  507. if (cc->connection == conn)
  508. return cc;
  509. return nullptr;
  510. }
  511. GraphEditorPanel::PinComponent* GraphEditorPanel::findPinAt (Point<float> pos) const
  512. {
  513. for (auto* fc : nodes)
  514. {
  515. // NB: A Visual Studio optimiser error means we have to put this Component* in a local
  516. // variable before trying to cast it, or it gets mysteriously optimised away..
  517. auto* comp = fc->getComponentAt (pos.toInt() - fc->getPosition());
  518. if (auto* pin = dynamic_cast<PinComponent*> (comp))
  519. return pin;
  520. }
  521. return nullptr;
  522. }
  523. void GraphEditorPanel::resized()
  524. {
  525. updateComponents();
  526. }
  527. void GraphEditorPanel::changeListenerCallback (ChangeBroadcaster*)
  528. {
  529. updateComponents();
  530. }
  531. void GraphEditorPanel::updateComponents()
  532. {
  533. for (int i = nodes.size(); --i >= 0;)
  534. if (graph.graph.getNodeForId (nodes.getUnchecked(i)->pluginID) == nullptr)
  535. nodes.remove (i);
  536. for (int i = connectors.size(); --i >= 0;)
  537. if (! graph.graph.isConnected (connectors.getUnchecked(i)->connection))
  538. connectors.remove (i);
  539. for (auto* fc : nodes)
  540. fc->update();
  541. for (auto* cc : connectors)
  542. cc->update();
  543. for (auto* f : graph.graph.getNodes())
  544. {
  545. if (getComponentForFilter (f->nodeID) == 0)
  546. {
  547. auto* comp = nodes.add (new FilterComponent (*this, f->nodeID));
  548. addAndMakeVisible (comp);
  549. comp->update();
  550. }
  551. }
  552. for (auto& c : graph.graph.getConnections())
  553. {
  554. if (getComponentForConnection (c) == 0)
  555. {
  556. auto* comp = connectors.add (new ConnectorComponent (*this));
  557. addAndMakeVisible (comp);
  558. comp->setInput (c.source);
  559. comp->setOutput (c.destination);
  560. }
  561. }
  562. }
  563. void GraphEditorPanel::beginConnectorDrag (AudioProcessorGraph::NodeAndChannel source,
  564. AudioProcessorGraph::NodeAndChannel dest,
  565. const MouseEvent& e)
  566. {
  567. auto* c = dynamic_cast<ConnectorComponent*> (e.originalComponent);
  568. connectors.removeObject (c, false);
  569. draggingConnector = c;
  570. if (draggingConnector == nullptr)
  571. draggingConnector = new ConnectorComponent (*this);
  572. draggingConnector->setInput (source);
  573. draggingConnector->setOutput (dest);
  574. addAndMakeVisible (draggingConnector);
  575. draggingConnector->toFront (false);
  576. dragConnector (e);
  577. }
  578. void GraphEditorPanel::dragConnector (const MouseEvent& e)
  579. {
  580. auto e2 = e.getEventRelativeTo (this);
  581. if (draggingConnector != nullptr)
  582. {
  583. draggingConnector->setTooltip ({});
  584. auto pos = e2.position;
  585. if (auto* pin = findPinAt (pos))
  586. {
  587. auto connection = draggingConnector->connection;
  588. if (connection.source.nodeID == 0 && ! pin->isInput)
  589. {
  590. connection.source = pin->pin;
  591. }
  592. else if (connection.destination.nodeID == 0 && pin->isInput)
  593. {
  594. connection.destination = pin->pin;
  595. }
  596. if (graph.graph.canConnect (connection))
  597. {
  598. pos = (pin->getParentComponent()->getPosition() + pin->getBounds().getCentre()).toFloat();
  599. draggingConnector->setTooltip (pin->getTooltip());
  600. }
  601. }
  602. if (draggingConnector->connection.source.nodeID == 0)
  603. draggingConnector->dragStart (pos);
  604. else
  605. draggingConnector->dragEnd (pos);
  606. }
  607. }
  608. void GraphEditorPanel::endDraggingConnector (const MouseEvent& e)
  609. {
  610. if (draggingConnector == nullptr)
  611. return;
  612. draggingConnector->setTooltip ({});
  613. auto e2 = e.getEventRelativeTo (this);
  614. auto connection = draggingConnector->connection;
  615. draggingConnector = nullptr;
  616. if (auto* pin = findPinAt (e2.position))
  617. {
  618. if (connection.source.nodeID == 0)
  619. {
  620. if (pin->isInput)
  621. return;
  622. connection.source = pin->pin;
  623. }
  624. else
  625. {
  626. if (! pin->isInput)
  627. return;
  628. connection.destination = pin->pin;
  629. }
  630. graph.graph.addConnection (connection);
  631. }
  632. }
  633. //==============================================================================
  634. struct GraphDocumentComponent::TooltipBar : public Component,
  635. private Timer
  636. {
  637. TooltipBar()
  638. {
  639. startTimer (100);
  640. }
  641. void paint (Graphics& g) override
  642. {
  643. g.setFont (Font (getHeight() * 0.7f, Font::bold));
  644. g.setColour (Colours::black);
  645. g.drawFittedText (tip, 10, 0, getWidth() - 12, getHeight(), Justification::centredLeft, 1);
  646. }
  647. void timerCallback() override
  648. {
  649. String newTip;
  650. if (auto* underMouse = Desktop::getInstance().getMainMouseSource().getComponentUnderMouse())
  651. if (auto* ttc = dynamic_cast<TooltipClient*> (underMouse))
  652. if (! (underMouse->isMouseButtonDown() || underMouse->isCurrentlyBlockedByAnotherModalComponent()))
  653. newTip = ttc->getTooltip();
  654. if (newTip != tip)
  655. {
  656. tip = newTip;
  657. repaint();
  658. }
  659. }
  660. String tip;
  661. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TooltipBar)
  662. };
  663. //==============================================================================
  664. GraphDocumentComponent::GraphDocumentComponent (AudioPluginFormatManager& fm, AudioDeviceManager& dm)
  665. : graph (new FilterGraph (fm)), deviceManager (dm),
  666. graphPlayer (getAppProperties().getUserSettings()->getBoolValue ("doublePrecisionProcessing", false))
  667. {
  668. addAndMakeVisible (graphPanel = new GraphEditorPanel (*graph));
  669. deviceManager.addChangeListener (graphPanel);
  670. graphPlayer.setProcessor (&graph->graph);
  671. keyState.addListener (&graphPlayer.getMidiMessageCollector());
  672. addAndMakeVisible (keyboardComp = new MidiKeyboardComponent (keyState, MidiKeyboardComponent::horizontalKeyboard));
  673. addAndMakeVisible (statusBar = new TooltipBar());
  674. deviceManager.addAudioCallback (&graphPlayer);
  675. deviceManager.addMidiInputCallback (String(), &graphPlayer.getMidiMessageCollector());
  676. graphPanel->updateComponents();
  677. }
  678. GraphDocumentComponent::~GraphDocumentComponent()
  679. {
  680. releaseGraph();
  681. keyState.removeListener (&graphPlayer.getMidiMessageCollector());
  682. }
  683. void GraphDocumentComponent::resized()
  684. {
  685. const int keysHeight = 60;
  686. const int statusHeight = 20;
  687. graphPanel->setBounds (0, 0, getWidth(), getHeight() - keysHeight);
  688. statusBar->setBounds (0, getHeight() - keysHeight - statusHeight, getWidth(), statusHeight);
  689. keyboardComp->setBounds (0, getHeight() - keysHeight, getWidth(), keysHeight);
  690. }
  691. void GraphDocumentComponent::createNewPlugin (const PluginDescription& desc, Point<int> pos)
  692. {
  693. graphPanel->createNewPlugin (desc, pos);
  694. }
  695. void GraphDocumentComponent::unfocusKeyboardComponent()
  696. {
  697. keyboardComp->unfocusAllComponents();
  698. }
  699. void GraphDocumentComponent::releaseGraph()
  700. {
  701. deviceManager.removeAudioCallback (&graphPlayer);
  702. deviceManager.removeMidiInputCallback (String(), &graphPlayer.getMidiMessageCollector());
  703. if (graphPanel != nullptr)
  704. {
  705. deviceManager.removeChangeListener (graphPanel);
  706. graphPanel = nullptr;
  707. }
  708. keyboardComp = nullptr;
  709. statusBar = nullptr;
  710. graphPlayer.setProcessor (nullptr);
  711. graph = nullptr;
  712. }
  713. void GraphDocumentComponent::setDoublePrecision (bool doublePrecision)
  714. {
  715. graphPlayer.setDoublePrecisionProcessing (doublePrecision);
  716. }
  717. bool GraphDocumentComponent::closeAnyOpenPluginWindows()
  718. {
  719. return graphPanel->graph.closeAnyOpenPluginWindows();
  720. }