kicad/eeschema/tools/lib_edit_tool.cpp

727 lines
21 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2019 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 <tool/tool_manager.h>
#include <tools/ee_selection_tool.h>
#include <tools/ee_picker_tool.h>
#include <tools/lib_pin_tool.h>
#include <tools/lib_drawing_tools.h>
#include <tools/lib_move_tool.h>
#include <ee_actions.h>
#include <bitmaps.h>
#include <confirm.h>
#include <sch_view.h>
#include <lib_edit_frame.h>
#include <eeschema_id.h>
#include <dialogs/dialog_lib_edit_draw_item.h>
#include <dialogs/dialog_lib_edit_text.h>
#include <dialogs/dialog_edit_one_field.h>
#include <dialogs/dialog_edit_component_in_lib.h>
#include <dialogs/dialog_lib_edit_pin_table.h>
#include <sch_legacy_plugin.h>
#include "lib_edit_tool.h"
LIB_EDIT_TOOL::LIB_EDIT_TOOL() :
EE_TOOL_BASE( "eeschema.SymbolEditTool" )
{
}
bool LIB_EDIT_TOOL::Init()
{
EE_TOOL_BASE::Init();
LIB_DRAWING_TOOLS* drawingTools = m_toolMgr->GetTool<LIB_DRAWING_TOOLS>();
LIB_MOVE_TOOL* moveTool = m_toolMgr->GetTool<LIB_MOVE_TOOL>();
wxASSERT_MSG( drawingTools, "eeschema.SymbolDrawing tool is not available" );
// Add edit actions to the move tool menu
//
if( moveTool )
{
CONDITIONAL_MENU& moveMenu = moveTool->GetToolMenu().GetMenu();
moveMenu.AddSeparator( 200 );
moveMenu.AddItem( EE_ACTIONS::rotateCCW, EE_CONDITIONS::NotEmpty, 200 );
moveMenu.AddItem( EE_ACTIONS::rotateCW, EE_CONDITIONS::NotEmpty, 200 );
moveMenu.AddItem( EE_ACTIONS::mirrorX, EE_CONDITIONS::NotEmpty, 200 );
moveMenu.AddItem( EE_ACTIONS::mirrorY, EE_CONDITIONS::NotEmpty, 200 );
moveMenu.AddItem( EE_ACTIONS::doDelete, EE_CONDITIONS::NotEmpty, 200 );
moveMenu.AddItem( EE_ACTIONS::properties, EE_CONDITIONS::Count( 1 ), 200 );
moveMenu.AddSeparator( 300 );
moveMenu.AddItem( ACTIONS::cut, EE_CONDITIONS::IdleSelection, 300 );
moveMenu.AddItem( ACTIONS::copy, EE_CONDITIONS::IdleSelection, 300 );
moveMenu.AddItem( ACTIONS::duplicate, EE_CONDITIONS::NotEmpty, 300 );
}
// Add editing actions to the drawing tool menu
//
CONDITIONAL_MENU& drawMenu = drawingTools->GetToolMenu().GetMenu();
drawMenu.AddSeparator( 200 );
drawMenu.AddItem( EE_ACTIONS::rotateCCW, EE_CONDITIONS::IdleSelection, 200 );
drawMenu.AddItem( EE_ACTIONS::rotateCW, EE_CONDITIONS::IdleSelection, 200 );
drawMenu.AddItem( EE_ACTIONS::mirrorX, EE_CONDITIONS::IdleSelection, 200 );
drawMenu.AddItem( EE_ACTIONS::mirrorY, EE_CONDITIONS::IdleSelection, 200 );
drawMenu.AddItem( EE_ACTIONS::properties, EE_CONDITIONS::Count( 1 ), 200 );
// Add editing actions to the selection tool menu
//
CONDITIONAL_MENU& selToolMenu = m_selectionTool->GetToolMenu().GetMenu();
selToolMenu.AddItem( EE_ACTIONS::rotateCCW, EE_CONDITIONS::NotEmpty, 200 );
selToolMenu.AddItem( EE_ACTIONS::rotateCW, EE_CONDITIONS::NotEmpty, 200 );
selToolMenu.AddItem( EE_ACTIONS::mirrorX, EE_CONDITIONS::NotEmpty, 200 );
selToolMenu.AddItem( EE_ACTIONS::mirrorY, EE_CONDITIONS::NotEmpty, 200 );
selToolMenu.AddItem( EE_ACTIONS::doDelete, EE_CONDITIONS::NotEmpty, 200 );
selToolMenu.AddItem( EE_ACTIONS::properties, EE_CONDITIONS::Count( 1 ), 200 );
selToolMenu.AddSeparator( 300 );
selToolMenu.AddItem( ACTIONS::cut, EE_CONDITIONS::IdleSelection, 300 );
selToolMenu.AddItem( ACTIONS::copy, EE_CONDITIONS::IdleSelection, 300 );
selToolMenu.AddItem( ACTIONS::paste, EE_CONDITIONS::Idle, 300 );
selToolMenu.AddItem( ACTIONS::duplicate, EE_CONDITIONS::NotEmpty, 300 );
return true;
}
int LIB_EDIT_TOOL::Rotate( const TOOL_EVENT& aEvent )
{
EE_SELECTION& selection = m_selectionTool->RequestSelection();
if( selection.GetSize() == 0 )
return 0;
wxPoint rotPoint;
bool ccw = ( aEvent.Matches( EE_ACTIONS::rotateCCW.MakeEvent() ) );
LIB_ITEM* item = static_cast<LIB_ITEM*>( selection.Front() );
if( !item->IsMoving() )
saveCopyInUndoList( m_frame->GetCurPart(), UR_LIBEDIT );
if( selection.GetSize() == 1 )
rotPoint = item->GetPosition();
else
rotPoint = m_frame->GetNearestGridPosition( mapCoords( selection.GetCenter() ) );
for( unsigned ii = 0; ii < selection.GetSize(); ii++ )
{
item = static_cast<LIB_ITEM*>( selection.GetItem( ii ) );
item->Rotate( rotPoint, ccw );
m_frame->RefreshItem( item );
}
m_toolMgr->PostEvent( EVENTS::SelectedItemsModified );
if( !item->IsMoving() )
{
if( selection.IsHover() )
m_toolMgr->RunAction( EE_ACTIONS::clearSelection, true );
m_frame->OnModify();
}
return 0;
}
int LIB_EDIT_TOOL::Mirror( const TOOL_EVENT& aEvent )
{
EE_SELECTION& selection = m_selectionTool->RequestSelection();
if( selection.GetSize() == 0 )
return 0;
wxPoint mirrorPoint;
bool xAxis = ( aEvent.Matches( EE_ACTIONS::mirrorX.MakeEvent() ) );
LIB_ITEM* item = static_cast<LIB_ITEM*>( selection.Front() );
if( !item->IsMoving() )
saveCopyInUndoList( m_frame->GetCurPart(), UR_LIBEDIT );
if( selection.GetSize() == 1 )
mirrorPoint = item->GetPosition();
else
mirrorPoint = m_frame->GetNearestGridPosition( mapCoords( selection.GetCenter() ) );
for( unsigned ii = 0; ii < selection.GetSize(); ii++ )
{
item = static_cast<LIB_ITEM*>( selection.GetItem( ii ) );
if( xAxis )
item->MirrorVertical( mirrorPoint );
else
item->MirrorHorizontal( mirrorPoint );
m_frame->RefreshItem( item );
}
m_toolMgr->PostEvent( EVENTS::SelectedItemsModified );
if( !item->IsMoving() )
{
if( selection.IsHover() )
m_toolMgr->RunAction( EE_ACTIONS::clearSelection, true );
m_frame->OnModify();
}
return 0;
}
static KICAD_T nonFields[] =
{
LIB_PART_T,
LIB_ALIAS_T,
LIB_ARC_T,
LIB_CIRCLE_T,
LIB_TEXT_T,
LIB_RECTANGLE_T,
LIB_POLYLINE_T,
LIB_BEZIER_T,
LIB_PIN_T,
EOT
};
int LIB_EDIT_TOOL::DoDelete( const TOOL_EVENT& aEvent )
{
LIB_PART* part = m_frame->GetCurPart();
auto items = m_selectionTool->RequestSelection( nonFields ).GetItems();
if( items.empty() )
return 0;
// Don't leave a freed pointer in the selection
m_toolMgr->RunAction( EE_ACTIONS::clearSelection, true );
saveCopyInUndoList( part, UR_LIBEDIT );
std::set<LIB_ITEM *> toDelete;
for( EDA_ITEM* item : items )
{
if( item->Type() == LIB_PIN_T )
{
LIB_PIN* pin = static_cast<LIB_PIN*>( item );
wxPoint pos = pin->GetPosition();
toDelete.insert( pin );
// when pin editing is synchronized, all pins of the same body style are removed:
if( m_frame->SynchronizePins() )
{
int curr_convert = pin->GetConvert();
LIB_PIN* next_pin = part->GetNextPin();
while( next_pin != NULL )
{
pin = next_pin;
next_pin = part->GetNextPin( pin );
if( pin->GetPosition() != pos )
continue;
if( pin->GetConvert() != curr_convert )
continue;
toDelete.insert( pin );
}
}
}
else
{
toDelete.insert( (LIB_ITEM*) item );
}
}
for( auto item : toDelete )
{
part->RemoveDrawItem( item );
}
m_frame->RebuildView();
m_frame->OnModify();
return 0;
}
#define HITTEST_THRESHOLD_PIXELS 5
int LIB_EDIT_TOOL::DeleteItemCursor( const TOOL_EVENT& aEvent )
{
m_toolMgr->RunAction( EE_ACTIONS::clearSelection, true );
m_frame->PushTool( aEvent.GetCommandStr().get() );
Activate();
EE_PICKER_TOOL* picker = m_toolMgr->GetTool<EE_PICKER_TOOL>();
m_pickerItem = nullptr;
picker->SetClickHandler( [this] ( const VECTOR2D& aPosition ) -> bool
{
if( m_pickerItem )
{
EE_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<EE_SELECTION_TOOL>();
selectionTool->UnbrightenItem( m_pickerItem );
selectionTool->AddItemToSel( m_pickerItem, true );
m_toolMgr->RunAction( EE_ACTIONS::doDelete, true );
m_pickerItem = nullptr;
}
return true;
} );
picker->SetMotionHandler( [this] ( const VECTOR2D& aPos )
{
EE_COLLECTOR collector;
collector.m_Threshold = KiROUND( getView()->ToWorld( HITTEST_THRESHOLD_PIXELS ) );
collector.Collect( m_frame->GetCurPart(), nonFields, (wxPoint) aPos );
EE_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<EE_SELECTION_TOOL>();
selectionTool->GuessSelectionCandidates( collector, aPos );
EDA_ITEM* item = collector.GetCount() == 1 ? collector[ 0 ] : nullptr;
if( m_pickerItem != item )
{
if( m_pickerItem )
selectionTool->UnbrightenItem( m_pickerItem );
m_pickerItem = item;
if( m_pickerItem )
selectionTool->BrightenItem( m_pickerItem );
}
} );
picker->SetFinalizeHandler( [this] ( const int& aFinalState )
{
if( m_pickerItem )
m_toolMgr->GetTool<EE_SELECTION_TOOL>()->UnbrightenItem( m_pickerItem );
} );
picker->Activate();
Wait();
m_frame->PopTool();
return 0;
}
int LIB_EDIT_TOOL::Properties( const TOOL_EVENT& aEvent )
{
EE_SELECTION& selection = m_selectionTool->RequestSelection();
if( selection.Empty() || aEvent.IsAction( &EE_ACTIONS::symbolProperties ) )
{
if( m_frame->GetCurPart() )
editSymbolProperties();
}
else if( selection.Size() == 1 )
{
LIB_ITEM* item = (LIB_ITEM*) selection.Front();
// Save copy for undo if not in edit (edit command already handle the save copy)
if( !item->InEditMode() )
saveCopyInUndoList( item->GetParent(), UR_LIBEDIT );
switch( item->Type() )
{
case LIB_PIN_T:
{
LIB_PIN_TOOL* pinTool = m_toolMgr->GetTool<LIB_PIN_TOOL>();
if( pinTool )
pinTool->EditPinProperties( (LIB_PIN*) item );
break;
}
case LIB_ARC_T:
case LIB_CIRCLE_T:
case LIB_RECTANGLE_T:
case LIB_POLYLINE_T:
editGraphicProperties( item );
break;
case LIB_TEXT_T:
editTextProperties( item );
break;
case LIB_FIELD_T:
editFieldProperties( (LIB_FIELD*) item );
break;
default:
wxFAIL_MSG( wxT( "Unhandled item <" ) + item->GetClass() + wxT( ">" ) );
break;
}
}
m_toolMgr->PostEvent( EVENTS::SelectedItemsModified );
return 0;
}
void LIB_EDIT_TOOL::editGraphicProperties( LIB_ITEM* aItem )
{
if( aItem == NULL )
return;
DIALOG_LIB_EDIT_DRAW_ITEM dialog( m_frame, aItem );
if( dialog.ShowModal() != wxID_OK )
return;
if( aItem->IsFillable() )
aItem->SetFillMode( (FILL_T) dialog.GetFillStyle() );
aItem->SetWidth( dialog.GetWidth() );
updateView( aItem );
m_frame->GetCanvas()->Refresh();
m_frame->OnModify( );
m_frame->g_LastLineWidth = dialog.GetWidth();
m_frame->m_DrawSpecificConvert = !dialog.GetApplyToAllConversions();
m_frame->m_DrawSpecificUnit = !dialog.GetApplyToAllUnits();
MSG_PANEL_ITEMS items;
aItem->GetMsgPanelInfo( m_frame->GetUserUnits(), items );
m_frame->SetMsgPanel( items );
}
void LIB_EDIT_TOOL::editTextProperties( LIB_ITEM* aItem )
{
if ( ( aItem == NULL ) || ( aItem->Type() != LIB_TEXT_T ) )
return;
DIALOG_LIB_EDIT_TEXT dlg( m_frame, (LIB_TEXT*) aItem );
if( dlg.ShowModal() != wxID_OK )
return;
updateView( aItem );
m_frame->GetCanvas()->Refresh();
m_frame->OnModify( );
}
void LIB_EDIT_TOOL::editFieldProperties( LIB_FIELD* aField )
{
if( aField == NULL )
return;
wxString caption;
LIB_PART* parent = aField->GetParent();
wxCHECK( parent, /* void */ );
// Editing the component value field is equivalent to creating a new component based
// on the current component. Set the dialog message to inform the user.
if( aField->GetId() == VALUE )
caption = _( "Edit Component Name" );
else
caption.Printf( _( "Edit %s Field" ), GetChars( aField->GetName() ) );
DIALOG_LIB_EDIT_ONE_FIELD dlg( m_frame, caption, aField );
// The dialog may invoke a kiway player for footprint fields
// so we must use a quasimodal dialog.
if( dlg.ShowQuasiModal() != wxID_OK )
return;
wxString newFieldValue = LIB_ID::FixIllegalChars( dlg.GetText(), LIB_ID::ID_SCH );
wxString oldFieldValue = aField->GetFullText( m_frame->GetUnit() );
bool renamed = aField->GetId() == VALUE && newFieldValue != oldFieldValue;
if( renamed )
saveCopyInUndoList( parent, UR_LIB_RENAME );
else
saveCopyInUndoList( parent, UR_LIBEDIT );
dlg.UpdateField( aField );
if( renamed )
{
parent->SetName( newFieldValue );
m_frame->UpdateAfterSymbolProperties( &oldFieldValue, nullptr );
}
else
{
updateView( aField );
m_frame->GetCanvas()->Refresh();
m_frame->OnModify( );
}
}
void LIB_EDIT_TOOL::editSymbolProperties()
{
LIB_PART* part = m_frame->GetCurPart();
bool partLocked = part->UnitsLocked();
wxString oldName = part->GetName();
wxArrayString oldAliases = part->GetAliasNames( false );
m_toolMgr->RunAction( ACTIONS::cancelInteractive, true );
m_toolMgr->RunAction( EE_ACTIONS::clearSelection, true );
DIALOG_EDIT_COMPONENT_IN_LIBRARY dlg( m_frame, part );
// This dialog itself subsequently can invoke a KIWAY_PLAYER as a quasimodal
// frame. Therefore this dialog as a modal frame parent, MUST be run under
// quasimodal mode for the quasimodal frame support to work. So don't use
// the QUASIMODAL macros here.
if( dlg.ShowQuasiModal() != wxID_OK )
return;
// if m_UnitSelectionLocked has changed, set some edit options or defaults
// to the best value
if( partLocked != part->UnitsLocked() )
{
// Enable synchronized pin edit mode for symbols with interchangeable units
m_frame->m_SyncPinEdit = !part->UnitsLocked();
// also set default edit options to the better value
// Usually if units are locked, graphic items are specific to each unit
// and if units are interchangeable, graphic items are common to units
m_frame->m_DrawSpecificUnit = part->UnitsLocked();
}
m_frame->UpdateAfterSymbolProperties( &oldName, &oldAliases );
}
int LIB_EDIT_TOOL::PinTable( const TOOL_EVENT& aEvent )
{
LIB_PART* part = m_frame->GetCurPart();
if( !part )
return 0;
m_toolMgr->RunAction( EE_ACTIONS::clearSelection, true );
saveCopyInUndoList( part, UR_LIBEDIT );
DIALOG_LIB_EDIT_PIN_TABLE dlg( m_frame, part );
if( dlg.ShowModal() == wxID_CANCEL )
return -1;
m_frame->RebuildView();
m_frame->OnModify();
return 0;
}
int LIB_EDIT_TOOL::Undo( const TOOL_EVENT& aEvent )
{
m_frame->GetComponentFromUndoList();
return 0;
}
int LIB_EDIT_TOOL::Redo( const TOOL_EVENT& aEvent )
{
m_frame->GetComponentFromRedoList();
return 0;
}
int LIB_EDIT_TOOL::Cut( const TOOL_EVENT& aEvent )
{
int retVal = Copy( aEvent );
if( retVal == 0 )
retVal = DoDelete( aEvent );
return retVal;
}
int LIB_EDIT_TOOL::Copy( const TOOL_EVENT& aEvent )
{
LIB_PART* part = m_frame->GetCurPart();
EE_SELECTION& selection = m_selectionTool->RequestSelection( nonFields );
if( !part || !selection.GetSize() )
return 0;
for( LIB_ITEM& item : part->GetDrawItems() )
{
if( item.Type() == LIB_FIELD_T )
continue;
wxASSERT( ( item.GetFlags() & STRUCT_DELETED ) == 0 );
if( !item.IsSelected() )
item.SetFlags( STRUCT_DELETED );
}
LIB_PART* partCopy = new LIB_PART( *part );
STRING_FORMATTER formatter;
SCH_LEGACY_PLUGIN::FormatPart( partCopy, formatter );
delete partCopy;
for( LIB_ITEM& item : part->GetDrawItems() )
item.ClearFlags( STRUCT_DELETED );
if( m_toolMgr->SaveClipboard( formatter.GetString() ) )
return 0;
else
return -1;
}
int LIB_EDIT_TOOL::Paste( const TOOL_EVENT& aEvent )
{
LIB_PART* part = m_frame->GetCurPart();
if( !part )
return 0;
EE_SELECTION& selection = m_selectionTool->GetSelection();
std::string text = m_toolMgr->GetClipboard();
STRING_LINE_READER reader( text, "Clipboard" );
LIB_PART* newPart;
try
{
reader.ReadLine();
newPart = SCH_LEGACY_PLUGIN::ParsePart( reader );
}
catch( IO_ERROR& e )
{
wxLogError( wxString::Format( "Malformed clipboard: %s" ), GetChars( e.What() ) );
return -1;
}
if( !newPart )
return -1;
m_frame->SaveCopyInUndoList( part );
m_selectionTool->ClearSelection();
for( LIB_ITEM& item : newPart->GetDrawItems() )
{
if( item.Type() == LIB_FIELD_T )
continue;
LIB_ITEM* newItem = (LIB_ITEM*) item.Clone();
newItem->SetParent( part );
newItem->SetFlags( IS_NEW | IS_PASTED | SELECTED );
part->GetDrawItems().push_back( newItem );
getView()->Add( newItem );
}
delete newPart;
m_selectionTool->RebuildSelection();
if( !selection.Empty() )
{
selection.SetReferencePoint( getViewControls()->GetCursorPosition( true ) );
m_toolMgr->RunAction( EE_ACTIONS::move, false );
}
return 0;
}
int LIB_EDIT_TOOL::Duplicate( const TOOL_EVENT& aEvent )
{
LIB_PART* part = m_frame->GetCurPart();
EE_SELECTION& selection = m_selectionTool->RequestSelection( nonFields );
if( selection.GetSize() == 0 )
return 0;
// Doing a duplicate of a new object doesn't really make any sense; we'd just end
// up dragging around a stack of objects...
if( selection.Front()->IsNew() )
return 0;
if( !selection.Front()->IsMoving() )
saveCopyInUndoList( m_frame->GetCurPart(), UR_LIBEDIT );
EDA_ITEMS newItems;
for( unsigned ii = 0; ii < selection.GetSize(); ++ii )
{
LIB_ITEM* oldItem = static_cast<LIB_ITEM*>( selection.GetItem( ii ) );
LIB_ITEM* newItem = (LIB_ITEM*) oldItem->Clone();
oldItem->ClearFlags( SELECTED );
newItem->SetFlags( IS_NEW | IS_PASTED | SELECTED );
newItems.push_back( newItem );
part->GetDrawItems().push_back( newItem );
getView()->Add( newItem );
}
m_selectionTool->RebuildSelection();
if( !selection.Empty() )
{
selection.SetReferencePoint( mapCoords( getViewControls()->GetCursorPosition( true ) ) );
m_toolMgr->RunAction( EE_ACTIONS::move, false );
}
return 0;
}
void LIB_EDIT_TOOL::setTransitions()
{
Go( &LIB_EDIT_TOOL::Undo, ACTIONS::undo.MakeEvent() );
Go( &LIB_EDIT_TOOL::Redo, ACTIONS::redo.MakeEvent() );
Go( &LIB_EDIT_TOOL::Cut, ACTIONS::cut.MakeEvent() );
Go( &LIB_EDIT_TOOL::Copy, ACTIONS::copy.MakeEvent() );
Go( &LIB_EDIT_TOOL::Paste, ACTIONS::paste.MakeEvent() );
Go( &LIB_EDIT_TOOL::Duplicate, ACTIONS::duplicate.MakeEvent() );
Go( &LIB_EDIT_TOOL::Rotate, EE_ACTIONS::rotateCW.MakeEvent() );
Go( &LIB_EDIT_TOOL::Rotate, EE_ACTIONS::rotateCCW.MakeEvent() );
Go( &LIB_EDIT_TOOL::Mirror, EE_ACTIONS::mirrorX.MakeEvent() );
Go( &LIB_EDIT_TOOL::Mirror, EE_ACTIONS::mirrorY.MakeEvent() );
Go( &LIB_EDIT_TOOL::DoDelete, EE_ACTIONS::doDelete.MakeEvent() );
Go( &LIB_EDIT_TOOL::DeleteItemCursor, EE_ACTIONS::deleteItemCursor.MakeEvent() );
Go( &LIB_EDIT_TOOL::Properties, EE_ACTIONS::properties.MakeEvent() );
Go( &LIB_EDIT_TOOL::Properties, EE_ACTIONS::symbolProperties.MakeEvent() );
Go( &LIB_EDIT_TOOL::PinTable, EE_ACTIONS::pinTable.MakeEvent() );
}