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.

724 lines
23 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2015 Chris Pavlina <pavlina.chris@gmail.com>
  5. * Copyright (C) 2015 KiCad Developers, see change_log.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. /******************************************************************************
  25. * Field autoplacer: Tries to find an optimal place for component fields, and
  26. * places them there. There are two modes: "auto"-autoplace, and "manual" autoplace.
  27. * Auto mode is for when the process is run automatically, like when rotating parts,
  28. * and it avoids doing things that would be helpful for the final positioning but
  29. * annoying if they happened without permission.
  30. * Short description of the process:
  31. *
  32. * 1. Compute the dimensions of the fields' bounding box ::ComputeFBoxSize
  33. * 2. Determine which side the fields will go on. ::choose_side_for_fields
  34. * 1. Sort the four sides in preference order,
  35. * depending on the component's shape and
  36. * orientation ::get_preferred_sides
  37. * 2. If in manual mode, sift out the sides that would
  38. * cause fields to overlap other items ::get_colliding_sides
  39. * 3. If any remaining sides have zero pins there,
  40. * choose the highest zero-pin side according to
  41. * preference order.
  42. * 4. If all sides have pins, choose the side with the
  43. * fewest pins.
  44. * 3. Compute the position of the fields' bounding box ::field_box_placement
  45. * 4. In manual mode, shift the box vertically if possible
  46. * to fit fields between adjacent wires ::fit_fields_between_wires
  47. * 5. Move all fields to their final positions
  48. * 1. Re-justify fields if options allow that ::justify_field
  49. * 2. Round to a 50-mil grid coordinate if desired
  50. */
  51. #include <boost/range/adaptor/reversed.hpp>
  52. #include <sch_edit_frame.h>
  53. #include <hotkeys_basic.h>
  54. #include <sch_component.h>
  55. #include <sch_line.h>
  56. #include <lib_pin.h>
  57. #include <sch_draw_panel.h>
  58. #include <class_libentry.h>
  59. #include <eeschema_config.h>
  60. #include <kiface_i.h>
  61. #include <vector>
  62. #include <algorithm>
  63. #define FIELD_PADDING 10 // arbitrarily chosen for aesthetics
  64. #define FIELD_PADDING_ALIGNED 18 // aligns 50 mil text to a 100 mil grid
  65. #define WIRE_V_SPACING 100
  66. #define HPADDING 25
  67. #define VPADDING 25
  68. /**
  69. * Function round_n
  70. * Round up/down to the nearest multiple of n
  71. */
  72. template<typename T> T round_n( const T& value, const T& n, bool aRoundUp )
  73. {
  74. if( value % n )
  75. return n * (value / n + (aRoundUp ? 1 : 0));
  76. else
  77. return value;
  78. }
  79. /**
  80. * Function TO_HJUSTIFY
  81. * Converts an integer to a horizontal justification; neg=L zero=C pos=R
  82. */
  83. EDA_TEXT_HJUSTIFY_T TO_HJUSTIFY( int x )
  84. {
  85. return static_cast<EDA_TEXT_HJUSTIFY_T>( x );
  86. }
  87. class AUTOPLACER
  88. {
  89. SCH_SCREEN* m_screen;
  90. SCH_COMPONENT* m_component;
  91. std::vector<SCH_FIELD*> m_fields;
  92. std::vector<SCH_ITEM*> m_colliders;
  93. EDA_RECT m_comp_bbox;
  94. wxSize m_fbox_size;
  95. bool m_allow_rejustify, m_align_to_grid;
  96. bool m_power_symbol;
  97. public:
  98. typedef wxPoint SIDE;
  99. static const SIDE SIDE_TOP, SIDE_BOTTOM, SIDE_LEFT, SIDE_RIGHT;
  100. enum COLLISION { COLLIDE_NONE, COLLIDE_OBJECTS, COLLIDE_H_WIRES };
  101. struct SIDE_AND_NPINS
  102. {
  103. SIDE side;
  104. unsigned pins;
  105. };
  106. struct SIDE_AND_COLL
  107. {
  108. SIDE side;
  109. COLLISION collision;
  110. };
  111. AUTOPLACER( SCH_COMPONENT* aComponent, SCH_SCREEN* aScreen )
  112. :m_screen( aScreen ), m_component( aComponent )
  113. {
  114. m_component->GetFields( m_fields, /* aVisibleOnly */ true );
  115. Kiface().KifaceSettings()->Read( AutoplaceJustifyEntry, &m_allow_rejustify, true );
  116. Kiface().KifaceSettings()->Read( AutoplaceAlignEntry, &m_align_to_grid, false );
  117. m_comp_bbox = m_component->GetBodyBoundingBox();
  118. m_fbox_size = ComputeFBoxSize( /* aDynamic */ true );
  119. m_power_symbol = ! m_component->IsInNetlist();
  120. if( aScreen )
  121. get_possible_colliders( m_colliders );
  122. }
  123. /**
  124. * Do the actual autoplacement.
  125. * @param aManual - if true, use extra heuristics for smarter placement when manually
  126. * called up.
  127. */
  128. void DoAutoplace( bool aManual )
  129. {
  130. bool force_wire_spacing = false;
  131. SIDE field_side = choose_side_for_fields( aManual );
  132. wxPoint fbox_pos = field_box_placement( field_side );
  133. EDA_RECT field_box( fbox_pos, m_fbox_size );
  134. if( aManual )
  135. force_wire_spacing = fit_fields_between_wires( &field_box, field_side );
  136. // Move the fields
  137. int last_y_coord = field_box.GetTop();
  138. for( unsigned field_idx = 0; field_idx < m_fields.size(); ++field_idx )
  139. {
  140. SCH_FIELD* field = m_fields[field_idx];
  141. if( m_allow_rejustify )
  142. justify_field( field, field_side );
  143. wxPoint pos(
  144. field_horiz_placement( field, field_box ),
  145. field_vert_placement( field, field_box, &last_y_coord, !force_wire_spacing ) );
  146. if( m_align_to_grid )
  147. {
  148. pos.x = round_n( pos.x, 50, field_side.x >= 0 );
  149. pos.y = round_n( pos.y, 50, field_side.y == 1 );
  150. }
  151. field->SetPosition( pos );
  152. }
  153. }
  154. protected:
  155. /**
  156. * Compute and return the size of the fields' bounding box.
  157. * @param aDynamic - if true, use dynamic spacing
  158. */
  159. wxSize ComputeFBoxSize( bool aDynamic )
  160. {
  161. int max_field_width = 0;
  162. int total_height = 0;
  163. for( SCH_FIELD* field : m_fields )
  164. {
  165. int field_width;
  166. int field_height;
  167. if( m_component->GetTransform().y1 )
  168. {
  169. field->SetTextAngle( TEXT_ANGLE_VERT );
  170. }
  171. else
  172. {
  173. field->SetTextAngle( TEXT_ANGLE_HORIZ );
  174. }
  175. field_width = field->GetBoundingBox().GetWidth();
  176. field_height = field->GetBoundingBox().GetHeight();
  177. max_field_width = std::max( max_field_width, field_width );
  178. if( aDynamic )
  179. total_height += field_height + get_field_padding();
  180. else
  181. total_height += WIRE_V_SPACING;
  182. }
  183. return wxSize( max_field_width, total_height );
  184. }
  185. /**
  186. * Function get_pin_side
  187. * Return the side that a pin is on.
  188. */
  189. SIDE get_pin_side( LIB_PIN* aPin )
  190. {
  191. int pin_orient = aPin->PinDrawOrient( m_component->GetTransform() );
  192. switch( pin_orient )
  193. {
  194. case PIN_RIGHT: return SIDE_LEFT;
  195. case PIN_LEFT: return SIDE_RIGHT;
  196. case PIN_UP: return SIDE_BOTTOM;
  197. case PIN_DOWN: return SIDE_TOP;
  198. default:
  199. wxFAIL_MSG( "Invalid pin orientation" );
  200. return SIDE_LEFT;
  201. }
  202. }
  203. /**
  204. * Function pins_on_side
  205. * Count the number of pins on a side of the component.
  206. */
  207. unsigned pins_on_side( SIDE aSide )
  208. {
  209. unsigned pin_count = 0;
  210. std::vector<LIB_PIN*> pins;
  211. m_component->GetPins( pins );
  212. for( LIB_PIN* each_pin : pins )
  213. {
  214. if( !each_pin->IsVisible() && !m_power_symbol )
  215. continue;
  216. if( get_pin_side( each_pin ) == aSide )
  217. ++pin_count;
  218. }
  219. return pin_count;
  220. }
  221. /**
  222. * Function get_possible_colliders
  223. * Populate a list of all drawing items that *may* collide with the fields. That is,
  224. * all drawing items, including other fields, that are not the current component or
  225. * its own fields.
  226. */
  227. void get_possible_colliders( std::vector<SCH_ITEM*>& aItems )
  228. {
  229. wxASSERT_MSG( m_screen, "get_possible_colliders() with null m_screen" );
  230. for( SCH_ITEM* item = m_screen->GetDrawItems(); item; item = item->Next() )
  231. {
  232. if( SCH_COMPONENT* comp = dynamic_cast<SCH_COMPONENT*>( item ) )
  233. {
  234. if( comp == m_component ) continue;
  235. std::vector<SCH_FIELD*> fields;
  236. comp->GetFields( fields, /* aVisibleOnly */ true );
  237. for( SCH_FIELD* field : fields )
  238. aItems.push_back( field );
  239. }
  240. aItems.push_back( item );
  241. }
  242. }
  243. /**
  244. * Function filtered_colliders
  245. * Filter a list of possible colliders to include only those that actually collide
  246. * with a given rectangle. Returns the new vector.
  247. */
  248. std::vector<SCH_ITEM*> filtered_colliders( const EDA_RECT& aRect )
  249. {
  250. std::vector<SCH_ITEM*> filtered;
  251. for( SCH_ITEM* item : m_colliders )
  252. {
  253. EDA_RECT item_box;
  254. if( SCH_COMPONENT* item_comp = dynamic_cast<SCH_COMPONENT*>( item ) )
  255. item_box = item_comp->GetBodyBoundingBox();
  256. else
  257. item_box = item->GetBoundingBox();
  258. if( item_box.Intersects( aRect ) )
  259. filtered.push_back( item );
  260. }
  261. return filtered;
  262. }
  263. /**
  264. * Function get_preferred_sides
  265. * Return a list with the preferred field sides for the component, in
  266. * decreasing order of preference.
  267. */
  268. std::vector<SIDE_AND_NPINS> get_preferred_sides()
  269. {
  270. SIDE_AND_NPINS sides_init[] = {
  271. { SIDE_RIGHT, pins_on_side( SIDE_RIGHT ) },
  272. { SIDE_TOP, pins_on_side( SIDE_TOP ) },
  273. { SIDE_LEFT, pins_on_side( SIDE_LEFT ) },
  274. { SIDE_BOTTOM, pins_on_side( SIDE_BOTTOM ) },
  275. };
  276. std::vector<SIDE_AND_NPINS> sides( sides_init, sides_init + arrayDim( sides_init ) );
  277. int orient = m_component->GetOrientation();
  278. int orient_angle = orient & 0xff; // enum is a bitmask
  279. bool h_mirrored = ( ( orient & CMP_MIRROR_X )
  280. && ( orient_angle == CMP_ORIENT_0 || orient_angle == CMP_ORIENT_180 ) );
  281. double w = double( m_comp_bbox.GetWidth() );
  282. double h = double( m_comp_bbox.GetHeight() );
  283. // The preferred-sides heuristics are a bit magical. These were determined mostly
  284. // by trial and error.
  285. if( m_power_symbol )
  286. {
  287. // For power symbols, we generally want the label at the top first.
  288. switch( orient_angle )
  289. {
  290. case CMP_ORIENT_0:
  291. std::swap( sides[0], sides[1] );
  292. std::swap( sides[1], sides[3] );
  293. // TOP, BOTTOM, RIGHT, LEFT
  294. break;
  295. case CMP_ORIENT_90:
  296. std::swap( sides[0], sides[2] );
  297. std::swap( sides[1], sides[2] );
  298. // LEFT, RIGHT, TOP, BOTTOM
  299. break;
  300. case CMP_ORIENT_180:
  301. std::swap( sides[0], sides[3] );
  302. // BOTTOM, TOP, LEFT, RIGHT
  303. break;
  304. case CMP_ORIENT_270:
  305. std::swap( sides[1], sides[2] );
  306. // RIGHT, LEFT, TOP, BOTTOM
  307. break;
  308. }
  309. }
  310. else
  311. {
  312. // If the component is horizontally mirrored, swap left and right
  313. if( h_mirrored )
  314. {
  315. std::swap( sides[0], sides[2] );
  316. }
  317. // If the component is very long or is a power symbol, swap H and V
  318. if( w/h > 3.0 )
  319. {
  320. std::swap( sides[0], sides[1] );
  321. std::swap( sides[1], sides[3] );
  322. }
  323. }
  324. return sides;
  325. }
  326. /**
  327. * Function get_colliding_sides
  328. * Return a list of the sides where a field set would collide with another item.
  329. */
  330. std::vector<SIDE_AND_COLL> get_colliding_sides()
  331. {
  332. SIDE sides_init[] = { SIDE_RIGHT, SIDE_TOP, SIDE_LEFT, SIDE_BOTTOM };
  333. std::vector<SIDE> sides( sides_init, sides_init + arrayDim( sides_init ) );
  334. std::vector<SIDE_AND_COLL> colliding;
  335. // Iterate over all sides and find the ones that collide
  336. for( SIDE side : sides )
  337. {
  338. EDA_RECT box( field_box_placement( side ), m_fbox_size );
  339. COLLISION collision = COLLIDE_NONE;
  340. for( SCH_ITEM* collider : filtered_colliders( box ) )
  341. {
  342. SCH_LINE* line = dynamic_cast<SCH_LINE*>( collider );
  343. if( line && !side.x )
  344. {
  345. wxPoint start = line->GetStartPoint(), end = line->GetEndPoint();
  346. if( start.y == end.y && collision != COLLIDE_OBJECTS )
  347. collision = COLLIDE_H_WIRES;
  348. else
  349. collision = COLLIDE_OBJECTS;
  350. }
  351. else
  352. collision = COLLIDE_OBJECTS;
  353. }
  354. if( collision != COLLIDE_NONE )
  355. colliding.push_back( { side, collision } );
  356. }
  357. return colliding;
  358. }
  359. /**
  360. * Function choose_side_filtered
  361. * Choose a side for the fields, filtered on only one side collision type.
  362. * Removes the sides matching the filter from the list.
  363. */
  364. SIDE_AND_NPINS choose_side_filtered( std::vector<SIDE_AND_NPINS>& aSides,
  365. const std::vector<SIDE_AND_COLL>& aCollidingSides, COLLISION aCollision,
  366. SIDE_AND_NPINS aLastSelection)
  367. {
  368. SIDE_AND_NPINS sel = aLastSelection;
  369. std::vector<SIDE_AND_NPINS>::iterator it = aSides.begin();
  370. while( it != aSides.end() )
  371. {
  372. bool collide = false;
  373. for( SIDE_AND_COLL collision : aCollidingSides )
  374. {
  375. if( collision.side == it->side && collision.collision == aCollision )
  376. collide = true;
  377. }
  378. if( !collide )
  379. ++it;
  380. else
  381. {
  382. if( it->pins <= sel.pins )
  383. {
  384. sel.pins = it->pins;
  385. sel.side = it->side;
  386. }
  387. it = aSides.erase( it );
  388. }
  389. }
  390. return sel;
  391. }
  392. /**
  393. * Function choose_side_for_fields
  394. * Look where a component's pins are to pick a side to put the fields on
  395. * @param aAvoidCollisions - if true, pick last the sides where the label will collide
  396. * with other items.
  397. */
  398. SIDE choose_side_for_fields( bool aAvoidCollisions )
  399. {
  400. std::vector<SIDE_AND_NPINS> sides = get_preferred_sides();
  401. std::reverse( sides.begin(), sides.end() );
  402. SIDE_AND_NPINS side = { wxPoint( 1, 0 ), UINT_MAX };
  403. if( aAvoidCollisions )
  404. {
  405. std::vector<SIDE_AND_COLL> colliding_sides = get_colliding_sides();
  406. side = choose_side_filtered( sides, colliding_sides, COLLIDE_OBJECTS, side );
  407. side = choose_side_filtered( sides, colliding_sides, COLLIDE_H_WIRES, side );
  408. }
  409. for( SIDE_AND_NPINS& each_side : sides | boost::adaptors::reversed )
  410. {
  411. if( !each_side.pins ) return each_side.side;
  412. }
  413. for( SIDE_AND_NPINS& each_side : sides )
  414. {
  415. if( each_side.pins <= side.pins )
  416. {
  417. side.pins = each_side.pins;
  418. side.side = each_side.side;
  419. }
  420. }
  421. return side.side;
  422. }
  423. /**
  424. * Function justify_field
  425. * Set the justification of a field based on the side it's supposed to be on, taking
  426. * into account whether the field will be displayed with flipped justification due to
  427. * mirroring.
  428. */
  429. void justify_field( SCH_FIELD* aField, SIDE aFieldSide )
  430. {
  431. // Justification is set twice to allow IsHorizJustifyFlipped() to work correctly.
  432. aField->SetHorizJustify( TO_HJUSTIFY( -aFieldSide.x ) );
  433. aField->SetHorizJustify( TO_HJUSTIFY( -aFieldSide.x *
  434. ( aField->IsHorizJustifyFlipped() ? -1 : 1 ) ) );
  435. aField->SetVertJustify( GR_TEXT_VJUSTIFY_CENTER );
  436. }
  437. /**
  438. * Function field_box_placement
  439. * Returns the position of the field bounding box.
  440. */
  441. wxPoint field_box_placement( SIDE aFieldSide )
  442. {
  443. wxPoint fbox_center = m_comp_bbox.Centre();
  444. int offs_x = ( m_comp_bbox.GetWidth() + m_fbox_size.GetWidth() ) / 2 + HPADDING;
  445. int offs_y = ( m_comp_bbox.GetHeight() + m_fbox_size.GetHeight() ) / 2 + VPADDING;
  446. fbox_center.x += aFieldSide.x * offs_x;
  447. fbox_center.y += aFieldSide.y * offs_y;
  448. wxPoint fbox_pos(
  449. fbox_center.x - m_fbox_size.GetWidth() / 2,
  450. fbox_center.y - m_fbox_size.GetHeight() / 2 );
  451. return fbox_pos;
  452. }
  453. /**
  454. * Function fit_fields_between_wires
  455. * Shift a field box up or down a bit to make the fields fit between some wires.
  456. * Returns true if a shift was made.
  457. */
  458. bool fit_fields_between_wires( EDA_RECT* aBox, SIDE aSide )
  459. {
  460. if( aSide != SIDE_TOP && aSide != SIDE_BOTTOM )
  461. return false;
  462. std::vector<SCH_ITEM*> colliders = filtered_colliders( *aBox );
  463. if( colliders.empty() )
  464. return false;
  465. // Find the offset of the wires for proper positioning
  466. int offset = 0;
  467. for( SCH_ITEM* item : colliders )
  468. {
  469. SCH_LINE* line = dynamic_cast<SCH_LINE*>( item );
  470. if( !line )
  471. return false;
  472. wxPoint start = line->GetStartPoint(), end = line->GetEndPoint();
  473. if( start.y != end.y )
  474. return false;
  475. int this_offset = (3 * WIRE_V_SPACING / 2) - ( start.y % WIRE_V_SPACING );
  476. if( offset == 0 )
  477. offset = this_offset;
  478. else if( offset != this_offset )
  479. return false;
  480. }
  481. // At this point we are recomputing the field box size. Do not
  482. // return false after this point.
  483. m_fbox_size = ComputeFBoxSize( /* aDynamic */ false );
  484. wxPoint pos = aBox->GetPosition();
  485. // Remove the existing padding to get a bit more space to work with
  486. if( aSide == SIDE_BOTTOM )
  487. {
  488. pos.y = m_comp_bbox.GetBottom() - get_field_padding();
  489. }
  490. else
  491. {
  492. pos.y = m_comp_bbox.GetTop() - m_fbox_size.y + get_field_padding();
  493. }
  494. pos.y = round_n( pos.y, WIRE_V_SPACING, aSide == SIDE_BOTTOM );
  495. aBox->SetOrigin( pos );
  496. return true;
  497. }
  498. /**
  499. * Function field_horiz_placement
  500. * Place a field horizontally, taking into account the field width and
  501. * justification.
  502. *
  503. * @param aField - the field to place.
  504. * @param aFieldBox - box in which fields will be placed
  505. *
  506. * @return Correct field horizontal position
  507. */
  508. int field_horiz_placement( SCH_FIELD *aField, const EDA_RECT &aFieldBox )
  509. {
  510. int field_hjust;
  511. int field_xcoord;
  512. if( aField->IsHorizJustifyFlipped() )
  513. field_hjust = -aField->GetHorizJustify();
  514. else
  515. field_hjust = aField->GetHorizJustify();
  516. switch( field_hjust )
  517. {
  518. case GR_TEXT_HJUSTIFY_LEFT:
  519. field_xcoord = aFieldBox.GetLeft();
  520. break;
  521. case GR_TEXT_HJUSTIFY_CENTER:
  522. field_xcoord = aFieldBox.Centre().x;
  523. break;
  524. case GR_TEXT_HJUSTIFY_RIGHT:
  525. field_xcoord = aFieldBox.GetRight();
  526. break;
  527. default:
  528. wxFAIL_MSG( "Unexpected value for SCH_FIELD::GetHorizJustify()" );
  529. field_xcoord = aFieldBox.Centre().x; // Most are centered
  530. }
  531. return field_xcoord;
  532. }
  533. /**
  534. * Function field_vert_placement
  535. * Place a field vertically. Because field vertical placements accumulate,
  536. * this takes a pointer to a vertical position accumulator.
  537. *
  538. * @param aField - the field to place.
  539. * @param aFieldBox - box in which fields will be placed.
  540. * @param aPosAccum - pointer to a position accumulator
  541. * @param aDynamic - use dynamic spacing
  542. *
  543. * @return Correct field vertical position
  544. */
  545. int field_vert_placement( SCH_FIELD *aField, const EDA_RECT &aFieldBox, int *aPosAccum,
  546. bool aDynamic )
  547. {
  548. int field_height;
  549. int padding;
  550. if( aDynamic )
  551. {
  552. field_height = aField->GetBoundingBox().GetHeight();
  553. padding = get_field_padding();
  554. }
  555. else
  556. {
  557. field_height = WIRE_V_SPACING / 2;
  558. padding = WIRE_V_SPACING / 2;
  559. }
  560. int placement = *aPosAccum + padding / 2 + field_height / 2;
  561. *aPosAccum += padding + field_height;
  562. return placement;
  563. }
  564. /**
  565. * Function get_field_padding
  566. * Return the desired padding between fields.
  567. */
  568. int get_field_padding()
  569. {
  570. if( m_align_to_grid )
  571. return FIELD_PADDING_ALIGNED;
  572. else
  573. return FIELD_PADDING;
  574. }
  575. };
  576. const AUTOPLACER::SIDE AUTOPLACER::SIDE_TOP( 0, -1 );
  577. const AUTOPLACER::SIDE AUTOPLACER::SIDE_BOTTOM( 0, 1 );
  578. const AUTOPLACER::SIDE AUTOPLACER::SIDE_LEFT( -1, 0 );
  579. const AUTOPLACER::SIDE AUTOPLACER::SIDE_RIGHT( 1, 0 );
  580. void SCH_EDIT_FRAME::OnAutoplaceFields( wxCommandEvent& aEvent )
  581. {
  582. SCH_SCREEN* screen = GetScreen();
  583. SCH_ITEM* item = screen->GetCurItem();
  584. // Get the item under cursor if we're not currently moving something
  585. if( !item )
  586. {
  587. if( aEvent.GetInt() == 0 )
  588. return;
  589. EDA_HOTKEY_CLIENT_DATA& data = dynamic_cast<EDA_HOTKEY_CLIENT_DATA&>(
  590. *aEvent.GetClientObject() );
  591. item = LocateItem( data.GetPosition(), SCH_COLLECTOR::MovableItems, aEvent.GetInt() );
  592. screen->SetCurItem( NULL );
  593. if( !item || item->GetFlags() )
  594. return;
  595. }
  596. SCH_COMPONENT* component = dynamic_cast<SCH_COMPONENT*>( item );
  597. if( !component )
  598. return;
  599. if( !component->IsNew() )
  600. SaveCopyInUndoList( component, UR_CHANGED );
  601. component->AutoplaceFields( screen, /* aManual */ true );
  602. RefreshItem( component );
  603. OnModify();
  604. }
  605. void SCH_COMPONENT::AutoplaceFields( SCH_SCREEN* aScreen, bool aManual )
  606. {
  607. if( aManual )
  608. wxASSERT_MSG( aScreen, "A SCH_SCREEN pointer must be given for manual autoplacement" );
  609. AUTOPLACER autoplacer( this, aScreen );
  610. autoplacer.DoAutoplace( aManual );
  611. m_fieldsAutoplaced = ( aManual? AUTOPLACED_MANUAL : AUTOPLACED_AUTO );
  612. }