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