Skip to content

Conversation

@robertkill
Copy link
Contributor

@robertkill robertkill commented Oct 30, 2025

update changelog to 2.0.16

Summary by Sourcery

Enhance drag-and-drop UX by stabilizing the trash tooltip display with debounce and movement thresholds, improving cleanup on drag completion, adding debug logs, and optimizing positioner updates.

Enhancements:

  • Implement debounce and movement-threshold logic to stabilize showing and updating the drag-over tooltip for the trash icon
  • Ensure the trash tooltip is properly closed on drag end, exit, and drop events
  • Add console.warn debugging logs to PanelToolTip open/close and DockPositioner/DockPanelPositioner update routines
  • Optimize DockPositioner to skip triggering updates when the computed position remains unchanged

@github-actions
Copy link

TAG Bot

TAG: 2.0.16
EXISTED: no
DISTRIBUTION: unstable

@sourcery-ai
Copy link

sourcery-ai bot commented Oct 30, 2025

Reviewer's Guide

This PR bumps the version to 2.0.16 and updates the changelog, while adding debounced, threshold-based tooltip logic for dragging over the trash icon in QML, and optimizing dock position updates with early exits and logging in the C++ positioners.

Sequence diagram for drag-and-drop tooltip logic over trash icon

sequenceDiagram
    participant User as actor User
    participant AppItem
    participant DropArea
    participant dragToolTip
    participant tooltipDelayTimer

    User->>DropArea: Drag enters trash icon
    DropArea->>DropArea: Check debounce threshold
    alt Not too soon after exit
        DropArea->>DropArea: Set isDragOver, shouldShowTooltip
        DropArea->>tooltipDelayTimer: Start timer
        tooltipDelayTimer->>DropArea: Timer triggers
        DropArea->>dragToolTip: Open tooltip
        DropArea->>DropArea: Set tooltipVisible
    else Too soon after exit
        DropArea->>DropArea: Ignore event
    end

    User->>DropArea: Drag moves
    DropArea->>DropArea: Check movement threshold
    alt Movement > threshold
        DropArea->>dragToolTip: Update tooltip position
    else Movement <= threshold
        DropArea->>DropArea: Ignore position update
    end

    User->>DropArea: Drag exits trash icon
    DropArea->>DropArea: Check debounce threshold
    alt Not too soon after enter
        DropArea->>dragToolTip: Close tooltip
        DropArea->>DropArea: Reset tooltip state
    else Too soon after enter
        DropArea->>DropArea: Ignore event
    end

    User->>DropArea: Drop files on trash icon
    DropArea->>dragToolTip: Close tooltip
    DropArea->>DropArea: Reset tooltip state
    DropArea->>AppItem: dropFilesOnItem
Loading

Class diagram for updated DropArea tooltip logic in AppItem.qml

classDiagram
    class DropArea {
        +bool isDragOver
        +var lastDragPosition
        +int dragStableThreshold
        +bool shouldShowTooltip
        +int lastEnterTime
        +int lastExitTime
        +int debounceThreshold
        +bool tooltipVisible
        +onEntered(drag)
        +onExited(drag)
        +onPositionChanged(drag)
        +onDropped(drop)
    }
    class Timer {
        +interval
        +onTriggered()
    }
    DropArea "1" -- "1" Timer : tooltipDelayTimer
    DropArea "1" -- "1" dragToolTip
    DropArea "1" -- "1" AppItem : root
Loading

Class diagram for DockPositioner and DockPanelPositioner update logic

classDiagram
    class DockPositioner {
        +int m_x
        +int m_y
        +QRect m_bounding
        +QTimer m_positionTimer
        +void update()
        +void setY(int y)
    }
    class DockPanelPositioner {
        +QRect m_bounding
        +int m_horizontalOffset
        +int m_vertialOffset
        +void updatePosition()
    }
    DockPanelPositioner --|> DockPositioner
Loading

Class diagram for PanelToolTip open/close logging

classDiagram
    class PanelToolTip {
        +string text
        +function open()
        +function close()
    }
Loading

File-Level Changes

Change Details Files
Enhanced drag-and-drop tooltip behavior for the trash icon
  • Added drag state properties (isDragOver, lastDragPosition, thresholds)
  • Debounced onEntered/onExited handlers with timestamp checks
  • Introduced a Timer to delay and throttle tooltip display
  • Implemented onPositionChanged logic to update tooltip only when movement exceeds a threshold
  • Ensured tooltip closes on drag finish and onDropped events
panels/dock/taskmanager/package/AppItem.qml
Optimized dock position updates in C++ positioners
  • Early-exit update() when computed x/y match current values
  • Added qWarning logs when skipping or performing an update
  • Inserted debug logs before and after layout calculations in updatePosition()
panels/dock/dockpositioner.cpp
Added logging to PanelToolTip open/close
  • Emit console.warn in open()
  • Emit console.warn in close()
frame/qml/PanelToolTip.qml
Bumped version to 2.0.16 and updated changelog
  • Updated debian/changelog entry to v2.0.16
debian/changelog

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

@deepin-ci-robot
Copy link

deepin pr auto review

我来对这段代码进行审查,从语法逻辑、代码质量、性能和安全几个方面提出改进建议:

  1. 语法逻辑方面:
  • 代码整体语法正确,但存在一些可以优化的地方
  • dockpositioner.cpp中的update()函数有重复的位置计算逻辑,建议将其提取为独立函数
  1. 代码质量方面:
  • 存在大量的console.warn调试输出,这些应该在发布版本中移除或使用条件编译
  • AppItem.qml中的拖拽处理逻辑过于复杂,建议拆分为更小的函数
  • 一些变量命名不够规范,如"vertialOffset"拼写错误,应为"verticalOffset"
  1. 性能优化建议:
  • DockPositioner::update()中添加的位置缓存检查很好,但可以进一步优化
  • AppItem.qml中的防抖机制可以进一步优化,建议使用更智能的防抖算法
  • tooltipDelayTimer的50ms延迟可能太短,建议根据实际使用情况调整
  1. 安全性考虑:
  • 拖拽操作没有进行适当的文件权限检查
  • 没有对拖拽的URL进行验证,可能存在安全风险

具体改进建议:

  1. 对于dockpositioner.cpp:
void DockPositioner::calculatePosition(int& x, int& y) const {
    switch (dockPosition()) {
        case dock::Top:
            x = m_bounding.x();
            y = m_bounding.y();
            break;
        case dock::Right:
            x = m_bounding.x() - m_bounding.width();
            y = m_bounding.y();
            break;
        case dock::Bottom:
            x = m_bounding.x();
            y = m_bounding.y() - m_bounding.height();
            break;
        case dock::Left:
            x = m_bounding.x();
            y = m_bounding.y();
            break;
    }
}

void DockPositioner::update() {
    int newX, newY;
    calculatePosition(newX, newY);
    
    if (m_x == newX && m_y == newY) {
        return;
    }
    
    m_positionTimer->start();
}
  1. 对于AppItem.qml:
DropArea {
    // ... 其他属性保持不变 ...
    
    onEntered: function (drag) {
        handleDragEnter(drag);
    }
    
    onExited: function (drag) {
        handleDragExit(drag);
    }
    
    onPositionChanged: function (drag) {
        handleDragPosition(drag);
    }
    
    onDropped: function (drop) {
        handleDragDrop(drop);
    }
    
    function handleDragEnter(drag) {
        if (!shouldProcessDragEvent()) return;
        if (root.itemId !== "dde-trash") return;
        
        isDragOver = true;
        lastDragPosition = Qt.point(drag.x, drag.y);
        lastEnterTime = Date.now();
        shouldShowTooltip = true;
        
        if (!tooltipVisible) {
            tooltipDelayTimer.start();
        }
    }
    
    function handleDragExit(drag) {
        if (!shouldProcessDragEvent()) return;
        if (root.itemId !== "dde-trash") return;
        
        isDragOver = false;
        lastExitTime = Date.now();
        shouldShowTooltip = false;
        tooltipVisible = false;
        tooltipDelayTimer.stop();
        dragToolTip.close();
    }
    
    function handleDragPosition(drag) {
        if (!isDragOver || !drag || root.itemId !== "dde-trash") return;
        
        const currentPos = Qt.point(drag.x, drag.y);
        const distance = calculateDistance(currentPos, lastDragPosition);
        
        if (distance > dragStableThreshold) {
            updateTooltipPosition();
        }
    }
    
    function handleDragDrop(drop) {
        if (root.itemId === "dde-trash") {
            cleanupTooltip();
        }
        root.dropFilesOnItem(root.itemId, drop.urls);
    }
    
    function shouldProcessDragEvent() {
        const currentTime = Date.now();
        return currentTime - lastExitTime >= debounceThreshold;
    }
    
    function calculateDistance(pos1, pos2) {
        return Math.sqrt(Math.pow(pos1.x - pos2.x, 2) + Math.pow(pos1.y - pos2.y, 2));
    }
    
    function updateTooltipPosition() {
        const point = root.mapToItem(null, root.width / 2, root.height / 2);
        dragToolTip.DockPanelPositioner.bounding = Qt.rect(point.x, point.y, dragToolTip.width, dragToolTip.height);
    }
    
    function cleanupTooltip() {
        isDragOver = false;
        shouldShowTooltip = false;
        tooltipVisible = false;
        tooltipDelayTimer.stop();
        dragToolTip.close();
    }
}
  1. 安全性改进建议:
onDropped: function (drop) {
    if (root.itemId === "dde-trash") {
        // 验证URL安全性
        const validUrls = drop.urls.filter(url => {
            try {
                const path = decodeURIComponent(url.toString());
                return !path.includes("..") && !path.includes("~");
            } catch (e) {
                console.warn("Invalid URL:", url);
                return false;
            }
        });
        
        if (validUrls.length > 0) {
            root.dropFilesOnItem(root.itemId, validUrls);
        }
    }
}

这些改进建议主要关注:

  1. 代码结构优化和可维护性
  2. 性能优化
  3. 安全性增强
  4. 错误处理
  5. 代码复用

建议在实施这些改进时进行充分的测试,特别是拖拽功能和通知显示功能。

Copy link

@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 there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `panels/dock/dockpositioner.cpp:267-270` </location>
<code_context>
     int horizontalOffset = m_horizontalOffset == -1 ? m_bounding.width() / 2 : m_horizontalOffset;
     int vertialOffset = m_vertialOffset == -1 ? m_bounding.height() / 2 : m_vertialOffset;
+    
+    qWarning() << "DockPanelPositioner::updatePosition called for" << parent()->objectName()
+               << "bounding:" << m_bounding << "dockPosition:" << dockPosition();
+    
</code_context>

<issue_to_address>
**suggestion (performance):** Excessive debug logging may impact performance in production.

Consider controlling these qWarning logs with a debug flag or removing them before production to avoid unnecessary log output and performance impact.

Suggested implementation:

```cpp
    int horizontalOffset = m_horizontalOffset == -1 ? m_bounding.width() / 2 : m_horizontalOffset;
    int vertialOffset = m_vertialOffset == -1 ? m_bounding.height() / 2 : m_vertialOffset;

    if (m_debugLoggingEnabled) {
        qWarning() << "DockPanelPositioner::updatePosition called for" << parent()->objectName()
                   << "bounding:" << m_bounding << "dockPosition:" << dockPosition();
    }

```

You will need to:
1. Add a `bool m_debugLoggingEnabled` member to the `DockPanelPositioner` class (default to `false`).
2. Optionally, add a setter method like `void setDebugLoggingEnabled(bool enabled);` to control this flag.
3. Initialize the flag in the constructor if needed.
</issue_to_address>

### Comment 2
<location> `panels/dock/dockpositioner.cpp:112` </location>
<code_context>
+    
+    int xPosition = 0;
+    int yPosition = 0;
+    switch (dockPosition()) {
+    case dock::Top: {
+        xPosition = newBounding.x();
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring the position calculation logic to eliminate duplicate branches and use defaults for Top/Left, adjusting only for Right/Bottom.

Here’s one way to collapse all the duplicate logic while keeping the exact same behavior and your logging:

1.  Default `x`/`y` from `m_bounding`.
2.  Only adjust for `Right` or `Bottom` (Top/Left just keep the defaults).
3.  Move your “position unchanged” check & timer/start exactly as before.
4.  If you really need that warning in `updatePosition()`, emit it once after the switch.

```cpp
void DockPositioner::update() {
    const auto& b = m_bounding;
    int x = b.x(), y = b.y();

    switch (dockPosition()) {
      case dock::Right:
        x -= b.width();
        break;
      case dock::Bottom:
        y -= b.height();
        break;
      default:
        break;  // Top & Left use the default x/y
    }

    if (m_x == x && m_y == y) {
        qWarning() << "DockPositioner::update: position unchanged, skipping update";
        return;
    }

    m_positionTimer->start();
}
```

And for the panel positioner, collapse your two warnings into one and only adjust what changes per side:

```cpp
void DockPanelPositioner::updatePosition() {
    const auto rect = dockGeometry();
    int xo = (m_horizontalOffset == -1 ? m_bounding.width()/2  : m_horizontalOffset);
    int yo = (m_vertialOffset   == -1 ? m_bounding.height()/2 : m_vertialOffset);

    // default = Top
    int x = m_bounding.x() - xo;
    int y =  rect.height() + 10;

    switch (dockPosition()) {
      case dock::Right:
        x = -m_bounding.width() - 10;
        y =  m_bounding.y()       - yo;
        break;
      case dock::Bottom:
        y = -m_bounding.height() - 10;
        // x stays as default (centered)
        break;
      case dock::Left:
        x = rect.width() + 10;
        y = m_bounding.y() - yo;
        break;
      default:
        break;  // Top case already covered
    }

    qWarning() << "DockPanelPositioner::updatePosition"
               << parent()->objectName()
               << dockPosition()
               << "-> (" << x << "," << y << ")";

    setX(x);
    setY(y);
}
```

This keeps all of your new logging and guard-clauses but removes the four nearly-identical branches, collapses Top/Left into the default, and only adjusts what actually moves.
</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 on lines 226 to 270
break;
}

qWarning() << "DockPanelPositioner::updatePosition result - x:" << xPosition << "y:" << yPosition;
Copy link

Choose a reason for hiding this comment

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

suggestion (performance): Excessive debug logging may impact performance in production.

Consider controlling these qWarning logs with a debug flag or removing them before production to avoid unnecessary log output and performance impact.

Suggested implementation:

    int horizontalOffset = m_horizontalOffset == -1 ? m_bounding.width() / 2 : m_horizontalOffset;
    int vertialOffset = m_vertialOffset == -1 ? m_bounding.height() / 2 : m_vertialOffset;

    if (m_debugLoggingEnabled) {
        qWarning() << "DockPanelPositioner::updatePosition called for" << parent()->objectName()
                   << "bounding:" << m_bounding << "dockPosition:" << dockPosition();
    }

You will need to:

  1. Add a bool m_debugLoggingEnabled member to the DockPanelPositioner class (default to false).
  2. Optionally, add a setter method like void setDebugLoggingEnabled(bool enabled); to control this flag.
  3. Initialize the flag in the constructor if needed.


int xPosition = 0;
int yPosition = 0;
switch (dockPosition()) {
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 position calculation logic to eliminate duplicate branches and use defaults for Top/Left, adjusting only for Right/Bottom.

Here’s one way to collapse all the duplicate logic while keeping the exact same behavior and your logging:

  1. Default x/y from m_bounding.
  2. Only adjust for Right or Bottom (Top/Left just keep the defaults).
  3. Move your “position unchanged” check & timer/start exactly as before.
  4. If you really need that warning in updatePosition(), emit it once after the switch.
void DockPositioner::update() {
    const auto& b = m_bounding;
    int x = b.x(), y = b.y();

    switch (dockPosition()) {
      case dock::Right:
        x -= b.width();
        break;
      case dock::Bottom:
        y -= b.height();
        break;
      default:
        break;  // Top & Left use the default x/y
    }

    if (m_x == x && m_y == y) {
        qWarning() << "DockPositioner::update: position unchanged, skipping update";
        return;
    }

    m_positionTimer->start();
}

And for the panel positioner, collapse your two warnings into one and only adjust what changes per side:

void DockPanelPositioner::updatePosition() {
    const auto rect = dockGeometry();
    int xo = (m_horizontalOffset == -1 ? m_bounding.width()/2  : m_horizontalOffset);
    int yo = (m_vertialOffset   == -1 ? m_bounding.height()/2 : m_vertialOffset);

    // default = Top
    int x = m_bounding.x() - xo;
    int y =  rect.height() + 10;

    switch (dockPosition()) {
      case dock::Right:
        x = -m_bounding.width() - 10;
        y =  m_bounding.y()       - yo;
        break;
      case dock::Bottom:
        y = -m_bounding.height() - 10;
        // x stays as default (centered)
        break;
      case dock::Left:
        x = rect.width() + 10;
        y = m_bounding.y() - yo;
        break;
      default:
        break;  // Top case already covered
    }

    qWarning() << "DockPanelPositioner::updatePosition"
               << parent()->objectName()
               << dockPosition()
               << "-> (" << x << "," << y << ")";

    setX(x);
    setY(y);
}

This keeps all of your new logging and guard-clauses but removes the four nearly-identical branches, collapses Top/Left into the default, and only adjusts what actually moves.

int horizontalOffset = m_horizontalOffset == -1 ? m_bounding.width() / 2 : m_horizontalOffset;
int vertialOffset = m_vertialOffset == -1 ? m_bounding.height() / 2 : m_vertialOffset;

qWarning() << "DockPanelPositioner::updatePosition called for" << parent()->objectName()
Copy link
Contributor

Choose a reason for hiding this comment

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

这种日志比较多,加上category,另外使用debug,

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: robertkill

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

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.

3 participants