From b09b89799bbf969c83da35f906bb0bcc867e5a3d Mon Sep 17 00:00:00 2001 From: xomx Date: Fri, 17 Jan 2025 01:54:26 +0100 Subject: [PATCH] Fix floating panels position resetting in multimon extended mode This fixes a regression caused by PR #15236 (Fix for the "lost" panels problem). As the Virtual Screen in the extended multi-monitor mode can start not a the point 0,0 (as the primary monitor does) but also at some negative coordinates, we have to deal with it. The MS Virtual Screen concept ref: https://learn.microsoft.com/en-us/windows/win32/gdi/the-virtual-screen Fix #15498 , fix #16077, close #16079 --- PowerEditor/src/MISC/Common/Common.cpp | 54 ++++++++++++++++++++++++++ PowerEditor/src/MISC/Common/Common.h | 6 ++- PowerEditor/src/Parameters.cpp | 50 ++++++++++-------------- 3 files changed, 80 insertions(+), 30 deletions(-) diff --git a/PowerEditor/src/MISC/Common/Common.cpp b/PowerEditor/src/MISC/Common/Common.cpp index 625249148..9ab45d9e3 100644 --- a/PowerEditor/src/MISC/Common/Common.cpp +++ b/PowerEditor/src/MISC/Common/Common.cpp @@ -13,6 +13,7 @@ // // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#include #include #include #include @@ -1903,3 +1904,56 @@ bool doesPathExist(const wchar_t* path, DWORD milliSec2wait, bool* isTimeoutReac getFileAttributesExWithTimeout(path, &attributes, milliSec2wait, isTimeoutReached); return (attributes.dwFileAttributes != INVALID_FILE_ATTRIBUTES); } + +#if defined(__GNUC__) +#define LAMBDA_STDCALL __attribute__((__stdcall__)) +#else +#define LAMBDA_STDCALL +#endif + +// check if the window rectangle intersects with any currently active monitor's working area +// (this func handles also possible extended monitors mode aka the MS Virtual Screen) +bool isWindowVisibleOnAnyMonitor(const RECT& rectWndIn) +{ + struct Param4InOut + { + const RECT& rectWndIn; + bool isWndVisibleOut = false; + }; + + // callback func to check for intersection with each existing monitor + auto callback = []([[maybe_unused]] HMONITOR hMon, [[maybe_unused]] HDC hdc, LPRECT lprcMon, LPARAM lpInOut) -> BOOL LAMBDA_STDCALL + { + Param4InOut* paramInOut = reinterpret_cast(lpInOut); + RECT rectIntersection{}; + if (::IntersectRect(&rectIntersection, &(paramInOut->rectWndIn), lprcMon)) + { + paramInOut->isWndVisibleOut = true; // the window is at least partially visible on this monitor + return FALSE; // ok, stop the enumeration + } + return TRUE; // continue enumeration as no intersection yet + }; + + // get scaled Virtual Screen size (scaled coordinates are saved by the Notepad++ into config.xml) + // - for unscaled, one has to 1st set the SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) & then use GetSystemMetricsForDpi with 96 + // - for getting the VS RECT, we cannot use here the SystemParametersInfo with SPI_GETWORKAREA! + // (while the SPI_SETWORKAREA is working with the VS coordinates the SPI_GETWORKAREA not...) + RECT rectVirtualScreen{ ::GetSystemMetrics(SM_XVIRTUALSCREEN), ::GetSystemMetrics(SM_YVIRTUALSCREEN), + ::GetSystemMetrics(SM_CXVIRTUALSCREEN), ::GetSystemMetrics(SM_CYVIRTUALSCREEN) }; + + // 1) Before checking for intersections with individual monitors, we verify if the window's rectangle + // is within the MS Virtual Screen area. If it is outside, this func exits with false early, + // as the window in question cannot be visible on any individual monitor present. + RECT rectIntersection{}; + if (!::IntersectRect(&rectIntersection, &rectWndIn, &rectVirtualScreen)) + { + // the window in question is completely outside the overall Virtual Screen bounds + return false; + } + + // 2) Using the EnumDisplayMonitors WINAPI to check each present monitor's visible area, we ensure that we are only looking + // at monitors that are part of the Virtual Screen but not at Virtual Space coordinates where is NOT a monitor present. + Param4InOut param4InOut{ rectWndIn, false }; + ::EnumDisplayMonitors(NULL, &rectVirtualScreen, callback, reinterpret_cast(¶m4InOut)); + return param4InOut.isWndVisibleOut; +} diff --git a/PowerEditor/src/MISC/Common/Common.h b/PowerEditor/src/MISC/Common/Common.h index 3d3548030..9ad19654c 100644 --- a/PowerEditor/src/MISC/Common/Common.h +++ b/PowerEditor/src/MISC/Common/Common.h @@ -287,4 +287,8 @@ BOOL getFileAttributesExWithTimeout(const wchar_t* filePath, WIN32_FILE_ATTRIBUT bool doesFileExist(const wchar_t* filePath, DWORD milliSec2wait = 0, bool* isTimeoutReached = nullptr); bool doesDirectoryExist(const wchar_t* dirPath, DWORD milliSec2wait = 0, bool* isTimeoutReached = nullptr); -bool doesPathExist(const wchar_t* path, DWORD milliSec2wait = 0, bool* isTimeoutReached = nullptr); \ No newline at end of file +bool doesPathExist(const wchar_t* path, DWORD milliSec2wait = 0, bool* isTimeoutReached = nullptr); + + +// check if the window rectangle intersects with any currently active monitor's working area +bool isWindowVisibleOnAnyMonitor(const RECT& rectWndIn); diff --git a/PowerEditor/src/Parameters.cpp b/PowerEditor/src/Parameters.cpp index 91bed4c82..aa32cdce3 100644 --- a/PowerEditor/src/Parameters.cpp +++ b/PowerEditor/src/Parameters.cpp @@ -6910,40 +6910,32 @@ void NppParameters::feedDockingManager(TiXmlNode *node) int w = FWI_PANEL_WH_DEFAULT; int h = FWI_PANEL_WH_DEFAULT; + bool bInputDataOk = false; if (floatElement->Attribute(L"x", &x)) { - if ((x > (maxMonitorSize.cx - 1)) || (x < 0)) - x = 0; // invalid, reset + if (floatElement->Attribute(L"y", &y)) + { + if (floatElement->Attribute(L"width", &w)) + { + if (floatElement->Attribute(L"height", &h)) + { + RECT rect{ x,y,w,h }; + bInputDataOk = isWindowVisibleOnAnyMonitor(rect); + } + } + } } - if (floatElement->Attribute(L"y", &y)) + + if (!bInputDataOk) { - if ((y > (maxMonitorSize.cy - 1)) || (y < 0)) - y = 0; // invalid, reset - } - if (floatElement->Attribute(L"width", &w)) - { - if (w > maxMonitorSize.cx) - { - w = maxMonitorSize.cx; // invalid, reset - } - else - { - if (w < _nppGUI._dockingData._minFloatingPanelSize.cx) - w = _nppGUI._dockingData._minFloatingPanelSize.cx; // invalid, reset - } - } - if (floatElement->Attribute(L"height", &h)) - { - if (h > maxMonitorSize.cy) - { - h = maxMonitorSize.cy; // invalid, reset - } - else - { - if (h < _nppGUI._dockingData._minFloatingPanelSize.cy) - h = _nppGUI._dockingData._minFloatingPanelSize.cy; // invalid, reset - } + // reset to adjusted factory defaults + // (and the panel will automatically be on the current primary monitor due to the x,y == 0,0) + x = 0; + y = 0; + w = _nppGUI._dockingData._minFloatingPanelSize.cx; + h = _nppGUI._dockingData._minFloatingPanelSize.cy + FWI_PANEL_WH_DEFAULT; } + _nppGUI._dockingData._floatingWindowInfo.push_back(FloatingWindowInfo(cont, x, y, w, h)); } }