From da9be1a812a3052d67c3787801fbe86c9bb05c2a Mon Sep 17 00:00:00 2001 From: Jon Evans Date: Thu, 8 Sep 2022 22:15:44 -0400 Subject: [PATCH] ADDED: Multi-selection cut/copy/paste in symbol editor library tree Fixes https://gitlab.com/kicad/code/kicad/-/issues/11505 --- common/widgets/lib_tree.cpp | 29 ++++- eeschema/dialogs/dialog_choose_symbol.cpp | 4 +- eeschema/symbol_editor/symbol_edit_frame.cpp | 19 +++ eeschema/symbol_editor/symbol_edit_frame.h | 9 ++ eeschema/symbol_editor/symbol_editor.cpp | 116 ++++++++++++------- eeschema/tools/symbol_editor_control.cpp | 33 ++++-- eeschema/widgets/symbol_tree_pane.cpp | 3 +- include/widgets/lib_tree.h | 30 ++++- pcbnew/dialogs/dialog_choose_footprint.cpp | 2 +- 9 files changed, 177 insertions(+), 68 deletions(-) diff --git a/common/widgets/lib_tree.cpp b/common/widgets/lib_tree.cpp index b6a104051e..172ad50504 100644 --- a/common/widgets/lib_tree.cpp +++ b/common/widgets/lib_tree.cpp @@ -34,7 +34,7 @@ LIB_TREE::LIB_TREE( wxWindow* aParent, LIB_TABLE* aLibTable, - wxObjectDataPtr& aAdapter, WIDGETS aWidgets, + wxObjectDataPtr& aAdapter, FLAGS aFlags, HTML_WINDOW* aDetails ) : wxPanel( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | wxTAB_TRAVERSAL | wxNO_BORDER ), @@ -44,7 +44,7 @@ LIB_TREE::LIB_TREE( wxWindow* aParent, LIB_TABLE* aLibTable, wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL ); // Search text control - if( aWidgets & SEARCH ) + if( aFlags & SEARCH ) { wxBoxSizer* search_sizer = new wxBoxSizer( wxHORIZONTAL ); @@ -80,17 +80,17 @@ LIB_TREE::LIB_TREE( wxWindow* aParent, LIB_TABLE* aLibTable, } // Tree control - m_tree_ctrl = new wxDataViewCtrl( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, - wxDV_SINGLE ); + int dvFlags = ( aFlags & MULTISELECT ) ? wxDV_MULTIPLE : wxDV_SINGLE; + m_tree_ctrl = new wxDataViewCtrl( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, dvFlags ); m_adapter->AttachTo( m_tree_ctrl ); - if( aWidgets & DETAILS ) + if( aFlags & DETAILS ) sizer->AddSpacer( 5 ); sizer->Add( m_tree_ctrl, 5, wxRIGHT | wxBOTTOM | wxEXPAND, 1 ); // Description panel - if( aWidgets & DETAILS ) + if( aFlags & DETAILS ) { if( !aDetails ) { @@ -171,6 +171,23 @@ LIB_ID LIB_TREE::GetSelectedLibId( int* aUnit ) const } +int LIB_TREE::GetSelectedLibIds( std::vector& aSelection, std::vector* aUnit ) const +{ + wxDataViewItemArray selection; + int count = m_tree_ctrl->GetSelections( selection ); + + for( const wxDataViewItem& item : selection ) + { + aSelection.emplace_back( m_adapter->GetAliasFor( item ) ); + + if( aUnit ) + aUnit->emplace_back( m_adapter->GetUnitFor( item ) ); + } + + return count; +} + + LIB_TREE_NODE* LIB_TREE::GetCurrentTreeNode() const { wxDataViewItem sel = m_tree_ctrl->GetSelection(); diff --git a/eeschema/dialogs/dialog_choose_symbol.cpp b/eeschema/dialogs/dialog_choose_symbol.cpp index 1b677e4cfb..ade5b0f583 100644 --- a/eeschema/dialogs/dialog_choose_symbol.cpp +++ b/eeschema/dialogs/dialog_choose_symbol.cpp @@ -126,8 +126,8 @@ DIALOG_CHOOSE_SYMBOL::DIALOG_CHOOSE_SYMBOL( SCH_BASE_FRAME* aParent, const wxStr wxBoxSizer* treeSizer = new wxBoxSizer( wxVERTICAL ); treePanel->SetSizer( treeSizer ); - m_tree = new LIB_TREE( treePanel, Prj().SchSymbolLibTable(), aAdapter, LIB_TREE::WIDGETS::ALL, - m_details ); + m_tree = new LIB_TREE( treePanel, Prj().SchSymbolLibTable(), aAdapter, + LIB_TREE::FLAGS::ALL_WIDGETS, m_details ); treeSizer->Add( m_tree, 1, wxEXPAND | wxALL, 5 ); treePanel->Layout(); diff --git a/eeschema/symbol_editor/symbol_edit_frame.cpp b/eeschema/symbol_editor/symbol_edit_frame.cpp index 5e572880e2..0c08326b0b 100644 --- a/eeschema/symbol_editor/symbol_edit_frame.cpp +++ b/eeschema/symbol_editor/symbol_edit_frame.cpp @@ -947,6 +947,17 @@ LIB_ID SYMBOL_EDIT_FRAME::GetTreeLIBID( int* aUnit ) const } +int SYMBOL_EDIT_FRAME::GetTreeSelectionCount() const +{ + return m_treePane->GetLibTree()->GetSelectionCount(); +} + +int SYMBOL_EDIT_FRAME::GetTreeLIBIDs( std::vector& aSelection ) const +{ + return m_treePane->GetLibTree()->GetSelectedLibIds( aSelection ); +} + + LIB_SYMBOL* SYMBOL_EDIT_FRAME::getTargetSymbol() const { LIB_ID libId = GetTreeLIBID(); @@ -975,6 +986,14 @@ LIB_ID SYMBOL_EDIT_FRAME::GetTargetLibId() const } +std::vector SYMBOL_EDIT_FRAME::GetSelectedLibIds() const +{ + std::vector ids; + GetTreeLIBIDs( ids ); + return ids; +} + + LIB_TREE_NODE* SYMBOL_EDIT_FRAME::GetCurrentTreeNode() const { return m_treePane->GetLibTree()->GetCurrentTreeNode(); diff --git a/eeschema/symbol_editor/symbol_edit_frame.h b/eeschema/symbol_editor/symbol_edit_frame.h index 61b9b13d65..5b9292e90c 100644 --- a/eeschema/symbol_editor/symbol_edit_frame.h +++ b/eeschema/symbol_editor/symbol_edit_frame.h @@ -98,6 +98,10 @@ public: */ LIB_ID GetTreeLIBID( int* aUnit = nullptr ) const; + int GetTreeSelectionCount() const; + + int GetTreeLIBIDs( std::vector& aSelection ) const; + /** * Return the current symbol being edited or NULL if none selected. * @@ -328,6 +332,11 @@ public: */ LIB_ID GetTargetLibId() const; + /** + * @return a list of selected items in the symbol tree + */ + std::vector GetSelectedLibIds() const; + void FocusOnLibId( const LIB_ID& aLibID ); /** diff --git a/eeschema/symbol_editor/symbol_editor.cpp b/eeschema/symbol_editor/symbol_editor.cpp index 5456aa65d0..ca64ceb52d 100644 --- a/eeschema/symbol_editor/symbol_editor.cpp +++ b/eeschema/symbol_editor/symbol_editor.cpp @@ -784,58 +784,72 @@ void SYMBOL_EDIT_FRAME::UpdateAfterSymbolProperties( wxString* aOldName ) void SYMBOL_EDIT_FRAME::DeleteSymbolFromLibrary() { - LIB_ID libId = GetTargetLibId(); + std::vector toDelete = GetSelectedLibIds(); - if( m_libMgr->IsSymbolModified( libId.GetLibItemName(), libId.GetLibNickname() ) - && !IsOK( this, wxString::Format( _( "The symbol '%s' has been modified.\n" - "Do you want to remove it from the library?" ), - libId.GetUniStringLibItemName() ) ) ) + if( toDelete.empty() ) + toDelete.emplace_back( GetTargetLibId() ); + + for( LIB_ID& libId : toDelete ) { - return; - } + if( m_libMgr->IsSymbolModified( libId.GetLibItemName(), libId.GetLibNickname() ) + && !IsOK( this, wxString::Format( _( "The symbol '%s' has been modified.\n" + "Do you want to remove it from the library?" ), + libId.GetUniStringLibItemName() ) ) ) + { + continue; + } - if( m_libMgr->HasDerivedSymbols( libId.GetLibItemName(), libId.GetLibNickname() ) ) - { - wxString msg; + if( m_libMgr->HasDerivedSymbols( libId.GetLibItemName(), libId.GetLibNickname() ) ) + { + wxString msg; - msg.Printf( _( "The symbol %s is used to derive other symbols.\n" + msg.Printf( + _( "The symbol %s is used to derive other symbols.\n" "Deleting this symbol will delete all of the symbols derived from it.\n\n" "Do you wish to delete this symbol and all of its derivatives?" ), libId.GetLibItemName().wx_str() ); - wxMessageDialog::ButtonLabel yesButtonLabel( _( "Delete Symbol" ) ); - wxMessageDialog::ButtonLabel noButtonLabel( _( "Keep Symbol" ) ); + wxMessageDialog::ButtonLabel yesButtonLabel( _( "Delete Symbol" ) ); + wxMessageDialog::ButtonLabel noButtonLabel( _( "Keep Symbol" ) ); - wxMessageDialog dlg( this, msg, _( "Warning" ), - wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION | wxCENTER ); - dlg.SetYesNoLabels( yesButtonLabel, noButtonLabel ); + wxMessageDialog dlg( this, msg, _( "Warning" ), + wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION | wxCENTER ); + dlg.SetYesNoLabels( yesButtonLabel, noButtonLabel ); - if( dlg.ShowModal() == wxID_NO ) - return; + if( dlg.ShowModal() == wxID_NO ) + continue; + } + + if( IsCurrentSymbol( libId ) ) + emptyScreen(); + + m_libMgr->RemoveSymbol( libId.GetLibItemName(), libId.GetLibNickname() ); } - if( IsCurrentSymbol( libId ) ) - emptyScreen(); - - m_libMgr->RemoveSymbol( libId.GetLibItemName(), libId.GetLibNickname() ); - m_treePane->GetLibTree()->RefreshLibTree(); } void SYMBOL_EDIT_FRAME::CopySymbolToClipboard() { - int dummyUnit; - LIB_ID libId = m_treePane->GetLibTree()->GetSelectedLibId( &dummyUnit ); - LIB_SYMBOL* symbol = m_libMgr->GetBufferedSymbol( libId.GetLibItemName(), - libId.GetLibNickname() ); + std::vector symbols; - if( !symbol ) + if( GetTreeLIBIDs( symbols ) == 0 ) return; - std::unique_ptr< LIB_SYMBOL> tmp = symbol->Flatten(); STRING_FORMATTER formatter; - SCH_SEXPR_PLUGIN::FormatLibSymbol( tmp.get(), formatter ); + + for( LIB_ID& libId : symbols ) + { + LIB_SYMBOL* symbol = m_libMgr->GetBufferedSymbol( libId.GetLibItemName(), + libId.GetLibNickname() ); + + if( !symbol ) + continue; + + std::unique_ptr tmp = symbol->Flatten(); + SCH_SEXPR_PLUGIN::FormatLibSymbol( tmp.get(), formatter ); + } wxLogNull doNotLog; // disable logging of failed clipboard actions @@ -863,6 +877,8 @@ void SYMBOL_EDIT_FRAME::DuplicateSymbol( bool aFromClipboard ) LIB_SYMBOL* srcSymbol = nullptr; LIB_SYMBOL* newSymbol = nullptr; + std::vector newSymbols; + if( aFromClipboard ) { wxLogNull doNotLog; // disable logging of failed clipboard actions @@ -881,17 +897,26 @@ void SYMBOL_EDIT_FRAME::DuplicateSymbol( bool aFromClipboard ) clipboard->GetData( data ); wxString symbolSource = data.GetText(); - STRING_LINE_READER reader( TO_UTF8( symbolSource ), "Clipboard" ); + std::unique_ptr reader = std::make_unique( TO_UTF8( symbolSource ), "Clipboard" ); - try + do { - newSymbol = SCH_SEXPR_PLUGIN::ParseLibSymbol( reader ); - } - catch( IO_ERROR& e ) - { - wxLogMessage( "Can not paste: %s", e.Problem() ); - return; + try + { + newSymbol = SCH_SEXPR_PLUGIN::ParseLibSymbol( *reader ); + } + catch( IO_ERROR& e ) + { + wxLogMessage( "Can not paste: %s", e.Problem() ); + break; + } + + if( newSymbol ) + newSymbols.emplace_back( newSymbol ); + + reader.reset( new STRING_LINE_READER( *reader ) ); } + while( newSymbol ); } else { @@ -899,7 +924,7 @@ void SYMBOL_EDIT_FRAME::DuplicateSymbol( bool aFromClipboard ) wxCHECK( srcSymbol, /* void */ ); - newSymbol = new LIB_SYMBOL( *srcSymbol ); + newSymbols.emplace_back( new LIB_SYMBOL( *srcSymbol ) ); // Derive from same parent. if( srcSymbol->IsAlias() ) @@ -912,16 +937,19 @@ void SYMBOL_EDIT_FRAME::DuplicateSymbol( bool aFromClipboard ) } } - if( !newSymbol ) + if( newSymbols.empty() ) return; - ensureUniqueName( newSymbol, lib ); - m_libMgr->UpdateSymbol( newSymbol, lib ); + for( LIB_SYMBOL* symbol : newSymbols ) + { + ensureUniqueName( symbol, lib ); + m_libMgr->UpdateSymbol( symbol, lib ); - LoadOneLibrarySymbolAux( newSymbol, lib, GetUnit(), GetConvert() ); + LoadOneLibrarySymbolAux( symbol, lib, GetUnit(), GetConvert() ); + } SyncLibraries( false ); - m_treePane->GetLibTree()->SelectLibId( LIB_ID( lib, newSymbol->GetName() ) ); + m_treePane->GetLibTree()->SelectLibId( LIB_ID( lib, newSymbols[0]->GetName() ) ); delete newSymbol; } diff --git a/eeschema/tools/symbol_editor_control.cpp b/eeschema/tools/symbol_editor_control.cpp index 91541d9b3a..71e924aeca 100644 --- a/eeschema/tools/symbol_editor_control.cpp +++ b/eeschema/tools/symbol_editor_control.cpp @@ -95,6 +95,11 @@ bool SYMBOL_EDITOR_CONTROL::Init() LIB_ID sel = editFrame->GetTargetLibId(); return !sel.GetLibNickname().empty() && !sel.GetLibItemName().empty(); }; + auto multiSelectedCondition = + [ editFrame ]( const SELECTION& aSel ) + { + return editFrame->GetTreeSelectionCount() > 1; + }; ctxMenu.AddItem( ACTIONS::pinLibrary, unpinnedLibSelectedCondition ); ctxMenu.AddItem( ACTIONS::unpinLibrary, pinnedLibSelectedCondition ); @@ -109,12 +114,12 @@ bool SYMBOL_EDITOR_CONTROL::Init() ctxMenu.AddItem( ACTIONS::revert, symbolSelectedCondition || libInferredCondition ); ctxMenu.AddSeparator(); - ctxMenu.AddItem( EE_ACTIONS::cutSymbol, symbolSelectedCondition ); - ctxMenu.AddItem( EE_ACTIONS::copySymbol, symbolSelectedCondition ); + ctxMenu.AddItem( EE_ACTIONS::cutSymbol, symbolSelectedCondition || multiSelectedCondition ); + ctxMenu.AddItem( EE_ACTIONS::copySymbol, symbolSelectedCondition || multiSelectedCondition ); ctxMenu.AddItem( EE_ACTIONS::pasteSymbol, libInferredCondition ); ctxMenu.AddItem( EE_ACTIONS::duplicateSymbol, symbolSelectedCondition ); ctxMenu.AddItem( EE_ACTIONS::renameSymbol, symbolSelectedCondition ); - ctxMenu.AddItem( EE_ACTIONS::deleteSymbol, symbolSelectedCondition ); + ctxMenu.AddItem( EE_ACTIONS::deleteSymbol, symbolSelectedCondition || multiSelectedCondition ); ctxMenu.AddSeparator(); ctxMenu.AddItem( EE_ACTIONS::importSymbol, libInferredCondition ); @@ -247,17 +252,25 @@ int SYMBOL_EDITOR_CONTROL::CutCopyDelete( const TOOL_EVENT& aEvt ) if( aEvt.IsAction( &EE_ACTIONS::cutSymbol ) || aEvt.IsAction( &EE_ACTIONS::deleteSymbol ) ) { - LIB_ID sel = editFrame->GetTreeLIBID(); - const wxString& libName = sel.GetLibNickname(); - wxString msg; + bool hasWritableLibs = false; + wxString msg; - if( editFrame->GetLibManager().IsLibraryReadOnly( libName ) ) + for( LIB_ID& sel : editFrame->GetSelectedLibIds() ) { - msg.Printf( _( "Symbol library '%s' is not writable." ), libName ); - m_frame->ShowInfoBarError( msg ); - return 0; + const wxString& libName = sel.GetLibNickname(); + + if( editFrame->GetLibManager().IsLibraryReadOnly( libName ) ) + msg.Printf( _( "Symbol library '%s' is not writable." ), libName ); + else + hasWritableLibs = true; } + if( !msg.IsEmpty() ) + m_frame->ShowInfoBarError( msg ); + + if( !hasWritableLibs ) + return 0; + editFrame->DeleteSymbolFromLibrary(); } } diff --git a/eeschema/widgets/symbol_tree_pane.cpp b/eeschema/widgets/symbol_tree_pane.cpp index 7dec0426c2..461c5428fb 100644 --- a/eeschema/widgets/symbol_tree_pane.cpp +++ b/eeschema/widgets/symbol_tree_pane.cpp @@ -40,7 +40,8 @@ SYMBOL_TREE_PANE::SYMBOL_TREE_PANE( SYMBOL_EDIT_FRAME* aParent, SYMBOL_LIBRARY_M // Create widgets wxBoxSizer* boxSizer = new wxBoxSizer( wxVERTICAL ); m_tree = new LIB_TREE( this, &SYMBOL_LIB_TABLE::GetGlobalLibTable(), m_libMgr->GetAdapter(), - LIB_TREE::SEARCH ); + static_cast( LIB_TREE::SEARCH | + LIB_TREE::MULTISELECT ) ); boxSizer->Add( m_tree, 1, wxEXPAND, 5 ); SetSizer( boxSizer ); // should remove the previous sizer according to wxWidgets docs diff --git a/include/widgets/lib_tree.h b/include/widgets/lib_tree.h index 31bdb591c9..bac70c340e 100644 --- a/include/widgets/lib_tree.h +++ b/include/widgets/lib_tree.h @@ -45,8 +45,15 @@ class LIB_TABLE; class LIB_TREE : public wxPanel { public: - ///< Flags to select extra widgets - enum WIDGETS { NONE = 0x00, SEARCH = 0x01, DETAILS = 0x02, ALL = 0xFF }; + ///< Flags to select extra widgets and options + enum FLAGS + { + NONE = 0x00, + SEARCH = 0x01, + DETAILS = 0x02, + ALL_WIDGETS = 0x0F, + MULTISELECT = 0x10 + }; /** * Construct a symbol tree. @@ -54,12 +61,12 @@ public: * @param aParent parent window containing this tree widget * @param aLibTable table containing libraries and items to display * @param aAdapter a LIB_TREE_MODEL_ADAPTER instance to use - * @param aWidgets selection of sub-widgets to include + * @param aFlags selection of sub-widgets to include and other options * @param aDetails if not null, a custom HTML_WINDOW to hold symbol details. If null this * will be created inside the LIB_TREE. */ LIB_TREE( wxWindow* aParent, LIB_TABLE* aLibTable, - wxObjectDataPtr& aAdapter, WIDGETS aWidgets = ALL, + wxObjectDataPtr& aAdapter, FLAGS aFlags = ALL_WIDGETS, HTML_WINDOW* aDetails = nullptr ); ~LIB_TREE() override; @@ -75,6 +82,21 @@ public: */ LIB_ID GetSelectedLibId( int* aUnit = nullptr ) const; + int GetSelectionCount() const + { + return m_tree_ctrl->GetSelectedItemsCount(); + } + + /** + * Retrieves a list of selections for trees that allow multi-selection + * @see GetSelectedLibId for details on how aUnit will be filled. + * @param aSelection will be filled with a list of selected LIB_IDs + * @param aUnit is an optional pointer to a list to fill with unit numbers + * @return the number of selected items + */ + int GetSelectedLibIds( std::vector& aSelection, + std::vector* aUnit = nullptr ) const; + LIB_TREE_NODE* GetCurrentTreeNode() const; /** diff --git a/pcbnew/dialogs/dialog_choose_footprint.cpp b/pcbnew/dialogs/dialog_choose_footprint.cpp index 98a68dc3c3..3d98106b78 100644 --- a/pcbnew/dialogs/dialog_choose_footprint.cpp +++ b/pcbnew/dialogs/dialog_choose_footprint.cpp @@ -82,7 +82,7 @@ DIALOG_CHOOSE_FOOTPRINT::DIALOG_CHOOSE_FOOTPRINT( PCB_BASE_FRAME* aParent, sizer->Add( m_vsplitter, 1, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 5 ); m_tree = new LIB_TREE( m_hsplitter, Prj().PcbFootprintLibs(), aAdapter, - LIB_TREE::WIDGETS::ALL, details ); + LIB_TREE::FLAGS::ALL_WIDGETS, details ); m_hsplitter->SetSashGravity( 0.8 ); m_hsplitter->SetMinimumPaneSize( 20 );