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 <class_board.h>
29 #include <board_design_settings.h>
30 #include <project.h>
31 #include <tool/tool_manager.h>
32 #include <panel_setup_rules.h>
33 #include <wx_html_report_box.h>
34 #include <html_messagebox.h>
35 #include <scintilla_tricks.h>
36 #include <drc/drc_rule_parser.h>
37 #include <tools/drc_tool.h>
38 
40  PANEL_SETUP_RULES_BASE( aParent->GetTreebook() ),
41  m_Parent( aParent ),
42  m_frame( aFrame ),
43  m_scintillaTricks( nullptr ),
44  m_helpDialog( nullptr )
45 {
46  m_scintillaTricks = new SCINTILLA_TRICKS( m_textEditor, wxT( "()" ) );
47 
48  int size = wxNORMAL_FONT->GetPointSize();
49  wxFont fixedFont( size, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL );
50 
51  for( size_t i = 0; i < wxSTC_STYLE_MAX; ++i )
52  m_textEditor->StyleSetFont( i, fixedFont );
53 
54  m_netClassRegex.Compile( "NetClass\\s*[!=]=\\s*$", wxRE_ADVANCED );
55  m_netNameRegex.Compile( "NetName\\s*[!=]=\\s*$", wxRE_ADVANCED );
56 
57  m_compileButton->SetBitmap( KiBitmap( drc_xpm ) );
58 
59  m_textEditor->Bind( wxEVT_STC_CHARADDED, &PANEL_SETUP_RULES::onScintillaCharAdded, this );
60  m_textEditor->Bind( wxEVT_STC_AUTOCOMP_CHAR_DELETED, &PANEL_SETUP_RULES::onScintillaCharAdded, this );
61 }
62 
63 
65 {
66  delete m_scintillaTricks;
67 
68  if( m_helpDialog )
69  m_helpDialog->Destroy();
70 };
71 
72 
73 void PANEL_SETUP_RULES::onScintillaCharAdded( wxStyledTextEvent &aEvent )
74 {
76  m_textEditor->SearchAnchor();
77 
78  wxString rules = m_textEditor->GetText();
79  int currentPos = m_textEditor->GetCurrentPos();
80  int startPos = 0;
81 
82  for( int line = m_textEditor->LineFromPosition( currentPos ); line > 0; line-- )
83  {
84  int lineStart = m_textEditor->PositionFromLine( line );
85  wxString beginning = m_textEditor->GetTextRange( lineStart, lineStart + 10 );
86 
87  if( beginning.StartsWith( "(rule " ) )
88  {
89  startPos = lineStart;
90  break;
91  }
92  }
93 
94  enum
95  {
96  NONE,
97  STRING,
98  SEXPR_OPEN,
99  SEXPR_TOKEN,
100  STRUCT_REF
101  };
102 
103  std::stack<wxString> sexprs;
104  wxString partial;
105  wxString last;
106  int context = NONE;
107  int expr_context = NONE;
108 
109  for( int i = startPos; i < currentPos; ++i )
110  {
111  wxChar c = m_textEditor->GetCharAt( i );
112 
113  if( c == '\\' )
114  {
115  i++; // skip escaped char
116  }
117  else if( context == STRING )
118  {
119  if( c == '"' )
120  {
121  context = NONE;
122  }
123  else
124  {
125  if( expr_context == STRING )
126  {
127  if( c == '\'' )
128  expr_context = NONE;
129  else
130  partial += c;
131  }
132  else if( c == '\'' )
133  {
134  last = partial;
135  partial = wxEmptyString;
136  expr_context = STRING;
137  }
138  else if( c == '.' )
139  {
140  partial = wxEmptyString;
141  expr_context = STRUCT_REF;
142  }
143  else
144  {
145  partial += c;
146  }
147  }
148  }
149  else if( c == '"' )
150  {
151  last = partial;
152  partial = wxEmptyString;
153  context = STRING;
154  }
155  else if( c == '(' )
156  {
157  if( context == SEXPR_OPEN && !partial.IsEmpty() )
158  {
159  m_textEditor->AutoCompCancel();
160  sexprs.push( partial );
161  }
162 
163  partial = wxEmptyString;
164  context = SEXPR_OPEN;
165  }
166  else if( c == ')' )
167  {
168  if( !sexprs.empty() )
169  sexprs.pop();
170 
171  context = NONE;
172  }
173  else if( c == ' ' )
174  {
175  if( context == SEXPR_OPEN && !partial.IsEmpty() )
176  {
177  m_textEditor->AutoCompCancel();
178  sexprs.push( partial );
179 
180  if( sexprs.size() && ( sexprs.top() == "constraint"
181  || sexprs.top() == "disallow"
182  || sexprs.top() == "layer" ) )
183  {
184  partial = wxEmptyString;
185  context = SEXPR_TOKEN;
186  continue;
187  }
188  }
189 
190  context = NONE;
191  }
192  else
193  {
194  partial += c;
195  }
196  }
197 
198  wxString tokens;
199 
200  if( context == SEXPR_OPEN )
201  {
202  if( sexprs.empty() )
203  tokens = "rule version";
204  else if( sexprs.top() == "rule" )
205  tokens = "condition constraint layer";
206  else if( sexprs.top() == "constraint" )
207  tokens = "max min opt";
208  }
209  else if( context == SEXPR_TOKEN )
210  {
211  if( sexprs.empty() )
212  {
213  /* badly formed grammar */
214  }
215  else if( sexprs.top() == "constraint" )
216  {
217  tokens = "annulus_width clearance disallow hole track_width";
218  }
219  else if( sexprs.top() == "disallow"
220  || sexprs.top() == "buried_via"
221  || sexprs.top() == "graphic"
222  || sexprs.top() == "hole"
223  || sexprs.top() == "micro_via"
224  || sexprs.top() == "pad"
225  || sexprs.top() == "text"
226  || sexprs.top() == "track"
227  || sexprs.top() == "via"
228  || sexprs.top() == "zone" )
229  {
230  tokens = "buried_via graphic hole micro_via pad text track via zone";
231  }
232  else if( sexprs.top() == "layer" )
233  {
234  tokens = "inner outer \"x\"";
235  }
236  }
237  else if( context == STRING && !sexprs.empty() && sexprs.top() == "condition" )
238  {
239  if( expr_context == STRUCT_REF )
240  {
242  std::set<wxString> propNames;
243 
244  for( const PROPERTY_MANAGER::CLASS_INFO& cls : propMgr.GetAllClasses() )
245  {
246  const PROPERTY_LIST& props = propMgr.GetProperties( cls.type );
247 
248  for( PROPERTY_BASE* prop : props )
249  {
250  wxString ref( prop->Name() );
251  ref.Replace( " ", "_" );
252  propNames.insert( ref );
253  }
254  }
255 
256  for( const wxString& propName : propNames )
257  tokens += " " + propName;
258 
260 
261  for( const wxString& funcSig : functions.GetSignatures() )
262  tokens += " " + funcSig;
263  }
264  else if( expr_context == STRING )
265  {
266  if( m_netClassRegex.Matches( last ) )
267  {
268  BOARD* board = m_frame->GetBoard();
270 
271  for( const std::pair<const wxString, NETCLASSPTR>& entry : bds.GetNetClasses() )
272  tokens += " " + entry.first;
273  }
274  else if( m_netNameRegex.Matches( last ) )
275  {
276  BOARD* board = m_frame->GetBoard();
277 
278  for( const wxString& netnameCandidate : board->GetNetClassAssignmentCandidates() )
279  tokens += " " + netnameCandidate;
280  }
281  }
282  }
283 
284  if( !tokens.IsEmpty() )
285  m_scintillaTricks->DoAutocomplete( partial, wxSplit( tokens, ' ' ) );
286 }
287 
288 
289 void PANEL_SETUP_RULES::OnCompile( wxCommandEvent& event )
290 {
292 
293  try
294  {
295  std::vector<DRC_RULE*> dummyRules;
296 
297  DRC_RULES_PARSER parser( m_textEditor->GetText(), _( "DRC rules" ) );
298 
299  parser.Parse( dummyRules, m_errorsReport );
300  }
301  catch( PARSE_ERROR& pe )
302  {
303  m_Parent->SetError( pe.What(), this, m_textEditor, pe.lineNumber, pe.byteIndex );
304  }
305 
307 }
308 
309 
310 void PANEL_SETUP_RULES::OnErrorLinkClicked( wxHtmlLinkEvent& event )
311 {
312  wxString link = event.GetLinkInfo().GetHref();
313  wxArrayString parts;
314  long line = 0, offset = 0;
315 
316  wxStringSplit( link, parts, ':' );
317 
318  if( parts.size() > 1 )
319  {
320  parts[0].ToLong( &line );
321  parts[1].ToLong( &offset );
322  }
323 
324  int pos = m_textEditor->PositionFromLine( line - 1 ) + ( offset - 1 );
325 
326  m_textEditor->GotoPos( pos );
327 
328  m_textEditor->SetFocus();
329 }
330 
331 
333 {
334  wxString rulesFilepath = m_frame->Prj().AbsolutePath( "drc-rules" );
335  wxFileName rulesFile( rulesFilepath );
336 
337  if( rulesFile.FileExists() )
338  {
339  wxTextFile file( rulesFile.GetFullPath() );
340 
341  if( file.Open() )
342  {
343  for ( wxString str = file.GetFirstLine(); !file.Eof(); str = file.GetNextLine() )
344  {
346  m_textEditor->AddText( str << '\n' );
347  }
348  }
349  }
350 
351  m_originalText = m_textEditor->GetText();
352 
353  return true;
354 }
355 
356 
358 {
359  if( m_originalText == m_textEditor->GetText() )
360  return true;
361 
362  try
363  {
364  std::vector<DRC_RULE*> dummyRules;
365 
366  DRC_RULES_PARSER parser( m_textEditor->GetText(), _( "DRC rules" ) );
367 
368  parser.Parse( dummyRules, m_errorsReport );
369  }
370  catch( PARSE_ERROR& pe )
371  {
372  m_Parent->SetError( pe.What(), this, m_textEditor, pe.lineNumber, pe.byteIndex );
373  return false;
374  }
375 
376  wxString rulesFilepath = m_frame->Prj().AbsolutePath( "drc-rules" );
377 
378  try
379  {
380  if( m_textEditor->SaveFile( rulesFilepath ) )
381  {
382  m_frame->GetBoard()->GetDesignSettings().m_DRCEngine->InitEngine( rulesFilepath );
383  return true;
384  }
385  }
386  catch( PARSE_ERROR& pe )
387  {
388  // Don't lock them in to the Setup dialog if they have bad rules. They've already
389  // saved them so we can allow an exit.
390  return true;
391  }
392 
393  return false;
394 }
395 
396 
397 void PANEL_SETUP_RULES::OnSyntaxHelp( wxHyperlinkEvent& aEvent )
398 {
399  wxString msg =
400 #include "dialogs/panel_setup_rules_help_txt.h"
401  ;
402 
403 #ifdef __WXMAC__
404  msg.Replace( "Ctrl+", "Cmd+" );
405 #endif
406 
407  m_helpDialog = new HTML_MESSAGE_BOX( nullptr, _( "Syntax Help" ) );
408  m_helpDialog->SetDialogSizeInDU( 320, 320 );
409 
410  m_helpDialog->AddHTML_Text( "<pre>" + EscapedHTML( msg ) + "</pre>" );
412 }
void wxStringSplit(const wxString &aText, wxArrayString &aStrings, wxChar aSplitter)
Split aString to a string list separated at aSplitter.
Definition: common.cpp:343
const PROPERTY_LIST & GetProperties(TYPE_ID aType) const
Returns all properties for a specific type.
static PROPERTY_MANAGER & Instance()
Definition: property_mgr.h:61
const wxArrayString GetSignatures() const
bool TransferDataFromWindow() override
void OnCompile(wxCommandEvent &event) override
wxString EscapedHTML(const wxString &aString)
Return a new wxString escaped for embedding in HTML.
Definition: string.cpp:341
SCINTILLA_TRICKS is used to add cut/copy/paste, autocomplete and brace highlighting to a wxStyleTextC...
bool TransferDataToWindow() override
void DoAutocomplete(const wxString &aPartial, const wxArrayString &aTokens)
wxBitmapButton * m_compileButton
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Function GetDesignSettings.
Definition: class_board.h:537
HTML_MESSAGE_BOX * m_helpDialog
const BITMAP_OPAQUE drc_xpm[1]
Definition: drc.cpp:90
void SetError(const wxString &aMessage, const wxString &aPageName, int aCtrlId, int aRow=-1, int aCol=-1)
SCINTILLA_TRICKS * m_scintillaTricks
PCB_EDIT_FRAME * m_frame
VTBL_ENTRY const wxString AbsolutePath(const wxString &aFileName) const
Function AbsolutePath fixes up aFileName if it is relative to the project's directory to be an absolu...
Definition: project.cpp:272
wxBitmap KiBitmap(BITMAP_DEF aBitmap)
Construct a wxBitmap from a memory record, held in a BITMAP_DEF.
Definition: bitmap.cpp:80
Class PANEL_SETUP_RULES_BASE.
WX_HTML_REPORT_BOX * m_errorsReport
virtual const wxString What() const
A composite of Problem() and Where()
Definition: exceptions.cpp:29
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
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
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:40
BOARD holds information pertinent to a Pcbnew printed circuit board.
Definition: class_board.h:178
#define _(s)
Definition: 3d_actions.cpp:33
void AddHTML_Text(const wxString &message)
Add HTML text (without any change) to message list.
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:58
BOARD * GetBoard() const
std::vector< wxString > GetNetClassAssignmentCandidates()
Function GetNetClassAssignmentCandidates Returns a list of name candidates for netclass assignment.
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