You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

318 lines
9.8 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2020 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. #include <fctsys.h>
  24. #include <kicad_string.h>
  25. #include <scintilla_tricks.h>
  26. #include <wx/stc/stc.h>
  27. #include <gal/color4d.h>
  28. #include <dialog_shim.h>
  29. #include <wx/clipbrd.h>
  30. SCINTILLA_TRICKS::SCINTILLA_TRICKS( wxStyledTextCtrl* aScintilla, const wxString& aBraces ) :
  31. m_te( aScintilla ),
  32. m_braces( aBraces ),
  33. m_lastCaretPos( -1 )
  34. {
  35. // A hack which causes Scintilla to auto-size the text editor canvas
  36. // See: https://github.com/jacobslusser/ScintillaNET/issues/216
  37. m_te->SetScrollWidth( 1 );
  38. m_te->SetScrollWidthTracking( true );
  39. // Set up the brace highlighting
  40. wxColour highlight = wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHT );
  41. wxColour highlightText = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT );
  42. unsigned char r = highlight.Red();
  43. unsigned char g = highlight.Green();
  44. unsigned char b = highlight.Blue();
  45. wxColour::MakeGrey( &r, &g, &b );
  46. highlight.Set( r, g, b );
  47. m_te->StyleSetForeground( wxSTC_STYLE_BRACELIGHT, highlightText );
  48. m_te->StyleSetBackground( wxSTC_STYLE_BRACELIGHT, highlight );
  49. m_te->StyleSetForeground( wxSTC_STYLE_BRACEBAD, *wxRED );
  50. // Set up autocomplete
  51. m_te->AutoCompSetIgnoreCase( true );
  52. m_te->AutoCompSetFillUps( m_braces[1] );
  53. m_te->AutoCompSetMaxHeight( 20 );
  54. // Hook up events
  55. m_te->Bind( wxEVT_STC_UPDATEUI, &SCINTILLA_TRICKS::onScintillaUpdateUI, this );
  56. // Dispatch command-keys in Scintilla control.
  57. m_te->Bind( wxEVT_CHAR_HOOK, &SCINTILLA_TRICKS::onCharHook, this );
  58. }
  59. bool isCtrlSlash( wxKeyEvent& aEvent )
  60. {
  61. if( !aEvent.ControlDown() || aEvent.MetaDown() )
  62. return false;
  63. if( aEvent.GetUnicodeKey() == '/' )
  64. return true;
  65. // OK, now the wxWidgets hacks start.
  66. // (We should abandon these if https://trac.wxwidgets.org/ticket/18911 gets resolved.)
  67. // Many Latin America and European keyboars have have the / over the 7. We know that
  68. // wxWidgets messes this up and returns Shift+7 through GetUnicodeKey(). However, other
  69. // keyboards (such as France and Belgium) have 7 in the shifted position, so a Shift+7
  70. // *could* be legitimate.
  71. // However, we *are* checking Ctrl, so to assume any Shift+7 is a Ctrl-/ really only
  72. // disallows Ctrl+Shift+7 from doing something else, which is probably OK. (This routine
  73. // is only used in the Scintilla editor, not in the rest of Kicad.)
  74. // The other main shifted loation of / is over : (France and Belgium), so we'll sacrifice
  75. // Ctrl+Shift+: too.
  76. if( aEvent.ShiftDown() && ( aEvent.GetUnicodeKey() == '7' || aEvent.GetUnicodeKey() == ':' ) )
  77. return true;
  78. // A few keyboards have / in an Alt position. Since we're expressly not checking Alt for
  79. // up or down, those should work. However, if they don't, there's room below for yet
  80. // another hack....
  81. return false;
  82. }
  83. void SCINTILLA_TRICKS::onCharHook( wxKeyEvent& aEvent )
  84. {
  85. wxString c = aEvent.GetUnicodeKey();
  86. if( ConvertSmartQuotesAndDashes( &c ) )
  87. {
  88. m_te->AddText( c );
  89. }
  90. else if( aEvent.GetKeyCode() == WXK_TAB )
  91. {
  92. if( aEvent.ControlDown() )
  93. {
  94. int flags = 0;
  95. if( !aEvent.ShiftDown() )
  96. flags |= wxNavigationKeyEvent::IsForward;
  97. wxWindow* parent = m_te->GetParent();
  98. while( parent && dynamic_cast<DIALOG_SHIM*>( parent ) == nullptr )
  99. parent = parent->GetParent();
  100. if( parent )
  101. parent->NavigateIn( flags );
  102. }
  103. else
  104. {
  105. m_te->Tab();
  106. }
  107. }
  108. else if( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKeyCode() == 'Z' )
  109. {
  110. m_te->Undo();
  111. }
  112. else if( ( aEvent.GetModifiers() == wxMOD_SHIFT+wxMOD_CONTROL && aEvent.GetKeyCode() == 'Z' )
  113. || ( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKeyCode() == 'Y' ) )
  114. {
  115. m_te->Redo();
  116. }
  117. else if( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKeyCode() == 'A' )
  118. {
  119. m_te->SelectAll();
  120. }
  121. else if( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKeyCode() == 'X' )
  122. {
  123. m_te->Cut();
  124. }
  125. else if( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKeyCode() == 'C' )
  126. {
  127. m_te->Copy();
  128. }
  129. else if( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKeyCode() == 'V' )
  130. {
  131. if( m_te->GetSelectionEnd() > m_te->GetSelectionStart() )
  132. m_te->DeleteBack();
  133. if( wxTheClipboard->Open() )
  134. {
  135. if( wxTheClipboard->IsSupported( wxDF_TEXT ) )
  136. {
  137. wxTextDataObject data;
  138. wxString str;
  139. wxTheClipboard->GetData( data );
  140. str = data.GetText();
  141. ConvertSmartQuotesAndDashes( &str );
  142. m_te->AddText( str );
  143. }
  144. wxTheClipboard->Close();
  145. }
  146. }
  147. else if( aEvent.GetKeyCode() == WXK_BACK )
  148. {
  149. m_te->DeleteBack();
  150. }
  151. else if( aEvent.GetKeyCode() == WXK_DELETE )
  152. {
  153. if( m_te->GetSelectionEnd() > m_te->GetSelectionStart() )
  154. m_te->DeleteBack();
  155. else
  156. m_te->DeleteRange( m_te->GetSelectionStart(), 1 );
  157. }
  158. else if( isCtrlSlash( aEvent ) )
  159. {
  160. int startLine = m_te->LineFromPosition( m_te->GetSelectionStart() );
  161. int endLine = m_te->LineFromPosition( m_te->GetSelectionEnd() );
  162. bool comment = firstNonWhitespace( startLine ) != '#';
  163. int whitespaceCount;
  164. m_te->BeginUndoAction();
  165. for( int ii = startLine; ii <= endLine; ++ii )
  166. {
  167. if( comment )
  168. m_te->InsertText( m_te->PositionFromLine( ii ), "#" );
  169. else if( firstNonWhitespace( ii, &whitespaceCount ) == '#' )
  170. m_te->DeleteRange( m_te->PositionFromLine( ii ) + whitespaceCount, 1 );
  171. }
  172. m_te->SetSelection( m_te->PositionFromLine( startLine ),
  173. m_te->PositionFromLine( endLine ) + m_te->GetLineLength( endLine ) );
  174. m_te->EndUndoAction();
  175. }
  176. else
  177. {
  178. aEvent.Skip();
  179. }
  180. }
  181. int SCINTILLA_TRICKS::firstNonWhitespace( int aLine, int* aWhitespaceCharCount )
  182. {
  183. int lineStart = m_te->PositionFromLine( aLine );
  184. if( aWhitespaceCharCount )
  185. *aWhitespaceCharCount = 0;
  186. for( int ii = 0; ii < m_te->GetLineLength( aLine ); ++ii )
  187. {
  188. int c = m_te->GetCharAt( lineStart + ii );
  189. if( c == ' ' || c == '\t' )
  190. {
  191. if( aWhitespaceCharCount )
  192. *aWhitespaceCharCount += 1;
  193. continue;
  194. }
  195. else
  196. {
  197. return c;
  198. }
  199. }
  200. return '\r';
  201. }
  202. void SCINTILLA_TRICKS::onScintillaUpdateUI( wxStyledTextEvent& aEvent )
  203. {
  204. auto isBrace = [this]( int c ) -> bool
  205. {
  206. return m_braces.Find( (wxChar) c ) >= 0;
  207. };
  208. // Has the caret changed position?
  209. int caretPos = m_te->GetCurrentPos();
  210. if( m_lastCaretPos != caretPos )
  211. {
  212. m_lastCaretPos = caretPos;
  213. int bracePos1 = -1;
  214. int bracePos2 = -1;
  215. // Is there a brace to the left or right?
  216. if( caretPos > 0 && isBrace( m_te->GetCharAt( caretPos-1 ) ) )
  217. bracePos1 = ( caretPos - 1 );
  218. else if( isBrace( m_te->GetCharAt( caretPos ) ) )
  219. bracePos1 = caretPos;
  220. if( bracePos1 >= 0 )
  221. {
  222. // Find the matching brace
  223. bracePos2 = m_te->BraceMatch( bracePos1 );
  224. if( bracePos2 == -1 )
  225. {
  226. m_te->BraceBadLight( bracePos1 );
  227. m_te->SetHighlightGuide( 0 );
  228. }
  229. else
  230. {
  231. m_te->BraceHighlight( bracePos1, bracePos2 );
  232. m_te->SetHighlightGuide( m_te->GetColumn( bracePos1 ) );
  233. }
  234. }
  235. else
  236. {
  237. // Turn off brace matching
  238. m_te->BraceHighlight( -1, -1 );
  239. m_te->SetHighlightGuide( 0 );
  240. }
  241. }
  242. }
  243. void SCINTILLA_TRICKS::DoAutocomplete( const wxString& aPartial, const wxArrayString& aTokens )
  244. {
  245. wxArrayString matchedTokens;
  246. wxString filter = wxT( "*" ) + aPartial.Lower() + wxT( "*" );
  247. for( const wxString& token : aTokens )
  248. {
  249. if( token.Lower().Matches( filter ) )
  250. matchedTokens.push_back( token );
  251. }
  252. if( matchedTokens.size() > 0 )
  253. {
  254. // NB: tokens MUST be in alphabetical order because the Scintilla engine is going
  255. // to do a binary search on them
  256. matchedTokens.Sort( []( const wxString& first, const wxString& second ) -> int
  257. {
  258. return first.CmpNoCase( second );
  259. });
  260. m_te->AutoCompShow( aPartial.size(), wxJoin( matchedTokens, ' ' ) );
  261. }
  262. }