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.

307 lines
9.2 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2014 John Beard, john.j.beard@gmail.com
  5. * Copyright (C) 2018-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 <dialogs/dialog_move_exact.h>
  25. #include <math/util.h> // for KiROUND
  26. #include <widgets/tab_traversal.h>
  27. #include <pcb_edit_frame.h>
  28. #include <trigo.h>
  29. // initialise statics
  30. DIALOG_MOVE_EXACT::MOVE_EXACT_OPTIONS DIALOG_MOVE_EXACT::m_options;
  31. DIALOG_MOVE_EXACT::DIALOG_MOVE_EXACT( PCB_BASE_FRAME *aParent, wxPoint& aTranslate,
  32. double& aRotate, ROTATION_ANCHOR& aAnchor,
  33. const EDA_RECT& aBbox ) :
  34. DIALOG_MOVE_EXACT_BASE( aParent ),
  35. m_translation( aTranslate ),
  36. m_rotation( aRotate ),
  37. m_rotationAnchor( aAnchor ),
  38. m_bbox( aBbox ),
  39. m_moveX( aParent, m_xLabel, m_xEntry, m_xUnit ),
  40. m_moveY( aParent, m_yLabel, m_yEntry, m_yUnit ),
  41. m_rotate( aParent, m_rotLabel, m_rotEntry, m_rotUnit ),
  42. m_stateX( 0.0 ),
  43. m_stateY( 0.0 ),
  44. m_stateRadius( 0.0 ),
  45. m_stateTheta( 0.0 )
  46. {
  47. // We can't set the tab order through wxWidgets due to shortcomings in their mnemonics
  48. // implementation on MSW
  49. m_tabOrder = {
  50. m_xEntry,
  51. m_yEntry,
  52. m_rotEntry,
  53. m_anchorOptions,
  54. m_stdButtonsOK,
  55. m_stdButtonsCancel
  56. };
  57. // Configure display origin transforms
  58. m_moveX.SetCoordType( ORIGIN_TRANSFORMS::REL_X_COORD );
  59. m_moveY.SetCoordType( ORIGIN_TRANSFORMS::REL_Y_COORD );
  60. updateDialogControls( m_options.polarCoords );
  61. m_menuIDs.push_back( aAnchor );
  62. m_menuIDs.push_back( ROTATE_AROUND_USER_ORIGIN );
  63. if( aParent->IsType( FRAME_PCB_EDITOR ) )
  64. m_menuIDs.push_back( ROTATE_AROUND_AUX_ORIGIN );
  65. buildRotationAnchorMenu();
  66. // and set up the entries according to the saved options
  67. m_polarCoords->SetValue( m_options.polarCoords );
  68. m_moveX.SetValue( m_options.entry1 );
  69. m_moveY.SetValue( m_options.entry2 );
  70. m_rotate.SetUnits( EDA_UNITS::DEGREES );
  71. m_rotate.SetValue( m_options.entryRotation );
  72. m_anchorOptions->SetSelection( std::min( m_options.entryAnchorSelection, m_menuIDs.size() ) );
  73. m_stdButtonsOK->SetDefault();
  74. finishDialogSettings();
  75. }
  76. void DIALOG_MOVE_EXACT::buildRotationAnchorMenu()
  77. {
  78. wxArrayString menuItems;
  79. for( auto anchorID : m_menuIDs )
  80. {
  81. switch( anchorID )
  82. {
  83. case ROTATE_AROUND_ITEM_ANCHOR:
  84. menuItems.push_back( _( "Rotate around item anchor" ) );
  85. break;
  86. case ROTATE_AROUND_SEL_CENTER:
  87. menuItems.push_back( _( "Rotate around selection center" ) );
  88. break;
  89. case ROTATE_AROUND_USER_ORIGIN:
  90. menuItems.push_back( _( "Rotate around local coordinates origin" ) );
  91. break;
  92. case ROTATE_AROUND_AUX_ORIGIN:
  93. menuItems.push_back( _( "Rotate around drill/place origin" ) );
  94. break;
  95. }
  96. }
  97. m_anchorOptions->Set( menuItems );
  98. }
  99. void DIALOG_MOVE_EXACT::ToPolarDeg( double x, double y, double& r, double& q )
  100. {
  101. // convert to polar coordinates
  102. r = hypot( x, y );
  103. q = ( r != 0) ? RAD2DEG( atan2( y, x ) ) : 0;
  104. }
  105. bool DIALOG_MOVE_EXACT::GetTranslationInIU( wxRealPoint& val, bool polar )
  106. {
  107. if( polar )
  108. {
  109. const double r = m_moveX.GetDoubleValue();
  110. const double q = m_moveY.GetDoubleValue();
  111. val.x = r * cos( DEG2RAD( q / 10.0 ) );
  112. val.y = r * sin( DEG2RAD( q / 10.0 ) );
  113. }
  114. else
  115. {
  116. // direct read
  117. val.x = m_moveX.GetDoubleValue();
  118. val.y = m_moveY.GetDoubleValue();
  119. }
  120. // no validation to do here, but in future, you could return false here
  121. return true;
  122. }
  123. void DIALOG_MOVE_EXACT::OnPolarChanged( wxCommandEvent& event )
  124. {
  125. bool newPolar = m_polarCoords->IsChecked();
  126. double moveX = m_moveX.GetDoubleValue();
  127. double moveY = m_moveY.GetDoubleValue();
  128. updateDialogControls( newPolar );
  129. if( newPolar )
  130. {
  131. if( moveX != m_stateX || moveY != m_stateY )
  132. {
  133. m_stateX = moveX;
  134. m_stateY = moveY;
  135. ToPolarDeg( m_stateX, m_stateY, m_stateRadius, m_stateTheta );
  136. m_stateTheta *= 10.0;
  137. m_moveX.SetDoubleValue( m_stateRadius );
  138. m_stateRadius = m_moveX.GetDoubleValue();
  139. m_moveY.SetDoubleValue( m_stateTheta );
  140. m_stateTheta = m_moveY.GetDoubleValue();
  141. }
  142. else
  143. {
  144. m_moveX.SetDoubleValue( m_stateRadius );
  145. m_moveY.SetDoubleValue( m_stateTheta );
  146. }
  147. }
  148. else
  149. {
  150. if( moveX != m_stateRadius || moveY != m_stateTheta )
  151. {
  152. m_stateRadius = moveX;
  153. m_stateTheta = moveY;
  154. m_stateX = m_stateRadius * cos( DEG2RAD( m_stateTheta / 10.0 ) );
  155. m_stateY = m_stateRadius * sin( DEG2RAD( m_stateTheta / 10.0 ) );
  156. m_moveX.SetDoubleValue( m_stateX );
  157. m_stateX = m_moveX.GetDoubleValue();
  158. m_moveY.SetDoubleValue( m_stateY );
  159. m_stateY = m_moveY.GetDoubleValue();
  160. }
  161. else
  162. {
  163. m_moveX.SetDoubleValue( m_stateX );
  164. m_moveY.SetDoubleValue( m_stateY );
  165. }
  166. }
  167. }
  168. void DIALOG_MOVE_EXACT::updateDialogControls( bool aPolar )
  169. {
  170. if( aPolar )
  171. {
  172. m_moveX.SetLabel( _( "Distance:" ) ); // Polar radius
  173. m_moveY.SetLabel( _( "Angle:" ) ); // Polar theta or angle
  174. m_moveY.SetUnits( EDA_UNITS::DEGREES );
  175. }
  176. else
  177. {
  178. m_moveX.SetLabel( _( "Move X:" ) );
  179. m_moveY.SetLabel( _( "Move Y:" ) );
  180. m_moveY.SetUnits( GetUserUnits() );
  181. }
  182. Layout();
  183. }
  184. void DIALOG_MOVE_EXACT::OnClear( wxCommandEvent& event )
  185. {
  186. wxObject* obj = event.GetEventObject();
  187. if( obj == m_clearX )
  188. {
  189. m_moveX.SetValue( 0 );
  190. }
  191. else if( obj == m_clearY )
  192. {
  193. m_moveY.SetValue( 0 );
  194. }
  195. else if( obj == m_clearRot )
  196. {
  197. m_rotate.SetValue( 0 );
  198. }
  199. // Keep m_stdButtonsOK focused to allow enter key actiavte the OK button
  200. m_stdButtonsOK->SetFocus();
  201. }
  202. bool DIALOG_MOVE_EXACT::TransferDataFromWindow()
  203. {
  204. // for the output, we only deliver a Cartesian vector
  205. wxRealPoint translation;
  206. bool ok = GetTranslationInIU( translation, m_polarCoords->IsChecked() );
  207. m_translation.x = KiROUND(translation.x);
  208. m_translation.y = KiROUND(translation.y);
  209. m_rotation = m_rotate.GetDoubleValue();
  210. m_rotationAnchor = m_menuIDs[ m_anchorOptions->GetSelection() ];
  211. if( ok )
  212. {
  213. // save the settings
  214. m_options.polarCoords = m_polarCoords->GetValue();
  215. m_options.entry1 = m_moveX.GetOriginalText();
  216. m_options.entry2 = m_moveY.GetOriginalText();
  217. m_options.entryRotation = m_rotate.GetOriginalText();
  218. m_options.entryAnchorSelection = (size_t) std::max( m_anchorOptions->GetSelection(), 0 );
  219. return true;
  220. }
  221. return false;
  222. }
  223. void DIALOG_MOVE_EXACT::OnTextFocusLost( wxFocusEvent& event )
  224. {
  225. wxTextCtrl* obj = static_cast<wxTextCtrl*>( event.GetEventObject() );
  226. if( obj->GetValue().IsEmpty() )
  227. obj->SetValue( "0" );
  228. event.Skip();
  229. }
  230. void DIALOG_MOVE_EXACT::OnTextChanged( wxCommandEvent& event )
  231. {
  232. double delta_x = m_moveX.GetDoubleValue();
  233. double delta_y = m_moveY.GetDoubleValue();
  234. double max_border = std::numeric_limits<int>::max() * 0.7071;
  235. if( m_bbox.GetLeft() + delta_x < -max_border ||
  236. m_bbox.GetRight() + delta_x > max_border ||
  237. m_bbox.GetTop() + delta_y < -max_border ||
  238. m_bbox.GetBottom() + delta_y > max_border )
  239. {
  240. const wxString invalid_length = _( "Invalid movement values. Movement would place selection "
  241. "outside of the maximum board area." );
  242. m_xEntry->SetToolTip( invalid_length );
  243. m_xEntry->SetForegroundColour( *wxRED );
  244. m_yEntry->SetToolTip( invalid_length );
  245. m_yEntry->SetForegroundColour( *wxRED );
  246. m_stdButtons->GetAffirmativeButton()->Disable();
  247. }
  248. else
  249. {
  250. m_xEntry->SetToolTip( "" );
  251. m_xEntry->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT ) );
  252. m_yEntry->SetToolTip( "" );
  253. m_yEntry->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT ) );
  254. m_stdButtons->GetAffirmativeButton()->Enable();
  255. event.Skip();
  256. }
  257. }