#include "LightsPanel.h" #include #include #include #include #include #include #include #include #include // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- static QString toDecRGB(const QColor &c) { return QString("%1 %2 %3").arg(c.red()).arg(c.green()).arg(c.blue()); } static void applyColorToBtn(QPushButton *btn, const QColor &c) { const bool dark = (c.red() * 299 + c.green() * 587 + c.blue() * 114) < 128000; btn->setStyleSheet(QString( "QPushButton { background:%1; color:%2; border:1px solid #555;" " border-radius:4px; padding:3px 10px; }" "QPushButton:hover { border:2px solid #fff; }") .arg(c.name()) .arg(dark ? "#ffffff" : "#000000")); btn->setText(QString("%1, %2, %3").arg(c.red()).arg(c.green()).arg(c.blue())); } // --------------------------------------------------------------------------- // Constructor // --------------------------------------------------------------------------- LightsPanel::LightsPanel(QWidget *parent) : QWidget(parent) { auto *root = new QVBoxLayout(this); root->setContentsMargins(4, 4, 4, 4); root->setSpacing(4); m_statusLabel = new QLabel("Connect to load panels...", this); m_statusLabel->setAlignment(Qt::AlignHCenter | Qt::AlignTop); m_statusLabel->setStyleSheet("color: #888; font-style: italic;"); root->addWidget(m_statusLabel); root->addStretch(1); // --- Control bar --- auto *bar = new QHBoxLayout(); bar->setSpacing(8); m_selectedLabel = new QLabel("No panel selected", this); m_selectedLabel->setStyleSheet("font-weight: bold; min-width: 140px;"); auto *colorLabel = new QLabel("Colour:", this); m_colorBtn = new QPushButton(this); m_colorBtn->setFixedWidth(130); applyColorToBtn(m_colorBtn, m_ledColor); m_sendBtn = new QPushButton("Send to Panel", this); m_sendBtn->setEnabled(false); m_sendBtn->setStyleSheet( "QPushButton { background:#01696f; color:white; border-radius:4px; padding:4px 10px; }" "QPushButton:hover { background:#0c4e54; }" "QPushButton:disabled { background:#888; color:#ccc; }"); m_sendAllBtn = new QPushButton("Send to All", this); m_sendAllBtn->setEnabled(false); m_sendAllBtn->setStyleSheet( "QPushButton { background:#437a22; color:white; border-radius:4px; padding:4px 10px; }" "QPushButton:hover { background:#2e5c10; }" "QPushButton:disabled { background:#888; color:#ccc; }"); m_clearAllBtn = new QPushButton("Clear All", this); m_clearAllBtn->setEnabled(false); m_clearAllBtn->setStyleSheet( "QPushButton { background:#555; color:white; border-radius:4px; padding:4px 10px; }" "QPushButton:hover { background:#333; }" "QPushButton:disabled { background:#888; color:#ccc; }"); bar->addWidget(m_selectedLabel); bar->addStretch(1); bar->addWidget(colorLabel); bar->addWidget(m_colorBtn); bar->addSpacing(8); bar->addWidget(m_sendBtn); bar->addWidget(m_sendAllBtn); bar->addWidget(m_clearAllBtn); root->addLayout(bar); connect(m_colorBtn, &QPushButton::clicked, this, &LightsPanel::pickColor); connect(m_sendBtn, &QPushButton::clicked, this, [this](){ if (m_selected >= 0) sendLedCommand(m_selected, m_ledColor); }); connect(m_sendAllBtn, &QPushButton::clicked, this, [this](){ for (int i = 0; i < m_count; ++i) sendLedCommand(i, m_ledColor); }); connect(m_clearAllBtn, &QPushButton::clicked, this, [this](){ const QColor black(0, 0, 0); for (int i = 0; i < m_count; ++i) sendLedCommand(i, black); }); setMinimumSize(220, 260); setCursor(Qt::PointingHandCursor); } // --------------------------------------------------------------------------- // Public API // --------------------------------------------------------------------------- void LightsPanel::setRnpCount(int count) { m_count = count; m_selected = -1; m_panelColors.clear(); m_statusLabel->setText(count > 0 ? QString("%1 panel%2 — click a segment to select").arg(count).arg(count == 1 ? "" : "s") : "No panels reported"); const bool ok = count > 0; m_sendAllBtn->setEnabled(ok); m_clearAllBtn->setEnabled(ok); updateSendBtn(); update(); } void LightsPanel::reset() { m_count = 0; m_selected = -1; m_panelColors.clear(); m_statusLabel->setText("Connect to load panels..."); m_sendBtn->setEnabled(false); m_sendAllBtn->setEnabled(false); m_clearAllBtn->setEnabled(false); update(); } // --------------------------------------------------------------------------- // Internal // --------------------------------------------------------------------------- void LightsPanel::updateSendBtn() { m_sendBtn->setEnabled(m_selected >= 0); m_sendBtn->setText(m_selected >= 0 ? QString("Send to Panel %1").arg(m_selected) : "Send to Panel"); } void LightsPanel::pickColor() { auto *dlg = new QColorDialog(m_ledColor, this); dlg->setOption(QColorDialog::DontUseNativeDialog); dlg->setAttribute(Qt::WA_DeleteOnClose); connect(dlg, &QColorDialog::colorSelected, this, [this](const QColor &c) { m_ledColor = c; applyColorToBtn(m_colorBtn, c); update(); }); dlg->open(); } void LightsPanel::sendLedCommand(int panel, const QColor &color) { emit commandRequested(QString("LED %1 %2 A").arg(panel).arg(toDecRGB(color))); m_panelColors[panel] = color; update(); } // --------------------------------------------------------------------------- // Geometry // --------------------------------------------------------------------------- static void calcGeom(const QRectF &widgetRect, double &cx, double &cy, double &outerR, double &innerR) { const QRectF draw = widgetRect.adjusted(0, 28, 0, 44); const double side = qMin(draw.width(), draw.height()) * 0.88; cx = draw.center().x(); cy = draw.center().y(); outerR = side / 2.0; innerR = outerR * 0.44; } int LightsPanel::segmentAt(const QPointF &pos) const { if (m_count <= 0) return -1; double cx, cy, outerR, innerR; calcGeom(rect(), cx, cy, outerR, innerR); const double dx = pos.x() - cx; const double dy = -(pos.y() - cy); const double dist = qSqrt(dx*dx + dy*dy); if (dist < innerR || dist > outerR) return -1; double cwFromTop = 90.0 - qRadiansToDegrees(qAtan2(dy, dx)); if (cwFromTop < 0) cwFromTop += 360.0; if (cwFromTop >= 360) cwFromTop -= 360.0; const double gapDeg = (m_count > 1) ? 3.0 : 0.0; const double segSpan = (360.0 - m_count * gapDeg) / m_count; for (int i = 0; i < m_count; ++i) { const double start = i * (segSpan + gapDeg); if (cwFromTop >= start && cwFromTop < start + segSpan) return i; } return -1; } // --------------------------------------------------------------------------- // Mouse // --------------------------------------------------------------------------- void LightsPanel::mousePressEvent(QMouseEvent *e) { const int idx = segmentAt(e->pos()); if (idx < 0) { QWidget::mousePressEvent(e); return; } m_selected = idx; m_selectedLabel->setText(QString("Panel %1 selected").arg(idx)); updateSendBtn(); update(); } // --------------------------------------------------------------------------- // Paint // --------------------------------------------------------------------------- void LightsPanel::paintEvent(QPaintEvent *) { if (m_count <= 0) return; QPainter p(this); p.setRenderHint(QPainter::Antialiasing, true); double cx, cy, outerR, innerR; calcGeom(rect(), cx, cy, outerR, innerR); const double side = outerR * 2.0; const QRectF outerRect(cx - outerR, cy - outerR, side, side); const QRectF innerRect(cx - innerR, cy - innerR, innerR * 2, innerR * 2); const double gapDeg = (m_count > 1) ? 3.0 : 0.0; const double segSpan = (360.0 - m_count * gapDeg) / m_count; static const QColor kDefault(70, 70, 80); static const QColor kSelected(40, 120, 140); for (int i = 0; i < m_count; ++i) { const double startAngle = 90.0 - i * (segSpan + gapDeg); const double sweep = -segSpan; const double midAngleRad = qDegreesToRadians(startAngle + sweep / 2.0); QPainterPath path; path.arcMoveTo(outerRect, startAngle); path.arcTo(outerRect, startAngle, sweep); path.arcTo(innerRect, startAngle + sweep, -sweep); path.closeSubpath(); QColor fill = m_panelColors.value(i, i == m_selected ? kSelected : kDefault); p.setBrush(fill); p.setPen(QPen(i == m_selected ? Qt::white : QColor(255,255,255,80), i == m_selected ? 3.0 : 1.5)); p.drawPath(path); // Colour preview dot on selected segment (outer rim) if (i == m_selected) { const double previewR = qMin((outerR - innerR) * 0.22, outerR * qSin(qDegreesToRadians(segSpan / 2.0)) * 0.55); const double bCentR = outerR - previewR - 2.0; const QPointF bc(cx + bCentR * qCos(midAngleRad), cy - bCentR * qSin(midAngleRad)); const QRectF pr(bc.x()-previewR, bc.y()-previewR, previewR*2, previewR*2); p.setBrush(m_ledColor); p.setPen(QPen(Qt::white, 1.5)); p.drawEllipse(pr); } // Panel index label const double midR = (outerR + innerR) / 2.0; const QPointF lp(cx + midR * qCos(midAngleRad), cy - midR * qSin(midAngleRad)); QFont font = p.font(); font.setBold(true); font.setPointSize(qMax(7, qMin(12, static_cast(outerR / (m_count * 0.55 + 2))))); p.setFont(font); const QColor bg = m_panelColors.value(i, i == m_selected ? kSelected : kDefault); const bool darkBg = (bg.red()*299 + bg.green()*587 + bg.blue()*114) < 128000; p.setPen(darkBg ? Qt::white : Qt::black); p.drawText(QRectF(lp.x()-22, lp.y()-11, 44, 22), Qt::AlignCenter, QString::number(i)); } // Centre count QFont cf = p.font(); cf.setBold(true); cf.setPointSize(qMax(8, static_cast(innerR * 0.45))); p.setFont(cf); p.setPen(palette().text().color()); p.drawText(innerRect, Qt::AlignCenter, QString::number(m_count)); }