diff --git a/modules/juce_gui_basics/widgets/juce_TextEditor.cpp b/modules/juce_gui_basics/widgets/juce_TextEditor.cpp index c40956cfe8..2a212ef24f 100644 --- a/modules/juce_gui_basics/widgets/juce_TextEditor.cpp +++ b/modules/juce_gui_basics/widgets/juce_TextEditor.cpp @@ -39,7 +39,7 @@ struct TextAtom bool isWhitespace() const noexcept { return CharacterFunctions::isWhitespace (atomText[0]); } bool isNewLine() const noexcept { return atomText[0] == '\r' || atomText[0] == '\n'; } - String getText (const juce_wchar passwordCharacter) const + String getText (juce_wchar passwordCharacter) const { if (passwordCharacter == 0) return atomText; @@ -73,30 +73,29 @@ public: initialiseAtoms (text, passwordChar); } - UniformTextSection (const UniformTextSection& other) - : font (other.font), colour (other.colour) - { - atoms.addCopiesOf (other.atoms); - } + UniformTextSection (const UniformTextSection&) = default; + UniformTextSection (UniformTextSection&&) = default; + UniformTextSection& operator= (const UniformTextSection&) = delete; void append (UniformTextSection& other, const juce_wchar passwordChar) { - if (other.atoms.size() > 0) + if (! other.atoms.isEmpty()) { int i = 0; - if (auto* lastAtom = atoms.getLast()) + if (! atoms.isEmpty()) { - if (! CharacterFunctions::isWhitespace (lastAtom->atomText.getLastCharacter())) - { - auto* first = other.atoms.getUnchecked(0); + auto& lastAtom = atoms.getReference (atoms.size() - 1); - if (! CharacterFunctions::isWhitespace (first->atomText[0])) + if (! CharacterFunctions::isWhitespace (lastAtom.atomText.getLastCharacter())) + { + auto& first = other.atoms.getReference(0); + + if (! CharacterFunctions::isWhitespace (first.atomText[0])) { - lastAtom->atomText += first->atomText; - lastAtom->numChars = (uint16) (lastAtom->numChars + first->numChars); - lastAtom->width = font.getStringWidthFloat (lastAtom->getText (passwordChar)); - delete first; + lastAtom.atomText += first.atomText; + lastAtom.numChars = (uint16) (lastAtom.numChars + first.numChars); + lastAtom.width = font.getStringWidthFloat (lastAtom.getText (passwordChar)); ++i; } } @@ -106,50 +105,48 @@ public: while (i < other.atoms.size()) { - atoms.add (other.atoms.getUnchecked(i)); + atoms.add (other.atoms.getReference(i)); ++i; } - - other.atoms.clear (false); } } - UniformTextSection* split (const int indexToBreakAt, const juce_wchar passwordChar) + UniformTextSection* split (int indexToBreakAt, juce_wchar passwordChar) { - UniformTextSection* const section2 = new UniformTextSection (String(), font, colour, passwordChar); + auto* section2 = new UniformTextSection (String(), font, colour, passwordChar); int index = 0; for (int i = 0; i < atoms.size(); ++i) { - auto* atom = atoms.getUnchecked(i); - auto nextIndex = index + atom->numChars; + auto& atom = atoms.getReference(i); + auto nextIndex = index + atom.numChars; if (index == indexToBreakAt) { for (int j = i; j < atoms.size(); ++j) section2->atoms.add (atoms.getUnchecked (j)); - atoms.removeRange (i, atoms.size(), false); + atoms.removeRange (i, atoms.size()); break; } - else if (indexToBreakAt >= index && indexToBreakAt < nextIndex) - { - auto* secondAtom = new TextAtom(); - secondAtom->atomText = atom->atomText.substring (indexToBreakAt - index); - secondAtom->width = font.getStringWidthFloat (secondAtom->getText (passwordChar)); - secondAtom->numChars = (uint16) secondAtom->atomText.length(); + if (indexToBreakAt >= index && indexToBreakAt < nextIndex) + { + TextAtom secondAtom; + secondAtom.atomText = atom.atomText.substring (indexToBreakAt - index); + 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 (passwordChar)); - atom->numChars = (uint16) (indexToBreakAt - index); + atom.atomText = atom.atomText.substring (0, indexToBreakAt - index); + atom.width = font.getStringWidthFloat (atom.getText (passwordChar)); + atom.numChars = (uint16) (indexToBreakAt - index); for (int j = i + 1; j < atoms.size(); ++j) section2->atoms.add (atoms.getUnchecked (j)); - atoms.removeRange (i + 1, atoms.size(), false); + atoms.removeRange (i + 1, atoms.size()); break; } @@ -161,27 +158,27 @@ public: void appendAllText (MemoryOutputStream& mo) const { - for (int i = 0; i < atoms.size(); ++i) - mo << atoms.getUnchecked(i)->atomText; + for (auto& atom : atoms) + mo << atom.atomText; } - void appendSubstring (MemoryOutputStream& mo, const Range range) const + void appendSubstring (MemoryOutputStream& mo, Range range) const { int index = 0; - for (auto* atom : atoms) + for (auto& atom : atoms) { - auto nextIndex = index + atom->numChars; + auto nextIndex = index + atom.numChars; if (range.getStart() < nextIndex) { if (range.getEnd() <= index) break; - auto r = (range - index).getIntersectionWith (Range (0, (int) atom->numChars)); + auto r = (range - index).getIntersectionWith ({ 0, (int) atom.numChars }); if (! r.isEmpty()) - mo << atom->atomText.substring (r.getStart(), r.getEnd()); + mo << atom.atomText.substring (r.getStart(), r.getEnd()); } index = nextIndex; @@ -192,8 +189,8 @@ public: { int total = 0; - for (auto* atom : atoms) - total += atom->numChars; + for (auto& atom : atoms) + total += atom.numChars; return total; } @@ -204,15 +201,15 @@ public: { font = newFont; - for (auto* atom : atoms) - atom->width = newFont.getStringWidthFloat (atom->getText (passwordChar)); + for (auto& atom : atoms) + atom.width = newFont.getStringWidthFloat (atom.getText (passwordChar)); } } //============================================================================== Font font; Colour colour; - OwnedArray atoms; + Array atoms; private: void initialiseAtoms (const String& textToParse, const juce_wchar passwordChar) @@ -262,32 +259,31 @@ private: } } - auto* atom = atoms.add (new TextAtom()); - - atom->atomText = String (start, numChars); - atom->width = font.getStringWidthFloat (atom->getText (passwordChar)); - atom->numChars = (uint16) numChars; + TextAtom atom; + atom.atomText = String (start, numChars); + atom.width = font.getStringWidthFloat (atom.getText (passwordChar)); + atom.numChars = (uint16) numChars; + atoms.add (atom); } } - UniformTextSection& operator= (const UniformTextSection&); JUCE_LEAK_DETECTOR (UniformTextSection) }; //============================================================================== -class TextEditor::Iterator +struct TextEditor::Iterator { -public: - Iterator (const OwnedArray& sectionList, - float wrapWidth, juce_wchar passwordChar, float spacing) - : sections (sectionList), - wordWrapWidth (wrapWidth), - passwordCharacter (passwordChar), - lineSpacing (spacing) + Iterator (const TextEditor& ed) + : sections (ed.sections), + justification (ed.justification), + justificationWidth (ed.getJustificationWidth()), + wordWrapWidth (ed.getWordWrapWidth()), + passwordCharacter (ed.passwordCharacter), + lineSpacing (ed.lineSpacing) { jassert (wordWrapWidth > 0); - if (sections.size() > 0) + if (! sections.isEmpty()) { currentSection = sections.getUnchecked (sectionIndex); @@ -296,38 +292,20 @@ public: } } - Iterator (const Iterator& other) - : indexInText (other.indexInText), - lineY (other.lineY), - lineHeight (other.lineHeight), - maxDescent (other.maxDescent), - atomX (other.atomX), - atomRight (other.atomRight), - atom (other.atom), - currentSection (other.currentSection), - sections (other.sections), - sectionIndex (other.sectionIndex), - atomIndex (other.atomIndex), - wordWrapWidth (other.wordWrapWidth), - passwordCharacter (other.passwordCharacter), - lineSpacing (other.lineSpacing), - tempAtom (other.tempAtom) - { - } + Iterator (const Iterator&) = default; + Iterator& operator= (const Iterator&) = delete; //============================================================================== bool next() { if (atom == &tempAtom) { - const int numRemaining = tempAtom.atomText.length() - tempAtom.numChars; + auto numRemaining = tempAtom.atomText.length() - tempAtom.numChars; if (numRemaining > 0) { tempAtom.atomText = tempAtom.atomText.substring (tempAtom.numChars); - atomX = 0; - if (tempAtom.numChars > 0) lineY += lineHeight * lineSpacing; @@ -345,6 +323,7 @@ public: { tempAtom.numChars = (uint16) split; tempAtom.width = g.getGlyph (split - 1).getRight(); + atomX = getJustificationOffset (tempAtom.width); atomRight = atomX + tempAtom.width; return true; } @@ -374,13 +353,13 @@ public: } else { - auto* lastAtom = currentSection->atoms.getUnchecked (atomIndex); + auto& lastAtom = currentSection->atoms.getReference (atomIndex); - if (! lastAtom->isWhitespace()) + if (! lastAtom.isWhitespace()) { // handle the case where the last atom in a section is actually part of the same // word as the first atom of the next section... - float right = atomRight + lastAtom->width; + float right = atomRight + lastAtom.width; float lineHeight2 = lineHeight; float maxDescent2 = maxDescent; @@ -391,12 +370,12 @@ public: if (s->atoms.size() == 0) break; - auto* nextAtom = s->atoms.getUnchecked (0); + auto& nextAtom = s->atoms.getReference (0); - if (nextAtom->isWhitespace()) + if (nextAtom.isWhitespace()) break; - right += nextAtom->width; + right += nextAtom.width; lineHeight2 = jmax (lineHeight2, s->font.getHeight()); maxDescent2 = jmax (maxDescent2, s->font.getDescent()); @@ -426,7 +405,7 @@ public: beginNewLine(); } - atom = currentSection->atoms.getUnchecked (atomIndex); + atom = &(currentSection->atoms.getReference (atomIndex)); atomRight = atomX + atom->width; ++atomIndex; @@ -439,22 +418,22 @@ public: } else { - atomRight = atom->width; - - if (shouldWrap (atomRight)) // atom too big to fit on a line, so break it up.. + if (shouldWrap (atom->width)) // atom too big to fit on a line, so break it up.. { tempAtom = *atom; tempAtom.width = 0; tempAtom.numChars = 0; atom = &tempAtom; - if (atomX > 0) + if (atomX > justificationOffset) beginNewLine(); return next(); } beginNewLine(); + atomX = justificationOffset; + atomRight = atomX + atom->width; return true; } } @@ -464,20 +443,22 @@ public: void beginNewLine() { - atomX = 0; lineY += lineHeight * lineSpacing; + float lineWidth = 0; - int tempSectionIndex = sectionIndex; - int tempAtomIndex = atomIndex; + auto tempSectionIndex = sectionIndex; + auto tempAtomIndex = atomIndex; auto* section = sections.getUnchecked (tempSectionIndex); lineHeight = section->font.getHeight(); maxDescent = section->font.getDescent(); - float x = (atom != nullptr) ? atom->width : 0; + float nextLineWidth = (atom != nullptr) ? atom->width : 0.0f; - while (! shouldWrap (x)) + while (! shouldWrap (nextLineWidth)) { + lineWidth = nextLineWidth; + if (tempSectionIndex >= sections.size()) break; @@ -493,14 +474,13 @@ public: checkSize = true; } - auto* nextAtom = section->atoms.getUnchecked (tempAtomIndex); - - if (nextAtom == nullptr) + if (! isPositiveAndBelow (tempAtomIndex, section->atoms.size())) break; - x += nextAtom->width; + auto& nextAtom = section->atoms.getReference (tempAtomIndex); + nextLineWidth += nextAtom.width; - if (shouldWrap (x) || nextAtom->isNewLine()) + if (shouldWrap (nextLineWidth) || nextAtom.isNewLine()) break; if (checkSize) @@ -511,6 +491,20 @@ public: ++tempAtomIndex; } + + justificationOffset = getJustificationOffset (lineWidth); + atomX = justificationOffset; + } + + float getJustificationOffset (float lineWidth) const + { + if (justification.getOnlyHorizontalFlags() == Justification::horizontallyCentred) + return jmax (0.0f, (justificationWidth - lineWidth) * 0.5f); + + if (justification.getOnlyHorizontalFlags() == Justification::right) + return jmax (0.0f, justificationWidth - lineWidth); + + return 0; } //============================================================================== @@ -535,28 +529,26 @@ public: } } - void addSelection (RectangleList& area, const Range selected) const + void addSelection (RectangleList& area, Range selected) const { - const float startX = indexToX (selected.getStart()); - const float endX = indexToX (selected.getEnd()); + auto startX = indexToX (selected.getStart()); + auto endX = indexToX (selected.getEnd()); area.add (startX, lineY, endX - startX, lineHeight * lineSpacing); } - void drawUnderline (Graphics& g, const Range underline, const Colour colour) const + void drawUnderline (Graphics& g, Range underline, Colour colour) const { - const int startX = roundToInt (indexToX (underline.getStart())); - const int endX = roundToInt (indexToX (underline.getEnd())); - const int baselineY = roundToInt (lineY + currentSection->font.getAscent() + 0.5f); + auto startX = roundToInt (indexToX (underline.getStart())); + auto endX = roundToInt (indexToX (underline.getEnd())); + auto baselineY = roundToInt (lineY + currentSection->font.getAscent() + 0.5f); Graphics::ScopedSaveState state (g); - g.reduceClipRegion (Rectangle (startX, baselineY, endX - startX, 1)); - g.fillCheckerBoard (Rectangle (endX, baselineY + 1), 3, 1, colour, Colours::transparentBlack); + g.reduceClipRegion ({ startX, baselineY, endX - startX, 1 }); + g.fillCheckerBoard ({ endX, baselineY + 1 }, 3, 1, colour, Colours::transparentBlack); } - void drawSelectedText (Graphics& g, - const Range selected, - const Colour selectedTextColour) const + void drawSelectedText (Graphics& g, Range selected, Colour selectedTextColour) const { if (passwordCharacter != 0 || ! atom->isWhitespace()) { @@ -591,7 +583,7 @@ public: } //============================================================================== - float indexToX (const int indexToFind) const + float indexToX (int indexToFind) const { if (indexToFind <= indexInText) return atomX; @@ -610,7 +602,7 @@ public: return jmin (atomRight, g.getGlyph (indexToFind - indexInText).getLeft()); } - int xToIndex (const float xToFind) const + int xToIndex (float xToFind) const { if (xToFind <= atomX || atom->isNewLine()) return indexInText; @@ -623,7 +615,7 @@ public: atom->getText (passwordCharacter), atomX, 0.0f); - const int numGlyphs = g.getNumGlyphs(); + auto numGlyphs = g.getNumGlyphs(); int j; for (j = 0; j < numGlyphs; ++j) @@ -638,28 +630,26 @@ public: } //============================================================================== - bool getCharPosition (const int index, float& cx, float& cy, float& lineHeightFound) + bool getCharPosition (int index, Point& anchor, float& lineHeightFound) { while (next()) { if (indexInText + atom->numChars > index) { - cx = indexToX (index); - cy = lineY; + anchor = { indexToX (index), lineY }; lineHeightFound = lineHeight; return true; } } - cx = atomX; - cy = lineY; + anchor = { atomX, lineY }; lineHeightFound = lineHeight; return false; } //============================================================================== int indexInText = 0; - float lineY = 0, lineHeight = 0, maxDescent = 0; + float lineY = 0, justificationOffset = 0, lineHeight = 0, maxDescent = 0; float atomX = 0, atomRight = 0; const TextAtom* atom = nullptr; const UniformTextSection* currentSection = nullptr; @@ -667,13 +657,12 @@ public: private: const OwnedArray& sections; int sectionIndex = 0, atomIndex = 0; - const float wordWrapWidth; + Justification justification; + const float justificationWidth, wordWrapWidth; const juce_wchar passwordCharacter; const float lineSpacing; TextAtom tempAtom; - Iterator& operator= (const Iterator&) = delete; - void moveToEndOfLastAtom() { if (atom != nullptr) @@ -698,16 +687,10 @@ private: //============================================================================== -class TextEditor::InsertAction : public UndoableAction +struct TextEditor::InsertAction : public UndoableAction { -public: - InsertAction (TextEditor& ed, - const String& newText, - const int insertPos, - const Font& newFont, - const Colour newColour, - const int oldCaret, - const int newCaret) + InsertAction (TextEditor& ed, const String& newText, int insertPos, + const Font& newFont, Colour newColour, int oldCaret, int newCaret) : owner (ed), text (newText), insertIndex (insertPos), @@ -726,7 +709,7 @@ public: bool undo() override { - owner.remove (Range (insertIndex, insertIndex + text.length()), 0, oldCaretPos); + owner.remove ({ insertIndex, insertIndex + text.length() }, 0, oldCaretPos); return true; } @@ -746,13 +729,9 @@ private: }; //============================================================================== -class TextEditor::RemoveAction : public UndoableAction +struct TextEditor::RemoveAction : public UndoableAction { -public: - RemoveAction (TextEditor& ed, - const Range rangeToRemove, - const int oldCaret, - const int newCaret, + RemoveAction (TextEditor& ed, Range rangeToRemove, int oldCaret, int newCaret, const Array& oldSections) : owner (ed), range (rangeToRemove), @@ -795,11 +774,10 @@ private: }; //============================================================================== -class TextEditor::TextHolderComponent : public Component, - public Timer, - public Value::Listener +struct TextEditor::TextHolderComponent : public Component, + public Timer, + public Value::Listener { -public: TextHolderComponent (TextEditor& ed) : owner (ed) { setWantsKeyboardFocus (false); @@ -834,16 +812,14 @@ public: owner.textWasChangedByValue(); } -private: TextEditor& owner; JUCE_DECLARE_NON_COPYABLE (TextHolderComponent) }; //============================================================================== -class TextEditorViewport : public Viewport +struct TextEditor::TextEditorViewport : public Viewport { -public: TextEditorViewport (TextEditor& ed) : owner (ed) {} void visibleAreaChanged (const Rectangle&) override @@ -851,7 +827,7 @@ public: if (! rentrant) // it's rare, but possible to get into a feedback loop as the viewport's scrollbars // appear and disappear, causing the wrap width to change. { - const float wordWrapWidth = owner.getWordWrapWidth(); + auto wordWrapWidth = owner.getWordWrapWidth(); if (wordWrapWidth != lastWordWrapWidth) { @@ -882,7 +858,7 @@ namespace TextEditorDefs const int maxActionsPerTransaction = 100; - static int getCharacterCategory (const juce_wchar character) + static int getCharacterCategory (juce_wchar character) noexcept { return CharacterFunctions::isLetterOrDigit (character) ? 2 : (CharacterFunctions::isWhitespace (character) ? 0 : 1); @@ -980,7 +956,7 @@ void TextEditor::setScrollbarsShown (bool shown) } } -void TextEditor::setReadOnly (const bool shouldBeReadOnly) +void TextEditor::setReadOnly (bool shouldBeReadOnly) { if (readOnly != shouldBeReadOnly) { @@ -999,26 +975,35 @@ bool TextEditor::isTextInputActive() const return ! isReadOnly(); } -void TextEditor::setReturnKeyStartsNewLine (const bool shouldStartNewLine) +void TextEditor::setReturnKeyStartsNewLine (bool shouldStartNewLine) { returnKeyStartsNewLine = shouldStartNewLine; } -void TextEditor::setTabKeyUsedAsCharacter (const bool shouldTabKeyBeUsed) +void TextEditor::setTabKeyUsedAsCharacter (bool shouldTabKeyBeUsed) { tabKeyUsed = shouldTabKeyBeUsed; } -void TextEditor::setPopupMenuEnabled (const bool b) +void TextEditor::setPopupMenuEnabled (bool b) { popupMenuEnabled = b; } -void TextEditor::setSelectAllWhenFocused (const bool b) +void TextEditor::setSelectAllWhenFocused (bool b) { selectAllTextWhenFocused = b; } +void TextEditor::setJustification (Justification j) +{ + if (justification != j) + { + justification = j; + resized(); + } +} + //============================================================================== void TextEditor::setFont (const Font& newFont) { @@ -1074,7 +1059,7 @@ void TextEditor::enablementChanged() repaint(); } -void TextEditor::setCaretVisible (const bool shouldCaretBeVisible) +void TextEditor::setCaretVisible (bool shouldCaretBeVisible) { if (caretVisible != shouldCaretBeVisible) { @@ -1127,7 +1112,7 @@ void TextEditor::setInputFilter (InputFilter* newFilter, bool takeOwnership) inputFilter.set (newFilter, takeOwnership); } -void TextEditor::setInputRestrictions (const int maxLen, const String& chars) +void TextEditor::setInputRestrictions (int maxLen, const String& chars) { setInputFilter (new LengthAndCharacterRestriction (maxLen, chars), true); } @@ -1138,7 +1123,7 @@ void TextEditor::setTextToShowWhenEmpty (const String& text, Colour colourToUse) colourForTextWhenEmpty = colourToUse; } -void TextEditor::setPasswordCharacter (const juce_wchar newPasswordCharacter) +void TextEditor::setPasswordCharacter (juce_wchar newPasswordCharacter) { if (passwordCharacter != newPasswordCharacter) { @@ -1147,7 +1132,7 @@ void TextEditor::setPasswordCharacter (const juce_wchar newPasswordCharacter) } } -void TextEditor::setScrollBarThickness (const int newThicknessPixels) +void TextEditor::setScrollBarThickness (int newThicknessPixels) { viewport->setScrollBarThickness (newThicknessPixels); } @@ -1160,10 +1145,9 @@ void TextEditor::clear() undoManager.clearUndoHistory(); } -void TextEditor::setText (const String& newText, - const bool sendTextChangeMessage) +void TextEditor::setText (const String& newText, bool sendTextChangeMessage) { - const int newLength = newText.length(); + auto newLength = newText.length(); if (newLength != getTotalNumChars() || getText() != newText) { @@ -1173,7 +1157,7 @@ void TextEditor::setText (const String& newText, textValue = newText; auto oldCursorPos = caretPosition; - const bool cursorWasAtEnd = oldCursorPos >= getTotalNumChars(); + bool cursorWasAtEnd = oldCursorPos >= getTotalNumChars(); clearInternal (nullptr); insert (newText, 0, currentFont, findColour (textColourId), 0, caretPosition); @@ -1255,21 +1239,20 @@ void TextEditor::timerCallbackInt() newTransaction(); } -void TextEditor::repaintText (const Range range) +void TextEditor::repaintText (Range range) { if (! range.isEmpty()) { - float x = 0, y = 0, lh = currentFont.getHeight(); - - const float wordWrapWidth = getWordWrapWidth(); + auto lh = currentFont.getHeight(); + auto wordWrapWidth = getWordWrapWidth(); if (wordWrapWidth > 0) { - Iterator i (sections, wordWrapWidth, passwordCharacter, lineSpacing); + Point anchor; + Iterator i (*this); + i.getCharPosition (range.getStart(), anchor, lh); - i.getCharPosition (range.getStart(), x, y, lh); - - auto y1 = (int) y; + auto y1 = (int) anchor.y; int y2; if (range.getEnd() >= getTotalNumChars()) @@ -1278,8 +1261,8 @@ void TextEditor::repaintText (const Range range) } else { - i.getCharPosition (range.getEnd(), x, y, lh); - y2 = (int) (y + lh * 2.0f); + i.getCharPosition (range.getEnd(), anchor, lh); + y2 = (int) (anchor.y + lh * 2.0f); } textHolder->repaint (0, y1, textHolder->getWidth(), y2 - y1); @@ -1316,7 +1299,7 @@ void TextEditor::setCaretPosition (const int newIndex) void TextEditor::moveCaretToEnd() { - moveCaretTo (std::numeric_limits::max(), false); + setCaretPosition (std::numeric_limits::max()); } void TextEditor::scrollEditorToPositionCaret (const int desiredCaretX, @@ -1326,8 +1309,8 @@ void TextEditor::scrollEditorToPositionCaret (const int desiredCaretX, updateCaretPosition(); auto caretPos = getCaretRectangle(); - int vx = caretPos.getX() - desiredCaretX; - int vy = caretPos.getY() - desiredCaretY; + auto vx = caretPos.getX() - desiredCaretX; + auto vy = caretPos.getY() - desiredCaretY; if (desiredCaretX < jmax (1, proportionOfWidth (0.05f))) vx += desiredCaretX - proportionOfWidth (0.2f); @@ -1355,11 +1338,11 @@ void TextEditor::scrollEditorToPositionCaret (const int desiredCaretX, Rectangle TextEditor::getCaretRectangle() { - float cursorX, cursorY; - float cursorHeight = currentFont.getHeight(); // (in case the text is empty and the call below doesn't set this value) - getCharPosition (caretPosition, cursorX, cursorY, cursorHeight); + Point anchor; + auto cursorHeight = currentFont.getHeight(); // (in case the text is empty and the call below doesn't set this value) + getCharPosition (caretPosition, anchor, cursorHeight); - return { roundToInt (cursorX), roundToInt (cursorY), 2, roundToInt (cursorHeight) }; + return { roundToInt (anchor.x), roundToInt (anchor.y), 2, roundToInt (cursorHeight) }; } //============================================================================== @@ -1367,26 +1350,27 @@ enum { rightEdgeSpace = 2 }; float TextEditor::getWordWrapWidth() const { - return wordWrap ? (float) (viewport->getMaximumVisibleWidth() - (leftIndent + rightEdgeSpace + 1)) + return wordWrap ? getJustificationWidth() : std::numeric_limits::max(); } +float TextEditor::getJustificationWidth() const +{ + return (float) (viewport->getMaximumVisibleWidth() - (leftIndent + rightEdgeSpace + 1)); +} + void TextEditor::updateTextHolderSize() { - const float wordWrapWidth = getWordWrapWidth(); - - if (wordWrapWidth > 0) + if (getWordWrapWidth() > 0) { - float maxWidth = 0.0f; - - Iterator i (sections, wordWrapWidth, passwordCharacter, lineSpacing); + float maxWidth = getJustificationWidth(); + Iterator i (*this); while (i.next()) maxWidth = jmax (maxWidth, i.atomRight); - const int w = leftIndent + roundToInt (maxWidth); - const int h = topIndent + roundToInt (jmax (i.lineY + i.lineHeight, - currentFont.getHeight())); + auto w = leftIndent + roundToInt (maxWidth); + auto h = topIndent + roundToInt (jmax (i.lineY + i.lineHeight, currentFont.getHeight())); textHolder->setSize (w + rightEdgeSpace, h + 1); // (allows a bit of space for the cursor to be at the right-hand-edge) } @@ -1395,7 +1379,7 @@ void TextEditor::updateTextHolderSize() 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 (int newLeftIndent, int newTopIndent) { leftIndent = newLeftIndent; topIndent = newTopIndent; @@ -1567,15 +1551,12 @@ void TextEditor::cut() //============================================================================== void TextEditor::drawContent (Graphics& g) { - const float wordWrapWidth = getWordWrapWidth(); - - if (wordWrapWidth > 0) + if (getWordWrapWidth() > 0) { g.setOrigin (leftIndent, topIndent); auto clip = g.getClipBounds(); Colour selectedTextColour; - - Iterator i (sections, wordWrapWidth, passwordCharacter, lineSpacing); + Iterator i (*this); if (! selection.isEmpty()) { @@ -1585,7 +1566,7 @@ void TextEditor::drawContent (Graphics& g) while (i2.next() && i2.lineY < clip.getBottom()) { if (i2.lineY + i2.lineHeight >= clip.getY() - && selection.intersects (Range (i2.indexInText, i2.indexInText + i2.atom->numChars))) + && selection.intersects ({ i2.indexInText, i2.indexInText + i2.atom->numChars })) { i2.addSelection (selectionArea, selection); } @@ -1603,7 +1584,7 @@ void TextEditor::drawContent (Graphics& g) { if (i.lineY + i.lineHeight >= clip.getY()) { - if (selection.intersects (Range (i.indexInText, i.indexInText + i.atom->numChars))) + if (selection.intersects ({ i.indexInText, i.indexInText + i.atom->numChars })) { i.drawSelectedText (g, selection, selectedTextColour); lastSection = nullptr; @@ -1615,16 +1596,14 @@ void TextEditor::drawContent (Graphics& g) } } - for (int j = underlinedSections.size(); --j >= 0;) + for (auto& underlinedSection : underlinedSections) { - const Range underlinedSection = underlinedSections.getReference (j); - - Iterator i2 (sections, wordWrapWidth, passwordCharacter, lineSpacing); + Iterator i2 (*this); while (i2.next() && i2.lineY < clip.getBottom()) { if (i2.lineY + i2.lineHeight >= clip.getY() - && underlinedSection.intersects (Range (i2.indexInText, i2.indexInText + i2.atom->numChars))) + && underlinedSection.intersects ({ i2.indexInText, i2.indexInText + i2.atom->numChars })) { i2.drawUnderline (g, underlinedSection, findColour (textColourId)); } @@ -1764,8 +1743,9 @@ void TextEditor::mouseDoubleClick (const MouseEvent& e) while (tokenEnd < totalLength) { + auto c = t[tokenEnd]; + // (note the slight bodge here - it's because iswalnum only checks for alphabetic chars in the current locale) - const juce_wchar c = t [tokenEnd]; if (CharacterFunctions::isLetterOrDigit (c) || c > 128) ++tokenEnd; else @@ -1776,8 +1756,9 @@ void TextEditor::mouseDoubleClick (const MouseEvent& e) while (tokenStart > 0) { + auto c = t[tokenStart - 1]; + // (note the slight bodge here - it's because iswalnum only checks for alphabetic chars in the current locale) - const juce_wchar c = t [tokenStart - 1]; if (CharacterFunctions::isLetterOrDigit (c) || c > 128) --tokenStart; else @@ -1788,7 +1769,8 @@ void TextEditor::mouseDoubleClick (const MouseEvent& e) { while (tokenEnd < totalLength) { - const juce_wchar c = t [tokenEnd]; + auto c = t[tokenEnd]; + if (c != '\r' && c != '\n') ++tokenEnd; else @@ -1797,7 +1779,8 @@ void TextEditor::mouseDoubleClick (const MouseEvent& e) while (tokenStart > 0) { - const juce_wchar c = t [tokenStart - 1]; + auto c = t[tokenStart - 1]; + if (c != '\r' && c != '\n') --tokenStart; else @@ -1826,7 +1809,7 @@ bool TextEditor::moveCaretWithTransaction (const int newPos, const bool selectin bool TextEditor::moveCaretLeft (bool moveInWholeWordSteps, bool selecting) { - int pos = getCaretPosition(); + auto pos = getCaretPosition(); if (moveInWholeWordSteps) pos = findWordBreakBefore (pos); @@ -1838,7 +1821,7 @@ bool TextEditor::moveCaretLeft (bool moveInWholeWordSteps, bool selecting) bool TextEditor::moveCaretRight (bool moveInWholeWordSteps, bool selecting) { - int pos = getCaretPosition(); + auto pos = getCaretPosition(); if (moveInWholeWordSteps) pos = findWordBreakAfter (pos); @@ -1929,7 +1912,7 @@ bool TextEditor::deleteBackwards (bool moveInWholeWordSteps) if (moveInWholeWordSteps) moveCaretTo (findWordBreakBefore (getCaretPosition()), true); else if (selection.isEmpty() && selection.getStart() > 0) - selection = Range (selection.getEnd() - 1, selection.getEnd()); + selection = { selection.getEnd() - 1, selection.getEnd() }; cut(); return true; @@ -1938,7 +1921,7 @@ bool TextEditor::deleteBackwards (bool moveInWholeWordSteps) bool TextEditor::deleteForwards (bool /*moveInWholeWordSteps*/) { if (selection.isEmpty() && selection.getStart() < getTotalNumChars()) - selection = Range (selection.getStart(), selection.getStart() + 1); + selection = { selection.getStart(), selection.getStart() + 1 }; cut(); return true; @@ -1993,7 +1976,9 @@ bool TextEditor::keyPressed (const KeyPress& key) newTransaction(); if (returnKeyStartsNewLine) + { insertTextAtCaret ("\n"); + } else { returnPressed(); @@ -2136,15 +2121,11 @@ UndoManager* TextEditor::getUndoManager() noexcept void TextEditor::clearInternal (UndoManager* const um) { - remove (Range (0, getTotalNumChars()), um, caretPosition); + remove ({ 0, getTotalNumChars() }, um, caretPosition); } -void TextEditor::insert (const String& text, - const int insertIndex, - const Font& font, - const Colour colour, - UndoManager* const um, - const int caretPositionToMoveTo) +void TextEditor::insert (const String& text, int insertIndex, const Font& font, + Colour colour, UndoManager* um, int caretPositionToMoveTo) { if (text.isNotEmpty()) { @@ -2158,8 +2139,8 @@ void TextEditor::insert (const String& text, } else { - repaintText (Range (insertIndex, getTotalNumChars())); // must do this before and after changing the data, in case - // a line gets moved due to word wrap + repaintText ({ insertIndex, getTotalNumChars() }); // must do this before and after changing the data, in case + // a line gets moved due to word wrap int index = 0; int nextIndex = 0; @@ -2173,7 +2154,8 @@ void TextEditor::insert (const String& text, sections.insert (i, new UniformTextSection (text, font, colour, passwordCharacter)); break; } - else if (insertIndex > index && insertIndex < nextIndex) + + if (insertIndex > index && insertIndex < nextIndex) { splitSection (i, insertIndex - index); sections.insert (i + 1, new UniformTextSection (text, font, colour, passwordCharacter)); @@ -2193,12 +2175,12 @@ void TextEditor::insert (const String& text, updateTextHolderSize(); moveCaretTo (caretPositionToMoveTo, false); - repaintText (Range (insertIndex, getTotalNumChars())); + repaintText ({ insertIndex, getTotalNumChars() }); } } } -void TextEditor::reinsert (const int insertIndex, const OwnedArray& sectionsToInsert) +void TextEditor::reinsert (int insertIndex, const OwnedArray& sectionsToInsert) { int index = 0; int nextIndex = 0; @@ -2214,7 +2196,8 @@ void TextEditor::reinsert (const int insertIndex, const OwnedArray index && insertIndex < nextIndex) + + if (insertIndex > index && insertIndex < nextIndex) { splitSection (i, insertIndex - index); @@ -2228,10 +2211,8 @@ void TextEditor::reinsert (const int insertIndex, const OwnedArray range, UndoManager* const um, const int care for (int i = 0; i < sections.size(); ++i) { - const int nextIndex = index + sections.getUnchecked(i)->getTotalLength(); + auto nextIndex = index + sections.getUnchecked(i)->getTotalLength(); if (range.getStart() > index && range.getStart() < nextIndex) { @@ -2273,12 +2254,11 @@ void TextEditor::remove (Range range, UndoManager* const um, const int care { Array removedSections; - for (int i = 0; i < sections.size(); ++i) + for (auto* section : sections) { if (range.getEnd() <= range.getStart()) break; - auto* section = sections.getUnchecked (i); auto nextIndex = index + section->getTotalLength(); if (range.getStart() <= index && range.getEnd() >= nextIndex) @@ -2300,8 +2280,7 @@ void TextEditor::remove (Range range, UndoManager* const um, const int care for (int i = 0; i < sections.size(); ++i) { auto* section = sections.getUnchecked (i); - - const int nextIndex = index + section->getTotalLength(); + auto nextIndex = index + section->getTotalLength(); if (remainingRange.getStart() <= index && remainingRange.getEnd() >= nextIndex) { @@ -2325,7 +2304,7 @@ void TextEditor::remove (Range range, UndoManager* const um, const int care moveCaretTo (caretPositionToMoveTo, false); - repaintText (Range (range.getStart(), getTotalNumChars())); + repaintText ({ range.getStart(), getTotalNumChars() }); } } } @@ -2336,8 +2315,8 @@ String TextEditor::getText() const MemoryOutputStream mo; mo.preallocate ((size_t) getTotalNumChars()); - for (int i = 0; i < sections.size(); ++i) - sections.getUnchecked (i)->appendAllText (mo); + for (auto* s : sections) + s->appendAllText (mo); return mo.toUTF8(); } @@ -2352,9 +2331,8 @@ String TextEditor::getTextInRange (const Range& range) const int index = 0; - for (int i = 0; i < sections.size(); ++i) + for (auto* s : sections) { - auto* s = sections.getUnchecked (i); auto nextIndex = index + s->getTotalLength(); if (range.getStart() < nextIndex) @@ -2382,8 +2360,8 @@ int TextEditor::getTotalNumChars() const { totalNumChars = 0; - for (int i = sections.size(); --i >= 0;) - totalNumChars += sections.getUnchecked (i)->getTotalLength(); + for (auto* s : sections) + totalNumChars += s->getTotalLength(); } return totalNumChars; @@ -2394,32 +2372,34 @@ bool TextEditor::isEmpty() const return getTotalNumChars() == 0; } -void TextEditor::getCharPosition (const int index, float& cx, float& cy, float& lineHeight) const +void TextEditor::getCharPosition (int index, Point& anchor, float& lineHeight) const { - const float wordWrapWidth = getWordWrapWidth(); - - if (wordWrapWidth > 0 && sections.size() > 0) + if (getWordWrapWidth() <= 0) { - Iterator i (sections, wordWrapWidth, passwordCharacter, lineSpacing); - - i.getCharPosition (index, cx, cy, lineHeight); + anchor = {}; + lineHeight = currentFont.getHeight(); } else { - cx = cy = 0; - lineHeight = currentFont.getHeight(); + Iterator i (*this); + + if (sections.isEmpty()) + { + anchor = { i.getJustificationOffset (0), 0 }; + lineHeight = currentFont.getHeight(); + } + else + { + i.getCharPosition (index, anchor, lineHeight); + } } } int TextEditor::indexAtPosition (const float x, const float y) { - const float wordWrapWidth = getWordWrapWidth(); - - if (wordWrapWidth > 0) + if (getWordWrapWidth() > 0) { - Iterator i (sections, wordWrapWidth, passwordCharacter, lineSpacing); - - while (i.next()) + for (Iterator i (*this); i.next();) { if (i.lineY + i.lineHeight > y) { @@ -2441,14 +2421,14 @@ int TextEditor::indexAtPosition (const float x, const float y) //============================================================================== int TextEditor::findWordBreakAfter (const int position) const { - auto t = getTextInRange (Range (position, position + 512)); + auto t = getTextInRange ({ position, position + 512 }); auto totalLength = t.length(); int i = 0; while (i < totalLength && CharacterFunctions::isWhitespace (t[i])) ++i; - const int type = TextEditorDefs::getCharacterCategory (t[i]); + auto type = TextEditorDefs::getCharacterCategory (t[i]); while (i < totalLength && type == TextEditorDefs::getCharacterCategory (t[i])) ++i; @@ -2465,7 +2445,7 @@ int TextEditor::findWordBreakBefore (const int position) const return 0; auto startOfBuffer = jmax (0, position - 512); - auto t = getTextInRange (Range (startOfBuffer, position)); + auto t = getTextInRange ({ startOfBuffer, position }); int i = position - startOfBuffer; diff --git a/modules/juce_gui_basics/widgets/juce_TextEditor.h b/modules/juce_gui_basics/widgets/juce_TextEditor.h index 7077d57c04..3315c583c3 100644 --- a/modules/juce_gui_basics/widgets/juce_TextEditor.h +++ b/modules/juce_gui_basics/widgets/juce_TextEditor.h @@ -475,8 +475,10 @@ public: */ void setScrollToShowCursor (bool shouldScrollToShowCaret); - /** Sets the line spacing of the TextEditor. + /** Modifies the horizontal justification of the text within the editor window. */ + void setJustification (Justification newJustification); + /** Sets the line spacing of the TextEditor. The default (and minimum) value is 1.0 and values > 1.0 will increase the line spacing as a multiple of the line height e.g. for double-spacing call this method with an argument of 2.0. */ @@ -680,17 +682,17 @@ protected: private: //============================================================================== - class Iterator; JUCE_PUBLIC_IN_DLL_BUILD (class UniformTextSection) - class TextHolderComponent; - class InsertAction; - class RemoveAction; - friend class InsertAction; - friend class RemoveAction; + struct Iterator; + struct TextHolderComponent; + struct TextEditorViewport; + struct InsertAction; + struct RemoveAction; ScopedPointer viewport; TextHolderComponent* textHolder; BorderSize borderSize { 1, 1, 1, 3 }; + Justification justification { Justification::left }; bool readOnly = false; bool caretVisible = true; @@ -743,10 +745,10 @@ private: void coalesceSimilarSections(); void splitSection (int sectionIndex, int charToSplitAt); void clearInternal (UndoManager*); - void insert (const String&, int insertIndex, const Font&, const Colour, UndoManager*, int newCaretPos); + void insert (const String&, int insertIndex, const Font&, Colour, UndoManager*, int newCaretPos); void reinsert (int insertIndex, const OwnedArray&); - void remove (Range range, UndoManager*, int caretPositionToMoveTo); - void getCharPosition (int index, float& x, float& y, float& lineHeight) const; + void remove (Range, UndoManager*, int caretPositionToMoveTo); + void getCharPosition (int index, Point&, float& lineHeight) const; void updateCaretPosition(); void updateValueFromText(); void textWasChangedByValue(); @@ -754,11 +756,10 @@ private: int findWordBreakAfter (int position) const; int findWordBreakBefore (int position) const; bool moveCaretWithTransaction (int newPos, bool selecting); - friend class TextHolderComponent; - friend class TextEditorViewport; void drawContent (Graphics&); void updateTextHolderSize(); float getWordWrapWidth() const; + float getJustificationWidth() const; void timerCallbackInt(); void repaintText (Range); void scrollByLines (int deltaLines);