KiCad PCB EDA Suite
Loading...
Searching...
No Matches
panel_design_block_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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <pgm_base.h>
21#include <design_block.h>
22#include <design_block_pane.h>
26#include <kiface_base.h>
27#include <kiway_holder.h>
28#include <eda_draw_frame.h>
30#include <widgets/lib_tree.h>
35#include <string_utils.h>
36#include <wx/log.h>
37#include <wx/panel.h>
38#include <wx/sizer.h>
39#include <wx/splitter.h>
40#include <wx/timer.h>
41#include <wx/wxhtml.h>
42#include <wx/msgdlg.h>
44
45
47
48
50 std::vector<LIB_ID>& aHistoryList,
51 std::function<void()> aSelectHandler,
52 TOOL_INTERACTIVE* aContextMenuTool ) :
53
54 wxPanel( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize ),
55 m_dbl_click_timer( nullptr ),
56 m_open_libs_timer( nullptr ),
57 m_vsplitter( nullptr ),
58 m_tree( nullptr ),
59 m_preview( nullptr ),
60 m_parent( aParent ),
61 m_frame( aFrame ),
62 m_selectHandler( std::move( aSelectHandler ) ),
63 m_historyList( aHistoryList )
64{
65 DESIGN_BLOCK_LIBRARY_ADAPTER* libs = m_frame->Prj().DesignBlockLibs();
66
68 m_frame, libs, m_frame->config()->m_DesignBlockChooserPanel.tree, aContextMenuTool );
69
70 // -------------------------------------------------------------------------------------
71 // Construct the actual panel
72 //
73
74 wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
75
76 m_vsplitter = new wxSplitterWindow( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
77 wxSP_LIVE_UPDATE | wxSP_NOBORDER | wxSP_3DSASH );
78
79
80 // Avoid the splitter window being assigned as the parent to additional windows.
81 m_vsplitter->SetExtraStyle( wxWS_EX_TRANSIENT );
82
83 wxPanel* treePanel = new wxPanel( m_vsplitter );
84 wxBoxSizer* treeSizer = new wxBoxSizer( wxVERTICAL );
85 treePanel->SetSizer( treeSizer );
86
87 m_detailsPanel = new wxPanel( m_vsplitter );
88 m_detailsSizer = new wxBoxSizer( wxVERTICAL );
89 m_detailsPanel->SetSizer( m_detailsSizer );
90
91 m_detailsPanel->Layout();
93
94 m_vsplitter->SetSashGravity( 0.5 );
95 m_vsplitter->SetMinimumPaneSize( 20 );
96 m_vsplitter->SplitHorizontally( treePanel, m_detailsPanel );
97
98 sizer->Add( m_vsplitter, 1, wxEXPAND, 5 );
99
100 m_tree = new LIB_TREE( treePanel, wxT( "design_blocks" ), m_adapter, LIB_TREE::FLAGS::ALL_WIDGETS, nullptr );
101
102 treeSizer->Add( m_tree, 1, wxEXPAND, 5 );
103 treePanel->Layout();
104 treeSizer->Fit( treePanel );
105
106 RefreshLibs();
107 m_adapter->FinishTreeInitialization();
108
109 m_tree->SetSearchString( g_designBlockSearchString );
110
111 m_dbl_click_timer = new wxTimer( this );
112 m_open_libs_timer = new wxTimer( this );
113
114 SetSizer( sizer );
115
116 Layout();
117
118 Bind( wxEVT_TIMER, &PANEL_DESIGN_BLOCK_CHOOSER::onCloseTimer, this, m_dbl_click_timer->GetId() );
119 Bind( wxEVT_TIMER, &PANEL_DESIGN_BLOCK_CHOOSER::onOpenLibsTimer, this, m_open_libs_timer->GetId() );
120 Bind( EVT_LIBITEM_CHOSEN, &PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockChosen, this );
121 Bind( wxEVT_CHAR_HOOK, &PANEL_DESIGN_BLOCK_CHOOSER::OnChar, this );
122
123 // Open the user's previously opened libraries on timer expiration.
124 // This is done on a timer because we need a gross hack to keep GTK from garbling the
125 // display. Must be longer than the search debounce timer.
126 m_open_libs_timer->StartOnce( 300 );
127}
128
129
131{
132 Unbind( wxEVT_TIMER, &PANEL_DESIGN_BLOCK_CHOOSER::onCloseTimer, this );
133 Unbind( EVT_LIBITEM_SELECTED, &PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockSelected, this );
134 Unbind( EVT_LIBITEM_CHOSEN, &PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockChosen, this );
135 Unbind( wxEVT_CHAR_HOOK, &PANEL_DESIGN_BLOCK_CHOOSER::OnChar, this );
136
137 // Stop the timer during destruction early to avoid potential race conditions (that do happen)
138 m_dbl_click_timer->Stop();
139 m_open_libs_timer->Stop();
140 delete m_dbl_click_timer;
141 delete m_open_libs_timer;
142}
143
144
146{
147 g_designBlockSearchString = m_tree->GetSearchString();
148
149 if( APP_SETTINGS_BASE* cfg = m_frame->config() )
150 {
151 // Save any changes to column widths, etc.
152 m_adapter->SaveSettings();
153
154 cfg->m_DesignBlockChooserPanel.width = GetParent()->ToDIP( GetParent()->GetSize().x );
155 cfg->m_DesignBlockChooserPanel.height = GetParent()->ToDIP( GetParent()->GetSize().y );
156 cfg->m_DesignBlockChooserPanel.sash_pos_v = m_vsplitter->GetSashPosition();
157 cfg->m_DesignBlockChooserPanel.sort_mode = m_tree->GetSortMode();
158 }
159}
160
161
163{
164 if( m_tree )
165 m_tree->ShowChangedLanguage();
166}
167
168
170 std::function<DESIGN_BLOCK_PREVIEW_WIDGET*( wxWindow* aParent )> aFactory )
171{
172 m_previewFactory = std::move( aFactory );
173
174 // Only a modern canvas costs a render thread and GPU device. Others are built with the pane,
175 // since creating them later reorders the startup of the editor's own canvas.
176 if( m_frame->GetCanvas() && m_frame->GetCanvas()->GetBackend() == EDA_DRAW_PANEL_GAL::GAL_TYPE_MODERN )
177 {
179 return;
180 }
181
183 m_detailsSizer->Add( m_preview, 1, wxEXPAND, 5 );
184 Layout();
185}
186
187
189{
190 m_detailsPanel->Unbind( wxEVT_PAINT, &PANEL_DESIGN_BLOCK_CHOOSER::onDetailsPaint, this );
191
192 // Creating a child window from inside a paint handler is not safe on every platform
194 aEvent.Skip();
195}
196
197
199{
201 return;
202
204 m_detailsSizer->Add( m_preview, 1, wxEXPAND, 5 );
205 m_detailsPanel->Layout();
206
207 // A block may have been selected while the pane was hidden
209}
210
211
212void PANEL_DESIGN_BLOCK_CHOOSER::OnChar( wxKeyEvent& aEvent )
213{
214 if( aEvent.GetKeyCode() == WXK_ESCAPE )
215 {
216 wxObject* eventSource = aEvent.GetEventObject();
217
218 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( eventSource ) )
219 {
220 // First escape cancels search string value
221 if( textCtrl->GetValue() == m_tree->GetSearchString() && !m_tree->GetSearchString().IsEmpty() )
222 {
223 m_tree->SetSearchString( wxEmptyString );
224 return;
225 }
226 }
227 }
228 else
229 {
230 aEvent.Skip();
231 }
232}
233
234
236{
237 if( APP_SETTINGS_BASE* cfg = m_frame->config() )
238 {
239 auto horizPixelsFromDU =
240 [&]( int x ) -> int
241 {
242 wxSize sz( x, 0 );
243 return GetParent()->ConvertDialogToPixels( sz ).x;
244 };
245
246 auto vertPixelsFromDU =
247 [&]( int y ) -> int
248 {
249 wxSize sz( 0, y );
250 return GetParent()->ConvertDialogToPixels( sz ).y;
251 };
252
253 APP_SETTINGS_BASE::PANEL_DESIGN_BLOCK_CHOOSER& panelCfg = cfg->m_DesignBlockChooserPanel;
254
255 int w = panelCfg.width > 40 ? panelCfg.width : horizPixelsFromDU( 440 );
256 int h = panelCfg.height > 40 ? panelCfg.height : vertPixelsFromDU( 340 );
257
258 GetParent()->SetSize( wxSize( w, h ) );
259 GetParent()->Layout();
260
261 // We specify the width of the right window (m_design_block_view_panel), because specify
262 // the width of the left window does not work as expected when SetSashGravity() is called
263
264 if( panelCfg.sash_pos_h < 0 )
265 panelCfg.sash_pos_h = horizPixelsFromDU( 220 );
266
267 if( panelCfg.sash_pos_v < 0 )
268 panelCfg.sash_pos_v = vertPixelsFromDU( 230 );
269
270 if( m_vsplitter )
271 m_vsplitter->SetSashPosition( panelCfg.sash_pos_v );
272
273 m_adapter->SetSortMode( (LIB_TREE_MODEL_ADAPTER::SORT_MODE) panelCfg.sort_mode );
274 }
275}
276
277
279{
280 // Unselect before syncing to avoid null reference in the adapter
281 // if a selected item is removed during the sync
282 LIB_ID savedSelection = GetSelectedLibId();
283 m_tree->Unselect();
284
286
287 {
288 // ClearLibraries frees nodes the tree control still holds rows for, and the repopulate
289 // reads from disk, so the view stays detached across both
290 LIB_TREE_MODEL_ADAPTER::ResetTreeView resetGuard( *adapter );
291
292 adapter->ClearLibraries();
293
295
296 if( !m_historyList.empty() )
297 adapter->SetPreselectNode( m_historyList[0], 0 );
298
299 adapter->AddLibraries( m_frame );
300 }
301
302 m_tree->Regenerate( true );
303
304 if( savedSelection.IsValid() )
305 SelectLibId( savedSelection );
306
307 Refresh();
308}
309
310
312{
313 m_preselect = aPreselect;
314 m_adapter->SetPreselectNode( aPreselect, 0 );
315
316 if( m_tree && aPreselect.IsValid() )
317 m_tree->SelectLibId( aPreselect );
318}
319
320
322{
323 return m_tree->GetSelectedLibId( aUnit );
324}
325
326
328{
329 m_tree->CenterLibId( aLibId );
330 m_tree->SelectLibId( aLibId );
331}
332
333
335{
336 // Hack because of eaten MouseUp event. See PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockChosen
337 // for the beginning of this spaghetti noodle.
338
339 wxMouseState state = wxGetMouseState();
340
341 if( state.LeftIsDown() )
342 {
343 // Mouse hasn't been raised yet, so fire the timer again. Otherwise the
344 // purpose of this timer is defeated.
346 }
347 else
348 {
349 m_frame->GetCanvas()->SetFocus();
351 addDesignBlockToHistory( m_tree->GetSelectedLibId() );
352 }
353}
354
355
357{
358 if( APP_SETTINGS_BASE* cfg = m_frame->config() )
359 m_adapter->OpenLibs( cfg->m_DesignBlockChooserPanel.tree.open_libs );
360
361 // Bind this now se we don't spam the event queue with EVT_LIBITEM_SELECTED events during
362 // the initial load.
363 Bind( EVT_LIBITEM_SELECTED, &PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockSelected, this );
364
365 if( m_preselect.IsValid() )
367}
368
369
374
375
377{
378 if( !m_preview || !m_preview->IsInitialized() )
379 return;
380
381 if( GetSelectedLibId().IsValid() )
382 {
383 std::unique_ptr<DESIGN_BLOCK> designBlock( m_parent->GetDesignBlock( GetSelectedLibId(), true, false ) );
384 m_preview->DisplayDesignBlock( designBlock.get() );
385 }
386}
387
388
390{
391 if( m_tree->GetSelectedLibId().IsValid() )
392 {
393 // Got a selection. We can't just end the modal dialog here, because wx leaks some events
394 // back to the parent window (in particular, the MouseUp following a double click).
395 //
396 // NOW, here's where it gets really fun. wxTreeListCtrl eats MouseUp. This isn't really
397 // feasible to bypass without a fully custom wxDataViewCtrl implementation, and even then
398 // might not be fully possible (docs are vague). To get around this, we use a one-shot
399 // timer to schedule the dialog close.
400 //
401 // See PANEL_DESIGN_BLOCK_CHOOSER::onCloseTimer for the other end of this spaghetti noodle.
403 }
404}
405
407{
408 LIB_ID savedId = GetSelectedLibId();
409
410 m_tree->Unselect();
411
412 // Remove duplicates
413 for( int i = (int) m_historyList.size() - 1; i >= 0; --i )
414 {
415 if( m_historyList[i] == aLibId )
416 m_historyList.erase( m_historyList.begin() + i );
417 }
418
419 // Add the new name at the beginning of the history list
420 m_historyList.insert( m_historyList.begin(), aLibId );
421
422 // Remove extra names
423 while( m_historyList.size() >= 8 )
424 m_historyList.pop_back();
425
426 // Detaching the view collapses it, and Regenerate() can only save state it can still read,
427 // so carry the open libraries across by nickname
428 std::vector<wxString> openLibs = m_adapter->GetOpenLibs();
429
430 {
431 // rebuildHistoryNode frees the recently-used nodes the tree control still holds rows for
433
435 }
436
437 m_tree->Regenerate( true );
438 m_adapter->OpenLibs( openLibs );
439
440 SelectLibId( savedId );
441}
442
443
445{
446 m_adapter->RemoveGroup( true, false );
447
448 // Build the history list
449 std::vector<LIB_TREE_ITEM*> historyInfos;
450
451 DESIGN_BLOCK_LIBRARY_ADAPTER* adapter = m_frame->Prj().DesignBlockLibs();
452
453 for( const LIB_ID& lib : m_historyList )
454 {
455 LIB_TREE_ITEM* info = adapter->LoadDesignBlock( lib.GetLibNickname(), lib.GetLibItemName() );
456
457 // this can be null, for example, if the design block has been deleted from a library.
458 if( info != nullptr )
459 historyInfos.push_back( info );
460 }
461
462 m_adapter->DoAddLibrary( wxT( "-- " ) + _( "Recently Used" ) + wxT( " --" ), wxEmptyString,
463 historyInfos, false, true )
464 .m_IsRecentlyUsedGroup = true;
465}
466
467
468void PANEL_DESIGN_BLOCK_CHOOSER::displayErrors( wxTopLevelWindow* aWindow )
469{
470 // @todo: go to a more HTML !<table>! ? centric output, possibly with recommendations
471 // for remedy of errors. Add numeric error codes to PARSE_ERROR, and switch on them for
472 // remedies, etc. Full access is provided to everything in every exception!
473
474 HTML_MESSAGE_BOX dlg( aWindow, _( "Load Error" ) );
475
476 dlg.MessageSet( _( "Errors were encountered loading design blocks:" ) );
477
478 wxString msg;
479 // TODO(JE) library tables - this function isn't even called, but would need fixup if so
480#if 0
481 while( std::unique_ptr<IO_ERROR> error = DESIGN_BLOCK_LIB_TABLE::GetGlobalList().PopError() )
482 {
483 wxString tmp = EscapeHTML( error->Problem() );
484
485 // Preserve new lines in error messages so queued errors don't run together.
486 tmp.Replace( wxS( "\n" ), wxS( "<BR>" ) );
487 msg += wxT( "<p>" ) + tmp + wxT( "</p>" );
488 }
489#endif
490 dlg.AddHTML_Text( msg );
491
492 dlg.ShowModal();
493}
APP_SETTINGS_BASE is a settings class that should be derived for each standalone KiCad application.
DESIGN_BLOCK * LoadDesignBlock(const wxString &aNickname, const wxString &aDesignBlockName, bool aKeepUUID=false, wxString *aErrorMsg=nullptr)
Load a design block having aDesignBlockName from the library given by aNickname.
static wxObjectDataPtr< LIB_TREE_MODEL_ADAPTER > Create(EDA_BASE_FRAME *aParent, DESIGN_BLOCK_LIBRARY_ADAPTER *aLibs, APP_SETTINGS_BASE::LIB_TREE &aSettings, TOOL_INTERACTIVE *aContextMenuTool)
Factory function: create a model adapter in a reference-counting container.
int ShowModal() override
The base class for create windows for drawing purpose.
@ GAL_TYPE_MODERN
RHI implementation, Vulkan or Metal on macOS.
void MessageSet(const wxString &message)
Add a message (in bold) to message list.
void AddHTML_Text(const wxString &message)
Add HTML text (without any change) to message list.
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
bool IsValid() const
Check if this LID_ID is valid.
Definition lib_id.h:168
A mix-in to provide polymorphism between items stored in libraries (symbols, aliases and footprints).
RAII guard that detaches the GtkTreeView from the model across a tree rebuild so a deferred frame-clo...
void SetPreselectNode(const LIB_ID &aLibId, int aUnit)
Set the symbol name to be selected if there are no search results.
Widget displaying a tree of symbols with optional search text control and description panel.
Definition lib_tree.h:44
@ ALL_WIDGETS
Definition lib_tree.h:53
void SetPreviewWidgetFactory(std::function< DESIGN_BLOCK_PREVIEW_WIDGET *(wxWindow *aParent)> aFactory)
Set how the preview is built.
DESIGN_BLOCK_PREVIEW_WIDGET * m_preview
void onOpenLibsTimer(wxTimerEvent &aEvent)
void addDesignBlockToHistory(const LIB_ID &aLibId)
PANEL_DESIGN_BLOCK_CHOOSER(EDA_DRAW_FRAME *aFrame, DESIGN_BLOCK_PANE *aParent, std::vector< LIB_ID > &aHistoryList, std::function< void()> aSelectHandler, TOOL_INTERACTIVE *aContextMenuTool)
Panel for using design blocks.
void onDetailsPaint(wxPaintEvent &aEvent)
void SetPreselect(const LIB_ID &aPreselect)
void onCloseTimer(wxTimerEvent &aEvent)
void displayErrors(wxTopLevelWindow *aWindow)
LIB_ID GetSelectedLibId(int *aUnit=nullptr) const
To be called after this dialog returns from ShowModal().
void RefreshLibs(bool aProgress=false)
wxObjectDataPtr< LIB_TREE_MODEL_ADAPTER > m_adapter
void onDesignBlockSelected(wxCommandEvent &aEvent)
void onDesignBlockChosen(wxCommandEvent &aEvent)
Handle the selection of an item.
std::function< DESIGN_BLOCK_PREVIEW_WIDGET *(wxWindow *aParent)> m_previewFactory
void SelectLibId(const LIB_ID &aLibId)
#define _(s)
STL namespace.
see class PGM_BASE
wxString EscapeHTML(const wxString &aString)
Return a new wxString escaped for embedding in HTML.