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