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.

380 lines
11 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) 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. #include <dialog_choose_component.h>
  26. #include <algorithm>
  27. #include <set>
  28. #include <wx/utils.h>
  29. #include <wx/button.h>
  30. #include <wx/dataview.h>
  31. #include <wx/panel.h>
  32. #include <wx/sizer.h>
  33. #include <wx/splitter.h>
  34. #include <wx/timer.h>
  35. #include <wx/utils.h>
  36. #include <class_library.h>
  37. #include <sch_base_frame.h>
  38. #include <template_fieldnames.h>
  39. #include <widgets/component_tree.h>
  40. #include <widgets/footprint_preview_widget.h>
  41. #include <widgets/footprint_select_widget.h>
  42. FOOTPRINT_ASYNC_LOADER DIALOG_CHOOSE_COMPONENT::m_fp_loader;
  43. std::unique_ptr<FOOTPRINT_LIST> DIALOG_CHOOSE_COMPONENT::m_fp_list;
  44. DIALOG_CHOOSE_COMPONENT::DIALOG_CHOOSE_COMPONENT( SCH_BASE_FRAME* aParent, const wxString& aTitle,
  45. CMP_TREE_MODEL_ADAPTER::PTR& aAdapter, int aDeMorganConvert, bool aAllowFieldEdits )
  46. : DIALOG_SHIM( aParent, wxID_ANY, aTitle, wxDefaultPosition, wxSize( 800, 650 ),
  47. wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
  48. m_parent( aParent ),
  49. m_deMorganConvert( aDeMorganConvert >= 0 ? aDeMorganConvert : 0 ),
  50. m_allow_field_edits( aAllowFieldEdits ),
  51. m_external_browser_requested( false )
  52. {
  53. wxBusyCursor busy_while_loading;
  54. auto sizer = new wxBoxSizer( wxVERTICAL );
  55. auto splitter = new wxSplitterWindow(
  56. this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_LIVE_UPDATE );
  57. m_tree = new COMPONENT_TREE( splitter, aAdapter );
  58. auto right_panel = ConstructRightPanel( splitter );
  59. auto buttons = new wxStdDialogButtonSizer();
  60. m_dbl_click_timer = new wxTimer( this );
  61. splitter->SetSashGravity( 0.9 );
  62. splitter->SetMinimumPaneSize( 1 );
  63. splitter->SplitVertically( m_tree, right_panel, -300 );
  64. buttons->AddButton( new wxButton( this, wxID_OK ) );
  65. buttons->AddButton( new wxButton( this, wxID_CANCEL ) );
  66. buttons->Realize();
  67. sizer->Add( splitter, 1, wxEXPAND | wxALL, 5 );
  68. sizer->Add( buttons, 0, wxEXPAND | wxBOTTOM, 10 );
  69. SetSizer( sizer );
  70. Bind( wxEVT_INIT_DIALOG, &DIALOG_CHOOSE_COMPONENT::OnInitDialog, this );
  71. Bind( wxEVT_TIMER, &DIALOG_CHOOSE_COMPONENT::OnCloseTimer, this, m_dbl_click_timer->GetId() );
  72. Bind( wxEVT_DATAVIEW_ITEM_ACTIVATED, &DIALOG_CHOOSE_COMPONENT::OnTreeActivate, this );
  73. Bind( wxEVT_DATAVIEW_SELECTION_CHANGED, &DIALOG_CHOOSE_COMPONENT::OnTreeSelect, this );
  74. m_sch_view_ctrl->Bind( wxEVT_LEFT_DCLICK, &DIALOG_CHOOSE_COMPONENT::OnSchViewDClick, this );
  75. m_sch_view_ctrl->Bind( wxEVT_PAINT, &DIALOG_CHOOSE_COMPONENT::OnSchViewPaint, this );
  76. if( m_fp_sel_ctrl )
  77. m_fp_sel_ctrl->Bind(
  78. EVT_FOOTPRINT_SELECTED, &DIALOG_CHOOSE_COMPONENT::OnFootprintSelected, this );
  79. Layout();
  80. }
  81. DIALOG_CHOOSE_COMPONENT::~DIALOG_CHOOSE_COMPONENT()
  82. {
  83. // I am not sure the following two lines are necessary,
  84. // but they will not hurt anyone
  85. m_dbl_click_timer->Stop();
  86. Unbind( wxEVT_TIMER, &DIALOG_CHOOSE_COMPONENT::OnCloseTimer, this );
  87. delete m_dbl_click_timer;
  88. }
  89. wxPanel* DIALOG_CHOOSE_COMPONENT::ConstructRightPanel( wxWindow* aParent )
  90. {
  91. auto panel = new wxPanel( aParent );
  92. auto sizer = new wxBoxSizer( wxVERTICAL );
  93. m_sch_view_ctrl = new wxPanel( panel, wxID_ANY, wxDefaultPosition, wxSize( -1, -1 ),
  94. wxFULL_REPAINT_ON_RESIZE | wxSUNKEN_BORDER | wxTAB_TRAVERSAL );
  95. m_sch_view_ctrl->SetLayoutDirection( wxLayout_LeftToRight );
  96. if( m_allow_field_edits )
  97. m_fp_sel_ctrl = new FOOTPRINT_SELECT_WIDGET( panel, m_fp_loader, m_fp_list, true );
  98. else
  99. m_fp_sel_ctrl = nullptr;
  100. m_fp_view_ctrl = new FOOTPRINT_PREVIEW_WIDGET( panel, Kiway() );
  101. sizer->Add( m_sch_view_ctrl, 1, wxEXPAND | wxALL, 5 );
  102. if( m_fp_sel_ctrl )
  103. sizer->Add( m_fp_sel_ctrl, 0, wxEXPAND | wxALL, 5 );
  104. sizer->Add( m_fp_view_ctrl, 1, wxEXPAND | wxALL, 5 );
  105. panel->SetSizer( sizer );
  106. panel->Layout();
  107. sizer->Fit( panel );
  108. return panel;
  109. }
  110. void DIALOG_CHOOSE_COMPONENT::OnInitDialog( wxInitDialogEvent& aEvent )
  111. {
  112. if( m_fp_view_ctrl->IsInitialized() )
  113. {
  114. // This hides the GAL panel and shows the status label
  115. m_fp_view_ctrl->SetStatusText( wxEmptyString );
  116. }
  117. if( m_fp_sel_ctrl )
  118. m_fp_sel_ctrl->Load( Kiway(), Prj() );
  119. }
  120. LIB_ALIAS* DIALOG_CHOOSE_COMPONENT::GetSelectedAlias( int* aUnit ) const
  121. {
  122. return m_tree->GetSelectedAlias( aUnit );
  123. }
  124. void DIALOG_CHOOSE_COMPONENT::OnTreeSelect( wxDataViewEvent& aEvent )
  125. {
  126. std::cout << "dialog choose component handler" << std::endl; // TODO
  127. int unit = 0;
  128. LIB_ALIAS* alias = m_tree->GetSelectedAlias( &unit );
  129. m_sch_view_ctrl->Refresh();
  130. if( alias )
  131. {
  132. ShowFootprintFor( alias );
  133. PopulateFootprintSelector( alias );
  134. }
  135. else
  136. {
  137. if( m_fp_view_ctrl->IsInitialized() )
  138. m_fp_view_ctrl->SetStatusText( wxEmptyString );
  139. PopulateFootprintSelector( nullptr );
  140. }
  141. }
  142. void DIALOG_CHOOSE_COMPONENT::OnTreeActivate( wxDataViewEvent& aEvent )
  143. {
  144. HandleItemSelection();
  145. }
  146. void DIALOG_CHOOSE_COMPONENT::OnCloseTimer( wxTimerEvent& aEvent )
  147. {
  148. // Hack handler because of eaten MouseUp event. See
  149. // DIALOG_CHOOSE_COMPONENT::OnDoubleClickTreeActivation for the beginning
  150. // of this spaghetti noodle.
  151. auto state = wxGetMouseState();
  152. if( state.LeftIsDown() )
  153. {
  154. // Mouse hasn't been raised yet, so fire the timer again. Otherwise the
  155. // purpose of this timer is defeated.
  156. m_dbl_click_timer->StartOnce( DIALOG_CHOOSE_COMPONENT::DblClickDelay );
  157. }
  158. else
  159. {
  160. EndQuasiModal( wxID_OK );
  161. }
  162. }
  163. void DIALOG_CHOOSE_COMPONENT::OnSchViewDClick( wxMouseEvent& aEvent )
  164. {
  165. m_external_browser_requested = true;
  166. EndQuasiModal( wxID_OK );
  167. }
  168. void DIALOG_CHOOSE_COMPONENT::ShowFootprintFor( LIB_ALIAS* aAlias )
  169. {
  170. if( !m_fp_view_ctrl->IsInitialized() )
  171. return;
  172. LIB_FIELD* fp_field = aAlias->GetPart()->GetField( FOOTPRINT );
  173. wxString fp_name = fp_field ? fp_field->GetFullText() : wxString( "" );
  174. ShowFootprint( fp_name );
  175. }
  176. void DIALOG_CHOOSE_COMPONENT::ShowFootprint( wxString const& aName )
  177. {
  178. if( aName == wxEmptyString )
  179. {
  180. m_fp_view_ctrl->SetStatusText( _( "No footprint specified" ) );
  181. }
  182. else
  183. {
  184. LIB_ID lib_id( aName );
  185. m_fp_view_ctrl->ClearStatus();
  186. m_fp_view_ctrl->CacheFootprint( lib_id );
  187. m_fp_view_ctrl->DisplayFootprint( lib_id );
  188. }
  189. }
  190. void DIALOG_CHOOSE_COMPONENT::PopulateFootprintSelector( LIB_ALIAS* aAlias )
  191. {
  192. if( !m_fp_sel_ctrl )
  193. return;
  194. m_fp_sel_ctrl->ClearFilters();
  195. if( aAlias )
  196. {
  197. LIB_PINS temp_pins;
  198. LIB_FIELD* fp_field = aAlias->GetPart()->GetField( FOOTPRINT );
  199. wxString fp_name = fp_field ? fp_field->GetFullText() : wxString( "" );
  200. aAlias->GetPart()->GetPins( temp_pins );
  201. m_fp_sel_ctrl->FilterByPinCount( temp_pins.size() );
  202. m_fp_sel_ctrl->FilterByFootprintFilters( aAlias->GetPart()->GetFootPrints(), true );
  203. m_fp_sel_ctrl->SetDefaultFootprint( fp_name );
  204. m_fp_sel_ctrl->UpdateList();
  205. m_fp_sel_ctrl->Enable();
  206. }
  207. else
  208. {
  209. m_fp_sel_ctrl->UpdateList();
  210. m_fp_sel_ctrl->Disable();
  211. }
  212. }
  213. void DIALOG_CHOOSE_COMPONENT::OnSchViewPaint( wxPaintEvent& aEvent )
  214. {
  215. int unit = 0;
  216. LIB_ALIAS* alias = m_tree->GetSelectedAlias( &unit );
  217. LIB_PART* part = alias ? alias->GetPart() : nullptr;
  218. // Don't draw anything (not even the background) if we don't have
  219. // a part to show
  220. if( !part )
  221. return;
  222. if( alias->IsRoot() )
  223. {
  224. // just show the part directly
  225. RenderPreview( part, unit );
  226. }
  227. else
  228. {
  229. // switch out the name temporarily for the alias name
  230. wxString tmp( part->GetName() );
  231. part->SetName( alias->GetName() );
  232. RenderPreview( part, unit );
  233. part->SetName( tmp );
  234. }
  235. }
  236. void DIALOG_CHOOSE_COMPONENT::OnFootprintSelected( wxCommandEvent& aEvent )
  237. {
  238. m_fp_override = aEvent.GetString();
  239. m_field_edits.erase(
  240. std::remove_if( m_field_edits.begin(), m_field_edits.end(),
  241. []( std::pair<int, wxString> const& i ) { return i.first == FOOTPRINT; } ),
  242. m_field_edits.end() );
  243. m_field_edits.push_back( std::make_pair( FOOTPRINT, m_fp_override ) );
  244. ShowFootprint( m_fp_override );
  245. }
  246. void DIALOG_CHOOSE_COMPONENT::RenderPreview( LIB_PART* aComponent, int aUnit )
  247. {
  248. wxPaintDC dc( m_sch_view_ctrl );
  249. const wxSize dc_size = dc.GetSize();
  250. // Avoid rendering when either dimension is zero
  251. if( dc_size.x == 0 || dc_size.y == 0 )
  252. return;
  253. GRResetPenAndBrush( &dc );
  254. COLOR4D bgColor = m_parent->GetDrawBgColor();
  255. dc.SetBackground( wxBrush( bgColor.ToColour() ) );
  256. dc.Clear();
  257. if( !aComponent )
  258. return;
  259. if( aUnit <= 0 )
  260. aUnit = 1;
  261. dc.SetDeviceOrigin( dc_size.x / 2, dc_size.y / 2 );
  262. // Find joint bounding box for everything we are about to draw.
  263. EDA_RECT bBox = aComponent->GetUnitBoundingBox( aUnit, m_deMorganConvert );
  264. const double xscale = (double) dc_size.x / bBox.GetWidth();
  265. const double yscale = (double) dc_size.y / bBox.GetHeight();
  266. const double scale = std::min( xscale, yscale ) * 0.85;
  267. dc.SetUserScale( scale, scale );
  268. wxPoint offset = -bBox.Centre();
  269. auto opts = PART_DRAW_OPTIONS::Default();
  270. opts.draw_hidden_fields = false;
  271. aComponent->Draw( nullptr, &dc, offset, aUnit, m_deMorganConvert, opts );
  272. }
  273. void DIALOG_CHOOSE_COMPONENT::HandleItemSelection()
  274. {
  275. if( m_tree->GetSelectedAlias() )
  276. {
  277. // Got a selection. We can't just end the modal dialog here, because
  278. // wx leaks some events back to the parent window (in particular, the
  279. // MouseUp following a double click).
  280. //
  281. // NOW, here's where it gets really fun. wxTreeListCtrl eats MouseUp.
  282. // This isn't really feasible to bypass without a fully custom
  283. // wxDataViewCtrl implementation, and even then might not be fully
  284. // possible (docs are vague). To get around this, we use a one-shot
  285. // timer to schedule the dialog close.
  286. //
  287. // See DIALOG_CHOOSE_COMPONENT::OnCloseTimer for the other end of this
  288. // spaghetti noodle.
  289. m_dbl_click_timer->StartOnce( DIALOG_CHOOSE_COMPONENT::DblClickDelay );
  290. }
  291. }