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.

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