KiCad PCB EDA Suite
Loading...
Searching...
No Matches
panel_drc_rule_editor.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 (C) 2024 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
27#include <wx/log.h>
28
29#include <pgm_base.h>
32#include <template_fieldnames.h>
33#include <grid_tricks.h>
34#include <eda_text.h>
36#include <bitmaps.h>
37#include <confirm.h>
38#include <kidialog.h>
39#include <layer_ids.h>
40#include <layer_range.h>
41#include <board.h>
42#include <idf_parser.h>
43#include <scintilla_tricks.h>
44#include <wx/stc/stc.h>
46#include <tools/drc_tool.h>
47#include <pcbexpr_evaluator.h>
48#include <string_utils.h>
49
50#include <drc/drc_rule_parser.h>
73
74static constexpr const wxChar* KI_TRACE_DRC_RULE_EDITOR = wxT( "KI_TRACE_DRC_RULE_EDITOR" );
75
77{
78 switch( aConstraintType )
79 {
80 case BASIC_CLEARANCE:
88 case HOLE_TO_HOLE_DISTANCE: return true;
89 default: return false;
90 }
91}
92
94 DRC_RULE_EDITOR_CONSTRAINT_NAME aConstraintType,
95 wxString* aConstraintTitle,
96 std::shared_ptr<DRC_RE_BASE_CONSTRAINT_DATA> aConstraintData ) :
98 m_board( aBoard ),
99 m_constraintTitle( aConstraintTitle ),
100 m_validationSucceeded( false ),
101 m_constraintData( aConstraintData ),
102 m_helpWindow( nullptr )
103{
104 wxLogTrace( KI_TRACE_DRC_RULE_EDITOR, wxS( "[PANEL_DRC_RULE_EDITOR] ctor START" ) );
105
106 m_constraintPanel = getConstraintPanel( this, aConstraintType );
107 wxLogTrace( KI_TRACE_DRC_RULE_EDITOR, wxS( "[PANEL_DRC_RULE_EDITOR] adding constraint panel to sizer" ) );
108 m_constraintContentSizer->Add( dynamic_cast<wxPanel*>( m_constraintPanel ), 0, wxEXPAND | wxTOP, 5 );
109 m_layerList = m_board->GetEnabledLayers().UIOrder();
110 m_constraintHeaderTitle->SetLabelText( *aConstraintTitle + " Constraint" );
111
113
114 m_layerListChoiceCtrl = new wxChoice( this, wxID_ANY );
115 m_layerListChoiceCtrl->Append( _( "Any" ) );
116
117 for( PCB_LAYER_ID id : m_layerIDs )
118 m_layerListChoiceCtrl->Append( m_board->GetLayerName( id ) );
119
120 m_layerListChoiceCtrl->SetSelection( 0 );
121 m_LayersComboBoxSizer->Add( m_layerListChoiceCtrl, 0, wxALL | wxEXPAND, 5 );
122
123
124 wxBoxSizer* buttonSizer = new wxBoxSizer( wxHORIZONTAL );
125 m_btnShowMatches = new wxButton( this, wxID_ANY, "Show Matches" );
126 buttonSizer->Add( m_btnShowMatches, 0, wxALL, 5 );
127
128 bContentSizer->Add( buttonSizer, 0, wxALIGN_RIGHT | wxALL, 2 );
129
131
132 m_btnShowMatches->Enable( true );
133
135
136 m_scintillaTricks = std::make_unique<SCINTILLA_TRICKS>(
137 m_textConditionCtrl, wxT( "()" ), false,
138 // onAcceptFn
139 [this]( wxKeyEvent& aEvent )
140 {
141 wxPostEvent( PAGED_DIALOG::GetDialog( this ), wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
142 },
143 // onCharFn
144 [this]( wxStyledTextEvent& aEvent )
145 {
146 onScintillaCharAdded( aEvent );
147 } );
148
149 m_textConditionCtrl->AutoCompSetSeparator( '|' );
150
151 // Hide the base class text control since we use the condition group panel
152 m_textConditionCtrl->Hide();
153
154 bool twoObjects = constraintNeedsTwoObjects( aConstraintType );
155
156 // Create condition group panel (multi-condition UI)
157 // Each condition row has its own custom query text control
158 wxLogTrace( KI_TRACE_DRC_RULE_EDITOR, wxS( "[PANEL_DRC_RULE_EDITOR] creating conditionGroupPanel" ) );
160 m_conditionGroupPanel->SetChangeCallback( [this]() {
162 } );
163 wxLogTrace( KI_TRACE_DRC_RULE_EDITOR, wxS( "[PANEL_DRC_RULE_EDITOR] inserting conditionGroupPanel" ) );
164 m_conditionControlsSizer->Insert( 0, m_conditionGroupPanel, 0, wxEXPAND | wxBOTTOM, 5 );
165
166 // Hide the base class syntax check controls since we use inline validation
167 m_checkSyntaxBtnCtrl->Hide();
168 m_syntaxErrorReport->Hide();
169
170 m_netClassRegex.Compile( "^NetClass\\s*[!=]=\\s*$", wxRE_ADVANCED );
171 m_netNameRegex.Compile( "^NetName\\s*[!=]=\\s*$", wxRE_ADVANCED );
172 m_typeRegex.Compile( "^Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
173 m_viaTypeRegex.Compile( "^Via_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
174 m_padTypeRegex.Compile( "^Pad_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
175 m_pinTypeRegex.Compile( "^Pin_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
176 m_fabPropRegex.Compile( "^Fabrication_Property\\s*[!=]=\\s*$", wxRE_ADVANCED );
177 m_shapeRegex.Compile( "^Shape\\s*[!=]=\\s*$", wxRE_ADVANCED );
178 m_padShapeRegex.Compile( "^Pad_Shape\\s*[!=]=\\s*$", wxRE_ADVANCED );
179 m_padConnectionsRegex.Compile( "^Pad_Connections\\s*[!=]=\\s*$", wxRE_ADVANCED );
180 m_zoneConnStyleRegex.Compile( "^Zone_Connection_Style\\s*[!=]=\\s*$", wxRE_ADVANCED );
181 m_lineStyleRegex.Compile( "^Line_Style\\s*[!=]=\\s*$", wxRE_ADVANCED );
182 m_hJustRegex.Compile( "^Horizontal_Justification\\s*[!=]=\\s*$", wxRE_ADVANCED );
183 m_vJustRegex.Compile( "^Vertical_Justification\\s*[!=]=\\s*$", wxRE_ADVANCED );
184}
185
186
194
195
197{
198 if( m_constraintData )
199 {
200 m_nameCtrl->SetValue( m_constraintData->GetRuleName() );
201 m_commentCtrl->SetValue( m_constraintData->GetComment() );
202 setSelectedLayers( m_constraintData->GetLayers() );
203 wxString cond = m_constraintData->GetRuleCondition();
204
205 // Use the new condition group panel
206 // Each row manages its own custom query text control visibility
207 m_conditionGroupPanel->ParseCondition( cond );
208 }
209
210 return dynamic_cast<wxPanel*>( m_constraintPanel )->TransferDataToWindow();
211}
212
213
215{
216 dynamic_cast<wxPanel*>( m_constraintPanel )->TransferDataFromWindow();
217
218 m_constraintData->SetRuleName( m_nameCtrl->GetValue() );
219 m_constraintData->SetComment( m_commentCtrl->GetValue() );
220 m_constraintData->SetLayers( getSelectedLayers() );
221
222 // Use the new condition group panel
223 wxString combined = m_conditionGroupPanel->BuildCondition();
224 m_constraintData->SetRuleCondition( combined );
225
227 context.ruleName = m_nameCtrl->GetValue();
228 context.comment = m_commentCtrl->GetValue();
230 context.layerClause = buildLayerClause();
231 context.constraintCode = m_constraintData->GetConstraintCode();
232
233 wxString generatedRule = m_constraintPanel->GenerateRule( context );
234 m_constraintData->SetGeneratedRule( generatedRule );
235
236 wxLogTrace( KI_TRACE_DRC_RULE_EDITOR, wxS( "Generated rule '%s':\n%s" ),
237 context.ruleName, generatedRule );
238
239 return true;
240}
241
242
245{
246 switch( aConstraintType )
247 {
248 case VIA_STYLE:
249 return new DRC_RE_VIA_STYLE_PANEL( aParent, m_constraintTitle,
250 dynamic_pointer_cast<DRC_RE_VIA_STYLE_CONSTRAINT_DATA>( m_constraintData ) );
253 aParent, m_constraintTitle,
254 dynamic_pointer_cast<DRC_RE_MINIMUM_TEXT_HEIGHT_THICKNESS_CONSTRAINT_DATA>( m_constraintData ) );
257 aParent, m_constraintTitle,
258 dynamic_pointer_cast<DRC_RE_ROUTING_DIFF_PAIR_CONSTRAINT_DATA>( m_constraintData ) );
259 case ROUTING_WIDTH:
261 aParent, m_constraintTitle,
262 dynamic_pointer_cast<DRC_RE_ROUTING_WIDTH_CONSTRAINT_DATA>( m_constraintData ) );
263 case PERMITTED_LAYERS:
265 aParent, m_constraintTitle,
266 dynamic_pointer_cast<DRC_RE_PERMITTED_LAYERS_CONSTRAINT_DATA>( m_constraintData ) );
269 aParent, m_constraintTitle,
270 dynamic_pointer_cast<DRC_RE_ALLOWED_ORIENTATION_CONSTRAINT_DATA>( m_constraintData ) );
271 case CUSTOM_RULE:
272 return new DRC_RE_CUSTOM_RULE_PANEL(
273 aParent, dynamic_pointer_cast<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA>( m_constraintData ) );
274 case ABSOLUTE_LENGTH:
276 aParent, m_constraintTitle,
277 dynamic_pointer_cast<DRC_RE_ABSOLUTE_LENGTH_TWO_CONSTRAINT_DATA>( m_constraintData ) );
278 default:
279 if( DRC_RULE_EDITOR_UTILS::IsNumericInputType( aConstraintType ) )
280 {
282 *m_constraintTitle, dynamic_pointer_cast<DRC_RE_NUMERIC_INPUT_CONSTRAINT_DATA>( m_constraintData ),
283 aConstraintType, *m_constraintTitle );
284
285 if( aConstraintType == MINIMUM_THERMAL_RELIEF_SPOKE_COUNT || aConstraintType == MAXIMUM_VIA_COUNT )
286 numericInputParams.SetInputIsCount( true );
287
288 return new DRC_RE_NUMERIC_INPUT_PANEL( aParent, numericInputParams );
289 }
290 else if( DRC_RULE_EDITOR_UTILS::IsBoolInputType( aConstraintType ) )
291 {
292 wxString customLabel;
293
294 switch( aConstraintType )
295 {
296 case VIAS_UNDER_SMD: customLabel = "Allow Vias under SMD Pads"; break;
297 default: customLabel = *m_constraintTitle;
298 }
299
301 *m_constraintTitle, dynamic_pointer_cast<DRC_RE_BOOL_INPUT_CONSTRAINT_DATA>( m_constraintData ),
302 aConstraintType, customLabel );
303
304 return new DRC_RE_BOOL_INPUT_PANEL( aParent, boolInputParams );
305 }
306 else
307 {
308 return nullptr;
309 };
310 }
311}
312
313
314bool PANEL_DRC_RULE_EDITOR::ValidateInputs( int* aErrorCount, std::string* aValidationMessage )
315{
316 if( !m_callBackRuleNameValidation( m_constraintData->GetId(), m_nameCtrl->GetValue() ) )
317 {
318 m_validationSucceeded = false;
319 ( *aErrorCount )++;
321 DRC_RULE_EDITOR_UTILS::FormatErrorMessage( *aErrorCount, "Rule Name should be unique !!" );
322 }
323
324 if( m_layerListChoiceCtrl->GetSelection() == wxNOT_FOUND )
325 {
326 m_validationSucceeded = false;
327 ( *aErrorCount )++;
329 DRC_RULE_EDITOR_UTILS::FormatErrorMessage( *aErrorCount, "Layers selection should not be empty !!" );
330 }
331
333}
334
335
337{
338 return m_constraintPanel->GenerateRule( aContext );
339}
340
341
342void PANEL_DRC_RULE_EDITOR::onSaveButtonClicked( wxCommandEvent& aEvent )
343{
345 int errorCount = 0;
347
348 ValidateInputs( &errorCount, &m_validationMessage );
349
350 if( !m_constraintPanel->ValidateInputs( &errorCount, &m_validationMessage ) )
351 {
352 m_validationSucceeded = false;
353 }
354
355 if( m_callBackSave )
356 {
358 }
359
361 m_btnShowMatches->Enable( true );
362}
363
364
365void PANEL_DRC_RULE_EDITOR::Save( wxCommandEvent& aEvent )
366{
367 onSaveButtonClicked( aEvent );
368}
369
370
372{
375}
376
377
378void PANEL_DRC_RULE_EDITOR::Cancel( wxCommandEvent& aEvent )
379{
380 if( m_constraintData && m_constraintData->IsNew() )
381 onRemoveButtonClicked( aEvent );
382 else
383 onCloseButtonClicked( aEvent );
384}
385
386
388{
391}
392
393
394void PANEL_DRC_RULE_EDITOR::onScintillaCharAdded( wxStyledTextEvent& aEvent )
395{
396 if( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKey() == ' ' )
397 {
398 // This is just a short-cut for do-auto-complete
399 }
400
401 m_textConditionCtrl->SearchAnchor();
402
403 wxString rules = m_textConditionCtrl->GetText();
404 int currentPos = m_textConditionCtrl->GetCurrentPos();
405 int startPos = 0;
406
407 for( int line = m_textConditionCtrl->LineFromPosition( currentPos ); line > 0; line-- )
408 {
409 int lineStart = m_textConditionCtrl->PositionFromLine( line );
410 wxString beginning = m_textConditionCtrl->GetTextRange( lineStart, lineStart + 10 );
411
412 if( beginning.StartsWith( wxT( "(condition " ) ) )
413 {
414 startPos = lineStart;
415 break;
416 }
417 }
418
419 enum class EXPR_CONTEXT_T : int
420 {
421 NONE,
422 STRING,
423 SEXPR_OPEN,
424 SEXPR_TOKEN,
425 SEXPR_STRING,
426 STRUCT_REF
427 };
428
429 std::stack<wxString> sexprs;
430 wxString partial;
431 wxString last;
432 wxString constraintType;
433 EXPR_CONTEXT_T context = EXPR_CONTEXT_T::NONE;
434 EXPR_CONTEXT_T expr_context = EXPR_CONTEXT_T::NONE;
435
436 for( int i = startPos; i < currentPos; ++i )
437 {
438 wxChar c = m_textConditionCtrl->GetCharAt( i );
439
440 if( c == '\\' )
441 {
442 i++; // skip escaped char
443 }
444 else if( context == EXPR_CONTEXT_T::STRING )
445 {
446 if( c == '"' )
447 {
448 context = EXPR_CONTEXT_T::NONE;
449 }
450 else
451 {
452 if( expr_context == EXPR_CONTEXT_T::STRING )
453 {
454 if( c == '\'' )
455 expr_context = EXPR_CONTEXT_T::NONE;
456 else
457 partial += c;
458 }
459 else if( c == '\'' )
460 {
461 last = partial;
462 partial = wxEmptyString;
463 expr_context = EXPR_CONTEXT_T::STRING;
464 }
465 else if( c == '.' )
466 {
467 partial = wxEmptyString;
468 expr_context = EXPR_CONTEXT_T::STRUCT_REF;
469 }
470 else
471 {
472 partial += c;
473 }
474 }
475 }
476 else if( c == '"' )
477 {
478 last = partial;
479 partial = wxEmptyString;
480 context = EXPR_CONTEXT_T::STRING;
481 }
482 else if( c == '(' )
483 {
484 if( context == EXPR_CONTEXT_T::SEXPR_OPEN && !partial.IsEmpty() )
485 {
486 m_textConditionCtrl->AutoCompCancel();
487 sexprs.push( partial );
488 }
489
490 partial = wxEmptyString;
491 context = EXPR_CONTEXT_T::SEXPR_OPEN;
492 }
493 else if( c == ')' )
494 {
495 context = EXPR_CONTEXT_T::NONE;
496 }
497 else if( c == ' ' )
498 {
499 if( context == EXPR_CONTEXT_T::SEXPR_OPEN && !partial.IsEmpty() )
500 {
501 m_textConditionCtrl->AutoCompCancel();
502 sexprs.push( partial );
503
504 if( partial == wxT( "condition" ) )
505 {
506 context = EXPR_CONTEXT_T::SEXPR_STRING;
507 }
508 else
509 {
510 context = EXPR_CONTEXT_T::NONE;
511 }
512
513 partial = wxEmptyString;
514 continue;
515 }
516
517 context = EXPR_CONTEXT_T::NONE;
518 }
519 else
520 {
521 partial += c;
522 }
523 }
524
525 wxString tokens;
526
527 if( context == EXPR_CONTEXT_T::SEXPR_OPEN )
528 {
529 if( sexprs.empty() )
530 {
531 tokens = wxT( "condition" );
532 }
533 }
534 else if( context == EXPR_CONTEXT_T::SEXPR_TOKEN )
535 {
536 if( sexprs.empty() )
537 {
538 /* badly formed grammar */
539 }
540 }
541 else if( context == EXPR_CONTEXT_T::SEXPR_STRING && !sexprs.empty() && sexprs.top() == wxT( "condition" ) )
542 {
543 m_textConditionCtrl->AddText( wxT( "\"" ) );
544 }
545 else if( context == EXPR_CONTEXT_T::STRING && !sexprs.empty() && sexprs.top() == wxT( "condition" ) )
546 {
547 if( expr_context == EXPR_CONTEXT_T::STRUCT_REF )
548 {
550 std::set<wxString> propNames;
551
552 for( const PROPERTY_MANAGER::CLASS_INFO& cls : propMgr.GetAllClasses() )
553 {
554 const std::vector<PROPERTY_BASE*>& props = propMgr.GetProperties( cls.type );
555
556 for( PROPERTY_BASE* prop : props )
557 {
558 // TODO: It would be nice to replace IsHiddenFromRulesEditor with a nickname
559 // system, so that two different properies don't need to be created. This is
560 // a bigger change than I want to make right now, though.
561 if( prop->IsHiddenFromRulesEditor() )
562 continue;
563
564 wxString ref( prop->Name() );
565 ref.Replace( wxT( " " ), wxT( "_" ) );
566 propNames.insert( ref );
567 }
568 }
569
570 for( const wxString& propName : propNames )
571 tokens += wxT( "|" ) + propName;
572
574
575 for( const wxString& funcSig : functions.GetSignatures() )
576 {
577 if( !funcSig.Contains( "DEPRECATED" ) )
578 tokens += wxT( "|" ) + funcSig;
579 }
580 }
581 else if( expr_context == EXPR_CONTEXT_T::STRING )
582 {
583 if( m_netClassRegex.Matches( last ) )
584 {
585 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
586 std::shared_ptr<NET_SETTINGS>& netSettings = bds.m_NetSettings;
587
588 for( const auto& [name, netclass] : netSettings->GetNetclasses() )
589 tokens += wxT( "|" ) + name;
590 }
591 else if( m_netNameRegex.Matches( last ) )
592 {
593 for( const wxString& netnameCandidate : m_board->GetNetClassAssignmentCandidates() )
594 tokens += wxT( "|" ) + netnameCandidate;
595 }
596 else if( m_typeRegex.Matches( last ) )
597 {
598 tokens = wxT( "Bitmap|"
599 "Dimension|"
600 "Footprint|"
601 "Graphic|"
602 "Group|"
603 "Leader|"
604 "Pad|"
605 "Target|"
606 "Text|"
607 "Text Box|"
608 "Track|"
609 "Via|"
610 "Zone" );
611 }
612 else if( m_viaTypeRegex.Matches( last ) )
613 {
614 tokens = wxT( "Through|"
615 "Blind/buried|"
616 "Micro" );
617 }
618 else if( m_padTypeRegex.Matches( last ) )
619 {
620 tokens = wxT( "Through-hole|"
621 "SMD|"
622 "Edge connector|"
623 "NPTH, mechanical" );
624 }
625 else if( m_pinTypeRegex.Matches( last ) )
626 {
627 tokens = wxT( "Input|"
628 "Output|"
629 "Bidirectional|"
630 "Tri-state|"
631 "Passive|"
632 "Free|"
633 "Unspecified|"
634 "Power input|"
635 "Power output|"
636 "Open collector|"
637 "Open emitter|"
638 "Unconnected" );
639 }
640 else if( m_fabPropRegex.Matches( last ) )
641 {
642 tokens = wxT( "None|"
643 "BGA pad|"
644 "Fiducial, global to board|"
645 "Fiducial, local to footprint|"
646 "Test point pad|"
647 "Heatsink pad|"
648 "Castellated pad" );
649 }
650 else if( m_shapeRegex.Matches( last ) )
651 {
652 tokens = wxT( "Segment|"
653 "Rectangle|"
654 "Arc|"
655 "Circle|"
656 "Polygon|"
657 "Bezier" );
658 }
659 else if( m_padShapeRegex.Matches( last ) )
660 {
661 tokens = wxT( "Circle|"
662 "Rectangle|"
663 "Oval|"
664 "Trapezoid|"
665 "Rounded rectangle|"
666 "Chamfered rectangle|"
667 "Custom" );
668 }
669 else if( m_padConnectionsRegex.Matches( last ) )
670 {
671 tokens = wxT( "Inherited|"
672 "None|"
673 "Solid|"
674 "Thermal reliefs|"
675 "Thermal reliefs for PTH" );
676 }
677 else if( m_zoneConnStyleRegex.Matches( last ) )
678 {
679 tokens = wxT( "Inherited|"
680 "None|"
681 "Solid|"
682 "Thermal reliefs" );
683 }
684 else if( m_lineStyleRegex.Matches( last ) )
685 {
686 tokens = wxT( "Default|"
687 "Solid|"
688 "Dashed|"
689 "Dotted|"
690 "Dash-Dot|"
691 "Dash-Dot-Dot" );
692 }
693 else if( m_hJustRegex.Matches( last ) )
694 {
695 tokens = wxT( "Left|"
696 "Center|"
697 "Right" );
698 }
699 else if( m_vJustRegex.Matches( last ) )
700 {
701 tokens = wxT( "Top|"
702 "Center|"
703 "Bottom" );
704 }
705 }
706 }
707
708 if( !tokens.IsEmpty() )
709 m_scintillaTricks->DoAutocomplete( partial, wxSplit( tokens, '|' ) );
710}
711
712
713void PANEL_DRC_RULE_EDITOR::onSyntaxHelp( wxHyperlinkEvent& aEvent )
714{
715 if( m_helpWindow )
716 {
717 m_helpWindow->ShowModeless();
718 return;
719 }
720
721 std::vector<wxString> msg;
722 msg.clear();
723
724 wxString t =
726 ;
727 msg.emplace_back( t );
728
729 t =
731 ;
732 msg.emplace_back( t );
733
734 t =
736 ;
737 msg.emplace_back( t );
738
739 t =
741 ;
742 msg.emplace_back( t );
743
744 t =
746 ;
747 msg.emplace_back( t );
748
749 t =
751 ;
752 msg.emplace_back( t );
753
754 t =
756 ;
757 msg.emplace_back( t );
758
759 t =
761 ;
762 msg.emplace_back( t );
763
764
765 wxString msg_txt = wxEmptyString;
766
767 for( wxString i : msg )
768 msg_txt << wxGetTranslation( i );
769
770#ifdef __WXMAC__
771 msg_txt.Replace( wxT( "Ctrl+" ), wxT( "Cmd+" ) );
772#endif
773 const wxString& msGg_txt = msg_txt;
774
775 m_helpWindow = new HTML_MESSAGE_BOX( this, _( "Syntax Help" ) );
776 m_helpWindow->SetDialogSizeInDU( 420, 320 );
777
778 wxString html_txt = wxEmptyString;
779 ConvertMarkdown2Html( msGg_txt, html_txt );
780
781 html_txt.Replace( wxS( "<td" ), wxS( "<td valign=top" ) );
782 m_helpWindow->AddHTML_Text( html_txt );
783
784 m_helpWindow->ShowModeless();
785}
786
787
788void PANEL_DRC_RULE_EDITOR::onCheckSyntax( wxCommandEvent& event )
789{
790 m_syntaxErrorReport->Clear();
791
792 // Get the complete condition from the condition group panel
793 wxString condition = m_conditionGroupPanel->BuildCondition();
794
795 if( condition.IsEmpty() )
796 {
797 wxString msg = _( "ERROR: No condition text provided for validation." );
799 m_syntaxErrorReport->Flush();
800 return;
801 }
802
803 try
804 {
805 std::vector<std::shared_ptr<DRC_RULE>> dummyRules;
806 wxString ruleTemplate = L"(version 1)\n(rule default\n (condition \"%s\")\n)";
807 wxString formattedRule = wxString::Format( ruleTemplate, condition );
808 DRC_RULES_PARSER ruleParser( formattedRule, _( "DRC rule" ) );
809 ruleParser.Parse( dummyRules, m_syntaxErrorReport );
810 }
811 catch( PARSE_ERROR& pe )
812 {
813 wxString msg = wxString::Format( wxT( "%s <a href='%d:%d'>%s</a>" ),
814 _( "ERROR:" ),
815 pe.lineNumber, pe.byteIndex, pe.ParseProblem() );
816
818 }
819
820 m_syntaxErrorReport->Flush();
821}
822
823
824void PANEL_DRC_RULE_EDITOR::onErrorLinkClicked( wxHtmlLinkEvent& event )
825{
826 wxString link = event.GetLinkInfo().GetHref();
827 wxArrayString parts;
828 long line = 0, offset = 0;
829
830 wxStringSplit( link, parts, ':' );
831
832 if( parts.size() > 1 )
833 {
834 parts[0].ToLong( &line );
835 parts[1].ToLong( &offset );
836 }
837
838 int pos = m_textConditionCtrl->PositionFromLine( line - 1 ) + ( offset - 1 );
839
840 m_textConditionCtrl->GotoPos( pos );
841
842 m_textConditionCtrl->SetFocus();
843}
844
845
846void PANEL_DRC_RULE_EDITOR::onContextMenu( wxMouseEvent& event )
847{
848 wxMenu menu;
849
850 menu.Append( wxID_UNDO, _( "Undo" ) );
851 menu.Append( wxID_REDO, _( "Redo" ) );
852
853 menu.AppendSeparator();
854
855 menu.Append( 1, _( "Cut" ) ); // Don't use wxID_CUT, wxID_COPY, etc. On Mac (at least),
856 menu.Append( 2, _( "Copy" ) ); // wxWidgets never delivers them to us.
857 menu.Append( 3, _( "Paste" ) );
858 menu.Append( 4, _( "Delete" ) );
859
860 menu.AppendSeparator();
861
862 menu.Append( 5, _( "Select All" ) );
863
864 menu.AppendSeparator();
865
866 menu.Append( wxID_ZOOM_IN, _( "Zoom In" ) );
867 menu.Append( wxID_ZOOM_OUT, _( "Zoom Out" ) );
868
869
870 switch( GetPopupMenuSelectionFromUser( menu ) )
871 {
872 case wxID_UNDO: m_textConditionCtrl->Undo(); break;
873 case wxID_REDO: m_textConditionCtrl->Redo(); break;
874
875 case 1: m_textConditionCtrl->Cut(); break;
876 case 2: m_textConditionCtrl->Copy(); break;
877 case 3: m_textConditionCtrl->Paste(); break;
878 case 4:
879 {
880 long from, to;
881 m_textConditionCtrl->GetSelection( &from, &to );
882
883 if( to > from )
884 m_textConditionCtrl->DeleteRange( from, to );
885
886 break;
887 }
888
889 case 5: m_textConditionCtrl->SelectAll(); break;
890
891 case wxID_ZOOM_IN: m_textConditionCtrl->ZoomIn(); break;
892 case wxID_ZOOM_OUT: m_textConditionCtrl->ZoomOut(); break;
893 }
894}
895
896
898{
899 if( !m_constraintData )
900 {
901 wxLogTrace( KI_TRACE_DRC_RULE_EDITOR, wxS( "Show Matches clicked: no constraint data" ) );
902 return;
903 }
904
905 wxLogTrace( KI_TRACE_DRC_RULE_EDITOR,
906 wxS( "Show Matches clicked: nodeId=%d, rule='%s', code='%s'" ),
907 m_constraintData->GetId(), m_constraintData->GetRuleName(),
908 m_constraintData->GetConstraintCode() );
909
911 {
912 int matchCount = m_callBackShowMatches( m_constraintData->GetId() );
913
914 if( matchCount < 0 )
915 {
916 m_btnShowMatches->SetLabel( _( "Show Matches (error)" ) );
917 }
918 else
919 {
920 m_btnShowMatches->SetLabel( wxString::Format( _( "Show Matches (%d)" ), matchCount ) );
921 }
922 }
923}
924
925
927{
928 m_btnShowMatches->SetLabel( _( "Show Matches" ) );
929}
930
932{
934 return wxEmptyString;
935
936 int selection = m_layerListChoiceCtrl->GetSelection();
937
938 if( selection <= 0 )
939 return wxEmptyString;
940
941 size_t index = static_cast<size_t>( selection - 1 );
942
943 if( index >= m_layerIDs.size() )
944 return wxEmptyString;
945
946 PCB_LAYER_ID layerId = m_layerIDs[index];
947 wxString clause = wxString::Format( wxS( "(layer %s)" ), m_board->GetLayerName( layerId ) );
948 wxLogTrace( KI_TRACE_DRC_RULE_EDITOR, wxS( "Layer clause: %s" ), clause );
949 return clause;
950}
951
953{
954 int sel = m_layerListChoiceCtrl->GetSelection();
955
956 if( sel <= 0 )
957 return {};
958
959 return { m_layerIDs[sel - 1] };
960}
961
962void PANEL_DRC_RULE_EDITOR::setSelectedLayers( const std::vector<PCB_LAYER_ID>& aLayers )
963{
964 if( aLayers.empty() )
965 {
966 m_layerListChoiceCtrl->SetSelection( 0 );
967 return;
968 }
969
970 PCB_LAYER_ID target = aLayers.front();
971
972 for( size_t i = 0; i < m_layerIDs.size(); ++i )
973 {
974 if( m_layerIDs[i] == target )
975 {
976 m_layerListChoiceCtrl->SetSelection( static_cast<int>( i ) + 1 );
977 return;
978 }
979 }
980
981 m_layerListChoiceCtrl->SetSelection( 0 );
982}
int index
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
Container panel that manages multiple condition rows with boolean operators.
Simple panel used for editing custom rule text.
void Parse(std::vector< std::shared_ptr< DRC_RULE > > &aRules, REPORTER *aReporter)
static bool IsNumericInputType(const DRC_RULE_EDITOR_CONSTRAINT_NAME &aConstraintType)
static std::string FormatErrorMessage(const int &aErrorCount, const std::string aErrorMessage)
static bool IsBoolInputType(const DRC_RULE_EDITOR_CONSTRAINT_NAME &aConstraintType)
static PAGED_DIALOG * GetDialog(wxWindow *aWindow)
PANEL_DRC_RULE_EDITOR_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)
std::shared_ptr< DRC_RE_BASE_CONSTRAINT_DATA > m_constraintData
void onCloseButtonClicked(wxCommandEvent &aEvent)
Handles the close button click event, invoking the close callback.
wxString GenerateRule(const RULE_GENERATION_CONTEXT &aContext) override
PANEL_DRC_RULE_EDITOR(wxWindow *aParent, BOARD *aBoard, DRC_RULE_EDITOR_CONSTRAINT_NAME aConstraintType, wxString *aConstraintTitle, std::shared_ptr< DRC_RE_BASE_CONSTRAINT_DATA > aConstraintData)
void onScintillaCharAdded(wxStyledTextEvent &aEvent)
Handles character addition in the Scintilla text control, performing auto-complete and context-sensit...
void onShowMatchesButtonClicked(wxCommandEvent &aEvent)
void onCheckSyntax(wxCommandEvent &aEvent) override
Checks the syntax of the DRC rule condition and reports any errors.
std::function< void(int aNodeId)> m_callBackSave
HTML_MESSAGE_BOX * m_helpWindow
bool ValidateInputs(int *aErrorCount, std::string *aValidationMessage) override
void onContextMenu(wxMouseEvent &aEvent) override
Handles right-click context menu actions for text editing (undo, redo, cut, copy, paste,...
void onRemoveButtonClicked(wxCommandEvent &aEvent)
Handles the remove button click event, invoking the remove callback.
std::function< bool(int aNodeId, wxString aRuleName)> m_callBackRuleNameValidation
void onSyntaxHelp(wxHyperlinkEvent &aEvent) override
Displays a modeless help window with syntax and rule documentation.
void Cancel(wxCommandEvent &aEvent)
void onErrorLinkClicked(wxHtmlLinkEvent &aEvent) override
Handles clicks on error links in the syntax error report and navigates to the error location.
std::function< void(int aNodeId)> m_callBackRemove
std::function< int(int aNodeId)> m_callBackShowMatches
DRC_RE_CONDITION_GROUP_PANEL * m_conditionGroupPanel
std::unique_ptr< SCINTILLA_TRICKS > m_scintillaTricks
void Save(wxCommandEvent &aEvent)
std::vector< PCB_LAYER_ID > m_layerIDs
void onSaveButtonClicked(wxCommandEvent &aEvent)
Handles the save button click event, validating inputs and invoking the save callback if valid.
DRC_RULE_EDITOR_CONTENT_PANEL_BASE * getConstraintPanel(wxWindow *aParent, const DRC_RULE_EDITOR_CONSTRAINT_NAME &aConstraintType)
void setSelectedLayers(const std::vector< PCB_LAYER_ID > &aLayers)
std::vector< PCB_LAYER_ID > getSelectedLayers()
std::function< void(int aNodeId)> m_callBackClose
DRC_RULE_EDITOR_CONTENT_PANEL_BASE * m_constraintPanel
const wxArrayString GetSignatures() const
static PCBEXPR_BUILTIN_FUNCTIONS & Instance()
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()
This file is part of the common library.
DRC_RULE_EDITOR_CONSTRAINT_NAME
@ ALLOWED_ORIENTATION
@ MINIMUM_ITEM_CLEARANCE
@ ROUTING_DIFF_PAIR
@ HOLE_TO_HOLE_CLEARANCE
@ BOARD_OUTLINE_CLEARANCE
@ MAXIMUM_VIA_COUNT
@ PHYSICAL_CLEARANCE
@ CREEPAGE_DISTANCE
@ ABSOLUTE_LENGTH
@ MINIMUM_CLEARANCE
@ MINIMUM_THERMAL_RELIEF_SPOKE_COUNT
@ BASIC_CLEARANCE
@ PERMITTED_LAYERS
@ MINIMUM_TEXT_HEIGHT_AND_THICKNESS
@ COPPER_TO_HOLE_CLEARANCE
@ HOLE_TO_HOLE_DISTANCE
#define _(s)
@ NONE
Definition eda_shape.h:69
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
static bool constraintNeedsTwoObjects(DRC_RULE_EDITOR_CONSTRAINT_NAME aConstraintType)
static constexpr const wxChar * KI_TRACE_DRC_RULE_EDITOR
see class PGM_BASE
@ RPT_SEVERITY_ERROR
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