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>
29#include <widgets/lib_tree.h>
34#include <string_utils.h>
35#include <wx/log.h>
36#include <wx/panel.h>
37#include <wx/sizer.h>
38#include <wx/splitter.h>
39#include <wx/timer.h>
40#include <wx/wxhtml.h>
41#include <wx/msgdlg.h>
43
44
46
47
49 std::vector<LIB_ID>& aHistoryList,
50 std::function<void()> aSelectHandler,
51 TOOL_INTERACTIVE* aContextMenuTool ) :
52
53 wxPanel( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize ),
54 m_dbl_click_timer( nullptr ),
55 m_open_libs_timer( nullptr ),
56 m_vsplitter( nullptr ),
57 m_tree( nullptr ),
58 m_preview( nullptr ),
59 m_parent( aParent ),
60 m_frame( aFrame ),
61 m_selectHandler( std::move( aSelectHandler ) ),
62 m_historyList( aHistoryList )
63{
64 DESIGN_BLOCK_LIBRARY_ADAPTER* libs = m_frame->Prj().DesignBlockLibs();
65
67 m_frame, libs, m_frame->config()->m_DesignBlockChooserPanel.tree, aContextMenuTool );
68
69 // -------------------------------------------------------------------------------------
70 // Construct the actual panel
71 //
72
73 wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
74
75 m_vsplitter = new wxSplitterWindow( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
76 wxSP_LIVE_UPDATE | wxSP_NOBORDER | wxSP_3DSASH );
77
78
79 // Avoid the splitter window being assigned as the parent to additional windows.
80 m_vsplitter->SetExtraStyle( wxWS_EX_TRANSIENT );
81
82 wxPanel* treePanel = new wxPanel( m_vsplitter );
83 wxBoxSizer* treeSizer = new wxBoxSizer( wxVERTICAL );
84 treePanel->SetSizer( treeSizer );
85
86 m_detailsPanel = new wxPanel( m_vsplitter );
87 m_detailsSizer = new wxBoxSizer( wxVERTICAL );
88 m_detailsPanel->SetSizer( m_detailsSizer );
89
90 m_detailsPanel->Layout();
92
93 m_vsplitter->SetSashGravity( 0.5 );
94 m_vsplitter->SetMinimumPaneSize( 20 );
95 m_vsplitter->SplitHorizontally( treePanel, m_detailsPanel );
96
97 sizer->Add( m_vsplitter, 1, wxEXPAND, 5 );
98
99 m_tree = new LIB_TREE( treePanel, wxT( "design_blocks" ), m_adapter, LIB_TREE::FLAGS::ALL_WIDGETS, nullptr );
100
101 treeSizer->Add( m_tree, 1, wxEXPAND, 5 );
102 treePanel->Layout();
103 treeSizer->Fit( treePanel );
104
105 RefreshLibs();
106 m_adapter->FinishTreeInitialization();
107
108 m_tree->SetSearchString( g_designBlockSearchString );
109
110 m_dbl_click_timer = new wxTimer( this );
111 m_open_libs_timer = new wxTimer( this );
112
113 SetSizer( sizer );
114
115 Layout();
116
117 Bind( wxEVT_TIMER, &PANEL_DESIGN_BLOCK_CHOOSER::onCloseTimer, this, m_dbl_click_timer->GetId() );
118 Bind( wxEVT_TIMER, &PANEL_DESIGN_BLOCK_CHOOSER::onOpenLibsTimer, this, m_open_libs_timer->GetId() );
119 Bind( EVT_LIBITEM_CHOSEN, &PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockChosen, this );
120 Bind( wxEVT_CHAR_HOOK, &PANEL_DESIGN_BLOCK_CHOOSER::OnChar, this );
121
122 // Open the user's previously opened libraries on timer expiration.
123 // This is done on a timer because we need a gross hack to keep GTK from garbling the
124 // display. Must be longer than the search debounce timer.
125 m_open_libs_timer->StartOnce( 300 );
126}
127
128
130{
131 Unbind( wxEVT_TIMER, &PANEL_DESIGN_BLOCK_CHOOSER::onCloseTimer, this );
132 Unbind( EVT_LIBITEM_SELECTED, &PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockSelected, this );
133 Unbind( EVT_LIBITEM_CHOSEN, &PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockChosen, this );
134 Unbind( wxEVT_CHAR_HOOK, &PANEL_DESIGN_BLOCK_CHOOSER::OnChar, this );
135
136 // Stop the timer during destruction early to avoid potential race conditions (that do happen)
137 m_dbl_click_timer->Stop();
138 m_open_libs_timer->Stop();
139 delete m_dbl_click_timer;
140 delete m_open_libs_timer;
141}
142
143
145{
146 g_designBlockSearchString = m_tree->GetSearchString();
147
148 if( APP_SETTINGS_BASE* cfg = m_frame->config() )
149 {
150 // Save any changes to column widths, etc.
151 m_adapter->SaveSettings();
152
153 cfg->m_DesignBlockChooserPanel.width = GetParent()->GetSize().x;
154 cfg->m_DesignBlockChooserPanel.height = GetParent()->GetSize().y;
155 cfg->m_DesignBlockChooserPanel.sash_pos_v = m_vsplitter->GetSashPosition();
156 cfg->m_DesignBlockChooserPanel.sort_mode = m_tree->GetSortMode();
157 }
158}
159
160
162{
163 if( m_tree )
164 m_tree->ShowChangedLanguage();
165}
166
167
169{
170 m_preview = aPreview;
171 m_detailsSizer->Add( m_preview, 1, wxEXPAND, 5 );
172 Layout();
173}
174
175
176void PANEL_DESIGN_BLOCK_CHOOSER::OnChar( wxKeyEvent& aEvent )
177{
178 if( aEvent.GetKeyCode() == WXK_ESCAPE )
179 {
180 wxObject* eventSource = aEvent.GetEventObject();
181
182 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( eventSource ) )
183 {
184 // First escape cancels search string value
185 if( textCtrl->GetValue() == m_tree->GetSearchString() && !m_tree->GetSearchString().IsEmpty() )
186 {
187 m_tree->SetSearchString( wxEmptyString );
188 return;
189 }
190 }
191 }
192 else
193 {
194 aEvent.Skip();
195 }
196}
197
198
200{
201 if( APP_SETTINGS_BASE* cfg = m_frame->config() )
202 {
203 auto horizPixelsFromDU =
204 [&]( int x ) -> int
205 {
206 wxSize sz( x, 0 );
207 return GetParent()->ConvertDialogToPixels( sz ).x;
208 };
209
210 APP_SETTINGS_BASE::PANEL_DESIGN_BLOCK_CHOOSER& panelCfg = cfg->m_DesignBlockChooserPanel;
211
212 int w = panelCfg.width > 40 ? panelCfg.width : horizPixelsFromDU( 440 );
213 int h = panelCfg.height > 40 ? panelCfg.height : horizPixelsFromDU( 340 );
214
215 GetParent()->SetSize( wxSize( w, h ) );
216 GetParent()->Layout();
217
218 // We specify the width of the right window (m_design_block_view_panel), because specify
219 // the width of the left window does not work as expected when SetSashGravity() is called
220
221 if( panelCfg.sash_pos_h < 0 )
222 panelCfg.sash_pos_h = horizPixelsFromDU( 220 );
223
224 if( panelCfg.sash_pos_v < 0 )
225 panelCfg.sash_pos_v = horizPixelsFromDU( 230 );
226
227 if( m_vsplitter )
228 m_vsplitter->SetSashPosition( panelCfg.sash_pos_v );
229
230 m_adapter->SetSortMode( (LIB_TREE_MODEL_ADAPTER::SORT_MODE) panelCfg.sort_mode );
231 }
232}
233
234
236{
237 // Unselect before syncing to avoid null reference in the adapter
238 // if a selected item is removed during the sync
239 LIB_ID savedSelection = GetSelectedLibId();
240 m_tree->Unselect();
241
243
244 // Clear all existing libraries then re-add
245 adapter->ClearLibraries();
246
248
249 if( !m_historyList.empty() )
250 adapter->SetPreselectNode( m_historyList[0], 0 );
251
252 adapter->AddLibraries( m_frame );
253
254 m_tree->Regenerate( true );
255
256 if( savedSelection.IsValid() )
257 SelectLibId( savedSelection );
258
259 Refresh();
260}
261
262
264{
265 m_adapter->SetPreselectNode( aPreselect, 0 );
266}
267
268
270{
271 return m_tree->GetSelectedLibId( aUnit );
272}
273
274
276{
277 m_tree->CenterLibId( aLibId );
278 m_tree->SelectLibId( aLibId );
279}
280
281
283{
284 // Hack because of eaten MouseUp event. See PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockChosen
285 // for the beginning of this spaghetti noodle.
286
287 wxMouseState state = wxGetMouseState();
288
289 if( state.LeftIsDown() )
290 {
291 // Mouse hasn't been raised yet, so fire the timer again. Otherwise the
292 // purpose of this timer is defeated.
294 }
295 else
296 {
297 m_frame->GetCanvas()->SetFocus();
299 addDesignBlockToHistory( m_tree->GetSelectedLibId() );
300 }
301}
302
303
305{
306 if( APP_SETTINGS_BASE* cfg = m_frame->config() )
307 m_adapter->OpenLibs( cfg->m_LibTree.open_libs );
308
309 // Bind this now se we don't spam the event queue with EVT_LIBITEM_SELECTED events during
310 // the initial load.
311 Bind( EVT_LIBITEM_SELECTED, &PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockSelected, this );
312}
313
314
316{
317 if( !m_preview || !m_preview->IsInitialized() )
318 return;
319
320 if( GetSelectedLibId().IsValid() )
321 {
322 std::unique_ptr<DESIGN_BLOCK> designBlock( m_parent->GetDesignBlock( GetSelectedLibId(), true, true ) );
323 m_preview->DisplayDesignBlock( designBlock.get() );
324 }
325}
326
327
329{
330 if( m_tree->GetSelectedLibId().IsValid() )
331 {
332 // Got a selection. We can't just end the modal dialog here, because wx leaks some events
333 // back to the parent window (in particular, the MouseUp following a double click).
334 //
335 // NOW, here's where it gets really fun. wxTreeListCtrl eats MouseUp. This isn't really
336 // feasible to bypass without a fully custom wxDataViewCtrl implementation, and even then
337 // might not be fully possible (docs are vague). To get around this, we use a one-shot
338 // timer to schedule the dialog close.
339 //
340 // See PANEL_DESIGN_BLOCK_CHOOSER::onCloseTimer for the other end of this spaghetti noodle.
342 }
343}
344
346{
347 LIB_ID savedId = GetSelectedLibId();
348
349 m_tree->Unselect();
350
351 // Remove duplicates
352 for( int i = (int) m_historyList.size() - 1; i >= 0; --i )
353 {
354 if( m_historyList[i] == aLibId )
355 m_historyList.erase( m_historyList.begin() + i );
356 }
357
358 // Add the new name at the beginning of the history list
359 m_historyList.insert( m_historyList.begin(), aLibId );
360
361 // Remove extra names
362 while( m_historyList.size() >= 8 )
363 m_historyList.pop_back();
364
366 m_tree->Regenerate( true );
367
368 SelectLibId( savedId );
369}
370
371
373{
374 m_adapter->RemoveGroup( true, false );
375
376 // Build the history list
377 std::vector<LIB_TREE_ITEM*> historyInfos;
378
379 DESIGN_BLOCK_LIBRARY_ADAPTER* adapter = m_frame->Prj().DesignBlockLibs();
380
381 for( const LIB_ID& lib : m_historyList )
382 {
383 LIB_TREE_ITEM* info = adapter->LoadDesignBlock( lib.GetLibNickname(), lib.GetLibItemName() );
384
385 // this can be null, for example, if the design block has been deleted from a library.
386 if( info != nullptr )
387 historyInfos.push_back( info );
388 }
389
390 m_adapter->DoAddLibrary( wxT( "-- " ) + _( "Recently Used" ) + wxT( " --" ), wxEmptyString,
391 historyInfos, false, true )
392 .m_IsRecentlyUsedGroup = true;
393}
394
395
396void PANEL_DESIGN_BLOCK_CHOOSER::displayErrors( wxTopLevelWindow* aWindow )
397{
398 // @todo: go to a more HTML !<table>! ? centric output, possibly with recommendations
399 // for remedy of errors. Add numeric error codes to PARSE_ERROR, and switch on them for
400 // remedies, etc. Full access is provided to everything in every exception!
401
402 HTML_MESSAGE_BOX dlg( aWindow, _( "Load Error" ) );
403
404 dlg.MessageSet( _( "Errors were encountered loading design blocks:" ) );
405
406 wxString msg;
407 // TODO(JE) library tables - this function isn't even called, but would need fixup if so
408#if 0
409 while( std::unique_ptr<IO_ERROR> error = DESIGN_BLOCK_LIB_TABLE::GetGlobalList().PopError() )
410 {
411 wxString tmp = EscapeHTML( error->Problem() );
412
413 // Preserve new lines in error messages so queued errors don't run together.
414 tmp.Replace( wxS( "\n" ), wxS( "<BR>" ) );
415 msg += wxT( "<p>" ) + tmp + wxT( "</p>" );
416 }
417#endif
418 dlg.AddHTML_Text( msg );
419
420 dlg.ShowModal();
421}
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)
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.
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).
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:46
@ ALL_WIDGETS
Definition lib_tree.h:55
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 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 SetPreviewWidget(DESIGN_BLOCK_PREVIEW_WIDGET *aPreview)
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.
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.