|
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2017 - ROLI Ltd.
-
- JUCE is an open source library subject to commercial or open-source
- licensing.
-
- By using JUCE, you agree to the terms of both the JUCE 5 End-User License
- Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
- 27th April 2017).
-
- End User License Agreement: www.juce.com/juce-5-licence
- Privacy Policy: www.juce.com/juce-5-privacy-policy
-
- Or: You may also use this code under the terms of the GPL v3 (see
- www.gnu.org/licenses).
-
- JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
- EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
- DISCLAIMED.
-
- ==============================================================================
- */
-
- #pragma once
-
- class LiveBuildCodeEditorDocument;
-
- //==============================================================================
- class LiveBuildCodeEditor : public CppCodeEditorComponent,
- private Timer
- {
- public:
- LiveBuildCodeEditor (LiveBuildCodeEditorDocument& edDoc, CodeDocument& doc)
- : CppCodeEditorComponent (edDoc.getFile(), doc),
- editorDoc (edDoc),
- classList (*this, edDoc)
- {
- }
-
- ~LiveBuildCodeEditor()
- {
- for (int i = getNumChildComponents(); --i >= 0;)
- if (auto* c = dynamic_cast<DiagnosticOverlayComponent*> (getChildComponent (i)))
- delete c;
- }
-
- CompileEngineChildProcess::Ptr getChildProcess() const
- {
- return editorDoc.getChildProcess();
- }
-
- Component* addDiagnosticOverlay (CodeDocument::Position start, CodeDocument::Position end,
- DiagnosticMessage::Type diagType)
- {
- auto* d = new DiagnosticOverlayComponent (*this, start, end, diagType);
- addAndMakeVisible (d);
- return d;
- }
-
- private:
- LiveBuildCodeEditorDocument& editorDoc;
-
- //==============================================================================
- struct OverlayComponent : public Component,
- private GenericCodeEditorComponent::Listener,
- private CodeDocument::Listener
- {
- OverlayComponent (CodeDocument::Position start,
- CodeDocument::Position end)
- : startPosition (start),
- endPosition (end)
- {
- startPosition.setPositionMaintained (true);
- endPosition.setPositionMaintained (true);
- }
-
- ~OverlayComponent()
- {
- setEditor (nullptr);
- }
-
- void setEditor (GenericCodeEditorComponent* editor)
- {
- if (editor != codeEditor)
- {
- if (codeEditor != nullptr)
- {
- codeEditor->removeListener (this);
- codeEditor->getDocument().removeListener (this);
- codeEditor->removeChildComponent (this);
- }
-
- codeEditor = editor;
-
- if (codeEditor != nullptr)
- {
- codeEditor->addListener (this);
- codeEditor->getDocument().addListener (this);
- codeEditor->addAndMakeVisible (this);
- }
-
- if (editor != nullptr)
- updatePosition();
- }
- }
-
- void codeEditorViewportMoved (CodeEditorComponent& editor) override
- {
- setEditor (dynamic_cast<GenericCodeEditorComponent*> (&editor));
- updatePosition();
- }
-
- void codeDocumentTextInserted (const String&, int) override { updatePosition(); }
- void codeDocumentTextDeleted (int, int) override { updatePosition(); }
-
- void parentSizeChanged() override
- {
- updatePosition();
- }
-
- virtual void updatePosition() = 0;
-
- Component::SafePointer<GenericCodeEditorComponent> codeEditor;
- CodeDocument::Position startPosition, endPosition;
- };
-
- //==============================================================================
- struct LaunchClassOverlayComponent : public OverlayComponent
- {
- LaunchClassOverlayComponent (GenericCodeEditorComponent& editor,
- CodeDocument::Position start, CodeDocument::Position end,
- const String className)
- : OverlayComponent (start, end),
- launchButton (className.fromLastOccurrenceOf ("::", false, false)),
- name (className)
- {
- setAlwaysOnTop (true);
- setEditor (&editor);
- addAndMakeVisible (launchButton);
- }
-
- void updatePosition() override
- {
- if (codeEditor != nullptr)
- {
- jassert (isVisible());
-
- const auto charArea = codeEditor->getCharacterBounds (startPosition);
- const int height = charArea.getHeight() + 8;
-
- Font f (height * 0.7f);
-
- const int width = jmin (height * 2 + f.getStringWidth (launchButton.getName()),
- jmax (120, codeEditor->proportionOfWidth (0.2f)));
-
- setBounds (codeEditor->getWidth() - width - 10, charArea.getY() - 4,
- width, height);
- }
- }
-
- void resized() override
- {
- launchButton.setBounds (getLocalBounds());
- }
-
- struct LaunchButton : public Button
- {
- LaunchButton (const String& nm) : Button (nm)
- {
- setMouseCursor (MouseCursor::PointingHandCursor);
- }
-
- void paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) override
- {
- Colour background (findColour (CodeEditorComponent::backgroundColourId)
- .contrasting()
- .overlaidWith (Colours::yellow.withAlpha (0.5f))
- .withAlpha (0.4f));
- g.setColour (background);
- g.fillRoundedRectangle (getLocalBounds().toFloat(), 3.0f);
-
- const Path& path = getIcons().play;
-
- Colour col (background.contrasting (Colours::lightgreen, 0.6f));
-
- Rectangle<int> r (getLocalBounds().reduced (getHeight() / 5));
-
- Icon (path, col.withAlpha (isButtonDown ? 1.0f : (isMouseOverButton ? 0.8f : 0.5f)))
- .draw (g, r.removeFromLeft (getHeight()).toFloat(), false);
-
- g.setColour (Colours::white);
- g.setFont (getHeight() * 0.7f);
- g.drawFittedText (getName(), r, Justification::centredLeft, 1);
- }
-
- void clicked() override
- {
- if (auto* l = findParentComponentOfClass<LaunchClassOverlayComponent>())
- l->launch();
- }
- };
-
- void launch()
- {
- if (auto* e = findParentComponentOfClass<LiveBuildCodeEditor>())
- e->launch (name);
- }
-
- private:
- LaunchButton launchButton;
- String name;
- };
-
- struct ComponentClassList : private Timer
- {
- ComponentClassList (GenericCodeEditorComponent& e, LiveBuildCodeEditorDocument& edDoc)
- : owner (e),
- childProcess (edDoc.getChildProcess()),
- file (edDoc.getFile())
- {
- startTimer (600);
- }
-
- ~ComponentClassList()
- {
- deleteOverlays();
- }
-
- void timerCallback() override
- {
- Array<ClassDatabase::Class*> newClasses;
-
- if (childProcess != nullptr)
- childProcess->getComponentList().globalNamespace.findClassesDeclaredInFile (newClasses, file);
-
- for (int i = newClasses.size(); --i >= 0;)
- if (! newClasses.getUnchecked(i)->getInstantiationFlags().canBeInstantiated())
- newClasses.remove (i);
-
- if (newClasses != classes)
- {
- classes = newClasses;
- deleteOverlays();
-
- for (auto& c : classes)
- {
- CodeDocument::Position pos (owner.getDocument(), c->getClassDeclarationRange().range.getStart());
- overlays.add (new LaunchClassOverlayComponent (owner, pos, pos, c->getName()));
- }
- }
- }
-
- void deleteOverlays()
- {
- for (auto& o : overlays)
- o.deleteAndZero();
-
- overlays.clear();
- }
-
- GenericCodeEditorComponent& owner;
- CompileEngineChildProcess::Ptr childProcess;
- File file;
- Array<ClassDatabase::Class*> classes;
- Array<Component::SafePointer<Component>> overlays;
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentClassList)
- };
-
- ComponentClassList classList;
-
- //==============================================================================
- struct DiagnosticOverlayComponent : public OverlayComponent
- {
- DiagnosticOverlayComponent (GenericCodeEditorComponent& editor,
- CodeDocument::Position start, CodeDocument::Position end,
- DiagnosticMessage::Type diagType)
- : OverlayComponent (start, end), diagnosticType (diagType)
- {
- setInterceptsMouseClicks (false, false);
- setEditor (&editor);
- }
-
- void updatePosition() override
- {
- if (codeEditor != nullptr)
- {
- jassert (isVisible());
-
- const auto charStartRect = codeEditor->getCharacterBounds (startPosition);
- const auto charEndRect = codeEditor->getCharacterBounds (endPosition);
-
- auto charHeight = charStartRect.getHeight();
- const auto editorBounds = codeEditor->getBounds();
-
- arrowXMin = static_cast<int> (jmin (charStartRect.getX(), charEndRect.getX()));
- arrowXMax = static_cast<int> (jmax (charStartRect.getX() + charStartRect.getWidth(), charEndRect.getX() + charEndRect.getWidth()));
-
- lineYMin = charStartRect.getY();
- lineOffset = charHeight;
-
- setBounds (0, lineYMin, editorBounds.getWidth(), lineOffset + charHeight);
- repaint();
- }
- }
-
- void paint (Graphics& g) override
- {
- const auto diagColour = diagnosticType == DiagnosticMessage::Type::error ? Colours::red
- : Colour (200, 200, 64);
-
- g.setColour (diagColour.withAlpha (0.2f));
- g.fillRect (getLocalBounds().withTrimmedBottom (lineOffset));
-
- Path path;
- const float bottomY = getHeight() - (lineOffset / 2.0f);
- path.addTriangle ((float) arrowXMin, bottomY,
- (arrowXMax + arrowXMin) / 2.0f, (float) lineOffset,
- (float) arrowXMax, bottomY);
-
- g.setColour (diagColour.withAlpha (0.8f));
- g.fillPath (path);
- }
-
- private:
- int arrowXMin, arrowXMax;
- int lineYMin, lineOffset;
- const DiagnosticMessage::Type diagnosticType;
- };
-
- //==============================================================================
- void timerCallback() override
- {
- if (isMouseButtonDownAnywhere())
- return;
-
- MouseInputSource mouse = Desktop::getInstance().getMainMouseSource();
- Component* underMouse = mouse.getComponentUnderMouse();
-
- if (underMouse != nullptr
- && (dynamic_cast<ControlsComponent*> (underMouse) != nullptr
- || underMouse->findParentComponentOfClass<ControlsComponent>() != nullptr))
- return;
-
- overlay = nullptr;
-
- if (hasKeyboardFocus (true) && underMouse != nullptr
- && (underMouse == this || underMouse->isParentOf (this)))
- {
- Point<int> mousePos = getLocalPoint (nullptr, mouse.getScreenPosition()).toInt();
-
- CodeDocument::Position start, end;
- getDocument().findTokenContaining (getPositionAt (mousePos.x, mousePos.y), start, end);
-
- if (end.getPosition() > start.getPosition())
- {
- Range<int> selection = optimiseSelection ({ start.getPosition(), end.getPosition() });
-
- String text = getTextInRange (selection).toLowerCase();
-
- if (isIntegerLiteral (text) || isFloatLiteral (text))
- overlay = new LiteralHighlightOverlay (*this, selection, mightBeColourValue (text));
- }
- }
-
- startTimerHz (10);
- }
-
- void hideOverlay()
- {
- stopTimer();
- overlay = nullptr;
- }
-
- void focusLost (FocusChangeType) override
- {
- if (CompileEngineChildProcess::Ptr childProcess = getChildProcess())
- childProcess->flushEditorChanges();
- }
-
- void mouseMove (const MouseEvent& e) override
- {
- if (overlay == nullptr)
- startTimer (100);
-
- CppCodeEditorComponent::mouseMove (e);
- }
-
- void mouseDrag (const MouseEvent& e) override
- {
- if (e.getDistanceFromDragStart() > 0)
- hideOverlay();
-
- CppCodeEditorComponent::mouseDrag (e);
- }
-
- void mouseDown (const MouseEvent& e) override
- {
- CppCodeEditorComponent::mouseDown (e);
- }
-
- void mouseUp (const MouseEvent& e) override
- {
- CppCodeEditorComponent::mouseUp (e);
- }
-
- bool keyPressed (const KeyPress& key) override
- {
- hideOverlay();
- return CppCodeEditorComponent::keyPressed (key);
- }
-
- static bool isIntegerLiteral (const String& text) { return CppParserHelpers::parseSingleToken (text) == CPlusPlusCodeTokeniser::tokenType_integer; }
- static bool isFloatLiteral (const String& text) { return CppParserHelpers::parseSingleToken (text) == CPlusPlusCodeTokeniser::tokenType_float; }
-
- static bool mightBeColourValue (const String& text)
- {
- return isIntegerLiteral (text)
- && text.trim().startsWith ("0x")
- && text.trim().length() > 7;
- }
-
- Range<int> optimiseSelection (Range<int> selection)
- {
- String text (getTextInRange (selection));
-
- if (CharacterFunctions::isDigit (text[0]) || text[0] == '.')
- if (getTextInRange (Range<int> (selection.getStart() - 1, selection.getStart())) == "-")
- selection.setStart (selection.getStart() - 1);
-
- selection.setStart (selection.getStart() + (text.length() - text.trimStart().length()));
- selection.setEnd (selection.getEnd() - (text.length() - text.trimEnd().length()));
-
- return selection;
- }
-
- void launch (const String& name)
- {
- if (CompileEngineChildProcess::Ptr p = getChildProcess())
- if (auto* cls = p->getComponentList().globalNamespace.findClass (name))
- p->openPreview (*cls);
- }
-
- //==============================================================================
- class ControlsComponent : public Component,
- private Slider::Listener,
- private ChangeListener
- {
- public:
- ControlsComponent (CodeDocument& doc, const Range<int>& selection,
- CompileEngineChildProcess* cp, bool showColourSelector)
- : document (doc),
- start (doc, selection.getStart()),
- end (doc, selection.getEnd()),
- childProcess (cp)
- {
- slider.setTextBoxStyle (Slider::NoTextBox, true, 0, 0);
- slider.setWantsKeyboardFocus (false);
- slider.setMouseClickGrabsKeyboardFocus (false);
- setWantsKeyboardFocus (false);
- setMouseClickGrabsKeyboardFocus (false);
- addAndMakeVisible (&slider);
- updateRange();
- slider.addListener (this);
-
- if (showColourSelector)
- {
- updateColourSelector();
- selector.setWantsKeyboardFocus (false);
- selector.setMouseClickGrabsKeyboardFocus (false);
- addAndMakeVisible (&selector);
- setSize (400, sliderHeight + 400);
- selector.addChangeListener (this);
- }
- else
- {
- setSize (400, sliderHeight);
- }
-
- end.setPositionMaintained (true);
- }
-
- void updateRange()
- {
- double v = getValue();
-
- if (isFloat())
- slider.setRange (v - 10, v + 10);
- else
- slider.setRange (v - 100, v + 100);
-
- slider.setValue (v, dontSendNotification);
- }
-
- private:
- Slider slider;
- ColourSelector selector;
-
- CodeDocument& document;
- CodeDocument::Position start, end;
- CompileEngineChildProcess::Ptr childProcess;
-
- static const int sliderHeight = 26;
-
- void paint (Graphics& g) override
- {
- g.setColour (LiteralHighlightOverlay::getBackgroundColour());
- g.fillRoundedRectangle (getLocalBounds().toFloat(), 8.0f);
- }
-
- void sliderValueChanged (Slider* s) override
- {
- const String oldText (document.getTextBetween (start, end));
- const String newText (CppParserHelpers::getReplacementStringInSameFormat (oldText, s->getValue()));
-
- if (oldText != newText)
- document.replaceSection (start.getPosition(), end.getPosition(), newText);
-
- if (childProcess != nullptr)
- childProcess->flushEditorChanges();
-
- updateColourSelector();
- }
-
- void sliderDragStarted (Slider*) override {}
- void sliderDragEnded (Slider*) override { updateRange(); }
-
- void changeListenerCallback (ChangeBroadcaster*) override
- {
- setNewColour (selector.getCurrentColour());
- }
-
- void updateColourSelector()
- {
- selector.setCurrentColour (getCurrentColour());
- }
-
- Colour getCurrentColour() const
- {
- int64 val;
- if (CppParserHelpers::parseInt (document.getTextBetween (start, end), val))
- return Colour ((uint32) val);
-
- return Colours::white;
- }
-
- void setNewColour (const Colour& c)
- {
- const String oldText (document.getTextBetween (start, end));
- const String newText (CppParserHelpers::getReplacementStringInSameFormat (oldText, (int64) c.getARGB()));
-
- if (oldText != newText)
- document.replaceSection (start.getPosition(), end.getPosition(), newText);
-
- if (childProcess != nullptr)
- childProcess->flushEditorChanges();
- }
-
- void resized() override
- {
- Rectangle<int> r (getLocalBounds());
-
- slider.setBounds (r.removeFromTop (sliderHeight));
-
- r.removeFromTop (10);
-
- if (selector.isVisible())
- selector.setBounds (r);
-
- }
-
- double getValue() const
- {
- const String text (document.getTextBetween (start, end));
-
- if (text.containsChar ('.'))
- {
- double f;
- if (CppParserHelpers::parseFloat (text, f))
- return f;
- }
- else
- {
- int64 val;
- if (CppParserHelpers::parseInt (text, val))
- return (double) val;
- }
-
- jassertfalse;
- return 0;
- }
-
- bool isFloat() const
- {
- return document.getTextBetween (start, end).containsChar ('.');
- }
- };
-
- //==============================================================================
- struct LiteralHighlightOverlay : public Component,
- private CodeDocument::Listener
- {
- LiteralHighlightOverlay (LiveBuildCodeEditor& e, Range<int> section, bool showColourSelector)
- : owner (e),
- start (e.getDocument(), section.getStart()),
- end (e.getDocument(), section.getEnd()),
- controls (e.getDocument(), section, e.getChildProcess(), showColourSelector)
- {
- if (e.hasKeyboardFocus (true))
- previouslyFocused = Component::getCurrentlyFocusedComponent();
-
- start.setPositionMaintained (true);
- end.setPositionMaintained (true);
-
- setInterceptsMouseClicks (false, false);
-
- if (Component* parent = owner.findParentComponentOfClass<ProjectContentComponent>())
- parent->addAndMakeVisible (controls);
- else
- jassertfalse;
-
- owner.addAndMakeVisible (this);
- toBack();
-
- updatePosition();
-
- owner.getDocument().addListener (this);
- }
-
- ~LiteralHighlightOverlay()
- {
- if (Component* p = getParentComponent())
- {
- p->removeChildComponent (this);
-
- if (previouslyFocused != nullptr && ! previouslyFocused->hasKeyboardFocus (true))
- previouslyFocused->grabKeyboardFocus();
- }
-
- owner.getDocument().removeListener (this);
- }
-
- void paint (Graphics& g) override
- {
- g.setColour (getBackgroundColour());
-
- Rectangle<int> r (getLocalBounds());
- g.fillRect (r.removeFromTop (borderSize));
- g.fillRect (r.removeFromLeft (borderSize));
- g.fillRect (r.removeFromRight (borderSize));
- }
-
- void updatePosition()
- {
- Rectangle<int> area = owner.getCharacterBounds (start)
- .getUnion (owner.getCharacterBounds (end.movedBy (-1)))
- .expanded (borderSize)
- .withTrimmedBottom (borderSize);
-
- setBounds (getParentComponent()->getLocalArea (&owner, area));
-
- area.setPosition (area.getX() - controls.getWidth() / 2, area.getBottom());
- area.setSize (controls.getWidth(), controls.getHeight());
-
- controls.setBounds (controls.getParentComponent()->getLocalArea (&owner, area));
- }
-
- void codeDocumentTextInserted (const String&, int) override { updatePosition(); }
- void codeDocumentTextDeleted (int, int) override { updatePosition(); }
-
- LiveBuildCodeEditor& owner;
- CodeDocument::Position start, end;
- ControlsComponent controls;
- Component::SafePointer<Component> previouslyFocused;
-
- static const int borderSize = 4;
- static Colour getBackgroundColour() { return Colour (0xcb5c7879); }
- };
-
- ScopedPointer<LiteralHighlightOverlay> overlay;
- };
-
- //==============================================================================
- class LiveBuildCodeEditorDocument : public SourceCodeDocument
- {
- public:
- LiveBuildCodeEditorDocument (Project* project, const File& file)
- : SourceCodeDocument (project, file)
- {
- if (project != nullptr)
- if (CompileEngineChildProcess::Ptr childProcess = getChildProcess())
- childProcess->editorOpened (file, getCodeDocument());
- }
-
- struct Type : public SourceCodeDocument::Type
- {
- Document* openFile (Project* proj, const File& file) override
- {
- return new LiveBuildCodeEditorDocument (proj, file);
- }
- };
-
- Component* createEditor() override
- {
- SourceCodeEditor* e = nullptr;
-
- if (fileNeedsCppSyntaxHighlighting (getFile()))
- e = new SourceCodeEditor (this, new LiveBuildCodeEditor (*this, getCodeDocument()));
- else
- e = new SourceCodeEditor (this, getCodeDocument());
-
- applyLastState (*(e->editor));
- return e;
- }
-
- // override save() to make a few more attempts at saving if it fails, since on Windows
- // the compiler can interfere with things saving..
- bool save() override
- {
- for (int i = 5; --i >= 0;)
- {
- if (SourceCodeDocument::save()) // should already re-try for up to half a second
- return true;
-
- Thread::sleep (100);
- }
-
- return false;
- }
-
- CompileEngineChildProcess::Ptr getChildProcess() const
- {
- return ProjucerApplication::getApp().childProcessCache->getExisting (*project);
- }
- };
|