KiCad PCB EDA Suite
panel_setup_rules.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) 2020-2021 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 
24 #include <bitmaps.h>
25 #include <confirm.h>
26 #include <widgets/paged_dialog.h>
27 #include <pcb_edit_frame.h>
28 #include <pcb_expr_evaluator.h>
29 #include <board.h>
30 #include <board_design_settings.h>
31 #include <project.h>
32 #include <kicad_string.h>
33 #include <tool/tool_manager.h>
34 #include <panel_setup_rules.h>
35 #include <wx_html_report_box.h>
36 #include <wx/treebook.h>
38 #include <scintilla_tricks.h>
39 #include <drc/drc_rule_parser.h>
40 #include <tools/drc_tool.h>
41 #include <dialog_helpers.h>
42 
44  PANEL_SETUP_RULES_BASE( aParent->GetTreebook() ),
45  m_Parent( aParent ),
46  m_frame( aFrame ),
47  m_scintillaTricks( nullptr ),
48  m_helpWindow( nullptr )
49 {
50  m_scintillaTricks = new SCINTILLA_TRICKS( m_textEditor, wxT( "()" ) );
51 
52  int size = wxNORMAL_FONT->GetPointSize();
53  wxFont fixedFont( size, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL );
54 
55  for( size_t i = 0; i < wxSTC_STYLE_MAX; ++i )
56  m_textEditor->StyleSetFont( i, fixedFont );
57 
58  m_netClassRegex.Compile( "NetClass\\s*[!=]=\\s*$", wxRE_ADVANCED );
59  m_netNameRegex.Compile( "NetName\\s*[!=]=\\s*$", wxRE_ADVANCED );
60 
61  m_compileButton->SetBitmap( KiBitmap( BITMAPS::drc ) );
62 
63  m_textEditor->Bind( wxEVT_STC_CHARADDED, &PANEL_SETUP_RULES::onScintillaCharAdded, this );
64  m_textEditor->Bind( wxEVT_STC_AUTOCOMP_CHAR_DELETED, &PANEL_SETUP_RULES::onScintillaCharAdded, this );
65  m_textEditor->Bind( wxEVT_CHAR_HOOK, &PANEL_SETUP_RULES::onCharHook, this );
66 }
67 
68 
70 {
71  delete m_scintillaTricks;
72 
73  if( m_helpWindow )
74  m_helpWindow->Destroy();
75 };
76 
77 
78 void PANEL_SETUP_RULES::onCharHook( wxKeyEvent& aEvent )
79 {
80  if( aEvent.GetKeyCode() == WXK_ESCAPE && !m_textEditor->AutoCompActive() )
81  {
82  if( m_originalText != m_textEditor->GetText() )
83  {
84  if( !IsOK( this, _( "Cancel Changes?" ) ) )
85  return;
86  }
87  }
88 
89  aEvent.Skip();
90 }
91 
92 
93 void PANEL_SETUP_RULES::onScintillaCharAdded( wxStyledTextEvent &aEvent )
94 {
96  m_textEditor->SearchAnchor();
97 
98  wxString rules = m_textEditor->GetText();
99  int currentPos = m_textEditor->GetCurrentPos();
100  int startPos = 0;
101 
102  for( int line = m_textEditor->LineFromPosition( currentPos ); line > 0; line-- )
103  {
104  int lineStart = m_textEditor->PositionFromLine( line );
105  wxString beginning = m_textEditor->GetTextRange( lineStart, lineStart + 10 );
106 
107  if( beginning.StartsWith( "(rule " ) )
108  {
109  startPos = lineStart;
110  break;
111  }
112  }
113 
114  enum
115  {
116  NONE,
117  STRING,
118  SEXPR_OPEN,
119  SEXPR_TOKEN,
120  STRUCT_REF
121  };
122 
123  std::stack<wxString> sexprs;
124  wxString partial;
125  wxString last;
126  int context = NONE;
127  int expr_context = NONE;
128 
129  for( int i = startPos; i < currentPos; ++i )
130  {
131  wxChar c = m_textEditor->GetCharAt( i );
132 
133  if( c == '\\' )
134  {
135  i++; // skip escaped char
136  }
137  else if( context == STRING )
138  {
139  if( c == '"' )
140  {
141  context = NONE;
142  }
143  else
144  {
145  if( expr_context == STRING )
146  {
147  if( c == '\'' )
148  expr_context = NONE;
149  else
150  partial += c;
151  }
152  else if( c == '\'' )
153  {
154  last = partial;
155  partial = wxEmptyString;
156  expr_context = STRING;
157  }
158  else if( c == '.' )
159  {
160  partial = wxEmptyString;
161  expr_context = STRUCT_REF;
162  }
163  else
164  {
165  partial += c;
166  }
167  }
168  }
169  else if( c == '"' )
170  {
171  last = partial;
172  partial = wxEmptyString;
173  context = STRING;
174  }
175  else if( c == '(' )
176  {
177  if( context == SEXPR_OPEN && !partial.IsEmpty() )
178  {
179  m_textEditor->AutoCompCancel();
180  sexprs.push( partial );
181  }
182 
183  partial = wxEmptyString;
184  context = SEXPR_OPEN;
185  }
186  else if( c == ')' )
187  {
188  if( !sexprs.empty() )
189  sexprs.pop();
190 
191  context = NONE;
192  }
193  else if( c == ' ' )
194  {
195  if( context == SEXPR_OPEN && !partial.IsEmpty() )
196  {
197  m_textEditor->AutoCompCancel();
198  sexprs.push( partial );
199 
200  if( sexprs.size() && ( sexprs.top() == "constraint"
201  || sexprs.top() == "disallow"
202  || sexprs.top() == "layer" ) )
203  {
204  partial = wxEmptyString;
205  context = SEXPR_TOKEN;
206  continue;
207  }
208  }
209 
210  context = NONE;
211  }
212  else
213  {
214  partial += c;
215  }
216  }
217 
218  wxString tokens;
219 
220  if( context == SEXPR_OPEN )
221  {
222  if( sexprs.empty() )
223  {
224  tokens = "rule "
225  "version";
226  }
227  else if( sexprs.top() == "rule" )
228  {
229  tokens = "condition "
230  "constraint "
231  "layer";
232  }
233  else if( sexprs.top() == "constraint" )
234  {
235  tokens = "max "
236  "min "
237  "opt";
238  }
239  }
240  else if( context == SEXPR_TOKEN )
241  {
242  if( sexprs.empty() )
243  {
244  /* badly formed grammar */
245  }
246  else if( sexprs.top() == "constraint" )
247  {
248  tokens = "annulus_width "
249  "clearance "
250  "courtyard_clearance "
251  "diff_pair_gap "
252  "diff_pair_uncoupled "
253  "disallow "
254  "edge_clearance "
255  "length "
256  "hole "
257  "hole_clearance "
258  "hole_to_hole "
259  "silk_clearance "
260  "skew "
261  "track_width "
262  "via_count ";
263  }
264  else if( sexprs.top() == "disallow"
265  || sexprs.top() == "buried_via"
266  || sexprs.top() == "graphic"
267  || sexprs.top() == "hole"
268  || sexprs.top() == "micro_via"
269  || sexprs.top() == "pad"
270  || sexprs.top() == "text"
271  || sexprs.top() == "track"
272  || sexprs.top() == "via"
273  || sexprs.top() == "zone" )
274  {
275  tokens = "buried_via "
276  "graphic "
277  "hole "
278  "micro_via "
279  "pad "
280  "text "
281  "track "
282  "via "
283  "zone";
284  }
285  else if( sexprs.top() == "layer" )
286  {
287  tokens = "inner "
288  "outer "
289  "\"x\"";
290  }
291  }
292  else if( context == STRING && !sexprs.empty() && sexprs.top() == "condition" )
293  {
294  if( expr_context == STRUCT_REF )
295  {
297  std::set<wxString> propNames;
298 
299  for( const PROPERTY_MANAGER::CLASS_INFO& cls : propMgr.GetAllClasses() )
300  {
301  const PROPERTY_LIST& props = propMgr.GetProperties( cls.type );
302 
303  for( PROPERTY_BASE* prop : props )
304  {
305  wxString ref( prop->Name() );
306  ref.Replace( " ", "_" );
307  propNames.insert( ref );
308  }
309  }
310 
311  for( const wxString& propName : propNames )
312  tokens += " " + propName;
313 
315 
316  for( const wxString& funcSig : functions.GetSignatures() )
317  tokens += " " + funcSig;
318  }
319  else if( expr_context == STRING )
320  {
321  if( m_netClassRegex.Matches( last ) )
322  {
323  BOARD* board = m_frame->GetBoard();
325 
326  for( const std::pair<const wxString, NETCLASSPTR>& entry : bds.GetNetClasses() )
327  tokens += " " + entry.first;
328  }
329  else if( m_netNameRegex.Matches( last ) )
330  {
331  BOARD* board = m_frame->GetBoard();
332 
333  for( const wxString& netnameCandidate : board->GetNetClassAssignmentCandidates() )
334  tokens += " " + netnameCandidate;
335  }
336  }
337  }
338 
339  if( !tokens.IsEmpty() )
340  m_scintillaTricks-> DoAutocomplete( partial, wxSplit( tokens, ' ' ) );
341 }
342 
343 
344 void PANEL_SETUP_RULES::OnCompile( wxCommandEvent& event )
345 {
347 
348  try
349  {
350  std::vector<DRC_RULE*> dummyRules;
351 
352  DRC_RULES_PARSER parser( m_textEditor->GetText(), _( "DRC rules" ) );
353 
354  parser.Parse( dummyRules, m_errorsReport );
355  }
356  catch( PARSE_ERROR& pe )
357  {
358  wxString msg = wxString::Format( "%s <a href='%d:%d'>%s</a>%s",
359  _( "ERROR:" ),
360  pe.lineNumber,
361  pe.byteIndex,
362  pe.ParseProblem(),
363  wxEmptyString );
364 
366  }
367 
369 }
370 
371 
372 void PANEL_SETUP_RULES::OnErrorLinkClicked( wxHtmlLinkEvent& event )
373 {
374  wxString link = event.GetLinkInfo().GetHref();
375  wxArrayString parts;
376  long line = 0, offset = 0;
377 
378  wxStringSplit( link, parts, ':' );
379 
380  if( parts.size() > 1 )
381  {
382  parts[0].ToLong( &line );
383  parts[1].ToLong( &offset );
384  }
385 
386  int pos = m_textEditor->PositionFromLine( line - 1 ) + ( offset - 1 );
387 
388  m_textEditor->GotoPos( pos );
389 
390  m_textEditor->SetFocus();
391 }
392 
393 
395 {
396  wxFileName rulesFile( m_frame->GetDesignRulesPath() );
397 
398  if( rulesFile.FileExists() )
399  {
400  wxTextFile file( rulesFile.GetFullPath() );
401 
402  if( file.Open() )
403  {
404  for ( wxString str = file.GetFirstLine(); !file.Eof(); str = file.GetNextLine() )
405  {
407  m_textEditor->AddText( str << '\n' );
408  }
409 
410  wxCommandEvent dummy;
411  OnCompile( dummy );
412  }
413  }
414 
415  m_originalText = m_textEditor->GetText();
416 
417  if( m_frame->Prj().IsNullProject() )
418  {
419  m_textEditor->ClearAll();
420  m_textEditor->AddText( _( "Design rules cannot be added without a project" ) );
421  m_textEditor->Disable();
422  }
423 
424  return true;
425 }
426 
427 
429 {
430  if( m_originalText == m_textEditor->GetText() )
431  return true;
432 
433  if( m_frame->Prj().IsNullProject() )
434  return true;
435 
436  wxString rulesFilepath = m_frame->GetDesignRulesPath();
437 
438  try
439  {
440  if( m_textEditor->SaveFile( rulesFilepath ) )
441  {
442  m_frame->GetBoard()->GetDesignSettings().m_DRCEngine->InitEngine( rulesFilepath );
443  return true;
444  }
445  }
446  catch( PARSE_ERROR& )
447  {
448  // Don't lock them in to the Setup dialog if they have bad rules. They've already
449  // saved them so we can allow an exit.
450  return true;
451  }
452 
453  return false;
454 }
455 
456 
457 void PANEL_SETUP_RULES::OnSyntaxHelp( wxHyperlinkEvent& aEvent )
458 {
459  if( m_helpWindow )
460  {
462  return;
463  }
464 
465  wxString msg =
466 #include "dialogs/panel_setup_rules_help_md.h"
467  ;
468 
469 #ifdef __WXMAC__
470  msg.Replace( "Ctrl+", "Cmd+" );
471 #endif
472 
473  m_helpWindow = new HTML_MESSAGE_BOX( nullptr, _( "Syntax Help" ) );
474  m_helpWindow->SetDialogSizeInDU( 320, 320 );
475 
476  wxString html_txt;
477  ConvertMarkdown2Html( wxGetTranslation( msg ), html_txt );
478  m_helpWindow->m_htmlWindow->AppendToPage( html_txt );
479 
481 }
const PROPERTY_LIST & GetProperties(TYPE_ID aType) const
Return all properties for a specific type.
static PROPERTY_MANAGER & Instance()
Definition: property_mgr.h:65
const wxArrayString GetSignatures() const
bool TransferDataFromWindow() override
wxString GetDesignRulesPath()
Return the absolute path to the design rules file for the currently-loaded board.
This file is part of the common library.
void Clear()
Delete the stored messages.
void ConvertMarkdown2Html(const wxString &aMarkdownInput, wxString &aHtmlOutput)
void OnCompile(wxCommandEvent &event) override
Add cut/copy/paste, autocomplete and brace highlighting to a wxStyleTextCtrl instance.
REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED) override
Report a string with a given severity.
bool TransferDataToWindow() override
void Flush()
Build the HTML messages page.
wxBitmapButton * m_compileButton
SCINTILLA_TRICKS * m_scintillaTricks
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:588
PCB_EDIT_FRAME * m_frame
const wxString ParseProblem()
Definition: ki_exception.h:150
static LIB_SYMBOL * dummy()
Used to draw a dummy shape when a LIB_SYMBOL is not found in library.
Definition: sch_symbol.cpp:70
Class PANEL_SETUP_RULES_BASE.
WX_HTML_REPORT_BOX * m_errorsReport
PAGED_DIALOG * m_Parent
int lineNumber
at which line number, 1 based index.
Definition: ki_exception.h:120
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
void Parse(std::vector< DRC_RULE * > &aRules, REPORTER *aReporter)
~PANEL_SETUP_RULES() override
Helper dialog and control classes.
std::vector< wxString > GetNetClassAssignmentCandidates() const
Return a list of name candidates for netclass assignment.
Definition: board.cpp:1467
#define _(s)
PANEL_SETUP_RULES(PAGED_DIALOG *aParent, PCB_EDIT_FRAME *aFrame)
NETCLASSES & GetNetClasses() const
void onCharHook(wxKeyEvent &aEvent)
void onScintillaCharAdded(wxStyledTextEvent &aEvent)
HTML_MESSAGE_BOX.
wxBitmap KiBitmap(BITMAPS aBitmap, int aHeightTag)
Construct a wxBitmap from an image identifier Returns the image from the active theme if the image ha...
Definition: bitmap.cpp:105
void OnSyntaxHelp(wxHyperlinkEvent &aEvent) override
virtual bool IsNullProject() const
Check if this project is a null project (i.e.
Definition: project.cpp:135
void wxStringSplit(const wxString &aText, wxArrayString &aStrings, wxChar aSplitter)
Split aString to a string list separated at aSplitter.
Definition: string.cpp:895
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
void ShowModeless()
Show a modeless version of the dialog (without an OK button).
void SetDialogSizeInDU(int aWidth, int aHeight)
set the dialog size, using a "logical" value.
A filename or source description, a problem input line, a line number, a byte offset,...
Definition: ki_exception.h:118
std::vector< PROPERTY_BASE * > PROPERTY_LIST
Definition: property_mgr.h:44
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:190
HTML_MESSAGE_BOX * m_helpWindow
The main frame for Pcbnew.
wxStyledTextCtrl * m_textEditor
int byteIndex
at which byte offset within the line, 1 based index
Definition: ki_exception.h:121
CLASSES_INFO GetAllClasses()
Provide class metadata.Helper macro to map type hashes to names.
Definition: property_mgr.h:62
BOARD * GetBoard() const
bool IsOK(wxWindow *aParent, const wxString &aMessage)
Display a yes/no dialog with aMessage and returns the user response.
Definition: confirm.cpp:296
std::shared_ptr< DRC_ENGINE > m_DRCEngine
bool ConvertSmartQuotesAndDashes(wxString *aString)
Convert curly quotes and em/en dashes to straight quotes and dashes.
Definition: string.cpp:114
Container for design settings for a BOARD object.
void SetModified()
Definition: paged_dialog.h:41
static PCB_EXPR_BUILTIN_FUNCTIONS & Instance()
void OnErrorLinkClicked(wxHtmlLinkEvent &event) override