mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-05-28 17:16:29 +03:00
Finished implementing the logic of warn before tab close on terminals (SpartanJ/ecode#644). Also added this feature in eterm (enabled with --warn-before-closing).
Fix a use-after-free bug in Font::getGlyph and FontTrueType::getGlyphIndex. Some extra checks to avoid crash reported in SpartanJ/ecode#650. Some minor improvement in auto-close brackets logic. Added clone document buffer (SpartanJ/ecode#632).
This commit is contained in:
@@ -302,7 +302,7 @@
|
||||
"working_dir": "${project_root}/bin"
|
||||
},
|
||||
{
|
||||
"args": "",
|
||||
"args": "--warn-before-closing",
|
||||
"command": "${project_root}/bin/eterm-debug",
|
||||
"name": "eterm-debug",
|
||||
"working_dir": "${project_root}/bin"
|
||||
|
||||
@@ -132,8 +132,8 @@ class EE_API Font {
|
||||
|
||||
virtual const Info& getInfo() const = 0;
|
||||
|
||||
virtual const Glyph& getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold,
|
||||
bool italic, Float outlineThickness = 0 ) const = 0;
|
||||
virtual Glyph getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold, bool italic,
|
||||
Float outlineThickness = 0 ) const = 0;
|
||||
|
||||
/** @return The glyph drawable that represents the glyph in a texture. The glyph drawable
|
||||
* allocation is managed by the font. */
|
||||
|
||||
@@ -36,7 +36,7 @@ class EE_API FontBMFont : public Font {
|
||||
|
||||
const Font::Info& getInfo() const;
|
||||
|
||||
const Glyph& getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold, bool italic,
|
||||
Glyph getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold, bool italic,
|
||||
Float outlineThickness = 0 ) const;
|
||||
|
||||
GlyphDrawable* getGlyphDrawable( Uint32 codePoint, unsigned int characterSize,
|
||||
|
||||
@@ -40,8 +40,8 @@ class EE_API FontSprite : public Font {
|
||||
|
||||
const Font::Info& getInfo() const;
|
||||
|
||||
const Glyph& getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold, bool italic,
|
||||
Float outlineThickness = 0 ) const;
|
||||
Glyph getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold, bool italic,
|
||||
Float outlineThickness = 0 ) const;
|
||||
|
||||
GlyphDrawable* getGlyphDrawable( Uint32 codePoint, unsigned int characterSize,
|
||||
bool bold = false, bool italic = false,
|
||||
|
||||
@@ -31,11 +31,11 @@ class EE_API FontTrueType : public Font {
|
||||
|
||||
const Font::Info& getInfo() const;
|
||||
|
||||
const Glyph& getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold, bool italic,
|
||||
Glyph getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold, bool italic,
|
||||
Float outlineThickness = 0 ) const;
|
||||
|
||||
const Glyph& getGlyphByIndex( Uint32 index, unsigned int characterSize, bool bold, bool italic,
|
||||
Float outlineThickness = 0 ) const;
|
||||
Glyph getGlyphByIndex( Uint32 index, unsigned int characterSize, bool bold, bool italic,
|
||||
Float outlineThickness = 0 ) const;
|
||||
|
||||
GlyphDrawable* getGlyphDrawable( Uint32 codePoint, unsigned int characterSize,
|
||||
bool bold = false, bool italic = false,
|
||||
@@ -183,10 +183,10 @@ class EE_API FontTrueType : public Font {
|
||||
|
||||
void cleanup();
|
||||
|
||||
const Glyph& getGlyphByIndex( Uint32 index, unsigned int characterSize, bool bold, bool italic,
|
||||
Glyph getGlyphByIndex( Uint32 index, unsigned int characterSize, bool bold, bool italic,
|
||||
Float outlineThickness, Page& page ) const;
|
||||
|
||||
const Glyph& getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold, bool italic,
|
||||
Glyph getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold, bool italic,
|
||||
Float outlineThickness, Page& page ) const;
|
||||
|
||||
GlyphDrawable* getGlyphDrawableFromGlyphIndex( Uint32 glyphIndex, unsigned int characterSize,
|
||||
|
||||
@@ -59,6 +59,10 @@ class EE_API UICodeEditorSplitter {
|
||||
virtual bool tryTabClose( UIWidget* widget, UITabWidget::FocusTabBehavior focusTabBehavior,
|
||||
std::function<void()> onMsgBoxCloseCb = {} );
|
||||
|
||||
virtual bool tryCodeEditorClose( UICodeEditor* editor,
|
||||
UITabWidget::FocusTabBehavior focusTabBehavior,
|
||||
std::function<void()> onMsgBoxCloseCb = {} );
|
||||
|
||||
virtual bool tryCloseAllTabs( UIWidget* widget,
|
||||
UITabWidget::FocusTabBehavior focusTabBehavior );
|
||||
|
||||
@@ -253,7 +257,8 @@ class EE_API UICodeEditorSplitter {
|
||||
t.setCommand( "switch-to-previous-split", [this] { switchPreviousSplit( mCurWidget ); } );
|
||||
t.setCommand( "switch-to-next-split", [this] { switchNextSplit( mCurWidget ); } );
|
||||
t.setCommand( "close-tab", [this] {
|
||||
tryTabClose( mCurWidget, UITabWidget::FocusTabBehavior::Default );
|
||||
if ( tryTabClose( mCurWidget, UITabWidget::FocusTabBehavior::Default ) )
|
||||
closeTab( mCurWidget, UITabWidget::FocusTabBehavior::Default );
|
||||
} );
|
||||
t.setCommand( "close-other-tabs", [this] {
|
||||
tryCloseOtherTabs( mCurWidget, UITabWidget::FocusTabBehavior::Default );
|
||||
@@ -365,9 +370,14 @@ class EE_API UICodeEditorSplitter {
|
||||
|
||||
std::shared_ptr<TextDocument> getTextDocumentRef( TextDocument* doc );
|
||||
|
||||
void setTabTryCloseCallback( UITabWidget::TabTryCloseCallback cb );
|
||||
// @return True if can be removed
|
||||
typedef std::function<bool( UIWidget*, UITabWidget::FocusTabBehavior,
|
||||
std::function<void()> onMsgBoxCloseCb )>
|
||||
TabTryCloseCallback;
|
||||
|
||||
bool isEditorInAnyWidget( UICodeEditor* ) const;
|
||||
void setTabTryCloseCallback( TabTryCloseCallback cb );
|
||||
|
||||
bool isWidgetInAnyWidget( UIWidget* ) const;
|
||||
|
||||
protected:
|
||||
UISceneNode* mUISceneNode{ nullptr };
|
||||
@@ -395,7 +405,7 @@ class EE_API UICodeEditorSplitter {
|
||||
size_t mNavigationHistoryPos{ std::numeric_limits<size_t>::max() };
|
||||
std::function<void( UITabWidget* )> mOnTabWidgetCreateCb;
|
||||
Float mVisualSplitEdgePercent{ 0.1 };
|
||||
UITabWidget::TabTryCloseCallback mTabTryCloseCb;
|
||||
TabTryCloseCallback mTabTryCloseCb;
|
||||
|
||||
UICodeEditorSplitter( UICodeEditorSplitter::Client* client, UISceneNode* sceneNode,
|
||||
std::shared_ptr<ThreadPool> threadPool,
|
||||
|
||||
@@ -157,8 +157,8 @@ bool FontBMFont::loadFromStream( IOStream& stream ) {
|
||||
glyph.textureRect = Rect( charX, charY, charWidth, charHeight );
|
||||
}
|
||||
|
||||
const Glyph& gl1 = getGlyph( '@', mFontSize, false, false );
|
||||
const Glyph& gl2 = getGlyph( '.', mFontSize, false, false );
|
||||
auto gl1 = getGlyph( '@', mFontSize, false, false );
|
||||
auto gl2 = getGlyph( '.', mFontSize, false, false );
|
||||
mIsMonospace = gl1.advance == gl2.advance;
|
||||
|
||||
sendEvent( Event::Load );
|
||||
@@ -185,8 +185,8 @@ const FontBMFont::Info& FontBMFont::getInfo() const {
|
||||
return mInfo;
|
||||
}
|
||||
|
||||
const Glyph& FontBMFont::getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold,
|
||||
bool /*italic*/, Float outlineThickness ) const {
|
||||
Glyph FontBMFont::getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold,
|
||||
bool /*italic*/, Float outlineThickness ) const {
|
||||
GlyphTable& glyphs = mPages[characterSize].glyphs;
|
||||
|
||||
GlyphTable::const_iterator it = glyphs.find( codePoint );
|
||||
@@ -207,7 +207,7 @@ GlyphDrawable* FontBMFont::getGlyphDrawable( Uint32 codePoint, unsigned int char
|
||||
if ( it != drawables.end() ) {
|
||||
return it->second;
|
||||
} else {
|
||||
const Glyph& glyph = getGlyph( codePoint, characterSize, bold, italic, outlineThickness );
|
||||
auto glyph = getGlyph( codePoint, characterSize, bold, italic, outlineThickness );
|
||||
const auto& page = mPages[characterSize];
|
||||
GlyphDrawable* region = GlyphDrawable::New(
|
||||
page.texture, glyph.textureRect, glyph.bounds.getSize(),
|
||||
@@ -225,7 +225,7 @@ Glyph FontBMFont::loadGlyph( Uint32 codePoint, unsigned int characterSize, bool,
|
||||
GlyphTable::const_iterator it = glyphs.find( codePoint );
|
||||
|
||||
if ( it != glyphs.end() ) {
|
||||
const Glyph& oriGlyph = it->second;
|
||||
auto oriGlyph = it->second;
|
||||
|
||||
Float scale = (Float)characterSize / (Float)mFontSize;
|
||||
|
||||
|
||||
@@ -164,8 +164,8 @@ const FontSprite::Info& FontSprite::getInfo() const {
|
||||
return mInfo;
|
||||
}
|
||||
|
||||
const Glyph& FontSprite::getGlyph( Uint32 codePoint, unsigned int characterSize, bool, bool,
|
||||
Float ) const {
|
||||
Glyph FontSprite::getGlyph( Uint32 codePoint, unsigned int characterSize, bool, bool,
|
||||
Float ) const {
|
||||
GlyphTable& glyphs = mPages[characterSize].glyphs;
|
||||
|
||||
GlyphTable::const_iterator it = glyphs.find( codePoint );
|
||||
@@ -186,7 +186,7 @@ GlyphDrawable* FontSprite::getGlyphDrawable( Uint32 codePoint, unsigned int char
|
||||
if ( it != drawables.end() ) {
|
||||
return it->second;
|
||||
} else {
|
||||
const Glyph& glyph = getGlyph( codePoint, characterSize, bold, italic, outlineThickness );
|
||||
auto glyph = getGlyph( codePoint, characterSize, bold, italic, outlineThickness );
|
||||
const auto& page = mPages[characterSize];
|
||||
GlyphDrawable* region = GlyphDrawable::New(
|
||||
page.texture, glyph.textureRect, glyph.bounds.getSize(),
|
||||
@@ -204,7 +204,7 @@ Glyph FontSprite::loadGlyph( Uint32 codePoint, unsigned int characterSize ) cons
|
||||
GlyphTable::const_iterator it = glyphs.find( codePoint );
|
||||
|
||||
if ( it != glyphs.end() ) {
|
||||
const Glyph& oriGlyph = it->second;
|
||||
auto oriGlyph = it->second;
|
||||
|
||||
Float scale = (Float)characterSize / (Float)mFontSize;
|
||||
|
||||
|
||||
@@ -328,8 +328,8 @@ Uint32 FontTrueType::getGlyphIndex( const Uint32& codePoint ) const {
|
||||
return index;
|
||||
}
|
||||
|
||||
const Glyph& FontTrueType::getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold,
|
||||
bool italic, Float outlineThickness ) const {
|
||||
Glyph FontTrueType::getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold, bool italic,
|
||||
Float outlineThickness ) const {
|
||||
Uint32 idx = 0;
|
||||
if ( mEnableEmojiFallback && !mIsColorEmojiFont && !mIsEmojiFont &&
|
||||
Font::isEmojiCodePoint( codePoint ) ) {
|
||||
@@ -400,15 +400,14 @@ const Glyph& FontTrueType::getGlyph( Uint32 codePoint, unsigned int characterSiz
|
||||
return getGlyphByIndex( idx, characterSize, bold, italic, outlineThickness );
|
||||
}
|
||||
|
||||
const Glyph& FontTrueType::getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold,
|
||||
bool italic, Float outlineThickness, Page& page ) const {
|
||||
Glyph FontTrueType::getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold, bool italic,
|
||||
Float outlineThickness, Page& page ) const {
|
||||
Uint32 index = getGlyphIndex( codePoint );
|
||||
return getGlyphByIndex( index, characterSize, bold, italic, outlineThickness, page );
|
||||
}
|
||||
|
||||
const Glyph& FontTrueType::getGlyphByIndex( Uint32 index, unsigned int characterSize, bool bold,
|
||||
bool italic, Float outlineThickness,
|
||||
Page& page ) const {
|
||||
Glyph FontTrueType::getGlyphByIndex( Uint32 index, unsigned int characterSize, bool bold,
|
||||
bool italic, Float outlineThickness, Page& page ) const {
|
||||
eeASSERT( Engine::isMainThread() );
|
||||
|
||||
// Get the page corresponding to the character size
|
||||
@@ -431,8 +430,8 @@ const Glyph& FontTrueType::getGlyphByIndex( Uint32 index, unsigned int character
|
||||
}
|
||||
}
|
||||
|
||||
const Glyph& FontTrueType::getGlyphByIndex( Uint32 index, unsigned int characterSize, bool bold,
|
||||
bool italic, Float outlineThickness ) const {
|
||||
Glyph FontTrueType::getGlyphByIndex( Uint32 index, unsigned int characterSize, bool bold,
|
||||
bool italic, Float outlineThickness ) const {
|
||||
return getGlyphByIndex( index, characterSize, bold, italic, outlineThickness,
|
||||
getPage( characterSize ) );
|
||||
}
|
||||
@@ -539,7 +538,7 @@ GlyphDrawable* FontTrueType::getGlyphDrawable( Uint32 codePoint, unsigned int ch
|
||||
if ( it != drawables.end() ) {
|
||||
return it->second;
|
||||
} else {
|
||||
const Glyph& glyph = getGlyph( codePoint, characterSize, bold, italic, outlineThickness );
|
||||
auto glyph = getGlyph( codePoint, characterSize, bold, italic, outlineThickness );
|
||||
GlyphDrawable* region = GlyphDrawable::New(
|
||||
page.texture, glyph.textureRect, glyph.size,
|
||||
String::format( "%s_%d_%u", mFontName.c_str(), characterSize, glyphIndex ) );
|
||||
@@ -566,7 +565,7 @@ GlyphDrawable* FontTrueType::getGlyphDrawableFromGlyphIndex( Uint32 glyphIndex,
|
||||
if ( it != drawables.end() ) {
|
||||
return it->second;
|
||||
} else {
|
||||
const Glyph& glyph =
|
||||
auto glyph =
|
||||
getGlyphByIndex( glyphIndex, characterSize, bold, italic, outlineThickness, page );
|
||||
GlyphDrawable* region = GlyphDrawable::New(
|
||||
page.texture, glyph.textureRect, glyph.size,
|
||||
@@ -600,8 +599,8 @@ Float FontTrueType::getKerning( Uint32 first, Uint32 second, unsigned int charac
|
||||
FT_Face face = static_cast<FT_Face>( mFace );
|
||||
|
||||
if ( face && setCurrentSize( characterSize ) ) {
|
||||
const Glyph& glyph1 = getGlyph( first, characterSize, bold, italic, outlineThickness );
|
||||
const Glyph& glyph2 = getGlyph( second, characterSize, bold, italic, outlineThickness );
|
||||
auto glyph1 = getGlyph( first, characterSize, bold, italic, outlineThickness );
|
||||
auto glyph2 = getGlyph( second, characterSize, bold, italic, outlineThickness );
|
||||
|
||||
if ( glyph1.font != glyph2.font )
|
||||
return 0.f;
|
||||
|
||||
@@ -1620,7 +1620,7 @@ void Text::updateWidthCache() {
|
||||
size_t size = mString.size();
|
||||
for ( std::size_t i = 0; i < size; ++i ) {
|
||||
codepoint = mString[i];
|
||||
const Glyph& glyph =
|
||||
auto glyph =
|
||||
mFontStyleConfig.Font->getGlyph( codepoint, mFontStyleConfig.CharacterSize, bold,
|
||||
italic, mFontStyleConfig.OutlineThickness );
|
||||
if ( codepoint != '\r' && codepoint != '\t' ) {
|
||||
@@ -2168,7 +2168,7 @@ void Text::ensureGeometryUpdate() {
|
||||
|
||||
// Apply the outline
|
||||
if ( mFontStyleConfig.OutlineThickness != 0 ) {
|
||||
const Glyph& glyph =
|
||||
auto glyph =
|
||||
mFontStyleConfig.Font->getGlyph( curChar, mFontStyleConfig.CharacterSize, bold,
|
||||
reqItalic, mFontStyleConfig.OutlineThickness );
|
||||
|
||||
@@ -2192,7 +2192,7 @@ void Text::ensureGeometryUpdate() {
|
||||
}
|
||||
|
||||
// Extract the current glyph's description
|
||||
const Glyph& glyph = mFontStyleConfig.Font->getGlyph(
|
||||
auto glyph = mFontStyleConfig.Font->getGlyph(
|
||||
curChar, mFontStyleConfig.CharacterSize, bold, reqItalic );
|
||||
|
||||
// Add the glyph to the vertices
|
||||
|
||||
@@ -1869,8 +1869,12 @@ void TextDocument::moveTo( const size_t& cursorIdx, int columnOffset ) {
|
||||
setSelection( cursorIdx, positionOffset( getSelection().start(), columnOffset ) );
|
||||
}
|
||||
|
||||
static inline bool isSpace( String::StringBaseType ch ) {
|
||||
return ch == ' ' || ch == '\t' || ch == '\n';
|
||||
}
|
||||
|
||||
std::vector<bool> TextDocument::autoCloseBrackets( const String& text ) {
|
||||
static std::vector<std::pair<String::StringBaseType, String::StringBaseType>>
|
||||
static const std::vector<std::pair<String::StringBaseType, String::StringBaseType>>
|
||||
sAutoCloseBracketsPairs = { { '(', ')' }, { '{', '}' }, { '[', ']' },
|
||||
{ '"', '"' }, { '\'', '\'' }, { '`', '`' } };
|
||||
|
||||
@@ -1912,13 +1916,11 @@ std::vector<bool> TextDocument::autoCloseBrackets( const String& text ) {
|
||||
bool mustClose = true;
|
||||
|
||||
if ( sel.start().column() < (Int64)line( sel.start().line() ).size() ) {
|
||||
auto ch = line( sel.start().line() ).getText()[sel.start().column()];
|
||||
auto ch = getChar( sel.start() );
|
||||
|
||||
if ( isClose && ch == closeChar &&
|
||||
( !isSame ||
|
||||
( sel.start().column() - 1 >= 0 &&
|
||||
line( sel.start().line() ).getText()[sel.start().column() - 1] ==
|
||||
text[0] ) ) ) {
|
||||
( !isSame || ( sel.start().column() - 1 >= 0 &&
|
||||
getPrevChar( sel.start() ) == text[0] ) ) ) {
|
||||
deleteTo( i, 1 );
|
||||
inserted.push_back( false );
|
||||
continue;
|
||||
@@ -1929,6 +1931,20 @@ std::vector<bool> TextDocument::autoCloseBrackets( const String& text ) {
|
||||
}
|
||||
|
||||
if ( mustClose ) {
|
||||
TextPosition openStart = positionOffset( sel.start(), 1 );
|
||||
if ( openStart != sel.start() ) {
|
||||
int maxIt = 100;
|
||||
while ( maxIt-- > 0 && openStart < endOfDoc() &&
|
||||
isSpace( getChar( openStart ) ) ) {
|
||||
openStart = nextChar( openStart );
|
||||
}
|
||||
if ( openStart < endOfDoc() && maxIt > 0 &&
|
||||
getChar( openStart ) == closeChar ) {
|
||||
inserted.push_back( false );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
setSelection(
|
||||
i, positionOffset( insert( i, sel.start(), text + String( closeChar ) ), -1 ) );
|
||||
inserted.push_back( true );
|
||||
|
||||
@@ -486,7 +486,7 @@ void UICodeEditorSplitter::loadAsyncFileFromPathInNewTab(
|
||||
|
||||
void UICodeEditorSplitter::setCurrentEditor( UICodeEditor* editor ) {
|
||||
eeASSERT( checkEditorExists( editor ) );
|
||||
if ( !isEditorInAnyWidget( editor ) ) {
|
||||
if ( !isWidgetInAnyWidget( editor ) ) {
|
||||
eeASSERT( true ); // This should not happen by design
|
||||
return;
|
||||
}
|
||||
@@ -641,6 +641,12 @@ UITabWidget* UICodeEditorSplitter::createTabWidget( Node* parent ) {
|
||||
}
|
||||
tabWidget->on( Event::OnTabSelected, [this]( const Event* event ) {
|
||||
UITabWidget* tabWidget = event->getNode()->asType<UITabWidget>();
|
||||
eeASSERT( nullptr != tabWidget && nullptr != tabWidget->getTabSelected() &&
|
||||
nullptr != tabWidget->getTabSelected()->getOwnedWidget() );
|
||||
if ( !isWidgetInAnyWidget(
|
||||
tabWidget->getTabSelected()->getOwnedWidget()->asType<UIWidget>() ) )
|
||||
return;
|
||||
|
||||
if ( tabWidget->getTabSelected()->getOwnedWidget()->isType( UI_TYPE_CODEEDITOR ) ) {
|
||||
setCurrentEditor(
|
||||
tabWidget->getTabSelected()->getOwnedWidget()->asType<UICodeEditor>() );
|
||||
@@ -650,14 +656,12 @@ UITabWidget* UICodeEditorSplitter::createTabWidget( Node* parent ) {
|
||||
} );
|
||||
tabWidget->setTabTryCloseCallback(
|
||||
[this]( UITab* tab, UITabWidget::FocusTabBehavior focusTabBehavior ) -> bool {
|
||||
if ( mTabTryCloseCb )
|
||||
return mTabTryCloseCb( tab, focusTabBehavior );
|
||||
|
||||
if ( tab->getOwnedWidget()->isType( UI_TYPE_CODEEDITOR ) ) {
|
||||
tryTabClose( tab->getOwnedWidget()->asType<UICodeEditor>(), focusTabBehavior );
|
||||
return false;
|
||||
if ( tab->getOwnedWidget() &&
|
||||
tryTabClose( tab->getOwnedWidget()->asType<UIWidget>(), focusTabBehavior ) ) {
|
||||
closeTab( tab->getOwnedWidget()->asType<UIWidget>(),
|
||||
UITabWidget::FocusTabBehavior::Default );
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
} );
|
||||
tabWidget->on( Event::OnTabClosed, [this]( const Event* event ) {
|
||||
onTabClosed( static_cast<const TabEvent*>( event ) );
|
||||
@@ -1016,50 +1020,59 @@ void UICodeEditorSplitter::zoomReset() {
|
||||
forEachEditor( []( UICodeEditor* editor ) { editor->fontSizeReset(); } );
|
||||
}
|
||||
|
||||
bool UICodeEditorSplitter::tryCodeEditorClose( UICodeEditor* editor,
|
||||
UITabWidget::FocusTabBehavior focusTabBehavior,
|
||||
std::function<void()> onMsgBoxCloseCb ) {
|
||||
if ( !editor )
|
||||
return true;
|
||||
|
||||
if ( editor->isType( UI_TYPE_CODEEDITOR ) && nullptr != editor && editor->isDirty() &&
|
||||
countEditorsOpeningDoc( editor->getDocument() ) == 1 ) {
|
||||
if ( nullptr != mTryCloseMsgBox )
|
||||
return false;
|
||||
mTryCloseMsgBox = UIMessageBox::New(
|
||||
UIMessageBox::OK_CANCEL,
|
||||
String::format( editor->getUISceneNode()
|
||||
->i18n( "confirm_close_tab",
|
||||
"Do you really want to close this tab?\nAll changes in "
|
||||
"\"%s\" will be lost." )
|
||||
.toUtf8(),
|
||||
editor->getDocument().getFilename() ) );
|
||||
mTryCloseMsgBox->on( Event::OnConfirm, [this, editor, focusTabBehavior]( const Event* ) {
|
||||
closeTab( editor, focusTabBehavior );
|
||||
} );
|
||||
mTryCloseMsgBox->on( Event::OnClose, [this, onMsgBoxCloseCb]( const Event* ) {
|
||||
mTryCloseMsgBox = nullptr;
|
||||
if ( mCurEditor )
|
||||
mCurEditor->setFocus();
|
||||
if ( onMsgBoxCloseCb )
|
||||
onMsgBoxCloseCb();
|
||||
} );
|
||||
mTryCloseMsgBox->setTitle(
|
||||
editor->getUISceneNode()->i18n( "ask_close_tab", "Close Tab?" ) );
|
||||
mTryCloseMsgBox->center();
|
||||
mTryCloseMsgBox->show();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UICodeEditorSplitter::tryTabClose( UIWidget* widget,
|
||||
UITabWidget::FocusTabBehavior focusTabBehavior,
|
||||
std::function<void()> onMsgBoxCloseCb ) {
|
||||
if ( !widget )
|
||||
return false;
|
||||
return true;
|
||||
|
||||
if ( mTabTryCloseCb )
|
||||
return mTabTryCloseCb( widget, focusTabBehavior, onMsgBoxCloseCb );
|
||||
|
||||
if ( widget->isType( UI_TYPE_CODEEDITOR ) ) {
|
||||
UICodeEditor* editor = widget->asType<UICodeEditor>();
|
||||
if ( nullptr != editor && editor->isDirty() &&
|
||||
countEditorsOpeningDoc( editor->getDocument() ) == 1 ) {
|
||||
if ( nullptr != mTryCloseMsgBox )
|
||||
return false;
|
||||
mTryCloseMsgBox = UIMessageBox::New(
|
||||
UIMessageBox::OK_CANCEL,
|
||||
String::format( widget->getUISceneNode()
|
||||
->i18n( "confirm_close_tab",
|
||||
"Do you really want to close this tab?\nAll changes in "
|
||||
"\"%s\" will be lost." )
|
||||
.toUtf8(),
|
||||
editor->getDocument().getFilename() ) );
|
||||
mTryCloseMsgBox->on( Event::OnConfirm,
|
||||
[this, editor, focusTabBehavior]( const Event* ) {
|
||||
closeTab( editor, focusTabBehavior );
|
||||
} );
|
||||
mTryCloseMsgBox->on( Event::OnClose, [this, onMsgBoxCloseCb]( const Event* ) {
|
||||
mTryCloseMsgBox = nullptr;
|
||||
if ( mCurEditor )
|
||||
mCurEditor->setFocus();
|
||||
if ( onMsgBoxCloseCb )
|
||||
onMsgBoxCloseCb();
|
||||
} );
|
||||
mTryCloseMsgBox->setTitle(
|
||||
widget->getUISceneNode()->i18n( "ask_close_tab", "Close Tab?" ) );
|
||||
mTryCloseMsgBox->center();
|
||||
mTryCloseMsgBox->show();
|
||||
return false;
|
||||
} else {
|
||||
closeTab( editor, focusTabBehavior );
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
closeTab( widget, focusTabBehavior );
|
||||
return true;
|
||||
return tryCodeEditorClose( widget->asType<UICodeEditor>(), focusTabBehavior,
|
||||
onMsgBoxCloseCb );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UICodeEditorSplitter::closeAllTabs( std::vector<UITab*> tabs,
|
||||
@@ -1073,6 +1086,8 @@ void UICodeEditorSplitter::closeAllTabs( std::vector<UITab*> tabs,
|
||||
} ) ) {
|
||||
return;
|
||||
} else {
|
||||
closeTab( tab->getOwnedWidget()->asType<UIWidget>(),
|
||||
UITabWidget::FocusTabBehavior::Default );
|
||||
tabs.pop_back();
|
||||
}
|
||||
}
|
||||
@@ -1086,8 +1101,10 @@ bool UICodeEditorSplitter::tryCloseAllTabs( UIWidget* widget,
|
||||
|
||||
size_t tabCount = tabW->getTabCount();
|
||||
std::vector<UITab*> tabs;
|
||||
for ( size_t i = 0; i < tabCount; i++ )
|
||||
tabs.push_back( tabW->getTab( i ) );
|
||||
for ( size_t i = 0; i < tabCount; i++ ) {
|
||||
if ( tabW->getTab( i )->getOwnedWidget() )
|
||||
tabs.push_back( tabW->getTab( i ) );
|
||||
}
|
||||
|
||||
closeAllTabs( tabs, focusTabBehavior );
|
||||
|
||||
@@ -1436,10 +1453,10 @@ bool UICodeEditorSplitter::checkEditorExists( UICodeEditor* checkEditor ) const
|
||||
return found || checkEditor == nullptr || mAboutToAddEditor == checkEditor || mFirstCodeEditor;
|
||||
}
|
||||
|
||||
bool UICodeEditorSplitter::isEditorInAnyWidget( UICodeEditor* checkEditor ) const {
|
||||
bool UICodeEditorSplitter::isWidgetInAnyWidget( UIWidget* checkWidget ) const {
|
||||
bool found = false;
|
||||
forEachEditorStoppable( [&found, checkEditor]( UICodeEditor* editor ) {
|
||||
if ( editor == checkEditor ) {
|
||||
forEachWidgetStoppable( [&found, checkWidget]( UIWidget* widget ) {
|
||||
if ( widget == checkWidget ) {
|
||||
found = true;
|
||||
return true;
|
||||
}
|
||||
@@ -1810,9 +1827,8 @@ std::shared_ptr<TextDocument> UICodeEditorSplitter::getTextDocumentRef( TextDocu
|
||||
return ret;
|
||||
}
|
||||
|
||||
void UICodeEditorSplitter::setTabTryCloseCallback( UITabWidget::TabTryCloseCallback cb ) {
|
||||
void UICodeEditorSplitter::setTabTryCloseCallback( TabTryCloseCallback cb ) {
|
||||
mTabTryCloseCb = cb;
|
||||
forEachTabWidget( [&cb]( UITabWidget* tw ) { tw->setTabTryCloseCallback( cb ); } );
|
||||
}
|
||||
|
||||
}}} // namespace EE::UI::Tools
|
||||
|
||||
@@ -97,11 +97,11 @@ bool App::onCloseRequestCallback( EE::Window::Window* ) {
|
||||
} else if ( mConfig.term.warnBeforeClosingTab && isAnyTerminalDirty() ) {
|
||||
if ( mCloseMsgBox )
|
||||
return false;
|
||||
mCloseMsgBox = UIMessageBox::New(
|
||||
UIMessageBox::OK_CANCEL, i18n( "confirm_ecode_exit_terminal_close_warn",
|
||||
"Do you really want to close the code editor?\nAt "
|
||||
"least one terminal is still running a process." )
|
||||
.unescape() );
|
||||
mCloseMsgBox = UIMessageBox::New( UIMessageBox::OK_CANCEL,
|
||||
i18n( "confirm_ecode_exit_terminal_close_warn",
|
||||
"Do you really want to close the code editor?\nAt "
|
||||
"least one terminal is still running a process." )
|
||||
.unescape() );
|
||||
|
||||
mCloseMsgBox->on( Event::OnConfirm, [this]( const Event* ) {
|
||||
saveProject();
|
||||
@@ -1548,6 +1548,13 @@ void App::onTabCreated( UITab* tab, UIWidget* ) {
|
||||
->setEnabled( enabled );
|
||||
}
|
||||
|
||||
if ( tab->getOwnedWidget()->isType( UI_TYPE_CODEEDITOR ) ) {
|
||||
menu->addSeparator();
|
||||
|
||||
menuAdd( "clone_document_buffer", "Clone Document Buffer", "copy",
|
||||
"clone-document-buffer" );
|
||||
}
|
||||
|
||||
menu->addEventListener( Event::OnItemClicked, [tab, this]( const Event* event ) {
|
||||
if ( !event->getNode()->isType( UI_TYPE_MENUITEM ) )
|
||||
return;
|
||||
@@ -2579,6 +2586,21 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) {
|
||||
tabWidget->moveTab( tab, tabWidget->getTabCount() );
|
||||
}
|
||||
} );
|
||||
doc.setCommand( "clone-document-buffer", [this] {
|
||||
if ( mSplitter->curEditorExistsAndFocused() && mSplitter->getCurEditor() ) {
|
||||
UICodeEditor* editor = mSplitter->getCurEditor();
|
||||
auto d =
|
||||
mSplitter->createCodeEditorInTabWidget( mSplitter->tabWidgetFromWidget( editor ) );
|
||||
if ( d.first == nullptr && d.second == nullptr && !mSplitter->getTabWidgets().empty() )
|
||||
d = mSplitter->createCodeEditorInTabWidget( mSplitter->getTabWidgets()[0] );
|
||||
if ( d.first != nullptr || d.second != nullptr ) {
|
||||
d.first->getTabWidget()->setTabSelected( d.first );
|
||||
d.second->getDocument().textInput( editor->getDocument().getText() );
|
||||
d.second->getDocument().setSyntaxDefinition(
|
||||
editor->getDocument().getSyntaxDefinition() );
|
||||
}
|
||||
}
|
||||
} );
|
||||
registerUnlockedCommands( doc );
|
||||
|
||||
editor->on( Event::OnDocumentSave, [this]( const Event* event ) {
|
||||
@@ -4152,31 +4174,40 @@ void App::init( InitParameters& params ) {
|
||||
tabWidget->getTabBar()->onDoubleClick(
|
||||
[this]( const MouseEvent* ) { mSplitter->createEditorInNewTab(); } );
|
||||
} );
|
||||
mSplitter->setTabTryCloseCallback(
|
||||
[this]( UITab* tab, UITabWidget::FocusTabBehavior focusTabBehavior ) -> bool {
|
||||
if ( tab->getOwnedWidget()->isType( UI_TYPE_CODEEDITOR ) ) {
|
||||
mSplitter->tryTabClose( tab->getOwnedWidget()->asType<UICodeEditor>(),
|
||||
focusTabBehavior );
|
||||
return false;
|
||||
} else if ( mConfig.term.warnBeforeClosingTab &&
|
||||
tab->getOwnedWidget()->isType( UI_TYPE_TERMINAL ) ) {
|
||||
UITerminal* term = tab->getOwnedWidget()->asType<UITerminal>();
|
||||
ProcessID pid = term->getTerm()->getTerminal()->getProcess()->pid();
|
||||
if ( Sys::processHasChildren( pid ) ) {
|
||||
UIMessageBox* msgBox =
|
||||
UIMessageBox::New( UIMessageBox::OK_CANCEL,
|
||||
i18n( "terminal_close_warn",
|
||||
"Are you sure you want to close this "
|
||||
"terminal?\nIt's still running a process." ) );
|
||||
msgBox->on( Event::OnConfirm, [tab]( auto ) { tab->removeTab(); } );
|
||||
msgBox->setTitle( "ecode" );
|
||||
msgBox->center();
|
||||
msgBox->showWhenReady();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
mSplitter->setTabTryCloseCallback( [this]( UIWidget* widget,
|
||||
UITabWidget::FocusTabBehavior focusTabBehavior,
|
||||
std::function<void()> onMsgBoxCloseCb ) -> bool {
|
||||
if ( widget == nullptr || widget->getData() == 0 )
|
||||
return true;
|
||||
} );
|
||||
if ( widget->isType( UI_TYPE_CODEEDITOR ) ) {
|
||||
return mSplitter->tryCodeEditorClose( widget->asType<UICodeEditor>(),
|
||||
focusTabBehavior, onMsgBoxCloseCb );
|
||||
} else if ( mConfig.term.warnBeforeClosingTab && widget->isType( UI_TYPE_TERMINAL ) ) {
|
||||
UITerminal* term = widget->asType<UITerminal>();
|
||||
ProcessID pid = term->getTerm()->getTerminal()->getProcess()->pid();
|
||||
if ( Sys::processHasChildren( pid ) ) {
|
||||
UIMessageBox* msgBox = UIMessageBox::New(
|
||||
UIMessageBox::OK_CANCEL,
|
||||
i18n( "terminal_close_warn", "Are you sure you want to close this "
|
||||
"terminal?\nIt's still running a process." ) );
|
||||
msgBox->on( Event::OnConfirm, [widget]( auto ) {
|
||||
reinterpret_cast<UITab*>( widget->getData() )->removeTab();
|
||||
} );
|
||||
|
||||
msgBox->on( Event::OnClose, [this, onMsgBoxCloseCb]( const Event* ) {
|
||||
if ( mSplitter->getCurEditor() )
|
||||
mSplitter->getCurEditor()->setFocus();
|
||||
if ( onMsgBoxCloseCb )
|
||||
onMsgBoxCloseCb();
|
||||
} );
|
||||
msgBox->setTitle( "ecode" );
|
||||
msgBox->center();
|
||||
msgBox->showWhenReady();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} );
|
||||
mPluginManager->setSplitter( mSplitter );
|
||||
|
||||
Log::info( "Base UI took: %.2f ms", globalClock.getElapsedTime().asMilliseconds() );
|
||||
|
||||
@@ -9,8 +9,14 @@ Clock lastRender;
|
||||
Clock secondsCounter;
|
||||
Time frameTime{ Time::Zero };
|
||||
bool benchmarkMode{ false };
|
||||
bool warnBeforeClose{ false };
|
||||
std::string windowStringData;
|
||||
std::map<std::string, TerminalColorScheme> terminalColorSchemes;
|
||||
bool displayingWarnBeforeClose{ false };
|
||||
bool yesPicked{ true };
|
||||
bool needsRedraw{ false };
|
||||
Rectf yesBtn;
|
||||
Rectf noBtn;
|
||||
|
||||
void loadColorSchemes( const std::string& resPath ) {
|
||||
auto configPath = Sys::getConfigPath( "eterm" );
|
||||
@@ -40,11 +46,22 @@ void inputCallback( InputEvent* event ) {
|
||||
break;
|
||||
}
|
||||
case InputEvent::MouseButtonDown: {
|
||||
terminal->onMouseDown( win->getInput()->getMousePos(),
|
||||
win->getInput()->getPressTrigger() );
|
||||
if ( displayingWarnBeforeClose ) {
|
||||
if ( ( win->getInput()->getPressTrigger() & EE_BUTTON_LMASK ) ) {
|
||||
if ( yesBtn.contains( win->getInput()->getMousePos().asFloat() ) ) {
|
||||
win->close();
|
||||
} else if ( noBtn.contains( win->getInput()->getMousePos().asFloat() ) ) {
|
||||
displayingWarnBeforeClose = false;
|
||||
needsRedraw = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
terminal->onMouseDown( win->getInput()->getMousePos(),
|
||||
win->getInput()->getPressTrigger() );
|
||||
#if EE_PLATFORM == EE_PLATFORM_ANDROID
|
||||
win->startTextInput();
|
||||
win->startTextInput();
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
}
|
||||
case InputEvent::MouseButtonUp: {
|
||||
@@ -72,8 +89,34 @@ void inputCallback( InputEvent* event ) {
|
||||
break;
|
||||
}
|
||||
case InputEvent::KeyDown: {
|
||||
terminal->onKeyDown( event->key.keysym.sym, event->key.keysym.unicode,
|
||||
event->key.keysym.mod, event->key.keysym.scancode );
|
||||
if ( displayingWarnBeforeClose ) {
|
||||
if ( event->key.keysym.sym == EE::Window::KEY_TAB ||
|
||||
event->key.keysym.sym == EE::Window::KEY_LEFT ||
|
||||
event->key.keysym.sym == EE::Window::KEY_RIGHT ) {
|
||||
yesPicked = !yesPicked;
|
||||
needsRedraw = true;
|
||||
} else if ( event->key.keysym.sym == EE::Window::KEY_Y ) {
|
||||
win->close();
|
||||
} else if ( event->key.keysym.sym == EE::Window::KEY_N ) {
|
||||
displayingWarnBeforeClose = false;
|
||||
needsRedraw = true;
|
||||
} else if ( event->key.keysym.sym == EE::Window::KEY_RETURN ||
|
||||
event->key.keysym.sym == EE::Window::KEY_KP_ENTER ) {
|
||||
if ( yesPicked )
|
||||
win->close();
|
||||
else {
|
||||
displayingWarnBeforeClose = false;
|
||||
needsRedraw = true;
|
||||
}
|
||||
} else if ( event->key.keysym.sym == EE::Window::KEY_ESCAPE ) {
|
||||
displayingWarnBeforeClose = false;
|
||||
needsRedraw = true;
|
||||
}
|
||||
} else {
|
||||
terminal->onKeyDown( event->key.keysym.sym, event->key.keysym.unicode,
|
||||
event->key.keysym.mod, event->key.keysym.scancode );
|
||||
}
|
||||
|
||||
#if EE_PLATFORM == EE_PLATFORM_ANDROID
|
||||
if ( event->key.keysym.sym == KEY_RETURN ||
|
||||
event->key.keysym.scancode == SCANCODE_RETURN ) {
|
||||
@@ -104,6 +147,16 @@ void inputCallback( InputEvent* event ) {
|
||||
}
|
||||
}
|
||||
|
||||
bool onCloseRequestCallback( EE::Window::Window* ) {
|
||||
if ( warnBeforeClose &&
|
||||
Sys::processHasChildren( terminal->getTerminal()->getProcess()->pid() ) ) {
|
||||
displayingWarnBeforeClose = true;
|
||||
needsRedraw = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
EE_MAIN_FUNC int main( int argc, char* argv[] ) {
|
||||
#ifdef EE_DEBUG
|
||||
Log::instance()->setLogToStdOut( true );
|
||||
@@ -146,6 +199,10 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) {
|
||||
args::Flag benchmarkModeFlag(
|
||||
parser, "benchmark-mode",
|
||||
"Render as much as possible to measure the rendering performance.", { "benchmark-mode" } );
|
||||
args::Flag warnBeforeCloseFlag(
|
||||
parser, "warn-before-closing",
|
||||
"Prompts for confirmation if a program is still running when closing the terminal.",
|
||||
{ "warn-before-closing" } );
|
||||
|
||||
try {
|
||||
parser.ParseCLI( argc, argv );
|
||||
@@ -208,6 +265,7 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) {
|
||||
win->setClearColor( RGB( 0, 0, 0 ) );
|
||||
|
||||
benchmarkMode = benchmarkModeFlag.Get();
|
||||
warnBeforeClose = warnBeforeCloseFlag.Get();
|
||||
|
||||
FontTrueType* fontMono = nullptr;
|
||||
if ( fontPath && FileSystem::fileExists( fontPath.Get() ) ) {
|
||||
@@ -289,19 +347,81 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) {
|
||||
|
||||
win->getInput()->pushCallback( &inputCallback );
|
||||
|
||||
win->runMainLoop( [] {
|
||||
win->setCloseRequestCallback(
|
||||
[]( EE::Window::Window* win ) -> bool { return onCloseRequestCallback( win ); } );
|
||||
|
||||
win->runMainLoop( [fontMono] {
|
||||
bool termNeedsUpdate = false;
|
||||
win->getInput()->update();
|
||||
|
||||
if ( terminal )
|
||||
termNeedsUpdate = !terminal->update();
|
||||
|
||||
if ( terminal && ( benchmarkMode || terminal->isDirty() ) &&
|
||||
( !termNeedsUpdate || lastRender.getElapsedTime() >= frameTime ) ) {
|
||||
if ( ( terminal && ( benchmarkMode || terminal->isDirty() ) &&
|
||||
( !termNeedsUpdate || lastRender.getElapsedTime() >= frameTime ) ) ||
|
||||
needsRedraw ) {
|
||||
lastRender.restart();
|
||||
win->clear();
|
||||
terminal->draw();
|
||||
|
||||
if ( displayingWarnBeforeClose ) {
|
||||
Sizef winSize{ win->getSize().asFloat() };
|
||||
Sizef buttonSize{ PixelDensity::dpToPx( 100 ), PixelDensity::dpToPx( 32 ) };
|
||||
Primitives p;
|
||||
p.setColor( Color( terminal->getColorScheme().getBackground(), 200 ) );
|
||||
p.drawRectangle( { { 0, 0 }, winSize } );
|
||||
|
||||
Text text( "Are you sure you want to close this window? It is still running a "
|
||||
"process.",
|
||||
fontMono );
|
||||
|
||||
text.draw( ( winSize.getWidth() - text.getLocalBounds().getWidth() ) * 0.5f,
|
||||
winSize.getHeight() * 0.5f - text.getTextHeight() -
|
||||
PixelDensity::dpToPx( 32 ) );
|
||||
|
||||
yesBtn = Rectf{ { ( winSize.getWidth() * 0.5f - buttonSize.getWidth() * 0.5f -
|
||||
PixelDensity::dpToPx( 75 ) ),
|
||||
win->getHeight() * 0.5f },
|
||||
buttonSize }
|
||||
.floor();
|
||||
|
||||
p.setColor( terminal->getColorScheme().getBackground() );
|
||||
p.drawRoundedRectangle( yesBtn );
|
||||
|
||||
noBtn = { Vector2f( yesBtn.getPosition().x + yesBtn.getSize().getWidth(),
|
||||
yesBtn.getPosition().y ) +
|
||||
Vector2f( PixelDensity::dpToPx( 50 ), 0 ),
|
||||
yesBtn.getSize() };
|
||||
|
||||
p.drawRoundedRectangle( noBtn );
|
||||
|
||||
Text yes( "Yes", fontMono );
|
||||
yes.draw(
|
||||
eefloor( yesBtn.getPosition().x +
|
||||
( yesBtn.getSize().getWidth() - yes.getLocalBounds().getWidth() ) *
|
||||
0.5f ),
|
||||
eefloor( yesBtn.getPosition().y +
|
||||
( yesBtn.getSize().getHeight() - yes.getTextHeight() ) * 0.5f ) );
|
||||
|
||||
Text no( "No", fontMono );
|
||||
no.draw(
|
||||
eeceil( noBtn.getPosition().x +
|
||||
( noBtn.getSize().getWidth() - no.getLocalBounds().getWidth() ) *
|
||||
0.5f ),
|
||||
eefloor( noBtn.getPosition().y +
|
||||
( noBtn.getSize().getHeight() - no.getTextHeight() ) * 0.5f ) );
|
||||
|
||||
p.setFillMode( PrimitiveFillMode::DRAW_LINE );
|
||||
p.setColor( terminal->getColorScheme().getForeground() );
|
||||
p.drawRoundedRectangle( yesBtn );
|
||||
p.drawRoundedRectangle( noBtn );
|
||||
p.setColor( terminal->getColorScheme().getPaletteIndex( 5 ) );
|
||||
p.drawRoundedRectangle( yesPicked ? yesBtn : noBtn );
|
||||
}
|
||||
|
||||
win->display();
|
||||
|
||||
needsRedraw = false;
|
||||
} else if ( !benchmarkMode && !termNeedsUpdate ) {
|
||||
win->getInput()->waitEvent( Milliseconds( win->hasFocus() ? 16 : 100 ) );
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user