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.

437 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) 2014-2019 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 "lib_tree.h"
  25. #include <wxdataviewctrl_helpers.h>
  26. #include <wx/artprov.h>
  27. #include <wx/sizer.h>
  28. #include <wx/html/htmlwin.h>
  29. #include <tool/tool_interactive.h>
  30. #include <tool/tool_manager.h>
  31. LIB_TREE::LIB_TREE( wxWindow* aParent, LIB_TABLE* aLibTable, LIB_TREE_MODEL_ADAPTER::PTR& aAdapter,
  32. WIDGETS aWidgets, wxHtmlWindow* aDetails )
  33. : wxPanel( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize,
  34. wxWANTS_CHARS | wxTAB_TRAVERSAL | wxNO_BORDER ),
  35. m_lib_table( aLibTable ),
  36. m_adapter( aAdapter ),
  37. m_query_ctrl( nullptr ),
  38. m_details_ctrl( nullptr )
  39. {
  40. auto sizer = new wxBoxSizer( wxVERTICAL );
  41. // Search text control
  42. if( aWidgets & SEARCH )
  43. {
  44. auto search_sizer = new wxBoxSizer( wxHORIZONTAL );
  45. m_query_ctrl = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition,
  46. wxDefaultSize, wxTE_PROCESS_ENTER );
  47. // Additional visual cue for GTK, which hides the placeholder text on focus
  48. #ifdef __WXGTK__
  49. auto bitmap = new wxStaticBitmap( this, wxID_ANY, wxArtProvider::GetBitmap( wxART_FIND, wxART_FRAME_ICON ) );
  50. if( aWidgets & DETAILS )
  51. search_sizer->Add( bitmap, 0, wxALIGN_CENTER | wxALL, 5 );
  52. else
  53. search_sizer->Add( bitmap, 0, wxALIGN_CENTER | wxRIGHT, 5 );
  54. #endif
  55. if( aWidgets & DETAILS )
  56. search_sizer->Add( m_query_ctrl, 1, wxLEFT | wxTOP | wxRIGHT | wxEXPAND, 5 );
  57. else
  58. search_sizer->Add( m_query_ctrl, 1, wxEXPAND, 5 );
  59. sizer->Add( search_sizer, 0, wxEXPAND, 5 );
  60. m_query_ctrl->Bind( wxEVT_TEXT, &LIB_TREE::onQueryText, this );
  61. m_query_ctrl->Bind( wxEVT_TEXT_ENTER, &LIB_TREE::onQueryEnter, this );
  62. m_query_ctrl->Bind( wxEVT_CHAR_HOOK, &LIB_TREE::onQueryCharHook, this );
  63. }
  64. // Tree control
  65. m_tree_ctrl = new wxDataViewCtrl( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
  66. wxDV_SINGLE );
  67. m_adapter->AttachTo( m_tree_ctrl );
  68. if( aWidgets & DETAILS )
  69. sizer->Add( m_tree_ctrl, 5, wxLEFT | wxTOP | wxRIGHT | wxEXPAND, 5 );
  70. else
  71. sizer->Add( m_tree_ctrl, 5, wxRIGHT | wxBOTTOM | wxEXPAND, 1 );
  72. // Description panel
  73. if( aWidgets & DETAILS )
  74. {
  75. if( !aDetails )
  76. {
  77. auto html_sz = ConvertDialogToPixels( wxPoint( 80, 80 ) );
  78. m_details_ctrl = new wxHtmlWindow(
  79. this, wxID_ANY, wxDefaultPosition, wxSize( html_sz.x, html_sz.y ),
  80. wxHW_SCROLLBAR_AUTO );
  81. sizer->Add( m_details_ctrl, 2, wxALL | wxEXPAND, 5 );
  82. }
  83. else
  84. {
  85. m_details_ctrl = aDetails;
  86. }
  87. m_details_ctrl->Bind( wxEVT_HTML_LINK_CLICKED, &LIB_TREE::onDetailsLink, this );
  88. }
  89. SetSizer( sizer );
  90. m_tree_ctrl->Bind( wxEVT_DATAVIEW_ITEM_ACTIVATED, &LIB_TREE::onTreeActivate, this );
  91. m_tree_ctrl->Bind( wxEVT_DATAVIEW_SELECTION_CHANGED, &LIB_TREE::onTreeSelect, this );
  92. m_tree_ctrl->Bind( wxEVT_COMMAND_DATAVIEW_ITEM_CONTEXT_MENU, &LIB_TREE::onContextMenu, this );
  93. Bind( COMPONENT_PRESELECTED, &LIB_TREE::onPreselect, this );
  94. // If wxTextCtrl::SetHint() is called before binding wxEVT_TEXT, the event
  95. // handler will intermittently fire.
  96. if( m_query_ctrl )
  97. {
  98. m_query_ctrl->SetHint( _( "Filter" ) );
  99. m_query_ctrl->SetFocus();
  100. m_query_ctrl->SetValue( wxEmptyString ); // SetValue() is required here to kick off
  101. // initial sorting and pre-selection.
  102. }
  103. // There may be a part preselected in the model. Make sure it is displayed.
  104. postPreselectEvent();
  105. Layout();
  106. sizer->Fit( this );
  107. #ifdef __WXGTK__
  108. // Scrollbars must be always enabled to prevent an infinite event loop
  109. // more details: http://trac.wxwidgets.org/ticket/18141
  110. if( m_details_ctrl )
  111. m_details_ctrl->ShowScrollbars( wxSHOW_SB_ALWAYS, wxSHOW_SB_ALWAYS );
  112. #endif /* __WXGTK__ */
  113. }
  114. LIB_TREE::~LIB_TREE()
  115. {
  116. // Save the column widths to the config file
  117. m_adapter->SaveColWidths();
  118. m_adapter->SavePinnedItems();
  119. }
  120. LIB_ID LIB_TREE::GetSelectedLibId( int* aUnit ) const
  121. {
  122. auto sel = m_tree_ctrl->GetSelection();
  123. if( !sel )
  124. {
  125. LIB_ID emptyId;
  126. return emptyId;
  127. }
  128. if( aUnit )
  129. *aUnit = m_adapter->GetUnitFor( sel );
  130. return m_adapter->GetAliasFor( sel );
  131. }
  132. LIB_TREE_NODE* LIB_TREE::GetCurrentTreeNode() const
  133. {
  134. auto sel = m_tree_ctrl->GetSelection();
  135. if( !sel )
  136. return nullptr;
  137. return m_adapter->GetTreeNodeFor( sel );
  138. }
  139. void LIB_TREE::SelectLibId( const LIB_ID& aLibId )
  140. {
  141. selectIfValid( m_adapter->FindItem( aLibId ) );
  142. }
  143. void LIB_TREE::CenterLibId( const LIB_ID& aLibId )
  144. {
  145. centerIfValid( m_adapter->FindItem( aLibId ) );
  146. }
  147. void LIB_TREE::Unselect()
  148. {
  149. m_tree_ctrl->UnselectAll();
  150. }
  151. void LIB_TREE::ExpandLibId( const LIB_ID& aLibId )
  152. {
  153. expandIfValid( m_adapter->FindItem( aLibId ) );
  154. }
  155. void LIB_TREE::Regenerate( bool aKeepState )
  156. {
  157. STATE current;
  158. // Store the state
  159. if( aKeepState )
  160. current = getState();
  161. wxString filter = m_query_ctrl->GetValue();
  162. m_adapter->UpdateSearchString( filter );
  163. postPreselectEvent();
  164. // Restore the state
  165. if( aKeepState )
  166. setState( current );
  167. }
  168. void LIB_TREE::RefreshLibTree()
  169. {
  170. m_adapter->RefreshTree();
  171. }
  172. void LIB_TREE::SetFocus()
  173. {
  174. if( m_query_ctrl )
  175. m_query_ctrl->SetFocus();
  176. else
  177. m_tree_ctrl->SetFocus();
  178. }
  179. void LIB_TREE::toggleExpand( const wxDataViewItem& aTreeId )
  180. {
  181. if( !aTreeId.IsOk() )
  182. return;
  183. if( m_tree_ctrl->IsExpanded( aTreeId ) )
  184. m_tree_ctrl->Collapse( aTreeId );
  185. else
  186. m_tree_ctrl->Expand( aTreeId );
  187. }
  188. void LIB_TREE::selectIfValid( const wxDataViewItem& aTreeId )
  189. {
  190. if( aTreeId.IsOk() )
  191. {
  192. m_tree_ctrl->EnsureVisible( aTreeId );
  193. m_tree_ctrl->Select( aTreeId );
  194. postPreselectEvent();
  195. }
  196. }
  197. void LIB_TREE::centerIfValid( const wxDataViewItem& aTreeId )
  198. {
  199. if( aTreeId.IsOk() )
  200. m_tree_ctrl->EnsureVisible( aTreeId );
  201. }
  202. void LIB_TREE::expandIfValid( const wxDataViewItem& aTreeId )
  203. {
  204. if( aTreeId.IsOk() && !m_tree_ctrl->IsExpanded( aTreeId ) )
  205. m_tree_ctrl->Expand( aTreeId );
  206. }
  207. void LIB_TREE::postPreselectEvent()
  208. {
  209. wxCommandEvent event( COMPONENT_PRESELECTED );
  210. wxPostEvent( this, event );
  211. }
  212. void LIB_TREE::postSelectEvent()
  213. {
  214. wxCommandEvent event( COMPONENT_SELECTED );
  215. wxPostEvent( this, event );
  216. }
  217. LIB_TREE::STATE LIB_TREE::getState() const
  218. {
  219. STATE state;
  220. wxDataViewItemArray items;
  221. m_adapter->GetChildren( wxDataViewItem( nullptr ), items );
  222. for( const auto& item : items )
  223. {
  224. if( m_tree_ctrl->IsExpanded( item ) )
  225. state.expanded.push_back( item );
  226. }
  227. state.selection = GetSelectedLibId();
  228. return state;
  229. }
  230. void LIB_TREE::setState( const STATE& aState )
  231. {
  232. m_tree_ctrl->Freeze();
  233. for( const auto& item : aState.expanded )
  234. m_tree_ctrl->Expand( item );
  235. // wxDataViewCtrl cannot be frozen when a selection
  236. // command is issued, otherwise it selects a random item (Windows)
  237. m_tree_ctrl->Thaw();
  238. if( !aState.selection.GetLibItemName().empty() || !aState.selection.GetLibNickname().empty() )
  239. SelectLibId( aState.selection );
  240. }
  241. void LIB_TREE::onQueryText( wxCommandEvent& aEvent )
  242. {
  243. Regenerate( false );
  244. // Required to avoid interaction with SetHint()
  245. // See documentation for wxTextEntry::SetHint
  246. aEvent.Skip();
  247. }
  248. void LIB_TREE::onQueryEnter( wxCommandEvent& aEvent )
  249. {
  250. if( GetSelectedLibId().IsValid() )
  251. postSelectEvent();
  252. }
  253. void LIB_TREE::onQueryCharHook( wxKeyEvent& aKeyStroke )
  254. {
  255. auto const sel = m_tree_ctrl->GetSelection();
  256. auto type = sel.IsOk() ? m_adapter->GetTypeFor( sel ) : LIB_TREE_NODE::INVALID;
  257. switch( aKeyStroke.GetKeyCode() )
  258. {
  259. case WXK_UP:
  260. selectIfValid( GetPrevItem( *m_tree_ctrl, sel ) );
  261. break;
  262. case WXK_DOWN:
  263. selectIfValid( GetNextItem( *m_tree_ctrl, sel ) );
  264. break;
  265. case WXK_ADD:
  266. if( type == LIB_TREE_NODE::LIB )
  267. m_tree_ctrl->Expand( sel );
  268. break;
  269. case WXK_SUBTRACT:
  270. if( type == LIB_TREE_NODE::LIB )
  271. m_tree_ctrl->Collapse( sel );
  272. break;
  273. case WXK_RETURN:
  274. if( type == LIB_TREE_NODE::LIB )
  275. {
  276. toggleExpand( sel );
  277. break;
  278. }
  279. // Intentionally fall through, so the selected component will be treated as the selected one
  280. default:
  281. aKeyStroke.Skip(); // Any other key: pass on to search box directly.
  282. break;
  283. }
  284. }
  285. void LIB_TREE::onTreeSelect( wxDataViewEvent& aEvent )
  286. {
  287. postPreselectEvent();
  288. }
  289. void LIB_TREE::onTreeActivate( wxDataViewEvent& aEvent )
  290. {
  291. if( !GetSelectedLibId().IsValid() )
  292. {
  293. // Expand library/part units subtree
  294. toggleExpand( m_tree_ctrl->GetSelection() );
  295. }
  296. else
  297. {
  298. postSelectEvent();
  299. }
  300. }
  301. void LIB_TREE::onDetailsLink( wxHtmlLinkEvent& aEvent )
  302. {
  303. const wxHtmlLinkInfo& info = aEvent.GetLinkInfo();
  304. ::wxLaunchDefaultBrowser( info.GetHref() );
  305. }
  306. void LIB_TREE::onPreselect( wxCommandEvent& aEvent )
  307. {
  308. if( m_details_ctrl )
  309. {
  310. int unit = 0;
  311. LIB_ID id = GetSelectedLibId( &unit );
  312. if( id.IsValid() )
  313. m_details_ctrl->SetPage( m_adapter->GenerateInfo( id, unit ) );
  314. else
  315. m_details_ctrl->SetPage( wxEmptyString );
  316. }
  317. aEvent.Skip();
  318. }
  319. void LIB_TREE::onContextMenu( wxDataViewEvent& aEvent )
  320. {
  321. TOOL_INTERACTIVE* tool = m_adapter->GetContextMenuTool();
  322. if( tool )
  323. {
  324. tool->Activate();
  325. tool->GetManager()->VetoContextMenuMouseWarp();
  326. tool->GetToolMenu().ShowContextMenu();
  327. TOOL_EVENT evt( TC_MOUSE, TA_MOUSE_CLICK, BUT_RIGHT );
  328. tool->GetManager()->DispatchContextMenu( evt );
  329. }
  330. }
  331. wxDEFINE_EVENT( COMPONENT_PRESELECTED, wxCommandEvent );
  332. wxDEFINE_EVENT( COMPONENT_SELECTED, wxCommandEvent );