#include "session_window.h" #include "about_dialog.h" #include "profiles_window.h" #include "user_guide_dialog.h" #include #include "session_tab.h" #include #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(QWidget* parent) : QMainWindow(parent), m_tabs(new QTabWidget(this)), m_profilesWidget(nullptr) { loadUiPreferences(); setWindowTitle(QStringLiteral("OrbitHub")); 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 (sessionTabCount() == 0) { setWindowTitle(QStringLiteral("OrbitHub")); } }); 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* zoomInAction = nullptr; QAction* zoomOutAction = nullptr; QAction* resetZoomAction = nullptr; QAction* setFontSizeAction = nullptr; if (tab->supportsZoom()) { menu.addSeparator(); zoomInAction = menu.addAction(QStringLiteral("Increase Font Size")); zoomOutAction = menu.addAction(QStringLiteral("Decrease Font Size")); resetZoomAction = menu.addAction(QStringLiteral("Reset Font Size")); setFontSizeAction = menu.addAction(QStringLiteral("Set Font Size...")); } 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 if (zoomInAction != nullptr && chosen == zoomInAction) { tab->zoomIn(); } else if (zoomOutAction != nullptr && chosen == zoomOutAction) { tab->zoomOut(); } else if (resetZoomAction != nullptr && chosen == resetZoomAction) { tab->resetZoom(); } else if (setFontSizeAction != nullptr && chosen == setFontSizeAction) { bool accepted = false; const int currentSize = tab->terminalFontPointSize() > 0 ? tab->terminalFontPointSize() : 10; const int newSize = QInputDialog::getInt(this, QStringLiteral("Set Font Size"), QStringLiteral("Font size (points):"), currentSize, 6, 72, 1, &accepted); if (accepted) { tab->setTerminalFontPointSize(newSize); } } else { for (QAction* themeAction : themeActions) { if (chosen == themeAction) { tab->setTerminalThemeName(themeAction->text()); break; } } } }); QMenu* fileMenu = menuBar()->addMenu(QStringLiteral("File")); QAction* newProfileAction = fileMenu->addAction(QStringLiteral("New Profile")); QAction* newFolderAction = fileMenu->addAction(QStringLiteral("New Folder")); fileMenu->addSeparator(); QAction* importProfilesAction = fileMenu->addAction(QStringLiteral("Import Profiles...")); QAction* exportProfilesAction = fileMenu->addAction(QStringLiteral("Export Profiles...")); fileMenu->addSeparator(); QAction* quitAction = fileMenu->addAction(QStringLiteral("Quit")); connect(newProfileAction, &QAction::triggered, this, [this]() { m_profilesWidget->createProfileInCurrentContext(); }); connect(newFolderAction, &QAction::triggered, this, [this]() { m_profilesWidget->createFolderInCurrentContext(); }); connect(importProfilesAction, &QAction::triggered, this, [this]() { m_profilesWidget->importProfiles(); }); connect(exportProfilesAction, &QAction::triggered, this, [this]() { m_profilesWidget->exportProfiles(); }); connect(quitAction, &QAction::triggered, this, []() { qApp->quit(); }); QMenu* helpMenu = menuBar()->addMenu(QStringLiteral("Help")); QAction* userGuideAction = helpMenu->addAction(QStringLiteral("User Guide")); connect(userGuideAction, &QAction::triggered, this, [this]() { UserGuideDialog dialog(this); dialog.exec(); }); QAction* aboutAction = helpMenu->addAction(QStringLiteral("About OrbitHub")); connect(aboutAction, &QAction::triggered, this, [this]() { AboutDialog dialog(this); dialog.exec(); }); setCentralWidget(m_tabs); m_profilesWidget = new ProfilesWindow(this); const int profilesIndex = m_tabs->addTab(m_profilesWidget, QStringLiteral("Profiles")); m_tabs->tabBar()->setTabButton(profilesIndex, QTabBar::LeftSide, nullptr); m_tabs->tabBar()->setTabButton(profilesIndex, QTabBar::RightSide, nullptr); connect(m_profilesWidget, &ProfilesWindow::connectRequested, this, &SessionWindow::addSessionTab); } 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 (sessionTabCount() > 1) { setWindowTitle(QStringLiteral("OrbitHub Sessions")); } else { setWindowTitle(QStringLiteral("OrbitHub Session - %1").arg(profile.name)); } 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::terminalFontSizeChanged, this, [this](int pointSize) { if (pointSize <= 0) { return; } m_preferences.terminalFontPointSize = pointSize; 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; } } } int SessionWindow::sessionTabCount() const { return m_tabs->count() - 1; } 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(); m_preferences.terminalFontPointSize = settings.value(QStringLiteral("session/terminalFontPointSize"), 0).toInt(); } void SessionWindow::saveUiPreferences() const { QSettings settings; settings.setValue(QStringLiteral("session/defaultTerminalTheme"), m_preferences.terminalThemeName); settings.setValue(QStringLiteral("session/eventsPanelExpanded"), m_preferences.eventsPanelExpanded); settings.setValue(QStringLiteral("session/terminalFontPointSize"), m_preferences.terminalFontPointSize); }