KiCad PCB EDA Suite
Loading...
Searching...
No Matches
drc_re_custom_rule_panel.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, see <https://www.gnu.org/licenses/>.
18 */
19
21
22#include <stack>
23
24#include <drc/drc_rule.h>
25#include <drc/drc_rule_parser.h>
26#include <ki_exception.h>
27#include <reporter.h>
28#include <scintilla_tricks.h>
30#include <wx/button.h>
31#include <wx/sizer.h>
32#include <wx/stc/stc.h>
33#include <wx/tipwin.h>
34
36 wxWindow* aParent, std::shared_ptr<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA> aConstraintData ) :
37 wxPanel( aParent ),
38 m_constraintData( aConstraintData ),
39 m_textCtrl( nullptr ),
40 m_checkSyntaxBtn( nullptr )
41{
42 wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
43
44 m_textCtrl = new wxStyledTextCtrl( this, wxID_ANY );
45 m_textCtrl->SetToolTip( _( "Complete rule text in the DRC rule language" ) );
46 sizer->Add( m_textCtrl, 1, wxEXPAND | wxALL, 5 );
47
48 m_checkSyntaxBtn = new wxButton( this, wxID_ANY, _( "Check Syntax" ) );
49 sizer->Add( m_checkSyntaxBtn, 0, wxALIGN_RIGHT | wxRIGHT | wxBOTTOM, 5 );
50
51 SetSizer( sizer );
52
53 m_textCtrl->Bind( wxEVT_STC_CHANGE,
54 [this]( wxStyledTextEvent& )
55 {
57
58 if( dlg )
59 dlg->SetModified();
60 } );
61
62 m_scintillaTricks = std::make_unique<SCINTILLA_TRICKS>(
63 m_textCtrl, wxT( "()" ), false,
64 // onAcceptFn
65 []( wxKeyEvent& aEvent )
66 {
67 aEvent.Skip();
68 },
69 // onCharFn
70 [this]( wxStyledTextEvent& aEvent )
71 {
72 onScintillaCharAdded( aEvent );
73 } );
74
75 m_textCtrl->AutoCompSetSeparator( '|' );
76
78}
79
80
84
85
87{
89 {
90 wxString text = m_constraintData->GetRuleText();
91
92 if( text.IsEmpty() )
93 {
94 text = wxS( " (constraint clearance (min 0.2mm))" );
95 }
96
97 m_textCtrl->SetValue( text );
98 }
99
100 return true;
101}
102
103
105{
106 if( m_constraintData )
107 m_constraintData->SetRuleText( m_textCtrl->GetValue() );
108
109 return true;
110}
111
112
113bool DRC_RE_CUSTOM_RULE_PANEL::ValidateInputs( int* aErrorCount, wxString* aValidationMessage )
114{
115 (void) aErrorCount;
116 (void) aValidationMessage;
117 return true;
118}
119
120
122{
123 wxString body;
124
125 if( m_textCtrl )
126 body = m_textCtrl->GetValue();
127 else if( m_constraintData )
128 body = m_constraintData->GetRuleText();
129
130 if( body.Trim().IsEmpty() )
131 return wxEmptyString;
132
133 wxString ruleName = aContext.ruleName;
134 ruleName.Replace( wxS( "\"" ), wxS( "\\\"" ) );
135
136 wxString rule;
137 rule << wxS( "(rule \"" ) << ruleName << wxS( "\"\n" );
138
139 if( !aContext.comment.IsEmpty() )
140 {
141 wxArrayString lines = wxSplit( aContext.comment, '\n', '\0' );
142
143 for( const wxString& line : lines )
144 {
145 if( line.IsEmpty() )
146 continue;
147
148 rule << wxS( "\t# " ) << line << wxS( "\n" );
149 }
150 }
151
152 rule << body << wxS( ")" );
153
154 return rule;
155}
156
157
158void DRC_RE_CUSTOM_RULE_PANEL::UpdateRuleName( const wxString& aName )
159{
160 if( m_constraintData )
161 m_constraintData->SetRuleName( aName );
162}
163
164
165void DRC_RE_CUSTOM_RULE_PANEL::onScintillaCharAdded( wxStyledTextEvent& aEvent )
166{
167 if( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKey() == ' ' )
168 {
169 // Short-cut for do-auto-complete
170 }
171
172 m_textCtrl->SearchAnchor();
173
174 wxString rules = m_textCtrl->GetText();
175 int currentPos = m_textCtrl->GetCurrentPos();
176 int startPos = 0;
177
178 enum class EXPR_CONTEXT_T : int
179 {
180 NONE,
181 STRING,
182 SEXPR_OPEN,
183 SEXPR_TOKEN,
184 };
185
186 std::stack<wxString> sexprs;
187 wxString partial;
188 EXPR_CONTEXT_T context = EXPR_CONTEXT_T::NONE;
189
190 for( int i = startPos; i < currentPos; ++i )
191 {
192 wxChar c = m_textCtrl->GetCharAt( i );
193
194 if( c == '\\' )
195 {
196 i++; // skip escaped char
197 }
198 else if( context == EXPR_CONTEXT_T::STRING )
199 {
200 if( c == '"' )
201 context = EXPR_CONTEXT_T::NONE;
202 }
203 else if( c == '"' )
204 {
205 partial = wxEmptyString;
206 context = EXPR_CONTEXT_T::STRING;
207 }
208 else if( c == '(' )
209 {
210 if( context == EXPR_CONTEXT_T::SEXPR_OPEN && !partial.IsEmpty() )
211 {
212 m_textCtrl->AutoCompCancel();
213 sexprs.push( partial );
214 }
215
216 partial = wxEmptyString;
217 context = EXPR_CONTEXT_T::SEXPR_OPEN;
218 }
219 else if( c == ')' )
220 {
221 if( !sexprs.empty() )
222 sexprs.pop();
223
224 context = EXPR_CONTEXT_T::NONE;
225 }
226 else if( c == ' ' )
227 {
228 if( context == EXPR_CONTEXT_T::SEXPR_OPEN && !partial.IsEmpty() )
229 {
230 m_textCtrl->AutoCompCancel();
231 sexprs.push( partial );
232 context = EXPR_CONTEXT_T::SEXPR_TOKEN;
233 partial = wxEmptyString;
234 continue;
235 }
236
237 context = EXPR_CONTEXT_T::NONE;
238 }
239 else
240 {
241 partial += c;
242 }
243 }
244
245 wxString tokens;
246
247 if( context == EXPR_CONTEXT_T::SEXPR_OPEN )
248 {
249 if( sexprs.empty() )
250 {
251 tokens = wxT( "condition|constraint|layer|severity" );
252 }
253 else if( sexprs.top() == wxT( "rule" ) )
254 {
255 // Inside (rule ...) - suggest sub-expressions
256 tokens = wxT( "condition|constraint|layer|severity" );
257 }
258 else if( sexprs.top() == wxT( "constraint" ) )
259 {
260 // Inside (constraint ...) - suggest constraint parameters
261 tokens = wxT( "min|max|opt" );
262 }
263 }
264 else if( context == EXPR_CONTEXT_T::SEXPR_TOKEN )
265 {
266 if( !sexprs.empty() && sexprs.top() == wxT( "constraint" ) )
267 {
268 // After (constraint - suggest constraint types
269 tokens = wxT( "annular_width|"
270 "assertion|"
271 "clearance|"
272 "connection_width|"
273 "courtyard_clearance|"
274 "creepage|"
275 "diff_pair_gap|"
276 "diff_pair_uncoupled|"
277 "disallow|"
278 "edge_clearance|"
279 "hole_clearance|"
280 "hole_size|"
281 "hole_to_hole|"
282 "length|"
283 "max_error|"
284 "min_resolved_spokes|"
285 "physical_clearance|"
286 "physical_hole_clearance|"
287 "silk_clearance|"
288 "skew|"
289 "text_height|"
290 "text_thickness|"
291 "thermal_relief_gap|"
292 "thermal_spoke_width|"
293 "track_angle|"
294 "track_segment_length|"
295 "track_width|"
296 "via_count|"
297 "via_diameter|"
298 "zone_connection" );
299 }
300 else if( !sexprs.empty() && sexprs.top() == wxT( "layer" ) )
301 {
302 // After (layer - suggest layer keywords
303 tokens = wxT( "inner|outer" );
304 }
305 else if( !sexprs.empty() && sexprs.top() == wxT( "severity" ) )
306 {
307 // After (severity - suggest severity levels
308 tokens = wxT( "error|warning|ignore|exclusion" );
309 }
310 }
311
312 if( !tokens.IsEmpty() )
313 m_scintillaTricks->DoAutocomplete( partial, wxSplit( tokens, '|' ) );
314}
315
316
317void DRC_RE_CUSTOM_RULE_PANEL::onCheckSyntax( wxCommandEvent& aEvent )
318{
319 // Close any existing tip window
320 if( m_tipWindow )
321 {
322 m_tipWindow->Close();
323 m_tipWindow = nullptr;
324 }
325
326 wxString body = m_textCtrl->GetText();
327 wxString ruleName = m_constraintData ? m_constraintData->GetRuleName() : wxString( wxS( "test" ) );
328 ruleName.Replace( wxS( "\"" ), wxS( "\\\"" ) );
329 wxString rulesText = wxString::Format( wxS( "(version 2)\n(rule \"%s\"\n%s)" ), ruleName, body );
330
331 if( body.Trim().IsEmpty() )
332 {
333#if wxCHECK_VERSION( 3, 3, 2 )
334 m_tipWindow = wxTipWindow::New( this, _( "No rule text to check." ) );
335#else
336 m_tipWindow = new wxTipWindow( this, _( "No rule text to check." ) );
337 m_tipWindow->SetTipWindowPtr( &m_tipWindow );
338#endif
339 return;
340 }
341
343
344 try
345 {
346 std::vector<std::shared_ptr<DRC_RULE>> dummyRules;
347 DRC_RULES_PARSER parser( rulesText, _( "Custom rule" ) );
348
349 parser.Parse( dummyRules, &reporter );
350 }
351 catch( PARSE_ERROR& pe )
352 {
353 reporter.Report( wxString::Format( _( "ERROR at line %d, column %d: %s" ),
354 pe.lineNumber,
355 pe.byteIndex,
356 pe.ParseProblem() ),
358 }
359
360 wxString message;
361
362 if( reporter.HasMessageOfSeverity( RPT_SEVERITY_ERROR ) )
363 {
364 message = reporter.GetMessages();
365 }
366 else if( reporter.HasMessage() )
367 {
368 message = _( "Syntax check passed with warnings:\n" ) + reporter.GetMessages();
369 }
370 else
371 {
372 message = _( "Syntax OK" );
373 }
374
375#if wxCHECK_VERSION( 3, 3, 2 )
376 m_tipWindow = wxTipWindow::New( this, message );
377#else
378 m_tipWindow = new wxTipWindow( this, message );
379 m_tipWindow->SetTipWindowPtr( &m_tipWindow );
380#endif
381}
void UpdateRuleName(const wxString &aName)
void onScintillaCharAdded(wxStyledTextEvent &aEvent)
bool ValidateInputs(int *aErrorCount, wxString *aValidationMessage) override
wxString GenerateRule(const RULE_GENERATION_CONTEXT &aContext) override
std::shared_ptr< DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA > m_constraintData
std::unique_ptr< SCINTILLA_TRICKS > m_scintillaTricks
DRC_RE_CUSTOM_RULE_PANEL(wxWindow *aParent, std::shared_ptr< DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA > aConstraintData)
void onCheckSyntax(wxCommandEvent &aEvent)
void Parse(std::vector< std::shared_ptr< DRC_RULE > > &aRules, REPORTER *aReporter)
void SetModified()
Marks the dialog as modified, indicating unsaved changes.
static RULE_EDITOR_DIALOG_BASE * GetDialog(wxWindow *aWindow)
Static method to retrieve the rule editor dialog instance associated with a given window.
A wrapper for reporting to a wxString object.
Definition reporter.h:242
#define _(s)
@ NONE
Definition eda_fill.h:42
@ RPT_SEVERITY_ERROR
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
IbisParser parser & reporter