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.

1178 lines
36KB

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