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