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