2 ==============================================================================
4 This file is part of the JUCE library - "Jules' Utility Class Extensions"
5 Copyright 2004-11 by Raw Material Software Ltd.
7 ------------------------------------------------------------------------------
9 JUCE can be redistributed and/or modified under the terms of the GNU General
10 Public License (Version 2), as published by the Free Software Foundation.
11 A copy of the license is included in the JUCE distribution, or can be found
12 online at www.gnu.org/licenses.
14 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 ------------------------------------------------------------------------------
20 To release a closed-source product which uses JUCE, commercial licenses are
21 available: visit www.rawmaterialsoftware.com/juce for more information.
23 ==============================================================================
26 #include "../../../core/juce_StandardHeader.h"
30 #include "juce_TextEditor.h"
31 #include "../windows/juce_ComponentPeer.h"
32 #include "../../graphics/fonts/juce_GlyphArrangement.h"
33 #include "../../../utilities/juce_SystemClipboard.h"
34 #include "../../../core/juce_Time.h"
35 #include "../../../text/juce_LocalisedStrings.h"
36 #include "../../../io/streams/juce_MemoryOutputStream.h"
37 #include "../lookandfeel/juce_LookAndFeel.h"
38 #include "../keyboard/juce_TextEditorKeyMapper.h"
41 //==============================================================================
42 // a word or space that can't be broken down any further
45 //==============================================================================
50 //==============================================================================
51 bool isWhitespace() const { return CharacterFunctions::isWhitespace (atomText
[0]); }
52 bool isNewLine() const { return atomText
[0] == '\r' || atomText
[0] == '\n'; }
54 String
getText (const juce_wchar passwordCharacter
) const
56 if (passwordCharacter
== 0)
59 return String::repeatedString (String::charToString (passwordCharacter
),
63 String
getTrimmedText (const juce_wchar passwordCharacter
) const
65 if (passwordCharacter
== 0)
66 return atomText
.substring (0, numChars
);
70 return String::repeatedString (String::charToString (passwordCharacter
), numChars
);
74 //==============================================================================
75 // a run of text with a single font and colour
76 class TextEditor::UniformTextSection
79 //==============================================================================
80 UniformTextSection (const String
& text
,
82 const Colour
& colour_
,
83 const juce_wchar passwordCharacter
)
87 initialiseAtoms (text
, passwordCharacter
);
90 UniformTextSection (const UniformTextSection
& other
)
94 atoms
.ensureStorageAllocated (other
.atoms
.size());
96 for (int i
= 0; i
< other
.atoms
.size(); ++i
)
97 atoms
.add (new TextAtom (*other
.atoms
.getUnchecked(i
)));
100 ~UniformTextSection()
102 // (no need to delete the atoms, as they're explicitly deleted by the caller)
107 for (int i
= atoms
.size(); --i
>= 0;)
113 int getNumAtoms() const
118 TextAtom
* getAtom (const int index
) const noexcept
120 return atoms
.getUnchecked (index
);
123 void append (const UniformTextSection
& other
, const juce_wchar passwordCharacter
)
125 if (other
.atoms
.size() > 0)
127 TextAtom
* const lastAtom
= atoms
.getLast();
130 if (lastAtom
!= nullptr)
132 if (! CharacterFunctions::isWhitespace (lastAtom
->atomText
.getLastCharacter()))
134 TextAtom
* const first
= other
.getAtom(0);
136 if (! CharacterFunctions::isWhitespace (first
->atomText
[0]))
138 lastAtom
->atomText
+= first
->atomText
;
139 lastAtom
->numChars
= (uint16
) (lastAtom
->numChars
+ first
->numChars
);
140 lastAtom
->width
= font
.getStringWidthFloat (lastAtom
->getText (passwordCharacter
));
147 atoms
.ensureStorageAllocated (atoms
.size() + other
.atoms
.size() - i
);
149 while (i
< other
.atoms
.size())
151 atoms
.add (other
.getAtom(i
));
157 UniformTextSection
* split (const int indexToBreakAt
,
158 const juce_wchar passwordCharacter
)
160 UniformTextSection
* const section2
= new UniformTextSection (String::empty
,
165 for (int i
= 0; i
< atoms
.size(); ++i
)
167 TextAtom
* const atom
= getAtom(i
);
169 const int nextIndex
= index
+ atom
->numChars
;
171 if (index
== indexToBreakAt
)
174 for (j
= i
; j
< atoms
.size(); ++j
)
175 section2
->atoms
.add (getAtom (j
));
177 for (j
= atoms
.size(); --j
>= i
;)
182 else if (indexToBreakAt
>= index
&& indexToBreakAt
< nextIndex
)
184 TextAtom
* const secondAtom
= new TextAtom();
186 secondAtom
->atomText
= atom
->atomText
.substring (indexToBreakAt
- index
);
187 secondAtom
->width
= font
.getStringWidthFloat (secondAtom
->getText (passwordCharacter
));
188 secondAtom
->numChars
= (uint16
) secondAtom
->atomText
.length();
190 section2
->atoms
.add (secondAtom
);
192 atom
->atomText
= atom
->atomText
.substring (0, indexToBreakAt
- index
);
193 atom
->width
= font
.getStringWidthFloat (atom
->getText (passwordCharacter
));
194 atom
->numChars
= (uint16
) (indexToBreakAt
- index
);
197 for (j
= i
+ 1; j
< atoms
.size(); ++j
)
198 section2
->atoms
.add (getAtom (j
));
200 for (j
= atoms
.size(); --j
> i
;)
212 void appendAllText (MemoryOutputStream
& mo
) const
214 for (int i
= 0; i
< atoms
.size(); ++i
)
215 mo
<< getAtom(i
)->atomText
;
218 void appendSubstring (MemoryOutputStream
& mo
, const Range
<int>& range
) const
221 for (int i
= 0; i
< atoms
.size(); ++i
)
223 const TextAtom
* const atom
= getAtom (i
);
224 const int nextIndex
= index
+ atom
->numChars
;
226 if (range
.getStart() < nextIndex
)
228 if (range
.getEnd() <= index
)
231 const Range
<int> r ((range
- index
).getIntersectionWith (Range
<int> (0, (int) atom
->numChars
)));
234 mo
<< atom
->atomText
.substring (r
.getStart(), r
.getEnd());
241 int getTotalLength() const
245 for (int i
= atoms
.size(); --i
>= 0;)
246 total
+= getAtom(i
)->numChars
;
251 void setFont (const Font
& newFont
,
252 const juce_wchar passwordCharacter
)
258 for (int i
= atoms
.size(); --i
>= 0;)
260 TextAtom
* const atom
= atoms
.getUnchecked(i
);
261 atom
->width
= newFont
.getStringWidthFloat (atom
->getText (passwordCharacter
));
266 //==============================================================================
271 Array
<TextAtom
*> atoms
;
273 //==============================================================================
274 void initialiseAtoms (const String
& textToParse
,
275 const juce_wchar passwordCharacter
)
277 String::CharPointerType
text (textToParse
.getCharPointer());
279 while (! text
.isEmpty())
282 String::CharPointerType
start (text
);
284 // create a whitespace atom unless it starts with non-ws
285 if (text
.isWhitespace() && *text
!= '\r' && *text
!= '\n')
292 while (text
.isWhitespace() && *text
!= '\r' && *text
!= '\n');
307 else if (*text
== '\n')
314 while (! (text
.isEmpty() || text
.isWhitespace()))
322 TextAtom
* const atom
= new TextAtom();
323 atom
->atomText
= String (start
, numChars
);
325 atom
->width
= font
.getStringWidthFloat (atom
->getText (passwordCharacter
));
326 atom
->numChars
= (uint16
) numChars
;
332 UniformTextSection
& operator= (const UniformTextSection
& other
);
333 JUCE_LEAK_DETECTOR (UniformTextSection
);
336 //==============================================================================
337 class TextEditor::Iterator
340 //==============================================================================
341 Iterator (const Array
<UniformTextSection
*>& sections_
,
342 const float wordWrapWidth_
,
343 const juce_wchar passwordCharacter_
)
351 currentSection (nullptr),
352 sections (sections_
),
355 wordWrapWidth (wordWrapWidth_
),
356 passwordCharacter (passwordCharacter_
)
358 jassert (wordWrapWidth_
> 0);
360 if (sections
.size() > 0)
362 currentSection
= sections
.getUnchecked (sectionIndex
);
364 if (currentSection
!= nullptr)
369 Iterator (const Iterator
& other
)
370 : indexInText (other
.indexInText
),
372 lineHeight (other
.lineHeight
),
373 maxDescent (other
.maxDescent
),
375 atomRight (other
.atomRight
),
377 currentSection (other
.currentSection
),
378 sections (other
.sections
),
379 sectionIndex (other
.sectionIndex
),
380 atomIndex (other
.atomIndex
),
381 wordWrapWidth (other
.wordWrapWidth
),
382 passwordCharacter (other
.passwordCharacter
),
383 tempAtom (other
.tempAtom
)
387 //==============================================================================
390 if (atom
== &tempAtom
)
392 const int numRemaining
= tempAtom
.atomText
.length() - tempAtom
.numChars
;
394 if (numRemaining
> 0)
396 tempAtom
.atomText
= tempAtom
.atomText
.substring (tempAtom
.numChars
);
400 if (tempAtom
.numChars
> 0)
403 indexInText
+= tempAtom
.numChars
;
406 g
.addLineOfText (currentSection
->font
, atom
->getText (passwordCharacter
), 0.0f
, 0.0f
);
409 for (split
= 0; split
< g
.getNumGlyphs(); ++split
)
410 if (shouldWrap (g
.getGlyph (split
).getRight()))
413 if (split
> 0 && split
<= numRemaining
)
415 tempAtom
.numChars
= (uint16
) split
;
416 tempAtom
.width
= g
.getGlyph (split
- 1).getRight();
417 atomRight
= atomX
+ tempAtom
.width
;
423 bool forceNewLine
= false;
425 if (sectionIndex
>= sections
.size())
427 moveToEndOfLastAtom();
430 else if (atomIndex
>= currentSection
->getNumAtoms() - 1)
432 if (atomIndex
>= currentSection
->getNumAtoms())
434 if (++sectionIndex
>= sections
.size())
436 moveToEndOfLastAtom();
441 currentSection
= sections
.getUnchecked (sectionIndex
);
445 const TextAtom
* const lastAtom
= currentSection
->getAtom (atomIndex
);
447 if (! lastAtom
->isWhitespace())
449 // handle the case where the last atom in a section is actually part of the same
450 // word as the first atom of the next section...
451 float right
= atomRight
+ lastAtom
->width
;
452 float lineHeight2
= lineHeight
;
453 float maxDescent2
= maxDescent
;
455 for (int section
= sectionIndex
+ 1; section
< sections
.size(); ++section
)
457 const UniformTextSection
* const s
= sections
.getUnchecked (section
);
459 if (s
->getNumAtoms() == 0)
462 const TextAtom
* const nextAtom
= s
->getAtom (0);
464 if (nextAtom
->isWhitespace())
467 right
+= nextAtom
->width
;
469 lineHeight2
= jmax (lineHeight2
, s
->font
.getHeight());
470 maxDescent2
= jmax (maxDescent2
, s
->font
.getDescent());
472 if (shouldWrap (right
))
474 lineHeight
= lineHeight2
;
475 maxDescent
= maxDescent2
;
481 if (s
->getNumAtoms() > 1)
491 indexInText
+= atom
->numChars
;
493 if (atom
->isNewLine())
497 atom
= currentSection
->getAtom (atomIndex
);
498 atomRight
= atomX
+ atom
->width
;
501 if (shouldWrap (atomRight
) || forceNewLine
)
503 if (atom
->isWhitespace())
505 // leave whitespace at the end of a line, but truncate it to avoid scrolling
506 atomRight
= jmin (atomRight
, wordWrapWidth
);
510 atomRight
= atom
->width
;
512 if (shouldWrap (atomRight
)) // atom too big to fit on a line, so break it up..
516 tempAtom
.numChars
= 0;
538 int tempSectionIndex
= sectionIndex
;
539 int tempAtomIndex
= atomIndex
;
540 const UniformTextSection
* section
= sections
.getUnchecked (tempSectionIndex
);
542 lineHeight
= section
->font
.getHeight();
543 maxDescent
= section
->font
.getDescent();
545 float x
= (atom
!= nullptr) ? atom
->width
: 0;
547 while (! shouldWrap (x
))
549 if (tempSectionIndex
>= sections
.size())
552 bool checkSize
= false;
554 if (tempAtomIndex
>= section
->getNumAtoms())
556 if (++tempSectionIndex
>= sections
.size())
560 section
= sections
.getUnchecked (tempSectionIndex
);
564 const TextAtom
* const nextAtom
= section
->getAtom (tempAtomIndex
);
566 if (nextAtom
== nullptr)
569 x
+= nextAtom
->width
;
571 if (shouldWrap (x
) || nextAtom
->isNewLine())
576 lineHeight
= jmax (lineHeight
, section
->font
.getHeight());
577 maxDescent
= jmax (maxDescent
, section
->font
.getDescent());
584 //==============================================================================
585 void draw (Graphics
& g
, const UniformTextSection
*& lastSection
) const
587 if (passwordCharacter
!= 0 || ! atom
->isWhitespace())
589 if (lastSection
!= currentSection
)
591 lastSection
= currentSection
;
592 g
.setColour (currentSection
->colour
);
593 g
.setFont (currentSection
->font
);
596 jassert (atom
->getTrimmedText (passwordCharacter
).isNotEmpty());
599 ga
.addLineOfText (currentSection
->font
,
600 atom
->getTrimmedText (passwordCharacter
),
602 (float) roundToInt (lineY
+ lineHeight
- maxDescent
));
607 void drawSelection (Graphics
& g
, const Range
<int>& selection
) const
609 const int startX
= roundToInt (indexToX (selection
.getStart()));
610 const int endX
= roundToInt (indexToX (selection
.getEnd()));
612 const int y
= roundToInt (lineY
);
613 const int nextY
= roundToInt (lineY
+ lineHeight
);
615 g
.fillRect (startX
, y
, endX
- startX
, nextY
- y
);
618 void drawUnderline (Graphics
& g
, const Range
<int>& underline
, const Colour
& colour
) const
620 const int startX
= roundToInt (indexToX (underline
.getStart()));
621 const int endX
= roundToInt (indexToX (underline
.getEnd()));
622 const int baselineY
= roundToInt (lineY
+ currentSection
->font
.getAscent() + 0.5f
);
624 Graphics::ScopedSaveState
state (g
);
625 g
.reduceClipRegion (Rectangle
<int> (startX
, baselineY
, endX
- startX
, 1));
626 g
.fillCheckerBoard (Rectangle
<int> (0, 0, endX
, baselineY
+ 1), 3, 1, colour
, Colours::transparentBlack
);
629 void drawSelectedText (Graphics
& g
,
630 const Range
<int>& selection
,
631 const Colour
& selectedTextColour
) const
633 if (passwordCharacter
!= 0 || ! atom
->isWhitespace())
636 ga
.addLineOfText (currentSection
->font
,
637 atom
->getTrimmedText (passwordCharacter
),
639 (float) roundToInt (lineY
+ lineHeight
- maxDescent
));
641 if (selection
.getEnd() < indexInText
+ atom
->numChars
)
643 GlyphArrangement
ga2 (ga
);
644 ga2
.removeRangeOfGlyphs (0, selection
.getEnd() - indexInText
);
645 ga
.removeRangeOfGlyphs (selection
.getEnd() - indexInText
, -1);
647 g
.setColour (currentSection
->colour
);
651 if (selection
.getStart() > indexInText
)
653 GlyphArrangement
ga2 (ga
);
654 ga2
.removeRangeOfGlyphs (selection
.getStart() - indexInText
, -1);
655 ga
.removeRangeOfGlyphs (0, selection
.getStart() - indexInText
);
657 g
.setColour (currentSection
->colour
);
661 g
.setColour (selectedTextColour
);
666 //==============================================================================
667 float indexToX (const int indexToFind
) const
669 if (indexToFind
<= indexInText
)
672 if (indexToFind
>= indexInText
+ atom
->numChars
)
676 g
.addLineOfText (currentSection
->font
,
677 atom
->getText (passwordCharacter
),
680 if (indexToFind
- indexInText
>= g
.getNumGlyphs())
683 return jmin (atomRight
, g
.getGlyph (indexToFind
- indexInText
).getLeft());
686 int xToIndex (const float xToFind
) const
688 if (xToFind
<= atomX
|| atom
->isNewLine())
691 if (xToFind
>= atomRight
)
692 return indexInText
+ atom
->numChars
;
695 g
.addLineOfText (currentSection
->font
,
696 atom
->getText (passwordCharacter
),
700 for (j
= 0; j
< g
.getNumGlyphs(); ++j
)
701 if ((g
.getGlyph(j
).getLeft() + g
.getGlyph(j
).getRight()) / 2 > xToFind
)
704 return indexInText
+ j
;
707 //==============================================================================
708 bool getCharPosition (const int index
, float& cx
, float& cy
, float& lineHeight_
)
712 if (indexInText
+ atom
->numChars
> index
)
714 cx
= indexToX (index
);
716 lineHeight_
= lineHeight
;
723 lineHeight_
= lineHeight
;
727 //==============================================================================
729 float lineY
, lineHeight
, maxDescent
;
730 float atomX
, atomRight
;
731 const TextAtom
* atom
;
732 const UniformTextSection
* currentSection
;
735 const Array
<UniformTextSection
*>& sections
;
736 int sectionIndex
, atomIndex
;
737 const float wordWrapWidth
;
738 const juce_wchar passwordCharacter
;
741 Iterator
& operator= (const Iterator
&);
743 void moveToEndOfLastAtom()
749 if (atom
->isNewLine())
757 bool shouldWrap (const float x
) const
759 return (x
- 0.0001f
) >= wordWrapWidth
;
762 JUCE_LEAK_DETECTOR (Iterator
);
766 //==============================================================================
767 class TextEditor::InsertAction
: public UndoableAction
770 InsertAction (TextEditor
& owner_
,
772 const int insertIndex_
,
774 const Colour
& colour_
,
775 const int oldCaretPos_
,
776 const int newCaretPos_
)
779 insertIndex (insertIndex_
),
780 oldCaretPos (oldCaretPos_
),
781 newCaretPos (newCaretPos_
),
789 owner
.insert (text
, insertIndex
, font
, colour
, 0, newCaretPos
);
795 owner
.remove (Range
<int> (insertIndex
, insertIndex
+ text
.length()), 0, oldCaretPos
);
801 return text
.length() + 16;
807 const int insertIndex
, oldCaretPos
, newCaretPos
;
811 JUCE_DECLARE_NON_COPYABLE (InsertAction
);
814 //==============================================================================
815 class TextEditor::RemoveAction
: public UndoableAction
818 RemoveAction (TextEditor
& owner_
,
819 const Range
<int> range_
,
820 const int oldCaretPos_
,
821 const int newCaretPos_
,
822 const Array
<UniformTextSection
*>& removedSections_
)
825 oldCaretPos (oldCaretPos_
),
826 newCaretPos (newCaretPos_
),
827 removedSections (removedSections_
)
833 for (int i
= removedSections
.size(); --i
>= 0;)
835 UniformTextSection
* const section
= removedSections
.getUnchecked (i
);
843 owner
.remove (range
, 0, newCaretPos
);
849 owner
.reinsert (range
.getStart(), removedSections
);
850 owner
.moveCaretTo (oldCaretPos
, false);
858 for (int i
= removedSections
.size(); --i
>= 0;)
859 n
+= removedSections
.getUnchecked (i
)->getTotalLength();
866 const Range
<int> range
;
867 const int oldCaretPos
, newCaretPos
;
868 Array
<UniformTextSection
*> removedSections
;
870 JUCE_DECLARE_NON_COPYABLE (RemoveAction
);
873 //==============================================================================
874 class TextEditor::TextHolderComponent
: public Component
,
879 TextHolderComponent (TextEditor
& owner_
)
882 setWantsKeyboardFocus (false);
883 setInterceptsMouseClicks (false, true);
885 owner
.getTextValue().addListener (this);
888 ~TextHolderComponent()
890 owner
.getTextValue().removeListener (this);
893 void paint (Graphics
& g
)
895 owner
.drawContent (g
);
905 owner
.timerCallbackInt();
908 const MouseCursor
getMouseCursor()
910 return owner
.getMouseCursor();
913 void valueChanged (Value
&)
915 owner
.textWasChangedByValue();
921 JUCE_DECLARE_NON_COPYABLE (TextHolderComponent
);
924 //==============================================================================
925 class TextEditorViewport
: public Viewport
928 TextEditorViewport (TextEditor
& owner_
)
929 : owner (owner_
), lastWordWrapWidth (0), rentrant (false)
933 void visibleAreaChanged (const Rectangle
<int>&)
935 if (! rentrant
) // it's rare, but possible to get into a feedback loop as the viewport's scrollbars
936 // appear and disappear, causing the wrap width to change.
938 const float wordWrapWidth
= owner
.getWordWrapWidth();
940 if (wordWrapWidth
!= lastWordWrapWidth
)
942 lastWordWrapWidth
= wordWrapWidth
;
945 owner
.updateTextHolderSize();
953 float lastWordWrapWidth
;
956 JUCE_DECLARE_NON_COPYABLE (TextEditorViewport
);
959 //==============================================================================
960 namespace TextEditorDefs
962 const int textChangeMessageId
= 0x10003001;
963 const int returnKeyMessageId
= 0x10003002;
964 const int escapeKeyMessageId
= 0x10003003;
965 const int focusLossMessageId
= 0x10003004;
967 const int maxActionsPerTransaction
= 100;
969 int getCharacterCategory (const juce_wchar character
)
971 return CharacterFunctions::isLetterOrDigit (character
)
972 ? 2 : (CharacterFunctions::isWhitespace (character
) ? 0 : 1);
976 //==============================================================================
977 TextEditor::TextEditor (const String
& name
,
978 const juce_wchar passwordCharacter_
)
980 borderSize (1, 1, 1, 3),
984 returnKeyStartsNewLine (false),
985 popupMenuEnabled (true),
986 selectAllTextWhenFocused (false),
987 scrollbarVisible (true),
989 keepCaretOnScreen (true),
992 valueTextNeedsUpdating (false),
996 lastTransactionTime (0),
1000 passwordCharacter (passwordCharacter_
),
1001 dragType (notDragging
)
1005 addAndMakeVisible (viewport
= new TextEditorViewport (*this));
1006 viewport
->setViewedComponent (textHolder
= new TextHolderComponent (*this));
1007 viewport
->setWantsKeyboardFocus (false);
1008 viewport
->setScrollBarsShown (false, false);
1010 setWantsKeyboardFocus (true);
1011 setCaretVisible (true);
1014 TextEditor::~TextEditor()
1018 ComponentPeer
* const peer
= getPeer();
1019 if (peer
!= nullptr)
1020 peer
->dismissPendingTextInput();
1023 textValue
.referTo (Value());
1026 textHolder
= nullptr;
1029 //==============================================================================
1030 void TextEditor::newTransaction()
1032 lastTransactionTime
= Time::getApproximateMillisecondCounter();
1033 undoManager
.beginNewTransaction();
1036 bool TextEditor::undoOrRedo (const bool shouldUndo
)
1042 if (shouldUndo
? undoManager
.undo()
1043 : undoManager
.redo())
1045 scrollToMakeSureCursorIsVisible();
1055 bool TextEditor::undo() { return undoOrRedo (true); }
1056 bool TextEditor::redo() { return undoOrRedo (false); }
1058 //==============================================================================
1059 void TextEditor::setMultiLine (const bool shouldBeMultiLine
,
1060 const bool shouldWordWrap
)
1062 if (multiline
!= shouldBeMultiLine
1063 || wordWrap
!= (shouldWordWrap
&& shouldBeMultiLine
))
1065 multiline
= shouldBeMultiLine
;
1066 wordWrap
= shouldWordWrap
&& shouldBeMultiLine
;
1068 viewport
->setScrollBarsShown (scrollbarVisible
&& multiline
,
1069 scrollbarVisible
&& multiline
);
1070 viewport
->setViewPosition (0, 0);
1072 scrollToMakeSureCursorIsVisible();
1076 bool TextEditor::isMultiLine() const
1081 void TextEditor::setScrollbarsShown (bool shown
)
1083 if (scrollbarVisible
!= shown
)
1085 scrollbarVisible
= shown
;
1086 shown
= shown
&& isMultiLine();
1087 viewport
->setScrollBarsShown (shown
, shown
);
1091 void TextEditor::setReadOnly (const bool shouldBeReadOnly
)
1093 if (readOnly
!= shouldBeReadOnly
)
1095 readOnly
= shouldBeReadOnly
;
1096 enablementChanged();
1100 bool TextEditor::isReadOnly() const
1102 return readOnly
|| ! isEnabled();
1105 bool TextEditor::isTextInputActive() const
1107 return ! isReadOnly();
1110 void TextEditor::setReturnKeyStartsNewLine (const bool shouldStartNewLine
)
1112 returnKeyStartsNewLine
= shouldStartNewLine
;
1115 void TextEditor::setTabKeyUsedAsCharacter (const bool shouldTabKeyBeUsed
)
1117 tabKeyUsed
= shouldTabKeyBeUsed
;
1120 void TextEditor::setPopupMenuEnabled (const bool b
)
1122 popupMenuEnabled
= b
;
1125 void TextEditor::setSelectAllWhenFocused (const bool b
)
1127 selectAllTextWhenFocused
= b
;
1130 //==============================================================================
1131 const Font
& TextEditor::getFont() const
1136 void TextEditor::setFont (const Font
& newFont
)
1138 currentFont
= newFont
;
1139 scrollToMakeSureCursorIsVisible();
1142 void TextEditor::applyFontToAllText (const Font
& newFont
)
1144 currentFont
= newFont
;
1146 const Colour
overallColour (findColour (textColourId
));
1148 for (int i
= sections
.size(); --i
>= 0;)
1150 UniformTextSection
* const uts
= sections
.getUnchecked (i
);
1151 uts
->setFont (newFont
, passwordCharacter
);
1152 uts
->colour
= overallColour
;
1155 coalesceSimilarSections();
1156 updateTextHolderSize();
1157 scrollToMakeSureCursorIsVisible();
1161 void TextEditor::colourChanged()
1163 setOpaque (findColour (backgroundColourId
).isOpaque());
1167 void TextEditor::lookAndFeelChanged()
1169 if (isCaretVisible())
1171 setCaretVisible (false);
1172 setCaretVisible (true);
1176 void TextEditor::setCaretVisible (const bool shouldCaretBeVisible
)
1178 if (shouldCaretBeVisible
&& ! isReadOnly())
1180 if (caret
== nullptr)
1181 textHolder
->addChildComponent (caret
= getLookAndFeel().createCaretComponent (this));
1188 setMouseCursor (shouldCaretBeVisible
? MouseCursor::IBeamCursor
1189 : MouseCursor::NormalCursor
);
1192 void TextEditor::updateCaretPosition()
1194 if (caret
!= nullptr)
1195 caret
->setCaretPosition (getCaretRectangle().translated (leftIndent
, topIndent
));
1198 void TextEditor::setInputRestrictions (const int maxLen
,
1199 const String
& chars
)
1201 maxTextLength
= jmax (0, maxLen
);
1202 allowedCharacters
= chars
;
1205 void TextEditor::setTextToShowWhenEmpty (const String
& text
, const Colour
& colourToUse
)
1207 textToShowWhenEmpty
= text
;
1208 colourForTextWhenEmpty
= colourToUse
;
1211 void TextEditor::setPasswordCharacter (const juce_wchar newPasswordCharacter
)
1213 if (passwordCharacter
!= newPasswordCharacter
)
1215 passwordCharacter
= newPasswordCharacter
;
1221 void TextEditor::setScrollBarThickness (const int newThicknessPixels
)
1223 viewport
->setScrollBarThickness (newThicknessPixels
);
1226 void TextEditor::setScrollBarButtonVisibility (const bool buttonsVisible
)
1228 viewport
->setScrollBarButtonVisibility (buttonsVisible
);
1231 //==============================================================================
1232 void TextEditor::clear()
1235 updateTextHolderSize();
1236 undoManager
.clearUndoHistory();
1239 void TextEditor::setText (const String
& newText
,
1240 const bool sendTextChangeMessage
)
1242 const int newLength
= newText
.length();
1244 if (newLength
!= getTotalNumChars() || getText() != newText
)
1246 const int oldCursorPos
= caretPosition
;
1247 const bool cursorWasAtEnd
= oldCursorPos
>= getTotalNumChars();
1250 insert (newText
, 0, currentFont
, findColour (textColourId
), 0, caretPosition
);
1252 // if you're adding text with line-feeds to a single-line text editor, it
1253 // ain't gonna look right!
1254 jassert (multiline
|| ! newText
.containsAnyOf ("\r\n"));
1256 if (cursorWasAtEnd
&& ! isMultiLine())
1257 moveCaretTo (getTotalNumChars(), false);
1259 moveCaretTo (oldCursorPos
, false);
1261 if (sendTextChangeMessage
)
1264 updateTextHolderSize();
1265 scrollToMakeSureCursorIsVisible();
1266 undoManager
.clearUndoHistory();
1272 //==============================================================================
1273 void TextEditor::updateValueFromText()
1275 if (valueTextNeedsUpdating
)
1277 valueTextNeedsUpdating
= false;
1278 textValue
= getText();
1282 Value
& TextEditor::getTextValue()
1284 updateValueFromText();
1288 void TextEditor::textWasChangedByValue()
1290 if (textValue
.getValueSource().getReferenceCount() > 1)
1291 setText (textValue
.getValue());
1294 //==============================================================================
1295 void TextEditor::textChanged()
1297 updateTextHolderSize();
1298 postCommandMessage (TextEditorDefs::textChangeMessageId
);
1300 if (textValue
.getValueSource().getReferenceCount() > 1)
1302 valueTextNeedsUpdating
= false;
1303 textValue
= getText();
1307 void TextEditor::returnPressed()
1309 postCommandMessage (TextEditorDefs::returnKeyMessageId
);
1312 void TextEditor::escapePressed()
1314 postCommandMessage (TextEditorDefs::escapeKeyMessageId
);
1317 void TextEditor::addListener (TextEditorListener
* const newListener
)
1319 listeners
.add (newListener
);
1322 void TextEditor::removeListener (TextEditorListener
* const listenerToRemove
)
1324 listeners
.remove (listenerToRemove
);
1327 //==============================================================================
1328 void TextEditor::timerCallbackInt()
1330 if (hasKeyboardFocus (false) && ! isCurrentlyBlockedByAnotherModalComponent())
1333 const unsigned int now
= Time::getApproximateMillisecondCounter();
1335 if (now
> lastTransactionTime
+ 200)
1339 void TextEditor::repaintText (const Range
<int>& range
)
1341 if (! range
.isEmpty())
1343 float x
= 0, y
= 0, lh
= currentFont
.getHeight();
1345 const float wordWrapWidth
= getWordWrapWidth();
1347 if (wordWrapWidth
> 0)
1349 Iterator
i (sections
, wordWrapWidth
, passwordCharacter
);
1351 i
.getCharPosition (range
.getStart(), x
, y
, lh
);
1353 const int y1
= (int) y
;
1356 if (range
.getEnd() >= getTotalNumChars())
1358 y2
= textHolder
->getHeight();
1362 i
.getCharPosition (range
.getEnd(), x
, y
, lh
);
1363 y2
= (int) (y
+ lh
* 2.0f
);
1366 textHolder
->repaint (0, y1
, textHolder
->getWidth(), y2
- y1
);
1371 //==============================================================================
1372 void TextEditor::moveCaret (int newCaretPos
)
1374 if (newCaretPos
< 0)
1376 else if (newCaretPos
> getTotalNumChars())
1377 newCaretPos
= getTotalNumChars();
1379 if (newCaretPos
!= getCaretPosition())
1381 caretPosition
= newCaretPos
;
1382 textHolder
->restartTimer();
1383 scrollToMakeSureCursorIsVisible();
1384 updateCaretPosition();
1388 void TextEditor::setCaretPosition (const int newIndex
)
1390 moveCaretTo (newIndex
, false);
1393 int TextEditor::getCaretPosition() const
1395 return caretPosition
;
1398 void TextEditor::scrollEditorToPositionCaret (const int desiredCaretX
,
1399 const int desiredCaretY
)
1402 updateCaretPosition();
1404 const Rectangle
<int> caretPos (getCaretRectangle());
1406 int vx
= caretPos
.getX() - desiredCaretX
;
1407 int vy
= caretPos
.getY() - desiredCaretY
;
1409 if (desiredCaretX
< jmax (1, proportionOfWidth (0.05f
)))
1410 vx
+= desiredCaretX
- proportionOfWidth (0.2f
);
1411 else if (desiredCaretX
> jmax (0, viewport
->getMaximumVisibleWidth() - (wordWrap
? 2 : 10)))
1412 vx
+= desiredCaretX
+ (isMultiLine() ? proportionOfWidth (0.2f
) : 10) - viewport
->getMaximumVisibleWidth();
1414 vx
= jlimit (0, jmax (0, textHolder
->getWidth() + 8 - viewport
->getMaximumVisibleWidth()), vx
);
1416 if (! isMultiLine())
1418 vy
= viewport
->getViewPositionY();
1422 vy
= jlimit (0, jmax (0, textHolder
->getHeight() - viewport
->getMaximumVisibleHeight()), vy
);
1424 if (desiredCaretY
< 0)
1425 vy
= jmax (0, desiredCaretY
+ vy
);
1426 else if (desiredCaretY
> jmax (0, viewport
->getMaximumVisibleHeight() - topIndent
- caretPos
.getHeight()))
1427 vy
+= desiredCaretY
+ 2 + caretPos
.getHeight() + topIndent
- viewport
->getMaximumVisibleHeight();
1430 viewport
->setViewPosition (vx
, vy
);
1433 const Rectangle
<int> TextEditor::getCaretRectangle()
1435 float cursorX
, cursorY
;
1436 float cursorHeight
= currentFont
.getHeight(); // (in case the text is empty and the call below doesn't set this value)
1437 getCharPosition (caretPosition
, cursorX
, cursorY
, cursorHeight
);
1439 return Rectangle
<int> (roundToInt (cursorX
), roundToInt (cursorY
), 2, roundToInt (cursorHeight
));
1442 //==============================================================================
1443 float TextEditor::getWordWrapWidth() const
1445 return wordWrap
? (float) (viewport
->getMaximumVisibleWidth() - leftIndent
- leftIndent
/ 2)
1446 : std::numeric_limits
<float>::max();
1449 void TextEditor::updateTextHolderSize()
1451 const float wordWrapWidth
= getWordWrapWidth();
1453 if (wordWrapWidth
> 0)
1455 float maxWidth
= 0.0f
;
1457 Iterator
i (sections
, wordWrapWidth
, passwordCharacter
);
1460 maxWidth
= jmax (maxWidth
, i
.atomRight
);
1462 const int w
= leftIndent
+ roundToInt (maxWidth
);
1463 const int h
= topIndent
+ roundToInt (jmax (i
.lineY
+ i
.lineHeight
,
1464 currentFont
.getHeight()));
1466 textHolder
->setSize (w
+ 2, h
+ 1); // (the +2 allows a bit of space for the cursor to be at the right-hand-edge)
1470 int TextEditor::getTextWidth() const
1472 return textHolder
->getWidth();
1475 int TextEditor::getTextHeight() const
1477 return textHolder
->getHeight();
1480 void TextEditor::setIndents (const int newLeftIndent
,
1481 const int newTopIndent
)
1483 leftIndent
= newLeftIndent
;
1484 topIndent
= newTopIndent
;
1487 void TextEditor::setBorder (const BorderSize
<int>& border
)
1489 borderSize
= border
;
1493 const BorderSize
<int> TextEditor::getBorder() const
1498 void TextEditor::setScrollToShowCursor (const bool shouldScrollToShowCursor
)
1500 keepCaretOnScreen
= shouldScrollToShowCursor
;
1503 void TextEditor::scrollToMakeSureCursorIsVisible()
1505 updateCaretPosition();
1507 if (keepCaretOnScreen
)
1509 int x
= viewport
->getViewPositionX();
1510 int y
= viewport
->getViewPositionY();
1512 const Rectangle
<int> caretPos (getCaretRectangle());
1514 const int relativeCursorX
= caretPos
.getX() - x
;
1515 const int relativeCursorY
= caretPos
.getY() - y
;
1517 if (relativeCursorX
< jmax (1, proportionOfWidth (0.05f
)))
1519 x
+= relativeCursorX
- proportionOfWidth (0.2f
);
1521 else if (relativeCursorX
> jmax (0, viewport
->getMaximumVisibleWidth() - (wordWrap
? 2 : 10)))
1523 x
+= relativeCursorX
+ (isMultiLine() ? proportionOfWidth (0.2f
) : 10) - viewport
->getMaximumVisibleWidth();
1526 x
= jlimit (0, jmax (0, textHolder
->getWidth() + 8 - viewport
->getMaximumVisibleWidth()), x
);
1528 if (! isMultiLine())
1530 y
= (getHeight() - textHolder
->getHeight() - topIndent
) / -2;
1534 if (relativeCursorY
< 0)
1536 y
= jmax (0, relativeCursorY
+ y
);
1538 else if (relativeCursorY
> jmax (0, viewport
->getMaximumVisibleHeight() - topIndent
- caretPos
.getHeight()))
1540 y
+= relativeCursorY
+ 2 + caretPos
.getHeight() + topIndent
- viewport
->getMaximumVisibleHeight();
1544 viewport
->setViewPosition (x
, y
);
1548 void TextEditor::moveCaretTo (const int newPosition
,
1549 const bool isSelecting
)
1553 moveCaret (newPosition
);
1555 const Range
<int> oldSelection (selection
);
1557 if (dragType
== notDragging
)
1559 if (abs (getCaretPosition() - selection
.getStart()) < abs (getCaretPosition() - selection
.getEnd()))
1560 dragType
= draggingSelectionStart
;
1562 dragType
= draggingSelectionEnd
;
1565 if (dragType
== draggingSelectionStart
)
1567 if (getCaretPosition() >= selection
.getEnd())
1568 dragType
= draggingSelectionEnd
;
1570 selection
= Range
<int>::between (getCaretPosition(), selection
.getEnd());
1574 if (getCaretPosition() < selection
.getStart())
1575 dragType
= draggingSelectionStart
;
1577 selection
= Range
<int>::between (getCaretPosition(), selection
.getStart());
1580 repaintText (selection
.getUnionWith (oldSelection
));
1584 dragType
= notDragging
;
1586 repaintText (selection
);
1588 moveCaret (newPosition
);
1589 selection
= Range
<int>::emptyRange (getCaretPosition());
1593 int TextEditor::getTextIndexAt (const int x
,
1596 return indexAtPosition ((float) (x
+ viewport
->getViewPositionX() - leftIndent
),
1597 (float) (y
+ viewport
->getViewPositionY() - topIndent
));
1600 void TextEditor::insertTextAtCaret (const String
& newText_
)
1602 String
newText (newText_
);
1604 if (allowedCharacters
.isNotEmpty())
1605 newText
= newText
.retainCharacters (allowedCharacters
);
1607 if (! isMultiLine())
1608 newText
= newText
.replaceCharacters ("\r\n", " ");
1610 newText
= newText
.replace ("\r\n", "\n");
1612 const int newCaretPos
= selection
.getStart() + newText
.length();
1613 const int insertIndex
= selection
.getStart();
1615 remove (selection
, getUndoManager(),
1616 newText
.isNotEmpty() ? newCaretPos
- 1 : newCaretPos
);
1618 if (maxTextLength
> 0)
1619 newText
= newText
.substring (0, maxTextLength
- getTotalNumChars());
1621 if (newText
.isNotEmpty())
1625 findColour (textColourId
),
1632 void TextEditor::setHighlightedRegion (const Range
<int>& newSelection
)
1634 moveCaretTo (newSelection
.getStart(), false);
1635 moveCaretTo (newSelection
.getEnd(), true);
1638 //==============================================================================
1639 void TextEditor::copy()
1641 if (passwordCharacter
== 0)
1643 const String
selectedText (getHighlightedText());
1645 if (selectedText
.isNotEmpty())
1646 SystemClipboard::copyTextToClipboard (selectedText
);
1650 void TextEditor::paste()
1654 const String
clip (SystemClipboard::getTextFromClipboard());
1656 if (clip
.isNotEmpty())
1657 insertTextAtCaret (clip
);
1661 void TextEditor::cut()
1665 moveCaret (selection
.getEnd());
1666 insertTextAtCaret (String::empty
);
1670 //==============================================================================
1671 void TextEditor::drawContent (Graphics
& g
)
1673 const float wordWrapWidth
= getWordWrapWidth();
1675 if (wordWrapWidth
> 0)
1677 g
.setOrigin (leftIndent
, topIndent
);
1678 const Rectangle
<int> clip (g
.getClipBounds());
1679 Colour selectedTextColour
;
1681 Iterator
i (sections
, wordWrapWidth
, passwordCharacter
);
1683 while (i
.lineY
+ 200.0 < clip
.getY() && i
.next())
1686 if (! selection
.isEmpty())
1688 g
.setColour (findColour (highlightColourId
).withMultipliedAlpha (hasKeyboardFocus (true) ? 1.0f
: 0.5f
));
1690 selectedTextColour
= findColour (highlightedTextColourId
);
1694 while (i2
.next() && i2
.lineY
< clip
.getBottom())
1696 if (i2
.lineY
+ i2
.lineHeight
>= clip
.getY()
1697 && selection
.intersects (Range
<int> (i2
.indexInText
, i2
.indexInText
+ i2
.atom
->numChars
)))
1699 i2
.drawSelection (g
, selection
);
1704 const UniformTextSection
* lastSection
= nullptr;
1706 while (i
.next() && i
.lineY
< clip
.getBottom())
1708 if (i
.lineY
+ i
.lineHeight
>= clip
.getY())
1710 if (selection
.intersects (Range
<int> (i
.indexInText
, i
.indexInText
+ i
.atom
->numChars
)))
1712 i
.drawSelectedText (g
, selection
, selectedTextColour
);
1713 lastSection
= nullptr;
1717 i
.draw (g
, lastSection
);
1722 for (int j
= underlinedSections
.size(); --j
>= 0;)
1724 const Range
<int>& underlinedSection
= underlinedSections
.getReference (j
);
1726 Iterator
i2 (sections
, wordWrapWidth
, passwordCharacter
);
1728 while (i2
.next() && i2
.lineY
< clip
.getBottom())
1730 if (i2
.lineY
+ i2
.lineHeight
>= clip
.getY()
1731 && underlinedSection
.intersects (Range
<int> (i2
.indexInText
, i2
.indexInText
+ i2
.atom
->numChars
)))
1733 i2
.drawUnderline (g
, underlinedSection
, findColour (textColourId
));
1740 void TextEditor::paint (Graphics
& g
)
1742 getLookAndFeel().fillTextEditorBackground (g
, getWidth(), getHeight(), *this);
1745 void TextEditor::paintOverChildren (Graphics
& g
)
1747 if (textToShowWhenEmpty
.isNotEmpty()
1748 && (! hasKeyboardFocus (false))
1749 && getTotalNumChars() == 0)
1751 g
.setColour (colourForTextWhenEmpty
);
1752 g
.setFont (getFont());
1756 g
.drawText (textToShowWhenEmpty
,
1757 0, 0, getWidth(), getHeight(),
1758 Justification::centred
, true);
1762 g
.drawText (textToShowWhenEmpty
,
1763 leftIndent
, topIndent
,
1764 viewport
->getWidth() - leftIndent
,
1765 viewport
->getHeight() - topIndent
,
1766 Justification::centredLeft
, true);
1770 getLookAndFeel().drawTextEditorOutline (g
, getWidth(), getHeight(), *this);
1773 //==============================================================================
1774 static void textEditorMenuCallback (int menuResult
, TextEditor
* editor
)
1776 if (editor
!= nullptr && menuResult
!= 0)
1777 editor
->performPopupMenuAction (menuResult
);
1780 void TextEditor::mouseDown (const MouseEvent
& e
)
1782 beginDragAutoRepeat (100);
1785 if (wasFocused
|| ! selectAllTextWhenFocused
)
1787 if (! (popupMenuEnabled
&& e
.mods
.isPopupMenu()))
1789 moveCaretTo (getTextIndexAt (e
.x
, e
.y
),
1790 e
.mods
.isShiftDown());
1795 m
.setLookAndFeel (&getLookAndFeel());
1796 addPopupMenuItems (m
, &e
);
1798 m
.showMenuAsync (PopupMenu::Options(),
1799 ModalCallbackFunction::forComponent (textEditorMenuCallback
, this));
1804 void TextEditor::mouseDrag (const MouseEvent
& e
)
1806 if (wasFocused
|| ! selectAllTextWhenFocused
)
1808 if (! (popupMenuEnabled
&& e
.mods
.isPopupMenu()))
1810 moveCaretTo (getTextIndexAt (e
.x
, e
.y
), true);
1815 void TextEditor::mouseUp (const MouseEvent
& e
)
1818 textHolder
->restartTimer();
1820 if (wasFocused
|| ! selectAllTextWhenFocused
)
1822 if (e
.mouseWasClicked() && ! (popupMenuEnabled
&& e
.mods
.isPopupMenu()))
1824 moveCaret (getTextIndexAt (e
.x
, e
.y
));
1831 void TextEditor::mouseDoubleClick (const MouseEvent
& e
)
1833 int tokenEnd
= getTextIndexAt (e
.x
, e
.y
);
1834 int tokenStart
= tokenEnd
;
1836 if (e
.getNumberOfClicks() > 3)
1839 tokenEnd
= getTotalNumChars();
1843 const String
t (getText());
1844 const int totalLength
= getTotalNumChars();
1846 while (tokenEnd
< totalLength
)
1848 // (note the slight bodge here - it's because iswalnum only checks for alphabetic chars in the current locale)
1849 const juce_wchar c
= t
[tokenEnd
];
1850 if (CharacterFunctions::isLetterOrDigit (c
) || c
> 128)
1856 tokenStart
= tokenEnd
;
1858 while (tokenStart
> 0)
1860 // (note the slight bodge here - it's because iswalnum only checks for alphabetic chars in the current locale)
1861 const juce_wchar c
= t
[tokenStart
- 1];
1862 if (CharacterFunctions::isLetterOrDigit (c
) || c
> 128)
1868 if (e
.getNumberOfClicks() > 2)
1870 while (tokenEnd
< totalLength
)
1872 const juce_wchar c
= t
[tokenEnd
];
1873 if (c
!= '\r' && c
!= '\n')
1879 while (tokenStart
> 0)
1881 const juce_wchar c
= t
[tokenStart
- 1];
1882 if (c
!= '\r' && c
!= '\n')
1890 moveCaretTo (tokenEnd
, false);
1891 moveCaretTo (tokenStart
, true);
1894 void TextEditor::mouseWheelMove (const MouseEvent
& e
, float wheelIncrementX
, float wheelIncrementY
)
1896 if (! viewport
->useMouseWheelMoveIfNeeded (e
, wheelIncrementX
, wheelIncrementY
))
1897 Component::mouseWheelMove (e
, wheelIncrementX
, wheelIncrementY
);
1900 //==============================================================================
1901 bool TextEditor::moveCaretWithTransation (const int newPos
, const bool selecting
)
1904 moveCaretTo (newPos
, selecting
);
1908 bool TextEditor::moveCaretLeft (bool moveInWholeWordSteps
, bool selecting
)
1910 int pos
= getCaretPosition();
1912 if (moveInWholeWordSteps
)
1913 pos
= findWordBreakBefore (pos
);
1917 return moveCaretWithTransation (pos
, selecting
);
1920 bool TextEditor::moveCaretRight (bool moveInWholeWordSteps
, bool selecting
)
1922 int pos
= getCaretPosition();
1924 if (moveInWholeWordSteps
)
1925 pos
= findWordBreakAfter (pos
);
1929 return moveCaretWithTransation (pos
, selecting
);
1932 bool TextEditor::moveCaretUp (bool selecting
)
1934 if (! isMultiLine())
1935 return moveCaretToStartOfLine (selecting
);
1937 const Rectangle
<float> caretPos (getCaretRectangle().toFloat());
1938 return moveCaretWithTransation (indexAtPosition (caretPos
.getX(), caretPos
.getY() - 1.0f
), selecting
);
1941 bool TextEditor::moveCaretDown (bool selecting
)
1943 if (! isMultiLine())
1944 return moveCaretToEndOfLine (selecting
);
1946 const Rectangle
<float> caretPos (getCaretRectangle().toFloat());
1947 return moveCaretWithTransation (indexAtPosition (caretPos
.getX(), caretPos
.getBottom() + 1.0f
), selecting
);
1950 bool TextEditor::pageUp (bool selecting
)
1952 if (! isMultiLine())
1953 return moveCaretToStartOfLine (selecting
);
1955 const Rectangle
<float> caretPos (getCaretRectangle().toFloat());
1956 return moveCaretWithTransation (indexAtPosition (caretPos
.getX(), caretPos
.getY() - viewport
->getViewHeight()), selecting
);
1959 bool TextEditor::pageDown (bool selecting
)
1961 if (! isMultiLine())
1962 return moveCaretToEndOfLine (selecting
);
1964 const Rectangle
<float> caretPos (getCaretRectangle().toFloat());
1965 return moveCaretWithTransation (indexAtPosition (caretPos
.getX(), caretPos
.getBottom() + viewport
->getViewHeight()), selecting
);
1968 void TextEditor::scrollByLines (int deltaLines
)
1970 ScrollBar
* scrollbar
= viewport
->getVerticalScrollBar();
1972 if (scrollbar
!= nullptr)
1973 scrollbar
->moveScrollbarInSteps (deltaLines
);
1976 bool TextEditor::scrollDown()
1982 bool TextEditor::scrollUp()
1988 bool TextEditor::moveCaretToTop (bool selecting
)
1990 return moveCaretWithTransation (0, selecting
);
1993 bool TextEditor::moveCaretToStartOfLine (bool selecting
)
1995 const Rectangle
<float> caretPos (getCaretRectangle().toFloat());
1996 return moveCaretWithTransation (indexAtPosition (0.0f
, caretPos
.getY()), selecting
);
1999 bool TextEditor::moveCaretToEnd (bool selecting
)
2001 return moveCaretWithTransation (getTotalNumChars(), selecting
);
2004 bool TextEditor::moveCaretToEndOfLine (bool selecting
)
2006 const Rectangle
<float> caretPos (getCaretRectangle().toFloat());
2007 return moveCaretWithTransation (indexAtPosition ((float) textHolder
->getWidth(), caretPos
.getY()), selecting
);
2010 bool TextEditor::deleteBackwards (bool moveInWholeWordSteps
)
2012 if (moveInWholeWordSteps
)
2013 moveCaretTo (findWordBreakBefore (getCaretPosition()), true);
2014 else if (selection
.isEmpty() && selection
.getStart() > 0)
2015 selection
.setStart (selection
.getEnd() - 1);
2021 bool TextEditor::deleteForwards (bool /*moveInWholeWordSteps*/)
2023 if (selection
.isEmpty() && selection
.getStart() < getTotalNumChars())
2024 selection
.setEnd (selection
.getStart() + 1);
2030 bool TextEditor::copyToClipboard()
2037 bool TextEditor::cutToClipboard()
2045 bool TextEditor::pasteFromClipboard()
2052 bool TextEditor::selectAll()
2055 moveCaretTo (getTotalNumChars(), false);
2056 moveCaretTo (0, true);
2060 //==============================================================================
2061 bool TextEditor::keyPressed (const KeyPress
& key
)
2063 if (isReadOnly() && key
!= KeyPress ('c', ModifierKeys::commandModifier
, 0))
2066 if (! TextEditorKeyMapper
<TextEditor
>::invokeKeyFunction (*this, key
))
2068 if (key
== KeyPress::returnKey
)
2072 if (returnKeyStartsNewLine
)
2073 insertTextAtCaret ("\n");
2077 else if (key
.isKeyCode (KeyPress::escapeKey
))
2080 moveCaretTo (getCaretPosition(), false);
2083 else if (key
.getTextCharacter() >= ' '
2084 || (tabKeyUsed
&& (key
.getTextCharacter() == '\t')))
2086 insertTextAtCaret (String::charToString (key
.getTextCharacter()));
2088 lastTransactionTime
= Time::getApproximateMillisecondCounter();
2099 bool TextEditor::keyStateChanged (const bool isKeyDown
)
2105 if (KeyPress (KeyPress::F4Key
, ModifierKeys::altModifier
, 0).isCurrentlyDown())
2106 return false; // We need to explicitly allow alt-F4 to pass through on Windows
2109 // (overridden to avoid forwarding key events to the parent)
2110 return ! ModifierKeys::getCurrentModifiers().isCommandDown();
2113 //==============================================================================
2114 const int baseMenuItemID
= 0x7fff0000;
2116 void TextEditor::addPopupMenuItems (PopupMenu
& m
, const MouseEvent
*)
2118 const bool writable
= ! isReadOnly();
2120 if (passwordCharacter
== 0)
2122 m
.addItem (baseMenuItemID
+ 1, TRANS("cut"), writable
);
2123 m
.addItem (baseMenuItemID
+ 2, TRANS("copy"), ! selection
.isEmpty());
2124 m
.addItem (baseMenuItemID
+ 3, TRANS("paste"), writable
);
2127 m
.addItem (baseMenuItemID
+ 4, TRANS("delete"), writable
);
2129 m
.addItem (baseMenuItemID
+ 5, TRANS("select all"));
2132 if (getUndoManager() != nullptr)
2134 m
.addItem (baseMenuItemID
+ 6, TRANS("undo"), undoManager
.canUndo());
2135 m
.addItem (baseMenuItemID
+ 7, TRANS("redo"), undoManager
.canRedo());
2139 void TextEditor::performPopupMenuAction (const int menuItemID
)
2143 case baseMenuItemID
+ 1: cutToClipboard(); break;
2144 case baseMenuItemID
+ 2: copyToClipboard(); break;
2145 case baseMenuItemID
+ 3: pasteFromClipboard(); break;
2146 case baseMenuItemID
+ 4: cut(); break;
2147 case baseMenuItemID
+ 5: selectAll(); break;
2148 case baseMenuItemID
+ 6: undo(); break;
2149 case baseMenuItemID
+ 7: redo(); break;
2154 //==============================================================================
2155 void TextEditor::focusGained (FocusChangeType
)
2159 if (selectAllTextWhenFocused
)
2161 moveCaretTo (0, false);
2162 moveCaretTo (getTotalNumChars(), true);
2166 updateCaretPosition();
2168 ComponentPeer
* const peer
= getPeer();
2169 if (peer
!= nullptr && ! isReadOnly())
2170 peer
->textInputRequired (getScreenPosition() - peer
->getScreenPosition());
2173 void TextEditor::focusLost (FocusChangeType
)
2178 textHolder
->stopTimer();
2180 underlinedSections
.clear();
2182 ComponentPeer
* const peer
= getPeer();
2183 if (peer
!= nullptr)
2184 peer
->dismissPendingTextInput();
2186 updateCaretPosition();
2188 postCommandMessage (TextEditorDefs::focusLossMessageId
);
2192 //==============================================================================
2193 void TextEditor::resized()
2195 viewport
->setBoundsInset (borderSize
);
2196 viewport
->setSingleStepSizes (16, roundToInt (currentFont
.getHeight()));
2198 updateTextHolderSize();
2200 if (! isMultiLine())
2202 scrollToMakeSureCursorIsVisible();
2206 updateCaretPosition();
2210 void TextEditor::handleCommandMessage (const int commandId
)
2212 Component::BailOutChecker
checker (this);
2216 case TextEditorDefs::textChangeMessageId
:
2217 listeners
.callChecked (checker
, &TextEditorListener::textEditorTextChanged
, (TextEditor
&) *this);
2220 case TextEditorDefs::returnKeyMessageId
:
2221 listeners
.callChecked (checker
, &TextEditorListener::textEditorReturnKeyPressed
, (TextEditor
&) *this);
2224 case TextEditorDefs::escapeKeyMessageId
:
2225 listeners
.callChecked (checker
, &TextEditorListener::textEditorEscapeKeyPressed
, (TextEditor
&) *this);
2228 case TextEditorDefs::focusLossMessageId
:
2229 updateValueFromText();
2230 listeners
.callChecked (checker
, &TextEditorListener::textEditorFocusLost
, (TextEditor
&) *this);
2239 void TextEditor::enablementChanged()
2241 setMouseCursor (isReadOnly() ? MouseCursor::NormalCursor
2242 : MouseCursor::IBeamCursor
);
2246 void TextEditor::setTemporaryUnderlining (const Array
<Range
<int> >& newUnderlinedSections
)
2248 underlinedSections
= newUnderlinedSections
;
2252 //==============================================================================
2253 UndoManager
* TextEditor::getUndoManager() noexcept
2255 return isReadOnly() ? 0 : &undoManager
;
2258 void TextEditor::clearInternal (UndoManager
* const um
)
2260 remove (Range
<int> (0, getTotalNumChars()), um
, caretPosition
);
2263 void TextEditor::insert (const String
& text
,
2264 const int insertIndex
,
2266 const Colour
& colour
,
2267 UndoManager
* const um
,
2268 const int caretPositionToMoveTo
)
2270 if (text
.isNotEmpty())
2274 if (um
->getNumActionsInCurrentTransaction() > TextEditorDefs::maxActionsPerTransaction
)
2277 um
->perform (new InsertAction (*this, text
, insertIndex
, font
, colour
,
2278 caretPosition
, caretPositionToMoveTo
));
2282 repaintText (Range
<int> (insertIndex
, getTotalNumChars())); // must do this before and after changing the data, in case
2283 // a line gets moved due to word wrap
2288 for (int i
= 0; i
< sections
.size(); ++i
)
2290 nextIndex
= index
+ sections
.getUnchecked (i
)->getTotalLength();
2292 if (insertIndex
== index
)
2294 sections
.insert (i
, new UniformTextSection (text
,
2296 passwordCharacter
));
2299 else if (insertIndex
> index
&& insertIndex
< nextIndex
)
2301 splitSection (i
, insertIndex
- index
);
2302 sections
.insert (i
+ 1, new UniformTextSection (text
,
2304 passwordCharacter
));
2311 if (nextIndex
== insertIndex
)
2312 sections
.add (new UniformTextSection (text
,
2314 passwordCharacter
));
2316 coalesceSimilarSections();
2318 valueTextNeedsUpdating
= true;
2320 updateTextHolderSize();
2321 moveCaretTo (caretPositionToMoveTo
, false);
2323 repaintText (Range
<int> (insertIndex
, getTotalNumChars()));
2328 void TextEditor::reinsert (const int insertIndex
,
2329 const Array
<UniformTextSection
*>& sectionsToInsert
)
2334 for (int i
= 0; i
< sections
.size(); ++i
)
2336 nextIndex
= index
+ sections
.getUnchecked (i
)->getTotalLength();
2338 if (insertIndex
== index
)
2340 for (int j
= sectionsToInsert
.size(); --j
>= 0;)
2341 sections
.insert (i
, new UniformTextSection (*sectionsToInsert
.getUnchecked(j
)));
2345 else if (insertIndex
> index
&& insertIndex
< nextIndex
)
2347 splitSection (i
, insertIndex
- index
);
2349 for (int j
= sectionsToInsert
.size(); --j
>= 0;)
2350 sections
.insert (i
+ 1, new UniformTextSection (*sectionsToInsert
.getUnchecked(j
)));
2358 if (nextIndex
== insertIndex
)
2360 for (int j
= 0; j
< sectionsToInsert
.size(); ++j
)
2361 sections
.add (new UniformTextSection (*sectionsToInsert
.getUnchecked(j
)));
2364 coalesceSimilarSections();
2366 valueTextNeedsUpdating
= true;
2369 void TextEditor::remove (const Range
<int>& range
,
2370 UndoManager
* const um
,
2371 const int caretPositionToMoveTo
)
2373 if (! range
.isEmpty())
2377 for (int i
= 0; i
< sections
.size(); ++i
)
2379 const int nextIndex
= index
+ sections
.getUnchecked(i
)->getTotalLength();
2381 if (range
.getStart() > index
&& range
.getStart() < nextIndex
)
2383 splitSection (i
, range
.getStart() - index
);
2386 else if (range
.getEnd() > index
&& range
.getEnd() < nextIndex
)
2388 splitSection (i
, range
.getEnd() - index
);
2395 if (index
> range
.getEnd())
2404 Array
<UniformTextSection
*> removedSections
;
2406 for (int i
= 0; i
< sections
.size(); ++i
)
2408 if (range
.getEnd() <= range
.getStart())
2411 UniformTextSection
* const section
= sections
.getUnchecked (i
);
2413 const int nextIndex
= index
+ section
->getTotalLength();
2415 if (range
.getStart() <= index
&& range
.getEnd() >= nextIndex
)
2416 removedSections
.add (new UniformTextSection (*section
));
2421 if (um
->getNumActionsInCurrentTransaction() > TextEditorDefs::maxActionsPerTransaction
)
2424 um
->perform (new RemoveAction (*this, range
, caretPosition
,
2425 caretPositionToMoveTo
, removedSections
));
2429 Range
<int> remainingRange (range
);
2431 for (int i
= 0; i
< sections
.size(); ++i
)
2433 UniformTextSection
* const section
= sections
.getUnchecked (i
);
2435 const int nextIndex
= index
+ section
->getTotalLength();
2437 if (remainingRange
.getStart() <= index
&& remainingRange
.getEnd() >= nextIndex
)
2443 remainingRange
.setEnd (remainingRange
.getEnd() - (nextIndex
- index
));
2444 if (remainingRange
.isEmpty())
2455 coalesceSimilarSections();
2457 valueTextNeedsUpdating
= true;
2459 moveCaretTo (caretPositionToMoveTo
, false);
2461 repaintText (Range
<int> (range
.getStart(), getTotalNumChars()));
2466 //==============================================================================
2467 String
TextEditor::getText() const
2469 MemoryOutputStream mo
;
2470 mo
.preallocate (getTotalNumChars());
2472 for (int i
= 0; i
< sections
.size(); ++i
)
2473 sections
.getUnchecked (i
)->appendAllText (mo
);
2475 return mo
.toString();
2478 const String
TextEditor::getTextInRange (const Range
<int>& range
) const
2480 if (range
.isEmpty())
2481 return String::empty
;
2483 MemoryOutputStream mo
;
2484 mo
.preallocate (jmin (getTotalNumChars(), range
.getLength()));
2488 for (int i
= 0; i
< sections
.size(); ++i
)
2490 const UniformTextSection
* const s
= sections
.getUnchecked (i
);
2491 const int nextIndex
= index
+ s
->getTotalLength();
2493 if (range
.getStart() < nextIndex
)
2495 if (range
.getEnd() <= index
)
2498 s
->appendSubstring (mo
, range
- index
);
2504 return mo
.toString();
2507 String
TextEditor::getHighlightedText() const
2509 return getTextInRange (selection
);
2512 int TextEditor::getTotalNumChars() const
2514 if (totalNumChars
< 0)
2518 for (int i
= sections
.size(); --i
>= 0;)
2519 totalNumChars
+= sections
.getUnchecked (i
)->getTotalLength();
2522 return totalNumChars
;
2525 bool TextEditor::isEmpty() const
2527 return getTotalNumChars() == 0;
2530 void TextEditor::getCharPosition (const int index
, float& cx
, float& cy
, float& lineHeight
) const
2532 const float wordWrapWidth
= getWordWrapWidth();
2534 if (wordWrapWidth
> 0 && sections
.size() > 0)
2536 Iterator
i (sections
, wordWrapWidth
, passwordCharacter
);
2538 i
.getCharPosition (index
, cx
, cy
, lineHeight
);
2543 lineHeight
= currentFont
.getHeight();
2547 int TextEditor::indexAtPosition (const float x
, const float y
)
2549 const float wordWrapWidth
= getWordWrapWidth();
2551 if (wordWrapWidth
> 0)
2553 Iterator
i (sections
, wordWrapWidth
, passwordCharacter
);
2557 if (i
.lineY
+ i
.lineHeight
> y
)
2560 return jmax (0, i
.indexInText
- 1);
2563 return i
.indexInText
;
2565 if (x
< i
.atomRight
)
2566 return i
.xToIndex (x
);
2571 return getTotalNumChars();
2574 //==============================================================================
2575 int TextEditor::findWordBreakAfter (const int position
) const
2577 const String
t (getTextInRange (Range
<int> (position
, position
+ 512)));
2578 const int totalLength
= t
.length();
2581 while (i
< totalLength
&& CharacterFunctions::isWhitespace (t
[i
]))
2584 const int type
= TextEditorDefs::getCharacterCategory (t
[i
]);
2586 while (i
< totalLength
&& type
== TextEditorDefs::getCharacterCategory (t
[i
]))
2589 while (i
< totalLength
&& CharacterFunctions::isWhitespace (t
[i
]))
2592 return position
+ i
;
2595 int TextEditor::findWordBreakBefore (const int position
) const
2600 const int startOfBuffer
= jmax (0, position
- 512);
2601 const String
t (getTextInRange (Range
<int> (startOfBuffer
, position
)));
2603 int i
= position
- startOfBuffer
;
2605 while (i
> 0 && CharacterFunctions::isWhitespace (t
[i
- 1]))
2610 const int type
= TextEditorDefs::getCharacterCategory (t
[i
- 1]);
2612 while (i
> 0 && type
== TextEditorDefs::getCharacterCategory (t
[i
- 1]))
2616 jassert (startOfBuffer
+ i
>= 0);
2617 return startOfBuffer
+ i
;
2621 //==============================================================================
2622 void TextEditor::splitSection (const int sectionIndex
,
2623 const int charToSplitAt
)
2625 jassert (sections
[sectionIndex
] != nullptr);
2627 sections
.insert (sectionIndex
+ 1,
2628 sections
.getUnchecked (sectionIndex
)->split (charToSplitAt
, passwordCharacter
));
2631 void TextEditor::coalesceSimilarSections()
2633 for (int i
= 0; i
< sections
.size() - 1; ++i
)
2635 UniformTextSection
* const s1
= sections
.getUnchecked (i
);
2636 UniformTextSection
* const s2
= sections
.getUnchecked (i
+ 1);
2638 if (s1
->font
== s2
->font
2639 && s1
->colour
== s2
->colour
)
2641 s1
->append (*s2
, passwordCharacter
);
2642 sections
.remove (i
+ 1);
2649 void TextEditor::Listener::textEditorTextChanged (TextEditor
&) {}
2650 void TextEditor::Listener::textEditorReturnKeyPressed (TextEditor
&) {}
2651 void TextEditor::Listener::textEditorEscapeKeyPressed (TextEditor
&) {}
2652 void TextEditor::Listener::textEditorFocusLost (TextEditor
&) {}