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