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

TextEditor: Improve Unicode editing behaviour and performance

This commit is contained in:
attila 2025-03-13 09:54:01 +01:00 committed by Attila Szarvas
parent 427852836c
commit 9ce2feaf41
30 changed files with 1370 additions and 1059 deletions

View file

@ -2411,6 +2411,7 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_gui_basics/widgets/juce_TableListBox.h"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditor.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditor.h"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditorModel.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_Toolbar.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_Toolbar.h"
"../../../../../modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp"
@ -5016,6 +5017,7 @@ set_source_files_properties(
"../../../../../modules/juce_gui_basics/widgets/juce_TableListBox.h"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditor.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditor.h"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditorModel.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_Toolbar.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_Toolbar.h"
"../../../../../modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp"

View file

@ -2949,6 +2949,9 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditor.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditorModel.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_Toolbar.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>

View file

@ -3772,6 +3772,9 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditor.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditorModel.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_Toolbar.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>

View file

@ -2949,6 +2949,9 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditor.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditorModel.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_Toolbar.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>

View file

@ -3772,6 +3772,9 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditor.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditorModel.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_Toolbar.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>

View file

@ -2173,6 +2173,7 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_gui_basics/widgets/juce_TableListBox.h"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditor.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditor.h"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditorModel.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_Toolbar.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_Toolbar.h"
"../../../../../modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp"
@ -4438,6 +4439,7 @@ set_source_files_properties(
"../../../../../modules/juce_gui_basics/widgets/juce_TableListBox.h"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditor.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditor.h"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditorModel.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_Toolbar.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_Toolbar.h"
"../../../../../modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp"

View file

@ -2642,6 +2642,9 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditor.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditorModel.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_Toolbar.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>

View file

@ -3289,6 +3289,9 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditor.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditorModel.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_Toolbar.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>

View file

@ -2303,6 +2303,7 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_gui_basics/widgets/juce_TableListBox.h"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditor.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditor.h"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditorModel.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_Toolbar.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_Toolbar.h"
"../../../../../modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp"
@ -4721,6 +4722,7 @@ set_source_files_properties(
"../../../../../modules/juce_gui_basics/widgets/juce_TableListBox.h"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditor.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditor.h"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditorModel.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_Toolbar.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_Toolbar.h"
"../../../../../modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp"

View file

@ -2776,6 +2776,9 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditor.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditorModel.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_Toolbar.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>

View file

@ -3496,6 +3496,9 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditor.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditorModel.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_Toolbar.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>

View file

@ -2776,6 +2776,9 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditor.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditorModel.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_Toolbar.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>

View file

@ -3496,6 +3496,9 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditor.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditorModel.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_Toolbar.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>

View file

@ -2192,6 +2192,7 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_gui_basics/widgets/juce_TableListBox.h"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditor.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditor.h"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditorModel.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_Toolbar.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_Toolbar.h"
"../../../../../modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp"
@ -4537,6 +4538,7 @@ set_source_files_properties(
"../../../../../modules/juce_gui_basics/widgets/juce_TableListBox.h"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditor.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditor.h"
"../../../../../modules/juce_gui_basics/widgets/juce_TextEditorModel.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_Toolbar.cpp"
"../../../../../modules/juce_gui_basics/widgets/juce_Toolbar.h"
"../../../../../modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp"

View file

@ -2663,6 +2663,9 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditor.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditorModel.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_Toolbar.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>

View file

@ -3343,6 +3343,9 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditor.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditorModel.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_Toolbar.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>

View file

@ -1805,6 +1805,9 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditor.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditorModel.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_Toolbar.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>

View file

@ -2182,6 +2182,9 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditor.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditorModel.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_Toolbar.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>

View file

@ -1805,6 +1805,9 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditor.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditorModel.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_Toolbar.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>

View file

@ -2182,6 +2182,9 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditor.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditorModel.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_Toolbar.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>

View file

@ -2784,6 +2784,9 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditor.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditorModel.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_Toolbar.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>

View file

@ -3544,6 +3544,9 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditor.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditorModel.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_Toolbar.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>

View file

@ -2784,6 +2784,9 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditor.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditorModel.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_Toolbar.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>

View file

@ -3544,6 +3544,9 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditor.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditorModel.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_Toolbar.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>

View file

@ -2662,6 +2662,9 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditor.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditorModel.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_Toolbar.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>

View file

@ -3340,6 +3340,9 @@
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditor.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_TextEditorModel.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\modules\juce_gui_basics\widgets\juce_Toolbar.cpp">
<Filter>JUCE Modules\juce_gui_basics\widgets</Filter>
</ClCompile>

View file

@ -369,6 +369,7 @@
#include "widgets/juce_Slider.cpp"
#include "widgets/juce_TableHeaderComponent.cpp"
#include "widgets/juce_TableListBox.cpp"
#include "widgets/juce_TextEditorModel.cpp"
#include "widgets/juce_TextEditor.cpp"
#include "widgets/juce_Toolbar.cpp"
#include "widgets/juce_ToolbarItemComponent.cpp"

File diff suppressed because it is too large Load diff

View file

@ -252,6 +252,9 @@ public:
/** Applies a font to all the text in the editor.
This function also calls
applyColourToAllText (findColour (TextEditor::ColourIds::textColourId), false);
If the changeCurrentFont argument is true then this will also set the
new font as the font to be used for any new text that's added.
@ -774,8 +777,6 @@ protected:
private:
//==============================================================================
JUCE_PUBLIC_IN_DLL_BUILD (class UniformTextSection)
struct Iterator;
struct TextHolderComponent;
struct TextEditorViewport;
struct InsertAction;
@ -827,8 +828,51 @@ private:
unsigned int lastTransactionTime = 0;
Font currentFont { withDefaultMetrics (FontOptions { 14.0f }) };
mutable int totalNumChars = 0;
int caretPosition = 0;
OwnedArray<UniformTextSection> sections;
//==============================================================================
enum class Edge
{
leading,
trailing
};
//==============================================================================
struct CaretState
{
public:
explicit CaretState (const TextEditor* ownerIn);
int getPosition() const { return position; }
Edge getEdge() const { return edge; }
void setPosition (int newPosition);
/* Not all visual edge positions are permitted e.g. a trailing caret after a newline
is not allowed. getVisualIndex() and getEdge() will return the closest permitted
values to the preferred one.
*/
void setPreferredEdge (Edge newEdge);
/* The returned value is in the range [0, TextEditor::getTotalNumChars()]. It returns the
glyph index to which the caret is closest visually. This is significant when
differentiating between the end of one line and the beginning of the next.
*/
int getVisualIndex() const;
void updateEdge();
//==============================================================================
CaretState withPosition (int newPosition) const;
CaretState withPreferredEdge (Edge newEdge) const;
private:
const TextEditor& owner;
int position = 0;
Edge edge = Edge::trailing;
Edge preferredEdge = Edge::trailing;
};
//==============================================================================
String textToShowWhenEmpty;
Colour colourForTextWhenEmpty;
juce_wchar passwordCharacter;
@ -849,17 +893,21 @@ private:
ListenerList<Listener> listeners;
Array<Range<int>> underlinedSections;
class ParagraphStorage;
class ParagraphsModel;
struct TextEditorStorageChunks;
class TextEditorStorage;
void moveCaret (int newCaretPos);
void moveCaretTo (int newPosition, bool isSelecting);
void recreateCaret();
void handleCommandMessage (int) override;
void coalesceSimilarSections();
void splitSection (int sectionIndex, int charToSplitAt);
void clearInternal (UndoManager*);
void insert (const String&, int insertIndex, const Font&, Colour, UndoManager*, int newCaretPos);
void reinsert (int insertIndex, const OwnedArray<UniformTextSection>&);
void remove (Range<int>, UndoManager*, int caretPositionToMoveTo);
void getCharPosition (int index, Point<float>&, float& lineHeight) const;
void reinsert (const TextEditorStorageChunks& chunks);
void remove (Range<int>, UndoManager*, int caretPositionToMoveTo, TextEditorStorageChunks* removedOut = nullptr);
std::pair<Point<float>, float> getTextSelectionEdge (int index, Edge edge) const;
std::pair<Point<float>, float> getCursorEdge (const CaretState& caret) const;
void updateCaretPosition();
void updateValueFromText();
void textWasChangedByValue();
@ -879,7 +927,21 @@ private:
bool undoOrRedo (bool shouldUndo);
UndoManager* getUndoManager() noexcept;
void setSelection (Range<int>) noexcept;
Point<int> getTextOffset() const noexcept;
Point<int> getTextOffset() const;
Edge getEdgeTypeCloserToPosition (int indexInText, Point<float> pos) const;
std::unique_ptr<TextEditorStorage> textStorage;
CaretState caretState;
bool isTextStorageHeightGreaterEqualThan (float value) const;
float getTextStorageHeight() const;
float getYOffset() const;
void updateBaseShapedTextOptions();
Range<int64> getLineRangeForIndex (int index);
template <typename T>
detail::RangedValues<T> getGlyphRanges (const detail::RangedValues<T>& textRanges) const;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TextEditor)
};

View file

@ -0,0 +1,572 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
class TextEditor::ParagraphStorage
{
public:
ParagraphStorage (String s, const TextEditorStorage* storageIn)
: text { std::move (s) },
numBytesAsUTF8 { text.getNumBytesAsUTF8() },
storage { *storageIn }
{
updatePasswordReplacementText();
}
const String& getText() const
{
return text;
}
const String& getTextForDisplay() const;
size_t getNumBytesAsUTF8() const;
void setRange (Range<int64> rangeIn)
{
range = rangeIn;
}
auto getRange() const
{
return range;
}
const detail::ShapedText& getShapedText();
float getHeight()
{
if (! height.has_value())
height = getShapedText().getHeight();
return *height;
}
int64 getNumGlyphs()
{
if (! numGlyphs.has_value())
numGlyphs = getShapedText().getNumGlyphs();
return *numGlyphs;
}
float getTop();
int64 getStartingGlyph();
void clearShapedText();
private:
void updatePasswordReplacementText();
String text;
std::optional<String> passwordReplacementText;
size_t numBytesAsUTF8;
Range<int64> range;
const TextEditorStorage& storage;
std::optional<detail::ShapedText> shapedText;
std::optional<float> height;
std::optional<int64> numGlyphs;
};
//==============================================================================
class TextEditor::ParagraphsModel
{
public:
using ParagraphItem = detail::RangedValuesIteratorItem<const std::unique_ptr<ParagraphStorage>>;
explicit ParagraphsModel (const TextEditorStorage* ownerIn)
: owner { *ownerIn }
{}
void set (Range<int64> range, const String& text)
{
using namespace detail;
const auto codepointBeforeRange = getTextInRange (Range<int64>::withStartAndLength (range.getStart() - 1, 1));
Ranges::Operations ops;
ranges.drop (range, ops);
if (! text.isEmpty())
{
ranges.insert ({ range.getStart(), range.getStart() + text.length() }, ops);
mergeForward (ranges, *ranges.getIndexForEnclosingRange (range.getStart()), ops);
}
if (const auto newParagraphIndex = ranges.getIndexForEnclosingRange (range.getStart()))
ranges.mergeBack (*newParagraphIndex, ops);
const auto splitBeforeOffset = range.getStart() + 1 - (codepointBeforeRange.isEmpty() ? 0 : 1);
for (auto breakAfterIndex : UnicodeHelpers::getLineBreaks (codepointBeforeRange + text))
ranges.split (breakAfterIndex + splitBeforeOffset, ops);
handleOps (ops, text);
}
String getText() const
{
const auto numBytes = std::accumulate (storage.begin(),
storage.end(),
(size_t) 0,
[] (auto sum, auto& p)
{
return sum + p->getNumBytesAsUTF8();
});
MemoryOutputStream mo;
mo.preallocate (numBytes);
for (const auto& paragraph : storage)
mo << paragraph->getText();
return mo.toUTF8();
}
String getTextInRange (Range<int64> range) const
{
String text;
for (const auto& partialRange : ranges.getIntersectionsWith (range))
{
const auto i = *ranges.getIndexForEnclosingRange (partialRange.getStart());
const auto fullRange = ranges.get (i);
auto& paragraph = *storage[i];
const auto startInParagraph = (int) (partialRange.getStart() - fullRange.getStart());
text += paragraph.getText().substring (startInParagraph,
startInParagraph + (int) partialRange.getLength());
}
return text;
}
auto begin() const
{
return detail::RangedValuesIterator<const std::unique_ptr<ParagraphStorage>> { storage.data(),
ranges.data(),
ranges.data() };
}
auto end() const
{
return detail::RangedValuesIterator<const std::unique_ptr<ParagraphStorage>> { storage.data(),
ranges.data(),
ranges.data() + ranges.size() };
}
std::optional<ParagraphItem> getParagraphContainingCodepointIndex (int64 index)
{
const auto paragraphIndex = ranges.getIndexForEnclosingRange (index);
if (! paragraphIndex.has_value())
return std::nullopt;
return ParagraphItem { ranges.get (*paragraphIndex),
storage[*paragraphIndex] };
}
bool isEmpty() const { return storage.empty(); }
ParagraphItem back() const
{
jassert (! ranges.isEmpty());
return { ranges.get (ranges.size() - 1), storage.back() };
}
int64 getTotalNumChars() const
{
if (ranges.isEmpty())
return 0;
return ranges.getRanges().back().getEnd();
}
int64 getTotalNumGlyphs() const
{
return std::accumulate (storage.begin(),
storage.end(),
(int64) 0,
[] (const auto& sum, const auto& item)
{
return sum + item->getNumGlyphs();
});
}
private:
static void mergeForward (detail::Ranges& ranges, size_t index, detail::Ranges::Operations& ops)
{
if (ranges.size() > index + 1)
return ranges.mergeBack (index + 1, ops);
}
void handleOps (const detail::Ranges::Operations& ops, const String& text)
{
using namespace detail;
for (const auto& op : ops)
{
if (auto* newOp = std::get_if<Ranges::Ops::New> (&op))
{
storage.insert (iteratorWithAdvance (storage.begin(), newOp->index),
createParagraph (text));
}
else if (auto* split = std::get_if<Ranges::Ops::Split> (&op))
{
const auto& splitValue = storage[split->index]->getText();
const auto localLeftRange = split->leftRange.movedToStartAt (0);
const auto localRightRange = split->rightRange.movedToStartAt (localLeftRange.getEnd());
auto leftSplitValue = splitValue.substring ((int) localLeftRange.getStart(),
(int) localLeftRange.getEnd());
auto rightSplitValue = splitValue.substring ((int) localRightRange.getStart(),
(int) localRightRange.getEnd());
storage[split->index] = createParagraph (std::move (leftSplitValue));
storage.insert (iteratorWithAdvance (storage.begin(), split->index + 1),
createParagraph (std::move (rightSplitValue)));
}
else if (auto* erased = std::get_if<Ranges::Ops::Erase> (&op))
{
storage.erase (iteratorWithAdvance (storage.begin(), erased->range.getStart()),
iteratorWithAdvance (storage.begin(), erased->range.getEnd()));
}
else if (auto* changed = std::get_if<Ranges::Ops::Change> (&op))
{
const auto oldRange = changed->oldRange;
const auto newRange = changed->newRange;
// This happens when a range just gets shifted due to drop or insert operations
if (oldRange.getLength() == newRange.getLength())
continue;
auto deltaStart = (int) (newRange.getStart() - oldRange.getStart());
auto deltaEnd = (int) (newRange.getEnd() - oldRange.getEnd());
auto& paragraph = storage[changed->index];
const auto& oldText = paragraph->getText();
jassert (deltaStart >= 0);
if (deltaEnd <= 0)
{
paragraph = createParagraph (oldText.substring (deltaStart, oldText.length() + deltaEnd));
}
else
{
jassert (changed->index + 1 < storage.size());
paragraph = createParagraph (oldText.substring (deltaStart, oldText.length())
+ storage[changed->index + 1]->getText().substring (0, deltaEnd));
}
}
}
for (const auto [index, range] : enumerate (ranges, size_t{}))
storage[index]->setRange (range);
}
std::unique_ptr<ParagraphStorage> createParagraph (String s) const
{
return std::make_unique<ParagraphStorage> (s, &owner);
}
const TextEditorStorage& owner;
detail::Ranges ranges;
std::vector<std::unique_ptr<ParagraphStorage>> storage;
};
//==============================================================================
struct TextEditor::TextEditorStorageChunks
{
std::vector<int64> positions;
std::vector<String> texts;
std::vector<Font> fonts;
std::vector<Colour> colours;
};
//==============================================================================
class TextEditor::TextEditorStorage
{
public:
void set (Range<int64> range, const String& text, const Font& font, const Colour& colour)
{
paragraphs.set (range, text);
detail::Ranges::Operations ops;
fonts.drop (range, ops);
colours.drop (range, ops);
ops.clear();
const auto insertionRange = Range<int64>::withStartAndLength (range.getStart(),
(int64) text.length());
fonts.insert (insertionRange, font, ops);
colours.insert (insertionRange, colour, ops);
}
void setFontForAllText (const Font& font)
{
detail::Ranges::Operations ops;
fonts.set ({ 0, paragraphs.getTotalNumChars() }, font, ops);
clearShapedTexts();
}
void setColourForAllText (const Colour& colour)
{
detail::Ranges::Operations ops;
colours.set ({ 0, paragraphs.getTotalNumChars() }, colour, ops);
clearShapedTexts();
}
void remove (Range<int64> range, TextEditorStorageChunks* removedOut)
{
using namespace detail;
detail::Ranges::Operations ops;
RangedValues<int64> rangeConstraint;
rangeConstraint.set (range, 0, ops);
if (removedOut != nullptr)
{
for (const auto [r, font, colour, _] : makeIntersectingRangedValues (&fonts, &colours, &rangeConstraint))
{
ignoreUnused (_);
removedOut->positions.push_back (r.getStart());
removedOut->texts.push_back (getTextInRange (r));
removedOut->fonts.push_back (font);
removedOut->colours.push_back (colour);
}
}
paragraphs.set (range, "");
ops.clear();
fonts.drop (range, ops);
colours.drop (range, ops);
}
void addChunks (const TextEditorStorageChunks& chunks)
{
for (size_t i = 0; i < chunks.positions.size(); ++i)
{
set (Range<int64>::withStartAndLength (chunks.positions[i], 0),
chunks.texts[i],
chunks.fonts[i],
chunks.colours[i]);
}
}
String getText() const
{
return paragraphs.getText();
}
String getTextInRange (Range<int64> range) const
{
return paragraphs.getTextInRange (range);
}
detail::RangedValues<Font> getFonts (Range<int64> range) const
{
return fonts.getIntersectionsStartingAtZeroWith (range);
}
const auto& getColours() const
{
return colours;
}
auto begin() const { return paragraphs.begin(); }
auto end() const { return paragraphs.end(); }
auto isEmpty() const { return paragraphs.isEmpty(); }
auto back() const { return paragraphs.back(); }
std::optional<Font> getLastFont() const
{
if (fonts.isEmpty())
return std::nullopt;
return fonts.back().value;
}
int64 getTotalNumChars() const
{
return paragraphs.getTotalNumChars();
}
int64 getTotalNumGlyphs() const
{
return paragraphs.getTotalNumGlyphs();
}
void setBaseShapedTextOptions (detail::ShapedTextOptions options, juce_wchar passwordCharacterIn)
{
if (std::exchange (baseShapedTextOptions, options) != options)
clearShapedTexts();
if (std::exchange (passwordCharacter, passwordCharacterIn) != passwordCharacterIn)
clearShapedTexts();
}
detail::ShapedTextOptions getShapedTextOptions (Range<int64> range) const
{
return baseShapedTextOptions.withFonts (getFonts (range));
}
juce_wchar getPasswordCharacter() const
{
return passwordCharacter;
}
auto getParagraphContainingCodepointIndex (int64 index)
{
return paragraphs.getParagraphContainingCodepointIndex (index);
}
private:
void clearShapedTexts()
{
for (auto p : paragraphs)
p.value->clearShapedText();
}
detail::RangedValues<Font> fonts;
detail::RangedValues<Colour> colours;
ParagraphsModel paragraphs { this };
detail::ShapedTextOptions baseShapedTextOptions;
juce_wchar passwordCharacter = 0;
};
//==============================================================================
const String& TextEditor::ParagraphStorage::getTextForDisplay() const
{
if (passwordReplacementText.has_value())
return *passwordReplacementText;
return text;
}
size_t TextEditor::ParagraphStorage::getNumBytesAsUTF8() const
{
return numBytesAsUTF8;
}
const detail::ShapedText& TextEditor::ParagraphStorage::getShapedText()
{
if (! shapedText.has_value())
shapedText.emplace (getTextForDisplay(), storage.getShapedTextOptions (range));
return *shapedText;
}
float TextEditor::ParagraphStorage::getTop()
{
float top = 0.0f;
for (const auto paragraphItem : storage)
{
if (paragraphItem.value.get() == this)
break;
top += paragraphItem.value->getHeight();
}
return top;
}
int64 TextEditor::ParagraphStorage::getStartingGlyph()
{
int64 startingGlyph = 0;
for (const auto paragraph : storage)
{
if (paragraph.value.get() == this)
break;
startingGlyph += paragraph.value->getNumGlyphs();
}
return startingGlyph;
}
void TextEditor::ParagraphStorage::updatePasswordReplacementText()
{
const auto passwordChar = storage.getPasswordCharacter();
if (passwordChar == 0)
{
passwordReplacementText.reset();
return;
}
constexpr juce_wchar cr = 0x0d;
constexpr juce_wchar lf = 0x0a;
const auto startIt = text.begin();
auto endIt = text.end();
for (int i = 0; i < 2; ++i)
{
if (endIt == startIt)
break;
auto newEnd = endIt - 1;
if (*newEnd != cr && *newEnd != lf)
break;
endIt = newEnd;
}
passwordReplacementText = String::repeatedString (String::charToString (passwordChar),
(int) startIt.lengthUpTo (endIt))
+ String { endIt, text.end() };
}
void TextEditor::ParagraphStorage::clearShapedText()
{
shapedText.reset();
height.reset();
numGlyphs.reset();
updatePasswordReplacementText();
}
}