Merge branch 'keep-on-same-display'

Keep window on the same display when its client size is changed.

See #23533.
This commit is contained in:
Vadim Zeitlin 2023-05-10 22:45:02 +01:00
commit 5f5e5c27dd
10 changed files with 161 additions and 77 deletions

View file

@ -88,7 +88,7 @@ jobs:
git fetch --depth=1 origin master
if git diff origin/master \
':**.h' ':**.cpp' \
| grep -E '^\+.*(wxOVERRIDE|wxNOEXCEPT|[^"_@]NULL[^"_])'; then
| grep -E '^\+.*(wxOVERRIDE|wxNOEXCEPT|[^"_@A-Za-z0-9]NULL[^"_A-Za-z0-9])'; then
echo "::error ::Please use C++11 equivalents of the deprecated macros in the new code."
exit 1
fi

View file

@ -67,6 +67,10 @@ public:
// it doesn't belong to any display
static int GetFromPoint(const wxPoint& pt);
// find the display which has the biggest intersection with the given
// rectangle or wxNOT_FOUND if the rectangle doesn't intersect any display
static int GetFromRect(const wxRect& rect);
// find the display where the given window lies, return wxNOT_FOUND if it
// is not shown at all
static int GetFromWindow(const wxWindow *window);

View file

@ -848,11 +848,20 @@ public:
// centre this rectangle in the given (usually, but not necessarily,
// larger) one
void MakeCenteredIn(const wxRect& r, int dir = wxBOTH)
{
if ( dir & wxHORIZONTAL )
x = r.x + (r.width - width)/2;
if ( dir & wxVERTICAL )
y = r.y + (r.height - height)/2;
}
// same as above but returns the new rectangle instead of modifying this one
wxRect CentreIn(const wxRect& r, int dir = wxBOTH) const
{
return wxRect(dir & wxHORIZONTAL ? r.x + (r.width - width)/2 : x,
dir & wxVERTICAL ? r.y + (r.height - height)/2 : y,
width, height);
wxRect rect(*this);
rect.MakeCenteredIn(r, dir);
return rect;
}
wxRect CenterIn(const wxRect& r, int dir = wxBOTH) const

View file

@ -68,6 +68,10 @@ public:
// return the display for the given point or wxNOT_FOUND
virtual int GetFromPoint(const wxPoint& pt) = 0;
// return the display with biggest intersection with the given rectangle or
// wxNOT_FOUND
virtual int GetFromRect(const wxRect& rect);
// return the display for the given window or wxNOT_FOUND
//
// the window pointer must not be null (i.e. caller should check it)

View file

@ -94,6 +94,24 @@ public:
*/
static int GetFromPoint(const wxPoint& pt);
/**
Returns the index of the display with biggest intersection with the
given rectangle or @c wxNOT_FOUND if the rectangle doesn't intersect
any display.
Note that usually the returned display will be the same display which
contains the center of the rectangle, but this is not always the case,
as rectangle might be partly visible even if its center is off screen,
and in this case GetFromPoint() would returns @c wxNOT_FOUND, but this
function would return a valid display.
@param rect
The rectangle to check.
@since 3.3.0
*/
static int GetFromRect(const wxRect& rect);
/**
Returns the index of the display on which the given window lies.

View file

@ -307,6 +307,8 @@ public:
centred in both directions but if @a dir includes only @c wxVERTICAL or
only @c wxHORIZONTAL, then it is only centered in this direction while
the other component of its position remains unchanged.
@see MakeCenteredIn()
*/
wxRect CentreIn(const wxRect& r, int dir = wxBOTH) const;
wxRect CenterIn(const wxRect& r, int dir = wxBOTH) const;
@ -475,6 +477,20 @@ public:
*/
bool IsEmpty() const;
/**
Center this rectangle inside the given rectangle @a r.
By default, rectangle is centred in both directions but if @a dir
includes only @c wxVERTICAL or only @c wxHORIZONTAL, then it is only
centered in this direction while the other component of its position
remains unchanged.
@see CenterIn()
@since 3.3.0
*/
void MakeCenteredIn(const wxRect& r, int dir = wxBOTH);
///@{
/**
Moves the rectangle by the specified offset. If @a dx is positive, the

View file

@ -106,6 +106,11 @@ wxDisplay::wxDisplay(const wxWindow* window)
return Factory().GetFromPoint(pt);
}
/* static */ int wxDisplay::GetFromRect(const wxRect& rect)
{
return Factory().GetFromRect(rect);
}
/* static */ int wxDisplay::GetFromWindow(const wxWindow *window)
{
wxCHECK_MSG( window, wxNOT_FOUND, wxT("invalid window") );
@ -241,6 +246,34 @@ wxDisplayImpl* wxDisplayFactory::GetPrimaryDisplay()
return nullptr;
}
int wxDisplayFactory::GetFromRect(const wxRect& r)
{
int display = wxNOT_FOUND;
// Find the display with the biggest intersection with the given window.
//
// Note that just using GetFromPoint() with the center of the rectangle is
// not correct in general, as the center might lie outside of the visible
// area, while the rectangle itself could be partially visible. Moreover,
// in some exotic (L-shaped) display layouts, the center might not actually
// be on the display containing the biggest part of the rectangle even if
// it is visible.
int biggestOverlapArea = 0;
const unsigned count = GetCount();
for ( unsigned n = 0; n < count; ++n )
{
const auto overlap = GetDisplay(n)->GetGeometry().Intersect(r);
const int overlapArea = overlap.width * overlap.height;
if ( overlapArea > biggestOverlapArea )
{
biggestOverlapArea = overlapArea;
display = n;
}
}
return display;
}
int wxDisplayFactory::GetFromWindow(const wxWindow *window)
{
wxCHECK_MSG( window, wxNOT_FOUND, "window can't be null" );
@ -252,9 +285,7 @@ int wxDisplayFactory::GetFromWindow(const wxWindow *window)
if ( !window->GetHandle() )
return wxNOT_FOUND;
// consider that the window belongs to the display containing its centre
const wxRect r(window->GetScreenRect());
return GetFromPoint(wxPoint(r.x + r.width/2, r.y + r.height/2));
return GetFromRect(window->GetScreenRect());
}
// ============================================================================

View file

@ -180,6 +180,7 @@ public:
virtual wxDisplayImpl *CreateDisplay(unsigned n) override;
virtual unsigned GetCount() override { return unsigned(m_displays.size()); }
virtual int GetFromPoint(const wxPoint& pt) override;
virtual int GetFromRect(const wxRect& rect) override;
virtual int GetFromWindow(const wxWindow *window) override;
void InvalidateCache() override
@ -608,7 +609,7 @@ wxDisplayImpl *wxDisplayFactoryMSW::CreateDisplay(unsigned n)
return new wxDisplayMSW(n, m_displays[n]);
}
// helper for GetFromPoint() and GetFromWindow()
// helper for all GetFromXXX() functions
int wxDisplayFactoryMSW::FindDisplayFromHMONITOR(HMONITOR hmon) const
{
if ( hmon )
@ -634,17 +635,21 @@ int wxDisplayFactoryMSW::GetFromPoint(const wxPoint& pt)
MONITOR_DEFAULTTONULL));
}
int wxDisplayFactoryMSW::GetFromRect(const wxRect& rect)
{
RECT rc;
wxCopyRectToRECT(rect, rc);
return FindDisplayFromHMONITOR(::MonitorFromRect(&rc,
MONITOR_DEFAULTTONULL));
}
int wxDisplayFactoryMSW::GetFromWindow(const wxWindow *window)
{
#ifdef __WXMSW__
return FindDisplayFromHMONITOR(::MonitorFromWindow(GetHwndOf(window),
MONITOR_DEFAULTTONULL));
#else
const wxSize halfsize = window->GetSize() / 2;
wxPoint pt = window->GetScreenPosition();
pt.x += halfsize.x;
pt.y += halfsize.y;
return GetFromPoint(pt);
return GetFromRect(window->GetScreenRect());
#endif
}

View file

@ -2277,6 +2277,10 @@ void wxWindowMSW::DoSetClientSize(int width, int height)
const int widthWin = rectWin.right - rectWin.left,
heightWin = rectWin.bottom - rectWin.top;
wxRect proposedRect(rectWin.left, rectWin.top,
width + widthWin - rectClient.right,
height + heightWin - rectClient.bottom);
if ( IsTopLevel() )
{
// toplevel window's coordinates are mirrored if the TLW is a child of another
@ -2288,8 +2292,34 @@ void wxWindowMSW::DoSetClientSize(int width, int height)
if ( tlwParent && (::GetWindowLong(tlwParent, GWL_EXSTYLE) & WS_EX_LAYOUTRTL) != 0 )
{
const int diffWidth = width - (rectClient.right - rectClient.left);
rectWin.left -= diffWidth;
rectWin.right -= diffWidth;
proposedRect.x -= diffWidth;
}
// Another complication with TLWs is that changing their size may
// change the monitor they are on, even without changing their
// position. This is unexpected and especially so if the new
// monitor uses a different DPI scaling and so moving the window to
// it changes its size -- which may result in an infinite recursion
// if the window calls SetClientSize() when DPI changes.
//
// So ensure that the window stays on the same monitor, adjusting
// its position if necessary.
const int currentDisplay =
wxDisplay::GetFromWindow(static_cast<const wxWindow*>(this));
if ( currentDisplay != wxNOT_FOUND &&
wxDisplay::GetFromRect(proposedRect) != currentDisplay )
{
// It's not obvious how to determine the smallest modification
// of the window position sufficient for keeping it on the
// current display, so keep things simple and preserve the
// position of its center horizontally.
const wxRect currentRect = wxRectFromRECT(rectWin);
proposedRect.MakeCenteredIn(currentRect, wxHORIZONTAL);
if ( wxDisplay::GetFromRect(proposedRect) != currentDisplay )
{
// And if this isn't sufficient, then vertically too.
proposedRect.MakeCenteredIn(currentRect, wxVERTICAL);
}
}
}
else
@ -2300,6 +2330,8 @@ void wxWindowMSW::DoSetClientSize(int width, int height)
if ( parent )
{
::ScreenToClient(GetHwndOf(parent), (POINT *)&rectWin);
proposedRect.x = rectWin.left;
proposedRect.y = rectWin.top;
}
}
@ -2307,9 +2339,9 @@ void wxWindowMSW::DoSetClientSize(int width, int height)
// and not defer it here as otherwise the value returned by
// GetClient/WindowRect() wouldn't change as the window wouldn't be
// really resized
MSWMoveWindowToAnyPosition(GetHwnd(), rectWin.left, rectWin.top,
width + widthWin - rectClient.right,
height + heightWin - rectClient.bottom, true);
MSWMoveWindowToAnyPosition(GetHwnd(), proposedRect.x, proposedRect.y,
proposedRect.width, proposedRect.height,
true);
}
}

View file

@ -22,64 +22,38 @@
#include "asserthelper.h"
// ----------------------------------------------------------------------------
// test class
// tests
// ----------------------------------------------------------------------------
class RectTestCase : public CppUnit::TestCase
{
public:
RectTestCase() { }
private:
CPPUNIT_TEST_SUITE( RectTestCase );
CPPUNIT_TEST( CentreIn );
CPPUNIT_TEST( InflateDeflate );
CPPUNIT_TEST( Operators );
CPPUNIT_TEST( Union );
CPPUNIT_TEST_SUITE_END();
void CentreIn();
void InflateDeflate();
void Operators();
void Union();
wxDECLARE_NO_COPY_CLASS(RectTestCase);
};
// register in the unnamed registry so that these tests are run by default
CPPUNIT_TEST_SUITE_REGISTRATION( RectTestCase );
// also include in its own registry so that these tests can be run alone
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( RectTestCase, "RectTestCase" );
void RectTestCase::CentreIn()
TEST_CASE("wxRect::CentreIn", "[rect]")
{
typedef wxRect R;
CPPUNIT_ASSERT_EQUAL( R(45, 45, 10, 10),
R(0, 0, 10, 10).CentreIn(R(0, 0, 100, 100)));
CHECK( R(0, 0, 10, 10).CentreIn(R(0, 0, 100, 100)) == R(45, 45, 10, 10) );
CHECK( R(0, 0, 20, 20).CentreIn(R(0, 0, 10, 10)) == R(-5, -5, 20, 20) );
CPPUNIT_ASSERT_EQUAL( R(-5, -5, 20, 20),
R(0, 0, 20, 20).CentreIn(R(0, 0, 10, 10)));
R r(-10, -10, 20, 20);
r.MakeCenteredIn(R(0, 0, 100, 100), wxHORIZONTAL);
CHECK( r == R(40, -10, 20, 20) );
}
void RectTestCase::InflateDeflate()
TEST_CASE("wxRect::InflateDeflate", "[rect]")
{
// This is the rectangle from the example in the documentation of wxRect::Inflate().
const wxRect r1(10, 10, 20, 40);
CPPUNIT_ASSERT(r1.Inflate( 10, 10)==wxRect( 0, 0, 40, 60));
CPPUNIT_ASSERT(r1.Inflate( 20, 30)==wxRect(-10, -20, 60, 100));
CPPUNIT_ASSERT(r1.Inflate(-10, -10)==wxRect( 20, 20, 0, 20));
CPPUNIT_ASSERT(r1.Inflate(-15, -15)==wxRect( 20, 25, 0, 10));
CHECK(r1.Inflate( 10, 10)==wxRect( 0, 0, 40, 60));
CHECK(r1.Inflate( 20, 30)==wxRect(-10, -20, 60, 100));
CHECK(r1.Inflate(-10, -10)==wxRect( 20, 20, 0, 20));
CHECK(r1.Inflate(-15, -15)==wxRect( 20, 25, 0, 10));
CPPUNIT_ASSERT(r1.Inflate( 10, 10)==r1.Deflate(-10, -10));
CPPUNIT_ASSERT(r1.Inflate( 20, 30)==r1.Deflate(-20, -30));
CPPUNIT_ASSERT(r1.Inflate(-10, -10)==r1.Deflate( 10, 10));
CPPUNIT_ASSERT(r1.Inflate(-15, -15)==r1.Deflate( 15, 15));
CHECK(r1.Inflate( 10, 10)==r1.Deflate(-10, -10));
CHECK(r1.Inflate( 20, 30)==r1.Deflate(-20, -30));
CHECK(r1.Inflate(-10, -10)==r1.Deflate( 10, 10));
CHECK(r1.Inflate(-15, -15)==r1.Deflate( 15, 15));
}
void RectTestCase::Operators()
TEST_CASE("wxRect::Operators", "[rect]")
{
// test + operator which works like Union but does not ignore empty rectangles
static const struct RectData
@ -106,25 +80,20 @@ void RectTestCase::Operators()
{
const RectData& data = s_rects[n];
CPPUNIT_ASSERT(
( data.GetFirst() + data.GetSecond() ) == data.GetResult()
);
CPPUNIT_ASSERT(
( data.GetSecond() + data.GetFirst() ) == data.GetResult()
);
CHECK( (data.GetFirst() + data.GetSecond()) == data.GetResult() );
CHECK( (data.GetSecond() + data.GetFirst()) == data.GetResult() );
}
// test operator*() which returns the intersection of two rectangles
wxRect r1 = wxRect(0, 2, 3, 4);
wxRect r2 = wxRect(1, 2, 7, 8);
r1 *= r2;
CPPUNIT_ASSERT(wxRect(1, 2, 2, 4) == r1);
CHECK(wxRect(1, 2, 2, 4) == r1);
CPPUNIT_ASSERT( (r1 * wxRect()).IsEmpty() );
CHECK( (r1 * wxRect()).IsEmpty() );
}
void RectTestCase::Union()
TEST_CASE("wxRect::Union", "[rect]")
{
static const struct RectData
{
@ -150,12 +119,8 @@ void RectTestCase::Union()
{
const RectData& data = s_rects[n];
CPPUNIT_ASSERT(
data.GetFirst().Union(data.GetSecond()) == data.GetResult()
);
CHECK( data.GetFirst().Union(data.GetSecond()) == data.GetResult() );
CPPUNIT_ASSERT(
data.GetSecond().Union(data.GetFirst()) == data.GetResult()
);
CHECK( data.GetSecond().Union(data.GetFirst()) == data.GetResult() );
}
}