1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2016 Mario Luzeiro <mrluzeiro@ua.pt>
5  * Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr
6  * Copyright (C) 2015 Dick Hollenbeck, dick@softplc.com
7  * Copyright (C) 2004-2020 KiCad Developers, see AUTHORS.txt for contributors.
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License
11  * as published by the Free Software Foundation; either version 2
12  * of the License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, you may find one here:
21  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
22  * or you may search the http://www.gnu.org website for the version 2 license,
23  * or you may write to the Free Software Foundation, Inc.,
24  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
25  */
26 
27 #include <3d_viewer/eda_3d_viewer_frame.h>
28 #include <bitmaps.h>
29 #include <board_commit.h>
30 #include <board_design_settings.h>
31 #include <footprint.h>
32 #include <confirm.h>
33 #include <dialogs/dialog_text_entry.h>
34 #include <filename_resolver.h>
35 #include <pcb_edit_frame.h>
36 #include <pcbnew_settings.h>
37 #include <pgm_base.h>
38 #include <validators.h>
39 #include <widgets/grid_text_button_helpers.h>
40 #include <widgets/text_ctrl_eval.h>
41 #include <widgets/wx_grid.h>
42 #include <settings/settings_manager.h>
43 
44 #include <panel_fp_properties_3d_model.h>
45 
46 #include "dialogs/3d_cache_dialogs.h"
47 #include "dialogs/panel_preview_3d_model.h"
48 
49 #include <dialog_footprint_properties.h>
50 
51 
52 int DIALOG_FOOTPRINT_PROPERTIES::m_page = 0;     // remember the last open page during session
53 
54 
DIALOG_FOOTPRINT_PROPERTIES(PCB_EDIT_FRAME * aParent,FOOTPRINT * aFootprint)55 DIALOG_FOOTPRINT_PROPERTIES::DIALOG_FOOTPRINT_PROPERTIES( PCB_EDIT_FRAME* aParent,
56                                                           FOOTPRINT* aFootprint ) :
57         DIALOG_FOOTPRINT_PROPERTIES_BASE( aParent ),
58         m_frame( aParent ),
59         m_footprint( aFootprint ),
60         m_posX( aParent, m_XPosLabel, m_ModPositionX, m_XPosUnit ),
61         m_posY( aParent, m_YPosLabel, m_ModPositionY, m_YPosUnit ),
62         m_orientValidator( 3, &m_orientValue ),
63         m_netClearance( aParent, m_NetClearanceLabel, m_NetClearanceCtrl, m_NetClearanceUnits ),
64         m_solderMask( aParent, m_SolderMaskMarginLabel, m_SolderMaskMarginCtrl,
65                       m_SolderMaskMarginUnits ),
66         m_solderPaste( aParent, m_SolderPasteMarginLabel, m_SolderPasteMarginCtrl,
67                        m_SolderPasteMarginUnits ),
68         m_solderPasteRatio( aParent, m_PasteMarginRatioLabel, m_PasteMarginRatioCtrl,
69                             m_PasteMarginRatioUnits ),
70         m_returnValue( FP_PROPS_CANCEL ),
71         m_initialized( false )
72 {
73     // Create the 3D models page
74     m_3dPanel = new PANEL_FP_PROPERTIES_3D_MODEL( m_frame, m_footprint, this, m_NoteBook );
75     m_NoteBook->AddPage( m_3dPanel, _("3D Models"), false );
76 
77     // Configure display origin transforms
78     m_posX.SetCoordType( ORIGIN_TRANSFORMS::ABS_X_COORD );
79     m_posY.SetCoordType( ORIGIN_TRANSFORMS::ABS_Y_COORD );
80 
81     for( size_t i = 0; i < m_NoteBook->GetPageCount(); ++i )
82    	    m_macHack.push_back( true );
83 
84     m_texts = new FP_TEXT_GRID_TABLE( m_units, m_frame );
85 
86     m_delayedErrorMessage = wxEmptyString;
87     m_delayedFocusGrid = nullptr;
88     m_delayedFocusRow = -1;
89     m_delayedFocusColumn = -1;
90     m_initialFocus = false;
91 
92     // Give an icon
93     wxIcon  icon;
94     icon.CopyFromBitmap( KiBitmap( BITMAPS::icon_modedit ) );
95     SetIcon( icon );
96 
97     // Give a bit more room for combobox editors
98     m_itemsGrid->SetDefaultRowSize( m_itemsGrid->GetDefaultRowSize() + 4 );
99 
100     m_itemsGrid->SetTable( m_texts );
101     m_itemsGrid->PushEventHandler( new GRID_TRICKS( m_itemsGrid ) );
102 
103     PCBNEW_SETTINGS* cfg = m_frame->GetPcbNewSettings();
104 
105     // Show/hide text item columns according to the user's preference
106     m_itemsGrid->ShowHideColumns( cfg->m_FootprintTextShownColumns );
107 
108     m_orientValidator.SetRange( -360.0, 360.0 );
109     m_OrientValueCtrl->SetValidator( m_orientValidator );
110     m_orientValidator.SetWindow( m_OrientValueCtrl );
111 
112     // Set font size for items showing long strings:
113     wxFont infoFont = KIUI::GetInfoFont( this );
114 #if __WXMAC__
115     m_allow90Label->SetFont( infoFont );
116     m_allow180Label->SetFont( infoFont );
117 #endif
118     m_libraryIDLabel->SetFont( infoFont );
119     m_tcLibraryID->SetFont( infoFont );
120 
121     infoFont.SetStyle( wxFONTSTYLE_ITALIC );
122     m_staticTextInfoValNeg->SetFont( infoFont );
123     m_staticTextInfoValPos->SetFont( infoFont );
124     m_staticTextInfoCopper->SetFont( infoFont );
125     m_staticTextInfoPaste->SetFont( infoFont );
126 
127     m_NoteBook->SetSelection( m_page );
128 
129     if( m_page == 0 )
130     {
131         m_delayedFocusGrid = m_itemsGrid;
132         m_delayedFocusRow = 0;
133         m_delayedFocusColumn = 0;
134     }
135     else if( m_page == 1 )
136     {
137         SetInitialFocus( m_NetClearanceCtrl );
138     }
139 
140     m_solderPaste.SetNegativeZero();
141 
142     m_solderPasteRatio.SetUnits( EDA_UNITS::PERCENT );
143     m_solderPasteRatio.SetNegativeZero();
144 
145     m_sdbSizerStdButtonsOK->SetDefault();
146 
147     m_orientValue = 0;
148 
149     // Configure button logos
150     m_bpAdd->SetBitmap( KiBitmap( BITMAPS::small_plus ) );
151     m_bpDelete->SetBitmap( KiBitmap( BITMAPS::small_trash ) );
152 
153     finishDialogSettings();
154     m_initialized = true;
155 }
156 
157 
~DIALOG_FOOTPRINT_PROPERTIES()158 DIALOG_FOOTPRINT_PROPERTIES::~DIALOG_FOOTPRINT_PROPERTIES()
159 {
160     m_frame->GetPcbNewSettings()->m_FootprintTextShownColumns =
161             m_itemsGrid->GetShownColumns().ToStdString();
162 
163     // Prevents crash bug in wxGrid's d'tor
164     m_itemsGrid->DestroyTable( m_texts );
165 
166     // Delete the GRID_TRICKS.
167     m_itemsGrid->PopEventHandler( true );
168 
169     // free the memory used by all models, otherwise models which were
170     // browsed but not used would consume memory
171     Prj().Get3DCacheManager()->FlushCache( false );
172 
173     // the GL canvas has to be visible before it is destroyed
174     m_page = m_NoteBook->GetSelection();
175     m_NoteBook->SetSelection( 1 );
176 }
177 
178 
EditFootprint(wxCommandEvent &)179 void DIALOG_FOOTPRINT_PROPERTIES::EditFootprint( wxCommandEvent&  )
180 {
181     if( TransferDataFromWindow() )
182     {
183         m_returnValue = FP_PROPS_EDIT_BOARD_FP;
184         Close();
185     }
186 }
187 
188 
EditLibraryFootprint(wxCommandEvent &)189 void DIALOG_FOOTPRINT_PROPERTIES::EditLibraryFootprint( wxCommandEvent&  )
190 {
191     if( TransferDataFromWindow() )
192     {
193         m_returnValue = FP_PROPS_EDIT_LIBRARY_FP;
194         Close();
195     }
196 }
197 
198 
UpdateFootprint(wxCommandEvent &)199 void DIALOG_FOOTPRINT_PROPERTIES::UpdateFootprint( wxCommandEvent&  )
200 {
201     if( TransferDataFromWindow() )
202     {
203         m_returnValue = FP_PROPS_UPDATE_FP;
204         Close();
205     }
206 }
207 
208 
ChangeFootprint(wxCommandEvent &)209 void DIALOG_FOOTPRINT_PROPERTIES::ChangeFootprint( wxCommandEvent&  )
210 {
211     if( TransferDataFromWindow() )
212     {
213         m_returnValue = FP_PROPS_CHANGE_FP;
214         Close();
215     }
216 }
217 
218 
FootprintOrientEvent(wxCommandEvent &)219 void DIALOG_FOOTPRINT_PROPERTIES::FootprintOrientEvent( wxCommandEvent&  )
220 {
221     if( m_Orient0->GetValue() )
222         m_orientValue = 0.0;
223     else if( m_Orient90->GetValue() )
224         m_orientValue = 90.0;
225     else if( m_Orient270->GetValue() )
226         m_orientValue = 270.0;
227     else if( m_Orient180->GetValue() )
228         m_orientValue = 180.0;
229 
230     updateOrientationControl();
231 }
232 
233 
OnOtherOrientation(wxCommandEvent & aEvent)234 void DIALOG_FOOTPRINT_PROPERTIES::OnOtherOrientation( wxCommandEvent& aEvent )
235 {
236     m_OrientOther->SetValue( true );
237 
238     aEvent.Skip();
239 }
240 
241 
TransferDataToWindow()242 bool DIALOG_FOOTPRINT_PROPERTIES::TransferDataToWindow()
243 {
244     if( !wxDialog::TransferDataToWindow() )
245         return false;
246 
247     if( !m_PanelGeneral->TransferDataToWindow() )
248         return false;
249 
250     // Add the models to the panel
251     if( !m_3dPanel->TransferDataToWindow() )
252         return false;
253 
254     // Footprint Texts
255     m_texts->push_back( m_footprint->Reference() );
256     m_texts->push_back( m_footprint->Value() );
257 
258     for( BOARD_ITEM* item : m_footprint->GraphicalItems() )
259     {
260         FP_TEXT* textItem = dyn_cast<FP_TEXT*>( item );
261 
262         if( textItem )
263             m_texts->push_back( *textItem );
264     }
265 
266     // notify the grid
267     wxGridTableMessage tmsg( m_texts, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, m_texts->GetNumberRows() );
268     m_itemsGrid->ProcessTableMessage( tmsg );
269 
270     // Footprint Properties
271 
272     m_posX.SetValue( m_footprint->GetPosition().x );
273     m_posY.SetValue( m_footprint->GetPosition().y );
274 
275     m_BoardSideCtrl->SetSelection( (m_footprint->GetLayer() == B_Cu) ? 1 : 0 );
276 
277     m_orientValue = m_footprint->GetOrientation() / 10.0;
278 
279     if( m_orientValue == 0.0 )
280         m_Orient0->SetValue( true );
281     else if( m_orientValue == 90.0 || m_orientValue == -270.0 )
282         m_Orient90->SetValue( true );
283     else if( m_orientValue == 270.0 || m_orientValue == -90.0 )
284         m_Orient270->SetValue( true );
285     else if( m_orientValue == 180.0 || m_orientValue == -180.0 )
286         m_Orient180->SetValue( true );
287     else
288         m_OrientOther->SetValue( true );
289 
290     updateOrientationControl();
291 
292     m_AutoPlaceCtrl->SetSelection( m_footprint->IsLocked() ? 1 : 0 );
293 
294     m_AutoPlaceCtrl->SetItemToolTip( 0, _( "Footprint can be freely moved and oriented on the "
295                                            "canvas." ) );
296     m_AutoPlaceCtrl->SetItemToolTip( 1, _( "Footprint is locked: it cannot be freely moved and "
297                                            "oriented on the canvas and can only be selected when "
298                                            "the 'Locked items' checkbox is enabled in the "
299                                            "selection filter." ) );
300 
301     m_CostRot90Ctrl->SetValue( m_footprint->GetPlacementCost90() );
302     m_CostRot180Ctrl->SetValue( m_footprint->GetPlacementCost180() );
303 
304     if( m_footprint->GetAttributes() & FP_THROUGH_HOLE )
305         m_componentType->SetSelection( 0 );
306     else if( m_footprint->GetAttributes() & FP_SMD )
307         m_componentType->SetSelection( 1 );
308     else
309         m_componentType->SetSelection( 2 );
310 
311     m_boardOnly->SetValue( m_footprint->GetAttributes() & FP_BOARD_ONLY );
312     m_excludeFromPosFiles->SetValue( m_footprint->GetAttributes() & FP_EXCLUDE_FROM_POS_FILES );
313     m_excludeFromBOM->SetValue( m_footprint->GetAttributes() & FP_EXCLUDE_FROM_BOM );
314 
315     // Local Clearances
316 
317     m_netClearance.SetValue( m_footprint->GetLocalClearance() );
318     m_solderMask.SetValue( m_footprint->GetLocalSolderMaskMargin() );
319     m_solderPaste.SetValue( m_footprint->GetLocalSolderPasteMargin() );
320     m_solderPasteRatio.SetDoubleValue( m_footprint->GetLocalSolderPasteMarginRatio() * 100.0 );
321 
322     switch( m_footprint->GetZoneConnection() )
323     {
324     default:
325     case ZONE_CONNECTION::INHERITED: m_ZoneConnectionChoice->SetSelection( 0 ); break;
326     case ZONE_CONNECTION::FULL:      m_ZoneConnectionChoice->SetSelection( 1 ); break;
327     case ZONE_CONNECTION::THERMAL:   m_ZoneConnectionChoice->SetSelection( 2 ); break;
328     case ZONE_CONNECTION::NONE:      m_ZoneConnectionChoice->SetSelection( 3 ); break;
329     }
330 
331     // Show the footprint's FPID.
332     m_tcLibraryID->SetValue( m_footprint->GetFPID().Format() );
333 
334     for( int col = 0; col < m_itemsGrid->GetNumberCols(); col++ )
335     {
336         m_itemsGrid->SetColMinimalWidth( col, m_itemsGrid->GetVisibleWidth( col, true, false,
337                                                                             false ) );
338         // Adjust the column size.
339         int col_size = m_itemsGrid->GetVisibleWidth( col, true, true, false );
340 
341         if( col == FPT_LAYER )  // This one's a drop-down.  Check all possible values.
342         {
343             BOARD* board = m_footprint->GetBoard();
344 
345             for( PCB_LAYER_ID layer : board->GetEnabledLayers().Seq() )
346                 col_size = std::max( col_size, GetTextExtent( board->GetLayerName( layer ) ).x );
347 
348             // And the swatch:
349             col_size += 20;
350         }
351 
352         if( m_itemsGrid->IsColShown( col ) )
353             m_itemsGrid->SetColSize( col, col_size );
354     }
355 
356     m_itemsGrid->SetRowLabelSize( m_itemsGrid->GetVisibleWidth( -1, false, true, true ) );
357 
358     Layout();
359     adjustGridColumns( m_itemsGrid->GetRect().GetWidth() );
360 
361     return true;
362 }
363 
364 
Validate()365 bool DIALOG_FOOTPRINT_PROPERTIES::Validate()
366 {
367     if( !m_itemsGrid->CommitPendingChanges() )
368         return false;
369 
370     if( !DIALOG_SHIM::Validate() )
371         return false;
372 
373     // Check for empty texts.
374     for( size_t i = 2; i < m_texts->size(); ++i )
375     {
376         FP_TEXT& text = m_texts->at( i );
377 
378         if( text.GetText().IsEmpty() )
379         {
380             if( m_NoteBook->GetSelection() != 0 )
381                 m_NoteBook->SetSelection( 0 );
382 
383             m_delayedFocusGrid = m_itemsGrid;
384             m_delayedErrorMessage = _( "Text items must have some content." );
385             m_delayedFocusColumn = FPT_TEXT;
386             m_delayedFocusRow = i;
387 
388             return false;
389         }
390     }
391 
392     if( !m_netClearance.Validate( 0, INT_MAX ) )
393         return false;
394 
395     return true;
396 }
397 
398 
TransferDataFromWindow()399 bool DIALOG_FOOTPRINT_PROPERTIES::TransferDataFromWindow()
400 {
401     if( !Validate() )
402         return false;
403 
404     if( !m_itemsGrid->CommitPendingChanges() )
405         return false;
406 
407     // This only commits the editor, model updating is done below so it is inside
408     // the commit
409     if( !m_3dPanel->TransferDataFromWindow() )
410         return false;
411 
412     auto view = m_frame->GetCanvas()->GetView();
413     BOARD_COMMIT commit( m_frame );
414     commit.Modify( m_footprint );
415 
416     // copy reference and value
417     m_footprint->Reference() = m_texts->at( 0 );
418     m_footprint->Value() = m_texts->at( 1 );
419 
420     size_t i = 2;
421 
422     for( BOARD_ITEM* item : m_footprint->GraphicalItems() )
423     {
424         FP_TEXT* textItem = dyn_cast<FP_TEXT*>( item );
425 
426         if( textItem )
427         {
428             // copy grid table entries till we run out, then delete any remaining texts
429             if( i < m_texts->size() )
430                 *textItem = m_texts->at( i++ );
431             else
432                 textItem->DeleteStructure();
433         }
434     }
435 
436     // if there are still grid table entries, create new texts for them
437     while( i < m_texts->size() )
438     {
439         auto newText = new FP_TEXT( m_texts->at( i++ ) );
440         m_footprint->Add( newText, ADD_MODE::APPEND );
441         view->Add( newText );
442     }
443 
444     // Initialize masks clearances
445     m_footprint->SetLocalClearance( m_netClearance.GetValue() );
446     m_footprint->SetLocalSolderMaskMargin( m_solderMask.GetValue() );
447     m_footprint->SetLocalSolderPasteMargin( m_solderPaste.GetValue() );
448     m_footprint->SetLocalSolderPasteMarginRatio( m_solderPasteRatio.GetDoubleValue() / 100.0 );
449 
450     switch( m_ZoneConnectionChoice->GetSelection() )
451     {
452     default:
453     case 0:  m_footprint->SetZoneConnection( ZONE_CONNECTION::INHERITED ); break;
454     case 1:  m_footprint->SetZoneConnection( ZONE_CONNECTION::FULL );      break;
455     case 2:  m_footprint->SetZoneConnection( ZONE_CONNECTION::THERMAL );   break;
456     case 3:  m_footprint->SetZoneConnection( ZONE_CONNECTION::NONE );      break;
457     }
458 
459     // Set Footprint Position
460     wxPoint pos( m_posX.GetValue(), m_posY.GetValue() );
461     m_footprint->SetPosition( pos );
462     m_footprint->SetLocked( m_AutoPlaceCtrl->GetSelection() == 1 );
463 
464     int attributes = 0;
465 
466     switch( m_componentType->GetSelection() )
467     {
468     case 0:  attributes |= FP_THROUGH_HOLE; break;
469     case 1:  attributes |= FP_SMD;          break;
470     default:                                break;
471     }
472 
473     if( m_boardOnly->GetValue() )
474         attributes |= FP_BOARD_ONLY;
475 
476     if( m_excludeFromPosFiles->GetValue() )
477         attributes |= FP_EXCLUDE_FROM_POS_FILES;
478 
479     if( m_excludeFromBOM->GetValue() )
480         attributes |= FP_EXCLUDE_FROM_BOM;
481 
482     m_footprint->SetAttributes( attributes );
483 
484     m_footprint->SetPlacementCost90( m_CostRot90Ctrl->GetValue() );
485     m_footprint->SetPlacementCost180( m_CostRot180Ctrl->GetValue() );
486 
487     // Now, set orientation.  Must be done after other changes because rotation changes field
488     // positions on board (so that relative positions are held constant)
489     m_orientValidator.TransferFromWindow();
490 
491     double orient = m_orientValue * 10;
492 
493     if( m_footprint->GetOrientation() != orient )
494         m_footprint->Rotate( m_footprint->GetPosition(), orient - m_footprint->GetOrientation() );
495 
496     // Set component side, that also have effect on the fields positions on board
497     bool change_layer = false;
498     if( m_BoardSideCtrl->GetSelection() == 0 )     // layer req = COMPONENT
499     {
500         if( m_footprint->GetLayer() == B_Cu )
501             change_layer = true;
502     }
503     else if( m_footprint->GetLayer() == F_Cu )
504         change_layer = true;
505 
506     if( change_layer )
507         m_footprint->Flip( m_footprint->GetPosition(), m_frame->Settings().m_FlipLeftRight );
508 
509     // Copy the models from the panel to the footprint
510     std::vector<FP_3DMODEL>& panelList = m_3dPanel->GetModelList();
511     std::list<FP_3DMODEL>*   fpList    = &m_footprint->Models();
512     fpList->clear();
513     fpList->insert( fpList->end(), panelList.begin(), panelList.end() );
514 
515     // This is a simple edit, we must create an undo entry
516     if( m_footprint->GetEditFlags() == 0 )    // i.e. not edited, or moved
517         commit.Push( _( "Modify footprint properties" ) );
518 
519     m_returnValue = FP_PROPS_OK;
520     return true;
521 }
522 
523 
OnAddField(wxCommandEvent &)524 void DIALOG_FOOTPRINT_PROPERTIES::OnAddField( wxCommandEvent&  )
525 {
526     if( !m_itemsGrid->CommitPendingChanges() )
527         return;
528 
529     const BOARD_DESIGN_SETTINGS& dsnSettings = m_frame->GetDesignSettings();
530     FP_TEXT textItem( m_footprint );
531 
532     // Set active layer if legal; otherwise copy layer from previous text item
533     if( LSET::AllTechMask().test( m_frame->GetActiveLayer() ) )
534         textItem.SetLayer( m_frame->GetActiveLayer() );
535     else
536         textItem.SetLayer( m_texts->at( m_texts->size() - 1 ).GetLayer() );
537 
538     textItem.SetTextSize( dsnSettings.GetTextSize( textItem.GetLayer() ) );
539     textItem.SetTextThickness( dsnSettings.GetTextThickness( textItem.GetLayer() ) );
540     textItem.SetItalic( dsnSettings.GetTextItalic( textItem.GetLayer() ) );
541     textItem.SetKeepUpright( dsnSettings.GetTextUpright( textItem.GetLayer() ) );
542     textItem.SetMirrored( IsBackLayer( textItem.GetLayer() ) );
543 
544     m_texts->push_back( textItem );
545 
546     // notify the grid
547     wxGridTableMessage msg( m_texts, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, 1 );
548     m_itemsGrid->ProcessTableMessage( msg );
549 
550     m_itemsGrid->SetFocus();
551     m_itemsGrid->MakeCellVisible( m_texts->size() - 1, 0 );
552     m_itemsGrid->SetGridCursor( m_texts->size() - 1, 0 );
553 
554     m_itemsGrid->EnableCellEditControl( true );
555     m_itemsGrid->ShowCellEditControl();
556 }
557 
558 
OnDeleteField(wxCommandEvent &)559 void DIALOG_FOOTPRINT_PROPERTIES::OnDeleteField( wxCommandEvent&  )
560 {
561     if( !m_itemsGrid->CommitPendingChanges() )
562         return;
563 
564     wxArrayInt selectedRows = m_itemsGrid->GetSelectedRows();
565 
566     if( selectedRows.empty() && m_itemsGrid->GetGridCursorRow() >= 0 )
567         selectedRows.push_back( m_itemsGrid->GetGridCursorRow() );
568 
569     if( selectedRows.empty() )
570         return;
571 
572     for( int row : selectedRows )
573     {
574         if( row < 2 )
575         {
576             DisplayError( nullptr, _( "Reference and value are mandatory." ) );
577             return;
578         }
579     }
580 
581     // Reverse sort so deleting a row doesn't change the indexes of the other rows.
582     selectedRows.Sort( []( int* first, int* second ) { return *second - *first; } );
583 
584     for( int row : selectedRows )
585     {
586         m_texts->erase( m_texts->begin() + row );
587 
588         // notify the grid
589         wxGridTableMessage msg( m_texts, wxGRIDTABLE_NOTIFY_ROWS_DELETED, row, 1 );
590         m_itemsGrid->ProcessTableMessage( msg );
591 
592         if( m_itemsGrid->GetNumberRows() > 0 )
593         {
594             m_itemsGrid->MakeCellVisible( std::max( 0, row-1 ), m_itemsGrid->GetGridCursorCol() );
595             m_itemsGrid->SetGridCursor( std::max( 0, row-1 ), m_itemsGrid->GetGridCursorCol() );
596         }
597     }
598 }
599 
600 
adjustGridColumns(int aWidth)601 void DIALOG_FOOTPRINT_PROPERTIES::adjustGridColumns( int aWidth )
602 {
603     // Account for scroll bars
604     int itemsWidth = aWidth - ( m_itemsGrid->GetSize().x - m_itemsGrid->GetClientSize().x );
605 
606     itemsWidth -= m_itemsGrid->GetRowLabelSize();
607 
608     for( int i = 1; i < m_itemsGrid->GetNumberCols(); i++ )
609         itemsWidth -= m_itemsGrid->GetColSize( i );
610 
611     if( itemsWidth > 0 )
612     {
613         m_itemsGrid->SetColSize( 0, std::max( itemsWidth,
614                 m_itemsGrid->GetVisibleWidth( 0, true, false, false ) ) );
615     }
616 
617     // Update the width of the 3D panel
618     m_3dPanel->AdjustGridColumnWidths( aWidth );
619 }
620 
621 
OnUpdateUI(wxUpdateUIEvent &)622 void DIALOG_FOOTPRINT_PROPERTIES::OnUpdateUI( wxUpdateUIEvent&  )
623 {
624     if( !m_initialized )
625         return;
626 
627     if( !m_itemsGrid->IsCellEditControlShown() )
628         adjustGridColumns( m_itemsGrid->GetRect().GetWidth() );
629 
630     // Handle a grid error.  This is delayed to OnUpdateUI so that we can change focus
631     // even when the original validation was triggered from a killFocus event, and so
632     // that the corresponding notebook page can be shown in the background when triggered
633     // from an OK.
634     if( m_delayedFocusRow >= 0 )
635     {
636         // We will re-enter this routine if an error dialog is displayed, so make sure we
637         // zero out our member variables first.
638         wxGrid*  grid = m_delayedFocusGrid;
639         int      row = m_delayedFocusRow;
640         int      col = m_delayedFocusColumn;
641         wxString msg = m_delayedErrorMessage;
642 
643         m_delayedFocusGrid = nullptr;
644         m_delayedFocusRow = -1;
645         m_delayedFocusColumn = -1;
646         m_delayedErrorMessage = wxEmptyString;
647 
648         if( !msg.IsEmpty() )
649         {
650             // Do not use DisplayErrorMessage(); it screws up window order on Mac
651             DisplayError( nullptr, msg );
652         }
653 
654         grid->SetFocus();
655         grid->MakeCellVisible( row, col );
656 
657         // Selecting the first grid item only makes sense for the
658         // items grid
659         if( !m_initialFocus || grid == m_itemsGrid )
660         {
661             grid->SetGridCursor( row, col );
662             grid->EnableCellEditControl( true );
663             grid->ShowCellEditControl();
664 
665             if( grid == m_itemsGrid && row == 0 && col == 0 )
666             {
667                 auto referenceEditor = grid->GetCellEditor( 0, 0 );
668 
669                 if( auto textEntry = dynamic_cast<wxTextEntry*>( referenceEditor->GetControl() ) )
670                     KIUI::SelectReferenceNumber( textEntry );
671 
672                 referenceEditor->DecRef();
673             }
674         }
675         m_initialFocus = false;
676     }
677 }
678 
679 
OnGridSize(wxSizeEvent & aEvent)680 void DIALOG_FOOTPRINT_PROPERTIES::OnGridSize( wxSizeEvent& aEvent )
681 {
682     // A trick to fix a cosmetic issue: when, in m_itemsGrid, a layer selector widget
683     // has the focus (is activated in column 6) when resizing the grid, the widget
684     // is not moved. So just change the widget having the focus in this case
685     if( m_NoteBook->GetSelection() == 0 && !m_itemsGrid->HasFocus() )
686     {
687         int col = m_itemsGrid->GetGridCursorCol();
688 
689         if( col == 6 )  // a layer selector widget can be activated
690              m_itemsGrid->SetFocus();
691     }
692 
693     adjustGridColumns( aEvent.GetSize().GetX() );
694 
695     aEvent.Skip();
696 }
697 
698 
OnPageChange(wxNotebookEvent & aEvent)699 void DIALOG_FOOTPRINT_PROPERTIES::OnPageChange( wxNotebookEvent& aEvent )
700 {
701     int page = aEvent.GetSelection();
702 
703     // Shouldn't be necessary, but is on at least OSX
704     if( page >= 0 )
705         m_NoteBook->ChangeSelection( (unsigned) page );
706 
707 #ifdef __WXMAC__
708     // Work around an OSX bug where the wxGrid children don't get placed correctly until
709     // the first resize event
710     if( m_macHack[ page ] )
711     {
712         wxSize pageSize = m_NoteBook->GetPage( page )->GetSize();
713         pageSize.x -= 1;
714 
715         m_NoteBook->GetPage( page )->SetSize( pageSize );
716         m_macHack[ page ] = false;
717     }
718 #endif
719 }
720 
721 
updateOrientationControl()722 void DIALOG_FOOTPRINT_PROPERTIES::updateOrientationControl()
723 {
724     KIUI::ValidatorTransferToWindowWithoutEvents( m_orientValidator );
725 }
726