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
This commit is contained in:
xomx 2025-01-17 01:54:26 +01:00 committed by Don Ho
parent 6dfbc1f7e8
commit b09b89799b
3 changed files with 80 additions and 30 deletions

View File

@ -13,6 +13,7 @@
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <windows.h>
#include <algorithm> #include <algorithm>
#include <stdexcept> #include <stdexcept>
#include <shlwapi.h> #include <shlwapi.h>
@ -1903,3 +1904,56 @@ bool doesPathExist(const wchar_t* path, DWORD milliSec2wait, bool* isTimeoutReac
getFileAttributesExWithTimeout(path, &attributes, milliSec2wait, isTimeoutReached); getFileAttributesExWithTimeout(path, &attributes, milliSec2wait, isTimeoutReached);
return (attributes.dwFileAttributes != INVALID_FILE_ATTRIBUTES); 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<Param4InOut*>(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<LPARAM>(&param4InOut));
return param4InOut.isWndVisibleOut;
}

View File

@ -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 doesFileExist(const wchar_t* filePath, DWORD milliSec2wait = 0, bool* isTimeoutReached = nullptr);
bool doesDirectoryExist(const wchar_t* dirPath, 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); 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);

View File

@ -6910,40 +6910,32 @@ void NppParameters::feedDockingManager(TiXmlNode *node)
int w = FWI_PANEL_WH_DEFAULT; int w = FWI_PANEL_WH_DEFAULT;
int h = FWI_PANEL_WH_DEFAULT; int h = FWI_PANEL_WH_DEFAULT;
bool bInputDataOk = false;
if (floatElement->Attribute(L"x", &x)) if (floatElement->Attribute(L"x", &x))
{ {
if ((x > (maxMonitorSize.cx - 1)) || (x < 0)) if (floatElement->Attribute(L"y", &y))
x = 0; // invalid, reset {
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)) // reset to adjusted factory defaults
y = 0; // invalid, reset // (and the panel will automatically be on the current primary monitor due to the x,y == 0,0)
} x = 0;
if (floatElement->Attribute(L"width", &w)) y = 0;
{ w = _nppGUI._dockingData._minFloatingPanelSize.cx;
if (w > maxMonitorSize.cx) h = _nppGUI._dockingData._minFloatingPanelSize.cy + FWI_PANEL_WH_DEFAULT;
{
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
}
} }
_nppGUI._dockingData._floatingWindowInfo.push_back(FloatingWindowInfo(cont, x, y, w, h)); _nppGUI._dockingData._floatingWindowInfo.push_back(FloatingWindowInfo(cont, x, y, w, h));
} }
} }