KiCad PCB EDA Suite
Loading...
Searching...
No Matches
panel_setup_rules.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include <bitmaps.h>
25#include <confirm.h>
28#include <pcb_edit_frame.h>
29#include <pcbexpr_evaluator.h>
30#include <board.h>
32#include <project.h>
33#include <string_utils.h>
34#include <tool/tool_manager.h>
35#include <panel_setup_rules.h>
38#include <scintilla_tricks.h>
40#include <drc/drc_rule_parser.h>
41#include <tools/drc_tool.h>
42#include <pgm_base.h>
44#include <regex>
45#include <unordered_map>
46
47PANEL_SETUP_RULES::PANEL_SETUP_RULES( wxWindow* aParentWindow, PCB_EDIT_FRAME* aFrame ) :
48 PANEL_SETUP_RULES_BASE( aParentWindow ),
49 m_frame( aFrame ),
50 m_scintillaTricks( nullptr ),
51 m_helpWindow( nullptr )
52{
53 m_scintillaTricks = new SCINTILLA_TRICKS( m_textEditor, wxT( "()" ), false,
54 // onAcceptFn
55 [this]( wxKeyEvent& aEvent )
56 {
57 wxPostEvent( PAGED_DIALOG::GetDialog( this ),
58 wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
59 },
60 // onCharFn
61 [this]( wxStyledTextEvent& aEvent )
62 {
63 onScintillaCharAdded( aEvent );
64 } );
65
66 m_textEditor->AutoCompSetSeparator( '|' );
67
68 m_netClassRegex.Compile( "^NetClass\\s*[!=]=\\s*$", wxRE_ADVANCED );
69 m_netNameRegex.Compile( "^NetName\\s*[!=]=\\s*$", wxRE_ADVANCED );
70 m_typeRegex.Compile( "^Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
71 m_viaTypeRegex.Compile( "^Via_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
72 m_padTypeRegex.Compile( "^Pad_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
73 m_pinTypeRegex.Compile( "^Pin_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
74 m_fabPropRegex.Compile( "^Fabrication_Property\\s*[!=]=\\s*$", wxRE_ADVANCED );
75 m_shapeRegex.Compile( "^Shape\\s*[!=]=\\s*$", wxRE_ADVANCED );
76 m_padShapeRegex.Compile( "^Pad_Shape\\s*[!=]=\\s*$", wxRE_ADVANCED );
77 m_padConnectionsRegex.Compile( "^Pad_Connections\\s*[!=]=\\s*$", wxRE_ADVANCED );
78 m_zoneConnStyleRegex.Compile( "^Zone_Connection_Style\\s*[!=]=\\s*$", wxRE_ADVANCED );
79 m_lineStyleRegex.Compile( "^Line_Style\\s*[!=]=\\s*$", wxRE_ADVANCED );
80 m_hJustRegex.Compile( "^Horizontal_Justification\\s*[!=]=\\s*$", wxRE_ADVANCED );
81 m_vJustRegex.Compile( "^Vertical_Justification\\s*[!=]=\\s*$", wxRE_ADVANCED );
82
83 m_compileButton->SetBitmap( KiBitmapBundle( BITMAPS::drc ) );
84
86
87 m_textEditor->UsePopUp( 0 );
88 m_textEditor->Bind( wxEVT_CHAR_HOOK, &PANEL_SETUP_RULES::onCharHook, this );
89}
90
91
93{
94 m_textEditor->Unbind( wxEVT_CHAR_HOOK, &PANEL_SETUP_RULES::onCharHook, this );
96
97 delete m_scintillaTricks;
98
99 if( m_helpWindow )
100 m_helpWindow->Destroy();
101};
102
103
104void PANEL_SETUP_RULES::onCharHook( wxKeyEvent& aEvent )
105{
106 if( aEvent.GetKeyCode() == WXK_ESCAPE && !m_textEditor->AutoCompActive() )
107 {
108 if( m_originalText != m_textEditor->GetText() )
109 {
110 if( IsOK( wxGetTopLevelParent( this ), _( "Cancel Changes?" ) ) )
111 {
112 m_textEditor->SetText( m_originalText );
113 m_textEditor->SelectAll();
114 }
115
116 return;
117 }
118 }
119
120 aEvent.Skip();
121}
122
123
124void PANEL_SETUP_RULES::OnContextMenu(wxMouseEvent &event)
125{
126 wxMenu menu;
127
128 menu.Append( wxID_UNDO, _( "Undo" ) );
129 menu.Append( wxID_REDO, _( "Redo" ) );
130
131 menu.AppendSeparator();
132
133 menu.Append( 1, _( "Cut" ) ); // Don't use wxID_CUT, wxID_COPY, etc. On Mac (at least),
134 menu.Append( 2, _( "Copy" ) ); // wxWidgets never delivers them to us.
135 menu.Append( 3, _( "Paste" ) );
136 menu.Append( 4, _( "Delete" ) );
137
138 menu.AppendSeparator();
139
140 menu.Append( 5, _( "Select All" ) );
141
142 menu.AppendSeparator();
143
144 menu.Append( wxID_ZOOM_IN, _( "Zoom In" ) );
145 menu.Append( wxID_ZOOM_OUT, _( "Zoom Out" ) );
146
147
148 switch( GetPopupMenuSelectionFromUser( menu ) )
149 {
150 case wxID_UNDO:
151 m_textEditor->Undo();
152 break;
153 case wxID_REDO:
154 m_textEditor->Redo();
155 break;
156
157 case 1:
158 m_textEditor->Cut();
159 break;
160 case 2:
161 m_textEditor->Copy();
162 break;
163 case 3:
164 m_textEditor->Paste();
165 break;
166 case 4:
167 {
168 long from, to;
169 m_textEditor->GetSelection( &from, &to );
170
171 if( to > from )
172 m_textEditor->DeleteRange( from, to );
173
174 break;
175 }
176
177 case 5:
178 m_textEditor->SelectAll();
179 break;
180
181 case wxID_ZOOM_IN:
182 m_textEditor->ZoomIn();
183 break;
184 case wxID_ZOOM_OUT:
185 m_textEditor->ZoomOut();
186 break;
187 }
188}
189
190
191void PANEL_SETUP_RULES::onScintillaCharAdded( wxStyledTextEvent &aEvent )
192{
193 if( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKey() == ' ' )
194 {
195 // This is just a short-cut for do-auto-complete
196 }
197 else
198 {
200 }
201
202 m_textEditor->SearchAnchor();
203
204 int currentPos = m_textEditor->GetCurrentPos();
205 int startPos = 0;
206
207 for( int line = m_textEditor->LineFromPosition( currentPos ); line > 0; line-- )
208 {
209 int lineStart = m_textEditor->PositionFromLine( line );
210 wxString beginning = m_textEditor->GetTextRange( lineStart, lineStart + 10 );
211
212 if( beginning.StartsWith( wxT( "(rule " ) ) )
213 {
214 startPos = lineStart;
215 break;
216 }
217 }
218
219 enum
220 {
221 NO_CONTEXT,
222 STRING,
223 SEXPR_OPEN,
224 SEXPR_TOKEN,
225 SEXPR_STRING,
226 STRUCT_REF
227 };
228
229 auto isDisallowToken =
230 []( const wxString& token ) -> bool
231 {
232 return token == wxT( "buried_via" )
233 || token == wxT( "graphic" )
234 || token == wxT( "hole" )
235 || token == wxT( "micro_via" )
236 || token == wxT( "pad" )
237 || token == wxT( "text" )
238 || token == wxT( "track" )
239 || token == wxT( "via" )
240 || token == wxT( "zone" );
241 };
242
243 auto isConstraintTypeToken =
244 []( const wxString& token ) -> bool
245 {
246 return token == wxT( "annular_width" )
247 || token == wxT( "assertion" )
248 || token == wxT( "clearance" )
249 || token == wxT( "connection_width" )
250 || token == wxT( "courtyard_clearance" )
251 || token == wxT( "diff_pair_gap" )
252 || token == wxT( "diff_pair_uncoupled" )
253 || token == wxT( "disallow" )
254 || token == wxT( "edge_clearance" )
255 || token == wxT( "length" )
256 || token == wxT( "hole_clearance" )
257 || token == wxT( "hole_size" )
258 || token == wxT( "hole_to_hole" )
259 || token == wxT( "min_resolved_spokes" )
260 || token == wxT( "physical_clearance" )
261 || token == wxT( "physical_hole_clearance" )
262 || token == wxT( "silk_clearance" )
263 || token == wxT( "skew" )
264 || token == wxT( "solder_mask_expansion" )
265 || token == wxT( "solder_paste_abs_margin" )
266 || token == wxT( "solder_paste_rel_margin" )
267 || token == wxT( "text_height" )
268 || token == wxT( "text_thickness" )
269 || token == wxT( "thermal_relief_gap" )
270 || token == wxT( "thermal_spoke_width" )
271 || token == wxT( "track_width" )
272 || token == wxT( "track_angle" )
273 || token == wxT( "track_segment_length" )
274 || token == wxT( "via_count" )
275 || token == wxT( "via_diameter" )
276 || token == wxT( "zone_connection" );
277 };
278
279 std::stack<wxString> sexprs;
280 wxString partial;
281 wxString last;
282 wxString constraintType;
283 int context = NO_CONTEXT;
284 int expr_context = NO_CONTEXT;
285
286 for( int i = startPos; i < currentPos; ++i )
287 {
288 wxChar c = m_textEditor->GetCharAt( i );
289
290 if( c == '\\' )
291 {
292 i++; // skip escaped char
293 }
294 else if( context == STRING )
295 {
296 if( c == '"' )
297 {
298 context = NO_CONTEXT;
299 }
300 else
301 {
302 if( expr_context == STRING )
303 {
304 if( c == '\'' )
305 expr_context = NO_CONTEXT;
306 else
307 partial += c;
308 }
309 else if( c == '\'' )
310 {
311 last = partial;
312 partial = wxEmptyString;
313 expr_context = STRING;
314 }
315 else if( c == '.' )
316 {
317 partial = wxEmptyString;
318 expr_context = STRUCT_REF;
319 }
320 else
321 {
322 partial += c;
323 }
324 }
325 }
326 else if( c == '"' )
327 {
328 last = partial;
329 partial = wxEmptyString;
330 context = STRING;
331 }
332 else if( c == '(' )
333 {
334 if( context == SEXPR_OPEN && !partial.IsEmpty() )
335 {
336 m_textEditor->AutoCompCancel();
337 sexprs.push( partial );
338 }
339
340 partial = wxEmptyString;
341 context = SEXPR_OPEN;
342 }
343 else if( c == ')' )
344 {
345 while( !sexprs.empty() && ( sexprs.top() == wxT( "assertion" )
346 || sexprs.top() == wxT( "disallow" )
347 || isDisallowToken( sexprs.top() )
348 || sexprs.top() == wxT( "min_resolved_spokes" )
349 || sexprs.top() == wxT( "zone_connection" ) ) )
350 {
351 sexprs.pop();
352 }
353
354 if( !sexprs.empty() )
355 {
356 // Ignore argument-less tokens
357 if( partial == wxT( "within_diff_pairs" ) )
358 {
359 partial = wxEmptyString;
360 }
361 else
362 {
363 if( sexprs.top() == wxT( "constraint" ) )
364 {
365 constraintType = wxEmptyString;
366 }
367
368 sexprs.pop();
369 }
370 }
371
372 context = NO_CONTEXT;
373 }
374 else if( c == ' ' )
375 {
376 if( context == SEXPR_OPEN && !partial.IsEmpty() )
377 {
378 m_textEditor->AutoCompCancel();
379 sexprs.push( partial );
380
381 if( partial == wxT( "constraint" )
382 || partial == wxT( "layer" )
383 || partial == wxT( "severity" ) )
384 {
385 context = SEXPR_TOKEN;
386 }
387 else if( partial == wxT( "rule" )
388 || partial == wxT( "condition" ) )
389 {
390 context = SEXPR_STRING;
391 }
392 else
393 {
394 context = NO_CONTEXT;
395 }
396
397 partial = wxEmptyString;
398 continue;
399 }
400 else if( partial == wxT( "disallow" )
401 || isDisallowToken( partial )
402 || partial == wxT( "min_resolved_spokes" )
403 || partial == wxT( "zone_connection" ) )
404 {
405 m_textEditor->AutoCompCancel();
406 sexprs.push( partial );
407
408 partial = wxEmptyString;
409 context = SEXPR_TOKEN;
410 continue;
411 }
412 else if( partial == wxT( "assertion" ) )
413 {
414 m_textEditor->AutoCompCancel();
415 sexprs.push( partial );
416
417 partial = wxEmptyString;
418 context = SEXPR_STRING;
419 continue;
420 }
421 else if( isConstraintTypeToken( partial ) )
422 {
423 constraintType = partial;
424 }
425
426 context = NO_CONTEXT;
427 }
428 else
429 {
430 partial += c;
431 }
432 }
433
434 wxString tokens;
435
436 if( context == SEXPR_OPEN )
437 {
438 if( sexprs.empty() )
439 {
440 tokens = wxT( "rule|"
441 "version" );
442 }
443 else if( sexprs.top() == wxT( "rule" ) )
444 {
445 tokens = wxT( "condition|"
446 "constraint|"
447 "layer|"
448 "severity" );
449 }
450 else if( sexprs.top() == wxT( "constraint" ) )
451 {
452 if( constraintType == wxT( "skew" ) )
453 tokens = wxT( "max|min|opt|within_diff_pairs" );
454 else
455 tokens = wxT( "max|min|opt" );
456 }
457 }
458 else if( context == SEXPR_TOKEN )
459 {
460 if( sexprs.empty() )
461 {
462 /* badly formed grammar */
463 }
464 else if( sexprs.top() == wxT( "constraint" ) )
465 {
466 tokens = wxT( "annular_width|"
467 "assertion|"
468 "clearance|"
469 "connection_width|"
470 "courtyard_clearance|"
471 "creepage|"
472 "diff_pair_gap|"
473 "diff_pair_uncoupled|"
474 "disallow|"
475 "edge_clearance|"
476 "length|"
477 "hole_clearance|"
478 "hole_size|"
479 "hole_to_hole|"
480 "min_resolved_spokes|"
481 "physical_clearance|"
482 "physical_hole_clearance|"
483 "silk_clearance|"
484 "skew|"
485 "solder_mask_expansion|"
486 "solder_paste_abs_margin|"
487 "solder_paste_rel_margin|"
488 "text_height|"
489 "text_thickness|"
490 "thermal_relief_gap|"
491 "thermal_spoke_width|"
492 "track_width|"
493 "track_angle|"
494 "track_segment_length|"
495 "via_count|"
496 "via_diameter|"
497 "zone_connection" );
498 }
499 else if( sexprs.top() == wxT( "disallow" ) || isDisallowToken( sexprs.top() ) )
500 {
501 tokens = wxT( "buried_via|"
502 "footprint|"
503 "graphic|"
504 "hole|"
505 "micro_via|"
506 "pad|"
507 "text|"
508 "track|"
509 "via|"
510 "zone" );
511 }
512 else if( sexprs.top() == wxT( "zone_connection" ) )
513 {
514 tokens = wxT( "none|solid|thermal_reliefs" );
515 }
516 else if( sexprs.top() == wxT( "min_resolved_spokes" ) )
517 {
518 tokens = wxT( "0|1|2|3|4" );
519 }
520 else if( sexprs.top() == wxT( "layer" ) )
521 {
522 tokens = wxT( "inner|outer|\"x\"" );
523 }
524 else if( sexprs.top() == wxT( "severity" ) )
525 {
526 tokens = wxT( "warning|error|ignore|exclusion" );
527 }
528 }
529 else if( context == SEXPR_STRING && !sexprs.empty()
530 && ( sexprs.top() == wxT( "condition" ) || sexprs.top() == wxT( "assertion" ) ) )
531 {
532 m_textEditor->AddText( wxT( "\"" ) );
533 }
534 else if( context == STRING && !sexprs.empty()
535 && ( sexprs.top() == wxT( "condition" ) || sexprs.top() == wxT( "assertion" ) ) )
536 {
537 if( expr_context == STRUCT_REF )
538 {
540 std::set<wxString> propNames;
541
542 for( const PROPERTY_MANAGER::CLASS_INFO& cls : propMgr.GetAllClasses() )
543 {
544 const std::vector<PROPERTY_BASE*>& props = propMgr.GetProperties( cls.type );
545
546 for( PROPERTY_BASE* prop : props )
547 {
548 // TODO: It would be nice to replace IsHiddenFromRulesEditor with a nickname
549 // system, so that two different properies don't need to be created. This is
550 // a bigger change than I want to make right now, though.
551 if( prop->IsHiddenFromRulesEditor() )
552 continue;
553
554 wxString ref( prop->Name() );
555 ref.Replace( wxT( " " ), wxT( "_" ) );
556 propNames.insert( ref );
557 }
558 }
559
560 for( const wxString& propName : propNames )
561 tokens += wxT( "|" ) + propName;
562
564
565 for( const wxString& funcSig : functions.GetSignatures() )
566 {
567 if( !funcSig.Contains( "DEPRECATED" ) )
568 tokens += wxT( "|" ) + funcSig;
569 }
570 }
571 else if( expr_context == STRING )
572 {
573 if( m_netClassRegex.Matches( last ) )
574 {
576 std::shared_ptr<NET_SETTINGS>& netSettings = bds.m_NetSettings;
577
578 for( const auto& [name, netclass] : netSettings->GetNetclasses() )
579 tokens += wxT( "|" ) + name;
580 }
581 else if( m_netNameRegex.Matches( last ) )
582 {
583 BOARD* board = m_frame->GetBoard();
584
585 for( const wxString& netnameCandidate : board->GetNetClassAssignmentCandidates() )
586 tokens += wxT( "|" ) + netnameCandidate;
587 }
588 else if( m_typeRegex.Matches( last ) )
589 {
590 tokens = wxT( "Bitmap|"
591 "Dimension|"
592 "Footprint|"
593 "Graphic|"
594 "Group|"
595 "Leader|"
596 "Pad|"
597 "Target|"
598 "Text|"
599 "Text Box|"
600 "Track|"
601 "Via|"
602 "Zone" );
603 }
604 else if( m_viaTypeRegex.Matches( last ) )
605 {
606 tokens = wxT( "Through|"
607 "Blind/buried|"
608 "Micro" );
609 }
610 else if( m_padTypeRegex.Matches( last ) )
611 {
612 tokens = wxT( "Through-hole|"
613 "SMD|"
614 "Edge connector|"
615 "NPTH, mechanical" );
616 }
617 else if( m_pinTypeRegex.Matches( last ) )
618 {
619 tokens = wxT( "Input|"
620 "Output|"
621 "Bidirectional|"
622 "Tri-state|"
623 "Passive|"
624 "Free|"
625 "Unspecified|"
626 "Power input|"
627 "Power output|"
628 "Open collector|"
629 "Open emitter|"
630 "Unconnected" );
631 }
632 else if( m_fabPropRegex.Matches( last ) )
633 {
634 tokens = wxT( "None|"
635 "BGA pad|"
636 "Fiducial, global to board|"
637 "Fiducial, local to footprint|"
638 "Test point pad|"
639 "Heatsink pad|"
640 "Castellated pad" );
641 }
642 else if( m_shapeRegex.Matches( last ) )
643 {
644 tokens = wxT( "Segment|"
645 "Rectangle|"
646 "Arc|"
647 "Circle|"
648 "Polygon|"
649 "Bezier" );
650 }
651 else if( m_padShapeRegex.Matches( last ) )
652 {
653 tokens = wxT( "Circle|"
654 "Rectangle|"
655 "Oval|"
656 "Trapezoid|"
657 "Rounded rectangle|"
658 "Chamfered rectangle|"
659 "Custom" );
660 }
661 else if( m_padConnectionsRegex.Matches( last ) )
662 {
663 tokens = wxT( "Inherited|"
664 "None|"
665 "Solid|"
666 "Thermal reliefs|"
667 "Thermal reliefs for PTH" );
668 }
669 else if( m_zoneConnStyleRegex.Matches( last ) )
670 {
671 tokens = wxT( "Inherited|"
672 "None|"
673 "Solid|"
674 "Thermal reliefs" );
675 }
676 else if( m_lineStyleRegex.Matches( last ) )
677 {
678 tokens = wxT( "Default|"
679 "Solid|"
680 "Dashed|"
681 "Dotted|"
682 "Dash-Dot|"
683 "Dash-Dot-Dot" );
684 }
685 else if( m_hJustRegex.Matches( last ) )
686 {
687 tokens = wxT( "Left|"
688 "Center|"
689 "Right" );
690 }
691 else if( m_vJustRegex.Matches( last ) )
692 {
693 tokens = wxT( "Top|"
694 "Center|"
695 "Bottom" );
696 }
697 }
698 }
699
700 if( !tokens.IsEmpty() )
701 m_scintillaTricks->DoAutocomplete( partial, wxSplit( tokens, '|' ) );
702}
703
704
705void PANEL_SETUP_RULES::OnCompile( wxCommandEvent& event )
706{
708
709 try
710 {
711 std::vector<std::shared_ptr<DRC_RULE>> dummyRules;
712
713 std::function<bool( wxString* )> resolver =
714 [&]( wxString* token ) -> bool
715 {
716 if( m_frame->Prj().TextVarResolver( token ) )
717 return true;
718
719 return false;
720 };
721
722 wxString rulesText = ExpandTextVars( m_textEditor->GetText(), &resolver );
723
724 DRC_RULES_PARSER parser( rulesText, _( "DRC rules" ) );
725
726 parser.Parse( dummyRules, m_errorsReport );
727 checkPlausibility( dummyRules );
728 }
729 catch( PARSE_ERROR& pe )
730 {
731 wxString msg = wxString::Format( wxT( "%s <a href='%d:%d'>%s</a>%s" ),
732 _( "ERROR:" ),
733 pe.lineNumber,
734 pe.byteIndex,
735 pe.ParseProblem(),
736 wxEmptyString );
737
739 }
740
742}
743
744
745void PANEL_SETUP_RULES::checkPlausibility( const std::vector<std::shared_ptr<DRC_RULE>>& aRules )
746{
747 BOARD* board = m_frame->GetBoard();
749 LSET enabledLayers = board->GetEnabledLayers();
750
751 std::unordered_map<wxString, wxString> seenConditions;
752 std::regex netclassPattern( "NetClass\\s*[!=]=\\s*\"?([^\"\\s]+)\"?" );
753
754 for( const auto& rule : aRules )
755 {
756 wxString condition;
757
758 if( rule->m_Condition )
759 condition = rule->m_Condition->GetExpression();
760
761 condition.Trim( true ).Trim( false );
762
763 if( seenConditions.count( condition ) )
764 {
765 wxString msg = wxString::Format( _( "Rules '%s' and '%s' share the same condition." ), rule->m_Name,
766 seenConditions[condition] );
768 }
769 else
770 {
771 seenConditions[condition] = rule->m_Name;
772 }
773
774 std::string condUtf8 = condition.ToStdString();
775 std::sregex_iterator it( condUtf8.begin(), condUtf8.end(), netclassPattern );
776 std::sregex_iterator end;
777
778 for( ; it != end; ++it )
779 {
780 wxString ncName = wxString::FromUTF8( ( *it )[1].str() );
781
782 if( !bds.m_NetSettings->HasNetclass( ncName ) )
783 {
784 wxString msg =
785 wxString::Format( _( "Rule '%s' references undefined netclass '%s'." ), rule->m_Name, ncName );
787 }
788 }
789
790 if( !rule->m_LayerSource.IsEmpty() )
791 {
792 LSET invalid = rule->m_LayerCondition & ~enabledLayers;
793
794 if( invalid.any() )
795 {
796 wxString badLayers;
797
798 for( PCB_LAYER_ID layer : invalid.Seq() )
799 {
800 if( !badLayers.IsEmpty() )
801 badLayers += ", ";
802
803 badLayers += board->GetLayerName( layer );
804 }
805
806 wxString msg = wxString::Format( _( "Rule '%s' references undefined layer(s): %s." ), rule->m_Name,
807 badLayers );
809 }
810 }
811 }
812}
813
814
815void PANEL_SETUP_RULES::OnErrorLinkClicked( wxHtmlLinkEvent& event )
816{
817 wxString link = event.GetLinkInfo().GetHref();
818 wxArrayString parts;
819 long line = 0, offset = 0;
820
821 wxStringSplit( link, parts, ':' );
822
823 if( parts.size() > 1 )
824 {
825 parts[0].ToLong( &line );
826 parts[1].ToLong( &offset );
827 }
828
829 int pos = m_textEditor->PositionFromLine( line - 1 ) + ( offset - 1 );
830
831 m_textEditor->GotoPos( pos );
832
833 m_textEditor->SetFocus();
834}
835
836
838{
839 wxFileName rulesFile( m_frame->GetDesignRulesPath() );
840
841 if( rulesFile.FileExists() )
842 {
843 wxTextFile file( rulesFile.GetFullPath() );
844
845 if( file.Open() )
846 {
847 for ( wxString str = file.GetFirstLine(); !file.Eof(); str = file.GetNextLine() )
848 {
850 m_textEditor->AddText( str << '\n' );
851 }
852
853 m_textEditor->EmptyUndoBuffer();
854
855 wxCommandEvent dummy;
856 OnCompile( dummy );
857 }
858 }
859 else
860 {
861 m_textEditor->AddText( wxT( "(version 1)\n" ) );
862 }
863
864 m_originalText = m_textEditor->GetText();
865
866 if( m_frame->Prj().IsNullProject() )
867 {
868 m_textEditor->ClearAll();
869 m_textEditor->AddText( _( "Design rules cannot be added without a project" ) );
870 m_textEditor->Disable();
871 }
872
873 return true;
874}
875
876
878{
879 if( m_originalText == m_textEditor->GetText() )
880 return true;
881
882 if( m_frame->Prj().IsNullProject() )
883 return true;
884
885 wxString rulesFilepath = m_frame->GetDesignRulesPath();
886
887 try
888 {
889 if( m_textEditor->SaveFile( rulesFilepath ) )
890 {
891 m_frame->GetBoard()->GetDesignSettings().m_DRCEngine->InitEngine( rulesFilepath );
892 return true;
893 }
894 }
895 catch( PARSE_ERROR& )
896 {
897 // Don't lock them in to the Setup dialog if they have bad rules. They've already
898 // saved them so we can allow an exit.
899 return true;
900 }
901
902 return false;
903}
904
905
906void PANEL_SETUP_RULES::OnSyntaxHelp( wxHyperlinkEvent& aEvent )
907{
908 if( m_helpWindow )
909 {
911 return;
912 }
913 std::vector<wxString> msg;
914 msg.clear();
915
916 wxString t =
918 ;
919 msg.emplace_back( t );
920 t =
922 ;
923 msg.emplace_back( t );
924 t =
926 ;
927 msg.emplace_back( t );
928 t =
930 ;
931 msg.emplace_back( t );
932 t =
934 ;
935 msg.emplace_back( t );
936 t =
938 ;
939 msg.emplace_back( t );
940 t =
942 ;
943 msg.emplace_back( t );
944 t =
946 ;
947 msg.emplace_back( t );
948 t =
950 ;
951 msg.emplace_back( t );
952 t =
954 ;
955 msg.emplace_back( t );
956
957 wxString msg_txt = wxEmptyString;
958
959 for( wxString i : msg )
960 msg_txt << wxGetTranslation( i );
961
962#ifdef __WXMAC__
963 msg_txt.Replace( wxT( "Ctrl+" ), wxT( "Cmd+" ) );
964#endif
965 const wxString& msGg_txt = msg_txt;
966
967 m_helpWindow = new HTML_MESSAGE_BOX( nullptr, _( "Syntax Help" ) );
968 m_helpWindow->SetDialogSizeInDU( 420, 320 );
969
970 wxString html_txt = wxEmptyString;
971 ConvertMarkdown2Html( msGg_txt, html_txt );
972
973 html_txt.Replace( wxS( "<td" ), wxS( "<td valign=top" ) );
974 m_helpWindow->AddHTML_Text( html_txt );
975
977}
978
979
981{
982 if( !m_frame->Prj().IsNullProject() )
983 {
984 wxFileName relFile = aBoard->GetFileName();
985 relFile.SetExt( FILEEXT::DesignRulesFileExtension );
986
987 wxFileName absFile( aBoard->GetProject()->AbsolutePath( relFile.GetFullName() ) );
988
989 if( absFile.FileExists() )
990 {
991 wxTextFile file( absFile.GetFullPath() );
992
993 if( file.Open() )
994 {
995 m_textEditor->ClearAll();
996
997 for ( wxString str = file.GetFirstLine(); !file.Eof(); str = file.GetNextLine() )
998 {
1000 m_textEditor->AddText( str << '\n' );
1001 }
1002
1003 m_textEditor->EmptyUndoBuffer();
1004
1005 wxCommandEvent dummy;
1006 OnCompile( dummy );
1007 }
1008 }
1009 }
1010}
const char * name
Definition: DXF_plotter.cpp:62
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition: bitmap.cpp:110
Container for design settings for a BOARD object.
std::shared_ptr< NET_SETTINGS > m_NetSettings
std::shared_ptr< DRC_ENGINE > m_DRCEngine
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:317
std::set< wxString > GetNetClassAssignmentCandidates() const
Return the set of netname candidates for netclass assignment.
Definition: board.cpp:2207
const wxString & GetFileName() const
Definition: board.h:354
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition: board.cpp:680
PROJECT * GetProject() const
Definition: board.h:538
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:1024
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition: board.cpp:907
APPEARANCE m_Appearance
void Parse(std::vector< std::shared_ptr< DRC_RULE > > &aRules, REPORTER *aReporter)
void SetDialogSizeInDU(int aWidth, int aHeight)
Set the dialog size, using a "logical" value.
void AddHTML_Text(const wxString &message)
Add HTML text (without any change) to message list.
void ShowModeless()
Show a modeless version of the dialog (without an OK button).
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
LSET is a set of PCB_LAYER_IDs.
Definition: lset.h:37
LSEQ Seq(const LSEQ &aSequence) const
Return an LSEQ from the union of this LSET and a desired sequence.
Definition: lset.cpp:296
void SetModified()
Definition: paged_dialog.h:43
static PAGED_DIALOG * GetDialog(wxWindow *aWindow)
Class PANEL_SETUP_RULES_BASE.
STD_BITMAP_BUTTON * m_compileButton
wxStyledTextCtrl * m_textEditor
WX_HTML_REPORT_BOX * m_errorsReport
bool TransferDataToWindow() override
void OnErrorLinkClicked(wxHtmlLinkEvent &event) override
void checkPlausibility(const std::vector< std::shared_ptr< DRC_RULE > > &aRules)
void onScintillaCharAdded(wxStyledTextEvent &aEvent)
void ImportSettingsFrom(BOARD *aBoard)
PCB_EDIT_FRAME * m_frame
void OnContextMenu(wxMouseEvent &event) override
~PANEL_SETUP_RULES() override
HTML_MESSAGE_BOX * m_helpWindow
void OnCompile(wxCommandEvent &event) override
bool TransferDataFromWindow() override
void OnSyntaxHelp(wxHyperlinkEvent &aEvent) override
PANEL_SETUP_RULES(wxWindow *aParentWindow, PCB_EDIT_FRAME *aFrame)
void onCharHook(wxKeyEvent &aEvent)
SCINTILLA_TRICKS * m_scintillaTricks
const wxArrayString GetSignatures() const
static PCBEXPR_BUILTIN_FUNCTIONS & Instance()
wxString GetDesignRulesPath()
Return the absolute path to the design rules file for the currently-loaded board.
BOARD * GetBoard() const
The main frame for Pcbnew.
virtual COMMON_SETTINGS * GetCommonSettings() const
Definition: pgm_base.cpp:565
virtual bool TextVarResolver(wxString *aToken) const
Definition: project.cpp:76
virtual const wxString AbsolutePath(const wxString &aFileName) const
Fix up aFileName if it is relative to the project's directory to be an absolute path and filename.
Definition: project.cpp:373
virtual bool IsNullProject() const
Check if this project is a null project (i.e.
Definition: project.cpp:167
Provide class metadata.Helper macro to map type hashes to names.
Definition: property_mgr.h:74
const std::vector< PROPERTY_BASE * > & GetProperties(TYPE_ID aType) const
Return all properties for a specific type.
CLASSES_INFO GetAllClasses()
static PROPERTY_MANAGER & Instance()
Definition: property_mgr.h:76
Add cut/copy/paste, dark theme, autocomplete and brace highlighting to a wxStyleTextCtrl instance.
void DoAutocomplete(const wxString &aPartial, const wxArrayString &aTokens)
void SetBitmap(const wxBitmapBundle &aBmp)
void Clear() override
Delete the stored messages.
REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED) override
Report a string with a given severity.
void Flush()
Build the HTML messages page.
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, int aFlags)
Definition: common.cpp:59
bool IsOK(wxWindow *aParent, const wxString &aMessage)
Display a yes/no dialog with aMessage and returns the user response.
Definition: confirm.cpp:251
This file is part of the common library.
#define _(s)
static FILENAME_RESOLVER * resolver
Definition: export_idf.cpp:53
static const std::string DesignRulesFileExtension
PCB_LAYER_ID
A quick note on layer IDs:
Definition: layer_ids.h:60
PGM_BASE & Pgm()
The global program "get" accessor.
Definition: pgm_base.cpp:902
see class PGM_BASE
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
std::vector< FAB_LAYER_COLOR > dummy
bool ConvertSmartQuotesAndDashes(wxString *aString)
Convert curly quotes and em/en dashes to straight quotes and dashes.
void wxStringSplit(const wxString &aText, wxArrayString &aStrings, wxChar aSplitter)
Split aString to a string list separated at aSplitter.
void ConvertMarkdown2Html(const wxString &aMarkdownInput, wxString &aHtmlOutput)
A filename or source description, a problem input line, a line number, a byte offset,...
Definition: ki_exception.h:120
int lineNumber
at which line number, 1 based index.
Definition: ki_exception.h:121
const wxString ParseProblem()
Definition: ki_exception.h:151
int byteIndex
at which byte offset within the line, 1 based index
Definition: ki_exception.h:122
VECTOR2I end
Definition of file extensions used in Kicad.