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.

488 lines
14KB

  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. resetButton.addListener (this);
  112. }
  113. void LivePropertyEditorBase::paint (Graphics& g)
  114. {
  115. g.setColour (Colours::white);
  116. g.fillRect (getLocalBounds().removeFromBottom (1));
  117. }
  118. void LivePropertyEditorBase::resized()
  119. {
  120. Rectangle<int> r (getLocalBounds().reduced (0, 3).withTrimmedBottom (1));
  121. Rectangle<int> left (r.removeFromLeft (jmax (200, r.getWidth() / 3)));
  122. Rectangle<int> top (left.removeFromTop (25));
  123. resetButton.setBounds (top.removeFromRight (35).reduced (0, 3));
  124. name.setBounds (top);
  125. if (customComp != nullptr)
  126. {
  127. valueEditor.setBounds (left.removeFromTop (25));
  128. left.removeFromTop (2);
  129. customComp->setBounds (left);
  130. }
  131. else
  132. {
  133. valueEditor.setBounds (left);
  134. }
  135. r.removeFromLeft (4);
  136. sourceEditor.setBounds (r);
  137. }
  138. void LivePropertyEditorBase::textEditorTextChanged (TextEditor&)
  139. {
  140. applyNewValue (valueEditor.getText());
  141. }
  142. void LivePropertyEditorBase::buttonClicked (Button*)
  143. {
  144. applyNewValue (value.getOriginalStringValue (wasHex));
  145. }
  146. void LivePropertyEditorBase::applyNewValue (const String& s)
  147. {
  148. value.setStringValue (s);
  149. document.replaceSection (valueStart.getPosition(), valueEnd.getPosition(), value.getCodeValue (wasHex));
  150. document.clearUndoHistory();
  151. selectOriginalValue();
  152. valueEditor.setText (s, dontSendNotification);
  153. AllComponentRepainter::getInstance()->trigger();
  154. }
  155. void LivePropertyEditorBase::selectOriginalValue()
  156. {
  157. sourceEditor.selectRegion (valueStart, valueEnd);
  158. }
  159. void LivePropertyEditorBase::findOriginalValueInCode()
  160. {
  161. CodeDocument::Position pos (document, value.sourceLine, 0);
  162. String line (pos.getLineText());
  163. String::CharPointerType p (line.getCharPointer());
  164. p = CharacterFunctions::find (p, CharPointer_ASCII ("JUCE_LIVE_CONSTANT"));
  165. if (p.isEmpty())
  166. {
  167. // Not sure how this would happen - some kind of mix-up between source code and line numbers..
  168. jassertfalse;
  169. return;
  170. }
  171. p += (int) (sizeof ("JUCE_LIVE_CONSTANT") - 1);
  172. p = p.findEndOfWhitespace();
  173. if (! CharacterFunctions::find (p, CharPointer_ASCII ("JUCE_LIVE_CONSTANT")).isEmpty())
  174. {
  175. // Aargh! You've added two JUCE_LIVE_CONSTANT macros on the same line!
  176. // They're identified by their line number, so you must make sure each
  177. // one goes on a separate line!
  178. jassertfalse;
  179. }
  180. if (p.getAndAdvance() == '(')
  181. {
  182. String::CharPointerType start (p), end (p);
  183. int depth = 1;
  184. while (! end.isEmpty())
  185. {
  186. const juce_wchar c = end.getAndAdvance();
  187. if (c == '(') ++depth;
  188. if (c == ')') --depth;
  189. if (depth == 0)
  190. {
  191. --end;
  192. break;
  193. }
  194. }
  195. if (end > start)
  196. {
  197. valueStart = CodeDocument::Position (document, value.sourceLine, (int) (start - line.getCharPointer()));
  198. valueEnd = CodeDocument::Position (document, value.sourceLine, (int) (end - line.getCharPointer()));
  199. valueStart.setPositionMaintained (true);
  200. valueEnd.setPositionMaintained (true);
  201. wasHex = String (start, end).containsIgnoreCase ("0x");
  202. }
  203. }
  204. }
  205. //==============================================================================
  206. class ValueListHolderComponent : public Component
  207. {
  208. public:
  209. ValueListHolderComponent (ValueList& l) : valueList (l)
  210. {
  211. setVisible (true);
  212. }
  213. void addItem (int width, LiveValueBase& v, CodeDocument& doc)
  214. {
  215. addAndMakeVisible (editors.add (v.createPropertyComponent (doc)));
  216. layout (width);
  217. }
  218. void layout (int width)
  219. {
  220. setSize (width, editors.size() * itemHeight);
  221. resized();
  222. }
  223. void resized() override
  224. {
  225. Rectangle<int> r (getLocalBounds().reduced (2, 0));
  226. for (int i = 0; i < editors.size(); ++i)
  227. editors.getUnchecked(i)->setBounds (r.removeFromTop (itemHeight));
  228. }
  229. enum { itemHeight = 120 };
  230. ValueList& valueList;
  231. OwnedArray<LivePropertyEditorBase> editors;
  232. };
  233. //==============================================================================
  234. class ValueList::EditorWindow : public DocumentWindow,
  235. private DeletedAtShutdown
  236. {
  237. public:
  238. EditorWindow (ValueList& list)
  239. : DocumentWindow ("Live Values", Colours::lightgrey, DocumentWindow::closeButton)
  240. {
  241. setLookAndFeel (&lookAndFeel);
  242. setUsingNativeTitleBar (true);
  243. viewport.setViewedComponent (new ValueListHolderComponent (list), true);
  244. viewport.setSize (700, 600);
  245. viewport.setScrollBarsShown (true, false);
  246. setContentNonOwned (&viewport, true);
  247. setResizable (true, false);
  248. setResizeLimits (500, 400, 10000, 10000);
  249. centreWithSize (getWidth(), getHeight());
  250. setVisible (true);
  251. }
  252. void closeButtonPressed() override
  253. {
  254. setVisible (false);
  255. }
  256. void updateItems (ValueList& list)
  257. {
  258. if (ValueListHolderComponent* l = dynamic_cast<ValueListHolderComponent*> (viewport.getViewedComponent()))
  259. {
  260. while (l->getNumChildComponents() < list.values.size())
  261. {
  262. if (LiveValueBase* v = list.values [l->getNumChildComponents()])
  263. l->addItem (viewport.getMaximumVisibleWidth(), *v, list.getDocument (v->sourceFile));
  264. else
  265. break;
  266. }
  267. setVisible (true);
  268. }
  269. }
  270. void resized() override
  271. {
  272. DocumentWindow::resized();
  273. if (ValueListHolderComponent* l = dynamic_cast<ValueListHolderComponent*> (viewport.getViewedComponent()))
  274. l->layout (viewport.getMaximumVisibleWidth());
  275. }
  276. Viewport viewport;
  277. LookAndFeel_V3 lookAndFeel;
  278. };
  279. //==============================================================================
  280. ValueList::ValueList() {}
  281. ValueList::~ValueList() { clearSingletonInstance(); }
  282. void ValueList::addValue (LiveValueBase* v)
  283. {
  284. values.add (v);
  285. triggerAsyncUpdate();
  286. }
  287. void ValueList::handleAsyncUpdate()
  288. {
  289. if (editorWindow == nullptr)
  290. editorWindow = new EditorWindow (*this);
  291. editorWindow->updateItems (*this);
  292. }
  293. CodeDocument& ValueList::getDocument (const File& file)
  294. {
  295. const int index = documentFiles.indexOf (file.getFullPathName());
  296. if (index >= 0)
  297. return *documents.getUnchecked (index);
  298. CodeDocument* doc = documents.add (new CodeDocument());
  299. documentFiles.add (file);
  300. doc->replaceAllContent (file.loadFileAsString());
  301. doc->clearUndoHistory();
  302. return *doc;
  303. }
  304. //==============================================================================
  305. struct ColourEditorComp : public Component,
  306. private ChangeListener
  307. {
  308. ColourEditorComp (LivePropertyEditorBase& e) : editor (e)
  309. {
  310. setMouseCursor (MouseCursor::PointingHandCursor);
  311. }
  312. Colour getColour() const
  313. {
  314. return Colour ((uint32) parseInt (editor.value.getStringValue (false)));
  315. }
  316. void paint (Graphics& g) override
  317. {
  318. g.fillCheckerBoard (getLocalBounds(), 6, 6,
  319. Colour (0xffdddddd).overlaidWith (getColour()),
  320. Colour (0xffffffff).overlaidWith (getColour()));
  321. }
  322. void mouseDown (const MouseEvent&) override
  323. {
  324. ColourSelector* colourSelector = new ColourSelector();
  325. colourSelector->setName ("Colour");
  326. colourSelector->setCurrentColour (getColour());
  327. colourSelector->addChangeListener (this);
  328. colourSelector->setColour (ColourSelector::backgroundColourId, Colours::transparentBlack);
  329. colourSelector->setSize (300, 400);
  330. CallOutBox::launchAsynchronously (colourSelector, getScreenBounds(), nullptr);
  331. }
  332. void changeListenerCallback (ChangeBroadcaster* source) override
  333. {
  334. if (ColourSelector* cs = dynamic_cast<ColourSelector*> (source))
  335. editor.applyNewValue (getAsString (cs->getCurrentColour(), true));
  336. repaint();
  337. }
  338. LivePropertyEditorBase& editor;
  339. };
  340. Component* createColourEditor (LivePropertyEditorBase& editor)
  341. {
  342. return new ColourEditorComp (editor);
  343. }
  344. //==============================================================================
  345. class SliderComp : public Component,
  346. private Slider::Listener
  347. {
  348. public:
  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. 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. private:
  366. LivePropertyEditorBase& editor;
  367. Slider slider;
  368. bool isFloat;
  369. void sliderValueChanged (Slider*)
  370. {
  371. editor.applyNewValue (isFloat ? getAsString ((double) slider.getValue(), editor.wasHex)
  372. : getAsString ((int64) slider.getValue(), editor.wasHex));
  373. }
  374. void sliderDragStarted (Slider*) {}
  375. void sliderDragEnded (Slider*) { updateRange(); }
  376. void resized()
  377. {
  378. slider.setBounds (getLocalBounds().removeFromTop (25));
  379. }
  380. };
  381. Component* createIntegerSlider (LivePropertyEditorBase& editor) { return new SliderComp (editor, false); }
  382. Component* createFloatSlider (LivePropertyEditorBase& editor) { return new SliderComp (editor, true); }
  383. }
  384. #endif