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