Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions plugins/dde-dock/bluetooth/componments/bluetoothapplet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@
#include <QBoxLayout>
#include <QDebug>
#include <QMouseEvent>
#include <QScroller>

Check warning on line 28 in plugins/dde-dock/bluetooth/componments/bluetoothapplet.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QScroller> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QString>

Check warning on line 29 in plugins/dde-dock/bluetooth/componments/bluetoothapplet.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QString> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QToolButton>

Check warning on line 30 in plugins/dde-dock/bluetooth/componments/bluetoothapplet.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QToolButton> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QCursor>

Check warning on line 31 in plugins/dde-dock/bluetooth/componments/bluetoothapplet.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QCursor> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QAbstractTextDocumentLayout>

Check warning on line 32 in plugins/dde-dock/bluetooth/componments/bluetoothapplet.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QAbstractTextDocumentLayout> not found. Please note: Cppcheck does not need standard library headers to get proper results.

SettingLabel::SettingLabel(QString text, QWidget *parent)
: QWidget(parent)
Expand Down Expand Up @@ -253,6 +255,9 @@
m_airplaneModeWidget->setFixedWidth(ItemWidth);
m_mainLayout->addWidget(m_airplaneModeWidget);

// Install event filter to handle Enter event for link hover detection
m_airplaneModeLabel->installEventFilter(this);

QToolButton *disableIcon = new QToolButton(m_disableWidget);
disableIcon->setAttribute(Qt::WA_TransparentForMouseEvents);
disableIcon->setIcon(QIcon::fromTheme("bluetooth_disable"));
Expand Down Expand Up @@ -373,3 +378,41 @@
m_minHeight = minHeight;
updateSize();
}

bool BluetoothApplet::eventFilter(QObject *watched, QEvent *event)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider refactoring the new eventFilter-based hover handling to cache the text document and/or move the logic into a dedicated label helper or subclass to avoid per-mouse-move reconstruction and generic casting.

The new eventFilter adds non‑trivial complexity. You can keep the behavior but reduce complexity and per‑event cost by:

  1. Avoiding document reconstruction on every mouse move
  2. Avoiding repeated casting/branching in a generic eventFilter
  3. Encapsulating the logic in the label itself (or at least in a small helper)

Concrete steps:


1. Cache a QTextDocument instead of rebuilding on every move

Add a member to BluetoothApplet (or to a dedicated label subclass, see next section):

// in BluetoothApplet
private:
    std::unique_ptr<QTextDocument> m_airplaneModeDoc;

Initialize/update it when the label text or size changes:

// after setting up m_airplaneModeLabel
m_airplaneModeLabel->installEventFilter(this);
updateAirplaneModeDoc();

connect(m_airplaneModeLabel, &QLabel::linkActivated, this, [this](const QString &) {
    // if text changes dynamically, call updateAirplaneModeDoc();
});

connect(m_airplaneModeLabel, &QLabel::windowTitleChanged, this, [this]() {
    updateAirplaneModeDoc();
});

connect(m_airplaneModeLabel, &QWidget::resizeEvent, this, [this]() {
    updateAirplaneModeDoc();
});

And the helper:

void BluetoothApplet::updateAirplaneModeDoc()
{
    if (!m_airplaneModeLabel)
        return;

    if (!m_airplaneModeDoc)
        m_airplaneModeDoc = std::make_unique<QTextDocument>();

    m_airplaneModeDoc->setDefaultFont(m_airplaneModeLabel->font());
    m_airplaneModeDoc->setMarkdown(m_airplaneModeLabel->text());
    m_airplaneModeDoc->setTextWidth(m_airplaneModeLabel->contentsRect().width());
}

Now eventFilter becomes cheaper and simpler:

bool BluetoothApplet::eventFilter(QObject *watched, QEvent *event)
{
    if (watched == m_airplaneModeLabel) {
        if (event->type() == QEvent::Leave) {
            m_airplaneModeLabel->setCursor(Qt::ArrowCursor);
            return true;
        }

        if (event->type() == QEvent::MouseMove && m_airplaneModeDoc) {
            auto *mouseEvent = static_cast<QMouseEvent *>(event);
            const QPoint textPos = mouseEvent->pos() - m_airplaneModeLabel->contentsRect().topLeft();
            const QString anchor = m_airplaneModeDoc->documentLayout()->anchorAt(textPos);
            m_airplaneModeLabel->setCursor(anchor.isEmpty() ? Qt::ArrowCursor
                                                            : Qt::PointingHandCursor);
            return true;
        }
    }

    return QWidget::eventFilter(watched, event);
}

This keeps your workaround but removes the per‑move document construction and most casting branching.


2. Optionally encapsulate in a small label subclass

If you want to go one step further and de‑mix concerns from BluetoothApplet, you can move the hover logic into a small subclass used only for this label:

class HoverLinkLabel : public DLabel {
    Q_OBJECT
public:
    explicit HoverLinkLabel(QWidget *parent = nullptr)
        : DLabel(parent)
    {
        setMouseTracking(true);
    }

protected:
    void resizeEvent(QResizeEvent *e) override
    {
        DLabel::resizeEvent(e);
        updateDoc();
    }

    void setText(const QString &text) override
    {
        DLabel::setText(text);
        updateDoc();
    }

    void leaveEvent(QEvent *e) override
    {
        setCursor(Qt::ArrowCursor);
        DLabel::leaveEvent(e);
    }

    void mouseMoveEvent(QMouseEvent *event) override
    {
        if (m_doc) {
            const QPoint textPos = event->pos() - contentsRect().topLeft();
            const QString anchor = m_doc->documentLayout()->anchorAt(textPos);
            setCursor(anchor.isEmpty() ? Qt::ArrowCursor : Qt::PointingHandCursor);
        }
        DLabel::mouseMoveEvent(event);
    }

private:
    void updateDoc()
    {
        if (!m_doc)
            m_doc = std::make_unique<QTextDocument>();

        m_doc->setDefaultFont(font());
        m_doc->setMarkdown(text());
        m_doc->setTextWidth(contentsRect().width());
    }

    std::unique_ptr<QTextDocument> m_doc;
};

Usage:

m_airplaneModeLabel = new HoverLinkLabel(m_airplaneModeWidget);
m_airplaneModeLabel->setText(tr("Disable [Airplane Mode](#) first if you want to connect to a Bluetooth"));
m_airplaneModeLabel->setTextFormat(Qt::MarkdownText);
m_airplaneModeLabel->setWordWrap(true);

This keeps all existing behavior, but removes link‑hover complexity from BluetoothApplet and avoids repeated document construction or casting in a generic eventFilter.

{
// Use eventFilter because standard linkHovered signal is unreliable for vertical movement
if (watched == m_airplaneModeLabel) {
if (event->type() == QEvent::Leave) {
m_airplaneModeLabel->setCursor(Qt::ArrowCursor);
} else if (event->type() == QEvent::MouseMove) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
QLabel *label = qobject_cast<QLabel*>(watched);
Comment on lines +386 to +390
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Redundant qobject_cast when watched is already known to be the target label.

Since this block only runs when watched == m_airplaneModeLabel, and m_airplaneModeLabel is (and should remain) a QLabel, the qobject_cast<QLabel*>(watched) and null-check aren’t needed. You can instead use m_airplaneModeLabel directly here and rely on that invariant, which simplifies the logic and avoids the extra RTTI cost.

if (label) {
// Manual hit test using a temporary QTextDocument
QTextDocument doc;
Comment on lines +388 to +393
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (performance): QTextDocument construction and markdown parsing on every mouse move can be expensive.

Since this runs on every mouse move, repeatedly creating a QTextDocument and reparsing the markdown may be costly. Please consider caching a prepared QTextDocument (or parsed content) that is updated only when the label text changes, and reuse it for hit testing instead of recreating it each time.

Suggested implementation:

            QLabel *label = qobject_cast<QLabel*>(watched);
            if (label) {
                // Manual hit test using a cached QTextDocument to avoid reparsing on every mouse move.
                // The document is updated only when the label's text/font/width changes.
                const QString currentText = label->text();
                const QFont currentFont = label->font();
                const qreal currentWidth = label->contentsRect().width();

                if (m_airplaneModeDocDirty
                    || m_cachedAirplaneModeText != currentText
                    || m_cachedAirplaneModeFont != currentFont
                    || !qFuzzyCompare(m_cachedAirplaneModeWidth, currentWidth)) {

                    m_airplaneModeDoc.setDefaultFont(currentFont);
                    m_airplaneModeDoc.setMarkdown(currentText);
                    m_airplaneModeDoc.setTextWidth(currentWidth);

                    m_cachedAirplaneModeText = currentText;
                    m_cachedAirplaneModeFont = currentFont;
                    m_cachedAirplaneModeWidth = currentWidth;
                    m_airplaneModeDocDirty = false;
                }

                QTextDocument &doc = m_airplaneModeDoc;

                // Adjust for alignment and margins if necessary.
                // DTipLabel/DLabel might have specific internal layouts, but assuming standard QLabel behavior:
                // Width must be set for word wrap to match (already set when cache is updated)

                // Map mouse position to document coordinates
                // Assuming text starts at contentsRect().topLeft()
                QPoint textPos = mouseEvent->pos() - label->contentsRect().topLeft();

To fully support this caching, the following changes are also needed elsewhere in the codebase:

  1. Add member variables to BluetoothApplet (likely in bluetoothapplet.h):

    private:
        QTextDocument m_airplaneModeDoc;
        QString m_cachedAirplaneModeText;
        QFont m_cachedAirplaneModeFont;
        qreal m_cachedAirplaneModeWidth = 0.0;
        bool m_airplaneModeDocDirty = true;
  2. Initialize state in the BluetoothApplet constructor (in bluetoothapplet.cpp):

    • Ensure m_airplaneModeDocDirty is set to true (if not already via in-class initializer).
    • Optionally pre-fill the cache once m_airplaneModeLabel is constructed and its initial text is set.
  3. Mark the cache dirty when the label content changes:

    • If m_airplaneModeLabel (or its actual type, e.g. DLabel) emits textChanged(const QString &), connect it to a lambda/slot that sets m_airplaneModeDocDirty = true;.
    • If the label's font or size can change dynamically (e.g. on theme/scale changes), also mark the cache dirty in those code paths or rely on the width/font comparison already present.
      Example connection in the constructor after m_airplaneModeLabel is created:
    connect(m_airplaneModeLabel, &QLabel::textChanged, this, [this]() {
        m_airplaneModeDocDirty = true;
    });

    Adjust the signal type if m_airplaneModeLabel is not a plain QLabel and uses a different signal.

doc.setDefaultFont(label->font());
doc.setMarkdown(label->text());

// Adjust for alignment and margins if necessary.
// DTipLabel/DLabel might have specific internal layouts, but assuming standard QLabel behavior:
// Width must be set for word wrap to match
doc.setTextWidth(label->contentsRect().width());

// Map mouse position to document coordinates
// Assuming text starts at contentsRect().topLeft()
QPoint textPos = mouseEvent->pos() - label->contentsRect().topLeft();

const QString anchor = doc.documentLayout()->anchorAt(textPos);

if (!anchor.isEmpty()) {
m_airplaneModeLabel->setCursor(Qt::PointingHandCursor);
} else {
m_airplaneModeLabel->setCursor(Qt::ArrowCursor);
}
}
}
}

return QWidget::eventFilter(watched, event);
}
3 changes: 3 additions & 0 deletions plugins/dde-dock/bluetooth/componments/bluetoothapplet.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ public slots:
// 更新蓝牙插件主界面大小
void updateSize();

protected:
bool eventFilter(QObject *watched, QEvent *event) override;

private:
QScrollArea *m_scrollArea;
QWidget *m_contentWidget;
Expand Down