#include "session_window.h" #include "about_dialog.h" #include #include "session_tab.h" #include #include #include #include #include #include #include #include #include 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)) { loadUiPreferences(); setWindowTitle(QStringLiteral("OrbitHub Session - %1").arg(profile.name)); resize(1080, 760); setWindowIcon(QApplication::windowIcon()); m_tabs->setTabsClosable(true); connect(m_tabs, &QTabWidget::tabCloseRequested, this, [this](int index) { QWidget* tab = m_tabs->widget(index); if (auto* sessionTab = qobject_cast(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(m_tabs->widget(index)); if (tab == nullptr) { return; } QMenu menu(this); QAction* disconnectAction = menu.addAction(QStringLiteral("Disconnect")); QAction* reconnectAction = menu.addAction(QStringLiteral("Reconnect")); QAction* toggleEventsAction = menu.addAction( tab->isEventsPanelExpanded() ? QStringLiteral("Hide Events") : QStringLiteral("Show Events")); QAction* copyEventsAction = menu.addAction(QStringLiteral("Copy Events")); QAction* exportEventsAction = menu.addAction(QStringLiteral("Export Events")); QAction* clearEventsAction = menu.addAction(QStringLiteral("Clear Events")); QList themeActions; if (tab->supportsThemeSelection()) { menu.addSeparator(); QMenu* themeMenu = menu.addMenu(QStringLiteral("Theme")); 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 = nullptr; if (tab->supportsClearAction()) { 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 == toggleEventsAction) { tab->setEventsPanelExpanded(!tab->isEventsPanelExpanded()); } else if (chosen == copyEventsAction) { tab->copyEvents(); } else if (chosen == exportEventsAction) { tab->exportEventsToFile(); } else if (chosen == clearEventsAction) { tab->clearEvents(); } else if (clearAction != nullptr && chosen == clearAction) { tab->clearTerminal(); } else { for (QAction* themeAction : themeActions) { if (chosen == themeAction) { tab->setTerminalThemeName(themeAction->text()); break; } } } }); QMenu* helpMenu = menuBar()->addMenu(QStringLiteral("Help")); QAction* aboutAction = helpMenu->addAction(QStringLiteral("About OrbitHub")); connect(aboutAction, &QAction::triggered, this, [this]() { AboutDialog dialog(this); dialog.exec(); }); setCentralWidget(m_tabs); addSessionTab(profile); } void SessionWindow::openProfile(const Profile& profile) { addSessionTab(profile); } void SessionWindow::addSessionTab(const Profile& profile) { auto* tab = new SessionTab(profile, m_preferences, this); const int index = m_tabs->addTab(tab, tab->tabTitle()); m_tabs->setCurrentIndex(index); if (m_tabs->count() > 1) { setWindowTitle(QStringLiteral("OrbitHub Sessions")); } 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; } } }); connect(tab, &SessionTab::terminalThemeChanged, this, [this](const QString& themeName) { m_preferences.terminalThemeName = themeName.trimmed().isEmpty() ? QStringLiteral("Dark") : themeName.trimmed(); saveUiPreferences(); }); connect(tab, &SessionTab::eventsPanelVisibilityChanged, this, [this](bool expanded) { m_preferences.eventsPanelExpanded = expanded; saveUiPreferences(); }); } void SessionWindow::updateTabTitle(SessionTab* tab, const QString& title) { for (int i = 0; i < m_tabs->count(); ++i) { if (m_tabs->widget(i) == tab) { m_tabs->setTabText(i, title); return; } } } void SessionWindow::loadUiPreferences() { QSettings settings; m_preferences.terminalThemeName = settings.value(QStringLiteral("session/defaultTerminalTheme"), QStringLiteral("Dark")) .toString() .trimmed(); if (m_preferences.terminalThemeName.isEmpty()) { m_preferences.terminalThemeName = QStringLiteral("Dark"); } m_preferences.eventsPanelExpanded = settings.value(QStringLiteral("session/eventsPanelExpanded"), false).toBool(); } void SessionWindow::saveUiPreferences() const { QSettings settings; settings.setValue(QStringLiteral("session/defaultTerminalTheme"), m_preferences.terminalThemeName); settings.setValue(QStringLiteral("session/eventsPanelExpanded"), m_preferences.eventsPanelExpanded); }