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.

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