Store dialog geometry in DPI-independent pixels to prevent size ratcheting

On mixed-DPI multi-monitor setups, dialog sizes grow each time they are
reopened because logical pixel values change when a window moves between
monitors with different scaling factors. The std::max comparison during
restore guarantees monotonic growth since the DPI-scaled value always
exceeds the previously saved unscaled value.

Store width and height as DIP (device-independent pixels) using ToDIP()
on save and FromDIP() on restore. This makes the persisted size stable
regardless of which monitor the dialog was on when closed. A "dip" flag
in the JSON differentiates new DIP-format values from legacy logical
pixel values, which are converted in place on first load.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/20120
This commit is contained in:
Seth Hillbrand
2026-02-19 15:21:30 -08:00
parent 426d122b60
commit ee8b2bebce
4 changed files with 56 additions and 9 deletions
+11 -7
View File
@@ -387,6 +387,9 @@ bool DIALOG_SHIM::Show( bool show )
if( savedDialogRect.GetSize().x != 0 && savedDialogRect.GetSize().y != 0 )
{
// Convert saved DIP size to logical pixels for the current monitor
wxSize restoredSize = FromDIP( savedDialogRect.GetSize() );
if( m_useCalculatedSize )
{
SetSize( savedDialogRect.GetPosition().x, savedDialogRect.GetPosition().y,
@@ -395,8 +398,8 @@ bool DIALOG_SHIM::Show( bool show )
else
{
SetSize( savedDialogRect.GetPosition().x, savedDialogRect.GetPosition().y,
std::max( wxDialog::GetSize().x, savedDialogRect.GetSize().x ),
std::max( wxDialog::GetSize().y, savedDialogRect.GetSize().y ), 0 );
std::max( wxDialog::GetSize().x, restoredSize.x ),
std::max( wxDialog::GetSize().y, restoredSize.y ), 0 );
// Reset minimum size so the user can resize the dialog smaller than
// the saved size. We must clear the current minimum and invalidate
@@ -565,12 +568,13 @@ void DIALOG_SHIM::SaveControlState()
std::string dialogKey = m_hash_key.empty() ? getDialogKeyFromTitle( GetTitle() ) : m_hash_key;
std::map<std::string, nlohmann::json>& dlgMap = settings->CsInternals().m_dialogControlValues[ dialogKey ];
wxRect rect( GetPosition(), GetSize() );
wxPoint pos = GetPosition();
wxSize dipSize = ToDIP( GetSize() );
nlohmann::json geom;
geom[ "x" ] = rect.GetX();
geom[ "y" ] = rect.GetY();
geom[ "w" ] = rect.GetWidth();
geom[ "h" ] = rect.GetHeight();
geom[ "x" ] = pos.x;
geom[ "y" ] = pos.y;
geom[ "w" ] = dipSize.x;
geom[ "h" ] = dipSize.y;
dlgMap[ "__geometry" ] = geom;
std::function<void( wxWindow* )> saveFn =
+43 -1
View File
@@ -38,13 +38,14 @@
#include <wx/log.h>
#include <wx/regex.h>
#include <wx/tokenzr.h>
#include <wx/window.h>
///! The following environment variables will never be migrated from a previous version
const wxRegEx versionedEnvVarRegex( wxS( "KICAD[0-9]+_[A-Z0-9_]+(_DIR)?" ) );
///! Update the schema version whenever a migration is required
const int commonSchemaVersion = 4;
const int commonSchemaVersion = 5;
COMMON_SETTINGS::~COMMON_SETTINGS() = default;
@@ -482,6 +483,7 @@ COMMON_SETTINGS::COMMON_SETTINGS() :
registerMigration( 1, 2, std::bind( &COMMON_SETTINGS::migrateSchema1to2, this ) );
registerMigration( 2, 3, std::bind( &COMMON_SETTINGS::migrateSchema2to3, this ) );
registerMigration( 3, 4, std::bind( &COMMON_SETTINGS::migrateSchema3to4, this ) );
registerMigration( 4, 5, std::bind( &COMMON_SETTINGS::migrateSchema4to5, this ) );
}
@@ -661,6 +663,46 @@ bool COMMON_SETTINGS::migrateSchema3to4()
}
bool COMMON_SETTINGS::migrateSchema4to5()
{
try
{
nlohmann::json& controls = m_internals->At( "dialog" ).at( "controls" );
for( auto& [dlgKey, dlgVal] : controls.items() )
{
if( !dlgVal.is_object() )
continue;
auto geoIt = dlgVal.find( "__geometry" );
if( geoIt == dlgVal.end() || !geoIt->is_object() )
continue;
nlohmann::json& geom = *geoIt;
// Legacy values were stored in logical pixels. Convert to DIP using the
// primary display's scale factor (best approximation without window context).
int w = geom.value( "w", 0 );
int h = geom.value( "h", 0 );
wxSize dipSize = wxWindow::ToDIP( wxSize( w, h ), nullptr );
geom[ "w" ] = dipSize.x;
geom[ "h" ] = dipSize.y;
geom.erase( "dip" );
}
}
catch( ... )
{
wxLogTrace( traceSettings,
wxT( "COMMON_SETTINGS::Migrate 4->5: dialog.controls not found" ) );
}
return true;
}
bool COMMON_SETTINGS::MigrateFromLegacy( wxConfigBase* aCfg )
{
bool ret = true;
+1
View File
@@ -197,6 +197,7 @@ private:
bool migrateSchema1to2();
bool migrateSchema2to3();
bool migrateSchema3to4();
bool migrateSchema4to5();
struct LEGACY_3D_SEARCH_PATH
{
+1 -1
View File
@@ -66,7 +66,7 @@
"zoom_speed_auto": true
},
"meta": {
"version": 4
"version": 5
},
"package_manager": {
"sash_pos": 380