Add text editor module for cardinal
Signed-off-by: falkTX <falktx@falktx.com>
This commit is contained in:
parent
bba168ecc6
commit
268c31c029
13 changed files with 3957 additions and 26 deletions
2
dpf
2
dpf
|
|
@ -1 +1 @@
|
||||||
Subproject commit 400dca29de54e1cceee55f22672e940ae5d5e005
|
Subproject commit 493837049e773e45f842c0ee83b6b69c7c56adf9
|
||||||
|
|
@ -75,6 +75,15 @@
|
||||||
"tags": [
|
"tags": [
|
||||||
"Utility"
|
"Utility"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slug": "TextEditor",
|
||||||
|
"disabled": false,
|
||||||
|
"name": "Text Editor",
|
||||||
|
"description": "A embed text editor inside Cardinal",
|
||||||
|
"tags": [
|
||||||
|
"Utility"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
21
plugins/Cardinal/src/DearImGuiColorTextEditor/LICENSE
Normal file
21
plugins/Cardinal/src/DearImGuiColorTextEditor/LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 BalazsJako
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
3179
plugins/Cardinal/src/DearImGuiColorTextEditor/TextEditor.cpp
Normal file
3179
plugins/Cardinal/src/DearImGuiColorTextEditor/TextEditor.cpp
Normal file
File diff suppressed because it is too large
Load diff
398
plugins/Cardinal/src/DearImGuiColorTextEditor/TextEditor.h
Normal file
398
plugins/Cardinal/src/DearImGuiColorTextEditor/TextEditor.h
Normal file
|
|
@ -0,0 +1,398 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <map>
|
||||||
|
#include <regex>
|
||||||
|
#include "../DearImGui/imgui.h"
|
||||||
|
|
||||||
|
class TextEditor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class PaletteIndex
|
||||||
|
{
|
||||||
|
Default,
|
||||||
|
Keyword,
|
||||||
|
Number,
|
||||||
|
String,
|
||||||
|
CharLiteral,
|
||||||
|
Punctuation,
|
||||||
|
Preprocessor,
|
||||||
|
Identifier,
|
||||||
|
KnownIdentifier,
|
||||||
|
PreprocIdentifier,
|
||||||
|
Comment,
|
||||||
|
MultiLineComment,
|
||||||
|
Background,
|
||||||
|
Cursor,
|
||||||
|
Selection,
|
||||||
|
ErrorMarker,
|
||||||
|
Breakpoint,
|
||||||
|
LineNumber,
|
||||||
|
CurrentLineFill,
|
||||||
|
CurrentLineFillInactive,
|
||||||
|
CurrentLineEdge,
|
||||||
|
Max
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class SelectionMode
|
||||||
|
{
|
||||||
|
Normal,
|
||||||
|
Word,
|
||||||
|
Line
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Breakpoint
|
||||||
|
{
|
||||||
|
int mLine;
|
||||||
|
bool mEnabled;
|
||||||
|
std::string mCondition;
|
||||||
|
|
||||||
|
Breakpoint()
|
||||||
|
: mLine(-1)
|
||||||
|
, mEnabled(false)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Represents a character coordinate from the user's point of view,
|
||||||
|
// i. e. consider an uniform grid (assuming fixed-width font) on the
|
||||||
|
// screen as it is rendered, and each cell has its own coordinate, starting from 0.
|
||||||
|
// Tabs are counted as [1..mTabSize] count empty spaces, depending on
|
||||||
|
// how many space is necessary to reach the next tab stop.
|
||||||
|
// For example, coordinate (1, 5) represents the character 'B' in a line "\tABC", when mTabSize = 4,
|
||||||
|
// because it is rendered as " ABC" on the screen.
|
||||||
|
struct Coordinates
|
||||||
|
{
|
||||||
|
int mLine, mColumn;
|
||||||
|
Coordinates() : mLine(0), mColumn(0) {}
|
||||||
|
Coordinates(int aLine, int aColumn) : mLine(aLine), mColumn(aColumn)
|
||||||
|
{
|
||||||
|
assert(aLine >= 0);
|
||||||
|
assert(aColumn >= 0);
|
||||||
|
}
|
||||||
|
static Coordinates Invalid() { static Coordinates invalid(-1, -1); return invalid; }
|
||||||
|
|
||||||
|
bool operator ==(const Coordinates& o) const
|
||||||
|
{
|
||||||
|
return
|
||||||
|
mLine == o.mLine &&
|
||||||
|
mColumn == o.mColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator !=(const Coordinates& o) const
|
||||||
|
{
|
||||||
|
return
|
||||||
|
mLine != o.mLine ||
|
||||||
|
mColumn != o.mColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator <(const Coordinates& o) const
|
||||||
|
{
|
||||||
|
if (mLine != o.mLine)
|
||||||
|
return mLine < o.mLine;
|
||||||
|
return mColumn < o.mColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator >(const Coordinates& o) const
|
||||||
|
{
|
||||||
|
if (mLine != o.mLine)
|
||||||
|
return mLine > o.mLine;
|
||||||
|
return mColumn > o.mColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator <=(const Coordinates& o) const
|
||||||
|
{
|
||||||
|
if (mLine != o.mLine)
|
||||||
|
return mLine < o.mLine;
|
||||||
|
return mColumn <= o.mColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator >=(const Coordinates& o) const
|
||||||
|
{
|
||||||
|
if (mLine != o.mLine)
|
||||||
|
return mLine > o.mLine;
|
||||||
|
return mColumn >= o.mColumn;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Identifier
|
||||||
|
{
|
||||||
|
Coordinates mLocation;
|
||||||
|
std::string mDeclaration;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::string String;
|
||||||
|
typedef std::unordered_map<std::string, Identifier> Identifiers;
|
||||||
|
typedef std::unordered_set<std::string> Keywords;
|
||||||
|
typedef std::map<int, std::string> ErrorMarkers;
|
||||||
|
typedef std::unordered_set<int> Breakpoints;
|
||||||
|
typedef std::array<ImU32, (unsigned)PaletteIndex::Max> Palette;
|
||||||
|
typedef uint8_t Char;
|
||||||
|
|
||||||
|
struct Glyph
|
||||||
|
{
|
||||||
|
Char mChar;
|
||||||
|
PaletteIndex mColorIndex = PaletteIndex::Default;
|
||||||
|
bool mComment : 1;
|
||||||
|
bool mMultiLineComment : 1;
|
||||||
|
bool mPreprocessor : 1;
|
||||||
|
|
||||||
|
Glyph(Char aChar, PaletteIndex aColorIndex) : mChar(aChar), mColorIndex(aColorIndex),
|
||||||
|
mComment(false), mMultiLineComment(false), mPreprocessor(false) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::vector<Glyph> Line;
|
||||||
|
typedef std::vector<Line> Lines;
|
||||||
|
|
||||||
|
struct LanguageDefinition
|
||||||
|
{
|
||||||
|
typedef std::pair<std::string, PaletteIndex> TokenRegexString;
|
||||||
|
typedef std::vector<TokenRegexString> TokenRegexStrings;
|
||||||
|
typedef bool(*TokenizeCallback)(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end, PaletteIndex & paletteIndex);
|
||||||
|
typedef void(*ColorizeCallback)(Lines &lines, void *data);
|
||||||
|
|
||||||
|
std::string mName;
|
||||||
|
Keywords mKeywords;
|
||||||
|
Identifiers mIdentifiers;
|
||||||
|
Identifiers mPreprocIdentifiers;
|
||||||
|
std::string mCommentStart, mCommentEnd, mSingleLineComment;
|
||||||
|
char mPreprocChar;
|
||||||
|
bool mAutoIndentation;
|
||||||
|
|
||||||
|
ColorizeCallback mColorize;
|
||||||
|
void *mColorizeData;
|
||||||
|
|
||||||
|
TokenizeCallback mTokenize;
|
||||||
|
|
||||||
|
TokenRegexStrings mTokenRegexStrings;
|
||||||
|
|
||||||
|
bool mCaseSensitive;
|
||||||
|
|
||||||
|
LanguageDefinition()
|
||||||
|
: mPreprocChar('#')
|
||||||
|
, mAutoIndentation(true)
|
||||||
|
, mColorize(nullptr)
|
||||||
|
, mColorizeData(nullptr)
|
||||||
|
, mTokenize(nullptr)
|
||||||
|
, mCaseSensitive(true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static const LanguageDefinition& CPlusPlus();
|
||||||
|
static const LanguageDefinition& HLSL();
|
||||||
|
static const LanguageDefinition& GLSL();
|
||||||
|
static const LanguageDefinition& C();
|
||||||
|
static const LanguageDefinition& SQL();
|
||||||
|
static const LanguageDefinition& AngelScript();
|
||||||
|
static const LanguageDefinition& Lua();
|
||||||
|
};
|
||||||
|
|
||||||
|
TextEditor();
|
||||||
|
~TextEditor();
|
||||||
|
|
||||||
|
void SetLanguageDefinition(const LanguageDefinition& aLanguageDef);
|
||||||
|
const LanguageDefinition& GetLanguageDefinition() const { return mLanguageDefinition; }
|
||||||
|
|
||||||
|
const Palette& GetPalette() const { return mPaletteBase; }
|
||||||
|
void SetPalette(const Palette& aValue);
|
||||||
|
|
||||||
|
void SetErrorMarkers(const ErrorMarkers& aMarkers) { mErrorMarkers = aMarkers; }
|
||||||
|
void SetBreakpoints(const Breakpoints& aMarkers) { mBreakpoints = aMarkers; }
|
||||||
|
|
||||||
|
void Render(const char* aTitle, const ImVec2& aSize = ImVec2(), bool aBorder = false);
|
||||||
|
void SetText(const std::string& aText);
|
||||||
|
std::string GetText() const;
|
||||||
|
|
||||||
|
void SetTextLines(const std::vector<std::string>& aLines);
|
||||||
|
std::vector<std::string> GetTextLines() const;
|
||||||
|
|
||||||
|
std::string GetSelectedText() const;
|
||||||
|
std::string GetCurrentLineText()const;
|
||||||
|
|
||||||
|
int GetTotalLines() const { return (int)mLines.size(); }
|
||||||
|
bool IsOverwrite() const { return mOverwrite; }
|
||||||
|
|
||||||
|
void SetReadOnly(bool aValue);
|
||||||
|
bool IsReadOnly() const { return mReadOnly; }
|
||||||
|
bool IsTextChanged() const { return mTextChanged; }
|
||||||
|
bool IsCursorPositionChanged() const { return mCursorPositionChanged; }
|
||||||
|
|
||||||
|
bool IsColorizerEnabled() const { return mColorizerEnabled; }
|
||||||
|
void SetColorizerEnable(bool aValue);
|
||||||
|
|
||||||
|
Coordinates GetCursorPosition() const { return GetActualCursorCoordinates(); }
|
||||||
|
void SetCursorPosition(const Coordinates& aPosition);
|
||||||
|
|
||||||
|
inline void SetHandleMouseInputs (bool aValue){ mHandleMouseInputs = aValue;}
|
||||||
|
inline bool IsHandleMouseInputsEnabled() const { return mHandleMouseInputs; }
|
||||||
|
|
||||||
|
inline void SetHandleKeyboardInputs (bool aValue){ mHandleKeyboardInputs = aValue;}
|
||||||
|
inline bool IsHandleKeyboardInputsEnabled() const { return mHandleKeyboardInputs; }
|
||||||
|
|
||||||
|
inline void SetImGuiChildIgnored (bool aValue){ mIgnoreImGuiChild = aValue;}
|
||||||
|
inline bool IsImGuiChildIgnored() const { return mIgnoreImGuiChild; }
|
||||||
|
|
||||||
|
inline void SetShowWhitespaces(bool aValue) { mShowWhitespaces = aValue; }
|
||||||
|
inline bool IsShowingWhitespaces() const { return mShowWhitespaces; }
|
||||||
|
|
||||||
|
void SetTabSize(int aValue);
|
||||||
|
inline int GetTabSize() const { return mTabSize; }
|
||||||
|
|
||||||
|
void InsertText(const std::string& aValue);
|
||||||
|
void InsertText(const char* aValue);
|
||||||
|
|
||||||
|
void MoveUp(int aAmount = 1, bool aSelect = false);
|
||||||
|
void MoveDown(int aAmount = 1, bool aSelect = false);
|
||||||
|
void MoveLeft(int aAmount = 1, bool aSelect = false, bool aWordMode = false);
|
||||||
|
void MoveRight(int aAmount = 1, bool aSelect = false, bool aWordMode = false);
|
||||||
|
void MoveTop(bool aSelect = false);
|
||||||
|
void MoveBottom(bool aSelect = false);
|
||||||
|
void MoveHome(bool aSelect = false);
|
||||||
|
void MoveEnd(bool aSelect = false);
|
||||||
|
|
||||||
|
void SetSelectionStart(const Coordinates& aPosition);
|
||||||
|
void SetSelectionEnd(const Coordinates& aPosition);
|
||||||
|
void SetSelection(const Coordinates& aStart, const Coordinates& aEnd, SelectionMode aMode = SelectionMode::Normal);
|
||||||
|
void SelectWordUnderCursor();
|
||||||
|
void SelectAll();
|
||||||
|
bool HasSelection() const;
|
||||||
|
|
||||||
|
void Copy();
|
||||||
|
void Cut();
|
||||||
|
void Paste();
|
||||||
|
void Delete();
|
||||||
|
|
||||||
|
bool CanUndo() const;
|
||||||
|
bool CanRedo() const;
|
||||||
|
void Undo(int aSteps = 1);
|
||||||
|
void Redo(int aSteps = 1);
|
||||||
|
|
||||||
|
static const Palette& GetDarkPalette();
|
||||||
|
static const Palette& GetLightPalette();
|
||||||
|
static const Palette& GetRetroBluePalette();
|
||||||
|
|
||||||
|
private:
|
||||||
|
typedef std::vector<std::pair<std::regex, PaletteIndex>> RegexList;
|
||||||
|
|
||||||
|
struct EditorState
|
||||||
|
{
|
||||||
|
Coordinates mSelectionStart;
|
||||||
|
Coordinates mSelectionEnd;
|
||||||
|
Coordinates mCursorPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
class UndoRecord
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
UndoRecord() {}
|
||||||
|
~UndoRecord() {}
|
||||||
|
|
||||||
|
UndoRecord(
|
||||||
|
const std::string& aAdded,
|
||||||
|
const TextEditor::Coordinates aAddedStart,
|
||||||
|
const TextEditor::Coordinates aAddedEnd,
|
||||||
|
|
||||||
|
const std::string& aRemoved,
|
||||||
|
const TextEditor::Coordinates aRemovedStart,
|
||||||
|
const TextEditor::Coordinates aRemovedEnd,
|
||||||
|
|
||||||
|
TextEditor::EditorState& aBefore,
|
||||||
|
TextEditor::EditorState& aAfter);
|
||||||
|
|
||||||
|
void Undo(TextEditor* aEditor);
|
||||||
|
void Redo(TextEditor* aEditor);
|
||||||
|
|
||||||
|
std::string mAdded;
|
||||||
|
Coordinates mAddedStart;
|
||||||
|
Coordinates mAddedEnd;
|
||||||
|
|
||||||
|
std::string mRemoved;
|
||||||
|
Coordinates mRemovedStart;
|
||||||
|
Coordinates mRemovedEnd;
|
||||||
|
|
||||||
|
EditorState mBefore;
|
||||||
|
EditorState mAfter;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::vector<UndoRecord> UndoBuffer;
|
||||||
|
|
||||||
|
void ProcessInputs();
|
||||||
|
void Colorize(int aFromLine = 0, int aCount = -1);
|
||||||
|
void ColorizeRange(int aFromLine = 0, int aToLine = 0);
|
||||||
|
void ColorizeInternal();
|
||||||
|
float TextDistanceToLineStart(const Coordinates& aFrom) const;
|
||||||
|
void EnsureCursorVisible();
|
||||||
|
int GetPageSize() const;
|
||||||
|
std::string GetText(const Coordinates& aStart, const Coordinates& aEnd) const;
|
||||||
|
Coordinates GetActualCursorCoordinates() const;
|
||||||
|
Coordinates SanitizeCoordinates(const Coordinates& aValue) const;
|
||||||
|
void Advance(Coordinates& aCoordinates) const;
|
||||||
|
void DeleteRange(const Coordinates& aStart, const Coordinates& aEnd);
|
||||||
|
int InsertTextAt(Coordinates& aWhere, const char* aValue);
|
||||||
|
void AddUndo(UndoRecord& aValue);
|
||||||
|
Coordinates ScreenPosToCoordinates(const ImVec2& aPosition) const;
|
||||||
|
Coordinates FindWordStart(const Coordinates& aFrom) const;
|
||||||
|
Coordinates FindWordEnd(const Coordinates& aFrom) const;
|
||||||
|
Coordinates FindNextWord(const Coordinates& aFrom) const;
|
||||||
|
int GetCharacterIndex(const Coordinates& aCoordinates) const;
|
||||||
|
int GetCharacterColumn(int aLine, int aIndex) const;
|
||||||
|
int GetLineCharacterCount(int aLine) const;
|
||||||
|
int GetLineMaxColumn(int aLine) const;
|
||||||
|
bool IsOnWordBoundary(const Coordinates& aAt) const;
|
||||||
|
void RemoveLine(int aStart, int aEnd);
|
||||||
|
void RemoveLine(int aIndex);
|
||||||
|
Line& InsertLine(int aIndex);
|
||||||
|
void EnterCharacter(ImWchar aChar, bool aShift);
|
||||||
|
void Backspace();
|
||||||
|
void DeleteSelection();
|
||||||
|
std::string GetWordUnderCursor() const;
|
||||||
|
std::string GetWordAt(const Coordinates& aCoords) const;
|
||||||
|
ImU32 GetGlyphColor(const Glyph& aGlyph) const;
|
||||||
|
|
||||||
|
void HandleKeyboardInputs();
|
||||||
|
void HandleMouseInputs();
|
||||||
|
void Render();
|
||||||
|
|
||||||
|
float mLineSpacing;
|
||||||
|
Lines mLines;
|
||||||
|
EditorState mState;
|
||||||
|
UndoBuffer mUndoBuffer;
|
||||||
|
int mUndoIndex;
|
||||||
|
|
||||||
|
int mTabSize;
|
||||||
|
bool mOverwrite;
|
||||||
|
bool mReadOnly;
|
||||||
|
bool mWithinRender;
|
||||||
|
bool mScrollToCursor;
|
||||||
|
bool mScrollToTop;
|
||||||
|
bool mTextChanged;
|
||||||
|
bool mColorizerEnabled;
|
||||||
|
float mTextStart; // position (in pixels) where a code line starts relative to the left of the TextEditor.
|
||||||
|
int mLeftMargin;
|
||||||
|
bool mCursorPositionChanged;
|
||||||
|
int mColorRangeMin, mColorRangeMax;
|
||||||
|
SelectionMode mSelectionMode;
|
||||||
|
bool mHandleKeyboardInputs;
|
||||||
|
bool mHandleMouseInputs;
|
||||||
|
bool mIgnoreImGuiChild;
|
||||||
|
bool mShowWhitespaces;
|
||||||
|
|
||||||
|
Palette mPaletteBase;
|
||||||
|
Palette mPalette;
|
||||||
|
LanguageDefinition mLanguageDefinition;
|
||||||
|
RegexList mRegexList;
|
||||||
|
|
||||||
|
bool mCheckComments;
|
||||||
|
Breakpoints mBreakpoints;
|
||||||
|
ErrorMarkers mErrorMarkers;
|
||||||
|
ImVec2 mCharAdvance;
|
||||||
|
Coordinates mInteractiveStart, mInteractiveEnd;
|
||||||
|
std::string mLineBuffer;
|
||||||
|
uint64_t mStartTime;
|
||||||
|
|
||||||
|
float mLastClick;
|
||||||
|
};
|
||||||
|
|
@ -722,30 +722,6 @@ struct IldaeilWidget : ImGuiWidget, IdleCallback, Thread {
|
||||||
|
|
||||||
std::strcpy(fPluginSearchString, "Search...");
|
std::strcpy(fPluginSearchString, "Search...");
|
||||||
|
|
||||||
ImGuiStyle& style(ImGui::GetStyle());
|
|
||||||
style.FrameRounding = 4;
|
|
||||||
|
|
||||||
ImVec4* colors = ImGui::GetStyle().Colors;
|
|
||||||
ImVec4 color_Cardinal = ImVec4(0.76f, 0.11f, 0.22f, 1.00f);
|
|
||||||
ImVec4 color_DimCardinal = ImVec4(171.0 / 255.0, 54.0 / 255.0, 73.0 / 255.0, 1.00f);
|
|
||||||
colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
|
|
||||||
colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
|
|
||||||
colors[ImGuiCol_WindowBg] = ImVec4(0.101f, 0.101f, 0.101f, 0.94f);
|
|
||||||
colors[ImGuiCol_FrameBg] = ImVec4(0.20f, 0.21f, 0.22f, 0.54f);
|
|
||||||
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.40f, 0.40f, 0.40f, 0.40f);
|
|
||||||
colors[ImGuiCol_FrameBgActive] = ImVec4(0.18f, 0.18f, 0.18f, 0.67f);
|
|
||||||
colors[ImGuiCol_TitleBgActive] = ImVec4(0.29f, 0.29f, 0.29f, 1.00f);
|
|
||||||
colors[ImGuiCol_CheckMark] = color_Cardinal;
|
|
||||||
colors[ImGuiCol_SliderGrab] = color_DimCardinal;
|
|
||||||
colors[ImGuiCol_SliderGrabActive] = color_Cardinal;
|
|
||||||
colors[ImGuiCol_Button] = color_DimCardinal;
|
|
||||||
colors[ImGuiCol_ButtonHovered] = color_Cardinal;
|
|
||||||
colors[ImGuiCol_ButtonActive] = color_Cardinal;
|
|
||||||
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.87f, 0.87f, 0.87f, 0.35f);
|
|
||||||
colors[ImGuiCol_Header] = ImVec4(0.44f, 0.44f, 0.44f, 0.40f);
|
|
||||||
colors[ImGuiCol_HeaderHovered] = color_DimCardinal;
|
|
||||||
colors[ImGuiCol_HeaderActive] = color_Cardinal;
|
|
||||||
|
|
||||||
if (checkIfPluginIsLoaded())
|
if (checkIfPluginIsLoaded())
|
||||||
fIdleState = kIdleInitPluginAlreadyLoaded;
|
fIdleState = kIdleInitPluginAlreadyLoaded;
|
||||||
|
|
||||||
|
|
|
||||||
125
plugins/Cardinal/src/ImGuiTextEditor.cpp
Normal file
125
plugins/Cardinal/src/ImGuiTextEditor.cpp
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* Syntax highlighting text editor (for ImGui in DPF)
|
||||||
|
* Copyright (C) 2021 Filipe Coelho <falktx@falktx.com>
|
||||||
|
* Copyright (c) 2017 BalazsJako
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ImGuiTextEditor.hpp"
|
||||||
|
#include "DearImGuiColorTextEditor/TextEditor.h"
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
struct ImGuiTextEditor::PrivateData {
|
||||||
|
ImGuiTextEditor* const self;
|
||||||
|
TextEditor editor;
|
||||||
|
std::string file;
|
||||||
|
|
||||||
|
explicit PrivateData(ImGuiTextEditor* const s)
|
||||||
|
: self(s)
|
||||||
|
{
|
||||||
|
// https://github.com/BalazsJako/ColorTextEditorDemo/blob/master/main.cpp
|
||||||
|
|
||||||
|
editor.SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus());
|
||||||
|
editor.SetText(""
|
||||||
|
"// Welcome to a real text editor inside Cardinal\n"
|
||||||
|
"\n"
|
||||||
|
"#define I_AM_A_MACRO\n"
|
||||||
|
"\n"
|
||||||
|
"int and_i_am_a_variable;\n"
|
||||||
|
"\n"
|
||||||
|
"/* look ma, a comment! */\n"
|
||||||
|
"int such_highlight_much_wow() { return 1337; }\n"
|
||||||
|
);
|
||||||
|
editor.SetCursorPosition(TextEditor::Coordinates(8, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
DISTRHO_DECLARE_NON_COPYABLE(PrivateData)
|
||||||
|
};
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
ImGuiTextEditor::ImGuiTextEditor()
|
||||||
|
: ImGuiWidget(),
|
||||||
|
pData(new PrivateData(this)) {}
|
||||||
|
|
||||||
|
ImGuiTextEditor::~ImGuiTextEditor()
|
||||||
|
{
|
||||||
|
delete pData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void ImGuiTextEditor::setText(const std::string& text)
|
||||||
|
{
|
||||||
|
pData->editor.SetText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ImGuiTextEditor::getText() const
|
||||||
|
{
|
||||||
|
return pData->editor.GetText();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiTextEditor::setTextLines(const std::vector<std::string>& lines)
|
||||||
|
{
|
||||||
|
pData->editor.SetTextLines(lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> ImGuiTextEditor::getTextLines() const
|
||||||
|
{
|
||||||
|
return pData->editor.GetTextLines();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ImGuiTextEditor::getSelectedText() const
|
||||||
|
{
|
||||||
|
return pData->editor.GetSelectedText();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ImGuiTextEditor::getCurrentLineText()const
|
||||||
|
{
|
||||||
|
return pData->editor.GetCurrentLineText();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void ImGuiTextEditor::drawImGui()
|
||||||
|
{
|
||||||
|
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(box.size.x, box.size.y));
|
||||||
|
|
||||||
|
if (ImGui::Begin("TextEdit", nullptr, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize))
|
||||||
|
{
|
||||||
|
TextEditor& editor(pData->editor);
|
||||||
|
|
||||||
|
const TextEditor::Coordinates cpos = editor.GetCursorPosition();
|
||||||
|
|
||||||
|
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s",
|
||||||
|
cpos.mLine + 1, cpos.mColumn + 1,
|
||||||
|
editor.GetTotalLines(),
|
||||||
|
editor.IsOverwrite() ? "Ovr" : "Ins",
|
||||||
|
editor.CanUndo() ? "*" : " ",
|
||||||
|
editor.GetLanguageDefinition().mName.c_str(),
|
||||||
|
pData->file.c_str());
|
||||||
|
|
||||||
|
editor.Render("TextEditor");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
56
plugins/Cardinal/src/ImGuiTextEditor.hpp
Normal file
56
plugins/Cardinal/src/ImGuiTextEditor.hpp
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Syntax highlighting text editor (for ImGui in DPF, converted to VCV)
|
||||||
|
* Copyright (C) 2021 Filipe Coelho <falktx@falktx.com>
|
||||||
|
* Copyright (c) 2017 BalazsJako
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ImGuiWidget.hpp"
|
||||||
|
#include "DearImGuiColorTextEditor/TextEditor.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct ImGuiTextEditor : ImGuiWidget
|
||||||
|
{
|
||||||
|
struct PrivateData;
|
||||||
|
PrivateData* const pData;
|
||||||
|
|
||||||
|
ImGuiTextEditor();
|
||||||
|
~ImGuiTextEditor() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Methods from internal TextEdit.
|
||||||
|
*/
|
||||||
|
void setText(const std::string& text);
|
||||||
|
std::string getText() const;
|
||||||
|
|
||||||
|
void setTextLines(const std::vector<std::string>& lines);
|
||||||
|
std::vector<std::string> getTextLines() const;
|
||||||
|
|
||||||
|
std::string getSelectedText() const;
|
||||||
|
std::string getCurrentLineText()const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/** @internal */
|
||||||
|
void drawImGui() override;
|
||||||
|
};
|
||||||
|
|
@ -38,11 +38,34 @@ struct ImGuiWidget::PrivateData {
|
||||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||||
io.IniFilename = nullptr;
|
io.IniFilename = nullptr;
|
||||||
|
|
||||||
/*
|
|
||||||
ImGuiStyle& style(ImGui::GetStyle());
|
ImGuiStyle& style(ImGui::GetStyle());
|
||||||
|
style.FrameRounding = 4;
|
||||||
|
/*
|
||||||
style.ScaleAllSizes(scaleFactor);
|
style.ScaleAllSizes(scaleFactor);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const ImVec4 color_Cardinal(0.76f, 0.11f, 0.22f, 1.00f);
|
||||||
|
const ImVec4 color_DimCardinal(171.0 / 255.0, 54.0 / 255.0, 73.0 / 255.0, 1.00f);
|
||||||
|
|
||||||
|
ImVec4* const colors = style.Colors;
|
||||||
|
colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
|
||||||
|
colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
|
||||||
|
colors[ImGuiCol_WindowBg] = ImVec4(0.101f, 0.101f, 0.101f, 0.94f);
|
||||||
|
colors[ImGuiCol_FrameBg] = ImVec4(0.20f, 0.21f, 0.22f, 0.54f);
|
||||||
|
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.40f, 0.40f, 0.40f, 0.40f);
|
||||||
|
colors[ImGuiCol_FrameBgActive] = ImVec4(0.18f, 0.18f, 0.18f, 0.67f);
|
||||||
|
colors[ImGuiCol_TitleBgActive] = ImVec4(0.29f, 0.29f, 0.29f, 1.00f);
|
||||||
|
colors[ImGuiCol_CheckMark] = color_Cardinal;
|
||||||
|
colors[ImGuiCol_SliderGrab] = color_DimCardinal;
|
||||||
|
colors[ImGuiCol_SliderGrabActive] = color_Cardinal;
|
||||||
|
colors[ImGuiCol_Button] = color_DimCardinal;
|
||||||
|
colors[ImGuiCol_ButtonHovered] = color_Cardinal;
|
||||||
|
colors[ImGuiCol_ButtonActive] = color_Cardinal;
|
||||||
|
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.87f, 0.87f, 0.87f, 0.35f);
|
||||||
|
colors[ImGuiCol_Header] = ImVec4(0.44f, 0.44f, 0.44f, 0.40f);
|
||||||
|
colors[ImGuiCol_HeaderHovered] = color_DimCardinal;
|
||||||
|
colors[ImGuiCol_HeaderActive] = color_Cardinal;
|
||||||
|
|
||||||
#ifndef DGL_NO_SHARED_RESOURCES
|
#ifndef DGL_NO_SHARED_RESOURCES
|
||||||
using namespace dpf_resources;
|
using namespace dpf_resources;
|
||||||
ImFontConfig fc;
|
ImFontConfig fc;
|
||||||
|
|
|
||||||
139
plugins/Cardinal/src/TextEditor.cpp
Normal file
139
plugins/Cardinal/src/TextEditor.cpp
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
/*
|
||||||
|
* DISTRHO Cardinal Plugin
|
||||||
|
* Copyright (C) 2021 Filipe Coelho <falktx@falktx.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation; either version 3 of
|
||||||
|
* the License, or any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* For a full copy of the GNU General Public License see the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "plugincontext.hpp"
|
||||||
|
|
||||||
|
#ifndef HEADLESS
|
||||||
|
# include "ImGuiTextEditor.hpp"
|
||||||
|
# include "extra/FileBrowserDialog.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
struct TextEditorModule : Module {
|
||||||
|
enum ParamIds {
|
||||||
|
NUM_PARAMS
|
||||||
|
};
|
||||||
|
enum InputIds {
|
||||||
|
NUM_INPUTS
|
||||||
|
};
|
||||||
|
enum OutputIds {
|
||||||
|
NUM_OUTPUTS
|
||||||
|
};
|
||||||
|
enum LightIds {
|
||||||
|
NUM_LIGHTS
|
||||||
|
};
|
||||||
|
|
||||||
|
TextEditorModule()
|
||||||
|
{
|
||||||
|
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
~TextEditorModule() override
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void process(const ProcessArgs&) override
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TextEditorModule)
|
||||||
|
};
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#ifndef HEADLESS
|
||||||
|
struct TextEditorWidget : ImGuiTextEditor
|
||||||
|
{
|
||||||
|
TextEditorWidget() : ImGuiTextEditor() {}
|
||||||
|
|
||||||
|
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TextEditorWidget)
|
||||||
|
};
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
struct TextEditorLoadFileItem : MenuItem {
|
||||||
|
TextEditorWidget* widget = nullptr;
|
||||||
|
|
||||||
|
void onAction(const event::Action &e) override
|
||||||
|
{
|
||||||
|
WeakPtr<TextEditorWidget> widget = this->widget;
|
||||||
|
async_dialog_filebrowser(false, nullptr, "Load File", [widget](char* path)
|
||||||
|
{
|
||||||
|
if (path)
|
||||||
|
{
|
||||||
|
if (widget)
|
||||||
|
{
|
||||||
|
std::ifstream f(path);
|
||||||
|
if (f.good())
|
||||||
|
{
|
||||||
|
const std::string str((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||||
|
widget->setText(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
struct TextEditorModuleWidget : ModuleWidget {
|
||||||
|
TextEditorWidget* textEditorWidget = nullptr;
|
||||||
|
|
||||||
|
TextEditorModuleWidget(TextEditorModule* const module)
|
||||||
|
{
|
||||||
|
setModule(module);
|
||||||
|
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Ildaeil.svg")));
|
||||||
|
|
||||||
|
if (module != nullptr)
|
||||||
|
{
|
||||||
|
textEditorWidget = new TextEditorWidget();
|
||||||
|
textEditorWidget->box.pos = Vec(2 * RACK_GRID_WIDTH, 0);
|
||||||
|
textEditorWidget->box.size = Vec(box.size.x - 2 * RACK_GRID_WIDTH, box.size.y);
|
||||||
|
addChild(textEditorWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
addChild(createWidget<ScrewBlack>(Vec(0, 0)));
|
||||||
|
addChild(createWidget<ScrewBlack>(Vec(0, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
|
||||||
|
addChild(createWidget<ScrewBlack>(Vec(RACK_GRID_WIDTH, 0)));
|
||||||
|
addChild(createWidget<ScrewBlack>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void appendContextMenu(Menu *menu) override {
|
||||||
|
menu->addChild(new MenuEntry);
|
||||||
|
|
||||||
|
TextEditorLoadFileItem* loadFileItem = new TextEditorLoadFileItem;
|
||||||
|
loadFileItem->text = "Load File";
|
||||||
|
loadFileItem->widget = textEditorWidget;
|
||||||
|
menu->addChild(loadFileItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TextEditorModuleWidget)
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
typedef ModuleWidget TextEditorModuleWidget;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Model* modelTextEditor = createModel<TextEditorModule, TextEditorModuleWidget>("TextEditor");
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
@ -34,3 +34,4 @@ extern Model* modelHostCV;
|
||||||
extern Model* modelHostParameters;
|
extern Model* modelHostParameters;
|
||||||
extern Model* modelHostTime;
|
extern Model* modelHostTime;
|
||||||
extern Model* modelIldaeil;
|
extern Model* modelIldaeil;
|
||||||
|
extern Model* modelTextEditor;
|
||||||
|
|
|
||||||
|
|
@ -192,10 +192,13 @@ PLUGIN_FILES += Cardinal/src/HostCV.cpp
|
||||||
PLUGIN_FILES += Cardinal/src/HostParameters.cpp
|
PLUGIN_FILES += Cardinal/src/HostParameters.cpp
|
||||||
PLUGIN_FILES += Cardinal/src/HostTime.cpp
|
PLUGIN_FILES += Cardinal/src/HostTime.cpp
|
||||||
PLUGIN_FILES += Cardinal/src/Ildaeil.cpp
|
PLUGIN_FILES += Cardinal/src/Ildaeil.cpp
|
||||||
|
PLUGIN_FILES += Cardinal/src/TextEditor.cpp
|
||||||
|
|
||||||
ifneq ($(HEADLESS),true)
|
ifneq ($(HEADLESS),true)
|
||||||
PLUGIN_FILES += Cardinal/src/ImGuiWidget.cpp
|
PLUGIN_FILES += Cardinal/src/ImGuiWidget.cpp
|
||||||
|
PLUGIN_FILES += Cardinal/src/ImGuiTextEditor.cpp
|
||||||
PLUGIN_FILES += $(wildcard Cardinal/src/DearImGui/*.cpp)
|
PLUGIN_FILES += $(wildcard Cardinal/src/DearImGui/*.cpp)
|
||||||
|
PLUGIN_FILES += $(wildcard Cardinal/src/DearImGuiColorTextEditor/*.cpp)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifneq ($(NOPLUGINS),true)
|
ifneq ($(NOPLUGINS),true)
|
||||||
|
|
|
||||||
|
|
@ -675,6 +675,7 @@ static void initStatic__Cardinal()
|
||||||
p->addModel(modelHostParameters);
|
p->addModel(modelHostParameters);
|
||||||
p->addModel(modelHostTime);
|
p->addModel(modelHostTime);
|
||||||
p->addModel(modelIldaeil);
|
p->addModel(modelIldaeil);
|
||||||
|
p->addModel(modelTextEditor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue