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.

501 lines
15KB

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