2 Commits

Author SHA1 Message Date
Keith Smith
b43fb6fb1a Record Milestone 0 completion progress 2026-03-01 08:56:13 -07:00
Keith Smith
285a81d9b5 Scaffold Qt6 Widgets app for Milestone 0 2026-03-01 08:56:10 -07:00
7 changed files with 296 additions and 0 deletions

27
CMakeLists.txt Normal file
View File

@@ -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)

17
docs/PROGRESS.md Normal file
View File

@@ -0,0 +1,17 @@
# OrbitHub Progress
## Milestone 0 - Restart in C++/Qt Widgets
Status: Completed
Delivered:
- Fresh C++17/Qt6 Widgets scaffold with CMake
- `ProfilesWindow` (`QMainWindow`) with search, profile list, and New/Edit/Delete controls
- Double-click in Profiles opens a `SessionWindow`
- `SessionWindow` (`QMainWindow`) with `QTabWidget`
- Placeholder tab content showing `OrbitHub Native Surface`
- `main.cpp` wiring for application startup
Git:
- Branch: `milestone-0-restart-cpp`
- Tag: `v0-m0-done`

13
src/main.cpp Normal file
View File

@@ -0,0 +1,13 @@
#include "profiles_window.h"
#include <QApplication>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
ProfilesWindow window;
window.show();
return app.exec();
}

135
src/profiles_window.cpp Normal file
View File

@@ -0,0 +1,135 @@
#include "profiles_window.h"
#include "session_window.h"
#include <algorithm>
#include <QAbstractItemView>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QListWidget>
#include <QListWidgetItem>
#include <QMessageBox>
#include <QPushButton>
#include <QStringList>
#include <QVBoxLayout>
#include <QWidget>
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<SessionWindow>& candidate) {
return candidate.isNull() || candidate.data() == object;
}),
m_sessionWindows.end());
});
session->show();
}

37
src/profiles_window.h Normal file
View File

@@ -0,0 +1,37 @@
#ifndef ORBITHUB_PROFILES_WINDOW_H
#define ORBITHUB_PROFILES_WINDOW_H
#include <QMainWindow>
#include <QString>
#include <QPointer>
#include <vector>
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<QPointer<SessionWindow>> m_sessionWindows;
void setupUi();
void populateSampleProfiles();
void filterProfiles(const QString& query);
void openSessionForItem(QListWidgetItem* item);
};
#endif

45
src/session_window.cpp Normal file
View File

@@ -0,0 +1,45 @@
#include "session_window.h"
#include <QFont>
#include <QLabel>
#include <QTabWidget>
#include <QVBoxLayout>
#include <QWidget>
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);
}

22
src/session_window.h Normal file
View File

@@ -0,0 +1,22 @@
#ifndef ORBITHUB_SESSION_WINDOW_H
#define ORBITHUB_SESSION_WINDOW_H
#include <QMainWindow>
#include <QString>
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