You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

542 lines
17 KiB

  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. #include <dialog_choose_component.h>
  25. #include <algorithm>
  26. #include <set>
  27. #include <wx/utils.h>
  28. #include <wx/button.h>
  29. #include <wx/dataview.h>
  30. #include <wx/panel.h>
  31. #include <wx/sizer.h>
  32. #include <wx/splitter.h>
  33. #include <wx/timer.h>
  34. #include <wx/utils.h>
  35. #include <class_library.h>
  36. #include <sch_base_frame.h>
  37. #include <template_fieldnames.h>
  38. #include <symbol_lib_table.h>
  39. #include <widgets/lib_tree.h>
  40. #include <widgets/footprint_preview_widget.h>
  41. #include <widgets/footprint_select_widget.h>
  42. wxSize DIALOG_CHOOSE_COMPONENT::m_last_dlg_size( -1, -1 );
  43. int DIALOG_CHOOSE_COMPONENT::m_h_sash_pos = 0;
  44. int DIALOG_CHOOSE_COMPONENT::m_v_sash_pos = 0;
  45. std::mutex DIALOG_CHOOSE_COMPONENT::g_Mutex;
  46. DIALOG_CHOOSE_COMPONENT::DIALOG_CHOOSE_COMPONENT( SCH_BASE_FRAME* aParent, const wxString& aTitle,
  47. SYMBOL_TREE_MODEL_ADAPTER::PTR& aAdapter,
  48. int aDeMorganConvert, bool aAllowFieldEdits,
  49. bool aShowFootprints, bool aAllowBrowser )
  50. : DIALOG_SHIM( aParent, wxID_ANY, aTitle, wxDefaultPosition, wxDefaultSize,
  51. wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
  52. m_browser_button( nullptr ),
  53. m_hsplitter( nullptr ),
  54. m_vsplitter( nullptr ),
  55. m_fp_sel_ctrl( nullptr ),
  56. m_fp_preview( nullptr ),
  57. m_parent( aParent ),
  58. m_deMorganConvert( aDeMorganConvert >= 0 ? aDeMorganConvert : 0 ),
  59. m_allow_field_edits( aAllowFieldEdits ),
  60. m_show_footprints( aShowFootprints ),
  61. m_external_browser_requested( false )
  62. {
  63. auto sizer = new wxBoxSizer( wxVERTICAL );
  64. wxHtmlWindow* details = nullptr;
  65. // Use a slightly different layout, with a details pane spanning the entire window,
  66. // if we're not showing footprints.
  67. if( aShowFootprints )
  68. {
  69. m_hsplitter = new wxSplitterWindow( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
  70. wxSP_LIVE_UPDATE | wxSP_3DSASH );
  71. //Avoid the splitter window being assigned as the Parent to additional windows
  72. m_hsplitter->SetExtraStyle( wxWS_EX_TRANSIENT );
  73. sizer->Add( m_hsplitter, 1, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 5 );
  74. }
  75. else
  76. {
  77. m_vsplitter = new wxSplitterWindow( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
  78. wxSP_LIVE_UPDATE | wxSP_3DSASH );
  79. m_hsplitter = new wxSplitterWindow( m_vsplitter, wxID_ANY, wxDefaultPosition, wxDefaultSize,
  80. wxSP_LIVE_UPDATE | wxSP_3DSASH );
  81. //Avoid the splitter window being assigned as the Parent to additional windows
  82. m_hsplitter->SetExtraStyle( wxWS_EX_TRANSIENT );
  83. auto detailsPanel = new wxPanel( m_vsplitter );
  84. auto detailsSizer = new wxBoxSizer( wxVERTICAL );
  85. detailsPanel->SetSizer( detailsSizer );
  86. details = new wxHtmlWindow( detailsPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize,
  87. wxHW_SCROLLBAR_AUTO | wxRAISED_BORDER );
  88. detailsSizer->Add( details, 1, wxEXPAND | wxLEFT | wxRIGHT, 5 );
  89. detailsPanel->Layout();
  90. detailsSizer->Fit( detailsPanel );
  91. m_vsplitter->SetSashGravity( 0.5 );
  92. m_vsplitter->SetMinimumPaneSize( 20 );
  93. m_vsplitter->SplitHorizontally( m_hsplitter, detailsPanel );
  94. sizer->Add( m_vsplitter, 1, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 5 );
  95. }
  96. m_tree = new LIB_TREE( m_hsplitter, Prj().SchSymbolLibTable(), aAdapter,
  97. LIB_TREE::WIDGETS::ALL, details );
  98. m_hsplitter->SetSashGravity( 0.8 );
  99. m_hsplitter->SetMinimumPaneSize( 20 );
  100. m_hsplitter->SplitVertically( m_tree, ConstructRightPanel( m_hsplitter ) );
  101. m_dbl_click_timer = new wxTimer( this );
  102. auto buttonsSizer = new wxBoxSizer( wxHORIZONTAL );
  103. if( aAllowBrowser )
  104. {
  105. m_browser_button = new wxButton( this, wxID_ANY, _( "Select with Browser" ) );
  106. buttonsSizer->Add( m_browser_button, 0, wxALL | wxALIGN_CENTER_VERTICAL, 10 );
  107. }
  108. auto sdbSizer = new wxStdDialogButtonSizer();
  109. auto okButton = new wxButton( this, wxID_OK );
  110. auto cancelButton = new wxButton( this, wxID_CANCEL );
  111. sdbSizer->AddButton( okButton );
  112. sdbSizer->AddButton( cancelButton );
  113. sdbSizer->Realize();
  114. buttonsSizer->Add( sdbSizer, 1, wxALL, 5 );
  115. sizer->Add( buttonsSizer, 0, wxEXPAND | wxLEFT, 5 );
  116. SetSizer( sizer );
  117. Bind( wxEVT_INIT_DIALOG, &DIALOG_CHOOSE_COMPONENT::OnInitDialog, this );
  118. Bind( wxEVT_TIMER, &DIALOG_CHOOSE_COMPONENT::OnCloseTimer, this, m_dbl_click_timer->GetId() );
  119. Bind( COMPONENT_PRESELECTED, &DIALOG_CHOOSE_COMPONENT::OnComponentPreselected, this );
  120. Bind( COMPONENT_SELECTED, &DIALOG_CHOOSE_COMPONENT::OnComponentSelected, this );
  121. m_symbol_preview->Bind( wxEVT_PAINT, &DIALOG_CHOOSE_COMPONENT::OnSymbolPreviewPaint, this );
  122. if( m_browser_button )
  123. m_browser_button->Bind( wxEVT_COMMAND_BUTTON_CLICKED, &DIALOG_CHOOSE_COMPONENT::OnUseBrowser, this );
  124. if( m_fp_sel_ctrl )
  125. m_fp_sel_ctrl->Bind( EVT_FOOTPRINT_SELECTED, &DIALOG_CHOOSE_COMPONENT::OnFootprintSelected, this );
  126. Layout();
  127. // We specify the width of the right window (m_symbol_view_panel), because specify
  128. // the width of the left window does not work as expected when SetSashGravity() is called
  129. m_hsplitter->SetSashPosition( m_h_sash_pos ? m_h_sash_pos : HorizPixelsFromDU( 240 ) );
  130. if( m_vsplitter )
  131. m_vsplitter->SetSashPosition( m_v_sash_pos ? m_v_sash_pos : VertPixelsFromDU( 170 ) );
  132. if( m_last_dlg_size == wxSize( -1, -1 ) )
  133. SetSizeInDU( 360, 280 );
  134. else
  135. SetSize( m_last_dlg_size );
  136. SetInitialFocus( m_tree );
  137. okButton->SetDefault();
  138. }
  139. DIALOG_CHOOSE_COMPONENT::~DIALOG_CHOOSE_COMPONENT()
  140. {
  141. Unbind( wxEVT_INIT_DIALOG, &DIALOG_CHOOSE_COMPONENT::OnInitDialog, this );
  142. Unbind( wxEVT_TIMER, &DIALOG_CHOOSE_COMPONENT::OnCloseTimer, this );
  143. Unbind( COMPONENT_PRESELECTED, &DIALOG_CHOOSE_COMPONENT::OnComponentPreselected, this );
  144. Unbind( COMPONENT_SELECTED, &DIALOG_CHOOSE_COMPONENT::OnComponentSelected, this );
  145. m_symbol_preview->Unbind( wxEVT_PAINT, &DIALOG_CHOOSE_COMPONENT::OnSymbolPreviewPaint, this );
  146. if( m_browser_button )
  147. m_browser_button->Unbind( wxEVT_COMMAND_BUTTON_CLICKED, &DIALOG_CHOOSE_COMPONENT::OnUseBrowser, this );
  148. if( m_fp_sel_ctrl )
  149. m_fp_sel_ctrl->Unbind( EVT_FOOTPRINT_SELECTED, &DIALOG_CHOOSE_COMPONENT::OnFootprintSelected, this );
  150. // I am not sure the following two lines are necessary,
  151. // but they will not hurt anyone
  152. m_dbl_click_timer->Stop();
  153. delete m_dbl_click_timer;
  154. m_last_dlg_size = GetSize();
  155. m_h_sash_pos = m_hsplitter->GetSashPosition();
  156. if( m_vsplitter )
  157. m_v_sash_pos = m_vsplitter->GetSashPosition();
  158. }
  159. wxPanel* DIALOG_CHOOSE_COMPONENT::ConstructRightPanel( wxWindow* aParent )
  160. {
  161. auto panel = new wxPanel( aParent );
  162. auto sizer = new wxBoxSizer( wxVERTICAL );
  163. m_symbol_preview = new wxPanel( panel, wxID_ANY, wxDefaultPosition, wxSize( -1, -1 ),
  164. wxFULL_REPAINT_ON_RESIZE | wxTAB_TRAVERSAL | wxRAISED_BORDER );
  165. m_symbol_preview->SetLayoutDirection( wxLayout_LeftToRight );
  166. if( m_show_footprints )
  167. {
  168. FOOTPRINT_LIST* fp_list = FOOTPRINT_LIST::GetInstance( Kiway() );
  169. if( m_allow_field_edits )
  170. m_fp_sel_ctrl = new FOOTPRINT_SELECT_WIDGET( panel, fp_list, true );
  171. m_fp_preview = new FOOTPRINT_PREVIEW_WIDGET( panel, Kiway() );
  172. sizer->Add( m_symbol_preview, 1, wxEXPAND | wxTOP | wxBOTTOM | wxRIGHT, 5 );
  173. if( m_fp_sel_ctrl )
  174. sizer->Add( m_fp_sel_ctrl, 0, wxEXPAND | wxTOP | wxRIGHT, 5 );
  175. sizer->Add( m_fp_preview, 1, wxEXPAND | wxBOTTOM | wxRIGHT, 5 );
  176. }
  177. else
  178. {
  179. sizer->Add( m_symbol_preview, 1, wxEXPAND | wxTOP | wxRIGHT, 5 );
  180. }
  181. panel->SetSizer( sizer );
  182. panel->Layout();
  183. sizer->Fit( panel );
  184. return panel;
  185. }
  186. void DIALOG_CHOOSE_COMPONENT::OnInitDialog( wxInitDialogEvent& aEvent )
  187. {
  188. if( m_fp_preview && m_fp_preview->IsInitialized() )
  189. {
  190. // This hides the GAL panel and shows the status label
  191. m_fp_preview->SetStatusText( wxEmptyString );
  192. }
  193. if( m_fp_sel_ctrl )
  194. m_fp_sel_ctrl->Load( Kiway(), Prj() );
  195. }
  196. LIB_ID DIALOG_CHOOSE_COMPONENT::GetSelectedLibId( int* aUnit ) const
  197. {
  198. return m_tree->GetSelectedLibId( aUnit );
  199. }
  200. void DIALOG_CHOOSE_COMPONENT::OnUseBrowser( wxCommandEvent& aEvent )
  201. {
  202. m_external_browser_requested = true;
  203. EndQuasiModal( wxID_OK );
  204. }
  205. void DIALOG_CHOOSE_COMPONENT::OnCloseTimer( wxTimerEvent& aEvent )
  206. {
  207. // Hack handler because of eaten MouseUp event. See
  208. // DIALOG_CHOOSE_COMPONENT::OnComponentSelected for the beginning
  209. // of this spaghetti noodle.
  210. auto state = wxGetMouseState();
  211. if( state.LeftIsDown() )
  212. {
  213. // Mouse hasn't been raised yet, so fire the timer again. Otherwise the
  214. // purpose of this timer is defeated.
  215. m_dbl_click_timer->StartOnce( DIALOG_CHOOSE_COMPONENT::DblClickDelay );
  216. }
  217. else
  218. {
  219. EndQuasiModal( wxID_OK );
  220. }
  221. }
  222. void DIALOG_CHOOSE_COMPONENT::ShowFootprintFor( LIB_ID const& aLibId )
  223. {
  224. if( !m_fp_preview || !m_fp_preview->IsInitialized() )
  225. return;
  226. LIB_ALIAS* alias = nullptr;
  227. try
  228. {
  229. alias = Prj().SchSymbolLibTable()->LoadSymbol( aLibId );
  230. }
  231. catch( const IO_ERROR& ioe )
  232. {
  233. wxLogError( wxString::Format( _( "Error occurred loading symbol %s from library %s."
  234. "\n\n%s" ),
  235. aLibId.GetLibItemName().wx_str(),
  236. aLibId.GetLibNickname().wx_str(),
  237. ioe.What() ) );
  238. }
  239. if( !alias )
  240. return;
  241. LIB_FIELD* fp_field = alias->GetPart()->GetField( FOOTPRINT );
  242. wxString fp_name = fp_field ? fp_field->GetFullText() : wxString( "" );
  243. ShowFootprint( fp_name );
  244. }
  245. void DIALOG_CHOOSE_COMPONENT::ShowFootprint( wxString const& aName )
  246. {
  247. if( !m_fp_preview || !m_fp_preview->IsInitialized() )
  248. return;
  249. if( aName == wxEmptyString )
  250. {
  251. m_fp_preview->SetStatusText( _( "No footprint specified" ) );
  252. }
  253. else
  254. {
  255. LIB_ID lib_id;
  256. if( lib_id.Parse( aName, LIB_ID::ID_PCB ) == -1 && lib_id.IsValid() )
  257. {
  258. m_fp_preview->ClearStatus();
  259. m_fp_preview->CacheFootprint( lib_id );
  260. m_fp_preview->DisplayFootprint( lib_id );
  261. }
  262. else
  263. {
  264. m_fp_preview->SetStatusText( _( "Invalid footprint specified" ) );
  265. }
  266. }
  267. }
  268. void DIALOG_CHOOSE_COMPONENT::PopulateFootprintSelector( LIB_ID const& aLibId )
  269. {
  270. if( !m_fp_sel_ctrl )
  271. return;
  272. m_fp_sel_ctrl->ClearFilters();
  273. LIB_ALIAS* alias = nullptr;
  274. if( aLibId.IsValid() )
  275. {
  276. try
  277. {
  278. alias = Prj().SchSymbolLibTable()->LoadSymbol( aLibId );
  279. }
  280. catch( const IO_ERROR& ioe )
  281. {
  282. wxLogError( wxString::Format( _( "Error occurred loading symbol %s from library %s."
  283. "\n\n%s" ),
  284. aLibId.GetLibItemName().wx_str(),
  285. aLibId.GetLibNickname().wx_str(),
  286. ioe.What() ) );
  287. }
  288. }
  289. if( alias != nullptr )
  290. {
  291. LIB_PINS temp_pins;
  292. LIB_FIELD* fp_field = alias->GetPart()->GetField( FOOTPRINT );
  293. wxString fp_name = fp_field ? fp_field->GetFullText() : wxString( "" );
  294. alias->GetPart()->GetPins( temp_pins );
  295. m_fp_sel_ctrl->FilterByPinCount( temp_pins.size() );
  296. m_fp_sel_ctrl->FilterByFootprintFilters( alias->GetPart()->GetFootprints(), true );
  297. m_fp_sel_ctrl->SetDefaultFootprint( fp_name );
  298. m_fp_sel_ctrl->UpdateList();
  299. m_fp_sel_ctrl->Enable();
  300. }
  301. else
  302. {
  303. m_fp_sel_ctrl->UpdateList();
  304. m_fp_sel_ctrl->Disable();
  305. }
  306. }
  307. void DIALOG_CHOOSE_COMPONENT::OnSymbolPreviewPaint( wxPaintEvent& aEvent )
  308. {
  309. int unit = 0;
  310. LIB_ID id = m_tree->GetSelectedLibId( &unit );
  311. if( !id.IsValid() )
  312. {
  313. // No symbol to show, display a tooltip
  314. RenderPreview( nullptr, unit );
  315. return;
  316. }
  317. LIB_ALIAS* alias = nullptr;
  318. try
  319. {
  320. alias = Prj().SchSymbolLibTable()->LoadSymbol( id );
  321. }
  322. catch( const IO_ERROR& ioe )
  323. {
  324. wxLogError( wxString::Format( _( "Error occurred loading symbol %s from library %s."
  325. "\n\n%s" ),
  326. id.GetLibItemName().wx_str(),
  327. id.GetLibNickname().wx_str(),
  328. ioe.What() ) );
  329. }
  330. if( !alias )
  331. return;
  332. LIB_PART* part = alias->GetPart();
  333. if( !part )
  334. return;
  335. if( alias->IsRoot() )
  336. {
  337. // just show the part directly
  338. RenderPreview( part, unit );
  339. }
  340. else
  341. {
  342. // switch out the name temporarily for the alias name
  343. wxString tmp( part->GetName() );
  344. part->SetName( alias->GetName() );
  345. RenderPreview( part, unit );
  346. part->SetName( tmp );
  347. }
  348. }
  349. void DIALOG_CHOOSE_COMPONENT::OnFootprintSelected( wxCommandEvent& aEvent )
  350. {
  351. m_fp_override = aEvent.GetString();
  352. m_field_edits.erase(
  353. std::remove_if( m_field_edits.begin(), m_field_edits.end(),
  354. []( std::pair<int, wxString> const& i ) { return i.first == FOOTPRINT; } ),
  355. m_field_edits.end() );
  356. m_field_edits.push_back( std::make_pair( FOOTPRINT, m_fp_override ) );
  357. ShowFootprint( m_fp_override );
  358. }
  359. void DIALOG_CHOOSE_COMPONENT::OnComponentPreselected( wxCommandEvent& aEvent )
  360. {
  361. int unit = 0;
  362. LIB_ID id = m_tree->GetSelectedLibId( &unit );
  363. m_symbol_preview->Refresh();
  364. if( id.IsValid() )
  365. {
  366. ShowFootprintFor( id );
  367. PopulateFootprintSelector( id );
  368. }
  369. else
  370. {
  371. if( m_fp_preview && m_fp_preview->IsInitialized() )
  372. m_fp_preview->SetStatusText( wxEmptyString );
  373. PopulateFootprintSelector( id );
  374. }
  375. }
  376. void DIALOG_CHOOSE_COMPONENT::OnComponentSelected( wxCommandEvent& aEvent )
  377. {
  378. if( m_tree->GetSelectedLibId().IsValid() )
  379. {
  380. // Got a selection. We can't just end the modal dialog here, because
  381. // wx leaks some events back to the parent window (in particular, the
  382. // MouseUp following a double click).
  383. //
  384. // NOW, here's where it gets really fun. wxTreeListCtrl eats MouseUp.
  385. // This isn't really feasible to bypass without a fully custom
  386. // wxDataViewCtrl implementation, and even then might not be fully
  387. // possible (docs are vague). To get around this, we use a one-shot
  388. // timer to schedule the dialog close.
  389. //
  390. // See DIALOG_CHOOSE_COMPONENT::OnCloseTimer for the other end of this
  391. // spaghetti noodle.
  392. m_dbl_click_timer->StartOnce( DIALOG_CHOOSE_COMPONENT::DblClickDelay );
  393. }
  394. }
  395. void DIALOG_CHOOSE_COMPONENT::RenderPreview( LIB_PART* aComponent, int aUnit )
  396. {
  397. wxPaintDC dc( m_symbol_preview );
  398. const wxSize dc_size = dc.GetSize();
  399. // Avoid rendering when either dimension is zero
  400. if( dc_size.x == 0 || dc_size.y == 0 )
  401. return;
  402. if( !aComponent )
  403. return;
  404. GRResetPenAndBrush( &dc );
  405. COLOR4D bgColor = m_parent->GetDrawBgColor();
  406. dc.SetBackground( wxBrush( bgColor.ToColour() ) );
  407. dc.Clear();
  408. int unit = aUnit > 0 ? aUnit : 1;
  409. int convert = m_deMorganConvert > 0 ? m_deMorganConvert : 1;
  410. dc.SetDeviceOrigin( dc_size.x / 2, dc_size.y / 2 );
  411. // Find joint bounding box for everything we are about to draw.
  412. EDA_RECT bBox = aComponent->GetUnitBoundingBox( unit, convert );
  413. const double xscale = (double) dc_size.x / bBox.GetWidth();
  414. const double yscale = (double) dc_size.y / bBox.GetHeight();
  415. const double scale = std::min( xscale, yscale ) * 0.85;
  416. dc.SetUserScale( scale, scale );
  417. wxPoint offset = -bBox.Centre();
  418. auto opts = PART_DRAW_OPTIONS::Default();
  419. opts.draw_hidden_fields = false;
  420. aComponent->Draw( nullptr, &dc, offset, unit, convert, opts );
  421. }