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.

1116 lines
34KB

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