// =============================================================================
//  QLineEdit 自绘小眼睛 / echo mode 切换 —— Day4 上午教学示例
//  对应课程：OUTLINE_4DAY.md Day4 上午「消息机制和事件 + 绘图事件」
//
//  覆盖两个主题：
//    1) 组件绘制  —— 覆写 paintEvent，用 QPainter 在 QLineEdit 右侧自绘眼睛图标
//    2) 事件捕捉  —— 覆写 mousePressEvent / mouseMoveEvent，几何 hit-test 命中眼睛
//                    时切换 echoMode，且不阻断基类的光标定位/拖选行为
//
//  三区对照（先简后深）：
//    ① echo mode 四态静态对照（Normal / NoEcho / Password / PasswordEchoOnEdit）
//    ② addAction 一行版（Qt 内置，作「为什么还要自绘」的引子）
//    ③ 自绘版主秀：PasswordLineEdit 自绘眼睛 + clear button 避让（读 clearButton 子 widget 几何）
// =============================================================================
#include <QAction>
#include <QApplication>
#include <QCheckBox>
#include <QEvent>
#include <QFormLayout>
#include <QGroupBox>
#include <QIcon>
#include <QLabel>
#include <QLineEdit>
#include <QMouseEvent>
#include <QPaintEvent>
#include <QPainter>
#include <QPixmap>
#include <QRect>
#include <QStyle>
#include <QTimer>
#include <QVBoxLayout>
#include <QDebug>

#include <iostream>

using namespace std;
// -----------------------------------------------------------------------------
//  眼睛图标绘制：PasswordLineEdit::paintEvent 与 ②区 QIcon pixmap 共用（DRY）。
//  closed=true  → 当前隐藏(密码)态：眼眶椭圆 + 斜杠（「看不见」）
//  closed=false → 当前明文态：眼眶椭圆 + 实心瞳孔（「能看见」）
//  hover=true   → 描边加深为蓝色，给即时反馈
// -----------------------------------------------------------------------------
static void paintEye(QPainter* p, const QRect& r, bool closed, bool hover)
{
    p->setRenderHint(QPainter::Antialiasing, true);
    const QColor stroke = hover ? QColor(QStringLiteral("#0078D7"))
                                : QColor(QStringLiteral("#5a5a5a"));
    // 眼眶：左右各留 10%、上下各留 20% 的内边距，画一个扁椭圆（杏仁眼简化形）
    const QRect eye = r.adjusted(r.width() * 10 / 100, r.height() * 20 / 100,
                                 -(r.width() * 10 / 100), -(r.height() * 20 / 100));
    p->setPen(QPen(stroke, hover ? 2.0 : 1.6));
    p->setBrush(Qt::NoBrush);
    p->drawEllipse(eye);

    if (closed) {
        // 密码态：一条斜杠盖住眼睛，直观表示「内容隐藏中」
        p->drawLine(eye.topLeft(), eye.bottomRight());
    } else {
        // 明文态：实心瞳孔，直观表示「内容可见」
        p->setPen(Qt::NoPen);
        p->setBrush(stroke);
        const QPoint c = eye.center();
        const int pr = eye.height() / 4;
        p->drawEllipse(c, pr, pr);
    }
}

// 用同一套绘制逻辑生成 QIcon（供 ②区 addAction 用，与 ③区自绘像素一致）
static QIcon makeEyeIcon(const QStyle* st, bool closed)
{
    const int sz = st->pixelMetric(QStyle::PM_SmallIconSize);
    QPixmap pm(sz, sz);
    pm.fill(Qt::transparent);
    QPainter p(&pm);
    paintEye(&p, QRect(0, 0, sz, sz), closed, /*hover=*/false);
    return QIcon(pm);
}

// =============================================================================
//  PasswordLineEdit：自绘版主秀
// =============================================================================
class PasswordLineEdit : public QLineEdit {
    Q_OBJECT
public:
    explicit PasswordLineEdit(QWidget* parent = nullptr) : QLineEdit(parent)
    {
        setEchoMode(QLineEdit::Password);
        // ★ mouseTracking 默认关：不按鼠标键收不到 mouseMoveEvent → hover 高亮出不来。
        setMouseTracking(true);
        // 右侧给眼睛预留宽度，避免文字被眼睛遮挡（clear button 的文本避让由 Qt 内部处理）。
        const int sz = style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, this);
        setTextMargins(0, 0, sz + 8, 0);
    }

    bool passwordMode() const { return m_passwordMode; }

signals:
    void passwordModeChanged(bool password);

protected:
    // 【组件绘制】先让基类画原生内容（文本 / 边框 / clear button），再叠加自绘的眼睛。
    // 顺序关键：必须先画基类，paint 后 clear button 几何才最终确定，recomputeEyeRect 才准。
    void paintEvent(QPaintEvent* e) override
    {
        QLineEdit::paintEvent(e);
        recomputeEyeRect();
        QPainter p(this);
        paintEye(&p, m_eyeRect, /*closed=*/m_passwordMode, m_eyeHover);
    }

    // 【事件捕捉】命中眼睛 → 切 echoMode 并 accept 阻断；否则交基类保留编辑行为。
    void mousePressEvent(QMouseEvent* e) override
    {
        recomputeEyeRect();
        if (e->button() == Qt::LeftButton && m_eyeRect.contains(e->pos())) {
            m_passwordMode = !m_passwordMode;
            setEchoMode(m_passwordMode ? QLineEdit::Password : QLineEdit::Normal);
            emit passwordModeChanged(m_passwordMode);
            update();
            e->accept();   // ★ accept + return：不让基类把这次点击当光标定位
            return;
        }
        QLineEdit::mousePressEvent(e);   // 非命中：光标定位 / 双击选词 / 右键菜单照常
    }

    // hover 高亮：命中眼睛变手势指针，离开变文本光标
    void mouseMoveEvent(QMouseEvent* e) override
    {
        recomputeEyeRect();
        const bool over = m_eyeRect.contains(e->pos());
        if (over != m_eyeHover) {
            m_eyeHover = over;
            update(m_eyeRect);   // 局部刷新，避免整控件重绘
        }
        setCursor(over ? Qt::PointingHandCursor : Qt::IBeamCursor);
        QLineEdit::mouseMoveEvent(e);   // 保留文本拖选
    }

    void leaveEvent(QEvent* e) override
    {
        if (m_eyeHover) {
            m_eyeHover = false;
            update(m_eyeRect);
        }
        QLineEdit::leaveEvent(e);
    }

private:
    // 【clear button 避让】读 QLineEdit 内置 clear button 子 widget 的真实几何，
    //   眼睛贴其左侧；未启用时贴右边。【注】QStyle 没有 CC_LineEdit / SC_LineEditClearButton
    //   ——QLineEdit 不经 QStyle 的 complex control 绘制，无对应 subcontrol 可查，故改读子 widget。
    //   clear button 的宽度随主题/DPI/字号变化，且仅在有文本时显示占位，故同时判文本。
    void recomputeEyeRect()
    {
        const int sz = style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, this);
        int rightEdge = width() - 6;   // 默认：贴右边内边距
        if (!text().isEmpty()) {
            const auto& children = this->children();
            auto cbFound = false;
            QWidget *cb = nullptr;
            for (auto & child : findChildren<QWidget *>("")) {

                ;
            }
            for (auto& child : children) {
                qDebug() << child->objectName() << child->metaObject()->className() << endl;
//                if (child->objectName().endsWith("clearaction")) {
//                    cbFound = true;
//                    cb = qobject_cast<QWidget *>(child);
//                    if (cbFound) {
//                        rightEdge = rightEdge - sz - 4;   // 有 clear button：眼睛贴其左侧，留 4px 缝
//                    } else {

//                        qDebug() << "cast failed" << endl;
//                    }
//                    break;
//                }
            }
        }
        m_eyeRect = QRect(rightEdge - sz, (height() - sz) / 2, sz, sz);
    }

    bool m_passwordMode = true;
    bool m_eyeHover = false;
    QRect m_eyeRect;
};

// =============================================================================
//  ShowcaseWidget：三区对照窗口
// =============================================================================
class ShowcaseWidget : public QWidget {
    Q_OBJECT
public:
    explicit ShowcaseWidget(QWidget* parent = nullptr) : QWidget(parent)
    {
        setWindowTitle(QStringLiteral("QLineEdit 自绘小眼睛 / echo mode 切换"));
        auto* root = new QVBoxLayout(this);
        const QString sample = QStringLiteral("hello123");

        // ---- ① echo mode 四态静态对照 -------------------------------------------------
        {
            auto* g = new QGroupBox(
                QStringLiteral("① echo mode 四态对照（文本 \"hello123\"）"), this);
            auto* f = new QFormLayout(g);
            auto* eNormal = makeRO(sample, QLineEdit::Normal);
            auto* eNoEcho = makeRO(sample, QLineEdit::NoEcho);
            auto* ePwd = makeRO(sample, QLineEdit::Password);
            auto* ePwdOnEdit = makeRO(sample, QLineEdit::PasswordEchoOnEdit);
            f->addRow(QStringLiteral("Normal（明文）"), eNormal);
            f->addRow(QStringLiteral("NoEcho（完全无显示，光标仍可移动）"), eNoEcho);
            f->addRow(QStringLiteral("Password（密文圆点）"), ePwd);
            // PasswordEchoOnEdit 的「编辑时明文」需取消只读后键入才显现（见 README）。
            f->addRow(QStringLiteral("PasswordEchoOnEdit（失焦密文/编辑明文）"), ePwdOnEdit);
            root->addWidget(g);
        }

        // ---- ② addAction 一行版（Qt 内置，对照） -------------------------------------
        {
            auto* g = new QGroupBox(
                QStringLiteral("② addAction 一行版（Qt 内置，对照「为什么还要自绘」）"), this);
            auto* f = new QFormLayout(g);
            auto* edit = new QLineEdit(this);
            edit->setEchoMode(QLineEdit::Password);
            edit->setText(sample);
            // 一行即可：Qt 自动处理位置 / 点击 / 与 clear button 的避让。但它碰不到「绘制/事件」主题。
            QAction* eyeAct = edit->addAction(makeEyeIcon(style(), /*closed=*/true),
                                              QLineEdit::TrailingPosition);
            eyeAct->setToolTip(QStringLiteral("点击切换显示"));
            connect(eyeAct, &QAction::triggered, this, [this, edit, eyeAct]() {
                const bool toNormal = edit->echoMode() == QLineEdit::Password;
                edit->setEchoMode(toNormal ? QLineEdit::Normal : QLineEdit::Password);
                eyeAct->setIcon(makeEyeIcon(style(), /*closed=*/!toNormal));
                log(QStringLiteral("② addAction：echoMode → %1")
                        .arg(toNormal ? QStringLiteral("Normal") : QStringLiteral("Password")));
            });
            f->addRow(QStringLiteral("密码框 + 眼睛(内置 action)"), edit);
            root->addWidget(g);
        }

        // ---- ③ 自绘版主秀 -------------------------------------------------------------
        {
            auto* g = new QGroupBox(
                QStringLiteral("③ 自绘版主秀：paintEvent 自绘 + mouseEvent 捕获 + clear button 避让"),
                this);
            auto* f = new QFormLayout(g);
            m_pwdEdit = new PasswordLineEdit(this);
            m_pwdEdit->setText(sample);
            connect(m_pwdEdit, &PasswordLineEdit::passwordModeChanged, this, [this](bool pwd) {
                log(QStringLiteral("③ 自绘：echoMode → %1")
                        .arg(pwd ? QStringLiteral("Password") : QStringLiteral("Normal")));
            });
            auto* cbClear = new QCheckBox(
                QStringLiteral("启用 QLineEdit 内置 clear button（观察眼睛如何避让到其左侧）"), this);
            cbClear->setChecked(false);
            connect(cbClear, &QCheckBox::toggled, m_pwdEdit, &QLineEdit::setClearButtonEnabled);
            connect(cbClear, &QCheckBox::toggled, this, [this](bool on) {
                log(QStringLiteral("③ clear button %1")
                        .arg(on ? QStringLiteral("开启") : QStringLiteral("关闭")));
            });
            f->addRow(QStringLiteral("密码框 + 自绘眼睛"), m_pwdEdit);
            f->addRow(QString(), cbClear);
            root->addWidget(g);
        }

        m_status = new QLabel(QStringLiteral("事件日志：就绪。点击 ②③ 的眼睛，或勾选 clear button。"), this);
        m_status->setStyleSheet(QStringLiteral("padding:4px; color:#333;"));
        m_status->setWordWrap(true);
        root->addWidget(m_status);
        root->addStretch();
    }

private:
    void log(const QString& s) { m_status->setText(QStringLiteral("事件日志：%1").arg(s)); }

    static QLineEdit* makeRO(const QString& text, QLineEdit::EchoMode mode)
    {
        auto* e = new QLineEdit(text);
        e->setEchoMode(mode);
        e->setReadOnly(true);
        return e;
    }

    PasswordLineEdit* m_pwdEdit = nullptr;
    QLabel* m_status = nullptr;
};

// =============================================================================
//  main
// =============================================================================
int main(int argc, char* argv[])
{
    QApplication app(argc, argv);
    ShowcaseWidget w;
    w.resize(540, 440);
    w.show();

    // 离屏自动化验证：200ms 后自动退出，退出码 0 表示窗口正常构建与渲染。
    if (qEnvironmentVariable("QT_QPA_PLATFORM") == QStringLiteral("offscreen")) {
        QTimer::singleShot(200, qApp, &QCoreApplication::quit);
    }
    return app.exec();
}

#include "qt_lineedit_eye_toggle.moc"
