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
84
85 m_textEditor->SetZoom( Pgm().GetCommonSettings()->m_Appearance.text_editor_zoom );
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 "bridged_mask|"
469 "clearance|"
470 "connection_width|"
471 "courtyard_clearance|"
472 "creepage|"
473 "diff_pair_gap|"
474 "diff_pair_uncoupled|"
475 "disallow|"
476 "edge_clearance|"
477 "length|"
478 "hole_clearance|"
479 "hole_size|"
480 "hole_to_hole|"
481 "min_resolved_spokes|"
482 "physical_clearance|"
483 "physical_hole_clearance|"
484 "silk_clearance|"
485 "skew|"
486 "solder_mask_expansion|"
487 "solder_paste_abs_margin|"
488 "solder_paste_rel_margin|"
489 "text_height|"
490 "text_thickness|"
491 "thermal_relief_gap|"
492 "thermal_spoke_width|"
493 "track_width|"
494 "track_angle|"
495 "track_segment_length|"
496 "via_count|"
497 "via_diameter|"
498 "zone_connection" );
499 }
500 else if( sexprs.top() == wxT( "disallow" ) || isDisallowToken( sexprs.top() ) )
501 {
502 tokens = wxT( "buried_via|"
503 "footprint|"
504 "graphic|"
505 "hole|"
506 "micro_via|"
507 "pad|"
508 "text|"
509 "track|"
510 "via|"
511 "zone" );
512 }
513 else if( sexprs.top() == wxT( "zone_connection" ) )
514 {
515 tokens = wxT( "none|solid|thermal_reliefs" );
516 }
517 else if( sexprs.top() == wxT( "min_resolved_spokes" ) )
518 {
519 tokens = wxT( "0|1|2|3|4" );
520 }
521 else if( sexprs.top() == wxT( "layer" ) )
522 {
523 tokens = wxT( "inner|outer|\"x\"" );
524 }
525 else if( sexprs.top() == wxT( "severity" ) )
526 {
527 tokens = wxT( "warning|error|ignore|exclusion" );
528 }
529 }
530 else if( context == SEXPR_STRING && !sexprs.empty()
531 && ( sexprs.top() == wxT( "condition" ) || sexprs.top() == wxT( "assertion" ) ) )
532 {
533 m_textEditor->AddText( wxT( "\"" ) );
534 }
535 else if( context == STRING && !sexprs.empty()
536 && ( sexprs.top() == wxT( "condition" ) || sexprs.top() == wxT( "assertion" ) ) )
537 {
538 if( expr_context == STRUCT_REF )
539 {
541 std::set<wxString> propNames;
542
543 for( const PROPERTY_MANAGER::CLASS_INFO& cls : propMgr.GetAllClasses() )
544 {
545 const std::vector<PROPERTY_BASE*>& props = propMgr.GetProperties( cls.type );
546
547 for( PROPERTY_BASE* prop : props )
548 {
549 // TODO: It would be nice to replace IsHiddenFromRulesEditor with a nickname
550 // system, so that two different properies don't need to be created. This is
551 // a bigger change than I want to make right now, though.
552 if( prop->IsHiddenFromRulesEditor() )
553 continue;
554
555 wxString ref( prop->Name() );
556 ref.Replace( wxT( " " ), wxT( "_" ) );
557 propNames.insert( ref );
558 }
559 }
560
561 for( const wxString& propName : propNames )
562 tokens += wxT( "|" ) + propName;
563
565
566 for( const wxString& funcSig : functions.GetSignatures() )
567 {
568 if( !funcSig.Contains( "DEPRECATED" ) )
569 tokens += wxT( "|" ) + funcSig;
570 }
571 }
572 else if( expr_context == STRING )
573 {
574 if( m_netClassRegex.Matches( last ) )
575 {
576 BOARD_DESIGN_SETTINGS& bds = m_frame->GetBoard()->GetDesignSettings();
577 std::shared_ptr<NET_SETTINGS>& netSettings = bds.m_NetSettings;
578
579 for( const auto& [name, netclass] : netSettings->GetNetclasses() )
580 tokens += wxT( "|" ) + name;
581 }
582 else if( m_netNameRegex.Matches( last ) )
583 {
584 BOARD* board = m_frame->GetBoard();
585
586 for( const wxString& netnameCandidate : board->GetNetClassAssignmentCandidates() )
587 tokens += wxT( "|" ) + netnameCandidate;
588 }
589 else if( m_typeRegex.Matches( last ) )
590 {
591 tokens = wxT( "Bitmap|"
592 "Dimension|"
593 "Footprint|"
594 "Graphic|"
595 "Group|"
596 "Leader|"
597 "Pad|"
598 "Target|"
599 "Text|"
600 "Text Box|"
601 "Track|"
602 "Via|"
603 "Zone" );
604 }
605 else if( m_viaTypeRegex.Matches( last ) )
606 {
607 tokens = wxT( "Through|"
608 "Blind/buried|"
609 "Micro" );
610 }
611 else if( m_padTypeRegex.Matches( last ) )
612 {
613 tokens = wxT( "Through-hole|"
614 "SMD|"
615 "Edge connector|"
616 "NPTH, mechanical" );
617 }
618 else if( m_pinTypeRegex.Matches( last ) )
619 {
620 tokens = wxT( "Input|"
621 "Output|"
622 "Bidirectional|"
623 "Tri-state|"
624 "Passive|"
625 "Free|"
626 "Unspecified|"
627 "Power input|"
628 "Power output|"
629 "Open collector|"
630 "Open emitter|"
631 "Unconnected" );
632 }
633 else if( m_fabPropRegex.Matches( last ) )
634 {
635 tokens = wxT( "None|"
636 "BGA pad|"
637 "Fiducial, global to board|"
638 "Fiducial, local to footprint|"
639 "Test point pad|"
640 "Heatsink pad|"
641 "Castellated pad" );
642 }
643 else if( m_shapeRegex.Matches( last ) )
644 {
645 tokens = wxT( "Segment|"
646 "Rectangle|"
647 "Arc|"
648 "Circle|"
649 "Polygon|"
650 "Bezier" );
651 }
652 else if( m_padShapeRegex.Matches( last ) )
653 {
654 tokens = wxT( "Circle|"
655 "Rectangle|"
656 "Oval|"
657 "Trapezoid|"
658 "Rounded rectangle|"
659 "Chamfered rectangle|"
660 "Custom" );
661 }
662 else if( m_padConnectionsRegex.Matches( last ) )
663 {
664 tokens = wxT( "Inherited|"
665 "None|"
666 "Solid|"
667 "Thermal reliefs|"
668 "Thermal reliefs for PTH" );
669 }
670 else if( m_zoneConnStyleRegex.Matches( last ) )
671 {
672 tokens = wxT( "Inherited|"
673 "None|"
674 "Solid|"
675 "Thermal reliefs" );
676 }
677 else if( m_lineStyleRegex.Matches( last ) )
678 {
679 tokens = wxT( "Default|"
680 "Solid|"
681 "Dashed|"
682 "Dotted|"
683 "Dash-Dot|"
684 "Dash-Dot-Dot" );
685 }
686 else if( m_hJustRegex.Matches( last ) )
687 {
688 tokens = wxT( "Left|"
689 "Center|"
690 "Right" );
691 }
692 else if( m_vJustRegex.Matches( last ) )
693 {
694 tokens = wxT( "Top|"
695 "Center|"
696 "Bottom" );
697 }
698 }
699 }
700
701 if( !tokens.IsEmpty() )
702 m_scintillaTricks->DoAutocomplete( partial, wxSplit( tokens, '|' ) );
703}
704
705
706void PANEL_SETUP_RULES::OnCompile( wxCommandEvent& event )
707{
708 m_errorsReport->Clear();
709
710 try
711 {
712 std::vector<std::shared_ptr<DRC_RULE>> dummyRules;
713
714 std::function<bool( wxString* )> resolver =
715 [&]( wxString* token ) -> bool
716 {
717 if( m_frame->Prj().TextVarResolver( token ) )
718 return true;
719
720 return false;
721 };
722
723 wxString rulesText = ExpandTextVars( m_textEditor->GetText(), &resolver );
724
725 DRC_RULES_PARSER parser( rulesText, _( "DRC rules" ) );
726
727 parser.Parse( dummyRules, m_errorsReport );
728 checkPlausibility( dummyRules );
729 }
730 catch( PARSE_ERROR& pe )
731 {
732 wxString msg = wxString::Format( wxT( "%s <a href='%d:%d'>%s</a>%s" ),
733 _( "ERROR:" ),
734 pe.lineNumber,
735 pe.byteIndex,
736 pe.ParseProblem(),
737 wxEmptyString );
738
739 m_errorsReport->Report( msg, RPT_SEVERITY_ERROR );
740 }
741
742 m_errorsReport->Flush();
743}
744
745
746void PANEL_SETUP_RULES::checkPlausibility( const std::vector<std::shared_ptr<DRC_RULE>>& aRules )
747{
748 BOARD* board = m_frame->GetBoard();
750 LSET enabledLayers = board->GetEnabledLayers();
751
752 std::unordered_map<wxString, wxString> seenConditions;
753 std::regex netclassPattern( "NetClass\\s*[!=]=\\s*\"?([^\"\\s]+)\"?" );
754
755 for( const auto& rule : aRules )
756 {
757 wxString condition;
758
759 if( rule->m_Condition )
760 condition = rule->m_Condition->GetExpression();
761
762 condition.Trim( true ).Trim( false );
763
764 if( seenConditions.count( condition ) )
765 {
766 wxString msg = wxString::Format( _( "Rules '%s' and '%s' share the same condition." ), rule->m_Name,
767 seenConditions[condition] );
768 m_errorsReport->Report( msg, RPT_SEVERITY_WARNING );
769 }
770 else
771 {
772 seenConditions[condition] = rule->m_Name;
773 }
774
775 std::string condUtf8 = condition.ToStdString();
776 std::sregex_iterator it( condUtf8.begin(), condUtf8.end(), netclassPattern );
777 std::sregex_iterator end;
778
779 for( ; it != end; ++it )
780 {
781 wxString ncName = wxString::FromUTF8( ( *it )[1].str() );
782
783 if( !bds.m_NetSettings->HasNetclass( ncName ) )
784 {
785 wxString msg =
786 wxString::Format( _( "Rule '%s' references undefined netclass '%s'." ), rule->m_Name, ncName );
787 m_errorsReport->Report( msg, RPT_SEVERITY_WARNING );
788 }
789 }
790
791 if( !rule->m_LayerSource.IsEmpty() )
792 {
793 LSET invalid = rule->m_LayerCondition & ~enabledLayers;
794
795 if( invalid.any() )
796 {
797 wxString badLayers;
798
799 for( PCB_LAYER_ID layer : invalid.Seq() )
800 {
801 if( !badLayers.IsEmpty() )
802 badLayers += ", ";
803
804 badLayers += board->GetLayerName( layer );
805 }
806
807 wxString msg = wxString::Format( _( "Rule '%s' references undefined layer(s): %s." ), rule->m_Name,
808 badLayers );
809 m_errorsReport->Report( msg, RPT_SEVERITY_WARNING );
810 }
811 }
812 }
813}
814
815
816void PANEL_SETUP_RULES::OnErrorLinkClicked( wxHtmlLinkEvent& event )
817{
818 wxString link = event.GetLinkInfo().GetHref();
819 wxArrayString parts;
820 long line = 0, offset = 0;
821
822 wxStringSplit( link, parts, ':' );
823
824 if( parts.size() > 1 )
825 {
826 parts[0].ToLong( &line );
827 parts[1].ToLong( &offset );
828 }
829
830 int pos = m_textEditor->PositionFromLine( line - 1 ) + ( offset - 1 );
831
832 m_textEditor->GotoPos( pos );
833
834 m_textEditor->SetFocus();
835}
836
837
839{
840 wxFileName rulesFile( m_frame->GetDesignRulesPath() );
841
842 if( rulesFile.FileExists() )
843 {
844 wxTextFile file( rulesFile.GetFullPath() );
845
846 if( file.Open() )
847 {
848 for ( wxString str = file.GetFirstLine(); !file.Eof(); str = file.GetNextLine() )
849 {
851 m_textEditor->AddText( str << '\n' );
852 }
853
854 m_textEditor->EmptyUndoBuffer();
855
856 wxCommandEvent dummy;
857 OnCompile( dummy );
858 }
859 }
860 else
861 {
862 m_textEditor->AddText( wxT( "(version 1)\n" ) );
863 }
864
865 m_originalText = m_textEditor->GetText();
866
867 if( m_frame->Prj().IsNullProject() )
868 {
869 m_textEditor->ClearAll();
870 m_textEditor->AddText( _( "Design rules cannot be added without a project" ) );
871 m_textEditor->Disable();
872 }
873
874 return true;
875}
876
877
879{
880 if( m_originalText == m_textEditor->GetText() )
881 return true;
882
883 if( m_frame->Prj().IsNullProject() )
884 return true;
885
886 wxString rulesFilepath = m_frame->GetDesignRulesPath();
887
888 try
889 {
890 if( m_textEditor->SaveFile( rulesFilepath ) )
891 {
892 m_frame->GetBoard()->GetDesignSettings().m_DRCEngine->InitEngine( rulesFilepath );
893 return true;
894 }
895 }
896 catch( PARSE_ERROR& )
897 {
898 // Don't lock them in to the Setup dialog if they have bad rules. They've already
899 // saved them so we can allow an exit.
900 return true;
901 }
902
903 return false;
904}
905
906
907void PANEL_SETUP_RULES::OnSyntaxHelp( wxHyperlinkEvent& aEvent )
908{
909 if( m_helpWindow )
910 {
911 m_helpWindow->ShowModeless();
912 return;
913 }
914 std::vector<wxString> msg;
915 msg.clear();
916
917 wxString t =
919 ;
920 msg.emplace_back( t );
921 t =
923 ;
924 msg.emplace_back( t );
925 t =
927 ;
928 msg.emplace_back( t );
929 t =
931 ;
932 msg.emplace_back( t );
933 t =
935 ;
936 msg.emplace_back( t );
937 t =
939 ;
940 msg.emplace_back( t );
941 t =
943 ;
944 msg.emplace_back( t );
945 t =
947 ;
948 msg.emplace_back( t );
949 t =
951 ;
952 msg.emplace_back( t );
953 t =
955 ;
956 msg.emplace_back( t );
957
958 wxString msg_txt = wxEmptyString;
959
960 for( wxString i : msg )
961 msg_txt << wxGetTranslation( i );
962
963#ifdef __WXMAC__
964 msg_txt.Replace( wxT( "Ctrl+" ), wxT( "Cmd+" ) );
965#endif
966 const wxString& msGg_txt = msg_txt;
967
968 m_helpWindow = new HTML_MESSAGE_BOX( nullptr, _( "Syntax Help" ) );
969 m_helpWindow->SetDialogSizeInDU( 420, 320 );
970
971 wxString html_txt = wxEmptyString;
972 ConvertMarkdown2Html( msGg_txt, html_txt );
973
974 html_txt.Replace( wxS( "<td" ), wxS( "<td valign=top" ) );
975 m_helpWindow->AddHTML_Text( html_txt );
976
977 m_helpWindow->ShowModeless();
978}
979
980
982{
983 if( !m_frame->Prj().IsNullProject() )
984 {
985 wxFileName relFile = aBoard->GetFileName();
986 relFile.SetExt( FILEEXT::DesignRulesFileExtension );
987
988 wxFileName absFile( aBoard->GetProject()->AbsolutePath( relFile.GetFullName() ) );
989
990 if( absFile.FileExists() )
991 {
992 wxTextFile file( absFile.GetFullPath() );
993
994 if( file.Open() )
995 {
996 m_textEditor->ClearAll();
997
998 for ( wxString str = file.GetFirstLine(); !file.Eof(); str = file.GetNextLine() )
999 {
1001 m_textEditor->AddText( str << '\n' );
1002 }
1003
1004 m_textEditor->EmptyUndoBuffer();
1005
1006 wxCommandEvent dummy;
1007 OnCompile( dummy );
1008 }
1009 }
1010 }
1011}
const char * name
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
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:2260
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:691
PROJECT * GetProject() const
Definition board.h:537
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1040
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:923
APPEARANCE m_Appearance
void Parse(std::vector< std::shared_ptr< DRC_RULE > > &aRules, REPORTER *aReporter)
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
bool HasNetclass(const wxString &netclassName) const
Determines if the given netclass exists.
void SetModified()
static PAGED_DIALOG * GetDialog(wxWindow *aWindow)
STD_BITMAP_BUTTON * m_compileButton
wxStyledTextCtrl * m_textEditor
PANEL_SETUP_RULES_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(-1,-1), long style=wxTAB_TRAVERSAL, const wxString &name=wxEmptyString)
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
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()
The main frame for Pcbnew.
virtual COMMON_SETTINGS * GetCommonSettings() const
Definition pgm_base.cpp:576
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:386
Provide class metadata.Helper macro to map type hashes to names.
const std::vector< PROPERTY_BASE * > & GetProperties(TYPE_ID aType) const
Return all properties for a specific type.
CLASSES_INFO GetAllClasses()
static PROPERTY_MANAGER & Instance()
Add cut/copy/paste, dark theme, autocomplete and brace highlighting to a wxStyleTextCtrl instance.
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
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:913
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,...
int lineNumber
at which line number, 1 based index.
const wxString ParseProblem()
int byteIndex
at which byte offset within the line, 1 based index
VECTOR2I end
Definition of file extensions used in Kicad.