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.

455 lines
13KB

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