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.

735 lines
25KB

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