diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index f51f5ff..d226a8c 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -12,12 +12,14 @@ #include #include #include +#include #include #include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), m_trayIcon(nullptr), m_authManager(new AuthManager(this)), m_settingsDialog(nullptr), - m_authDialog(nullptr), m_data(nullptr), m_networkStreamModel(nullptr), m_particleListModel(nullptr) + m_authDialog(nullptr), m_data(nullptr), m_networkStreamModel(nullptr), m_particleListModel(nullptr), + m_selectedParticleRow(-1) { m_authDialog = new AuthDialog(m_authManager, this); @@ -25,6 +27,51 @@ MainWindow::MainWindow(QWidget *parent) ui->setupUi(this); // this->setWindowFlags(Qt::FramelessWindowHint); + // Style for grey status bar text + QString statusBarLabelStyle = "QLabel { color: #808080; }"; + QString pingLabelStyle = "QLabel { color: #00AA00; }"; // Green for ping + + m_currentPathLabel = new QLabel(this); + m_currentPathLabel->setMargin(5); + m_currentPathLabel->setStyleSheet(statusBarLabelStyle); + QFont font = QFont(); + font.setStyle(QFont::StyleItalic); + m_currentPathLabel->setFont(font); + ui->statusbar->addWidget(m_currentPathLabel); + + // Add spacer to push right-aligned items to the right + ui->statusbar->addPermanentWidget(new QLabel(""), 1); + + // Ping indicator (green text) + m_pingLabel = new QLabel(this); + m_pingLabel->setMargin(5); + m_pingLabel->setMinimumWidth(100); + m_pingLabel->setAlignment(Qt::AlignCenter); + m_pingLabel->setStyleSheet(pingLabelStyle); + ui->statusbar->addPermanentWidget(m_pingLabel); + + // Position indicator (vim-style) + m_positionLabel = new QLabel(this); + m_positionLabel->setMargin(5); + m_positionLabel->setMinimumWidth(50); + m_positionLabel->setAlignment(Qt::AlignCenter); + m_positionLabel->setStyleSheet(statusBarLabelStyle); + ui->statusbar->addPermanentWidget(m_positionLabel); + + // Storage indicator + m_storageLabel = new QLabel(this); + m_storageLabel->setMargin(5); + m_storageLabel->setMinimumWidth(60); + m_storageLabel->setAlignment(Qt::AlignCenter); + m_storageLabel->setStyleSheet(statusBarLabelStyle); + ui->statusbar->addPermanentWidget(m_storageLabel); + + // Setup ping timer to oscillate every 500ms + m_pingTimer = new QTimer(this); + connect(m_pingTimer, &QTimer::timeout, this, &MainWindow::updatePingIndicator); + m_pingTimer->start(500); + updatePingIndicator(); + ui->splitter->setStretchFactor(0, 1); ui->splitter->setStretchFactor(1, 1); ui->splitter->setStretchFactor(2, 2); @@ -126,21 +173,15 @@ void MainWindow::setupModels() ui->particlesView->setTextElideMode(Qt::ElideRight); // Connect selection changes - connect(ui->topLevelParticlesView->selectionModel(), - &QItemSelectionModel::selectionChanged, - this, + connect(ui->topLevelParticlesView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::onTreeSelectionChanged); // Connect particle selection changes - connect(ui->particlesView->selectionModel(), - &QItemSelectionModel::selectionChanged, - this, + connect(ui->particlesView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::onParticleSelectionChanged); } -void MainWindow::onTreeSelectionChanged( - const QItemSelection &selected, - const QItemSelection &deselected) +void MainWindow::onTreeSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { Q_UNUSED(deselected); @@ -156,6 +197,12 @@ void MainWindow::onTreeSelectionChanged( { QString streamId = m_networkStreamModel->streamIdFromIndex(index); m_particleListModel->setStreamId(streamId); + m_selectedStreamId = streamId; + + // Also track the parent network + QModelIndex parentIndex = m_networkStreamModel->parent(index); + QString networkId = m_networkStreamModel->networkIdFromIndex(parentIndex); + m_selectedNetworkName = networkId; // Auto-select first unseen particle (or first particle if all seen) int unseenRow = m_particleListModel->firstUnseenRow(); @@ -166,32 +213,149 @@ void MainWindow::onTreeSelectionChanged( } else if (m_particleListModel->rowCount() > 0) { - ui->particlesView->setCurrentIndex(m_particleListModel->index(0, 0)); + ui->particlesView->setCurrentIndex(m_particleListModel->index(m_particleListModel->rowCount() - 1, 0)); ui->particlesView->setFocus(); } } else // network selected { m_particleListModel->setStreamId(QString()); + m_selectedStreamId.clear(); + m_selectedParticleRow = -1; + + QString networkId = m_networkStreamModel->networkIdFromIndex(index); + m_selectedNetworkName = networkId; } + + updateStatusBar(); } -void MainWindow::onParticleSelectionChanged( - const QItemSelection &selected, - const QItemSelection &deselected) +void MainWindow::onParticleSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { Q_UNUSED(deselected); if (selected.indexes().isEmpty()) { ui->label->setText(""); + ui->label_2->setText(""); + m_selectedParticleId.clear(); + m_selectedParticleRow = -1; + updateStatusBar(); return; } QModelIndex index = selected.indexes().first(); + m_selectedParticleRow = index.row(); const Particle *particle = m_particleListModel->particleAtRow(index.row()); if (particle) { ui->label->setText(particle->summary); + ui->label_2->setText(particle->createdBy); + m_selectedParticleId = particle->id; + } + + updateStatusBar(); +} + +void MainWindow::updateStatusBar() +{ + QStringList pathComponents; + + // Add network if selected + if (!m_selectedNetworkName.isEmpty()) + { + for (const auto &network : m_data->networks()) + { + if (network.id == m_selectedNetworkName) + { + pathComponents << network.name; + break; + } + } + } + + // Add stream if selected + if (!m_selectedStreamId.isEmpty() && !m_selectedNetworkName.isEmpty()) + { + const QList &streams = m_data->streamsForNetwork(m_selectedNetworkName); + for (const auto &stream : streams) + { + if (stream.id == m_selectedStreamId) + { + pathComponents << stream.name; + break; + } + } + } + + // Add particle if selected + if (!m_selectedParticleId.isEmpty() && !m_selectedStreamId.isEmpty()) + { + const QList &particles = m_data->particlesForStream(m_selectedStreamId); + for (const auto &particle : particles) + { + if (particle.id == m_selectedParticleId) + { + pathComponents << particle.id; + break; + } + } + } + + m_currentPathLabel->setText(pathComponents.join(" > ")); + + // Update position indicator (vim-style) + if (!m_selectedStreamId.isEmpty()) + { + int totalCount = m_particleListModel->rowCount(); + if (totalCount == 0) + { + m_positionLabel->setText(""); + } + else if (totalCount == 1) + { + m_positionLabel->setText("All"); + } + else if (m_selectedParticleRow == 0) + { + m_positionLabel->setText("Top"); + } + else if (m_selectedParticleRow == totalCount - 1) + { + m_positionLabel->setText("Bot"); + } + else if (m_selectedParticleRow >= 0) + { + int percent = (m_selectedParticleRow * 100) / (totalCount - 1); + m_positionLabel->setText(QString::number(percent) + "%"); + } + else + { + m_positionLabel->setText(""); + } + + // Update storage indicator with pseudo-random size based on stream ID + uint hash = qHash(m_selectedStreamId); + int sizeKB = 50 + (hash % 950); // Random size between 50-1000 kB + m_storageLabel->setText(QString::number(sizeKB) + " kB"); + } + else + { + m_positionLabel->setText(""); + m_storageLabel->setText(""); } } + +void MainWindow::updatePingIndicator() +{ + // Oscillate ping between 15-35ms + static int basePing = 23; + int variation = (QRandomGenerator::global()->bounded(21)) - 10; // -10 to +10 + int currentPing = basePing + variation; + if (currentPing < 15) + currentPing = 15; + if (currentPing > 35) + currentPing = 35; + + m_pingLabel->setText("connected " + QString::number(currentPing) + "ms"); +} diff --git a/src/MainWindow.h b/src/MainWindow.h index 8ead63e..e3d5fe9 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -14,6 +14,7 @@ QT_BEGIN_NAMESPACE class QSystemTrayIcon; class AuthManager; class QItemSelection; +class QLabel; QT_END_NAMESPACE class AuthDialog; @@ -57,6 +58,18 @@ private: Data *m_data; NetworkStreamModel *m_networkStreamModel; ParticleListModel *m_particleListModel; + + void updateStatusBar(); + void updatePingIndicator(); + QString m_selectedParticleId; + QString m_selectedNetworkName; + QString m_selectedStreamId; + int m_selectedParticleRow; + QLabel *m_currentPathLabel; + QLabel *m_positionLabel; + QLabel *m_storageLabel; + QLabel *m_pingLabel; + QTimer *m_pingTimer; }; #endif // LLINK_MAINWINDOW_H diff --git a/src/networkstreammodel.cpp b/src/networkstreammodel.cpp index 8373e56..c4ab792 100644 --- a/src/networkstreammodel.cpp +++ b/src/networkstreammodel.cpp @@ -1,7 +1,6 @@ #include "networkstreammodel.h" -NetworkStreamModel::NetworkStreamModel(Data *data, QObject *parent) - : QAbstractItemModel(parent), m_data(data) +NetworkStreamModel::NetworkStreamModel(Data *data, QObject *parent) : QAbstractItemModel(parent), m_data(data) { } @@ -82,7 +81,7 @@ int NetworkStreamModel::rowCount(const QModelIndex &parent) const int NetworkStreamModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); - return 2; // Column 0: name, Column 1: unseen count + return 2; // Column 0: name, Column 1: unseen count } QVariant NetworkStreamModel::data(const QModelIndex &index, int role) const @@ -103,7 +102,7 @@ QVariant NetworkStreamModel::data(const QModelIndex &index, int role) const int actualNetworkIndex = static_cast(id & 0xFFFFFFFF); if (actualNetworkIndex >= 0 && actualNetworkIndex < m_data->networks().size()) { - return m_data->networks().at(actualNetworkIndex).name; + return m_data->networks().at(actualNetworkIndex).name.toUpper(); } } // Networks don't show unseen count in column 1 @@ -179,6 +178,29 @@ QString NetworkStreamModel::streamIdFromIndex(const QModelIndex &index) const return QString(); } +QString NetworkStreamModel::networkIdFromIndex(const QModelIndex &index) const +{ + if (!index.isValid()) + { + return QString(); + } + + quintptr id = index.internalId(); + int networkIndex = static_cast((id >> 32) & 0xFFFFFFFF); + + // Check if this is a stream item (not a network) + if (networkIndex == 0xFFFFFFFF) + { + int actualNetworkIndex = static_cast(id & 0xFFFFFFFF); + if (actualNetworkIndex >= 0 && actualNetworkIndex < m_data->networks().size()) + { + return m_data->networks().at(actualNetworkIndex).id; + } + } + + return QString(); +} + bool NetworkStreamModel::isStreamItem(const QModelIndex &index) const { if (!index.isValid()) diff --git a/src/networkstreammodel.h b/src/networkstreammodel.h index 8ed5cf1..a808558 100644 --- a/src/networkstreammodel.h +++ b/src/networkstreammodel.h @@ -21,6 +21,7 @@ public: // Helper methods for MainWindow QString streamIdFromIndex(const QModelIndex &index) const; bool isStreamItem(const QModelIndex &index) const; + QString networkIdFromIndex(const QModelIndex &index) const; private: Data *m_data;