Skip to content

fix(ui): fix tab titles shifting left after window restore from maximize#260

Merged
deepin-bot[bot] merged 1 commit intolinuxdeepin:masterfrom
pengfeixx:master
Apr 20, 2026
Merged

fix(ui): fix tab titles shifting left after window restore from maximize#260
deepin-bot[bot] merged 1 commit intolinuxdeepin:masterfrom
pengfeixx:master

Conversation

@pengfeixx
Copy link
Copy Markdown
Contributor

Three changes to fix maximize→restore tab offset bug:

  1. Disable Qt internal expanding on inner QTabBar to prevent it from overriding manual tab sizes.
  2. Fix updateTabWidth formula to use (width-44)/tabCount matching DTK embedStyle layout, move setUsesScrollButtons outside loop.
  3. Force inner QTabBar re-layout in resizeEvent to reset stale scrollOffset that DTK's paintEvent never corrects.

修复窗口最大化还原后标签页偏左不显示的问题。DTK的paintEvent
覆盖了Qt原生实现导致scrollOffset无法自动修正。

Log: 修复最大化还原后标签页偏移问题
PMS: BUG-306499
Influence: 修复后窗口最大化还原时标签页标题正常显示,不再偏移。

Three changes to fix maximize→restore tab offset bug:
1. Disable Qt internal expanding on inner QTabBar to prevent it from
   overriding manual tab sizes.
2. Fix updateTabWidth formula to use (width-44)/tabCount matching DTK
   embedStyle layout, move setUsesScrollButtons outside loop.
3. Force inner QTabBar re-layout in resizeEvent to reset stale
   scrollOffset that DTK's paintEvent never corrects.

修复窗口最大化还原后标签页偏左不显示的问题。DTK的paintEvent
覆盖了Qt原生实现导致scrollOffset无法自动修正。

Log: 修复最大化还原后标签页偏移问题
PMS: BUG-306499
Influence: 修复后窗口最大化还原时标签页标题正常显示,不再偏移。
@deepin-ci-robot
Copy link
Copy Markdown

deepin pr auto review

这段代码主要对 DocTabBar 类进行了修改,涉及标签页宽度计算、布局调整以及版权更新。以下是对这段 diff 的详细审查意见:

1. 语法与逻辑审查

  • 版权年份更新

    • +// Copyright (C) 2019 ~ 2026 Uniontech Software Technology Co.,Ltd.
    • 意见:将版权年份更新到 2026 年是合理的,表示对代码维护周期的预期。但在逻辑上,通常建议只更新到当前年份或下一年(如 2024 或 2025),除非有特定的长期维护计划。
  • 构造函数中的 innerTabBar 设置

    • 代码在构造函数中查找子对象 QTabBar 并设置 setExpanding(false)
    • 意见findChild 在构造函数中执行通常是安全的,因为此时对象树可能尚未完全构建完毕(取决于 DTabBar 的实现),但在 Qt 中,子控件通常在构造函数中已创建。不过,DTabBar 内部可能使用了 QStackedWidget 或其他布局容器,直接查找 QTabBar 可能依赖于 DTabBar 的内部实现细节。如果 DTabBar 升级改变了内部结构,这段代码可能会失效。建议确认 DTabBar 的源码实现是否保证此时能找到 QTabBar
  • updateTabWidth 函数逻辑

    • 逻辑变更:原代码逻辑是"如果计算出的宽度小于等于 140,则固定为 140 并显示滚动条;否则平铺"。新代码逻辑是"计算宽度,取计算值和 140 中的较大值,且仅在计算值 <= 140 时显示滚动条"。
    • 潜在问题
      • 新逻辑中,setUsesScrollButtons(tabWidth <= 140) 意味着只要计算出的平均宽度小于 140,就显示滚动条。
      • 随后的循环中,int w = qMax(tabWidth, 140); 将实际宽度设置为不小于 140。
      • 冲突点:如果 tabWidth 是 100(显示滚动条),但实际设置的宽度 w 是 140。如果有 10 个标签页,总宽度需求为 1400。如果窗口宽度 innerWidth 只有 1000,这会导致标签页总宽度超出窗口,即使设置了 setUsesScrollButtons(true),也可能因为布局策略问题导致显示异常(虽然 QTabBar 通常能处理溢出,但强制设置大小可能会干扰其内部布局计算)。
      • 建议:通常标签页宽度策略是:空间足够时平铺(expanding),空间不足时固定最小宽度并滚动。原代码逻辑更符合常规 UI 交互。新代码试图强制最小宽度为 140,但在空间不足时(tabWidth < 140)强制设置 w=140 会导致总宽度溢出。需要确认 DTabBarQTabBar 在这种强制设置大于可用空间宽度时的行为是否是预期的。
  • resizeEvent 中的 Hack 代码

    • innerTabBar->resize(w + 1, h); innerTabBar->resize(w, h);
    • 意见:这是一种常见的 Qt Hack,用于强制触发布局重绘或解决某些特定样式下的渲染故障。
    • 风险:频繁的 resize 操作(在 resizeEvent 中)会触发额外的布局计算和重绘事件,可能影响性能。此外,findChild 每次调用都有一定的查找开销,虽然在 UI 事件中通常可以忽略,但不是最优解。
    • 建议:尝试在构造函数中缓存 innerTabBar 指针(作为类成员变量),避免在 resizeEvent 中重复查找。如果必须用这个 Hack,请添加详细注释说明原因(例如:"修复 DTK 某版本下标签栏宽度未及时更新的渲染 bug")。

2. 代码质量审查

  • 魔法数字

    • 代码中大量使用了硬编码的数字:44, 140, 37, 1
    • 建议:将这些数字定义为具有命名的静态常量或枚举,例如 const int kTabMinWidth = 140;,以提高代码可读性和可维护性。
  • 冗余计算

    • resizeEvent 中先调用 updateTabWidth(),再调用 DTabBar::resizeEvent(e)
    • updateTabWidth 内部使用了 this->width()。如果在 DTabBar::resizeEvent 之前调用,此时 this->width() 应该已经是新的宽度(因为 resizeEvent 参数 e 包含了新尺寸,且通常 QWidget::resizeEvent 更新内部状态前,width() 已返回新值)。顺序看起来没问题,但依赖隐式行为。
    • 建议:确认 DTabBar::resizeEvent 是否会重置 Tab 宽度。如果会,那么当前的调用顺序(先 updateTabWidthresizeEvent)会导致自定义宽度被覆盖,除非 updateTabWidth 触发了某种延迟更新或信号槽机制。如果 DTabBar 内部没有自动调整宽度的逻辑,这个顺序是可以接受的。

3. 代码性能审查

  • findChild 的调用频率

    • 在构造函数和 resizeEvent 中都调用了 findChild<QTabBar *>()
    • 建议:如前所述,findChild 遍历对象树。虽然对于简单的对象树开销不大,但在 resizeEvent 这种高频触发的事件中,建议缓存 innerTabBar 指针。
  • updateTabWidth 的循环

    • 循环中对每个 Tab 调用 setTabMinimumSizesetTabMaximumSize。这是必要的,但要注意 Tab 数量非常多时可能会有轻微性能开销。

4. 代码安全审查

  • 空指针检查

    • 代码中对 innerTabBar 进行了 if (innerTabBar) 检查,这是良好的编程习惯,防止空指针解引用。
  • 类型安全

    • findChild<QTabBar *>() 是类型安全的。
  • 潜在的布局崩溃

    • resizeEvent 中强制修改子控件大小(resize)可能会干扰父控件的布局管理器(如果 innerTabBar 处于某个 QLayout 中)。虽然 QTabBar 通常能处理,但这种强制修改属于破坏封装性的行为,未来库升级时容易导致崩溃或显示错乱。

综合改进建议

  1. 缓存指针:将 innerTabBar 的查找结果缓存为 DocTabBar 的成员变量 QTabBar *m_innerTabBar,在构造函数中初始化。
  2. 常量定义:定义 static const int kTabMinWidth = 140; 等常量替换魔法数字。
  3. 逻辑验证:仔细验证 updateTabWidth 中的逻辑。当窗口宽度不足以容纳所有最小宽度(140)的标签页时,强制设置宽度为 140 会导致总宽度 > 窗口宽度。
    • 如果目的是:空间够时平铺,空间不够时固定 140 并滚动。
    • 修正逻辑建议:
      int innerWidth = this->width() - 44;
      int tabWidth = innerWidth / tabCount;
      bool useScroll = tabWidth < 140;
      setUsesScrollButtons(useScroll);
      
      for (int i = 0; i < tabCount; i++) {
          // 如果需要滚动条,宽度固定为 140(或最小宽度);否则平铺
          int w = useScroll ? 140 : tabWidth; 
          // 或者使用 max,但要注意溢出问题
          // int w = qMax(tabWidth, 140); 
          
          setTabMinimumSize(i, QSize(w, 37));
          setTabMaximumSize(i, QSize(w, 37));
      }
  4. 添加注释:为 resizeEvent 中的 resize(w+1... hack 添加详细的注释,解释其必要性,否则会被视为错误的代码。

修改后的代码示例建议

// 在头文件中添加成员变量
// private:
//    QTabBar *m_innerTabBar = nullptr;
//    static const int kTabMargin = 44;
//    static const int kTabMinWidth = 140;
//    static const int kTabHeight = 37;

// 构造函数中
DocTabBar::DocTabBar(QWidget *parent)
    : DTabBar(parent)
{
    // ... 初始化代码 ...
    
    // 缓存内部 TabBar 指针
    m_innerTabBar = findChild<QTabBar *>();
    if (m_innerTabBar) {
        m_innerTabBar->setExpanding(false);
    }
    
    // ...
}

void DocTabBar::updateTabWidth()
{
    int tabCount = count();
    if (tabCount == 0) return;

    int innerWidth = this->width() - kTabMargin;
    int tabWidth = innerWidth / tabCount;
    
    // 如果计算宽度小于最小宽度,启用滚动条并固定宽度
    bool useScroll = tabWidth < kTabMinWidth;
    setUsesScrollButtons(useScroll);

    for (int i = 0; i < tabCount; i++) {
        // 只有在不滚动时才使用计算出的宽度,否则使用最小宽度
        // 这样可以避免强制设置大宽度导致总宽度超出窗口
        int w = useScroll ? kTabMinWidth : tabWidth;
        
        setTabMinimumSize(i, QSize(w, kTabHeight));
        setTabMaximumSize(i, QSize(w, kTabHeight));
    }
}

void DocTabBar::resizeEvent(QResizeEvent *e)
{
    // 先更新宽度逻辑
    updateTabWidth();
    // 调用父类处理
    DTabBar::resizeEvent(e);
    
    // Hack: 强制触发内部 TabBar 布局更新以解决渲染故障
    if (m_innerTabBar) {
        int w = m_innerTabBar->width();
        int h = m_innerTabBar->height();
        m_innerTabBar->resize(w + 1, h);
        m_innerTabBar->resize(w, h);
    }
}

@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

@pengfeixx
Copy link
Copy Markdown
Contributor Author

/merge

@deepin-bot deepin-bot Bot merged commit 94487cf into linuxdeepin:master Apr 20, 2026
10 checks passed
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