Skip to content

Commit

Permalink
switch texteditor internals to use UTF-32 to support all characters
Browse files Browse the repository at this point in the history
  • Loading branch information
scheffle committed Nov 20, 2024
1 parent 1535d83 commit f379495
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 78 deletions.
104 changes: 38 additions & 66 deletions vstgui/lib/ctexteditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
namespace VSTGUI {
namespace TextEditor {

using CharT = char16_t;
using CharT = char32_t;
#define STB_TEXTEDIT_CHARTYPE CharT
#define STB_TEXTEDIT_POSITIONTYPE int32_t
#define STB_TEXTEDIT_STRING const TextEditorView
Expand All @@ -53,34 +53,33 @@ using CharT = char16_t;
#pragma warning(disable:4996)
#endif


using StringConvert = std::wstring_convert<std::codecvt_utf8_utf16<CharT>, CharT>;
using StringConvert = std::wstring_convert<std::codecvt_utf8<CharT>, CharT>;
//------------------------------------------------------------------------
inline std::u16string convert (const char* text, size_t numChars)
inline std::u32string convert (const char* text, size_t numChars)
{
return StringConvert {}.from_bytes (text, text + numChars);
}

//------------------------------------------------------------------------
inline std::u16string convert (const std::string& str) { return StringConvert {}.from_bytes (str); }
inline std::u32string convert (const std::string& str) { return StringConvert {}.from_bytes (str); }

//------------------------------------------------------------------------
inline std::string convert (const char16_t* text, size_t numChars)
inline std::string convert (const char32_t* text, size_t numChars)
{
return StringConvert {}.to_bytes (text, text + numChars);
}

//------------------------------------------------------------------------
inline std::string convert (const std::u16string& str) { return StringConvert {}.to_bytes (str); }
inline std::string convert (const std::u32string& str) { return StringConvert {}.to_bytes (str); }

#ifdef __clang__
#pragma clang diagnostic pop
#elif defined(_MSC_VER)
#pragma warning(pop)
#endif

using String = std::u16string;
using StringView = std::u16string_view;
using String = std::u32string;
using StringView = std::u32string_view;

//------------------------------------------------------------------------
struct Range
Expand Down Expand Up @@ -153,25 +152,12 @@ inline void convertWinLineEndingsToUnixLineEndings (String& text)
}

//------------------------------------------------------------------------
inline bool isStopChar (char16_t character)
inline bool isStopChar (char32_t character)
{
return std::iswpunct (character) || std::iswcntrl (character) || std::iswspace (character);
auto ch = static_cast<wint_t> (character);
return std::iswpunct (ch) || std::iswcntrl (ch) || std::iswspace (ch);
};

//------------------------------------------------------------------------
inline bool containsSurrogatePairs (std::u16string_view string)
{
if (string.length () > 1)
{
for (auto character : string)
{
if (character >= 0xD800 && character <= 0xDFFF)
return true;
}
}
return false;
}

//------------------------------------------------------------------------
struct Line
{
Expand Down Expand Up @@ -203,7 +189,7 @@ static constexpr CPoint MouseOutsidePos = {std::numeric_limits<CCoord>::max (),
//------------------------------------------------------------------------
struct Key
{
char16_t character;
char32_t character;
VirtualKey virt;
Modifiers modifiers;

Expand Down Expand Up @@ -302,13 +288,13 @@ struct TextEditorView : public CView,
void setStyle (const Style& style) const override;
bool canHandleCommand (Command cmd) const override;
bool handleCommand (Command cmd) const override;
bool setCommandKeyBinding (Command cmd, char16_t character, VirtualKey virt,
bool setCommandKeyBinding (Command cmd, char32_t character, VirtualKey virt,
Modifiers modifiers) const override;
void setFindOptions (FindOptions opt) const override;
void setFindString (std::string_view utf8Text) const override;

// TextEditorHighlighting::IEditorExt
std::u16string_view readText (size_t startOffset, size_t length) const override;
std::u32string_view readText (size_t startOffset, size_t length) const override;
size_t getTextLength () const override;

// commandos
Expand All @@ -334,8 +320,8 @@ struct TextEditorView : public CView,
void doUndoRedo () const;

// ICocoaTextInputClient
void insertText (const std::u16string& string, TextRange range) override;
void setMarkedText (const std::u16string& string, TextRange selectedRange,
void insertText (const std::u32string& string, TextRange range) override;
void setMarkedText (const std::u32string& string, TextRange selectedRange,
TextRange replacementRange) override;
bool hasMarkedText () override;
void unmarkText () override;
Expand Down Expand Up @@ -374,7 +360,7 @@ struct TextEditorView : public CView,
void onSelectionChanged (Range newSel, bool forceInvalidation = false) const;
void selectOnDoubleClick (uint32_t clickCount) const;
template<bool forward>
void selectPair (size_t startPos, char16_t closingChar) const;
void selectPair (size_t startPos, char32_t closingChar) const;
void updateSelectionOnDoubleClickMove (uint32_t clickCount) const;
void insertNewLine () const;
/** will return the last line if pos not found instead of end */
Expand Down Expand Up @@ -455,7 +441,7 @@ struct TextEditorView : public CView,
private:
mutable ModelData md;
Range markedRange {};
std::u16string markedText;
std::u32string markedText;
};

#define VIRTUAL_KEY_BIT 0x80000000
Expand Down Expand Up @@ -966,15 +952,15 @@ bool TextEditorView::handleCommand (Command cmd) const
}

//------------------------------------------------------------------------
bool TextEditorView::setCommandKeyBinding (Command cmd, char16_t character, VirtualKey virt,
bool TextEditorView::setCommandKeyBinding (Command cmd, char32_t character, VirtualKey virt,
Modifiers modifiers) const
{
md.commandKeys[Index (cmd)] = {character, virt, modifiers};
return true;
}

//------------------------------------------------------------------------
std::u16string_view TextEditorView::readText (size_t startOffset, size_t length) const
std::u32string_view TextEditorView::readText (size_t startOffset, size_t length) const
{
if (startOffset >= md.model.text.length () || md.model.lines.empty ())
return {};
Expand Down Expand Up @@ -1304,7 +1290,7 @@ void TextEditorView::onKeyboardEvent (KeyboardEvent& event)
auto tmp = convert (txt->getString ());
key = tmp[0];
}
if (isStopChar (static_cast<char16_t> (key)))
if (isStopChar (static_cast<char32_t> (key)))
checkCurrentUndoGroup (true);
}
if (event.virt != VirtualKey::None)
Expand Down Expand Up @@ -1591,8 +1577,8 @@ void TextEditorView::insertNewLine () const
auto cursor = md.editState.cursor;
auto currentLine = findLine (md.model.lines.begin (), md.model.lines.end (), cursor);
vstgui_assert (currentLine != md.model.lines.end ());
std::u16string insertStr = u"\n";
auto isWhiteSpace = [] (char16_t character) {
std::u32string insertStr = U"\n";
auto isWhiteSpace = [] (char32_t character) {
return character == u'\t' || character == u' ';
};
for (auto index = static_cast<int> (currentLine->range.start); index < cursor; ++index)
Expand Down Expand Up @@ -1924,7 +1910,7 @@ inline bool findStopChar (iterator_t& it, iterator_t end)

//------------------------------------------------------------------------
template<bool forward>
void TextEditorView::selectPair (size_t startPos, char16_t closingChar) const
void TextEditorView::selectPair (size_t startPos, char32_t closingChar) const
{
auto it = md.model.text.begin ();
std::advance (it, startPos);
Expand Down Expand Up @@ -2244,7 +2230,7 @@ bool TextEditorView::doShifting (bool right) const
}
if (hasSelection)
{
if (originSelectStart == lineStart->range.start)
if (originSelectStart == static_cast<int> (lineStart->range.start))
md.editState.select_start = originSelectStart;
}

Expand Down Expand Up @@ -2306,8 +2292,6 @@ bool TextEditorView::doPaste () const
try
{
auto uText = convert (txt, size);
if (containsSurrogatePairs (uText))
return false;
convertWinLineEndingsToUnixLineEndings (uText);
callSTB ([&] () {
stb_textedit_paste (this, &md.editState, uText.data (),
Expand Down Expand Up @@ -2960,38 +2944,26 @@ void TextEditorView::doRedo () const
}

//------------------------------------------------------------------------
void TextEditorView::insertText (const std::u16string& string, TextRange range)
void TextEditorView::insertText (const std::u32string& string, TextRange range)
{
if (!containsSurrogatePairs (string))
{
if (range.length > 0 && range.position < md.model.text.size ())
{
md.editState.select_start = static_cast<int> (range.position);
md.editState.select_end = static_cast<int> (range.position + range.length);
md.editState.cursor = md.editState.select_start;
}
callSTB ([&] () {
stb_textedit_paste (this, &md.editState, string.data (),
static_cast<int> (string.length ()));
});
unmarkText ();
restartBlinkTimer ();
}
else
if (range.length > 0 && range.position < md.model.text.size ())
{
insertText (u"_", range);
md.editState.select_start = static_cast<int> (range.position);
md.editState.select_end = static_cast<int> (range.position + range.length);
md.editState.cursor = md.editState.select_start;
}
callSTB ([&] () {
stb_textedit_paste (this, &md.editState, string.data (),
static_cast<int> (string.length ()));
});
unmarkText ();
restartBlinkTimer ();
}

//------------------------------------------------------------------------
void TextEditorView::setMarkedText (const std::u16string& string, TextRange selectedRange,
void TextEditorView::setMarkedText (const std::u32string& string, TextRange selectedRange,
TextRange replacementRange)
{
if (containsSurrogatePairs (string))
{
setMarkedText (u"_", selectedRange, replacementRange);
return;
}
markedText = string;
markedRange = {selectedRange.position, selectedRange.length};
if (replacementRange.length > 0 && replacementRange.position < md.model.text.size ())
Expand Down Expand Up @@ -3045,7 +3017,7 @@ CRect TextEditorView::firstRectForCharacterRange (TextRange range, TextRange& ac
auto it = findLine (md.model.lines.begin (), md.model.lines.end (), md.editState.cursor);
auto r = calculateLineRect (it);
actualRange = {static_cast<size_t> (md.editState.cursor), 0};
if (it->range.start != md.editState.cursor)
if (static_cast<int> (it->range.start) != md.editState.cursor)
{
auto t = md.model.text.substr (it->range.start, md.editState.cursor - it->range.start);
auto nonSelectedText = convert (t);
Expand Down
4 changes: 2 additions & 2 deletions vstgui/lib/ctexteditor.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ struct ITextEditor

virtual bool canHandleCommand (Command cmd) const = 0;
virtual bool handleCommand (Command cmd) const = 0;
virtual bool setCommandKeyBinding (Command cmd, char16_t character, VirtualKey virt,
virtual bool setCommandKeyBinding (Command cmd, char32_t character, VirtualKey virt,
Modifiers modifiers) const = 0;

enum class FindOption : uint32_t
Expand Down Expand Up @@ -129,7 +129,7 @@ struct IEditorExt
* @param length number of characters
* @return a string view into the buffer
*/
virtual std::u16string_view readText (size_t startOffset, size_t length) const = 0;
virtual std::u32string_view readText (size_t startOffset, size_t length) const = 0;
/** get the length of the text
*
* @return number of characters
Expand Down
4 changes: 2 additions & 2 deletions vstgui/lib/platform/iplatformtextinputclient.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ struct ICocoaTextInputClient
size_t length;
};

virtual void insertText (const std::u16string& string, TextRange range) = 0;
virtual void setMarkedText (const std::u16string& string, TextRange selectedRange,
virtual void insertText (const std::u32string& string, TextRange range) = 0;
virtual void setMarkedText (const std::u32string& string, TextRange selectedRange,
TextRange replacementRange) = 0;
virtual bool hasMarkedText () = 0;
virtual void unmarkText () = 0;
Expand Down
40 changes: 32 additions & 8 deletions vstgui/lib/platform/mac/cocoa/nsviewframe.mm
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,36 @@ static void draggedImageEndedAtOperation (id self, SEL _cmd, NSImage* image, NSP
}
}
#endif
static std::u32string convert (NSString* str)
{
NSUInteger maxLength {};
NSUInteger usedLength {};

if ([str getBytes:nullptr
maxLength:0
usedLength:&maxLength
encoding:NSUTF32StringEncoding
options:0
range:NSMakeRange (0, str.length)
remainingRange:nullptr] == NO)
{
return {};
}
std::u32string result (maxLength / sizeof (char32_t), 0);
if ([str getBytes:result.data ()
maxLength:maxLength
usedLength:&usedLength
encoding:NSUTF32StringEncoding
options:0
range:NSMakeRange (0, str.length)
remainingRange:nullptr] == NO)
{
return {};
}
result.resize (usedLength / sizeof (char32_t));
return result;
}

// @protocol NSTextInputClient
static void insertText (id self, SEL _cmd, id string, NSRange replacementRange)
{
Expand All @@ -956,10 +986,7 @@ static void insertText (id self, SEL _cmd, id string, NSRange replacementRange)
auto hadMarkedText = textInputClient->hasMarkedText ();
if ([string isKindOfClass:[NSAttributedString class]])
string = [string string];
std::u16string str;
str.resize ([string length]);
[string getCharacters:reinterpret_cast<unichar*> (str.data ())
range:NSMakeRange (0, str.length ())];
auto str = convert (string);
textInputClient->insertText (str, {replacementRange.location, replacementRange.length});
if (hadMarkedText && textInputClient->hasMarkedText () == false)
{
Expand Down Expand Up @@ -988,10 +1015,7 @@ static void setMarkedText (id self, SEL _cmd, id string, NSRange selectedRange,
if ([string isKindOfClass:[NSAttributedString class]])
string = [string string];

std::u16string str;
str.resize ([string length]);
[string getCharacters:reinterpret_cast<unichar*> (str.data ())
range:NSMakeRange (0, str.length ())];
auto str = convert (string);
frame->getTextInputClient ()->setMarkedText (
str, {selectedRange.location, selectedRange.length},
{replacementRange.location, replacementRange.length});
Expand Down

0 comments on commit f379495

Please sign in to comment.