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