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>
43#include <idf_parser.h>
44#include <scintilla_tricks.h>
45#include <wx/stc/stc.h>
48#include <tools/drc_tool.h>
49#include <pcbexpr_evaluator.h>
50#include <string_utils.h>
51
52#include <drc/drc_rule_parser.h>
65
66#include <eda_units.h>
77#include <properties/property.h>
79
80static constexpr const wxChar* KI_TRACE_DRC_RULE_EDITOR = wxT( "KI_TRACE_DRC_RULE_EDITOR" );
81
83{
84 switch( aConstraintType )
85 {
91 case HOLE_TO_HOLE_DISTANCE: return true;
92 default: return false;
93 }
94}
95
97 DRC_RULE_EDITOR_CONSTRAINT_NAME aConstraintType,
98 wxString* aConstraintTitle,
99 std::shared_ptr<DRC_RE_BASE_CONSTRAINT_DATA> aConstraintData ) :
101 m_board( aBoard ),
102 m_constraintTitle( aConstraintTitle ),
103 m_validationSucceeded( false ),
104 m_constraintData( aConstraintData ),
105 m_helpWindow( nullptr )
106{
107 wxLogTrace( KI_TRACE_DRC_RULE_EDITOR, wxS( "[PANEL_DRC_RULE_EDITOR] ctor START" ) );
108
109 m_constraintType = aConstraintType;
110 m_constraintPanel = getConstraintPanel( this, aConstraintType );
111 wxLogTrace( KI_TRACE_DRC_RULE_EDITOR, wxS( "[PANEL_DRC_RULE_EDITOR] adding constraint panel to sizer" ) );
112 m_constraintContentSizer->Add( dynamic_cast<wxPanel*>( m_constraintPanel ), 0, wxEXPAND | wxTOP, 5 );
113 m_layerList = m_board->GetEnabledLayers().UIOrder();
114 m_constraintHeaderTitle->SetLabelText( *aConstraintTitle + " Constraint" );
115
116 m_layerListChoiceCtrl = new wxChoice( this, wxID_ANY );
119 m_LayersComboBoxSizer->Add( m_layerListChoiceCtrl, 0, wxALL | wxEXPAND, 5 );
120 m_layerListChoiceCtrl->Bind( wxEVT_CHOICE, [this]( wxCommandEvent& )
121 {
123 if( dlg )
124 dlg->SetModified();
125 } );
126
127 // Hide layer selector for constraints where it doesn't apply
129 {
130 m_LayersComboBoxSizer->Show( false );
131 m_staticText711->Show( false );
132 m_staticline111->Show( false );
133 }
134
135 wxBoxSizer* buttonSizer = new wxBoxSizer( wxHORIZONTAL );
136 m_btnShowMatches = new wxButton( this, wxID_ANY, "Show Matches" );
137 buttonSizer->Add( m_btnShowMatches, 0, wxALL, 5 );
138
139 bContentSizer->Add( buttonSizer, 0, wxALIGN_RIGHT | wxALL, 2 );
140
142
143 m_btnShowMatches->Enable( true );
144
146
147 m_scintillaTricks = std::make_unique<SCINTILLA_TRICKS>(
148 m_textConditionCtrl, wxT( "()" ), false,
149 // onAcceptFn
150 [this]( wxKeyEvent& aEvent )
151 {
152 wxPostEvent( PAGED_DIALOG::GetDialog( this ), wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
153 },
154 // onCharFn
155 [this]( wxStyledTextEvent& aEvent )
156 {
157 onScintillaCharAdded( aEvent );
158 } );
159
160 m_textConditionCtrl->AutoCompSetSeparator( '|' );
161
162 // Hide the base class text control since we use the condition group panel
163 m_textConditionCtrl->Hide();
164
165 bool twoObjects = constraintNeedsTwoObjects( aConstraintType );
166
167 // Create condition group panel (multi-condition UI)
168 // Each condition row has its own custom query text control
169 wxLogTrace( KI_TRACE_DRC_RULE_EDITOR, wxS( "[PANEL_DRC_RULE_EDITOR] creating conditionGroupPanel" ) );
171 m_conditionGroupPanel->SetChangeCallback( [this]() {
173
175 if( dlg )
176 {
177 dlg->SetModified();
179 }
180 } );
181 wxLogTrace( KI_TRACE_DRC_RULE_EDITOR, wxS( "[PANEL_DRC_RULE_EDITOR] inserting conditionGroupPanel" ) );
182 m_conditionControlsSizer->Insert( 0, m_conditionGroupPanel, 0, wxEXPAND | wxBOTTOM, 5 );
183
184 m_nameCtrl->Bind( wxEVT_TEXT, [this]( wxCommandEvent& )
185 {
187 if( dlg )
188 dlg->SetModified();
189 });
190
191 m_commentCtrl->Bind( wxEVT_TEXT, [this]( wxCommandEvent& )
192 {
194 if( dlg )
195 dlg->SetModified();
196 });
197
198 // Hide the base class syntax check controls since we use inline validation
199 m_checkSyntaxBtnCtrl->Hide();
200 m_syntaxErrorReport->Hide();
201
202 m_netClassRegex.Compile( "^NetClass\\s*[!=]=\\s*$", wxRE_ADVANCED );
203 m_netNameRegex.Compile( "^NetName\\s*[!=]=\\s*$", wxRE_ADVANCED );
204 m_typeRegex.Compile( "^Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
205 m_viaTypeRegex.Compile( "^Via_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
206 m_padTypeRegex.Compile( "^Pad_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
207 m_pinTypeRegex.Compile( "^Pin_Type\\s*[!=]=\\s*$", wxRE_ADVANCED );
208 m_fabPropRegex.Compile( "^Fabrication_Property\\s*[!=]=\\s*$", wxRE_ADVANCED );
209 m_shapeRegex.Compile( "^Shape\\s*[!=]=\\s*$", wxRE_ADVANCED );
210 m_padShapeRegex.Compile( "^Pad_Shape\\s*[!=]=\\s*$", wxRE_ADVANCED );
211 m_padConnectionsRegex.Compile( "^Pad_Connections\\s*[!=]=\\s*$", wxRE_ADVANCED );
212 m_zoneConnStyleRegex.Compile( "^Zone_Connection_Style\\s*[!=]=\\s*$", wxRE_ADVANCED );
213 m_lineStyleRegex.Compile( "^Line_Style\\s*[!=]=\\s*$", wxRE_ADVANCED );
214 m_hJustRegex.Compile( "^Horizontal_Justification\\s*[!=]=\\s*$", wxRE_ADVANCED );
215 m_vJustRegex.Compile( "^Vertical_Justification\\s*[!=]=\\s*$", wxRE_ADVANCED );
216}
217
218
226
227
229{
230 if( m_constraintData )
231 {
232 m_nameCtrl->SetValue( m_constraintData->GetRuleName() );
233 m_commentCtrl->SetValue( m_constraintData->GetComment() );
234 setSelectedLayers( m_constraintData->GetLayers(), m_constraintData->GetLayerSource() );
235 wxString cond = m_constraintData->GetRuleCondition();
236
237 // Use the new condition group panel
238 // Each row manages its own custom query text control visibility
239 m_conditionGroupPanel->ParseCondition( cond );
240 }
241
243 return m_constraintPanel->TransferDataToWindow();
244
245 return true;
246}
247
248
250{
252 return wxEmptyString;
253
254 int sel = m_layerListChoiceCtrl->GetSelection();
255 if( sel <= 0 )
256 return wxEmptyString;
257
258 int layerValue = m_layerIDs[sel - 1];
259
260 switch( layerValue )
261 {
262 case LAYER_SEL_OUTER: return wxS( "outer" );
263 case LAYER_SEL_INNER: return wxS( "inner" );
264 case LAYER_SEL_TOP: return m_board ? m_board->GetLayerName( F_Cu ) : wxString();
265 case LAYER_SEL_BOTTOM: return m_board ? m_board->GetLayerName( B_Cu ) : wxString();
266 default:
267 if( layerValue >= 0 && m_board )
268 return m_board->GetLayerName( static_cast<PCB_LAYER_ID>( layerValue ) );
269 return wxEmptyString;
270 }
271}
272
273
275{
277 m_constraintPanel->TransferDataFromWindow();
278
279 m_constraintData->SetRuleName( m_nameCtrl->GetValue() );
280 m_constraintData->SetComment( m_commentCtrl->GetValue() );
281 m_constraintData->SetLayers( getSelectedLayers() );
282 m_constraintData->SetLayerSource( getSelectedLayerSource() );
283
284 // Use the new condition group panel
285 wxString combined = m_conditionGroupPanel->BuildCondition();
286 m_constraintData->SetRuleCondition( combined );
287
289 context.ruleName = m_nameCtrl->GetValue();
290 context.comment = m_commentCtrl->GetValue();
292 context.layerClause = buildLayerClause();
293 context.constraintCode = m_constraintData->GetConstraintCode();
294
295 wxString generatedRule = m_constraintPanel->GenerateRule( context );
296 m_constraintData->SetGeneratedRule( generatedRule );
297
298 wxLogTrace( KI_TRACE_DRC_RULE_EDITOR, wxS( "Generated rule '%s':\n%s" ),
299 context.ruleName, generatedRule );
300
301 return true;
302}
303
304
307{
308 EDA_UNITS units = EDA_UNITS::MM;
309
310 for( wxWindow* win = GetParent(); win; win = win->GetParent() )
311 {
312 if( auto* frame = dynamic_cast<EDA_BASE_FRAME*>( win ) )
313 {
314 units = frame->GetUserUnits();
315 break;
316 }
317 }
318
319 switch( aConstraintType )
320 {
321 case VIA_STYLE:
323 aParent,
324 dynamic_pointer_cast<DRC_RE_VIA_STYLE_CONSTRAINT_DATA>( m_constraintData ).get(),
325 units );
326
329 aParent,
330 dynamic_pointer_cast<DRC_RE_MINIMUM_TEXT_HEIGHT_THICKNESS_CONSTRAINT_DATA>( m_constraintData ).get(),
331 units );
332
335 aParent,
336 dynamic_pointer_cast<DRC_RE_ROUTING_DIFF_PAIR_CONSTRAINT_DATA>( m_constraintData ).get(),
337 units );
338
339 case ROUTING_WIDTH:
341 aParent,
342 dynamic_pointer_cast<DRC_RE_ROUTING_WIDTH_CONSTRAINT_DATA>( m_constraintData ).get(),
343 units );
344
345 case PERMITTED_LAYERS:
347 aParent,
348 dynamic_pointer_cast<DRC_RE_PERMITTED_LAYERS_CONSTRAINT_DATA>( m_constraintData ).get() );
349
352 aParent,
353 dynamic_pointer_cast<DRC_RE_ALLOWED_ORIENTATION_CONSTRAINT_DATA>( m_constraintData ).get() );
354
355 case CUSTOM_RULE:
356 return new DRC_RE_CUSTOM_RULE_PANEL(
357 aParent, dynamic_pointer_cast<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA>( m_constraintData ) );
358
360 case ABSOLUTE_LENGTH:
362 aParent,
363 dynamic_pointer_cast<DRC_RE_ABSOLUTE_LENGTH_TWO_CONSTRAINT_DATA>( m_constraintData ).get(),
364 units );
365
366 default:
367 if( DRC_RULE_EDITOR_UTILS::IsNumericInputType( aConstraintType ) )
368 {
370 aParent,
371 dynamic_pointer_cast<DRC_RE_NUMERIC_INPUT_CONSTRAINT_DATA>( m_constraintData ).get(),
372 units );
373 }
374 else if( DRC_RULE_EDITOR_UTILS::IsBoolInputType( aConstraintType ) )
375 {
377 aParent,
378 dynamic_pointer_cast<DRC_RE_BOOL_INPUT_CONSTRAINT_DATA>( m_constraintData ).get() );
379 }
380 else
381 {
382 return nullptr;
383 };
384 }
385}
386
387
388bool PANEL_DRC_RULE_EDITOR::ValidateInputs( int* aErrorCount, wxString* aValidationMessage )
389{
390 if( !m_callBackRuleNameValidation( m_constraintData->GetId(), m_nameCtrl->GetValue() ) )
391 {
392 m_validationSucceeded = false;
393 ( *aErrorCount )++;
395 DRC_RULE_EDITOR_UTILS::FormatErrorMessage( *aErrorCount, _( "Rule name must be unique." ) );
396 }
397
398 if( m_layerListChoiceCtrl->GetSelection() == wxNOT_FOUND )
399 {
400 m_validationSucceeded = false;
401 ( *aErrorCount )++;
403 DRC_RULE_EDITOR_UTILS::FormatErrorMessage( *aErrorCount, _( "Layer selection is required." ) );
404 }
405
407}
408
409
411{
412 return m_constraintPanel->GenerateRule( aContext );
413}
414
415
416void PANEL_DRC_RULE_EDITOR::onSaveButtonClicked( wxCommandEvent& aEvent )
417{
419 int errorCount = 0;
420 m_validationMessage.Clear();
421
422 ValidateInputs( &errorCount, &m_validationMessage );
423
424 if( !m_constraintPanel->ValidateInputs( &errorCount, &m_validationMessage ) )
425 {
426 m_validationSucceeded = false;
427 }
428
429 if( m_callBackSave )
430 {
432 }
433
435 m_btnShowMatches->Enable( true );
436}
437
438
439void PANEL_DRC_RULE_EDITOR::OnEnterKey( wxCommandEvent& aEvent )
440{
441 onSaveButtonClicked( aEvent );
442}
443
444
445void PANEL_DRC_RULE_EDITOR::Save( wxCommandEvent& aEvent )
446{
447 onSaveButtonClicked( aEvent );
448}
449
450
452{
455}
456
457
458void PANEL_DRC_RULE_EDITOR::Cancel( wxCommandEvent& aEvent )
459{
460 if( m_constraintData && m_constraintData->IsNew() )
461 onRemoveButtonClicked( aEvent );
462 else
463 onCloseButtonClicked( aEvent );
464}
465
466
468{
471}
472
473
474void PANEL_DRC_RULE_EDITOR::onScintillaCharAdded( wxStyledTextEvent& aEvent )
475{
476 if( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKey() == ' ' )
477 {
478 // This is just a short-cut for do-auto-complete
479 }
480
481 m_textConditionCtrl->SearchAnchor();
482
483 int currentPos = m_textConditionCtrl->GetCurrentPos();
484 int startPos = 0;
485
486 for( int line = m_textConditionCtrl->LineFromPosition( currentPos ); line > 0; line-- )
487 {
488 int lineStart = m_textConditionCtrl->PositionFromLine( line );
489 wxString beginning = m_textConditionCtrl->GetTextRange( lineStart, lineStart + 10 );
490
491 if( beginning.StartsWith( wxT( "(condition " ) ) )
492 {
493 startPos = lineStart;
494 break;
495 }
496 }
497
498 enum class EXPR_CONTEXT_T : int
499 {
500 NONE,
501 STRING,
502 SEXPR_OPEN,
503 SEXPR_TOKEN,
504 SEXPR_STRING,
505 STRUCT_REF
506 };
507
508 std::stack<wxString> sexprs;
509 wxString partial;
510 wxString last;
511 EXPR_CONTEXT_T context = EXPR_CONTEXT_T::NONE;
512 EXPR_CONTEXT_T expr_context = EXPR_CONTEXT_T::NONE;
513
514 for( int i = startPos; i < currentPos; ++i )
515 {
516 wxChar c = m_textConditionCtrl->GetCharAt( i );
517
518 if( c == '\\' )
519 {
520 i++; // skip escaped char
521 }
522 else if( context == EXPR_CONTEXT_T::STRING )
523 {
524 if( c == '"' )
525 {
526 context = EXPR_CONTEXT_T::NONE;
527 }
528 else
529 {
530 if( expr_context == EXPR_CONTEXT_T::STRING )
531 {
532 if( c == '\'' )
533 expr_context = EXPR_CONTEXT_T::NONE;
534 else
535 partial += c;
536 }
537 else if( c == '\'' )
538 {
539 last = partial;
540 partial = wxEmptyString;
541 expr_context = EXPR_CONTEXT_T::STRING;
542 }
543 else if( c == '.' )
544 {
545 partial = wxEmptyString;
546 expr_context = EXPR_CONTEXT_T::STRUCT_REF;
547 }
548 else
549 {
550 partial += c;
551 }
552 }
553 }
554 else if( c == '"' )
555 {
556 last = partial;
557 partial = wxEmptyString;
558 context = EXPR_CONTEXT_T::STRING;
559 }
560 else if( c == '(' )
561 {
562 if( context == EXPR_CONTEXT_T::SEXPR_OPEN && !partial.IsEmpty() )
563 {
564 m_textConditionCtrl->AutoCompCancel();
565 sexprs.push( partial );
566 }
567
568 partial = wxEmptyString;
569 context = EXPR_CONTEXT_T::SEXPR_OPEN;
570 }
571 else if( c == ')' )
572 {
573 context = EXPR_CONTEXT_T::NONE;
574 }
575 else if( c == ' ' )
576 {
577 if( context == EXPR_CONTEXT_T::SEXPR_OPEN && !partial.IsEmpty() )
578 {
579 m_textConditionCtrl->AutoCompCancel();
580 sexprs.push( partial );
581
582 if( partial == wxT( "condition" ) )
583 {
584 context = EXPR_CONTEXT_T::SEXPR_STRING;
585 }
586 else
587 {
588 context = EXPR_CONTEXT_T::NONE;
589 }
590
591 partial = wxEmptyString;
592 continue;
593 }
594
595 context = EXPR_CONTEXT_T::NONE;
596 }
597 else
598 {
599 partial += c;
600 }
601 }
602
603 wxString tokens;
604
605 if( context == EXPR_CONTEXT_T::SEXPR_OPEN )
606 {
607 if( sexprs.empty() )
608 {
609 tokens = wxT( "condition" );
610 }
611 }
612 else if( context == EXPR_CONTEXT_T::SEXPR_TOKEN )
613 {
614 if( sexprs.empty() )
615 {
616 /* badly formed grammar */
617 }
618 }
619 else if( context == EXPR_CONTEXT_T::SEXPR_STRING && !sexprs.empty() && sexprs.top() == wxT( "condition" ) )
620 {
621 m_textConditionCtrl->AddText( wxT( "\"" ) );
622 }
623 else if( context == EXPR_CONTEXT_T::STRING && !sexprs.empty() && sexprs.top() == wxT( "condition" ) )
624 {
625 if( expr_context == EXPR_CONTEXT_T::STRUCT_REF )
626 {
628 std::set<wxString> propNames;
629
630 for( const PROPERTY_MANAGER::CLASS_INFO& cls : propMgr.GetAllClasses() )
631 {
632 const std::vector<PROPERTY_BASE*>& props = propMgr.GetProperties( cls.type );
633
634 for( PROPERTY_BASE* prop : props )
635 {
636 // TODO: It would be nice to replace IsHiddenFromRulesEditor with a nickname
637 // system, so that two different properies don't need to be created. This is
638 // a bigger change than I want to make right now, though.
639 if( prop->IsHiddenFromRulesEditor() )
640 continue;
641
642 wxString ref( prop->Name() );
643 ref.Replace( wxT( " " ), wxT( "_" ) );
644 propNames.insert( ref );
645 }
646 }
647
648 for( const wxString& propName : propNames )
649 tokens += wxT( "|" ) + propName;
650
652
653 for( const wxString& funcSig : functions.GetSignatures() )
654 {
655 if( !funcSig.Contains( "DEPRECATED" ) )
656 tokens += wxT( "|" ) + funcSig;
657 }
658 }
659 else if( expr_context == EXPR_CONTEXT_T::STRING )
660 {
661 if( m_netClassRegex.Matches( last ) )
662 {
663 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
664 std::shared_ptr<NET_SETTINGS>& netSettings = bds.m_NetSettings;
665
666 for( const auto& [name, netclass] : netSettings->GetNetclasses() )
667 tokens += wxT( "|" ) + name;
668 }
669 else if( m_netNameRegex.Matches( last ) )
670 {
671 for( const wxString& netnameCandidate : m_board->GetNetClassAssignmentCandidates() )
672 tokens += wxT( "|" ) + netnameCandidate;
673 }
674 else if( m_typeRegex.Matches( last ) )
675 {
676 tokens = wxT( "Bitmap|"
677 "Dimension|"
678 "Footprint|"
679 "Graphic|"
680 "Group|"
681 "Leader|"
682 "Pad|"
683 "Target|"
684 "Text|"
685 "Text Box|"
686 "Track|"
687 "Via|"
688 "Zone" );
689 }
690 else if( m_viaTypeRegex.Matches( last ) )
691 {
692 tokens = wxT( "Through|"
693 "Blind/buried|"
694 "Micro" );
695 }
696 else if( m_padTypeRegex.Matches( last ) )
697 {
698 tokens = wxT( "Through-hole|"
699 "SMD|"
700 "Edge connector|"
701 "NPTH, mechanical" );
702 }
703 else if( m_pinTypeRegex.Matches( last ) )
704 {
705 tokens = wxT( "Input|"
706 "Output|"
707 "Bidirectional|"
708 "Tri-state|"
709 "Passive|"
710 "Free|"
711 "Unspecified|"
712 "Power input|"
713 "Power output|"
714 "Open collector|"
715 "Open emitter|"
716 "Unconnected" );
717 }
718 else if( m_fabPropRegex.Matches( last ) )
719 {
720 tokens = wxT( "None|"
721 "BGA pad|"
722 "Fiducial, global to board|"
723 "Fiducial, local to footprint|"
724 "Test point pad|"
725 "Heatsink pad|"
726 "Castellated pad" );
727 }
728 else if( m_shapeRegex.Matches( last ) )
729 {
730 tokens = wxT( "Segment|"
731 "Rectangle|"
732 "Arc|"
733 "Circle|"
734 "Polygon|"
735 "Bezier" );
736 }
737 else if( m_padShapeRegex.Matches( last ) )
738 {
739 tokens = wxT( "Circle|"
740 "Rectangle|"
741 "Oval|"
742 "Trapezoid|"
743 "Rounded rectangle|"
744 "Chamfered rectangle|"
745 "Custom" );
746 }
747 else if( m_padConnectionsRegex.Matches( last ) )
748 {
749 tokens = wxT( "Inherited|"
750 "None|"
751 "Solid|"
752 "Thermal reliefs|"
753 "Thermal reliefs for PTH" );
754 }
755 else if( m_zoneConnStyleRegex.Matches( last ) )
756 {
757 tokens = wxT( "Inherited|"
758 "None|"
759 "Solid|"
760 "Thermal reliefs" );
761 }
762 else if( m_lineStyleRegex.Matches( last ) )
763 {
764 tokens = wxT( "Default|"
765 "Solid|"
766 "Dashed|"
767 "Dotted|"
768 "Dash-Dot|"
769 "Dash-Dot-Dot" );
770 }
771 else if( m_hJustRegex.Matches( last ) )
772 {
773 tokens = wxT( "Left|"
774 "Center|"
775 "Right" );
776 }
777 else if( m_vJustRegex.Matches( last ) )
778 {
779 tokens = wxT( "Top|"
780 "Center|"
781 "Bottom" );
782 }
783 }
784 }
785
786 if( !tokens.IsEmpty() )
787 m_scintillaTricks->DoAutocomplete( partial, wxSplit( tokens, '|' ) );
788}
789
790
791void PANEL_DRC_RULE_EDITOR::onSyntaxHelp( wxHyperlinkEvent& aEvent )
792{
793 if( m_helpWindow )
794 {
795 m_helpWindow->ShowModeless();
796 return;
797 }
798
799 std::vector<wxString> msg;
800 msg.clear();
801
802 wxString t =
804 ;
805 msg.emplace_back( t );
806
807 t =
809 ;
810 msg.emplace_back( t );
811
812 t =
814 ;
815 msg.emplace_back( t );
816
817 t =
819 ;
820 msg.emplace_back( t );
821
822 t =
824 ;
825 msg.emplace_back( t );
826
827 t =
829 ;
830 msg.emplace_back( t );
831
832 t =
834 ;
835 msg.emplace_back( t );
836
837 t =
839 ;
840 msg.emplace_back( t );
841
842
843 wxString msg_txt = wxEmptyString;
844
845 for( wxString i : msg )
846 msg_txt << wxGetTranslation( i );
847
848#ifdef __WXMAC__
849 msg_txt.Replace( wxT( "Ctrl+" ), wxT( "Cmd+" ) );
850#endif
851 const wxString& msGg_txt = msg_txt;
852
853 m_helpWindow = new HTML_MESSAGE_BOX( this, _( "Syntax Help" ) );
854 m_helpWindow->SetDialogSizeInDU( 420, 320 );
855
856 wxString html_txt = wxEmptyString;
857 ConvertMarkdown2Html( msGg_txt, html_txt );
858
859 html_txt.Replace( wxS( "<td" ), wxS( "<td valign=top" ) );
860 m_helpWindow->AddHTML_Text( html_txt );
861
862 m_helpWindow->ShowModeless();
863}
864
865
866void PANEL_DRC_RULE_EDITOR::onCheckSyntax( wxCommandEvent& event )
867{
868 m_syntaxErrorReport->Clear();
869
870 // Get the complete condition from the condition group panel
871 wxString condition = m_conditionGroupPanel->BuildCondition();
872
873 if( condition.IsEmpty() )
874 {
875 wxString msg = _( "ERROR: No condition text provided for validation." );
877 m_syntaxErrorReport->Flush();
878 return;
879 }
880
881 try
882 {
883 std::vector<std::shared_ptr<DRC_RULE>> dummyRules;
884 wxString ruleTemplate = L"(version 1)\n(rule default\n (condition \"%s\")\n)";
885 wxString formattedRule = wxString::Format( ruleTemplate, condition );
886 DRC_RULES_PARSER ruleParser( formattedRule, _( "DRC rule" ) );
887 ruleParser.Parse( dummyRules, m_syntaxErrorReport );
888 }
889 catch( PARSE_ERROR& pe )
890 {
891 wxString msg = wxString::Format( wxT( "%s <a href='%d:%d'>%s</a>" ),
892 _( "ERROR:" ),
893 pe.lineNumber, pe.byteIndex, pe.ParseProblem() );
894
896 }
897
898 m_syntaxErrorReport->Flush();
899}
900
901
902void PANEL_DRC_RULE_EDITOR::onErrorLinkClicked( wxHtmlLinkEvent& event )
903{
904 wxString link = event.GetLinkInfo().GetHref();
905 wxArrayString parts;
906 long line = 0, offset = 0;
907
908 wxStringSplit( link, parts, ':' );
909
910 if( parts.size() > 1 )
911 {
912 parts[0].ToLong( &line );
913 parts[1].ToLong( &offset );
914 }
915
916 int pos = m_textConditionCtrl->PositionFromLine( line - 1 ) + ( offset - 1 );
917
918 m_textConditionCtrl->GotoPos( pos );
919
920 m_textConditionCtrl->SetFocus();
921}
922
923
924void PANEL_DRC_RULE_EDITOR::onContextMenu( wxMouseEvent& event )
925{
926 wxMenu menu;
927
928 menu.Append( wxID_UNDO, _( "Undo" ) );
929 menu.Append( wxID_REDO, _( "Redo" ) );
930
931 menu.AppendSeparator();
932
933 menu.Append( 1, _( "Cut" ) ); // Don't use wxID_CUT, wxID_COPY, etc. On Mac (at least),
934 menu.Append( 2, _( "Copy" ) ); // wxWidgets never delivers them to us.
935 menu.Append( 3, _( "Paste" ) );
936 menu.Append( 4, _( "Delete" ) );
937
938 menu.AppendSeparator();
939
940 menu.Append( 5, _( "Select All" ) );
941
942 menu.AppendSeparator();
943
944 menu.Append( wxID_ZOOM_IN, _( "Zoom In" ) );
945 menu.Append( wxID_ZOOM_OUT, _( "Zoom Out" ) );
946
947
948 switch( GetPopupMenuSelectionFromUser( menu ) )
949 {
950 case wxID_UNDO: m_textConditionCtrl->Undo(); break;
951 case wxID_REDO: m_textConditionCtrl->Redo(); break;
952
953 case 1: m_textConditionCtrl->Cut(); break;
954 case 2: m_textConditionCtrl->Copy(); break;
955 case 3: m_textConditionCtrl->Paste(); break;
956 case 4:
957 {
958 long from, to;
959 m_textConditionCtrl->GetSelection( &from, &to );
960
961 if( to > from )
962 m_textConditionCtrl->DeleteRange( from, to );
963
964 break;
965 }
966
967 case 5: m_textConditionCtrl->SelectAll(); break;
968
969 case wxID_ZOOM_IN: m_textConditionCtrl->ZoomIn(); break;
970 case wxID_ZOOM_OUT: m_textConditionCtrl->ZoomOut(); break;
971 }
972}
973
974
976{
977 if( !m_constraintData )
978 {
979 wxLogTrace( KI_TRACE_DRC_RULE_EDITOR, wxS( "Show Matches clicked: no constraint data" ) );
980 return;
981 }
982
983 wxLogTrace( KI_TRACE_DRC_RULE_EDITOR,
984 wxS( "Show Matches clicked: nodeId=%d, rule='%s', code='%s'" ),
985 m_constraintData->GetId(), m_constraintData->GetRuleName(),
986 m_constraintData->GetConstraintCode() );
987
989 {
990 int matchCount = m_callBackShowMatches( m_constraintData->GetId() );
991
992 if( matchCount < 0 )
993 {
994 m_btnShowMatches->SetLabel( _( "Show Matches (error)" ) );
995 }
996 else
997 {
998 m_btnShowMatches->SetLabel( wxString::Format( _( "Show Matches (%d)" ), matchCount ) );
999 }
1000 }
1001}
1002
1003
1005{
1006 m_btnShowMatches->SetLabel( _( "Show Matches" ) );
1007}
1008
1009
1011{
1012 m_layerListChoiceCtrl->Clear();
1013 m_layerIDs.clear();
1014
1015 m_layerListChoiceCtrl->Append( _( "Any" ) );
1016
1017 switch( aCategory )
1018 {
1020 m_layerListChoiceCtrl->Append( _( "outer" ) );
1021 m_layerIDs.push_back( LAYER_SEL_OUTER );
1022 m_layerListChoiceCtrl->Append( _( "inner" ) );
1023 m_layerIDs.push_back( LAYER_SEL_INNER );
1024
1025 for( PCB_LAYER_ID id : m_board->GetEnabledLayers().CuStack() )
1026 {
1027 m_layerListChoiceCtrl->Append( m_board->GetLayerName( id ) );
1028 m_layerIDs.push_back( static_cast<int>( id ) );
1029 }
1030
1031 break;
1032
1034 m_layerListChoiceCtrl->Append( m_board->GetLayerName( F_SilkS ) );
1035 m_layerIDs.push_back( static_cast<int>( F_SilkS ) );
1036 m_layerListChoiceCtrl->Append( m_board->GetLayerName( B_SilkS ) );
1037 m_layerIDs.push_back( static_cast<int>( B_SilkS ) );
1038 break;
1039
1041 m_layerListChoiceCtrl->Append( m_board->GetLayerName( F_Mask ) );
1042 m_layerIDs.push_back( static_cast<int>( F_Mask ) );
1043 m_layerListChoiceCtrl->Append( m_board->GetLayerName( B_Mask ) );
1044 m_layerIDs.push_back( static_cast<int>( B_Mask ) );
1045 break;
1046
1048 m_layerListChoiceCtrl->Append( m_board->GetLayerName( F_Paste ) );
1049 m_layerIDs.push_back( static_cast<int>( F_Paste ) );
1050 m_layerListChoiceCtrl->Append( m_board->GetLayerName( B_Paste ) );
1051 m_layerIDs.push_back( static_cast<int>( B_Paste ) );
1052 break;
1053
1055 m_layerListChoiceCtrl->Append( _( "Top" ) );
1056 m_layerIDs.push_back( LAYER_SEL_TOP );
1057 m_layerListChoiceCtrl->Append( _( "Bottom" ) );
1058 m_layerIDs.push_back( LAYER_SEL_BOTTOM );
1059 break;
1060
1062 m_layerListChoiceCtrl->Append( _( "outer" ) );
1063 m_layerIDs.push_back( LAYER_SEL_OUTER );
1064 m_layerListChoiceCtrl->Append( _( "inner" ) );
1065 m_layerIDs.push_back( LAYER_SEL_INNER );
1066
1067 for( PCB_LAYER_ID id : m_board->GetEnabledLayers().UIOrder() )
1068 {
1069 m_layerListChoiceCtrl->Append( m_board->GetLayerName( id ) );
1070 m_layerIDs.push_back( static_cast<int>( id ) );
1071 }
1072
1073 break;
1074
1076 // No layers to add - selector will be hidden
1077 break;
1078 }
1079
1080 m_layerListChoiceCtrl->SetSelection( 0 );
1081}
1082
1083
1085{
1087 return wxEmptyString;
1088
1089 int selection = m_layerListChoiceCtrl->GetSelection();
1090
1091 if( selection <= 0 )
1092 return wxEmptyString;
1093
1094 size_t index = static_cast<size_t>( selection - 1 );
1095
1096 if( index >= m_layerIDs.size() )
1097 return wxEmptyString;
1098
1099 int layerValue = m_layerIDs[index];
1100
1101 // Handle synthetic layers
1102 if( layerValue < 0 )
1103 {
1104 switch( layerValue )
1105 {
1106 case LAYER_SEL_OUTER:
1107 return wxS( "(layer outer)" );
1108
1109 case LAYER_SEL_INNER:
1110 return wxS( "(layer inner)" );
1111
1112 case LAYER_SEL_TOP:
1114
1115 case LAYER_SEL_BOTTOM:
1117
1118 default:
1119 return wxEmptyString;
1120 }
1121 }
1122
1123 // Real layer ID
1124 PCB_LAYER_ID layerId = static_cast<PCB_LAYER_ID>( layerValue );
1125 wxString clause = wxString::Format( wxS( "(layer \"%s\")" ), m_board->GetLayerName( layerId ) );
1126 wxLogTrace( KI_TRACE_DRC_RULE_EDITOR, wxS( "Layer clause: %s" ), clause );
1127 return clause;
1128}
1129
1131{
1132 int sel = m_layerListChoiceCtrl->GetSelection();
1133
1134 if( sel <= 0 )
1135 return {};
1136
1137 int layerValue = m_layerIDs[sel - 1];
1138
1139 // Translate pseudo-IDs to real layer IDs
1140 if( layerValue < 0 )
1141 {
1142 switch( layerValue )
1143 {
1144 case LAYER_SEL_OUTER: return { F_Cu, B_Cu };
1145 case LAYER_SEL_INNER: return { In1_Cu };
1146 case LAYER_SEL_TOP: return { F_Cu };
1147 case LAYER_SEL_BOTTOM: return { B_Cu };
1148 default: return {};
1149 }
1150 }
1151
1152 return { static_cast<PCB_LAYER_ID>( layerValue ) };
1153}
1154
1155
1156void PANEL_DRC_RULE_EDITOR::setSelectedLayers( const std::vector<PCB_LAYER_ID>& aLayers,
1157 const wxString& aLayerSource )
1158{
1159 // Check for synthetic layer keywords first (for round-trip preservation)
1160 if( aLayerSource == wxS( "outer" ) )
1161 {
1162 for( size_t i = 0; i < m_layerIDs.size(); ++i )
1163 {
1164 if( m_layerIDs[i] == LAYER_SEL_OUTER )
1165 {
1166 m_layerListChoiceCtrl->SetSelection( static_cast<int>( i ) + 1 );
1167 return;
1168 }
1169 }
1170 }
1171
1172 if( aLayerSource == wxS( "inner" ) )
1173 {
1174 for( size_t i = 0; i < m_layerIDs.size(); ++i )
1175 {
1176 if( m_layerIDs[i] == LAYER_SEL_INNER )
1177 {
1178 m_layerListChoiceCtrl->SetSelection( static_cast<int>( i ) + 1 );
1179 return;
1180 }
1181 }
1182 }
1183
1184 // Map real layer IDs to Top/Bottom pseudo-entries (for TOP_BOTTOM_ANY dropdowns)
1185 if( !aLayers.empty() )
1186 {
1187 PCB_LAYER_ID target = aLayers.front();
1188 bool isFront = IsFrontLayer( target );
1189 bool isBack = IsBackLayer( target );
1190
1191 if( isFront || isBack )
1192 {
1193 int pseudoId = isFront ? LAYER_SEL_TOP : LAYER_SEL_BOTTOM;
1194
1195 for( size_t i = 0; i < m_layerIDs.size(); ++i )
1196 {
1197 if( m_layerIDs[i] == pseudoId )
1198 {
1199 m_layerListChoiceCtrl->SetSelection( static_cast<int>( i ) + 1 );
1200 return;
1201 }
1202 }
1203 }
1204 }
1205
1206 // Handle empty layers (default to "Any")
1207 if( aLayers.empty() )
1208 {
1209 m_layerListChoiceCtrl->SetSelection( 0 );
1210 return;
1211 }
1212
1213 // Find and select the matching layer
1214 int target = static_cast<int>( aLayers.front() );
1215
1216 for( size_t i = 0; i < m_layerIDs.size(); ++i )
1217 {
1218 if( m_layerIDs[i] == target )
1219 {
1220 m_layerListChoiceCtrl->SetSelection( static_cast<int>( i ) + 1 );
1221 return;
1222 }
1223 }
1224
1225 m_layerListChoiceCtrl->SetSelection( 0 );
1226}
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
Overlay panel for absolute length constraints showing min, opt, and max length fields positioned over...
Overlay panel for allowed orientation constraints showing checkboxes for each orientation angle posit...
Overlay panel for boolean input constraints showing a single checkbox positioned over a diagram.
Container panel that manages multiple condition rows with boolean operators.
Simple panel used for editing custom rule text.
Overlay panel for minimum text height and thickness constraints showing text dimension fields positio...
Overlay panel for numeric input constraints showing a single numeric value field positioned over a di...
Overlay panel for permitted layers constraints showing checkboxes for top and bottom layer selection ...
Overlay panel for differential pair routing constraints showing gap, width, and uncoupled length fiel...
Overlay panel for routing width constraints showing min/preferred/max width fields positioned over a ...
Overlay panel for via style constraints showing via diameter and hole size fields positioned over a d...
void Parse(std::vector< std::shared_ptr< DRC_RULE > > &aRules, REPORTER *aReporter)
static bool IsNumericInputType(const DRC_RULE_EDITOR_CONSTRAINT_NAME &aConstraintType)
static wxString FormatErrorMessage(int aErrorCount, const wxString &aErrorMessage)
static wxString TranslateTopBottomLayer(DRC_RULE_EDITOR_CONSTRAINT_NAME aConstraintType, bool aIsTop)
Translate a top/bottom selection to the appropriate layer clause or condition.
static DRC_LAYER_CATEGORY GetLayerCategoryForConstraint(DRC_RULE_EDITOR_CONSTRAINT_NAME aConstraintType)
Get the layer category for a constraint type.
static bool IsBoolInputType(const DRC_RULE_EDITOR_CONSTRAINT_NAME &aConstraintType)
The base frame for deriving all KiCad main window classes.
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
wxString getSelectedLayerSource() const
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.
void setSelectedLayers(const std::vector< PCB_LAYER_ID > &aLayers, const wxString &aLayerSource=wxEmptyString)
DRC_LAYER_CATEGORY m_layerCategory
std::function< bool(int aNodeId, wxString aRuleName)> m_callBackRuleNameValidation
void onSyntaxHelp(wxHyperlinkEvent &aEvent) override
Displays a modeless help window with syntax and rule documentation.
std::vector< int > m_layerIDs
void populateLayerSelector(DRC_LAYER_CATEGORY aCategory)
DRC_RULE_EDITOR_CONSTRAINT_NAME m_constraintType
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
void OnEnterKey(wxCommandEvent &aEvent) override
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)
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)
std::vector< PCB_LAYER_ID > getSelectedLayers()
std::function< void(int aNodeId)> m_callBackClose
bool ValidateInputs(int *aErrorCount, wxString *aValidationMessage) override
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()
void SetModified()
Marks the dialog as modified, indicating unsaved changes.
void RefreshContentScrollArea()
Recalculates the scrolled content area's virtual size based on the current content panel's best size,...
static RULE_EDITOR_DIALOG_BASE * GetDialog(wxWindow *aWindow)
Static method to retrieve the rule editor dialog instance associated with a given window.
This file is part of the common library.
DRC_RULE_EDITOR_CONSTRAINT_NAME
@ ALLOWED_ORIENTATION
@ ROUTING_DIFF_PAIR
@ HOLE_TO_HOLE_CLEARANCE
@ PHYSICAL_CLEARANCE
@ CREEPAGE_DISTANCE
@ ABSOLUTE_LENGTH
@ MINIMUM_CLEARANCE
@ PERMITTED_LAYERS
@ MINIMUM_TEXT_HEIGHT_AND_THICKNESS
@ COPPER_TO_HOLE_CLEARANCE
@ HOLE_TO_HOLE_DISTANCE
@ MATCHED_LENGTH_DIFF_PAIR
DRC_LAYER_CATEGORY
Layer categories for filtering the layer selector dropdown.
@ NO_LAYER_SELECTOR
Hide layer selector entirely.
@ TOP_BOTTOM_ANY
Simplified top/bottom/any selector with custom translation.
@ SOLDERMASK_ONLY
F_Mask, B_Mask.
@ GENERAL_ANY_LAYER
All layers + inner/outer synthetic.
@ SOLDERPASTE_ONLY
F_Paste, B_Paste.
@ COPPER_ONLY
Copper layers + inner/outer synthetic.
@ SILKSCREEN_ONLY
F_SilkS, B_SilkS.
@ LAYER_SEL_BOTTOM
Context-dependent back/bottom layer.
@ LAYER_SEL_OUTER
External copper layers (F_Cu + B_Cu)
@ LAYER_SEL_TOP
Context-dependent front/top layer.
@ LAYER_SEL_INNER
Internal copper layers (In1_Cu through In30_Cu)
#define _(s)
@ NONE
Definition eda_shape.h:69
EDA_UNITS
Definition eda_units.h:48
bool IsFrontLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a front layer.
Definition layer_ids.h:780
bool IsBackLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a back layer.
Definition layer_ids.h:803
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ F_Paste
Definition layer_ids.h:104
@ B_Mask
Definition layer_ids.h:98
@ B_Cu
Definition layer_ids.h:65
@ F_Mask
Definition layer_ids.h:97
@ B_Paste
Definition layer_ids.h:105
@ F_SilkS
Definition layer_ids.h:100
@ In1_Cu
Definition layer_ids.h:66
@ B_SilkS
Definition layer_ids.h:101
@ F_Cu
Definition layer_ids.h:64
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