From 39f0b3ab98e89d4fd89e6fc75d4126548727f3d1 Mon Sep 17 00:00:00 2001 From: leoshone Date: Fri, 4 Sep 2026 18:15:49 +0800 Subject: [PATCH 1/3] Fix bottom rows being clipped when the panel is resized at high DPI AdjustDocPanelSize multiplied the pixel delta between the new panel size and the initial one by the desktop DPI scale. Both values are already in physical pixels, so on any monitor whose scale is not 100% the tree (and the node path box) grew s times faster than the panel itself and slid below the panel's client area. The tree control computed its scroll range from its own oversized height, so the scrollbar reported the end while the last (s-1)*growth/itemHeight rows were physically outside the visible panel: fully expanded long documents showed rows that could never be scrolled into view. Measurements at 150% scaling before the fix: tree bottom 213 px below the panel client area (5+ unreachable rows at 100% zoom), node path box 115 px below it. Positioning is now absolute - template rect plus the unscaled delta, with the tree ending above a node path box that is pinned to the bottom of the client area - which also makes repeated resizes idempotent instead of accumulated. --- src/NppJsonViewer/JsonViewDlg.cpp | 102 ++++++++++++++++-------------- src/NppJsonViewer/JsonViewDlg.h | 9 ++- 2 files changed, 62 insertions(+), 49 deletions(-) diff --git a/src/NppJsonViewer/JsonViewDlg.cpp b/src/NppJsonViewer/JsonViewDlg.cpp index 4080af0..88c8f43 100644 --- a/src/NppJsonViewer/JsonViewDlg.cpp +++ b/src/NppJsonViewer/JsonViewDlg.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -52,6 +53,10 @@ void JsonViewDlg::ShowDlg(bool bShow) m_lfInitialClientWidth = rc.right - rc.left; m_lfInitialClientHeight = rc.bottom - rc.top; + // Remember the template layout: every later resize is expressed as + // "template rect + (current client size - initial client size)". + CaptureInitialControlRects(); + // define the default docking behaviour data.uMask = DWS_DF_CONT_LEFT | DWS_ICONTAB | DWS_ADDINFO; data.pszModuleName = getPluginFileName(); @@ -654,64 +659,67 @@ void JsonViewDlg::SetIconAndTooltip(eButton ctrlType, const std::wstring& toolTi CUtility::CreateToolTip(_hSelf, nCtrlID, toolTip, _hInst); } -void JsonViewDlg::AdjustDocPanelSize(int nWidth, int nHeight) +void JsonViewDlg::CaptureInitialControlRects() { - // Calculate desktop scale. - float fDeskScale = CUtility::GetDesktopScale(_hSelf); - - auto newDeltaWidth = nWidth - m_lfInitialClientWidth - 2; // -2 is used for margin - auto addWidth = static_cast((newDeltaWidth - m_lfDeltaWidth) * fDeskScale); - m_lfDeltaWidth = newDeltaWidth; - - auto newDeltaHeight = nHeight - m_lfInitialClientHeight; - auto addHeight = static_cast((newDeltaHeight - m_lfDeltaHeight) * fDeskScale); - m_lfDeltaHeight = newDeltaHeight; - - // elements that need to be resized horizontally - const auto resizeWindowIDs = {IDC_EDT_SEARCH, IDC_TREE}; - - // elements that need to be moved - const auto moveWindowIDs = {IDC_BTN_SEARCH}; + auto capture = [this](int id, RECT& out) { + RECT r {}; + ::GetWindowRect(::GetDlgItem(getHSelf(), id), &r); + ::MapWindowPoints(NULL, getHSelf(), reinterpret_cast(&r), 2); + out = r; + }; + + capture(IDC_EDT_SEARCH, m_rcInitSearch); + capture(IDC_BTN_SEARCH, m_rcInitSearchBtn); + capture(IDC_TREE, m_rcInitTree); + capture(IDC_EDT_NODEPATH, m_rcInitNodePath); +} - // elements which requires both resizing and move - const auto resizeAndMoveWindowIDs = {IDC_EDT_NODEPATH}; +void JsonViewDlg::AdjustDocPanelSize(int nWidth, int nHeight) +{ + // nWidth/nHeight (WM_SIZE) and m_lfInitialClient* (GetClientRect) are both + // already in physical pixels, so the delta must NOT be multiplied by the + // desktop DPI scale. The previous code did exactly that: on any monitor + // whose scale is not 100% the tree grew faster than its parent panel and + // its bottom rows - together with the node path box - slid below the + // panel's client area, where no scroll bar can ever reach them. + // Every control is therefore positioned from the *current* client size + // (template rect + unscaled delta), which also makes repeated resizes + // idempotent instead of accumulated. + const int addWidth = nWidth - m_lfInitialClientWidth; + const int addHeight = nHeight - m_lfInitialClientHeight; const UINT flags = SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_SHOWWINDOW; - RECT rc; - for (int id : resizeWindowIDs) - { - HWND hWnd = ::GetDlgItem(_hSelf, id); - ::GetWindowRect(hWnd, &rc); - int cx = rc.right - rc.left + addWidth; - int cy = rc.bottom - rc.top; - - if (id == IDC_TREE) - cy += addHeight; + const auto width = [](const RECT& r) { return r.right - r.left; }; + const auto height = [](const RECT& r) { return r.bottom - r.top; }; - ::SetWindowPos(hWnd, NULL, 0, 0, cx, cy, SWP_NOMOVE | flags); - } + // Pixels kept below the node path box. The dialog template reserves a small + // margin; fall back to 2 px when the template is already tighter than that. + // (RECT members are LONG: cast so std::max deduces a single type.) + const int bottomMargin = std::max(2, static_cast(m_lfInitialClientHeight) - static_cast(m_rcInitNodePath.bottom)); + const int gapTreeToPath = std::max(1, static_cast(m_rcInitNodePath.top) - static_cast(m_rcInitTree.bottom)); - for (int id : moveWindowIDs) - { - HWND hWnd = GetDlgItem(_hSelf, id); - ::GetWindowRect(hWnd, &rc); - ::MapWindowPoints(NULL, _hSelf, (LPPOINT)&rc, 2); + const int nodePathTop = nHeight - bottomMargin - height(m_rcInitNodePath); + const int treeHeight = std::max(20, nodePathTop - gapTreeToPath - static_cast(m_rcInitTree.top)); - ::SetWindowPos(hWnd, NULL, rc.left + addWidth, rc.top, 0, 0, SWP_NOSIZE | flags); - } + // search box stretches to the right, the search button slides along with it + ::SetWindowPos(::GetDlgItem(_hSelf, IDC_EDT_SEARCH), NULL, + m_rcInitSearch.left, m_rcInitSearch.top, + width(m_rcInitSearch) + addWidth, height(m_rcInitSearch), flags); - for (int id : resizeAndMoveWindowIDs) - { - HWND hWnd = GetDlgItem(_hSelf, id); + ::SetWindowPos(::GetDlgItem(_hSelf, IDC_BTN_SEARCH), NULL, + m_rcInitSearchBtn.left + addWidth, m_rcInitSearchBtn.top, + 0, 0, SWP_NOSIZE | flags); - ::GetWindowRect(hWnd, &rc); - int cx = rc.right - rc.left + addWidth; - int cy = rc.bottom - rc.top; - ::MapWindowPoints(NULL, _hSelf, (LPPOINT)&rc, 2); + // node path box: pinned to the bottom, full width + ::SetWindowPos(::GetDlgItem(_hSelf, IDC_EDT_NODEPATH), NULL, + m_rcInitNodePath.left, nodePathTop, + width(m_rcInitNodePath) + addWidth, height(m_rcInitNodePath), flags); - ::SetWindowPos(hWnd, NULL, rc.left, rc.top + addHeight, cx, cy, flags); - } + // tree: everything between the tool bar row and the node path box + ::SetWindowPos(::GetDlgItem(_hSelf, IDC_TREE), NULL, + m_rcInitTree.left, m_rcInitTree.top, + width(m_rcInitTree) + addWidth, treeHeight, flags); } void JsonViewDlg::ShowContextMenu(int x, int y) diff --git a/src/NppJsonViewer/JsonViewDlg.h b/src/NppJsonViewer/JsonViewDlg.h index 00cc599..236725e 100644 --- a/src/NppJsonViewer/JsonViewDlg.h +++ b/src/NppJsonViewer/JsonViewDlg.h @@ -71,6 +71,7 @@ class JsonViewDlg void SetIconAndTooltip(eButton ctrlType, const std::wstring& toolTip); void AdjustDocPanelSize(int nWidth, int nHeight); + void CaptureInitialControlRects(); // Context menu related functions void ShowContextMenu(int x, int y); @@ -116,8 +117,12 @@ class JsonViewDlg const bool& m_IsNppReady; // To handle doc panel resizing - LONG m_lfDeltaWidth = 0; - LONG m_lfDeltaHeight = 0; + // Template rects of the resizable controls, captured once at creation and + // used as the baseline for every later resize (see AdjustDocPanelSize). + RECT m_rcInitSearch = {}; + RECT m_rcInitSearchBtn = {}; + RECT m_rcInitTree = {}; + RECT m_rcInitNodePath = {}; LONG m_lfInitialClientWidth = 0; LONG m_lfInitialClientHeight = 0; RECT m_rcInitialWindowRect = {}; From b32e8d8bec4aa82d4d44ac13373a8490e8cfb3d3 Mon Sep 17 00:00:00 2001 From: leoshone Date: Fri, 4 Sep 2026 18:27:17 +0800 Subject: [PATCH 2/3] Defeat the windows.h min/max macros when calling (std::max) MSVC's windows.h defines max as a macro unless NOMINMAX is set, so std::max(...) failed to compile as std::(...). Parenthesize the call - the standard portable workaround - instead of touching the project's include settings. --- src/NppJsonViewer/JsonViewDlg.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/NppJsonViewer/JsonViewDlg.cpp b/src/NppJsonViewer/JsonViewDlg.cpp index 88c8f43..f2ef1c3 100644 --- a/src/NppJsonViewer/JsonViewDlg.cpp +++ b/src/NppJsonViewer/JsonViewDlg.cpp @@ -695,12 +695,13 @@ void JsonViewDlg::AdjustDocPanelSize(int nWidth, int nHeight) // Pixels kept below the node path box. The dialog template reserves a small // margin; fall back to 2 px when the template is already tighter than that. - // (RECT members are LONG: cast so std::max deduces a single type.) - const int bottomMargin = std::max(2, static_cast(m_lfInitialClientHeight) - static_cast(m_rcInitNodePath.bottom)); - const int gapTreeToPath = std::max(1, static_cast(m_rcInitNodePath.top) - static_cast(m_rcInitTree.bottom)); + // (RECT members are LONG: cast so (std::max) deduces a single type. + // The parens also defeat the windows.h min/max macros on MSVC.) + const int bottomMargin = (std::max)(2, static_cast(m_lfInitialClientHeight) - static_cast(m_rcInitNodePath.bottom)); + const int gapTreeToPath = (std::max)(1, static_cast(m_rcInitNodePath.top) - static_cast(m_rcInitTree.bottom)); const int nodePathTop = nHeight - bottomMargin - height(m_rcInitNodePath); - const int treeHeight = std::max(20, nodePathTop - gapTreeToPath - static_cast(m_rcInitTree.top)); + const int treeHeight = (std::max)(20, nodePathTop - gapTreeToPath - static_cast(m_rcInitTree.top)); // search box stretches to the right, the search button slides along with it ::SetWindowPos(::GetDlgItem(_hSelf, IDC_EDT_SEARCH), NULL, From be21c0cd66c734e711b6ece2f03d4c28c5021cb8 Mon Sep 17 00:00:00 2001 From: leoshone Date: Fri, 4 Sep 2026 18:30:53 +0800 Subject: [PATCH 3/3] Drop the unused addHeight local (MSVC W4 treats C4189 as an error) The tree height is now derived from the node path box position instead of the raw height delta, so the variable is gone. --- src/NppJsonViewer/JsonViewDlg.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/NppJsonViewer/JsonViewDlg.cpp b/src/NppJsonViewer/JsonViewDlg.cpp index f2ef1c3..7a7632e 100644 --- a/src/NppJsonViewer/JsonViewDlg.cpp +++ b/src/NppJsonViewer/JsonViewDlg.cpp @@ -685,8 +685,7 @@ void JsonViewDlg::AdjustDocPanelSize(int nWidth, int nHeight) // Every control is therefore positioned from the *current* client size // (template rect + unscaled delta), which also makes repeated resizes // idempotent instead of accumulated. - const int addWidth = nWidth - m_lfInitialClientWidth; - const int addHeight = nHeight - m_lfInitialClientHeight; + const int addWidth = nWidth - m_lfInitialClientWidth; const UINT flags = SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_SHOWWINDOW;