Skip to content

Feat: add HeaderInfoTableWidget for displaying device information#632

Merged
deepin-bot[bot] merged 1 commit intolinuxdeepin:develop/eagle-20260325from
GongHeng2017:202604010916-tmp-dev-feat
Apr 1, 2026
Merged

Feat: add HeaderInfoTableWidget for displaying device information#632
deepin-bot[bot] merged 1 commit intolinuxdeepin:develop/eagle-20260325from
GongHeng2017:202604010916-tmp-dev-feat

Conversation

@GongHeng2017
Copy link
Copy Markdown
Contributor

@GongHeng2017 GongHeng2017 commented Apr 1, 2026

Add a custom table widget with rounded border and alternating row colors for displaying device information as key-value pairs.

  • Add HeaderInfoDelegate for custom text color handling in selection
  • Add HeaderInfoTableWidget with rounded border painting and vertical divider
  • Support dynamic height adjustment based on content
  • Use DPalette for consistent theming with active/inactive states

Log: add feature for cpu info show.
Task: https://pms.uniontech.com/task-view-387697.html

Summary by Sourcery

Introduce a themed header information table widget for displaying device key–value data with custom rendering and layout.

New Features:

  • Add HeaderInfoTableWidget for displaying device information as a two-column key–value table with alternating row backgrounds and no scrollbars.
  • Implement a custom HeaderInfoDelegate to control text colors for normal and selected states using the application palette.
  • Support dynamic height adjustment of the header info table based on the number of rows to avoid vertical scrollbars.

Enhancements:

  • Apply rounded border rendering with a frame-colored outline and internal vertical divider consistent with existing detail views.
  • Integrate DPalette-based theming to respect active and inactive window states for background, border, and text colors.

Add a custom table widget with rounded border and alternating row colors
for displaying device information as key-value pairs.

- Add HeaderInfoDelegate for custom text color handling in selection
- Add HeaderInfoTableWidget with rounded border painting and vertical divider
- Support dynamic height adjustment based on content
- Use DPalette for consistent theming with active/inactive states

Log: add feature for cpu info show.
Task: https://pms.uniontech.com/task-view-387697.html
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Apr 1, 2026

Reviewer's Guide

Implements a new HeaderInfoTableWidget and HeaderInfoDelegate to display key-value device information in a non-editable, styled, two-column table with rounded borders, alternating row colors, theming via DPalette, and dynamic height sizing based on content.

Sequence diagram for HeaderInfoDelegate paint with theming

sequenceDiagram
    participant View
    participant Delegate
    participant Application
    participant PaletteHelper

    View->>Delegate: paint(painter, option, index)
    Delegate->>Application: activeWindow()
    Application-->>Delegate: activeWindow_or_null
    Delegate->>PaletteHelper: palette(option.widget)
    PaletteHelper-->>Delegate: palette
    alt item_selected
        Delegate->>Delegate: set Text and WindowText to HighlightedText
    else item_not_selected
        Delegate->>Delegate: set Text and WindowText to Text
    end
    Delegate->>View: QStyledItemDelegate::paint(painter, option, index)
Loading

Class diagram for HeaderInfoTableWidget and HeaderInfoDelegate

classDiagram
    class DTableWidget
    class DWidget
    class QStyledItemDelegate
    class QObject

    class HeaderInfoTableWidget {
        +HeaderInfoTableWidget(DWidget* parent)
        +void updateData(QList_QPair data)
        #void paintEvent(QPaintEvent* event)
        -void initUI()
        -void clear()
        -HeaderInfoDelegate* m_delegate
    }

    class HeaderInfoDelegate {
        +HeaderInfoDelegate(QObject* parent)
        +void paint(QPainter* painter, QStyleOptionViewItem option, QModelIndex index)
    }

    HeaderInfoTableWidget --|> DTableWidget
    HeaderInfoDelegate --|> QStyledItemDelegate
    HeaderInfoTableWidget o--> HeaderInfoDelegate
Loading

File-Level Changes

Change Details Files
Introduce HeaderInfoTableWidget as a styled two-column DTableWidget for key-value device info with dynamic height and custom painting.
  • Subclass DTableWidget to create HeaderInfoTableWidget with a fixed two-column layout for key/value pairs.
  • Implement updateData to clear existing rows, repopulate items from a QList<QPair<QString, QString>>, disable selection on cells, and apply alternating row background colors using DPalette active/inactive color groups.
  • Adjust the widget fixed height based on the number of rows and a ROW_HEIGHT constant to avoid vertical scrollbars while preserving rounded corners.
  • Configure table properties in initUI: disable editing and selection highlight, hide headers and grid, set default section sizes, enable alternating row colors, and remove the default frame in preparation for custom border painting.
  • Override paintEvent to draw a rounded-rectangle border and frame using DPalette frame border color and render a vertical divider line between the two columns aligned with DetailTreeView.
deepin-devicemanager/src/Widget/headerinfotablewidget.cpp
deepin-devicemanager/src/Widget/headerinfotablewidget.h
Add HeaderInfoDelegate to control text colors in selected and normal states using the application palette.
  • Subclass QStyledItemDelegate as HeaderInfoDelegate and override paint to customize text colors based on selection state.
  • Use DPaletteHelper and the active/inactive color group (based on DApplication::activeWindow) to choose HighlightedText for selected items and Text for normal items, updating both QPalette::Text and QPalette::WindowText.
  • Defer the actual painting to QStyledItemDelegate::paint after modifying the style option palette.
deepin-devicemanager/src/Widget/headerinfotableDelegate.cpp
deepin-devicemanager/src/Widget/headerinfotableDelegate.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 2 issues, and left some high level feedback:

  • Redefining clear() as a private helper in HeaderInfoTableWidget hides the public QTableWidget::clear() API; consider renaming this helper (e.g. resetTable()) to avoid surprising callers who may expect to access clear().
  • The vertical divider in paintEvent uses a hard-coded x-coordinate (179) while the first column width is configured separately; it would be more robust to compute the divider position from the header (e.g. horizontalHeader()->sectionPosition(0) + horizontalHeader()->sectionSize(0)) to keep them in sync if the column width changes.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Redefining `clear()` as a private helper in `HeaderInfoTableWidget` hides the public `QTableWidget::clear()` API; consider renaming this helper (e.g. `resetTable()`) to avoid surprising callers who may expect to access `clear()`.
- The vertical divider in `paintEvent` uses a hard-coded x-coordinate (`179`) while the first column width is configured separately; it would be more robust to compute the divider position from the header (e.g. `horizontalHeader()->sectionPosition(0) + horizontalHeader()->sectionSize(0)`) to keep them in sync if the column width changes.

## Individual Comments

### Comment 1
<location path="deepin-devicemanager/src/Widget/headerinfotablewidget.cpp" line_range="117-118" />
<code_context>
+    painter.drawRoundedRect(rectIn, radius, radius);
+
+    // Draw vertical divider between first and second columns (same as DetailTreeView)
+    QLine vline(rect.topLeft().x() + 179, rect.topLeft().y(), rect.bottomLeft().x() + 179, rect.bottomLeft().y());
+    painter.drawLine(vline);
+}
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Hardcoded divider position will desync if the column width changes (resize, DPI, style).

The X-position is hardcoded to 179 while the first column width is 180 and can change (resize, DPI, layout). When that happens, the divider won’t align with the column border. Please compute the position dynamically (e.g., `columnViewportPosition(0) + columnWidth(0)`) so it always matches the actual column boundary.
</issue_to_address>

### Comment 2
<location path="deepin-devicemanager/src/Widget/headerinfotablewidget.cpp" line_range="26" />
<code_context>
+    initUI();
+}
+
+void HeaderInfoTableWidget::updateData(const QList<QPair<QString, QString>> &data)
+{
+    clear();
</code_context>
<issue_to_address>
**issue (complexity):** Consider refactoring palette handling, geometry calculations, and the `clear()` helper to remove duplication, magic numbers, and surprising overrides while keeping the widget’s behavior unchanged.

You can reduce complexity and keep behavior by extracting the repeated palette logic, deriving geometry from the table state, and clarifying the `clear()` override.

### 1. Centralize palette/color-group logic

`updateData()` and `paintEvent()` both compute the active window, color group, and palette. Extract this into a small helper:

```cpp
// in headerinfotablewidget.h (private):
DPalette paletteForCurrentState() const;
DPalette::ColorGroup colorGroupForCurrentState() const;
```

```cpp
// in headerinfotablewidget.cpp
DPalette::ColorGroup HeaderInfoTableWidget::colorGroupForCurrentState() const
{
    QWidget *wnd = DApplication::activeWindow();
    return wnd ? DPalette::Active : DPalette::Inactive;
}

DPalette HeaderInfoTableWidget::paletteForCurrentState() const
{
    return DPaletteHelper::instance()->palette(this);
}
```

Then use it in both places:

```cpp
void HeaderInfoTableWidget::updateData(const QList<QPair<QString, QString>> &data)
{
    resetTableContents(); // see section 3

    const int nRow = data.size();
    setRowCount(nRow);

    const auto cg      = colorGroupForCurrentState();
    const auto palette = paletteForCurrentState();
    const QColor colorEven = palette.color(cg, DPalette::Base);
    const QColor colorOdd  = palette.color(cg, DPalette::ItemBackground);

    // ...
}
```

```cpp
void HeaderInfoTableWidget::paintEvent(QPaintEvent *event)
{
    DTableWidget::paintEvent(event);

    const auto cg      = colorGroupForCurrentState();
    const auto palette = paletteForCurrentState();

    // ...
    QBrush bgBrush(palette.color(cg, DPalette::FrameBorder));
    // ...
}
```

This keeps the color behavior identical but removes duplication and makes future palette changes localized.

### 2. Derive divider position from header instead of magic constant

The `179` constant is implicitly tied to the header’s default section size of `180`. Use the header API to compute the divider, so column width changes stay in sync:

```cpp
void HeaderInfoTableWidget::paintEvent(QPaintEvent *event)
{
    DTableWidget::paintEvent(event);

    const auto cg      = colorGroupForCurrentState();
    const auto palette = paletteForCurrentState();

    QRect rect = viewport()->rect().adjusted(0, 0, -1, -1);
    const int width  = 1;
    const int radius = 8;

    QPainter painter(viewport());
    painter.setRenderHint(QPainter::Antialiasing, true);

    // ... border drawing as before ...

    // Derive divider X from first column width
    int firstColumnWidth = horizontalHeader()->sectionSize(0);
    int dividerX = rect.left() + firstColumnWidth - 1;  // keep visual alignment similar

    QLine vline(dividerX, rect.top(), dividerX, rect.bottom());
    painter.drawLine(vline);
}
```

This preserves the look while removing the hard-coded coupling between `179` and `180`.

### 3. Clarify and avoid shadowing `clear()`

Your private `clear()` shadows `QTableWidget::clear()` and adds `setRowCount(0)`. To make intent clearer and reduce surprise, rename and use a more descriptive helper:

```cpp
// headerinfotablewidget.h (private)
void resetTableContents();
```

```cpp
// headerinfotablewidget.cpp
void HeaderInfoTableWidget::resetTableContents()
{
    DTableWidget::clear();
    setRowCount(0);
}
```

Then in `updateData()`:

```cpp
void HeaderInfoTableWidget::updateData(const QList<QPair<QString, QString>> &data)
{
    resetTableContents();
    // ...
}
```

This keeps the exact behavior but avoids shadowing the base `clear()` and makes the reset semantics explicit.

### 4. Optionally extract border drawing into a helper

If you want to further simplify `paintEvent()`, move the border logic into a separate private method, keeping `paintEvent()` focused:

```cpp
// headerinfotablewidget.h (private):
void drawRoundedBorderAndDivider(QPainter &painter, const QRect &rect,
                                 const DPalette &palette, DPalette::ColorGroup cg);
```

```cpp
void HeaderInfoTableWidget::paintEvent(QPaintEvent *event)
{
    DTableWidget::paintEvent(event);

    const auto cg      = colorGroupForCurrentState();
    const auto palette = paletteForCurrentState();

    QRect rect = viewport()->rect().adjusted(0, 0, -1, -1);
    QPainter painter(viewport());
    painter.setRenderHint(QPainter::Antialiasing, true);

    drawRoundedBorderAndDivider(painter, rect, palette, cg);
}

void HeaderInfoTableWidget::drawRoundedBorderAndDivider(
        QPainter &painter, const QRect &rect,
        const DPalette &palette, DPalette::ColorGroup cg)
{
    const int width  = 1;
    const int radius = 8;

    QPainterPath outer;
    outer.addRoundedRect(rect, radius, radius);

    QRect inner(rect.adjusted(width, width, -width, -width));
    QPainterPath innerPath;
    innerPath.addRoundedRect(inner, radius, radius);

    QPainterPath borderPath = outer.subtracted(innerPath);
    painter.fillPath(borderPath, palette.color(cg, DPalette::FrameBorder));

    QPen pen = painter.pen();
    pen.setWidth(width);
    pen.setColor(palette.color(cg, DPalette::FrameBorder));
    painter.setPen(pen);
    painter.drawRoundedRect(inner, radius, radius);

    int firstColumnWidth = horizontalHeader()->sectionSize(0);
    int dividerX = rect.left() + firstColumnWidth - 1;
    painter.drawLine(dividerX, rect.top(), dividerX, rect.bottom());
}
```

This keeps all visuals intact while making `paintEvent()` much easier to read and maintain.
</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 +117 to +118
QLine vline(rect.topLeft().x() + 179, rect.topLeft().y(), rect.bottomLeft().x() + 179, rect.bottomLeft().y());
painter.drawLine(vline);
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): Hardcoded divider position will desync if the column width changes (resize, DPI, style).

The X-position is hardcoded to 179 while the first column width is 180 and can change (resize, DPI, layout). When that happens, the divider won’t align with the column border. Please compute the position dynamically (e.g., columnViewportPosition(0) + columnWidth(0)) so it always matches the actual column boundary.

initUI();
}

void HeaderInfoTableWidget::updateData(const QList<QPair<QString, QString>> &data)
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 refactoring palette handling, geometry calculations, and the clear() helper to remove duplication, magic numbers, and surprising overrides while keeping the widget’s behavior unchanged.

You can reduce complexity and keep behavior by extracting the repeated palette logic, deriving geometry from the table state, and clarifying the clear() override.

1. Centralize palette/color-group logic

updateData() and paintEvent() both compute the active window, color group, and palette. Extract this into a small helper:

// in headerinfotablewidget.h (private):
DPalette paletteForCurrentState() const;
DPalette::ColorGroup colorGroupForCurrentState() const;
// in headerinfotablewidget.cpp
DPalette::ColorGroup HeaderInfoTableWidget::colorGroupForCurrentState() const
{
    QWidget *wnd = DApplication::activeWindow();
    return wnd ? DPalette::Active : DPalette::Inactive;
}

DPalette HeaderInfoTableWidget::paletteForCurrentState() const
{
    return DPaletteHelper::instance()->palette(this);
}

Then use it in both places:

void HeaderInfoTableWidget::updateData(const QList<QPair<QString, QString>> &data)
{
    resetTableContents(); // see section 3

    const int nRow = data.size();
    setRowCount(nRow);

    const auto cg      = colorGroupForCurrentState();
    const auto palette = paletteForCurrentState();
    const QColor colorEven = palette.color(cg, DPalette::Base);
    const QColor colorOdd  = palette.color(cg, DPalette::ItemBackground);

    // ...
}
void HeaderInfoTableWidget::paintEvent(QPaintEvent *event)
{
    DTableWidget::paintEvent(event);

    const auto cg      = colorGroupForCurrentState();
    const auto palette = paletteForCurrentState();

    // ...
    QBrush bgBrush(palette.color(cg, DPalette::FrameBorder));
    // ...
}

This keeps the color behavior identical but removes duplication and makes future palette changes localized.

2. Derive divider position from header instead of magic constant

The 179 constant is implicitly tied to the header’s default section size of 180. Use the header API to compute the divider, so column width changes stay in sync:

void HeaderInfoTableWidget::paintEvent(QPaintEvent *event)
{
    DTableWidget::paintEvent(event);

    const auto cg      = colorGroupForCurrentState();
    const auto palette = paletteForCurrentState();

    QRect rect = viewport()->rect().adjusted(0, 0, -1, -1);
    const int width  = 1;
    const int radius = 8;

    QPainter painter(viewport());
    painter.setRenderHint(QPainter::Antialiasing, true);

    // ... border drawing as before ...

    // Derive divider X from first column width
    int firstColumnWidth = horizontalHeader()->sectionSize(0);
    int dividerX = rect.left() + firstColumnWidth - 1;  // keep visual alignment similar

    QLine vline(dividerX, rect.top(), dividerX, rect.bottom());
    painter.drawLine(vline);
}

This preserves the look while removing the hard-coded coupling between 179 and 180.

3. Clarify and avoid shadowing clear()

Your private clear() shadows QTableWidget::clear() and adds setRowCount(0). To make intent clearer and reduce surprise, rename and use a more descriptive helper:

// headerinfotablewidget.h (private)
void resetTableContents();
// headerinfotablewidget.cpp
void HeaderInfoTableWidget::resetTableContents()
{
    DTableWidget::clear();
    setRowCount(0);
}

Then in updateData():

void HeaderInfoTableWidget::updateData(const QList<QPair<QString, QString>> &data)
{
    resetTableContents();
    // ...
}

This keeps the exact behavior but avoids shadowing the base clear() and makes the reset semantics explicit.

4. Optionally extract border drawing into a helper

If you want to further simplify paintEvent(), move the border logic into a separate private method, keeping paintEvent() focused:

// headerinfotablewidget.h (private):
void drawRoundedBorderAndDivider(QPainter &painter, const QRect &rect,
                                 const DPalette &palette, DPalette::ColorGroup cg);
void HeaderInfoTableWidget::paintEvent(QPaintEvent *event)
{
    DTableWidget::paintEvent(event);

    const auto cg      = colorGroupForCurrentState();
    const auto palette = paletteForCurrentState();

    QRect rect = viewport()->rect().adjusted(0, 0, -1, -1);
    QPainter painter(viewport());
    painter.setRenderHint(QPainter::Antialiasing, true);

    drawRoundedBorderAndDivider(painter, rect, palette, cg);
}

void HeaderInfoTableWidget::drawRoundedBorderAndDivider(
        QPainter &painter, const QRect &rect,
        const DPalette &palette, DPalette::ColorGroup cg)
{
    const int width  = 1;
    const int radius = 8;

    QPainterPath outer;
    outer.addRoundedRect(rect, radius, radius);

    QRect inner(rect.adjusted(width, width, -width, -width));
    QPainterPath innerPath;
    innerPath.addRoundedRect(inner, radius, radius);

    QPainterPath borderPath = outer.subtracted(innerPath);
    painter.fillPath(borderPath, palette.color(cg, DPalette::FrameBorder));

    QPen pen = painter.pen();
    pen.setWidth(width);
    pen.setColor(palette.color(cg, DPalette::FrameBorder));
    painter.setPen(pen);
    painter.drawRoundedRect(inner, radius, radius);

    int firstColumnWidth = horizontalHeader()->sectionSize(0);
    int dividerX = rect.left() + firstColumnWidth - 1;
    painter.drawLine(dividerX, rect.top(), dividerX, rect.bottom());
}

This keeps all visuals intact while making paintEvent() much easier to read and maintain.

@deepin-ci-robot
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: GongHeng2017, max-lvs

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

@GongHeng2017
Copy link
Copy Markdown
Contributor Author

/forcemerge

@deepin-bot
Copy link
Copy Markdown
Contributor

deepin-bot bot commented Apr 1, 2026

This pr force merged! (status: unstable)

@deepin-bot deepin-bot bot merged commit e5f236f into linuxdeepin:develop/eagle-20260325 Apr 1, 2026
20 of 22 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