gui: RPC client, download table model, and a live main window

First vertical slice of velox-gui, built entirely against tools/mockd
(no daemon dependency):

- rpc/: RpcConnection runs a QLocalSocket on a worker thread with
  newline-delimited JSON-RPC framing, drives the session.hello /
  session.subscribe handshake, and reconnects with exponential backoff
  (250 ms -> 8 s). RpcClient is the main-thread face: marshals calls onto
  the worker, delivers replies as main-thread callbacks, re-emits server
  notifications as typed Qt signals, and issues the one-shot download.list
  on reaching Connected.
- models/DownloadTableModel: QAbstractTableModel over TaskSummary. A
  progress batch is a row patch with a narrow dataChanged over the value
  columns only; beginResetModel() is reserved for the initial load and a
  reconnect resync.
- widgets/ProgressDelegate: in-cell progress bar for the Status column.
- mainwindow/MainWindow: the table, a status-bar connection dot, an
  offline banner instead of a modal, dialog-free pause/resume/stop
  actions, and QSettings column/geometry persistence.
- gui/CMakeLists.txt links velox::proto (never velox::core, ADR 0009) and
  self-guards on the veloxproto target so main keeps configuring if it is
  ever absent again.
- i18n from the first commit: every string via tr(), plus an Arabic .ts
  stub for the RTL check.
- tests/: headless QTest for the model — proves the progress patch is a
  narrow dataChanged and never resets the model.

Verified end-to-end against `mockd --tasks 300`: handshake, initial list,
live progress batches applied to the model, and a clean
Reconnecting -> Connected recovery when mockd is bounced mid-run.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_016Ne28kx4VreeBWZv82Nksd
This commit is contained in:
2026-09-10 00:58:53 +04:00
co-authored by Claude Sonnet 5
parent 850e85de2a
commit 2a87abe96d
16 changed files with 1572 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
// The main window. Lane GUI.
//
// M1 slice: category tree, dialogs and the tray come later. This is the table driven by
// mockd, a status-bar connection dot, an offline banner instead of a modal, and the three
// selection actions that need no dialog (pause / resume / cancel).
#pragma once
#include <QMainWindow>
#include "rpc/Protocol.hpp"
class QLabel;
class QSortFilterProxyModel;
class QTreeView;
namespace velox::gui {
class DownloadTableModel;
namespace rpc {
class RpcClient;
} // namespace rpc
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(rpc::RpcClient *client, QWidget *parent = nullptr);
~MainWindow() override;
protected:
void closeEvent(QCloseEvent *event) override;
private slots:
void onConnectionState(rpc::ConnectionState state);
void onSpeedGlobal(const QJsonObject &params);
void updateCounts();
void pauseSelection();
void resumeSelection();
void cancelSelection();
private:
QStringList selectedTaskIds() const;
void actOnSelection(const char *methodName);
void restoreLayout();
void saveLayout();
rpc::RpcClient *client_;
DownloadTableModel *model_;
QSortFilterProxyModel *proxy_;
QTreeView *view_;
QLabel *connDot_;
QLabel *connText_;
QLabel *countsLabel_;
QWidget *offlineBanner_;
qint64 globalDownBps_ = 0;
};
} // namespace velox::gui