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.

504 lines
18 KiB

5 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2012 Jean-Pierre Charras, jean-pierre.charras@gipsa-lab.inpg.com
  5. * Copyright (C) 2016 Wayne Stambaugh, stambaughw@gmail.com
  6. * Copyright (C) 2004-2021 KiCad Developers, see AITHORS.txt for contributors.
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public License
  10. * as published by the Free Software Foundation; either version 2
  11. * of the License, or (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, you may find one here:
  20. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  21. * or you may search the http://www.gnu.org website for the version 2 license,
  22. * or you may write to the Free Software Foundation, Inc.,
  23. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  24. */
  25. #include <bitmaps.h>
  26. #include <kiway.h>
  27. #include <confirm.h>
  28. #include <kicad_string.h>
  29. #include <sch_base_frame.h>
  30. #include <sch_edit_frame.h>
  31. #include <ee_collectors.h>
  32. #include <sch_symbol.h>
  33. #include <lib_field.h>
  34. #include <template_fieldnames.h>
  35. #include <symbol_library.h>
  36. #include <sch_validators.h>
  37. #include <schematic.h>
  38. #include <dialog_edit_one_field.h>
  39. #include <sch_text.h>
  40. #include <scintilla_tricks.h>
  41. DIALOG_EDIT_ONE_FIELD::DIALOG_EDIT_ONE_FIELD( SCH_BASE_FRAME* aParent, const wxString& aTitle,
  42. const EDA_TEXT* aTextItem ) :
  43. DIALOG_LIB_EDIT_TEXT_BASE( aParent ),
  44. m_posX( aParent, m_xPosLabel, m_xPosCtrl, m_xPosUnits, true ),
  45. m_posY( aParent, m_yPosLabel, m_yPosCtrl, m_yPosUnits, true ),
  46. m_textSize( aParent, m_textSizeLabel, m_textSizeCtrl, m_textSizeUnits, true ),
  47. m_firstFocus( true ),
  48. m_scintillaTricks( nullptr )
  49. {
  50. wxASSERT( aTextItem );
  51. SetTitle( aTitle );
  52. // The field ID and power status are Initialized in the derived object's ctor.
  53. m_fieldId = VALUE_FIELD;
  54. m_isPower = false;
  55. m_scintillaTricks = new SCINTILLA_TRICKS( m_StyledTextCtrl, wxT( "{}" ) );
  56. m_StyledTextCtrl->SetEOLMode( wxSTC_EOL_LF ); // Normalize EOL across platforms
  57. m_text = aTextItem->GetText();
  58. m_isItalic = aTextItem->IsItalic();
  59. m_isBold = aTextItem->IsBold();
  60. m_position = aTextItem->GetTextPos();
  61. m_size = aTextItem->GetTextWidth();
  62. m_isVertical = ( aTextItem->GetTextAngle() == TEXT_ANGLE_VERT );
  63. m_verticalJustification = aTextItem->GetVertJustify() + 1;
  64. m_horizontalJustification = aTextItem->GetHorizJustify() + 1;
  65. m_isVisible = aTextItem->IsVisible();
  66. }
  67. DIALOG_EDIT_ONE_FIELD::~DIALOG_EDIT_ONE_FIELD()
  68. {
  69. delete m_scintillaTricks;
  70. }
  71. void DIALOG_EDIT_ONE_FIELD::init()
  72. {
  73. SCH_BASE_FRAME* parent = GetParent();
  74. bool isSymbolEditor = parent && parent->IsType( FRAME_SCH_SYMBOL_EDITOR );
  75. // Disable options for graphic text editing which are not needed for fields.
  76. m_CommonConvert->Show( false );
  77. m_CommonUnit->Show( false );
  78. // Predefined fields cannot contain some chars, or cannot be empty,
  79. // and need a SCH_FIELD_VALIDATOR (m_StyledTextCtrl cannot use a SCH_FIELD_VALIDATOR).
  80. bool use_validator = m_fieldId == REFERENCE_FIELD
  81. || m_fieldId == VALUE_FIELD
  82. || m_fieldId == FOOTPRINT_FIELD
  83. || m_fieldId == DATASHEET_FIELD
  84. || m_fieldId == SHEETNAME_V
  85. || m_fieldId == SHEETFILENAME_V;
  86. if( use_validator )
  87. {
  88. m_TextCtrl->SetValidator( SCH_FIELD_VALIDATOR( isSymbolEditor, m_fieldId, &m_text ) );
  89. SetInitialFocus( m_TextCtrl );
  90. m_StyledTextCtrl->Show( false );
  91. }
  92. else
  93. {
  94. SetInitialFocus( m_StyledTextCtrl );
  95. m_TextCtrl->Show( false );
  96. }
  97. // Show the footprint selection dialog if this is the footprint field.
  98. m_TextValueSelectButton->SetBitmap( KiBitmap( BITMAPS::small_library ) );
  99. m_TextValueSelectButton->Show( m_fieldId == FOOTPRINT_FIELD );
  100. // Value fields of power symbols cannot be modified. This will grey out
  101. // the text box and display an explanation.
  102. if( m_fieldId == VALUE_FIELD && m_isPower )
  103. {
  104. m_PowerComponentValues->Show( true );
  105. m_TextCtrl->Enable( false );
  106. }
  107. else
  108. {
  109. m_PowerComponentValues->Show( false );
  110. m_TextCtrl->Enable( true );
  111. }
  112. m_sdbSizerButtonsOK->SetDefault();
  113. GetSizer()->SetSizeHints( this );
  114. // Adjust the height of the scintilla text editor after the first layout
  115. // To show only one line
  116. // (multiline text are is supported in fields and will be removed)
  117. if( m_StyledTextCtrl->IsShown() )
  118. {
  119. wxSize maxSize = m_StyledTextCtrl->GetSize();
  120. maxSize.y = m_xPosCtrl->GetSize().y;
  121. m_StyledTextCtrl->SetMaxSize( maxSize );
  122. m_StyledTextCtrl->SetUseVerticalScrollBar( false );
  123. m_StyledTextCtrl->SetUseHorizontalScrollBar( false );
  124. }
  125. // Now all widgets have the size fixed, call FinishDialogSettings
  126. finishDialogSettings();
  127. }
  128. void DIALOG_EDIT_ONE_FIELD::OnTextValueSelectButtonClick( wxCommandEvent& aEvent )
  129. {
  130. // pick a footprint using the footprint picker.
  131. wxString fpid;
  132. if( m_StyledTextCtrl->IsShown() )
  133. fpid = m_StyledTextCtrl->GetValue();
  134. else
  135. fpid = m_TextCtrl->GetValue();
  136. KIWAY_PLAYER* frame = Kiway().Player( FRAME_FOOTPRINT_VIEWER_MODAL, true );
  137. if( frame->ShowModal( &fpid, this ) )
  138. {
  139. if( m_StyledTextCtrl->IsShown() )
  140. m_StyledTextCtrl->SetValue( fpid );
  141. else
  142. m_TextCtrl->SetValue( fpid );
  143. }
  144. frame->Destroy();
  145. }
  146. void DIALOG_EDIT_ONE_FIELD::OnSetFocusText( wxFocusEvent& event )
  147. {
  148. if( m_firstFocus )
  149. {
  150. #ifdef __WXGTK__
  151. // Force an update of the text control before setting the text selection
  152. // This is needed because GTK seems to ignore the selection on first update
  153. //
  154. // Note that we can't do this on OSX as it tends to provoke Apple's
  155. // "[NSAlert runModal] may not be invoked inside of transaction begin/commit pair"
  156. // bug. See: https://bugs.launchpad.net/kicad/+bug/1837225
  157. if( m_fieldId == REFERENCE_FIELD || m_fieldId == VALUE_FIELD || m_fieldId == SHEETNAME_V )
  158. m_TextCtrl->Update();
  159. #endif
  160. if( m_fieldId == REFERENCE_FIELD )
  161. KIUI::SelectReferenceNumber( static_cast<wxTextEntry*>( m_TextCtrl ) );
  162. else if( m_fieldId == VALUE_FIELD || m_fieldId == SHEETNAME_V )
  163. m_TextCtrl->SetSelection( -1, -1 );
  164. m_firstFocus = false;
  165. }
  166. event.Skip();
  167. }
  168. bool DIALOG_EDIT_ONE_FIELD::TransferDataToWindow()
  169. {
  170. if( m_TextCtrl->IsShown() )
  171. m_TextCtrl->SetValue( m_text );
  172. else if( m_StyledTextCtrl->IsShown() )
  173. m_StyledTextCtrl->SetValue( m_text );
  174. m_posX.SetValue( m_position.x );
  175. m_posY.SetValue( m_position.y );
  176. m_textSize.SetValue( m_size );
  177. m_orientChoice->SetSelection( m_isVertical ? 1 : 0 );
  178. m_hAlignChoice->SetSelection( m_horizontalJustification );
  179. m_vAlignChoice->SetSelection( m_verticalJustification );
  180. m_visible->SetValue( m_isVisible );
  181. m_italic->SetValue( m_isItalic );
  182. m_bold->SetValue( m_isBold );
  183. return true;
  184. }
  185. bool DIALOG_EDIT_ONE_FIELD::TransferDataFromWindow()
  186. {
  187. if( m_TextCtrl->IsShown() )
  188. m_text = m_TextCtrl->GetValue();
  189. else if( m_StyledTextCtrl->IsShown() )
  190. m_text = m_StyledTextCtrl->GetValue();
  191. if( m_fieldId == REFERENCE_FIELD )
  192. {
  193. // Test if the reference string is valid:
  194. if( !SCH_SYMBOL::IsReferenceStringValid( m_text ) )
  195. {
  196. DisplayError( this, _( "Illegal reference designator value!" ) );
  197. return false;
  198. }
  199. }
  200. else if( m_fieldId == VALUE_FIELD )
  201. {
  202. if( m_text.IsEmpty() )
  203. {
  204. DisplayError( this, _( "Value may not be empty." ) );
  205. return false;
  206. }
  207. }
  208. m_isVertical = m_orientChoice->GetSelection() == 1;
  209. m_position = wxPoint( m_posX.GetValue(), m_posY.GetValue() );
  210. m_size = m_textSize.GetValue();
  211. m_horizontalJustification = m_hAlignChoice->GetSelection();
  212. m_verticalJustification = m_vAlignChoice->GetSelection();
  213. m_isVisible = m_visible->GetValue();
  214. m_isItalic = m_italic->GetValue();
  215. m_isBold = m_bold->GetValue();
  216. return true;
  217. }
  218. void DIALOG_EDIT_ONE_FIELD::updateText( EDA_TEXT* aText )
  219. {
  220. aText->SetTextPos( m_position );
  221. aText->SetTextSize( wxSize( m_size, m_size ) );
  222. aText->SetVisible( m_isVisible );
  223. aText->SetTextAngle( m_isVertical ? TEXT_ANGLE_VERT : TEXT_ANGLE_HORIZ );
  224. aText->SetItalic( m_isItalic );
  225. aText->SetBold( m_isBold );
  226. aText->SetHorizJustify( EDA_TEXT::MapHorizJustify( m_horizontalJustification - 1 ) );
  227. aText->SetVertJustify( EDA_TEXT::MapVertJustify( m_verticalJustification - 1 ) );
  228. }
  229. DIALOG_LIB_EDIT_ONE_FIELD::DIALOG_LIB_EDIT_ONE_FIELD( SCH_BASE_FRAME* aParent,
  230. const wxString& aTitle,
  231. const LIB_FIELD* aField ) :
  232. DIALOG_EDIT_ONE_FIELD( aParent, aTitle, aField )
  233. {
  234. m_fieldId = aField->GetId();
  235. // When in the library editor, power symbols can be renamed.
  236. m_isPower = false;
  237. init();
  238. }
  239. DIALOG_SCH_EDIT_ONE_FIELD::DIALOG_SCH_EDIT_ONE_FIELD( SCH_BASE_FRAME* aParent,
  240. const wxString& aTitle,
  241. const SCH_FIELD* aField ) :
  242. DIALOG_EDIT_ONE_FIELD( aParent, aTitle, aField ),
  243. m_field( aField )
  244. {
  245. if( aField->GetParent() && aField->GetParent()->Type() == SCH_SYMBOL_T )
  246. {
  247. m_fieldId = aField->GetId();
  248. }
  249. else if( aField->GetParent() && aField->GetParent()->Type() == SCH_SHEET_T )
  250. {
  251. switch( aField->GetId() )
  252. {
  253. case SHEETNAME: m_fieldId = SHEETNAME_V; break;
  254. case SHEETFILENAME: m_fieldId = SHEETFILENAME_V; break;
  255. default: m_fieldId = SHEETUSERFIELD_V; break;
  256. }
  257. }
  258. // show text variable cross-references in a human-readable format
  259. m_text = aField->Schematic()->ConvertKIIDsToRefs( aField->GetText() );
  260. m_isPower = false;
  261. m_textLabel->SetLabel( m_field->GetName() + ":" );
  262. // The library symbol may have been removed so using SCH_SYMBOL::GetLibSymbolRef() here
  263. // could result in a segfault. If the library symbol is no longer available, the
  264. // schematic fields can still edit so set the power symbol flag to false. This may not
  265. // be entirely accurate if the power library is missing but it's better then a segfault.
  266. if( aField->GetParent() && aField->GetParent()->Type() == SCH_SYMBOL_T )
  267. {
  268. const SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( aField->GetParent() );
  269. const LIB_SYMBOL* libSymbol = GetParent()->GetLibSymbol( symbol->GetLibId(), true );
  270. if( libSymbol && libSymbol->IsPower() )
  271. m_isPower = true;
  272. }
  273. m_StyledTextCtrl->Bind( wxEVT_STC_CHARADDED,
  274. &DIALOG_SCH_EDIT_ONE_FIELD::onScintillaCharAdded, this );
  275. init();
  276. }
  277. void DIALOG_SCH_EDIT_ONE_FIELD::onScintillaCharAdded( wxStyledTextEvent &aEvent )
  278. {
  279. int key = aEvent.GetKey();
  280. SCH_EDIT_FRAME* editFrame = static_cast<SCH_EDIT_FRAME*>( GetParent() );
  281. wxArrayString autocompleteTokens;
  282. int pos = m_StyledTextCtrl->GetCurrentPos();
  283. int start = m_StyledTextCtrl->WordStartPosition( pos, true );
  284. wxString partial;
  285. // Currently, '\n' is not allowed in fields. So remove it when entered
  286. // TODO: see if we must close the dialog. However this is not obvious, as
  287. // if a \n is typed (and removed) when a text is selected, this text is deleted
  288. // (in fact replaced by \n, that is removed by the filter)
  289. if( key == '\n' )
  290. {
  291. wxString text = m_StyledTextCtrl->GetText();
  292. int currpos = m_StyledTextCtrl->GetCurrentPos();
  293. text.Replace( "\n", "" );
  294. m_StyledTextCtrl->SetText( text );
  295. m_StyledTextCtrl->GotoPos( currpos-1 );
  296. return;
  297. }
  298. auto textVarRef =
  299. [&]( int pt )
  300. {
  301. return pt >= 2
  302. && m_StyledTextCtrl->GetCharAt( pt - 2 ) == '$'
  303. && m_StyledTextCtrl->GetCharAt( pt - 1 ) == '{';
  304. };
  305. // Check for cross-reference
  306. if( start > 1 && m_StyledTextCtrl->GetCharAt( start - 1 ) == ':' )
  307. {
  308. int refStart = m_StyledTextCtrl->WordStartPosition( start - 1, true );
  309. if( textVarRef( refStart ) )
  310. {
  311. partial = m_StyledTextCtrl->GetRange( start, pos );
  312. wxString ref = m_StyledTextCtrl->GetRange( refStart, start - 1 );
  313. SCH_SHEET_LIST sheets = editFrame->Schematic().GetSheets();
  314. SCH_REFERENCE_LIST refs;
  315. SCH_SYMBOL* refSymbol = nullptr;
  316. sheets.GetSymbols( refs );
  317. for( size_t jj = 0; jj < refs.GetCount(); jj++ )
  318. {
  319. if( refs[ jj ].GetSymbol()->GetRef( &refs[ jj ].GetSheetPath(), true ) == ref )
  320. {
  321. refSymbol = refs[ jj ].GetSymbol();
  322. break;
  323. }
  324. }
  325. if( refSymbol )
  326. refSymbol->GetContextualTextVars( &autocompleteTokens );
  327. }
  328. }
  329. else if( textVarRef( start ) )
  330. {
  331. partial = m_StyledTextCtrl->GetTextRange( start, pos );
  332. SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( m_field->GetParent() );
  333. SCH_SHEET* sheet = dynamic_cast<SCH_SHEET*>( m_field->GetParent() );
  334. if( symbol )
  335. {
  336. symbol->GetContextualTextVars( &autocompleteTokens );
  337. SCHEMATIC* schematic = symbol->Schematic();
  338. if( schematic && schematic->CurrentSheet().Last() )
  339. schematic->CurrentSheet().Last()->GetContextualTextVars( &autocompleteTokens );
  340. }
  341. if( sheet )
  342. sheet->GetContextualTextVars( &autocompleteTokens );
  343. for( std::pair<wxString, wxString> entry : Prj().GetTextVars() )
  344. autocompleteTokens.push_back( entry.first );
  345. }
  346. m_scintillaTricks->DoAutocomplete( partial, autocompleteTokens );
  347. m_StyledTextCtrl->SetFocus();
  348. }
  349. void DIALOG_SCH_EDIT_ONE_FIELD::UpdateField( SCH_FIELD* aField, SCH_SHEET_PATH* aSheetPath )
  350. {
  351. SCH_EDIT_FRAME* editFrame = dynamic_cast<SCH_EDIT_FRAME*>( GetParent() );
  352. SCH_ITEM* parent = dynamic_cast<SCH_ITEM*>( aField->GetParent() );
  353. int fieldType = aField->GetId();
  354. if( parent && parent->Type() == SCH_SYMBOL_T )
  355. {
  356. SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( parent );
  357. if( fieldType == REFERENCE_FIELD )
  358. symbol->SetRef( aSheetPath, m_text );
  359. else if( fieldType == VALUE_FIELD )
  360. symbol->SetValue( m_text );
  361. else if( fieldType == FOOTPRINT_FIELD )
  362. symbol->SetFootprint( m_text );
  363. }
  364. bool positioningModified = false;
  365. if( aField->GetTextPos() != m_position )
  366. positioningModified = true;
  367. if( ( aField->GetTextAngle() == TEXT_ANGLE_VERT ) != m_isVertical )
  368. positioningModified = true;
  369. if( aField->GetHorizJustify() != EDA_TEXT::MapHorizJustify( m_horizontalJustification - 1 ) )
  370. positioningModified = true;
  371. if( aField->GetVertJustify() != EDA_TEXT::MapVertJustify( m_verticalJustification - 1 ) )
  372. positioningModified = true;
  373. // convert any text variable cross-references to their UUIDs
  374. m_text = aField->Schematic()->ConvertRefsToKIIDs( m_text );
  375. aField->SetText( m_text );
  376. updateText( aField );
  377. // The value, footprint and datasheet fields should be kept in sync in multi-unit parts.
  378. // Of course the symbol must be annotated to collect other units.
  379. if( editFrame && parent && parent->Type() == SCH_SYMBOL_T )
  380. {
  381. SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( parent );
  382. if( symbol->IsAnnotated( aSheetPath ) && ( fieldType == VALUE_FIELD
  383. || fieldType == FOOTPRINT_FIELD
  384. || fieldType == DATASHEET_FIELD ) )
  385. {
  386. wxString ref = symbol->GetRef( aSheetPath );
  387. int unit = symbol->GetUnit();
  388. LIB_ID libId = symbol->GetLibId();
  389. for( SCH_SHEET_PATH& sheet : editFrame->Schematic().GetSheets() )
  390. {
  391. SCH_SCREEN* screen = sheet.LastScreen();
  392. std::vector<SCH_SYMBOL*> otherUnits;
  393. constexpr bool appendUndo = true;
  394. CollectOtherUnits( ref, unit, libId, sheet, &otherUnits );
  395. for( SCH_SYMBOL* otherUnit : otherUnits )
  396. {
  397. editFrame->SaveCopyInUndoList( screen, otherUnit, UNDO_REDO::CHANGED,
  398. appendUndo );
  399. if( fieldType == VALUE_FIELD )
  400. otherUnit->SetValue( m_text );
  401. else if( fieldType == FOOTPRINT_FIELD )
  402. otherUnit->SetFootprint( m_text );
  403. else
  404. otherUnit->GetField( DATASHEET_FIELD )->SetText( m_text );
  405. editFrame->UpdateItem( otherUnit );
  406. }
  407. }
  408. }
  409. }
  410. if( positioningModified && parent )
  411. parent->ClearFieldsAutoplaced();
  412. }