Skip to content

refactor(gui): integrate debug dialog access via hidden click mechanism#716

Closed
pppanghu77 wants to merge 1 commit intolinuxdeepin:masterfrom
pppanghu77:master
Closed

refactor(gui): integrate debug dialog access via hidden click mechanism#716
pppanghu77 wants to merge 1 commit intolinuxdeepin:masterfrom
pppanghu77:master

Conversation

@pppanghu77
Copy link
Copy Markdown
Contributor

@pppanghu77 pppanghu77 commented Mar 24, 2026

  • Add 10-click sequence on Basic Settings label to trigger debug dialog
  • Implement click counter with 3-second timeout reset functionality
  • Add similar click-based debug access to data transfer start widget
  • Install event filters for mouse button release detection
  • Connect timer signals for automatic click count reset

Summary by Sourcery

Add a hidden debug dialog accessible from key GUI labels and wire it into shared utilities and plugins.

New Features:

  • Introduce a reusable debug dialog for adjusting log level, exporting logs, and detecting network ports.
  • Enable hidden 10-click access to the debug dialog from the Basic Settings label in the settings dialog.
  • Enable hidden 10-click access to the debug dialog from the data transfer start widget title.

Enhancements:

  • Extend common utilities with a helper to return the current log directory for use by debugging tools.

Build:

  • Include the new debug dialog sources in cooperation core, transfer, and data-transfer core plugin builds.

@deepin-ci-robot
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: pppanghu77

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Mar 24, 2026

Reviewer's Guide

Adds a reusable DebugDialog utility and wires it into the cooperation and data-transfer GUIs via a hidden 10-click mechanism on key labels, including timer-based click reset and log export/port-detection functionality, plus minor common utils and CMake updates.

Sequence diagram for 10-click Basic Settings label debug dialog trigger

sequenceDiagram
    actor User
    participant SettingDialog
    participant SettingDialogPrivate as D_ptr
    participant basicLabel as BasicLabel
    participant clickTimer as QTimer
    participant DebugDialog

    User->>basicLabel: Left mouse button release
    basicLabel->>SettingDialog: eventFilter(watched, MouseButtonRelease)
    SettingDialog->>D_ptr: handle basicLabel click
    alt Left button clicked
        D_ptr->>D_ptr: clickCount++
        alt clickCount < CLICK_THRESHOLD
            D_ptr->>clickTimer: start(3000)
        else clickCount >= CLICK_THRESHOLD
            D_ptr->>D_ptr: showDebugDialog()
            D_ptr->>DebugDialog: create
            DebugDialog->>DebugDialog: exec()
            D_ptr->>D_ptr: resetClickCount()
        end
        SettingDialog-->>basicLabel: event handled (true)
    else Other mouse button
        SettingDialog-->>basicLabel: pass to base eventFilter
    end

    clickTimer-->>D_ptr: timeout()
    D_ptr->>D_ptr: resetClickCount()
Loading

Sequence diagram for 10-click StartWidget title debug dialog trigger

sequenceDiagram
    actor User
    participant StartWidget
    participant titleLabel as TitleLabel
    participant clickTimer as QTimer
    participant DebugDialog

    User->>titleLabel: Left mouse button release
    titleLabel->>StartWidget: eventFilter(obj, MouseButtonRelease)
    alt obj == titleLabel and LeftButton
        StartWidget->>StartWidget: clickCount++
        alt clickCount < CLICK_THRESHOLD
            StartWidget->>clickTimer: start(3000)
        else clickCount >= CLICK_THRESHOLD
            StartWidget->>StartWidget: showDebugDialog()
            StartWidget->>DebugDialog: create
            DebugDialog->>DebugDialog: exec()
            StartWidget->>StartWidget: resetClickCount()
        end
        StartWidget-->>titleLabel: event handled (true)
    else other object or button
        StartWidget-->>titleLabel: pass to QFrame::eventFilter
    end

    clickTimer-->>StartWidget: timeout()
    StartWidget->>StartWidget: resetClickCount()
Loading

Class diagram for DebugDialog and hidden click trigger integration

classDiagram
    class StartWidget {
        +StartWidget(parent: QWidget*)
        +void nextPage()
        +void themeChanged(theme: int)
        +bool eventFilter(obj: QObject*, event: QEvent*)
        -void initUI()
        -void resetClickCount()
        -void showDebugDialog()
        -QPushButton* nextButton
        -QLabel* titleLabel
        -QTimer* clickTimer
        -int clickCount
        -static int CLICK_THRESHOLD
    }

    class SettingDialog {
        +SettingDialog(QWidget* parent)
        +~SettingDialog()
        +bool eventFilter(watched: QObject*, event: QEvent*)
        -SettingDialogPrivate* d
    }

    class SettingDialogPrivate {
        +SettingDialogPrivate(SettingDialog* qq)
        +~SettingDialogPrivate()
        +void initWindow()
        +void createBasicWidget()
        +void createTransferWidget()
        +void createClipboardShareWidget()
        +bool checkNameValid()
        +void resetClickCount()
        +void showDebugDialog()
        -void initFont()
        -void initWindowContent()
        -void initConnections()
        -SettingDialog* q
        -QVBoxLayout* mainLayout
        -QScrollArea* scrollWidget
        -QWidget* contentWidget
        -QGridLayout* contentLayout
        -CooperationLabel* basicLabel
        -QComboBox* findCB
        -QComboBox* connectCB
        -QLineEdit* nameEdit
        -CooperationSwitchButton* devShareSwitchBtn
        -CooperationSwitchButton* clipShareSwitchBtn
        -FileChooserEdit* chooserEdit
        -QStringList findComboBoxInfo
        -QList~QPair~QString,QString~~ connectComboBoxInfo
        -QFont groupFont
        -QFont tipFont
        -QTimer* clickTimer
        -int clickCount
        -static int CLICK_THRESHOLD
    }

    class DebugDialog {
        +DebugDialog(parent: QWidget*)
        +~DebugDialog()
        -void initUI()
        -void loadCurrentLogLevel()
        -void onLogLevelChanged(index: int)
        -void onExportLog()
        -void onDetectPorts()
        -void onRefreshPorts()
        -void detectPortsInternal(targetIP: QString, ports: QStringList)
        -bool copyDirectory(src: QString, dest: QString)
        -QLabel* m_logLevelLabel
        -QComboBox* m_logLevelCombo
        -QPushButton* m_exportLogBtn
        -QPushButton* m_detectPortsBtn
        -QPushButton* m_refreshBtn
        -QPushButton* m_closeBtn
        -QTextEdit* m_resultText
        -QTimer* m_detectTimer
    }

    class CommonUitls {
        +static int getAvailablePort()
        +static QString ipcServerName(appName: QString)
        +static QString getLogDir()
        -static QString logDir()
        -static bool detailLog()
    }

    StartWidget --|> QFrame
    DebugDialog --|> QDialog

    SettingDialog o-- SettingDialogPrivate
    SettingDialogPrivate --> CooperationLabel

    StartWidget ..> DebugDialog : creates
    SettingDialogPrivate ..> DebugDialog : creates
    DebugDialog ..> CommonUitls : uses
    DebugDialog ..> QTimer : uses
    StartWidget ..> QTimer : uses
    SettingDialogPrivate ..> QTimer : uses
Loading

File-Level Changes

Change Details Files
Introduce a reusable DebugDialog with log-level control, log export, and port-detection utilities.
  • Create DebugDialog QDialog with log settings section, including a combo box bound to global log level and status output.
  • Implement log export by obtaining the internal log directory from CommonUitls, letting the user choose a destination, and recursively copying the log tree.
  • Add port detection UI and logic that queries local interfaces and tests reachability of user-specified ports on a target IP via QTcpSocket.
src/common/debugdialog.h
src/common/debugdialog.cpp
Expose the internal log directory path via CommonUitls so DebugDialog can export logs.
  • Add getLogDir() static method to CommonUitls as a public wrapper around the existing private logDir().
  • Update the implementation to return logDir() and adjust copyright years.
src/common/commonutils.h
src/common/commonutils.cpp
Wire the hidden debug dialog trigger into the data-transfer StartWidget via a 10-click sequence on the title label.
  • Track the title label as a member, install an event filter, and handle left-button MouseButtonRelease events to count clicks.
  • Use a single-shot QTimer with a 3-second interval to reset the click count if the sequence is not completed in time.
  • On reaching the CLICK_THRESHOLD, instantiate and exec DebugDialog, then reset the click counter.
src/lib/data-transfer/core/gui/connect/startwidget.h
src/lib/data-transfer/core/gui/connect/startwidget.cpp
Add a similar 10-click hidden debug trigger to the cooperation settings dialog on the "Basic Settings" label.
  • Promote the Basic Settings label to a member (basicLabel), install an event filter on the public SettingDialog, and handle left-button MouseButtonRelease events.
  • Maintain a click counter and 3-second single-shot QTimer in SettingDialogPrivate to reset the sequence when timing out.
  • On reaching CLICK_THRESHOLD, open DebugDialog modally and reset the click counter.
src/lib/cooperation/core/gui/dialogs/settingdialog_p.h
src/lib/cooperation/core/gui/dialogs/settingdialog.cpp
Ensure the new DebugDialog is built into the cooperation and data-transfer plugins and update SPDX headers.
  • Register debugdialog.h/cpp in the PLUGIN_FILES glob for cooperation core, cooperation transfer, and data-transfer core CMakeLists.
  • Update SPDX copyright year ranges to 2023 - 2026 in touched source and header files.
src/lib/cooperation/core/CMakeLists.txt
src/lib/cooperation/transfer/CMakeLists.txt
src/lib/data-transfer/core/CMakeLists.txt
src/lib/data-transfer/core/gui/connect/startwidget.cpp
src/lib/data-transfer/core/gui/connect/startwidget.h
src/lib/cooperation/core/gui/dialogs/settingdialog.cpp
src/lib/cooperation/core/gui/dialogs/settingdialog_p.h
src/common/commonutils.cpp
src/common/commonutils.h

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 5 issues, and left some high level feedback:

  • In both StartWidget and SettingDialogPrivate, the clickTimer interval is set to 3000 ms but the comment still says // 1 second timeout; update the comment or the interval to keep them consistent.
  • In DebugDialog, m_detectTimer is created, configured, and connected but never started, so the timeout handling is effectively dead code; either start the timer in the detection flow or remove it.
  • The Refresh logic in DebugDialog currently just calls onDetectPorts() again, re-prompting for IP and ports; if the intent is to re-run the last detection, consider caching the last IP/ports and reusing them in onRefreshPorts() instead of asking the user again.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In both `StartWidget` and `SettingDialogPrivate`, the `clickTimer` interval is set to 3000 ms but the comment still says `// 1 second timeout`; update the comment or the interval to keep them consistent.
- In `DebugDialog`, `m_detectTimer` is created, configured, and connected but never started, so the timeout handling is effectively dead code; either start the timer in the detection flow or remove it.
- The `Refresh` logic in `DebugDialog` currently just calls `onDetectPorts()` again, re-prompting for IP and ports; if the intent is to re-run the last detection, consider caching the last IP/ports and reusing them in `onRefreshPorts()` instead of asking the user again.

## Individual Comments

### Comment 1
<location path="src/common/debugdialog.cpp" line_range="103-68" />
<code_context>
+            this, &QDialog::accept);
+
+    // Setup detection timer
+    m_detectTimer = new QTimer(this);
+    m_detectTimer->setSingleShot(true);
+    m_detectTimer->setInterval(5000); // 5 second timeout
+    connect(m_detectTimer, &QTimer::timeout, this, [this]() {
+        m_resultText->append(tr("\nPort detection timeout!"));
+        m_detectPortsBtn->setEnabled(true);
+        m_refreshBtn->setEnabled(false);
+    });
+}
</code_context>
<issue_to_address>
**issue (bug_risk):** Detection timeout timer is never started, so the timeout logic will never trigger.

m_detectTimer is created and wired up, but never started (e.g. in onDetectPorts or detectPortsInternal), so the timeout handler that appends "Port detection timeout!" and re-enables buttons will never run. Either start/stop the timer around the detection process or remove the timer and its related logic to avoid dead code and confusion.
</issue_to_address>

### Comment 2
<location path="src/lib/data-transfer/core/gui/connect/startwidget.cpp" line_range="27" />
<code_context>
     DLOG << "Widget constructor called";
+    clickTimer = new QTimer(this);
+    clickTimer->setSingleShot(true);
+    clickTimer->setInterval(3000); // 1 second timeout
+    connect(clickTimer, &QTimer::timeout, this, &StartWidget::resetClickCount);
+
</code_context>
<issue_to_address>
**nitpick:** Timer interval and comment are inconsistent (3000 ms vs "1 second timeout").

Please align the timer interval with the comment—either change the interval to 1000 ms for a 1-second timeout or update the comment to indicate a 3-second timeout.
</issue_to_address>

### Comment 3
<location path="src/lib/data-transfer/core/gui/connect/startwidget.cpp" line_range="100" />
<code_context>
     }
 }
+
+bool StartWidget::eventFilter(QObject *obj, QEvent *event)
+{
+    if (obj == titleLabel && event->type() == QEvent::MouseButtonRelease) {
</code_context>
<issue_to_address>
**issue (complexity):** Consider extracting the click-sequence detection into a reusable helper class and using it from StartWidget (and SettingDialog) instead of keeping the timer/counter/eventFilter logic inside each widget.

You can keep the feature and significantly reduce per‑widget complexity by extracting the click‑sequence logic into a reusable helper, then reusing it in both `StartWidget` and `SettingDialog`.

### 1. Extract reusable click‑sequence helper

For example, add a small QObject in a common location (`common/clicksequencetrigger.h/.cpp`):

```cpp
// common/clicksequencetrigger.h
#pragma once

#include <QObject>
#include <QPointer>

class QTimer;
class QWidget;
class QMouseEvent;

class ClickSequenceTrigger : public QObject
{
    Q_OBJECT
public:
    explicit ClickSequenceTrigger(
        QWidget *target,
        int threshold,
        int timeoutMs,
        std::function<void()> onTriggered,
        QObject *parent = nullptr);

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

private:
    void resetClickCount();

    QPointer<QWidget> m_target;
    QTimer *m_timer;
    int m_clickCount = 0;
    const int m_threshold;
    const int m_timeoutMs;
    std::function<void()> m_onTriggered;
};
```

```cpp
// common/clicksequencetrigger.cpp
#include "clicksequencetrigger.h"

#include <QTimer>
#include <QMouseEvent>
#include <QWidget>

ClickSequenceTrigger::ClickSequenceTrigger(
    QWidget *target,
    int threshold,
    int timeoutMs,
    std::function<void()> onTriggered,
    QObject *parent)
    : QObject(parent)
    , m_target(target)
    , m_timer(new QTimer(this))
    , m_threshold(threshold)
    , m_timeoutMs(timeoutMs)
    , m_onTriggered(std::move(onTriggered))
{
    m_timer->setSingleShot(true);
    m_timer->setInterval(m_timeoutMs);
    connect(m_timer, &QTimer::timeout, this, &ClickSequenceTrigger::resetClickCount);

    if (m_target) {
        m_target->installEventFilter(this);
    }
}

bool ClickSequenceTrigger::eventFilter(QObject *obj, QEvent *event)
{
    if (obj == m_target && event->type() == QEvent::MouseButtonRelease) {
        auto *mouseEvent = static_cast<QMouseEvent*>(event);
        if (mouseEvent->button() == Qt::LeftButton) {
            ++m_clickCount;
            if (m_clickCount >= m_threshold) {
                if (m_onTriggered)
                    m_onTriggered();
                resetClickCount();
            } else {
                m_timer->start();
            }
            return true;
        }
    }
    return QObject::eventFilter(obj, event);
}

void ClickSequenceTrigger::resetClickCount()
{
    m_clickCount = 0;
}
```

### 2. Simplify `StartWidget`

Then `StartWidget` no longer needs `clickTimer`, `clickCount`, `CLICK_THRESHOLD` or its own `eventFilter` override:

```cpp
// startwidget.h (remove clickTimer/clickCount/CLICK_THRESHOLD/eventFilter/resetClickCount)
#include "common/clicksequencetrigger.h"

// ...
private:
    QLabel *titleLabel = nullptr;
    std::unique_ptr<ClickSequenceTrigger> titleClickTrigger;
// ...
```

```cpp
// startwidget.cpp
StartWidget::StartWidget(QWidget *parent)
    : QFrame(parent)
{
    DLOG << "Widget constructor called";
    initUI();

    titleClickTrigger = std::make_unique<ClickSequenceTrigger>(
        titleLabel,
        /*threshold*/ 5,          // or your existing CLICK_THRESHOLD
        /*timeoutMs*/ 3000,
        [this] { showDebugDialog(); },
        this
    );
}
```

And keep only the feature logic close to where it belongs:

```cpp
void StartWidget::showDebugDialog()
{
    DLOG << "Opening debug dialog";
    DebugDialog dialog(this);
    dialog.exec();
}
```

### 3. Reuse in `SettingDialog`

You can then apply the same helper in `SettingDialog` instead of duplicating the timer + counter + `eventFilter` pattern:

```cpp
// in SettingDialog constructor or init:
m_titleClickTrigger = std::make_unique<ClickSequenceTrigger>(
    m_titleLabel,
    5,
    3000,
    [this] { showDebugDialog(); },
    this
);
```

This keeps all behavior intact, removes duplicated low‑level event logic from widgets, and centralizes the click‑sequence behavior in a single, well‑named component.
</issue_to_address>

### Comment 4
<location path="src/lib/cooperation/core/gui/dialogs/settingdialog.cpp" line_range="485" />
<code_context>

 bool SettingDialog::eventFilter(QObject *watched, QEvent *event)
 {
+    // Check for basic label click
</code_context>
<issue_to_address>
**issue (complexity):** Consider extracting the secret multi-click handling on basicLabel into a reusable helper class and delegating from eventFilter instead of embedding the logic directly there.

You can reduce the added complexity by encapsulating the “secret click” behavior into a reusable helper and delegating from `eventFilter` instead of inlining the logic.

### 1. Extract a small reusable helper

Create a generic helper that owns the timer and click count and can be reused in `StartWidget` and `SettingDialog`:

```cpp
// secretclickhelper.h
class SecretClickHelper : public QObject
{
    Q_OBJECT
public:
    explicit SecretClickHelper(QObject *watched, QObject *parent = nullptr)
        : QObject(parent)
    {
        watched->installEventFilter(this);
        m_timer.setSingleShot(true);
        m_timer.setInterval(3000);
        connect(&m_timer, &QTimer::timeout, this, &SecretClickHelper::reset);
    }

signals:
    void activated();

protected:
    bool eventFilter(QObject *watched, QEvent *event) override
    {
        if (event->type() == QEvent::MouseButtonRelease) {
            auto *mouseEvent = static_cast<QMouseEvent *>(event);
            if (mouseEvent->button() == Qt::LeftButton) {
                if (++m_clickCount >= CLICK_THRESHOLD) {
                    emit activated();
                    reset();
                } else {
                    m_timer.start();
                }
                return true;
            }
        }
        return QObject::eventFilter(watched, event);
    }

private:
    void reset()
    {
        m_clickCount = 0;
    }

    static constexpr int CLICK_THRESHOLD = 5; // or whatever you use
    int m_clickCount = 0;
    QTimer m_timer;
};
```

### 2. Use the helper in `SettingDialogPrivate`

Wire it up in the private class instead of keeping `clickTimer`, `clickCount`, `resetClickCount`, and `showDebugDialog` there:

```cpp
// settingdialog_p.h
#include "secretclickhelper.h"

class SettingDialogPrivate : public QObject
{
    // ...
    std::unique_ptr<SecretClickHelper> basicLabelSecretClick;
    // remove: QTimer *clickTimer; int clickCount; void resetClickCount(); etc.
};
```

```cpp
// settingdialog.cpp (or .p.cpp)
void SettingDialogPrivate::createBasicWidget()
{
    basicLabel = new CooperationLabel(tr("Basic Settings"), q);
    // ... existing setup ...

    basicLabelSecretClick = std::make_unique<SecretClickHelper>(basicLabel, q);
    connect(basicLabelSecretClick.get(), &SecretClickHelper::activated,
            this, [this] {
                DLOG << "Opening debug dialog from settings";
                ::DebugDialog dialog(q);
                dialog.exec();
            });
}
```

### 3. Simplify `SettingDialog::eventFilter`

Once the helper owns the click logic, you can keep `eventFilter` focused on painting:

```cpp
bool SettingDialog::eventFilter(QObject *watched, QEvent *event)
{
    // no special-case for basicLabel here anymore

    if (event->type() != QEvent::Paint)
        return QDialog::eventFilter(watched, event);

    QWidget *widget = qobject_cast<QWidget *>(watched);
    if (!widget)
        return QDialog::eventFilter(watched, event);

    QPainter painter(widget);
    // ... existing background drawing ...
    return QDialog::eventFilter(watched, event);
}
```

You can apply the same `SecretClickHelper` to `StartWidget` to remove the duplicated click-count / timer logic there as well. This keeps the dialog’s `eventFilter` narrowly focused and consolidates the “secret click” behavior in one place.
</issue_to_address>

### Comment 5
<location path="src/lib/cooperation/core/gui/dialogs/settingdialog_p.h" line_range="48" />
<code_context>
     void onClipboardShareButtonClicked(bool clicked);
     void onFileChoosed(const QString &path);
     void reportDeviceStatus(const QString &type, bool status);
+    void resetClickCount();

 private:
</code_context>
<issue_to_address>
**issue (complexity):** Consider extracting the debug click-sequence handling into a dedicated helper QObject so the dialog class only wires it up instead of managing all the timer and counter state itself.

You can reduce the added complexity by extracting the click‑sequence debug logic into a small reusable helper, instead of storing `clickTimer`, `clickCount`, `CLICK_THRESHOLD`, and `basicLabel`-specific behavior directly in each private class.

For example, introduce a dedicated QObject that encapsulates the sequence logic and emits a signal when the threshold is reached:

```cpp
// clicksequencetrigger.h
class ClickSequenceTrigger : public QObject
{
    Q_OBJECT
public:
    explicit ClickSequenceTrigger(int threshold, int timeoutMs, QObject *parent = nullptr);

    void attachTo(QWidget *widget);  // e.g. CooperationLabel*

Q_SIGNALS:
    void triggered(); // emit when click sequence reached

private Q_SLOTS:
    void onClicked();
    void reset();

private:
    QTimer m_timer;
    int m_count = 0;
    int m_threshold = 0;
};
```

Usage in `SettingDialogPrivate` becomes focused and removes duplicated state:

```cpp
// settingdialog_p.h
#include "clicksequencetrigger.h"

// ...
private:
    void createBasicWidget();
    void showDebugDialog();

// ...
    ClickSequenceTrigger *debugClickTrigger { nullptr };
```

```cpp
// settingdialog_p.cpp (inside createBasicWidget)
basicLabel = new CooperationLabel(/*...*/);

debugClickTrigger = new ClickSequenceTrigger(/*threshold*/ 10, /*timeoutMs*/ 1500, this);
debugClickTrigger->attachTo(basicLabel);
connect(debugClickTrigger, &ClickSequenceTrigger::triggered,
        this, &SettingDialogPrivate::showDebugDialog);
```

Then reuse the same `ClickSequenceTrigger` in `StartWidget` (or any other UI) instead of adding another set of `QTimer*`, `clickCount`, `CLICK_THRESHOLD`, and `resetClickCount()`/`showDebugDialog()` methods. This keeps each private class focused on its own UI state and behavior while centralizing the debug‑click mechanism.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/common/debugdialog.cpp Outdated

m_detectPortsBtn = new QPushButton(tr("Detect Ports"), this);
m_refreshBtn = new QPushButton(tr("Refresh"), this);
m_refreshBtn->setEnabled(false);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): Detection timeout timer is never started, so the timeout logic will never trigger.

m_detectTimer is created and wired up, but never started (e.g. in onDetectPorts or detectPortsInternal), so the timeout handler that appends "Port detection timeout!" and re-enables buttons will never run. Either start/stop the timer around the detection process or remove the timer and its related logic to avoid dead code and confusion.

DLOG << "Widget constructor called";
clickTimer = new QTimer(this);
clickTimer->setSingleShot(true);
clickTimer->setInterval(3000); // 1 second timeout
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nitpick: Timer interval and comment are inconsistent (3000 ms vs "1 second timeout").

Please align the timer interval with the comment—either change the interval to 1000 ms for a 1-second timeout or update the comment to indicate a 3-second timeout.

}
}

bool StartWidget::eventFilter(QObject *obj, QEvent *event)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (complexity): Consider extracting the click-sequence detection into a reusable helper class and using it from StartWidget (and SettingDialog) instead of keeping the timer/counter/eventFilter logic inside each widget.

You can keep the feature and significantly reduce per‑widget complexity by extracting the click‑sequence logic into a reusable helper, then reusing it in both StartWidget and SettingDialog.

1. Extract reusable click‑sequence helper

For example, add a small QObject in a common location (common/clicksequencetrigger.h/.cpp):

// common/clicksequencetrigger.h
#pragma once

#include <QObject>
#include <QPointer>

class QTimer;
class QWidget;
class QMouseEvent;

class ClickSequenceTrigger : public QObject
{
    Q_OBJECT
public:
    explicit ClickSequenceTrigger(
        QWidget *target,
        int threshold,
        int timeoutMs,
        std::function<void()> onTriggered,
        QObject *parent = nullptr);

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

private:
    void resetClickCount();

    QPointer<QWidget> m_target;
    QTimer *m_timer;
    int m_clickCount = 0;
    const int m_threshold;
    const int m_timeoutMs;
    std::function<void()> m_onTriggered;
};
// common/clicksequencetrigger.cpp
#include "clicksequencetrigger.h"

#include <QTimer>
#include <QMouseEvent>
#include <QWidget>

ClickSequenceTrigger::ClickSequenceTrigger(
    QWidget *target,
    int threshold,
    int timeoutMs,
    std::function<void()> onTriggered,
    QObject *parent)
    : QObject(parent)
    , m_target(target)
    , m_timer(new QTimer(this))
    , m_threshold(threshold)
    , m_timeoutMs(timeoutMs)
    , m_onTriggered(std::move(onTriggered))
{
    m_timer->setSingleShot(true);
    m_timer->setInterval(m_timeoutMs);
    connect(m_timer, &QTimer::timeout, this, &ClickSequenceTrigger::resetClickCount);

    if (m_target) {
        m_target->installEventFilter(this);
    }
}

bool ClickSequenceTrigger::eventFilter(QObject *obj, QEvent *event)
{
    if (obj == m_target && event->type() == QEvent::MouseButtonRelease) {
        auto *mouseEvent = static_cast<QMouseEvent*>(event);
        if (mouseEvent->button() == Qt::LeftButton) {
            ++m_clickCount;
            if (m_clickCount >= m_threshold) {
                if (m_onTriggered)
                    m_onTriggered();
                resetClickCount();
            } else {
                m_timer->start();
            }
            return true;
        }
    }
    return QObject::eventFilter(obj, event);
}

void ClickSequenceTrigger::resetClickCount()
{
    m_clickCount = 0;
}

2. Simplify StartWidget

Then StartWidget no longer needs clickTimer, clickCount, CLICK_THRESHOLD or its own eventFilter override:

// startwidget.h (remove clickTimer/clickCount/CLICK_THRESHOLD/eventFilter/resetClickCount)
#include "common/clicksequencetrigger.h"

// ...
private:
    QLabel *titleLabel = nullptr;
    std::unique_ptr<ClickSequenceTrigger> titleClickTrigger;
// ...
// startwidget.cpp
StartWidget::StartWidget(QWidget *parent)
    : QFrame(parent)
{
    DLOG << "Widget constructor called";
    initUI();

    titleClickTrigger = std::make_unique<ClickSequenceTrigger>(
        titleLabel,
        /*threshold*/ 5,          // or your existing CLICK_THRESHOLD
        /*timeoutMs*/ 3000,
        [this] { showDebugDialog(); },
        this
    );
}

And keep only the feature logic close to where it belongs:

void StartWidget::showDebugDialog()
{
    DLOG << "Opening debug dialog";
    DebugDialog dialog(this);
    dialog.exec();
}

3. Reuse in SettingDialog

You can then apply the same helper in SettingDialog instead of duplicating the timer + counter + eventFilter pattern:

// in SettingDialog constructor or init:
m_titleClickTrigger = std::make_unique<ClickSequenceTrigger>(
    m_titleLabel,
    5,
    3000,
    [this] { showDebugDialog(); },
    this
);

This keeps all behavior intact, removes duplicated low‑level event logic from widgets, and centralizes the click‑sequence behavior in a single, well‑named component.

DLOG << "SettingDialog destroyed";
}

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

Choose a reason for hiding this comment

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

issue (complexity): Consider extracting the secret multi-click handling on basicLabel into a reusable helper class and delegating from eventFilter instead of embedding the logic directly there.

You can reduce the added complexity by encapsulating the “secret click” behavior into a reusable helper and delegating from eventFilter instead of inlining the logic.

1. Extract a small reusable helper

Create a generic helper that owns the timer and click count and can be reused in StartWidget and SettingDialog:

// secretclickhelper.h
class SecretClickHelper : public QObject
{
    Q_OBJECT
public:
    explicit SecretClickHelper(QObject *watched, QObject *parent = nullptr)
        : QObject(parent)
    {
        watched->installEventFilter(this);
        m_timer.setSingleShot(true);
        m_timer.setInterval(3000);
        connect(&m_timer, &QTimer::timeout, this, &SecretClickHelper::reset);
    }

signals:
    void activated();

protected:
    bool eventFilter(QObject *watched, QEvent *event) override
    {
        if (event->type() == QEvent::MouseButtonRelease) {
            auto *mouseEvent = static_cast<QMouseEvent *>(event);
            if (mouseEvent->button() == Qt::LeftButton) {
                if (++m_clickCount >= CLICK_THRESHOLD) {
                    emit activated();
                    reset();
                } else {
                    m_timer.start();
                }
                return true;
            }
        }
        return QObject::eventFilter(watched, event);
    }

private:
    void reset()
    {
        m_clickCount = 0;
    }

    static constexpr int CLICK_THRESHOLD = 5; // or whatever you use
    int m_clickCount = 0;
    QTimer m_timer;
};

2. Use the helper in SettingDialogPrivate

Wire it up in the private class instead of keeping clickTimer, clickCount, resetClickCount, and showDebugDialog there:

// settingdialog_p.h
#include "secretclickhelper.h"

class SettingDialogPrivate : public QObject
{
    // ...
    std::unique_ptr<SecretClickHelper> basicLabelSecretClick;
    // remove: QTimer *clickTimer; int clickCount; void resetClickCount(); etc.
};
// settingdialog.cpp (or .p.cpp)
void SettingDialogPrivate::createBasicWidget()
{
    basicLabel = new CooperationLabel(tr("Basic Settings"), q);
    // ... existing setup ...

    basicLabelSecretClick = std::make_unique<SecretClickHelper>(basicLabel, q);
    connect(basicLabelSecretClick.get(), &SecretClickHelper::activated,
            this, [this] {
                DLOG << "Opening debug dialog from settings";
                ::DebugDialog dialog(q);
                dialog.exec();
            });
}

3. Simplify SettingDialog::eventFilter

Once the helper owns the click logic, you can keep eventFilter focused on painting:

bool SettingDialog::eventFilter(QObject *watched, QEvent *event)
{
    // no special-case for basicLabel here anymore

    if (event->type() != QEvent::Paint)
        return QDialog::eventFilter(watched, event);

    QWidget *widget = qobject_cast<QWidget *>(watched);
    if (!widget)
        return QDialog::eventFilter(watched, event);

    QPainter painter(widget);
    // ... existing background drawing ...
    return QDialog::eventFilter(watched, event);
}

You can apply the same SecretClickHelper to StartWidget to remove the duplicated click-count / timer logic there as well. This keeps the dialog’s eventFilter narrowly focused and consolidates the “secret click” behavior in one place.

void onClipboardShareButtonClicked(bool clicked);
void onFileChoosed(const QString &path);
void reportDeviceStatus(const QString &type, bool status);
void resetClickCount();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (complexity): Consider extracting the debug click-sequence handling into a dedicated helper QObject so the dialog class only wires it up instead of managing all the timer and counter state itself.

You can reduce the added complexity by extracting the click‑sequence debug logic into a small reusable helper, instead of storing clickTimer, clickCount, CLICK_THRESHOLD, and basicLabel-specific behavior directly in each private class.

For example, introduce a dedicated QObject that encapsulates the sequence logic and emits a signal when the threshold is reached:

// clicksequencetrigger.h
class ClickSequenceTrigger : public QObject
{
    Q_OBJECT
public:
    explicit ClickSequenceTrigger(int threshold, int timeoutMs, QObject *parent = nullptr);

    void attachTo(QWidget *widget);  // e.g. CooperationLabel*

Q_SIGNALS:
    void triggered(); // emit when click sequence reached

private Q_SLOTS:
    void onClicked();
    void reset();

private:
    QTimer m_timer;
    int m_count = 0;
    int m_threshold = 0;
};

Usage in SettingDialogPrivate becomes focused and removes duplicated state:

// settingdialog_p.h
#include "clicksequencetrigger.h"

// ...
private:
    void createBasicWidget();
    void showDebugDialog();

// ...
    ClickSequenceTrigger *debugClickTrigger { nullptr };
// settingdialog_p.cpp (inside createBasicWidget)
basicLabel = new CooperationLabel(/*...*/);

debugClickTrigger = new ClickSequenceTrigger(/*threshold*/ 10, /*timeoutMs*/ 1500, this);
debugClickTrigger->attachTo(basicLabel);
connect(debugClickTrigger, &ClickSequenceTrigger::triggered,
        this, &SettingDialogPrivate::showDebugDialog);

Then reuse the same ClickSequenceTrigger in StartWidget (or any other UI) instead of adding another set of QTimer*, clickCount, CLICK_THRESHOLD, and resetClickCount()/showDebugDialog() methods. This keeps each private class focused on its own UI state and behavior while centralizing the debug‑click mechanism.

- Extract only the filename part from the full path to support both
  relative and absolute path formats
- Use QFileInfo to properly handle path separators across different
  platforms
- Replace complex string manipulation with more robust file name
  extraction logic
@deepin-ci-robot
Copy link
Copy Markdown

deepin pr auto review

我已对提供的 git diff 进行了审查,主要涉及 src/lib/data-transfer/core/utils/settinghepler.cpp 文件的修改。以下是详细的审查意见,分为语法逻辑、代码质量、代码性能和代码安全四个方面:

1. 语法逻辑

改进意见:

  • 路径拼接逻辑优化 (handleDataConfiguration 函数):

    • 原代码: QString filepath = pdir.absolutePath() + "/"; 后续使用 filepath + "transfer.json"
    • 修改后: 使用 QDir(filepath).filePath("transfer.json")
    • 评价: 这是一个改进。使用 QDir::filePath() 自动处理路径分隔符(/\),比手动拼接字符串更健壮,能避免因缺少分隔符或多余分隔符导致的路径错误,逻辑更严谨。
  • 文件名提取逻辑优化 (setFile 函数):

    • 原代码: QString file = filepath + filename.mid(filename.indexOf('/') + 1);
    • 修改后: QString file = filepath + QFileInfo(filename).fileName();
    • 评价: 这是一个改进。原代码假设 filename 中一定包含 /,如果 filename 只是单纯的文件名(如 "config.txt"),indexOf('/') 返回 -1,mid(0) 会保留原字符串,导致路径错误。新代码使用 QFileInfo::fileName() 能够安全准确地提取文件名部分,兼容相对路径、绝对路径和纯文件名,逻辑更严密。

2. 代码质量

改进意见:

  • 许可证头文件添加:

    • 修改: 添加了 SPDX-FileCopyrightText 和 SPDX-License-Identifier 注释。
    • 评价: 这是一个改进。符合开源规范,明确了版权和许可证信息,提高了项目的合规性和可维护性。
  • 错误处理增强 (handleDataConfiguration 函数):

    • 原代码: pdir.removeRecursively(); 删除整个目录。
    • 修改后: 仅删除 transfer.json 文件,并增加了删除失败的日志记录 WLOG
    • 评价: 这是一个改进。原代码删除整个目录可能过于激进,可能误删用户数据。修改后仅删除配置文件,更加安全。增加失败日志有助于问题排查。
  • 代码注释:

    • 修改: 添加了 // 只保留文件名部分,兼容相对路径和绝对路径格式 注释。
    • 评价: 这是一个改进。解释了修改意图,提高了代码可读性。

3. 代码性能

改进意见:

  • QFileInfo 的使用 (setFile 函数):
    • 修改: 引入了 QFileInfo(filename) 来获取文件名。
    • 评价: QFileInfo 的构造涉及一定的开销(如文件系统查询,尽管 fileName() 通常只处理字符串)。但在循环中(for (const auto &value : userFileArray)),如果 userFileArray 很大,频繁构造 QFileInfo 对象可能会带来微小的性能开销。不过,考虑到逻辑正确性和健壮性的提升,这种开销通常是可接受的。如果对性能极其敏感,可以考虑使用字符串操作(如 QString::split('/')QString::section('/'))提取最后一部分,但 QFileInfo 是最安全且跨平台的方式。

4. 代码安全

改进意见:

  • 路径遍历风险 (setFile 函数):

    • 现状: 代码中 QString targetFile = QDir::homePath() + "/" + filename;
    • 潜在风险: 如果 filename 来自外部不可信输入(例如 transfer.json 的内容被篡改),且包含 ../,可能会导致路径遍历攻击,写入到预期之外的目录。
    • 建议: 应对 filename 进行校验,确保它不包含路径遍历字符(如 ..)或绝对路径标记(如 / 开头)。可以使用 QFileInfo::fileName() 来清理输入,确保 targetFile 始终指向 Home 目录下的相对位置。
    • 修改建议:
      // 清理 filename,防止路径遍历
      QString cleanFilename = QFileInfo(filename).fileName();
      QString targetFile = QDir::homePath() + "/" + cleanFilename;
      注:当前 Diff 中 QString file = filepath + QFileInfo(filename).fileName(); 已经对源路径做了处理,但目标路径 targetFile 仍建议同样处理以增强安全性。
  • 资源清理 (handleDataConfiguration 函数):

    • 现状: 从删除整个目录改为仅删除文件。
    • 评价: 降低了数据丢失的风险,提高了安全性。

总结

总体来看,这次代码修改是正向的。主要改进了路径处理的健壮性和安全性,增加了必要的版权信息和错误日志,并优化了资源清理逻辑。

主要建议:

  1. setFile 函数中,对 targetFile 的构建也使用 QFileInfo(filename).fileName() 进行清理,以防止潜在的路径遍历安全风险。
  2. 保持当前的路径处理方式(使用 QDir::filePathQFileInfo),这比字符串拼接更安全可靠。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants