diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3f8822d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.21) + +project(OrbitHub VERSION 0.1.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTORCC ON) + +find_package(Qt6 6.2 REQUIRED COMPONENTS Widgets) + +qt_standard_project_setup() + +add_executable(orbithub + src/main.cpp + src/profiles_window.cpp + src/profiles_window.h + src/session_window.cpp + src/session_window.h +) + +target_link_libraries(orbithub PRIVATE Qt6::Widgets) + +install(TARGETS orbithub RUNTIME DESTINATION bin) diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..cf983f5 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,13 @@ +#include "profiles_window.h" + +#include + +int main(int argc, char* argv[]) +{ + QApplication app(argc, argv); + + ProfilesWindow window; + window.show(); + + return app.exec(); +} diff --git a/src/profiles_window.cpp b/src/profiles_window.cpp new file mode 100644 index 0000000..bbcea37 --- /dev/null +++ b/src/profiles_window.cpp @@ -0,0 +1,135 @@ +#include "profiles_window.h" + +#include "session_window.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +ProfilesWindow::ProfilesWindow(QWidget* parent) + : QMainWindow(parent), + m_searchBox(nullptr), + m_profilesList(nullptr), + m_newButton(nullptr), + m_editButton(nullptr), + m_deleteButton(nullptr) +{ + setWindowTitle(QStringLiteral("OrbitHub Profiles")); + resize(520, 620); + + setupUi(); + populateSampleProfiles(); +} + +void ProfilesWindow::setupUi() +{ + auto* central = new QWidget(this); + auto* rootLayout = new QVBoxLayout(central); + + auto* searchLabel = new QLabel(QStringLiteral("Search"), central); + m_searchBox = new QLineEdit(central); + m_searchBox->setPlaceholderText(QStringLiteral("Filter profiles...")); + + m_profilesList = new QListWidget(central); + m_profilesList->setSelectionMode(QAbstractItemView::SingleSelection); + + auto* buttonRow = new QHBoxLayout(); + m_newButton = new QPushButton(QStringLiteral("New"), central); + m_editButton = new QPushButton(QStringLiteral("Edit"), central); + m_deleteButton = new QPushButton(QStringLiteral("Delete"), central); + + buttonRow->addWidget(m_newButton); + buttonRow->addWidget(m_editButton); + buttonRow->addWidget(m_deleteButton); + buttonRow->addStretch(); + + rootLayout->addWidget(searchLabel); + rootLayout->addWidget(m_searchBox); + rootLayout->addWidget(m_profilesList, 1); + rootLayout->addLayout(buttonRow); + + setCentralWidget(central); + + connect(m_searchBox, + &QLineEdit::textChanged, + this, + [this](const QString& text) { filterProfiles(text); }); + + connect(m_profilesList, + &QListWidget::itemDoubleClicked, + this, + [this](QListWidgetItem* item) { openSessionForItem(item); }); + + // Milestone 0 keeps profile management as placeholders. + auto showTodo = [this](const QString& action) { + QMessageBox::information(this, + QStringLiteral("Milestone 0"), + QStringLiteral("%1 is planned for Milestone 1.").arg(action)); + }; + + connect(m_newButton, &QPushButton::clicked, this, [showTodo]() { showTodo(QStringLiteral("New Profile")); }); + connect(m_editButton, &QPushButton::clicked, this, [showTodo]() { showTodo(QStringLiteral("Edit Profile")); }); + connect(m_deleteButton, + &QPushButton::clicked, + this, + [showTodo]() { showTodo(QStringLiteral("Delete Profile")); }); +} + +void ProfilesWindow::populateSampleProfiles() +{ + const QStringList sampleProfiles = { + QStringLiteral("Production Bastion"), + QStringLiteral("Staging API Host"), + QStringLiteral("Local Lab VM"), + QStringLiteral("CI Build Agent")}; + + m_profilesList->addItems(sampleProfiles); +} + +void ProfilesWindow::filterProfiles(const QString& query) +{ + const QString trimmed = query.trimmed(); + + for (int i = 0; i < m_profilesList->count(); ++i) { + QListWidgetItem* item = m_profilesList->item(i); + const bool matches = trimmed.isEmpty() + || item->text().contains(trimmed, Qt::CaseInsensitive); + item->setHidden(!matches); + } +} + +void ProfilesWindow::openSessionForItem(QListWidgetItem* item) +{ + if (item == nullptr) { + return; + } + + auto* session = new SessionWindow(item->text()); + session->setAttribute(Qt::WA_DeleteOnClose); + + m_sessionWindows.emplace_back(session); + connect(session, + &QObject::destroyed, + this, + [this](QObject* object) { + m_sessionWindows.erase( + std::remove_if(m_sessionWindows.begin(), + m_sessionWindows.end(), + [object](const QPointer& candidate) { + return candidate.isNull() || candidate.data() == object; + }), + m_sessionWindows.end()); + }); + + session->show(); +} diff --git a/src/profiles_window.h b/src/profiles_window.h new file mode 100644 index 0000000..67965de --- /dev/null +++ b/src/profiles_window.h @@ -0,0 +1,37 @@ +#ifndef ORBITHUB_PROFILES_WINDOW_H +#define ORBITHUB_PROFILES_WINDOW_H + +#include +#include + +#include +#include + +class QListWidget; +class QListWidgetItem; +class QLineEdit; +class QPushButton; +class SessionWindow; + +class ProfilesWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit ProfilesWindow(QWidget* parent = nullptr); + +private: + QLineEdit* m_searchBox; + QListWidget* m_profilesList; + QPushButton* m_newButton; + QPushButton* m_editButton; + QPushButton* m_deleteButton; + std::vector> m_sessionWindows; + + void setupUi(); + void populateSampleProfiles(); + void filterProfiles(const QString& query); + void openSessionForItem(QListWidgetItem* item); +}; + +#endif diff --git a/src/session_window.cpp b/src/session_window.cpp new file mode 100644 index 0000000..c6c6fb7 --- /dev/null +++ b/src/session_window.cpp @@ -0,0 +1,45 @@ +#include "session_window.h" + +#include +#include +#include +#include +#include + +SessionWindow::SessionWindow(const QString& profileName, QWidget* parent) + : QMainWindow(parent), m_tabs(new QTabWidget(this)) +{ + setWindowTitle(QStringLiteral("OrbitHub Session - %1").arg(profileName)); + resize(900, 600); + + setCentralWidget(m_tabs); + addPlaceholderTab(profileName); +} + +void SessionWindow::addPlaceholderTab(const QString& profileName) +{ + auto* container = new QWidget(this); + auto* layout = new QVBoxLayout(container); + + auto* titleLabel = new QLabel(QStringLiteral("Profile: %1").arg(profileName), container); + auto* surfaceLabel = new QLabel(QStringLiteral("OrbitHub Native Surface"), container); + + QFont titleFont = titleLabel->font(); + titleFont.setBold(true); + titleLabel->setFont(titleFont); + + QFont surfaceFont = surfaceLabel->font(); + surfaceFont.setPointSize(surfaceFont.pointSize() + 6); + surfaceFont.setBold(true); + surfaceLabel->setFont(surfaceFont); + + surfaceLabel->setAlignment(Qt::AlignCenter); + surfaceLabel->setMinimumHeight(200); + surfaceLabel->setStyleSheet( + QStringLiteral("border: 1px solid #8a8a8a; background-color: #f5f5f5;")); + + layout->addWidget(titleLabel); + layout->addWidget(surfaceLabel, 1); + + m_tabs->addTab(container, profileName); +} diff --git a/src/session_window.h b/src/session_window.h new file mode 100644 index 0000000..c2a1330 --- /dev/null +++ b/src/session_window.h @@ -0,0 +1,22 @@ +#ifndef ORBITHUB_SESSION_WINDOW_H +#define ORBITHUB_SESSION_WINDOW_H + +#include +#include + +class QTabWidget; + +class SessionWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit SessionWindow(const QString& profileName, QWidget* parent = nullptr); + +private: + QTabWidget* m_tabs; + + void addPlaceholderTab(const QString& profileName); +}; + +#endif