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:
parent
be5fb2c7b8
commit
da9be1a812
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 );
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 );
|
||||
|
|
Loading…
Reference in New Issue