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.

485 lines
14KB

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