ADDED: Multi-selection cut/copy/paste in symbol editor library tree

Fixes https://gitlab.com/kicad/code/kicad/-/issues/11505
This commit is contained in:
Jon Evans 2022-09-08 22:15:44 -04:00
parent be5fb2c7b8
commit da9be1a812
9 changed files with 177 additions and 68 deletions

View File

@ -34,7 +34,7 @@
LIB_TREE::LIB_TREE( wxWindow* aParent, LIB_TABLE* aLibTable,
wxObjectDataPtr<LIB_TREE_MODEL_ADAPTER>& aAdapter, WIDGETS aWidgets,
wxObjectDataPtr<LIB_TREE_MODEL_ADAPTER>& 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<LIB_ID>& aSelection, std::vector<int>* 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();

View File

@ -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();

View File

@ -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<LIB_ID>& 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<LIB_ID> SYMBOL_EDIT_FRAME::GetSelectedLibIds() const
{
std::vector<LIB_ID> ids;
GetTreeLIBIDs( ids );
return ids;
}
LIB_TREE_NODE* SYMBOL_EDIT_FRAME::GetCurrentTreeNode() const
{
return m_treePane->GetLibTree()->GetCurrentTreeNode();

View File

@ -98,6 +98,10 @@ public:
*/
LIB_ID GetTreeLIBID( int* aUnit = nullptr ) const;
int GetTreeSelectionCount() const;
int GetTreeLIBIDs( std::vector<LIB_ID>& 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<LIB_ID> GetSelectedLibIds() const;
void FocusOnLibId( const LIB_ID& aLibID );
/**

View File

@ -784,58 +784,72 @@ void SYMBOL_EDIT_FRAME::UpdateAfterSymbolProperties( wxString* aOldName )
void SYMBOL_EDIT_FRAME::DeleteSymbolFromLibrary()
{
LIB_ID libId = GetTargetLibId();
std::vector<LIB_ID> 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<LIB_ID> 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<LIB_SYMBOL> 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<LIB_SYMBOL*> 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<STRING_LINE_READER> reader = std::make_unique<STRING_LINE_READER>( 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;
}

View File

@ -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();
}
}

View File

@ -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::FLAGS>( LIB_TREE::SEARCH |
LIB_TREE::MULTISELECT ) );
boxSizer->Add( m_tree, 1, wxEXPAND, 5 );
SetSizer( boxSizer ); // should remove the previous sizer according to wxWidgets docs

View File

@ -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<LIB_TREE_MODEL_ADAPTER>& aAdapter, WIDGETS aWidgets = ALL,
wxObjectDataPtr<LIB_TREE_MODEL_ADAPTER>& 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<LIB_ID>& aSelection,
std::vector<int>* aUnit = nullptr ) const;
LIB_TREE_NODE* GetCurrentTreeNode() const;
/**

View File

@ -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 );