KiCad PCB EDA Suite
Loading...
Searching...
No Matches
lib_tree.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 <[email protected]>
5 * Copyright The 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, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <widgets/lib_tree.h>
23#include <core/kicad_algo.h>
24#include <algorithm>
25#include <macros.h>
26#include <bitmaps.h>
29#include <tool/tool_manager.h>
30#include <tool/action_manager.h>
31#include <tool/actions.h>
34#include <wx/settings.h>
35#include <wx/sizer.h>
36#include <wx/srchctrl.h>
37#include <wx/statline.h>
38#include <wx/popupwin.h>
39
40#include <eda_doc.h> // for GetAssociatedDocument()
41#include <pgm_base.h> // for PROJECT
42#include <settings/settings_manager.h> // for PROJECT
43
44constexpr int RECENT_SEARCHES_MAX = 10;
45
46std::map<wxString, std::vector<wxString>> g_recentSearches;
47
48
49LIB_TREE::LIB_TREE( wxWindow* aParent, const wxString& aRecentSearchesKey,
50 wxObjectDataPtr<LIB_TREE_MODEL_ADAPTER>& aAdapter, int aFlags,
51 HTML_WINDOW* aDetails ) :
52 wxPanel( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize,
53 wxWANTS_CHARS | wxTAB_TRAVERSAL | wxNO_BORDER ),
54 m_adapter( aAdapter ),
55 m_query_ctrl( nullptr ),
56 m_sort_ctrl( nullptr ),
57 m_details_ctrl( nullptr ),
58 m_inTimerEvent( false ),
59 m_recentSearchesKey( aRecentSearchesKey ),
60 m_filtersSizer( nullptr ),
61 m_skipNextRightClick( false ),
62 m_previewWindow( nullptr ),
63 m_previewDisabled( false )
64{
65 wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
66
67 m_hoverTimer.SetOwner( this );
68 Bind( wxEVT_TIMER, &LIB_TREE::onHoverTimer, this, m_hoverTimer.GetId() );
69
70 // Search text control
71 if( aFlags & SEARCH )
72 {
73 wxBoxSizer* search_sizer = new wxBoxSizer( wxHORIZONTAL );
74
75 m_query_ctrl = new wxSearchCtrl( this, wxID_ANY );
76
77 m_query_ctrl->ShowCancelButton( true );
78
79 m_debounceTimer = new wxTimer( this );
80
81 search_sizer->Add( m_query_ctrl, 1, wxALIGN_CENTER_VERTICAL|wxRIGHT, 4 );
82
83 wxStaticLine* separator = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL );
84 search_sizer->Add( separator, 0, wxEXPAND|wxTOP|wxBOTTOM, 3 );
85
86 m_sort_ctrl = new BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition,
87 wxDefaultSize, wxBU_AUTODRAW|0 );
89 m_sort_ctrl->Bind( wxEVT_LEFT_DOWN,
90 [&]( wxMouseEvent& aEvent )
91 {
92 // Build a pop menu:
93 wxMenu menu;
94
95 menu.Append( 4201, _( "Sort by Best Match" ), wxEmptyString, wxITEM_CHECK );
96 menu.Append( 4202, _( "Sort Alphabetically" ), wxEmptyString, wxITEM_CHECK );
97 menu.AppendSeparator();
98 menu.Append( 4203, ACTIONS::expandAll.GetMenuItem() );
99 menu.Append( 4204, ACTIONS::collapseAll.GetMenuItem() );
100
101 if( m_adapter->GetSortMode() == LIB_TREE_MODEL_ADAPTER::BEST_MATCH )
102 menu.Check( 4201, true );
103 else
104 menu.Check( 4202, true );
105
106 // menu_id is the selected submenu id from the popup menu or wxID_NONE
107 int menu_id = m_sort_ctrl->GetPopupMenuSelectionFromUser( menu );
108
109 if( menu_id == 0 || menu_id == 4201 )
110 {
112 Regenerate( true );
113 }
114 else if( menu_id == 1 || menu_id == 4202 )
115 {
117 Regenerate( true );
118 }
119 else if( menu_id == 3 || menu_id == 4203 )
120 {
121 ExpandAll();
122 }
123 else if( menu_id == 4 || menu_id == 4204 )
124 {
125 CollapseAll();
126 }
127 } );
128
129 m_sort_ctrl->Bind( wxEVT_CHAR_HOOK, &LIB_TREE::onTreeCharHook, this );
130 search_sizer->Add( m_sort_ctrl, 0, wxALIGN_CENTER_VERTICAL, 5 );
131
132 sizer->Add( search_sizer, 0, wxEXPAND, 5 );
133
134 m_query_ctrl->Bind( wxEVT_TEXT, &LIB_TREE::onQueryText, this );
135
136 m_query_ctrl->Bind( wxEVT_SEARCH_CANCEL, &LIB_TREE::onQueryText, this );
137 m_query_ctrl->Bind( wxEVT_CHAR_HOOK, &LIB_TREE::onQueryCharHook, this );
138 m_query_ctrl->Bind( wxEVT_MOTION, &LIB_TREE::onQueryMouseMoved, this );
139
140#if defined( __WXOSX__ ) // Doesn't work properly on other ports
141 m_query_ctrl->Bind( wxEVT_LEAVE_WINDOW,
142 [this]( wxMouseEvent& aEvt )
143 {
144 SetCursor( wxCURSOR_ARROW );
145 } );
146#endif
147
148 m_query_ctrl->Bind( wxEVT_MENU,
149 [this]( wxCommandEvent& aEvent )
150 {
151 size_t idx = aEvent.GetId() - 1;
152
153 if( idx < g_recentSearches[ m_recentSearchesKey ].size() )
155 },
157
158 Bind( wxEVT_TIMER, &LIB_TREE::onDebounceTimer, this, m_debounceTimer->GetId() );
159 }
160
161 if( aFlags & FILTERS )
162 {
163 m_filtersSizer = new wxBoxSizer( wxVERTICAL );
164 sizer->Add( m_filtersSizer, 0, wxEXPAND | wxLEFT, 4 );
165 }
166
167 // Tree control
168 int dvFlags = ( aFlags & MULTISELECT ) ? wxDV_MULTIPLE : wxDV_SINGLE;
169 m_tree_ctrl = new WX_DATAVIEWCTRL( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, dvFlags );
170 m_adapter->AttachTo( m_tree_ctrl );
171
172#ifdef __WXGTK__
173 // The GTK renderer seems to calculate row height incorrectly sometimes; but can be overridden
174 int rowHeight = FromDIP( 6 ) + GetTextExtent( wxS( "pdI" ) ).y;
175 m_tree_ctrl->SetRowHeight( rowHeight );
176#endif
177
178 sizer->Add( m_tree_ctrl, 5, wxEXPAND, 5 );
179
180 // Description panel
181 if( aFlags & DETAILS )
182 {
183 if( !aDetails )
184 {
185 wxPoint html_size = ConvertDialogToPixels( wxPoint( 80, 80 ) );
186
187 m_details_ctrl = new HTML_WINDOW( this, wxID_ANY, wxDefaultPosition,
188 wxSize( html_size.x, html_size.y ) );
189
190 sizer->Add( m_details_ctrl, 2, wxTOP | wxEXPAND, 5 );
191 }
192 else
193 {
194 m_details_ctrl = aDetails;
195 }
196
197 m_details_ctrl->Bind( wxEVT_HTML_LINK_CLICKED, &LIB_TREE::onDetailsLink, this );
198 }
199
200 SetSizer( sizer );
201
202 m_tree_ctrl->Bind( wxEVT_DATAVIEW_ITEM_ACTIVATED, &LIB_TREE::onTreeActivate, this );
203 m_tree_ctrl->Bind( wxEVT_DATAVIEW_SELECTION_CHANGED, &LIB_TREE::onTreeSelect, this );
204 m_tree_ctrl->Bind( wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, &LIB_TREE::onItemContextMenu, this );
205 m_tree_ctrl->Bind( wxEVT_DATAVIEW_COLUMN_HEADER_RIGHT_CLICK, &LIB_TREE::onHeaderContextMenu,
206 this );
207
208 // wxDataViewCtrl eats its mouseMoved events, so we're forced to use idle events to track
209 // the hover state
210 Bind( wxEVT_IDLE, &LIB_TREE::onIdle, this );
211
212 // Process hotkeys when the tree control has focus:
213 m_tree_ctrl->Bind( wxEVT_CHAR_HOOK, &LIB_TREE::onTreeCharHook, this );
214
215 Bind( EVT_LIBITEM_SELECTED, &LIB_TREE::onPreselect, this );
216
217 if( m_query_ctrl )
218 {
219 m_query_ctrl->SetDescriptiveText( _( "Filter" ) );
220 m_query_ctrl->SetFocus();
221 m_query_ctrl->ChangeValue( wxEmptyString );
223
224 // Force an update of the adapter with the empty text to ensure preselect is done
225 Regenerate( false );
226 }
227 else
228 {
229 // There may be a part preselected in the model. Make sure it is displayed.
230 // Regenerate does this in the other branch
232 }
233
234 Layout();
235 sizer->Fit( this );
236
237#ifdef __WXGTK__
238 // Scrollbars must be always enabled to prevent an infinite event loop
239 // more details: http://trac.wxwidgets.org/ticket/18141
240 if( m_details_ctrl )
241 m_details_ctrl->ShowScrollbars( wxSHOW_SB_ALWAYS, wxSHOW_SB_ALWAYS );
242#endif /* __WXGTK__ */
243}
244
245
247{
248 Unbind( wxEVT_TIMER, &LIB_TREE::onHoverTimer, this, m_hoverTimer.GetId() );
249
250 m_tree_ctrl->Unbind( wxEVT_DATAVIEW_ITEM_ACTIVATED, &LIB_TREE::onTreeActivate, this );
251 m_tree_ctrl->Unbind( wxEVT_DATAVIEW_SELECTION_CHANGED, &LIB_TREE::onTreeSelect, this );
252 m_tree_ctrl->Unbind( wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, &LIB_TREE::onItemContextMenu, this );
253 m_tree_ctrl->Unbind( wxEVT_DATAVIEW_COLUMN_HEADER_RIGHT_CLICK, &LIB_TREE::onHeaderContextMenu,
254 this );
255
256 Unbind( wxEVT_IDLE, &LIB_TREE::onIdle, this );
257 m_tree_ctrl->Unbind( wxEVT_CHAR_HOOK, &LIB_TREE::onTreeCharHook, this );
258 Unbind( EVT_LIBITEM_SELECTED, &LIB_TREE::onPreselect, this );
259
260 if( m_query_ctrl )
261 {
262 m_query_ctrl->Unbind( wxEVT_TEXT, &LIB_TREE::onQueryText, this );
263
264 m_query_ctrl->Unbind( wxEVT_SEARCH_CANCEL, &LIB_TREE::onQueryText, this );
265 m_query_ctrl->Unbind( wxEVT_CHAR_HOOK, &LIB_TREE::onQueryCharHook, this );
266 m_query_ctrl->Unbind( wxEVT_MOTION, &LIB_TREE::onQueryMouseMoved, this );
267 }
268
269 // Stop the timer during destruction early to avoid potential race conditions (that do happen)]
270 if( m_debounceTimer )
271 {
272 m_debounceTimer->Stop();
273 Unbind( wxEVT_TIMER, &LIB_TREE::onDebounceTimer, this, m_debounceTimer->GetId() );
274 }
275
276 if( m_details_ctrl )
277 m_details_ctrl->Unbind( wxEVT_HTML_LINK_CLICKED, &LIB_TREE::onDetailsLink, this );
278
279 m_hoverTimer.Stop();
280}
281
282
284{
285 m_hoverTimer.Stop();
286 m_previewDisabled = true;
287
288 if( m_previewWindow )
289 {
290 // Shutdown the preview window's canvas
291 m_adapter->ShutdownPreview( m_previewWindow );
292
293 m_previewWindow->Hide();
294 m_previewWindow->Destroy();
295 m_previewWindow = nullptr;
296 }
297}
298
299
301{
302 if( m_query_ctrl )
303 m_query_ctrl->SetDescriptiveText( _( "Filter" ) );
304
305 if( m_adapter )
306 m_adapter->ShowChangedLanguage();
307}
308
309
311{
312 wxDataViewItem sel = m_tree_ctrl->GetSelection();
313
314 if( !sel )
315 return LIB_ID();
316
317 if( m_adapter->GetTreeNodeFor( sel )->m_IsAlreadyPlacedGroup
318 || m_adapter->GetTreeNodeFor( sel )->m_IsRecentlyUsedGroup )
319 {
320 return LIB_ID();
321 }
322
323 if( aUnit )
324 *aUnit = m_adapter->GetUnitFor( sel );
325
326 return m_adapter->GetAliasFor( sel );
327}
328
329
330int LIB_TREE::GetSelectedLibIds( std::vector<LIB_ID>& aSelection, std::vector<int>* aUnit ) const
331{
332 wxDataViewItemArray selection;
333 int count = m_tree_ctrl->GetSelections( selection );
334
335 for( const wxDataViewItem& item : selection )
336 {
337 aSelection.emplace_back( m_adapter->GetAliasFor( item ) );
338
339 if( aUnit )
340 aUnit->emplace_back( m_adapter->GetUnitFor( item ) );
341 }
342
343 return count;
344}
345
346
348{
349 wxDataViewItem sel = m_tree_ctrl->GetSelection();
350
351 if( !sel )
352 return nullptr;
353
354 return m_adapter->GetTreeNodeFor( sel );
355}
356
357int LIB_TREE::GetSelectedTreeNodes( std::vector<LIB_TREE_NODE*>& aSelection ) const
358{
359 wxDataViewItemArray selection;
360 int count = m_tree_ctrl->GetSelections( selection );
361
362 for( const wxDataViewItem& item : selection )
363 {
364 aSelection.push_back( m_adapter->GetTreeNodeFor( item ) );
365 }
366
367 return count;
368}
369
370
371void LIB_TREE::SelectLibId( const LIB_ID& aLibId )
372{
373 wxDataViewItem item = m_adapter->FindItem( aLibId );
374
375 // Expand the parent library so the row is visible and can be selected/scrolled to.
376 if( item.IsOk() )
377 m_tree_ctrl->ExpandAncestors( item );
378
379 selectIfValid( item );
380}
381
382
383void LIB_TREE::CenterLibId( const LIB_ID& aLibId )
384{
385 centerIfValid( m_adapter->FindItem( aLibId ) );
386}
387
388
390{
391 m_tree_ctrl->Freeze();
392 m_tree_ctrl->UnselectAll();
393 m_tree_ctrl->Thaw();
394}
395
396
397void LIB_TREE::ExpandLibId( const LIB_ID& aLibId )
398{
399 expandIfValid( m_adapter->FindItem( aLibId ) );
400}
401
402
403std::vector<LIB_ID> LIB_TREE::GetExpandedLibraries() const
404{
405 std::vector<LIB_ID> expanded;
406 wxDataViewItemArray items;
407 m_adapter->GetChildren( wxDataViewItem( nullptr ), items );
408
409 for( const wxDataViewItem& item : items )
410 {
411 if( m_tree_ctrl->IsExpanded( item ) )
412 {
413 if( LIB_TREE_NODE* node = m_adapter->GetTreeNodeFor( item ) )
414 expanded.push_back( node->m_LibId );
415 }
416 }
417
418 return expanded;
419}
420
421
423{
424 m_tree_ctrl->ExpandAll();
425}
426
427
429{
430 m_tree_ctrl->CollapseAll();
431}
432
433
434void LIB_TREE::SetSearchString( const wxString& aSearchString )
435{
436 m_query_ctrl->ChangeValue( aSearchString );
437}
438
439
441{
442 return m_query_ctrl->GetValue();
443}
444
445
447{
448 wxString newEntry = GetSearchString();
449
450 std::vector<wxString>& recents = g_recentSearches[ m_recentSearchesKey ];
451
452 if( !newEntry.IsEmpty() )
453 {
454 if( alg::contains( recents, newEntry ) )
455 std::erase( recents, newEntry );
456
457 if( recents.size() >= RECENT_SEARCHES_MAX )
458 recents.pop_back();
459
460 recents.insert( recents.begin(), newEntry );
461 }
462
463 wxMenu* menu = new wxMenu();
464
465 for( const wxString& recent : recents )
466 menu->Append( menu->GetMenuItemCount() + 1, recent );
467
468 if( recents.empty() )
469 menu->Append( wxID_ANY, _( "recent searches" ) );
470
471 m_query_ctrl->SetMenu( menu );
472}
473
474
475void LIB_TREE::Regenerate( bool aKeepState )
476{
477 STATE current;
478
479 // Store the state
480 if( aKeepState )
481 current = getState();
482
483 wxString filter = m_query_ctrl->GetValue();
484 m_adapter->UpdateSearchString( filter, aKeepState );
486
487 // Restore the state
488 if( aKeepState )
489 setState( current );
490}
491
492
494{
495 m_adapter->RefreshTree();
496}
497
498
500{
501 if( m_query_ctrl )
502 return m_query_ctrl;
503 else
504 return m_tree_ctrl;
505}
506
507
509{
510 if( m_query_ctrl )
511 m_query_ctrl->SetFocus();
512}
513
514
515void LIB_TREE::toggleExpand( const wxDataViewItem& aTreeId )
516{
517 if( !aTreeId.IsOk() )
518 return;
519
520 if( m_tree_ctrl->IsExpanded( aTreeId ) )
521 m_tree_ctrl->Collapse( aTreeId );
522 else
523 m_tree_ctrl->Expand( aTreeId );
524}
525
526
527void LIB_TREE::selectIfValid( const wxDataViewItem& aTreeId )
528{
529 if( aTreeId.IsOk() )
530 {
532 m_tree_ctrl->UnselectAll();
533 m_tree_ctrl->Select( aTreeId );
535 }
536}
537
538
539void LIB_TREE::centerIfValid( const wxDataViewItem& aTreeId )
540{
541 /*
542 * This doesn't actually center because the wxWidgets API is poorly suited to that (and
543 * it might be too noisy as well).
544 *
545 * It does try to keep the given item a bit off the top or bottom of the window.
546 */
547
548 if( aTreeId.IsOk() )
549 {
550 LIB_TREE_NODE* node = m_adapter->GetTreeNodeFor( aTreeId );
551 LIB_TREE_NODE* parent = node->m_Parent;
552 LIB_TREE_NODE* grandParent = parent ? parent->m_Parent : nullptr;
553
554 if( parent )
555 {
556 wxDataViewItemArray siblings;
557 m_adapter->GetChildren( wxDataViewItem( parent ), siblings );
558
559 int idx = siblings.Index( aTreeId );
560
561 if( idx + 5 < (int) siblings.GetCount() )
562 {
563 EnsureVisibleIfEnabled( m_tree_ctrl, siblings.Item( idx + 5 ) );
564 }
565 else if( grandParent )
566 {
567 wxDataViewItemArray parentsSiblings;
568 m_adapter->GetChildren( wxDataViewItem( grandParent ), parentsSiblings );
569
570 int p_idx = parentsSiblings.Index( wxDataViewItem( parent ) );
571
572 if( p_idx + 1 < (int) parentsSiblings.GetCount() )
573 EnsureVisibleIfEnabled( m_tree_ctrl, parentsSiblings.Item( p_idx + 1 ) );
574 }
575
576 if( idx - 5 >= 0 )
577 EnsureVisibleIfEnabled( m_tree_ctrl, siblings.Item( idx - 5 ) );
578 else
579 EnsureVisibleIfEnabled( m_tree_ctrl, wxDataViewItem( parent ) );
580 }
581
583 }
584}
585
586
587void LIB_TREE::expandIfValid( const wxDataViewItem& aTreeId )
588{
589 if( aTreeId.IsOk() && !m_tree_ctrl->IsExpanded( aTreeId ) )
590 m_tree_ctrl->Expand( aTreeId );
591}
592
593
595{
596 wxCommandEvent event( EVT_LIBITEM_SELECTED );
597 wxPostEvent( this, event );
598}
599
600
602{
603 wxCommandEvent event( EVT_LIBITEM_CHOSEN );
604 wxPostEvent( this, event );
605}
606
607
609{
610 STATE state;
611 wxDataViewItemArray items;
612 m_adapter->GetChildren( wxDataViewItem( nullptr ), items );
613
614 for( const wxDataViewItem& item : items )
615 {
616 if( m_tree_ctrl->IsExpanded( item ) )
617 state.expanded.push_back( item );
618 }
619
620 state.selection = GetSelectedLibId();
621
622 state.scrollpos = {
623 m_tree_ctrl->HasScrollbar( wxHORIZONTAL ) ? m_tree_ctrl->GetScrollPos( wxHORIZONTAL ) : 0,
624 m_tree_ctrl->HasScrollbar( wxVERTICAL ) ? m_tree_ctrl->GetScrollPos( wxVERTICAL ) : 0
625 };
626
627 return state;
628}
629
630
631void LIB_TREE::setState( const STATE& aState )
632{
633 m_tree_ctrl->Freeze();
634
635 for( const wxDataViewItem& item : aState.expanded )
636 m_tree_ctrl->Expand( item );
637
638 // TODO(JE) probably remove this; it fights with centerIfValid
639 // m_tree_ctrl->SetScrollPos( wxHORIZONTAL, aState.scrollpos.x );
640 // m_tree_ctrl->SetScrollPos( wxVERTICAL, aState.scrollpos.y );
641
642 // wxDataViewCtrl cannot be frozen when a selection
643 // command is issued, otherwise it selects a random item (Windows)
644 m_tree_ctrl->Thaw();
645
646 if( !aState.selection.GetLibItemName().empty() || !aState.selection.GetLibNickname().empty() )
647 SelectLibId( aState.selection );
648}
649
650
651void LIB_TREE::onQueryText( wxCommandEvent& aEvent )
652{
653 m_debounceTimer->StartOnce( 200 );
654
655 // Required to avoid interaction with SetHint()
656 // See documentation for wxTextEntry::SetHint
657 aEvent.Skip();
658}
659
660
661void LIB_TREE::onDebounceTimer( wxTimerEvent& aEvent )
662{
663 m_inTimerEvent = true;
664 Regenerate( false );
665 m_inTimerEvent = false;
666}
667
668
669void LIB_TREE::onQueryCharHook( wxKeyEvent& aKeyStroke )
670{
671 int hotkey = aKeyStroke.GetKeyCode();
672
673 int mods = aKeyStroke.GetModifiers();
674
675 // the flag wxMOD_ALTGR is defined in wxWidgets as wxMOD_CONTROL|wxMOD_ALT
676 // So AltGr key cannot used as modifier key because it is the same as Alt key + Ctrl key.
677#if CAN_USE_ALTGR_KEY
678 if( wxmods & wxMOD_ALTGR )
679 mods |= MD_ALTGR;
680 else
681#endif
682 {
683 if( mods & wxMOD_CONTROL )
684 hotkey += MD_CTRL;
685
686 if( mods & wxMOD_ALT )
687 hotkey += MD_ALT;
688 }
689
690 if( mods & wxMOD_SHIFT )
691 hotkey += MD_SHIFT;
692
693#ifdef wxMOD_META
694 if( mods & wxMOD_META )
695 hotkey += MD_META;
696#endif
697
698#ifdef wxMOD_WIN
699 if( mods & wxMOD_WIN )
700 hotkey += MD_SUPER;
701#endif
702
703 if( hotkey == ACTIONS::expandAll.GetHotKey()
704 || hotkey == ACTIONS::expandAll.GetHotKeyAlt() )
705 {
706 m_tree_ctrl->ExpandAll();
707 return;
708 }
709 else if( hotkey == ACTIONS::collapseAll.GetHotKey()
710 || hotkey == ACTIONS::collapseAll.GetHotKeyAlt() )
711 {
712 m_tree_ctrl->CollapseAll();
713 return;
714 }
715
716 wxDataViewItem sel = m_tree_ctrl->GetSelection();
717
718 if( !sel.IsOk() )
719 sel = m_adapter->GetCurrentDataViewItem();
720
721 LIB_TREE_NODE::TYPE type = sel.IsOk() ? m_adapter->GetTypeFor( sel )
722 : LIB_TREE_NODE::TYPE::INVALID;
723
724 switch( aKeyStroke.GetKeyCode() )
725 {
726 case WXK_UP:
728 selectIfValid( m_tree_ctrl->GetPrevItem( sel ) );
729 break;
730
731 case WXK_DOWN:
733 selectIfValid( m_tree_ctrl->GetNextItem( sel ) );
734 break;
735
736 case WXK_ADD:
738
739 if( type == LIB_TREE_NODE::TYPE::LIBRARY )
740 m_tree_ctrl->Expand( sel );
741
742 break;
743
744 case WXK_SUBTRACT:
746
747 if( type == LIB_TREE_NODE::TYPE::LIBRARY )
748 m_tree_ctrl->Collapse( sel );
749
750 break;
751
752 case WXK_RETURN:
753 case WXK_NUMPAD_ENTER:
755
756 if( GetSelectedLibId().IsValid() )
758 else if( type == LIB_TREE_NODE::TYPE::LIBRARY )
759 toggleExpand( sel );
760
761 break;
762
763 default:
764 aKeyStroke.Skip(); // Any other key: pass on to search box directly.
765 break;
766 }
767}
768
769
770void LIB_TREE::onQueryMouseMoved( wxMouseEvent& aEvent )
771{
772#if defined( __WXOSX__ ) // Doesn't work properly on other ports
773 wxPoint pos = aEvent.GetPosition();
774 wxRect ctrlRect = m_query_ctrl->GetScreenRect();
775 int buttonWidth = ctrlRect.GetHeight(); // Presume buttons are square
776
777 if( m_query_ctrl->IsSearchButtonVisible() && pos.x < buttonWidth )
778 SetCursor( wxCURSOR_ARROW );
779 else if( m_query_ctrl->IsCancelButtonVisible() && pos.x > ctrlRect.GetWidth() - buttonWidth )
780 SetCursor( wxCURSOR_ARROW );
781 else
782 SetCursor( wxCURSOR_IBEAM );
783#endif
784}
785
786
787#define PREVIEW_SIZE wxSize( 240, 200 )
788#define HOVER_TIMER_MILLIS 400
789
790
791void LIB_TREE::showPreview( wxDataViewItem aItem )
792{
793 if( aItem.IsOk() && m_adapter->HasPreview( aItem ) )
794 {
795 m_previewItem = aItem;
797
798 wxWindow* topLevelParent = wxGetTopLevelParent( m_parent );
799
800 if( !m_previewWindow )
801 m_previewWindow = new wxPopupWindow( topLevelParent );
802
803 m_previewWindow->SetSize( PREVIEW_SIZE );
804
805 m_adapter->ShowPreview( m_previewWindow, aItem );
806
807 m_previewWindow->SetPosition( wxPoint( m_tree_ctrl->GetScreenRect().GetRight() - 10,
808 wxGetMousePosition().y - PREVIEW_SIZE.y / 2 ) );
809
810 m_previewWindow->Show();
811 }
812}
813
814
816{
817 m_previewItem = wxDataViewItem();
818
819 if( m_previewWindow )
820 m_previewWindow->Hide();
821}
822
823
824void LIB_TREE::onIdle( wxIdleEvent& aEvent )
825{
826 // The wxDataViewCtrl won't give us its mouseMoved events so we're forced to use idle
827 // events to track the hover state
828
829 // The dang thing won't give us scroll events either, so we implement a poor-man's
830 // scroll-checker using the last-known positions of the preview or hover items.
831
832 wxWindow* topLevelParent = wxGetTopLevelParent( m_parent );
833 wxWindow* topLevelFocus = wxGetTopLevelParent( wxWindow::FindFocus() );
834
835 bool mouseOverWindow = false;
836 wxPoint screenPos = wxGetMousePosition();
837
838 if( m_tree_ctrl->IsShownOnScreen() )
839 mouseOverWindow |= m_tree_ctrl->GetScreenRect().Contains( screenPos );
840
841 if( m_previewDisabled || topLevelFocus != topLevelParent || !mouseOverWindow )
842 {
843 m_hoverTimer.Stop();
844 hidePreview();
845 return;
846 }
847
848 wxPoint clientPos = m_tree_ctrl->ScreenToClient( screenPos );
849 wxDataViewItem item;
850 wxDataViewColumn* col = nullptr;
851
852 m_tree_ctrl->HitTest( clientPos, item, col );
853
854 if( m_previewItem.IsOk() )
855 {
856 if( item != m_previewItem )
857 {
858#ifdef __WXGTK__
859 // Hide the preview, because Wayland can't move windows.
860 if( wxGetDisplayInfo().type == wxDisplayType::wxDisplayWayland )
861 hidePreview();
862#endif
863 showPreview( item );
864 }
865
866 return;
867 }
868
869 if( m_hoverPos != clientPos )
870 {
871 m_hoverPos = clientPos;
872 m_hoverItem = item;
873 m_hoverItemRect = m_tree_ctrl->GetItemRect( m_hoverItem );
874 m_hoverTimer.StartOnce( HOVER_TIMER_MILLIS );
875 }
876}
877
878
879void LIB_TREE::onHoverTimer( wxTimerEvent& aEvent )
880{
881 hidePreview();
882
883 if( !m_tree_ctrl->IsShownOnScreen() || m_previewDisabled )
884 return;
885
886 wxDataViewItem item;
887 wxDataViewColumn* col = nullptr;
888 m_tree_ctrl->HitTest( m_hoverPos, item, col );
889
890 if( item == m_hoverItem && m_tree_ctrl->GetItemRect( item ) == m_hoverItemRect )
891 {
892 if( item != m_tree_ctrl->GetSelection() )
893 showPreview( item );
894 }
895 else // view must have been scrolled
896 {
897 m_hoverItem = item;
898 m_hoverItemRect = m_tree_ctrl->GetItemRect( m_hoverItem );
899 m_hoverTimer.StartOnce( HOVER_TIMER_MILLIS );
900 }
901}
902
903
904void LIB_TREE::onTreeCharHook( wxKeyEvent& aKeyStroke )
905{
906 onQueryCharHook( aKeyStroke );
907
908 if( aKeyStroke.GetSkipped() )
909 {
910 if( TOOL_INTERACTIVE* tool = m_adapter->GetContextMenuTool() )
911 {
912 int hotkey = aKeyStroke.GetKeyCode();
913
914 int mods = aKeyStroke.GetModifiers();
915
916 if( mods & wxMOD_ALTGR )
917 hotkey |= MD_ALTGR;
918 else
919 {
920 if( mods & wxMOD_ALT )
921 hotkey |= MD_ALT;
922
923 if( mods & wxMOD_CONTROL )
924 hotkey |= MD_CTRL;
925 }
926
927 if( mods & wxMOD_SHIFT )
928 hotkey |= MD_SHIFT;
929
930#ifdef wxMOD_META
931 if( mods & wxMOD_META )
932 hotkey |= MD_META;
933#endif
934
935#ifdef wxMOD_WIN
936 if( mods & wxMOD_WIN )
937 hotkey |= MD_SUPER;
938#endif
939
940 if( tool->GetManager()->GetActionManager()->RunHotKey( hotkey ) )
941 aKeyStroke.Skip( false );
942 }
943 }
944}
945
946
947void LIB_TREE::onTreeSelect( wxDataViewEvent& aEvent )
948{
949 if( m_tree_ctrl->IsFrozen() )
950 return;
951
952 if( !m_inTimerEvent )
954
956}
957
958
959void LIB_TREE::onTreeActivate( wxDataViewEvent& aEvent )
960{
961 hidePreview();
962
963 if( !m_inTimerEvent )
965
966 if( !GetSelectedLibId().IsValid() )
967 toggleExpand( m_tree_ctrl->GetSelection() ); // Expand library/part units subtree
968 else
969 postSelectEvent(); // Open symbol/footprint
970}
971
972
973void LIB_TREE::onDetailsLink( wxHtmlLinkEvent& aEvent )
974{
975 const wxHtmlLinkInfo& info = aEvent.GetLinkInfo();
976 wxString docname = info.GetHref();
977 PROJECT& prj = Pgm().GetSettingsManager().Prj();
978
979 GetAssociatedDocument( this, docname, &prj );
980}
981
982
983void LIB_TREE::onPreselect( wxCommandEvent& aEvent )
984{
985 hidePreview();
986
987 if( m_details_ctrl )
988 {
989 int unit = 0;
990 LIB_ID id = GetSelectedLibId( &unit );
991
992 if( id.IsValid() )
993 m_details_ctrl->SetPage( m_adapter->GenerateInfo( id, unit ) );
994 else
995 m_details_ctrl->SetPage( wxEmptyString );
996 }
997
998 aEvent.Skip();
999}
1000
1001
1002void LIB_TREE::onItemContextMenu( wxDataViewEvent& aEvent )
1003{
1004 hidePreview();
1005
1007 {
1008 m_skipNextRightClick = false;
1009 return;
1010 }
1011
1012 m_previewDisabled = true;
1013
1014 if( TOOL_INTERACTIVE* tool = m_adapter->GetContextMenuTool() )
1015 {
1016 if( !GetCurrentTreeNode() )
1017 {
1018 wxPoint pos = m_tree_ctrl->ScreenToClient( wxGetMousePosition() );
1019
1020 wxDataViewItem item;
1021 wxDataViewColumn* col;
1022 m_tree_ctrl->HitTest( pos, item, col );
1023
1024 if( item.IsOk() )
1025 {
1026 m_tree_ctrl->SetFocus();
1027 m_tree_ctrl->Select( item );
1028 wxSafeYield();
1029 }
1030 }
1031
1032 tool->Activate();
1033 tool->GetManager()->VetoContextMenuMouseWarp();
1034 tool->GetToolMenu().ShowContextMenu();
1035
1037 tool->GetManager()->DispatchContextMenu( evt );
1038 }
1039 else
1040 {
1041 LIB_TREE_NODE* current = GetCurrentTreeNode();
1042
1043 if( current && current->m_Type == LIB_TREE_NODE::TYPE::LIBRARY )
1044 {
1045 ACTION_MENU menu( true, nullptr );
1046
1047 if( current->m_Pinned )
1048 {
1049 menu.Add( ACTIONS::unpinLibrary );
1050
1051 if( GetPopupMenuSelectionFromUser( menu ) != wxID_NONE )
1052 m_adapter->UnpinLibrary( current );
1053 }
1054 else
1055 {
1056 menu.Add( ACTIONS::pinLibrary );
1057
1058 if( GetPopupMenuSelectionFromUser( menu ) != wxID_NONE )
1059 m_adapter->PinLibrary( current );
1060 }
1061 }
1062 }
1063
1064 m_previewDisabled = false;
1065}
1066
1067
1068void LIB_TREE::onHeaderContextMenu( wxDataViewEvent& aEvent )
1069{
1070 hidePreview();
1071 m_previewDisabled = true;
1072
1073 ACTION_MENU menu( true, nullptr );
1074
1076
1077 if( GetPopupMenuSelectionFromUser( menu ) != wxID_NONE )
1078 {
1079 EDA_REORDERABLE_LIST_DIALOG dlg( m_parent, _( "Select Columns" ),
1080 m_adapter->GetAvailableColumns(),
1081 m_adapter->GetShownColumns() );
1082
1083 if( dlg.ShowModal() == wxID_OK )
1084 {
1085 m_adapter->SetShownColumns( dlg.EnabledList() );
1086 Regenerate( true );
1087 }
1088 }
1089
1090 m_previewDisabled = false;
1091}
1092
1093
1094wxDEFINE_EVENT( EVT_LIBITEM_SELECTED, wxCommandEvent );
1095wxDEFINE_EVENT( EVT_LIBITEM_CHOSEN, wxCommandEvent );
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
static TOOL_ACTION pinLibrary
Definition actions.h:158
static TOOL_ACTION selectLibTreeColumns
Definition actions.h:273
static TOOL_ACTION unpinLibrary
Definition actions.h:159
static TOOL_ACTION expandAll
Definition actions.h:86
static TOOL_ACTION collapseAll
Definition actions.h:87
Define the structure of a menu based on ACTIONs.
Definition action_menu.h:43
wxMenuItem * Add(const wxString &aLabel, int aId, BITMAPS aIcon)
Add a wxWidgets-style entry to the menu.
A bitmap button widget that behaves like an AUI toolbar item's button when it is drawn.
int ShowModal() override
A dialog which allows selecting a list of items from a list of available items, and reordering those ...
const std::vector< wxString > & EnabledList()
Add dark theme support to wxHtmlWindow.
Definition html_window.h:31
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
const UTF8 & GetLibItemName() const
Definition lib_id.h:98
const UTF8 & GetLibNickname() const
Return the logical library name portion of a LIB_ID.
Definition lib_id.h:83
Model class in the component selector Model-View-Adapter (mediated MVC) architecture.
LIB_TREE_NODE * m_Parent
wxRect m_previewItemRect
Definition lib_tree.h:278
void RefreshLibTree()
Refresh the tree (mainly to update highlighting and asterisking)
Definition lib_tree.cpp:493
wxPoint m_hoverPos
Definition lib_tree.h:273
wxTimer * m_debounceTimer
Definition lib_tree.h:264
void onHoverTimer(wxTimerEvent &aEvent)
Definition lib_tree.cpp:879
LIB_TREE_NODE * GetCurrentTreeNode() const
Retrieve the tree node for the first selected item.
Definition lib_tree.cpp:347
void onQueryMouseMoved(wxMouseEvent &aEvent)
Definition lib_tree.cpp:770
wxDataViewItem m_previewItem
Definition lib_tree.h:277
HTML_WINDOW * m_details_ctrl
Definition lib_tree.h:263
void onTreeActivate(wxDataViewEvent &aEvent)
Definition lib_tree.cpp:959
wxString m_recentSearchesKey
Definition lib_tree.h:267
LIB_TREE(wxWindow *aParent, const wxString &aRecentSearchesKey, wxObjectDataPtr< LIB_TREE_MODEL_ADAPTER > &aAdapter, int aFlags=ALL_WIDGETS, HTML_WINDOW *aDetails=nullptr)
Construct a symbol tree.
Definition lib_tree.cpp:49
void onQueryCharHook(wxKeyEvent &aEvent)
Definition lib_tree.cpp:669
void CenterLibId(const LIB_ID &aLibId)
Ensure that an item is visible (preferably centered).
Definition lib_tree.cpp:383
void postSelectEvent()
Post #SYMBOL_SELECTED event to notify the selection handler that a part has been selected.
Definition lib_tree.cpp:601
wxWindow * GetFocusTarget()
Definition lib_tree.cpp:499
void ShutdownPreviews()
Definition lib_tree.cpp:283
void onItemContextMenu(wxDataViewEvent &aEvent)
void ShowChangedLanguage()
Definition lib_tree.cpp:300
WX_DATAVIEWCTRL * m_tree_ctrl
Definition lib_tree.h:262
void onQueryText(wxCommandEvent &aEvent)
Definition lib_tree.cpp:651
void toggleExpand(const wxDataViewItem &aTreeId)
Expand or collapse a node, switching it to the opposite state.
Definition lib_tree.cpp:515
int GetSelectedTreeNodes(std::vector< LIB_TREE_NODE * > &aSelection) const
Retrieve a list of pointers to selected tree nodes for trees that allow multi-selection.
Definition lib_tree.cpp:357
bool m_skipNextRightClick
Definition lib_tree.h:271
bool m_inTimerEvent
Definition lib_tree.h:265
void selectIfValid(const wxDataViewItem &aTreeId)
If a wxDataViewitem is valid, select it and post a selection event.
Definition lib_tree.cpp:527
void onIdle(wxIdleEvent &aEvent)
Definition lib_tree.cpp:824
void FocusSearchFieldIfExists()
Focus the search widget if it exists.
Definition lib_tree.cpp:508
std::vector< LIB_ID > GetExpandedLibraries() const
Definition lib_tree.cpp:403
void expandIfValid(const wxDataViewItem &aTreeId)
Definition lib_tree.cpp:587
void onPreselect(wxCommandEvent &aEvent)
Definition lib_tree.cpp:983
wxPopupWindow * m_previewWindow
Definition lib_tree.h:279
void SelectLibId(const LIB_ID &aLibId)
Select an item in the tree widget.
Definition lib_tree.cpp:371
void updateRecentSearchMenu()
Definition lib_tree.cpp:446
void hidePreview()
Definition lib_tree.cpp:815
void showPreview(wxDataViewItem aItem)
Definition lib_tree.cpp:791
wxString GetSearchString() const
Definition lib_tree.cpp:440
void onTreeSelect(wxDataViewEvent &aEvent)
Definition lib_tree.cpp:947
int GetSelectedLibIds(std::vector< LIB_ID > &aSelection, std::vector< int > *aUnit=nullptr) const
Retrieve a list of selections for trees that allow multi-selection.
Definition lib_tree.cpp:330
wxTimer m_hoverTimer
Definition lib_tree.h:276
void postPreselectEvent()
Post a wxEVT_DATAVIEW_SELECTION_CHANGED to notify the selection handler that a new part has been pres...
Definition lib_tree.cpp:594
void Unselect()
Unselect currently selected item in wxDataViewCtrl.
Definition lib_tree.cpp:389
BITMAP_BUTTON * m_sort_ctrl
Definition lib_tree.h:261
void onTreeCharHook(wxKeyEvent &aEvent)
Definition lib_tree.cpp:904
void onDetailsLink(wxHtmlLinkEvent &aEvent)
Definition lib_tree.cpp:973
LIB_ID GetSelectedLibId(int *aUnit=nullptr) const
For multi-unit symbols, if the user selects the symbol itself rather than picking an individual unit,...
Definition lib_tree.cpp:310
@ MULTISELECT
Definition lib_tree.h:56
@ FILTERS
Definition lib_tree.h:53
@ DETAILS
Definition lib_tree.h:54
void ExpandAll()
Definition lib_tree.cpp:422
wxBoxSizer * m_filtersSizer
Definition lib_tree.h:269
wxDataViewItem m_hoverItem
Definition lib_tree.h:274
~LIB_TREE() override
Definition lib_tree.cpp:246
void SetSearchString(const wxString &aSearchString)
Save/restore search string.
Definition lib_tree.cpp:434
void setState(const STATE &aState)
Restore the symbol tree widget state from an object.
Definition lib_tree.cpp:631
STATE getState() const
Return the symbol tree widget state.
Definition lib_tree.cpp:608
bool m_previewDisabled
Definition lib_tree.h:280
void ExpandLibId(const LIB_ID &aLibId)
Expand and item i the tree widget.
Definition lib_tree.cpp:397
wxSearchCtrl * m_query_ctrl
Definition lib_tree.h:260
void CollapseAll()
Definition lib_tree.cpp:428
void centerIfValid(const wxDataViewItem &aTreeId)
Definition lib_tree.cpp:539
void onDebounceTimer(wxTimerEvent &aEvent)
Definition lib_tree.cpp:661
wxRect m_hoverItemRect
Definition lib_tree.h:275
void onHeaderContextMenu(wxDataViewEvent &aEvent)
void Regenerate(bool aKeepState)
Regenerate the tree.
Definition lib_tree.cpp:475
wxObjectDataPtr< LIB_TREE_MODEL_ADAPTER > m_adapter
Definition lib_tree.h:258
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition pgm_base.h:124
Container for project specific data.
Definition project.h:62
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
Generic, UI-independent tool event.
Definition tool_event.h:167
bool empty() const
Definition utf8.h:105
Extension of the wxDataViewCtrl to include some helper functions for working with items.
#define _(s)
bool GetAssociatedDocument(wxWindow *aParent, const wxString &aDocName, PROJECT *aProject, SEARCH_STACK *aPaths, std::vector< EMBEDDED_FILES * > aFilesStack)
Open a document (file) with the suitable browser.
Definition eda_doc.cpp:59
This file is part of the common library.
#define HOVER_TIMER_MILLIS
Definition lib_tree.cpp:788
constexpr int RECENT_SEARCHES_MAX
Definition lib_tree.cpp:44
#define PREVIEW_SIZE
Definition lib_tree.cpp:787
std::map< wxString, std::vector< wxString > > g_recentSearches
Definition lib_tree.cpp:46
wxDEFINE_EVENT(EVT_LIBITEM_SELECTED, wxCommandEvent)
This file contains miscellaneous commonly used macros and functions.
bool contains(const _Container &__container, _Value __value)
Returns true if the container contains the given value.
Definition kicad_algo.h:96
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
Structure storing state of the symbol tree widget.
Definition lib_tree.h:214
LIB_ID selection
Current selection, might be not valid if nothing was selected.
Definition lib_tree.h:219
VECTOR2I scrollpos
Definition lib_tree.h:221
std::vector< wxDataViewItem > expanded
List of expanded nodes.
Definition lib_tree.h:216
@ TA_MOUSE_CLICK
Definition tool_event.h:63
@ MD_META
Definition tool_event.h:143
@ MD_ALT
Definition tool_event.h:141
@ MD_CTRL
Definition tool_event.h:140
@ MD_SUPER
Definition tool_event.h:142
@ MD_ALTGR
Definition tool_event.h:144
@ MD_SHIFT
Definition tool_event.h:139
@ TC_MOUSE
Definition tool_event.h:51
@ BUT_RIGHT
Definition tool_event.h:129
void EnsureVisibleIfEnabled(wxDataViewCtrl *aWidget, const wxDataViewItem &aItem)
Scroll aItem into view only when aWidget is currently enabled.