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.

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