diff --git a/bin/assets/i18n/de.xml b/bin/assets/i18n/de.xml
index 04ba49cad..02ddbf361 100644
--- a/bin/assets/i18n/de.xml
+++ b/bin/assets/i18n/de.xml
@@ -307,6 +307,7 @@ Text selbst.
Unicode-Zeichen \, \t, \n, \r und ( ersetzen
Escape-Sequenzen
Exklusivmodus
+ Exklusivmodus aktivieren
Auszuführende Programmdatei
Verzeichnis in Konsole öffnen
In Konsole ausführen
diff --git a/bin/assets/i18n/en.xml b/bin/assets/i18n/en.xml
index 36be4506e..2ee79ecb0 100644
--- a/bin/assets/i18n/en.xml
+++ b/bin/assets/i18n/en.xml
@@ -291,6 +291,7 @@ is hover a word that represents a color.
Replace \, \t, \n, \r and (Unicode characters
Escape Sequences
Exclusive Mode
+ Enable Exclusive Mode
Executable to Run
Open directory in terminal
Execute in terminal
diff --git a/bin/assets/i18n/fr.xml b/bin/assets/i18n/fr.xml
index b1abe9313..de1ce5a2d 100644
--- a/bin/assets/i18n/fr.xml
+++ b/bin/assets/i18n/fr.xml
@@ -290,6 +290,7 @@ texte lui-même.
Remplacer \, \t, \n, \r et (caractères Unicode)
Séquences d'échappement
Mode exclusif
+ Activer le mode exclusif
Exécutable à exécuter
Ouvrir le répertoire dans le terminal
Exécuter dans le terminal
diff --git a/bin/assets/i18n/zh.xml b/bin/assets/i18n/zh.xml
index d347ecc64..575b64cff 100644
--- a/bin/assets/i18n/zh.xml
+++ b/bin/assets/i18n/zh.xml
@@ -223,6 +223,7 @@
,
和 (Unicode字符
Exclusive Mode
+ 启用独占模式
在终端打开文件夹
在终端执行
Expand All
diff --git a/src/modules/eterm/src/eterm/terminal/terminaldisplay.cpp b/src/modules/eterm/src/eterm/terminal/terminaldisplay.cpp
index 26f93222f..40bc5fc7e 100644
--- a/src/modules/eterm/src/eterm/terminal/terminaldisplay.cpp
+++ b/src/modules/eterm/src/eterm/terminal/terminaldisplay.cpp
@@ -1876,7 +1876,10 @@ void TerminalDisplay::onSizeChange() {
mSession->resize( gridSize.getWidth(), gridSize.getHeight(),
gridSize.getWidth() * cellSize.getWidth(),
gridSize.getHeight() * cellSize.getHeight() );
- mDirtyLines.resize( gridSize.getHeight(), 1 );
+ // Rendering must continue using the currently published snapshot until the worker
+ // publishes the resized grid. consumeSnapshot() updates mRows and mDirtyLines together;
+ // shrinking the dirty vector here leaves drawGrid() indexing it with the old mRows.
+ mDirtyLines.resize( std::max( mRows, gridSize.getHeight() ), true );
}
if ( mFrameBuffer && ( mFrameBuffer->getWidth() < mSize.getWidth() ||
diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp
index d9fd15896..8a79ddb2e 100644
--- a/src/tools/ecode/ecode.cpp
+++ b/src/tools/ecode/ecode.cpp
@@ -556,6 +556,8 @@ void App::maximizeTabWidget() {
UIWindow::StyleConfig winCfg;
winCfg.WinFlags = UI_WIN_SHADOW | UI_WIN_MODAL | UI_WIN_EPHEMERAL | UI_WIN_NO_DECORATION;
UIWindow* win = UIWindow::NewOpt( UIWindow::SIMPLE_LAYOUT, winCfg );
+ mMaximizedTabWidgetWindow = win;
+ mMaximizedTabWidget = curTabWidget;
win->setPixelsSize( getUISceneNode()->getPixelsSize() - PixelDensity::dpToPx( 64 ) );
win->setId( "detached_tab_widget_win" );
win->addClass( "tab_widget_cont" );
@@ -587,6 +589,7 @@ void App::maximizeTabWidget() {
bool wasFirstSplit = tabWidgetParent->isType( UI_TYPE_SPLITTER ) &&
tabWidgetParent->asType()->getFirstWidget() == curTabWidget;
auto nodeLink = UINodeLink::NewLink( curTabWidget );
+ mMaximizedTabWidgetLink = nodeLink;
if ( wasFirstSplit )
nodeLink->setClass( "was_first_split" );
curTabWidget->setParent( win );
@@ -626,27 +629,24 @@ void App::maximizeTabWidget() {
void App::restoreMaximizedTabWidget() {
if ( !App::instance() || !SceneManager::isActive() )
return;
- auto sceneNode = appInstance->getUISceneNode();
- if ( !sceneNode )
- return;
- auto nodeLink = sceneNode->getRoot()->find( "nodelink_tab_widget" );
- if ( !nodeLink )
- return;
- auto splitterParent = nodeLink->getParent();
- nodeLink->setParent( sceneNode );
- auto curTabWidget = sceneNode->getRoot()->find( "detached_tab_widget" );
- if ( !curTabWidget )
+ if ( !mMaximizedTabWidgetLink || !mMaximizedTabWidget )
return;
+ auto* nodeLink = mMaximizedTabWidgetLink;
+ auto* curTabWidget = mMaximizedTabWidget;
+ auto* win = mMaximizedTabWidgetWindow;
+ auto* splitterParent = nodeLink->getParent();
+ nodeLink->setParent( mUISceneNode );
curTabWidget->setParent( splitterParent );
curTabWidget->setAnchors( 0 );
curTabWidget->setId( "" );
- if ( nodeLink->asType()->hasClass( "was_first_split" ) &&
- splitterParent->isType( UI_TYPE_SPLITTER ) ) {
+ if ( nodeLink->hasClass( "was_first_split" ) && splitterParent->isType( UI_TYPE_SPLITTER ) ) {
splitterParent->asType()->swap();
}
nodeLink->close();
- auto win = mUISceneNode->find( "detached_tab_widget_win" );
- if ( win )
+ mMaximizedTabWidgetLink = nullptr;
+ mMaximizedTabWidget = nullptr;
+ mMaximizedTabWidgetWindow = nullptr;
+ if ( win && !win->isClosing() )
win->close();
}
@@ -1734,6 +1734,16 @@ void App::onTabCreated( UITab* tab, UIWidget* ) {
if ( tab->getOwnedWidget()->isType( UI_TYPE_CODEEDITOR ) ||
tab->getOwnedWidget()->isType( UI_TYPE_TERMINAL ) ) {
+ if ( tab->getOwnedWidget()->isType( UI_TYPE_TERMINAL ) ) {
+ menu->addSeparator();
+ auto* terminal = tab->getOwnedWidget()->asType();
+ menu->addCheckBox( i18n( "enable_exclusive_mode", "Enable Exclusive Mode" ),
+ terminal->getExclusiveMode(),
+ getKeybind( UITerminal::getExclusiveModeToggleCommandName() ) )
+ ->setId( UITerminal::getExclusiveModeToggleCommandName() );
+ menuAdd( "rename_session", "Rename Session", "", "terminal-rename" );
+ }
+
menu->addSeparator();
bool enabled = tab->getTabWidget()->getTabCount() > 1;
@@ -5073,6 +5083,11 @@ void App::init( InitParameters& params ) {
std::function onMsgBoxCloseCb ) -> bool {
if ( widget == nullptr || widget->getData() == 0 )
return true;
+ if ( auto* detachedTabWidget =
+ mUISceneNode->getRoot()->find( "detached_tab_widget" );
+ detachedTabWidget && detachedTabWidget->inParentTreeOf( widget ) ) {
+ restoreMaximizedTabWidget();
+ }
if ( widget->isType( UI_TYPE_CODEEDITOR ) ) {
return mSplitter->tryCodeEditorClose( widget->asType(),
focusTabBehavior, onMsgBoxCloseCb );
diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp
index a1e563675..9bc45dd2b 100644
--- a/src/tools/ecode/ecode.hpp
+++ b/src/tools/ecode/ecode.hpp
@@ -680,6 +680,9 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
UIMainLayout* mMainLayout{ nullptr };
UILayout* mBaseLayout{ nullptr };
UILayout* mImageLayout{ nullptr };
+ UIWindow* mMaximizedTabWidgetWindow{ nullptr };
+ UITabWidget* mMaximizedTabWidget{ nullptr };
+ UINodeLink* mMaximizedTabWidgetLink{ nullptr };
UITextView* mDocInfo{ nullptr };
std::string mDocInfoLineAbbr;
std::string mDocInfoColAbbr;
diff --git a/src/tools/eterm/eterm.cpp b/src/tools/eterm/eterm.cpp
index 014c4c1dc..3bd8b7e5f 100644
--- a/src/tools/eterm/eterm.cpp
+++ b/src/tools/eterm/eterm.cpp
@@ -3,6 +3,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -40,39 +41,76 @@ struct TerminalLaunchConfig {
bool closeOnExit{ false };
};
-EE::Window::Window* appWindow{ nullptr };
-UISceneNode* scene{ nullptr };
-UILinearLayout* mainLayout{ nullptr };
-UITabWidgetSplitter* tabSplitter{ nullptr };
-FontTrueType* terminalFont{ nullptr };
-UIIcon* terminalIcon{ nullptr };
-UIMessageBox* closeDialog{ nullptr };
-UIWidget* closeDialogWidget{ nullptr };
-TerminalLaunchConfig terminalConfig;
-std::map terminalColorSchemes;
-const TerminalColorScheme* selectedColorScheme{ nullptr };
-Float terminalFontSize{ 12 };
-bool warnBeforeClose{ false };
-bool closeApproved{ false };
-bool benchmarkMode{ false };
-Clock secondsCounter;
-SmallVector pendingExitCloseTabs;
-
-void updateWindowTitle();
-
-class TerminalSplitterClient : public UITabWidgetSplitter::Client {
+class EtermApp {
public:
- void onTabCreated( UITab* tab, UIWidget* ) override {
- if ( terminalIcon )
- tab->setIcon( terminalIcon->createDrawable( PixelDensity::dpToPxI( 12 ) ) );
- }
+ int run( int argc, char* argv[] );
- void onWidgetFocusChange( UIWidget* ) override { updateWindowTitle(); }
+ class TerminalSplitterClient : public UITabWidgetSplitter::Client {
+ public:
+ explicit TerminalSplitterClient( EtermApp& app ) : mApp( app ) {}
+
+ void onTabCreated( UITab* tab, UIWidget* ) override;
+ void onWidgetFocusChange( UIWidget* ) override;
+
+ private:
+ EtermApp& mApp;
+ };
+
+ private:
+ std::string getResourcePath() const;
+ String i18n( const std::string& key, const String& defaultValue ) const;
+ void loadColorSchemes( const std::string& resPath );
+ static UITerminal* terminalFromTab( UITab* tab );
+ void updateWindowTitle();
+ bool hasTerminals() const;
+ static bool hasRunningChildren( UITab* tab );
+ void closeTab( UITab* tab );
+ void queueExitCloseTab( UITab* tab );
+ void queueExitedTabs();
+ void requestCloseTab( UITab* tab );
+ void renameSession( UITerminal* terminal );
+ void maximizeTabWidget( UITabWidget* tabWidget );
+ void restoreMaximizedTabWidget();
+ void configureTab( UITab* tab );
+ UITerminal* createTerminal( UITabWidget* target = nullptr );
+ UITerminal* createTerminalSplit( SplitDirection direction, UITerminal* terminal );
+ void addTabKeyBindings( UITerminal* terminal );
+ bool closeWindow( EE::Window::Window* );
+
+ EE::Window::Window* appWindow{ nullptr };
+ UISceneNode* scene{ nullptr };
+ UILinearLayout* mainLayout{ nullptr };
+ UITabWidgetSplitter* tabSplitter{ nullptr };
+ FontTrueType* terminalFont{ nullptr };
+ UIIcon* terminalIcon{ nullptr };
+ UIMessageBox* closeDialog{ nullptr };
+ UIWidget* closeDialogWidget{ nullptr };
+ UIWindow* maximizedTabWidgetWindow{ nullptr };
+ UITabWidget* maximizedTabWidget{ nullptr };
+ UINodeLink* maximizedTabWidgetLink{ nullptr };
+ TerminalLaunchConfig terminalConfig;
+ std::map terminalColorSchemes;
+ const TerminalColorScheme* selectedColorScheme{ nullptr };
+ Float terminalFontSize{ 12 };
+ bool warnBeforeClose{ false };
+ bool closeApproved{ false };
+ bool benchmarkMode{ false };
+ Clock secondsCounter;
+ SmallVector pendingExitCloseTabs;
+ TerminalSplitterClient splitterClient{ *this };
};
-TerminalSplitterClient splitterClient;
+void EtermApp::TerminalSplitterClient::onTabCreated( UITab* tab, UIWidget* ) {
+ if ( mApp.terminalIcon )
+ tab->setIcon( mApp.terminalIcon->createDrawable( PixelDensity::dpToPxI( 12 ) ) );
+ mApp.configureTab( tab );
+}
-std::string getResourcePath() {
+void EtermApp::TerminalSplitterClient::onWidgetFocusChange( UIWidget* ) {
+ mApp.updateWindowTitle();
+}
+
+std::string EtermApp::getResourcePath() const {
std::string resPath = Sys::getProcessPath();
#if EE_PLATFORM == EE_PLATFORM_MACOS
if ( String::contains( resPath, "ecode.app" ) ) {
@@ -92,7 +130,11 @@ std::string getResourcePath() {
return resPath;
}
-void loadColorSchemes( const std::string& resPath ) {
+String EtermApp::i18n( const std::string& key, const String& defaultValue ) const {
+ return scene ? scene->i18n( key, defaultValue ) : defaultValue;
+}
+
+void EtermApp::loadColorSchemes( const std::string& resPath ) {
auto colorSchemes =
TerminalColorScheme::loadFromFile( resPath + "colorschemes/terminalcolorschemes.conf" );
const std::string configColorSchemesPath =
@@ -111,16 +153,16 @@ void loadColorSchemes( const std::string& resPath ) {
}
}
-UITerminal* terminalFromTab( UITab* tab ) {
+UITerminal* EtermApp::terminalFromTab( UITab* tab ) {
return tab && tab->getOwnedWidget() && tab->getOwnedWidget()->isType( UI_TYPE_TERMINAL )
? tab->getOwnedWidget()->asType()
: nullptr;
}
-void updateWindowTitle() {
+void EtermApp::updateWindowTitle() {
if ( !appWindow )
return;
- std::string title{ "eterm" };
+ String title{ "eterm" };
if ( tabSplitter ) {
if ( auto* terminal = tabSplitter->getCurWidget() &&
tabSplitter->getCurWidget()->isType( UI_TYPE_TERMINAL )
@@ -134,12 +176,12 @@ void updateWindowTitle() {
if ( benchmarkMode ) {
title += " - ";
title += String::toString( appWindow->getFPS() );
- title += " FPS";
+ title += " " + i18n( "fps", "FPS" );
}
appWindow->setTitle( title );
}
-bool hasTerminals() {
+bool EtermApp::hasTerminals() const {
bool found = false;
if ( tabSplitter )
tabSplitter->forEachWidgetStoppable( [&found]( UIWidget* ) {
@@ -149,30 +191,30 @@ bool hasTerminals() {
return found;
}
-bool hasRunningChildren( UITab* tab ) {
+bool EtermApp::hasRunningChildren( UITab* tab ) {
auto* terminal = terminalFromTab( tab );
return terminal && terminal->getTerm() &&
Sys::processHasChildren( terminal->getTerm()->getProcessId() );
}
-void closeTab( UITab* tab ) {
+void EtermApp::closeTab( UITab* tab ) {
if ( !tabSplitter || !tab || !tab->getOwnedWidget() )
return;
tabSplitter->closeTab( tab->getOwnedWidget()->asType(),
UITabWidget::FocusTabBehavior::Default );
}
-void queueExitCloseTab( UITab* tab ) {
+void EtermApp::queueExitCloseTab( UITab* tab ) {
if ( tab && std::find( pendingExitCloseTabs.begin(), pendingExitCloseTabs.end(), tab ) ==
pendingExitCloseTabs.end() ) {
pendingExitCloseTabs.emplace_back( tab );
}
}
-void queueExitedTabs() {
+void EtermApp::queueExitedTabs() {
if ( !terminalConfig.closeOnExit )
return;
- tabSplitter->forEachTab( []( UITab* tab ) {
+ tabSplitter->forEachTab( [this]( UITab* tab ) {
auto* terminal = terminalFromTab( tab );
if ( !terminal || !terminal->getTerm() )
return;
@@ -183,7 +225,7 @@ void queueExitedTabs() {
} );
}
-void requestCloseTab( UITab* tab ) {
+void EtermApp::requestCloseTab( UITab* tab ) {
if ( !warnBeforeClose || !hasRunningChildren( tab ) ) {
closeTab( tab );
return;
@@ -192,14 +234,15 @@ void requestCloseTab( UITab* tab ) {
return;
closeDialog = UIMessageBox::New(
UIMessageBox::OK_CANCEL,
- "Are you sure you want to close this terminal?\nIt is still running a process." );
+ i18n( "close_terminal_running_process_confirm",
+ "Are you sure you want to close this terminal?\nIt is still running a process." ) );
closeDialogWidget = tab->getOwnedWidget()->asType();
closeDialog->setTitle( "eterm" );
- closeDialog->on( Event::OnConfirm, []( const Event* ) {
+ closeDialog->on( Event::OnConfirm, [this]( const Event* ) {
if ( closeDialogWidget && tabSplitter->ownedWidgetExists( closeDialogWidget ) )
tabSplitter->closeTab( closeDialogWidget, UITabWidget::FocusTabBehavior::Default );
} );
- closeDialog->on( Event::OnClose, []( const Event* ) {
+ closeDialog->on( Event::OnClose, [this]( const Event* ) {
closeDialog = nullptr;
closeDialogWidget = nullptr;
} );
@@ -207,9 +250,194 @@ void requestCloseTab( UITab* tab ) {
closeDialog->showWhenReady();
}
-void addTabKeyBindings( UITerminal* terminal, UITab* tab );
+void EtermApp::renameSession( UITerminal* terminal ) {
+ if ( !terminal )
+ return;
+ auto* msgBox =
+ UIMessageBox::New( UIMessageBox::INPUT, i18n( "new_terminal_name", "New terminal name:" ) );
+ msgBox->setTitle( "eterm" );
+ msgBox->getTextInput()->setHint( i18n( "any_name_ellipsis", "Any name..." ) );
+ msgBox->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } );
+ msgBox->on( Event::OnConfirm, [msgBox, terminal]( const Event* ) {
+ terminal->setTitle( msgBox->getTextInput()->getText().toUtf8() );
+ msgBox->close();
+ terminal->setFocus();
+ } );
+ msgBox->showWhenReady();
+}
-UITerminal* createTerminal( UITabWidget* target = nullptr ) {
+void EtermApp::maximizeTabWidget( UITabWidget* tabWidget ) {
+ if ( !tabWidget || scene->getRoot()->hasChild( "detached_tab_widget_win" ) )
+ return;
+
+ UIWindow::StyleConfig winCfg;
+ winCfg.WinFlags = UI_WIN_SHADOW | UI_WIN_MODAL | UI_WIN_EPHEMERAL | UI_WIN_NO_DECORATION;
+ auto* win = UIWindow::NewOpt( UIWindow::SIMPLE_LAYOUT, winCfg );
+ maximizedTabWidgetWindow = win;
+ maximizedTabWidget = tabWidget;
+ win->setPixelsSize( scene->getPixelsSize() - PixelDensity::dpToPx( 64 ) );
+ win->setId( "detached_tab_widget_win" );
+ win->getModalWidget()->onClick( [this]( auto ) { restoreMaximizedTabWidget(); } );
+ win->setAnchors( UI_ANCHOR_TOP | UI_ANCHOR_LEFT | UI_ANCHOR_BOTTOM | UI_ANCHOR_RIGHT );
+ win->toFront();
+ win->center();
+ win->setKeyBindingCommand( "close-maximized-tab-widget",
+ [this] { restoreMaximizedTabWidget(); } );
+ win->getKeyBindings().addKeybind( { KEY_ESCAPE }, "close-maximized-tab-widget" );
+ win->setCheckEphemeralCloseFn( [this]( Node* focusNode ) {
+ if ( focusNode->isType( UI_TYPE_POPUPMENU ) )
+ return false;
+ if ( focusNode->getSceneNode()->isUISceneNode() ) {
+ auto* sceneNode = static_cast( focusNode->getSceneNode() );
+ auto* widgetTreeView = sceneNode->getRoot()->hasChild( "widget-tree-view" );
+ if ( widgetTreeView &&
+ ( focusNode == widgetTreeView || widgetTreeView->inParentTreeOf( focusNode ) ) )
+ return false;
+ }
+ return true;
+ } );
+ auto* tabWidgetParent = tabWidget->getParent();
+ const bool wasFirstSplit = tabWidgetParent->isType( UI_TYPE_SPLITTER ) &&
+ tabWidgetParent->asType()->getFirstWidget() == tabWidget;
+ auto* nodeLink = UINodeLink::NewLink( tabWidget );
+ maximizedTabWidgetLink = nodeLink;
+ if ( wasFirstSplit )
+ nodeLink->setClass( "was_first_split" );
+ tabWidget->setParent( win );
+ tabWidget->setId( "detached_tab_widget" );
+ tabWidget->setPixelsPosition( Vector2f::Zero );
+ tabWidget->setPixelsSize( win->getContainer()->getPixelsSize() );
+ tabWidget->setAnchors( UI_ANCHOR_TOP | UI_ANCHOR_LEFT | UI_ANCHOR_BOTTOM | UI_ANCHOR_RIGHT );
+ if ( !tabWidget->inParentTreeOf( scene->getEventDispatcher()->getFocusNode() ) )
+ tabWidget->getTabSelected()->getOwnedWidget()->setFocus();
+ win->on( Event::OnWindowClose, [this]( auto ) { restoreMaximizedTabWidget(); } );
+ nodeLink->setParent( tabWidgetParent );
+ nodeLink->setId( "nodelink_tab_widget" );
+ if ( wasFirstSplit && tabWidgetParent->isType( UI_TYPE_SPLITTER ) )
+ tabWidgetParent->asType()->swap();
+
+ auto* fullscreenImg = UIImage::New();
+ fullscreenImg->unsetFlags( UI_AUTO_SIZE );
+ fullscreenImg->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
+ fullscreenImg->setParent( win );
+ fullscreenImg->setSize( { 24, tabWidget->getTabBar()->getSize().getHeight() } );
+ fullscreenImg->setPosition( { tabWidget->getSize().getWidth() - 24, 0 } );
+ fullscreenImg->setAnchors( UI_ANCHOR_TOP | UI_ANCHOR_RIGHT );
+ if ( auto* icon = scene->findIcon( "fullscreen" ) )
+ fullscreenImg->setDrawable( icon->createDrawable( PixelDensity::dpToPxI( 16 ) ) );
+ fullscreenImg->setVerticalAlign( UI_VALIGN_CENTER );
+ fullscreenImg->setHorizontalAlign( UI_HALIGN_CENTER );
+ fullscreenImg->addClass( "pseudo_anchor" );
+ fullscreenImg->toFront();
+ fullscreenImg->onClick( [this]( const Event* ) {
+ restoreMaximizedTabWidget();
+ appWindow->getCursorManager()->set( Cursor::SysType::SysArrow );
+ } );
+}
+
+void EtermApp::restoreMaximizedTabWidget() {
+ if ( !scene || !SceneManager::isActive() )
+ return;
+ if ( !maximizedTabWidgetLink || !maximizedTabWidget )
+ return;
+ auto* nodeLink = maximizedTabWidgetLink;
+ auto* curTabWidget = maximizedTabWidget;
+ auto* win = maximizedTabWidgetWindow;
+ auto* splitterParent = nodeLink->getParent();
+ nodeLink->setParent( scene );
+ curTabWidget->setParent( splitterParent );
+ curTabWidget->setAnchors( 0 );
+ curTabWidget->setId( "" );
+ if ( nodeLink->hasClass( "was_first_split" ) && splitterParent->isType( UI_TYPE_SPLITTER ) ) {
+ splitterParent->asType()->swap();
+ }
+ nodeLink->close();
+ maximizedTabWidgetLink = nullptr;
+ maximizedTabWidget = nullptr;
+ maximizedTabWidgetWindow = nullptr;
+ if ( win && !win->isClosing() )
+ win->close();
+}
+
+void EtermApp::configureTab( UITab* tab ) {
+ tab->on( Event::OnCreateContextMenu, [this]( const Event* event ) {
+ const auto* menuEvent = static_cast( event );
+ auto* menu = menuEvent->getMenu();
+ auto* clickedTab = event->getNode()->asType();
+ auto* terminal = terminalFromTab( clickedTab );
+ if ( !menu || !terminal || !clickedTab->getTabWidget() )
+ return;
+
+ const auto addItem = [this, menu]( const String& text, const std::string& icon,
+ const std::string& command ) {
+ DrawablePtr drawable;
+ if ( auto* menuIcon = scene->findIcon( icon ) )
+ drawable = menuIcon->createDrawable( PixelDensity::dpToPxI( 12 ) );
+ auto* item = menu->add( text, std::move( drawable ) );
+ item->setId( command );
+ return item;
+ };
+ addItem( i18n( "close_tab", "Close Tab" ), "document-close", "close-tab" );
+ addItem( i18n( "close_other_tabs", "Close Other Tabs" ), "", "close-other-tabs" );
+ addItem( i18n( "close_clean_tabs", "Close Clean Tabs" ), "", "close-clean-tabs" );
+ addItem( i18n( "close_all_tabs", "Close All Tabs" ), "", "close-all-tabs" );
+ addItem( i18n( "close_tabs_to_the_left", "Close Tabs To The Left" ), "",
+ "close-tabs-to-the-left" );
+ addItem( i18n( "close_tabs_to_the_right", "Close Tabs To The Right" ), "",
+ "close-tabs-to-the-right" );
+
+ menu->addSeparator();
+ auto* exclusiveMode =
+ menu->addCheckBox( i18n( "enable_exclusive_mode", "Enable Exclusive Mode" ),
+ terminal->getExclusiveMode() );
+ exclusiveMode->setId( UITerminal::getExclusiveModeToggleCommandName() );
+ addItem( i18n( "rename_session", "Rename Session" ), "", "terminal-rename" );
+
+ menu->addSeparator();
+ const bool canMove = clickedTab->getTabWidget()->getTabCount() > 1;
+ addItem( i18n( "move_tab_to_start", "Move Tab To Start" ), "window", "move-tab-to-start" )
+ ->setEnabled( canMove );
+ addItem( i18n( "move_tab_to_end", "Move Tab To End" ), "window", "move-tab-to-end" )
+ ->setEnabled( canMove );
+
+ menu->addSeparator();
+ addItem( scene->getRoot()->hasChild( "detached_tab_widget_win" )
+ ? i18n( "restore_maximized_tab_widget", "Restore Maximized Tab Widget" )
+ : i18n( "maximize_tab_widget", "Maximize Tab Widget" ),
+ "fullscreen",
+ scene->getRoot()->hasChild( "detached_tab_widget_win" )
+ ? "restore-maximized-tab-widget"
+ : "maximize-tab-widget" );
+
+ menu->on( Event::OnItemClicked, [this, clickedTab, terminal]( const Event* itemEvent ) {
+ if ( !itemEvent->getNode()->isType( UI_TYPE_MENUITEM ) )
+ return;
+ const auto& command = itemEvent->getNode()->getId();
+ auto* previous = tabSplitter->getCurWidget();
+ tabSplitter->setCurrentWidget( terminal );
+
+ if ( command == "close-clean-tabs" ) {
+ tabSplitter->tryCloseAllTabs( terminal, UITabWidget::FocusTabBehavior::Default );
+ } else if ( command == "move-tab-to-start" ) {
+ clickedTab->getTabWidget()->moveTab( clickedTab, 0 );
+ } else if ( command == "move-tab-to-end" ) {
+ clickedTab->getTabWidget()->moveTab( clickedTab,
+ clickedTab->getTabWidget()->getTabCount() );
+ } else if ( command == "maximize-tab-widget" ) {
+ maximizeTabWidget( clickedTab->getTabWidget() );
+ } else if ( command == "restore-maximized-tab-widget" ) {
+ restoreMaximizedTabWidget();
+ } else {
+ terminal->execute( command );
+ }
+
+ if ( previous && tabSplitter->ownedWidgetExists( previous ) )
+ tabSplitter->setCurrentWidget( previous );
+ } );
+ } );
+}
+
+UITerminal* EtermApp::createTerminal( UITabWidget* target ) {
if ( !target && tabSplitter ) {
auto* current = tabSplitter->getCurWidget();
target = current ? tabSplitter->tabWidgetFromWidget( current )
@@ -239,14 +467,17 @@ UITerminal* createTerminal( UITabWidget* target = nullptr ) {
if ( selectedColorScheme )
terminal->setColorScheme( *selectedColorScheme );
- auto* tab = tabSplitter->createWidgetInTabWidget( target, terminal, "Terminal" ).first;
- addTabKeyBindings( terminal, tab );
- terminal->on( Event::OnTitleChange, [tab, terminal]( const Event* ) {
- tab->setText( terminal->getTitle().empty() ? "Terminal" : terminal->getTitle() );
+ auto* tab =
+ tabSplitter->createWidgetInTabWidget( target, terminal, i18n( "terminal", "Terminal" ) )
+ .first;
+ addTabKeyBindings( terminal );
+ terminal->on( Event::OnTitleChange, [this, tab, terminal]( const Event* ) {
+ tab->setText( terminal->getTitle().empty() ? i18n( "terminal", "Terminal" )
+ : String( terminal->getTitle() ) );
if ( tabSplitter->getCurWidget() == terminal )
updateWindowTitle();
} );
- terminal->getTerm()->pushEventCallback( [tab]( const TerminalDisplay::Event& event ) {
+ terminal->getTerm()->pushEventCallback( [this, tab]( const TerminalDisplay::Event& event ) {
if ( terminalConfig.closeOnExit && event.type == TerminalDisplay::EventType::PROCESS_EXIT )
queueExitCloseTab( tab );
} );
@@ -258,38 +489,48 @@ UITerminal* createTerminal( UITabWidget* target = nullptr ) {
return terminal;
}
-UITerminal* createTerminalSplit( SplitDirection direction, UITerminal* terminal ) {
+UITerminal* EtermApp::createTerminalSplit( SplitDirection direction, UITerminal* terminal ) {
auto* source = terminal ? tabSplitter->tabWidgetFromWidget( terminal ) : nullptr;
auto* target = source ? tabSplitter->splitTabWidget( direction, source ) : nullptr;
return target ? createTerminal( target ) : nullptr;
}
-void addTabKeyBindings( UITerminal* terminal, UITab* tab ) {
- terminal->setCommand( "create-new-terminal", [] { createTerminal(); } );
- terminal->setCommand( "close-tab", [tab] { requestCloseTab( tab ); } );
- terminal->setCommand( "next-tab", [terminal] {
+void EtermApp::addTabKeyBindings( UITerminal* terminal ) {
+ tabSplitter->registerSplitterCommands( *terminal );
+ terminal->setCommand( "create-new-terminal", [this] { createTerminal(); } );
+ terminal->setCommand( "debug-widget-tree-view",
+ [this] { UIWidgetInspector::create( scene ); } );
+ terminal->setCommand( "terminal-rename", [this, terminal] { renameSession( terminal ); } );
+ terminal->setCommand( UITerminal::getExclusiveModeToggleCommandName(), [terminal] {
+ terminal->setExclusiveMode( !terminal->getExclusiveMode() );
+ } );
+ terminal->setCommand( "next-tab", [this, terminal] {
if ( auto* tabs = tabSplitter->tabWidgetFromWidget( terminal ) )
tabs->focusNextTab();
} );
- terminal->setCommand( "previous-tab", [terminal] {
+ terminal->setCommand( "previous-tab", [this, terminal] {
if ( auto* tabs = tabSplitter->tabWidgetFromWidget( terminal ) )
tabs->focusPreviousTab();
} );
- terminal->setCommand( "split-right",
- [terminal] { createTerminalSplit( SplitDirection::Right, terminal ); } );
- terminal->setCommand( "split-bottom",
- [terminal] { createTerminalSplit( SplitDirection::Bottom, terminal ); } );
- terminal->setCommand( "split-left",
- [terminal] { createTerminalSplit( SplitDirection::Left, terminal ); } );
- terminal->setCommand( "split-top",
- [terminal] { createTerminalSplit( SplitDirection::Top, terminal ); } );
+ terminal->setCommand( "split-right", [this, terminal] {
+ createTerminalSplit( SplitDirection::Right, terminal );
+ } );
+ terminal->setCommand( "split-bottom", [this, terminal] {
+ createTerminalSplit( SplitDirection::Bottom, terminal );
+ } );
+ terminal->setCommand(
+ "split-left", [this, terminal] { createTerminalSplit( SplitDirection::Left, terminal ); } );
+ terminal->setCommand(
+ "split-top", [this, terminal] { createTerminalSplit( SplitDirection::Top, terminal ); } );
terminal->setCommand( "switch-to-previous-split",
- [terminal] { tabSplitter->switchPreviousSplit( terminal ); } );
+ [this, terminal] { tabSplitter->switchPreviousSplit( terminal ); } );
terminal->setCommand( "switch-to-next-split",
- [terminal] { tabSplitter->switchNextSplit( terminal ); } );
+ [this, terminal] { tabSplitter->switchNextSplit( terminal ); } );
terminal->addKeyBinding( { KEY_T, KeyMod::getDefaultModifier() | KEYMOD_SHIFT },
"create-new-terminal" );
terminal->addKeyBinding( { KEY_W, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "close-tab" );
+ terminal->addKeyBinding( { KEY_F11, KeyMod::getDefaultModifier() | KEYMOD_SHIFT },
+ "debug-widget-tree-view" );
terminal->addKeyBinding( { KEY_PAGEDOWN, KEYMOD_CTRL }, "next-tab" );
terminal->addKeyBinding( { KEY_PAGEUP, KEYMOD_CTRL }, "previous-tab" );
terminal->addKeyBinding( { KEY_TAB, KEYMOD_CTRL }, "next-tab" );
@@ -310,7 +551,7 @@ void addTabKeyBindings( UITerminal* terminal, UITab* tab ) {
"switch-to-next-split" );
}
-bool closeWindow( EE::Window::Window* ) {
+bool EtermApp::closeWindow( EE::Window::Window* ) {
if ( closeApproved || !warnBeforeClose )
return true;
bool running = false;
@@ -321,13 +562,14 @@ bool closeWindow( EE::Window::Window* ) {
return false;
closeDialog = UIMessageBox::New(
UIMessageBox::OK_CANCEL,
- "Are you sure you want to close this window? It is still running a process." );
+ i18n( "close_window_running_process_confirm",
+ "Are you sure you want to close this window? It is still running a process." ) );
closeDialog->setTitle( "eterm" );
- closeDialog->on( Event::OnConfirm, []( const Event* ) {
+ closeDialog->on( Event::OnConfirm, [this]( const Event* ) {
closeApproved = true;
appWindow->close();
} );
- closeDialog->on( Event::OnClose, []( const Event* ) {
+ closeDialog->on( Event::OnClose, [this]( const Event* ) {
closeDialog = nullptr;
closeDialogWidget = nullptr;
} );
@@ -336,11 +578,9 @@ bool closeWindow( EE::Window::Window* ) {
return false;
}
-} // namespace
-
-EE_MAIN_FUNC int main( int argc, char* argv[] ) {
+int EtermApp::run( int argc, char* argv[] ) {
#ifdef EE_DEBUG
- Log::instance()->setLogToStdOut( true );
+ Log::instance()->setLogToStdOut( !Runtime::isOffscreen() );
Log::instance()->setLiveWrite( true );
#endif
args::ArgumentParser parser( "eterm" );
@@ -407,6 +647,9 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) {
parser, "warn-before-closing",
"Prompts for confirmation if a program is still running when closing the terminal.",
{ "warn-before-closing" } );
+ args::Flag alwaysShowTabBar( parser, "always-show-tab-bar",
+ "Always show the tab bar, even with a single tab.",
+ { "always-show-tab-bar" } );
args::ValueFlag initialTabs( parser, "tabs", "Number of initial terminal tabs",
{ "tabs" }, 1 );
@@ -476,6 +719,23 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) {
return EXIT_FAILURE;
FileSystem::changeWorkingDirectory( initialWorkingDirectory );
appWindow->setClearColor( RGB( 0, 0, 0 ) );
+ scene->getUIThemeManager()->setDefaultEffectsEnabled( false );
+ scene->combineStyleSheet( R"css(
+ TabWidget {
+ max-tab-width: 200dp;
+ }
+ Tab > Tab::Text {
+ text-overflow: ellipsis;
+ }
+ .pseudo_anchor {
+ tint: var(--floating-icon);
+ cursor: arrow;
+ }
+ .pseudo_anchor:hover {
+ tint: var(--primary);
+ cursor: hand;
+ }
+ )css" );
auto& resourceScope = *scene->getResourceScope();
auto remixIconFont = FontTrueType::New( "eterm-remixicon", resourceScope );
@@ -539,21 +799,29 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) {
mainLayout->setPixelsSize( appWindow->getSize().asFloat() );
tabSplitter = UITabWidgetSplitter::New( &splitterClient, scene );
- tabSplitter->setHideTabBarOnSingleTab( true );
- tabSplitter->setTabTryCloseCallback(
- []( UIWidget* widget, UITabWidget::FocusTabBehavior, std::function ) {
- if ( warnBeforeClose ) {
- auto* tab = tabSplitter->getTabFromWidget( widget );
- if ( hasRunningChildren( tab ) ) {
- requestCloseTab( tab );
- return false;
- }
+ tabSplitter->setHideTabBarOnSingleTab( !alwaysShowTabBar.Get() );
+ tabSplitter->setCanCreateSplitFn( [this]( SplitDirection, UIWidget* ) {
+ restoreMaximizedTabWidget();
+ return true;
+ } );
+ tabSplitter->setTabTryCloseCallback( [this]( UIWidget* widget, UITabWidget::FocusTabBehavior,
+ std::function ) {
+ if ( auto* detachedTabWidget = scene->getRoot()->find( "detached_tab_widget" );
+ widget && detachedTabWidget && detachedTabWidget->inParentTreeOf( widget ) ) {
+ restoreMaximizedTabWidget();
+ }
+ if ( warnBeforeClose ) {
+ auto* tab = tabSplitter->getTabFromWidget( widget );
+ if ( hasRunningChildren( tab ) ) {
+ requestCloseTab( tab );
+ return false;
}
- return true;
- } );
- tabSplitter->setOnTabWidgetCreateCb( []( UITabWidget* tabs ) {
- tabs->on( Event::OnTabSelected, []( const Event* ) { updateWindowTitle(); } );
- tabs->on( Event::OnTabClosed, []( const Event* event ) {
+ }
+ return true;
+ } );
+ tabSplitter->setOnTabWidgetCreateCb( [this]( UITabWidget* tabs ) {
+ tabs->on( Event::OnTabSelected, [this]( const Event* ) { updateWindowTitle(); } );
+ tabs->on( Event::OnTabClosed, [this]( const Event* event ) {
auto* closedTab = static_cast( event )->getTab();
pendingExitCloseTabs.erase(
std::remove( pendingExitCloseTabs.begin(), pendingExitCloseTabs.end(), closedTab ),
@@ -575,15 +843,17 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) {
for ( size_t tab = 0; tab < eemax( static_cast( 1 ), initialTabs.Get() ); ++tab ) {
if ( !createTerminal( tabs ) ) {
- appWindow->showMessageBox( EE::Window::Window::MessageBoxType::Error, "eterm",
- "Operating System not supported." );
+ appWindow->showMessageBox(
+ EE::Window::Window::MessageBoxType::Error, "eterm",
+ i18n( "operating_system_not_supported", "Operating System not supported." ) );
return EXIT_FAILURE;
}
}
- appWindow->setCloseRequestCallback( &closeWindow );
+ appWindow->setCloseRequestCallback(
+ [this]( EE::Window::Window* window ) { return closeWindow( window ); } );
app.setShowMemoryManagerResult( true );
- appWindow->runMainLoop( [] {
+ appWindow->runMainLoop( [this] {
appWindow->getInput()->update();
SceneManager::instance()->update();
queueExitedTabs();
@@ -610,3 +880,10 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) {
} );
return EXIT_SUCCESS;
}
+
+} // namespace
+
+EE_MAIN_FUNC int main( int argc, char* argv[] ) {
+ EtermApp app;
+ return app.run( argc, argv );
+}