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.

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