diff --git a/.ecode/project_build.json b/.ecode/project_build.json index 507d60d73..141c891de 100644 --- a/.ecode/project_build.json +++ b/.ecode/project_build.json @@ -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" diff --git a/include/eepp/graphics/font.hpp b/include/eepp/graphics/font.hpp index 13b3a0fdb..65c316a54 100644 --- a/include/eepp/graphics/font.hpp +++ b/include/eepp/graphics/font.hpp @@ -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. */ diff --git a/include/eepp/graphics/fontbmfont.hpp b/include/eepp/graphics/fontbmfont.hpp index 37e8d8f4f..36489185c 100644 --- a/include/eepp/graphics/fontbmfont.hpp +++ b/include/eepp/graphics/fontbmfont.hpp @@ -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, diff --git a/include/eepp/graphics/fontsprite.hpp b/include/eepp/graphics/fontsprite.hpp index 1f18e97c7..39cb8b06d 100644 --- a/include/eepp/graphics/fontsprite.hpp +++ b/include/eepp/graphics/fontsprite.hpp @@ -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, diff --git a/include/eepp/graphics/fonttruetype.hpp b/include/eepp/graphics/fonttruetype.hpp index 8862c7d08..2a669fc7b 100644 --- a/include/eepp/graphics/fonttruetype.hpp +++ b/include/eepp/graphics/fonttruetype.hpp @@ -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, diff --git a/include/eepp/ui/tools/uicodeeditorsplitter.hpp b/include/eepp/ui/tools/uicodeeditorsplitter.hpp index 405239188..bc20b0115 100644 --- a/include/eepp/ui/tools/uicodeeditorsplitter.hpp +++ b/include/eepp/ui/tools/uicodeeditorsplitter.hpp @@ -59,6 +59,10 @@ class EE_API UICodeEditorSplitter { virtual bool tryTabClose( UIWidget* widget, UITabWidget::FocusTabBehavior focusTabBehavior, std::function onMsgBoxCloseCb = {} ); + virtual bool tryCodeEditorClose( UICodeEditor* editor, + UITabWidget::FocusTabBehavior focusTabBehavior, + std::function 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 getTextDocumentRef( TextDocument* doc ); - void setTabTryCloseCallback( UITabWidget::TabTryCloseCallback cb ); + // @return True if can be removed + typedef std::function 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::max() }; std::function mOnTabWidgetCreateCb; Float mVisualSplitEdgePercent{ 0.1 }; - UITabWidget::TabTryCloseCallback mTabTryCloseCb; + TabTryCloseCallback mTabTryCloseCb; UICodeEditorSplitter( UICodeEditorSplitter::Client* client, UISceneNode* sceneNode, std::shared_ptr threadPool, diff --git a/src/eepp/graphics/fontbmfont.cpp b/src/eepp/graphics/fontbmfont.cpp index 98ecc75b8..be7acab63 100644 --- a/src/eepp/graphics/fontbmfont.cpp +++ b/src/eepp/graphics/fontbmfont.cpp @@ -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; diff --git a/src/eepp/graphics/fontsprite.cpp b/src/eepp/graphics/fontsprite.cpp index 5ac4337f1..987005b25 100644 --- a/src/eepp/graphics/fontsprite.cpp +++ b/src/eepp/graphics/fontsprite.cpp @@ -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; diff --git a/src/eepp/graphics/fonttruetype.cpp b/src/eepp/graphics/fonttruetype.cpp index 6ea639a39..d07745ae1 100644 --- a/src/eepp/graphics/fonttruetype.cpp +++ b/src/eepp/graphics/fonttruetype.cpp @@ -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( 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; diff --git a/src/eepp/graphics/text.cpp b/src/eepp/graphics/text.cpp index 9b5c16e48..9d53e4580 100644 --- a/src/eepp/graphics/text.cpp +++ b/src/eepp/graphics/text.cpp @@ -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 diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index 805351e47..159c2f1e0 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -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 TextDocument::autoCloseBrackets( const String& text ) { - static std::vector> + static const std::vector> sAutoCloseBracketsPairs = { { '(', ')' }, { '{', '}' }, { '[', ']' }, { '"', '"' }, { '\'', '\'' }, { '`', '`' } }; @@ -1912,13 +1916,11 @@ std::vector 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 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 ); diff --git a/src/eepp/ui/tools/uicodeeditorsplitter.cpp b/src/eepp/ui/tools/uicodeeditorsplitter.cpp index 01bfc189c..afe618bf3 100644 --- a/src/eepp/ui/tools/uicodeeditorsplitter.cpp +++ b/src/eepp/ui/tools/uicodeeditorsplitter.cpp @@ -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(); + eeASSERT( nullptr != tabWidget && nullptr != tabWidget->getTabSelected() && + nullptr != tabWidget->getTabSelected()->getOwnedWidget() ); + if ( !isWidgetInAnyWidget( + tabWidget->getTabSelected()->getOwnedWidget()->asType() ) ) + return; + if ( tabWidget->getTabSelected()->getOwnedWidget()->isType( UI_TYPE_CODEEDITOR ) ) { setCurrentEditor( tabWidget->getTabSelected()->getOwnedWidget()->asType() ); @@ -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(), focusTabBehavior ); - return false; + if ( tab->getOwnedWidget() && + tryTabClose( tab->getOwnedWidget()->asType(), focusTabBehavior ) ) { + closeTab( tab->getOwnedWidget()->asType(), + UITabWidget::FocusTabBehavior::Default ); } - return true; + return false; } ); tabWidget->on( Event::OnTabClosed, [this]( const Event* event ) { onTabClosed( static_cast( event ) ); @@ -1016,50 +1020,59 @@ void UICodeEditorSplitter::zoomReset() { forEachEditor( []( UICodeEditor* editor ) { editor->fontSizeReset(); } ); } +bool UICodeEditorSplitter::tryCodeEditorClose( UICodeEditor* editor, + UITabWidget::FocusTabBehavior focusTabBehavior, + std::function 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 onMsgBoxCloseCb ) { if ( !widget ) - return false; + return true; + + if ( mTabTryCloseCb ) + return mTabTryCloseCb( widget, focusTabBehavior, onMsgBoxCloseCb ); if ( widget->isType( UI_TYPE_CODEEDITOR ) ) { - UICodeEditor* editor = widget->asType(); - 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(), focusTabBehavior, + onMsgBoxCloseCb ); } + + return true; } void UICodeEditorSplitter::closeAllTabs( std::vector tabs, @@ -1073,6 +1086,8 @@ void UICodeEditorSplitter::closeAllTabs( std::vector tabs, } ) ) { return; } else { + closeTab( tab->getOwnedWidget()->asType(), + UITabWidget::FocusTabBehavior::Default ); tabs.pop_back(); } } @@ -1086,8 +1101,10 @@ bool UICodeEditorSplitter::tryCloseAllTabs( UIWidget* widget, size_t tabCount = tabW->getTabCount(); std::vector 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 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 diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index 92a718ad0..c97028eeb 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -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(), - focusTabBehavior ); - return false; - } else if ( mConfig.term.warnBeforeClosingTab && - tab->getOwnedWidget()->isType( UI_TYPE_TERMINAL ) ) { - UITerminal* term = tab->getOwnedWidget()->asType(); - 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 onMsgBoxCloseCb ) -> bool { + if ( widget == nullptr || widget->getData() == 0 ) return true; - } ); + if ( widget->isType( UI_TYPE_CODEEDITOR ) ) { + return mSplitter->tryCodeEditorClose( widget->asType(), + focusTabBehavior, onMsgBoxCloseCb ); + } else if ( mConfig.term.warnBeforeClosingTab && widget->isType( UI_TYPE_TERMINAL ) ) { + UITerminal* term = widget->asType(); + 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( 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() ); diff --git a/src/tools/eterm/eterm.cpp b/src/tools/eterm/eterm.cpp index 337ed25fb..aae2977a0 100644 --- a/src/tools/eterm/eterm.cpp +++ b/src/tools/eterm/eterm.cpp @@ -9,8 +9,14 @@ Clock lastRender; Clock secondsCounter; Time frameTime{ Time::Zero }; bool benchmarkMode{ false }; +bool warnBeforeClose{ false }; std::string windowStringData; std::map 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 ) ); }