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.

867 lines
26KB

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