Audio plugin host https://kx.studio/carla
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.

508 lines
15KB

  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. namespace juce
  20. {
  21. #if JUCE_ENABLE_LIVE_CONSTANT_EDITOR
  22. namespace LiveConstantEditor
  23. {
  24. //==============================================================================
  25. class AllComponentRepainter : private Timer,
  26. private DeletedAtShutdown
  27. {
  28. public:
  29. AllComponentRepainter() {}
  30. ~AllComponentRepainter() { clearSingletonInstance(); }
  31. juce_DeclareSingleton (AllComponentRepainter, false)
  32. void trigger()
  33. {
  34. if (! isTimerRunning())
  35. startTimer (100);
  36. }
  37. private:
  38. void timerCallback() override
  39. {
  40. stopTimer();
  41. Array<Component*> alreadyDone;
  42. for (int i = TopLevelWindow::getNumTopLevelWindows(); --i >= 0;)
  43. if (auto* c = TopLevelWindow::getTopLevelWindow(i))
  44. repaintAndResizeAllComps (c, alreadyDone);
  45. auto& desktop = Desktop::getInstance();
  46. for (int i = desktop.getNumComponents(); --i >= 0;)
  47. if (auto* c = desktop.getComponent(i))
  48. repaintAndResizeAllComps (c, alreadyDone);
  49. }
  50. static void repaintAndResizeAllComps (Component::SafePointer<Component> c,
  51. Array<Component*>& alreadyDone)
  52. {
  53. if (c->isVisible() && ! alreadyDone.contains (c))
  54. {
  55. c->repaint();
  56. c->resized();
  57. for (int i = c->getNumChildComponents(); --i >= 0;)
  58. {
  59. if (auto* child = c->getChildComponent(i))
  60. {
  61. repaintAndResizeAllComps (child, alreadyDone);
  62. alreadyDone.add (child);
  63. }
  64. if (c == nullptr)
  65. break;
  66. }
  67. }
  68. }
  69. };
  70. juce_ImplementSingleton (AllComponentRepainter)
  71. juce_ImplementSingleton (ValueList)
  72. //==============================================================================
  73. int64 parseInt (String s)
  74. {
  75. s = s.trimStart();
  76. if (s.startsWithChar ('-'))
  77. return -parseInt (s.substring (1));
  78. if (s.startsWith ("0x"))
  79. return s.substring(2).getHexValue64();
  80. return s.getLargeIntValue();
  81. }
  82. double parseDouble (const String& s)
  83. {
  84. return s.retainCharacters ("0123456789.eE-").getDoubleValue();
  85. }
  86. String intToString (int v, bool preferHex) { return preferHex ? "0x" + String::toHexString (v) : String (v); }
  87. String intToString (int64 v, bool preferHex) { return preferHex ? "0x" + String::toHexString (v) : String (v); }
  88. //==============================================================================
  89. LiveValueBase::LiveValueBase (const char* file, int line)
  90. : sourceFile (file), sourceLine (line)
  91. {
  92. name = File (sourceFile).getFileName() + " : " + String (sourceLine);
  93. }
  94. LiveValueBase::~LiveValueBase()
  95. {
  96. }
  97. //==============================================================================
  98. LivePropertyEditorBase::LivePropertyEditorBase (LiveValueBase& v, CodeDocument& d)
  99. : value (v), document (d), sourceEditor (document, &tokeniser)
  100. {
  101. setSize (600, 100);
  102. addAndMakeVisible (name);
  103. addAndMakeVisible (resetButton);
  104. addAndMakeVisible (valueEditor);
  105. addAndMakeVisible (sourceEditor);
  106. findOriginalValueInCode();
  107. selectOriginalValue();
  108. name.setFont (13.0f);
  109. name.setText (v.name, dontSendNotification);
  110. valueEditor.setMultiLine (v.isString());
  111. valueEditor.setReturnKeyStartsNewLine (v.isString());
  112. valueEditor.setText (v.getStringValue (wasHex), dontSendNotification);
  113. valueEditor.addListener (this);
  114. sourceEditor.setReadOnly (true);
  115. sourceEditor.setFont (sourceEditor.getFont().withHeight (13.0f));
  116. resetButton.addListener (this);
  117. }
  118. void LivePropertyEditorBase::paint (Graphics& g)
  119. {
  120. g.setColour (Colours::white);
  121. g.fillRect (getLocalBounds().removeFromBottom (1));
  122. }
  123. void LivePropertyEditorBase::resized()
  124. {
  125. auto r = getLocalBounds().reduced (0, 3).withTrimmedBottom (1);
  126. auto left = r.removeFromLeft (jmax (200, r.getWidth() / 3));
  127. auto top = left.removeFromTop (25);
  128. resetButton.setBounds (top.removeFromRight (35).reduced (0, 3));
  129. name.setBounds (top);
  130. if (customComp != nullptr)
  131. {
  132. valueEditor.setBounds (left.removeFromTop (25));
  133. left.removeFromTop (2);
  134. customComp->setBounds (left);
  135. }
  136. else
  137. {
  138. valueEditor.setBounds (left);
  139. }
  140. r.removeFromLeft (4);
  141. sourceEditor.setBounds (r);
  142. }
  143. void LivePropertyEditorBase::textEditorTextChanged (TextEditor&)
  144. {
  145. applyNewValue (valueEditor.getText());
  146. }
  147. void LivePropertyEditorBase::buttonClicked (Button*)
  148. {
  149. applyNewValue (value.getOriginalStringValue (wasHex));
  150. }
  151. void LivePropertyEditorBase::applyNewValue (const String& s)
  152. {
  153. value.setStringValue (s);
  154. document.replaceSection (valueStart.getPosition(), valueEnd.getPosition(), value.getCodeValue (wasHex));
  155. document.clearUndoHistory();
  156. selectOriginalValue();
  157. valueEditor.setText (s, dontSendNotification);
  158. AllComponentRepainter::getInstance()->trigger();
  159. }
  160. void LivePropertyEditorBase::selectOriginalValue()
  161. {
  162. sourceEditor.selectRegion (valueStart, valueEnd);
  163. }
  164. void LivePropertyEditorBase::findOriginalValueInCode()
  165. {
  166. CodeDocument::Position pos (document, value.sourceLine, 0);
  167. auto line = pos.getLineText();
  168. auto p = line.getCharPointer();
  169. p = CharacterFunctions::find (p, CharPointer_ASCII ("JUCE_LIVE_CONSTANT"));
  170. if (p.isEmpty())
  171. {
  172. // Not sure how this would happen - some kind of mix-up between source code and line numbers..
  173. jassertfalse;
  174. return;
  175. }
  176. p += (int) (sizeof ("JUCE_LIVE_CONSTANT") - 1);
  177. p = p.findEndOfWhitespace();
  178. if (! CharacterFunctions::find (p, CharPointer_ASCII ("JUCE_LIVE_CONSTANT")).isEmpty())
  179. {
  180. // Aargh! You've added two JUCE_LIVE_CONSTANT macros on the same line!
  181. // They're identified by their line number, so you must make sure each
  182. // one goes on a separate line!
  183. jassertfalse;
  184. }
  185. if (p.getAndAdvance() == '(')
  186. {
  187. auto start = p, end = p;
  188. int depth = 1;
  189. while (! end.isEmpty())
  190. {
  191. auto c = end.getAndAdvance();
  192. if (c == '(') ++depth;
  193. if (c == ')') --depth;
  194. if (depth == 0)
  195. {
  196. --end;
  197. break;
  198. }
  199. }
  200. if (end > start)
  201. {
  202. valueStart = CodeDocument::Position (document, value.sourceLine, (int) (start - line.getCharPointer()));
  203. valueEnd = CodeDocument::Position (document, value.sourceLine, (int) (end - line.getCharPointer()));
  204. valueStart.setPositionMaintained (true);
  205. valueEnd.setPositionMaintained (true);
  206. wasHex = String (start, end).containsIgnoreCase ("0x");
  207. }
  208. }
  209. }
  210. //==============================================================================
  211. class ValueListHolderComponent : public Component
  212. {
  213. public:
  214. ValueListHolderComponent (ValueList& l) : valueList (l)
  215. {
  216. setVisible (true);
  217. }
  218. void addItem (int width, LiveValueBase& v, CodeDocument& doc)
  219. {
  220. addAndMakeVisible (editors.add (v.createPropertyComponent (doc)));
  221. layout (width);
  222. }
  223. void layout (int width)
  224. {
  225. setSize (width, editors.size() * itemHeight);
  226. resized();
  227. }
  228. void resized() override
  229. {
  230. Rectangle<int> r (getLocalBounds().reduced (2, 0));
  231. for (int i = 0; i < editors.size(); ++i)
  232. editors.getUnchecked(i)->setBounds (r.removeFromTop (itemHeight));
  233. }
  234. enum { itemHeight = 120 };
  235. ValueList& valueList;
  236. OwnedArray<LivePropertyEditorBase> editors;
  237. };
  238. //==============================================================================
  239. class ValueList::EditorWindow : public DocumentWindow,
  240. private DeletedAtShutdown
  241. {
  242. public:
  243. EditorWindow (ValueList& list)
  244. : DocumentWindow ("Live Values", Colours::lightgrey, DocumentWindow::closeButton)
  245. {
  246. setLookAndFeel (&lookAndFeel);
  247. setUsingNativeTitleBar (true);
  248. viewport.setViewedComponent (new ValueListHolderComponent (list), true);
  249. viewport.setSize (700, 600);
  250. viewport.setScrollBarsShown (true, false);
  251. setContentNonOwned (&viewport, true);
  252. setResizable (true, false);
  253. setResizeLimits (500, 400, 10000, 10000);
  254. centreWithSize (getWidth(), getHeight());
  255. setVisible (true);
  256. }
  257. void closeButtonPressed() override
  258. {
  259. setVisible (false);
  260. }
  261. void updateItems (ValueList& list)
  262. {
  263. if (auto* l = dynamic_cast<ValueListHolderComponent*> (viewport.getViewedComponent()))
  264. {
  265. while (l->getNumChildComponents() < list.values.size())
  266. {
  267. if (auto* v = list.values [l->getNumChildComponents()])
  268. l->addItem (viewport.getMaximumVisibleWidth(), *v, list.getDocument (v->sourceFile));
  269. else
  270. break;
  271. }
  272. setVisible (true);
  273. }
  274. }
  275. void resized() override
  276. {
  277. DocumentWindow::resized();
  278. if (auto* l = dynamic_cast<ValueListHolderComponent*> (viewport.getViewedComponent()))
  279. l->layout (viewport.getMaximumVisibleWidth());
  280. }
  281. Viewport viewport;
  282. LookAndFeel_V3 lookAndFeel;
  283. };
  284. //==============================================================================
  285. ValueList::ValueList() {}
  286. ValueList::~ValueList() { clearSingletonInstance(); }
  287. void ValueList::addValue (LiveValueBase* v)
  288. {
  289. values.add (v);
  290. triggerAsyncUpdate();
  291. }
  292. void ValueList::handleAsyncUpdate()
  293. {
  294. if (editorWindow == nullptr)
  295. editorWindow = new EditorWindow (*this);
  296. editorWindow->updateItems (*this);
  297. }
  298. CodeDocument& ValueList::getDocument (const File& file)
  299. {
  300. const int index = documentFiles.indexOf (file.getFullPathName());
  301. if (index >= 0)
  302. return *documents.getUnchecked (index);
  303. auto* doc = documents.add (new CodeDocument());
  304. documentFiles.add (file);
  305. doc->replaceAllContent (file.loadFileAsString());
  306. doc->clearUndoHistory();
  307. return *doc;
  308. }
  309. //==============================================================================
  310. struct ColourEditorComp : public Component,
  311. private ChangeListener
  312. {
  313. ColourEditorComp (LivePropertyEditorBase& e) : editor (e)
  314. {
  315. setMouseCursor (MouseCursor::PointingHandCursor);
  316. }
  317. Colour getColour() const
  318. {
  319. return Colour ((uint32) parseInt (editor.value.getStringValue (false)));
  320. }
  321. void paint (Graphics& g) override
  322. {
  323. g.fillCheckerBoard (getLocalBounds(), 6, 6,
  324. Colour (0xffdddddd).overlaidWith (getColour()),
  325. Colour (0xffffffff).overlaidWith (getColour()));
  326. }
  327. void mouseDown (const MouseEvent&) override
  328. {
  329. auto* colourSelector = new ColourSelector();
  330. colourSelector->setName ("Colour");
  331. colourSelector->setCurrentColour (getColour());
  332. colourSelector->addChangeListener (this);
  333. colourSelector->setColour (ColourSelector::backgroundColourId, Colours::transparentBlack);
  334. colourSelector->setSize (300, 400);
  335. CallOutBox::launchAsynchronously (colourSelector, getScreenBounds(), nullptr);
  336. }
  337. void changeListenerCallback (ChangeBroadcaster* source) override
  338. {
  339. if (auto* cs = dynamic_cast<ColourSelector*> (source))
  340. editor.applyNewValue (getAsString (cs->getCurrentColour(), true));
  341. repaint();
  342. }
  343. LivePropertyEditorBase& editor;
  344. };
  345. Component* createColourEditor (LivePropertyEditorBase& editor)
  346. {
  347. return new ColourEditorComp (editor);
  348. }
  349. //==============================================================================
  350. struct SliderComp : public Component,
  351. private Slider::Listener
  352. {
  353. SliderComp (LivePropertyEditorBase& e, bool useFloat)
  354. : editor (e), isFloat (useFloat)
  355. {
  356. slider.setTextBoxStyle (Slider::NoTextBox, true, 0, 0);
  357. addAndMakeVisible (slider);
  358. updateRange();
  359. slider.addListener (this);
  360. }
  361. virtual void updateRange()
  362. {
  363. double v = isFloat ? parseDouble (editor.value.getStringValue (false))
  364. : (double) parseInt (editor.value.getStringValue (false));
  365. double range = isFloat ? 10 : 100;
  366. slider.setRange (v - range, v + range);
  367. slider.setValue (v, dontSendNotification);
  368. }
  369. void sliderValueChanged (Slider*) override
  370. {
  371. editor.applyNewValue (isFloat ? getAsString ((double) slider.getValue(), editor.wasHex)
  372. : getAsString ((int64) slider.getValue(), editor.wasHex));
  373. }
  374. void sliderDragStarted (Slider*) override {}
  375. void sliderDragEnded (Slider*) override { updateRange(); }
  376. void resized() override
  377. {
  378. slider.setBounds (getLocalBounds().removeFromTop (25));
  379. }
  380. LivePropertyEditorBase& editor;
  381. Slider slider;
  382. bool isFloat;
  383. };
  384. //==============================================================================
  385. struct BoolSliderComp : public SliderComp
  386. {
  387. BoolSliderComp (LivePropertyEditorBase& e) : SliderComp (e, false) {}
  388. void updateRange() override
  389. {
  390. slider.setRange (0.0, 1.0, dontSendNotification);
  391. slider.setValue (editor.value.getStringValue (false) == "true", dontSendNotification);
  392. }
  393. void sliderValueChanged (Slider*) override { editor.applyNewValue (slider.getValue() > 0.5 ? "true" : "false"); }
  394. };
  395. Component* createIntegerSlider (LivePropertyEditorBase& editor) { return new SliderComp (editor, false); }
  396. Component* createFloatSlider (LivePropertyEditorBase& editor) { return new SliderComp (editor, true); }
  397. Component* createBoolSlider (LivePropertyEditorBase& editor) { return new BoolSliderComp (editor); }
  398. }
  399. #endif
  400. } // namespace juce