1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-20 01:14:20 +00:00

Added line-numbering to the CodeEditorComponent.

This commit is contained in:
jules 2012-07-04 13:11:25 +01:00
parent f824e99f2d
commit d3bb7fb9cc
7 changed files with 155 additions and 32 deletions

View file

@ -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<XmlElement> 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<XmlElement> xml (XmlDocument::parse (file));
return xml != nullptr && readFromXML (*xml);
}
bool AppearanceSettings::writeToFile (const File& file) const
{
const ScopedPointer<XmlElement> 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))
{

View file

@ -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;

View file

@ -130,7 +130,7 @@ void StoredSettings::reload()
const ScopedPointer<XmlElement> xml (props->getXmlValue ("editorColours"));
if (xml != nullptr)
appearance.settings = ValueTree::fromXml (*xml);
appearance.readFromXML (*xml);
loadSwatchColours();
}

View file

@ -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,

View file

@ -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:

View file

@ -226,12 +226,44 @@ private:
}
};
//==============================================================================
class CodeEditorComponent::GutterComponent : public Component
{
public:
GutterComponent() {}
void paint (Graphics& g)
{
jassert (dynamic_cast <CodeEditorComponent*> (getParentComponent()) != nullptr);
const CodeEditorComponent& editor = *static_cast <CodeEditorComponent*> (getParentComponent());
g.fillAll (editor.findColour (lineNumberBackgroundId));
const Rectangle<int> 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<int> 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<int> 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<int> CodeEditorComponent::getCharacterBounds (const CodeDocument::Position& pos) const
{
return Rectangle<int> (roundToInt ((gutter - xOffset * charWidth) + indexToColumn (pos.getLineNumber(), pos.getIndexInLine()) * charWidth),
return Rectangle<int> (roundToInt ((getGutterSize() - xOffset * charWidth) + indexToColumn (pos.getLineNumber(), pos.getIndexInLine()) * charWidth),
(pos.getLineNumber() - firstLineOnScreen) * lineHeight,
roundToInt (charWidth),
lineHeight);
@ -577,7 +632,7 @@ Rectangle<int> 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);

View file

@ -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<CaretComponent> caret;
ScrollBar verticalScrollBar, horizontalScrollBar;
class GutterComponent;
friend class GutterComponent;
friend class ScopedPointer<GutterComponent>;
ScopedPointer<GutterComponent> 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();