1 /*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2020 CERN
5 * Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
6 * @author Jon Evans <jon@craftyjon.com>
7 *
8 * This program is free software: you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation, either version 3 of the License, or (at your
11 * option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include <project.h>
23 #include <project/project_local_settings.h>
24 #include <settings/json_settings_internals.h>
25 #include <settings/parameters.h>
26
27 const int projectLocalSettingsVersion = 3;
28
29
PROJECT_LOCAL_SETTINGS(PROJECT * aProject,const wxString & aFilename)30 PROJECT_LOCAL_SETTINGS::PROJECT_LOCAL_SETTINGS( PROJECT* aProject, const wxString& aFilename ) :
31 JSON_SETTINGS( aFilename, SETTINGS_LOC::PROJECT, projectLocalSettingsVersion,
32 /* aCreateIfMissing = */ true, /* aCreateIfDefault = */ false,
33 /* aWriteFile = */ true ),
34 m_ActiveLayer( UNDEFINED_LAYER ),
35 m_ContrastModeDisplay( HIGH_CONTRAST_MODE::NORMAL ),
36 m_NetColorMode( NET_COLOR_MODE::RATSNEST ),
37 m_RatsnestMode( RATSNEST_MODE::ALL ),
38 m_AutoTrackWidth( true ),
39 m_ZoneDisplayMode( ZONE_DISPLAY_MODE::SHOW_FILLED ),
40 m_TrackOpacity( 1.0 ),
41 m_ViaOpacity( 1.0 ),
42 m_PadOpacity( 1.0 ),
43 m_ZoneOpacity( 0.6 ),
44 m_SelectionFilter(),
45 m_project( aProject )
46 {
47 // Keep old files around
48 m_deleteLegacyAfterMigration = false;
49
50 m_params.emplace_back( new PARAM_LAMBDA<std::string>( "board.visible_layers",
51 [&]() -> std::string
52 {
53 return m_VisibleLayers.FmtHex();
54 },
55 [&]( const std::string& aString )
56 {
57 m_VisibleLayers.ParseHex( aString.c_str(), aString.size() );
58 },
59 LSET::AllLayersMask().FmtHex() ) );
60
61 m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "board.visible_items",
62 [&]() -> nlohmann::json
63 {
64 nlohmann::json ret = nlohmann::json::array();
65
66 for( size_t i = 0; i < m_VisibleItems.size(); i++ )
67 if( m_VisibleItems.test( i ) )
68 ret.push_back( i );
69
70 return ret;
71 },
72 [&]( const nlohmann::json& aVal )
73 {
74 if( !aVal.is_array() || aVal.empty() )
75 {
76 m_VisibleItems = GAL_SET::DefaultVisible();
77 return;
78 }
79
80 m_VisibleItems.reset();
81
82 for( const nlohmann::json& entry : aVal )
83 {
84 try
85 {
86 int i = entry.get<int>();
87 m_VisibleItems.set( i );
88 }
89 catch( ... )
90 {
91 // Non-integer or out of range entry in the array; ignore
92 }
93 }
94 },
95 {} ) );
96
97 m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "board.selection_filter",
98 [&]() -> nlohmann::json
99 {
100 nlohmann::json ret;
101
102 ret["lockedItems"] = m_SelectionFilter.lockedItems;
103 ret["footprints"] = m_SelectionFilter.footprints;
104 ret["text"] = m_SelectionFilter.text;
105 ret["tracks"] = m_SelectionFilter.tracks;
106 ret["vias"] = m_SelectionFilter.vias;
107 ret["pads"] = m_SelectionFilter.pads;
108 ret["graphics"] = m_SelectionFilter.graphics;
109 ret["zones"] = m_SelectionFilter.zones;
110 ret["keepouts"] = m_SelectionFilter.keepouts;
111 ret["dimensions"] = m_SelectionFilter.dimensions;
112 ret["otherItems"] = m_SelectionFilter.otherItems;
113
114 return ret;
115 },
116 [&]( const nlohmann::json& aVal )
117 {
118 if( aVal.empty() || !aVal.is_object() )
119 return;
120
121 SetIfPresent( aVal, "lockedItems", m_SelectionFilter.lockedItems );
122 SetIfPresent( aVal, "footprints", m_SelectionFilter.footprints );
123 SetIfPresent( aVal, "text", m_SelectionFilter.text );
124 SetIfPresent( aVal, "tracks", m_SelectionFilter.tracks );
125 SetIfPresent( aVal, "vias", m_SelectionFilter.vias );
126 SetIfPresent( aVal, "pads", m_SelectionFilter.pads );
127 SetIfPresent( aVal, "graphics", m_SelectionFilter.graphics );
128 SetIfPresent( aVal, "zones", m_SelectionFilter.zones );
129 SetIfPresent( aVal, "keepouts", m_SelectionFilter.keepouts );
130 SetIfPresent( aVal, "dimensions", m_SelectionFilter.dimensions );
131 SetIfPresent( aVal, "otherItems", m_SelectionFilter.otherItems );
132 },
133 {
134 { "lockedItems", true },
135 { "footprints", true },
136 { "text", true },
137 { "tracks", true },
138 { "vias", true },
139 { "pads", true },
140 { "graphics", true },
141 { "zones", true },
142 { "keepouts", true },
143 { "dimensions", true },
144 { "otherItems", true }
145 } ) );
146
147 m_params.emplace_back( new PARAM_ENUM<PCB_LAYER_ID>( "board.active_layer",
148 &m_ActiveLayer, F_Cu, PCBNEW_LAYER_ID_START, F_Fab ) );
149
150 m_params.emplace_back( new PARAM<wxString>( "board.active_layer_preset",
151 &m_ActiveLayerPreset, "" ) );
152
153 m_params.emplace_back( new PARAM_ENUM<HIGH_CONTRAST_MODE>( "board.high_contrast_mode",
154 &m_ContrastModeDisplay, HIGH_CONTRAST_MODE::NORMAL,
155 HIGH_CONTRAST_MODE::NORMAL, HIGH_CONTRAST_MODE::HIDDEN ) );
156
157 m_params.emplace_back( new PARAM<double>( "board.opacity.tracks", &m_TrackOpacity, 1.0 ) );
158 m_params.emplace_back( new PARAM<double>( "board.opacity.vias", &m_ViaOpacity, 1.0 ) );
159 m_params.emplace_back( new PARAM<double>( "board.opacity.pads", &m_PadOpacity, 1.0 ) );
160 m_params.emplace_back( new PARAM<double>( "board.opacity.zones", &m_ZoneOpacity, 0.6 ) );
161
162 m_params.emplace_back( new PARAM_LIST<wxString>( "board.hidden_nets", &m_HiddenNets, {} ) );
163
164 m_params.emplace_back( new PARAM_ENUM<NET_COLOR_MODE>( "board.net_color_mode",
165 &m_NetColorMode, NET_COLOR_MODE::RATSNEST, NET_COLOR_MODE::OFF,
166 NET_COLOR_MODE::ALL ) );
167
168 m_params.emplace_back( new PARAM_ENUM<RATSNEST_MODE>( "board.ratsnest_display_mode",
169 &m_RatsnestMode, RATSNEST_MODE::ALL, RATSNEST_MODE::ALL,
170 RATSNEST_MODE::VISIBLE ) );
171
172 m_params.emplace_back( new PARAM<bool>( "board.auto_track_width",
173 &m_AutoTrackWidth, true ) );
174
175 m_params.emplace_back( new PARAM_ENUM<ZONE_DISPLAY_MODE>( "board.zone_display_mode",
176 &m_ZoneDisplayMode,
177 ZONE_DISPLAY_MODE::SHOW_FILLED, ZONE_DISPLAY_MODE::SHOW_FILLED,
178 ZONE_DISPLAY_MODE::SHOW_TRIANGULATION ) );
179
180 m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "project.files",
181 [&]() -> nlohmann::json
182 {
183 nlohmann::json ret = nlohmann::json::array();
184
185 for( PROJECT_FILE_STATE& fileState : m_files )
186 {
187 nlohmann::json file;
188 file["name"] = fileState.fileName;
189 file["open"] = fileState.open;
190
191 nlohmann::json window;
192 window["maximized"] = fileState.window.maximized;
193 window["size_x"] = fileState.window.size_x;
194 window["size_y"] = fileState.window.size_y;
195 window["pos_x"] = fileState.window.pos_x;
196 window["pos_y"] = fileState.window.pos_y;
197 window["display"] = fileState.window.display;
198
199 file["window"] = window;
200
201 ret.push_back( file );
202 }
203
204 return ret;
205 },
206 [&]( const nlohmann::json& aVal )
207 {
208 if( !aVal.is_array() || aVal.empty() )
209 {
210 return;
211 }
212
213 for( const nlohmann::json& file : aVal )
214 {
215 PROJECT_FILE_STATE fileState;
216
217 try
218 {
219 SetIfPresent( file, "name", fileState.fileName );
220 SetIfPresent( file, "open", fileState.open );
221 SetIfPresent( file, "window.size_x", fileState.window.size_x );
222 SetIfPresent( file, "window.size_y", fileState.window.size_y );
223 SetIfPresent( file, "window.pos_x", fileState.window.pos_x );
224 SetIfPresent( file, "window.pos_y", fileState.window.pos_y );
225 SetIfPresent( file, "window.maximized", fileState.window.maximized );
226 SetIfPresent( file, "window.display", fileState.window.display );
227
228 m_files.push_back( fileState );
229 }
230 catch( ... )
231 {
232 // Non-integer or out of range entry in the array; ignore
233 }
234 }
235
236 },
237 {
238 } ) );
239
240 registerMigration( 1, 2,
241 [&]()
242 {
243 /**
244 * Schema version 1 to 2:
245 * LAYER_PADS and LAYER_ZONES added to visibility controls
246 */
247
248 std::string ptr( "board.visible_items" );
249
250 if( Contains( ptr ) )
251 {
252 if( At( ptr ).is_array() )
253 {
254 At( ptr ).push_back( LAYER_PADS );
255 At( ptr ).push_back( LAYER_ZONES );
256 }
257 else
258 {
259 At( "board" ).erase( "visible_items" );
260 }
261 }
262
263 return true;
264 } );
265
266 registerMigration( 2, 3,
267 [&]()
268 {
269 /**
270 * Schema version 2 to 3:
271 * Fix issue with object visibility not migrating from legacy, which required
272 * remapping of GAL_LAYER_ID to match the legacy bitmask ordering.
273 */
274
275 /// Stores a mapping from old to new enum offset
276 const std::map<int, int> offsets = {
277 { 22, 34 }, // LAYER_PAD_HOLEWALLS
278 { 23, 22 }, // LAYER_VIA_HOLES
279 { 24, 35 }, // LAYER_VIA_HOLEWALLS
280 { 25, 23 }, // LAYER_DRC_ERROR
281 { 26, 36 }, // LAYER_DRC_WARNING
282 { 27, 37 }, // LAYER_DRC_EXCLUSION
283 { 28, 38 }, // LAYER_MARKER_SHADOWS
284 { 29, 24 }, // LAYER_DRAWINGSHEET
285 { 30, 25 }, // LAYER_GP_OVERLAY
286 { 31, 26 }, // LAYER_SELECT_OVERLAY
287 { 32, 27 }, // LAYER_PCB_BACKGROUND
288 { 33, 28 }, // LAYER_CURSOR
289 { 34, 29 }, // LAYER_AUX_ITEM
290 { 35, 30 }, // LAYER_DRAW_BITMAPS
291 { 39, 32 }, // LAYER_PADS
292 { 40, 33 }, // LAYER_ZONES
293 };
294
295 std::string ptr( "board.visible_items" );
296
297 if( Contains( ptr ) && At( ptr ).is_array() )
298 {
299 nlohmann::json visible = nlohmann::json::array();
300
301 for( const nlohmann::json& val : At( ptr ) )
302 {
303 try
304 {
305 int layer = val.get<int>();
306
307 if( offsets.count( layer ) )
308 visible.push_back( offsets.at( layer ) );
309 else
310 visible.push_back( layer );
311 }
312 catch( ... )
313 {
314 // skip invalid value
315 }
316 }
317
318 At( "board" )["visible_items"] = visible;
319 }
320
321 return true;
322 } );
323 }
324
325
MigrateFromLegacy(wxConfigBase * aLegacyConfig)326 bool PROJECT_LOCAL_SETTINGS::MigrateFromLegacy( wxConfigBase* aLegacyConfig )
327 {
328 /**
329 * The normal legacy migration code won't be used for this because the only legacy
330 * information stored here was stored in board files, so we do that migration when loading
331 * the board.
332 */
333 return true;
334 }
335
336
SaveToFile(const wxString & aDirectory,bool aForce)337 bool PROJECT_LOCAL_SETTINGS::SaveToFile( const wxString& aDirectory, bool aForce )
338 {
339 wxASSERT( m_project );
340
341 Set( "meta.filename", m_project->GetProjectName() + "." + ProjectLocalSettingsFileExtension );
342
343 return JSON_SETTINGS::SaveToFile( aDirectory, aForce );
344 }
345
346
SaveAs(const wxString & aDirectory,const wxString & aFile)347 bool PROJECT_LOCAL_SETTINGS::SaveAs( const wxString& aDirectory, const wxString& aFile )
348 {
349 Set( "meta.filename", aFile + "." + ProjectLocalSettingsFileExtension );
350 SetFilename( aFile );
351
352 return JSON_SETTINGS::SaveToFile( aDirectory, true );
353 }
354
355
GetFileState(const wxString & aFileName)356 const PROJECT_FILE_STATE* PROJECT_LOCAL_SETTINGS::GetFileState( const wxString& aFileName )
357 {
358 auto it = std::find_if( m_files.begin(), m_files.end(),
359 [&aFileName]( const PROJECT_FILE_STATE &a )
360 {
361 return a.fileName == aFileName;
362 } );
363
364 if( it != m_files.end() )
365 {
366 return &( *it );
367 }
368
369 return nullptr;
370 }
371
372
SaveFileState(const wxString & aFileName,const WINDOW_SETTINGS * aWindowCfg,bool aOpen)373 void PROJECT_LOCAL_SETTINGS::SaveFileState( const wxString& aFileName,
374 const WINDOW_SETTINGS* aWindowCfg, bool aOpen )
375 {
376 auto it = std::find_if( m_files.begin(), m_files.end(),
377 [&aFileName]( const PROJECT_FILE_STATE& a )
378 {
379 return a.fileName == aFileName;
380 } );
381
382 if( it == m_files.end() )
383 {
384 PROJECT_FILE_STATE fileState;
385 fileState.fileName = aFileName;
386 fileState.open = false;
387 fileState.window.maximized = false;
388 fileState.window.size_x = -1;
389 fileState.window.size_y = -1;
390 fileState.window.pos_x = -1;
391 fileState.window.pos_y = -1;
392 fileState.window.display = 0;
393
394 m_files.push_back( fileState );
395
396 it = m_files.end() - 1;
397 }
398
399 ( *it ).window = aWindowCfg->state;
400 ( *it ).open = aOpen;
401 }
402
403
ClearFileState()404 void PROJECT_LOCAL_SETTINGS::ClearFileState()
405 {
406 m_files.clear();
407 }
408