Complete Milestone 4 interactive SSH session UX

This commit is contained in:
2026-03-01 11:00:31 -07:00
parent 776ddc1a53
commit 2b25f805cd
4 changed files with 190 additions and 190 deletions
+92
View File
@@ -2,8 +2,37 @@
#include "session_tab.h"
#include <QAction>
#include <QColor>
#include <QMenu>
#include <QPalette>
#include <QStringList>
#include <QTabBar>
#include <QTabWidget>
namespace {
QColor tabColorForState(SessionState state, const QPalette& palette)
{
switch (state) {
case SessionState::Disconnected:
return palette.color(QPalette::WindowText);
case SessionState::Connecting:
return QColor(QStringLiteral("#9a6700"));
case SessionState::Connected:
return QColor(QStringLiteral("#2e7d32"));
case SessionState::Failed:
return QColor(QStringLiteral("#c62828"));
}
return palette.color(QPalette::WindowText);
}
QStringList terminalThemeNames()
{
return {QStringLiteral("Dark"), QStringLiteral("Light"), QStringLiteral("Solarized Dark")};
}
}
SessionWindow::SessionWindow(const Profile& profile, QWidget* parent)
: QMainWindow(parent), m_tabs(new QTabWidget(this))
{
@@ -16,12 +45,61 @@ SessionWindow::SessionWindow(const Profile& profile, QWidget* parent)
this,
[this](int index) {
QWidget* tab = m_tabs->widget(index);
if (auto* sessionTab = qobject_cast<SessionTab*>(tab)) {
sessionTab->disconnectSession();
}
m_tabs->removeTab(index);
delete tab;
if (m_tabs->count() == 0) {
close();
}
});
m_tabs->tabBar()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_tabs->tabBar(),
&QWidget::customContextMenuRequested,
this,
[this](const QPoint& pos) {
const int index = m_tabs->tabBar()->tabAt(pos);
if (index < 0) {
return;
}
auto* tab = qobject_cast<SessionTab*>(m_tabs->widget(index));
if (tab == nullptr) {
return;
}
QMenu menu(this);
QAction* disconnectAction = menu.addAction(QStringLiteral("Disconnect"));
QAction* reconnectAction = menu.addAction(QStringLiteral("Reconnect"));
menu.addSeparator();
QMenu* themeMenu = menu.addMenu(QStringLiteral("Theme"));
QList<QAction*> themeActions;
const QString currentTheme = tab->terminalThemeName();
for (const QString& themeName : terminalThemeNames()) {
QAction* themeAction = themeMenu->addAction(themeName);
themeAction->setCheckable(true);
themeAction->setChecked(
themeName.compare(currentTheme, Qt::CaseInsensitive) == 0);
themeActions.append(themeAction);
}
QAction* clearAction = menu.addAction(QStringLiteral("Clear"));
QAction* chosen = menu.exec(m_tabs->tabBar()->mapToGlobal(pos));
if (chosen == disconnectAction) {
tab->disconnectSession();
} else if (chosen == reconnectAction) {
tab->reconnectSession();
} else if (chosen == clearAction) {
tab->clearTerminal();
} else {
for (QAction* themeAction : themeActions) {
if (chosen == themeAction) {
tab->setTerminalThemeName(themeAction->text());
break;
}
}
}
});
setCentralWidget(m_tabs);
addSessionTab(profile);
@@ -32,11 +110,25 @@ void SessionWindow::addSessionTab(const Profile& profile)
auto* tab = new SessionTab(profile, this);
const int index = m_tabs->addTab(tab, tab->tabTitle());
m_tabs->setCurrentIndex(index);
m_tabs->tabBar()->setTabTextColor(
index, tabColorForState(SessionState::Disconnected, m_tabs->palette()));
connect(tab,
&SessionTab::tabTitleChanged,
this,
[this, tab](const QString& title) { updateTabTitle(tab, title); });
connect(tab,
&SessionTab::tabStateChanged,
this,
[this, tab](SessionState state) {
for (int i = 0; i < m_tabs->count(); ++i) {
if (m_tabs->widget(i) == tab) {
m_tabs->tabBar()->setTabTextColor(
i, tabColorForState(state, m_tabs->palette()));
return;
}
}
});
}
void SessionWindow::updateTabTitle(SessionTab* tab, const QString& title)