feat: show fake data network, stream, and particles data

This commit is contained in:
talksik
2026-01-28 08:26:30 -08:00
parent 5b271ab623
commit dfcd366f9d
11 changed files with 750 additions and 74 deletions
+6
View File
@@ -29,6 +29,12 @@ qt_add_executable(llink
src/authdialog.ui
src/authdialog.cpp
src/settingsdialog.h src/settingsdialog.cpp src/settingsdialog.ui
src/data.h
src/data.cpp
src/networkstreammodel.h
src/networkstreammodel.cpp
src/particlelistmodel.h
src/particlelistmodel.cpp
)
qt_add_resources(llink
+31 -31
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 16.0.2, 2026-01-27T16:56:03. -->
<!-- Written by QtCreator 16.0.2, 2026-01-28T08:11:26. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
@@ -106,15 +106,15 @@
<value type="int" key="CMake.Configure.BaseEnvironment">2</value>
<value type="bool" key="CMake.Configure.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="CMake.Configure.UserEnvironmentChanges"/>
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
-DCMAKE_BUILD_TYPE:STRING=Debug
-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
-DCMAKE_GENERATOR:STRING=Ninja
-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
-DCMAKE_BUILD_TYPE:STRING=Debug
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON</value>
-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}</value>
<value type="int" key="EnableQmlDebugging">0</value>
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/Users/talksik/Documents/code/llink/build/Qt_6_8_6_for_macOS-Debug</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
@@ -164,15 +164,15 @@
<value type="int" key="CMake.Configure.BaseEnvironment">2</value>
<value type="bool" key="CMake.Configure.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="CMake.Configure.UserEnvironmentChanges"/>
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
-DCMAKE_BUILD_TYPE:STRING=Release
-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
-DCMAKE_GENERATOR:STRING=Ninja
-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
-DCMAKE_BUILD_TYPE:STRING=Release
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON</value>
-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}</value>
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/Users/talksik/Documents/code/llink/build/Qt_6_8_6_for_macOS-Release</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
@@ -219,15 +219,15 @@
<value type="int" key="CMake.Configure.BaseEnvironment">2</value>
<value type="bool" key="CMake.Configure.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="CMake.Configure.UserEnvironmentChanges"/>
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
-DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo
-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
-DCMAKE_GENERATOR:STRING=Ninja
-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
-DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON</value>
-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}</value>
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/Users/talksik/Documents/code/llink/build/Qt_6_8_6_for_macOS-RelWithDebInfo</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
@@ -274,15 +274,15 @@
<value type="int" key="CMake.Configure.BaseEnvironment">2</value>
<value type="bool" key="CMake.Configure.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="CMake.Configure.UserEnvironmentChanges"/>
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
-DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo
-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
-DCMAKE_GENERATOR:STRING=Ninja
-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
-DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON</value>
-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}</value>
<value type="int" key="EnableQmlDebugging">0</value>
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/Users/talksik/Documents/code/llink/build/Qt_6_8_6_for_macOS-Profile</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
@@ -330,15 +330,15 @@
<value type="int" key="CMake.Configure.BaseEnvironment">2</value>
<value type="bool" key="CMake.Configure.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="CMake.Configure.UserEnvironmentChanges"/>
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
-DCMAKE_BUILD_TYPE:STRING=MinSizeRel
-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
-DCMAKE_GENERATOR:STRING=Ninja
-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
-DCMAKE_BUILD_TYPE:STRING=MinSizeRel
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON</value>
-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}</value>
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/Users/talksik/Documents/code/llink/build/Qt_6_8_6_for_macOS-MinSizeRel</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
+71 -1
View File
@@ -8,6 +8,8 @@
#include "settingsdialog.h"
#include "ui_mainwindow.h"
#include <QApplication>
#include <QHeaderView>
#include <QItemSelectionModel>
#include <QLabel>
#include <QMenu>
#include <QSystemTrayIcon>
@@ -15,7 +17,7 @@
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), m_trayIcon(nullptr), m_authManager(new AuthManager(this)), m_settingsDialog(nullptr),
m_authDialog(nullptr)
m_authDialog(nullptr), m_data(nullptr), m_networkStreamModel(nullptr), m_particleListModel(nullptr)
{
m_authDialog = new AuthDialog(m_authManager, this);
@@ -27,6 +29,8 @@ MainWindow::MainWindow(QWidget *parent)
ui->splitter->setStretchFactor(1, 1);
ui->splitter->setStretchFactor(2, 2);
setupModels();
m_authDialog->setModal(true);
QTimer::singleShot(3000, this, [this]() {
@@ -95,3 +99,69 @@ void MainWindow::on_settingsButton_clicked()
}
m_settingsDialog->show();
}
void MainWindow::setupModels()
{
// Create data with fake data
m_data = new Data(this);
// Create and set tree model
m_networkStreamModel = new NetworkStreamModel(m_data, this);
ui->topLevelParticlesView->setModel(m_networkStreamModel);
ui->topLevelParticlesView->setHeaderHidden(true);
ui->topLevelParticlesView->expandAll();
// Configure column behavior: name stretches, unseen count is fixed width
ui->topLevelParticlesView->header()->setStretchLastSection(false);
ui->topLevelParticlesView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
ui->topLevelParticlesView->header()->setSectionResizeMode(1, QHeaderView::Fixed);
ui->topLevelParticlesView->setColumnWidth(1, 40);
// Create and set list model
m_particleListModel = new ParticleListModel(m_data, this);
ui->particlesView->setModel(m_particleListModel);
// Connect selection changes
connect(ui->topLevelParticlesView->selectionModel(),
&QItemSelectionModel::selectionChanged,
this,
&MainWindow::onTreeSelectionChanged);
}
void MainWindow::onTreeSelectionChanged(
const QItemSelection &selected,
const QItemSelection &deselected)
{
Q_UNUSED(deselected);
if (selected.indexes().isEmpty())
{
m_particleListModel->setStreamId(QString());
return;
}
QModelIndex index = selected.indexes().first();
if (m_networkStreamModel->isStreamItem(index))
{
QString streamId = m_networkStreamModel->streamIdFromIndex(index);
m_particleListModel->setStreamId(streamId);
// Auto-select first unseen particle (or first particle if all seen)
int unseenRow = m_particleListModel->firstUnseenRow();
if (unseenRow >= 0)
{
ui->particlesView->setCurrentIndex(m_particleListModel->index(unseenRow, 0));
ui->particlesView->setFocus();
}
else if (m_particleListModel->rowCount() > 0)
{
ui->particlesView->setCurrentIndex(m_particleListModel->index(0, 0));
ui->particlesView->setFocus();
}
}
else
{
m_particleListModel->setStreamId(QString());
}
}
+12
View File
@@ -6,10 +6,14 @@
#define LLINK_MAINWINDOW_H
#include <QMainWindow>
#include "data.h"
#include "networkstreammodel.h"
#include "particlelistmodel.h"
QT_BEGIN_NAMESPACE
class QSystemTrayIcon;
class AuthManager;
class QItemSelection;
QT_END_NAMESPACE
class AuthDialog;
@@ -33,8 +37,11 @@ public:
void on_settingsButton_clicked();
void onTreeSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
private:
void setupTrayIcon();
void setupModels();
QSystemTrayIcon *m_trayIcon;
Ui::MainWindow *ui;
@@ -43,6 +50,11 @@ public:
AuthDialog *m_authDialog;
SettingsDialog *m_settingsDialog;
// Data and models
Data *m_data;
NetworkStreamModel *m_networkStreamModel;
ParticleListModel *m_particleListModel;
};
#endif //LLINK_MAINWINDOW_H
+142
View File
@@ -0,0 +1,142 @@
#include "data.h"
Data::Data(QObject *parent) : QObject(parent)
{
initializeFakeData();
}
void Data::initializeFakeData()
{
// Network 1: Private
Network net1;
net1.id = "net-private";
net1.name = "Private";
net1.openStreamCount = 2;
net1.openStreamCapacity = 5;
net1.countHumans = 1;
m_networks.append(net1);
// Stream: Personal Notes
Stream stream1;
stream1.id = "stream-notes";
stream1.name = "Personal Notes";
stream1.status = Stream::OPEN;
stream1.unseenCount = 3;
m_streams[net1.id].append(stream1);
// Particles for Personal Notes
m_particles[stream1.id].append({"p1", "text", true});
m_particles[stream1.id].append({"p2", "link", false});
m_particles[stream1.id].append({"p3", "image", true});
m_particles[stream1.id].append({"p4", "text", false});
m_particles[stream1.id].append({"p5", "link", true});
// Stream: Quick Captures
Stream stream2;
stream2.id = "stream-captures";
stream2.name = "Quick Captures";
stream2.status = Stream::OPEN;
stream2.unseenCount = 0;
m_streams[net1.id].append(stream2);
// Particles for Quick Captures
m_particles[stream2.id].append({"p6", "text", true});
m_particles[stream2.id].append({"p7", "image", true});
// Network 2: Flowy Labs, Inc
Network net2;
net2.id = "net-flowy";
net2.name = "Flowy Labs, Inc";
net2.openStreamCount = 3;
net2.openStreamCapacity = 10;
net2.countHumans = 12;
m_networks.append(net2);
// Stream: Engineering
Stream stream3;
stream3.id = "stream-eng";
stream3.name = "wild-shape";
stream3.status = Stream::OPEN;
stream3.unseenCount = 5;
m_streams[net2.id].append(stream3);
// Particles for Engineering
m_particles[stream3.id].append({"p8", "text", false});
m_particles[stream3.id].append({"p9", "link", true});
m_particles[stream3.id].append({"p10", "text", false});
m_particles[stream3.id].append({"p11", "image", true});
m_particles[stream3.id].append({"p12", "text", false});
m_particles[stream3.id].append({"p13", "link", false});
m_particles[stream3.id].append({"p14", "text", true});
m_particles[stream3.id].append({"p15", "image", false});
// Stream: Design
Stream stream4;
stream4.id = "stream-design";
stream4.name = "arch-rival";
stream4.status = Stream::OPEN;
stream4.unseenCount = 2;
m_streams[net2.id].append(stream4);
// Particles for Design
m_particles[stream4.id].append({"p16", "image", false});
m_particles[stream4.id].append({"p17", "text", true});
m_particles[stream4.id].append({"p18", "link", false});
m_particles[stream4.id].append({"p19", "image", true});
// Stream: Archive
Stream stream5;
stream5.id = "stream-archive";
stream5.name = "anthem-project";
stream5.status = Stream::CLOSED;
stream5.unseenCount = 0;
m_streams[net2.id].append(stream5);
// Particles for Archive
for (int i = 20; i < 35; i++)
{
m_particles[stream5.id].append({QString("p%1").arg(i), "text", true});
}
}
const QList<Network>& Data::networks() const
{
return m_networks;
}
const QList<Stream>& Data::streamsForNetwork(const QString &networkId) const
{
static QList<Stream> emptyList;
auto it = m_streams.find(networkId);
if (it == m_streams.end())
{
return emptyList;
}
return it.value();
}
const QList<Particle>& Data::particlesForStream(const QString &streamId) const
{
static QList<Particle> emptyList;
auto it = m_particles.find(streamId);
if (it == m_particles.end())
{
return emptyList;
}
return it.value();
}
int Data::networkCount() const
{
return m_networks.size();
}
int Data::streamCount(const QString &networkId) const
{
return m_streams.value(networkId).size();
}
int Data::particleCount(const QString &streamId) const
{
return m_particles.value(streamId).size();
}
+66
View File
@@ -0,0 +1,66 @@
#ifndef DATA_H
#define DATA_H
#include <QObject>
#include <QMap>
struct Network
{
QString id;
QString name;
int openStreamCount;
int openStreamCapacity;
int countHumans;
};
struct Stream
{
QString id;
QString name;
enum StreamStatus
{
OPEN,
CLOSED,
};
StreamStatus status;
int unseenCount;
};
struct Particle
{
QString id;
QString type;
bool seen;
};
class Data : public QObject
{
Q_OBJECT
public:
explicit Data(QObject *parent = nullptr);
// Accessors for models
const QList<Network>& networks() const;
const QList<Stream>& streamsForNetwork(const QString &networkId) const;
const QList<Particle>& particlesForStream(const QString &streamId) const;
int networkCount() const;
int streamCount(const QString &networkId) const;
int particleCount(const QString &streamId) const;
signals:
void networksChanged();
void streamsChanged(const QString &networkId);
void particlesChanged(const QString &streamId);
private:
void initializeFakeData();
QList<Network> m_networks;
// Network -> Streams
QMap<QString, QList<Stream>> m_streams;
// Stream -> Particles
QMap<QString, QList<Particle>> m_particles;
};
#endif // DATA_H
+95 -42
View File
@@ -14,7 +14,7 @@
<string>Flowy.llink</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
@@ -41,61 +41,114 @@
<property name="minimumSize">
<size>
<width>0</width>
<height>28</height>
<height>20</height>
</size>
</property>
<widget class="QToolButton" name="exitButton">
<property name="geometry">
<rect>
<x>770</x>
<y>0</y>
<width>26</width>
<height>22</height>
</rect>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="text">
<string>...</string>
<property name="leftMargin">
<number>0</number>
</property>
<property name="icon">
<iconset theme="QIcon::ThemeIcon::ApplicationExit"/>
<property name="topMargin">
<number>0</number>
</property>
</widget>
<widget class="QToolButton" name="settingsButton">
<property name="geometry">
<rect>
<x>740</x>
<y>0</y>
<width>26</width>
<height>22</height>
</rect>
<property name="rightMargin">
<number>0</number>
</property>
<property name="text">
<string>...</string>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="icon">
<iconset theme="QIcon::ThemeIcon::HelpAbout"/>
</property>
</widget>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="settingsButton">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset theme="QIcon::ThemeIcon::HelpAbout"/>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="exitButton">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset theme="QIcon::ThemeIcon::ApplicationExit"/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QSplitter" name="splitter">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<widget class="QTreeView" name="topLevelParticlesView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<widget class="QWidget" name="widget_2" native="true">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTreeView" name="topLevelParticlesView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>New stream</string>
</property>
<property name="icon">
<iconset theme="QIcon::ThemeIcon::ListAdd"/>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_2">
<property name="text">
<string>Create network</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QListView" name="particlesView">
<property name="sizePolicy">
+194
View File
@@ -0,0 +1,194 @@
#include "networkstreammodel.h"
NetworkStreamModel::NetworkStreamModel(Data *data, QObject *parent)
: QAbstractItemModel(parent), m_data(data)
{
}
QModelIndex NetworkStreamModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent))
{
return QModelIndex();
}
if (!parent.isValid())
{
// Top-level item (Network)
// Encode: networkIndex in lower 32 bits, -1 in upper 32 bits
quintptr id = static_cast<quintptr>(row) | (static_cast<quintptr>(0xFFFFFFFF) << 32);
return createIndex(row, column, id);
}
else
{
// Child item (Stream)
// Decode parent to get network index
quintptr parentId = parent.internalId();
int networkIndex = static_cast<int>(parentId & 0xFFFFFFFF);
// Encode: streamIndex in lower 32 bits, networkIndex in upper 32 bits
quintptr id = static_cast<quintptr>(row) | (static_cast<quintptr>(networkIndex) << 32);
return createIndex(row, column, id);
}
}
QModelIndex NetworkStreamModel::parent(const QModelIndex &child) const
{
if (!child.isValid())
{
return QModelIndex();
}
quintptr id = child.internalId();
int networkIndex = static_cast<int>((id >> 32) & 0xFFFFFFFF);
// If networkIndex is 0xFFFFFFFF, this is a network item (no parent)
if (networkIndex == 0xFFFFFFFF)
{
return QModelIndex();
}
// This is a stream item, return its parent network
quintptr parentId = static_cast<quintptr>(networkIndex) | (static_cast<quintptr>(0xFFFFFFFF) << 32);
return createIndex(networkIndex, 0, parentId);
}
int NetworkStreamModel::rowCount(const QModelIndex &parent) const
{
if (!parent.isValid())
{
// Root level - return number of networks
return m_data->networkCount();
}
quintptr id = parent.internalId();
int networkIndex = static_cast<int>((id >> 32) & 0xFFFFFFFF);
// If this is a network item, return number of streams
if (networkIndex == 0xFFFFFFFF)
{
int actualNetworkIndex = static_cast<int>(id & 0xFFFFFFFF);
if (actualNetworkIndex >= 0 && actualNetworkIndex < m_data->networks().size())
{
const QString &networkId = m_data->networks().at(actualNetworkIndex).id;
return m_data->streamCount(networkId);
}
}
// Stream items have no children
return 0;
}
int NetworkStreamModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return 2; // Column 0: name, Column 1: unseen count
}
QVariant NetworkStreamModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
{
return QVariant();
}
quintptr id = index.internalId();
int networkIndex = static_cast<int>((id >> 32) & 0xFFFFFFFF);
if (networkIndex == 0xFFFFFFFF)
{
// This is a network item
if (role == Qt::DisplayRole && index.column() == 0)
{
int actualNetworkIndex = static_cast<int>(id & 0xFFFFFFFF);
if (actualNetworkIndex >= 0 && actualNetworkIndex < m_data->networks().size())
{
return m_data->networks().at(actualNetworkIndex).name;
}
}
// Networks don't show unseen count in column 1
return QVariant();
}
else
{
// This is a stream item
int streamIndex = static_cast<int>(id & 0xFFFFFFFF);
if (networkIndex >= 0 && networkIndex < m_data->networks().size())
{
const QString &networkId = m_data->networks().at(networkIndex).id;
const QList<Stream> &streams = m_data->streamsForNetwork(networkId);
if (streamIndex >= 0 && streamIndex < streams.size())
{
const Stream &stream = streams.at(streamIndex);
if (role == Qt::DisplayRole)
{
if (index.column() == 0)
{
// Column 0: stream name
return stream.name;
}
else if (index.column() == 1)
{
// Column 1: unseen count (only show if > 0)
if (stream.unseenCount > 0)
{
return stream.unseenCount;
}
}
}
else if (role == Qt::TextAlignmentRole && index.column() == 1)
{
// Right-align the unseen count
return static_cast<int>(Qt::AlignRight | Qt::AlignVCenter);
}
}
}
}
return QVariant();
}
QString NetworkStreamModel::streamIdFromIndex(const QModelIndex &index) const
{
if (!index.isValid())
{
return QString();
}
quintptr id = index.internalId();
int networkIndex = static_cast<int>((id >> 32) & 0xFFFFFFFF);
// Check if this is a stream item (not a network)
if (networkIndex != 0xFFFFFFFF)
{
int streamIndex = static_cast<int>(id & 0xFFFFFFFF);
if (networkIndex >= 0 && networkIndex < m_data->networks().size())
{
const QString &networkId = m_data->networks().at(networkIndex).id;
const QList<Stream> &streams = m_data->streamsForNetwork(networkId);
if (streamIndex >= 0 && streamIndex < streams.size())
{
return streams.at(streamIndex).id;
}
}
}
return QString();
}
bool NetworkStreamModel::isStreamItem(const QModelIndex &index) const
{
if (!index.isValid())
{
return false;
}
quintptr id = index.internalId();
int networkIndex = static_cast<int>((id >> 32) & 0xFFFFFFFF);
// If networkIndex is not 0xFFFFFFFF, this is a stream item
return networkIndex != 0xFFFFFFFF;
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef NETWORKSTREAMMODEL_H
#define NETWORKSTREAMMODEL_H
#include <QAbstractItemModel>
#include "data.h"
class NetworkStreamModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit NetworkStreamModel(Data *data, QObject *parent = nullptr);
// Required QAbstractItemModel interface
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &child) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
// Helper methods for MainWindow
QString streamIdFromIndex(const QModelIndex &index) const;
bool isStreamItem(const QModelIndex &index) const;
private:
Data *m_data;
};
#endif // NETWORKSTREAMMODEL_H
+79
View File
@@ -0,0 +1,79 @@
#include "particlelistmodel.h"
ParticleListModel::ParticleListModel(Data *data, QObject *parent)
: QAbstractListModel(parent), m_data(data), m_currentStreamId()
{
}
int ParticleListModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
{
return 0;
}
if (m_currentStreamId.isEmpty())
{
return 0;
}
return m_data->particleCount(m_currentStreamId);
}
QVariant ParticleListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || role != Qt::DisplayRole)
{
return QVariant();
}
if (m_currentStreamId.isEmpty())
{
return QVariant();
}
const QList<Particle> &particles = m_data->particlesForStream(m_currentStreamId);
if (index.row() < 0 || index.row() >= particles.size())
{
return QVariant();
}
const Particle &particle = particles.at(index.row());
// Show dot (•) for unseen, nothing for seen
if (particle.seen)
{
return particle.type;
}
else
{
return QString("%1 •").arg(particle.type);
}
}
void ParticleListModel::setStreamId(const QString &streamId)
{
beginResetModel();
m_currentStreamId = streamId;
endResetModel();
}
int ParticleListModel::firstUnseenRow() const
{
if (m_currentStreamId.isEmpty())
{
return -1;
}
const QList<Particle> &particles = m_data->particlesForStream(m_currentStreamId);
for (int i = 0; i < particles.size(); ++i)
{
if (!particles.at(i).seen)
{
return i;
}
}
return -1; // All seen or empty
}
+25
View File
@@ -0,0 +1,25 @@
#ifndef PARTICLELISTMODEL_H
#define PARTICLELISTMODEL_H
#include <QAbstractListModel>
#include "data.h"
class ParticleListModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit ParticleListModel(Data *data, QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
void setStreamId(const QString &streamId);
int firstUnseenRow() const; // Returns -1 if none unseen
private:
Data *m_data;
QString m_currentStreamId;
};
#endif // PARTICLELISTMODEL_H