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.

400 lines
13 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2004-2018 Jean-Pierre Charras jp.charras at wanadoo.fr
  5. * Copyright (C) 2010-2020 KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, you may find one here:
  19. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  20. * or you may search the http://www.gnu.org website for the version 2 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. #include <dialog_text_properties.h>
  25. #include <confirm.h>
  26. #include <widgets/unit_binder.h>
  27. #include <board_commit.h>
  28. #include <board.h>
  29. #include <footprint.h>
  30. #include <kicad_string.h>
  31. #include <pcb_text.h>
  32. #include <fp_text.h>
  33. #include <pcb_edit_frame.h>
  34. #include <pcb_layer_box_selector.h>
  35. #include <wx/valnum.h>
  36. #include <math/util.h> // for KiROUND
  37. DIALOG_TEXT_PROPERTIES::DIALOG_TEXT_PROPERTIES( PCB_BASE_EDIT_FRAME* aParent, BOARD_ITEM* aItem ) :
  38. DIALOG_TEXT_PROPERTIES_BASE( aParent ),
  39. m_Parent( aParent ),
  40. m_item( aItem ),
  41. m_edaText( nullptr ),
  42. m_fpText( nullptr ),
  43. m_pcbText( nullptr ),
  44. m_textWidth( aParent, m_SizeXLabel, m_SizeXCtrl, m_SizeXUnits, true ),
  45. m_textHeight( aParent, m_SizeYLabel, m_SizeYCtrl, m_SizeYUnits, true ),
  46. m_thickness( aParent, m_ThicknessLabel, m_ThicknessCtrl, m_ThicknessUnits, true ),
  47. m_posX( aParent, m_PositionXLabel, m_PositionXCtrl, m_PositionXUnits ),
  48. m_posY( aParent, m_PositionYLabel, m_PositionYCtrl, m_PositionYUnits ),
  49. m_orientation( aParent, m_OrientLabel, m_OrientCtrl, nullptr, false )
  50. {
  51. wxString title;
  52. // Configure display origin transforms
  53. m_posX.SetCoordType( ORIGIN_TRANSFORMS::ABS_X_COORD );
  54. m_posY.SetCoordType( ORIGIN_TRANSFORMS::ABS_Y_COORD );
  55. m_MultiLineText->SetEOLMode( wxSTC_EOL_LF );
  56. // A hack which causes Scintilla to auto-size the text editor canvas
  57. // See: https://github.com/jacobslusser/ScintillaNET/issues/216
  58. m_MultiLineText->SetScrollWidth( 1 );
  59. m_MultiLineText->SetScrollWidthTracking( true );
  60. if( m_item->Type() == PCB_FP_TEXT_T )
  61. {
  62. title = _( "Footprint Text Properties" );
  63. m_fpText = (FP_TEXT*) m_item;
  64. m_edaText = static_cast<EDA_TEXT*>( m_fpText );
  65. switch( m_fpText->GetType() )
  66. {
  67. case FP_TEXT::TEXT_is_REFERENCE: m_TextLabel->SetLabel( _( "Reference:" ) ); break;
  68. case FP_TEXT::TEXT_is_VALUE: m_TextLabel->SetLabel( _( "Value:" ) ); break;
  69. case FP_TEXT::TEXT_is_DIVERS: m_TextLabel->SetLabel( _( "Text:" ) ); break;
  70. }
  71. SetInitialFocus( m_SingleLineText );
  72. m_MultiLineSizer->Show( false );
  73. }
  74. else
  75. {
  76. title = _( "Text Properties" );
  77. m_pcbText = (PCB_TEXT*) aItem;
  78. m_edaText = static_cast<EDA_TEXT*>( m_pcbText );
  79. SetInitialFocus( m_MultiLineText );
  80. m_SingleLineSizer->Show( false );
  81. // This option makes sense only for footprint texts; texts on board are always visible.
  82. m_Visible->SetValue( true );
  83. m_Visible->Enable( false );
  84. m_KeepUpright->Show( false );
  85. m_statusLine->Show( false );
  86. }
  87. SetTitle( title );
  88. m_hash_key = title;
  89. // Configure the layers list selector. Note that footprints are built outside the current
  90. // board and so we may need to show all layers if the text is on an unactivated layer.
  91. if( !m_Parent->GetBoard()->IsLayerEnabled( m_item->GetLayer() ) )
  92. m_LayerSelectionCtrl->ShowNonActivatedLayers( true );
  93. m_LayerSelectionCtrl->SetLayersHotkeys( false );
  94. m_LayerSelectionCtrl->SetBoardFrame( m_Parent );
  95. m_LayerSelectionCtrl->Resync();
  96. m_OrientValue = 0.0;
  97. m_orientation.SetUnits( EDA_UNITS::DEGREES );
  98. m_orientation.SetPrecision( 3 );
  99. // Set predefined rotations in combo dropdown, according to the locale floating point
  100. // separator notation
  101. double rot_list[] = { 0.0, 90.0, -90.0, 180.0 };
  102. for( size_t ii = 0; ii < m_OrientCtrl->GetCount() && ii < 4; ++ii )
  103. m_OrientCtrl->SetString( ii, wxString::Format( "%.1f", rot_list[ii] ) );
  104. // Set font sizes
  105. wxFont infoFont = wxSystemSettings::GetFont( wxSYS_DEFAULT_GUI_FONT );
  106. infoFont.SetSymbolicSize( wxFONTSIZE_X_SMALL );
  107. m_statusLine->SetFont( infoFont );
  108. m_sdbSizerOK->SetDefault();
  109. // We can't set the tab order through wxWidgets due to shortcomings in their mnemonics
  110. // implementation on MSW
  111. m_tabOrder = {
  112. m_LayerLabel,
  113. m_LayerSelectionCtrl,
  114. m_SizeXCtrl,
  115. m_SizeYCtrl,
  116. m_ThicknessCtrl,
  117. m_PositionXCtrl,
  118. m_PositionYCtrl,
  119. m_Visible,
  120. m_Italic,
  121. m_JustifyChoice,
  122. m_OrientCtrl,
  123. m_Mirrored,
  124. m_KeepUpright,
  125. m_sdbSizerOK,
  126. m_sdbSizerCancel
  127. };
  128. // wxTextCtrls fail to generate wxEVT_CHAR events when the wxTE_MULTILINE flag is set,
  129. // so we have to listen to wxEVT_CHAR_HOOK events instead.
  130. Connect( wxEVT_CHAR_HOOK, wxKeyEventHandler( DIALOG_TEXT_PROPERTIES::OnCharHook ), NULL, this );
  131. finishDialogSettings();
  132. }
  133. DIALOG_TEXT_PROPERTIES::~DIALOG_TEXT_PROPERTIES()
  134. {
  135. Disconnect( wxEVT_CHAR_HOOK, wxKeyEventHandler( DIALOG_TEXT_PROPERTIES::OnCharHook ), NULL, this );
  136. }
  137. /**
  138. * Routine for main window class to launch text properties dialog.
  139. */
  140. void PCB_BASE_EDIT_FRAME::ShowTextPropertiesDialog( BOARD_ITEM* aText )
  141. {
  142. DIALOG_TEXT_PROPERTIES dlg( this, aText );
  143. dlg.ShowModal();
  144. }
  145. void DIALOG_TEXT_PROPERTIES::OnCharHook( wxKeyEvent& aEvent )
  146. {
  147. if( aEvent.GetKeyCode() == WXK_RETURN && aEvent.ShiftDown() )
  148. {
  149. if( TransferDataFromWindow() )
  150. EndModal( wxID_OK );
  151. }
  152. else if( m_MultiLineText->IsShown() && m_MultiLineText->HasFocus() )
  153. {
  154. if( aEvent.GetKeyCode() == WXK_TAB && !aEvent.ControlDown() )
  155. {
  156. m_MultiLineText->Tab();
  157. }
  158. else if( IsCtrl( 'Z', aEvent ) )
  159. {
  160. m_MultiLineText->Undo();
  161. }
  162. else if( IsShiftCtrl( 'Z', aEvent ) || IsCtrl( 'Y', aEvent ) )
  163. {
  164. m_MultiLineText->Redo();
  165. }
  166. else if( IsCtrl( 'X', aEvent ) )
  167. {
  168. m_MultiLineText->Cut();
  169. }
  170. else if( IsCtrl( 'C', aEvent ) )
  171. {
  172. m_MultiLineText->Copy();
  173. }
  174. else if( IsCtrl( 'V', aEvent ) )
  175. {
  176. m_MultiLineText->Paste();
  177. }
  178. else if( IsCtrl( 'A', aEvent ) )
  179. {
  180. m_MultiLineText->SelectAll();
  181. }
  182. else
  183. {
  184. aEvent.Skip();
  185. }
  186. }
  187. else
  188. {
  189. aEvent.Skip();
  190. }
  191. }
  192. void DIALOG_TEXT_PROPERTIES::OnSetFocusText( wxFocusEvent& event )
  193. {
  194. #ifdef __WXGTK__
  195. // Force an update of the text control before setting the text selection
  196. // This is needed because GTK seems to ignore the selection on first update
  197. //
  198. // Note that we can't do this on OSX as it tends to provoke Apple's
  199. // "[NSAlert runModal] may not be invoked inside of transaction begin/commit pair"
  200. // bug. See: https://bugs.launchpad.net/kicad/+bug/1837225
  201. if( m_fpText->GetType() == FP_TEXT::TEXT_is_REFERENCE )
  202. m_SingleLineText->Update();
  203. #endif
  204. if( m_fpText->GetType() == FP_TEXT::TEXT_is_REFERENCE )
  205. KIUI::SelectReferenceNumber( static_cast<wxTextEntry*>( m_SingleLineText ) );
  206. else
  207. m_SingleLineText->SetSelection( -1, -1 );
  208. event.Skip();
  209. }
  210. bool DIALOG_TEXT_PROPERTIES::TransferDataToWindow()
  211. {
  212. if( m_SingleLineText->IsShown() )
  213. {
  214. m_SingleLineText->SetValue( m_edaText->GetText() );
  215. if( m_fpText && m_fpText->GetType() == FP_TEXT::TEXT_is_REFERENCE )
  216. KIUI::SelectReferenceNumber( static_cast<wxTextEntry*>( m_SingleLineText ) );
  217. else
  218. m_SingleLineText->SetSelection( -1, -1 );
  219. }
  220. else if( m_MultiLineText->IsShown() )
  221. {
  222. BOARD* board = m_Parent->GetBoard();
  223. wxString converted = board->ConvertKIIDsToCrossReferences( m_edaText->GetText() );
  224. m_MultiLineText->SetValue( converted );
  225. m_MultiLineText->SetSelection( -1, -1 );
  226. }
  227. if( m_item->Type() == PCB_FP_TEXT_T && m_fpText )
  228. {
  229. FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( m_fpText->GetParent() );
  230. wxString msg;
  231. if( footprint )
  232. {
  233. msg.Printf( _( "Footprint %s (%s), %s, rotated %.1f deg"),
  234. footprint->GetReference(),
  235. footprint->GetValue(),
  236. footprint->IsFlipped() ? _( "back side (mirrored)" ) : _( "front side" ),
  237. footprint->GetOrientation() / 10.0 );
  238. }
  239. m_statusLine->SetLabel( msg );
  240. }
  241. else
  242. {
  243. m_statusLine->Show( false );
  244. }
  245. m_cbLocked->SetValue( m_item->IsLocked() );
  246. m_LayerSelectionCtrl->SetLayerSelection( m_item->GetLayer() );
  247. m_textWidth.SetValue( m_edaText->GetTextSize().x );
  248. m_textHeight.SetValue( m_edaText->GetTextSize().y );
  249. m_thickness.SetValue( m_edaText->GetTextThickness() );
  250. m_posX.SetValue( m_edaText->GetTextPos().x );
  251. m_posY.SetValue( m_edaText->GetTextPos().y );
  252. m_Visible->SetValue( m_edaText->IsVisible() );
  253. m_Italic->SetValue( m_edaText->IsItalic() );
  254. EDA_TEXT_HJUSTIFY_T hJustify = m_edaText->GetHorizJustify();
  255. m_JustifyChoice->SetSelection( (int) hJustify + 1 );
  256. m_OrientValue = m_edaText->GetTextAngle();
  257. m_orientation.SetDoubleValue( m_OrientValue );
  258. m_Mirrored->SetValue( m_edaText->IsMirrored() );
  259. if( m_fpText )
  260. m_KeepUpright->SetValue( m_fpText->IsKeepUpright() );
  261. return DIALOG_TEXT_PROPERTIES_BASE::TransferDataToWindow();
  262. }
  263. bool DIALOG_TEXT_PROPERTIES::TransferDataFromWindow()
  264. {
  265. if( !DIALOG_TEXT_PROPERTIES_BASE::TransferDataFromWindow() )
  266. return false;
  267. if( !m_textWidth.Validate( TEXTS_MIN_SIZE, TEXTS_MAX_SIZE )
  268. || !m_textHeight.Validate( TEXTS_MIN_SIZE, TEXTS_MAX_SIZE ) )
  269. {
  270. return false;
  271. }
  272. BOARD_COMMIT commit( m_Parent );
  273. commit.Modify( m_item );
  274. // If no other command in progress, prepare undo command
  275. // (for a command in progress, will be made later, at the completion of command)
  276. bool pushCommit = ( m_item->GetEditFlags() == 0 );
  277. // Set IN_EDIT flag to force undo/redo/abort proper operation and avoid new calls to
  278. // SaveCopyInUndoList for the same text if is moved, and then rotated, edited, etc....
  279. if( !pushCommit )
  280. m_item->SetFlags( IN_EDIT );
  281. // Set the new text content
  282. if( m_SingleLineText->IsShown() )
  283. {
  284. if( !m_SingleLineText->GetValue().IsEmpty() )
  285. m_edaText->SetText( m_SingleLineText->GetValue() );
  286. }
  287. else if( m_MultiLineText->IsShown() )
  288. {
  289. if( !m_MultiLineText->GetValue().IsEmpty() )
  290. {
  291. BOARD* board = m_Parent->GetBoard();
  292. wxString txt = board->ConvertCrossReferencesToKIIDs( m_MultiLineText->GetValue() );
  293. #ifdef __WINDOWS__
  294. // On Windows, a new line is coded as \r\n. We use only \n in kicad files and in
  295. // drawing routines so strip the \r char.
  296. txt.Replace( "\r", "" );
  297. #endif
  298. m_edaText->SetText( EscapeString( txt, CTX_QUOTED_STR ) );
  299. }
  300. }
  301. m_item->SetLocked( m_cbLocked->GetValue() );
  302. m_item->SetLayer( ToLAYER_ID( m_LayerSelectionCtrl->GetLayerSelection() ) );
  303. m_edaText->SetTextSize( wxSize( m_textWidth.GetValue(), m_textHeight.GetValue() ) );
  304. m_edaText->SetTextThickness( m_thickness.GetValue() );
  305. m_edaText->SetTextPos( wxPoint( m_posX.GetValue(), m_posY.GetValue() ) );
  306. if( m_fpText )
  307. m_fpText->SetLocalCoord();
  308. // Test for acceptable values for thickness and size and clamp if fails
  309. int maxPenWidth = Clamp_Text_PenSize( m_edaText->GetTextThickness(), m_edaText->GetTextSize() );
  310. if( m_edaText->GetTextThickness() > maxPenWidth )
  311. {
  312. DisplayError( this, _( "The text thickness is too large for the text size.\n"
  313. "It will be clamped." ) );
  314. m_edaText->SetTextThickness( maxPenWidth );
  315. }
  316. m_edaText->SetVisible( m_Visible->GetValue() );
  317. m_edaText->SetItalic( m_Italic->GetValue() );
  318. m_OrientValue = m_orientation.GetDoubleValue();
  319. m_edaText->SetTextAngle( m_OrientValue );
  320. m_edaText->SetMirrored( m_Mirrored->GetValue() );
  321. if( m_fpText )
  322. m_fpText->SetKeepUpright( m_KeepUpright->GetValue() );
  323. switch( m_JustifyChoice->GetSelection() )
  324. {
  325. case 0: m_edaText->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT ); break;
  326. case 1: m_edaText->SetHorizJustify( GR_TEXT_HJUSTIFY_CENTER ); break;
  327. case 2: m_edaText->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT ); break;
  328. default: break;
  329. }
  330. if( pushCommit )
  331. commit.Push( _( "Change text properties" ) );
  332. return true;
  333. }