// panelwidget.cpp - the front panel of the AdvancedController (see panelwidget.h) #include "panelwidget.h" #include "remoteclient.h" #include "style.h" #include #include #include #include #include #include #include namespace { // Panel geometry in "units" (1 unit = distance between button centres) const double PanelW = 12.5; const double PanelH = 7.0; const double Btn = 0.86; // button side const double LedR = 0.045; // led radius const double Pi = 3.14159265358979323846; enum class Icon { LedOn, LedAuto, Agc, Sun, Zoom, Mirror, Flip, Focus, Sliders, Sliders1, Sliders2, Save1, Save2, Xh1, Xh2, Temp, Camera, Gear, Up, Down, Left, Right, Enter }; struct Button { const char *name; // name for "KEY" const char *longName; // name for a long press, nullptr = none double x; // centre, units double y; Icon icon; const char *leds[2]; // leds shown on this button }; const Button Buttons[] = { {"LEDON", nullptr, 2.75, 1.55, Icon::LedOn, {"LEDON", nullptr}}, {"LEDAUTO", nullptr, 3.75, 1.55, Icon::LedAuto, {"LEDAUTO", nullptr}}, {"AGC", nullptr, 4.75, 1.55, Icon::Agc, {"AGC", nullptr}}, {"BRIGHT", nullptr, 5.75, 1.55, Icon::Sun, {"BRIGHT", nullptr}}, {"ZOOM", nullptr, 6.75, 1.55, Icon::Zoom, {"ZOOM", nullptr}}, {"MIRROR", nullptr, 7.75, 1.55, Icon::Mirror, {"MIRROR", nullptr}}, {"FLIP", nullptr, 8.75, 1.55, Icon::Flip, {"FLIP", nullptr}}, {"FOCUS", nullptr, 9.75, 1.55, Icon::Focus, {"FOCUS", nullptr}}, {"CAMRESET", nullptr, 5.75, 2.80, Icon::Sliders, {"CAMMENU", nullptr}}, // camera reset (its led blinks during the reset) {"LOAD1", nullptr, 6.75, 2.80, Icon::Sliders1, {"LOAD1", nullptr}}, {"LOAD2", nullptr, 7.75, 2.80, Icon::Sliders2, {"LOAD2", nullptr}}, {"SAVE1", nullptr, 8.75, 2.80, Icon::Save1, {"SAVE1", nullptr}}, {"SAVE2", nullptr, 9.75, 2.80, Icon::Save2, {"SAVE2", nullptr}}, {"XH1", "XH1LONG", 7.75, 4.05, Icon::Xh1, {"XH1", nullptr}}, {"XH2", "XH2LONG", 8.75, 4.05, Icon::Xh2, {"XH2", nullptr}}, {"TEMP", nullptr, 9.75, 4.05, Icon::Temp, {"TEMP", nullptr}}, {"CAMSEL", nullptr, 8.75, 5.30, Icon::Camera, {"CAM1SEL", "CAM2SEL"}}, {"SYSRESET", nullptr, 9.75, 5.30, Icon::Gear, {"SYSRESET", nullptr}}, // gear: leave all adjust modes; led = remote-control sign {"UP", nullptr, 3.75, 3.25, Icon::Up, {"UP", nullptr}}, {"LEFT", nullptr, 2.75, 4.25, Icon::Left, {"LEFT", nullptr}}, {"ENTER", "LONGENTER", 3.75, 4.25, Icon::Enter, {"ENTER", nullptr}}, {"RIGHT", nullptr, 4.75, 4.25, Icon::Right, {"RIGHT", nullptr}}, {"DOWN", nullptr, 3.75, 5.30, Icon::Down, {"DOWN", nullptr}}, }; // Every icon is drawn inside the unit square (-0.5..0.5) around the button centre, // line widths in pixels as on the device drawing (2 px, thick lines 3 px). class IconPainter { public: IconPainter(QPainter &painter, const QPointF ¢re, double size) : m_p(painter), m_c(centre), m_s(size) { } void draw(Icon icon) { switch (icon) { case Icon::LedOn: headlight(QString()); break; case Icon::LedAuto: headlight(QStringLiteral("A")); break; case Icon::Agc: agc(); break; case Icon::Sun: sun(); break; case Icon::Zoom: zoom(); break; case Icon::Mirror: mirror(); break; case Icon::Flip: flip(); break; case Icon::Focus: focus(); break; case Icon::Sliders: sliders(QString()); break; case Icon::Sliders1: sliders(QStringLiteral("1")); break; case Icon::Sliders2: sliders(QStringLiteral("2")); break; case Icon::Save1: save(QStringLiteral("1")); break; case Icon::Save2: save(QStringLiteral("2")); break; case Icon::Xh1: xh1(); break; case Icon::Xh2: xh2(); break; case Icon::Temp: temp(); break; case Icon::Camera: camera(); break; case Icon::Gear: gear(); break; case Icon::Up: arrow(0, -1); break; case Icon::Down: arrow(0, 1); break; case Icon::Left: arrow(-1, 0); break; case Icon::Right: arrow(1, 0); break; case Icon::Enter: enter(); break; } } private: QPointF p(double x, double y) const { return QPointF(m_c.x() + x * m_s, m_c.y() + y * m_s); } QPolygonF points(std::initializer_list unit) const { QPolygonF result; for (const QPointF &u : unit) result << p(u.x(), u.y()); return result; } QPen pen(double width, Qt::PenJoinStyle join = Qt::RoundJoin) const { return QPen(Style::Line, width, Qt::SolidLine, Qt::RoundCap, join); } void line(const QPolygonF &pts, double width = 2) { m_p.setPen(pen(width)); m_p.setBrush(Qt::NoBrush); m_p.drawPolyline(pts); } void dashedLine(const QPolygonF &pts, double width = 2) { QPen dashed(Style::Line, width, Qt::CustomDashLine, Qt::FlatCap); dashed.setDashPattern({6 / width, 4 / width}); // 6 px dash, 4 px gap m_p.setPen(dashed); m_p.setBrush(Qt::NoBrush); m_p.drawPolyline(pts); } void poly(const QPolygonF &pts, bool filled = false, double width = 2) { m_p.setPen(pen(width)); m_p.setBrush(filled ? QBrush(Style::Line) : QBrush(Qt::NoBrush)); m_p.drawPolygon(pts); } void circle(double x, double y, double r, bool filled = false, double width = 2) { m_p.setPen(pen(width)); m_p.setBrush(filled ? QBrush(Style::Line) : QBrush(Qt::NoBrush)); m_p.drawEllipse(p(x, y), r * m_s, r * m_s); } void rect(double x0, double y0, double x1, double y1, bool filled = false, double width = 2) { m_p.setPen(pen(width, Qt::MiterJoin)); m_p.setBrush(filled ? QBrush(Style::Line) : QBrush(Qt::NoBrush)); m_p.drawRect(QRectF(p(x0, y0), p(x1, y1))); } void text(double x, double y, const QString &s, double size) { QFont font(QStringLiteral("Arial")); font.setStyleHint(QFont::SansSerif); font.setBold(true); font.setPointSize(std::max(6, int(m_s * size))); m_p.setFont(font); m_p.setPen(Style::Line); const QPointF c = p(x, y); m_p.drawText(QRectF(c.x() - m_s, c.y() - m_s, 2 * m_s, 2 * m_s), Qt::AlignCenter, s); } // A line with an arrowhead at its end. shape = (d1, d2, d3) in pixels: distance from the // neck to the tip, from the trailing points to the tip, from the line edge to the trailing points. void arrowLine(QPolygonF pts, double width, double d1, double d2, double d3) { const QPointF tip = pts.last(); QPointF dir = tip - pts.at(pts.size() - 2); dir /= std::hypot(dir.x(), dir.y()); const QPointF normal(-dir.y(), dir.x()); const QPointF neck = tip - dir * d1; const QPointF base = tip - dir * d2; const double half = width / 2 + d3; pts.last() = neck; line(pts, width); m_p.setPen(Qt::NoPen); m_p.setBrush(Style::Line); m_p.drawPolygon(QPolygonF({tip, base + normal * half, neck, base - normal * half})); } // --- the icons ------------------------------------------------------------- void headlight(const QString &letter) { // "D" shaped lamp + three beams QPolygonF pts = points({{-0.30, -0.19}, {-0.06, -0.19}}); for (int i = 1; i < 8; ++i) { const double a = -Pi / 2 + Pi * i / 8; pts << p(-0.06 + 0.19 * std::cos(a), 0.19 * std::sin(a)); } pts << p(-0.06, 0.19) << p(-0.30, 0.19); poly(pts); for (const double y : {-0.14, 0.0, 0.14}) line(points({{0.17, y}, {0.34, y}})); if (!letter.isEmpty()) text(-0.12, 0.0, letter, 0.24); } void agc() { rect(-0.30, -0.26, 0.06, 0.02); rect(-0.06, -0.02, 0.30, 0.26, true); } void sun() { circle(0, 0, 0.13); for (int i = 0; i < 8; ++i) { const double a = i * Pi / 4; line(points({{0.20 * std::cos(a), 0.20 * std::sin(a)}, {0.31 * std::cos(a), 0.31 * std::sin(a)}})); } } void zoom() { circle(-0.06, -0.06, 0.17); line(points({{0.07, 0.07}, {0.29, 0.29}}), 3); } void mirror() { poly(points({{-0.06, -0.27}, {-0.06, 0.27}, {-0.33, 0.0}})); poly(points({{0.06, -0.27}, {0.06, 0.27}, {0.33, 0.0}}), true); } void flip() { poly(points({{-0.27, -0.06}, {0.27, -0.06}, {0.0, -0.33}})); poly(points({{-0.27, 0.06}, {0.27, 0.06}, {0.0, 0.33}}), true); } void focus() { circle(0, 0, 0.22); circle(0, 0, 0.06, true); const int dirs[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; for (const auto &d : dirs) line(points({{0.15 * d[0], 0.15 * d[1]}, {0.30 * d[0], 0.30 * d[1]}})); } void sliders(const QString &label) { double ys[3] = {0.22, 0.22, 0.22}; if (label == QLatin1String("1")) { ys[0] = -0.02; ys[1] = 0.12; ys[2] = -0.14; } else if (label == QLatin1String("2")) { ys[0] = 0.10; ys[1] = -0.12; ys[2] = 0.02; } const double xs[3] = {-0.20, 0.0, 0.20}; for (int i = 0; i < 3; ++i) { line(points({{xs[i], -0.30}, {xs[i], 0.28}})); circle(xs[i], ys[i], 0.055, true); } if (!label.isEmpty()) text(0.30, 0.30, label, 0.18); } void save(const QString &label) { arrowLine(points({{0.0, -0.32}, {0.0, 0.06}}), 3, 10, 12, 5); line(points({{-0.28, 0.10}, {-0.28, 0.24}, {0.28, 0.24}, {0.28, 0.10}})); text(0.0, 0.17, label, 0.16); } void xh1() { // crosshair 1: long horizontal line near the top, long vertical line on the left line(points({{-0.36, -0.18}, {0.36, -0.18}})); line(points({{-0.17, -0.36}, {-0.17, 0.36}})); } void xh2() { // crosshair 1 (dashed) + crosshair 2 (solid, lower right) - as on the device dashedLine(points({{-0.36, -0.18}, {0.36, -0.18}})); dashedLine(points({{-0.17, -0.36}, {-0.17, 0.36}})); line(points({{-0.36, 0.30}, {0.36, 0.30}})); line(points({{0.26, -0.36}, {0.26, 0.36}})); } void temp() { rect(-0.08, -0.32, 0.08, 0.10); circle(0.0, 0.18, 0.14, true); line(points({{0.0, -0.10}, {0.0, 0.10}}), 3); } void camera() { // two cameras as on the device: body + a lens that narrows to the right; // the upper one outlined, the lower one filled const struct { double y0; bool filled; } cams[2] = {{-0.35, false}, {0.03, true}}; for (const auto &cam : cams) { const double y0 = cam.y0; const double y1 = y0 + 0.30; rect(-0.33, y0, 0.11, y1, cam.filled); poly(points({{0.11, y0 + 0.04}, {0.27, y0 + 0.09}, {0.27, y1 - 0.09}, {0.11, y1 - 0.04}}), cam.filled); } } void gear() { QPolygonF pts; for (int i = 0; i < 16; ++i) { const double r = i % 2 == 0 ? 0.31 : 0.23; const double a = i * Pi / 8; pts << p(r * std::cos(a), r * std::sin(a)); } poly(pts); circle(0, 0, 0.09); } void arrow(int dx, int dy) { arrowLine(points({{-0.28 * dx, -0.28 * dy}, {0.28 * dx, 0.28 * dy}}), 3, 12, 14, 6); } void enter() { arrowLine(points({{0.24, -0.24}, {0.24, 0.06}, {-0.26, 0.06}}), 3, 12, 14, 6); } QPainter &m_p; QPointF m_c; double m_s; }; } // namespace PanelWidget::PanelWidget(QWidget *parent) : QWidget(parent) { setMinimumSize(200, 120); } void PanelWidget::setLeds(quint32 mask) { if (mask == m_mask) return; m_mask = mask; update(); } void PanelWidget::setInteractive(bool on) { m_interactive = on; } PanelWidget::Geometry PanelWidget::geometry() const { const double w = std::max(width(), 200); const double h = std::max(height(), 120); const double scale = std::min(w / PanelW, h / PanelH); return {scale, (w - PanelW * scale) / 2, (h - PanelH * scale) / 2}; } void PanelWidget::paintEvent(QPaintEvent *) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); painter.fillRect(rect(), Style::Background); const Geometry g = geometry(); painter.setPen(QPen(Qt::black, 3)); painter.setBrush(Style::Panel); painter.drawRect(QRectF(g.ox, g.oy, PanelW * g.scale, PanelH * g.scale)); const double s = Btn * g.scale; for (const Button &b : Buttons) { const QPointF c(g.ox + b.x * g.scale, g.oy + b.y * g.scale); const bool held = m_pressed.contains(QLatin1String(b.name)); painter.setPen(QPen(Style::Line, held ? 3 : 1, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin)); painter.setBrush(Style::Panel); painter.drawRect(QRectF(c.x() - s / 2, c.y() - s / 2, s, s)); IconPainter(painter, c, s).draw(b.icon); for (int i = 0; i < 2 && b.leds[i]; ++i) { const qsizetype bit = AdvCtrl::RemoteClient::LedNames.indexOf(QLatin1String(b.leds[i])); const bool on = bit >= 0 && (m_mask & (1u << bit)); const QPointF led(c.x() + s / 2 - 0.11 * s, c.y() - s / 2 + 0.11 * s + i * 0.16 * s); const double r = LedR * g.scale; painter.setPen(Qt::NoPen); painter.setBrush(on ? Style::LedOn : Style::LedOff); painter.drawEllipse(led, r, r); } } } QString PanelWidget::buttonAt(const QPointF &pos) const { const Geometry g = geometry(); const double s = Btn * g.scale; for (const Button &b : Buttons) { const QPointF c(g.ox + b.x * g.scale, g.oy + b.y * g.scale); if (QRectF(c.x() - s / 2, c.y() - s / 2, s, s).contains(pos)) return QLatin1String(b.name); } return QString(); } void PanelWidget::mousePressEvent(QMouseEvent *event) { if (event->button() != Qt::LeftButton) return; m_mouseButton = buttonAt(event->position()); if (!m_mouseButton.isEmpty()) press(m_mouseButton); } void PanelWidget::mouseReleaseEvent(QMouseEvent *event) { if (event->button() != Qt::LeftButton || m_mouseButton.isEmpty()) return; const QString button = m_mouseButton; m_mouseButton.clear(); release(button); // the button pressed, also when the mouse moved away } void PanelWidget::press(const QString &button) { if (!m_interactive || m_pressed.contains(button)) return; QElapsedTimer held; held.start(); m_pressed.insert(button, held); update(); } void PanelWidget::release(const QString &button) { const auto it = m_pressed.constFind(button); if (it == m_pressed.constEnd()) return; const qint64 heldMs = it->elapsed(); m_pressed.erase(it); update(); QString key = button; for (const Button &b : Buttons) { if (button == QLatin1String(b.name) && b.longName && heldMs >= LongPressMs) key = QLatin1String(b.longName); } emit keyPressed(key); }