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 <config_params.h>
23 #include <project.h>
24 #include <project/net_settings.h>
25 #include <settings/json_settings_internals.h>
26 #include <project/project_file.h>
27 #include <settings/common_settings.h>
28 #include <settings/parameters.h>
29 #include <wildcards_and_files_ext.h>
30 #include <wx/config.h>
31 #include <wx/log.h>
32 
33 
34 ///! Update the schema version whenever a migration is required
35 const int projectFileSchemaVersion = 1;
36 
37 
PROJECT_FILE(const wxString & aFullPath)38 PROJECT_FILE::PROJECT_FILE( const wxString& aFullPath ) :
39         JSON_SETTINGS( aFullPath, SETTINGS_LOC::PROJECT, projectFileSchemaVersion ),
40         m_ErcSettings( nullptr ),
41         m_SchematicSettings( nullptr ),
42         m_BoardSettings(),
43         m_sheets(),
44         m_boards(),
45         m_project( nullptr )
46 {
47     // Keep old files around
48     m_deleteLegacyAfterMigration = false;
49 
50     m_params.emplace_back( new PARAM_LIST<FILE_INFO_PAIR>( "sheets", &m_sheets, {} ) );
51 
52     m_params.emplace_back( new PARAM_LIST<FILE_INFO_PAIR>( "boards", &m_boards, {} ) );
53 
54     m_params.emplace_back( new PARAM_WXSTRING_MAP( "text_variables", &m_TextVars, {} ) );
55 
56     m_params.emplace_back(
57             new PARAM_LIST<wxString>( "libraries.pinned_symbol_libs", &m_PinnedSymbolLibs, {} ) );
58 
59     m_params.emplace_back( new PARAM_LIST<wxString>(
60             "libraries.pinned_footprint_libs", &m_PinnedFootprintLibs, {} ) );
61 
62     m_params.emplace_back(
63             new PARAM_PATH_LIST( "cvpcb.equivalence_files", &m_EquivalenceFiles, {} ) );
64 
65     m_params.emplace_back(
66             new PARAM_PATH( "pcbnew.page_layout_descr_file", &m_BoardDrawingSheetFile, "" ) );
67 
68     m_params.emplace_back(
69             new PARAM_PATH( "pcbnew.last_paths.netlist", &m_PcbLastPath[LAST_PATH_NETLIST], "" ) );
70 
71     m_params.emplace_back(
72             new PARAM_PATH( "pcbnew.last_paths.step", &m_PcbLastPath[LAST_PATH_STEP], "" ) );
73 
74     m_params.emplace_back(
75             new PARAM_PATH( "pcbnew.last_paths.idf", &m_PcbLastPath[LAST_PATH_IDF], "" ) );
76 
77     m_params.emplace_back(
78             new PARAM_PATH( "pcbnew.last_paths.vrml", &m_PcbLastPath[LAST_PATH_VRML], "" ) );
79 
80     m_params.emplace_back( new PARAM_PATH(
81             "pcbnew.last_paths.specctra_dsn", &m_PcbLastPath[LAST_PATH_SPECCTRADSN], "" ) );
82 
83     m_params.emplace_back(
84             new PARAM_PATH( "pcbnew.last_paths.gencad", &m_PcbLastPath[LAST_PATH_GENCAD], "" ) );
85 
86     m_params.emplace_back( new PARAM<wxString>( "schematic.legacy_lib_dir", &m_LegacyLibDir, "" ) );
87 
88     m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "schematic.legacy_lib_list",
89             [&]() -> nlohmann::json
90             {
91                 nlohmann::json ret = nlohmann::json::array();
92 
93                 for( const wxString& libName : m_LegacyLibNames )
94                     ret.push_back( libName );
95 
96                 return ret;
97             },
98             [&]( const nlohmann::json& aJson )
99             {
100                 if( aJson.empty() || !aJson.is_array() )
101                     return;
102 
103                 m_LegacyLibNames.clear();
104 
105                 for( const nlohmann::json& entry : aJson )
106                     m_LegacyLibNames.push_back( entry.get<wxString>() );
107             }, {} ) );
108 
109     m_NetSettings = std::make_shared<NET_SETTINGS>( this, "net_settings" );
110 
111     m_params.emplace_back( new PARAM_LAYER_PRESET( "board.layer_presets", &m_LayerPresets ) );
112 }
113 
114 
MigrateFromLegacy(wxConfigBase * aCfg)115 bool PROJECT_FILE::MigrateFromLegacy( wxConfigBase* aCfg )
116 {
117     bool     ret = true;
118     wxString str;
119     long     index = 0;
120 
121     std::set<wxString> group_blacklist;
122 
123     // Legacy files don't store board info; they assume board matches project name
124     // We will leave m_boards empty here so it can be populated with other code
125 
126     // First handle migration of data that will be stored locally in this object
127 
128     auto loadPinnedLibs =
129             [&]( const std::string& aDest )
130             {
131                 int      libIndex = 1;
132                 wxString libKey   = wxT( "PinnedItems" );
133                 libKey << libIndex;
134 
135                 nlohmann::json libs = nlohmann::json::array();
136 
137                 while( aCfg->Read( libKey, &str ) )
138                 {
139                     libs.push_back( str );
140 
141                     aCfg->DeleteEntry( libKey, true );
142 
143                     libKey = wxT( "PinnedItems" );
144                     libKey << ++libIndex;
145                 }
146 
147                 Set( aDest, libs );
148             };
149 
150     aCfg->SetPath( wxT( "/LibeditFrame" ) );
151     loadPinnedLibs( "libraries.pinned_symbol_libs" );
152 
153     aCfg->SetPath( wxT( "/ModEditFrame" ) );
154     loadPinnedLibs( "libraries.pinned_footprint_libs" );
155 
156     aCfg->SetPath( wxT( "/cvpcb/equfiles" ) );
157 
158     {
159         int      eqIdx = 1;
160         wxString eqKey = wxT( "EquName" );
161         eqKey << eqIdx;
162 
163         nlohmann::json eqs = nlohmann::json::array();
164 
165         while( aCfg->Read( eqKey, &str ) )
166         {
167             eqs.push_back( str );
168 
169             eqKey = wxT( "EquName" );
170             eqKey << ++eqIdx;
171         }
172 
173         Set( "cvpcb.equivalence_files", eqs );
174     }
175 
176     // All CvPcb params that we want to keep have been migrated above
177     group_blacklist.insert( wxT( "/cvpcb" ) );
178 
179     aCfg->SetPath( wxT( "/eeschema" ) );
180     fromLegacyString( aCfg, "LibDir", "schematic.legacy_lib_dir" );
181 
182     aCfg->SetPath( wxT( "/eeschema/libraries" ) );
183 
184     {
185         int      libIdx = 1;
186         wxString libKey = wxT( "LibName" );
187         libKey << libIdx;
188 
189         nlohmann::json libs = nlohmann::json::array();
190 
191         while( aCfg->Read( libKey, &str ) )
192         {
193             libs.push_back( str );
194 
195             libKey = wxT( "LibName" );
196             libKey << ++libIdx;
197         }
198 
199         Set( "schematic.legacy_lib_list", libs );
200     }
201 
202     group_blacklist.insert( wxT( "/eeschema" ) );
203 
204     aCfg->SetPath( wxT( "/text_variables" ) );
205 
206     {
207         int      txtIdx = 1;
208         wxString txtKey;
209         txtKey << txtIdx;
210 
211         nlohmann::json vars = nlohmann::json();
212 
213         while( aCfg->Read( txtKey, &str ) )
214         {
215             wxArrayString tokens = wxSplit( str, ':' );
216 
217             if( tokens.size() == 2 )
218                 vars[ tokens[0].ToStdString() ] = tokens[1];
219 
220             txtKey.clear();
221             txtKey << ++txtIdx;
222         }
223 
224         Set( "text_variables", vars );
225     }
226 
227     group_blacklist.insert( wxT( "/text_variables" ) );
228 
229     aCfg->SetPath( wxT( "/schematic_editor" ) );
230 
231     fromLegacyString( aCfg, "PageLayoutDescrFile",     "schematic.page_layout_descr_file" );
232     fromLegacyString( aCfg, "PlotDirectoryName",       "schematic.plot_directory" );
233     fromLegacyString( aCfg, "NetFmtName",              "schematic.net_format_name" );
234     fromLegacy<bool>( aCfg, "SpiceAjustPassiveValues", "schematic.spice_adjust_passive_values" );
235     fromLegacy<int>(  aCfg, "SubpartIdSeparator",      "schematic.subpart_id_separator" );
236     fromLegacy<int>(  aCfg, "SubpartFirstId",          "schematic.subpart_first_id" );
237 
238     fromLegacy<int>( aCfg, "LineThickness",         "schematic.drawing.default_line_thickness" );
239     fromLegacy<int>( aCfg, "WireThickness",         "schematic.drawing.default_wire_thickness" );
240     fromLegacy<int>( aCfg, "BusThickness",          "schematic.drawing.default_bus_thickness" );
241     fromLegacy<int>( aCfg, "LabSize",               "schematic.drawing.default_text_size" );
242     fromLegacy<int>( aCfg, "PinSymbolSize",         "schematic.drawing.pin_symbol_size" );
243     fromLegacy<int>( aCfg, "JunctionSize",          "schematic.drawing.default_junction_size" );
244 
245     fromLegacyString(   aCfg, "FieldNameTemplates", "schematic.drawing.field_names" );
246 
247     if( !fromLegacy<double>( aCfg, "TextOffsetRatio", "schematic.drawing.text_offset_ratio" ) )
248     {
249         // Use the spacing of Eeschema V5
250         Set( "schematic.drawing.text_offset_ratio", 0.08 );
251         Set( "schematic.drawing.label_size_ratio", 0.25 );
252     }
253 
254     // All schematic_editor keys we keep are migrated above
255     group_blacklist.insert( wxT( "/schematic_editor" ) );
256 
257     aCfg->SetPath( wxT( "/pcbnew" ) );
258 
259     fromLegacyString( aCfg, "PageLayoutDescrFile",       "pcbnew.page_layout_descr_file" );
260     fromLegacyString( aCfg, "LastNetListRead",           "pcbnew.last_paths.netlist" );
261     fromLegacyString( aCfg, "LastSTEPExportPath",        "pcbnew.last_paths.step" );
262     fromLegacyString( aCfg, "LastIDFExportPath",         "pcbnew.last_paths.idf" );
263     fromLegacyString( aCfg, "LastVRMLExportPath",        "pcbnew.last_paths.vmrl" );
264     fromLegacyString( aCfg, "LastSpecctraDSNExportPath", "pcbnew.last_paths.specctra_dsn" );
265     fromLegacyString( aCfg, "LastGenCADExportPath",      "pcbnew.last_paths.gencad" );
266 
267     std::string bp = "board.design_settings.";
268 
269     {
270         int      idx = 1;
271         wxString key = wxT( "DRCExclusion" );
272         key << idx;
273 
274         nlohmann::json exclusions = nlohmann::json::array();
275 
276         while( aCfg->Read( key, &str ) )
277         {
278             exclusions.push_back( str );
279 
280             key = wxT( "DRCExclusion" );
281             key << ++idx;
282         }
283 
284         Set( bp + "drc_exclusions", exclusions );
285     }
286 
287     fromLegacy<bool>( aCfg,   "AllowMicroVias",  bp + "rules.allow_microvias" );
288     fromLegacy<bool>( aCfg,   "AllowBlindVias",  bp + "rules.allow_blind_buried_vias" );
289     fromLegacy<double>( aCfg, "MinClearance",    bp + "rules.min_clearance" );
290     fromLegacy<double>( aCfg, "MinTrackWidth",   bp + "rules.min_track_width" );
291     fromLegacy<double>( aCfg, "MinViaAnnulus",   bp + "rules.min_via_annulus" );
292     fromLegacy<double>( aCfg, "MinViaDiameter",  bp + "rules.min_via_diameter" );
293 
294     if( !fromLegacy<double>( aCfg, "MinThroughDrill", bp + "rules.min_through_hole_diameter" ) )
295         fromLegacy<double>( aCfg, "MinViaDrill", bp + "rules.min_through_hole_diameter" );
296 
297     fromLegacy<double>( aCfg, "MinMicroViaDiameter",  bp + "rules.min_microvia_diameter" );
298     fromLegacy<double>( aCfg, "MinMicroViaDrill",     bp + "rules.min_microvia_drill" );
299     fromLegacy<double>( aCfg, "MinHoleToHole",        bp + "rules.min_hole_to_hole" );
300     fromLegacy<double>( aCfg, "CopperEdgeClearance",  bp + "rules.min_copper_edge_clearance" );
301     fromLegacy<double>( aCfg, "SolderMaskClearance",  bp + "rules.solder_mask_clearance" );
302     fromLegacy<double>( aCfg, "SolderMaskMinWidth",   bp + "rules.solder_mask_min_width" );
303     fromLegacy<double>( aCfg, "SolderPasteClearance", bp + "rules.solder_paste_clearance" );
304     fromLegacy<double>( aCfg, "SolderPasteRatio",     bp + "rules.solder_paste_margin_ratio" );
305 
306     if( !fromLegacy<double>( aCfg, "SilkLineWidth", bp + "defaults.silk_line_width" ) )
307         fromLegacy<double>( aCfg, "ModuleOutlineThickness", bp + "defaults.silk_line_width" );
308 
309     if( !fromLegacy<double>( aCfg, "SilkTextSizeV", bp + "defaults.silk_text_size_v" ) )
310         fromLegacy<double>( aCfg, "ModuleTextSizeV", bp + "defaults.silk_text_size_v" );
311 
312     if( !fromLegacy<double>( aCfg, "SilkTextSizeH", bp + "defaults.silk_text_size_h" ) )
313         fromLegacy<double>( aCfg, "ModuleTextSizeH", bp + "defaults.silk_text_size_h" );
314 
315     if( !fromLegacy<double>( aCfg, "SilkTextSizeThickness", bp + "defaults.silk_text_thickness" ) )
316         fromLegacy<double>( aCfg, "ModuleTextSizeThickness", bp + "defaults.silk_text_thickness" );
317 
318     fromLegacy<bool>( aCfg, "SilkTextItalic",   bp + "defaults.silk_text_italic" );
319     fromLegacy<bool>( aCfg, "SilkTextUpright",  bp + "defaults.silk_text_upright" );
320 
321     if( !fromLegacy<double>( aCfg, "CopperLineWidth", bp + "defaults.copper_line_width" ) )
322         fromLegacy<double>( aCfg, "DrawSegmentWidth", bp + "defaults.copper_line_width" );
323 
324     if( !fromLegacy<double>( aCfg, "CopperTextSizeV", bp + "defaults.copper_text_size_v" ) )
325         fromLegacy<double>( aCfg, "PcbTextSizeV", bp + "defaults.copper_text_size_v" );
326 
327     if( !fromLegacy<double>( aCfg, "CopperTextSizeH", bp + "defaults.copper_text_size_h" ) )
328         fromLegacy<double>( aCfg, "PcbTextSizeH", bp + "defaults.copper_text_size_h" );
329 
330     if( !fromLegacy<double>( aCfg, "CopperTextThickness", bp + "defaults.copper_text_thickness" ) )
331         fromLegacy<double>( aCfg, "PcbTextThickness", bp + "defaults.copper_text_thickness" );
332 
333     fromLegacy<bool>( aCfg, "CopperTextItalic",   bp + "defaults.copper_text_italic" );
334     fromLegacy<bool>( aCfg, "CopperTextUpright",  bp + "defaults.copper_text_upright" );
335 
336     if( !fromLegacy<double>( aCfg, "EdgeCutLineWidth", bp + "defaults.board_outline_line_width" ) )
337         fromLegacy<double>( aCfg, "BoardOutlineThickness", bp + "defaults.board_outline_line_width" );
338 
339     fromLegacy<double>( aCfg, "CourtyardLineWidth",   bp + "defaults.courtyard_line_width" );
340 
341     fromLegacy<double>( aCfg, "FabLineWidth",         bp + "defaults.fab_line_width" );
342     fromLegacy<double>( aCfg, "FabTextSizeV",         bp + "defaults.fab_text_size_v" );
343     fromLegacy<double>( aCfg, "FabTextSizeH",         bp + "defaults.fab_text_size_h" );
344     fromLegacy<double>( aCfg, "FabTextSizeThickness", bp + "defaults.fab_text_thickness" );
345     fromLegacy<bool>(   aCfg, "FabTextItalic",        bp + "defaults.fab_text_italic" );
346     fromLegacy<bool>(   aCfg, "FabTextUpright",       bp + "defaults.fab_text_upright" );
347 
348     if( !fromLegacy<double>( aCfg, "OthersLineWidth", bp + "defaults.other_line_width" ) )
349         fromLegacy<double>( aCfg, "ModuleOutlineThickness", bp + "defaults.other_line_width" );
350 
351     fromLegacy<double>( aCfg, "OthersTextSizeV",         bp + "defaults.other_text_size_v" );
352     fromLegacy<double>( aCfg, "OthersTextSizeH",         bp + "defaults.other_text_size_h" );
353     fromLegacy<double>( aCfg, "OthersTextSizeThickness", bp + "defaults.other_text_thickness" );
354     fromLegacy<bool>(   aCfg, "OthersTextItalic",        bp + "defaults.other_text_italic" );
355     fromLegacy<bool>(   aCfg, "OthersTextUpright",       bp + "defaults.other_text_upright" );
356 
357     fromLegacy<int>( aCfg, "DimensionUnits",     bp + "defaults.dimension_units" );
358     fromLegacy<int>( aCfg, "DimensionPrecision", bp + "defaults.dimension_precision" );
359 
360     std::string sev = bp + "rule_severities";
361 
362     fromLegacy<bool>( aCfg, "RequireCourtyardDefinitions", sev + "legacy_no_courtyard_defined" );
363 
364     fromLegacy<bool>( aCfg, "ProhibitOverlappingCourtyards", sev + "legacy_courtyards_overlap" );
365 
366     {
367         int      idx     = 1;
368         wxString keyBase = "TrackWidth";
369         wxString key     = keyBase;
370         double   val;
371 
372         nlohmann::json widths = nlohmann::json::array();
373 
374         key << idx;
375 
376         while( aCfg->Read( key, &val ) )
377         {
378             widths.push_back( val );
379             key = keyBase;
380             key << ++idx;
381         }
382 
383         Set( bp + "track_widths", widths );
384     }
385 
386     {
387         int      idx     = 1;
388         wxString keyBase = "ViaDiameter";
389         wxString key     = keyBase;
390         double   diameter;
391         double   drill   = 1.0;
392 
393         nlohmann::json vias = nlohmann::json::array();
394 
395         key << idx;
396 
397         while( aCfg->Read( key, &diameter ) )
398         {
399             key = "ViaDrill";
400             aCfg->Read( key << idx, &drill );
401 
402             nlohmann::json via = { { "diameter", diameter }, { "drill", drill } };
403             vias.push_back( via );
404 
405             key = keyBase;
406             key << ++idx;
407         }
408 
409         Set( bp + "via_dimensions", vias );
410     }
411 
412     {
413         int      idx     = 1;
414         wxString keyBase = "dPairWidth";
415         wxString key     = keyBase;
416         double   width;
417         double   gap     = 1.0;
418         double   via_gap = 1.0;
419 
420         nlohmann::json pairs = nlohmann::json::array();
421 
422         key << idx;
423 
424         while( aCfg->Read( key, &width ) )
425         {
426             key = "dPairGap";
427             aCfg->Read( key << idx, &gap );
428 
429             key = "dPairViaGap";
430             aCfg->Read( key << idx, &via_gap );
431 
432             nlohmann::json pair = { { "width", width }, { "gap", gap }, { "via_gap", via_gap } };
433             pairs.push_back( pair );
434 
435             key = keyBase;
436             key << ++idx;
437         }
438 
439         Set( bp + "diff_pair_dimensions",  pairs );
440     }
441 
442     group_blacklist.insert( wxT( "/pcbnew" ) );
443 
444     // General group is unused these days, we can throw it away
445     group_blacklist.insert( wxT( "/general" ) );
446 
447     // Next load sheet names and put all other legacy data in the legacy dict
448     aCfg->SetPath( wxT( "/" ) );
449 
450     auto loadSheetNames =
451             [&]() -> bool
452             {
453                 int            sheet = 1;
454                 wxString       entry;
455                 nlohmann::json arr   = nlohmann::json::array();
456 
457                 wxLogTrace( traceSettings, "Migrating sheet names" );
458 
459                 aCfg->SetPath( wxT( "/sheetnames" ) );
460 
461                 while( aCfg->Read( wxString::Format( "%d", sheet++ ), &entry ) )
462                 {
463                     wxArrayString tokens = wxSplit( entry, ':' );
464 
465                     if( tokens.size() == 2 )
466                     {
467                         wxLogTrace( traceSettings, "%d: %s = %s", sheet, tokens[0], tokens[1] );
468                         arr.push_back( nlohmann::json::array( { tokens[0], tokens[1] } ) );
469                     }
470                 }
471 
472                 Set( "sheets", arr );
473 
474                 aCfg->SetPath( "/" );
475 
476                 // TODO: any reason we want to fail on this?
477                 return true;
478             };
479 
480     std::vector<wxString> groups;
481 
482     groups.emplace_back( "" );
483 
484     auto loadLegacyPairs =
485             [&]( const std::string& aGroup ) -> bool
486             {
487                 wxLogTrace( traceSettings, "Migrating group %s", aGroup );
488                 bool     success = true;
489                 wxString keyStr;
490                 wxString val;
491 
492                 index = 0;
493 
494                 while( aCfg->GetNextEntry( keyStr, index ) )
495                 {
496                     if( !aCfg->Read( keyStr, &val ) )
497                         continue;
498 
499                     std::string key( keyStr.ToUTF8() );
500 
501                     wxLogTrace( traceSettings, "    %s = %s", key, val );
502 
503                     try
504                     {
505                         Set( "legacy." + aGroup + "." + key, val );
506                     }
507                     catch( ... )
508                     {
509                         success = false;
510                     }
511                 }
512 
513                 return success;
514             };
515 
516     for( size_t i = 0; i < groups.size(); i++ )
517     {
518         aCfg->SetPath( groups[i] );
519 
520         if( groups[i] == wxT( "/sheetnames" ) )
521         {
522             ret |= loadSheetNames();
523             continue;
524         }
525 
526         aCfg->DeleteEntry( wxT( "last_client" ), true );
527         aCfg->DeleteEntry( wxT( "update" ), true );
528         aCfg->DeleteEntry( wxT( "version" ), true );
529 
530         ret &= loadLegacyPairs( groups[i].ToStdString() );
531 
532         index = 0;
533 
534         while( aCfg->GetNextGroup( str, index ) )
535         {
536             wxString group = groups[i] + "/" + str;
537 
538             if( !group_blacklist.count( group ) )
539                 groups.emplace_back( group );
540         }
541 
542         aCfg->SetPath( "/" );
543     }
544 
545     return ret;
546 }
547 
548 
SaveToFile(const wxString & aDirectory,bool aForce)549 bool PROJECT_FILE::SaveToFile( const wxString& aDirectory, bool aForce )
550 {
551     wxASSERT( m_project );
552 
553     Set( "meta.filename", m_project->GetProjectName() + "." + ProjectFileExtension );
554 
555     return JSON_SETTINGS::SaveToFile( aDirectory, aForce );
556 }
557 
558 
SaveAs(const wxString & aDirectory,const wxString & aFile)559 bool PROJECT_FILE::SaveAs( const wxString& aDirectory, const wxString& aFile )
560 {
561     Set( "meta.filename", aFile + "." + ProjectFileExtension );
562     SetFilename( aFile );
563 
564     // While performing Save As, we have already checked that we can write to the directory
565     // so don't carry the previous flag
566     SetReadOnly( false );
567     return JSON_SETTINGS::SaveToFile( aDirectory, true );
568 }
569 
570 
getFileExt() const571 wxString PROJECT_FILE::getFileExt() const
572 {
573     return ProjectFileExtension;
574 }
575 
576 
getLegacyFileExt() const577 wxString PROJECT_FILE::getLegacyFileExt() const
578 {
579     return LegacyProjectFileExtension;
580 }
581 
582 
to_json(nlohmann::json & aJson,const FILE_INFO_PAIR & aPair)583 void to_json( nlohmann::json& aJson, const FILE_INFO_PAIR& aPair )
584 {
585     aJson = nlohmann::json::array( { aPair.first.AsString().ToUTF8(), aPair.second.ToUTF8() } );
586 }
587 
588 
from_json(const nlohmann::json & aJson,FILE_INFO_PAIR & aPair)589 void from_json( const nlohmann::json& aJson, FILE_INFO_PAIR& aPair )
590 {
591     wxCHECK( aJson.is_array() && aJson.size() == 2, /* void */ );
592     aPair.first  = KIID( wxString( aJson[0].get<std::string>().c_str(), wxConvUTF8 ) );
593     aPair.second = wxString( aJson[1].get<std::string>().c_str(), wxConvUTF8 );
594 }
595