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.

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