diff --git a/extras/Introjucer/Source/Application/jucer_AppearanceSettings.cpp b/extras/Introjucer/Source/Application/jucer_AppearanceSettings.cpp index 697b27f152..3bccffd116 100644 --- a/extras/Introjucer/Source/Application/jucer_AppearanceSettings.cpp +++ b/extras/Introjucer/Source/Application/jucer_AppearanceSettings.cpp @@ -32,6 +32,8 @@ AppearanceSettings::AppearanceSettings (const CodeEditorComponent& editor) : settings ("COLOUR_SCHEME") { getColourValue ("Background") = editor.findColour (CodeEditorComponent::backgroundColourId).toString(); + getColourValue ("Line Number Bkgd") = editor.findColour (CodeEditorComponent::lineNumberBackgroundId).toString(); + getColourValue ("Line Numbers") = editor.findColour (CodeEditorComponent::lineNumberTextId).toString(); getColourValue ("Plain Text") = editor.findColour (CodeEditorComponent::defaultTextColourId).toString(); getColourValue ("Selected Background") = editor.findColour (CodeEditorComponent::highlightColourId).toString(); @@ -48,19 +50,32 @@ AppearanceSettings::AppearanceSettings (const CodeEditorComponent& editor) getCodeFontValue() = f.toString(); } -bool AppearanceSettings::readFromFile (const File& file) +bool AppearanceSettings::readFromXML (const XmlElement& xml) { - const ScopedPointer xml (XmlDocument::parse (file)); - - if (xml != nullptr && xml->hasTagName (settings.getType().toString())) + if (xml.hasTagName (settings.getType().toString())) { - settings = ValueTree::fromXml (*xml); + ValueTree newSettings (ValueTree::fromXml (xml)); + + for (int i = settings.getNumChildren(); --i >= 0;) + { + const ValueTree c (settings.getChild (i)); + if (! newSettings.getChildWithProperty (Ids::name, c.getProperty (Ids::name)).isValid()) + newSettings.addChild (c.createCopy(), 0, nullptr); + } + + settings = newSettings; return true; } return false; } +bool AppearanceSettings::readFromFile (const File& file) +{ + const ScopedPointer xml (XmlDocument::parse (file)); + return xml != nullptr && readFromXML (*xml); +} + bool AppearanceSettings::writeToFile (const File& file) const { const ScopedPointer xml (settings.createXml()); @@ -97,6 +112,8 @@ void AppearanceSettings::applyToCodeEditor (CodeEditorComponent& editor) const Colour col; if (getColour ("Plain Text", col)) editor.setColour (CodeEditorComponent::defaultTextColourId, col); if (getColour ("Selected Background", col)) editor.setColour (CodeEditorComponent::highlightColourId, col); + if (getColour ("Line Number Bkgd", col)) editor.setColour (CodeEditorComponent::lineNumberBackgroundId, col); + if (getColour ("Line Numbers", col)) editor.setColour (CodeEditorComponent::lineNumberTextId, col); if (getColour ("Background", col)) { diff --git a/extras/Introjucer/Source/Application/jucer_AppearanceSettings.h b/extras/Introjucer/Source/Application/jucer_AppearanceSettings.h index 69b74dc9d6..5d6c1809eb 100644 --- a/extras/Introjucer/Source/Application/jucer_AppearanceSettings.h +++ b/extras/Introjucer/Source/Application/jucer_AppearanceSettings.h @@ -33,6 +33,7 @@ public: AppearanceSettings (const CodeEditorComponent& editorToCopyFrom); bool readFromFile (const File& file); + bool readFromXML (const XmlElement&); bool writeToFile (const File& file) const; void applyToCodeEditor (CodeEditorComponent& editor) const; diff --git a/extras/Introjucer/Source/Utility/jucer_StoredSettings.cpp b/extras/Introjucer/Source/Utility/jucer_StoredSettings.cpp index f8d1d1145a..022d2a841c 100644 --- a/extras/Introjucer/Source/Utility/jucer_StoredSettings.cpp +++ b/extras/Introjucer/Source/Utility/jucer_StoredSettings.cpp @@ -130,7 +130,7 @@ void StoredSettings::reload() const ScopedPointer xml (props->getXmlValue ("editorColours")); if (xml != nullptr) - appearance.settings = ValueTree::fromXml (*xml); + appearance.readFromXML (*xml); loadSwatchColours(); } diff --git a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel.cpp b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel.cpp index 5dd51315bc..662765c540 100644 --- a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel.cpp +++ b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel.cpp @@ -239,6 +239,8 @@ LookAndFeel::LookAndFeel() 0x1004500, /*CodeEditorComponent::backgroundColourId*/ 0xffffffff, 0x1004502, /*CodeEditorComponent::highlightColourId*/ textHighlightColour, 0x1004503, /*CodeEditorComponent::defaultTextColourId*/ 0xff000000, + 0x1004504, /*CodeEditorComponent::lineNumberBackgroundId*/ 0x44999999, + 0x1004505, /*CodeEditorComponent::lineNumberTextId*/ 0x44000000, 0x1007000, /*ColourSelector::backgroundColourId*/ 0xffe5e5e5, 0x1007001, /*ColourSelector::labelTextColourId*/ 0xff000000, diff --git a/modules/juce_gui_extra/code_editor/juce_CPlusPlusCodeTokeniser.cpp b/modules/juce_gui_extra/code_editor/juce_CPlusPlusCodeTokeniser.cpp index 786ad2d530..b6da3f96bf 100644 --- a/modules/juce_gui_extra/code_editor/juce_CPlusPlusCodeTokeniser.cpp +++ b/modules/juce_gui_extra/code_editor/juce_CPlusPlusCodeTokeniser.cpp @@ -333,6 +333,46 @@ namespace CppTokeniser } } + static void skipPreprocessorLine (CodeDocument::Iterator& source) noexcept + { + bool lastWasBackslash = false; + + for (;;) + { + const juce_wchar c = source.peekNextChar(); + + if (c == '"') + { + skipQuotedString (source); + continue; + } + + if (c == '/') + { + const juce_wchar c2 = source.peekNextChar(); + + if (c2 == '/' || c2 == '*') + return; + } + + if (c == 0) + break; + + if (c == '\n' || c == '\r') + { + source.skipToEndOfLine(); + + if (lastWasBackslash) + skipPreprocessorLine (source); + + break; + } + + lastWasBackslash = (c == '\\'); + source.skip(); + } + } + static void skipIfNextCharMatches (CodeDocument::Iterator& source, const juce_wchar c) noexcept { if (source.peekNextChar() == c) @@ -478,7 +518,7 @@ int CPlusPlusCodeTokeniser::readNextToken (CodeDocument::Iterator& source) case '#': result = tokenType_preprocessor; - source.skipToEndOfLine(); + skipPreprocessorLine (source); break; default: diff --git a/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp b/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp index fc4bf2f8d7..db82b5e510 100644 --- a/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp +++ b/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp @@ -226,12 +226,44 @@ private: } }; +//============================================================================== +class CodeEditorComponent::GutterComponent : public Component +{ +public: + GutterComponent() {} + + void paint (Graphics& g) + { + jassert (dynamic_cast (getParentComponent()) != nullptr); + const CodeEditorComponent& editor = *static_cast (getParentComponent()); + + g.fillAll (editor.findColour (lineNumberBackgroundId)); + + const Rectangle clip (g.getClipBounds()); + const int lineHeight = editor.lineHeight; + const int firstLineToDraw = jmax (0, clip.getY() / lineHeight); + const int lastLineToDraw = jmin (editor.lines.size(), clip.getBottom() / lineHeight + 1); + + const Font lineNumberFont (editor.getFont().withHeight (lineHeight * 0.8f)); + const float y = (lineHeight - lineNumberFont.getHeight()) / 2.0f + lineNumberFont.getAscent(); + const float w = getWidth() - 2.0f; + + GlyphArrangement ga; + for (int i = firstLineToDraw; i < lastLineToDraw; ++i) + ga.addJustifiedText (lineNumberFont, String (editor.firstLineOnScreen + i + 1), + 0.0f, y + (lineHeight * i), w, Justification::centredRight); + + g.setColour (editor.findColour (lineNumberTextId)); + ga.draw (g); + } +}; + + //============================================================================== CodeEditorComponent::CodeEditorComponent (CodeDocument& document_, CodeTokeniser* const codeTokeniser_) : document (document_), firstLineOnScreen (0), - gutter (5), spacesPerTab (4), lineHeight (0), linesOnScreen (0), @@ -239,6 +271,7 @@ CodeEditorComponent::CodeEditorComponent (CodeDocument& document_, scrollbarThickness (16), columnToTryToMaintain (-1), useSpacesForTabs (false), + showLineNumbers (false), xOffset (0), verticalScrollBar (true), horizontalScrollBar (false), @@ -272,6 +305,8 @@ CodeEditorComponent::CodeEditorComponent (CodeDocument& document_, if (codeTokeniser != nullptr) setColourScheme (codeTokeniser->getDefaultColourScheme()); + setLineNumbersShown (true); + verticalScrollBar.addListener (this); horizontalScrollBar.addListener (this); document.addListener (this); @@ -282,6 +317,11 @@ CodeEditorComponent::~CodeEditorComponent() document.removeListener (this); } +int CodeEditorComponent::getGutterSize() const noexcept +{ + return showLineNumbers ? 35 : 5; +} + void CodeEditorComponent::loadContent (const String& newContent) { clearCachedIterators (0); @@ -309,6 +349,20 @@ Rectangle CodeEditorComponent::getCaretRectangle() return getLocalArea (caret, caret->getLocalBounds()); } +void CodeEditorComponent::setLineNumbersShown (const bool shouldBeShown) +{ + if (showLineNumbers != shouldBeShown) + { + showLineNumbers = shouldBeShown; + gutter = nullptr; + + if (shouldBeShown) + addAndMakeVisible (gutter = new GutterComponent(), 0); + + resized(); + } +} + //============================================================================== void CodeEditorComponent::codeDocumentChanged (const CodeDocument::Position& affectedTextStart, const CodeDocument::Position& affectedTextEnd) @@ -339,8 +393,14 @@ void CodeEditorComponent::resized() rebuildLineTokens(); updateCaretPosition(); - verticalScrollBar.setBounds (getWidth() - scrollbarThickness, 0, scrollbarThickness, getHeight() - scrollbarThickness); - horizontalScrollBar.setBounds (gutter, getHeight() - scrollbarThickness, getWidth() - scrollbarThickness - gutter, scrollbarThickness); + if (gutter != nullptr) + gutter->setBounds (0, 0, getGutterSize() - 2, getHeight()); + + verticalScrollBar.setBounds (getWidth() - scrollbarThickness, 0, + scrollbarThickness, getHeight() - scrollbarThickness); + + horizontalScrollBar.setBounds (getGutterSize(), getHeight() - scrollbarThickness, + getWidth() - scrollbarThickness - getGutterSize(), scrollbarThickness); updateScrollBars(); } @@ -350,24 +410,22 @@ void CodeEditorComponent::paint (Graphics& g) g.fillAll (findColour (CodeEditorComponent::backgroundColourId)); + const int gutter = getGutterSize(); g.reduceClipRegion (gutter, 0, verticalScrollBar.getX() - gutter, horizontalScrollBar.getY()); g.setFont (font); const int baselineOffset = (int) font.getAscent(); - const Colour defaultColour (findColour (CodeEditorComponent::defaultTextColourId)); const Colour highlightColour (findColour (CodeEditorComponent::highlightColourId)); const Rectangle clip (g.getClipBounds()); const int firstLineToDraw = jmax (0, clip.getY() / lineHeight); const int lastLineToDraw = jmin (lines.size(), clip.getBottom() / lineHeight + 1); - for (int j = firstLineToDraw; j < lastLineToDraw; ++j) - { - lines.getUnchecked(j)->draw (*this, g, font, + for (int i = firstLineToDraw; i < lastLineToDraw; ++i) + lines.getUnchecked(i)->draw (*this, g, font, (float) (gutter - xOffset * charWidth), - lineHeight * j, baselineOffset, lineHeight, + lineHeight * i, baselineOffset, lineHeight, highlightColour); - } } void CodeEditorComponent::setScrollbarThickness (const int thickness) @@ -422,11 +480,8 @@ void CodeEditorComponent::rebuildLineTokens() } if (minLineToRepaint <= maxLineToRepaint) - { - repaint (gutter, lineHeight * minLineToRepaint - 1, - verticalScrollBar.getX() - gutter, - lineHeight * (1 + maxLineToRepaint - minLineToRepaint) + 2); - } + repaint (0, lineHeight * minLineToRepaint - 1, + verticalScrollBar.getX(), lineHeight * (1 + maxLineToRepaint - minLineToRepaint) + 2); } //============================================================================== @@ -568,7 +623,7 @@ void CodeEditorComponent::scrollToKeepCaretOnScreen() Rectangle CodeEditorComponent::getCharacterBounds (const CodeDocument::Position& pos) const { - return Rectangle (roundToInt ((gutter - xOffset * charWidth) + indexToColumn (pos.getLineNumber(), pos.getIndexInLine()) * charWidth), + return Rectangle (roundToInt ((getGutterSize() - xOffset * charWidth) + indexToColumn (pos.getLineNumber(), pos.getIndexInLine()) * charWidth), (pos.getLineNumber() - firstLineOnScreen) * lineHeight, roundToInt (charWidth), lineHeight); @@ -577,7 +632,7 @@ Rectangle CodeEditorComponent::getCharacterBounds (const CodeDocument::Posi CodeDocument::Position CodeEditorComponent::getPositionAt (int x, int y) { const int line = y / lineHeight + firstLineOnScreen; - const int column = roundToInt ((x - (gutter - xOffset * charWidth)) / charWidth); + const int column = roundToInt ((x - (getGutterSize() - xOffset * charWidth)) / charWidth); const int index = columnToIndex (line, column); return CodeDocument::Position (&document, line, index); diff --git a/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.h b/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.h index d96b5fd02d..c9e2a3f1ae 100644 --- a/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.h +++ b/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.h @@ -110,6 +110,9 @@ public: */ CodeDocument::Position getPositionAt (int x, int y); + /** Enables or disables the line-number display in the gutter. */ + void setLineNumbersShown (bool shouldBeShown); + //============================================================================== bool moveCaretLeft (bool moveInWholeWordSteps, bool selecting); bool moveCaretRight (bool moveInWholeWordSteps, bool selecting); @@ -213,10 +216,10 @@ public: enum ColourIds { backgroundColourId = 0x1004500, /**< A colour to use to fill the editor's background. */ - highlightColourId = 0x1004502, /**< The colour to use for the highlighted background under - selected text. */ - defaultTextColourId = 0x1004503 /**< The colour to use for text when no syntax colouring is - enabled. */ + highlightColourId = 0x1004502, /**< The colour to use for the highlighted background under selected text. */ + defaultTextColourId = 0x1004503, /**< The colour to use for text when no syntax colouring is enabled. */ + lineNumberBackgroundId = 0x1004504, /**< The colour to use for filling the background of the line-number gutter. */ + lineNumberTextId = 0x1004505, /**< The colour to use for drawing the line numbers. */ }; //============================================================================== @@ -250,12 +253,11 @@ public: /** @internal */ void timerCallback(); /** @internal */ - void scrollBarMoved (ScrollBar*, double newRangeStart); + void scrollBarMoved (ScrollBar*, double); /** @internal */ void handleAsyncUpdate(); /** @internal */ - void codeDocumentChanged (const CodeDocument::Position& affectedTextStart, - const CodeDocument::Position& affectedTextEnd); + void codeDocumentChanged (const CodeDocument::Position&, const CodeDocument::Position&); /** @internal */ bool isTextInputActive() const; /** @internal */ @@ -266,11 +268,11 @@ private: CodeDocument& document; Font font; - int firstLineOnScreen, gutter, spacesPerTab; + int firstLineOnScreen, spacesPerTab; float charWidth; int lineHeight, linesOnScreen, columnsOnScreen; int scrollbarThickness, columnToTryToMaintain; - bool useSpacesForTabs; + bool useSpacesForTabs, showLineNumbers; double xOffset; CodeDocument::Position caretPos; @@ -279,6 +281,11 @@ private: ScopedPointer caret; ScrollBar verticalScrollBar, horizontalScrollBar; + class GutterComponent; + friend class GutterComponent; + friend class ScopedPointer; + ScopedPointer gutter; + enum DragType { notDragging, @@ -301,6 +308,7 @@ private: void updateCachedIterators (int maxLineNum); void getIteratorForPosition (int position, CodeDocument::Iterator& result); void moveLineDelta (int delta, bool selecting); + int getGutterSize() const noexcept; //============================================================================== void updateCaretPosition();