From 7535ae22915d777d890fe551146f049bed5d0abc Mon Sep 17 00:00:00 2001 From: Da Shen Date: Wed, 27 May 2026 20:16:26 +0800 Subject: [PATCH 1/4] =?UTF-8?q?[0169]=20=E6=9B=B4=E6=96=B0=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E6=96=87=E6=A1=A3=EF=BC=9APDF=E9=98=85=E8=AF=BB?= =?UTF-8?q?=E5=99=A8=E5=9C=A8=E5=8D=A0=E6=8D=AE=E5=B1=8F=E5=B9=95=E4=B8=80?= =?UTF-8?q?=E5=8D=8A=E7=9A=84=E6=97=B6=E5=80=99=EF=BC=8C=E8=87=AA=E5=8A=A8?= =?UTF-8?q?Fit=20Width?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 --- devel/0169.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 devel/0169.md diff --git a/devel/0169.md b/devel/0169.md new file mode 100644 index 0000000000..8a92047b42 --- /dev/null +++ b/devel/0169.md @@ -0,0 +1,48 @@ +# [0169] PDF阅读器在占据屏幕一半的时候,自动Fit Width + +## 相关文档 +- [dddd.md](dddd.md) - 任务文档模板 + +## 任务相关的代码文件 +- `src/Plugins/Qt/qt_pdf_reader_widget.hpp` +- `src/Plugins/Qt/qt_pdf_reader_widget.cpp` +- `tests/Plugins/Qt/qt_pdf_reader_widget_test.cpp` + +## 如何测试 + +### 确定性测试(单元测试) +```bash +xmake b qt_pdf_reader_widget_test +xmake r qt_pdf_reader_widget_test +``` + +新增测试用例验证: +- 当 `PDFReaderWidget` 宽度不超过屏幕一半时,加载 PDF 后自动触发 Fit Width +- 当 `PDFReaderWidget` 宽度超过屏幕一半时,加载 PDF 后保持默认 100% 缩放 + +### 非确定性测试(文档验证) +打开 Mogan,调整窗口宽度为屏幕一半或更小,然后打开 PDF 文件,确认页面自动适应阅读器宽度。 + +## 如何提交 + +提交前执行以下最少步骤: +```bash +gf fmt --changed-since=main +xmake b qt_pdf_reader_widget_test +xmake r qt_pdf_reader_widget_test +``` + +## What + +当 PDF 阅读器占据屏幕一半或更窄时,加载 PDF 后自动执行 Fit Width,使页面宽度适应视口。 + +## Why + +在分屏或窄窗口场景下,PDF 阅读器默认以 100% 缩放显示,导致页面内容超出视口,需要用户手动调整缩放。自动 Fit Width 可以提升窄窗口下的阅读体验。 + +## How + +1. 在 `PDFReaderWidget` 中新增 `maybeAutoFitWidth()` 方法:当 widget 宽度不超过屏幕可用宽度的一半时,自动调用 `fitWidth()`。 +2. 在 `loadFromFile()` 成功加载 PDF 后调用 `maybeAutoFitWidth()`,确保初始显示时自动适应宽度。 +3. 在 resize 防抖超时回调 `onResizeDebounced()` 中同样调用 `maybeAutoFitWidth()`,确保窗口从宽变窄时也能自动适应。 +4. 引入 `autoFitApplied_` 标志位,每个 PDF 加载周期内只自动触发一次 Fit Width,避免覆盖用户手动缩放。 From 9ab176215f0fb1ceffe2a7c48ad2e6335993d908 Mon Sep 17 00:00:00 2001 From: Da Shen Date: Wed, 27 May 2026 20:16:32 +0800 Subject: [PATCH 2/4] =?UTF-8?q?[0169]=20PDF=E9=98=85=E8=AF=BB=E5=99=A8?= =?UTF-8?q?=E5=9C=A8=E5=8D=A0=E6=8D=AE=E5=B1=8F=E5=B9=95=E4=B8=80=E5=8D=8A?= =?UTF-8?q?=E7=9A=84=E6=97=B6=E5=80=99=EF=BC=8C=E8=87=AA=E5=8A=A8Fit=20Wid?= =?UTF-8?q?th?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 maybeAutoFitWidth():当 widget 宽度不超过屏幕可用宽度一半时,自动调用 fitWidth() - 在 loadFromFile() 加载 PDF 后调用 maybeAutoFitWidth(),确保初始显示自动适应宽度 - 在 resize 防抖回调 onResizeDebounced() 中同样调用 maybeAutoFitWidth(),支持窗口从宽变窄时自动适应 - 引入 autoFitApplied_ 标志位,每个 PDF 加载周期内只自动触发一次,避免覆盖用户手动缩放 - 新增单元测试 test_autoFitWidth_whenNarrow 和 test_noAutoFitWidth_whenWide Co-Authored-By: Claude Opus 4.7 --- src/Plugins/Qt/qt_pdf_reader_widget.cpp | 34 +++++++++++++++-- src/Plugins/Qt/qt_pdf_reader_widget.hpp | 3 ++ .../Plugins/Qt/qt_pdf_reader_widget_test.cpp | 38 +++++++++++++++++++ 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/src/Plugins/Qt/qt_pdf_reader_widget.cpp b/src/Plugins/Qt/qt_pdf_reader_widget.cpp index b85671036e..a72a0ff4ce 100644 --- a/src/Plugins/Qt/qt_pdf_reader_widget.cpp +++ b/src/Plugins/Qt/qt_pdf_reader_widget.cpp @@ -76,8 +76,8 @@ PDFReaderWidget::PDFReaderWidget (QWidget* parent) zoomFactor_ (1.0), pageAspectRatio_ (0.0), pageBaseWidthPts_ (0.0), overLink_ (false), zoomDebounceTimer_ (nullptr), resizeDebounceTimer_ (nullptr), gestureSafetyTimer_ (nullptr), - inPinchGesture_ (false), blockRender_ (false), pinchStartZoom_ (1.0), - renderCallCount_ (0) { + inPinchGesture_ (false), blockRender_ (false), autoFitApplied_ (false), + pinchStartZoom_ (1.0), renderCallCount_ (0) { mainLayout_= new QVBoxLayout (this); mainLayout_->setContentsMargins (0, 0, 0, 0); @@ -169,7 +169,7 @@ PDFReaderWidget::PDFReaderWidget (QWidget* parent) resizeDebounceTimer_->setSingleShot (true); resizeDebounceTimer_->setInterval (RESIZE_DEBOUNCE_MS); connect (resizeDebounceTimer_, &QTimer::timeout, this, - &PDFReaderWidget::rebuildPages); + &PDFReaderWidget::onResizeDebounced); gestureSafetyTimer_= new QTimer (this); gestureSafetyTimer_->setSingleShot (true); @@ -346,6 +346,31 @@ PDFReaderWidget::updateZoomDisplay () { zoomCombo_->blockSignals (blocked); } +void +PDFReaderWidget::onResizeDebounced () { + if (!maybeAutoFitWidth ()) { + rebuildPages (); + } +} + +bool +PDFReaderWidget::maybeAutoFitWidth () { + if (autoFitApplied_) return false; + if (pdfData_.isEmpty () || pageCount_ <= 0) return false; + if (pageBaseWidthPts_ <= 0) return false; + + QScreen* screen= this->screen (); + if (!screen) screen= QApplication::primaryScreen (); + int screenWidth= screen ? screen->availableSize ().width () : 1920; + + if (width () <= screenWidth / 2) { + fitWidth (); + autoFitApplied_= true; + return true; + } + return false; +} + void PDFReaderWidget::applyZoomToLabels () { int width= pageWidth (); @@ -976,6 +1001,7 @@ PDFReaderWidget::rebuildPages () { bool PDFReaderWidget::loadFromFile (const QString& filePath, int dpi) { clear (); + autoFitApplied_= false; targetDpi_= dpi; hasError_ = false; @@ -1089,6 +1115,7 @@ PDFReaderWidget::loadFromFile (const QString& filePath, int dpi) { } pageLayout_->addStretch (1); + maybeAutoFitWidth (); rebuildPages (); contentWidget_->adjustSize (); updateZoomDisplay (); @@ -1105,6 +1132,7 @@ PDFReaderWidget::clear () { pageAspectRatio_ = 0.0; pageBaseWidthPts_= 0.0; pageAspectRatios_.clear (); + autoFitApplied_= false; clearPageLinks (); pageCache_.clear (); diff --git a/src/Plugins/Qt/qt_pdf_reader_widget.hpp b/src/Plugins/Qt/qt_pdf_reader_widget.hpp index 6dd7979e8e..ea93472663 100644 --- a/src/Plugins/Qt/qt_pdf_reader_widget.hpp +++ b/src/Plugins/Qt/qt_pdf_reader_widget.hpp @@ -107,6 +107,8 @@ private slots: void finishPinchGesture (); bool renderPageToLabel (int pageNumber, QLabel* label, int targetWidth); void rebuildPages (); + void onResizeDebounced (); + bool maybeAutoFitWidth (); int pageWidth () const; void setupToolBar (); void updateZoomDisplay (); @@ -180,6 +182,7 @@ private slots: bool inPinchGesture_; bool blockRender_; + bool autoFitApplied_; double pinchStartZoom_; int renderCallCount_; diff --git a/tests/Plugins/Qt/qt_pdf_reader_widget_test.cpp b/tests/Plugins/Qt/qt_pdf_reader_widget_test.cpp index cb671f4e73..a3ca792942 100644 --- a/tests/Plugins/Qt/qt_pdf_reader_widget_test.cpp +++ b/tests/Plugins/Qt/qt_pdf_reader_widget_test.cpp @@ -1271,6 +1271,44 @@ private slots: delete widget; } + + void test_autoFitWidth_whenNarrow () { + PDFReaderWidget* widget= new PDFReaderWidget (); + QScreen* screen= QApplication::primaryScreen (); + int screenWidth= screen ? screen->availableSize ().width () : 1920; + widget->resize (screenWidth / 4, 300); + widget->show (); + + url pdfUrl= url_system ("$TEXMACS_PATH/tests/PDF/pdf_1_4_sample.pdf"); + QVERIFY (is_regular (pdfUrl)); + + bool result= widget->loadFromFile (to_qstring (as_string (pdfUrl))); + QVERIFY (result); + QApplication::processEvents (); + + // 当 widget 宽度不超过屏幕一半时,应自动触发 Fit Width + QVERIFY (widget->zoomFactor () != 1.0); + delete widget; + } + + void test_noAutoFitWidth_whenWide () { + PDFReaderWidget* widget= new PDFReaderWidget (); + QScreen* screen= QApplication::primaryScreen (); + int screenWidth= screen ? screen->availableSize ().width () : 1920; + widget->resize (screenWidth * 2 / 3, 800); + widget->show (); + + url pdfUrl= url_system ("$TEXMACS_PATH/tests/PDF/pdf_1_4_sample.pdf"); + QVERIFY (is_regular (pdfUrl)); + + bool result= widget->loadFromFile (to_qstring (as_string (pdfUrl))); + QVERIFY (result); + QApplication::processEvents (); + + // 当 widget 宽度超过屏幕一半时,应保持默认 100% 缩放 + QCOMPARE (widget->zoomFactor (), 1.0); + delete widget; + } }; QTEST_MAIN (TestPdfReaderWidget) From b933ff0ea0e57e8a790431b9ada6e9d90a6e3b62 Mon Sep 17 00:00:00 2001 From: Da Shen Date: Thu, 28 May 2026 10:30:30 +0800 Subject: [PATCH 3/4] =?UTF-8?q?[0169]=20=E6=9B=B4=E6=96=B0=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E6=96=87=E6=A1=A3=EF=BC=9A=E7=B2=BE=E7=A1=AE=E6=A3=80?= =?UTF-8?q?=E6=B5=8B=E5=B7=A6/=E5=8F=B3=E5=8D=8A=E5=B1=8F=E8=B4=B4?= =?UTF-8?q?=E9=9D=A0=E5=90=8E=E8=87=AA=E5=8A=A8Fit=20Width?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 --- devel/0169.md | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/devel/0169.md b/devel/0169.md index 8a92047b42..66bc68ff88 100644 --- a/devel/0169.md +++ b/devel/0169.md @@ -20,8 +20,26 @@ xmake r qt_pdf_reader_widget_test - 当 `PDFReaderWidget` 宽度不超过屏幕一半时,加载 PDF 后自动触发 Fit Width - 当 `PDFReaderWidget` 宽度超过屏幕一半时,加载 PDF 后保持默认 100% 缩放 +> **注意**:由于精确检测半屏贴靠需要窗口几何信息与屏幕边缘对齐,在 headless/虚拟屏幕测试环境中可能无法完全模拟系统分屏(Snap)行为。上述单元测试主要覆盖宽度阈值分支;贴靠边缘的判定建议以手工测试为准。 + ### 非确定性测试(文档验证) -打开 Mogan,调整窗口宽度为屏幕一半或更小,然后打开 PDF 文件,确认页面自动适应阅读器宽度。 + +1. **左半屏贴靠测试** + - 打开 Mogan 并加载一个 PDF 文件 + - 将软件窗口拖动到屏幕**左边缘**,直到系统自动将其贴靠到左半屏 + - 确认 PDF 页面自动执行 Fit Width,宽度适应视口 + +2. **右半屏贴靠测试** + - 将软件窗口拖动到屏幕**右边缘**,直到系统自动将其贴靠到右半屏 + - 确认 PDF 页面自动执行 Fit Width + +3. **非贴靠窄窗口测试** + - 手动调整窗口大小,使其宽度小于屏幕一半,但**不贴靠**到屏幕边缘 + - 确认**不会**自动触发 Fit Width,保持当前缩放比例 + +4. **恢复全屏测试** + - 将窗口从半屏状态拖回屏幕中央或最大化 + - 确认窗口恢复后,再次贴靠到左/右半屏时仍能自动触发 Fit Width ## 如何提交 @@ -34,15 +52,21 @@ xmake r qt_pdf_reader_widget_test ## What -当 PDF 阅读器占据屏幕一半或更窄时,加载 PDF 后自动执行 Fit Width,使页面宽度适应视口。 +当 PDF 阅读器被系统贴靠(Snap)到屏幕左半屏或右半屏时,自动执行 Fit Width,使页面宽度适应视口。 ## Why -在分屏或窄窗口场景下,PDF 阅读器默认以 100% 缩放显示,导致页面内容超出视口,需要用户手动调整缩放。自动 Fit Width 可以提升窄窗口下的阅读体验。 +在分屏场景下(如将软件拖动到屏幕左/右边缘自动占据半屏),PDF 阅读器默认以 100% 缩放显示,导致页面内容超出视口,需要用户手动调整缩放。自动 Fit Width 可以提升分屏阅读体验;但如果只是用户手动缩小窗口,则不应强制改变缩放,避免干扰用户意图。 ## How -1. 在 `PDFReaderWidget` 中新增 `maybeAutoFitWidth()` 方法:当 widget 宽度不超过屏幕可用宽度的一半时,自动调用 `fitWidth()`。 -2. 在 `loadFromFile()` 成功加载 PDF 后调用 `maybeAutoFitWidth()`,确保初始显示时自动适应宽度。 -3. 在 resize 防抖超时回调 `onResizeDebounced()` 中同样调用 `maybeAutoFitWidth()`,确保窗口从宽变窄时也能自动适应。 -4. 引入 `autoFitApplied_` 标志位,每个 PDF 加载周期内只自动触发一次 Fit Width,避免覆盖用户手动缩放。 +1. 在 `PDFReaderWidget` 中新增 `maybeAutoFitWidth()` 方法,精确检测窗口是否被贴靠到左/右半屏: + - 窗口宽度约等于屏幕可用宽度的一半(允许容差) + - 窗口高度约等于屏幕可用高度(允许容差) + - 窗口左边缘贴近屏幕左边缘(左半屏)或右边缘贴近屏幕右边缘(右半屏) + - 排除最大化、全屏状态 + 当同时满足以上条件时,自动调用 `fitWidth()`。 +2. 在 `loadFromFile()` 成功加载 PDF 后调用 `maybeAutoFitWidth()`,确保初始显示时自动适应。 +3. 在 resize 防抖超时回调 `onResizeDebounced()` 中同样调用 `maybeAutoFitWidth()`,确保窗口贴靠变化时也能自动适应。 +4. 在 `onResizeDebounced()` 中增加重置逻辑:当窗口明显离开半屏状态时,重置 `autoFitApplied_`,使下次贴靠半屏仍能自动触发。 +5. 引入 `autoFitApplied_` 标志位,避免在同一状态下重复触发,覆盖用户手动缩放。 From 185d20f42c39c35c9655a0661f1445d336ff473e Mon Sep 17 00:00:00 2001 From: Da Shen Date: Thu, 28 May 2026 10:30:39 +0800 Subject: [PATCH 4/4] =?UTF-8?q?[0169]=20PDF=E9=98=85=E8=AF=BB=E5=99=A8?= =?UTF-8?q?=E7=B2=BE=E7=A1=AE=E6=A3=80=E6=B5=8B=E5=B7=A6/=E5=8F=B3?= =?UTF-8?q?=E5=8D=8A=E5=B1=8F=E8=B4=B4=E9=9D=A0=E5=90=8E=E8=87=AA=E5=8A=A8?= =?UTF-8?q?Fit=20Width?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - maybeAutoFitWidth() 现在检测窗口是否真正贴靠到屏幕左/右边缘 - 排除最大化/全屏状态,避免手动缩小窗口误触发 - onResizeDebounced() 增加重置逻辑,离开半屏后可再次触发 Co-Authored-By: Claude Opus 4.7 --- src/Plugins/Qt/qt_pdf_reader_widget.cpp | 45 +++++++++++++++++-- .../Plugins/Qt/qt_pdf_reader_widget_test.cpp | 4 +- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/Plugins/Qt/qt_pdf_reader_widget.cpp b/src/Plugins/Qt/qt_pdf_reader_widget.cpp index a72a0ff4ce..bfd02f8c7a 100644 --- a/src/Plugins/Qt/qt_pdf_reader_widget.cpp +++ b/src/Plugins/Qt/qt_pdf_reader_widget.cpp @@ -348,6 +348,23 @@ PDFReaderWidget::updateZoomDisplay () { void PDFReaderWidget::onResizeDebounced () { + // 当窗口离开半屏贴靠状态时,重置自动适配标志, + // 以便下次贴靠到左/右半屏时仍能触发 Fit Width + if (autoFitApplied_) { + QScreen* screen= this->screen (); + if (!screen) screen= QApplication::primaryScreen (); + if (screen) { + QRect screenGeo= screen->availableGeometry (); + int screenW = screenGeo.width (); + QRect winGeo = window ()->frameGeometry (); + int halfWidth= screenW / 2; + int tolerance= qMax (20, screenW / 20); + if (qAbs (winGeo.width () - halfWidth) > tolerance) { + autoFitApplied_= false; + } + } + } + if (!maybeAutoFitWidth ()) { rebuildPages (); } @@ -358,12 +375,34 @@ PDFReaderWidget::maybeAutoFitWidth () { if (autoFitApplied_) return false; if (pdfData_.isEmpty () || pageCount_ <= 0) return false; if (pageBaseWidthPts_ <= 0) return false; + if (isMaximized () || isFullScreen ()) return false; QScreen* screen= this->screen (); if (!screen) screen= QApplication::primaryScreen (); - int screenWidth= screen ? screen->availableSize ().width () : 1920; - - if (width () <= screenWidth / 2) { + if (!screen) return false; + + QRect screenGeo= screen->availableGeometry (); + int screenW = screenGeo.width (); + int screenH = screenGeo.height (); + QRect winGeo = window ()->frameGeometry (); + + // 判断是否贴靠到左半屏或右半屏: + // 1. 宽度约等于屏幕宽度的一半 + // 2. 高度约等于屏幕可用高度 + // 3. 窗口左边缘贴近屏幕左边缘(左半屏)或 + // 窗口右边缘贴近屏幕右边缘(右半屏) + int halfWidth = screenW / 2; + int widthTolerance = qMax (20, screenW / 20); + int heightTolerance= qMax (40, screenH / 20); + + if (qAbs (winGeo.width () - halfWidth) > widthTolerance) return false; + if (qAbs (winGeo.height () - screenH) > heightTolerance) return false; + + bool snappedLeft = qAbs (winGeo.x () - screenGeo.x ()) <= 10; + bool snappedRight= qAbs ((winGeo.x () + winGeo.width ()) - + (screenGeo.x () + screenGeo.width ())) <= 10; + + if (snappedLeft || snappedRight) { fitWidth (); autoFitApplied_= true; return true; diff --git a/tests/Plugins/Qt/qt_pdf_reader_widget_test.cpp b/tests/Plugins/Qt/qt_pdf_reader_widget_test.cpp index a3ca792942..b1916838c8 100644 --- a/tests/Plugins/Qt/qt_pdf_reader_widget_test.cpp +++ b/tests/Plugins/Qt/qt_pdf_reader_widget_test.cpp @@ -1275,7 +1275,7 @@ private slots: void test_autoFitWidth_whenNarrow () { PDFReaderWidget* widget= new PDFReaderWidget (); QScreen* screen= QApplication::primaryScreen (); - int screenWidth= screen ? screen->availableSize ().width () : 1920; + int screenWidth = screen ? screen->availableSize ().width () : 1920; widget->resize (screenWidth / 4, 300); widget->show (); @@ -1294,7 +1294,7 @@ private slots: void test_noAutoFitWidth_whenWide () { PDFReaderWidget* widget= new PDFReaderWidget (); QScreen* screen= QApplication::primaryScreen (); - int screenWidth= screen ? screen->availableSize ().width () : 1920; + int screenWidth = screen ? screen->availableSize ().width () : 1920; widget->resize (screenWidth * 2 / 3, 800); widget->show ();