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.

739 lines
25KB

  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. #pragma once
  20. class LiveBuildCodeEditorDocument;
  21. //==============================================================================
  22. class LiveBuildCodeEditor : public CppCodeEditorComponent,
  23. private Timer
  24. {
  25. public:
  26. LiveBuildCodeEditor (LiveBuildCodeEditorDocument& edDoc, CodeDocument& doc)
  27. : CppCodeEditorComponent (edDoc.getFile(), doc),
  28. editorDoc (edDoc),
  29. classList (*this, edDoc)
  30. {
  31. }
  32. ~LiveBuildCodeEditor()
  33. {
  34. for (int i = getNumChildComponents(); --i >= 0;)
  35. if (auto* c = dynamic_cast<DiagnosticOverlayComponent*> (getChildComponent (i)))
  36. delete c;
  37. }
  38. CompileEngineChildProcess::Ptr getChildProcess() const
  39. {
  40. return editorDoc.getChildProcess();
  41. }
  42. Component* addDiagnosticOverlay (CodeDocument::Position start, CodeDocument::Position end,
  43. DiagnosticMessage::Type diagType)
  44. {
  45. auto* d = new DiagnosticOverlayComponent (*this, start, end, diagType);
  46. addAndMakeVisible (d);
  47. return d;
  48. }
  49. private:
  50. LiveBuildCodeEditorDocument& editorDoc;
  51. //==============================================================================
  52. struct OverlayComponent : public Component,
  53. private GenericCodeEditorComponent::Listener,
  54. private CodeDocument::Listener
  55. {
  56. OverlayComponent (CodeDocument::Position start,
  57. CodeDocument::Position end)
  58. : startPosition (start),
  59. endPosition (end)
  60. {
  61. startPosition.setPositionMaintained (true);
  62. endPosition.setPositionMaintained (true);
  63. }
  64. ~OverlayComponent()
  65. {
  66. setEditor (nullptr);
  67. }
  68. void setEditor (GenericCodeEditorComponent* editor)
  69. {
  70. if (editor != codeEditor)
  71. {
  72. if (codeEditor != nullptr)
  73. {
  74. codeEditor->removeListener (this);
  75. codeEditor->getDocument().removeListener (this);
  76. codeEditor->removeChildComponent (this);
  77. }
  78. codeEditor = editor;
  79. if (codeEditor != nullptr)
  80. {
  81. codeEditor->addListener (this);
  82. codeEditor->getDocument().addListener (this);
  83. codeEditor->addAndMakeVisible (this);
  84. }
  85. if (editor != nullptr)
  86. updatePosition();
  87. }
  88. }
  89. void codeEditorViewportMoved (CodeEditorComponent& editor) override
  90. {
  91. setEditor (dynamic_cast<GenericCodeEditorComponent*> (&editor));
  92. updatePosition();
  93. }
  94. void codeDocumentTextInserted (const String&, int) override { updatePosition(); }
  95. void codeDocumentTextDeleted (int, int) override { updatePosition(); }
  96. void parentSizeChanged() override
  97. {
  98. updatePosition();
  99. }
  100. virtual void updatePosition() = 0;
  101. Component::SafePointer<GenericCodeEditorComponent> codeEditor;
  102. CodeDocument::Position startPosition, endPosition;
  103. };
  104. //==============================================================================
  105. struct LaunchClassOverlayComponent : public OverlayComponent
  106. {
  107. LaunchClassOverlayComponent (GenericCodeEditorComponent& editor,
  108. CodeDocument::Position start, CodeDocument::Position end,
  109. const String className)
  110. : OverlayComponent (start, end),
  111. launchButton (className.fromLastOccurrenceOf ("::", false, false)),
  112. name (className)
  113. {
  114. setAlwaysOnTop (true);
  115. setEditor (&editor);
  116. addAndMakeVisible (launchButton);
  117. }
  118. void updatePosition() override
  119. {
  120. if (codeEditor != nullptr)
  121. {
  122. jassert (isVisible());
  123. const auto charArea = codeEditor->getCharacterBounds (startPosition);
  124. const int height = charArea.getHeight() + 8;
  125. Font f (height * 0.7f);
  126. const int width = jmin (height * 2 + f.getStringWidth (launchButton.getName()),
  127. jmax (120, codeEditor->proportionOfWidth (0.2f)));
  128. setBounds (codeEditor->getWidth() - width - 10, charArea.getY() - 4,
  129. width, height);
  130. }
  131. }
  132. void resized() override
  133. {
  134. launchButton.setBounds (getLocalBounds());
  135. }
  136. struct LaunchButton : public Button
  137. {
  138. LaunchButton (const String& nm) : Button (nm)
  139. {
  140. setMouseCursor (MouseCursor::PointingHandCursor);
  141. }
  142. void paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) override
  143. {
  144. Colour background (findColour (CodeEditorComponent::backgroundColourId)
  145. .contrasting()
  146. .overlaidWith (Colours::yellow.withAlpha (0.5f))
  147. .withAlpha (0.4f));
  148. g.setColour (background);
  149. g.fillRoundedRectangle (getLocalBounds().toFloat(), 3.0f);
  150. const Path& path = getIcons().play;
  151. Colour col (background.contrasting (Colours::lightgreen, 0.6f));
  152. Rectangle<int> r (getLocalBounds().reduced (getHeight() / 5));
  153. Icon (path, col.withAlpha (isButtonDown ? 1.0f : (isMouseOverButton ? 0.8f : 0.5f)))
  154. .draw (g, r.removeFromLeft (getHeight()).toFloat(), false);
  155. g.setColour (Colours::white);
  156. g.setFont (getHeight() * 0.7f);
  157. g.drawFittedText (getName(), r, Justification::centredLeft, 1);
  158. }
  159. void clicked() override
  160. {
  161. if (auto* l = findParentComponentOfClass<LaunchClassOverlayComponent>())
  162. l->launch();
  163. }
  164. };
  165. void launch()
  166. {
  167. if (auto* e = findParentComponentOfClass<LiveBuildCodeEditor>())
  168. e->launch (name);
  169. }
  170. private:
  171. LaunchButton launchButton;
  172. String name;
  173. };
  174. struct ComponentClassList : private Timer
  175. {
  176. ComponentClassList (GenericCodeEditorComponent& e, LiveBuildCodeEditorDocument& edDoc)
  177. : owner (e),
  178. childProcess (edDoc.getChildProcess()),
  179. file (edDoc.getFile())
  180. {
  181. startTimer (600);
  182. }
  183. ~ComponentClassList()
  184. {
  185. deleteOverlays();
  186. }
  187. void timerCallback() override
  188. {
  189. Array<ClassDatabase::Class*> newClasses;
  190. if (childProcess != nullptr)
  191. childProcess->getComponentList().globalNamespace.findClassesDeclaredInFile (newClasses, file);
  192. for (int i = newClasses.size(); --i >= 0;)
  193. if (! newClasses.getUnchecked(i)->getInstantiationFlags().canBeInstantiated())
  194. newClasses.remove (i);
  195. if (newClasses != classes)
  196. {
  197. classes = newClasses;
  198. deleteOverlays();
  199. for (auto& c : classes)
  200. {
  201. CodeDocument::Position pos (owner.getDocument(), c->getClassDeclarationRange().range.getStart());
  202. overlays.add (new LaunchClassOverlayComponent (owner, pos, pos, c->getName()));
  203. }
  204. }
  205. }
  206. void deleteOverlays()
  207. {
  208. for (auto& o : overlays)
  209. o.deleteAndZero();
  210. overlays.clear();
  211. }
  212. GenericCodeEditorComponent& owner;
  213. CompileEngineChildProcess::Ptr childProcess;
  214. File file;
  215. Array<ClassDatabase::Class*> classes;
  216. Array<Component::SafePointer<Component>> overlays;
  217. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentClassList)
  218. };
  219. ComponentClassList classList;
  220. //==============================================================================
  221. struct DiagnosticOverlayComponent : public OverlayComponent
  222. {
  223. DiagnosticOverlayComponent (GenericCodeEditorComponent& editor,
  224. CodeDocument::Position start, CodeDocument::Position end,
  225. DiagnosticMessage::Type diagType)
  226. : OverlayComponent (start, end), diagnosticType (diagType)
  227. {
  228. setInterceptsMouseClicks (false, false);
  229. setEditor (&editor);
  230. }
  231. void updatePosition() override
  232. {
  233. if (codeEditor != nullptr)
  234. {
  235. jassert (isVisible());
  236. const auto charStartRect = codeEditor->getCharacterBounds (startPosition);
  237. const auto charEndRect = codeEditor->getCharacterBounds (endPosition);
  238. auto charHeight = charStartRect.getHeight();
  239. const auto editorBounds = codeEditor->getBounds();
  240. arrowXMin = static_cast<int> (jmin (charStartRect.getX(), charEndRect.getX()));
  241. arrowXMax = static_cast<int> (jmax (charStartRect.getX() + charStartRect.getWidth(), charEndRect.getX() + charEndRect.getWidth()));
  242. lineYMin = charStartRect.getY();
  243. lineOffset = charHeight;
  244. setBounds (0, lineYMin, editorBounds.getWidth(), lineOffset + charHeight);
  245. repaint();
  246. }
  247. }
  248. void paint (Graphics& g) override
  249. {
  250. const auto diagColour = diagnosticType == DiagnosticMessage::Type::error ? Colours::red
  251. : Colour (200, 200, 64);
  252. g.setColour (diagColour.withAlpha (0.2f));
  253. g.fillRect (getLocalBounds().withTrimmedBottom (lineOffset));
  254. Path path;
  255. const float bottomY = getHeight() - (lineOffset / 2.0f);
  256. path.addTriangle ((float) arrowXMin, bottomY,
  257. (arrowXMax + arrowXMin) / 2.0f, (float) lineOffset,
  258. (float) arrowXMax, bottomY);
  259. g.setColour (diagColour.withAlpha (0.8f));
  260. g.fillPath (path);
  261. }
  262. private:
  263. int arrowXMin, arrowXMax;
  264. int lineYMin, lineOffset;
  265. const DiagnosticMessage::Type diagnosticType;
  266. };
  267. //==============================================================================
  268. void timerCallback() override
  269. {
  270. if (isMouseButtonDownAnywhere())
  271. return;
  272. MouseInputSource mouse = Desktop::getInstance().getMainMouseSource();
  273. Component* underMouse = mouse.getComponentUnderMouse();
  274. if (underMouse != nullptr
  275. && (dynamic_cast<ControlsComponent*> (underMouse) != nullptr
  276. || underMouse->findParentComponentOfClass<ControlsComponent>() != nullptr))
  277. return;
  278. overlay = nullptr;
  279. if (hasKeyboardFocus (true) && underMouse != nullptr
  280. && (underMouse == this || underMouse->isParentOf (this)))
  281. {
  282. Point<int> mousePos = getLocalPoint (nullptr, mouse.getScreenPosition()).toInt();
  283. CodeDocument::Position start, end;
  284. getDocument().findTokenContaining (getPositionAt (mousePos.x, mousePos.y), start, end);
  285. if (end.getPosition() > start.getPosition())
  286. {
  287. Range<int> selection = optimiseSelection ({ start.getPosition(), end.getPosition() });
  288. String text = getTextInRange (selection).toLowerCase();
  289. if (isIntegerLiteral (text) || isFloatLiteral (text))
  290. overlay = new LiteralHighlightOverlay (*this, selection, mightBeColourValue (text));
  291. }
  292. }
  293. startTimerHz (10);
  294. }
  295. void hideOverlay()
  296. {
  297. stopTimer();
  298. overlay = nullptr;
  299. }
  300. void focusLost (FocusChangeType) override
  301. {
  302. if (CompileEngineChildProcess::Ptr childProcess = getChildProcess())
  303. childProcess->flushEditorChanges();
  304. }
  305. void mouseMove (const MouseEvent& e) override
  306. {
  307. if (overlay == nullptr)
  308. startTimer (100);
  309. CppCodeEditorComponent::mouseMove (e);
  310. }
  311. void mouseDrag (const MouseEvent& e) override
  312. {
  313. if (e.getDistanceFromDragStart() > 0)
  314. hideOverlay();
  315. CppCodeEditorComponent::mouseDrag (e);
  316. }
  317. void mouseDown (const MouseEvent& e) override
  318. {
  319. CppCodeEditorComponent::mouseDown (e);
  320. }
  321. void mouseUp (const MouseEvent& e) override
  322. {
  323. CppCodeEditorComponent::mouseUp (e);
  324. }
  325. bool keyPressed (const KeyPress& key) override
  326. {
  327. hideOverlay();
  328. return CppCodeEditorComponent::keyPressed (key);
  329. }
  330. static bool isIntegerLiteral (const String& text) { return CppParserHelpers::parseSingleToken (text) == CPlusPlusCodeTokeniser::tokenType_integer; }
  331. static bool isFloatLiteral (const String& text) { return CppParserHelpers::parseSingleToken (text) == CPlusPlusCodeTokeniser::tokenType_float; }
  332. static bool mightBeColourValue (const String& text)
  333. {
  334. return isIntegerLiteral (text)
  335. && text.trim().startsWith ("0x")
  336. && text.trim().length() > 7;
  337. }
  338. Range<int> optimiseSelection (Range<int> selection)
  339. {
  340. String text (getTextInRange (selection));
  341. if (CharacterFunctions::isDigit (text[0]) || text[0] == '.')
  342. if (getTextInRange (Range<int> (selection.getStart() - 1, selection.getStart())) == "-")
  343. selection.setStart (selection.getStart() - 1);
  344. selection.setStart (selection.getStart() + (text.length() - text.trimStart().length()));
  345. selection.setEnd (selection.getEnd() - (text.length() - text.trimEnd().length()));
  346. return selection;
  347. }
  348. void launch (const String& name)
  349. {
  350. if (CompileEngineChildProcess::Ptr p = getChildProcess())
  351. if (auto* cls = p->getComponentList().globalNamespace.findClass (name))
  352. p->openPreview (*cls);
  353. }
  354. //==============================================================================
  355. class ControlsComponent : public Component,
  356. private Slider::Listener,
  357. private ChangeListener
  358. {
  359. public:
  360. ControlsComponent (CodeDocument& doc, const Range<int>& selection,
  361. CompileEngineChildProcess* cp, bool showColourSelector)
  362. : document (doc),
  363. start (doc, selection.getStart()),
  364. end (doc, selection.getEnd()),
  365. childProcess (cp)
  366. {
  367. slider.setTextBoxStyle (Slider::NoTextBox, true, 0, 0);
  368. slider.setWantsKeyboardFocus (false);
  369. slider.setMouseClickGrabsKeyboardFocus (false);
  370. setWantsKeyboardFocus (false);
  371. setMouseClickGrabsKeyboardFocus (false);
  372. addAndMakeVisible (&slider);
  373. updateRange();
  374. slider.addListener (this);
  375. if (showColourSelector)
  376. {
  377. updateColourSelector();
  378. selector.setWantsKeyboardFocus (false);
  379. selector.setMouseClickGrabsKeyboardFocus (false);
  380. addAndMakeVisible (&selector);
  381. setSize (400, sliderHeight + 400);
  382. selector.addChangeListener (this);
  383. }
  384. else
  385. {
  386. setSize (400, sliderHeight);
  387. }
  388. end.setPositionMaintained (true);
  389. }
  390. void updateRange()
  391. {
  392. double v = getValue();
  393. if (isFloat())
  394. slider.setRange (v - 10, v + 10);
  395. else
  396. slider.setRange (v - 100, v + 100);
  397. slider.setValue (v, dontSendNotification);
  398. }
  399. private:
  400. Slider slider;
  401. ColourSelector selector;
  402. CodeDocument& document;
  403. CodeDocument::Position start, end;
  404. CompileEngineChildProcess::Ptr childProcess;
  405. static const int sliderHeight = 26;
  406. void paint (Graphics& g) override
  407. {
  408. g.setColour (LiteralHighlightOverlay::getBackgroundColour());
  409. g.fillRoundedRectangle (getLocalBounds().toFloat(), 8.0f);
  410. }
  411. void sliderValueChanged (Slider* s) override
  412. {
  413. const String oldText (document.getTextBetween (start, end));
  414. const String newText (CppParserHelpers::getReplacementStringInSameFormat (oldText, s->getValue()));
  415. if (oldText != newText)
  416. document.replaceSection (start.getPosition(), end.getPosition(), newText);
  417. if (childProcess != nullptr)
  418. childProcess->flushEditorChanges();
  419. updateColourSelector();
  420. }
  421. void sliderDragStarted (Slider*) override {}
  422. void sliderDragEnded (Slider*) override { updateRange(); }
  423. void changeListenerCallback (ChangeBroadcaster*) override
  424. {
  425. setNewColour (selector.getCurrentColour());
  426. }
  427. void updateColourSelector()
  428. {
  429. selector.setCurrentColour (getCurrentColour());
  430. }
  431. Colour getCurrentColour() const
  432. {
  433. int64 val;
  434. if (CppParserHelpers::parseInt (document.getTextBetween (start, end), val))
  435. return Colour ((uint32) val);
  436. return Colours::white;
  437. }
  438. void setNewColour (const Colour& c)
  439. {
  440. const String oldText (document.getTextBetween (start, end));
  441. const String newText (CppParserHelpers::getReplacementStringInSameFormat (oldText, (int64) c.getARGB()));
  442. if (oldText != newText)
  443. document.replaceSection (start.getPosition(), end.getPosition(), newText);
  444. if (childProcess != nullptr)
  445. childProcess->flushEditorChanges();
  446. }
  447. void resized() override
  448. {
  449. Rectangle<int> r (getLocalBounds());
  450. slider.setBounds (r.removeFromTop (sliderHeight));
  451. r.removeFromTop (10);
  452. if (selector.isVisible())
  453. selector.setBounds (r);
  454. }
  455. double getValue() const
  456. {
  457. const String text (document.getTextBetween (start, end));
  458. if (text.containsChar ('.'))
  459. {
  460. double f;
  461. if (CppParserHelpers::parseFloat (text, f))
  462. return f;
  463. }
  464. else
  465. {
  466. int64 val;
  467. if (CppParserHelpers::parseInt (text, val))
  468. return (double) val;
  469. }
  470. jassertfalse;
  471. return 0;
  472. }
  473. bool isFloat() const
  474. {
  475. return document.getTextBetween (start, end).containsChar ('.');
  476. }
  477. };
  478. //==============================================================================
  479. struct LiteralHighlightOverlay : public Component,
  480. private CodeDocument::Listener
  481. {
  482. LiteralHighlightOverlay (LiveBuildCodeEditor& e, Range<int> section, bool showColourSelector)
  483. : owner (e),
  484. start (e.getDocument(), section.getStart()),
  485. end (e.getDocument(), section.getEnd()),
  486. controls (e.getDocument(), section, e.getChildProcess(), showColourSelector)
  487. {
  488. if (e.hasKeyboardFocus (true))
  489. previouslyFocused = Component::getCurrentlyFocusedComponent();
  490. start.setPositionMaintained (true);
  491. end.setPositionMaintained (true);
  492. setInterceptsMouseClicks (false, false);
  493. if (Component* parent = owner.findParentComponentOfClass<ProjectContentComponent>())
  494. parent->addAndMakeVisible (controls);
  495. else
  496. jassertfalse;
  497. owner.addAndMakeVisible (this);
  498. toBack();
  499. updatePosition();
  500. owner.getDocument().addListener (this);
  501. }
  502. ~LiteralHighlightOverlay()
  503. {
  504. if (Component* p = getParentComponent())
  505. {
  506. p->removeChildComponent (this);
  507. if (previouslyFocused != nullptr && ! previouslyFocused->hasKeyboardFocus (true))
  508. previouslyFocused->grabKeyboardFocus();
  509. }
  510. owner.getDocument().removeListener (this);
  511. }
  512. void paint (Graphics& g) override
  513. {
  514. g.setColour (getBackgroundColour());
  515. Rectangle<int> r (getLocalBounds());
  516. g.fillRect (r.removeFromTop (borderSize));
  517. g.fillRect (r.removeFromLeft (borderSize));
  518. g.fillRect (r.removeFromRight (borderSize));
  519. }
  520. void updatePosition()
  521. {
  522. Rectangle<int> area = owner.getCharacterBounds (start)
  523. .getUnion (owner.getCharacterBounds (end.movedBy (-1)))
  524. .expanded (borderSize)
  525. .withTrimmedBottom (borderSize);
  526. setBounds (getParentComponent()->getLocalArea (&owner, area));
  527. area.setPosition (area.getX() - controls.getWidth() / 2, area.getBottom());
  528. area.setSize (controls.getWidth(), controls.getHeight());
  529. controls.setBounds (controls.getParentComponent()->getLocalArea (&owner, area));
  530. }
  531. void codeDocumentTextInserted (const String&, int) override { updatePosition(); }
  532. void codeDocumentTextDeleted (int, int) override { updatePosition(); }
  533. LiveBuildCodeEditor& owner;
  534. CodeDocument::Position start, end;
  535. ControlsComponent controls;
  536. Component::SafePointer<Component> previouslyFocused;
  537. static const int borderSize = 4;
  538. static Colour getBackgroundColour() { return Colour (0xcb5c7879); }
  539. };
  540. ScopedPointer<LiteralHighlightOverlay> overlay;
  541. };
  542. //==============================================================================
  543. class LiveBuildCodeEditorDocument : public SourceCodeDocument
  544. {
  545. public:
  546. LiveBuildCodeEditorDocument (Project* project, const File& file)
  547. : SourceCodeDocument (project, file)
  548. {
  549. if (project != nullptr)
  550. if (CompileEngineChildProcess::Ptr childProcess = getChildProcess())
  551. childProcess->editorOpened (file, getCodeDocument());
  552. }
  553. struct Type : public SourceCodeDocument::Type
  554. {
  555. Document* openFile (Project* proj, const File& file) override
  556. {
  557. return new LiveBuildCodeEditorDocument (proj, file);
  558. }
  559. };
  560. Component* createEditor() override
  561. {
  562. SourceCodeEditor* e = nullptr;
  563. if (fileNeedsCppSyntaxHighlighting (getFile()))
  564. e = new SourceCodeEditor (this, new LiveBuildCodeEditor (*this, getCodeDocument()));
  565. else
  566. e = new SourceCodeEditor (this, getCodeDocument());
  567. applyLastState (*(e->editor));
  568. return e;
  569. }
  570. // override save() to make a few more attempts at saving if it fails, since on Windows
  571. // the compiler can interfere with things saving..
  572. bool save() override
  573. {
  574. for (int i = 5; --i >= 0;)
  575. {
  576. if (SourceCodeDocument::save()) // should already re-try for up to half a second
  577. return true;
  578. Thread::sleep (100);
  579. }
  580. return false;
  581. }
  582. CompileEngineChildProcess::Ptr getChildProcess() const
  583. {
  584. return ProjucerApplication::getApp().childProcessCache->getExisting (*project);
  585. }
  586. };