diff --git a/.github/workflows/code_checks.yml b/.github/workflows/code_checks.yml index d3ce8b644c..c11fd2f583 100644 --- a/.github/workflows/code_checks.yml +++ b/.github/workflows/code_checks.yml @@ -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 diff --git a/include/wx/display.h b/include/wx/display.h index 3bcb0274c4..4589279804 100644 --- a/include/wx/display.h +++ b/include/wx/display.h @@ -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); diff --git a/include/wx/gdicmn.h b/include/wx/gdicmn.h index 46de754f82..236ba350ee 100644 --- a/include/wx/gdicmn.h +++ b/include/wx/gdicmn.h @@ -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 diff --git a/include/wx/private/display.h b/include/wx/private/display.h index 29d73003b9..8950fa3e80 100644 --- a/include/wx/private/display.h +++ b/include/wx/private/display.h @@ -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) diff --git a/interface/wx/display.h b/interface/wx/display.h index 202dc3a1d6..4a9ac5ca65 100644 --- a/interface/wx/display.h +++ b/interface/wx/display.h @@ -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. diff --git a/interface/wx/gdicmn.h b/interface/wx/gdicmn.h index 383a0f6307..822c338a67 100644 --- a/interface/wx/gdicmn.h +++ b/interface/wx/gdicmn.h @@ -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 diff --git a/src/common/dpycmn.cpp b/src/common/dpycmn.cpp index 4a9b97871e..c119aee923 100644 --- a/src/common/dpycmn.cpp +++ b/src/common/dpycmn.cpp @@ -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()); } // ============================================================================ diff --git a/src/msw/display.cpp b/src/msw/display.cpp index 22cd36756f..a7ebef571f 100644 --- a/src/msw/display.cpp +++ b/src/msw/display.cpp @@ -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 } diff --git a/src/msw/window.cpp b/src/msw/window.cpp index 0b6f5b098b..4a5a9979bf 100644 --- a/src/msw/window.cpp +++ b/src/msw/window.cpp @@ -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(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); } } diff --git a/tests/geometry/rect.cpp b/tests/geometry/rect.cpp index 6404d6763f..bbb473d024 100644 --- a/tests/geometry/rect.cpp +++ b/tests/geometry/rect.cpp @@ -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() ); } }