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) 2017 Chris Pavlina <pavlina.chris@gmail.com>
6  * Copyright (C) 2016-2017 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 
28 #include <algorithm>
29 #include <set>
30 #include <wx/utils.h>
31 
32 #include <wx/button.h>
33 #include <wx/dataview.h>
34 #include <wx/panel.h>
35 #include <wx/sizer.h>
36 #include <wx/splitter.h>
37 #include <wx/timer.h>
38 #include <wx/utils.h>
39 
40 #include <class_library.h>
41 #include <sch_base_frame.h>
42 #include <template_fieldnames.h>
43 #include <symbol_lib_table.h>
44 #include <widgets/component_tree.h>
47 
48 
50 std::unique_ptr<FOOTPRINT_LIST> DIALOG_CHOOSE_COMPONENT::m_fp_list;
51 
53  CMP_TREE_MODEL_ADAPTER::PTR& aAdapter, int aDeMorganConvert, bool aAllowFieldEdits )
54  : DIALOG_SHIM( aParent, wxID_ANY, aTitle, wxDefaultPosition, wxSize( 800, 650 ),
55  wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
56  m_parent( aParent ),
57  m_deMorganConvert( aDeMorganConvert >= 0 ? aDeMorganConvert : 0 ),
58  m_allow_field_edits( aAllowFieldEdits ),
59  m_external_browser_requested( false )
60 {
61  wxBusyCursor busy_while_loading;
62 
63  auto sizer = new wxBoxSizer( wxVERTICAL );
64 
65  auto splitter = new wxSplitterWindow(
66  this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_LIVE_UPDATE );
67  m_tree = new COMPONENT_TREE( splitter, Prj().SchSymbolLibTable(), aAdapter );
68  auto right_panel = ConstructRightPanel( splitter );
69  auto buttons = new wxStdDialogButtonSizer();
70  m_dbl_click_timer = new wxTimer( this );
71 
72  splitter->SetSashGravity( 0.9 );
73  splitter->SetMinimumPaneSize( 1 );
74  splitter->SplitVertically( m_tree, right_panel, -300 );
75 
76  buttons->AddButton( new wxButton( this, wxID_OK ) );
77  buttons->AddButton( new wxButton( this, wxID_CANCEL ) );
78  buttons->Realize();
79 
80  sizer->Add( splitter, 1, wxEXPAND | wxALL, 5 );
81  sizer->Add( buttons, 0, wxEXPAND | wxBOTTOM, 10 );
82  SetSizer( sizer );
83 
84  Bind( wxEVT_INIT_DIALOG, &DIALOG_CHOOSE_COMPONENT::OnInitDialog, this );
85  Bind( wxEVT_TIMER, &DIALOG_CHOOSE_COMPONENT::OnCloseTimer, this, m_dbl_click_timer->GetId() );
86  Bind( COMPONENT_PRESELECTED, &DIALOG_CHOOSE_COMPONENT::OnComponentPreselected, this );
87  Bind( COMPONENT_SELECTED, &DIALOG_CHOOSE_COMPONENT::OnComponentSelected, this );
88 
89  m_sch_view_ctrl->Bind( wxEVT_LEFT_DCLICK, &DIALOG_CHOOSE_COMPONENT::OnSchViewDClick, this );
90  m_sch_view_ctrl->Bind( wxEVT_PAINT, &DIALOG_CHOOSE_COMPONENT::OnSchViewPaint, this );
91 
92  if( m_fp_sel_ctrl )
93  m_fp_sel_ctrl->Bind(
94  EVT_FOOTPRINT_SELECTED, &DIALOG_CHOOSE_COMPONENT::OnFootprintSelected, this );
95 
96  Layout();
97 }
98 
99 
101 {
102  // I am not sure the following two lines are necessary,
103  // but they will not hurt anyone
104  m_dbl_click_timer->Stop();
105  Unbind( wxEVT_TIMER, &DIALOG_CHOOSE_COMPONENT::OnCloseTimer, this );
106 
107  delete m_dbl_click_timer;
108 }
109 
110 
111 wxPanel* DIALOG_CHOOSE_COMPONENT::ConstructRightPanel( wxWindow* aParent )
112 {
113  auto panel = new wxPanel( aParent );
114  auto sizer = new wxBoxSizer( wxVERTICAL );
115 
116  m_sch_view_ctrl = new wxPanel( panel, wxID_ANY, wxDefaultPosition, wxSize( -1, -1 ),
117  wxFULL_REPAINT_ON_RESIZE | wxSUNKEN_BORDER | wxTAB_TRAVERSAL );
118  m_sch_view_ctrl->SetLayoutDirection( wxLayout_LeftToRight );
119 
120  if( m_allow_field_edits )
122  else
123  m_fp_sel_ctrl = nullptr;
124 
126 
127 
128  sizer->Add( m_sch_view_ctrl, 1, wxEXPAND | wxALL, 5 );
129 
130  if( m_fp_sel_ctrl )
131  sizer->Add( m_fp_sel_ctrl, 0, wxEXPAND | wxALL, 5 );
132 
133  sizer->Add( m_fp_view_ctrl, 1, wxEXPAND | wxALL, 5 );
134 
135 
136  panel->SetSizer( sizer );
137  panel->Layout();
138  sizer->Fit( panel );
139 
140  return panel;
141 }
142 
143 
144 void DIALOG_CHOOSE_COMPONENT::OnInitDialog( wxInitDialogEvent& aEvent )
145 {
147  {
148  // This hides the GAL panel and shows the status label
149  m_fp_view_ctrl->SetStatusText( wxEmptyString );
150  }
151 
152  if( m_fp_sel_ctrl )
153  m_fp_sel_ctrl->Load( Kiway(), Prj() );
154 }
155 
156 
158 {
159  return m_tree->GetSelectedLibId( aUnit );
160 }
161 
162 
163 void DIALOG_CHOOSE_COMPONENT::OnCloseTimer( wxTimerEvent& aEvent )
164 {
165  // Hack handler because of eaten MouseUp event. See
166  // DIALOG_CHOOSE_COMPONENT::OnDoubleClickTreeActivation for the beginning
167  // of this spaghetti noodle.
168 
169  auto state = wxGetMouseState();
170 
171  if( state.LeftIsDown() )
172  {
173  // Mouse hasn't been raised yet, so fire the timer again. Otherwise the
174  // purpose of this timer is defeated.
176  }
177  else
178  {
179  EndQuasiModal( wxID_OK );
180  }
181 }
182 
183 
184 void DIALOG_CHOOSE_COMPONENT::OnSchViewDClick( wxMouseEvent& aEvent )
185 {
187  EndQuasiModal( wxID_OK );
188 }
189 
190 
192 {
193  if( !m_fp_view_ctrl->IsInitialized() )
194  return;
195 
196  LIB_ALIAS* alias = nullptr;
197 
198  try
199  {
200  alias = Prj().SchSymbolLibTable()->LoadSymbol( aLibId );
201  }
202  catch( const IO_ERROR& ioe )
203  {
204  wxLogError( wxString::Format( _( "Error occurred loading symbol %s from library %s."
205  "\n\n%s" ),
206  aLibId.GetLibItemName().wx_str(),
207  aLibId.GetLibNickname().wx_str(),
208  ioe.What() ) );
209  }
210 
211  if( alias == nullptr )
212  {
213  return;
214  }
215 
216  LIB_FIELD* fp_field = alias->GetPart()->GetField( FOOTPRINT );
217  wxString fp_name = fp_field ? fp_field->GetFullText() : wxString( "" );
218 
219  ShowFootprint( fp_name );
220 }
221 
222 
223 void DIALOG_CHOOSE_COMPONENT::ShowFootprint( wxString const& aName )
224 {
225  if( aName == wxEmptyString )
226  {
227  m_fp_view_ctrl->SetStatusText( _( "No footprint specified" ) );
228  }
229  else
230  {
231  LIB_ID lib_id;
232 
233  if( lib_id.Parse( aName ) == -1 && lib_id.IsValid() )
234  {
236  m_fp_view_ctrl->CacheFootprint( lib_id );
237  m_fp_view_ctrl->DisplayFootprint( lib_id );
238  }
239  else
240  {
241  m_fp_view_ctrl->SetStatusText( _( "Invalid footprint specified" ) );
242  }
243  }
244 }
245 
246 
248 {
249  if( !m_fp_sel_ctrl )
250  return;
251 
253 
254  LIB_ALIAS* alias = nullptr;
255 
256  if( aLibId.IsValid() )
257  {
258  try
259  {
260  alias = Prj().SchSymbolLibTable()->LoadSymbol( aLibId );
261  }
262  catch( const IO_ERROR& ioe )
263  {
264  wxLogError( wxString::Format( _( "Error occurred loading symbol %s from library %s."
265  "\n\n%s" ),
266  aLibId.GetLibItemName().wx_str(),
267  aLibId.GetLibNickname().wx_str(),
268  ioe.What() ) );
269  }
270  }
271 
272  if( alias != nullptr )
273  {
274  LIB_PINS temp_pins;
275  LIB_FIELD* fp_field = alias->GetPart()->GetField( FOOTPRINT );
276  wxString fp_name = fp_field ? fp_field->GetFullText() : wxString( "" );
277 
278  alias->GetPart()->GetPins( temp_pins );
279 
280  m_fp_sel_ctrl->FilterByPinCount( temp_pins.size() );
285  }
286  else
287  {
289  m_fp_sel_ctrl->Disable();
290  }
291 }
292 
293 
294 void DIALOG_CHOOSE_COMPONENT::OnSchViewPaint( wxPaintEvent& aEvent )
295 {
296  int unit = 0;
297  LIB_ID id = m_tree->GetSelectedLibId( &unit );
298 
299  if( !id.IsValid() )
300  {
301  // No symbol to show, display a tooltip
302  RenderPreview( nullptr, unit );
303  return;
304  }
305 
306  LIB_ALIAS* alias = nullptr;
307 
308  try
309  {
310  alias = Prj().SchSymbolLibTable()->LoadSymbol( id );
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  id.GetLibItemName().wx_str(),
317  id.GetLibNickname().wx_str(),
318  ioe.What() ) );
319  }
320 
321  if( alias == nullptr )
322  return;
323 
324  LIB_PART* part = alias ? alias->GetPart() : nullptr;
325 
326  // Don't draw if we don't have a part to show
327  // just display a tooltip
328  if( !part )
329  {
330  RenderPreview( nullptr, unit );
331  return;
332  }
333 
334  if( alias->IsRoot() )
335  {
336  // just show the part directly
337  RenderPreview( part, unit );
338  }
339  else
340  {
341  // switch out the name temporarily for the alias name
342  wxString tmp( part->GetName() );
343  part->SetName( alias->GetName() );
344 
345  RenderPreview( part, unit );
346 
347  part->SetName( tmp );
348  }
349 }
350 
351 
352 void DIALOG_CHOOSE_COMPONENT::OnFootprintSelected( wxCommandEvent& aEvent )
353 {
354  m_fp_override = aEvent.GetString();
355 
356  m_field_edits.erase(
357  std::remove_if( m_field_edits.begin(), m_field_edits.end(),
358  []( std::pair<int, wxString> const& i ) { return i.first == FOOTPRINT; } ),
359  m_field_edits.end() );
360 
361  m_field_edits.push_back( std::make_pair( FOOTPRINT, m_fp_override ) );
362 
364 }
365 
366 
368 {
369  int unit = 0;
370 
371  LIB_ID id = m_tree->GetSelectedLibId( &unit );
372 
373  m_sch_view_ctrl->Refresh();
374 
375  if( id.IsValid() )
376  {
377  ShowFootprintFor( id );
379  }
380  else
381  {
383  m_fp_view_ctrl->SetStatusText( wxEmptyString );
384 
386  }
387 }
388 
389 
390 void DIALOG_CHOOSE_COMPONENT::OnComponentSelected( wxCommandEvent& aEvent )
391 {
392  if( m_tree->GetSelectedLibId().IsValid() )
393  {
394  // Got a selection. We can't just end the modal dialog here, because
395  // wx leaks some events back to the parent window (in particular, the
396  // MouseUp following a double click).
397  //
398  // NOW, here's where it gets really fun. wxTreeListCtrl eats MouseUp.
399  // This isn't really feasible to bypass without a fully custom
400  // wxDataViewCtrl implementation, and even then might not be fully
401  // possible (docs are vague). To get around this, we use a one-shot
402  // timer to schedule the dialog close.
403  //
404  // See DIALOG_CHOOSE_COMPONENT::OnCloseTimer for the other end of this
405  // spaghetti noodle.
407  }
408 }
409 
410 
411 void DIALOG_CHOOSE_COMPONENT::RenderPreview( LIB_PART* aComponent, int aUnit )
412 {
413  wxPaintDC dc( m_sch_view_ctrl );
414 
415  const wxSize dc_size = dc.GetSize();
416 
417  // Avoid rendering when either dimension is zero
418  if( dc_size.x == 0 || dc_size.y == 0 )
419  return;
420 
421  if( !aComponent ) // display a tooltip
422  {
423  wxString tooltip = _( "Double click here to select a symbol\nfrom the library browser" );
424  wxSize tsize = dc.GetTextExtent( tooltip.BeforeLast( '\n' ) );
425  dc.DrawText( tooltip.BeforeLast( '\n' ), ( dc_size.x - tsize.x )/2, ( dc_size.y - tsize.y )/2 );
426  tsize = dc.GetTextExtent( tooltip.AfterLast( '\n' ) );
427  dc.DrawText( tooltip.AfterLast( '\n' ), ( dc_size.x - tsize.x )/2, ( dc_size.y + tsize.y )/2 );
428  return;
429  }
430 
431  GRResetPenAndBrush( &dc );
432 
433  COLOR4D bgColor = m_parent->GetDrawBgColor();
434 
435  dc.SetBackground( wxBrush( bgColor.ToColour() ) );
436  dc.Clear();
437 
438  if( !aComponent )
439  return;
440 
441  int unit = aUnit > 0 ? aUnit : 1;
442  int convert = m_deMorganConvert > 0 ? m_deMorganConvert : 1;
443 
444  dc.SetDeviceOrigin( dc_size.x / 2, dc_size.y / 2 );
445 
446  // Find joint bounding box for everything we are about to draw.
447  EDA_RECT bBox = aComponent->GetUnitBoundingBox( unit, convert );
448  const double xscale = (double) dc_size.x / bBox.GetWidth();
449  const double yscale = (double) dc_size.y / bBox.GetHeight();
450  const double scale = std::min( xscale, yscale ) * 0.85;
451 
452  dc.SetUserScale( scale, scale );
453 
454  wxPoint offset = -bBox.Centre();
455 
456  auto opts = PART_DRAW_OPTIONS::Default();
457  opts.draw_hidden_fields = false;
458  aComponent->Draw( nullptr, &dc, offset, unit, convert, opts );
459 }
FOOTPRINT_SELECT_WIDGET * m_fp_sel_ctrl
bool IsValid() const
Function IsValid.
Definition: lib_id.h:177
void GRResetPenAndBrush(wxDC *DC)
Definition: gr_basic.cpp:196
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
Function GetBoundingBox.
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 OnFootprintSelected(wxCommandEvent &aEvent)
Class LIB_FIELD is used in symbol libraries.
Definition: lib_field.h:60
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
This class can be used to populate a FOOTPRINT_LIST asynchronously.
int Parse(const UTF8 &aId)
Function Parse.
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:60
wxString GetFullText(int unit=1) const
Return the text of a field.
Definition: lib_field.cpp:529
static FOOTPRINT_ASYNC_LOADER m_fp_loader
Class LIB_ID.
Definition: lib_id.h:56
Field Name Module PCB, i.e. "16DIP300".
void OnComponentPreselected(wxCommandEvent &aEvent)
void ClearFilters()
Clear all filters.
DIALOG_CHOOSE_COMPONENT(SCH_BASE_FRAME *aParent, const wxString &aTitle, CMP_TREE_MODEL_ADAPTER::PTR &aAdapter, int aDeMorganConvert, bool aAllowFieldEdits)
Create dialog to choose component.
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 wxString & GetName() const
const UTF8 & GetLibItemName() const
Function GetLibItemName.
Definition: lib_id.h:129
wxObjectDataPtr< CMP_TREE_MODEL_ADAPTER_BASE > PTR
Reference-counting container for a pointer to CMP_TREE_MODEL_ADAPTER_BASE.
Class LIB_PART defines a library part 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)
wxPoint Centre() const
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...
LIB_PART * GetPart() const
Function GetPart gets 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.
void DisplayFootprint(const LIB_ID &aFPID)
Set the currently displayed footprint.
static constexpr int DblClickDelay
Class EDA_RECT handles the component boundary box.
wxArrayString & GetFootPrints()
int GetWidth() const
void OnSchViewDClick(wxMouseEvent &aEvent)
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.
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
Function GetLibNickname.
Definition: lib_id.h:108
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