From a180c6c35857a90815eb74b6f024eef5c52cbb24 Mon Sep 17 00:00:00 2001 From: jules Date: Mon, 24 Sep 2012 14:04:46 +0100 Subject: [PATCH] Added a class TextEditor::InputFilter to perform custom filters on text input. --- .../widgets/juce_TextEditor.cpp | 265 +++++++----------- .../juce_gui_basics/widgets/juce_TextEditor.h | 84 ++++-- 2 files changed, 171 insertions(+), 178 deletions(-) diff --git a/modules/juce_gui_basics/widgets/juce_TextEditor.cpp b/modules/juce_gui_basics/widgets/juce_TextEditor.cpp index 5a397758ad..f5fea44397 100644 --- a/modules/juce_gui_basics/widgets/juce_TextEditor.cpp +++ b/modules/juce_gui_basics/widgets/juce_TextEditor.cpp @@ -67,7 +67,7 @@ public: } UniformTextSection (const UniformTextSection& other) - : font (other.font), colour (other.colour) + : font (other.font), colour (other.colour) { atoms.ensureStorageAllocated (other.atoms.size()); @@ -80,39 +80,28 @@ public: void clear() { for (int i = atoms.size(); --i >= 0;) - delete getAtom(i); + delete atoms.getUnchecked (i); atoms.clear(); } - int getNumAtoms() const - { - return atoms.size(); - } - - TextAtom* getAtom (const int index) const noexcept - { - return atoms.getUnchecked (index); - } - - void append (const UniformTextSection& other, const juce_wchar passwordCharacter) + void append (const UniformTextSection& other, const juce_wchar passwordChar) { if (other.atoms.size() > 0) { - TextAtom* const lastAtom = atoms.getLast(); int i = 0; - if (lastAtom != nullptr) + if (TextAtom* const lastAtom = atoms.getLast()) { if (! CharacterFunctions::isWhitespace (lastAtom->atomText.getLastCharacter())) { - TextAtom* const first = other.getAtom(0); + TextAtom* const first = other.atoms.getUnchecked(0); if (! CharacterFunctions::isWhitespace (first->atomText[0])) { lastAtom->atomText += first->atomText; lastAtom->numChars = (uint16) (lastAtom->numChars + first->numChars); - lastAtom->width = font.getStringWidthFloat (lastAtom->getText (passwordCharacter)); + lastAtom->width = font.getStringWidthFloat (lastAtom->getText (passwordChar)); delete first; ++i; } @@ -123,30 +112,27 @@ public: while (i < other.atoms.size()) { - atoms.add (other.getAtom(i)); + atoms.add (other.atoms.getUnchecked(i)); ++i; } } } - UniformTextSection* split (const int indexToBreakAt, - const juce_wchar passwordCharacter) + UniformTextSection* split (const int indexToBreakAt, const juce_wchar passwordChar) { - UniformTextSection* const section2 = new UniformTextSection (String::empty, - font, colour, - passwordCharacter); + UniformTextSection* const section2 = new UniformTextSection (String::empty, font, colour, passwordChar); int index = 0; for (int i = 0; i < atoms.size(); ++i) { - TextAtom* const atom = getAtom(i); + TextAtom* const atom = atoms.getUnchecked(i); const int nextIndex = index + atom->numChars; if (index == indexToBreakAt) { for (int j = i; j < atoms.size(); ++j) - section2->atoms.add (getAtom (j)); + section2->atoms.add (atoms.getUnchecked (j)); for (int j = atoms.size(); --j >= i;) atoms.remove (j); @@ -158,17 +144,17 @@ public: TextAtom* const secondAtom = new TextAtom(); secondAtom->atomText = atom->atomText.substring (indexToBreakAt - index); - secondAtom->width = font.getStringWidthFloat (secondAtom->getText (passwordCharacter)); + secondAtom->width = font.getStringWidthFloat (secondAtom->getText (passwordChar)); secondAtom->numChars = (uint16) secondAtom->atomText.length(); section2->atoms.add (secondAtom); atom->atomText = atom->atomText.substring (0, indexToBreakAt - index); - atom->width = font.getStringWidthFloat (atom->getText (passwordCharacter)); + atom->width = font.getStringWidthFloat (atom->getText (passwordChar)); atom->numChars = (uint16) (indexToBreakAt - index); for (int j = i + 1; j < atoms.size(); ++j) - section2->atoms.add (getAtom (j)); + section2->atoms.add (atoms.getUnchecked (j)); for (int j = atoms.size(); --j > i;) atoms.remove (j); @@ -185,7 +171,7 @@ public: void appendAllText (MemoryOutputStream& mo) const { for (int i = 0; i < atoms.size(); ++i) - mo << getAtom(i)->atomText; + mo << atoms.getUnchecked(i)->atomText; } void appendSubstring (MemoryOutputStream& mo, const Range& range) const @@ -193,7 +179,7 @@ public: int index = 0; for (int i = 0; i < atoms.size(); ++i) { - const TextAtom* const atom = getAtom (i); + const TextAtom* const atom = atoms.getUnchecked (i); const int nextIndex = index + atom->numChars; if (range.getStart() < nextIndex) @@ -211,18 +197,17 @@ public: } } - int getTotalLength() const + int getTotalLength() const noexcept { int total = 0; for (int i = atoms.size(); --i >= 0;) - total += getAtom(i)->numChars; + total += atoms.getUnchecked(i)->numChars; return total; } - void setFont (const Font& newFont, - const juce_wchar passwordCharacter) + void setFont (const Font& newFont, const juce_wchar passwordChar) { if (font != newFont) { @@ -231,7 +216,7 @@ public: for (int i = atoms.size(); --i >= 0;) { TextAtom* const atom = atoms.getUnchecked(i); - atom->width = newFont.getStringWidthFloat (atom->getText (passwordCharacter)); + atom->width = newFont.getStringWidthFloat (atom->getText (passwordChar)); } } } @@ -239,13 +224,10 @@ public: //============================================================================== Font font; Colour colour; - -private: Array atoms; - //============================================================================== - void initialiseAtoms (const String& textToParse, - const juce_wchar passwordCharacter) +private: + void initialiseAtoms (const String& textToParse, const juce_wchar passwordChar) { String::CharPointerType text (textToParse.getCharPointer()); @@ -294,8 +276,7 @@ private: TextAtom* const atom = new TextAtom(); atom->atomText = String (start, numChars); - - atom->width = font.getStringWidthFloat (atom->getText (passwordCharacter)); + atom->width = font.getStringWidthFloat (atom->getText (passwordChar)); atom->numChars = (uint16) numChars; atoms.add (atom); @@ -399,9 +380,9 @@ public: moveToEndOfLastAtom(); return false; } - else if (atomIndex >= currentSection->getNumAtoms() - 1) + else if (atomIndex >= currentSection->atoms.size() - 1) { - if (atomIndex >= currentSection->getNumAtoms()) + if (atomIndex >= currentSection->atoms.size()) { if (++sectionIndex >= sections.size()) { @@ -414,7 +395,7 @@ public: } else { - const TextAtom* const lastAtom = currentSection->getAtom (atomIndex); + const TextAtom* const lastAtom = currentSection->atoms.getUnchecked (atomIndex); if (! lastAtom->isWhitespace()) { @@ -428,10 +409,10 @@ public: { const UniformTextSection* const s = sections.getUnchecked (section); - if (s->getNumAtoms() == 0) + if (s->atoms.size() == 0) break; - const TextAtom* const nextAtom = s->getAtom (0); + const TextAtom* const nextAtom = s->atoms.getUnchecked (0); if (nextAtom->isWhitespace()) break; @@ -450,7 +431,7 @@ public: break; } - if (s->getNumAtoms() > 1) + if (s->atoms.size() > 1) break; } } @@ -466,7 +447,7 @@ public: beginNewLine(); } - atom = currentSection->getAtom (atomIndex); + atom = currentSection->atoms.getUnchecked (atomIndex); atomRight = atomX + atom->width; ++atomIndex; @@ -523,7 +504,7 @@ public: bool checkSize = false; - if (tempAtomIndex >= section->getNumAtoms()) + if (tempAtomIndex >= section->atoms.size()) { if (++tempSectionIndex >= sections.size()) break; @@ -533,7 +514,7 @@ public: checkSize = true; } - const TextAtom* const nextAtom = section->getAtom (tempAtomIndex); + const TextAtom* const nextAtom = section->atoms.getUnchecked (tempAtomIndex); if (nextAtom == nullptr) break; @@ -570,16 +551,15 @@ public: GlyphArrangement ga; ga.addLineOfText (currentSection->font, atom->getTrimmedText (passwordCharacter), - atomX, - (float) roundToInt (lineY + lineHeight - maxDescent)); + atomX, (float) roundToInt (lineY + lineHeight - maxDescent)); ga.draw (g); } } - void drawSelection (Graphics& g, const Range& selection) const + void drawSelection (Graphics& g, const Range& selected) const { - const int startX = roundToInt (indexToX (selection.getStart())); - const int endX = roundToInt (indexToX (selection.getEnd())); + const int startX = roundToInt (indexToX (selected.getStart())); + const int endX = roundToInt (indexToX (selected.getEnd())); const int y = roundToInt (lineY); const int nextY = roundToInt (lineY + lineHeight); @@ -599,7 +579,7 @@ public: } void drawSelectedText (Graphics& g, - const Range& selection, + const Range& selected, const Colour& selectedTextColour) const { if (passwordCharacter != 0 || ! atom->isWhitespace()) @@ -607,24 +587,23 @@ public: GlyphArrangement ga; ga.addLineOfText (currentSection->font, atom->getTrimmedText (passwordCharacter), - atomX, - (float) roundToInt (lineY + lineHeight - maxDescent)); + atomX, (float) roundToInt (lineY + lineHeight - maxDescent)); - if (selection.getEnd() < indexInText + atom->numChars) + if (selected.getEnd() < indexInText + atom->numChars) { GlyphArrangement ga2 (ga); - ga2.removeRangeOfGlyphs (0, selection.getEnd() - indexInText); - ga.removeRangeOfGlyphs (selection.getEnd() - indexInText, -1); + ga2.removeRangeOfGlyphs (0, selected.getEnd() - indexInText); + ga.removeRangeOfGlyphs (selected.getEnd() - indexInText, -1); g.setColour (currentSection->colour); ga2.draw (g); } - if (selection.getStart() > indexInText) + if (selected.getStart() > indexInText) { GlyphArrangement ga2 (ga); - ga2.removeRangeOfGlyphs (selection.getStart() - indexInText, -1); - ga.removeRangeOfGlyphs (0, selection.getStart() - indexInText); + ga2.removeRangeOfGlyphs (selected.getStart() - indexInText, -1); + ga.removeRangeOfGlyphs (0, selected.getStart() - indexInText); g.setColour (currentSection->colour); ga2.draw (g); @@ -726,7 +705,7 @@ private: } } - bool shouldWrap (const float x) const + bool shouldWrap (const float x) const noexcept { return (x - 0.0001f) >= wordWrapWidth; } @@ -824,12 +803,11 @@ public: int getSizeInUnits() { - int n = 0; - + int n = 16; for (int i = removedSections.size(); --i >= 0;) n += removedSections.getUnchecked (i)->getTotalLength(); - return n + 16; + return n; } private: @@ -957,7 +935,6 @@ TextEditor::TextEditor (const String& name, menuActive (false), valueTextNeedsUpdating (false), consumeEscAndReturnKeys (true), - maxTextLength (0), leftIndent (4), topIndent (4), lastTransactionTime (0), @@ -982,11 +959,8 @@ TextEditor::TextEditor (const String& name, TextEditor::~TextEditor() { if (wasFocused) - { - ComponentPeer* const peer = getPeer(); - if (peer != nullptr) + if (ComponentPeer* const peer = getPeer()) peer->dismissPendingTextInput(); - } textValue.referTo (Value()); clearInternal (0); @@ -1105,7 +1079,6 @@ void TextEditor::setFont (const Font& newFont) void TextEditor::applyFontToAllText (const Font& newFont) { currentFont = newFont; - const Colour overallColour (findColour (textColourId)); for (int i = sections.size(); --i >= 0;) @@ -1155,11 +1128,32 @@ void TextEditor::updateCaretPosition() caret->setCaretPosition (getCaretRectangle().translated (leftIndent, topIndent)); } +TextEditor::LengthAndCharacterRestriction::LengthAndCharacterRestriction (int maxLen, const String& chars) + : allowedCharacters (chars), maxLength (maxLen) +{} + +String TextEditor::LengthAndCharacterRestriction::filterNewText (TextEditor& ed, const String& newInput) +{ + String t (newInput); + + if (allowedCharacters.isNotEmpty()) + t = t.retainCharacters (allowedCharacters); + + if (maxLength > 0) + t = t.substring (0, maxLength - (ed.getTotalNumChars() - ed.getHighlightedRegion().getLength())); + + return t; +} + +void TextEditor::setInputFilter (InputFilter* newFilter, bool takeOwnership) +{ + inputFilter.set (newFilter, takeOwnership); +} + void TextEditor::setInputRestrictions (const int maxLen, const String& chars) { - maxTextLength = jmax (0, maxLen); - allowedCharacters = chars; + setInputFilter (new LengthAndCharacterRestriction (maxLen, chars), true); } void TextEditor::setTextToShowWhenEmpty (const String& text, const Colour& colourToUse) @@ -1262,25 +1256,11 @@ void TextEditor::textChanged() } } -void TextEditor::returnPressed() -{ - postCommandMessage (TextEditorDefs::returnKeyMessageId); -} - -void TextEditor::escapePressed() -{ - postCommandMessage (TextEditorDefs::escapeKeyMessageId); -} - -void TextEditor::addListener (TextEditorListener* const newListener) -{ - listeners.add (newListener); -} +void TextEditor::returnPressed() { postCommandMessage (TextEditorDefs::returnKeyMessageId); } +void TextEditor::escapePressed() { postCommandMessage (TextEditorDefs::escapeKeyMessageId); } -void TextEditor::removeListener (TextEditorListener* const listenerToRemove) -{ - listeners.remove (listenerToRemove); -} +void TextEditor::addListener (TextEditorListener* const l) { listeners.add (l); } +void TextEditor::removeListener (TextEditorListener* const l) { listeners.remove (l); } //============================================================================== void TextEditor::timerCallbackInt() @@ -1430,21 +1410,13 @@ void TextEditor::updateTextHolderSize() } } -int TextEditor::getTextWidth() const -{ - return textHolder->getWidth(); -} - -int TextEditor::getTextHeight() const -{ - return textHolder->getHeight(); -} +int TextEditor::getTextWidth() const { return textHolder->getWidth(); } +int TextEditor::getTextHeight() const { return textHolder->getHeight(); } -void TextEditor::setIndents (const int newLeftIndent, - const int newTopIndent) +void TextEditor::setIndents (const int newLeftIndent, const int newTopIndent) { leftIndent = newLeftIndent; - topIndent = newTopIndent; + topIndent = newTopIndent; } void TextEditor::setBorder (const BorderSize& border) @@ -1561,32 +1533,21 @@ int TextEditor::getTextIndexAt (const int x, const int y) void TextEditor::insertTextAtCaret (const String& t) { - String newText (t); - - if (allowedCharacters.isNotEmpty()) - newText = newText.retainCharacters (allowedCharacters); + String newText (inputFilter != nullptr ? inputFilter->filterNewText (*this, t) : t); - if (! isMultiLine()) - newText = newText.replaceCharacters ("\r\n", " "); - else + if (isMultiLine()) newText = newText.replace ("\r\n", "\n"); + else + newText = newText.replaceCharacters ("\r\n", " "); - const int newCaretPos = selection.getStart() + newText.length(); const int insertIndex = selection.getStart(); + const int newCaretPos = insertIndex + newText.length(); remove (selection, getUndoManager(), newText.isNotEmpty() ? newCaretPos - 1 : newCaretPos); - if (maxTextLength > 0) - newText = newText.substring (0, maxTextLength - getTotalNumChars()); - - if (newText.isNotEmpty()) - insert (newText, - insertIndex, - currentFont, - findColour (textColourId), - getUndoManager(), - newCaretPos); + insert (newText, insertIndex, currentFont, findColour (textColourId), + getUndoManager(), newCaretPos); textChanged(); } @@ -1714,16 +1675,12 @@ void TextEditor::paintOverChildren (Graphics& g) g.setFont (getFont()); if (isMultiLine()) - { g.drawText (textToShowWhenEmpty, getLocalBounds(), Justification::centred, true); - } else - { g.drawText (textToShowWhenEmpty, leftIndent, 0, viewport->getWidth() - leftIndent, getHeight(), Justification::centredLeft, true); - } } getLookAndFeel().drawTextEditorOutline (g, getWidth(), getHeight(), *this); @@ -1888,7 +1845,7 @@ void TextEditor::mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& w } //============================================================================== -bool TextEditor::moveCaretWithTransation (const int newPos, const bool selecting) +bool TextEditor::moveCaretWithTransaction (const int newPos, const bool selecting) { newTransaction(); moveCaretTo (newPos, selecting); @@ -1904,7 +1861,7 @@ bool TextEditor::moveCaretLeft (bool moveInWholeWordSteps, bool selecting) else --pos; - return moveCaretWithTransation (pos, selecting); + return moveCaretWithTransaction (pos, selecting); } bool TextEditor::moveCaretRight (bool moveInWholeWordSteps, bool selecting) @@ -1916,7 +1873,7 @@ bool TextEditor::moveCaretRight (bool moveInWholeWordSteps, bool selecting) else ++pos; - return moveCaretWithTransation (pos, selecting); + return moveCaretWithTransaction (pos, selecting); } bool TextEditor::moveCaretUp (bool selecting) @@ -1925,7 +1882,7 @@ bool TextEditor::moveCaretUp (bool selecting) return moveCaretToStartOfLine (selecting); const Rectangle caretPos (getCaretRectangle().toFloat()); - return moveCaretWithTransation (indexAtPosition (caretPos.getX(), caretPos.getY() - 1.0f), selecting); + return moveCaretWithTransaction (indexAtPosition (caretPos.getX(), caretPos.getY() - 1.0f), selecting); } bool TextEditor::moveCaretDown (bool selecting) @@ -1934,7 +1891,7 @@ bool TextEditor::moveCaretDown (bool selecting) return moveCaretToEndOfLine (selecting); const Rectangle caretPos (getCaretRectangle().toFloat()); - return moveCaretWithTransation (indexAtPosition (caretPos.getX(), caretPos.getBottom() + 1.0f), selecting); + return moveCaretWithTransaction (indexAtPosition (caretPos.getX(), caretPos.getBottom() + 1.0f), selecting); } bool TextEditor::pageUp (bool selecting) @@ -1943,7 +1900,7 @@ bool TextEditor::pageUp (bool selecting) return moveCaretToStartOfLine (selecting); const Rectangle caretPos (getCaretRectangle().toFloat()); - return moveCaretWithTransation (indexAtPosition (caretPos.getX(), caretPos.getY() - viewport->getViewHeight()), selecting); + return moveCaretWithTransaction (indexAtPosition (caretPos.getX(), caretPos.getY() - viewport->getViewHeight()), selecting); } bool TextEditor::pageDown (bool selecting) @@ -1952,14 +1909,12 @@ bool TextEditor::pageDown (bool selecting) return moveCaretToEndOfLine (selecting); const Rectangle caretPos (getCaretRectangle().toFloat()); - return moveCaretWithTransation (indexAtPosition (caretPos.getX(), caretPos.getBottom() + viewport->getViewHeight()), selecting); + return moveCaretWithTransaction (indexAtPosition (caretPos.getX(), caretPos.getBottom() + viewport->getViewHeight()), selecting); } void TextEditor::scrollByLines (int deltaLines) { - ScrollBar* scrollbar = viewport->getVerticalScrollBar(); - - if (scrollbar != nullptr) + if (ScrollBar* scrollbar = viewport->getVerticalScrollBar()) scrollbar->moveScrollbarInSteps (deltaLines); } @@ -1977,24 +1932,24 @@ bool TextEditor::scrollUp() bool TextEditor::moveCaretToTop (bool selecting) { - return moveCaretWithTransation (0, selecting); + return moveCaretWithTransaction (0, selecting); } bool TextEditor::moveCaretToStartOfLine (bool selecting) { const Rectangle caretPos (getCaretRectangle().toFloat()); - return moveCaretWithTransation (indexAtPosition (0.0f, caretPos.getY()), selecting); + return moveCaretWithTransaction (indexAtPosition (0.0f, caretPos.getY()), selecting); } bool TextEditor::moveCaretToEnd (bool selecting) { - return moveCaretWithTransation (getTotalNumChars(), selecting); + return moveCaretWithTransaction (getTotalNumChars(), selecting); } bool TextEditor::moveCaretToEndOfLine (bool selecting) { const Rectangle caretPos (getCaretRectangle().toFloat()); - return moveCaretWithTransation (indexAtPosition ((float) textHolder->getWidth(), caretPos.getY()), selecting); + return moveCaretWithTransaction (indexAtPosition ((float) textHolder->getWidth(), caretPos.getY()), selecting); } bool TextEditor::deleteBackwards (bool moveInWholeWordSteps) @@ -2128,9 +2083,9 @@ void TextEditor::focusGained (FocusChangeType) repaint(); updateCaretPosition(); - ComponentPeer* const peer = getPeer(); - if (peer != nullptr && ! isReadOnly()) - peer->textInputRequired (getScreenPosition() - peer->getScreenPosition()); + if (ComponentPeer* const peer = getPeer()) + if (! isReadOnly()) + peer->textInputRequired (getScreenPosition() - peer->getScreenPosition()); } void TextEditor::focusLost (FocusChangeType) @@ -2142,8 +2097,7 @@ void TextEditor::focusLost (FocusChangeType) underlinedSections.clear(); - ComponentPeer* const peer = getPeer(); - if (peer != nullptr) + if (ComponentPeer* const peer = getPeer()) peer->dismissPendingTextInput(); updateCaretPosition(); @@ -2160,14 +2114,10 @@ void TextEditor::resized() updateTextHolderSize(); - if (! isMultiLine()) - { - scrollToMakeSureCursorIsVisible(); - } - else - { + if (isMultiLine()) updateCaretPosition(); - } + else + scrollToMakeSureCursorIsVisible(); } void TextEditor::handleCommandMessage (const int commandId) @@ -2600,8 +2550,3 @@ void TextEditor::coalesceSimilarSections() } } } - -void TextEditor::Listener::textEditorTextChanged (TextEditor&) {} -void TextEditor::Listener::textEditorReturnKeyPressed (TextEditor&) {} -void TextEditor::Listener::textEditorEscapeKeyPressed (TextEditor&) {} -void TextEditor::Listener::textEditorFocusLost (TextEditor&) {} diff --git a/modules/juce_gui_basics/widgets/juce_TextEditor.h b/modules/juce_gui_basics/widgets/juce_TextEditor.h index ff63157457..a48c9d31f9 100644 --- a/modules/juce_gui_basics/widgets/juce_TextEditor.h +++ b/modules/juce_gui_basics/widgets/juce_TextEditor.h @@ -262,16 +262,6 @@ public: */ void setSelectAllWhenFocused (bool shouldSelectAll); - /** Sets limits on the characters that can be entered. - - @param maxTextLength if this is > 0, it sets a maximum length limit; if 0, no - limit is set - @param allowedCharacters if this is non-empty, then only characters that occur in - this string are allowed to be entered into the editor. - */ - void setInputRestrictions (int maxTextLength, - const String& allowedCharacters = String::empty); - /** When the text editor is empty, it can be set to display a message. This is handy for things like telling the user what to type in the box - the @@ -292,23 +282,23 @@ public: @see TextEditor::addListener */ - class JUCE_API Listener + class Listener { public: /** Destructor. */ virtual ~Listener() {} /** Called when the user changes the text in some way. */ - virtual void textEditorTextChanged (TextEditor& editor); + virtual void textEditorTextChanged (TextEditor&) {} /** Called when the user presses the return key. */ - virtual void textEditorReturnKeyPressed (TextEditor& editor); + virtual void textEditorReturnKeyPressed (TextEditor&) {} /** Called when the user presses the escape key. */ - virtual void textEditorEscapeKeyPressed (TextEditor& editor); + virtual void textEditorEscapeKeyPressed (TextEditor&) {} /** Called when the text editor loses focus. */ - virtual void textEditorFocusLost (TextEditor& editor); + virtual void textEditorFocusLost (TextEditor&) {} }; /** Registers a listener to be told when things happen to the text. @@ -538,6 +528,65 @@ public: */ virtual void performPopupMenuAction (int menuItemID); + //============================================================================== + /** Base class for input filters that can be applied to a TextEditor to restrict + the text that can be entered. + */ + class InputFilter + { + public: + InputFilter() {} + virtual ~InputFilter() {} + + /** This method is called whenever text is entered into the editor. + An implementation of this class should should check the input string, + and return an edited version of it that should be used. + */ + virtual String filterNewText (TextEditor&, const String& newInput) = 0; + }; + + /** An input filter for a TextEditor that limits the length of text and/or the + characters that it may contain. + */ + class JUCE_API LengthAndCharacterRestriction : public InputFilter + { + public: + /** Creates a filter that limits the length of text, and/or the characters that it can contain. + @param maxTextLength if this is > 0, it sets a maximum length limit; if <= 0, no + limit is set + @param allowedCharacters if this is non-empty, then only characters that occur in + this string are allowed to be entered into the editor. + */ + LengthAndCharacterRestriction (int maxNumChars, const String& allowedCharacters); + + private: + String allowedCharacters; + int maxLength; + + String filterNewText (TextEditor&, const String&); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LengthAndCharacterRestriction); + }; + + /** Sets an input filter that should be applied to this editor. + The filter can be nullptr, to remove any existing filters. + If takeOwnership is true, then the filter will be owned and deleted by the editor + when no longer needed. + */ + void setInputFilter (InputFilter* newFilter, bool takeOwnership); + + /** Sets limits on the characters that can be entered. + This is just a shortcut that passes an instance of the LengthAndCharacterRestriction + class to setInputFilter(). + + @param maxTextLength if this is > 0, it sets a maximum length limit; if 0, no + limit is set + @param allowedCharacters if this is non-empty, then only characters that occur in + this string are allowed to be entered into the editor. + */ + void setInputRestrictions (int maxTextLength, + const String& allowedCharacters = String::empty); + //============================================================================== /** @internal */ void paint (Graphics&); @@ -621,7 +670,6 @@ private: UndoManager undoManager; ScopedPointer caret; - int maxTextLength; Range selection; int leftIndent, topIndent; unsigned int lastTransactionTime; @@ -632,6 +680,7 @@ private: String textToShowWhenEmpty; Colour colourForTextWhenEmpty; juce_wchar passwordCharacter; + OptionalScopedPointer inputFilter; Value textValue; enum @@ -641,7 +690,6 @@ private: draggingSelectionEnd } dragType; - String allowedCharacters; ListenerList listeners; Array > underlinedSections; @@ -661,7 +709,7 @@ private: int indexAtPosition (float x, float y); int findWordBreakAfter (int position) const; int findWordBreakBefore (int position) const; - bool moveCaretWithTransation (int newPos, bool selecting); + bool moveCaretWithTransaction (int newPos, bool selecting); friend class TextHolderComponent; friend class TextEditorViewport; void drawContent (Graphics&);