KiCad PCB EDA Suite
Loading...
Searching...
No Matches
scintilla_tricks.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-2023 KiCad Developers, see change_log.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
25#include <string_utils.h>
26#include <scintilla_tricks.h>
27#include <widgets/wx_grid.h>
28#include <wx/stc/stc.h>
29#include <gal/color4d.h>
30#include <dialog_shim.h>
31#include <wx/clipbrd.h>
32#include <wx/log.h>
33#include <wx/settings.h>
34#include <confirm.h>
35
36SCINTILLA_TRICKS::SCINTILLA_TRICKS( wxStyledTextCtrl* aScintilla, const wxString& aBraces,
37 bool aSingleLine,
38 std::function<void( wxKeyEvent& )> onAcceptFn,
39 std::function<void( wxStyledTextEvent& )> onCharAddedFn ) :
40 m_te( aScintilla ),
41 m_braces( aBraces ),
42 m_lastCaretPos( -1 ),
43 m_lastSelStart( -1 ),
44 m_lastSelEnd( -1 ),
45 m_suppressAutocomplete( false ),
46 m_singleLine( aSingleLine ),
47 m_onAcceptFn( std::move( onAcceptFn ) ),
48 m_onCharAddedFn( std::move( onCharAddedFn ) )
49{
50 // Always use LF as eol char, regardless the platform
51 m_te->SetEOLMode( wxSTC_EOL_LF );
52
53 // A hack which causes Scintilla to auto-size the text editor canvas
54 // See: https://github.com/jacobslusser/ScintillaNET/issues/216
55 m_te->SetScrollWidth( 1 );
56 m_te->SetScrollWidthTracking( true );
57
58 if( m_singleLine )
59 {
60 m_te->SetUseVerticalScrollBar( false );
61 m_te->SetUseHorizontalScrollBar( false );
62 }
63
65
66 // Set up autocomplete
67 m_te->AutoCompSetIgnoreCase( true );
68 m_te->AutoCompSetMaxHeight( 20 );
69
70 if( aBraces.Length() >= 2 )
71 m_te->AutoCompSetFillUps( m_braces[1] );
72
73 // Hook up events
74 m_te->Bind( wxEVT_STC_UPDATEUI, &SCINTILLA_TRICKS::onScintillaUpdateUI, this );
75 m_te->Bind( wxEVT_STC_MODIFIED, &SCINTILLA_TRICKS::onModified, this );
76
77 // Handle autocomplete
78 m_te->Bind( wxEVT_STC_CHARADDED, &SCINTILLA_TRICKS::onChar, this );
79 m_te->Bind( wxEVT_STC_AUTOCOMP_CHAR_DELETED, &SCINTILLA_TRICKS::onChar, this );
80
81 // Dispatch command-keys in Scintilla control.
82 m_te->Bind( wxEVT_CHAR_HOOK, &SCINTILLA_TRICKS::onCharHook, this );
83
84 m_te->Bind( wxEVT_SYS_COLOUR_CHANGED,
85 wxSysColourChangedEventHandler( SCINTILLA_TRICKS::onThemeChanged ), this );
86}
87
88
89void SCINTILLA_TRICKS::onThemeChanged( wxSysColourChangedEvent &aEvent )
90{
92
93 aEvent.Skip();
94}
95
96
98{
99 wxTextCtrl dummy( m_te->GetParent(), wxID_ANY );
100 KIGFX::COLOR4D foreground = dummy.GetForegroundColour();
101 KIGFX::COLOR4D background = dummy.GetBackgroundColour();
102 KIGFX::COLOR4D highlight = wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHT );
103 KIGFX::COLOR4D highlightText = wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHTTEXT );
104
105 m_te->StyleSetForeground( wxSTC_STYLE_DEFAULT, foreground.ToColour() );
106 m_te->StyleSetBackground( wxSTC_STYLE_DEFAULT, background.ToColour() );
107 m_te->StyleClearAll();
108
109 // Scintilla doesn't handle alpha channel, which at least OSX uses in some highlight colours,
110 // such as "graphite".
111 highlight = highlight.Mix( background, highlight.a ).WithAlpha( 1.0 );
112 highlightText = highlightText.Mix( background, highlightText.a ).WithAlpha( 1.0 );
113
114 m_te->SetSelForeground( true, highlightText.ToColour() );
115 m_te->SetSelBackground( true, highlight.ToColour() );
116 m_te->SetCaretForeground( foreground.ToColour() );
117
118 if( !m_singleLine )
119 {
120 // Set a monospace font with a tab width of 4. This is the closest we can get to having
121 // Scintilla mimic the stroke font's tab positioning.
122 wxFont fixedFont = KIUI::GetMonospacedUIFont();
123
124 for( size_t i = 0; i < wxSTC_STYLE_MAX; ++i )
125 m_te->StyleSetFont( i, fixedFont );
126
127 m_te->SetTabWidth( 4 );
128 }
129
130 // Set up the brace highlighting. Scintilla doesn't handle alpha, so we construct our own
131 // 20% wash by blending with the background.
132 KIGFX::COLOR4D braceText = foreground;
133 KIGFX::COLOR4D braceHighlight = braceText.Mix( background, 0.2 );
134
135 m_te->StyleSetForeground( wxSTC_STYLE_BRACELIGHT, highlightText.ToColour() );
136 m_te->StyleSetBackground( wxSTC_STYLE_BRACELIGHT, braceHighlight.ToColour() );
137 m_te->StyleSetForeground( wxSTC_STYLE_BRACEBAD, *wxRED );
138}
139
140
141bool isCtrlSlash( wxKeyEvent& aEvent )
142{
143 if( !aEvent.ControlDown() || aEvent.MetaDown() )
144 return false;
145
146 if( aEvent.GetUnicodeKey() == '/' )
147 return true;
148
149 // OK, now the wxWidgets hacks start.
150 // (We should abandon these if https://trac.wxwidgets.org/ticket/18911 gets resolved.)
151
152 // Many Latin America and European keyboars have have the / over the 7. We know that
153 // wxWidgets messes this up and returns Shift+7 through GetUnicodeKey(). However, other
154 // keyboards (such as France and Belgium) have 7 in the shifted position, so a Shift+7
155 // *could* be legitimate.
156
157 // However, we *are* checking Ctrl, so to assume any Shift+7 is a Ctrl-/ really only
158 // disallows Ctrl+Shift+7 from doing something else, which is probably OK. (This routine
159 // is only used in the Scintilla editor, not in the rest of Kicad.)
160
161 // The other main shifted loation of / is over : (France and Belgium), so we'll sacrifice
162 // Ctrl+Shift+: too.
163
164 if( aEvent.ShiftDown() && ( aEvent.GetUnicodeKey() == '7' || aEvent.GetUnicodeKey() == ':' ) )
165 return true;
166
167 // A few keyboards have / in an Alt position. Since we're expressly not checking Alt for
168 // up or down, those should work. However, if they don't, there's room below for yet
169 // another hack....
170
171 return false;
172}
173
174
175void SCINTILLA_TRICKS::onChar( wxStyledTextEvent& aEvent )
176{
177 m_onCharAddedFn( aEvent );
178}
179
180
181void SCINTILLA_TRICKS::onModified( wxStyledTextEvent& aEvent )
182{
183 if( m_singleLine )
184 {
185 wxString curr_text = m_te->GetText();
186
187 if( curr_text.Contains( wxS( "\n" ) ) || curr_text.Contains( wxS( "\r" ) ) )
188 {
189 // Scintilla won't allow us to call SetText() from within this event processor,
190 // so we have to delay the processing.
191 CallAfter( [this]()
192 {
193 wxString text = m_te->GetText();
194 int currpos = m_te->GetCurrentPos();
195
196 text.Replace( wxS( "\n" ), wxS( "" ) );
197 text.Replace( wxS( "\r" ), wxS( "" ) );
198 m_te->SetText( text );
199 m_te->GotoPos( currpos-1 );
200 } );
201 }
202 }
203}
204
205
206void SCINTILLA_TRICKS::onCharHook( wxKeyEvent& aEvent )
207{
208 wxString c = aEvent.GetUnicodeKey();
209
210 if( m_te->AutoCompActive() )
211 {
212 if( aEvent.GetKeyCode() == WXK_ESCAPE )
213 {
214 m_te->AutoCompCancel();
215 m_suppressAutocomplete = true; // Don't run autocomplete again on the next char...
216 }
217 else if( aEvent.GetKeyCode() == WXK_RETURN || aEvent.GetKeyCode() == WXK_NUMPAD_ENTER )
218 {
219 m_te->AutoCompComplete();
220 }
221 else
222 {
223 aEvent.Skip();
224 }
225
226 return;
227 }
228
229#ifdef __WXMAC__
230 if( aEvent.GetModifiers() == wxMOD_RAW_CONTROL && aEvent.GetKeyCode() == WXK_SPACE )
231#else
232 if( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKeyCode() == WXK_SPACE )
233#endif
234 {
236
237 wxStyledTextEvent event;
238 event.SetKey( ' ' );
239 event.SetModifiers( wxMOD_CONTROL );
240 m_onCharAddedFn( event );
241
242 return;
243 }
244
245 if( !isalpha( aEvent.GetKeyCode() ) )
247
248 if( ( aEvent.GetKeyCode() == WXK_RETURN || aEvent.GetKeyCode() == WXK_NUMPAD_ENTER )
249 && ( m_singleLine || aEvent.ShiftDown() ) )
250 {
251 m_onAcceptFn( aEvent );
252 }
253 else if( ConvertSmartQuotesAndDashes( &c ) )
254 {
255 m_te->AddText( c );
256 }
257 else if( aEvent.GetKeyCode() == WXK_TAB )
258 {
259 wxWindow* ancestor = m_te->GetParent();
260
261 while( ancestor && !dynamic_cast<WX_GRID*>( ancestor ) )
262 ancestor = ancestor->GetParent();
263
264 if( aEvent.ControlDown() )
265 {
266 int flags = 0;
267
268 if( !aEvent.ShiftDown() )
269 flags |= wxNavigationKeyEvent::IsForward;
270
271 wxWindow* parent = m_te->GetParent();
272
273 while( parent && dynamic_cast<DIALOG_SHIM*>( parent ) == nullptr )
274 parent = parent->GetParent();
275
276 if( parent )
277 parent->NavigateIn( flags );
278 }
279 else if( dynamic_cast<WX_GRID*>( ancestor ) )
280 {
281 WX_GRID* grid = static_cast<WX_GRID*>( ancestor );
282 int row = grid->GetGridCursorRow();
283 int col = grid->GetGridCursorCol();
284
285 if( aEvent.ShiftDown() )
286 {
287 if( col > 0 )
288 {
289 col--;
290 }
291 else if( row > 0 )
292 {
293 col = (int) grid->GetNumberCols() - 1;
294
295 if( row > 0 )
296 row--;
297 else
298 row = (int) grid->GetNumberRows() - 1;
299 }
300 }
301 else
302 {
303 if( col < (int) grid->GetNumberCols() - 1 )
304 {
305 col++;
306 }
307 else if( row < grid->GetNumberRows() - 1 )
308 {
309 col = 0;
310
311 if( row < grid->GetNumberRows() - 1 )
312 row++;
313 else
314 row = 0;
315 }
316 }
317
318 grid->SetGridCursor( row, col );
319 }
320 else
321 {
322 m_te->Tab();
323 }
324 }
325 else if( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKeyCode() == 'Z' )
326 {
327 m_te->Undo();
328 }
329 else if( ( aEvent.GetModifiers() == wxMOD_SHIFT+wxMOD_CONTROL && aEvent.GetKeyCode() == 'Z' )
330 || ( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKeyCode() == 'Y' ) )
331 {
332 m_te->Redo();
333 }
334 else if( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKeyCode() == 'A' )
335 {
336 m_te->SelectAll();
337 }
338 else if( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKeyCode() == 'X' )
339 {
340 m_te->Cut();
341
342 if( wxTheClipboard->Open() )
343 {
344 wxTheClipboard->Flush(); // Allow data to be available after closing KiCad
345 wxTheClipboard->Close();
346 }
347 }
348 else if( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKeyCode() == 'C' )
349 {
350 m_te->Copy();
351
352 if( wxTheClipboard->Open() )
353 {
354 wxTheClipboard->Flush(); // Allow data to be available after closing KiCad
355 wxTheClipboard->Close();
356 }
357 }
358 else if( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKeyCode() == 'V' )
359 {
360 if( m_te->GetSelectionEnd() > m_te->GetSelectionStart() )
361 m_te->DeleteBack();
362
363 wxLogNull doNotLog; // disable logging of failed clipboard actions
364
365 if( wxTheClipboard->Open() )
366 {
367 if( wxTheClipboard->IsSupported( wxDF_TEXT ) ||
368 wxTheClipboard->IsSupported( wxDF_UNICODETEXT ) )
369 {
370 wxTextDataObject data;
371 wxString str;
372
373 wxTheClipboard->GetData( data );
374 str = data.GetText();
375
377
378 if( m_singleLine )
379 {
380 str.Replace( wxS( "\n" ), wxEmptyString );
381 str.Replace( wxS( "\r" ), wxEmptyString );
382 }
383
384 m_te->BeginUndoAction();
385 m_te->AddText( str );
386 m_te->EndUndoAction();
387 }
388
389 wxTheClipboard->Close();
390 }
391 }
392 else if( aEvent.GetKeyCode() == WXK_BACK )
393 {
394 if( aEvent.GetModifiers() == wxMOD_CONTROL )
395#ifdef __WXMAC__
396 m_te->HomeExtend();
397 else if( aEvent.GetModifiers() == wxMOD_ALT )
398#endif
399 m_te->WordLeftExtend();
400
401 m_te->DeleteBack();
402 }
403 else if( aEvent.GetKeyCode() == WXK_DELETE )
404 {
405 if( m_te->GetSelectionEnd() == m_te->GetSelectionStart() )
406 m_te->CharRightExtend();
407
408 if( m_te->GetSelectionEnd() > m_te->GetSelectionStart() )
409 m_te->DeleteBack();
410 }
411 else if( isCtrlSlash( aEvent ) )
412 {
413 int startLine = m_te->LineFromPosition( m_te->GetSelectionStart() );
414 int endLine = m_te->LineFromPosition( m_te->GetSelectionEnd() );
415 bool comment = firstNonWhitespace( startLine ) != '#';
416 int whitespaceCount;
417
418 m_te->BeginUndoAction();
419
420 for( int ii = startLine; ii <= endLine; ++ii )
421 {
422 if( comment )
423 m_te->InsertText( m_te->PositionFromLine( ii ), wxT( "#" ) );
424 else if( firstNonWhitespace( ii, &whitespaceCount ) == '#' )
425 m_te->DeleteRange( m_te->PositionFromLine( ii ) + whitespaceCount, 1 );
426 }
427
428 m_te->SetSelection( m_te->PositionFromLine( startLine ),
429 m_te->PositionFromLine( endLine ) + m_te->GetLineLength( endLine ) );
430
431 m_te->EndUndoAction();
432 }
433#ifdef __WXMAC__
434 else if( aEvent.GetModifiers() == wxMOD_RAW_CONTROL && aEvent.GetKeyCode() == 'A' )
435 {
436 m_te->HomeWrap();
437 }
438 else if( aEvent.GetModifiers() == wxMOD_RAW_CONTROL && aEvent.GetKeyCode() == 'E' )
439 {
440 m_te->LineEndWrap();
441 }
442 else if( ( aEvent.GetModifiers() & wxMOD_RAW_CONTROL ) && aEvent.GetKeyCode() == 'B' )
443 {
444 if( aEvent.GetModifiers() & wxMOD_ALT )
445 m_te->WordLeft();
446 else
447 m_te->CharLeft();
448 }
449 else if( ( aEvent.GetModifiers() & wxMOD_RAW_CONTROL ) && aEvent.GetKeyCode() == 'F' )
450 {
451 if( aEvent.GetModifiers() & wxMOD_ALT )
452 m_te->WordRight();
453 else
454 m_te->CharRight();
455 }
456 else if( aEvent.GetModifiers() == wxMOD_RAW_CONTROL && aEvent.GetKeyCode() == 'D' )
457 {
458 if( m_te->GetSelectionEnd() == m_te->GetSelectionStart() )
459 m_te->CharRightExtend();
460
461 if( m_te->GetSelectionEnd() > m_te->GetSelectionStart() )
462 m_te->DeleteBack();
463 }
464#endif
465 else if( aEvent.GetKeyCode() == WXK_SPECIAL20 )
466 {
467 // Proxy for a wxSysColourChangedEvent
468 setupStyles();
469 }
470 else
471 {
472 aEvent.Skip();
473 }
474}
475
476
477int SCINTILLA_TRICKS::firstNonWhitespace( int aLine, int* aWhitespaceCharCount )
478{
479 int lineStart = m_te->PositionFromLine( aLine );
480
481 if( aWhitespaceCharCount )
482 *aWhitespaceCharCount = 0;
483
484 for( int ii = 0; ii < m_te->GetLineLength( aLine ); ++ii )
485 {
486 int c = m_te->GetCharAt( lineStart + ii );
487
488 if( c == ' ' || c == '\t' )
489 {
490 if( aWhitespaceCharCount )
491 *aWhitespaceCharCount += 1;
492
493 continue;
494 }
495 else
496 {
497 return c;
498 }
499 }
500
501 return '\r';
502}
503
504
505void SCINTILLA_TRICKS::onScintillaUpdateUI( wxStyledTextEvent& aEvent )
506{
507 auto isBrace = [this]( int c ) -> bool
508 {
509 return m_braces.Find( (wxChar) c ) >= 0;
510 };
511
512 // Has the caret changed position?
513 int caretPos = m_te->GetCurrentPos();
514 int selStart = m_te->GetSelectionStart();
515 int selEnd = m_te->GetSelectionEnd();
516
517 if( m_lastCaretPos != caretPos || m_lastSelStart != selStart || m_lastSelEnd != selEnd )
518 {
519 m_lastCaretPos = caretPos;
520 m_lastSelStart = selStart;
521 m_lastSelEnd = selEnd;
522 int bracePos1 = -1;
523 int bracePos2 = -1;
524
525 // Is there a brace to the left or right?
526 if( caretPos > 0 && isBrace( m_te->GetCharAt( caretPos-1 ) ) )
527 bracePos1 = ( caretPos - 1 );
528 else if( isBrace( m_te->GetCharAt( caretPos ) ) )
529 bracePos1 = caretPos;
530
531 if( bracePos1 >= 0 )
532 {
533 // Find the matching brace
534 bracePos2 = m_te->BraceMatch( bracePos1 );
535
536 if( bracePos2 == -1 )
537 {
538 m_te->BraceBadLight( bracePos1 );
539 m_te->SetHighlightGuide( 0 );
540 }
541 else
542 {
543 m_te->BraceHighlight( bracePos1, bracePos2 );
544 m_te->SetHighlightGuide( m_te->GetColumn( bracePos1 ) );
545 }
546 }
547 else
548 {
549 // Turn off brace matching
550 m_te->BraceHighlight( -1, -1 );
551 m_te->SetHighlightGuide( 0 );
552 }
553 }
554}
555
556
558 const std::function<void( const wxString& xRef, wxArrayString* tokens )>& getTokensFn )
559{
560 wxArrayString autocompleteTokens;
561 int text_pos = m_te->GetCurrentPos();
562 int start = m_te->WordStartPosition( text_pos, true );
563 wxString partial;
564
565 auto textVarRef =
566 [&]( int pos )
567 {
568 return pos >= 2 && m_te->GetCharAt( pos-2 ) == '$' && m_te->GetCharAt( pos-1 ) == '{';
569 };
570
571 // Check for cross-reference
572 if( start > 1 && m_te->GetCharAt( start-1 ) == ':' )
573 {
574 int refStart = m_te->WordStartPosition( start-1, true );
575
576 if( textVarRef( refStart ) )
577 {
578 partial = m_te->GetRange( start, text_pos );
579 getTokensFn( m_te->GetRange( refStart, start-1 ), &autocompleteTokens );
580 }
581 }
582 else if( textVarRef( start ) )
583 {
584 partial = m_te->GetTextRange( start, text_pos );
585 getTokensFn( wxEmptyString, &autocompleteTokens );
586 }
587
588 DoAutocomplete( partial, autocompleteTokens );
589 m_te->SetFocus();
590}
591
592
593void SCINTILLA_TRICKS::DoAutocomplete( const wxString& aPartial, const wxArrayString& aTokens )
594{
596 return;
597
598 wxArrayString matchedTokens;
599
600 wxString filter = wxT( "*" ) + aPartial.Lower() + wxT( "*" );
601
602 for( const wxString& token : aTokens )
603 {
604 if( token.Lower().Matches( filter ) )
605 matchedTokens.push_back( token );
606 }
607
608 if( matchedTokens.size() > 0 )
609 {
610 // NB: tokens MUST be in alphabetical order because the Scintilla engine is going
611 // to do a binary search on them
612 matchedTokens.Sort( []( const wxString& first, const wxString& second ) -> int
613 {
614 return first.CmpNoCase( second );
615 });
616
617 m_te->AutoCompShow( aPartial.size(), wxJoin( matchedTokens, m_te->AutoCompGetSeparator() ) );
618 }
619}
620
621
623{
624 m_te->AutoCompCancel();
625}
626
Dialog helper object to sit in the inheritance tree between wxDialog and any class written by wxFormB...
Definition: dialog_shim.h:84
A color representation with 4 components: red, green, blue, alpha.
Definition: color4d.h:104
COLOR4D WithAlpha(double aAlpha) const
Return a color with the same color, but the given alpha.
Definition: color4d.h:311
double a
Alpha component.
Definition: color4d.h:379
wxColour ToColour() const
Definition: color4d.cpp:220
COLOR4D Mix(const COLOR4D &aColor, double aFactor) const
Return a color that is mixed with the input by a factor.
Definition: color4d.h:295
void onChar(wxStyledTextEvent &aEvent)
int firstNonWhitespace(int aLine, int *aWhitespaceCount=nullptr)
virtual void onCharHook(wxKeyEvent &aEvent)
std::function< void(wxKeyEvent &aEvent)> m_onAcceptFn
void onThemeChanged(wxSysColourChangedEvent &aEvent)
void DoAutocomplete(const wxString &aPartial, const wxArrayString &aTokens)
void onScintillaUpdateUI(wxStyledTextEvent &aEvent)
void DoTextVarAutocomplete(const std::function< void(const wxString &xRef, wxArrayString *tokens)> &getTokensFn)
std::function< void(wxStyledTextEvent &aEvent)> m_onCharAddedFn
void onModified(wxStyledTextEvent &aEvent)
SCINTILLA_TRICKS(wxStyledTextCtrl *aScintilla, const wxString &aBraces, bool aSingleLine, std::function< void(wxKeyEvent &)> onAcceptHandler=[](wxKeyEvent &aEvent) { }, std::function< void(wxStyledTextEvent &)> onCharAddedHandler=[](wxStyledTextEvent &) { })
wxStyledTextCtrl * m_te
This file is part of the common library.
KICOMMON_API wxFont GetMonospacedUIFont()
Definition: ui_common.cpp:89
STL namespace.
bool isCtrlSlash(wxKeyEvent &aEvent)
std::vector< FAB_LAYER_COLOR > dummy
bool ConvertSmartQuotesAndDashes(wxString *aString)
Convert curly quotes and em/en dashes to straight quotes and dashes.