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( "blind_via" )
233 || token == wxT( "buried_via" )
234 || token == wxT( "graphic" )
235 || token == wxT( "hole" )
236 || token == wxT( "micro_via" )
237 || token == wxT( "pad" )
238 || token == wxT( "text" )
239 || token == wxT( "through_via" )
240 || token == wxT( "track" )
241 || token == wxT( "via" )
242 || token == wxT( "zone" );
243 };
244
245 auto isConstraintTypeToken =
246 []( const wxString& token ) -> bool
247 {
248 return token == wxT( "annular_width" )
249 || token == wxT( "assertion" )
250 || token == wxT( "clearance" )
251 || token == wxT( "connection_width" )
252 || token == wxT( "courtyard_clearance" )
253 || token == wxT( "diff_pair_gap" )
254 || token == wxT( "diff_pair_uncoupled" )
255 || token == wxT( "disallow" )
256 || token == wxT( "edge_clearance" )
257 || token == wxT( "length" )
258 || token == wxT( "hole_clearance" )
259 || token == wxT( "hole_size" )
260 || token == wxT( "hole_to_hole" )
261 || token == wxT( "min_resolved_spokes" )
262 || token == wxT( "physical_clearance" )
263 || token == wxT( "physical_hole_clearance" )
264 || token == wxT( "silk_clearance" )
265 || token == wxT( "skew" )
266 || token == wxT( "solder_mask_expansion" )
267 || token == wxT( "solder_paste_abs_margin" )
268 || token == wxT( "solder_paste_rel_margin" )
269 || token == wxT( "text_height" )
270 || token == wxT( "text_thickness" )
271 || token == wxT( "thermal_relief_gap" )
272 || token == wxT( "thermal_spoke_width" )
273 || token == wxT( "track_width" )
274 || token == wxT( "track_angle" )
275 || token == wxT( "track_segment_length" )
276 || token == wxT( "via_count" )
277 || token == wxT( "via_diameter" )
278 || token == wxT( "zone_connection" );
279 };
280
281 std::stack<wxString> sexprs;
282 wxString partial;
283 wxString last;
284 wxString constraintType;
285 int context = NO_CONTEXT;
286 int expr_context = NO_CONTEXT;
287
288 for( int i = startPos; i < currentPos; ++i )
289 {
290 wxChar c = m_textEditor->GetCharAt( i );
291
292 if( c == '\\' )
293 {
294 i++; // skip escaped char
295 }
296 else if( context == STRING )
297 {
298 if( c == '"' )
299 {
300 context = NO_CONTEXT;
301 }
302 else
303 {
304 if( expr_context == STRING )
305 {
306 if( c == '\'' )
307 expr_context = NO_CONTEXT;
308 else
309 partial += c;
310 }
311 else if( c == '\'' )
312 {
313 last = partial;
314 partial = wxEmptyString;
315 expr_context = STRING;
316 }
317 else if( c == '.' )
318 {
319 partial = wxEmptyString;
320 expr_context = STRUCT_REF;
321 }
322 else
323 {
324 partial += c;
325 }
326 }
327 }
328 else if( c == '"' )
329 {
330 last = partial;
331 partial = wxEmptyString;
332 context = STRING;
333 }
334 else if( c == '(' )
335 {
336 if( context == SEXPR_OPEN && !partial.IsEmpty() )
337 {
338 m_textEditor->AutoCompCancel();
339 sexprs.push( partial );
340 }
341
342 partial = wxEmptyString;
343 context = SEXPR_OPEN;
344 }
345 else if( c == ')' )
346 {
347 while( !sexprs.empty() && ( sexprs.top() == wxT( "assertion" )
348 || sexprs.top() == wxT( "disallow" )
349 || isDisallowToken( sexprs.top() )
350 || sexprs.top() == wxT( "min_resolved_spokes" )
351 || sexprs.top() == wxT( "zone_connection" ) ) )
352 {
353 sexprs.pop();
354 }
355
356 if( !sexprs.empty() )
357 {
358 // Ignore argument-less tokens
359 if( partial == wxT( "within_diff_pairs" ) )
360 {
361 partial = wxEmptyString;
362 }
363 else
364 {
365 if( sexprs.top() == wxT( "constraint" ) )
366 {
367 constraintType = wxEmptyString;
368 }
369
370 sexprs.pop();
371 }
372 }
373
374 context = NO_CONTEXT;
375 }
376 else if( c == ' ' )
377 {
378 if( context == SEXPR_OPEN && !partial.IsEmpty() )
379 {
380 m_textEditor->AutoCompCancel();
381 sexprs.push( partial );
382
383 if( partial == wxT( "constraint" )
384 || partial == wxT( "layer" )
385 || partial == wxT( "severity" ) )
386 {
387 context = SEXPR_TOKEN;
388 }
389 else if( partial == wxT( "rule" )
390 || partial == wxT( "condition" ) )
391 {
392 context = SEXPR_STRING;
393 }
394 else
395 {
396 context = NO_CONTEXT;
397 }
398
399 partial = wxEmptyString;
400 continue;
401 }
402 else if( partial == wxT( "disallow" )
403 || isDisallowToken( partial )
404 || partial == wxT( "min_resolved_spokes" )
405 || partial == wxT( "zone_connection" ) )
406 {
407 m_textEditor->AutoCompCancel();
408 sexprs.push( partial );
409
410 partial = wxEmptyString;
411 context = SEXPR_TOKEN;
412 continue;
413 }
414 else if( partial == wxT( "assertion" ) )
415 {
416 m_textEditor->AutoCompCancel();
417 sexprs.push( partial );
418
419 partial = wxEmptyString;
420 context = SEXPR_STRING;
421 continue;
422 }
423 else if( isConstraintTypeToken( partial ) )
424 {
425 constraintType = partial;
426 }
427
428 context = NO_CONTEXT;
429 }
430 else
431 {
432 partial += c;
433 }
434 }
435
436 wxString tokens;
437
438 if( context == SEXPR_OPEN )
439 {
440 if( sexprs.empty() )
441 {
442 tokens = wxT( "rule|"
443 "version" );
444 }
445 else if( sexprs.top() == wxT( "rule" ) )
446 {
447 tokens = wxT( "condition|"
448 "constraint|"
449 "layer|"
450 "severity" );
451 }
452 else if( sexprs.top() == wxT( "constraint" ) )
453 {
454 if( constraintType == wxT( "skew" ) )
455 tokens = wxT( "max|min|opt|within_diff_pairs" );
456 else
457 tokens = wxT( "max|min|opt" );
458 }
459 }
460 else if( context == SEXPR_TOKEN )
461 {
462 if( sexprs.empty() )
463 {
464 /* badly formed grammar */
465 }
466 else if( sexprs.top() == wxT( "constraint" ) )
467 {
468 tokens = wxT( "annular_width|"
469 "assertion|"
470 "bridged_mask|"
471 "clearance|"
472 "connection_width|"
473 "courtyard_clearance|"
474 "creepage|"
475 "diff_pair_gap|"
476 "diff_pair_uncoupled|"
477 "disallow|"
478 "edge_clearance|"
479 "length|"
480 "hole_clearance|"
481 "hole_size|"
482 "hole_to_hole|"
483 "min_resolved_spokes|"
484 "physical_clearance|"
485 "physical_hole_clearance|"
486 "silk_clearance|"
487 "skew|"
488 "solder_mask_expansion|"
489 "solder_paste_abs_margin|"
490 "solder_paste_rel_margin|"
491 "text_height|"
492 "text_thickness|"
493 "thermal_relief_gap|"
494 "thermal_spoke_width|"
495 "track_width|"
496 "track_angle|"
497 "track_segment_length|"
498 "via_count|"
499 "via_diameter|"
500 "zone_connection" );
501 }
502 else if( sexprs.top() == wxT( "disallow" ) || isDisallowToken( sexprs.top() ) )
503 {
504 tokens = wxT( "buried_via|"
505 "footprint|"
506 "graphic|"
507 "hole|"
508 "micro_via|"
509 "pad|"
510 "text|"
511 "track|"
512 "via|"
513 "zone" );
514 }
515 else if( sexprs.top() == wxT( "zone_connection" ) )
516 {
517 tokens = wxT( "none|solid|thermal_reliefs" );
518 }
519 else if( sexprs.top() == wxT( "min_resolved_spokes" ) )
520 {
521 tokens = wxT( "0|1|2|3|4" );
522 }
523 else if( sexprs.top() == wxT( "layer" ) )
524 {
525 tokens = wxT( "inner|outer|\"x\"" );
526 }
527 else if( sexprs.top() == wxT( "severity" ) )
528 {
529 tokens = wxT( "warning|error|ignore|exclusion" );
530 }
531 }
532 else if( context == SEXPR_STRING && !sexprs.empty()
533 && ( sexprs.top() == wxT( "condition" ) || sexprs.top() == wxT( "assertion" ) ) )
534 {
535 m_textEditor->AddText( wxT( "\"" ) );
536 }
537 else if( context == STRING && !sexprs.empty()
538 && ( sexprs.top() == wxT( "condition" ) || sexprs.top() == wxT( "assertion" ) ) )
539 {
540 if( expr_context == STRUCT_REF )
541 {
543 std::set<wxString> propNames;
544
545 for( const PROPERTY_MANAGER::CLASS_INFO& cls : propMgr.GetAllClasses() )
546 {
547 const std::vector<PROPERTY_BASE*>& props = propMgr.GetProperties( cls.type );
548
549 for( PROPERTY_BASE* prop : props )
550 {
551 // TODO: It would be nice to replace IsHiddenFromRulesEditor with a nickname
552 // system, so that two different properies don't need to be created. This is
553 // a bigger change than I want to make right now, though.
554 if( prop->IsHiddenFromRulesEditor() )
555 continue;
556
557 wxString ref( prop->Name() );
558 ref.Replace( wxT( " " ), wxT( "_" ) );
559 propNames.insert( ref );
560 }
561 }
562
563 for( const wxString& propName : propNames )
564 tokens += wxT( "|" ) + propName;
565
567
568 for( const wxString& funcSig : functions.GetSignatures() )
569 {
570 if( !funcSig.Contains( "DEPRECATED" ) )
571 tokens += wxT( "|" ) + funcSig;
572 }
573 }
574 else if( expr_context == STRING )
575 {
576 if( m_netClassRegex.Matches( last ) )
577 {
578 BOARD_DESIGN_SETTINGS& bds = m_frame->GetBoard()->GetDesignSettings();
579 std::shared_ptr<NET_SETTINGS>& netSettings = bds.m_NetSettings;
580
581 for( const auto& [name, netclass] : netSettings->GetNetclasses() )
582 tokens += wxT( "|" ) + name;
583 }
584 else if( m_netNameRegex.Matches( last ) )
585 {
586 BOARD* board = m_frame->GetBoard();
587
588 for( const wxString& netnameCandidate : board->GetNetClassAssignmentCandidates() )
589 tokens += wxT( "|" ) + netnameCandidate;
590 }
591 else if( m_typeRegex.Matches( last ) )
592 {
593 tokens = wxT( "Bitmap|"
594 "Dimension|"
595 "Footprint|"
596 "Graphic|"
597 "Group|"
598 "Leader|"
599 "Pad|"
600 "Target|"
601 "Text|"
602 "Text Box|"
603 "Track|"
604 "Via|"
605 "Zone" );
606 }
607 else if( m_viaTypeRegex.Matches( last ) )
608 {
609 tokens = wxT( "Through|"
610 "Blind|"
611 "Buried|"
612 "Micro" );
613 }
614 else if( m_padTypeRegex.Matches( last ) )
615 {
616 tokens = wxT( "Through-hole|"
617 "SMD|"
618 "Edge connector|"
619 "NPTH, mechanical" );
620 }
621 else if( m_pinTypeRegex.Matches( last ) )
622 {
623 tokens = wxT( "Input|"
624 "Output|"
625 "Bidirectional|"
626 "Tri-state|"
627 "Passive|"
628 "Free|"
629 "Unspecified|"
630 "Power input|"
631 "Power output|"
632 "Open collector|"
633 "Open emitter|"
634 "Unconnected" );
635 }
636 else if( m_fabPropRegex.Matches( last ) )
637 {
638 tokens = wxT( "None|"
639 "BGA pad|"
640 "Fiducial, global to board|"
641 "Fiducial, local to footprint|"
642 "Test point pad|"
643 "Heatsink pad|"
644 "Castellated pad" );
645 }
646 else if( m_shapeRegex.Matches( last ) )
647 {
648 tokens = wxT( "Segment|"
649 "Rectangle|"
650 "Arc|"
651 "Circle|"
652 "Polygon|"
653 "Bezier" );
654 }
655 else if( m_padShapeRegex.Matches( last ) )
656 {
657 tokens = wxT( "Circle|"
658 "Rectangle|"
659 "Oval|"
660 "Trapezoid|"
661 "Rounded rectangle|"
662 "Chamfered rectangle|"
663 "Custom" );
664 }
665 else if( m_padConnectionsRegex.Matches( last ) )
666 {
667 tokens = wxT( "Inherited|"
668 "None|"
669 "Solid|"
670 "Thermal reliefs|"
671 "Thermal reliefs for PTH" );
672 }
673 else if( m_zoneConnStyleRegex.Matches( last ) )
674 {
675 tokens = wxT( "Inherited|"
676 "None|"
677 "Solid|"
678 "Thermal reliefs" );
679 }
680 else if( m_lineStyleRegex.Matches( last ) )
681 {
682 tokens = wxT( "Default|"
683 "Solid|"
684 "Dashed|"
685 "Dotted|"
686 "Dash-Dot|"
687 "Dash-Dot-Dot" );
688 }
689 else if( m_hJustRegex.Matches( last ) )
690 {
691 tokens = wxT( "Left|"
692 "Center|"
693 "Right" );
694 }
695 else if( m_vJustRegex.Matches( last ) )
696 {
697 tokens = wxT( "Top|"
698 "Center|"
699 "Bottom" );
700 }
701 }
702 }
703
704 if( !tokens.IsEmpty() )
705 m_scintillaTricks->DoAutocomplete( partial, wxSplit( tokens, '|' ) );
706}
707
708
709void PANEL_SETUP_RULES::OnCompile( wxCommandEvent& event )
710{
711 m_errorsReport->Clear();
712
713 try
714 {
715 std::vector<std::shared_ptr<DRC_RULE>> dummyRules;
716
717 std::function<bool( wxString* )> resolver =
718 [&]( wxString* token ) -> bool
719 {
720 if( m_frame->Prj().TextVarResolver( token ) )
721 return true;
722
723 return false;
724 };
725
726 wxString rulesText = ExpandTextVars( m_textEditor->GetText(), &resolver );
727
728 DRC_RULES_PARSER parser( rulesText, _( "DRC rules" ) );
729
730 parser.Parse( dummyRules, m_errorsReport );
731 checkPlausibility( dummyRules );
732 }
733 catch( PARSE_ERROR& pe )
734 {
735 wxString msg = wxString::Format( wxT( "%s <a href='%d:%d'>%s</a>%s" ),
736 _( "ERROR:" ),
737 pe.lineNumber,
738 pe.byteIndex,
739 pe.ParseProblem(),
740 wxEmptyString );
741
742 m_errorsReport->Report( msg, RPT_SEVERITY_ERROR );
743 }
744
745 m_errorsReport->Flush();
746}
747
748
749void PANEL_SETUP_RULES::checkPlausibility( const std::vector<std::shared_ptr<DRC_RULE>>& aRules )
750{
751 BOARD* board = m_frame->GetBoard();
753 LSET enabledLayers = board->GetEnabledLayers();
754
755 std::unordered_map<wxString, wxString> seenConditions;
756 std::regex netclassPattern( "NetClass\\s*[!=]=\\s*\"?([^\"\\s]+)\"?" );
757
758 for( const auto& rule : aRules )
759 {
760 wxString condition;
761
762 if( rule->m_Condition )
763 condition = rule->m_Condition->GetExpression();
764
765 condition.Trim( true ).Trim( false );
766
767 if( seenConditions.count( condition ) )
768 {
769 wxString msg = wxString::Format( _( "Rules '%s' and '%s' share the same condition." ), rule->m_Name,
770 seenConditions[condition] );
771 m_errorsReport->Report( msg, RPT_SEVERITY_WARNING );
772 }
773 else
774 {
775 seenConditions[condition] = rule->m_Name;
776 }
777
778 std::string condUtf8 = condition.ToStdString();
779 std::sregex_iterator it( condUtf8.begin(), condUtf8.end(), netclassPattern );
780 std::sregex_iterator end;
781
782 for( ; it != end; ++it )
783 {
784 wxString ncName = wxString::FromUTF8( ( *it )[1].str() );
785
786 if( !bds.m_NetSettings->HasNetclass( ncName ) )
787 {
788 wxString msg =
789 wxString::Format( _( "Rule '%s' references undefined netclass '%s'." ), rule->m_Name, ncName );
790 m_errorsReport->Report( msg, RPT_SEVERITY_WARNING );
791 }
792 }
793
794 if( !rule->m_LayerSource.IsEmpty() )
795 {
796 LSET invalid = rule->m_LayerCondition & ~enabledLayers;
797
798 if( invalid.any() )
799 {
800 wxString badLayers;
801
802 for( PCB_LAYER_ID layer : invalid.Seq() )
803 {
804 if( !badLayers.IsEmpty() )
805 badLayers += ", ";
806
807 badLayers += board->GetLayerName( layer );
808 }
809
810 wxString msg = wxString::Format( _( "Rule '%s' references undefined layer(s): %s." ), rule->m_Name,
811 badLayers );
812 m_errorsReport->Report( msg, RPT_SEVERITY_WARNING );
813 }
814 }
815 }
816}
817
818
819void PANEL_SETUP_RULES::OnErrorLinkClicked( wxHtmlLinkEvent& event )
820{
821 wxString link = event.GetLinkInfo().GetHref();
822 wxArrayString parts;
823 long line = 0, offset = 0;
824
825 wxStringSplit( link, parts, ':' );
826
827 if( parts.size() > 1 )
828 {
829 parts[0].ToLong( &line );
830 parts[1].ToLong( &offset );
831 }
832
833 int pos = m_textEditor->PositionFromLine( line - 1 ) + ( offset - 1 );
834
835 m_textEditor->GotoPos( pos );
836
837 m_textEditor->SetFocus();
838}
839
840
842{
843 wxFileName rulesFile( m_frame->GetDesignRulesPath() );
844
845 if( rulesFile.FileExists() )
846 {
847 wxTextFile file( rulesFile.GetFullPath() );
848
849 if( file.Open() )
850 {
851 for ( wxString str = file.GetFirstLine(); !file.Eof(); str = file.GetNextLine() )
852 {
854 m_textEditor->AddText( str << '\n' );
855 }
856
857 m_textEditor->EmptyUndoBuffer();
858
859 wxCommandEvent dummy;
860 OnCompile( dummy );
861 }
862 }
863 else
864 {
865 m_textEditor->AddText( wxT( "(version 1)\n" ) );
866 }
867
868 m_originalText = m_textEditor->GetText();
869
870 if( m_frame->Prj().IsNullProject() )
871 {
872 m_textEditor->ClearAll();
873 m_textEditor->AddText( _( "Design rules cannot be added without a project" ) );
874 m_textEditor->Disable();
875 }
876
877 return true;
878}
879
880
882{
883 if( m_originalText == m_textEditor->GetText() )
884 return true;
885
886 if( m_frame->Prj().IsNullProject() )
887 return true;
888
889 wxString rulesFilepath = m_frame->GetDesignRulesPath();
890
891 try
892 {
893 if( m_textEditor->SaveFile( rulesFilepath ) )
894 {
895 m_frame->GetBoard()->GetDesignSettings().m_DRCEngine->InitEngine( rulesFilepath );
896 return true;
897 }
898 }
899 catch( PARSE_ERROR& )
900 {
901 // Don't lock them in to the Setup dialog if they have bad rules. They've already
902 // saved them so we can allow an exit.
903 return true;
904 }
905
906 return false;
907}
908
909
910void PANEL_SETUP_RULES::OnSyntaxHelp( wxHyperlinkEvent& aEvent )
911{
912 if( m_helpWindow )
913 {
914 m_helpWindow->ShowModeless();
915 return;
916 }
917 std::vector<wxString> msg;
918 msg.clear();
919
920 wxString 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 t =
958 ;
959 msg.emplace_back( t );
960
961 wxString msg_txt = wxEmptyString;
962
963 for( wxString i : msg )
964 msg_txt << wxGetTranslation( i );
965
966#ifdef __WXMAC__
967 msg_txt.Replace( wxT( "Ctrl+" ), wxT( "Cmd+" ) );
968#endif
969 const wxString& msGg_txt = msg_txt;
970
971 m_helpWindow = new HTML_MESSAGE_BOX( nullptr, _( "Syntax Help" ) );
972 m_helpWindow->SetDialogSizeInDU( 420, 320 );
973
974 wxString html_txt = wxEmptyString;
975 ConvertMarkdown2Html( msGg_txt, html_txt );
976
977 html_txt.Replace( wxS( "<td" ), wxS( "<td valign=top" ) );
978 m_helpWindow->AddHTML_Text( html_txt );
979
980 m_helpWindow->ShowModeless();
981}
982
983
985{
986 if( !m_frame->Prj().IsNullProject() )
987 {
988 wxFileName relFile = aBoard->GetFileName();
989 relFile.SetExt( FILEEXT::DesignRulesFileExtension );
990
991 wxFileName absFile( aBoard->GetProject()->AbsolutePath( relFile.GetFullName() ) );
992
993 if( absFile.FileExists() )
994 {
995 wxTextFile file( absFile.GetFullPath() );
996
997 if( file.Open() )
998 {
999 m_textEditor->ClearAll();
1000
1001 for ( wxString str = file.GetFirstLine(); !file.Eof(); str = file.GetNextLine() )
1002 {
1004 m_textEditor->AddText( str << '\n' );
1005 }
1006
1007 m_textEditor->EmptyUndoBuffer();
1008
1009 wxCommandEvent dummy;
1010 OnCompile( dummy );
1011 }
1012 }
1013 }
1014}
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:322
std::set< wxString > GetNetClassAssignmentCandidates() const
Return the set of netname candidates for netclass assignment.
Definition board.cpp:2390
const wxString & GetFileName() const
Definition board.h:359
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:692
PROJECT * GetProject() const
Definition board.h:554
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1041
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:924
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.