diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 496611f7ea..f9a412ecce 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -201,6 +201,7 @@ set( COMMON_WIDGET_SRCS widgets/wx_busy_indicator.cpp widgets/wx_ellipsized_static_text.cpp widgets/wx_grid.cpp + widgets/wx_listbox.cpp widgets/wx_panel.cpp widgets/wx_progress_reporters.cpp widgets/wx_splitter_window.cpp diff --git a/common/widgets/wx_listbox.cpp b/common/widgets/wx_listbox.cpp new file mode 100644 index 0000000000..1512670d47 --- /dev/null +++ b/common/widgets/wx_listbox.cpp @@ -0,0 +1,80 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 3 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include + +/* + * A specialization of wxListBox with support for pinned items. + */ + + +wxString WX_LISTBOX::GetStringSelection() const +{ + wxString str = wxListBox::GetStringSelection(); + + if( str.StartsWith( LIB_TREE_MODEL_ADAPTER::GetPinningSymbol() ) ) + str = str.substr( LIB_TREE_MODEL_ADAPTER::GetPinningSymbol().length() ); + + return str; +} + + +bool WX_LISTBOX::SetStringSelection( const wxString& s ) +{ + if( wxListBox::SetStringSelection( LIB_TREE_MODEL_ADAPTER::GetPinningSymbol() + s ) ) + return true; + + return wxListBox::SetStringSelection( s ); +} + + +bool WX_LISTBOX::SetStringSelection( const wxString& s, bool select ) +{ + if( wxListBox::SetStringSelection( LIB_TREE_MODEL_ADAPTER::GetPinningSymbol() + s, select ) ) + return true; + + return wxListBox::SetStringSelection( s, select ); +} + + +wxString WX_LISTBOX::GetBaseString( int n ) const +{ + wxString str = wxListBox::GetString( n ); + + if( str.StartsWith( LIB_TREE_MODEL_ADAPTER::GetPinningSymbol() ) ) + str = str.substr( LIB_TREE_MODEL_ADAPTER::GetPinningSymbol().length() ); + + return str; +} + + +int WX_LISTBOX::FindString( const wxString& s, bool bCase ) const +{ + int retVal = wxListBox::FindString( LIB_TREE_MODEL_ADAPTER::GetPinningSymbol() + s, bCase ); + + if( retVal == wxNOT_FOUND ) + retVal = wxListBox::FindString( s, bCase ); + + return retVal; +} \ No newline at end of file diff --git a/eeschema/eeschema_id.h b/eeschema/eeschema_id.h index eeb3f0517a..606fa590a6 100644 --- a/eeschema/eeschema_id.h +++ b/eeschema/eeschema_id.h @@ -70,7 +70,9 @@ enum id_eeschema_frm ID_LIBVIEW_NEXT, ID_LIBVIEW_PREVIOUS, ID_LIBVIEW_SELECT_UNIT_NUMBER, + ID_LIBVIEW_LIB_FILTER, ID_LIBVIEW_LIB_LIST, + ID_LIBVIEW_SYM_FILTER, ID_LIBVIEW_SYM_LIST, ID_SIM_RUN, diff --git a/eeschema/symbol_viewer_frame.cpp b/eeschema/symbol_viewer_frame.cpp index 1819488ba0..803bc9c65c 100644 --- a/eeschema/symbol_viewer_frame.cpp +++ b/eeschema/symbol_viewer_frame.cpp @@ -33,11 +33,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -51,10 +53,11 @@ #include #include #include -#include +#include #include #include +#include "eda_pattern_match.h" // Save previous symbol library viewer state. wxString SYMBOL_VIEWER_FRAME::m_libraryName; @@ -76,9 +79,11 @@ BEGIN_EVENT_TABLE( SYMBOL_VIEWER_FRAME, EDA_DRAW_FRAME ) EVT_CHOICE( ID_LIBVIEW_SELECT_UNIT_NUMBER, SYMBOL_VIEWER_FRAME::onSelectSymbolUnit ) // listbox events + EVT_TEXT( ID_LIBVIEW_LIB_FILTER, SYMBOL_VIEWER_FRAME::OnLibFilter ) EVT_LISTBOX( ID_LIBVIEW_LIB_LIST, SYMBOL_VIEWER_FRAME::ClickOnLibList ) - EVT_LISTBOX( ID_LIBVIEW_SYM_LIST, SYMBOL_VIEWER_FRAME::ClickOnCmpList ) - EVT_LISTBOX_DCLICK( ID_LIBVIEW_SYM_LIST, SYMBOL_VIEWER_FRAME::DClickOnCmpList ) + EVT_TEXT( ID_LIBVIEW_SYM_FILTER, SYMBOL_VIEWER_FRAME::OnSymFilter ) + EVT_LISTBOX( ID_LIBVIEW_SYM_LIST, SYMBOL_VIEWER_FRAME::ClickOnSymbolList ) + EVT_LISTBOX_DCLICK( ID_LIBVIEW_SYM_LIST, SYMBOL_VIEWER_FRAME::DClickOnSymbolList ) // Menu (and/or hotkey) events EVT_MENU( wxID_CLOSE, SYMBOL_VIEWER_FRAME::CloseLibraryViewer ) @@ -150,11 +155,46 @@ SYMBOL_VIEWER_FRAME::SYMBOL_VIEWER_FRAME( KIWAY* aKiway, wxWindow* aParent, FRAM ReCreateVToolbar(); ReCreateMenuBar(); - m_libList = new wxListBox( this, ID_LIBVIEW_LIB_LIST, wxDefaultPosition, wxDefaultSize, - 0, nullptr, wxLB_HSCROLL | wxNO_BORDER ); + wxPanel* libPanel = new wxPanel( this ); + wxSizer* libSizer = new wxBoxSizer( wxVERTICAL ); - m_symbolList = new wxListBox( this, ID_LIBVIEW_SYM_LIST, wxDefaultPosition, wxDefaultSize, - 0, nullptr, wxLB_HSCROLL | wxNO_BORDER ); + m_libFilter = new wxSearchCtrl( libPanel, ID_LIBVIEW_LIB_FILTER, wxEmptyString, + wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER ); + m_libFilter->SetDescriptiveText( _( "Filter" ) ); + libSizer->Add( m_libFilter, 0, wxEXPAND, 5 ); + + m_libList = new WX_LISTBOX( libPanel, ID_LIBVIEW_LIB_LIST, wxDefaultPosition, wxDefaultSize, + 0, nullptr, wxLB_HSCROLL | wxNO_BORDER ); + libSizer->Add( m_libList, 1, wxEXPAND, 5 ); + + libPanel->SetSizer( libSizer ); + libPanel->Fit(); + + wxPanel* symbolPanel = new wxPanel( this ); + wxSizer* symbolSizer = new wxBoxSizer( wxVERTICAL ); + + m_symbolFilter = new wxSearchCtrl( symbolPanel, ID_LIBVIEW_SYM_FILTER, wxEmptyString, + wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER ); + m_symbolFilter->SetDescriptiveText( _( "Filter" ) ); + m_symbolFilter->SetToolTip( + _( "Filter on symbol name, keywords, description and pin count.\n" + "Search terms are separated by spaces. All search terms must match.\n" + "A term which is a number will also match against the pin count." ) ); + symbolSizer->Add( m_symbolFilter, 0, wxEXPAND, 5 ); + +#ifdef __WXGTK__ + // wxSearchCtrl vertical height is not calculated correctly on some GTK setups + // See https://gitlab.com/kicad/code/kicad/-/issues/9019 + m_libFilter->SetMinSize( wxSize( -1, GetTextExtent( wxT( "qb" ) ).y + 10 ) ); + m_symbolFilter->SetMinSize( wxSize( -1, GetTextExtent( wxT( "qb" ) ).y + 10 ) ); +#endif + + m_symbolList = new WX_LISTBOX( symbolPanel, ID_LIBVIEW_SYM_LIST, wxDefaultPosition, wxDefaultSize, + 0, nullptr, wxLB_HSCROLL | wxNO_BORDER ); + symbolSizer->Add( m_symbolList, 1, wxEXPAND, 5 ); + + symbolPanel->SetSizer( symbolSizer ); + symbolPanel->Fit(); if( aLibraryName.empty() ) { @@ -175,19 +215,17 @@ SYMBOL_VIEWER_FRAME::SYMBOL_VIEWER_FRAME( KIWAY* aKiway, wxWindow* aParent, FRAM m_auimgr.SetManagedWindow( this ); // Manage main toolbar - m_auimgr.AddPane( m_mainToolBar, EDA_PANE().HToolbar().Name( "MainToolbar" ).Top().Layer( 6 ) ); - m_auimgr.AddPane( m_messagePanel, EDA_PANE().Messages().Name( "MsgPanel" ) - .Bottom().Layer( 6 ) ); + m_auimgr.AddPane( m_mainToolBar, EDA_PANE().HToolbar().Name( "MainToolbar" ).Top().Layer(6) ); + m_auimgr.AddPane( m_messagePanel, EDA_PANE().Messages().Name( "MsgPanel" ) .Bottom().Layer(6) ); - m_auimgr.AddPane( m_libList, EDA_PANE().Palette().Name( "Libraries" ).Left().Layer(3) - .CaptionVisible( false ).MinSize( 80, -1 ).BestSize( m_libListWidth, -1 ) ); - m_auimgr.AddPane( m_symbolList, EDA_PANE().Palette().Name( "Symbols" ).Left().Layer(1) - .CaptionVisible( false ).MinSize( 80, -1 ) - .BestSize( m_symbolListWidth, -1 ) ); + m_auimgr.AddPane( libPanel, EDA_PANE().Palette().Name( "Libraries" ).Left().Layer(2) + .CaptionVisible( false ).MinSize( 100, -1 ).BestSize( m_libListWidth, -1 ) ); + m_auimgr.AddPane( symbolPanel, EDA_PANE().Palette().Name( "Symbols" ).Left().Layer(1) + .CaptionVisible( false ).MinSize( 100, -1 ).BestSize( m_symbolListWidth, -1 ) ); m_auimgr.AddPane( GetCanvas(), EDA_PANE().Canvas().Name( "DrawFrame" ).Center() ); - m_auimgr.GetPane( m_libList ).Show( aLibraryName.empty() ); + m_auimgr.GetPane( libPanel ).Show( aLibraryName.empty() ); m_auimgr.Update(); @@ -408,6 +446,7 @@ bool SYMBOL_VIEWER_FRAME::ShowModal( wxString* aSymbol, wxWindow* aParent ) } } + m_libFilter->SetFocus(); return KIWAY_PLAYER::ShowModal( aSymbol, aParent ); } @@ -483,45 +522,69 @@ bool SYMBOL_VIEWER_FRAME::ReCreateLibList() m_libList->Clear(); + PROJECT_FILE& project = Kiway().Prj().GetProjectFile(); std::vector libs = Prj().SchSymbolLibTable()->GetLogicalLibs(); + std::set pinnedMatches; + std::set otherMatches; - // Remove not allowed libs from main list, if the allowed lib list is not empty - if( m_allowedLibs.GetCount() ) + auto process = + [&]( const wxString& aLib ) + { + // Remove not allowed libs, if the allowed lib list is not empty + if( m_allowedLibs.GetCount() ) + { + if( m_allowedLibs.Index( aLib ) == wxNOT_FOUND ) + return; + } + + // Remove libs which have no power symbols, if this filter is activated + if( m_listPowerOnly ) + { + wxArrayString aliasNames; + + Prj().SchSymbolLibTable()->EnumerateSymbolLib( aLib, aliasNames, true ); + + if( aliasNames.IsEmpty() ) + return; + } + + if( alg::contains( project.m_PinnedSymbolLibs, aLib ) ) + pinnedMatches.insert( aLib ); + else + otherMatches.insert( aLib ); + }; + + if( m_libFilter->GetValue().IsEmpty() ) { - for( unsigned ii = 0; ii < libs.size(); ) - { - if( m_allowedLibs.Index( libs[ii] ) == wxNOT_FOUND ) - libs.erase( libs.begin() + ii ); - else - ii++; - } + for( const wxString& lib : libs ) + process( lib ); } - - // Remove libs which have no power symbols, if this filter is activated - if( m_listPowerOnly ) + else { - for( unsigned ii = 0; ii < libs.size(); ) + wxStringTokenizer tokenizer( m_libFilter->GetValue() ); + + while( tokenizer.HasMoreTokens() ) { - wxArrayString aliasNames; + const wxString term = tokenizer.GetNextToken().Lower(); + EDA_COMBINED_MATCHER matcher( term ); + int matches, position; - Prj().SchSymbolLibTable()->EnumerateSymbolLib( libs[ii], aliasNames, true ); - - if( aliasNames.IsEmpty() ) - libs.erase( libs.begin() + ii ); - else - ii++; + for( const wxString& lib : libs ) + { + if( matcher.Find( lib.Lower(), matches, position ) ) + process( lib ); + } } } if( libs.empty() ) return true; - wxArrayString libNames; + for( const wxString& name : pinnedMatches ) + m_libList->Append( LIB_TREE_MODEL_ADAPTER::GetPinningSymbol() + UnescapeString( name ) ); - for( const auto& name : libs ) - libNames.Add( UnescapeString( name ) ); - - m_libList->Append( libNames ); + for( const wxString& name : otherMatches ) + m_libList->Append( UnescapeString( name ) ); // Search for a previous selection: int index = m_libList->FindString( UnescapeString( m_libraryName ) ); @@ -534,7 +597,7 @@ bool SYMBOL_VIEWER_FRAME::ReCreateLibList() { // If not found, clear current library selection because it can be // deleted after a config change. - m_libraryName = libs[0]; + m_libraryName = m_libList->GetBaseString( 0 ); m_entryName = wxEmptyString; m_unit = 1; m_convert = LIB_ITEM::LIB_CONVERT::BASE; @@ -553,18 +616,53 @@ bool SYMBOL_VIEWER_FRAME::ReCreateSymbolList() if( m_symbolList == nullptr ) return false; - wxArrayString aliasNames; + m_symbolList->Clear(); + + if( m_libraryName.IsEmpty() ) + return false; + + std::vector symbols; try { - Prj().SchSymbolLibTable()->EnumerateSymbolLib( m_libraryName, aliasNames, - m_listPowerOnly ); + if( Prj().SchSymbolLibTable()->FindRow( m_libraryName ) ) + Prj().SchSymbolLibTable()->LoadSymbolLib( symbols, m_libraryName, m_listPowerOnly ); } catch( const IO_ERROR& ) {} // ignore, it is handled below - m_symbolList->Clear(); + std::set excludes; - if( aliasNames.IsEmpty() ) + if( !m_symbolFilter->GetValue().IsEmpty() ) + { + wxStringTokenizer tokenizer( m_symbolFilter->GetValue() ); + + while( tokenizer.HasMoreTokens() ) + { + const wxString term = tokenizer.GetNextToken().Lower(); + EDA_COMBINED_MATCHER matcher( term ); + int matches, position; + + for( LIB_SYMBOL* symbol : symbols ) + { + wxString search = symbol->GetName() + wxS( " " ) + symbol->GetSearchText(); + bool matched = matcher.Find( search.Lower(), matches, position ); + + if( !matched && term.IsNumber() ) + matched = ( wxAtoi( term ) == (int)symbol->GetPinCount() ); + + if( !matched ) + excludes.insert( symbol->GetName() ); + } + } + } + + for( const LIB_SYMBOL* symbol : symbols ) + { + if( !excludes.count( symbol->GetName() ) ) + m_symbolList->Append( UnescapeString( symbol->GetName() ) ); + } + + if( m_symbolList->IsEmpty() ) { m_libraryName = wxEmptyString; m_entryName = wxEmptyString; @@ -573,13 +671,6 @@ bool SYMBOL_VIEWER_FRAME::ReCreateSymbolList() return true; } - wxArrayString unescapedNames; - - for( const wxString& name : aliasNames ) - unescapedNames.Add( UnescapeString( name ) ); - - m_symbolList->Append( unescapedNames ); - int index = m_symbolList->FindString( UnescapeString( m_entryName ) ); bool changed = false; @@ -596,9 +687,6 @@ bool SYMBOL_VIEWER_FRAME::ReCreateSymbolList() m_symbolList->SetSelection( index, true ); - wxCommandEvent evt( wxEVT_COMMAND_LISTBOX_SELECTED, ID_LIBVIEW_SYM_LIST ); - ProcessEvent( evt ); - return changed; } @@ -612,7 +700,7 @@ void SYMBOL_VIEWER_FRAME::ClickOnLibList( wxCommandEvent& event ) m_selection_changed = true; - SetSelectedLibrary( EscapeString( m_libList->GetString( ii ), CTX_LIBID ) ); + SetSelectedLibrary( EscapeString( m_libList->GetBaseString( ii ), CTX_LIBID ) ); } @@ -639,7 +727,7 @@ void SYMBOL_VIEWER_FRAME::SetSelectedLibrary( const wxString& aLibraryName ) } -void SYMBOL_VIEWER_FRAME::ClickOnCmpList( wxCommandEvent& event ) +void SYMBOL_VIEWER_FRAME::ClickOnSymbolList( wxCommandEvent& event ) { int ii = m_symbolList->GetSelection(); @@ -648,7 +736,7 @@ void SYMBOL_VIEWER_FRAME::ClickOnCmpList( wxCommandEvent& event ) m_selection_changed = true; - SetSelectedSymbol( EscapeString( m_symbolList->GetString( ii ), CTX_LIBID ) ); + SetSelectedSymbol( EscapeString( m_symbolList->GetBaseString( ii ), CTX_LIBID ) ); // The m_symbolList has now the focus, in order to be able to use arrow keys // to navigate inside the list. @@ -666,7 +754,7 @@ void SYMBOL_VIEWER_FRAME::SetSelectedSymbol( const wxString& aSymbolName ) // Ensure the corresponding line in m_symbolList is selected // (which is not necessarily the case if SetSelectedSymbol is called - // by another caller than ClickOnCmpList. + // by another caller than ClickOnSymbolList. m_symbolList->SetStringSelection( UnescapeString( aSymbolName ), true ); DisplayLibInfos(); @@ -682,7 +770,7 @@ void SYMBOL_VIEWER_FRAME::SetSelectedSymbol( const wxString& aSymbolName ) } -void SYMBOL_VIEWER_FRAME::DClickOnCmpList( wxCommandEvent& event ) +void SYMBOL_VIEWER_FRAME::DClickOnSymbolList( wxCommandEvent& event ) { m_toolManager->RunAction( EE_ACTIONS::addSymbolToSchematic, true ); } @@ -881,6 +969,96 @@ void SYMBOL_VIEWER_FRAME::OnSelectSymbol( wxCommandEvent& aEvent ) } +void SYMBOL_VIEWER_FRAME::OnLibFilter( wxCommandEvent& aEvent ) +{ + ReCreateLibList(); + + // Required to avoid interaction with SetHint() + // See documentation for wxTextEntry::SetHint + aEvent.Skip(); +} + + +void SYMBOL_VIEWER_FRAME::OnSymFilter( wxCommandEvent& aEvent ) +{ + ReCreateSymbolList(); + + // Required to avoid interaction with SetHint() + // See documentation for wxTextEntry::SetHint + aEvent.Skip(); +} + + +void SYMBOL_VIEWER_FRAME::OnCharHook( wxKeyEvent& aEvent ) +{ + if( aEvent.GetKeyCode() == WXK_UP ) + { + if( m_libFilter->HasFocus() || m_libList->HasFocus() ) + { + int prev = m_libList->GetSelection() - 1; + + if( prev >= 0 ) + { + m_libList->SetSelection( prev ); + m_libList->EnsureVisible( prev ); + + wxCommandEvent dummy; + ClickOnLibList( dummy ); + } + } + else + { + wxCommandEvent dummy; + onSelectPreviousSymbol( dummy ); + } + } + else if( aEvent.GetKeyCode() == WXK_DOWN ) + { + if( m_libFilter->HasFocus() || m_libList->HasFocus() ) + { + int next = m_libList->GetSelection() + 1; + + if( next < (int)m_libList->GetCount() ) + { + m_libList->SetSelection( next ); + m_libList->EnsureVisible( next ); + + wxCommandEvent dummy; + ClickOnLibList( dummy ); + } + } + else + { + wxCommandEvent dummy; + onSelectNextSymbol( dummy ); + } + } + else if( aEvent.GetKeyCode() == WXK_TAB && m_libFilter->HasFocus() ) + { + if( !aEvent.ShiftDown() ) + m_symbolFilter->SetFocus(); + else + aEvent.Skip(); + } + else if( aEvent.GetKeyCode() == WXK_TAB && m_symbolFilter->HasFocus() ) + { + if( aEvent.ShiftDown() ) + m_libFilter->SetFocus(); + else + aEvent.Skip(); + } + else if( aEvent.GetKeyCode() == WXK_RETURN && m_symbolList->GetSelection() >= 0 ) + { + wxCommandEvent dummy; + DClickOnSymbolList( dummy ); + } + else + { + aEvent.Skip(); + } +} + + void SYMBOL_VIEWER_FRAME::onSelectNextSymbol( wxCommandEvent& aEvent ) { wxCommandEvent evt( wxEVT_COMMAND_LISTBOX_SELECTED, ID_LIBVIEW_SYM_LIST ); diff --git a/eeschema/symbol_viewer_frame.h b/eeschema/symbol_viewer_frame.h index 02a8eceb89..45821145e6 100644 --- a/eeschema/symbol_viewer_frame.h +++ b/eeschema/symbol_viewer_frame.h @@ -31,7 +31,8 @@ #include #include -class wxListBox; +class WX_LISTBOX; +class wxSearchCtrl; class SYMBOL_LIBRARY_FILTER; class LIB_SYMBOL; class SYMBOL_LIB_TABLE_ROW; @@ -95,7 +96,7 @@ public: void ReCreateMenuBar() override; void ClickOnLibList( wxCommandEvent& event ); - void ClickOnCmpList( wxCommandEvent& event ); + void ClickOnSymbolList( wxCommandEvent& event ); void OnSelectSymbol( wxCommandEvent& aEvent ); void LoadSettings( APP_SETTINGS_BASE* aCfg ) override; @@ -154,10 +155,14 @@ private: */ void OnActivate( wxActivateEvent& event ); - void DClickOnCmpList( wxCommandEvent& event ); + void DClickOnSymbolList( wxCommandEvent& event ); void onUpdateUnitChoice( wxUpdateUIEvent& aEvent ); + void OnLibFilter( wxCommandEvent& aEvent ); + void OnSymFilter( wxCommandEvent& aEvent ); + void OnCharHook( wxKeyEvent& aEvent ) override; + void onSelectNextSymbol( wxCommandEvent& aEvent ); void onSelectPreviousSymbol( wxCommandEvent& aEvent ); void onSelectSymbolUnit( wxCommandEvent& aEvent ); @@ -167,10 +172,12 @@ private: private: wxChoice* m_unitChoice; - wxListBox* m_libList; // The list of libraries. + wxSearchCtrl* m_libFilter; + WX_LISTBOX* m_libList; // The list of libraries. int m_libListWidth; // Last width of the window. - wxListBox* m_symbolList; // The list of symbols. + wxSearchCtrl* m_symbolFilter; + WX_LISTBOX* m_symbolList; // The list of symbols. int m_symbolListWidth; // Last width of the window. // Filters to build list of libs/list of symbols. diff --git a/include/lib_tree_model_adapter.h b/include/lib_tree_model_adapter.h index e75ae22379..f27dd54663 100644 --- a/include/lib_tree_model_adapter.h +++ b/include/lib_tree_model_adapter.h @@ -96,6 +96,16 @@ class EDA_BASE_FRAME; class LIB_TREE_MODEL_ADAPTER: public wxDataViewModel { +public: + /** + * @return a unicode string to mark a node name like a pinned library name. + * This is not an ASCII7 char, but a unicode char. + */ + static const wxString GetPinningSymbol() + { + return wxString::FromUTF8( "☆ " ); + } + public: /** * Destructor. Do NOT delete this class manually; it is reference-counted @@ -354,16 +364,6 @@ protected: unsigned int aCol, wxDataViewItemAttr& aAttr ) const override; - /** - * @return a unicode string to mark a node name like - * a pinned library name - * This is not an ASCII7 char, but a unicode char - */ - const wxString GetPinningSymbol() const - { - return wxString::FromUTF8( "☆ " ); - } - private: /** * Find any results worth highlighting and expand them, according to given criteria diff --git a/include/widgets/wx_listbox.h b/include/widgets/wx_listbox.h new file mode 100644 index 0000000000..392861897f --- /dev/null +++ b/include/widgets/wx_listbox.h @@ -0,0 +1,47 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 3 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef KICAD_WX_LISTBOX_H +#define KICAD_WX_LISTBOX_H + +#include + + +class WX_LISTBOX : public wxListBox +{ +public: + WX_LISTBOX( wxWindow *parent, wxWindowID winid, const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, int n = 0, const wxString choices[] = NULL, + long style = 0 ) : + wxListBox( parent, winid, pos, size, n, choices, style ) + { } + + wxString GetStringSelection() const override; + bool SetStringSelection( const wxString& s ) override; + bool SetStringSelection( const wxString& s, bool select ) override; + + wxString GetBaseString( int n ) const; + int FindString( const wxString& s, bool bCase = false ) const override; +}; + +#endif //KICAD_WX_LISTBOX_H diff --git a/pcbnew/footprint_viewer_frame.cpp b/pcbnew/footprint_viewer_frame.cpp index efedaa1fe6..c9a95cf4a6 100644 --- a/pcbnew/footprint_viewer_frame.cpp +++ b/pcbnew/footprint_viewer_frame.cpp @@ -35,11 +35,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -56,7 +58,7 @@ #include #include #include -#include +#include #include #include #include @@ -143,8 +145,8 @@ FOOTPRINT_VIEWER_FRAME::FOOTPRINT_VIEWER_FRAME( KIWAY* aKiway, wxWindow* aParent m_libFilter->SetDescriptiveText( _( "Filter" ) ); libSizer->Add( m_libFilter, 0, wxEXPAND, 5 ); - m_libList = new wxListBox( libPanel, ID_MODVIEW_LIB_LIST, wxDefaultPosition, wxDefaultSize, - 0, nullptr, wxLB_HSCROLL | wxNO_BORDER ); + m_libList = new WX_LISTBOX( libPanel, ID_MODVIEW_LIB_LIST, wxDefaultPosition, wxDefaultSize, + 0, nullptr, wxLB_HSCROLL | wxNO_BORDER ); libSizer->Add( m_libList, 1, wxEXPAND, 5 ); libPanel->SetSizer( libSizer ); @@ -169,8 +171,8 @@ FOOTPRINT_VIEWER_FRAME::FOOTPRINT_VIEWER_FRAME( KIWAY* aKiway, wxWindow* aParent m_fpFilter->SetMinSize( wxSize( -1, GetTextExtent( wxT( "qb" ) ).y + 10 ) ); #endif - m_fpList = new wxListBox( fpPanel, ID_MODVIEW_FOOTPRINT_LIST, wxDefaultPosition, wxDefaultSize, - 0, nullptr, wxLB_HSCROLL | wxNO_BORDER ); + m_fpList = new WX_LISTBOX( fpPanel, ID_MODVIEW_FOOTPRINT_LIST, wxDefaultPosition, wxDefaultSize, + 0, nullptr, wxLB_HSCROLL | wxNO_BORDER ); m_fpList->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( FOOTPRINT_VIEWER_FRAME::DClickOnFootprintList ), nullptr, this ); fpSizer->Add( m_fpList, 1, wxEXPAND, 5 ); @@ -266,7 +268,7 @@ FOOTPRINT_VIEWER_FRAME::FOOTPRINT_VIEWER_FRAME( KIWAY* aKiway, wxWindow* aParent // Vertical items; layers 1 - 3 m_auimgr.AddPane( libPanel, EDA_PANE().Palette().Name( "Libraries" ).Left().Layer(2) .CaptionVisible( false ).MinSize( 100, -1 ).BestSize( 200, -1 ) ); - m_auimgr.AddPane( fpPanel, EDA_PANE().Palette().Name( "Footprints" ).Left().Layer( 1) + m_auimgr.AddPane( fpPanel, EDA_PANE().Palette().Name( "Footprints" ).Left().Layer(1) .CaptionVisible( false ).MinSize( 100, -1 ).BestSize( 300, -1 ) ); m_auimgr.AddPane( GetCanvas(), EDA_PANE().Canvas().Name( "DrawFrame" ).Center() ); @@ -398,10 +400,26 @@ void FOOTPRINT_VIEWER_FRAME::ReCreateLibraryList() { m_libList->Clear(); + PROJECT_FILE& project = Kiway().Prj().GetProjectFile(); std::vector nicknames = Prj().PcbFootprintLibs()->GetLogicalLibs(); - std::set excludes; + std::set pinnedMatches; + std::set otherMatches; - if( !m_libFilter->GetValue().IsEmpty() ) + auto process = + [&]( const wxString& aNickname ) + { + if( alg::contains( project.m_PinnedFootprintLibs, aNickname ) ) + pinnedMatches.insert( aNickname ); + else + otherMatches.insert( aNickname ); + }; + + if( m_libFilter->GetValue().IsEmpty() ) + { + for( const wxString& nickname : nicknames ) + process( nickname ); + } + else { wxStringTokenizer tokenizer( m_libFilter->GetValue() ); @@ -413,17 +431,17 @@ void FOOTPRINT_VIEWER_FRAME::ReCreateLibraryList() for( const wxString& nickname : nicknames ) { - if( !matcher.Find( nickname.Lower(), matches, position ) ) - excludes.insert( nickname ); + if( matcher.Find( nickname.Lower(), matches, position ) ) + process( nickname ); } } } - for( const wxString& nickname : nicknames ) - { - if( !excludes.count( nickname ) ) - m_libList->Append( nickname ); - } + for( const wxString& nickname : pinnedMatches ) + m_libList->Append( LIB_TREE_MODEL_ADAPTER::GetPinningSymbol() + nickname ); + + for( const wxString& nickname : otherMatches ) + m_libList->Append( nickname ); // Search for a previous selection: int index = m_libList->FindString( getCurNickname(), true ); @@ -595,7 +613,7 @@ void FOOTPRINT_VIEWER_FRAME::OnCharHook( wxKeyEvent& aEvent ) } -void FOOTPRINT_VIEWER_FRAME::selectPrev( wxListBox* aListBox ) +void FOOTPRINT_VIEWER_FRAME::selectPrev( WX_LISTBOX* aListBox ) { int prev = aListBox->GetSelection() - 1; @@ -614,7 +632,7 @@ void FOOTPRINT_VIEWER_FRAME::selectPrev( wxListBox* aListBox ) } -void FOOTPRINT_VIEWER_FRAME::selectNext( wxListBox* aListBox ) +void FOOTPRINT_VIEWER_FRAME::selectNext( WX_LISTBOX* aListBox ) { int next = aListBox->GetSelection() + 1; @@ -640,7 +658,7 @@ void FOOTPRINT_VIEWER_FRAME::ClickOnLibList( wxCommandEvent& aEvent ) if( ii < 0 ) return; - wxString name = m_libList->GetString( ii ); + wxString name = m_libList->GetBaseString( ii ); if( getCurNickname() == name ) return; @@ -662,7 +680,7 @@ void FOOTPRINT_VIEWER_FRAME::ClickOnFootprintList( wxCommandEvent& aEvent ) if( ii < 0 ) return; - wxString name = m_fpList->GetString( ii ); + wxString name = m_fpList->GetBaseString( ii ); if( getCurFootprintName().CmpNoCase( name ) != 0 ) { @@ -888,7 +906,7 @@ void FOOTPRINT_VIEWER_FRAME::OnActivate( wxActivateEvent& event ) { for( unsigned ii = 0; ii < libNicknames.size(); ii++ ) { - if( libNicknames[ii] != m_libList->GetString( ii ) ) + if( libNicknames[ii] != m_libList->GetBaseString( ii ) ) { stale = true; break; @@ -1002,13 +1020,17 @@ void FOOTPRINT_VIEWER_FRAME::UpdateTitle() if( !getCurNickname().IsEmpty() ) { - title = getCurNickname(); + try + { + FP_LIB_TABLE* libtable = Prj().PcbFootprintLibs(); + const LIB_TABLE_ROW* row = libtable->FindRow( getCurNickname() ); - FP_LIB_TABLE* libtable = Prj().PcbFootprintLibs(); - const LIB_TABLE_ROW* row = libtable->FindRow( getCurNickname() ); - - if( row ) - title += wxT( " \u2014 " ) + row->GetFullURI( true ); + title = getCurNickname() + wxT( " \u2014 " ) + row->GetFullURI( true ); + } + catch( ... ) + { + title = _( "[no library selected]" ); + } } else { @@ -1045,7 +1067,7 @@ void FOOTPRINT_VIEWER_FRAME::SelectAndViewFootprint( int aMode ) m_fpList->SetSelection( selection ); m_fpList->EnsureVisible( selection ); - setCurFootprintName( m_fpList->GetString((unsigned) selection ) ); + setCurFootprintName( m_fpList->GetBaseString( selection ) ); // Delete the current footprint GetBoard()->DeleteAllFootprints(); diff --git a/pcbnew/footprint_viewer_frame.h b/pcbnew/footprint_viewer_frame.h index d05796b69d..9687aca449 100644 --- a/pcbnew/footprint_viewer_frame.h +++ b/pcbnew/footprint_viewer_frame.h @@ -31,7 +31,7 @@ #include class wxSashLayoutWindow; -class wxListBox; +class WX_LISTBOX; class wxSearchCtrl; class FP_LIB_TABLE; class BOARD_ITEM; @@ -118,8 +118,8 @@ private: void OnFPFilter( wxCommandEvent& aEvent ); void OnCharHook( wxKeyEvent& aEvent ) override; - void selectPrev( wxListBox* aListBox ); - void selectNext( wxListBox* aListBox ); + void selectPrev( WX_LISTBOX* aListBox ); + void selectNext( WX_LISTBOX* aListBox ); void ClickOnLibList( wxCommandEvent& aEvent ); void ClickOnFootprintList( wxCommandEvent& aEvent ); void DClickOnFootprintList( wxMouseEvent& aEvent ); @@ -166,9 +166,9 @@ private: friend struct PCB::IFACE; // constructor called from here only wxSearchCtrl* m_libFilter; - wxListBox* m_libList; // The list of library names. + WX_LISTBOX* m_libList; // The list of library names. wxSearchCtrl* m_fpFilter; - wxListBox* m_fpList; // The list of footprint names. + WX_LISTBOX* m_fpList; // The list of footprint names. bool m_autoZoom; double m_lastZoom;