KiCad PCB EDA Suite
Loading...
Searching...
No Matches
panel_footprint_chooser.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2014 Henner Zeller <[email protected]>
5 * Copyright (C) 2023 CERN
6 * Copyright The 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, see <https://www.gnu.org/licenses/>.
20 */
21
23#include <algorithm>
24#include <wx/button.h>
25#include <wx/clipbrd.h>
26#include <wx/display.h>
27#include <wx/log.h>
28#include <wx/panel.h>
29#include <wx/sizer.h>
30#include <wx/splitter.h>
31#include <wx/timer.h>
32#include <wx/wxhtml.h>
33#include <pcb_base_frame.h>
34#include <pcbnew_settings.h>
35#include <pgm_base.h>
38#include <widgets/lib_tree.h>
40#include <footprint.h>
41#include <project_pcb.h>
42#include <kiface_base.h>
43#include <tool/actions.h>
44#include <tool/tool_manager.h>
45#include <widgets/kistatusbar.h>
46
47// When a new footprint is selected, a custom event is sent, for instance to update
48// 3D viewer. So define a FP_SELECTION_EVENT event
49wxDEFINE_EVENT( FP_SELECTION_EVENT, wxCommandEvent );
50
52 const wxArrayString& aFootprintHistoryList,
53 std::function<bool( LIB_TREE_NODE& )> aFilter,
54 std::function<void()> aAcceptHandler,
55 std::function<void()> aEscapeHandler ) :
56 wxPanel( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize ),
57 m_hsplitter( nullptr ),
58 m_vsplitter( nullptr ),
59 m_frame( aFrame ),
60 m_filter( std::move( aFilter ) ),
61 m_acceptHandler( std::move( aAcceptHandler ) ),
62 m_escapeHandler( std::move( aEscapeHandler ) )
63{
64 m_CurrFootprint = nullptr;
66
67 // Ensure libraries are loaded before building the tree. This is necessary when the footprint
68 // chooser is opened from contexts that don't preload libraries (e.g., from Eeschema).
69 footprints->AsyncLoad();
70 footprints->BlockUntilLoaded();
71 footprints->RefreshChangedLibraries();
72
73 m_adapter = FP_TREE_MODEL_ADAPTER::Create( aFrame, footprints );
74 FP_TREE_MODEL_ADAPTER* adapter = static_cast<FP_TREE_MODEL_ADAPTER*>( m_adapter.get() );
75
76 std::vector<LIB_TREE_ITEM*> historyInfos;
77
78 for( const wxString& item : aFootprintHistoryList )
79 {
80 LIB_ID fpid;
81
82 if( fpid.Parse( item ) >= 0 )
83 continue;
84
85 FOOTPRINT* fp = footprints->LoadFootprint( fpid, false );
86
87 if( fp != nullptr )
88 {
89 historyInfos.push_back( fp );
90 m_historyFootprints.push_back( std::unique_ptr<FOOTPRINT>( fp ) );
91 }
92 }
93
94 adapter->DoAddLibrary( wxT( "-- " ) + _( "Recently Used" ) + wxT( " --" ), wxEmptyString,
95 historyInfos, false, true )
97
98 if( historyInfos.size() )
99 adapter->SetPreselectNode( historyInfos[0]->GetLIB_ID(), 0 );
100
101 adapter->SetFilter( &m_filter );
102 adapter->AddLibraries( m_frame );
103
104 // -------------------------------------------------------------------------------------
105 // Construct the actual panel
106 //
107
108 wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
109
110 m_vsplitter = new wxSplitterWindow( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
111 wxSP_LIVE_UPDATE | wxSP_NOBORDER | wxSP_3DSASH );
112
113 m_hsplitter = new wxSplitterWindow( m_vsplitter, wxID_ANY, wxDefaultPosition, wxDefaultSize,
114 wxSP_LIVE_UPDATE | wxSP_NOBORDER | wxSP_3DSASH );
115
116 // Avoid the splitter window being assigned as the Parent to additional windows
117 m_vsplitter->SetExtraStyle( wxWS_EX_TRANSIENT );
118 m_hsplitter->SetExtraStyle( wxWS_EX_TRANSIENT );
119
120 m_detailsPanel = new wxPanel( m_vsplitter );
121 auto detailsSizer = new wxBoxSizer( wxVERTICAL );
122 m_detailsPanel->SetSizer( detailsSizer );
123
124 m_details = new HTML_WINDOW( m_detailsPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize );
125 detailsSizer->Add( m_details, 1, wxEXPAND, 5 );
126 m_detailsPanel->Layout();
127 detailsSizer->Fit( m_detailsPanel );
128
129 m_vsplitter->SetSashGravity( 0.5 );
130
131 // Ensure splitted areas are always shown (i.e. 0 size not allowed) when m_detailsPanel is shown
132 m_vsplitter->SetMinimumPaneSize( 80 ); // arbitrary value but reasonable min size
133 m_vsplitter->SplitHorizontally( m_hsplitter, m_detailsPanel );
134
135 sizer->Add( m_vsplitter, 1, wxEXPAND, 5 );
136
137 m_tree = new LIB_TREE( m_hsplitter, wxT( "footprints" ), m_adapter,
139
140 m_hsplitter->SetSashGravity( 0.8 );
141 m_hsplitter->SetMinimumPaneSize( 20 );
142
143 m_RightPanel = new wxPanel( m_hsplitter );
144 m_RightPanelSizer = new wxBoxSizer( wxVERTICAL );
145
147 m_preview_ctrl->SetUserUnits( m_frame->GetUserUnits() );
148 m_RightPanelSizer->Add( m_preview_ctrl, 1, wxEXPAND, 5 );
149
150 m_RightPanel->SetSizer( m_RightPanelSizer );
151 m_RightPanel->Layout();
153
154 m_hsplitter->SplitVertically( m_tree, m_RightPanel );
155
156 m_dbl_click_timer = new wxTimer( this );
157 m_open_libs_timer = new wxTimer( this );
158
159 SetSizer( sizer );
160
161 m_adapter->FinishTreeInitialization();
162
163 Bind( wxEVT_TIMER, &PANEL_FOOTPRINT_CHOOSER::onCloseTimer, this, m_dbl_click_timer->GetId() );
164 Bind( wxEVT_TIMER, &PANEL_FOOTPRINT_CHOOSER::onOpenLibsTimer, this, m_open_libs_timer->GetId() );
165 Bind( EVT_LIBITEM_SELECTED, &PANEL_FOOTPRINT_CHOOSER::onFootprintSelected, this );
166 Bind( EVT_LIBITEM_CHOSEN, &PANEL_FOOTPRINT_CHOOSER::onFootprintChosen, this );
167 m_frame->Bind( wxEVT_MENU_OPEN, &PANEL_FOOTPRINT_CHOOSER::onMenuOpen, this );
168 m_frame->Bind( wxEVT_MENU_CLOSE, &PANEL_FOOTPRINT_CHOOSER::onMenuClose, this );
169
170 m_details->Connect( wxEVT_CHAR_HOOK,
172 nullptr, this );
173
174 Layout();
175
176 // Open the user's previously opened libraries on timer expiration.
177 // This is done on a timer because we need a gross hack to keep GTK from garbling the
178 // display. Must be longer than the search debounce timer.
179 m_open_libs_timer->StartOnce( 300 );
180}
181
182
184{
185 m_frame->Unbind( wxEVT_MENU_OPEN, &PANEL_FOOTPRINT_CHOOSER::onMenuOpen, this );
186 m_frame->Unbind( wxEVT_MENU_CLOSE, &PANEL_FOOTPRINT_CHOOSER::onMenuClose, this );
187 Unbind( wxEVT_TIMER, &PANEL_FOOTPRINT_CHOOSER::onCloseTimer, this );
188 Unbind( EVT_LIBITEM_SELECTED, &PANEL_FOOTPRINT_CHOOSER::onFootprintSelected, this );
189 Unbind( EVT_LIBITEM_CHOSEN, &PANEL_FOOTPRINT_CHOOSER::onFootprintChosen, this );
190
191 m_details->Disconnect( wxEVT_CHAR_HOOK, wxKeyEventHandler( PANEL_FOOTPRINT_CHOOSER::OnDetailsCharHook ),
192 nullptr, this );
193
194 // I am not sure the following two lines are necessary, but they will not hurt anyone
195 m_dbl_click_timer->Stop();
196 m_open_libs_timer->Stop();
197 delete m_dbl_click_timer;
198 delete m_open_libs_timer;
199
200 if( PCBNEW_SETTINGS* cfg = GetAppSettings<PCBNEW_SETTINGS>( "pcbnew" ) )
201 {
202 // Save any changes to column widths, etc.
203 m_adapter->SaveSettings();
204
205 cfg->m_FootprintChooser.width = GetParent()->ToDIP( GetParent()->GetSize().x );
206 cfg->m_FootprintChooser.height = GetParent()->ToDIP( GetParent()->GetSize().y );
207 cfg->m_FootprintChooser.sash_h = m_hsplitter->GetSashPosition();
208
209 if( m_vsplitter )
210 cfg->m_FootprintChooser.sash_v = m_vsplitter->GetSashPosition();
211
212 cfg->m_FootprintChooser.sort_mode = m_tree->GetSortMode();
213 }
214}
215
216
217void PANEL_FOOTPRINT_CHOOSER::OnChar( wxKeyEvent& aEvent )
218{
219 if( aEvent.GetKeyCode() == WXK_ESCAPE )
220 {
221 // First escape clears search string, second escape closes
222 if( !m_tree->GetSearchString().IsEmpty() )
223 {
224 m_tree->SetSearchString( wxEmptyString );
225 GetFocusTarget()->SetFocus();
226 return;
227 }
228
230 }
231 else
232 {
233 aEvent.Skip();
234 }
235}
236
237
238void PANEL_FOOTPRINT_CHOOSER::onMenuOpen( wxMenuEvent& aEvent )
239{
240 m_tree->BlockPreview( true );
241 aEvent.Skip();
242}
243
244
245void PANEL_FOOTPRINT_CHOOSER::onMenuClose( wxMenuEvent& aEvent )
246{
247 m_tree->BlockPreview( false );
248 aEvent.Skip();
249}
250
251
253{
254 if( PCBNEW_SETTINGS* settings = GetAppSettings<PCBNEW_SETTINGS>( "pcbnew" ) )
255 {
256 auto horizPixelsFromDU =
257 [&]( int x ) -> int
258 {
259 wxSize sz( x, 0 );
260 return GetParent()->ConvertDialogToPixels( sz ).x;
261 };
262
263 PCBNEW_SETTINGS::FOOTPRINT_CHOOSER& cfg = settings->m_FootprintChooser;
264
265 // The persisted size is stored in DIP so it is independent of the monitor it was saved
266 // on. Restoring raw pixels would scale the window by the DPI ratio when reopened on a
267 // different-scale display, producing a window that spills across monitors.
268 int w = cfg.width > 40 ? GetParent()->FromDIP( cfg.width ) : horizPixelsFromDU( 440 );
269 int h = cfg.height > 40 ? GetParent()->FromDIP( cfg.height ) : horizPixelsFromDU( 340 );
270
271 // Cap to the work area so a stale pre-DIP setting cannot reopen the window across monitors.
272 if( int display = wxDisplay::GetFromWindow( GetParent() ); display != wxNOT_FOUND )
273 {
274 wxRect workArea = wxDisplay( display ).GetClientArea();
275 w = std::min( w, workArea.GetWidth() );
276 h = std::min( h, workArea.GetHeight() );
277 }
278
279 GetParent()->SetSize( wxSize( w, h ) );
280 GetParent()->Layout();
281
282 // We specify the width of the right window (m_symbol_view_panel), because specify
283 // the width of the left window does not work as expected when SetSashGravity() is called
284 if( cfg.sash_h < 0 )
285 cfg.sash_h = horizPixelsFromDU( 220 );
286
287 m_hsplitter->SetSashPosition( cfg.sash_h );
288
289 if( cfg.sash_v < 0 )
290 cfg.sash_v = horizPixelsFromDU( 230 );
291
292 if( m_vsplitter )
293 m_vsplitter->SetSashPosition( cfg.sash_v );
294
296 }
297}
298
299
301{
302 m_preselect = aPreselect;
303 m_adapter->SetPreselectNode( aPreselect, 0 );
304
305 if( m_tree && aPreselect.IsValid() )
306 m_tree->SelectLibId( aPreselect );
307}
308
309
311{
312 return m_tree->GetSelectedLibId();
313}
314
315
316void PANEL_FOOTPRINT_CHOOSER::onCloseTimer( wxTimerEvent& aEvent )
317{
318 // Hack because of eaten MouseUp event. See PANEL_FOOTPRINT_CHOOSER::onFootprintChosen
319 // for the beginning of this spaghetti noodle.
320
321 auto state = wxGetMouseState();
322
323 if( state.LeftIsDown() )
324 {
325 // Mouse hasn't been raised yet, so fire the timer again. Otherwise the
326 // purpose of this timer is defeated.
328 }
329 else
330 {
332 }
333}
334
335
337
339{
340 // Freeze across both steps so the expand and the re-scroll paint as a single final
341 // state, otherwise the selected row visibly shifts and snaps back.
342 m_tree->Freeze();
343
344 if( PCBNEW_SETTINGS* cfg = dynamic_cast<PCBNEW_SETTINGS*>( Kiface().KifaceSettings() ) )
345 m_adapter->OpenLibs( cfg->m_LibTree.open_libs );
346
347 // OpenLibs reshuffles the tree, so re-assert the preselected footprint.
348 if( m_preselect.IsValid() )
349 m_tree->SelectLibId( m_preselect );
350
351 m_tree->Thaw();
352}
353
354
356{
357 if( !m_preview_ctrl || !m_preview_ctrl->IsInitialized() )
358 return;
359
360 LIB_ID lib_id = m_tree->GetSelectedLibId();
361
362 if( !lib_id.IsValid() )
363 {
364 m_preview_ctrl->SetStatusText( _( "No footprint selected" ) );
365 }
366 else
367 {
368 m_preview_ctrl->ClearStatus();
369 m_preview_ctrl->DisplayFootprint( lib_id );
370 }
371
372 m_CurrFootprint = static_cast<FOOTPRINT_PREVIEW_PANEL*>( m_preview_ctrl->GetPreviewPanel() )->GetCurrentFootprint();
373
374 // Send a FP_SELECTION_EVENT event after a footprint change
375 wxCommandEvent event( FP_SELECTION_EVENT, GetId() );
376 event.SetEventObject( this );
377
378 ProcessWindowEvent( event );
379}
380
381
382void PANEL_FOOTPRINT_CHOOSER::onFootprintChosen( wxCommandEvent& aEvent )
383{
384 if( m_tree->GetSelectedLibId().IsValid() )
385 {
386 // Got a selection. We can't just end the modal dialog here, because wx leaks some
387 // events back to the parent window (in particular, the MouseUp following a double click).
388 //
389 // NOW, here's where it gets really fun. wxTreeListCtrl eats MouseUp. This isn't really
390 // feasible to bypass without a fully custom wxDataViewCtrl implementation, and even then
391 // might not be fully possible (docs are vague). To get around this, we use a one-shot
392 // timer to schedule the dialog close.
393 //
394 // See PANEL_FOOTPRINT_CHOOSER::onCloseTimer for the other end of this spaghetti noodle.
396 }
397}
398
399
401{
402 if( m_details && e.GetKeyCode() == 'C' && e.ControlDown() &&
403 !e.AltDown() && !e.ShiftDown() && !e.MetaDown() )
404 {
405 wxString txt = m_details->SelectionToText();
406 wxLogNull doNotLog; // disable logging of failed clipboard actions
407
408 if( wxTheClipboard->Open() )
409 {
410 wxTheClipboard->SetData( new wxTextDataObject( txt ) );
411 wxTheClipboard->Flush(); // Allow data to be available after closing KiCad
412 wxTheClipboard->Close();
413 }
414 }
415 else
416 {
417 e.Skip();
418 }
419}
KIFACE_BASE & Kiface()
Global KIFACE_BASE "get" accessor.
An interface to the global shared library manager that is schematic-specific and linked to one projec...
FOOTPRINT * LoadFootprint(const wxString &aNickname, const wxString &aName, bool aKeepUUID)
Load a FOOTPRINT having aName from the library given by aNickname.
Panel that renders a single footprint via Cairo GAL, meant to be exported through Kiface.
FOOTPRINT * GetCurrentFootprint() const
void AddLibraries(EDA_BASE_FRAME *aParent)
static wxObjectDataPtr< LIB_TREE_MODEL_ADAPTER > Create(PCB_BASE_FRAME *aParent, FOOTPRINT_LIBRARY_ADAPTER *aLibs)
Factory function: create a model adapter in a reference-counting container.
Add dark theme support to wxHtmlWindow.
Definition html_window.h:31
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
void AsyncLoad()
Loads all available libraries for this adapter type in the background.
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
int Parse(const UTF8 &aId, bool aFix=false)
Parse LIB_ID with the information from aId.
Definition lib_id.cpp:65
bool IsValid() const
Check if this LID_ID is valid.
Definition lib_id.h:168
void SetPreselectNode(const LIB_ID &aLibId, int aUnit)
Set the symbol name to be selected if there are no search results.
LIB_TREE_NODE_LIBRARY & DoAddLibrary(const wxString &aNodeName, const wxString &aDesc, const std::vector< LIB_TREE_ITEM * > &aItemList, bool pinned, bool presorted)
Add the given list of symbols by alias.
void SetFilter(std::function< bool(LIB_TREE_NODE &aNode)> *aFilter)
Set the filter.
Model class in the component selector Model-View-Adapter (mediated MVC) architecture.
Widget displaying a tree of symbols with optional search text control and description panel.
Definition lib_tree.h:46
@ ALL_WIDGETS
Definition lib_tree.h:55
void onFootprintSelected(wxCommandEvent &aEvent)
wxObjectDataPtr< LIB_TREE_MODEL_ADAPTER > m_adapter
void onCloseTimer(wxTimerEvent &aEvent)
std::function< void()> m_acceptHandler
FOOTPRINT_PREVIEW_WIDGET * m_preview_ctrl
void OnChar(wxKeyEvent &aEvent)
std::function< void()> m_escapeHandler
void onMenuOpen(wxMenuEvent &aEvent)
Handle parent frame menu events to block tree preview.
static constexpr int DblClickDelay
std::function< bool(LIB_TREE_NODE &)> m_filter
void OnDetailsCharHook(wxKeyEvent &aEvt)
void onOpenLibsTimer(wxTimerEvent &aEvent)
void SetPreselect(const LIB_ID &aPreselect)
PANEL_FOOTPRINT_CHOOSER(PCB_BASE_FRAME *aFrame, wxTopLevelWindow *aParent, const wxArrayString &aFootprintHistoryList, std::function< bool(LIB_TREE_NODE &)> aFilter, std::function< void()> aAcceptHandler, std::function< void()> aEscapeHandler)
Create dialog to choose component.
std::vector< std::unique_ptr< FOOTPRINT > > m_historyFootprints
void onMenuClose(wxMenuEvent &aEvent)
void onFootprintChosen(wxCommandEvent &aEvent)
Handle the selection of an item.
LIB_ID GetSelectedLibId() const
To be called after this dialog returns from ShowModal().
Base PCB main window class for Pcbnew, Gerbview, and CvPcb footprint viewer.
static FOOTPRINT_LIBRARY_ADAPTER * FootprintLibAdapter(PROJECT *aProject)
#define _(s)
STL namespace.
wxDEFINE_EVENT(FP_SELECTION_EVENT, wxCommandEvent)
see class PGM_BASE
T * GetAppSettings(const char *aFilename)