| 340 | } |
| 341 | |
| 342 | void StripChainWidget::paintEvent(QPaintEvent*) |
| 343 | { |
| 344 | rebuildLayout(); |
| 345 | |
| 346 | QPainter p(this); |
| 347 | p.setRenderHint(QPainter::Antialiasing, true); |
| 348 | p.fillRect(rect(), AetherSDR::ThemeManager::instance().color("color.background.0")); |
| 349 | |
| 350 | if (m_boxes.isEmpty()) return; |
| 351 | |
| 352 | // Connector lines between adjacent boxes — handles three cases: |
| 353 | // - same row, both visual-left-to-right (even row) |
| 354 | // - same row, both visual-right-to-left (odd row) |
| 355 | // - row transition: a small L / U bend with a downward arrow |
| 356 | auto drawArrowHead = [&](QPointF tip, QPointF from) { |
| 357 | // Filled triangle pointing from `from` toward `tip`. |
| 358 | const QPointF dir = tip - from; |
| 359 | const qreal len = std::hypot(dir.x(), dir.y()); |
| 360 | if (len < 0.01) return; |
| 361 | const QPointF u(dir.x() / len, dir.y() / len); |
| 362 | const QPointF perp(-u.y(), u.x()); |
| 363 | QPainterPath head; |
| 364 | head.moveTo(tip); |
| 365 | head.lineTo(tip.x() - u.x() * kArrowTip + perp.x() * kArrowTip, |
| 366 | tip.y() - u.y() * kArrowTip + perp.y() * kArrowTip); |
| 367 | head.lineTo(tip.x() - u.x() * kArrowTip - perp.x() * kArrowTip, |
| 368 | tip.y() - u.y() * kArrowTip - perp.y() * kArrowTip); |
| 369 | head.closeSubpath(); |
| 370 | p.setBrush(kConnector()); |
| 371 | p.setPen(Qt::NoPen); |
| 372 | p.drawPath(head); |
| 373 | }; |
| 374 | |
| 375 | p.setPen(QPen(kConnector(), 2.0)); |
| 376 | for (int i = 0; i + 1 < m_boxes.size(); ++i) { |
| 377 | const QRectF a = m_boxes[i].rect; |
| 378 | const QRectF b = m_boxes[i + 1].rect; |
| 379 | const bool sameRow = qFuzzyCompare(a.center().y(), b.center().y()); |
| 380 | if (sameRow) { |
| 381 | // Horizontal connector — direction depends on row parity. |
| 382 | // Even row: a is left, b is right → arrow into b's left edge. |
| 383 | // Odd row: a is right, b is left → arrow into b's right edge. |
| 384 | const bool rtl = a.center().x() > b.center().x(); |
| 385 | QPointF from, to; |
| 386 | if (rtl) { |
| 387 | from = QPointF(a.left(), a.center().y()); |
| 388 | to = QPointF(b.right() + 1, b.center().y()); |
| 389 | } else { |
| 390 | from = QPointF(a.right(), a.center().y()); |
| 391 | to = QPointF(b.left() - 1, b.center().y()); |
| 392 | } |
| 393 | p.setPen(QPen(kConnector(), 2.0)); |
| 394 | p.drawLine(from, to); |
| 395 | drawArrowHead(to, from); |
| 396 | } else { |
| 397 | // Row transition — snake layout always turns at whichever |
| 398 | // side of the widget the boxes currently occupy. Compare |
| 399 | // the box centre to the widget centre: right half → turn |
nothing calls this directly
no test coverage detected