6.4 KiB
StreamHub Qt Client Implementation Plan
For agentic workers: implement task-by-task. Each task ends with a buildable deliverable. Build with
cmake -B build && cmake --build build -jfromClient/streamhub-qt/.
Goal: Native Qt5/6 oscilloscope client equivalent to the ImGui
Client/streamhub/ client, same StreamHub WS protocol.
Architecture: Qt Widgets + custom QPainter plot widget + QtWebSockets.
Reuse ../streamhub/Protocol.{h,cpp} and SignalBuffer.h verbatim. Single GUI
thread; QWebSocket signals drive a model; 60 Hz QTimer repaints plots.
Tech Stack: C++17, Qt5 or Qt6 (autodetect), CMake ≥3.16.
Global Constraints
- Must compile against Qt5 and Qt6 — use
Qt${QT_VERSION_MAJOR}::targets, no version-specific APIs without a guard. - Do not modify
Client/streamhub/(reference). Reuse itsProtocol.cpp,Protocol.h,SignalBuffer.hby include path / direct source compile. - Domain model uses
QColor(notImVec4). - Reuse
LTTBDecimateandSignalBufferfromSignalBuffer.h.
Task 1: Project scaffold + CMake autodetect + empty MainWindow
Files: Create CMakeLists.txt, main.cpp, MainWindow.{h,cpp},
Theme.{h,cpp}, Model.h.
- CMake:
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets WebSockets),find_package(Qt${QT_VERSION_MAJOR} ...),CMAKE_AUTOMOC ON, C++17. Sources include${CMAKE_CURRENT_SOURCE_DIR}/../streamhub/Protocol.cpp; include dir../streamhub. LinkQt${QT_VERSION_MAJOR}::Widgets,Qt${QT_VERSION_MAJOR}::WebSockets. Model.h:SignalView{SignalMeta meta; SignalBuffer buf; QColor color; double lineWidth; int marker; bool visible;},Source,VScale,PlotAssignment,PlotLayoutenum +layoutDims/layoutNames/layoutCount.Theme: apply Catppuccin-MochaQPalette+ stylesheet;tracePalette(i).main.cpp: parse-host/-port, buildQApplication, apply theme, showMainWindow.- Deliverable: builds, opens an empty dark window.
Task 2: WsClient + Hub controller (data model + WS dispatch)
Files: Create WsClient.{h,cpp}, Hub.{h,cpp}.
WsClient: wrapsQWebSocket;connectTo(host,port),reconnect,sendText; 3 s reconnectQTimer; signalsconnectedChanged(bool),textReceived(QString),binaryReceived(QByteArray).Hub: ownsWsClient+std::vector<Source>+TriggerCfgState+CaptureFrame+ per-plot zoom caches +HistoryInfoMsg. On connect sendsgetSources/getStats/historyInfo. Routes text viaParseTypetoonSources/onConfig/onStats/onTriggerState/onZoom/onHistoryZoom/onHistoryInfo/onMaxPointsUpdated; binary via first byte (v2 capture vs v1 push). Command senders mirrorBuild*inProtocol.h. Emits signals listed in the spec. Holds afindSource/findSignal/slotKey. Zoom caches keyed by plot index (requestZoom,requestHistoryZoom,nextZoomReqId).- Deliverable: builds; connecting to a hub logs received message types
(temporary
qDebug), model populates (verified via a debug dump).
Task 3: PlotWidget — static render + live scroll
Files: Create PlotWidget.{h,cpp}.
PlotWidget(Hub*, int plotIdx, QWidget*).paintEvent: draw frame, grid, ±4-div Y axis with 9 ticks, X axis with 10 ticks. Live mode: X =[wallNow-windowSec, wallNow]. For each assigned slot: read ring (buf.readLast(maxPoints)), clip to visible X,LTTBDecimateto 2400, normalize (normal/digital/mixed via portedresolveVScale/normalizeY/bandNormalize), map to pixels,drawPolylinein trace color/width; optional markers. Badge row painted at top (trace name + V/div).- Deliverable: dropping handled later; for now a hard-coded assignment shows a live trace scrolling against wall clock.
Task 4: PlotWidget — interaction (zoom/pan/cursors/active/vmode) + drag-drop
Files: Modify PlotWidget.{h,cpp}; add small per-plot toolbar (Live/Back/
Fit, N/D/M, V-scale controls) as child widgets or painted buttons.
- Mouse: wheel = Y zoom active trace (X zoom if none); Ctrl+wheel = X zoom/window; Shift+wheel = Y pan; right-drag = X pan (live→non-live); box-zoom optional. Click a badge = toggle active slot; context menu on badge = color/ width/marker/digital-in-mixed/remove. Zoom history Back/Fit/Live.
- Global A/B cursors (drawn + draggable) with readouts; pause snapshot.
dragEnterEvent/dropEventaccept a signal-ref mime → append assignment.- Hi-res WS zoom + history zoom requests through
Hub(live-narrow throttle, non-live debounce) and prefer cached hi-res/history data when it covers view. - Deliverable: full single-plot interaction parity.
Task 5: PlotGrid + SourceSidebar + MainWindow toolbar
Files: Create PlotGrid.{h,cpp}, SourceSidebar.{h,cpp}; expand
MainWindow.{h,cpp}.
PlotGrid: nestedQSplitters realizing eachPlotLayout; rebuild on change.SourceSidebar:QTreeWidgetsources→signals; drag a signal (mime with src/sig index); "Add source" + remove; reflectsHubsignals.- MainWindow toolbar: sidebar toggle, layout picker (menu of the 8 layouts), pause, cursors toggle, trigger-bar toggle, history-bar toggle, window-preset combo, stats button, connection (host/port + reconnect), status LED.
- Add-source dialog (label/host/port/multicast/dataPort) →
Hub::addSource. - Deliverable: drag-drop from sidebar to any layout cell works end to end.
Task 6: TriggerBar + StatsDialog + HistoryBar
Files: Create TriggerBar.{h,cpp}, StatsDialog.{h,cpp},
HistoryBar.{h,cpp}; wire into MainWindow.
TriggerBar: signal combo (full keys incl array index), edge combo, threshold spin, window combo (100 µs…10 s), pre% slider, normal/single →setTrigger; Arm/Disarm/Rearm/Stop; badge fromtriggerStateChanged. Capture frame routes to plots' trigger view.StatsDialog: table of metrics per source + custom 20-bin histogram widget (QPainterbars), refreshed onstatsChanged.HistoryBar: Live, range readout, pan ←/→, jump presets, All; drives plots' non-live X range + history-zoom.- Deliverable: trigger capture, stats histogram, history browse all match the ImGui client.
Task 7: Build, run, verify, document
- Clean Release build on Qt6 (and confirm Qt5 configure path).
- Launch against a live hub; verify the spec's parity checklist.
- Add a
Client/streamhub-qtbuild note toCLAUDE.mdandARCHITECTURE.md. - Deliverable: working client + docs.