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