From f379495f428e8a64076168d410dabfe1113bbda5 Mon Sep 17 00:00:00 2001 From: scheffle Date: Wed, 20 Nov 2024 12:45:23 +0100 Subject: [PATCH] switch texteditor internals to use UTF-32 to support all characters --- vstgui/lib/ctexteditor.cpp | 104 +++++++----------- vstgui/lib/ctexteditor.h | 4 +- .../lib/platform/iplatformtextinputclient.h | 4 +- vstgui/lib/platform/mac/cocoa/nsviewframe.mm | 40 +++++-- 4 files changed, 74 insertions(+), 78 deletions(-) diff --git a/vstgui/lib/ctexteditor.cpp b/vstgui/lib/ctexteditor.cpp index 65f3fe9ce..9e6da3ab1 100644 --- a/vstgui/lib/ctexteditor.cpp +++ b/vstgui/lib/ctexteditor.cpp @@ -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 @@ -53,25 +53,24 @@ using CharT = char16_t; #pragma warning(disable:4996) #endif - -using StringConvert = std::wstring_convert, CharT>; +using StringConvert = std::wstring_convert, 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 @@ -79,8 +78,8 @@ inline std::string convert (const std::u16string& str) { return StringConvert {} #pragma warning(pop) #endif -using String = std::u16string; -using StringView = std::u16string_view; +using String = std::u32string; +using StringView = std::u32string_view; //------------------------------------------------------------------------ struct Range @@ -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 (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 { @@ -203,7 +189,7 @@ static constexpr CPoint MouseOutsidePos = {std::numeric_limits::max (), //------------------------------------------------------------------------ struct Key { - char16_t character; + char32_t character; VirtualKey virt; Modifiers modifiers; @@ -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 @@ -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; @@ -374,7 +360,7 @@ struct TextEditorView : public CView, void onSelectionChanged (Range newSel, bool forceInvalidation = false) const; void selectOnDoubleClick (uint32_t clickCount) const; template - 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 */ @@ -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 @@ -966,7 +952,7 @@ 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}; @@ -974,7 +960,7 @@ bool TextEditorView::setCommandKeyBinding (Command cmd, char16_t character, Virt } //------------------------------------------------------------------------ -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 {}; @@ -1304,7 +1290,7 @@ void TextEditorView::onKeyboardEvent (KeyboardEvent& event) auto tmp = convert (txt->getString ()); key = tmp[0]; } - if (isStopChar (static_cast (key))) + if (isStopChar (static_cast (key))) checkCurrentUndoGroup (true); } if (event.virt != VirtualKey::None) @@ -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 (currentLine->range.start); index < cursor; ++index) @@ -1924,7 +1910,7 @@ inline bool findStopChar (iterator_t& it, iterator_t end) //------------------------------------------------------------------------ template -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); @@ -2244,7 +2230,7 @@ bool TextEditorView::doShifting (bool right) const } if (hasSelection) { - if (originSelectStart == lineStart->range.start) + if (originSelectStart == static_cast (lineStart->range.start)) md.editState.select_start = originSelectStart; } @@ -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 (), @@ -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 (range.position); - md.editState.select_end = static_cast (range.position + range.length); - md.editState.cursor = md.editState.select_start; - } - callSTB ([&] () { - stb_textedit_paste (this, &md.editState, string.data (), - static_cast (string.length ())); - }); - unmarkText (); - restartBlinkTimer (); - } - else + if (range.length > 0 && range.position < md.model.text.size ()) { - insertText (u"_", range); + md.editState.select_start = static_cast (range.position); + md.editState.select_end = static_cast (range.position + range.length); + md.editState.cursor = md.editState.select_start; } + callSTB ([&] () { + stb_textedit_paste (this, &md.editState, string.data (), + static_cast (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 ()) @@ -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 (md.editState.cursor), 0}; - if (it->range.start != md.editState.cursor) + if (static_cast (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); diff --git a/vstgui/lib/ctexteditor.h b/vstgui/lib/ctexteditor.h index 522ceeadf..3554300bc 100644 --- a/vstgui/lib/ctexteditor.h +++ b/vstgui/lib/ctexteditor.h @@ -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 @@ -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 diff --git a/vstgui/lib/platform/iplatformtextinputclient.h b/vstgui/lib/platform/iplatformtextinputclient.h index 36ca966fb..5d1f19375 100644 --- a/vstgui/lib/platform/iplatformtextinputclient.h +++ b/vstgui/lib/platform/iplatformtextinputclient.h @@ -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; diff --git a/vstgui/lib/platform/mac/cocoa/nsviewframe.mm b/vstgui/lib/platform/mac/cocoa/nsviewframe.mm index 6ae7ca288..1f4f51d46 100644 --- a/vstgui/lib/platform/mac/cocoa/nsviewframe.mm +++ b/vstgui/lib/platform/mac/cocoa/nsviewframe.mm @@ -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) { @@ -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 (str.data ()) - range:NSMakeRange (0, str.length ())]; + auto str = convert (string); textInputClient->insertText (str, {replacementRange.location, replacementRange.length}); if (hadMarkedText && textInputClient->hasMarkedText () == false) { @@ -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 (str.data ()) - range:NSMakeRange (0, str.length ())]; + auto str = convert (string); frame->getTextInputClient ()->setMarkedText ( str, {selectedRange.location, selectedRange.length}, {replacementRange.location, replacementRange.length});