KiCad PCB EDA Suite
dialog_choose_component.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 <h.zeller@acm.org>
5  * Copyright (C) 2016-2018 KiCad Developers, see AUTHORS.txt for contributors.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, you may find one here:
19  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20  * or you may search the http://www.gnu.org website for the version 2 license,
21  * or you may write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23  */
24 
26 
27 #include <algorithm>
28 #include <set>
29 #include <wx/utils.h>
30 
31 #include <wx/button.h>
32 #include <wx/dataview.h>
33 #include <wx/panel.h>
34 #include <wx/sizer.h>
35 #include <wx/splitter.h>
36 #include <wx/timer.h>
37 #include <wx/utils.h>
38 
39 #include <class_library.h>
40 #include <sch_base_frame.h>
41 #include <template_fieldnames.h>
42 #include <symbol_lib_table.h>
43 #include <widgets/component_tree.h>
46 
47 
49 std::unique_ptr<FOOTPRINT_LIST> DIALOG_CHOOSE_COMPONENT::m_fp_list;
50 
53 
55  CMP_TREE_MODEL_ADAPTER::PTR& aAdapter, int aDeMorganConvert, bool aAllowFieldEdits,
56  bool aShowFootprints )
57  : DIALOG_SHIM( aParent, wxID_ANY, aTitle, wxDefaultPosition, wxDefaultSize,
58  wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
59  m_fp_sel_ctrl( nullptr ),
60  m_fp_view_ctrl( nullptr ),
61  m_parent( aParent ),
62  m_deMorganConvert( aDeMorganConvert >= 0 ? aDeMorganConvert : 0 ),
63  m_allow_field_edits( aAllowFieldEdits ),
64  m_show_footprints( aShowFootprints ),
65  m_external_browser_requested( false )
66 {
67  wxBusyCursor busy_while_loading;
68 
69  auto sizer = new wxBoxSizer( wxVERTICAL );
70 
71  // Use a slightly different layout, with a details pane spanning the entire window,
72  // if we're not showing footprints.
73  auto vsplitter = aShowFootprints ? nullptr : new wxSplitterWindow(
74  this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_LIVE_UPDATE | wxSP_3DSASH );
75 
76  m_splitter_tree_canvas = new wxSplitterWindow(
77  vsplitter ? static_cast<wxWindow *>( vsplitter ) : static_cast<wxWindow *>( this ),
78  wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_LIVE_UPDATE | wxSP_3DSASH );
79 
80  auto details = aShowFootprints ? nullptr : new wxHtmlWindow(
81  vsplitter, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO );
82 
83  m_tree = new COMPONENT_TREE( m_splitter_tree_canvas, Prj().SchSymbolLibTable(),
84  aAdapter, COMPONENT_TREE::WIDGETS::ALL, details );
86  auto buttons = new wxStdDialogButtonSizer();
87  m_dbl_click_timer = new wxTimer( this );
88 
89  if( vsplitter )
90  sizer->Add( vsplitter, 1, wxEXPAND | wxALL, 5 );
91  else
92  sizer->Add( m_splitter_tree_canvas, 1, wxEXPAND | wxALL, 5 );
93 
94  buttons->AddButton( new wxButton( this, wxID_OK ) );
95  buttons->AddButton( new wxButton( this, wxID_CANCEL ) );
96  buttons->Realize();
97 
98  sizer->Add( buttons, 0, wxEXPAND | wxBOTTOM, 10 );
99  SetSizer( sizer );
100 
101  Bind( wxEVT_INIT_DIALOG, &DIALOG_CHOOSE_COMPONENT::OnInitDialog, this );
102  Bind( wxEVT_TIMER, &DIALOG_CHOOSE_COMPONENT::OnCloseTimer, this, m_dbl_click_timer->GetId() );
103  Bind( COMPONENT_PRESELECTED, &DIALOG_CHOOSE_COMPONENT::OnComponentPreselected, this );
104  Bind( COMPONENT_SELECTED, &DIALOG_CHOOSE_COMPONENT::OnComponentSelected, this );
105 
106  m_sch_view_ctrl->Bind( wxEVT_LEFT_DCLICK, &DIALOG_CHOOSE_COMPONENT::OnSchViewDClick, this );
107  m_sch_view_ctrl->Bind( wxEVT_PAINT, &DIALOG_CHOOSE_COMPONENT::OnSchViewPaint, this );
108 
109  if( m_fp_sel_ctrl )
110  m_fp_sel_ctrl->Bind(
111  EVT_FOOTPRINT_SELECTED, &DIALOG_CHOOSE_COMPONENT::OnFootprintSelected, this );
112 
113  Layout();
114 
115  if( m_last_dlg_size == wxSize( -1, -1 ) )
116  SetSizeInDU( 320, 256 );
117  else
118  SetSize( m_last_dlg_size );
119 
120  m_splitter_tree_canvas->SetSashGravity( 0.8 );
121  m_splitter_tree_canvas->SetMinimumPaneSize( 20 );
122  // We specify the width of the right window (m_symbol_view_panel), because specify
123  // the width of the left window does not work as expected when SetSashGravity() is called
126  : HorizPixelsFromDU( -100 ) );
127 
128  if( vsplitter )
129  {
130  vsplitter->SetSashGravity( 0.5 );
131  vsplitter->SetMinimumPaneSize( 20 );
132  vsplitter->SplitHorizontally( m_splitter_tree_canvas, details, VertPixelsFromDU( -80 ) );
133  }
134 }
135 
136 
138 {
139  // I am not sure the following two lines are necessary,
140  // but they will not hurt anyone
141  m_dbl_click_timer->Stop();
142  Unbind( wxEVT_TIMER, &DIALOG_CHOOSE_COMPONENT::OnCloseTimer, this );
143 
144  delete m_dbl_click_timer;
145 
146  m_last_dlg_size = GetSize();
148  - m_splitter_tree_canvas->GetSashPosition();
149 }
150 
151 
152 wxPanel* DIALOG_CHOOSE_COMPONENT::ConstructRightPanel( wxWindow* aParent )
153 {
154  auto panel = new wxPanel( aParent );
155  auto sizer = new wxBoxSizer( wxVERTICAL );
156 
157  m_sch_view_ctrl = new wxPanel( panel, wxID_ANY, wxDefaultPosition, wxSize( -1, -1 ),
158  wxFULL_REPAINT_ON_RESIZE | wxTAB_TRAVERSAL );
159  m_sch_view_ctrl->SetLayoutDirection( wxLayout_LeftToRight );
160 
161  if( m_show_footprints )
162  {
163  if( m_allow_field_edits )
165 
167 
168 
169  sizer->Add( m_sch_view_ctrl, 1, wxEXPAND | wxALL, 5 );
170 
171  if( m_fp_sel_ctrl )
172  sizer->Add( m_fp_sel_ctrl, 0, wxEXPAND | wxALL, 5 );
173 
174  sizer->Add( m_fp_view_ctrl, 1, wxEXPAND | wxALL, 5 );
175  }
176  else
177  {
178  sizer->Add( m_sch_view_ctrl, 1, wxEXPAND | wxALL, 5 );
179  }
180 
181  panel->SetSizer( sizer );
182  panel->Layout();
183  sizer->Fit( panel );
184 
185  return panel;
186 }
187 
188 
189 void DIALOG_CHOOSE_COMPONENT::OnInitDialog( wxInitDialogEvent& aEvent )
190 {
192  {
193  // This hides the GAL panel and shows the status label
194  m_fp_view_ctrl->SetStatusText( wxEmptyString );
195  }
196 
197  if( m_fp_sel_ctrl )
198  m_fp_sel_ctrl->Load( Kiway(), Prj() );
199 }
200 
201 
203 {
204  return m_tree->GetSelectedLibId( aUnit );
205 }
206 
207 
208 void DIALOG_CHOOSE_COMPONENT::OnCloseTimer( wxTimerEvent& aEvent )
209 {
210  // Hack handler because of eaten MouseUp event. See
211  // DIALOG_CHOOSE_COMPONENT::OnDoubleClickTreeActivation for the beginning
212  // of this spaghetti noodle.
213 
214  auto state = wxGetMouseState();
215 
216  if( state.LeftIsDown() )
217  {
218  // Mouse hasn't been raised yet, so fire the timer again. Otherwise the
219  // purpose of this timer is defeated.
221  }
222  else
223  {
224  EndQuasiModal( wxID_OK );
225  }
226 }
227 
228 
229 void DIALOG_CHOOSE_COMPONENT::OnSchViewDClick( wxMouseEvent& aEvent )
230 {
232  EndQuasiModal( wxID_OK );
233 }
234 
235 
237 {
239  return;
240 
241  LIB_ALIAS* alias = nullptr;
242 
243  try
244  {
245  alias = Prj().SchSymbolLibTable()->LoadSymbol( aLibId );
246  }
247  catch( const IO_ERROR& ioe )
248  {
249  wxLogError( wxString::Format( _( "Error occurred loading symbol %s from library %s."
250  "\n\n%s" ),
251  aLibId.GetLibItemName().wx_str(),
252  aLibId.GetLibNickname().wx_str(),
253  ioe.What() ) );
254  }
255 
256  if( alias == nullptr )
257  {
258  return;
259  }
260 
261  LIB_FIELD* fp_field = alias->GetPart()->GetField( FOOTPRINT );
262  wxString fp_name = fp_field ? fp_field->GetFullText() : wxString( "" );
263 
264  ShowFootprint( fp_name );
265 }
266 
267 
268 void DIALOG_CHOOSE_COMPONENT::ShowFootprint( wxString const& aName )
269 {
270  if( !m_fp_view_ctrl )
271  {
272  return;
273  }
274 
275  if( aName == wxEmptyString )
276  {
277  m_fp_view_ctrl->SetStatusText( _( "No footprint specified" ) );
278  }
279  else
280  {
281  LIB_ID lib_id;
282 
283  if( lib_id.Parse( aName ) == -1 && lib_id.IsValid() )
284  {
286  m_fp_view_ctrl->CacheFootprint( lib_id );
287  m_fp_view_ctrl->DisplayFootprint( lib_id );
288  }
289  else
290  {
291  m_fp_view_ctrl->SetStatusText( _( "Invalid footprint specified" ) );
292  }
293  }
294 }
295 
296 
298 {
299  if( !m_fp_sel_ctrl )
300  return;
301 
303 
304  LIB_ALIAS* alias = nullptr;
305 
306  if( aLibId.IsValid() )
307  {
308  try
309  {
310  alias = Prj().SchSymbolLibTable()->LoadSymbol( aLibId );
311  }
312  catch( const IO_ERROR& ioe )
313  {
314  wxLogError( wxString::Format( _( "Error occurred loading symbol %s from library %s."
315  "\n\n%s" ),
316  aLibId.GetLibItemName().wx_str(),
317  aLibId.GetLibNickname().wx_str(),
318  ioe.What() ) );
319  }
320  }
321 
322  if( alias != nullptr )
323  {
324  LIB_PINS temp_pins;
325  LIB_FIELD* fp_field = alias->GetPart()->GetField( FOOTPRINT );
326  wxString fp_name = fp_field ? fp_field->GetFullText() : wxString( "" );
327 
328  alias->GetPart()->GetPins( temp_pins );
329 
330  m_fp_sel_ctrl->FilterByPinCount( temp_pins.size() );
335  }
336  else
337  {
339  m_fp_sel_ctrl->Disable();
340  }
341 }
342 
343 
344 void DIALOG_CHOOSE_COMPONENT::OnSchViewPaint( wxPaintEvent& aEvent )
345 {
346  int unit = 0;
347  LIB_ID id = m_tree->GetSelectedLibId( &unit );
348 
349  if( !id.IsValid() )
350  {
351  // No symbol to show, display a tooltip
352  RenderPreview( nullptr, unit );
353  return;
354  }
355 
356  LIB_ALIAS* alias = nullptr;
357 
358  try
359  {
360  alias = Prj().SchSymbolLibTable()->LoadSymbol( id );
361  }
362  catch( const IO_ERROR& ioe )
363  {
364  wxLogError( wxString::Format( _( "Error occurred loading symbol %s from library %s."
365  "\n\n%s" ),
366  id.GetLibItemName().wx_str(),
367  id.GetLibNickname().wx_str(),
368  ioe.What() ) );
369  }
370 
371  if( alias == nullptr )
372  return;
373 
374  LIB_PART* part = alias ? alias->GetPart() : nullptr;
375 
376  // Don't draw if we don't have a part to show
377  // just display a tooltip
378  if( !part )
379  {
380  RenderPreview( nullptr, unit );
381  return;
382  }
383 
384  if( alias->IsRoot() )
385  {
386  // just show the part directly
387  RenderPreview( part, unit );
388  }
389  else
390  {
391  // switch out the name temporarily for the alias name
392  wxString tmp( part->GetName() );
393  part->SetName( alias->GetName() );
394 
395  RenderPreview( part, unit );
396 
397  part->SetName( tmp );
398  }
399 }
400 
401 
402 void DIALOG_CHOOSE_COMPONENT::OnFootprintSelected( wxCommandEvent& aEvent )
403 {
404  m_fp_override = aEvent.GetString();
405 
406  m_field_edits.erase(
407  std::remove_if( m_field_edits.begin(), m_field_edits.end(),
408  []( std::pair<int, wxString> const& i ) { return i.first == FOOTPRINT; } ),
409  m_field_edits.end() );
410 
411  m_field_edits.push_back( std::make_pair( FOOTPRINT, m_fp_override ) );
412 
414 }
415 
416 
418 {
419  int unit = 0;
420 
421  LIB_ID id = m_tree->GetSelectedLibId( &unit );
422 
423  m_sch_view_ctrl->Refresh();
424 
425  if( id.IsValid() )
426  {
427  ShowFootprintFor( id );
429  }
430  else
431  {
433  m_fp_view_ctrl->SetStatusText( wxEmptyString );
434 
436  }
437 }
438 
439 
440 void DIALOG_CHOOSE_COMPONENT::OnComponentSelected( wxCommandEvent& aEvent )
441 {
442  if( m_tree->GetSelectedLibId().IsValid() )
443  {
444  // Got a selection. We can't just end the modal dialog here, because
445  // wx leaks some events back to the parent window (in particular, the
446  // MouseUp following a double click).
447  //
448  // NOW, here's where it gets really fun. wxTreeListCtrl eats MouseUp.
449  // This isn't really feasible to bypass without a fully custom
450  // wxDataViewCtrl implementation, and even then might not be fully
451  // possible (docs are vague). To get around this, we use a one-shot
452  // timer to schedule the dialog close.
453  //
454  // See DIALOG_CHOOSE_COMPONENT::OnCloseTimer for the other end of this
455  // spaghetti noodle.
457  }
458 }
459 
460 
461 void DIALOG_CHOOSE_COMPONENT::RenderPreview( LIB_PART* aComponent, int aUnit )
462 {
463  wxPaintDC dc( m_sch_view_ctrl );
464 
465  const wxSize dc_size = dc.GetSize();
466 
467  // Avoid rendering when either dimension is zero
468  if( dc_size.x == 0 || dc_size.y == 0 )
469  return;
470 
471  if( !aComponent ) // display a tooltip
472  {
473  wxString tooltip = _( "Double-click here to select a symbol from the library browser" );
474  GRDrawWrappedText( dc, tooltip );
475  return;
476  }
477 
478  GRResetPenAndBrush( &dc );
479 
480  COLOR4D bgColor = m_parent->GetDrawBgColor();
481 
482  dc.SetBackground( wxBrush( bgColor.ToColour() ) );
483  dc.Clear();
484 
485  int unit = aUnit > 0 ? aUnit : 1;
486  int convert = m_deMorganConvert > 0 ? m_deMorganConvert : 1;
487 
488  dc.SetDeviceOrigin( dc_size.x / 2, dc_size.y / 2 );
489 
490  // Find joint bounding box for everything we are about to draw.
491  EDA_RECT bBox = aComponent->GetUnitBoundingBox( unit, convert );
492  const double xscale = (double) dc_size.x / bBox.GetWidth();
493  const double yscale = (double) dc_size.y / bBox.GetHeight();
494  const double scale = std::min( xscale, yscale ) * 0.85;
495 
496  dc.SetUserScale( scale, scale );
497 
498  wxPoint offset = -bBox.Centre();
499 
500  auto opts = PART_DRAW_OPTIONS::Default();
501  opts.draw_hidden_fields = false;
502  aComponent->Draw( nullptr, &dc, offset, unit, convert, opts );
503 }
FOOTPRINT_SELECT_WIDGET * m_fp_sel_ctrl
bool IsValid() const
Definition: lib_id.h:155
void GRResetPenAndBrush(wxDC *DC)
Definition: gr_basic.cpp:218
LIB_FIELD * GetField(int aId)
Return pointer to the requested field.
void Draw(EDA_DRAW_PANEL *aPanel, wxDC *aDc, const wxPoint &aOffset, int aMulti, int aConvert, const PART_DRAW_OPTIONS &aOpts)
Draw part.
const EDA_RECT GetUnitBoundingBox(int aUnit, int aConvert) const
Get the bounding box for the symbol.
Part library alias object definition.
KIWAY & Kiway() const
Function Kiway returns a reference to the KIWAY that this object has an opportunity to participate in...
Definition: kiway_player.h:60
void FilterByFootprintFilters(wxArrayString const &aFilters, bool aZeroFilters)
Filter by footprint filter list.
std::vector< std::pair< int, wxString > > m_field_edits
void PopulateFootprintSelector(LIB_ID const &aLibId)
Populate the footprint selector for a given alias.
void GRDrawWrappedText(wxDC &aDC, wxString const &aText)
Draw text centered on a wxDC with wrapping.
Definition: gr_basic.cpp:1302
void OnFootprintSelected(wxCommandEvent &aEvent)
Field object used in symbol libraries.
Definition: lib_field.h:59
void SetDefaultFootprint(wxString const &aFp)
Set the default footprint for a part.
void ShowFootprint(wxString const &aFootprint)
Display the given footprint by name.
int GetHeight() const
Definition: eda_rect.h:118
This class can be used to populate a FOOTPRINT_LIST asynchronously.
int Parse(const UTF8 &aId)
Parse LIB_ID with the information from aId.
Definition: lib_id.cpp:122
Class DIALOG_SHIM may sit in the inheritance tree between wxDialog and any class written by wxFormBui...
Definition: dialog_shim.h:70
wxString GetFullText(int unit=1) const
Return the text of a field.
Definition: lib_field.cpp:356
static FOOTPRINT_ASYNC_LOADER m_fp_loader
A logical library item identifier and consists of various portions much like a URI.
Definition: lib_id.h:51
DIALOG_CHOOSE_COMPONENT(SCH_BASE_FRAME *aParent, const wxString &aTitle, CMP_TREE_MODEL_ADAPTER::PTR &aAdapter, int aDeMorganConvert, bool aAllowFieldEdits, bool aShowFootprints)
Create dialog to choose component.
Field Name Module PCB, i.e. "16DIP300".
void OnComponentPreselected(wxCommandEvent &aEvent)
void ClearFilters()
Clear all filters.
bool UpdateList()
Update the contents of the list to match the filters.
void GetPins(LIB_PINS &aList, int aUnit=0, int aConvert=0)
Return a list of pin object pointers from the draw item list.
PROJECT & Prj() const
Function Prj returns a reference to the PROJECT "associated with" this KIWAY.
bool IsRoot() const
wxString wx_str() const
Definition: utf8.cpp:48
void OnInitDialog(wxInitDialogEvent &aEvent)
LIB_ID GetSelectedLibId(int *aUnit=nullptr) const
For multi-unit components, if the user selects the component itself rather than picking an individual...
void ClearStatus()
Clear the contents of the status label and hide it.
const UTF8 & GetLibItemName() const
Definition: lib_id.h:115
wxObjectDataPtr< CMP_TREE_MODEL_ADAPTER_BASE > PTR
Reference-counting container for a pointer to CMP_TREE_MODEL_ADAPTER_BASE.
void SetSizeInDU(int x, int y)
Set the dialog to the given dimensions in "dialog units".
Define a library symbol object.
static std::unique_ptr< FOOTPRINT_LIST > m_fp_list
void OnCloseTimer(wxTimerEvent &aEvent)
virtual bool Enable(bool aEnable=true) override
Enable or disable the control for input.
wxPanel * ConstructRightPanel(wxWindow *aParent)
Definition: hash_eda.h:46
wxPoint Centre() const
Definition: eda_rect.h:60
static PART_DRAW_OPTIONS Default()
void SetStatusText(wxString const &aText)
Set the contents of the status label and display it.
COLOR4D GetDrawBgColor() const override
Widget displaying a tree of components with optional search text control and description panel...
wxArrayString & GetFootprints()
int VertPixelsFromDU(int y)
Convert an integer number of dialog units to pixels, vertically.
LIB_PART * GetPart() const
Get the shared LIB_PART.
void EndQuasiModal(int retCode)
virtual const wxString What() const
A composite of Problem() and Where()
Definition: exceptions.cpp:33
std::vector< LIB_PIN * > LIB_PINS
Helper for defining a list of pin object pointers.
Definition: lib_draw_item.h:60
const int scale
void CacheFootprint(const LIB_ID &aFPID)
Preload a footprint into the cache.
const wxString & GetName() const
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, CPTREE &aTree)
Function Format outputs a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:205
bool IsInitialized() const
Return whether the widget initialized properly.
wxSplitterWindow * m_splitter_tree_canvas
void DisplayFootprint(const LIB_ID &aFPID)
Set the currently displayed footprint.
static constexpr int DblClickDelay
Class EDA_RECT handles the component boundary box.
Definition: eda_rect.h:44
int GetWidth() const
Definition: eda_rect.h:117
void OnSchViewDClick(wxMouseEvent &aEvent)
const wxString & GetName() const
LIB_ID GetSelectedLibId(int *aUnit=nullptr) const
To be called after this dialog returns from ShowModal().
virtual void SetName(const wxString &aName)
void Load(KIWAY &aKiway, PROJECT &aProject)
Start loading.
int HorizPixelsFromDU(int x)
Convert an integer number of dialog units to pixels, horizontally.
void ShowFootprintFor(LIB_ID const &aLibId)
Look up the footprint for a given symbol specified in the LIB_ID and display it.
FOOTPRINT_PREVIEW_WIDGET * m_fp_view_ctrl
Definition for part library class.
const UTF8 & GetLibNickname() const
Return the logical library name portion of a LIB_ID.
Definition: lib_id.h:98
void OnSchViewPaint(wxPaintEvent &aEvent)
A shim class between EDA_DRAW_FRAME and several derived classes: LIB_EDIT_FRAME, LIB_VIEW_FRAME, and SCH_EDIT_FRAME, and it brings in a common way of handling the provided virtual functions for the derived classes.
void OnComponentSelected(wxCommandEvent &aEvent)
Handle the selection of an item.
Struct IO_ERROR is a class used to hold an error message and may be used when throwing exceptions con...
Definition: ki_exception.h:47
void FilterByPinCount(int aPinCount)
Filter by pin count.
#define min(a, b)
Definition: auxiliary.h:85
void RenderPreview(LIB_PART *aComponent, int aUnit)
Display a given symbol into the schematic symbol preview.
Class COLOR4D is the color representation with 4 components: red, green, blue, alpha.
Definition: color4d.h:39