7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-04-11 14:50:11 +00:00

Add 'Copy as Text' action

This is useful when you want to copy text content out to some
external program (or put it in a text item/box).

I'm not sure it's possible to intuit exactly what a user wants,
as you will often want to copy items as the real items, and sometimes
as text. While KiCad might be able to make a smart guess, external
programs will have no chance!
This commit is contained in:
John Beard 2024-10-13 13:26:11 +08:00
parent d9890e38ff
commit b0043587c2
16 changed files with 358 additions and 0 deletions

View File

@ -228,6 +228,15 @@ TOOL_ACTION ACTIONS::copy( TOOL_ACTION_ARGS()
.Flags( AF_NONE )
.UIId( wxID_COPY ) );
TOOL_ACTION ACTIONS::copyAsText( TOOL_ACTION_ARGS()
.Name( "common.Interactive.copyAsText" )
.Scope( AS_GLOBAL )
.DefaultHotkey( MD_CTRL + MD_SHIFT + 'C' )
.FriendlyName( _( "Copy as Text" ) )
.Tooltip( _( "Copy selected item(s) to clipboard as text" ) )
.Icon( BITMAPS::copy )
.Flags( AF_NONE ) );
TOOL_ACTION ACTIONS::paste( TOOL_ACTION_ARGS()
.Name( "common.Interactive.paste" )
.Scope( AS_GLOBAL )

View File

@ -447,6 +447,7 @@ set( EESCHEMA_SRCS
tools/ee_point_editor.cpp
tools/ee_selection.cpp
tools/ee_selection_tool.cpp
tools/ee_tool_utils.cpp
tools/rule_area_create_helper.cpp
tools/sch_drawing_tools.cpp
tools/sch_design_block_control.cpp

View File

@ -146,6 +146,7 @@ void SCH_EDIT_FRAME::doReCreateMenuBar()
editMenu->AppendSeparator();
editMenu->Add( ACTIONS::cut );
editMenu->Add( ACTIONS::copy );
editMenu->Add( ACTIONS::copyAsText );
editMenu->Add( ACTIONS::paste );
editMenu->Add( ACTIONS::pasteSpecial );
editMenu->Add( ACTIONS::doDelete );

View File

@ -645,6 +645,7 @@ void SCH_EDIT_FRAME::setupUIConditions()
mgr->SetConditions( ACTIONS::cut, ENABLE( hasElements ) );
mgr->SetConditions( ACTIONS::copy, ENABLE( hasElements ) );
mgr->SetConditions( ACTIONS::copyAsText, ENABLE( hasElements ) );
mgr->SetConditions( ACTIONS::paste, ENABLE( SELECTION_CONDITIONS::Idle && cond.NoActiveTool() ) );
mgr->SetConditions( ACTIONS::pasteSpecial, ENABLE( SELECTION_CONDITIONS::Idle && cond.NoActiveTool() ) );
mgr->SetConditions( ACTIONS::doDelete, ENABLE( hasElements ) );

View File

@ -96,6 +96,7 @@ void SYMBOL_EDIT_FRAME::doReCreateMenuBar()
editMenu->AppendSeparator();
editMenu->Add( ACTIONS::cut );
editMenu->Add( ACTIONS::copy );
editMenu->Add( ACTIONS::copyAsText );
editMenu->Add( ACTIONS::paste );
editMenu->Add( ACTIONS::doDelete );
editMenu->Add( ACTIONS::duplicate );

View File

@ -490,6 +490,7 @@ void SYMBOL_EDIT_FRAME::setupUIConditions()
mgr->SetConditions( ACTIONS::cut, ENABLE( isEditableCond ) );
mgr->SetConditions( ACTIONS::copy, ENABLE( haveSymbolCond ) );
mgr->SetConditions( ACTIONS::copyAsText, ENABLE( haveSymbolCond ) );
mgr->SetConditions( ACTIONS::paste, ENABLE( isEditableCond &&
SELECTION_CONDITIONS::Idle && cond.NoActiveTool() ) );
mgr->SetConditions( ACTIONS::doDelete, ENABLE( isEditableCond ) );

View File

@ -0,0 +1,126 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 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 2
* 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 "ee_tool_utils.h"
#include <sch_text.h>
#include <sch_field.h>
#include <sch_pin.h>
#include <sch_table.h>
#include <sch_tablecell.h>
#include <sch_textbox.h>
#include <wx/arrstr.h>
wxString GetSchItemAsText( const SCH_ITEM& aItem )
{
switch( aItem.Type() )
{
case SCH_TEXT_T:
case SCH_LABEL_T:
case SCH_HIER_LABEL_T:
case SCH_GLOBAL_LABEL_T:
case SCH_DIRECTIVE_LABEL_T:
case SCH_SHEET_PIN_T:
{
const SCH_TEXT& text = static_cast<const SCH_TEXT&>( aItem );
return text.GetShownText( true );
}
case SCH_FIELD_T:
{
// Goes via EDA_TEXT
const SCH_FIELD& field = static_cast<const SCH_FIELD&>( aItem );
return field.GetShownText( true );
}
case SCH_TEXTBOX_T:
case SCH_TABLECELL_T:
{
// Also EDA_TEXT
const SCH_TEXTBOX& textbox = static_cast<const SCH_TEXTBOX&>( aItem );
return textbox.GetShownText( true );
}
case SCH_PIN_T:
{
// This is a choice - probably the name makes more sense than the number
// (or should it be name/number?)
const SCH_PIN& pin = static_cast<const SCH_PIN&>( aItem );
return pin.GetShownName();
}
case SCH_TABLE_T:
{
// A simple tabbed list of the cells seems like a place to start here
const SCH_TABLE& table = static_cast<const SCH_TABLE&>( aItem );
wxString s;
for( int row = 0; row < table.GetRowCount(); ++row )
{
for( int col = 0; col < table.GetColCount(); ++col )
{
const SCH_TABLECELL* cell = table.GetCell( row, col );
s << cell->GetShownText( true );
if( col < table.GetColCount() - 1 )
{
s << '\t';
}
}
if( row < table.GetRowCount() - 1 )
{
s << '\n';
}
}
return s;
}
default:
{
break;
}
}
return wxEmptyString;
};
wxString GetSelectedItemsAsText( const SELECTION& aSel )
{
wxArrayString itemTexts;
for( EDA_ITEM* item : aSel )
{
if( item->IsSCH_ITEM() )
{
const SCH_ITEM& schItem = static_cast<const SCH_ITEM&>( *item );
wxString itemText = GetSchItemAsText( schItem );
itemText.Trim( false ).Trim( true );
if( !itemText.IsEmpty() )
{
itemTexts.Add( std::move( itemText ) );
}
}
}
return wxJoin( itemTexts, '\n', '\0' );
}

View File

@ -0,0 +1,31 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 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 2
* 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
*/
#pragma once
#include <sch_item.h>
#include <tool/selection.h>
wxString GetSchItemAsText( const SCH_ITEM& aItem );
wxString GetSelectedItemsAsText( const SELECTION& aSel );

View File

@ -587,6 +587,20 @@ bool SCH_EDIT_TOOL::Init()
return menu;
};
const auto canCopyText = EE_CONDITIONS::OnlyTypes( {
SCH_TEXT_T,
SCH_TEXTBOX_T,
SCH_FIELD_T,
SCH_LABEL_T,
SCH_HIER_LABEL_T,
SCH_GLOBAL_LABEL_T,
SCH_DIRECTIVE_LABEL_T,
SCH_SHEET_PIN_T,
SCH_PIN_T,
SCH_TABLE_T,
SCH_TABLECELL_T,
} );
//
// Add edit actions to the move tool menu
//
@ -605,6 +619,7 @@ bool SCH_EDIT_TOOL::Init()
moveMenu.AddSeparator();
moveMenu.AddItem( ACTIONS::cut, E_C::IdleSelection );
moveMenu.AddItem( ACTIONS::copy, E_C::IdleSelection );
moveMenu.AddItem( ACTIONS::copyAsText, canCopyText && E_C::IdleSelection );
moveMenu.AddItem( ACTIONS::doDelete, E_C::NotEmpty );
moveMenu.AddItem( ACTIONS::duplicate, duplicateCondition );
@ -665,6 +680,7 @@ bool SCH_EDIT_TOOL::Init()
selToolMenu.AddSeparator( 300 );
selToolMenu.AddItem( ACTIONS::cut, E_C::IdleSelection, 300 );
selToolMenu.AddItem( ACTIONS::copy, E_C::IdleSelection, 300 );
selToolMenu.AddItem( ACTIONS::copyAsText, canCopyText && E_C::IdleSelection, 300 );
selToolMenu.AddItem( ACTIONS::paste, E_C::Idle, 300 );
selToolMenu.AddItem( ACTIONS::pasteSpecial, E_C::Idle, 300 );
selToolMenu.AddItem( ACTIONS::doDelete, E_C::NotEmpty, 300 );

View File

@ -65,6 +65,7 @@
#include <tools/ee_actions.h>
#include <tools/ee_selection.h>
#include <tools/ee_selection_tool.h>
#include <tools/ee_tool_utils.h>
#include <drawing_sheet/ds_proxy_undo_item.h>
#include <eda_list_dialog.h>
#include <view/view_controls.h>
@ -1397,6 +1398,23 @@ int SCH_EDITOR_CONTROL::Copy( const TOOL_EVENT& aEvent )
}
int SCH_EDITOR_CONTROL::CopyAsText( const TOOL_EVENT& aEvent )
{
EE_SELECTION_TOOL* selTool = m_toolMgr->GetTool<EE_SELECTION_TOOL>();
EE_SELECTION& selection = selTool->RequestSelection();
if( selection.Empty() )
return false;
wxString itemsAsText = GetSelectedItemsAsText( selection );
if( selection.IsHover() )
m_toolMgr->RunAction( EE_ACTIONS::clearSelection );
return m_toolMgr->SaveClipboard( itemsAsText.ToStdString() );
}
void SCH_EDITOR_CONTROL::updatePastedSymbol( SCH_SYMBOL* aSymbol,
const SCH_SHEET_PATH& aPastePath,
const KIID_PATH& aClipPath,
@ -2812,6 +2830,7 @@ void SCH_EDITOR_CONTROL::setTransitions()
Go( &SCH_EDITOR_CONTROL::Redo, ACTIONS::redo.MakeEvent() );
Go( &SCH_EDITOR_CONTROL::Cut, ACTIONS::cut.MakeEvent() );
Go( &SCH_EDITOR_CONTROL::Copy, ACTIONS::copy.MakeEvent() );
Go( &SCH_EDITOR_CONTROL::CopyAsText, ACTIONS::copyAsText.MakeEvent() );
Go( &SCH_EDITOR_CONTROL::Paste, ACTIONS::paste.MakeEvent() );
Go( &SCH_EDITOR_CONTROL::Paste, ACTIONS::pasteSpecial.MakeEvent() );
Go( &SCH_EDITOR_CONTROL::Duplicate, ACTIONS::duplicate.MakeEvent() );

View File

@ -108,6 +108,7 @@ public:
///< Clipboard support.
int Cut( const TOOL_EVENT& aEvent );
int Copy( const TOOL_EVENT& aEvent );
int CopyAsText( const TOOL_EVENT& aEvent );
int Paste( const TOOL_EVENT& aEvent );
int Duplicate( const TOOL_EVENT& aEvent );

View File

@ -26,6 +26,7 @@
#include <tool/picker_tool.h>
#include <tools/ee_selection_tool.h>
#include <tools/ee_tool_utils.h>
#include <tools/symbol_editor_pin_tool.h>
#include <tools/symbol_editor_drawing_tools.h>
#include <tools/symbol_editor_move_tool.h>
@ -86,6 +87,15 @@ bool SYMBOL_EDITOR_EDIT_TOOL::Init()
return true;
};
const auto canCopyText = EE_CONDITIONS::OnlyTypes( {
SCH_TEXT_T,
SCH_TEXTBOX_T,
SCH_FIELD_T,
SCH_PIN_T,
SCH_TABLE_T,
SCH_TABLECELL_T,
} );
// Add edit actions to the move tool menu
if( moveTool )
{
@ -103,6 +113,7 @@ bool SYMBOL_EDITOR_EDIT_TOOL::Init()
moveMenu.AddSeparator( 300 );
moveMenu.AddItem( ACTIONS::cut, EE_CONDITIONS::IdleSelection, 300 );
moveMenu.AddItem( ACTIONS::copy, EE_CONDITIONS::IdleSelection, 300 );
moveMenu.AddItem( ACTIONS::copyAsText, canCopyText && EE_CONDITIONS::IdleSelection, 300 );
moveMenu.AddItem( ACTIONS::duplicate, canEdit && EE_CONDITIONS::NotEmpty, 300 );
moveMenu.AddItem( ACTIONS::doDelete, canEdit && EE_CONDITIONS::NotEmpty, 200 );
@ -136,6 +147,7 @@ bool SYMBOL_EDITOR_EDIT_TOOL::Init()
selToolMenu.AddSeparator( 300 );
selToolMenu.AddItem( ACTIONS::cut, EE_CONDITIONS::IdleSelection, 300 );
selToolMenu.AddItem( ACTIONS::copy, EE_CONDITIONS::IdleSelection, 300 );
selToolMenu.AddItem( ACTIONS::copyAsText, canCopyText && EE_CONDITIONS::IdleSelection, 300 );
selToolMenu.AddItem( ACTIONS::paste, canEdit && EE_CONDITIONS::Idle, 300 );
selToolMenu.AddItem( ACTIONS::duplicate, canEdit && EE_CONDITIONS::NotEmpty, 300 );
selToolMenu.AddItem( ACTIONS::doDelete, canEdit && EE_CONDITIONS::NotEmpty, 300 );
@ -898,6 +910,23 @@ int SYMBOL_EDITOR_EDIT_TOOL::Copy( const TOOL_EVENT& aEvent )
}
int SYMBOL_EDITOR_EDIT_TOOL::CopyAsText( const TOOL_EVENT& aEvent )
{
EE_SELECTION_TOOL* selTool = m_toolMgr->GetTool<EE_SELECTION_TOOL>();
EE_SELECTION& selection = selTool->RequestSelection();
if( selection.Empty() )
return 0;
wxString itemsAsText = GetSelectedItemsAsText( selection );
if( selection.IsHover() )
m_toolMgr->RunAction( EE_ACTIONS::clearSelection );
return m_toolMgr->SaveClipboard( itemsAsText.ToStdString() );
}
int SYMBOL_EDITOR_EDIT_TOOL::Paste( const TOOL_EVENT& aEvent )
{
LIB_SYMBOL* symbol = m_frame->GetCurSymbol();
@ -1051,10 +1080,12 @@ int SYMBOL_EDITOR_EDIT_TOOL::Duplicate( const TOOL_EVENT& aEvent )
void SYMBOL_EDITOR_EDIT_TOOL::setTransitions()
{
// clang-format off
Go( &SYMBOL_EDITOR_EDIT_TOOL::Undo, ACTIONS::undo.MakeEvent() );
Go( &SYMBOL_EDITOR_EDIT_TOOL::Redo, ACTIONS::redo.MakeEvent() );
Go( &SYMBOL_EDITOR_EDIT_TOOL::Cut, ACTIONS::cut.MakeEvent() );
Go( &SYMBOL_EDITOR_EDIT_TOOL::Copy, ACTIONS::copy.MakeEvent() );
Go( &SYMBOL_EDITOR_EDIT_TOOL::CopyAsText, ACTIONS::copyAsText.MakeEvent() );
Go( &SYMBOL_EDITOR_EDIT_TOOL::Paste, ACTIONS::paste.MakeEvent() );
Go( &SYMBOL_EDITOR_EDIT_TOOL::Duplicate, ACTIONS::duplicate.MakeEvent() );
@ -1071,4 +1102,5 @@ void SYMBOL_EDITOR_EDIT_TOOL::setTransitions()
Go( &SYMBOL_EDITOR_EDIT_TOOL::PinTable, EE_ACTIONS::pinTable.MakeEvent() );
Go( &SYMBOL_EDITOR_EDIT_TOOL::UpdateSymbolFields, EE_ACTIONS::updateSymbolFields.MakeEvent() );
Go( &SYMBOL_EDITOR_EDIT_TOOL::SetUnitDisplayName, EE_ACTIONS::setUnitDisplayName.MakeEvent() );
// clang-format on
}

View File

@ -56,6 +56,7 @@ public:
int Redo( const TOOL_EVENT& aEvent );
int Cut( const TOOL_EVENT& aEvent );
int Copy( const TOOL_EVENT& aEvent );
int CopyAsText( const TOOL_EVENT& aEvent );
int Paste( const TOOL_EVENT& aEvent );
/**

View File

@ -69,6 +69,7 @@ public:
static TOOL_ACTION redo;
static TOOL_ACTION cut;
static TOOL_ACTION copy;
static TOOL_ACTION copyAsText;
static TOOL_ACTION paste;
static TOOL_ACTION pasteSpecial;
static TOOL_ACTION selectAll;

View File

@ -326,6 +326,20 @@ bool EDIT_TOOL::Init()
return frame()->IsCurrentTool( PCB_ACTIONS::moveIndividually );
};
const auto canCopyAsText = SELECTION_CONDITIONS::NotEmpty
&& SELECTION_CONDITIONS::OnlyTypes( {
PCB_FIELD_T,
PCB_TEXT_T,
PCB_TEXTBOX_T,
PCB_DIM_ALIGNED_T,
PCB_DIM_LEADER_T,
PCB_DIM_CENTER_T,
PCB_DIM_RADIAL_T,
PCB_DIM_ORTHOGONAL_T,
PCB_TABLE_T,
PCB_TABLECELL_T,
} );
// Add context menu entries that are displayed when selection tool is active
CONDITIONAL_MENU& menu = m_selectionTool->GetToolMenu().GetMenu();
@ -378,6 +392,7 @@ bool EDIT_TOOL::Init()
menu.AddSeparator( 150 );
menu.AddItem( ACTIONS::cut, SELECTION_CONDITIONS::NotEmpty, 150 );
menu.AddItem( ACTIONS::copy, SELECTION_CONDITIONS::NotEmpty, 150 );
menu.AddItem( ACTIONS::copyAsText, canCopyAsText, 150 );
// Selection tool handles the context menu for some other tools, such as the Picker.
// Don't add things like Paste when another tool is active.
@ -3195,6 +3210,102 @@ int EDIT_TOOL::copyToClipboard( const TOOL_EVENT& aEvent )
}
int EDIT_TOOL::copyToClipboardAsText( const TOOL_EVENT& aEvent )
{
PCB_SELECTION& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
{
// Anything unsupported will just be ignored
},
// No prompt for locked items
false );
if( selection.IsHover() )
m_selectionTool->ClearSelection();
const auto getItemText = [&]( const BOARD_ITEM& aItem ) -> wxString
{
switch( aItem.Type() )
{
case PCB_TEXT_T:
case PCB_FIELD_T:
case PCB_DIM_ALIGNED_T:
case PCB_DIM_LEADER_T:
case PCB_DIM_CENTER_T:
case PCB_DIM_RADIAL_T:
case PCB_DIM_ORTHOGONAL_T:
{
// These can all go via the PCB_TEXT class
const PCB_TEXT& text = static_cast<const PCB_TEXT&>( aItem );
return text.GetShownText( true );
}
case PCB_TEXTBOX_T:
case PCB_TABLECELL_T:
{
// This one goes via EDA_TEXT
const PCB_TEXTBOX& textBox = static_cast<const PCB_TEXTBOX&>( aItem );
return textBox.GetShownText( true );
}
case PCB_TABLE_T:
{
const PCB_TABLE& table = static_cast<const PCB_TABLE&>( aItem );
wxString s;
for( int row = 0; row < table.GetRowCount(); ++row )
{
for( int col = 0; col < table.GetColCount(); ++col )
{
const PCB_TABLECELL* cell = table.GetCell( row, col );
s << cell->GetShownText( true );
if( col < table.GetColCount() - 1 )
{
s << '\t';
}
}
if( row < table.GetRowCount() - 1 )
{
s << '\n';
}
}
return s;
}
default:
// No string representation for this item type
break;
}
return wxEmptyString;
};
wxArrayString itemTexts;
for( EDA_ITEM* item : selection )
{
if( item->IsBOARD_ITEM() )
{
BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
wxString itemText = getItemText( *boardItem );
itemText.Trim( false ).Trim( true );
if( !itemText.IsEmpty() )
{
itemTexts.Add( std::move( itemText ) );
}
}
}
// Send the text to the clipboard
if( !itemTexts.empty() )
{
m_toolMgr->SaveClipboard( wxJoin( itemTexts, '\n', '\0' ).ToStdString() );
}
return 0;
}
int EDIT_TOOL::cutToClipboard( const TOOL_EVENT& aEvent )
{
if( !copyToClipboard( aEvent ) )
@ -3261,6 +3372,7 @@ void EDIT_TOOL::setTransitions()
Go( &EDIT_TOOL::copyToClipboard, ACTIONS::copy.MakeEvent() );
Go( &EDIT_TOOL::copyToClipboard, PCB_ACTIONS::copyWithReference.MakeEvent() );
Go( &EDIT_TOOL::copyToClipboardAsText, ACTIONS::copyAsText.MakeEvent() );
Go( &EDIT_TOOL::cutToClipboard, ACTIONS::cut.MakeEvent() );
}
// clang-format on

View File

@ -197,6 +197,11 @@ private:
*/
int copyToClipboard( const TOOL_EVENT& aEvent );
/**
* Send the current selection to the clipboard as text.
*/
int copyToClipboardAsText( const TOOL_EVENT& aEvent );
/**
* Cut the current selection to the clipboard by formatting it as a fake pcb
* see #AppendBoardFromClipboard for importing.