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