Save DPI in wxPersistentTLW to handle DPI change correctly
Add support at wxPersistentWindow level for saving the DPI at which the
window coordinates were saved to allow rescaling the values by the ratio
of this DPI to the current one if the DPI has changed between saving and
restoring.
This allows keeping the same proportional size of the window after DPI
change on the platforms not using DPI-independent pixels (such as wxMSW)
too by just using the new {Save,Restore}Coord() functions instead of
{Save,Restore}Value() for any quantities that need to be scaled.
Do this for wxPersistentTLW to update the window size to remain
proportionally the same after DPI change.
Factor out SavePersistenceTestDVC() in wxPersistDVC unit test Make the code creating a wxDVC and saving it available in a reusable function to allow calling it from another place in the upcoming commit. No real changes, this is a pure refactoring.
Account for DPI in wxPersistentDataViewCtrl too
Just use {Save,Restore}Coord() to restore the columns widths instead of
{Save,Restore}Value() to rescale the saved values if necessary.
Add a unit test checking that this works as expected.
Fix restoring wrong last split position in wxPersistentSplitter This was broken since f24b3d5483 (Save last wxSplitterWindow position before it was unsplit, 2024-02-12) which accidentally used the wrong lastSplitPos component when restoring.
Account for DPI in wxPersistentSplitter too
Rescale the saved splitter position if the DPI has changed when
restoring it by using {Save,Restore}Coord().
Fix updating saved splitter position on DPI change While the current and minimum positions were updated, the saved last split position was not, so unsplitting the splitter, changing the DPI and splitting it again wouldn't use the correct position for the new DPI. Also rescale m_requestedSashPosition if it's valid even if it's not clear whether this can happen or not.
Fix checkbox drawing for empty label in wxMSW dark mode For a checkbox with an empty label, do not draw the focus rectangle with size 0x0. Fixes #26778. Closes #27003.
Fix header button dark mode theme in wxMSW Do not use the theme Explorer::HEADER because the rendering loses this theme when switching from dark mode to light. Instead, specify separate themes for light and dark mode, choosing the best dark mode theme available. Do this the same way in wxRendererXP and wxMSWHeaderCtrl. See #26790. Closes #27005.
Provide wxrc as an imported target This lets users use the target wx::wxrc directly as an executable in add_custom_command(), instead of needing to guess its path. Closes #27008.
Fix wxStaticBox for dark mode with double buffering Set custom painting mode for dark mode in MSWSetDarkOrLightMode() because custom painting mode is not set in Create() when double buffering. Fixes #27004. Closes #27007.
Merge branch 'persist-dpi' Handle restoring settings saved by persistent objects after DPI change. Closes #18752. See #27006.
| ... | ... | @@ -27,6 +27,7 @@ if(wxUSE_XRC) |
| 27 | 27 | |
| 28 | 28 | wx_get_install_dir(runtime)
|
| 29 | 29 | wx_install(TARGETS wxrc
|
| 30 | + EXPORT wxWidgetsTargets
|
|
| 30 | 31 | RUNTIME DESTINATION "${runtime_dir}"
|
| 31 | 32 | BUNDLE DESTINATION "${runtime_dir}"
|
| 32 | 33 | )
|
| ... | ... | @@ -137,6 +137,13 @@ support for your custom class @c MyWidget you just need to: |
| 137 | 137 | -# Define wxCreatePersistentObject() overload taking `MyWidget*` and
|
| 138 | 138 | returning a new `MyPersistentWidget` object.
|
| 139 | 139 | |
| 140 | +If any of the saved values is a size or a position in pixels, use
|
|
| 141 | +wxPersistentWindow::SaveCoord() and wxPersistentWindow::RestoreCoord()
|
|
| 142 | +instead of the generic functions: they also store the DPI at which the value
|
|
| 143 | +was saved and rescale it if it changes before the value is restored, which is
|
|
| 144 | +important for the applications which may be used with different DPI values,
|
|
| 145 | +e.g. when a display is replaced or its scaling factor is changed.
|
|
| 146 | + |
|
| 140 | 147 | If you want to add persistence support for a class not deriving from wxWindow,
|
| 141 | 148 | you need to derive `MyPersistentWidget` directly from wxPersistentObject and
|
| 142 | 149 | so implement its pure virtual wxPersistentObject::GetName() method too.
|
| ... | ... | @@ -68,6 +68,10 @@ public: |
| 68 | 68 | !store.SaveValue(wxPERSIST_TLW_MAX_Y, pt.y) )
|
| 69 | 69 | return false;
|
| 70 | 70 | |
| 71 | + // Finally save the DPI at which all the values above were taken.
|
|
| 72 | + if ( !SaveDPI(store) )
|
|
| 73 | + return false;
|
|
| 74 | + |
|
| 71 | 75 | // We don't currently save the minimized window position, it doesn't
|
| 72 | 76 | // seem useful for anything and is probably just a left over from
|
| 73 | 77 | // Windows 3.1 days, when icons were positioned on the desktop instead
|
| ... | ... | @@ -115,6 +119,8 @@ public: |
| 115 | 119 | |
| 116 | 120 | m_offScreen = store.RestoreValue(wxPERSIST_TLW_OFF_SCREEN, &tmp) && tmp;
|
| 117 | 121 | |
| 122 | + RestoreDPI(store);
|
|
| 123 | + |
|
| 118 | 124 | return true;
|
| 119 | 125 | }
|
| 120 | 126 | |
| ... | ... | @@ -160,11 +166,17 @@ public: |
| 160 | 166 | |
| 161 | 167 | m_offScreen = !IsFullyOnScreen(GetNormalRect());
|
| 162 | 168 | |
| 169 | + SetDPIFrom(tlw);
|
|
| 170 | + |
|
| 163 | 171 | return true;
|
| 164 | 172 | }
|
| 165 | 173 | |
| 166 | 174 | virtual bool ApplyTo(wxTopLevelWindow* tlw) override
|
| 167 | 175 | {
|
| 176 | + // If the DPI has changed since the geometry was saved, the window size
|
|
| 177 | + // needs to be adjusted to keep its contents at the same apparent size.
|
|
| 178 | + RescaleIfNeeded();
|
|
| 179 | + |
|
| 168 | 180 | // The saved geometry may not make sense any more, e.g. if the window
|
| 169 | 181 | // had been shown on a monitor which is not connected any longer, and
|
| 170 | 182 | // restoring it as is would leave the window invisible, so adjust it if
|
| ... | ... | @@ -213,6 +225,31 @@ public: |
| 213 | 225 | }
|
| 214 | 226 | |
| 215 | 227 | private:
|
| 228 | + // Rescale the normal window size if the DPI of the display where it is
|
|
| 229 | + // going to be shown is different from the one at which it was saved.
|
|
| 230 | + void RescaleIfNeeded()
|
|
| 231 | + {
|
|
| 232 | + wxRect rect = GetNormalRect();
|
|
| 233 | + |
|
| 234 | + // Note that we can't use the current DPI of the window here, as it is
|
|
| 235 | + // going to be moved to the display containing its saved position,
|
|
| 236 | + // which may well use a different DPI.
|
|
| 237 | + const int n = wxDisplay::GetFromRect(rect);
|
|
| 238 | + const wxSize dpi = n == wxNOT_FOUND
|
|
| 239 | + ? wxDisplay().GetPPI()
|
|
| 240 | + : wxDisplay(static_cast<unsigned>(n)).GetPPI();
|
|
| 241 | + |
|
| 242 | + const wxSize size = RescaleSize(rect.GetSize(), dpi);
|
|
| 243 | + if ( size == rect.GetSize() )
|
|
| 244 | + return;
|
|
| 245 | + |
|
| 246 | + // Note that the position is deliberately not changed here, see comment
|
|
| 247 | + // before RescaleSize().
|
|
| 248 | + rect.SetSize(size);
|
|
| 249 | + |
|
| 250 | + wxCopyRectToRECT(rect, m_placement.rcNormalPosition);
|
|
| 251 | + }
|
|
| 252 | + |
|
| 216 | 253 | // Return the normal, i.e. neither maximized nor iconized, window rect.
|
| 217 | 254 | wxRect GetNormalRect() const
|
| 218 | 255 | {
|
| ... | ... | @@ -78,6 +78,8 @@ public: |
| 78 | 78 | protected:
|
| 79 | 79 | virtual wxWindowList GetCompositeWindowParts() const override;
|
| 80 | 80 | |
| 81 | + virtual void MSWSetDarkOrLightMode(SetMode setmode) override;
|
|
| 82 | + |
|
| 81 | 83 | // return the region with all the windows inside this static box excluded
|
| 82 | 84 | WXHRGN MSWGetRegionWithoutChildren();
|
| 83 | 85 |
| ... | ... | @@ -18,6 +18,20 @@ |
| 18 | 18 | |
| 19 | 19 | class wxPersistentObject;
|
| 20 | 20 | |
| 21 | +// ----------------------------------------------------------------------------
|
|
| 22 | +// constants
|
|
| 23 | +// ----------------------------------------------------------------------------
|
|
| 24 | + |
|
| 25 | +// Name of the entry storing the DPI at which the other, pixel-valued, entries
|
|
| 26 | +// of the same object were saved, used to rescale them if the DPI changes
|
|
| 27 | +// between saving and restoring them.
|
|
| 28 | +//
|
|
| 29 | +// Note that the absence of this entry doesn't mean that the values were saved
|
|
| 30 | +// at the standard DPI, but that they were saved by an older version of the
|
|
| 31 | +// library not storing the DPI at all, in which case they can't be rescaled and
|
|
| 32 | +// must be used as is.
|
|
| 33 | +#define wxPERSIST_DPI "DPI"
|
|
| 34 | + |
|
| 21 | 35 | // ----------------------------------------------------------------------------
|
| 22 | 36 | // global functions
|
| 23 | 37 | // ----------------------------------------------------------------------------
|
| ... | ... | @@ -73,7 +73,7 @@ public: |
| 73 | 73 | // entire control width.
|
| 74 | 74 | const int width = column->WXGetSpecifiedWidth();
|
| 75 | 75 | if ( width > 0 )
|
| 76 | - SaveValue(columnPrefix + wxASCII_STR(wxPERSIST_DVC_WIDTH), width);
|
|
| 76 | + SaveCoord(columnPrefix + wxASCII_STR(wxPERSIST_DVC_WIDTH), width);
|
|
| 77 | 77 | |
| 78 | 78 | // Check if this column is the current sort key.
|
| 79 | 79 | if ( column->IsSortKey() )
|
| ... | ... | @@ -115,7 +115,7 @@ public: |
| 115 | 115 | |
| 116 | 116 | // Restore the column width.
|
| 117 | 117 | int width;
|
| 118 | - if ( RestoreValue(columnPrefix + wxASCII_STR(wxPERSIST_DVC_WIDTH), &width) )
|
|
| 118 | + if ( RestoreCoord(columnPrefix + wxASCII_STR(wxPERSIST_DVC_WIDTH), &width) )
|
|
| 119 | 119 | column->SetWidth(width);
|
| 120 | 120 | |
| 121 | 121 | // Restore the column's view position.
|
| ... | ... | @@ -43,21 +43,21 @@ public: |
| 43 | 43 | wxSplitterWindow* const splitter = Get();
|
| 44 | 44 | |
| 45 | 45 | int pos = splitter->IsSplit() ? splitter->GetSashPosition() : -1;
|
| 46 | - SaveValue(wxPERSIST_SPLITTER_POSITION, pos);
|
|
| 46 | + SaveCoord(wxPERSIST_SPLITTER_POSITION, pos);
|
|
| 47 | 47 | |
| 48 | 48 | // Save the previously used position too if we have them.
|
| 49 | 49 | const wxPoint lastSplitPos = splitter->GetLastSplitPosition();
|
| 50 | 50 | if ( lastSplitPos.x || lastSplitPos.y )
|
| 51 | 51 | {
|
| 52 | - SaveValue(wxPERSIST_SPLITTER_DEFAULT_HORIZONTAL, lastSplitPos.y);
|
|
| 53 | - SaveValue(wxPERSIST_SPLITTER_DEFAULT_VERTICAL, lastSplitPos.x);
|
|
| 52 | + SaveCoord(wxPERSIST_SPLITTER_DEFAULT_HORIZONTAL, lastSplitPos.y);
|
|
| 53 | + SaveCoord(wxPERSIST_SPLITTER_DEFAULT_VERTICAL, lastSplitPos.x);
|
|
| 54 | 54 | }
|
| 55 | 55 | }
|
| 56 | 56 | |
| 57 | 57 | virtual bool Restore() override
|
| 58 | 58 | {
|
| 59 | 59 | int pos;
|
| 60 | - if ( !RestoreValue(wxPERSIST_SPLITTER_POSITION, &pos) )
|
|
| 60 | + if ( !RestoreCoord(wxPERSIST_SPLITTER_POSITION, &pos) )
|
|
| 61 | 61 | return false;
|
| 62 | 62 | |
| 63 | 63 | if ( pos == -1 )
|
| ... | ... | @@ -68,8 +68,8 @@ public: |
| 68 | 68 | // Note that it's possible that default position was not stored, in
|
| 69 | 69 | // which case lastSplitPos will just remain as (0, 0) and that's ok.
|
| 70 | 70 | wxPoint lastSplitPos;
|
| 71 | - RestoreValue(wxPERSIST_SPLITTER_DEFAULT_HORIZONTAL, &lastSplitPos.x);
|
|
| 72 | - RestoreValue(wxPERSIST_SPLITTER_DEFAULT_VERTICAL, &lastSplitPos.y);
|
|
| 71 | + RestoreCoord(wxPERSIST_SPLITTER_DEFAULT_HORIZONTAL, &lastSplitPos.y);
|
|
| 72 | + RestoreCoord(wxPERSIST_SPLITTER_DEFAULT_VERTICAL, &lastSplitPos.x);
|
|
| 73 | 73 | |
| 74 | 74 | Get()->SetLastSplitPosition(lastSplitPos);
|
| 75 | 75 |
| ... | ... | @@ -41,6 +41,77 @@ public: |
| 41 | 41 | protected:
|
| 42 | 42 | wxWindow *GetWindow() const { return static_cast<wxWindow *>(GetObject()); }
|
| 43 | 43 | |
| 44 | + // Save a coordinate, i.e. either a position or a size, in pixels.
|
|
| 45 | + //
|
|
| 46 | + // Unlike SaveValue(), this function also saves the DPI at which the
|
|
| 47 | + // coordinate was computed, which allows RestoreCoord() to rescale it if
|
|
| 48 | + // the DPI changes in the meanwhile.
|
|
| 49 | + //
|
|
| 50 | + // The special values which are not really coordinates, such as -1, may be
|
|
| 51 | + // passed to this function too, they're just saved as is.
|
|
| 52 | + bool SaveCoord(const wxString& name, int value) const
|
|
| 53 | + {
|
|
| 54 | + if ( !SaveValue(name, value) )
|
|
| 55 | + return false;
|
|
| 56 | + |
|
| 57 | + // Under the platforms using DPI-independent pixels the values don't
|
|
| 58 | + // change with the DPI, so we don't need to store it at all.
|
|
| 59 | +#ifndef wxHAS_DPI_INDEPENDENT_PIXELS
|
|
| 60 | + // Don't store the DPI if we haven't saved anything depending on it.
|
|
| 61 | + if ( value > 0 )
|
|
| 62 | + {
|
|
| 63 | + // All the values of the same object are saved at the same DPI, so
|
|
| 64 | + // write it just once, using the same key for all of them, but do
|
|
| 65 | + // write it again if it has somehow changed since the last time.
|
|
| 66 | + const int dpi = GetWindow()->GetDPI().y;
|
|
| 67 | + if ( dpi != m_dpiSaved )
|
|
| 68 | + {
|
|
| 69 | + if ( !SaveValue(wxASCII_STR(wxPERSIST_DPI), dpi) )
|
|
| 70 | + return false;
|
|
| 71 | + |
|
| 72 | + m_dpiSaved = dpi;
|
|
| 73 | + }
|
|
| 74 | + }
|
|
| 75 | +#endif // !wxHAS_DPI_INDEPENDENT_PIXELS
|
|
| 76 | + |
|
| 77 | + return true;
|
|
| 78 | + }
|
|
| 79 | + |
|
| 80 | + // Restore a coordinate saved by SaveCoord(), rescaling it if the DPI has
|
|
| 81 | + // changed since it had been saved.
|
|
| 82 | + bool RestoreCoord(const wxString& name, int* value)
|
|
| 83 | + {
|
|
| 84 | + if ( !RestoreValue(name, value) )
|
|
| 85 | + return false;
|
|
| 86 | + |
|
| 87 | +#ifndef wxHAS_DPI_INDEPENDENT_PIXELS
|
|
| 88 | + // Only the values really expressed in pixels can be rescaled, the
|
|
| 89 | + // special ones, such as -1, must be preserved as is.
|
|
| 90 | + if ( *value <= 0 )
|
|
| 91 | + return true;
|
|
| 92 | + |
|
| 93 | + int dpiSaved;
|
|
| 94 | + if ( !RestoreValue(wxASCII_STR(wxPERSIST_DPI), &dpiSaved) )
|
|
| 95 | + {
|
|
| 96 | + // The value was saved by a version of the library which didn't
|
|
| 97 | + // store the DPI, so we have no choice but to use it as is.
|
|
| 98 | + return true;
|
|
| 99 | + }
|
|
| 100 | + |
|
| 101 | + if ( dpiSaved <= 0 )
|
|
| 102 | + {
|
|
| 103 | + // Don't use invalid value, just ignore the saved DPI in this case.
|
|
| 104 | + return true;
|
|
| 105 | + }
|
|
| 106 | + |
|
| 107 | + const int dpi = GetWindow()->GetDPI().y;
|
|
| 108 | + if ( dpi != dpiSaved )
|
|
| 109 | + *value = wxRound(*value*static_cast<double>(dpi)/dpiSaved);
|
|
| 110 | +#endif // !wxHAS_DPI_INDEPENDENT_PIXELS
|
|
| 111 | + |
|
| 112 | + return true;
|
|
| 113 | + }
|
|
| 114 | + |
|
| 44 | 115 | private:
|
| 45 | 116 | void HandleDestroy(wxWindowDestroyEvent& event)
|
| 46 | 117 | {
|
| ... | ... | @@ -55,6 +126,11 @@ private: |
| 55 | 126 | }
|
| 56 | 127 | }
|
| 57 | 128 | |
| 129 | +#ifndef wxHAS_DPI_INDEPENDENT_PIXELS
|
|
| 130 | + // The last DPI written by SaveCoord() or 0 if none yet.
|
|
| 131 | + mutable int m_dpiSaved = 0;
|
|
| 132 | +#endif // !wxHAS_DPI_INDEPENDENT_PIXELS
|
|
| 133 | + |
|
| 58 | 134 | wxDECLARE_NO_COPY_CLASS(wxPersistentWindowBase);
|
| 59 | 135 | };
|
| 60 | 136 |
| ... | ... | @@ -13,6 +13,12 @@ |
| 13 | 13 | #include "wx/display.h"
|
| 14 | 14 | #include "wx/toplevel.h"
|
| 15 | 15 | |
| 16 | +#include "wx/private/rescale.h"
|
|
| 17 | + |
|
| 18 | +// Name of the entry storing the DPI at which the geometry was saved: it's the
|
|
| 19 | +// same as wxPERSIST_DPI in wx/persist.h, but avoid including it just for this.
|
|
| 20 | +#define wxPERSIST_TLW_DPI "DPI"
|
|
| 21 | + |
|
| 16 | 22 | // ----------------------------------------------------------------------------
|
| 17 | 23 | // wxTLWGeometryBase: abstract base class for platform-specific classes
|
| 18 | 24 | // ----------------------------------------------------------------------------
|
| ... | ... | @@ -42,6 +48,86 @@ public: |
| 42 | 48 | // writing/reading the values of the different fields of this object.
|
| 43 | 49 | virtual bool Save(Store& store) const = 0;
|
| 44 | 50 | virtual bool Restore(const Store& store) = 0;
|
| 51 | + |
|
| 52 | +protected:
|
|
| 53 | +#ifdef wxHAS_DPI_INDEPENDENT_PIXELS
|
|
| 54 | + // The geometry doesn't change with the DPI under these platforms, so we
|
|
| 55 | + // don't need to save DPI at all but still provide stubs for the functions
|
|
| 56 | + // below to avoid having to use #ifdefs when calling them.
|
|
| 57 | + |
|
| 58 | + bool SaveDPI(Store& WXUNUSED(store)) const
|
|
| 59 | + {
|
|
| 60 | + return true;
|
|
| 61 | + }
|
|
| 62 | + |
|
| 63 | + void RestoreDPI(const Store& WXUNUSED(store))
|
|
| 64 | + {
|
|
| 65 | + }
|
|
| 66 | + |
|
| 67 | + void SetDPIFrom(const wxTopLevelWindow* WXUNUSED(tlw))
|
|
| 68 | + {
|
|
| 69 | + }
|
|
| 70 | + |
|
| 71 | + wxSize RescaleSize(const wxSize& size, const wxSize& WXUNUSED(dpi)) const
|
|
| 72 | + {
|
|
| 73 | + return size;
|
|
| 74 | + }
|
|
| 75 | +#else // !wxHAS_DPI_INDEPENDENT_PIXELS
|
|
| 76 | + // Helpers for the derived classes Save() and Restore() implementations:
|
|
| 77 | + // they must be used to save and restore the DPI at which the geometry was
|
|
| 78 | + // taken, as it is needed by RescaleSize() below.
|
|
| 79 | + bool SaveDPI(Store& store) const
|
|
| 80 | + {
|
|
| 81 | + wxASSERT_MSG( m_dpi != wxDefaultSize, "forgot to call SetDPIFrom()?" );
|
|
| 82 | + |
|
| 83 | + return store.SaveValue(wxPERSIST_TLW_DPI, m_dpi.y);
|
|
| 84 | + }
|
|
| 85 | + |
|
| 86 | + void RestoreDPI(const Store& store)
|
|
| 87 | + {
|
|
| 88 | + int dpi;
|
|
| 89 | + if ( store.RestoreValue(wxPERSIST_TLW_DPI, &dpi) )
|
|
| 90 | + {
|
|
| 91 | + if ( dpi <= 0 )
|
|
| 92 | + {
|
|
| 93 | + // Probably not worth logging this, just ignore.
|
|
| 94 | + return;
|
|
| 95 | + }
|
|
| 96 | + |
|
| 97 | + m_dpi = wxSize(dpi, dpi);
|
|
| 98 | + }
|
|
| 99 | + //else: leave it as wxDefaultSize, which means that we don't know the
|
|
| 100 | + // DPI at which the geometry was saved and so shouldn't rescale it.
|
|
| 101 | + }
|
|
| 102 | + |
|
| 103 | + // Return the size to use for a window which had the given size when its
|
|
| 104 | + // geometry was saved if it is going to be used with the given DPI now.
|
|
| 105 | + //
|
|
| 106 | + // Note that this function only deals with the size because the position of
|
|
| 107 | + // the window doesn't need to be changed, screen resolution doesn't depend
|
|
| 108 | + // on the DPI on the platforms without DPI-independent pixels, where this
|
|
| 109 | + // really matters.
|
|
| 110 | + wxSize RescaleSize(const wxSize& size, const wxSize& dpi) const
|
|
| 111 | + {
|
|
| 112 | + if ( m_dpi == wxDefaultSize || dpi == m_dpi || !dpi.x || !dpi.y )
|
|
| 113 | + return size;
|
|
| 114 | + |
|
| 115 | + return wxRescaleCoord(size).From(m_dpi).To(dpi);
|
|
| 116 | + }
|
|
| 117 | + |
|
| 118 | + // Set the DPI to save together with the geometry of the given window, must
|
|
| 119 | + // be called from GetFrom().
|
|
| 120 | + void SetDPIFrom(const wxTopLevelWindow* tlw)
|
|
| 121 | + {
|
|
| 122 | + m_dpi = tlw->GetDPI();
|
|
| 123 | + }
|
|
| 124 | + |
|
| 125 | +private:
|
|
| 126 | + // The DPI at which the geometry was saved or wxDefaultSize if it is not
|
|
| 127 | + // known, which is the case when restoring the geometry saved by a previous
|
|
| 128 | + // version of the library which didn't store it.
|
|
| 129 | + wxSize m_dpi = wxDefaultSize;
|
|
| 130 | +#endif // wxHAS_DPI_INDEPENDENT_PIXELS
|
|
| 45 | 131 | };
|
| 46 | 132 | |
| 47 | 133 | // ----------------------------------------------------------------------------
|
| ... | ... | @@ -87,6 +173,9 @@ public: |
| 87 | 173 | if ( !store.SaveValue(wxPERSIST_TLW_ICONIZED, m_iconized) )
|
| 88 | 174 | return false;
|
| 89 | 175 | |
| 176 | + if ( !SaveDPI(store) )
|
|
| 177 | + return false;
|
|
| 178 | + |
|
| 90 | 179 | return true;
|
| 91 | 180 | }
|
| 92 | 181 | |
| ... | ... | @@ -105,6 +194,8 @@ public: |
| 105 | 194 | if ( store.RestoreValue(wxPERSIST_TLW_ICONIZED, &tmp) )
|
| 106 | 195 | m_iconized = tmp != 0;
|
| 107 | 196 | |
| 197 | + RestoreDPI(store);
|
|
| 198 | + |
|
| 108 | 199 | // If we restored at least something, return true.
|
| 109 | 200 | return m_hasPos || m_hasSize || m_maximized || m_iconized;
|
| 110 | 201 | }
|
| ... | ... | @@ -112,6 +203,7 @@ public: |
| 112 | 203 | virtual bool GetFrom(const wxTopLevelWindow* tlw) override
|
| 113 | 204 | {
|
| 114 | 205 | m_rectScreen = tlw->GetScreenRect();
|
| 206 | + SetDPIFrom(tlw);
|
|
| 115 | 207 | m_hasPos =
|
| 116 | 208 | m_hasSize = true;
|
| 117 | 209 | m_iconized = tlw->IsIconized();
|
| ... | ... | @@ -142,11 +234,13 @@ public: |
| 142 | 234 | |
| 143 | 235 | if ( m_hasSize )
|
| 144 | 236 | {
|
| 237 | + // Adjust the size to the currently used DPI if necessary.
|
|
| 238 | + wxSize size = RescaleSize(m_rectScreen.GetSize(), tlw->GetDPI());
|
|
| 239 | + |
|
| 145 | 240 | // a previous version of the program could have saved the window
|
| 146 | 241 | // size which used to be big enough, but which is not big enough
|
| 147 | 242 | // any more for the new version, so check that the size we restore
|
| 148 | 243 | // doesn't cut off parts of the window
|
| 149 | - wxSize size = m_rectScreen.GetSize();
|
|
| 150 | 244 | size.IncTo(tlw->GetBestSize());
|
| 151 | 245 | tlw->SetSize(size);
|
| 152 | 246 | }
|
| ... | ... | @@ -47,4 +47,59 @@ public: |
| 47 | 47 | before saving or restoring persistent properties.
|
| 48 | 48 | */
|
| 49 | 49 | virtual wxString GetName() const;
|
| 50 | + |
|
| 51 | +protected:
|
|
| 52 | + /**
|
|
| 53 | + Save a coordinate, i.e. either a position or a size, in pixels.
|
|
| 54 | + |
|
| 55 | + This function must be used instead of wxPersistentObject::SaveValue()
|
|
| 56 | + for the values representing a size or a position in pixels, as it also
|
|
| 57 | + stores the DPI in which the coordinate is expressed, allowing
|
|
| 58 | + RestoreCoord() to rescale it if the DPI changes when the coordinate
|
|
| 59 | + is restored later.
|
|
| 60 | + |
|
| 61 | + Note that under the platforms using DPI-independent pixels, such as
|
|
| 62 | + wxGTK and wxOSX, the saved values don't change with the DPI and so no
|
|
| 63 | + DPI is stored for them at all, meaning that they are never rescaled.
|
|
| 64 | + |
|
| 65 | + Also note that the values which are not really expressed in pixels,
|
|
| 66 | + such as the special value -1 often used to indicate the absence of a
|
|
| 67 | + value, are not affected by the rescaling and can be saved using this
|
|
| 68 | + function too.
|
|
| 69 | + |
|
| 70 | + @param name
|
|
| 71 | + The name of the value in the configuration file.
|
|
| 72 | + @param value
|
|
| 73 | + The value to save.
|
|
| 74 | + @return
|
|
| 75 | + @true if the value was saved or @false if an error occurred.
|
|
| 76 | + |
|
| 77 | + @since 3.3.4
|
|
| 78 | + */
|
|
| 79 | + bool SaveCoord(const wxString& name, int value) const;
|
|
| 80 | + |
|
| 81 | + /**
|
|
| 82 | + Restore a coordinate saved by SaveCoord().
|
|
| 83 | + |
|
| 84 | + If the DPI in which the coordinate is expressed is different from the
|
|
| 85 | + DPI at which it had been saved, it is rescaled accordingly. If the
|
|
| 86 | + latter is unknown, e.g. because the value was saved by a version of
|
|
| 87 | + wxWidgets older than 3.3.4, the coordinate is restored unchanged.
|
|
| 88 | + |
|
| 89 | + As explained in SaveCoord() documentation, no DPI is stored under
|
|
| 90 | + the platforms using DPI-independent pixels, so this function simply
|
|
| 91 | + behaves as wxPersistentObject::RestoreValue() there.
|
|
| 92 | + |
|
| 93 | + @param name
|
|
| 94 | + The same name as was used by SaveCoord().
|
|
| 95 | + @param value
|
|
| 96 | + Non-@NULL pointer to the variable to fill with the saved value if
|
|
| 97 | + it was read successfully, which is not modified otherwise.
|
|
| 98 | + @return
|
|
| 99 | + @true if the value was successfully read or @false if it was not
|
|
| 100 | + found or an error occurred.
|
|
| 101 | + |
|
| 102 | + @since 3.3.4
|
|
| 103 | + */
|
|
| 104 | + bool RestoreCoord(const wxString& name, int* value);
|
|
| 50 | 105 | }; |
| ... | ... | @@ -512,6 +512,13 @@ void wxSplitterWindow::OnDPIChanged(wxDPIChangedEvent& event) |
| 512 | 512 | m_minimumPaneSize = event.ScaleX(m_minimumPaneSize);
|
| 513 | 513 | m_sashPosition = event.ScaleX(m_sashPosition);
|
| 514 | 514 | m_lastSize = event.Scale(m_lastSize);
|
| 515 | + |
|
| 516 | + // Any saved or pending positions need to be updated too.
|
|
| 517 | + m_lastSplitPosition = event.Scale(m_lastSplitPosition);
|
|
| 518 | + |
|
| 519 | + // But this one should only be scaled if it is really valid.
|
|
| 520 | + if ( m_requestedSashPosition != INT_MAX )
|
|
| 521 | + m_requestedSashPosition = event.ScaleX(m_requestedSashPosition);
|
|
| 515 | 522 | #endif // !wxHAS_DPI_INDEPENDENT_PIXELS
|
| 516 | 523 | |
| 517 | 524 | event.Skip();
|
| ... | ... | @@ -599,12 +599,20 @@ bool wxMSWOwnerDrawnButtonBase::MSWDrawButton(WXDRAWITEMSTRUCT *item) |
| 599 | 599 | {
|
| 600 | 600 | RECT oldLabelRect = rectLabel; // needed if right aligned
|
| 601 | 601 | |
| 602 | - if ( !::DrawText(hdc, label.t_str(), label.length(), &rectLabel,
|
|
| 602 | + // If the label is empty, use a space character to avoid a focus
|
|
| 603 | + // rectangle size 0x0.
|
|
| 604 | + auto s = label.empty() ? wxString(" ") : label;
|
|
| 605 | + if ( !::DrawText(hdc, s.t_str(), s.length(), &rectLabel,
|
|
| 603 | 606 | fmt | DT_CALCRECT) )
|
| 604 | 607 | {
|
| 605 | 608 | wxLogLastError(wxT("DrawText(DT_CALCRECT)"));
|
| 606 | 609 | }
|
| 607 | 610 | |
| 611 | + // For empty label, mimic the native control by extending to the
|
|
| 612 | + // client width.
|
|
| 613 | + if ( label.empty() )
|
|
| 614 | + rectLabel.right = dis->rcItem.right - 1;
|
|
| 615 | + |
|
| 608 | 616 | if ( isRightAligned )
|
| 609 | 617 | {
|
| 610 | 618 | // move the label rect to the right
|
| ... | ... | @@ -238,7 +238,11 @@ WXDWORD wxMSWHeaderCtrl::MSWGetStyle(long style, WXDWORD *exstyle) const |
| 238 | 238 | |
| 239 | 239 | void wxMSWHeaderCtrl::MSWGetDarkModeSupport(MSWDarkModeSupport& support) const
|
| 240 | 240 | {
|
| 241 | - support.themeName = L"ItemsView";
|
|
| 241 | + // Use DarkMode_DarkTheme if available as it looks better.
|
|
| 242 | + if ( wxMSWDarkMode::HasDarkTheme() )
|
|
| 243 | + support.themeName = L"DarkMode_DarkTheme";
|
|
| 244 | + else
|
|
| 245 | + support.themeName = L"ItemsView";
|
|
| 242 | 246 | }
|
| 243 | 247 | |
| 244 | 248 | void wxMSWHeaderCtrl::MSWSetDarkOrLightMode(SetMode setmode)
|
| ... | ... | @@ -636,7 +636,10 @@ wxRendererXP::DrawHeaderButton(wxWindow *win, |
| 636 | 636 | wxHeaderSortIconType sortArrow,
|
| 637 | 637 | wxHeaderButtonParams* params)
|
| 638 | 638 | {
|
| 639 | - wxUxThemeHandle hTheme(win, L"Explorer::HEADER");
|
|
| 639 | + // Use DarkMode_DarkTheme if available as it looks better.
|
|
| 640 | + auto dark = wxMSWDarkMode::HasDarkTheme() ?
|
|
| 641 | + L"DarkMode_DarkTheme::Header" : L"ItemsView::Header";
|
|
| 642 | + wxUxThemeHandle hTheme(win, L"HEADER", dark);
|
|
| 640 | 643 | if ( !hTheme )
|
| 641 | 644 | {
|
| 642 | 645 | return m_rendererNative.DrawHeaderButton(win, dc, rect, flags, sortArrow, params);
|
| ... | ... | @@ -95,6 +95,19 @@ bool wxStaticBox::Create(wxWindow *parent, |
| 95 | 95 | return true;
|
| 96 | 96 | }
|
| 97 | 97 | |
| 98 | +void wxStaticBox::MSWSetDarkOrLightMode(SetMode setmode)
|
|
| 99 | +{
|
|
| 100 | + wxStaticBoxBase::MSWSetDarkOrLightMode(setmode);
|
|
| 101 | + |
|
| 102 | + // Set custom painting because the native control does not support dark mode.
|
|
| 103 | + //
|
|
| 104 | + // Note that it's not useful to check if we're using dark mode now because
|
|
| 105 | + // we must either be starting to use it now or had been using it at some
|
|
| 106 | + // point for this function to be called and once custom painting is
|
|
| 107 | + // enabled, calling this function doesn't do anything anyhow.
|
|
| 108 | + UseCustomPaint();
|
|
| 109 | +}
|
|
| 110 | + |
|
| 98 | 111 | bool wxStaticBox::ShouldUseCustomPaint() const
|
| 99 | 112 | {
|
| 100 | 113 | // When not using double buffering, we paint the box ourselves by default
|
| ... | ... | @@ -98,6 +98,27 @@ void GTKWaitRealized(wxDataViewCtrl* list) |
| 98 | 98 | #endif // __WXGTK__/!__WXGTK__
|
| 99 | 99 | }
|
| 100 | 100 | |
| 101 | +// Save the state of the test control with the given column widths.
|
|
| 102 | +static void SavePersistenceTestDVC(int width1, int width2)
|
|
| 103 | +{
|
|
| 104 | + wxDataViewCtrl* const list = CreatePersistenceTestDVC();
|
|
| 105 | + |
|
| 106 | + list->GetColumn(0)->SetWidth(width1);
|
|
| 107 | + list->GetColumn(1)->SetWidth(width2);
|
|
| 108 | + list->GetColumn(1)->SetSortOrder(false);
|
|
| 109 | + |
|
| 110 | + CHECK(wxPersistenceManager::Get().Register(list));
|
|
| 111 | + |
|
| 112 | + // We need to wait until the window is fully realized and the column
|
|
| 113 | + // widths are actually set.
|
|
| 114 | + GTKWaitRealized(list);
|
|
| 115 | + |
|
| 116 | + // Deleting the control itself doesn't allow it to save its state as
|
|
| 117 | + // the wxEVT_DESTROY handler is called too late, so delete its parent
|
|
| 118 | + // (as would usually be the case) instead.
|
|
| 119 | + delete list->GetParent();
|
|
| 120 | +}
|
|
| 121 | + |
|
| 101 | 122 | // --------------------------------------------------------------------------
|
| 102 | 123 | // tests themselves
|
| 103 | 124 | // --------------------------------------------------------------------------
|
| ... | ... | @@ -107,23 +128,7 @@ void GTKWaitRealized(wxDataViewCtrl* list) |
| 107 | 128 | TEST_CASE_METHOD(PersistenceTests, "wxPersistDVC", "[persist][wxDataViewCtrl]")
|
| 108 | 129 | {
|
| 109 | 130 | {
|
| 110 | - wxDataViewCtrl* const list = CreatePersistenceTestDVC();
|
|
| 111 | - |
|
| 112 | - // Adjust the initial settings.
|
|
| 113 | - list->GetColumn(0)->SetWidth(150);
|
|
| 114 | - list->GetColumn(1)->SetWidth(250);
|
|
| 115 | - list->GetColumn(1)->SetSortOrder(false);
|
|
| 116 | - |
|
| 117 | - CHECK(wxPersistenceManager::Get().Register(list));
|
|
| 118 | - |
|
| 119 | - // We need to wait until the window is fully realized and the column
|
|
| 120 | - // widths are actually set.
|
|
| 121 | - GTKWaitRealized(list);
|
|
| 122 | - |
|
| 123 | - // Deleting the control itself doesn't allow it to save its state as
|
|
| 124 | - // the wxEVT_DESTROY handler is called too late, so delete its parent
|
|
| 125 | - // (as would usually be the case) instead.
|
|
| 126 | - delete list->GetParent();
|
|
| 131 | + SavePersistenceTestDVC(150, 250);
|
|
| 127 | 132 | |
| 128 | 133 | // Test that the relevant keys have been stored correctly.
|
| 129 | 134 | int val = -1;
|
| ... | ... | @@ -162,4 +167,63 @@ TEST_CASE_METHOD(PersistenceTests, "wxPersistDVC", "[persist][wxDataViewCtrl]") |
| 162 | 167 | }
|
| 163 | 168 | }
|
| 164 | 169 | |
| 170 | +#ifdef __WXMSW__
|
|
| 171 | + |
|
| 172 | +// Check that the column widths saved at one DPI are rescaled when they are
|
|
| 173 | +// restored at a different one.
|
|
| 174 | +TEST_CASE_METHOD(PersistenceTests, "wxPersistDVC::DPI",
|
|
| 175 | + "[persist][wxDataViewCtrl][dpi]")
|
|
| 176 | +{
|
|
| 177 | + // Use half of the widths used by the other tests, so that the columns have
|
|
| 178 | + // exactly those widths after being scaled by 2 below.
|
|
| 179 | + SavePersistenceTestDVC(75, 125);
|
|
| 180 | + |
|
| 181 | + const int dpi = wxTheApp->GetTopWindow()->GetDPI().y;
|
|
| 182 | + |
|
| 183 | + // All the DPI values used in practice are even, which allows us to use
|
|
| 184 | + // exact values in the checks below.
|
|
| 185 | + REQUIRE(dpi % 2 == 0);
|
|
| 186 | + |
|
| 187 | + // The DPI must have been saved together with the widths.
|
|
| 188 | + int dpiSaved = -1;
|
|
| 189 | + CHECK(GetConfig().Read(DVC_PREFIX "/DPI", &dpiSaved));
|
|
| 190 | + CHECK(dpiSaved == dpi);
|
|
| 191 | + |
|
| 192 | + SECTION("Rescale")
|
|
| 193 | + {
|
|
| 194 | + // Pretend that the widths had been saved at half the current DPI, in
|
|
| 195 | + // which case they must be doubled when restoring them.
|
|
| 196 | + GetConfig().Write(DVC_PREFIX "/DPI", dpi / 2);
|
|
| 197 | + |
|
| 198 | + wxDataViewCtrl* const list = CreatePersistenceTestDVC();
|
|
| 199 | + CHECK(wxPersistenceManager::Get().RegisterAndRestore(list));
|
|
| 200 | + |
|
| 201 | + GTKWaitRealized(list);
|
|
| 202 | + |
|
| 203 | + CHECK(list->GetColumn(0)->GetWidth() == 150);
|
|
| 204 | + CHECK(list->GetColumn(1)->GetWidth() == 250);
|
|
| 205 | + |
|
| 206 | + delete list->GetParent();
|
|
| 207 | + }
|
|
| 208 | + |
|
| 209 | + SECTION("Compatibility")
|
|
| 210 | + {
|
|
| 211 | + // The values saved by the previous versions of the library don't have
|
|
| 212 | + // any DPI associated with them and must be restored as is.
|
|
| 213 | + GetConfig().DeleteEntry(DVC_PREFIX "/DPI");
|
|
| 214 | + |
|
| 215 | + wxDataViewCtrl* const list = CreatePersistenceTestDVC();
|
|
| 216 | + CHECK(wxPersistenceManager::Get().RegisterAndRestore(list));
|
|
| 217 | + |
|
| 218 | + GTKWaitRealized(list);
|
|
| 219 | + |
|
| 220 | + CHECK(list->GetColumn(0)->GetWidth() == 75);
|
|
| 221 | + CHECK(list->GetColumn(1)->GetWidth() == 125);
|
|
| 222 | + |
|
| 223 | + delete list->GetParent();
|
|
| 224 | + }
|
|
| 225 | +}
|
|
| 226 | + |
|
| 227 | +#endif // __WXMSW__
|
|
| 228 | + |
|
| 165 | 229 | #endif |
| ... | ... | @@ -325,4 +325,61 @@ TEST_CASE_METHOD(PersistenceTests, "wxPersistTLW::OffScreen", "[persist][tlw]") |
| 325 | 325 | }
|
| 326 | 326 | }
|
| 327 | 327 | |
| 328 | +// Check that the geometry saved at one DPI is rescaled when it is restored at
|
|
| 329 | +// a different one.
|
|
| 330 | +TEST_CASE_METHOD(PersistenceTests, "wxPersistTLW::DPI", "[persist][tlw][dpi]")
|
|
| 331 | +{
|
|
| 332 | + const wxPoint pos(100, 150);
|
|
| 333 | + |
|
| 334 | + // Use a relatively small size, so that the frame still fits on the screen
|
|
| 335 | + // after being scaled by 2 below.
|
|
| 336 | + const wxSize size(225, 175);
|
|
| 337 | + |
|
| 338 | + SavePersistenceTestFrame(pos, size);
|
|
| 339 | + |
|
| 340 | + // The frame size can be greater than the requested one, e.g. because the
|
|
| 341 | + // system doesn't allow frames narrower than their caption buttons, so
|
|
| 342 | + // check the restored size against the size which was really saved and not
|
|
| 343 | + // the one we had asked for.
|
|
| 344 | + wxSize sizeSaved;
|
|
| 345 | + REQUIRE(GetConfig().Read(FRAME_OPTIONS_PREFIX "/w", &sizeSaved.x));
|
|
| 346 | + REQUIRE(GetConfig().Read(FRAME_OPTIONS_PREFIX "/h", &sizeSaved.y));
|
|
| 347 | + |
|
| 348 | + const int dpi = wxTheApp->GetTopWindow()->GetDPI().y;
|
|
| 349 | + |
|
| 350 | + // All the DPI values used in practice are even, which allows us to use
|
|
| 351 | + // exact values in the checks below.
|
|
| 352 | + REQUIRE(dpi % 2 == 0);
|
|
| 353 | + |
|
| 354 | + // The DPI must have been saved together with the geometry.
|
|
| 355 | + int dpiSaved = -1;
|
|
| 356 | + CHECK(GetConfig().Read(FRAME_OPTIONS_PREFIX "/DPI", &dpiSaved));
|
|
| 357 | + CHECK(dpiSaved == dpi);
|
|
| 358 | + |
|
| 359 | + SECTION("Rescale")
|
|
| 360 | + {
|
|
| 361 | + // Pretend that the geometry had been saved at half the current DPI:
|
|
| 362 | + // in this case the size must be doubled when restoring it, but not the
|
|
| 363 | + // position, which doesn't depend on the DPI.
|
|
| 364 | + GetConfig().Write(FRAME_OPTIONS_PREFIX "/DPI", dpi / 2);
|
|
| 365 | + |
|
| 366 | + auto const frame = RestorePersistenceTestFrame();
|
|
| 367 | + |
|
| 368 | + CHECK(frame->GetSize() == sizeSaved*2);
|
|
| 369 | + CHECK(frame->GetPosition() == pos);
|
|
| 370 | + }
|
|
| 371 | + |
|
| 372 | + SECTION("Compatibility")
|
|
| 373 | + {
|
|
| 374 | + // The geometry saved by the previous versions of the library doesn't
|
|
| 375 | + // have any DPI associated with it and must be restored as is.
|
|
| 376 | + GetConfig().DeleteEntry(FRAME_OPTIONS_PREFIX "/DPI");
|
|
| 377 | + |
|
| 378 | + auto const frame = RestorePersistenceTestFrame();
|
|
| 379 | + |
|
| 380 | + CHECK(frame->GetSize() == sizeSaved);
|
|
| 381 | + CHECK(frame->GetPosition() == pos);
|
|
| 382 | + }
|
|
| 383 | +}
|
|
| 384 | + |
|
| 328 | 385 | #endif // __WXMSW__ |
—
View it on GitLab.
You're receiving this email because of your account on gitlab.com. Manage all notifications · Help