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, 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
27#include <wx/button.h>
28#include <wx/clipbrd.h>
29#include <wx/log.h>
30#include <wx/panel.h>
31#include <wx/sizer.h>
32#include <wx/splitter.h>
33#include <wx/timer.h>
34#include <wx/wxhtml.h>
35#include <pcb_base_frame.h>
36#include <pcbnew_settings.h>
37#include <pgm_base.h>
40#include <widgets/lib_tree.h>
43#include <footprint_info_impl.h>
44#include <project_pcb.h>
45#include <kiface_base.h>
46#include <tool/actions.h>
47#include <tool/tool_manager.h>
48#include <widgets/kistatusbar.h>
49
50// When a new footprint is selected, a custom event is sent, for instance to update
51// 3D viewer. So define a FP_SELECTION_EVENT event
52wxDEFINE_EVENT( FP_SELECTION_EVENT, wxCommandEvent );
53
55 const wxArrayString& aFootprintHistoryList,
56 std::function<bool( LIB_TREE_NODE& )> aFilter,
57 std::function<void()> aAcceptHandler,
58 std::function<void()> aEscapeHandler ) :
59 wxPanel( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize ),
60 m_hsplitter( nullptr ),
61 m_vsplitter( nullptr ),
62 m_frame( aFrame ),
63 m_filter( std::move( aFilter ) ),
64 m_acceptHandler( std::move( aAcceptHandler ) ),
65 m_escapeHandler( std::move( aEscapeHandler ) )
66{
67 m_CurrFootprint = nullptr;
69
70 // Load footprint files:
71 auto* progressReporter = new WX_PROGRESS_REPORTER( aParent, _( "Load Footprint Libraries" ), 1,
73 GFootprintList.ReadFootprintFiles( footprints, nullptr, progressReporter );
74
75 // Force immediate deletion of the WX_PROGRESS_REPORTER. Do not use Destroy(), or use
76 // Destroy() followed by wxSafeYield() because on Windows, APP_PROGRESS_DIALOG and
77 // WX_PROGRESS_REPORTER have some side effects on the event loop manager. For instance, a
78 // subsequent call to ShowModal() or ShowQuasiModal() for a dialog following the use of a
79 // WX_PROGRESS_REPORTER results in incorrect modal or quasi modal behavior.
80 delete progressReporter;
81
82 if( GFootprintList.GetErrorCount() )
83 {
84 // Show errors in status bar instead of popup dialog
85 if( KISTATUSBAR* statusBar = dynamic_cast<KISTATUSBAR*>( aFrame->GetStatusBar() ) )
86 statusBar->SetLoadWarningMessages( GFootprintList.GetErrorMessages() );
87 }
88
89 m_adapter = FP_TREE_MODEL_ADAPTER::Create( aFrame, footprints );
90 FP_TREE_MODEL_ADAPTER* adapter = static_cast<FP_TREE_MODEL_ADAPTER*>( m_adapter.get() );
91
92 std::vector<LIB_TREE_ITEM*> historyInfos;
93
94 for( const wxString& item : aFootprintHistoryList )
95 {
96 LIB_TREE_ITEM* fp_info = GFootprintList.GetFootprintInfo( item );
97
98 // this can be null, for example, if the footprint has been deleted from a library.
99 if( fp_info != nullptr )
100 historyInfos.push_back( fp_info );
101 }
102
103 adapter->DoAddLibrary( wxT( "-- " ) + _( "Recently Used" ) + wxT( " --" ), wxEmptyString,
104 historyInfos, false, true )
105 .m_IsRecentlyUsedGroup = true;
106
107 if( historyInfos.size() )
108 adapter->SetPreselectNode( historyInfos[0]->GetLIB_ID(), 0 );
109
110 adapter->SetFilter( &m_filter );
111 adapter->AddLibraries( m_frame );
112
113 // -------------------------------------------------------------------------------------
114 // Construct the actual panel
115 //
116
117 wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
118
119 m_vsplitter = new wxSplitterWindow( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
120 wxSP_LIVE_UPDATE | wxSP_NOBORDER | wxSP_3DSASH );
121
122 m_hsplitter = new wxSplitterWindow( m_vsplitter, wxID_ANY, wxDefaultPosition, wxDefaultSize,
123 wxSP_LIVE_UPDATE | wxSP_NOBORDER | wxSP_3DSASH );
124
125 // Avoid the splitter window being assigned as the Parent to additional windows
126 m_vsplitter->SetExtraStyle( wxWS_EX_TRANSIENT );
127 m_hsplitter->SetExtraStyle( wxWS_EX_TRANSIENT );
128
129 m_detailsPanel = new wxPanel( m_vsplitter );
130 auto detailsSizer = new wxBoxSizer( wxVERTICAL );
131 m_detailsPanel->SetSizer( detailsSizer );
132
133 m_details = new HTML_WINDOW( m_detailsPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize );
134 detailsSizer->Add( m_details, 1, wxEXPAND, 5 );
135 m_detailsPanel->Layout();
136 detailsSizer->Fit( m_detailsPanel );
137
138 m_vsplitter->SetSashGravity( 0.5 );
139
140 // Ensure splitted areas are always shown (i.e. 0 size not allowed) when m_detailsPanel is shown
141 m_vsplitter->SetMinimumPaneSize( 80 ); // arbitrary value but reasonable min size
142 m_vsplitter->SplitHorizontally( m_hsplitter, m_detailsPanel );
143
144 sizer->Add( m_vsplitter, 1, wxEXPAND, 5 );
145
146 m_tree = new LIB_TREE( m_hsplitter, wxT( "footprints" ), m_adapter,
148
149 m_hsplitter->SetSashGravity( 0.8 );
150 m_hsplitter->SetMinimumPaneSize( 20 );
151
152 m_RightPanel = new wxPanel( m_hsplitter );
153 m_RightPanelSizer = new wxBoxSizer( wxVERTICAL );
154
156 m_preview_ctrl->SetUserUnits( m_frame->GetUserUnits() );
157 m_RightPanelSizer->Add( m_preview_ctrl, 1, wxEXPAND, 5 );
158
159 m_RightPanel->SetSizer( m_RightPanelSizer );
160 m_RightPanel->Layout();
162
163 m_hsplitter->SplitVertically( m_tree, m_RightPanel );
164
165 m_dbl_click_timer = new wxTimer( this );
166 m_open_libs_timer = new wxTimer( this );
167
168 SetSizer( sizer );
169
170 m_adapter->FinishTreeInitialization();
171
172 Bind( wxEVT_TIMER, &PANEL_FOOTPRINT_CHOOSER::onCloseTimer, this, m_dbl_click_timer->GetId() );
173 Bind( wxEVT_TIMER, &PANEL_FOOTPRINT_CHOOSER::onOpenLibsTimer, this, m_open_libs_timer->GetId() );
174 Bind( EVT_LIBITEM_SELECTED, &PANEL_FOOTPRINT_CHOOSER::onFootprintSelected, this );
175 Bind( EVT_LIBITEM_CHOSEN, &PANEL_FOOTPRINT_CHOOSER::onFootprintChosen, this );
176 m_frame->Bind( wxEVT_MENU_OPEN, &PANEL_FOOTPRINT_CHOOSER::onMenuOpen, this );
177 m_frame->Bind( wxEVT_MENU_CLOSE, &PANEL_FOOTPRINT_CHOOSER::onMenuClose, this );
178
179 m_details->Connect( wxEVT_CHAR_HOOK,
181 nullptr, this );
182
183 Bind( wxEVT_CHAR_HOOK,
184 [&]( wxKeyEvent& aEvent )
185 {
186 if( aEvent.GetKeyCode() == WXK_ESCAPE )
187 {
188 wxObject* eventSource = aEvent.GetEventObject();
189
190 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( eventSource ) )
191 {
192 // First escape cancels search string value
193 if( textCtrl->GetValue() == m_tree->GetSearchString()
194 && !m_tree->GetSearchString().IsEmpty() )
195 {
196 m_tree->SetSearchString( wxEmptyString );
197 return;
198 }
199 }
200
202 }
203 else
204 {
205 // aEvent.Skip() should be sufficient to allow the normal key events to be
206 // generated (at least according to the wxWidgets documentation). And yet,
207 // here we are.
208 aEvent.DoAllowNextEvent();
209
210 aEvent.Skip();
211 }
212 } );
213
214 Layout();
215
216 // Open the user's previously opened libraries on timer expiration.
217 // This is done on a timer because we need a gross hack to keep GTK from garbling the
218 // display. Must be longer than the search debounce timer.
219 m_open_libs_timer->StartOnce( 300 );
220}
221
222
224{
225 m_frame->Unbind( wxEVT_MENU_OPEN, &PANEL_FOOTPRINT_CHOOSER::onMenuOpen, this );
226 m_frame->Unbind( wxEVT_MENU_CLOSE, &PANEL_FOOTPRINT_CHOOSER::onMenuClose, this );
227 Unbind( wxEVT_TIMER, &PANEL_FOOTPRINT_CHOOSER::onCloseTimer, this );
228 Unbind( EVT_LIBITEM_SELECTED, &PANEL_FOOTPRINT_CHOOSER::onFootprintSelected, this );
229 Unbind( EVT_LIBITEM_CHOSEN, &PANEL_FOOTPRINT_CHOOSER::onFootprintChosen, this );
230
231 m_details->Disconnect( wxEVT_CHAR_HOOK, wxKeyEventHandler( PANEL_FOOTPRINT_CHOOSER::OnDetailsCharHook ),
232 nullptr, this );
233
234 // I am not sure the following two lines are necessary, but they will not hurt anyone
235 m_dbl_click_timer->Stop();
236 m_open_libs_timer->Stop();
237 delete m_dbl_click_timer;
238 delete m_open_libs_timer;
239
240 if( PCBNEW_SETTINGS* cfg = GetAppSettings<PCBNEW_SETTINGS>( "pcbnew" ) )
241 {
242 // Save any changes to column widths, etc.
243 m_adapter->SaveSettings();
244
245 cfg->m_FootprintChooser.width = GetParent()->GetSize().x;
246 cfg->m_FootprintChooser.height = GetParent()->GetSize().y;
247 cfg->m_FootprintChooser.sash_h = m_hsplitter->GetSashPosition();
248
249 if( m_vsplitter )
250 cfg->m_FootprintChooser.sash_v = m_vsplitter->GetSashPosition();
251
252 cfg->m_FootprintChooser.sort_mode = m_tree->GetSortMode();
253 }
254}
255
256
257void PANEL_FOOTPRINT_CHOOSER::onMenuOpen( wxMenuEvent& aEvent )
258{
259 m_tree->BlockPreview( true );
260 aEvent.Skip();
261}
262
263
264void PANEL_FOOTPRINT_CHOOSER::onMenuClose( wxMenuEvent& aEvent )
265{
266 m_tree->BlockPreview( false );
267 aEvent.Skip();
268}
269
270
272{
273 if( PCBNEW_SETTINGS* settings = GetAppSettings<PCBNEW_SETTINGS>( "pcbnew" ) )
274 {
275 auto horizPixelsFromDU =
276 [&]( int x ) -> int
277 {
278 wxSize sz( x, 0 );
279 return GetParent()->ConvertDialogToPixels( sz ).x;
280 };
281
282 PCBNEW_SETTINGS::FOOTPRINT_CHOOSER& cfg = settings->m_FootprintChooser;
283
284 int w = cfg.width < 40 ? horizPixelsFromDU( 440 ) : cfg.width;
285 int h = cfg.height < 40 ? horizPixelsFromDU( 340 ) : cfg.height;
286
287 GetParent()->SetSize( wxSize( w, h ) );
288 GetParent()->Layout();
289
290 // We specify the width of the right window (m_symbol_view_panel), because specify
291 // the width of the left window does not work as expected when SetSashGravity() is called
292 if( cfg.sash_h < 0 )
293 cfg.sash_h = horizPixelsFromDU( 220 );
294
295 m_hsplitter->SetSashPosition( cfg.sash_h );
296
297 if( cfg.sash_v < 0 )
298 cfg.sash_v = horizPixelsFromDU( 230 );
299
300 if( m_vsplitter )
301 m_vsplitter->SetSashPosition( cfg.sash_v );
302
304 }
305}
306
307
309{
310 m_adapter->SetPreselectNode( aPreselect, 0 );
311}
312
313
315{
316 return m_tree->GetSelectedLibId();
317}
318
319
320void PANEL_FOOTPRINT_CHOOSER::onCloseTimer( wxTimerEvent& aEvent )
321{
322 // Hack because of eaten MouseUp event. See PANEL_FOOTPRINT_CHOOSER::onFootprintChosen
323 // for the beginning of this spaghetti noodle.
324
325 auto state = wxGetMouseState();
326
327 if( state.LeftIsDown() )
328 {
329 // Mouse hasn't been raised yet, so fire the timer again. Otherwise the
330 // purpose of this timer is defeated.
332 }
333 else
334 {
336 }
337}
338
339
341
343{
344 if( PCBNEW_SETTINGS* cfg = dynamic_cast<PCBNEW_SETTINGS*>( Kiface().KifaceSettings() ) )
345 m_adapter->OpenLibs( cfg->m_LibTree.open_libs );
346}
347
348
350{
351 if( !m_preview_ctrl || !m_preview_ctrl->IsInitialized() )
352 return;
353
354 LIB_ID lib_id = m_tree->GetSelectedLibId();
355
356 if( !lib_id.IsValid() )
357 {
358 m_preview_ctrl->SetStatusText( _( "No footprint selected" ) );
359 }
360 else
361 {
362 m_preview_ctrl->ClearStatus();
363 m_preview_ctrl->DisplayFootprint( lib_id );
364 }
365
366 m_CurrFootprint = static_cast<FOOTPRINT_PREVIEW_PANEL*>( m_preview_ctrl->GetPreviewPanel() )->GetCurrentFootprint();
367
368 // Send a FP_SELECTION_EVENT event after a footprint change
369 wxCommandEvent event( FP_SELECTION_EVENT, GetId() );
370 event.SetEventObject( this );
371
372 ProcessWindowEvent( event );
373}
374
375
376void PANEL_FOOTPRINT_CHOOSER::onFootprintChosen( wxCommandEvent& aEvent )
377{
378 if( m_tree->GetSelectedLibId().IsValid() )
379 {
380 // Got a selection. We can't just end the modal dialog here, because wx leaks some
381 // events back to the parent window (in particular, the MouseUp following a double click).
382 //
383 // NOW, here's where it gets really fun. wxTreeListCtrl eats MouseUp. This isn't really
384 // feasible to bypass without a fully custom wxDataViewCtrl implementation, and even then
385 // might not be fully possible (docs are vague). To get around this, we use a one-shot
386 // timer to schedule the dialog close.
387 //
388 // See PANEL_FOOTPRINT_CHOOSER::onCloseTimer for the other end of this spaghetti noodle.
390 }
391}
392
393
395{
396 if( m_details && e.GetKeyCode() == 'C' && e.ControlDown() &&
397 !e.AltDown() && !e.ShiftDown() && !e.MetaDown() )
398 {
399 wxString txt = m_details->SelectionToText();
400 wxLogNull doNotLog; // disable logging of failed clipboard actions
401
402 if( wxTheClipboard->Open() )
403 {
404 wxTheClipboard->SetData( new wxTextDataObject( txt ) );
405 wxTheClipboard->Flush(); // Allow data to be available after closing KiCad
406 wxTheClipboard->Close();
407 }
408 }
409 else
410 {
411 e.Skip();
412 }
413}
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...
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:35
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:49
bool IsValid() const
Check if this LID_ID is valid.
Definition lib_id.h:172
A mix-in to provide polymorphism between items stored in libraries (symbols, aliases and footprints).
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:48
@ ALL_WIDGETS
Definition lib_tree.h:57
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
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.
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)
Multi-thread safe progress reporter dialog, intended for use of tasks that parallel reporting back of...
#define _(s)
FOOTPRINT_LIST_IMPL GFootprintList
The global footprint info table.
Definition cvpcb.cpp:138
STL namespace.
wxDEFINE_EVENT(FP_SELECTION_EVENT, wxCommandEvent)
see class PGM_BASE
T * GetAppSettings(const char *aFilename)
#define PR_CAN_ABORT