From 518ac61ec121eb75dbf68f1dc096299555e9ebae Mon Sep 17 00:00:00 2001 From: yeshanshan Date: Tue, 11 Aug 2026 12:14:56 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20DCrumbEdit=20crumb=20=E8=87=AA?= =?UTF-8?q?=E9=80=82=E5=BA=94=E5=AE=B9=E5=99=A8=E5=AE=BD=E5=BA=A6=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=BC=A9=E5=B0=8F=E6=BA=A2=E5=87=BA=E4=B8=8E?= =?UTF-8?q?=E6=94=BE=E5=A4=A7=E7=95=99=E7=99=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit drawObject 中带 tagColor 的 crumb 文字由 AlignRight 改为 AlignLeft, 消除放大时色块与文字间的空白;并依据 doc->textWidth() 收缩绘制矩形、 对超长文字做 elidedText 省略,避免缩小侧边栏时标记信息溢出框外。 无 tagColor 分支与 intrinsicSize 保持不变。 新增 ut_dcrumbedit 用例 tagCrumbElidedAndNoOverflowWhenContainerNarrow, 验证容器宽度小于 crumb 宽度时文字省略、不溢出。 Log: 修复预览侧边栏标记在放大/缩小时不自适应的问题 Bug: https://pms.uniontech.com/bug-view-346439.html --- src/widgets/dcrumbedit.cpp | 30 +++++++++-- tests/testcases/widgets/ut_dcrumbedit.cpp | 65 +++++++++++++++++++++++ 2 files changed, 91 insertions(+), 4 deletions(-) diff --git a/src/widgets/dcrumbedit.cpp b/src/widgets/dcrumbedit.cpp index 41bb51f81..565f58d47 100644 --- a/src/widgets/dcrumbedit.cpp +++ b/src/widgets/dcrumbedit.cpp @@ -572,14 +572,28 @@ QSizeF CrumbObjectInterface::intrinsicSize(QTextDocument *doc, int posInDocument void CrumbObjectInterface::drawObject(QPainter *painter, const QRectF &rect, QTextDocument *doc, int posInDocument, const QTextFormat &format) { - Q_UNUSED(doc) Q_UNUSED(posInDocument) - const QRect new_rect = rect.adjusted(LeftMargin, TopMargin, 0, -TopMargin).toRect(); + QRect new_rect = rect.adjusted(LeftMargin, TopMargin, 0, -TopMargin).toRect(); const DCrumbTextFormat crumb_format(format); const QFontMetricsF font_metrics(crumb_format.font()); const int radius = crumb_format.backgroundRadius(); + // 当容器(文档)可用宽度小于 crumb 固有宽度时,将 crumb 收缩到可用宽度, + // 配合下方的文字省略,避免缩小侧边栏时标记信息溢出框外。 + // 仅对带标记颜色的 crumb 生效(本 issue 场景),无标记颜色的 crumb 保持原样。 + // 注意:intrinsicSize 拿不到可靠视口宽度,故不在此处收缩固有尺寸, + // 而是在绘制阶段依据 doc->textWidth() 收缩,文档宽度变化时会自动重新布局重绘。 + const qreal docTextWidth = doc->textWidth(); + const bool shrink = crumb_format.tagColor().isValid() + && docTextWidth > 0 + && new_rect.x() + new_rect.width() > docTextWidth; + if (shrink) { + const int maxRight = int(docTextWidth) - 1; + if (maxRight > new_rect.x() + radius) + new_rect.setRight(maxRight); + } + QPainterPath background_path; QPainterPath tag_path; const QRectF tag_rect(new_rect.x() + 2, new_rect.y() + 2, font_metrics.height() - 4, font_metrics.height() - 4); @@ -588,18 +602,26 @@ void CrumbObjectInterface::drawObject(QPainter *painter, const QRectF &rect, background_path.addRoundedRect(new_rect, radius, crumb_format.backgroundRadius()); painter->setRenderHint(QPainter::Antialiasing); + if (shrink) { + painter->save(); + painter->setClipRect(new_rect); + } painter->fillPath(background_path, backgroundBrush(new_rect, crumb_format.background())); if (crumb_format.tagColor().isValid()) { painter->fillPath(tag_path, crumb_format.tagColor()); painter->setPen(crumb_format.textColor()); - painter->drawText(new_rect.adjusted(tag_rect.width() + 2, 0, -radius, 0), - crumb_format.text(), Qt::AlignVCenter | Qt::AlignRight); + const QRectF textRect = new_rect.adjusted(tag_rect.width() + 2, 0, -radius, 0); + const QString displayText = font_metrics.elidedText( + crumb_format.text(), Qt::ElideRight, textRect.width()); + painter->drawText(textRect, displayText, Qt::AlignVCenter | Qt::AlignLeft); } else { painter->setPen(crumb_format.textColor()); painter->drawText(new_rect, Qt::AlignCenter, crumb_format.text()); } + if (shrink) + painter->restore(); } QBrush CrumbObjectInterface::backgroundBrush(const QRect &rect, const QBrush &brush) diff --git a/tests/testcases/widgets/ut_dcrumbedit.cpp b/tests/testcases/widgets/ut_dcrumbedit.cpp index 737741c02..f4e986b3f 100644 --- a/tests/testcases/widgets/ut_dcrumbedit.cpp +++ b/tests/testcases/widgets/ut_dcrumbedit.cpp @@ -6,6 +6,10 @@ #include #include #include +#include +#include +#include +#include #include "dcrumbedit.h" #include @@ -104,3 +108,64 @@ TEST_F(ut_DCrumbedit, createMimeDataFromSelection) ASSERT_EQ(qApp->clipboard()->text(), "测试1 人物 测试2 儿童 测试3 照片 测试代码调试添加GTest"); delete data; } + +// 验证:容器(文档)可用宽度小于 crumb 固有宽度时,带标记颜色的 crumb 文字会被省略且不溢出框外。 +// 对应 issue:放大缩小右侧边栏时预览区标记自适应不好(缩小溢出、放大留白)。 +TEST_F(ut_DCrumbedit, tagCrumbElidedAndNoOverflowWhenContainerNarrow) +{ + // 创建带标记颜色 + 超长文字的 crumb + DCrumbTextFormat format = edit->makeTextFormat(DCrumbEdit::red); + ASSERT_TRUE(format.tagColor().isValid()); + + const QString longText = QString::fromUtf8("标记文字").repeated(20); // 远超容器宽度的超长文字 + format.setText(longText); + // 用纯色背景/文字,便于按像素检测绘制范围 + format.setBackground(QBrush(Qt::yellow)); + format.setTextColor(Qt::black); + + ASSERT_TRUE(edit->appendCrumb(format)); + ASSERT_EQ(edit->crumbList().size(), 1); + ASSERT_EQ(edit->crumbList().first(), longText); + + QTextDocument *doc = edit->document(); + const QFontMetricsF fm(format.font()); + // crumb 固有宽度(与 intrinsicSize 口径一致) + const qreal intrinsicWidth = + fm.horizontalAdvance(longText) + fm.height() + format.backgroundRadius() + 2; + // 将文档可用宽度设为远小于固有宽度,模拟缩小侧边栏 + const int narrowWidth = int(intrinsicWidth / 3); + ASSERT_GT(narrowWidth, 0); + ASSERT_LT(qreal(narrowWidth), intrinsicWidth); + doc->setTextWidth(narrowWidth); + + // 渲染文档到一张比 narrowWidth 更宽的图片,检测 crumb 是否溢出文档可用宽度 + QImage img(int(intrinsicWidth) + 40, 100, QImage::Format_ARGB32); + img.fill(Qt::white); + QPainter p(&img); + QAbstractTextDocumentLayout::PaintContext ctx; + doc->documentLayout()->draw(&p, ctx); + p.end(); + + // 找出最右侧的非背景像素,crumb 不应溢出到 narrowWidth 右侧(留少量抗锯齿容差) + int rightMostContentX = -1; + for (int y = 0; y < img.height(); ++y) { + for (int x = img.width() - 1; x > rightMostContentX; --x) { + if (img.pixelColor(x, y) != Qt::white) { + rightMostContentX = x; + break; + } + } + } + // 容差 8 像素用于抗锯齿/圆角;无修复时 crumb 会溢出到约 intrinsicWidth 处 + EXPECT_LT(rightMostContentX, narrowWidth + 8) + << "crumb 内容溢出文档可用宽度(" << narrowWidth + << "),rightMostContentX=" << rightMostContentX; + + // 绘制层省略不改变实际 crumb 文本 + EXPECT_EQ(edit->crumbList().first(), longText); + + // 省略逻辑:对窄宽度做 elidedText 应得到含省略号的较短文本 + const QString elided = fm.elidedText(longText, Qt::ElideRight, narrowWidth); + EXPECT_LT(fm.horizontalAdvance(elided), fm.horizontalAdvance(longText)); + EXPECT_TRUE(elided.contains(QChar(0x2026))); // 包含省略号 “…” +} From 51acca5758c9b327586309c32d0385e1f42845c1 Mon Sep 17 00:00:00 2001 From: yeshanshan Date: Tue, 11 Aug 2026 17:43:03 +0800 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20elidedText=20=E4=BB=85=E5=9C=A8?= =?UTF-8?q?=20shrink=20=E6=97=B6=E8=B0=83=E7=94=A8=EF=BC=8C=E5=B8=B8?= =?UTF-8?q?=E8=A7=84=E8=B7=AF=E5=BE=84=E9=9B=B6=E5=BC=80=E9=94=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 采纳代码审核建议 A(非阻塞优化):带 tagColor 分支中,elidedText 仅在容器宽度不足(shrink=true)时调用,常规宽度直接绘制原文。 行为等价(常规宽度下 elidedText 本就返回原文),但意图更清晰、 省去常规路径的无谓省略计算,并避免将来口径微调导致意外省略号回归。 Log: 优化 DCrumbEdit drawObject 省略逻辑 Bug: https://pms.uniontech.com/bug-view-346439.html --- src/widgets/dcrumbedit.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/widgets/dcrumbedit.cpp b/src/widgets/dcrumbedit.cpp index 565f58d47..23e80902a 100644 --- a/src/widgets/dcrumbedit.cpp +++ b/src/widgets/dcrumbedit.cpp @@ -613,8 +613,10 @@ void CrumbObjectInterface::drawObject(QPainter *painter, const QRectF &rect, painter->setPen(crumb_format.textColor()); const QRectF textRect = new_rect.adjusted(tag_rect.width() + 2, 0, -radius, 0); - const QString displayText = font_metrics.elidedText( - crumb_format.text(), Qt::ElideRight, textRect.width()); + // 仅在容器宽度不足(shrink)时做省略;常规宽度直接绘制原文,避免无谓省略计算 + QString displayText = crumb_format.text(); + if (shrink) + displayText = font_metrics.elidedText(displayText, Qt::ElideRight, textRect.width()); painter->drawText(textRect, displayText, Qt::AlignVCenter | Qt::AlignLeft); } else { painter->setPen(crumb_format.textColor()); From 3ea0257b90c498f1877708fd7729a6027c41bcbc Mon Sep 17 00:00:00 2001 From: yeshanshan Date: Fri, 14 Aug 2026 18:12:05 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20intrinsicSize=20=E9=99=90=E5=AE=BD?= =?UTF-8?q?=E9=81=BF=E5=85=8D=E8=B6=85=E5=AE=BD=E6=A0=87=E8=AE=B0=E6=92=91?= =?UTF-8?q?=E5=BC=80=E6=96=87=E6=A1=A3=E5=87=BA=E7=8E=B0=E6=B0=B4=E5=B9=B3?= =?UTF-8?q?=E6=BB=9A=E5=8A=A8=E6=9D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit intrinsicSize 带 tagColor 分支将固有宽度限制在文档可用内容宽度内 (doc->textWidth() - 2*documentMargin()),超宽时收缩到 avail, 避免单个超宽 crumb 撑开文档导致水平滚动条。 drawObject 相应简化:删除原 shrink 矩形收缩块(已成死代码), 省略条件改为文字宽度 > 可用宽度时才 elidedText,setClipRect 改为 无条件 save/restore 兜底。 UT 补 EXPECT_LE(doc->idealWidth(), narrowWidth) 断言锁定根因。 Log: 修复单个超宽标记撑开容器出现水平滚动条的问题 Bug: https://pms.uniontech.com/bug-view-346439.html --- src/widgets/dcrumbedit.cpp | 55 +++++++++++------------ tests/testcases/widgets/ut_dcrumbedit.cpp | 6 +++ 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/widgets/dcrumbedit.cpp b/src/widgets/dcrumbedit.cpp index 23e80902a..3a909da4c 100644 --- a/src/widgets/dcrumbedit.cpp +++ b/src/widgets/dcrumbedit.cpp @@ -556,74 +556,69 @@ class DCrumbEditPrivate : public DCORE_NAMESPACE::DObjectPrivate QSizeF CrumbObjectInterface::intrinsicSize(QTextDocument *doc, int posInDocument, const QTextFormat &format) { - Q_UNUSED(doc) Q_UNUSED(posInDocument) const DCrumbTextFormat crumb_format(format); const QFontMetricsF font_metrics(crumb_format.font()); int radius = crumb_format.backgroundRadius(); - if (crumb_format.tagColor().isValid()) - return QSizeF(font_metrics.horizontalAdvance(crumb_format.text()) + font_metrics.height() + radius + 2, font_metrics.height() + 2); + if (crumb_format.tagColor().isValid()) { + qreal width = font_metrics.horizontalAdvance(crumb_format.text()) + + font_metrics.height() + radius + 2; + // 限制单个 crumb 不超过文档可用内容宽度,避免超宽标记撑开文档导致水平滚动条。 + // 超宽时由 drawObject 做省略显示。仅对带标记颜色的 crumb 生效(本 issue 场景)。 + const qreal textWidth = doc->textWidth(); + if (textWidth > 0) { + const qreal avail = textWidth - 2 * doc->documentMargin(); + if (avail > 0 && width > avail) + width = avail; + } + return QSizeF(width, font_metrics.height() + 2); + } - return QSizeF(font_metrics.horizontalAdvance(crumb_format.text()) + 2 * radius + 2, font_metrics.height() + 2 + TopMargin *2); + return QSizeF(font_metrics.horizontalAdvance(crumb_format.text()) + 2 * radius + 2, + font_metrics.height() + 2 + TopMargin * 2); } void CrumbObjectInterface::drawObject(QPainter *painter, const QRectF &rect, QTextDocument *doc, int posInDocument, const QTextFormat &format) { + Q_UNUSED(doc) Q_UNUSED(posInDocument) - QRect new_rect = rect.adjusted(LeftMargin, TopMargin, 0, -TopMargin).toRect(); + const QRect new_rect = rect.adjusted(LeftMargin, TopMargin, 0, -TopMargin).toRect(); const DCrumbTextFormat crumb_format(format); const QFontMetricsF font_metrics(crumb_format.font()); const int radius = crumb_format.backgroundRadius(); - // 当容器(文档)可用宽度小于 crumb 固有宽度时,将 crumb 收缩到可用宽度, - // 配合下方的文字省略,避免缩小侧边栏时标记信息溢出框外。 - // 仅对带标记颜色的 crumb 生效(本 issue 场景),无标记颜色的 crumb 保持原样。 - // 注意:intrinsicSize 拿不到可靠视口宽度,故不在此处收缩固有尺寸, - // 而是在绘制阶段依据 doc->textWidth() 收缩,文档宽度变化时会自动重新布局重绘。 - const qreal docTextWidth = doc->textWidth(); - const bool shrink = crumb_format.tagColor().isValid() - && docTextWidth > 0 - && new_rect.x() + new_rect.width() > docTextWidth; - if (shrink) { - const int maxRight = int(docTextWidth) - 1; - if (maxRight > new_rect.x() + radius) - new_rect.setRight(maxRight); - } - QPainterPath background_path; QPainterPath tag_path; - const QRectF tag_rect(new_rect.x() + 2, new_rect.y() + 2, font_metrics.height() - 4, font_metrics.height() - 4); + const QRectF tag_rect(new_rect.x() + 2, new_rect.y() + 2, + font_metrics.height() - 4, font_metrics.height() - 4); tag_path.addEllipse(tag_rect); background_path.addRoundedRect(new_rect, radius, crumb_format.backgroundRadius()); painter->setRenderHint(QPainter::Antialiasing); - if (shrink) { - painter->save(); - painter->setClipRect(new_rect); - } + // 极窄宽度下色块可能超出收缩后的矩形,裁剪兜底防绘制越界 + painter->save(); + painter->setClipRect(new_rect); painter->fillPath(background_path, backgroundBrush(new_rect, crumb_format.background())); if (crumb_format.tagColor().isValid()) { painter->fillPath(tag_path, crumb_format.tagColor()); - painter->setPen(crumb_format.textColor()); const QRectF textRect = new_rect.adjusted(tag_rect.width() + 2, 0, -radius, 0); - // 仅在容器宽度不足(shrink)时做省略;常规宽度直接绘制原文,避免无谓省略计算 + // intrinsicSize 已将超宽 crumb 收缩到可用宽度,此处按实际可用宽度省略文字 QString displayText = crumb_format.text(); - if (shrink) + if (font_metrics.horizontalAdvance(displayText) > textRect.width()) displayText = font_metrics.elidedText(displayText, Qt::ElideRight, textRect.width()); painter->drawText(textRect, displayText, Qt::AlignVCenter | Qt::AlignLeft); } else { painter->setPen(crumb_format.textColor()); painter->drawText(new_rect, Qt::AlignCenter, crumb_format.text()); } - if (shrink) - painter->restore(); + painter->restore(); } QBrush CrumbObjectInterface::backgroundBrush(const QRect &rect, const QBrush &brush) diff --git a/tests/testcases/widgets/ut_dcrumbedit.cpp b/tests/testcases/widgets/ut_dcrumbedit.cpp index f4e986b3f..3513a0f0d 100644 --- a/tests/testcases/widgets/ut_dcrumbedit.cpp +++ b/tests/testcases/widgets/ut_dcrumbedit.cpp @@ -138,6 +138,12 @@ TEST_F(ut_DCrumbedit, tagCrumbElidedAndNoOverflowWhenContainerNarrow) ASSERT_LT(qreal(narrowWidth), intrinsicWidth); doc->setTextWidth(narrowWidth); + // 修复前 intrinsicSize 未限宽,单个超宽 crumb 会撑开文档宽度导致水平滚动条; + // 修复后 idealWidth 应不超出文档可用宽度。 + EXPECT_LE(doc->idealWidth(), qreal(narrowWidth)) + << "单个超宽 crumb 撑开了文档宽度(" << doc->idealWidth() + << " > " << narrowWidth << "),会产生水平滚动条"; + // 渲染文档到一张比 narrowWidth 更宽的图片,检测 crumb 是否溢出文档可用宽度 QImage img(int(intrinsicWidth) + 40, 100, QImage::Format_ARGB32); img.fill(Qt::white);