1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2012-2015 Jean-Pierre Charras, jp.charras at wanadoo.fr
5  * Copyright (C) 2008-2016 Wayne Stambaugh <stambaughw@gmail.com>
6  * Copyright (C) 2004-2021 KiCad Developers, see AUTHORS.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 
26 #include <3d_viewer/eda_3d_viewer_frame.h>
27 #include <bitmaps.h>
28 #include <board_commit.h>
29 #include <board.h>
30 #include <footprint.h>
31 #include <confirm.h>
32 #include <eda_pattern_match.h>
33 #include <footprint_info.h>
34 #include <footprint_viewer_frame.h>
35 #include <fp_lib_table.h>
36 #include <kiway.h>
37 #include <widgets/msgpanel.h>
38 #include <pcb_draw_panel_gal.h>
39 #include <pcb_painter.h>
40 #include <pcbnew_id.h>
41 #include <footprint_editor_settings.h>
42 #include <pgm_base.h>
43 #include <settings/settings_manager.h>
44 #include <tool/action_toolbar.h>
45 #include <tool/common_control.h>
46 #include <tool/common_tools.h>
47 #include <tool/selection.h>
48 #include <tool/tool_dispatcher.h>
49 #include <tool/tool_manager.h>
50 #include <tool/zoom_tool.h>
51 #include <tools/pcb_viewer_tools.h>
52 #include <tools/pcb_actions.h>
53 #include <tools/pcb_editor_conditions.h>
54 #include <tools/pcb_control.h>
55 #include <tools/pcb_picker_tool.h>
56 #include <tools/pcb_selection_tool.h>
57 #include <tools/board_editor_control.h>
58 #include <wildcards_and_files_ext.h>
59 #include <wx/listbox.h>
60 #include <wx/srchctrl.h>
61 #include <wx/tokenzr.h>
62 #include <wx/choice.h>
63 
64 using namespace std::placeholders;
65 
66 
67 #define NEXT_PART       1
68 #define NEW_PART        0
69 #define PREVIOUS_PART   -1
70 
71 
BEGIN_EVENT_TABLE(FOOTPRINT_VIEWER_FRAME,EDA_DRAW_FRAME)72 BEGIN_EVENT_TABLE( FOOTPRINT_VIEWER_FRAME, EDA_DRAW_FRAME )
73     // Window events
74     EVT_SIZE( FOOTPRINT_VIEWER_FRAME::OnSize )
75     EVT_ACTIVATE( FOOTPRINT_VIEWER_FRAME::OnActivate )
76 
77     EVT_MENU( wxID_EXIT, FOOTPRINT_VIEWER_FRAME::OnExitKiCad )
78     EVT_MENU( wxID_CLOSE, FOOTPRINT_VIEWER_FRAME::CloseFootprintViewer )
79 
80     // Toolbar events
81     EVT_TOOL( ID_MODVIEW_NEXT, FOOTPRINT_VIEWER_FRAME::OnIterateFootprintList )
82     EVT_TOOL( ID_MODVIEW_PREVIOUS, FOOTPRINT_VIEWER_FRAME::OnIterateFootprintList )
83     EVT_TOOL( ID_ADD_FOOTPRINT_TO_BOARD, FOOTPRINT_VIEWER_FRAME::AddFootprintToPCB )
84     EVT_CHOICE( ID_ON_ZOOM_SELECT, FOOTPRINT_VIEWER_FRAME::OnSelectZoom )
85     EVT_CHOICE( ID_ON_GRID_SELECT, FOOTPRINT_VIEWER_FRAME::OnSelectGrid )
86 
87     EVT_UPDATE_UI( ID_ADD_FOOTPRINT_TO_BOARD, FOOTPRINT_VIEWER_FRAME::OnUpdateFootprintButton )
88 
89     EVT_TEXT( ID_MODVIEW_LIB_FILTER, FOOTPRINT_VIEWER_FRAME::OnLibFilter )
90     EVT_TEXT( ID_MODVIEW_FOOTPRINT_FILTER, FOOTPRINT_VIEWER_FRAME::OnFPFilter )
91 
92     // listbox events
93     EVT_LISTBOX( ID_MODVIEW_LIB_LIST, FOOTPRINT_VIEWER_FRAME::ClickOnLibList )
94     EVT_LISTBOX( ID_MODVIEW_FOOTPRINT_LIST, FOOTPRINT_VIEWER_FRAME::ClickOnFootprintList )
95     EVT_LISTBOX_DCLICK( ID_MODVIEW_FOOTPRINT_LIST, FOOTPRINT_VIEWER_FRAME::DClickOnFootprintList )
96 
97 END_EVENT_TABLE()
98 
99 
100 /*
101  * Note: FOOTPRINT_VIEWER_FRAME can be created in "modal mode", or as a usual frame.
102  */
103 #define PARENT_STYLE   ( KICAD_DEFAULT_DRAWFRAME_STYLE | wxFRAME_FLOAT_ON_PARENT )
104 #define MODAL_STYLE    ( KICAD_DEFAULT_DRAWFRAME_STYLE | wxSTAY_ON_TOP )
105 #define NONMODAL_STYLE ( KICAD_DEFAULT_DRAWFRAME_STYLE )
106 
107 
108 FOOTPRINT_VIEWER_FRAME::FOOTPRINT_VIEWER_FRAME( KIWAY* aKiway, wxWindow* aParent,
109                                                 FRAME_T aFrameType ) :
110     PCB_BASE_FRAME( aKiway, aParent, aFrameType, _( "Footprint Library Browser" ),
111             wxDefaultPosition, wxDefaultSize,
112             aFrameType == FRAME_FOOTPRINT_VIEWER_MODAL ? ( aParent ? PARENT_STYLE : MODAL_STYLE )
113                                                        : NONMODAL_STYLE,
114             aFrameType == FRAME_FOOTPRINT_VIEWER_MODAL ? FOOTPRINT_VIEWER_FRAME_NAME_MODAL
115                                                        : FOOTPRINT_VIEWER_FRAME_NAME )
116 {
117     wxASSERT( aFrameType == FRAME_FOOTPRINT_VIEWER_MODAL || aFrameType == FRAME_FOOTPRINT_VIEWER );
118 
119     if( aFrameType == FRAME_FOOTPRINT_VIEWER_MODAL )
120         SetModal( true );
121 
122     m_aboutTitle = _( "KiCad Footprint Library Viewer" );
123 
124     // Force the items to always snap
125     m_magneticItems.pads     = MAGNETIC_OPTIONS::CAPTURE_ALWAYS;
126     m_magneticItems.tracks   = MAGNETIC_OPTIONS::CAPTURE_ALWAYS;
127     m_magneticItems.graphics = true;
128 
129     // Force the frame name used in config. the footprint viewer frame has a name
130     // depending on aFrameType (needed to identify the frame by wxWidgets),
131     // but only one configuration is preferable.
132     m_configName = FOOTPRINT_VIEWER_FRAME_NAME;
133 
134     // Give an icon
135     wxIcon  icon;
136     icon.CopyFromBitmap( KiBitmap( BITMAPS::icon_footprint_browser ) );
137     SetIcon( icon );
138 
139     wxPanel* libPanel = new wxPanel( this );
140     wxSizer* libSizer = new wxBoxSizer( wxVERTICAL );
141 
142     m_libFilter = new wxSearchCtrl( libPanel, ID_MODVIEW_LIB_FILTER, wxEmptyString,
143                                   wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER );
144     m_libFilter->SetDescriptiveText( _( "Filter" ) );
145     libSizer->Add( m_libFilter, 0, wxEXPAND, 5 );
146 
147     m_libList = new wxListBox( libPanel, ID_MODVIEW_LIB_LIST, wxDefaultPosition, wxDefaultSize,
148                                0, nullptr, wxLB_HSCROLL | wxNO_BORDER );
149     libSizer->Add( m_libList, 1, wxEXPAND, 5 );
150 
151     libPanel->SetSizer( libSizer );
152     libPanel->Fit();
153 
154     wxPanel* fpPanel = new wxPanel( this );
155     wxSizer* fpSizer = new wxBoxSizer( wxVERTICAL );
156 
157     m_fpFilter = new wxSearchCtrl( fpPanel, ID_MODVIEW_FOOTPRINT_FILTER, wxEmptyString,
158                                  wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER );
159     m_fpFilter->SetDescriptiveText( _( "Filter" ) );
160     m_fpFilter->SetToolTip(
161             _( "Filter on footprint name, keywords, description and pad count.\n"
162                "Search terms are separated by spaces.  All search terms must match.\n"
163                "A term which is a number will also match against the pad count." ) );
164     fpSizer->Add( m_fpFilter, 0, wxEXPAND, 5 );
165 
166 #ifdef __WXGTK__
167     // wxSearchCtrl vertical height is not calculated correctly on some GTK setups
168     // See https://gitlab.com/kicad/code/kicad/-/issues/9019
169     m_libFilter->SetMinSize( wxSize( -1, GetTextExtent( wxT( "qb" ) ).y + 10 ) );
170     m_fpFilter->SetMinSize( wxSize( -1, GetTextExtent( wxT( "qb" ) ).y + 10 ) );
171 #endif
172 
173     m_fpList = new wxListBox( fpPanel, ID_MODVIEW_FOOTPRINT_LIST, wxDefaultPosition, wxDefaultSize,
174                               0, nullptr, wxLB_HSCROLL | wxNO_BORDER );
175     fpSizer->Add( m_fpList, 1, wxEXPAND, 5 );
176 
177     fpPanel->SetSizer( fpSizer );
178     fpPanel->Fit();
179 
180     // Create GAL canvas
181     m_canvasType = loadCanvasTypeSetting();
182 
183     PCB_DRAW_PANEL_GAL* drawPanel = new PCB_DRAW_PANEL_GAL( this, -1, wxPoint( 0, 0 ), m_frameSize,
184                                                             GetGalDisplayOptions(), m_canvasType );
185     SetCanvas( drawPanel );
186 
187     SetBoard( new BOARD() );
188 
189     // This board will only be used to hold a footprint for viewing
190     GetBoard()->SetBoardUse( BOARD_USE::FPHOLDER );
191 
192     // In viewer, the default net clearance is not known (it depends on the actual board).
193     // So we do not show the default clearance, by setting it to 0
194     // The footprint or pad specific clearance will be shown
195     GetBoard()->GetDesignSettings().GetDefault()->SetClearance( 0 );
196 
197     // Don't show the default board solder mask clearance in the footprint viewer.  Only the
198     // footprint or pad clearance setting should be shown if it is not 0.
199     GetBoard()->GetDesignSettings().m_SolderMaskMargin = 0;
200 
201     // Ensure all layers and items are visible:
202     GetBoard()->SetVisibleAlls();
203     SetScreen( new PCB_SCREEN( GetPageSizeIU() ) );
204 
205     GetScreen()->m_Center = true;      // Center coordinate origins on screen.
206     LoadSettings( config() );
207     GetGalDisplayOptions().m_axesEnabled = true;
208 
209     // Call resolveCanvasType after loading settings:
210     resolveCanvasType();
211 
212     // Create the manager and dispatcher & route draw panel events to the dispatcher
213     m_toolManager = new TOOL_MANAGER;
214     m_toolManager->SetEnvironment( GetBoard(), drawPanel->GetView(),
215                                    drawPanel->GetViewControls(), config(), this );
216     m_actions = new PCB_ACTIONS();
217     m_toolDispatcher = new TOOL_DISPATCHER( m_toolManager );
218     drawPanel->SetEventDispatcher( m_toolDispatcher );
219 
220     m_toolManager->RegisterTool( new PCB_CONTROL );
221     m_toolManager->RegisterTool( new PCB_SELECTION_TOOL );
222     m_toolManager->RegisterTool( new COMMON_TOOLS );    // for std context menus (zoom & grid)
223     m_toolManager->RegisterTool( new COMMON_CONTROL );
224     m_toolManager->RegisterTool( new PCB_PICKER_TOOL ); // for setting grid origin
225     m_toolManager->RegisterTool( new ZOOM_TOOL );
226     m_toolManager->RegisterTool( new PCB_VIEWER_TOOLS );
227 
228     m_toolManager->GetTool<PCB_VIEWER_TOOLS>()->SetFootprintFrame( true );
229 
230     m_toolManager->InitTools();
231     m_toolManager->InvokeTool( "pcbnew.InteractiveSelection" );
232 
233     setupUIConditions();
234     ReCreateMenuBar();
235     ReCreateHToolbar();
236     ReCreateVToolbar();
237     ReCreateOptToolbar();
238 
239     ReCreateLibraryList();
240     UpdateTitle();
241 
242     // If a footprint was previously loaded, reload it
243     if( getCurNickname().size() && getCurFootprintName().size() )
244     {
245         LIB_ID id;
246 
247         id.SetLibNickname( getCurNickname() );
248         id.SetLibItemName( getCurFootprintName() );
249         GetBoard()->Add( loadFootprint( id ) );
250     }
251 
252     drawPanel->DisplayBoard( m_pcb );
253 
254     m_auimgr.SetManagedWindow( this );
255 
256     // Horizontal items; layers 4 - 6
257     m_auimgr.AddPane( m_mainToolBar, EDA_PANE().VToolbar().Name( "MainToolbar" ).Top().Layer(6) );
258     m_auimgr.AddPane( m_optionsToolBar, EDA_PANE().VToolbar().Name( "OptToolbar" ).Left().Layer(3) );
259     m_auimgr.AddPane( m_messagePanel, EDA_PANE().Messages().Name( "MsgPanel" ).Bottom().Layer(6) );
260 
261     // Vertical items; layers 1 - 3
262     m_auimgr.AddPane( libPanel, EDA_PANE().Palette().Name( "Libraries" ).Left().Layer(2)
263                       .CaptionVisible( false ).MinSize( 100, -1 ).BestSize( 200, -1 ) );
264     m_auimgr.AddPane( fpPanel, EDA_PANE().Palette().Name( "Footprints" ).Left().Layer( 1)
265                       .CaptionVisible( false ).MinSize( 100, -1 ).BestSize( 300, -1 ) );
266 
267     m_auimgr.AddPane( GetCanvas(), EDA_PANE().Canvas().Name( "DrawFrame" ).Center() );
268 
269     // after changing something to the aui manager call Update() to reflect the changes
270     m_auimgr.Update();
271 
272     // The canvas should not steal the focus from the list boxes
273     GetCanvas()->SetCanFocus( false );
274     GetCanvas()->GetGAL()->SetAxesEnabled( true );
275     ActivateGalCanvas();
276 
277     // Restore last zoom.  (If auto-zooming we'll adjust when we load the footprint.)
278     PCBNEW_SETTINGS* cfg = GetPcbNewSettings();
279     wxASSERT( cfg );
280     GetCanvas()->GetView()->SetScale( cfg->m_FootprintViewerZoom );
281 
282     updateView();
283     setupUnits( config() );
284 
285     if( !IsModal() )        // For modal mode, calling ShowModal() will show this frame
286     {
287         ReCreateFootprintList();
288         Raise();            // On some window managers, this is needed
289         Show( true );
290     }
291 }
292 
293 
~FOOTPRINT_VIEWER_FRAME()294 FOOTPRINT_VIEWER_FRAME::~FOOTPRINT_VIEWER_FRAME()
295 {
296     // Shutdown all running tools
297     if( m_toolManager )
298         m_toolManager->ShutdownAllTools();
299 
300     GetCanvas()->StopDrawing();
301     GetCanvas()->GetView()->Clear();
302     // Be sure any event cannot be fired after frame deletion:
303     GetCanvas()->SetEvtHandlerEnabled( false );
304 }
305 
306 
GetCurrentSelection()307 SELECTION& FOOTPRINT_VIEWER_FRAME::GetCurrentSelection()
308 {
309     return m_toolManager->GetTool<PCB_SELECTION_TOOL>()->GetSelection();
310 }
311 
312 
setupUIConditions()313 void FOOTPRINT_VIEWER_FRAME::setupUIConditions()
314 {
315     PCB_BASE_FRAME::setupUIConditions();
316 
317     ACTION_MANAGER*       mgr = m_toolManager->GetActionManager();
318     PCB_EDITOR_CONDITIONS cond( this );
319 
320     wxASSERT( mgr );
321 
322 #define ENABLE( x ) ACTION_CONDITIONS().Enable( x )
323 #define CHECK( x )  ACTION_CONDITIONS().Check( x )
324 
325     mgr->SetConditions( ACTIONS::toggleGrid,             CHECK( cond.GridVisible() ) );
326     mgr->SetConditions( ACTIONS::toggleCursorStyle,      CHECK( cond.FullscreenCursor() ) );
327     mgr->SetConditions( ACTIONS::millimetersUnits,
328                         CHECK( cond.Units( EDA_UNITS::MILLIMETRES ) ) );
329     mgr->SetConditions( ACTIONS::inchesUnits,            CHECK( cond.Units( EDA_UNITS::INCHES ) ) );
330     mgr->SetConditions( ACTIONS::milsUnits,              CHECK( cond.Units( EDA_UNITS::MILS ) ) );
331 
332 
333     mgr->SetConditions( ACTIONS::zoomTool,
334                         CHECK( cond.CurrentTool( ACTIONS::zoomTool ) ) );
335     mgr->SetConditions( ACTIONS::measureTool,
336                         CHECK( cond.CurrentTool( ACTIONS::measureTool ) ) );
337     mgr->SetConditions( ACTIONS::selectionTool,
338                         CHECK( cond.CurrentTool( ACTIONS::selectionTool ) ) );
339 
340     mgr->SetConditions( PCB_ACTIONS::showPadNumbers,     CHECK( cond.PadNumbersDisplay() ) );
341     mgr->SetConditions( PCB_ACTIONS::padDisplayMode,     CHECK( !cond.PadFillDisplay() ) );
342     mgr->SetConditions( PCB_ACTIONS::textOutlines,       CHECK( !cond.TextFillDisplay() ) );
343     mgr->SetConditions( PCB_ACTIONS::graphicsOutlines,   CHECK( !cond.GraphicsFillDisplay() ) );
344 
345 #undef ENABLE
346 #undef CHECK
347 }
348 
349 
doCloseWindow()350 void FOOTPRINT_VIEWER_FRAME::doCloseWindow()
351 {
352     // A workaround to avoid flicker, in modal mode when modview frame is destroyed,
353     // when the aui toolbar is not docked (i.e. shown in a miniframe)
354     // (useful on windows only)
355     m_mainToolBar->SetFocus();
356 
357     GetCanvas()->StopDrawing();
358 
359     if( IsModal() )
360     {
361         // Only dismiss a modal frame once, so that the return values set by
362         // the prior DismissModal() are not bashed for ShowModal().
363         if( !IsDismissed() )
364             DismissModal( false );
365 
366         // window to be destroyed by the caller of KIWAY_PLAYER::ShowModal()
367     }
368     else
369     {
370         Destroy();
371     }
372 }
373 
374 
OnSize(wxSizeEvent & SizeEv)375 void FOOTPRINT_VIEWER_FRAME::OnSize( wxSizeEvent& SizeEv )
376 {
377     if( m_auimgr.GetManagedWindow() )
378         m_auimgr.Update();
379 
380     SizeEv.Skip();
381 }
382 
383 
ReCreateLibraryList()384 void FOOTPRINT_VIEWER_FRAME::ReCreateLibraryList()
385 {
386     m_libList->Clear();
387 
388     std::vector<wxString> nicknames = Prj().PcbFootprintLibs()->GetLogicalLibs();
389     std::set<wxString>    excludes;
390 
391     if( !m_libFilter->GetValue().IsEmpty() )
392     {
393         wxStringTokenizer tokenizer( m_libFilter->GetValue() );
394 
395         while( tokenizer.HasMoreTokens() )
396         {
397             const wxString       term = tokenizer.GetNextToken().Lower();
398             EDA_COMBINED_MATCHER matcher( term );
399             int                  matches, position;
400 
401             for( const wxString& nickname : nicknames )
402             {
403                 if( !matcher.Find( nickname.Lower(), matches, position ) )
404                     excludes.insert( nickname );
405             }
406         }
407     }
408 
409     for( const wxString& nickname : nicknames )
410     {
411         if( !excludes.count( nickname ) )
412             m_libList->Append( nickname );
413     }
414 
415     // Search for a previous selection:
416     int index =  m_libList->FindString( getCurNickname(), true );
417 
418     if( index == wxNOT_FOUND )
419     {
420         if( m_libList->GetCount() > 0 )
421         {
422             m_libList->SetSelection( 0 );
423             wxCommandEvent dummy;
424             ClickOnLibList( dummy );
425         }
426         else
427         {
428             setCurNickname( wxEmptyString );
429             setCurFootprintName( wxEmptyString );
430         }
431     }
432     else
433     {
434         m_libList->SetSelection( index, true );
435         wxCommandEvent dummy;
436         ClickOnLibList( dummy );
437     }
438 
439     GetCanvas()->Refresh();
440 }
441 
442 
ReCreateFootprintList()443 void FOOTPRINT_VIEWER_FRAME::ReCreateFootprintList()
444 {
445     m_fpList->Clear();
446 
447     if( !getCurNickname() )
448         setCurFootprintName( wxEmptyString );
449 
450     auto fp_info_list = FOOTPRINT_LIST::GetInstance( Kiway() );
451 
452     wxString nickname = getCurNickname();
453 
454     fp_info_list->ReadFootprintFiles( Prj().PcbFootprintLibs(), !nickname ? nullptr : &nickname );
455 
456     if( fp_info_list->GetErrorCount() )
457     {
458         fp_info_list->DisplayErrors( this );
459 
460         // For footprint libraries that support one footprint per file, there may have been
461         // valid footprints read so show the footprints that loaded properly.
462         if( fp_info_list->GetList().empty() )
463             return;
464     }
465 
466     std::set<wxString> excludes;
467 
468     if( !m_fpFilter->GetValue().IsEmpty() )
469     {
470         wxStringTokenizer tokenizer( m_fpFilter->GetValue() );
471 
472         while( tokenizer.HasMoreTokens() )
473         {
474             const wxString       term = tokenizer.GetNextToken().Lower();
475             EDA_COMBINED_MATCHER matcher( term );
476             int                  matches, position;
477 
478             for( const std::unique_ptr<FOOTPRINT_INFO>& footprint : fp_info_list->GetList() )
479             {
480                 wxString search = footprint->GetFootprintName() + " " + footprint->GetSearchText();
481                 bool     matched = matcher.Find( search.Lower(), matches, position );
482 
483                 if( !matched && term.IsNumber() )
484                     matched = ( wxAtoi( term ) == (int)footprint->GetPadCount() );
485 
486                 if( !matched )
487                     excludes.insert( footprint->GetFootprintName() );
488             }
489         }
490     }
491 
492     for( const std::unique_ptr<FOOTPRINT_INFO>& footprint : fp_info_list->GetList() )
493     {
494         if( !excludes.count( footprint->GetFootprintName() ) )
495             m_fpList->Append( footprint->GetFootprintName() );
496     }
497 
498     int index = m_fpList->FindString( getCurFootprintName(), true );
499 
500     if( index == wxNOT_FOUND )
501     {
502         if( m_fpList->GetCount() > 0 )
503         {
504             m_fpList->SetSelection( 0 );
505             m_fpList->EnsureVisible( 0 );
506 
507             wxCommandEvent dummy;
508             ClickOnFootprintList( dummy );
509         }
510         else
511         {
512             setCurFootprintName( wxEmptyString );
513         }
514     }
515     else
516     {
517         m_fpList->SetSelection( index, true );
518         m_fpList->EnsureVisible( index );
519     }
520 }
521 
522 
OnLibFilter(wxCommandEvent & aEvent)523 void FOOTPRINT_VIEWER_FRAME::OnLibFilter( wxCommandEvent& aEvent )
524 {
525     ReCreateLibraryList();
526 
527     // Required to avoid interaction with SetHint()
528     // See documentation for wxTextEntry::SetHint
529     aEvent.Skip();
530 }
531 
532 
OnFPFilter(wxCommandEvent & aEvent)533 void FOOTPRINT_VIEWER_FRAME::OnFPFilter( wxCommandEvent& aEvent )
534 {
535     ReCreateFootprintList();
536 
537     // Required to avoid interaction with SetHint()
538     // See documentation for wxTextEntry::SetHint
539     aEvent.Skip();
540 }
541 
542 
OnCharHook(wxKeyEvent & aEvent)543 void FOOTPRINT_VIEWER_FRAME::OnCharHook( wxKeyEvent& aEvent )
544 {
545     if( aEvent.GetKeyCode() == WXK_UP )
546     {
547         if( m_libFilter->HasFocus() || m_libList->HasFocus() )
548             selectPrev( m_libList );
549         else
550             selectPrev( m_fpList );
551     }
552     else if( aEvent.GetKeyCode() == WXK_DOWN )
553     {
554         if( m_libFilter->HasFocus() || m_libList->HasFocus() )
555             selectNext( m_libList );
556         else
557             selectNext( m_fpList );
558     }
559     else if( aEvent.GetKeyCode() == WXK_TAB && m_libFilter->HasFocus() )
560     {
561         if( !aEvent.ShiftDown() )
562             m_fpFilter->SetFocus();
563         else
564             aEvent.Skip();
565     }
566     else if( aEvent.GetKeyCode() == WXK_TAB && m_fpFilter->HasFocus() )
567     {
568         if( aEvent.ShiftDown() )
569             m_libFilter->SetFocus();
570         else
571             aEvent.Skip();
572     }
573     else if( aEvent.GetKeyCode() == WXK_RETURN && m_fpList->GetSelection() >= 0 )
574     {
575         wxCommandEvent dummy;
576         AddFootprintToPCB( dummy );
577     }
578     else
579     {
580         aEvent.Skip();
581     }
582 }
583 
584 
selectPrev(wxListBox * aListBox)585 void FOOTPRINT_VIEWER_FRAME::selectPrev( wxListBox* aListBox )
586 {
587     int prev = aListBox->GetSelection() - 1;
588 
589     if( prev >= 0 )
590     {
591         aListBox->SetSelection( prev );
592         aListBox->EnsureVisible( prev );
593 
594         wxCommandEvent dummy;
595 
596         if( aListBox == m_libList )
597             ClickOnLibList( dummy );
598         else
599             ClickOnFootprintList( dummy );
600     }
601 }
602 
603 
selectNext(wxListBox * aListBox)604 void FOOTPRINT_VIEWER_FRAME::selectNext( wxListBox* aListBox )
605 {
606     int next = aListBox->GetSelection() + 1;
607 
608     if( next < (int)aListBox->GetCount() )
609     {
610         aListBox->SetSelection( next );
611         aListBox->EnsureVisible( next );
612 
613         wxCommandEvent dummy;
614 
615         if( aListBox == m_libList )
616             ClickOnLibList( dummy );
617         else
618             ClickOnFootprintList( dummy );
619     }
620 }
621 
622 
ClickOnLibList(wxCommandEvent & aEvent)623 void FOOTPRINT_VIEWER_FRAME::ClickOnLibList( wxCommandEvent& aEvent )
624 {
625     int ii = m_libList->GetSelection();
626 
627     if( ii < 0 )
628         return;
629 
630     wxString name = m_libList->GetString( ii );
631 
632     if( getCurNickname() == name )
633         return;
634 
635     setCurNickname( name );
636 
637     ReCreateFootprintList();
638     UpdateTitle();
639 }
640 
641 
ClickOnFootprintList(wxCommandEvent & aEvent)642 void FOOTPRINT_VIEWER_FRAME::ClickOnFootprintList( wxCommandEvent& aEvent )
643 {
644     if( m_fpList->GetCount() == 0 )
645         return;
646 
647     int ii = m_fpList->GetSelection();
648 
649     if( ii < 0 )
650         return;
651 
652     wxString name = m_fpList->GetString( ii );
653 
654     if( getCurFootprintName().CmpNoCase( name ) != 0 )
655     {
656         setCurFootprintName( name );
657 
658         // Delete the current footprint (MUST reset tools first)
659         GetToolManager()->ResetTools( TOOL_BASE::MODEL_RELOAD );
660 
661         GetBoard()->DeleteAllFootprints();
662 
663         LIB_ID id;
664         id.SetLibNickname( getCurNickname() );
665         id.SetLibItemName( getCurFootprintName() );
666 
667         try
668         {
669             GetBoard()->Add( loadFootprint( id ) );
670         }
671         catch( const IO_ERROR& ioe )
672         {
673             wxString msg =
674                     wxString::Format( _( "Could not load footprint '%s' from library '%s'."
675                                          "\n\n%s" ),
676                                       getCurFootprintName(), getCurNickname(), ioe.Problem() );
677             DisplayError( this, msg );
678         }
679 
680         UpdateTitle();
681 
682         updateView();
683 
684         GetCanvas()->Refresh();
685         Update3DView( true, true );
686     }
687 }
688 
689 
DClickOnFootprintList(wxCommandEvent & aEvent)690 void FOOTPRINT_VIEWER_FRAME::DClickOnFootprintList( wxCommandEvent& aEvent )
691 {
692     AddFootprintToPCB( aEvent );
693 }
694 
695 
AddFootprintToPCB(wxCommandEvent & aEvent)696 void FOOTPRINT_VIEWER_FRAME::AddFootprintToPCB( wxCommandEvent& aEvent )
697 {
698     if( IsModal() )
699     {
700         if( m_fpList->GetSelection() >= 0 )
701         {
702             LIB_ID fpid( getCurNickname(), m_fpList->GetStringSelection() );
703             DismissModal( true, fpid.Format() );
704         }
705         else
706         {
707             DismissModal( false );
708         }
709     }
710     else if( GetBoard()->GetFirstFootprint() )
711     {
712         PCB_EDIT_FRAME* pcbframe = (PCB_EDIT_FRAME*) Kiway().Player( FRAME_PCB_EDITOR, false );
713 
714         if( pcbframe == nullptr )      // happens when the board editor is not active (or closed)
715         {
716             DisplayErrorMessage( this, _( "No board currently open." ) );
717             return;
718         }
719 
720         TOOL_MANAGER*   toolMgr = pcbframe->GetToolManager();
721 
722         if( toolMgr->GetTool<BOARD_EDITOR_CONTROL>()->PlacingFootprint() )
723         {
724             DisplayError( this, _( "Previous footprint placement still in progress." ) );
725             return;
726         }
727 
728         toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
729         BOARD_COMMIT commit( pcbframe );
730 
731         // Create the "new" footprint
732         FOOTPRINT* newFootprint = (FOOTPRINT*) GetBoard()->GetFirstFootprint()->Duplicate();
733         newFootprint->SetParent( pcbframe->GetBoard() );
734         newFootprint->SetLink( niluuid );
735         newFootprint->SetFlags(IS_NEW ); // whatever
736 
737         for( PAD* pad : newFootprint->Pads() )
738         {
739             // Set the pads ratsnest settings to the global settings
740             pad->SetLocalRatsnestVisible( pcbframe->GetDisplayOptions().m_ShowGlobalRatsnest );
741 
742             // Pads in the library all have orphaned nets.  Replace with Default.
743             pad->SetNetCode( 0 );
744         }
745 
746         // Put it on FRONT layer,
747         // (Can be stored flipped if the lib is an archive built from a board)
748         if( newFootprint->IsFlipped() )
749             newFootprint->Flip( newFootprint->GetPosition(),
750                                 pcbframe->Settings().m_FlipLeftRight );
751 
752         KIGFX::VIEW_CONTROLS* viewControls = pcbframe->GetCanvas()->GetViewControls();
753         VECTOR2D              cursorPos = viewControls->GetCursorPosition();
754 
755         commit.Add( newFootprint );
756         viewControls->SetCrossHairCursorPosition( VECTOR2D( 0, 0 ), false );
757         pcbframe->PlaceFootprint( newFootprint );
758 
759         newFootprint->SetPosition( wxPoint( 0, 0 ) );
760         viewControls->SetCrossHairCursorPosition( cursorPos, false );
761         commit.Push( wxT( "Insert footprint" ) );
762 
763         pcbframe->Raise();
764         toolMgr->RunAction( PCB_ACTIONS::placeFootprint, true, newFootprint );
765 
766         newFootprint->ClearFlags();
767     }
768 }
769 
770 
LoadSettings(APP_SETTINGS_BASE * aCfg)771 void FOOTPRINT_VIEWER_FRAME::LoadSettings( APP_SETTINGS_BASE* aCfg )
772 {
773     PCBNEW_SETTINGS* cfg = dynamic_cast<PCBNEW_SETTINGS*>( aCfg );
774     wxCHECK( cfg, /*void*/ );
775 
776     // We don't allow people to change this right now, so make sure it's on
777     GetWindowSettings( cfg )->cursor.always_show_cursor = true;
778 
779     PCB_BASE_FRAME::LoadSettings( aCfg );
780 
781     // Fetch display and grid settings from Footprint Editor
782     auto fpedit = Pgm().GetSettingsManager().GetAppSettings<FOOTPRINT_EDITOR_SETTINGS>();
783     m_displayOptions = fpedit->m_Display;
784     GetGalDisplayOptions().ReadWindowSettings( fpedit->m_Window );
785 }
786 
787 
SaveSettings(APP_SETTINGS_BASE * aCfg)788 void FOOTPRINT_VIEWER_FRAME::SaveSettings( APP_SETTINGS_BASE* aCfg )
789 {
790     PCBNEW_SETTINGS* cfg = dynamic_cast<PCBNEW_SETTINGS*>( aCfg );
791     wxCHECK( cfg, /*void*/ );
792 
793     GetGalDisplayOptions().m_axesEnabled = true;
794 
795     // We don't want to store anything other than the window settings
796     PCB_BASE_FRAME::SaveSettings( cfg );
797 
798     if( GetCanvas() && GetCanvas()->GetView() )
799         cfg->m_FootprintViewerZoom = GetCanvas()->GetView()->GetScale();
800 }
801 
802 
GetWindowSettings(APP_SETTINGS_BASE * aCfg)803 WINDOW_SETTINGS* FOOTPRINT_VIEWER_FRAME::GetWindowSettings( APP_SETTINGS_BASE* aCfg )
804 {
805     PCBNEW_SETTINGS* cfg = dynamic_cast<PCBNEW_SETTINGS*>( aCfg );
806     wxCHECK_MSG( cfg, nullptr, "config not existing" );
807 
808     return &cfg->m_FootprintViewer;
809 }
810 
811 
GetColorSettings() const812 COLOR_SETTINGS* FOOTPRINT_VIEWER_FRAME::GetColorSettings() const
813 {
814     auto* settings = Pgm().GetSettingsManager().GetAppSettings<FOOTPRINT_EDITOR_SETTINGS>();
815 
816     if( settings )
817         return Pgm().GetSettingsManager().GetColorSettings( settings->m_ColorTheme );
818     else
819         return Pgm().GetSettingsManager().GetColorSettings();
820 }
821 
822 
CommonSettingsChanged(bool aEnvVarsChanged,bool aTextVarsChanged)823 void FOOTPRINT_VIEWER_FRAME::CommonSettingsChanged( bool aEnvVarsChanged, bool aTextVarsChanged )
824 {
825     PCB_BASE_FRAME::CommonSettingsChanged( aEnvVarsChanged, aTextVarsChanged );
826 
827     if( aEnvVarsChanged )
828         ReCreateLibraryList();
829 }
830 
831 
getCurNickname()832 const wxString FOOTPRINT_VIEWER_FRAME::getCurNickname()
833 {
834     return Prj().GetRString( PROJECT::PCB_FOOTPRINT_VIEWER_LIB_NICKNAME );
835 }
836 
837 
setCurNickname(const wxString & aNickname)838 void FOOTPRINT_VIEWER_FRAME::setCurNickname( const wxString& aNickname )
839 {
840     Prj().SetRString( PROJECT::PCB_FOOTPRINT_VIEWER_LIB_NICKNAME, aNickname );
841 }
842 
843 
getCurFootprintName()844 const wxString FOOTPRINT_VIEWER_FRAME::getCurFootprintName()
845 {
846     return Prj().GetRString( PROJECT::PCB_FOOTPRINT_VIEWER_FP_NAME );
847 }
848 
849 
setCurFootprintName(const wxString & aName)850 void FOOTPRINT_VIEWER_FRAME::setCurFootprintName( const wxString& aName )
851 {
852     Prj().SetRString( PROJECT::PCB_FOOTPRINT_VIEWER_FP_NAME, aName );
853 }
854 
855 
OnActivate(wxActivateEvent & event)856 void FOOTPRINT_VIEWER_FRAME::OnActivate( wxActivateEvent& event )
857 {
858     if( event.GetActive() )
859     {
860         // Ensure we have the right library list:
861         std::vector< wxString > libNicknames = Prj().PcbFootprintLibs()->GetLogicalLibs();
862         bool                    stale = false;
863 
864         if( libNicknames.size() != m_libList->GetCount() )
865             stale = true;
866         else
867         {
868             for( unsigned ii = 0;  ii < libNicknames.size();  ii++ )
869             {
870                 if( libNicknames[ii] != m_libList->GetString( ii ) )
871                 {
872                     stale = true;
873                     break;
874                 }
875             }
876         }
877 
878         if( stale )
879         {
880             ReCreateLibraryList();
881             UpdateTitle();
882         }
883     }
884 
885     event.Skip();    // required under wxMAC
886 }
887 
888 
OnUpdateFootprintButton(wxUpdateUIEvent & aEvent)889 void FOOTPRINT_VIEWER_FRAME::OnUpdateFootprintButton( wxUpdateUIEvent& aEvent )
890 {
891     aEvent.Enable( GetBoard()->GetFirstFootprint() != nullptr );
892 }
893 
894 
ShowModal(wxString * aFootprint,wxWindow * aParent)895 bool FOOTPRINT_VIEWER_FRAME::ShowModal( wxString* aFootprint, wxWindow* aParent )
896 {
897     if( aFootprint && !aFootprint->IsEmpty() )
898     {
899         wxString msg;
900         LIB_TABLE* fpTable = Prj().PcbFootprintLibs();
901         LIB_ID fpid;
902 
903         fpid.Parse( *aFootprint, true );
904 
905         if( fpid.IsValid() )
906         {
907             wxString libraryName = fpid.GetLibNickname();
908 
909             if( !fpTable->HasLibrary( fpid.GetLibNickname(), false ) )
910             {
911                 msg.sprintf( _( "The current configuration does not include library '%s'.  Use "
912                                 "Manage Footprint Libraries to edit the configuration." ),
913                              libraryName );
914                 DisplayErrorMessage( aParent, _( "Footprint library not found." ), msg );
915             }
916             else if ( !fpTable->HasLibrary( fpid.GetLibNickname(), true ) )
917             {
918                 msg.sprintf( _( "Library '%s' is not enabled in the current configuration.  Use "
919                                 "Manage Footprint Libraries to edit the configuration." ),
920                              libraryName );
921                 DisplayErrorMessage( aParent, _( "Footprint library not enabled." ), msg );
922             }
923             else
924             {
925                 // Update last selection:
926                 setCurNickname( libraryName );
927                 setCurFootprintName( fpid.GetLibItemName() );
928                 m_libList->SetStringSelection( libraryName );
929             }
930         }
931     }
932 
933     // Rebuild the fp list from the last selected library,
934     // and show the last selected footprint
935     ReCreateFootprintList();
936     SelectAndViewFootprint( NEW_PART );
937 
938     bool retval = KIWAY_PLAYER::ShowModal( aFootprint, aParent );
939 
940     m_libFilter->SetFocus();
941     return retval;
942 }
943 
944 
Update3DView(bool aMarkDirty,bool aRefresh,const wxString * aTitle)945 void FOOTPRINT_VIEWER_FRAME::Update3DView( bool aMarkDirty, bool aRefresh, const wxString* aTitle )
946 {
947     wxString title = _( "3D Viewer" ) + wxT( " \u2014 " ) + getCurFootprintName();
948     PCB_BASE_FRAME::Update3DView( aMarkDirty, aRefresh, &title );
949 }
950 
951 
GetGridColor()952 COLOR4D FOOTPRINT_VIEWER_FRAME::GetGridColor()
953 {
954     return GetColorSettings()->GetColor( LAYER_GRID );
955 }
956 
957 
OnIterateFootprintList(wxCommandEvent & event)958 void FOOTPRINT_VIEWER_FRAME::OnIterateFootprintList( wxCommandEvent& event )
959 {
960     switch( event.GetId() )
961     {
962     case ID_MODVIEW_NEXT:
963         SelectAndViewFootprint( NEXT_PART );
964         break;
965 
966     case ID_MODVIEW_PREVIOUS:
967         SelectAndViewFootprint( PREVIOUS_PART );
968         break;
969 
970     default:
971         wxString id = wxString::Format( "%i", event.GetId() );
972         wxFAIL_MSG( "FOOTPRINT_VIEWER_FRAME::OnIterateFootprintList error: id = " + id );
973     }
974 }
975 
976 
UpdateTitle()977 void FOOTPRINT_VIEWER_FRAME::UpdateTitle()
978 {
979     wxString title;
980     wxString path;
981 
982     if( !getCurNickname().IsEmpty() )
983     {
984         title = getCurNickname();
985 
986         FP_LIB_TABLE* libtable = Prj().PcbFootprintLibs();
987         const LIB_TABLE_ROW* row = libtable->FindRow( getCurNickname() );
988 
989         if( row )
990             title += wxT( " \u2014 " ) + row->GetFullURI( true );
991     }
992     else
993     {
994         title = _( "[no library selected]" );
995     }
996 
997     title += wxT( " \u2014 " ) + _( "Footprint Library Browser" );
998 
999     SetTitle( title );
1000 }
1001 
1002 
SelectAndViewFootprint(int aMode)1003 void FOOTPRINT_VIEWER_FRAME::SelectAndViewFootprint( int aMode )
1004 {
1005     if( !getCurNickname() )
1006         return;
1007 
1008     int selection = m_fpList->FindString( getCurFootprintName(), true );
1009 
1010     if( aMode == NEXT_PART )
1011     {
1012         if( selection != wxNOT_FOUND && selection < (int)m_fpList->GetCount() - 1 )
1013             selection++;
1014     }
1015 
1016     if( aMode == PREVIOUS_PART )
1017     {
1018         if( selection != wxNOT_FOUND && selection > 0 )
1019             selection--;
1020     }
1021 
1022     if( selection != wxNOT_FOUND )
1023     {
1024         m_fpList->SetSelection( selection );
1025         m_fpList->EnsureVisible( selection );
1026 
1027         setCurFootprintName( m_fpList->GetString((unsigned) selection ) );
1028 
1029         // Delete the current footprint
1030         GetBoard()->DeleteAllFootprints();
1031 
1032         FOOTPRINT* footprint = Prj().PcbFootprintLibs()->FootprintLoad( getCurNickname(),
1033                                                                         getCurFootprintName() );
1034 
1035         if( footprint )
1036             GetBoard()->Add( footprint, ADD_MODE::APPEND );
1037 
1038         Update3DView( true, true );
1039 
1040         updateView();
1041     }
1042 
1043     UpdateTitle();
1044 
1045     GetCanvas()->Refresh();
1046 }
1047 
1048 
updateView()1049 void FOOTPRINT_VIEWER_FRAME::updateView()
1050 {
1051     GetCanvas()->UpdateColors();
1052     GetCanvas()->DisplayBoard( GetBoard() );
1053 
1054     m_toolManager->ResetTools( TOOL_BASE::MODEL_RELOAD );
1055 
1056     if( m_zoomSelectBox->GetSelection() == 0 )
1057         m_toolManager->RunAction( ACTIONS::zoomFitScreen, true );
1058     else
1059         m_toolManager->RunAction( ACTIONS::centerContents, true );
1060 
1061     UpdateMsgPanel();
1062 }
1063 
1064 
OnExitKiCad(wxCommandEvent & event)1065 void FOOTPRINT_VIEWER_FRAME::OnExitKiCad( wxCommandEvent& event )
1066 {
1067     Kiway().OnKiCadExit();
1068 }
1069 
1070 
CloseFootprintViewer(wxCommandEvent & event)1071 void FOOTPRINT_VIEWER_FRAME::CloseFootprintViewer( wxCommandEvent& event )
1072 {
1073     Close( false );
1074 }
1075 
1076 
GetModel() const1077 BOARD_ITEM_CONTAINER* FOOTPRINT_VIEWER_FRAME::GetModel() const
1078 {
1079     return GetBoard()->GetFirstFootprint();
1080 }
1081 
1082