From 0b2f0f086cdb661fa0ccc81f76dd018968de651e Mon Sep 17 00:00:00 2001 From: Julian Storer Date: Wed, 4 Nov 2009 13:23:42 +0000 Subject: [PATCH] added a code-editor demo page to the juce demo and added some comments to the code editor classes --- .../macosx/jucedemo.xcodeproj/project.pbxproj | 4 + .../juce demo/build/win32_vc8/jucedemo.vcproj | 4 + extras/juce demo/src/MainDemoWindow.cpp | 14 + extras/juce demo/src/demos/CodeEditorDemo.cpp | 96 +++++ extras/juce demo/src/jucedemo_headers.h | 1 + juce_amalgamated.cpp | 116 +++--- juce_amalgamated.h | 390 +++++++++++++----- .../code_editor/juce_CodeDocument.cpp | 34 +- .../code_editor/juce_CodeDocument.h | 222 +++++++--- .../code_editor/juce_CodeEditorComponent.cpp | 82 ++-- .../code_editor/juce_CodeEditorComponent.h | 167 ++++++-- src/text/juce_String.h | 4 +- 12 files changed, 823 insertions(+), 311 deletions(-) create mode 100644 extras/juce demo/src/demos/CodeEditorDemo.cpp diff --git a/extras/juce demo/build/macosx/jucedemo.xcodeproj/project.pbxproj b/extras/juce demo/build/macosx/jucedemo.xcodeproj/project.pbxproj index 3ae37fcd8c..92e5467b2a 100644 --- a/extras/juce demo/build/macosx/jucedemo.xcodeproj/project.pbxproj +++ b/extras/juce demo/build/macosx/jucedemo.xcodeproj/project.pbxproj @@ -36,6 +36,7 @@ 849144091064E54800456AFC /* AudioDemoRecordPage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 849144071064E54800456AFC /* AudioDemoRecordPage.cpp */; }; 849786F3103560630020003B /* DiscRecording.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 849786F2103560630020003B /* DiscRecording.framework */; }; 84E81551100BAF6200FAE212 /* WebBrowserDemo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 84E81550100BAF6200FAE212 /* WebBrowserDemo.cpp */; }; + 84EDCA1F10A19E730079DB17 /* CodeEditorDemo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 84EDCA1E10A19E730079DB17 /* CodeEditorDemo.cpp */; }; 84EE00FA0FF22E390093FACA /* CameraDemo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 84EE00F90FF22E390093FACA /* CameraDemo.cpp */; }; 84EE01200FF23BBE0093FACA /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84EE011F0FF23BBE0093FACA /* QuartzCore.framework */; }; 84FFCF3B0EDAFE7F007D5302 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84FFCF3A0EDAFE7F007D5302 /* Carbon.framework */; }; @@ -83,6 +84,7 @@ 849144081064E54800456AFC /* AudioDemoRecordPage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AudioDemoRecordPage.h; path = ../../src/demos/AudioDemoRecordPage.h; sourceTree = SOURCE_ROOT; }; 849786F2103560630020003B /* DiscRecording.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DiscRecording.framework; path = System/Library/Frameworks/DiscRecording.framework; sourceTree = SDKROOT; }; 84E81550100BAF6200FAE212 /* WebBrowserDemo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 2; name = WebBrowserDemo.cpp; path = ../../src/demos/WebBrowserDemo.cpp; sourceTree = SOURCE_ROOT; }; + 84EDCA1E10A19E730079DB17 /* CodeEditorDemo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CodeEditorDemo.cpp; path = ../../src/demos/CodeEditorDemo.cpp; sourceTree = SOURCE_ROOT; }; 84EE00F90FF22E390093FACA /* CameraDemo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CameraDemo.cpp; path = ../../src/demos/CameraDemo.cpp; sourceTree = SOURCE_ROOT; }; 84EE011F0FF23BBE0093FACA /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 84FFCF3A0EDAFE7F007D5302 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = /System/Library/Frameworks/Carbon.framework; sourceTree = ""; }; @@ -189,6 +191,7 @@ 84913FDC1063947900456AFC /* AudioDemoTabComponent.cpp */, 84913FDD1063947900456AFC /* AudioDemoTabComponent.h */, 84EE00F90FF22E390093FACA /* CameraDemo.cpp */, + 84EDCA1E10A19E730079DB17 /* CodeEditorDemo.cpp */, 847F4E9B0E8BA9C300F64426 /* DragAndDropDemo.cpp */, 847F4E9C0E8BA9C300F64426 /* FontsAndTextDemo.cpp */, 847F4E9D0E8BA9C300F64426 /* InterprocessCommsDemo.cpp */, @@ -279,6 +282,7 @@ 84913FE11063947900456AFC /* AudioDemoSynthPage.cpp in Sources */, 84913FE21063947900456AFC /* AudioDemoTabComponent.cpp in Sources */, 849144091064E54800456AFC /* AudioDemoRecordPage.cpp in Sources */, + 84EDCA1F10A19E730079DB17 /* CodeEditorDemo.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/extras/juce demo/build/win32_vc8/jucedemo.vcproj b/extras/juce demo/build/win32_vc8/jucedemo.vcproj index d42d5800bd..8c0649694b 100644 --- a/extras/juce demo/build/win32_vc8/jucedemo.vcproj +++ b/extras/juce demo/build/win32_vc8/jucedemo.vcproj @@ -354,6 +354,10 @@ RelativePath="..\..\src\demos\CameraDemo.cpp" > + + diff --git a/extras/juce demo/src/MainDemoWindow.cpp b/extras/juce demo/src/MainDemoWindow.cpp index bff296e166..6c19a2f746 100644 --- a/extras/juce demo/src/MainDemoWindow.cpp +++ b/extras/juce demo/src/MainDemoWindow.cpp @@ -59,6 +59,7 @@ class ContentComp : public Component, showTable = 0x2010, showCamera = 0x2011, showWebBrowser = 0x2012, + showCodeEditor = 0x2013, setDefaultLookAndFeel = 0x200b, setOldSchoolLookAndFeel = 0x200c, @@ -133,6 +134,7 @@ public: menu.addCommandItem (commandManager, showInterprocessComms); menu.addCommandItem (commandManager, showCamera); menu.addCommandItem (commandManager, showWebBrowser); + menu.addCommandItem (commandManager, showCodeEditor); menu.addSeparator(); menu.addCommandItem (commandManager, StandardApplicationCommandIDs::quit); @@ -190,6 +192,7 @@ public: showQuicktime, showCamera, showWebBrowser, + showCodeEditor, showInterprocessComms, setDefaultLookAndFeel, setOldSchoolLookAndFeel, @@ -299,6 +302,12 @@ public: #endif break; + case showCodeEditor: + result.setInfo (T("Code Editor"), T("Shows the code editor demo"), demosCategory, 0); + result.addDefaultKeypress (T('e'), ModifierKeys::commandModifier); + result.setTicked (currentDemoId == showCodeEditor); + break; + case showInterprocessComms: result.setInfo (T("Interprocess Comms"), T("Shows the interprocess communications demo"), demosCategory, 0); result.addDefaultKeypress (T('0'), ModifierKeys::commandModifier); @@ -412,6 +421,11 @@ public: #endif break; + case showCodeEditor: + showDemo (createCodeEditorDemo()); + currentDemoId = showCodeEditor; + break; + case showInterprocessComms: showDemo (createInterprocessCommsDemo()); currentDemoId = showInterprocessComms; diff --git a/extras/juce demo/src/demos/CodeEditorDemo.cpp b/extras/juce demo/src/demos/CodeEditorDemo.cpp new file mode 100644 index 0000000000..9ae64b3a58 --- /dev/null +++ b/extras/juce demo/src/demos/CodeEditorDemo.cpp @@ -0,0 +1,96 @@ +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-9 by Raw Material Software Ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the GNU General + Public License (Version 2), as published by the Free Software Foundation. + A copy of the license is included in the JUCE distribution, or can be found + online at www.gnu.org/licenses. + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.rawmaterialsoftware.com/juce for more information. + + ============================================================================== +*/ + +#include "../jucedemo_headers.h" + + +//============================================================================== +class CodeEditorDemo : public Component, + public FilenameComponentListener +{ +public: + //============================================================================== + CodeEditorDemo() + { + setName ("Code Editor"); + setOpaque (true); + + // Create the editor.. + addAndMakeVisible (editor = new CodeEditorComponent (codeDocument, &cppTokeniser)); + + // Create a file chooser control to load files into it.. + addAndMakeVisible (fileChooser = new FilenameComponent ("File", File::nonexistent, true, false, false, + "*.cpp;*.h;*.hpp;*.c;*.mm;*.m", String::empty, + "Choose a C++ file to open it in the editor")); + fileChooser->addListener (this); + + + editor->loadContent ("\n\n/* Code editor demo! Please be gentle, this component is still an alpha version! */\n\n"); + } + + ~CodeEditorDemo() + { + deleteAllChildren(); + } + + void filenameComponentChanged (FilenameComponent* fileComponentThatHasChanged) + { + File f (fileChooser->getCurrentFile()); + editor->loadContent (f.loadFileAsString()); + } + + void paint (Graphics& g) + { + g.fillAll (Colours::lightgrey); + } + + void resized() + { + editor->setBounds (10, 45, getWidth() - 20, getHeight() - 55); + fileChooser->setBounds (10, 10, getWidth() - 20, 25); + } + + //============================================================================== + juce_UseDebuggingNewOperator + +private: + // this is the document that the editor component is showing + CodeDocument codeDocument; + + // this is a tokeniser to do the c++ syntax highlighting + CPlusPlusCodeTokeniser cppTokeniser; + + // the editor component + CodeEditorComponent* editor; + + FilenameComponent* fileChooser; +}; + + +//============================================================================== +Component* createCodeEditorDemo() +{ + return new CodeEditorDemo(); +} diff --git a/extras/juce demo/src/jucedemo_headers.h b/extras/juce demo/src/jucedemo_headers.h index d5a736883d..890af0ab51 100644 --- a/extras/juce demo/src/jucedemo_headers.h +++ b/extras/juce demo/src/jucedemo_headers.h @@ -50,6 +50,7 @@ Component* createTableDemo(); Component* createAudioDemo(); Component* createDragAndDropDemo(); Component* createInterprocessCommsDemo(); +Component* createCodeEditorDemo(); #if JUCE_QUICKTIME && ! JUCE_LINUX Component* createQuickTimeDemo(); diff --git a/juce_amalgamated.cpp b/juce_amalgamated.cpp index cd3b7db963..2dbf719e40 100644 --- a/juce_amalgamated.cpp +++ b/juce_amalgamated.cpp @@ -43627,18 +43627,14 @@ void CodeDocument::Position::setPosition (const int newPosition) throw() } } -void CodeDocument::Position::updateLineAndIndexFromPosition() throw() -{ - setPosition (getPosition()); -} - void CodeDocument::Position::moveBy (int characterDelta) throw() { jassert (owner != 0); - updateLineAndIndexFromPosition(); if (characterDelta == 1) { + setPosition (getPosition()); + // If moving right, make sure we don't get stuck between the \r and \n characters.. CodeDocumentLine* const l = owner->lines.getUnchecked (line); if (indexInLine + characterDelta < l->lineLength @@ -43701,7 +43697,8 @@ CodeDocument::CodeDocument() : undoManager (INT_MAX, 10000), currentActionIndex (0), indexOfSavedState (-1), - maximumLineLength (-1) + maximumLineLength (-1), + newLineChars ("\r\n") { } @@ -43807,6 +43804,12 @@ void CodeDocument::replaceAllContent (const String& newContent) insert (newContent, 0, true); } +void CodeDocument::setNewLineCharacters (const String& newLine) throw() +{ + jassert (newLine == T("\r\n") || newLine == T("\n") || newLine == T("\r")); + newLineChars = newLine; +} + void CodeDocument::newTransaction() { undoManager.beginNewTransaction (String::empty); @@ -43823,7 +43826,7 @@ void CodeDocument::redo() undoManager.redo(); } -void CodeDocument::clearUndoManager() +void CodeDocument::clearUndoHistory() { undoManager.clearUndoHistory(); } @@ -43922,12 +43925,12 @@ const CodeDocument::Position CodeDocument::findWordBreakBefore (const Position& return p; } -void CodeDocument::addListener (CodeDocument::Listener* const listener) +void CodeDocument::addListener (CodeDocument::Listener* const listener) throw() { listeners.addIfNotAlreadyThere (listener); } -void CodeDocument::removeListener (CodeDocument::Listener* const listener) +void CodeDocument::removeListener (CodeDocument::Listener* const listener) throw() { listeners.removeValue (listener); } @@ -44028,11 +44031,12 @@ void CodeDocument::insert (const String& text, const int insertPos, const bool u lastAffectedLine = lines.size(); } - for (int i = firstAffectedLine + 1; i < lines.size(); ++i) + int lineStart = newFirstLine->lineStartInFile; + for (int i = firstAffectedLine; i < lines.size(); ++i) { CodeDocumentLine* const l = lines.getUnchecked (i); - const CodeDocumentLine* const previousLine = lines.getUnchecked (i - 1); - l->lineStartInFile = previousLine->lineStartInFile + previousLine->lineLength; + l->lineStartInFile = lineStart; + lineStart += l->lineLength; } const int newTextLength = text.length(); @@ -44041,7 +44045,7 @@ void CodeDocument::insert (const String& text, const int insertPos, const bool u CodeDocument::Position* const p = positionsToMaintain.getUnchecked(i); if (p->getPosition() >= insertPos) - p->moveBy (newTextLength); + p->setPosition (p->getPosition() + newTextLength); } sendListenerChangeMessage (firstAffectedLine, lastAffectedLine); @@ -44141,7 +44145,7 @@ void CodeDocument::remove (const int startPos, const int endPos, const bool undo CodeDocument::Position* p = positionsToMaintain.getUnchecked(i); if (p->getPosition() > startPosition.getPosition()) - p->setPosition (jmax (startPos, startPos - endPos)); + p->setPosition (jmax (startPos, p->getPosition() + startPos - endPos)); if (p->getPosition() > totalChars) p->setPosition (totalChars); @@ -44411,6 +44415,7 @@ CodeEditorComponent::CodeEditorComponent (CodeDocument& document_, gutter (5), spacesPerTab (4), lineHeight (0), + firstLineOnScreen (0), linesOnScreen (0), columnsOnScreen (0), scrollbarThickness (16), @@ -44442,7 +44447,7 @@ CodeEditorComponent::CodeEditorComponent (CodeDocument& document_, f.setTypefaceName (Font::getDefaultMonospacedFontName()); setFont (f); - setDefaultColours(); + resetToDefaultColours(); verticalScrollBar->addListener (this); horizontalScrollBar->addListener (this); @@ -44459,7 +44464,7 @@ void CodeEditorComponent::loadContent (const String& newContent) { clearCachedIterators (0); document.replaceAllContent (newContent); - document.clearUndoManager(); + document.clearUndoHistory(); document.setSavePoint(); caretPos.setPosition (0); selectionStart.setPosition (0); @@ -44472,7 +44477,8 @@ void CodeEditorComponent::codeDocumentChanged (const CodeDocument::Position& aff { clearCachedIterators (affectedTextStart.getLineNumber()); - rebuildLineTokens(); + triggerAsyncUpdate(); + ((CaretComponent*) caret)->updatePosition (*this); if (affectedTextEnd.getPosition() >= selectionStart.getPosition() @@ -44495,12 +44501,14 @@ void CodeEditorComponent::resized() ((CaretComponent*) caret)->updatePosition (*this); verticalScrollBar->setBounds (getWidth() - scrollbarThickness, 0, scrollbarThickness, getHeight() - scrollbarThickness); - horizontalScrollBar->setBounds (0, getHeight() - scrollbarThickness, getWidth() - scrollbarThickness, scrollbarThickness); + horizontalScrollBar->setBounds (gutter, getHeight() - scrollbarThickness, getWidth() - scrollbarThickness - gutter, scrollbarThickness); updateScrollBars(); } void CodeEditorComponent::paint (Graphics& g) { + handleUpdateNowIfNeeded(); + g.fillAll (findColour (CodeEditorComponent::backgroundColourId)); g.reduceClipRegion (gutter, 0, verticalScrollBar->getX() - gutter, horizontalScrollBar->getY()); @@ -44518,8 +44526,14 @@ void CodeEditorComponent::paint (Graphics& g) highlightColour, charWidth); } +void CodeEditorComponent::handleAsyncUpdate() +{ + rebuildLineTokens(); +} + void CodeEditorComponent::rebuildLineTokens() { + cancelPendingUpdate(); const int numNeeded = linesOnScreen + 1; if (numNeeded != lines.size()) @@ -44588,7 +44602,7 @@ void CodeEditorComponent::moveCaretTo (const CodeDocument::Position& newPos, con } } - rebuildLineTokens(); + triggerAsyncUpdate(); } else { @@ -44602,11 +44616,11 @@ void CodeEditorComponent::moveCaretTo (const CodeDocument::Position& newPos, con void CodeEditorComponent::deselectAll() { - if (selectionStart != caretPos || selectionEnd != caretPos) - { - selectionStart = selectionEnd = caretPos; - rebuildLineTokens(); - } + if (selectionStart != selectionEnd) + triggerAsyncUpdate(); + + selectionStart = caretPos; + selectionEnd = caretPos; } void CodeEditorComponent::updateScrollBars() @@ -44620,13 +44634,17 @@ void CodeEditorComponent::updateScrollBars() void CodeEditorComponent::scrollToLineInternal (int newFirstLineOnScreen) { - firstLineOnScreen = jlimit (0, jmax (0, document.getNumLines() - 1), - newFirstLineOnScreen); + newFirstLineOnScreen = jlimit (0, jmax (0, document.getNumLines() - 1), + newFirstLineOnScreen); - ((CaretComponent*) caret)->updatePosition (*this); + if (newFirstLineOnScreen != firstLineOnScreen) + { + firstLineOnScreen = newFirstLineOnScreen; + ((CaretComponent*) caret)->updatePosition (*this); - updateCachedIterators (firstLineOnScreen); - rebuildLineTokens(); + updateCachedIterators (firstLineOnScreen); + triggerAsyncUpdate(); + } } void CodeEditorComponent::scrollToColumnInternal (double column) @@ -44741,7 +44759,7 @@ void CodeEditorComponent::copyThenCut() void CodeEditorComponent::paste() { newTransaction(); - const String clip (SystemClipboard::getTextFromClipboard()/*.replace (T("\r\n"), T("\n"))*/); + const String clip (SystemClipboard::getTextFromClipboard()); if (clip.isNotEmpty()) insertTextAtCaret (clip); @@ -44823,7 +44841,7 @@ void CodeEditorComponent::scrollDown() moveCaretTo (caretPos.movedByLines (-1), false); } -void CodeEditorComponent::goToStart (const bool selecting) +void CodeEditorComponent::goToStartOfDocument (const bool selecting) { newTransaction(); moveCaretTo (CodeDocument::Position (&document, 0, 0), selecting); @@ -44852,7 +44870,7 @@ void CodeEditorComponent::goToStartOfLine (const bool selecting) moveCaretTo (CodeDocument::Position (&document, caretPos.getLineNumber(), index), selecting); } -void CodeEditorComponent::goToEnd (const bool selecting) +void CodeEditorComponent::goToEndOfDocument (const bool selecting) { newTransaction(); moveCaretTo (CodeDocument::Position (&document, INT_MAX, INT_MAX), selecting); @@ -44947,7 +44965,7 @@ bool CodeEditorComponent::keyPressed (const KeyPress& key) scrollDown(); #if JUCE_MAC else if (key.getModifiers().isCommandDown()) - goToStart (shiftDown); + goToStartOfDocument (shiftDown); #endif else cursorUp (shiftDown); @@ -44958,7 +44976,7 @@ bool CodeEditorComponent::keyPressed (const KeyPress& key) scrollUp(); #if JUCE_MAC else if (key.getModifiers().isCommandDown()) - goToEnd (shiftDown); + goToEndOfDocument (shiftDown); #endif else cursorDown (shiftDown); @@ -44974,14 +44992,14 @@ bool CodeEditorComponent::keyPressed (const KeyPress& key) else if (key.isKeyCode (KeyPress::homeKey)) { if (moveInWholeWordSteps) - goToStart (shiftDown); + goToStartOfDocument (shiftDown); else goToStartOfLine (shiftDown); } else if (key.isKeyCode (KeyPress::endKey)) { if (moveInWholeWordSteps) - goToEnd (shiftDown); + goToEndOfDocument (shiftDown); else goToEndOfLine (shiftDown); } @@ -45025,7 +45043,7 @@ bool CodeEditorComponent::keyPressed (const KeyPress& key) else if (key == KeyPress::returnKey) { newTransaction(); - insertTextAtCaret (T("\r\n")); + insertTextAtCaret (document.getNewLineCharacters()); } else if (key.isKeyCode (KeyPress::escapeKey)) { @@ -45119,14 +45137,14 @@ void CodeEditorComponent::scrollBarMoved (ScrollBar* scrollBarThatHasMoved, cons scrollToColumnInternal (newRangeStart); } -void CodeEditorComponent::setTabSize (const int numSpaces, const bool insertSpaces) +void CodeEditorComponent::setTabSize (const int numSpaces, const bool insertSpaces) throw() { useSpacesForTabs = insertSpaces; if (spacesPerTab != numSpaces) { spacesPerTab = numSpaces; - rebuildLineTokens(); + triggerAsyncUpdate(); } } @@ -45175,34 +45193,34 @@ void CodeEditorComponent::setFont (const Font& newFont) resized(); } -void CodeEditorComponent::setDefaultColours() +void CodeEditorComponent::resetToDefaultColours() { coloursForTokenCategories.clear(); if (codeTokeniser != 0) { for (int i = codeTokeniser->getTokenTypes().size(); --i >= 0;) - setColourForTokenCategory (i, codeTokeniser->getDefaultColour (i)); + setColourForTokenType (i, codeTokeniser->getDefaultColour (i)); } } -void CodeEditorComponent::setColourForTokenCategory (const int tokenCategory, const Colour& colour) +void CodeEditorComponent::setColourForTokenType (const int tokenType, const Colour& colour) { - jassert (tokenCategory < 256); + jassert (tokenType < 256); - while (coloursForTokenCategories.size() < tokenCategory) + while (coloursForTokenCategories.size() < tokenType) coloursForTokenCategories.add (Colours::black); - coloursForTokenCategories.set (tokenCategory, colour); + coloursForTokenCategories.set (tokenType, colour); repaint(); } -const Colour CodeEditorComponent::getColourForTokenCategory (const int tokenCategory) const +const Colour CodeEditorComponent::getColourForTokenType (const int tokenType) const throw() { - if (((unsigned int) tokenCategory) >= (unsigned int) coloursForTokenCategories.size()) + if (((unsigned int) tokenType) >= (unsigned int) coloursForTokenCategories.size()) return findColour (CodeEditorComponent::defaultTextColourId); - return coloursForTokenCategories.getReference (tokenCategory); + return coloursForTokenCategories.getReference (tokenType); } void CodeEditorComponent::clearCachedIterators (const int firstLineToBeInvalid) throw() diff --git a/juce_amalgamated.h b/juce_amalgamated.h index 4b90562cfc..2e8d898b95 100644 --- a/juce_amalgamated.h +++ b/juce_amalgamated.h @@ -2251,14 +2251,14 @@ public: explicit String (const double doubleValue, const int numberOfDecimalPlaces = 0) throw(); - /** Parses this string to find its numerical value (up to 32 bits in size). + /** Reads the value of the string as a decimal number (up to 32 bits in size). @returns the value of the string as a 32 bit signed base-10 integer. @see getTrailingIntValue, getHexValue32, getHexValue64 */ int getIntValue() const throw(); - /** Parses this string to find its numerical value (up to 64 bits in size). + /** Reads the value of the string as a decimal number (up to 64 bits in size). @returns the value of the string as a 64 bit signed base-10 integer. */ @@ -44462,12 +44462,11 @@ class CodeDocumentLine; class JUCE_API CodeDocument { public: - /** + /** Creates a new, empty document. */ CodeDocument(); - /** - */ + /** Destructor. */ ~CodeDocument(); /** A position in a code document. @@ -44480,61 +44479,118 @@ public: class JUCE_API Position { public: + /** Creates an uninitialised postion. + Don't attempt to call any methods on this until you've given it an owner document + to refer to! + */ Position() throw(); - Position (const CodeDocument* const ownerDocument, const int line, const int indexInLine) throw(); - Position (const CodeDocument* const ownerDocument, const int characterPos) throw(); + + /** Creates a position based on a line and index in a document. + + Note that this index is NOT the column number, it's the number of characters from the + start of the line. The "column" number isn't quite the same, because if the line + contains any tab characters, the relationship of the index to its visual column depends on + the number of spaces per tab being used! + + Lines are numbered from zero, and if the line or index are beyond the bounds of the document, + they will be adjusted to keep them within its limits. + */ + Position (const CodeDocument* const ownerDocument, + const int line, const int indexInLine) throw(); + + /** Creates a position based on a character index in a document. + This position is placed at the specified number of characters from the start of the + document. The line and column are auto-calculated. + + If the position is beyond the range of the document, it'll be adjusted to keep it + inside. + */ + Position (const CodeDocument* const ownerDocument, + const int charactersFromStartOfDocument) throw(); + + /** Creates a copy of another position. + + This will copy the position, but the new object will not be set to maintain its position, + even if the source object was set to do so. + */ Position (const Position& other) throw(); + + /** Destructor. */ ~Position() throw(); const Position& operator= (const Position& other) throw(); bool operator== (const Position& other) const throw(); bool operator!= (const Position& other) const throw(); - /** - */ - void setLineAndIndex (const int newLine, const int newIndexInLine) throw(); + /** Points this object at a new position within the document. - /** + If the position is beyond the range of the document, it'll be adjusted to keep it + inside. + @see getPosition, setLineAndIndex */ - void setPosition (const int newPosition) throw(); + void setPosition (const int charactersFromStartOfDocument) throw(); - /** + /** Returns the position as the number of characters from the start of the document. + @see setPosition, getLineNumber, getIndexInLine */ int getPosition() const throw() { return characterPos; } - /** + /** Moves the position to a new line and index within the line. + + Note that the index is NOT the column at which the position appears in an editor. + If the line contains any tab characters, the relationship of the index to its + visual position depends on the number of spaces per tab being used! + + Lines are numbered from zero, and if the line or index are beyond the bounds of the document, + they will be adjusted to keep them within its limits. + */ + void setLineAndIndex (const int newLine, const int newIndexInLine) throw(); + + /** Returns the line number of this position. + The first line in the document is numbered zero, not one! */ int getLineNumber() const throw() { return line; } - /** + /** Returns the number of characters from the start of the line. + + Note that this value is NOT the column at which the position appears in an editor. + If the line contains any tab characters, the relationship of the index to its + visual position depends on the number of spaces per tab being used! */ int getIndexInLine() const throw() { return indexInLine; } - /** - */ - void updateLineAndIndexFromPosition() throw(); + /** Allows the position to be automatically updated when the document changes. - /** + If this is set to true, the positon will register with its document so that + when the document has text inserted or deleted, this position will be automatically + moved to keep it at the same position in the text. */ void setPositionMaintained (const bool isMaintained) throw(); - /** + /** Moves the position forwards or backwards by the specified number of characters. + @see movedBy */ void moveBy (int characterDelta) throw(); - /** + /** Returns a position which is the same as this one, moved by the specified number of + characters. + @see moveBy */ const Position movedBy (const int characterDelta) const throw(); - /** + /** Returns a position which is the same as this one, moved up or down by the specified + number of lines. + @see movedBy */ const Position movedByLines (const int deltaLines) const throw(); - /** + /** Returns the character in the document at this position. + @see getLineText */ const tchar getCharacter() const throw(); - /** + /** Returns the line from the document that this position is within. + @see getCharacter, getLineNumber */ const String getLineText() const throw(); @@ -44544,72 +44600,103 @@ public: bool positionMaintained; }; - /** - */ + /** Returns the full text of the document. */ const String getAllContent() const throw(); - /** - */ + /** Returns a section of the document's text. */ const String getTextBetween (const Position& start, const Position& end) const throw(); - /** - */ + /** Returns a line from the document. */ const String getLine (const int lineIndex) const throw(); - /** - */ + /** Returns the number of characters in the document. */ int getNumCharacters() const throw(); - /** - */ + /** Returns the number of lines in the document. */ int getNumLines() const throw() { return lines.size(); } - /** - */ + /** Returns the number of characters in the longest line of the document. */ int getMaximumLineLength() throw(); - /** + /** Deletes a section of the text. + + This operation is undoable. */ void deleteSection (const Position& startPosition, const Position& endPosition); - /** + /** Inserts some text into the document at a given position. + + This operation is undoable. */ void insertText (const Position& position, const String& text); - /** + /** Clears the document and replaces it with some new text. + + This operation is undoable - if you're trying to completely reset the document, you + might want to also call clearUndoHistory() and setSavePoint() after using this method. */ void replaceAllContent (const String& newContent); - /** + /** Returns the preferred new-line characters for the document. + This will be either "\n", "\r\n", or (rarely) "\r". + @see setNewLineCharacters + */ + const String getNewLineCharacters() const throw() { return newLineChars; } + + /** Sets the new-line characters that the document should use. + The string must be either "\n", "\r\n", or (rarely) "\r". + @see getNewLineCharacters + */ + void setNewLineCharacters (const String& newLine) throw(); + + /** Begins a new undo transaction. + + The document itself will not call this internally, so relies on whatever is using the + document to periodically call this to break up the undo sequence into sensible chunks. + @see UndoManager::beginNewTransaction */ void newTransaction(); - /** + /** Undo the last operation. + @see UndoManager::undo */ void undo(); - /** + /** Redo the last operation. + @see UndoManager::redo */ void redo(); - /** + /** Clears the undo history. + @see UndoManager::clearUndoHistory */ - void clearUndoManager(); + void clearUndoHistory(); - /** + /** Returns the document's UndoManager */ + UndoManager& getUndoManager() throw() { return undoManager; } + + /** Makes a note that the document's current state matches the one that is saved. + + After this has been called, hasChangedSinceSavePoint() will return false until + the document has been altered, and then it'll start returning true. If the document is + altered, but then undone until it gets back to this state, hasChangedSinceSavePoint() + will again return false. + + @see hasChangedSinceSavePoint */ void setSavePoint() throw(); - /** + /** Returns true if the state of the document differs from the state it was in when + setSavePoint() was last called. + + @see setSavePoint */ bool hasChangedSinceSavePoint() const throw(); - /** - */ + /** Searches for a word-break. */ const Position findWordBreakAfter (const Position& position) const throw(); - /** - */ + /** Searches for a word-break. */ const Position findWordBreakBefore (const Position& position) const throw(); /** An object that receives callbacks from the CodeDocument when its text changes. @@ -44621,19 +44708,22 @@ public: Listener() {} virtual ~Listener() {} - /** + /** Called by a CodeDocument when it is altered. */ virtual void codeDocumentChanged (const CodeDocument::Position& affectedTextStart, const CodeDocument::Position& affectedTextEnd) = 0; }; - /** + /** Registers a listener object to receive callbacks when the document changes. + If the listener is already registered, this method has no effect. + @see removeListener */ - void addListener (Listener* const listener); + void addListener (Listener* const listener) throw(); - /** + /** Deregisters a listener. + @see addListener */ - void removeListener (Listener* const listener); + void removeListener (Listener* const listener) throw(); /** Iterates the text in a CodeDocument. @@ -44650,36 +44740,32 @@ public: const Iterator& operator= (const Iterator& other) throw(); ~Iterator() throw(); - /** + /** Reads the next character and returns it. + @see peekNextChar */ juce_wchar nextChar() throw(); - /** - */ + /** Reads the next character without advancing the current position. */ juce_wchar peekNextChar() const throw(); - /** - */ + /** Advances the position by one character. */ void skip() throw(); - /** + /** Returns the position of the next character as its position within the + whole document. */ int getPosition() const throw() { return position; } - /** - */ + /** Skips over any whitespace characters until the next character is non-whitespace. */ void skipWhitespace(); - /** - */ + /** Skips forward until the next character will be the first character on the next line */ void skipToEndOfLine(); - /** - */ + /** Returns the line number of the next character. */ int getLine() const throw() { return line; } - /** - */ + /** Returns true if the iterator has reached the end of the document. */ bool isEOF() const throw(); private: @@ -44700,6 +44786,7 @@ private: int currentActionIndex, indexOfSavedState; int maximumLineLength; VoidArray listeners; + String newLineChars; void sendListenerChangeMessage (const int startLine, const int endLine); @@ -44765,18 +44852,95 @@ class CodeEditorLine; class JUCE_API CodeEditorComponent : public Component, public Timer, public ScrollBarListener, - public CodeDocument::Listener + public CodeDocument::Listener, + public AsyncUpdater { public: - CodeEditorComponent (CodeDocument& document, CodeTokeniser* const codeTokeniser); + /** Creates an editor for a document. + + The tokeniser object is optional - pass 0 to disable syntax highlighting. + The object that you pass in is not owned or deleted by the editor - you must + make sure that it doesn't get deleted while this component is still using it. + + @see CodeDocument + */ + CodeEditorComponent (CodeDocument& document, + CodeTokeniser* const codeTokeniser); + + /** Destructor. */ ~CodeEditorComponent(); + /** Returns the code document that this component is editing. */ CodeDocument& getDocument() const throw() { return document; } + /** Loads the given content into the document. + This will completely reset the CodeDocument object, clear its undo history, + and fill it with this text. + */ void loadContent (const String& newContent); - void insertTextAtCaret (const String& newText); + /** Returns the standard character width. */ + float getCharWidth() const throw() { return charWidth; } + + /** Returns the height of a line of text, in pixels. */ + int getLineHeight() const throw() { return lineHeight; } + + /** Returns the number of whole lines visible on the screen, + This doesn't include a cut-off line that might be visible at the bottom if the + component's height isn't an exact multiple of the line-height. + */ + int getNumLinesOnScreen() const throw() { return linesOnScreen; } + + /** Returns the number of whole columns visible on the screen. + This doesn't include any cut-off columns at the right-hand edge. + */ + int getNumColumnsOnScreen() const throw() { return columnsOnScreen; } + + /** Returns the current caret position. */ + const CodeDocument::Position getCaretPos() const { return caretPos; } + + /** Moves the caret. + If selecting is true, the section of the document between the current + caret position and the new one will become selected. If false, any currently + selected region will be deselected. + */ + void moveCaretTo (const CodeDocument::Position& newPos, const bool selecting); + + /** Returns the on-screen position of a character in the document. + The rectangle returned is relative to this component's top-left origin. + */ + const Rectangle getCharacterBounds (const CodeDocument::Position& pos) const throw(); + + /** Finds the character at a given on-screen position. + The co-ordinates are relative to this component's top-left origin. + */ + const CodeDocument::Position getPositionAt (int x, int y); + + void cursorLeft (const bool moveInWholeWordSteps, const bool selecting); + void cursorRight (const bool moveInWholeWordSteps, const bool selecting); + void cursorDown (const bool selecting); + void cursorUp (const bool selecting); + + void pageDown (const bool selecting); + void pageUp (const bool selecting); + + void scrollDown(); + void scrollUp(); + void scrollToLine (int newFirstLineOnScreen); + void scrollBy (int deltaLines); + void scrollToColumn (int newFirstColumnOnScreen); + void scrollToKeepCaretOnScreen(); + + void goToStartOfDocument (const bool selecting); + void goToStartOfLine (const bool selecting); + void goToEndOfDocument (const bool selecting); + void goToEndOfLine (const bool selecting); + + void deselectAll(); + void selectAll(); + + void insertTextAtCaret (const String& textToInsert); void insertTabAtCaret(); void cut(); void copy(); @@ -44785,49 +44949,50 @@ public: void backspace (const bool moveInWholeWordSteps); void deleteForward (const bool moveInWholeWordSteps); - void cursorLeft (const bool moveInWholeWordSteps, const bool selecting); - void cursorRight (const bool moveInWholeWordSteps, const bool selecting); - void cursorDown (const bool selecting); - void cursorUp (const bool selecting); - void pageDown (const bool selecting); - void pageUp (const bool selecting); - void scrollDown(); - void scrollUp(); - void goToStart (const bool selecting); - void goToStartOfLine (const bool selecting); - void goToEnd (const bool selecting); - void goToEndOfLine (const bool selecting); - void selectAll(); - void undo(); void redo(); - float getCharWidth() const throw() { return charWidth; } - int getLineHeight() const throw() { return lineHeight; } - int getNumLinesOnScreen() const throw() { return linesOnScreen; } - int getNumColumnsOnScreen() const throw() { return columnsOnScreen; } + /** Changes the current tab settings. + This lets you change the tab size and whether pressing the tab key inserts a + tab character, or its equivalent number of spaces. + */ + void setTabSize (const int numSpacesPerTab, + const bool insertSpacesInsteadOfTabCharacters) throw(); - const CodeDocument::Position getCaretPos() const { return caretPos; } - void moveCaretTo (const CodeDocument::Position& newPos, const bool highlighting); - - void deselectAll(); - void scrollToLine (int firstLineOnScreen); - void scrollToColumn (int firstColumnOnScreen); - void scrollBy (int deltaLines); - void scrollToKeepCaretOnScreen(); - - const Rectangle getCharacterBounds (const CodeDocument::Position& pos) const throw(); - const CodeDocument::Position getPositionAt (int x, int y); - - void setTabSize (const int numSpaces, const bool insertSpaces); + /** Returns the current number of spaces per tab. + @see setTabSize + */ int getTabSize() const throw() { return spacesPerTab; } + + /** Returns true if the tab key will insert spaces instead of actual tab characters. + @see setTabSize + */ bool areSpacesInsertedForTabs() const { return useSpacesForTabs; } + /** Changes the font. + Make sure you only use a fixed-width font, or this component will look pretty nasty! + */ void setFont (const Font& newFont); - void setDefaultColours(); - void setColourForTokenCategory (const int tokenCategory, const Colour& colour); - const Colour getColourForTokenCategory (const int tokenCategory) const; + /** Resets the syntax highlighting colours to the default ones provided by the + code tokeniser. + @see CodeTokeniser::getDefaultColour + */ + void resetToDefaultColours(); + + /** Changes one of the syntax highlighting colours. + The token type values are dependent on the tokeniser being used - use + CodeTokeniser::getTokenTypes() to get a list of the token types. + @see getColourForTokenType + */ + void setColourForTokenType (const int tokenType, const Colour& colour); + + /** Returns one of the syntax highlighting colours. + The token type values are dependent on the tokeniser being used - use + CodeTokeniser::getTokenTypes() to get a list of the token types. + @see setColourForTokenType + */ + const Colour getColourForTokenType (const int tokenType) const throw(); /** A set of colour IDs to use to change the colour of various aspects of the editor. @@ -44846,16 +45011,29 @@ public: enabled. */ }; + /** @internal */ void resized(); + /** @internal */ void paint (Graphics& g); + /** @internal */ bool keyPressed (const KeyPress& key); + /** @internal */ void mouseDown (const MouseEvent& e); + /** @internal */ void mouseDrag (const MouseEvent& e); + /** @internal */ void mouseUp (const MouseEvent& e); + /** @internal */ void mouseDoubleClick (const MouseEvent& e); + /** @internal */ void mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY); + /** @internal */ void timerCallback(); + /** @internal */ void scrollBarMoved (ScrollBar* scrollBarThatHasMoved, const double newRangeStart); + /** @internal */ + void handleAsyncUpdate(); + /** @internal */ void codeDocumentChanged (const CodeDocument::Position& affectedTextStart, const CodeDocument::Position& affectedTextEnd); diff --git a/src/gui/components/code_editor/juce_CodeDocument.cpp b/src/gui/components/code_editor/juce_CodeDocument.cpp index 0d7bc50ed3..03aa8820e6 100644 --- a/src/gui/components/code_editor/juce_CodeDocument.cpp +++ b/src/gui/components/code_editor/juce_CodeDocument.cpp @@ -369,18 +369,14 @@ void CodeDocument::Position::setPosition (const int newPosition) throw() } } -void CodeDocument::Position::updateLineAndIndexFromPosition() throw() -{ - setPosition (getPosition()); -} - void CodeDocument::Position::moveBy (int characterDelta) throw() { jassert (owner != 0); - updateLineAndIndexFromPosition(); if (characterDelta == 1) { + setPosition (getPosition()); + // If moving right, make sure we don't get stuck between the \r and \n characters.. CodeDocumentLine* const l = owner->lines.getUnchecked (line); if (indexInLine + characterDelta < l->lineLength @@ -444,7 +440,8 @@ CodeDocument::CodeDocument() : undoManager (INT_MAX, 10000), currentActionIndex (0), indexOfSavedState (-1), - maximumLineLength (-1) + maximumLineLength (-1), + newLineChars ("\r\n") { } @@ -550,6 +547,12 @@ void CodeDocument::replaceAllContent (const String& newContent) insert (newContent, 0, true); } +void CodeDocument::setNewLineCharacters (const String& newLine) throw() +{ + jassert (newLine == T("\r\n") || newLine == T("\n") || newLine == T("\r")); + newLineChars = newLine; +} + void CodeDocument::newTransaction() { undoManager.beginNewTransaction (String::empty); @@ -566,7 +569,7 @@ void CodeDocument::redo() undoManager.redo(); } -void CodeDocument::clearUndoManager() +void CodeDocument::clearUndoHistory() { undoManager.clearUndoHistory(); } @@ -668,12 +671,12 @@ const CodeDocument::Position CodeDocument::findWordBreakBefore (const Position& //============================================================================== -void CodeDocument::addListener (CodeDocument::Listener* const listener) +void CodeDocument::addListener (CodeDocument::Listener* const listener) throw() { listeners.addIfNotAlreadyThere (listener); } -void CodeDocument::removeListener (CodeDocument::Listener* const listener) +void CodeDocument::removeListener (CodeDocument::Listener* const listener) throw() { listeners.removeValue (listener); } @@ -775,11 +778,12 @@ void CodeDocument::insert (const String& text, const int insertPos, const bool u lastAffectedLine = lines.size(); } - for (int i = firstAffectedLine + 1; i < lines.size(); ++i) + int lineStart = newFirstLine->lineStartInFile; + for (int i = firstAffectedLine; i < lines.size(); ++i) { CodeDocumentLine* const l = lines.getUnchecked (i); - const CodeDocumentLine* const previousLine = lines.getUnchecked (i - 1); - l->lineStartInFile = previousLine->lineStartInFile + previousLine->lineLength; + l->lineStartInFile = lineStart; + lineStart += l->lineLength; } const int newTextLength = text.length(); @@ -788,7 +792,7 @@ void CodeDocument::insert (const String& text, const int insertPos, const bool u CodeDocument::Position* const p = positionsToMaintain.getUnchecked(i); if (p->getPosition() >= insertPos) - p->moveBy (newTextLength); + p->setPosition (p->getPosition() + newTextLength); } sendListenerChangeMessage (firstAffectedLine, lastAffectedLine); @@ -889,7 +893,7 @@ void CodeDocument::remove (const int startPos, const int endPos, const bool undo CodeDocument::Position* p = positionsToMaintain.getUnchecked(i); if (p->getPosition() > startPosition.getPosition()) - p->setPosition (jmax (startPos, startPos - endPos)); + p->setPosition (jmax (startPos, p->getPosition() + startPos - endPos)); if (p->getPosition() > totalChars) p->setPosition (totalChars); diff --git a/src/gui/components/code_editor/juce_CodeDocument.h b/src/gui/components/code_editor/juce_CodeDocument.h index 0d451e79ce..abde03dfc4 100644 --- a/src/gui/components/code_editor/juce_CodeDocument.h +++ b/src/gui/components/code_editor/juce_CodeDocument.h @@ -45,12 +45,11 @@ class CodeDocumentLine; class JUCE_API CodeDocument { public: - /** + /** Creates a new, empty document. */ CodeDocument(); - /** - */ + /** Destructor. */ ~CodeDocument(); //============================================================================== @@ -64,62 +63,119 @@ public: class JUCE_API Position { public: + /** Creates an uninitialised postion. + Don't attempt to call any methods on this until you've given it an owner document + to refer to! + */ Position() throw(); - Position (const CodeDocument* const ownerDocument, const int line, const int indexInLine) throw(); - Position (const CodeDocument* const ownerDocument, const int characterPos) throw(); + + /** Creates a position based on a line and index in a document. + + Note that this index is NOT the column number, it's the number of characters from the + start of the line. The "column" number isn't quite the same, because if the line + contains any tab characters, the relationship of the index to its visual column depends on + the number of spaces per tab being used! + + Lines are numbered from zero, and if the line or index are beyond the bounds of the document, + they will be adjusted to keep them within its limits. + */ + Position (const CodeDocument* const ownerDocument, + const int line, const int indexInLine) throw(); + + /** Creates a position based on a character index in a document. + This position is placed at the specified number of characters from the start of the + document. The line and column are auto-calculated. + + If the position is beyond the range of the document, it'll be adjusted to keep it + inside. + */ + Position (const CodeDocument* const ownerDocument, + const int charactersFromStartOfDocument) throw(); + + /** Creates a copy of another position. + + This will copy the position, but the new object will not be set to maintain its position, + even if the source object was set to do so. + */ Position (const Position& other) throw(); + + /** Destructor. */ ~Position() throw(); const Position& operator= (const Position& other) throw(); bool operator== (const Position& other) const throw(); bool operator!= (const Position& other) const throw(); - /** - */ - void setLineAndIndex (const int newLine, const int newIndexInLine) throw(); + /** Points this object at a new position within the document. - /** + If the position is beyond the range of the document, it'll be adjusted to keep it + inside. + @see getPosition, setLineAndIndex */ - void setPosition (const int newPosition) throw(); + void setPosition (const int charactersFromStartOfDocument) throw(); - /** + /** Returns the position as the number of characters from the start of the document. + @see setPosition, getLineNumber, getIndexInLine */ int getPosition() const throw() { return characterPos; } - /** + /** Moves the position to a new line and index within the line. + + Note that the index is NOT the column at which the position appears in an editor. + If the line contains any tab characters, the relationship of the index to its + visual position depends on the number of spaces per tab being used! + + Lines are numbered from zero, and if the line or index are beyond the bounds of the document, + they will be adjusted to keep them within its limits. + */ + void setLineAndIndex (const int newLine, const int newIndexInLine) throw(); + + /** Returns the line number of this position. + The first line in the document is numbered zero, not one! */ int getLineNumber() const throw() { return line; } - /** + /** Returns the number of characters from the start of the line. + + Note that this value is NOT the column at which the position appears in an editor. + If the line contains any tab characters, the relationship of the index to its + visual position depends on the number of spaces per tab being used! */ int getIndexInLine() const throw() { return indexInLine; } - /** - */ - void updateLineAndIndexFromPosition() throw(); + /** Allows the position to be automatically updated when the document changes. - /** + If this is set to true, the positon will register with its document so that + when the document has text inserted or deleted, this position will be automatically + moved to keep it at the same position in the text. */ void setPositionMaintained (const bool isMaintained) throw(); //============================================================================== - /** + /** Moves the position forwards or backwards by the specified number of characters. + @see movedBy */ void moveBy (int characterDelta) throw(); - /** + /** Returns a position which is the same as this one, moved by the specified number of + characters. + @see moveBy */ const Position movedBy (const int characterDelta) const throw(); - /** + /** Returns a position which is the same as this one, moved up or down by the specified + number of lines. + @see movedBy */ const Position movedByLines (const int deltaLines) const throw(); - /** + /** Returns the character in the document at this position. + @see getLineText */ const tchar getCharacter() const throw(); - /** + /** Returns the line from the document that this position is within. + @see getCharacter, getLineNumber */ const String getLineText() const throw(); @@ -131,75 +187,107 @@ public: }; //============================================================================== - /** - */ + /** Returns the full text of the document. */ const String getAllContent() const throw(); - /** - */ + /** Returns a section of the document's text. */ const String getTextBetween (const Position& start, const Position& end) const throw(); - /** - */ + /** Returns a line from the document. */ const String getLine (const int lineIndex) const throw(); - /** - */ + /** Returns the number of characters in the document. */ int getNumCharacters() const throw(); - /** - */ + /** Returns the number of lines in the document. */ int getNumLines() const throw() { return lines.size(); } - /** - */ + /** Returns the number of characters in the longest line of the document. */ int getMaximumLineLength() throw(); - /** + /** Deletes a section of the text. + + This operation is undoable. */ void deleteSection (const Position& startPosition, const Position& endPosition); - /** + /** Inserts some text into the document at a given position. + + This operation is undoable. */ void insertText (const Position& position, const String& text); - /** + /** Clears the document and replaces it with some new text. + + This operation is undoable - if you're trying to completely reset the document, you + might want to also call clearUndoHistory() and setSavePoint() after using this method. */ void replaceAllContent (const String& newContent); //============================================================================== - /** + /** Returns the preferred new-line characters for the document. + This will be either "\n", "\r\n", or (rarely) "\r". + @see setNewLineCharacters + */ + const String getNewLineCharacters() const throw() { return newLineChars; } + + /** Sets the new-line characters that the document should use. + The string must be either "\n", "\r\n", or (rarely) "\r". + @see getNewLineCharacters + */ + void setNewLineCharacters (const String& newLine) throw(); + + //============================================================================== + /** Begins a new undo transaction. + + The document itself will not call this internally, so relies on whatever is using the + document to periodically call this to break up the undo sequence into sensible chunks. + @see UndoManager::beginNewTransaction */ void newTransaction(); - /** + /** Undo the last operation. + @see UndoManager::undo */ void undo(); - /** + /** Redo the last operation. + @see UndoManager::redo */ void redo(); - /** + /** Clears the undo history. + @see UndoManager::clearUndoHistory */ - void clearUndoManager(); + void clearUndoHistory(); + + /** Returns the document's UndoManager */ + UndoManager& getUndoManager() throw() { return undoManager; } //============================================================================== - /** + /** Makes a note that the document's current state matches the one that is saved. + + After this has been called, hasChangedSinceSavePoint() will return false until + the document has been altered, and then it'll start returning true. If the document is + altered, but then undone until it gets back to this state, hasChangedSinceSavePoint() + will again return false. + + @see hasChangedSinceSavePoint */ void setSavePoint() throw(); - /** + /** Returns true if the state of the document differs from the state it was in when + setSavePoint() was last called. + + @see setSavePoint */ bool hasChangedSinceSavePoint() const throw(); //============================================================================== - /** - */ + /** Searches for a word-break. */ const Position findWordBreakAfter (const Position& position) const throw(); - /** - */ + /** Searches for a word-break. */ const Position findWordBreakBefore (const Position& position) const throw(); //============================================================================== @@ -212,19 +300,22 @@ public: Listener() {} virtual ~Listener() {} - /** + /** Called by a CodeDocument when it is altered. */ virtual void codeDocumentChanged (const CodeDocument::Position& affectedTextStart, const CodeDocument::Position& affectedTextEnd) = 0; }; - /** + /** Registers a listener object to receive callbacks when the document changes. + If the listener is already registered, this method has no effect. + @see removeListener */ - void addListener (Listener* const listener); + void addListener (Listener* const listener) throw(); - /** + /** Deregisters a listener. + @see addListener */ - void removeListener (Listener* const listener); + void removeListener (Listener* const listener) throw(); //============================================================================== /** Iterates the text in a CodeDocument. @@ -242,36 +333,32 @@ public: const Iterator& operator= (const Iterator& other) throw(); ~Iterator() throw(); - /** + /** Reads the next character and returns it. + @see peekNextChar */ juce_wchar nextChar() throw(); - /** - */ + /** Reads the next character without advancing the current position. */ juce_wchar peekNextChar() const throw(); - /** - */ + /** Advances the position by one character. */ void skip() throw(); - /** + /** Returns the position of the next character as its position within the + whole document. */ int getPosition() const throw() { return position; } - /** - */ + /** Skips over any whitespace characters until the next character is non-whitespace. */ void skipWhitespace(); - /** - */ + /** Skips forward until the next character will be the first character on the next line */ void skipToEndOfLine(); - /** - */ + /** Returns the line number of the next character. */ int getLine() const throw() { return line; } - /** - */ + /** Returns true if the iterator has reached the end of the document. */ bool isEOF() const throw(); private: @@ -293,6 +380,7 @@ private: int currentActionIndex, indexOfSavedState; int maximumLineLength; VoidArray listeners; + String newLineChars; void sendListenerChangeMessage (const int startLine, const int endLine); diff --git a/src/gui/components/code_editor/juce_CodeEditorComponent.cpp b/src/gui/components/code_editor/juce_CodeEditorComponent.cpp index 7561ebed9f..369ee724a4 100644 --- a/src/gui/components/code_editor/juce_CodeEditorComponent.cpp +++ b/src/gui/components/code_editor/juce_CodeEditorComponent.cpp @@ -288,6 +288,7 @@ CodeEditorComponent::CodeEditorComponent (CodeDocument& document_, gutter (5), spacesPerTab (4), lineHeight (0), + firstLineOnScreen (0), linesOnScreen (0), columnsOnScreen (0), scrollbarThickness (16), @@ -319,7 +320,7 @@ CodeEditorComponent::CodeEditorComponent (CodeDocument& document_, f.setTypefaceName (Font::getDefaultMonospacedFontName()); setFont (f); - setDefaultColours(); + resetToDefaultColours(); verticalScrollBar->addListener (this); horizontalScrollBar->addListener (this); @@ -336,7 +337,7 @@ void CodeEditorComponent::loadContent (const String& newContent) { clearCachedIterators (0); document.replaceAllContent (newContent); - document.clearUndoManager(); + document.clearUndoHistory(); document.setSavePoint(); caretPos.setPosition (0); selectionStart.setPosition (0); @@ -350,7 +351,8 @@ void CodeEditorComponent::codeDocumentChanged (const CodeDocument::Position& aff { clearCachedIterators (affectedTextStart.getLineNumber()); - rebuildLineTokens(); + triggerAsyncUpdate(); + ((CaretComponent*) caret)->updatePosition (*this); if (affectedTextEnd.getPosition() >= selectionStart.getPosition() @@ -373,12 +375,14 @@ void CodeEditorComponent::resized() ((CaretComponent*) caret)->updatePosition (*this); verticalScrollBar->setBounds (getWidth() - scrollbarThickness, 0, scrollbarThickness, getHeight() - scrollbarThickness); - horizontalScrollBar->setBounds (0, getHeight() - scrollbarThickness, getWidth() - scrollbarThickness, scrollbarThickness); + horizontalScrollBar->setBounds (gutter, getHeight() - scrollbarThickness, getWidth() - scrollbarThickness - gutter, scrollbarThickness); updateScrollBars(); } void CodeEditorComponent::paint (Graphics& g) { + handleUpdateNowIfNeeded(); + g.fillAll (findColour (CodeEditorComponent::backgroundColourId)); g.reduceClipRegion (gutter, 0, verticalScrollBar->getX() - gutter, horizontalScrollBar->getY()); @@ -396,8 +400,14 @@ void CodeEditorComponent::paint (Graphics& g) highlightColour, charWidth); } +void CodeEditorComponent::handleAsyncUpdate() +{ + rebuildLineTokens(); +} + void CodeEditorComponent::rebuildLineTokens() { + cancelPendingUpdate(); const int numNeeded = linesOnScreen + 1; if (numNeeded != lines.size()) @@ -467,7 +477,7 @@ void CodeEditorComponent::moveCaretTo (const CodeDocument::Position& newPos, con } } - rebuildLineTokens(); + triggerAsyncUpdate(); } else { @@ -481,11 +491,11 @@ void CodeEditorComponent::moveCaretTo (const CodeDocument::Position& newPos, con void CodeEditorComponent::deselectAll() { - if (selectionStart != caretPos || selectionEnd != caretPos) - { - selectionStart = selectionEnd = caretPos; - rebuildLineTokens(); - } + if (selectionStart != selectionEnd) + triggerAsyncUpdate(); + + selectionStart = caretPos; + selectionEnd = caretPos; } void CodeEditorComponent::updateScrollBars() @@ -499,13 +509,17 @@ void CodeEditorComponent::updateScrollBars() void CodeEditorComponent::scrollToLineInternal (int newFirstLineOnScreen) { - firstLineOnScreen = jlimit (0, jmax (0, document.getNumLines() - 1), - newFirstLineOnScreen); + newFirstLineOnScreen = jlimit (0, jmax (0, document.getNumLines() - 1), + newFirstLineOnScreen); - ((CaretComponent*) caret)->updatePosition (*this); + if (newFirstLineOnScreen != firstLineOnScreen) + { + firstLineOnScreen = newFirstLineOnScreen; + ((CaretComponent*) caret)->updatePosition (*this); - updateCachedIterators (firstLineOnScreen); - rebuildLineTokens(); + updateCachedIterators (firstLineOnScreen); + triggerAsyncUpdate(); + } } void CodeEditorComponent::scrollToColumnInternal (double column) @@ -621,7 +635,7 @@ void CodeEditorComponent::copyThenCut() void CodeEditorComponent::paste() { newTransaction(); - const String clip (SystemClipboard::getTextFromClipboard()/*.replace (T("\r\n"), T("\n"))*/); + const String clip (SystemClipboard::getTextFromClipboard()); if (clip.isNotEmpty()) insertTextAtCaret (clip); @@ -703,7 +717,7 @@ void CodeEditorComponent::scrollDown() moveCaretTo (caretPos.movedByLines (-1), false); } -void CodeEditorComponent::goToStart (const bool selecting) +void CodeEditorComponent::goToStartOfDocument (const bool selecting) { newTransaction(); moveCaretTo (CodeDocument::Position (&document, 0, 0), selecting); @@ -732,7 +746,7 @@ void CodeEditorComponent::goToStartOfLine (const bool selecting) moveCaretTo (CodeDocument::Position (&document, caretPos.getLineNumber(), index), selecting); } -void CodeEditorComponent::goToEnd (const bool selecting) +void CodeEditorComponent::goToEndOfDocument (const bool selecting) { newTransaction(); moveCaretTo (CodeDocument::Position (&document, INT_MAX, INT_MAX), selecting); @@ -829,7 +843,7 @@ bool CodeEditorComponent::keyPressed (const KeyPress& key) scrollDown(); #if JUCE_MAC else if (key.getModifiers().isCommandDown()) - goToStart (shiftDown); + goToStartOfDocument (shiftDown); #endif else cursorUp (shiftDown); @@ -840,7 +854,7 @@ bool CodeEditorComponent::keyPressed (const KeyPress& key) scrollUp(); #if JUCE_MAC else if (key.getModifiers().isCommandDown()) - goToEnd (shiftDown); + goToEndOfDocument (shiftDown); #endif else cursorDown (shiftDown); @@ -856,14 +870,14 @@ bool CodeEditorComponent::keyPressed (const KeyPress& key) else if (key.isKeyCode (KeyPress::homeKey)) { if (moveInWholeWordSteps) - goToStart (shiftDown); + goToStartOfDocument (shiftDown); else goToStartOfLine (shiftDown); } else if (key.isKeyCode (KeyPress::endKey)) { if (moveInWholeWordSteps) - goToEnd (shiftDown); + goToEndOfDocument (shiftDown); else goToEndOfLine (shiftDown); } @@ -907,7 +921,7 @@ bool CodeEditorComponent::keyPressed (const KeyPress& key) else if (key == KeyPress::returnKey) { newTransaction(); - insertTextAtCaret (T("\r\n")); + insertTextAtCaret (document.getNewLineCharacters()); } else if (key.isKeyCode (KeyPress::escapeKey)) { @@ -1002,14 +1016,14 @@ void CodeEditorComponent::scrollBarMoved (ScrollBar* scrollBarThatHasMoved, cons } //============================================================================== -void CodeEditorComponent::setTabSize (const int numSpaces, const bool insertSpaces) +void CodeEditorComponent::setTabSize (const int numSpaces, const bool insertSpaces) throw() { useSpacesForTabs = insertSpaces; if (spacesPerTab != numSpaces) { spacesPerTab = numSpaces; - rebuildLineTokens(); + triggerAsyncUpdate(); } } @@ -1059,34 +1073,34 @@ void CodeEditorComponent::setFont (const Font& newFont) resized(); } -void CodeEditorComponent::setDefaultColours() +void CodeEditorComponent::resetToDefaultColours() { coloursForTokenCategories.clear(); if (codeTokeniser != 0) { for (int i = codeTokeniser->getTokenTypes().size(); --i >= 0;) - setColourForTokenCategory (i, codeTokeniser->getDefaultColour (i)); + setColourForTokenType (i, codeTokeniser->getDefaultColour (i)); } } -void CodeEditorComponent::setColourForTokenCategory (const int tokenCategory, const Colour& colour) +void CodeEditorComponent::setColourForTokenType (const int tokenType, const Colour& colour) { - jassert (tokenCategory < 256); + jassert (tokenType < 256); - while (coloursForTokenCategories.size() < tokenCategory) + while (coloursForTokenCategories.size() < tokenType) coloursForTokenCategories.add (Colours::black); - coloursForTokenCategories.set (tokenCategory, colour); + coloursForTokenCategories.set (tokenType, colour); repaint(); } -const Colour CodeEditorComponent::getColourForTokenCategory (const int tokenCategory) const +const Colour CodeEditorComponent::getColourForTokenType (const int tokenType) const throw() { - if (((unsigned int) tokenCategory) >= (unsigned int) coloursForTokenCategories.size()) + if (((unsigned int) tokenType) >= (unsigned int) coloursForTokenCategories.size()) return findColour (CodeEditorComponent::defaultTextColourId); - return coloursForTokenCategories.getReference (tokenCategory); + return coloursForTokenCategories.getReference (tokenType); } void CodeEditorComponent::clearCachedIterators (const int firstLineToBeInvalid) throw() diff --git a/src/gui/components/code_editor/juce_CodeEditorComponent.h b/src/gui/components/code_editor/juce_CodeEditorComponent.h index 8ac8b2d6e7..c4c57ccc92 100644 --- a/src/gui/components/code_editor/juce_CodeEditorComponent.h +++ b/src/gui/components/code_editor/juce_CodeEditorComponent.h @@ -43,20 +43,98 @@ class CodeEditorLine; class JUCE_API CodeEditorComponent : public Component, public Timer, public ScrollBarListener, - public CodeDocument::Listener + public CodeDocument::Listener, + public AsyncUpdater { public: //============================================================================== - CodeEditorComponent (CodeDocument& document, CodeTokeniser* const codeTokeniser); + /** Creates an editor for a document. + + The tokeniser object is optional - pass 0 to disable syntax highlighting. + The object that you pass in is not owned or deleted by the editor - you must + make sure that it doesn't get deleted while this component is still using it. + + @see CodeDocument + */ + CodeEditorComponent (CodeDocument& document, + CodeTokeniser* const codeTokeniser); + + /** Destructor. */ ~CodeEditorComponent(); //============================================================================== + /** Returns the code document that this component is editing. */ CodeDocument& getDocument() const throw() { return document; } + /** Loads the given content into the document. + This will completely reset the CodeDocument object, clear its undo history, + and fill it with this text. + */ void loadContent (const String& newContent); //============================================================================== - void insertTextAtCaret (const String& newText); + /** Returns the standard character width. */ + float getCharWidth() const throw() { return charWidth; } + + /** Returns the height of a line of text, in pixels. */ + int getLineHeight() const throw() { return lineHeight; } + + /** Returns the number of whole lines visible on the screen, + This doesn't include a cut-off line that might be visible at the bottom if the + component's height isn't an exact multiple of the line-height. + */ + int getNumLinesOnScreen() const throw() { return linesOnScreen; } + + /** Returns the number of whole columns visible on the screen. + This doesn't include any cut-off columns at the right-hand edge. + */ + int getNumColumnsOnScreen() const throw() { return columnsOnScreen; } + + /** Returns the current caret position. */ + const CodeDocument::Position getCaretPos() const { return caretPos; } + + /** Moves the caret. + If selecting is true, the section of the document between the current + caret position and the new one will become selected. If false, any currently + selected region will be deselected. + */ + void moveCaretTo (const CodeDocument::Position& newPos, const bool selecting); + + /** Returns the on-screen position of a character in the document. + The rectangle returned is relative to this component's top-left origin. + */ + const Rectangle getCharacterBounds (const CodeDocument::Position& pos) const throw(); + + /** Finds the character at a given on-screen position. + The co-ordinates are relative to this component's top-left origin. + */ + const CodeDocument::Position getPositionAt (int x, int y); + + //============================================================================== + void cursorLeft (const bool moveInWholeWordSteps, const bool selecting); + void cursorRight (const bool moveInWholeWordSteps, const bool selecting); + void cursorDown (const bool selecting); + void cursorUp (const bool selecting); + + void pageDown (const bool selecting); + void pageUp (const bool selecting); + + void scrollDown(); + void scrollUp(); + void scrollToLine (int newFirstLineOnScreen); + void scrollBy (int deltaLines); + void scrollToColumn (int newFirstColumnOnScreen); + void scrollToKeepCaretOnScreen(); + + void goToStartOfDocument (const bool selecting); + void goToStartOfLine (const bool selecting); + void goToEndOfDocument (const bool selecting); + void goToEndOfLine (const bool selecting); + + void deselectAll(); + void selectAll(); + + void insertTextAtCaret (const String& textToInsert); void insertTabAtCaret(); void cut(); void copy(); @@ -65,51 +143,51 @@ public: void backspace (const bool moveInWholeWordSteps); void deleteForward (const bool moveInWholeWordSteps); - void cursorLeft (const bool moveInWholeWordSteps, const bool selecting); - void cursorRight (const bool moveInWholeWordSteps, const bool selecting); - void cursorDown (const bool selecting); - void cursorUp (const bool selecting); - void pageDown (const bool selecting); - void pageUp (const bool selecting); - void scrollDown(); - void scrollUp(); - void goToStart (const bool selecting); - void goToStartOfLine (const bool selecting); - void goToEnd (const bool selecting); - void goToEndOfLine (const bool selecting); - void selectAll(); - void undo(); void redo(); //============================================================================== - float getCharWidth() const throw() { return charWidth; } - int getLineHeight() const throw() { return lineHeight; } - int getNumLinesOnScreen() const throw() { return linesOnScreen; } - int getNumColumnsOnScreen() const throw() { return columnsOnScreen; } + /** Changes the current tab settings. + This lets you change the tab size and whether pressing the tab key inserts a + tab character, or its equivalent number of spaces. + */ + void setTabSize (const int numSpacesPerTab, + const bool insertSpacesInsteadOfTabCharacters) throw(); - const CodeDocument::Position getCaretPos() const { return caretPos; } - void moveCaretTo (const CodeDocument::Position& newPos, const bool highlighting); - - void deselectAll(); - void scrollToLine (int firstLineOnScreen); - void scrollToColumn (int firstColumnOnScreen); - void scrollBy (int deltaLines); - void scrollToKeepCaretOnScreen(); - - const Rectangle getCharacterBounds (const CodeDocument::Position& pos) const throw(); - const CodeDocument::Position getPositionAt (int x, int y); - - //============================================================================== - void setTabSize (const int numSpaces, const bool insertSpaces); + /** Returns the current number of spaces per tab. + @see setTabSize + */ int getTabSize() const throw() { return spacesPerTab; } + + /** Returns true if the tab key will insert spaces instead of actual tab characters. + @see setTabSize + */ bool areSpacesInsertedForTabs() const { return useSpacesForTabs; } + /** Changes the font. + Make sure you only use a fixed-width font, or this component will look pretty nasty! + */ void setFont (const Font& newFont); - void setDefaultColours(); - void setColourForTokenCategory (const int tokenCategory, const Colour& colour); - const Colour getColourForTokenCategory (const int tokenCategory) const; + /** Resets the syntax highlighting colours to the default ones provided by the + code tokeniser. + @see CodeTokeniser::getDefaultColour + */ + void resetToDefaultColours(); + + /** Changes one of the syntax highlighting colours. + The token type values are dependent on the tokeniser being used - use + CodeTokeniser::getTokenTypes() to get a list of the token types. + @see getColourForTokenType + */ + void setColourForTokenType (const int tokenType, const Colour& colour); + + /** Returns one of the syntax highlighting colours. + The token type values are dependent on the tokeniser being used - use + CodeTokeniser::getTokenTypes() to get a list of the token types. + @see setColourForTokenType + */ + const Colour getColourForTokenType (const int tokenType) const throw(); //============================================================================== /** A set of colour IDs to use to change the colour of various aspects of the editor. @@ -130,16 +208,29 @@ public: }; //============================================================================== + /** @internal */ void resized(); + /** @internal */ void paint (Graphics& g); + /** @internal */ bool keyPressed (const KeyPress& key); + /** @internal */ void mouseDown (const MouseEvent& e); + /** @internal */ void mouseDrag (const MouseEvent& e); + /** @internal */ void mouseUp (const MouseEvent& e); + /** @internal */ void mouseDoubleClick (const MouseEvent& e); + /** @internal */ void mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY); + /** @internal */ void timerCallback(); + /** @internal */ void scrollBarMoved (ScrollBar* scrollBarThatHasMoved, const double newRangeStart); + /** @internal */ + void handleAsyncUpdate(); + /** @internal */ void codeDocumentChanged (const CodeDocument::Position& affectedTextStart, const CodeDocument::Position& affectedTextEnd); diff --git a/src/text/juce_String.h b/src/text/juce_String.h index b274821f54..f0e363766a 100644 --- a/src/text/juce_String.h +++ b/src/text/juce_String.h @@ -859,14 +859,14 @@ public: explicit String (const double doubleValue, const int numberOfDecimalPlaces = 0) throw(); - /** Parses this string to find its numerical value (up to 32 bits in size). + /** Reads the value of the string as a decimal number (up to 32 bits in size). @returns the value of the string as a 32 bit signed base-10 integer. @see getTrailingIntValue, getHexValue32, getHexValue64 */ int getIntValue() const throw(); - /** Parses this string to find its numerical value (up to 64 bits in size). + /** Reads the value of the string as a decimal number (up to 64 bits in size). @returns the value of the string as a 64 bit signed base-10 integer. */