diff --git a/cockatrice/src/client/latency_graph_widget.cpp b/cockatrice/src/client/latency_graph_widget.cpp index 786de5cb6..46d6ccdd3 100644 --- a/cockatrice/src/client/latency_graph_widget.cpp +++ b/cockatrice/src/client/latency_graph_widget.cpp @@ -44,8 +44,7 @@ void LatencyGraphWidget::paintEvent(QPaintEvent * /* event */) QColor color; color.setHsv(qRound(120.0 * (1.0 - colorRatio)), 255, 255); - const QRectF bar(static_cast(i) * widthPerBar + 1.0, - static_cast(height()) - barHeight, + const QRectF bar(static_cast(i) * widthPerBar + 1.0, static_cast(height()) - barHeight, qMax(1.0, widthPerBar - 2.0), barHeight); painter.fillRect(bar, color); } diff --git a/cockatrice/src/client/latency_status_widget.cpp b/cockatrice/src/client/latency_status_widget.cpp index a8d48d126..68f7fcd7a 100644 --- a/cockatrice/src/client/latency_status_widget.cpp +++ b/cockatrice/src/client/latency_status_widget.cpp @@ -7,8 +7,10 @@ #include "latency_graph_widget.h" +#include #include #include +#include LatencyStatusWidget::LatencyStatusWidget(QWidget *parent) : QWidget(parent) { @@ -24,6 +26,12 @@ LatencyStatusWidget::LatencyStatusWidget(QWidget *parent) : QWidget(parent) layout->addWidget(latencyGraph); layout->addWidget(pingLabel); + // Clicking anywhere in the area opens the detail view. + for (QObject *child : QList{pingLabel, latencyGraph}) { + child->installEventFilter(this); + } + setCursor(Qt::PointingHandCursor); + hide(); } @@ -34,17 +42,73 @@ void LatencyStatusWidget::updateStats(int lastMs, int medianMs, int p95Ms, int m return; } - const QString stats = tr("Connection quality over the last %n sample(s):", "", sampleCount) + "\n" + - tr("Last: %1 ms").arg(lastMs) + "\n" + tr("Median: %1 ms").arg(medianMs) + "\n" + - tr("95th percentile: %1 ms").arg(p95Ms) + "\n" + tr("Maximum: %1 ms").arg(maxMs); + const QString stats = statsText(lastMs, medianMs, p95Ms, maxMs, sampleCount); pingLabel->setText(tr("Ping: %1 ms").arg(lastMs)); pingLabel->setToolTip(stats); pingLabel->setAccessibleDescription(stats); + if (popup && popup->isVisible() && detailLabel) { + detailLabel->setText(stats); + } show(); } void LatencyStatusWidget::updateSamples(const QList &samplesMs) { + latestSamples = samplesMs; latencyGraph->setSamples(samplesMs); + if (popup && popup->isVisible() && detailGraph) { + detailGraph->setSamples(samplesMs); + } +} + +bool LatencyStatusWidget::eventFilter(QObject *watched, QEvent *event) +{ + if ((watched == pingLabel || watched == latencyGraph) && event->type() == QEvent::MouseButtonPress) { + togglePopup(); + return true; + } + return QWidget::eventFilter(watched, event); +} + +void LatencyStatusWidget::togglePopup() +{ + if (!popup) { + popup = new QWidget(this, Qt::Popup | Qt::FramelessWindowHint); + auto *layout = new QVBoxLayout(popup); + layout->setContentsMargins(8, 8, 8, 8); + + detailLabel = new QLabel(popup); + detailLabel->setAccessibleName(tr("Connection latency details")); + detailLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); + + detailGraph = new LatencyGraphWidget(popup); + detailGraph->setFixedSize(280, 80); + + layout->addWidget(detailLabel, 0, Qt::AlignLeft); + layout->addWidget(detailGraph, 0, Qt::AlignHCenter); + } + + if (popup->isVisible()) { + popup->hide(); + return; + } + + // Qt::Popup closes itself on any outside click, so just position and show. + if (latestSamples.isEmpty()) { + return; + } + detailGraph->setSamples(latestSamples); + detailLabel->setText(pingLabel->toolTip()); + popup->adjustSize(); + const QPoint anchor = mapToGlobal(QPoint(width() / 2, 0)); + popup->move(anchor.x() - popup->width() / 2, anchor.y() - popup->height() - 6); + popup->show(); +} + +QString LatencyStatusWidget::statsText(int lastMs, int medianMs, int p95Ms, int maxMs, int sampleCount) const +{ + return tr("Connection quality over the last %n sample(s):", "", sampleCount) + "\n" + + tr("Last: %1 ms").arg(lastMs) + "\n" + tr("Median: %1 ms").arg(medianMs) + "\n" + + tr("95th percentile: %1 ms").arg(p95Ms) + "\n" + tr("Maximum: %1 ms").arg(maxMs); } diff --git a/cockatrice/src/client/latency_status_widget.h b/cockatrice/src/client/latency_status_widget.h index bb8824178..fb38a6617 100644 --- a/cockatrice/src/client/latency_status_widget.h +++ b/cockatrice/src/client/latency_status_widget.h @@ -16,9 +16,11 @@ class LatencyGraphWidget; * @brief Status bar presentation of server round-trip health. * * Combines the textual "Ping" readout with a small LatencyGraphWidget - * sparkline of the rolling sample window. Stays hidden while disconnected or - * before any samples exist. Owns all latency display state so MainWindow only - * needs to forward two signals here. + * sparkline of the rolling sample window. Clicking anywhere in the area opens + * a popup with a larger graph and the numeric statistics. It closes on any + * outside click. Stays hidden while disconnected or before any samples exist. + * Owns all latency display state so MainWindow only needs to forward two + * signals here. */ class LatencyStatusWidget : public QWidget { @@ -30,9 +32,19 @@ public slots: void updateStats(int lastMs, int medianMs, int p95Ms, int maxMs, int sampleCount); void updateSamples(const QList &samplesMs); +protected: + bool eventFilter(QObject *watched, QEvent *event) override; + private: + void togglePopup(); + QString statsText(int lastMs, int medianMs, int p95Ms, int maxMs, int sampleCount) const; + QLabel *pingLabel = nullptr; LatencyGraphWidget *latencyGraph = nullptr; + QWidget *popup = nullptr; + LatencyGraphWidget *detailGraph = nullptr; + QLabel *detailLabel = nullptr; + QList latestSamples; }; #endif