Terminal status bar now is a Tab Widget and can create any number of terminals.

Fix a bug when initializing the LSP that provoked to not correctly send the initial commands.
Added `expand-text` to UIPushButton.
Added `setAcceptsDropOfWidgetFn` to externally control which widgets are accepted as droppable in UITabWidget.
Improved draw invalidation in UIStackWidget.
This commit is contained in:
Martín Lucas Golini
2025-12-27 17:22:12 -03:00
parent 5fc5fcccb2
commit 0ca36374c4
21 changed files with 371 additions and 75 deletions

View File

@@ -8,17 +8,114 @@ StatusTerminalController::StatusTerminalController( UISplitter* mainSplitter,
StatusBarElement( mainSplitter, uiSceneNode, app ), mApp( app ) {}
UIWidget* StatusTerminalController::getWidget() {
return mUITerminal;
return mContainer;
}
UIWidget* StatusTerminalController::createWidget() {
if ( mUITerminal == nullptr )
mUITerminal = createTerminal();
if ( mContainer == nullptr )
mContainer = createContainer();
return getWidget();
}
bool StatusTerminalController::tryTabClose( UITab* tab ) {
if ( !tab->getOwnedWidget()->isWidget() || tab->getOwnedWidget() == nullptr )
return true;
UIWidget* widget = tab->getOwnedWidget()->asType<UIWidget>();
if ( widget == nullptr || widget->getData() == 0 )
return true;
if ( mContext->getConfig().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,
mContext->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]( const Event* ) { mContainer->setFocus(); } );
msgBox->setTitle( "ecode" );
msgBox->center();
msgBox->showWhenReady();
return false;
}
}
return true;
}
UIHLinearLayoutCommandExecuter* StatusTerminalController::createContainer() {
if ( mContainer )
return mContainer;
static const auto XML = R"xml(
<hboxce id="terminal_panel" class="tab_widget_cont" lw="mp" lh="mp">
<TabWidget id="terminal_panel_tab_widget" lw="0dp" lh="mp" lw8="1"
tabbar-hide-on-single-tab="true"
tabbar-allow-rearrange="true"
tabbar-allow-drag-and-drop-tabs="true"
tabbar-allow-switch-tabs-in-empty-spaces="true"
tab-close-button-visible="true"
tab-closable="true"></TabWidget>
<vbox lw="16dp" lh="mp" class="vertical_bar">
<PushButton id="terminal_panel_add" lw="mp" icon="icon(add, 12dp)" tooltip="@string(add_terminal, Add Terminal)" />
</vbox>
</hboxce>
)xml";
if ( mMainSplitter->getLastWidget() != nullptr ) {
mMainSplitter->getLastWidget()->setVisible( false );
mMainSplitter->getLastWidget()->setParent( mUISceneNode );
}
mContainer = mUISceneNode->loadLayoutFromString( XML, mMainSplitter )
->asType<UIHLinearLayoutCommandExecuter>();
mContainer->bind( "terminal_panel_tab_widget", mTabWidget );
mContainer->bind( "terminal_panel_add", mAddBtn );
mContainer->on( Event::OnFocus, [this]( auto ) {
if ( mTabWidget->getTabSelected() && mTabWidget->getTabSelected()->getOwnedWidget() )
mTabWidget->getTabSelected()->getOwnedWidget()->setFocus();
} );
mTabWidget->setAcceptsDropOfWidgetFn( []( const UIWidget* widget ) {
return widget->isType( UI_TYPE_TAB ) &&
widget->asConstType<UITab>()->getOwnedWidget()->isType( UI_TYPE_TERMINAL );
} );
mTabWidget->setTabTryCloseCallback(
[this]( UITab* tab, UITabWidget::FocusTabBehavior ) -> bool {
return tryTabClose( tab );
} );
createTerminal();
mAddBtn->onClick( [this]( auto ) { createTerminal(); } );
const auto onTabCountChange = [this]( auto ) {
if ( SceneManager::instance()->isShuttingDown() )
return;
auto tabCount = mTabWidget->getTabCount();
if ( tabCount == 0 )
createTerminal();
};
mTabWidget->on( Event::OnTabAdded, onTabCountChange );
mTabWidget->on( Event::OnTabClosed, onTabCountChange );
return mContainer;
}
UITabWidget* StatusTerminalController::getTabWidget() {
return mTabWidget;
}
UITerminal* StatusTerminalController::getUITerminal() {
return mUITerminal;
return mTabWidget->getTabCount() > 0 &&
mTabWidget->getTab( 0 )->getOwnedWidget()->isType( UI_TYPE_TERMINAL )
? mTabWidget->getTab( 0 )->getOwnedWidget()->asType<UITerminal>()
: nullptr;
}
UITerminal* StatusTerminalController::createTerminal(
@@ -56,6 +153,9 @@ UITerminal* StatusTerminalController::createTerminal(
? terminalColorSchemes.at( currentTerminalColorScheme )
: TerminalColorScheme::getDefault() );
mContext->getTerminalManager()->setKeybindings( term );
mApp->registerUnlockedCommands( *term );
term->setCommand( "switch-to-previous-colorscheme", [this] {
auto it = mContext->getTerminalManager()->getTerminalColorSchemes().find(
mContext->getTerminalManager()->getTerminalCurrentColorScheme() );
@@ -79,10 +179,53 @@ UITerminal* StatusTerminalController::createTerminal(
term->setExclusiveMode( !term->getExclusiveMode() );
mApp->updateTerminalMenu();
} );
mContext->getSplitter()->registerSplitterCommands( *term );
mApp->registerUnlockedCommands( *term );
term->setCommand( "close-tab", [this] {
if ( tryTabClose( mTabWidget->getTabSelected() ) )
mTabWidget->removeTab( mTabWidget->getTabSelected() );
} );
term->setCommand( "create-new", [this] { createTerminal(); } );
term->setCommand( "create-new-terminal", [this] { createTerminal(); } );
term->setCommand( "next-tab", [this] { mTabWidget->focusNextTab(); } );
term->setCommand( "previous-tab", [this] { mTabWidget->focusPreviousTab(); } );
for ( int i = 1; i <= 10; i++ ) {
term->setCommand( String::format( "switch-to-tab-%d", i ), [this, i] {
mTabWidget->setTabSelected( eeclamp<Uint32>( i, 0, mTabWidget->getTabCount() - 1 ) );
} );
}
term->setCommand( "switch-to-first-tab", [this] {
if ( mTabWidget->getTabCount() )
mTabWidget->setTabSelected( (Uint32)0 );
} );
term->setCommand( "switch-to-last-tab", [this] {
if ( mTabWidget->getTabCount() )
mTabWidget->setTabSelected( (Uint32)( mTabWidget->getTabCount() - 1 ) );
} );
term->setCommand( "terminal-rename", [this, term] {
UIMessageBox* msgBox = UIMessageBox::New(
UIMessageBox::INPUT, mApp->i18n( "new_terminal_name", "New terminal name:" ) );
msgBox->setTitle( mApp->getWindowTitle() );
msgBox->getTextInput()->setHint( mApp->i18n( "any_name_ellipsis", "Any name..." ) );
msgBox->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } );
msgBox->showWhenReady();
msgBox->on( Event::OnConfirm, [msgBox, term]( const Event* ) {
std::string title( msgBox->getTextInput()->getText().toUtf8() );
term->setTitle( title );
msgBox->close();
term->setFocus();
} );
} );
term->setFocus();
term->setId( "terminal" );
term->setParent( mTabWidget );
UIIcon* icon = mUISceneNode->findIcon( "terminal" );
auto tab = mTabWidget->add(
program, term, icon != nullptr ? icon->getSize( PixelDensity::dpToPxI( 12 ) ) : nullptr );
term->setData( (UintPtr)tab );
term->on( Event::OnTitleChange, [tab, term]( auto ) { tab->setText( term->getTitle() ); } );
mTabWidget->setTabSelected( tab );
return term;
}