Skip to content

fix(editor): resolve tab bar jitter when adding new tabs#450

Merged
deepin-bot[bot] merged 1 commit intolinuxdeepin:masterfrom
pengfeixx:fix/tabbar-jitter
Apr 23, 2026
Merged

fix(editor): resolve tab bar jitter when adding new tabs#450
deepin-bot[bot] merged 1 commit intolinuxdeepin:masterfrom
pengfeixx:fix/tabbar-jitter

Conversation

@pengfeixx
Copy link
Copy Markdown
Contributor

@pengfeixx pengfeixx commented Apr 23, 2026

Batch layout operations with setUpdatesEnabled and double layout()->activate() to prevent scroll button cascade flicker.

修复新建标签页时标签栏抖动问题,通过批量布局操作和
双重layout激活消除滚动按钮级联闪烁。

Log: 修复新建标签页时标签栏抖动
PMS: BUG-353507
Influence: 新建标签页(2个及以上)时不再出现标签栏抖动现象,提升用户体验。

Summary by Sourcery

Prevent tab bar jitter and unnecessary tab text updates when creating or reloading editor tabs.

Bug Fixes:

  • Eliminate tab bar jitter and scroll button flicker when adding new tabs by batching layout and update operations.
  • Avoid redundant tab text updates when toggling read-only state if the tab caption is already correct.

Enhancements:

  • Adjust tab width calculation to use a fixed maximum width until tabs overflow, then distribute available space within min/max bounds for more stable layouts.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Apr 23, 2026

Reviewer's Guide

Batches tabbar layout and paint operations when adding a tab to eliminate jitter, refines tab width calculation logic, and avoids redundant tab text updates when toggling read-only state after reload checks.

Sequence diagram for batched tabbar layout when adding a new tab

sequenceDiagram
    actor User
    participant Window
    participant Tabbar
    participant DTabBar
    participant QTabBarLayout as QTabBar_internal_layout

    User->>Window: requestNewTab(filePath, tabName)
    Window->>Tabbar: addTabWithIndex(index, filePath, tabName, tipPath)
    Tabbar->>Tabbar: trimmedName = replaceMnemonic(tabName.simplified())

    Tabbar->>Tabbar: setUpdatesEnabled(false)
    Tabbar->>DTabBar: insertTab(index, trimmedName)
    Tabbar->>DTabBar: setCurrentIndex(index)

    Tabbar->>Tabbar: layout().activate()
    Tabbar->>Tabbar: setIconSize(iconSize())
    Tabbar->>Tabbar: layout().activate()

    Tabbar->>Tabbar: setUpdatesEnabled(true)
    Tabbar-->>Window: tabAddedWithStableLayout()
    Window-->>User: new tab visible without jitter
Loading

Sequence diagram for avoiding redundant tab text updates on reload check

sequenceDiagram
    participant Window
    participant Tabbar
    participant Wrapper
    participant TextEditor

    Window->>Window: checkTabbarForReload()
    Window->>Window: compute tabName with or without readOnlyStr
    Window->>Tabbar: currentName()?
    Tabbar-->>Window: currentTabName

    alt name changed
        Window->>Tabbar: setTabText(currentIndex, tabName)
    else name unchanged
        Window->>Tabbar: skip setTabText
    end

    Window->>Wrapper: textEditor()
    Wrapper-->>Window: TextEditor
    Window->>TextEditor: setReadOnlyPermission(isReadOnly)
    TextEditor-->>Window: done
Loading

Class diagram for updated Tabbar and Window behaviors

classDiagram
    class Tabbar {
        +void addTabWithIndex(int index, QString filePath, QString tabName, QString tipPath)
        +QSize tabSizeHint(int index) const
        +void setUpdatesEnabled(bool enabled)
        +QLayout* layout()
        +void setIconSize(QSize size)
        +int width() const
    }

    class DTabBar {
        +void insertTab(int index, QString text)
        +void setCurrentIndex(int index)
        +int count() const
    }

    class Window {
        +void checkTabbarForReload()
        -Tabbar* m_tabbar
    }

    class DGuiApplicationHelper {
        +static bool isCompactMode()
    }

    class QSize {
        +QSize(int width, int height)
    }

    Tabbar --|> DTabBar : inherits
    Window --> Tabbar : uses
    Tabbar ..> DGuiApplicationHelper : queries mode
    Tabbar ..> QSize : returns
Loading

File-Level Changes

Change Details Files
Batch layout and paint operations when inserting a new tab to remove tab bar jitter and scroll-button flicker.
  • Wrap tab insertion logic with setUpdatesEnabled(false/true) to defer repaints until the layout is settled.
  • After inserting and configuring the new tab, force the DTabBar layout with layout()->activate() twice to fully settle size and scroll-button visibility changes.
  • Call setIconSize(iconSize()) after layout activation to trigger QTabBar internal layout recalculation using the new width.
src/controls/tabbar.cpp
Adjust tab size hint calculation to use a fixed max width until overflow, then evenly distribute width within a min/max range.
  • Replace previous average-width clamping logic with explicit max/min tab width constants and a conditional overflow calculation.
  • Use qBound to constrain computed tab width between the defined minimum and maximum.
  • Return the new tabWidth value in both compact and non-compact branches.
src/controls/tabbar.cpp
Avoid redundant tab text updates when toggling read-only marker during reload checks.
  • Before calling setTabText for read-only and writable states, compare the target tabName with the current tab name via currentName().
  • Only invoke setTabText when the new name differs, preventing unnecessary updates and potential layout/paint churn.
src/widgets/window.cpp

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 left some high level feedback:

  • The new batching logic around setUpdatesEnabled(false)/true and double layout()->activate() is quite subtle; consider extracting this into a small helper (or RAII-style scope object) with a clear name so future changes don’t accidentally leave updates disabled or break the intended ordering.
  • When calling layout()->activate() in addTabWithIndex, it may be safer to guard against a null layout() pointer (or assert its presence) to avoid potential crashes if the widget’s layout is changed in the future.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new batching logic around `setUpdatesEnabled(false)`/`true` and double `layout()->activate()` is quite subtle; consider extracting this into a small helper (or RAII-style scope object) with a clear name so future changes don’t accidentally leave updates disabled or break the intended ordering.
- When calling `layout()->activate()` in `addTabWithIndex`, it may be safer to guard against a null `layout()` pointer (or assert its presence) to avoid potential crashes if the widget’s layout is changed in the future.

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.

@github-actions
Copy link
Copy Markdown

  • 敏感词检查失败, 检测到1个文件存在敏感词
详情
{
    "src/widgets/window.cpp": [
        {
            "line": "    QString key = \"base/enable\";",
            "line_number": 390,
            "rule": "S106",
            "reason": "Var naming | 64f28539d9"
        }
    ]
}

@deepin-ci-robot
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: lzwind, pengfeixx

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

Batch layout operations with setUpdatesEnabled and double
layout()->activate() to prevent scroll button cascade flicker.

修复新建标签页时标签栏抖动问题,通过批量布局操作和
双重layout激活消除滚动按钮级联闪烁。

Log: 修复新建标签页时标签栏抖动
PMS: BUG-353507
Influence: 新建标签页(2个及以上)时不再出现标签栏抖动现象,提升用户体验。
@github-actions
Copy link
Copy Markdown

  • 敏感词检查失败, 检测到1个文件存在敏感词
详情
{
    "src/widgets/window.cpp": [
        {
            "line": "    QString key = \"base/enable\";",
            "line_number": 389,
            "rule": "S106",
            "reason": "Var naming | 64f28539d9"
        }
    ]
}

@deepin-ci-robot
Copy link
Copy Markdown

deepin pr auto review

这段代码主要针对 Tabbar 类的标签页添加逻辑和尺寸计算进行了优化,并改进了 Window 类中设置标签文本的冗余调用。整体来看,修改是有价值的,旨在解决UI抖动和布局刷新问题。

以下是针对语法逻辑、代码质量、代码性能和代码安全的详细审查意见:

1. 语法逻辑

  • Tabbar::addTabWithIndex 中的布局逻辑

    • 问题:代码中使用了 layout()->activate() 两次,中间穿插了 setIconSize(iconSize())
    • 分析setIconSize 会触发 QTabBar 的内部几何变化请求,这可能会导致 DTabBar 的布局失效。因此,第二次 activate() 是为了确保在图标大小调整后,布局能够根据最新的尺寸进行最终计算。逻辑上是合理的,用于处理级联的布局失效。
    • 建议:虽然逻辑正确,但这种“暴力”强制刷新布局的方式比较脆弱。如果未来 DTabBarQt 内部实现改变,可能需要调整。建议添加更详细的注释说明为什么需要这两次 activate,特别是针对 setIconSize 的副作用。
  • Tabbar::tabSizeHint 中的宽度计算

    • 改进点:原代码逻辑 aveargeWidth = total / count() 后再进行边界判断,修改后的逻辑先判断 tabCount * maxTabWidth > total 再计算,逻辑更清晰。
    • 细节:使用了 qBound 来限制宽度范围,比原来的 if-else 链更简洁且不易出错。

2. 代码质量

  • 调试信息

    • 问题:代码中保留了大量的 qDebug() 输出(例如 qDebug() << "trimmedName:" << trimmedName;)。
    • 建议:在发布版本中,过多的 qDebug 会影响性能。建议使用 qCDebug 配合日志分类,或者使用条件编译(如 #ifdef QT_DEBUG)包裹调试代码,以便在 Release 构建中自动移除。
  • 命名规范

    • 改进点:将 aveargeWidth(原拼写错误)改为了 tabWidth,将变量定义提升为 const(如 maxTabWidth),提高了代码的可读性和可维护性。
  • 注释

    • 改进点addTabWithIndex 中添加了详细的步骤注释,解释了为什么需要禁用更新和强制布局,这对后续维护非常有帮助。

3. 代码性能

  • 批量更新优化

    • 优点:在 addTabWithIndex 中使用 setUpdatesEnabled(false)true 包裹了一系列操作。这是 Qt 中优化绘制性能的标准做法,有效避免了多次重绘导致的 UI 闪烁和抖动,是一个很好的性能优化点。
  • 减少冗余调用

    • 优点:在 Window::checkTabbarForReload 中,增加了 if (m_tabbar->currentName() != tabName) 判断。
    • 分析setTabText 通常会触发标签的重绘和布局更新。如果文本内容没有变化(例如已经是只读状态再次设置为只读),跳过这个调用可以节省不必要的 CPU 消耗和布局计算。

4. 代码安全

  • 空指针检查

    • 潜在风险:在 Tabbar::addTabWithIndex 中调用了 layout()->activate()。虽然 DTabBar 通常默认有布局,但如果在某些极端情况下或子类化时布局被删除,layout() 可能返回 nullptr,导致程序崩溃。
    • 建议:建议增加空指针检查:
      if (auto *lay = layout()) {
          lay->activate();
          // ...
          lay->activate();
      }
  • 边界检查

    • 现状:在 tabSizeHint 中,index >= 0 检查了索引的有效性。但在 addTabWithIndex 中,调用 DTabBar::insertTabsetCurrentIndex 之前,并没有显式检查 index 是否在有效范围内(虽然 insertTab 内部通常会处理,但显式检查更安全)。
    • 建议:在函数入口处添加 Q_ASSERT(index >= 0 && index <= count()); 以便在调试阶段尽早发现逻辑错误。

5. 其他改进建议

  • 关于 setIconSize(iconSize()) 的 Hack
    • addTabWithIndex 中调用 setIconSize(iconSize()) 仅仅是为了触发 QTabBar 的内部布局更新,这是一种“副作用”编程。
    • 建议:虽然这在当前 Qt 版本下有效,但不够直观。如果 DTabBarQTabBar 提供了更官方的 polishupdateGeometry 接口,优先使用官方接口。如果没有,建议将此行代码提取为一个独立的私有辅助函数,例如 triggerTabBarLayoutRecalculation(),并添加注释说明这是为了触发 Qt 内部的布局更新机制。

总结

这段 diff 的整体质量较高,主要解决了 UI 渲染时的抖动问题,并优化了标签宽度的计算逻辑。

  1. 核心改进:通过 setUpdatesEnabled 和强制布局激活解决了视觉抖动。
  2. 代码清理:修正了拼写错误,使用了更合理的变量名和 qBound
  3. 性能提升:避免了不必要的 setTabText 调用。

最终建议:在合并前,建议移除或条件编译掉 qDebug 语句,并对 layout() 指针增加防空保护,以增强代码的健壮性。

@pengfeixx
Copy link
Copy Markdown
Contributor Author

/merge

@deepin-bot deepin-bot Bot merged commit 3f5c949 into linuxdeepin:master Apr 23, 2026
22 checks passed
@pengfeixx pengfeixx deleted the fix/tabbar-jitter branch April 23, 2026 07:12
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