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