Compare commits
6 Commits
da11b96dab
...
v0-m1-done
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87b0f60569 | ||
|
|
09c54313af | ||
|
|
fba7336f69 | ||
|
|
6d147894c5 | ||
|
|
b43fb6fb1a | ||
|
|
285a81d9b5 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/build/
|
||||||
29
CMakeLists.txt
Normal file
29
CMakeLists.txt
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
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 Sql)
|
||||||
|
|
||||||
|
qt_standard_project_setup()
|
||||||
|
|
||||||
|
add_executable(orbithub
|
||||||
|
src/main.cpp
|
||||||
|
src/profile_repository.cpp
|
||||||
|
src/profile_repository.h
|
||||||
|
src/profiles_window.cpp
|
||||||
|
src/profiles_window.h
|
||||||
|
src/session_window.cpp
|
||||||
|
src/session_window.h
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(orbithub PRIVATE Qt6::Widgets Qt6::Sql)
|
||||||
|
|
||||||
|
install(TARGETS orbithub RUNTIME DESTINATION bin)
|
||||||
56
docs/BUILDING.md
Normal file
56
docs/BUILDING.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# Building OrbitHub (C++ / Qt6 Widgets)
|
||||||
|
|
||||||
|
Run all commands from the repository root unless noted.
|
||||||
|
|
||||||
|
## Linux (Ubuntu / Mint)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y \
|
||||||
|
build-essential cmake ninja-build git pkg-config \
|
||||||
|
qt6-base-dev qt6-base-dev-tools qt6-tools-dev qt6-tools-dev-tools
|
||||||
|
|
||||||
|
cmake -S . -B build -G Ninja
|
||||||
|
cmake --build build
|
||||||
|
./build/orbithub
|
||||||
|
```
|
||||||
|
|
||||||
|
## macOS (Homebrew)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
xcode-select --install
|
||||||
|
brew update
|
||||||
|
brew install cmake ninja pkg-config qt@6
|
||||||
|
|
||||||
|
cmake -S . -B build -G Ninja -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6)"
|
||||||
|
cmake --build build
|
||||||
|
./build/orbithub
|
||||||
|
```
|
||||||
|
|
||||||
|
## Windows 11 (PowerShell + MSVC + vcpkg)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
winget install -e --id Git.Git
|
||||||
|
winget install -e --id Kitware.CMake
|
||||||
|
winget install -e --id Ninja-build.Ninja
|
||||||
|
winget install -e --id Microsoft.VisualStudio.2022.BuildTools `
|
||||||
|
--override "--quiet --wait --norestart --add Microsoft.VisualStudio.Workload.VCTools"
|
||||||
|
```
|
||||||
|
|
||||||
|
Open a new terminal after installs, then:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git clone https://github.com/microsoft/vcpkg C:\dev\vcpkg
|
||||||
|
C:\dev\vcpkg\bootstrap-vcpkg.bat
|
||||||
|
C:\dev\vcpkg\vcpkg.exe install qtbase:x64-windows
|
||||||
|
|
||||||
|
cmake -S . -B build -G Ninja `
|
||||||
|
-DCMAKE_TOOLCHAIN_FILE=C:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake
|
||||||
|
cmake --build build
|
||||||
|
.\build\orbithub.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- OrbitHub currently requires Qt6 Widgets and CMake 3.21+.
|
||||||
|
- If Qt is installed in a custom location, pass `-DCMAKE_PREFIX_PATH=/path/to/Qt/6.x.x/<toolchain>` to CMake.
|
||||||
33
docs/PROGRESS.md
Normal file
33
docs/PROGRESS.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# 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
|
||||||
|
- Cross-platform build command guide in `docs/BUILDING.md`
|
||||||
|
|
||||||
|
Git:
|
||||||
|
- Branch: `milestone-0-restart-cpp`
|
||||||
|
- Tag: `v0-m0-done`
|
||||||
|
|
||||||
|
## Milestone 1 - Storage and CRUD
|
||||||
|
|
||||||
|
Status: Completed
|
||||||
|
|
||||||
|
Delivered:
|
||||||
|
- SQLite integration via Qt SQL (`QSQLITE`)
|
||||||
|
- Persistent profile database bootstrap (`profiles` table)
|
||||||
|
- Profiles CRUD (New / Edit / Delete) in `ProfilesWindow`
|
||||||
|
- Search-backed profile listing from storage
|
||||||
|
- Double-click connect opens `SessionWindow` tab with selected profile name
|
||||||
|
|
||||||
|
Git:
|
||||||
|
- Branch: `milestone-1-storage`
|
||||||
|
- Tag: `v0-m1-done`
|
||||||
13
src/main.cpp
Normal file
13
src/main.cpp
Normal 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();
|
||||||
|
}
|
||||||
163
src/profile_repository.cpp
Normal file
163
src/profile_repository.cpp
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
#include "profile_repository.h"
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QSqlDatabase>
|
||||||
|
#include <QSqlError>
|
||||||
|
#include <QSqlQuery>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
QString buildDatabasePath()
|
||||||
|
{
|
||||||
|
QString appDataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||||
|
if (appDataPath.isEmpty()) {
|
||||||
|
appDataPath = QDir::currentPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
QDir dataDir(appDataPath);
|
||||||
|
dataDir.mkpath(QStringLiteral("."));
|
||||||
|
|
||||||
|
return dataDir.filePath(QStringLiteral("orbithub_profiles.sqlite"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfileRepository::ProfileRepository() : m_connectionName(QStringLiteral("orbithub_main"))
|
||||||
|
{
|
||||||
|
if (!initializeDatabase()) {
|
||||||
|
QSqlDatabase::removeDatabase(m_connectionName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfileRepository::~ProfileRepository()
|
||||||
|
{
|
||||||
|
if (QSqlDatabase::contains(m_connectionName)) {
|
||||||
|
QSqlDatabase db = QSqlDatabase::database(m_connectionName);
|
||||||
|
if (db.isOpen()) {
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QSqlDatabase::removeDatabase(m_connectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProfileRepository::initError() const
|
||||||
|
{
|
||||||
|
return m_initError;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Profile> ProfileRepository::listProfiles(const QString& searchQuery) const
|
||||||
|
{
|
||||||
|
std::vector<Profile> result;
|
||||||
|
|
||||||
|
if (!QSqlDatabase::contains(m_connectionName)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery query(QSqlDatabase::database(m_connectionName));
|
||||||
|
if (searchQuery.trimmed().isEmpty()) {
|
||||||
|
query.prepare(QStringLiteral(
|
||||||
|
"SELECT id, name FROM profiles ORDER BY lower(name) ASC, id ASC"));
|
||||||
|
} else {
|
||||||
|
query.prepare(QStringLiteral(
|
||||||
|
"SELECT id, name FROM profiles "
|
||||||
|
"WHERE lower(name) LIKE lower(?) "
|
||||||
|
"ORDER BY lower(name) ASC, id ASC"));
|
||||||
|
query.addBindValue(QStringLiteral("%") + searchQuery.trimmed() + QStringLiteral("%"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!query.exec()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (query.next()) {
|
||||||
|
result.push_back(Profile{query.value(0).toLongLong(), query.value(1).toString()});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<Profile> ProfileRepository::createProfile(const QString& name) const
|
||||||
|
{
|
||||||
|
if (!QSqlDatabase::contains(m_connectionName)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString trimmedName = name.trimmed();
|
||||||
|
if (trimmedName.isEmpty()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery query(QSqlDatabase::database(m_connectionName));
|
||||||
|
query.prepare(QStringLiteral("INSERT INTO profiles(name) VALUES (?)"));
|
||||||
|
query.addBindValue(trimmedName);
|
||||||
|
|
||||||
|
if (!query.exec()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Profile{query.lastInsertId().toLongLong(), trimmedName};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProfileRepository::updateProfile(qint64 id, const QString& name) const
|
||||||
|
{
|
||||||
|
if (!QSqlDatabase::contains(m_connectionName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString trimmedName = name.trimmed();
|
||||||
|
if (trimmedName.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery query(QSqlDatabase::database(m_connectionName));
|
||||||
|
query.prepare(QStringLiteral("UPDATE profiles SET name = ? WHERE id = ?"));
|
||||||
|
query.addBindValue(trimmedName);
|
||||||
|
query.addBindValue(id);
|
||||||
|
|
||||||
|
if (!query.exec()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return query.numRowsAffected() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProfileRepository::deleteProfile(qint64 id) const
|
||||||
|
{
|
||||||
|
if (!QSqlDatabase::contains(m_connectionName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery query(QSqlDatabase::database(m_connectionName));
|
||||||
|
query.prepare(QStringLiteral("DELETE FROM profiles WHERE id = ?"));
|
||||||
|
query.addBindValue(id);
|
||||||
|
|
||||||
|
if (!query.exec()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return query.numRowsAffected() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProfileRepository::initializeDatabase()
|
||||||
|
{
|
||||||
|
QSqlDatabase database = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_connectionName);
|
||||||
|
database.setDatabaseName(buildDatabasePath());
|
||||||
|
|
||||||
|
if (!database.open()) {
|
||||||
|
m_initError = database.lastError().text();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery query(database);
|
||||||
|
const bool created = query.exec(QStringLiteral(
|
||||||
|
"CREATE TABLE IF NOT EXISTS profiles ("
|
||||||
|
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||||
|
"name TEXT NOT NULL UNIQUE"
|
||||||
|
")"));
|
||||||
|
|
||||||
|
if (!created) {
|
||||||
|
m_initError = query.lastError().text();
|
||||||
|
}
|
||||||
|
|
||||||
|
return created;
|
||||||
|
}
|
||||||
35
src/profile_repository.h
Normal file
35
src/profile_repository.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#ifndef ORBITHUB_PROFILE_REPOSITORY_H
|
||||||
|
#define ORBITHUB_PROFILE_REPOSITORY_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct Profile
|
||||||
|
{
|
||||||
|
qint64 id;
|
||||||
|
QString name;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ProfileRepository
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ProfileRepository();
|
||||||
|
~ProfileRepository();
|
||||||
|
|
||||||
|
QString initError() const;
|
||||||
|
|
||||||
|
std::vector<Profile> listProfiles(const QString& searchQuery = QString()) const;
|
||||||
|
std::optional<Profile> createProfile(const QString& name) const;
|
||||||
|
bool updateProfile(qint64 id, const QString& name) const;
|
||||||
|
bool deleteProfile(qint64 id) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_connectionName;
|
||||||
|
QString m_initError;
|
||||||
|
|
||||||
|
bool initializeDatabase();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
236
src/profiles_window.cpp
Normal file
236
src/profiles_window.cpp
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
#include "profiles_window.h"
|
||||||
|
|
||||||
|
#include "profile_repository.h"
|
||||||
|
#include "session_window.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <QAbstractItemView>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QInputDialog>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QListWidget>
|
||||||
|
#include <QListWidgetItem>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QVariant>
|
||||||
|
#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),
|
||||||
|
m_repository(std::make_unique<ProfileRepository>())
|
||||||
|
{
|
||||||
|
setWindowTitle(QStringLiteral("OrbitHub Profiles"));
|
||||||
|
resize(520, 620);
|
||||||
|
|
||||||
|
setupUi();
|
||||||
|
|
||||||
|
if (!m_repository->initError().isEmpty()) {
|
||||||
|
QMessageBox::critical(this,
|
||||||
|
QStringLiteral("Database Error"),
|
||||||
|
QStringLiteral("Failed to initialize SQLite database: %1")
|
||||||
|
.arg(m_repository->initError()));
|
||||||
|
m_newButton->setEnabled(false);
|
||||||
|
m_editButton->setEnabled(false);
|
||||||
|
m_deleteButton->setEnabled(false);
|
||||||
|
m_searchBox->setEnabled(false);
|
||||||
|
m_profilesList->setEnabled(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadProfiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfilesWindow::~ProfilesWindow() = default;
|
||||||
|
|
||||||
|
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) { loadProfiles(text); });
|
||||||
|
|
||||||
|
connect(m_profilesList,
|
||||||
|
&QListWidget::itemDoubleClicked,
|
||||||
|
this,
|
||||||
|
[this](QListWidgetItem* item) { openSessionForItem(item); });
|
||||||
|
|
||||||
|
connect(m_newButton, &QPushButton::clicked, this, [this]() { createProfile(); });
|
||||||
|
connect(m_editButton, &QPushButton::clicked, this, [this]() { editSelectedProfile(); });
|
||||||
|
connect(m_deleteButton, &QPushButton::clicked, this, [this]() { deleteSelectedProfile(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfilesWindow::loadProfiles(const QString& query)
|
||||||
|
{
|
||||||
|
m_profilesList->clear();
|
||||||
|
|
||||||
|
const std::vector<Profile> profiles = m_repository->listProfiles(query);
|
||||||
|
for (const Profile& profile : profiles) {
|
||||||
|
auto* item = new QListWidgetItem(profile.name, m_profilesList);
|
||||||
|
item->setData(Qt::UserRole, QVariant::fromValue(profile.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<qint64> ProfilesWindow::selectedProfileId() const
|
||||||
|
{
|
||||||
|
QListWidgetItem* item = m_profilesList->currentItem();
|
||||||
|
if (item == nullptr) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QVariant value = item->data(Qt::UserRole);
|
||||||
|
if (!value.isValid()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.toLongLong();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfilesWindow::createProfile()
|
||||||
|
{
|
||||||
|
bool accepted = false;
|
||||||
|
const QString name = QInputDialog::getText(this,
|
||||||
|
QStringLiteral("New Profile"),
|
||||||
|
QStringLiteral("Profile name:"),
|
||||||
|
QLineEdit::Normal,
|
||||||
|
QString(),
|
||||||
|
&accepted);
|
||||||
|
|
||||||
|
if (!accepted || name.trimmed().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_repository->createProfile(name).has_value()) {
|
||||||
|
QMessageBox::warning(this,
|
||||||
|
QStringLiteral("Create Profile"),
|
||||||
|
QStringLiteral("Failed to create profile. Names must be unique."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadProfiles(m_searchBox->text());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfilesWindow::editSelectedProfile()
|
||||||
|
{
|
||||||
|
const std::optional<qint64> profileId = selectedProfileId();
|
||||||
|
QListWidgetItem* currentItem = m_profilesList->currentItem();
|
||||||
|
if (!profileId.has_value() || currentItem == nullptr) {
|
||||||
|
QMessageBox::information(this,
|
||||||
|
QStringLiteral("Edit Profile"),
|
||||||
|
QStringLiteral("Select a profile first."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accepted = false;
|
||||||
|
const QString name = QInputDialog::getText(this,
|
||||||
|
QStringLiteral("Edit Profile"),
|
||||||
|
QStringLiteral("Profile name:"),
|
||||||
|
QLineEdit::Normal,
|
||||||
|
currentItem->text(),
|
||||||
|
&accepted);
|
||||||
|
|
||||||
|
if (!accepted || name.trimmed().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_repository->updateProfile(profileId.value(), name)) {
|
||||||
|
QMessageBox::warning(this,
|
||||||
|
QStringLiteral("Edit Profile"),
|
||||||
|
QStringLiteral("Failed to update profile. Names must be unique."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadProfiles(m_searchBox->text());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfilesWindow::deleteSelectedProfile()
|
||||||
|
{
|
||||||
|
const std::optional<qint64> profileId = selectedProfileId();
|
||||||
|
QListWidgetItem* currentItem = m_profilesList->currentItem();
|
||||||
|
if (!profileId.has_value() || currentItem == nullptr) {
|
||||||
|
QMessageBox::information(this,
|
||||||
|
QStringLiteral("Delete Profile"),
|
||||||
|
QStringLiteral("Select a profile first."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QMessageBox::StandardButton confirm = QMessageBox::question(
|
||||||
|
this,
|
||||||
|
QStringLiteral("Delete Profile"),
|
||||||
|
QStringLiteral("Delete profile '%1'?").arg(currentItem->text()),
|
||||||
|
QMessageBox::Yes | QMessageBox::No,
|
||||||
|
QMessageBox::No);
|
||||||
|
|
||||||
|
if (confirm != QMessageBox::Yes) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_repository->deleteProfile(profileId.value())) {
|
||||||
|
QMessageBox::warning(this,
|
||||||
|
QStringLiteral("Delete Profile"),
|
||||||
|
QStringLiteral("Failed to delete profile."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadProfiles(m_searchBox->text());
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
46
src/profiles_window.h
Normal file
46
src/profiles_window.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#ifndef ORBITHUB_PROFILES_WINDOW_H
|
||||||
|
#define ORBITHUB_PROFILES_WINDOW_H
|
||||||
|
|
||||||
|
#include <QMainWindow>
|
||||||
|
#include <QString>
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class QListWidget;
|
||||||
|
class QListWidgetItem;
|
||||||
|
class QLineEdit;
|
||||||
|
class QPushButton;
|
||||||
|
class SessionWindow;
|
||||||
|
class ProfileRepository;
|
||||||
|
|
||||||
|
class ProfilesWindow : public QMainWindow
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ProfilesWindow(QWidget* parent = nullptr);
|
||||||
|
~ProfilesWindow() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QLineEdit* m_searchBox;
|
||||||
|
QListWidget* m_profilesList;
|
||||||
|
QPushButton* m_newButton;
|
||||||
|
QPushButton* m_editButton;
|
||||||
|
QPushButton* m_deleteButton;
|
||||||
|
std::vector<QPointer<SessionWindow>> m_sessionWindows;
|
||||||
|
std::unique_ptr<ProfileRepository> m_repository;
|
||||||
|
|
||||||
|
void setupUi();
|
||||||
|
void loadProfiles(const QString& query = QString());
|
||||||
|
std::optional<qint64> selectedProfileId() const;
|
||||||
|
void createProfile();
|
||||||
|
void editSelectedProfile();
|
||||||
|
void deleteSelectedProfile();
|
||||||
|
void openSessionForItem(QListWidgetItem* item);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
45
src/session_window.cpp
Normal file
45
src/session_window.cpp
Normal 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
22
src/session_window.h
Normal 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
|
||||||
Reference in New Issue
Block a user