// This file is part of VSTGUI. It is subject to the license terms // in the LICENSE file found in the top-level directory of this // distribution and at http://github.com/steinbergmedia/vstgui/LICENSE #include "ccolor.h" #include "cdrawcontext.h" #include "cdropsource.h" #include "cframe.h" #include "controls/cscrollbar.h" #include "cscrollview.h" #include "ctexteditor.h" #include "cgraphicspath.h" #include "events.h" #include "iviewlistener.h" #include "cvstguitimer.h" #include "finally.h" #include "platform/iplatformfont.h" #include "platform/iplatformframe.h" #include "platform/iplatformtextinputclient.h" #include "platform/platformfactory.h" #include "platform/platform_macos.h" #include "platform/platform_win32.h" #include "controls/cbuttons.h" #include "controls/ctextedit.h" #include "animation/timingfunctions.h" #include "animation/animations.h" #include #include #include #include #include #include #include //------------------------------------------------------------------------ namespace VSTGUI { namespace TextEditor { using CharT = char32_t; #define STB_TEXTEDIT_CHARTYPE CharT #define STB_TEXTEDIT_POSITIONTYPE int32_t #define STB_TEXTEDIT_STRING const TextEditorView #define STB_TEXTEDIT_KEYTYPE uint32_t #define STB_TEXTEDIT_UNDOSTATECOUNT 0 #define STB_TEXTEDIT_UNDOCHARCOUNT 0 #include "../thirdparty/stb_textedit.h" #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" #elif defined(_MSC_VER) #pragma warning(push) #pragma warning(disable:4996) #endif using StringConvert = std::wstring_convert, CharT>; //------------------------------------------------------------------------ inline std::u32string convert (const char* text, size_t numChars) { return StringConvert {}.from_bytes (text, text + numChars); } //------------------------------------------------------------------------ inline std::u32string convert (const std::string& str) { return StringConvert {}.from_bytes (str); } //------------------------------------------------------------------------ inline std::string convert (const char32_t* text, size_t numChars) { return StringConvert {}.to_bytes (text, text + numChars); } //------------------------------------------------------------------------ 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::u32string; using StringView = std::u32string_view; //------------------------------------------------------------------------ struct Range { size_t start {0}; size_t length {0}; explicit operator bool () const { return length > 0; } size_t end () const { return start + length; } }; //------------------------------------------------------------------------ bool operator== (const Range& r1, const Range& r2) { return r1.start == r2.start && r1.length == r2.length; } //------------------------------------------------------------------------ bool operator!= (const Range& r1, const Range& r2) { return r1.start != r2.start || r1.length != r2.length; } //------------------------------------------------------------------------ inline Range makeRange (size_t start, size_t end) { if (start > end) std::swap (start, end); return Range {static_cast (start), static_cast (end - start)}; } //------------------------------------------------------------------------ inline Range makeRange (const STB_TexteditState& state) { return makeRange (state.select_start, state.select_end); } //------------------------------------------------------------------------ inline size_t replaceTabs (std::string& str, uint32_t tabWidth, size_t lineOffset) { if (tabWidth < 1) return 0; auto numReplacedChars = 0u; auto whiteSpace = ' '; std::string::size_type pos = std::string::npos; while ((pos = str.find_first_of ('\t')) != std::string::npos) { auto numWhiteSpace = tabWidth - ((pos + lineOffset) % tabWidth); std::string s (numWhiteSpace, whiteSpace); str.replace (pos, 1, s); numReplacedChars += numWhiteSpace - 1; } return numReplacedChars; } //------------------------------------------------------------------------ inline void convertWinLineEndingsToUnixLineEndings (String& text) { auto it = text.begin (); if (it == text.end ()) return; auto lastChar = it; ++it; for (; it != text.end (); ++it) { if (*lastChar == '\r' && *it == '\n') { it = text.erase (lastChar); ++it; if (it == text.end ()) break; } lastChar = it; } } //------------------------------------------------------------------------ inline bool isStopChar (char32_t character) { auto ch = static_cast (character); return std::iswpunct (ch) || std::iswcntrl (ch) || std::iswspace (ch); }; //------------------------------------------------------------------------ struct Line { Range range; UTF8String text; CCoord width {}; }; using Lines = std::vector; //------------------------------------------------------------------------ struct TextModel { String text; Lines lines; }; //------------------------------------------------------------------------ struct NewLineProcessor { static void update (Lines& lines, StringView text, size_t startRow); }; struct LineNumberView; struct FindPanelController; static constexpr CPoint MouseOutsidePos = {std::numeric_limits::max (), std::numeric_limits::max ()}; //------------------------------------------------------------------------ struct Key { char32_t character; VirtualKey virt; Modifiers modifiers; bool operator== (const KeyboardEvent& event) const { return event.character == character && event.virt == virt && event.modifiers == modifiers; } }; using CommandKeyArray = std::array (ITextEditor::Command::TakeFocus) + 1>; //------------------------------------------------------------------------ struct TextEditorView : public CView, public ITextEditor, public TextEditorColorization::IEditorExt, public IFocusDrawing, public ViewEventListenerAdapter { TextEditorView (ITextEditorController* controller); void beforeDelete () override; void drawRect (CDrawContext* pContext, const CRect& dirtyRect) override; bool attached (CView* parent) override; bool removed (CView* parent) override; void parentSizeChanged () override; void looseFocus () override; void takeFocus () override; void onKeyboardEvent (KeyboardEvent& event) override; void onMouseDownEvent (MouseDownEvent& event) override; void onMouseMoveEvent (MouseMoveEvent& event) override; void onMouseUpEvent (MouseUpEvent& event) override; void onMouseCancelEvent (MouseCancelEvent& event) override; void onMouseEnterEvent (MouseEnterEvent& event) override; void onMouseExitEvent (MouseExitEvent& event) override; void viewOnEvent (CView* view, Event& event) override; int32_t deleteChars (size_t pos, size_t num) const; int32_t insertChars (size_t pos, const CharT* text, size_t num) const; void layout (StbTexteditRow* row, size_t start_i) const; float getCharWidth (size_t row, size_t pos) const; CharT getChar (int32_t pos) const; int32_t getLength () const; size_t moveToWordPrevious (size_t pos) const; size_t moveToWordNext (size_t pos) const; // STB static int32_t deleteChars (const TextEditorView* self, size_t pos, size_t num) { return self->deleteChars (pos, num); } static int32_t insertChars (const TextEditorView* self, size_t pos, const CharT* text, size_t num) { return self->insertChars (pos, text, num); } static void layout (StbTexteditRow* row, const TextEditorView* self, size_t start_i) { self->layout (row, start_i); } static float getCharWidth (const TextEditorView* self, size_t row, size_t pos) { return self->getCharWidth (row, pos); } static CharT getChar (const TextEditorView* self, int32_t pos) { return self->getChar (pos); } static int32_t getLength (const TextEditorView* self) { return self->getLength (); } static int moveToWordPrevious (const TextEditorView* self, size_t pos) { return static_cast (self->moveToWordPrevious (pos)); } static int moveToWordNext (const TextEditorView* self, size_t pos) { return static_cast (self->moveToWordNext (pos)); } static CharT* createUndoRecord (const TextEditorView* self, size_t pos, size_t insert_len, size_t delete_len) { return self->createUndoRecord (pos, insert_len, delete_len); } static void undo (const TextEditorView* self) { self->doUndo (); } static void redo (const TextEditorView* self) { self->doRedo (); } protected: // IFocusDrawing bool drawFocusOnTop () override; bool getFocusPath (CGraphicsPath& outPath) override; // ITextEditor bool setPlainText (std::string_view utf8Text, bool clearSelection) const override; std::string getPlainText () const override; void resetController () const override; void setStyle (const Style& style) const override; bool canHandleCommand (Command cmd) const override; bool handleCommand (Command cmd) const override; 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 bool readText (size_t startOffset, size_t length, const ReadCallbackFunc& callback) const override; size_t getTextLength () const override; // commandos bool doShifting (bool right) const; void selectAll () const; bool doCut () const; bool doCopy () const; bool doPaste () const; bool useSelectionForFind () const; bool doFind (bool forward = true, size_t oldPos = String::npos) const; bool showFindPanel () const; bool gotoLine (size_t lineNo) const; String::size_type doFindCaseSensitive (bool forward) const; String::size_type doFindIgnoreCase (bool forward) const; // undo / redo CharT* createUndoRecord (size_t pos, size_t insertLen, size_t deleteLen) const; void checkCurrentUndoGroup (bool force) const; void doUndo () const; void doRedo () const; template void doUndoRedo () const; void flushUndoList () const; void clearUndoList () const; private: template bool callSTB (Proc proc) const; enum Dirty { UI = 1 << 0, Layout = 1 << 1, All = UI | Layout, }; void validateLineStarts (Lines::const_iterator it, const Lines::const_iterator& end) const; void invalidateSingleLine (size_t pos, int32_t numChars) const; void invalidateSingleLine (Lines::iterator& line, int32_t numChars) const; void invalidateLines (size_t pos, int32_t numChars) const; void invalidate (Dirty what = Dirty::UI) const; void invalidLine (size_t index, bool completeWidth = false) const; void invalidLine (Lines::const_iterator it, bool completeWidth = false) const; void invalidateRect (CRect r) const; void invalidSelectedLines () const; CCoord updateLineText (Lines::iterator& line) const; CRect calculateLineRect (size_t index) const; CRect calculateLineRect (Lines::const_iterator it) const; CCoord calculateMaxWidth () const; CRect calculateSelectedLinesRect () const; void updateLineNumbersView () const; void layoutRows () const; void onCursorChanged (int oldCursorPos, int newCursorPos) const; void onSelectionChanged (Range newSel, bool forceInvalidation = false) const; void selectOnDoubleClick (uint32_t clickCount) const; template 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 */ template T findLine (T begin, T end, size_t pos) const; CRect calculateCursorRect (int cursor) const; CRect invalidCursorRect () const; void toggleCursorVisibility () const; void restartBlinkTimer () const; void onStyleChanged () const; void setFindString (String&& text) const; bool isReadOnlyMode () const; TextEditorView& mutableThis () const { return *const_cast (this); } public: //------------------------------------------------------------------------ struct UndoRecord { size_t position {}; size_t deleted {0}; String characters; }; using UndoRecords = std::vector; //------------------------------------------------------------------------ struct UndoGroup { uint64_t time {0}; UndoRecords record; }; using UndoList = std::vector; //------------------------------------------------------------------------ struct ModelData { TextModel model; std::shared_ptr