|
|
/*
* This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2019 CERN * Copyright (C) 2019-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 <advanced_config.h>
#include <core/typeinfo.h>
#include <core/kicad_algo.h>
#include <gal/graphics_abstraction_layer.h>
#include <geometry/shape_compound.h>
#include <ee_actions.h>
#include <ee_collectors.h>
#include <ee_selection_tool.h>
#include <eeschema_id.h>
#include <symbol_edit_frame.h>
#include <symbol_viewer_frame.h>
#include <math/util.h>
#include <geometry/shape_rect.h>
#include <sch_painter.h>
#include <preview_items/selection_area.h>
#include <sch_base_frame.h>
#include <sch_commit.h>
#include <sch_symbol.h>
#include <sch_field.h>
#include <sch_edit_frame.h>
#include <sch_item.h>
#include <sch_line.h>
#include <sch_bus_entry.h>
#include <sch_marker.h>
#include <sch_no_connect.h>
#include <sch_render_settings.h>
#include <sch_sheet.h>
#include <sch_sheet_pin.h>
#include <sch_table.h>
#include <sch_tablecell.h>
#include <schematic.h>
#include <tool/tool_event.h>
#include <tool/tool_manager.h>
#include <tools/ee_grid_helper.h>
#include <tools/ee_point_editor.h>
#include <tools/sch_line_wire_bus_tool.h>
#include <tools/sch_editor_control.h>
#include <trigo.h>
#include <view/view.h>
#include <view/view_controls.h>
#include <wx/log.h>
#include "symb_transforms_utils.h"
SELECTION_CONDITION EE_CONDITIONS::SingleSymbol = []( const SELECTION& aSel ) { if( aSel.GetSize() == 1 ) { SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( aSel.Front() );
if( symbol ) return !symbol->GetLibSymbolRef() || !symbol->GetLibSymbolRef()->IsPower(); }
return false; };
SELECTION_CONDITION EE_CONDITIONS::SingleSymbolOrPower = []( const SELECTION& aSel ) { return aSel.GetSize() == 1 && aSel.Front()->Type() == SCH_SYMBOL_T; };
SELECTION_CONDITION EE_CONDITIONS::SingleDeMorganSymbol = []( const SELECTION& aSel ) { if( aSel.GetSize() == 1 ) { SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( aSel.Front() );
if( symbol ) return symbol->GetLibSymbolRef() && symbol->GetLibSymbolRef()->HasAlternateBodyStyle(); }
return false; };
SELECTION_CONDITION EE_CONDITIONS::SingleMultiUnitSymbol = []( const SELECTION& aSel ) { if( aSel.GetSize() == 1 ) { SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( aSel.Front() );
if( symbol ) return symbol->GetLibSymbolRef() && symbol->GetLibSymbolRef()->GetUnitCount() >= 2; }
return false; };
SELECTION_CONDITION EE_CONDITIONS::SingleMultiFunctionPin = []( const SELECTION& aSel ) { if( aSel.GetSize() == 1 ) { SCH_PIN* pin = dynamic_cast<SCH_PIN*>( aSel.Front() );
if( pin && pin->GetLibPin() ) return !pin->GetLibPin()->GetAlternates().empty(); }
return false; };
SELECTION_CONDITION EE_CONDITIONS::SingleNonExcludedMarker = []( const SELECTION& aSel ) { if( aSel.CountType( SCH_MARKER_T ) != 1 ) return false;
return !static_cast<SCH_MARKER*>( aSel.Front() )->IsExcluded(); };
SELECTION_CONDITION EE_CONDITIONS::MultipleSymbolsOrPower = []( const SELECTION& aSel ) { return aSel.GetSize() > 1 && aSel.OnlyContains( { SCH_SYMBOL_T } ); };
SELECTION_CONDITION EE_CONDITIONS::AllPins = []( const SELECTION& aSel ) { return aSel.GetSize() >= 1 && aSel.OnlyContains( { SCH_PIN_T } ); };
SELECTION_CONDITION EE_CONDITIONS::AllPinsOrSheetPins = []( const SELECTION& aSel ) { return aSel.GetSize() >= 1 && aSel.OnlyContains( { SCH_PIN_T, SCH_SHEET_PIN_T } ); };
#define HITTEST_THRESHOLD_PIXELS 5
EE_SELECTION_TOOL::EE_SELECTION_TOOL() : SELECTION_TOOL( "eeschema.InteractiveSelection" ), m_frame( nullptr ), m_nonModifiedCursor( KICURSOR::ARROW ), m_isSymbolEditor( false ), m_isSymbolViewer( false ), m_unit( 0 ), m_bodyStyle( 0 ) { m_filter.SetDefaults(); m_selection.Clear(); }
EE_SELECTION_TOOL::~EE_SELECTION_TOOL() { getView()->Remove( &m_selection ); }
using E_C = EE_CONDITIONS;
static std::vector<KICAD_T> connectedTypes = { SCH_SYMBOL_LOCATE_POWER_T, SCH_PIN_T, SCH_ITEM_LOCATE_WIRE_T, SCH_ITEM_LOCATE_BUS_T, SCH_BUS_WIRE_ENTRY_T, SCH_BUS_BUS_ENTRY_T, SCH_LABEL_T, SCH_HIER_LABEL_T, SCH_GLOBAL_LABEL_T, SCH_SHEET_PIN_T, SCH_DIRECTIVE_LABEL_T, SCH_JUNCTION_T };
static std::vector<KICAD_T> connectedLineTypes = { SCH_ITEM_LOCATE_WIRE_T, SCH_ITEM_LOCATE_BUS_T };
static std::vector<KICAD_T> crossProbingTypes = { SCH_SYMBOL_T, SCH_PIN_T, SCH_SHEET_T };
static std::vector<KICAD_T> lineTypes = { SCH_LINE_T }; static std::vector<KICAD_T> sheetTypes = { SCH_SHEET_T }; static std::vector<KICAD_T> tableCellTypes = { SCH_TABLECELL_T };
bool EE_SELECTION_TOOL::Init() { m_frame = getEditFrame<SCH_BASE_FRAME>();
SYMBOL_VIEWER_FRAME* symbolViewerFrame = dynamic_cast<SYMBOL_VIEWER_FRAME*>( m_frame ); SYMBOL_EDIT_FRAME* symbolEditorFrame = dynamic_cast<SYMBOL_EDIT_FRAME*>( m_frame );
if( symbolEditorFrame ) { m_isSymbolEditor = true; m_unit = symbolEditorFrame->GetUnit(); m_bodyStyle = symbolEditorFrame->GetBodyStyle(); } else { m_isSymbolViewer = symbolViewerFrame != nullptr; }
auto linesSelection = E_C::MoreThan( 0 ) && E_C::OnlyTypes( lineTypes ); auto wireOrBusSelection = E_C::Count( 1 ) && E_C::OnlyTypes( connectedLineTypes ); auto connectedSelection = E_C::Count( 1 ) && E_C::OnlyTypes( connectedTypes ); auto sheetSelection = E_C::Count( 1 ) && E_C::OnlyTypes( sheetTypes ); auto crossProbingSelection = E_C::MoreThan( 0 ) && E_C::HasTypes( crossProbingTypes ); auto tableCellSelection = E_C::MoreThan( 0 ) && E_C::OnlyTypes( tableCellTypes );
auto schEditSheetPageNumberCondition = [&] ( const SELECTION& aSel ) { if( m_isSymbolEditor || m_isSymbolViewer ) return false;
return E_C::LessThan( 2 )( aSel ) && E_C::OnlyTypes( sheetTypes )( aSel ); };
auto schEditCondition = [this] ( const SELECTION& aSel ) { return !m_isSymbolEditor && !m_isSymbolViewer; };
auto belowRootSheetCondition = [&]( const SELECTION& aSel ) { SCH_EDIT_FRAME* editFrame = dynamic_cast<SCH_EDIT_FRAME*>( m_frame );
return editFrame && editFrame->GetCurrentSheet().Last() != &editFrame->Schematic().Root(); };
auto haveHighlight = [&]( const SELECTION& sel ) { SCH_EDIT_FRAME* editFrame = dynamic_cast<SCH_EDIT_FRAME*>( m_frame );
return editFrame && !editFrame->GetHighlightedConnection().IsEmpty(); };
auto haveSymbol = [&]( const SELECTION& sel ) { return m_isSymbolEditor && static_cast<SYMBOL_EDIT_FRAME*>( m_frame )->GetCurSymbol(); };
auto symbolDisplayNameIsEditable = [&]( const SELECTION& sel ) { if ( !m_isSymbolEditor ) return false;
SYMBOL_EDIT_FRAME* symbEditorFrame = dynamic_cast<SYMBOL_EDIT_FRAME*>( m_frame );
return symbEditorFrame && symbEditorFrame->GetCurSymbol() && symbEditorFrame->GetCurSymbol()->IsMulti() && symbEditorFrame->IsSymbolEditable() && !symbEditorFrame->IsSymbolAlias(); };
auto& menu = m_menu.GetMenu();
menu.AddItem( EE_ACTIONS::clearHighlight, haveHighlight && EE_CONDITIONS::Idle, 1 ); menu.AddSeparator( haveHighlight && EE_CONDITIONS::Idle, 1 );
menu.AddItem( ACTIONS::selectColumns, tableCellSelection && EE_CONDITIONS::Idle, 2 ); menu.AddItem( ACTIONS::selectRows, tableCellSelection && EE_CONDITIONS::Idle, 2 ); menu.AddItem( ACTIONS::selectTable, tableCellSelection && EE_CONDITIONS::Idle, 2 );
menu.AddSeparator( 100 ); menu.AddItem( EE_ACTIONS::drawWire, schEditCondition && EE_CONDITIONS::Empty, 100 ); menu.AddItem( EE_ACTIONS::drawBus, schEditCondition && EE_CONDITIONS::Empty, 100 );
menu.AddSeparator( 100 ); menu.AddItem( ACTIONS::finishInteractive, SCH_LINE_WIRE_BUS_TOOL::IsDrawingLineWireOrBus, 100 );
menu.AddItem( EE_ACTIONS::enterSheet, sheetSelection && EE_CONDITIONS::Idle, 150 ); menu.AddItem( EE_ACTIONS::selectOnPCB, crossProbingSelection && EE_CONDITIONS::Idle, 150 ); menu.AddItem( EE_ACTIONS::leaveSheet, belowRootSheetCondition, 150 );
menu.AddSeparator( 200 ); menu.AddItem( EE_ACTIONS::placeJunction, wireOrBusSelection && EE_CONDITIONS::Idle, 250 ); menu.AddItem( EE_ACTIONS::placeLabel, wireOrBusSelection && EE_CONDITIONS::Idle, 250 ); menu.AddItem( EE_ACTIONS::placeClassLabel, wireOrBusSelection && EE_CONDITIONS::Idle, 250 ); menu.AddItem( EE_ACTIONS::placeGlobalLabel, wireOrBusSelection && EE_CONDITIONS::Idle, 250 ); menu.AddItem( EE_ACTIONS::placeHierLabel, wireOrBusSelection && EE_CONDITIONS::Idle, 250 ); menu.AddItem( EE_ACTIONS::breakWire, linesSelection && EE_CONDITIONS::Idle, 250 ); menu.AddItem( EE_ACTIONS::slice, linesSelection && EE_CONDITIONS::Idle, 250 ); menu.AddItem( EE_ACTIONS::placeSheetPin, sheetSelection && EE_CONDITIONS::Idle, 250 ); menu.AddItem( EE_ACTIONS::syncSheetPins, sheetSelection && EE_CONDITIONS::Idle, 250 ); menu.AddItem( EE_ACTIONS::assignNetclass, connectedSelection && EE_CONDITIONS::Idle, 250 ); menu.AddItem( EE_ACTIONS::editPageNumber, schEditSheetPageNumberCondition, 250 );
menu.AddSeparator( 400 ); menu.AddItem( EE_ACTIONS::symbolProperties, haveSymbol && EE_CONDITIONS::Empty, 400 ); menu.AddItem( EE_ACTIONS::pinTable, haveSymbol && EE_CONDITIONS::Empty, 400 ); menu.AddItem( EE_ACTIONS::setUnitDisplayName, haveSymbol && symbolDisplayNameIsEditable && EE_CONDITIONS::Empty, 400 );
menu.AddSeparator( 1000 ); m_frame->AddStandardSubMenus( m_menu );
m_disambiguateTimer.SetOwner( this ); Connect( wxEVT_TIMER, wxTimerEventHandler( EE_SELECTION_TOOL::onDisambiguationExpire ), nullptr, this );
return true; }
void EE_SELECTION_TOOL::Reset( RESET_REASON aReason ) { m_frame = getEditFrame<SCH_BASE_FRAME>();
if( aReason != TOOL_BASE::REDRAW ) { // Remove pointers to the selected items from containers without changing their
// properties (as they are already deleted while a new sheet is loaded)
m_selection.Clear(); }
if( aReason == RESET_REASON::SHUTDOWN ) return;
if( aReason == TOOL_BASE::MODEL_RELOAD || aReason == TOOL_BASE::SUPERMODEL_RELOAD ) { getView()->GetPainter()->GetSettings()->SetHighlight( false );
SYMBOL_EDIT_FRAME* symbolEditFrame = dynamic_cast<SYMBOL_EDIT_FRAME*>( m_frame ); SYMBOL_VIEWER_FRAME* symbolViewerFrame = dynamic_cast<SYMBOL_VIEWER_FRAME*>( m_frame );
if( symbolEditFrame ) { m_isSymbolEditor = true; m_unit = symbolEditFrame->GetUnit(); m_bodyStyle = symbolEditFrame->GetBodyStyle(); } else { m_isSymbolViewer = symbolViewerFrame != nullptr; } }
// Reinsert the VIEW_GROUP, in case it was removed from the VIEW
getView()->Remove( &m_selection ); getView()->Add( &m_selection ); }
int EE_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent ) { m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
KIID lastRolloverItem = niluuid; EE_GRID_HELPER grid( m_toolMgr );
auto pinOrientation = []( EDA_ITEM* aItem ) { SCH_PIN* pin = dynamic_cast<SCH_PIN*>( aItem );
if( pin ) { const SCH_SYMBOL* parent = dynamic_cast<const SCH_SYMBOL*>( pin->GetParentSymbol() );
if( !parent ) return pin->GetOrientation(); else { SCH_PIN dummy( *pin ); RotateAndMirrorPin( dummy, parent->GetOrientation() ); return dummy.GetOrientation(); } }
SCH_SHEET_PIN* sheetPin = dynamic_cast<SCH_SHEET_PIN*>( aItem );
if( sheetPin ) { switch( sheetPin->GetSide() ) { default: case SHEET_SIDE::LEFT: return PIN_ORIENTATION::PIN_RIGHT; case SHEET_SIDE::RIGHT: return PIN_ORIENTATION::PIN_LEFT; case SHEET_SIDE::TOP: return PIN_ORIENTATION::PIN_DOWN; case SHEET_SIDE::BOTTOM: return PIN_ORIENTATION::PIN_UP; } }
return PIN_ORIENTATION::PIN_LEFT; };
// Main loop: keep receiving events
while( TOOL_EVENT* evt = Wait() ) { bool selCancelled = false; bool displayWireCursor = false; bool displayBusCursor = false; bool displayLineCursor = false; KIID rolloverItem = lastRolloverItem;
// on left click, a selection is made, depending on modifiers ALT, SHIFT, CTRL:
setModifiersState( evt->Modifier( MD_SHIFT ), evt->Modifier( MD_CTRL ), evt->Modifier( MD_ALT ) );
MOUSE_DRAG_ACTION drag_action = m_frame->GetDragAction();
if( evt->IsMouseDown( BUT_LEFT ) ) { if( !m_frame->ToolStackIsEmpty() ) { // Avoid triggering when running under other tools
} else if( m_toolMgr->GetTool<EE_POINT_EDITOR>() && m_toolMgr->GetTool<EE_POINT_EDITOR>()->HasPoint() ) { // Distinguish point editor from selection modification by checking modifiers
if( hasModifier() ) { m_originalCursor = m_toolMgr->GetMousePosition(); m_disambiguateTimer.StartOnce( ADVANCED_CFG::GetCfg().m_DisambiguationMenuDelay ); } } else { m_originalCursor = m_toolMgr->GetMousePosition(); m_disambiguateTimer.StartOnce( ADVANCED_CFG::GetCfg().m_DisambiguationMenuDelay ); } } // Single click? Select single object
else if( evt->IsClick( BUT_LEFT ) ) { // If the timer has stopped, then we have already run the disambiguate routine
// and we don't want to register an extra click here
if( !m_disambiguateTimer.IsRunning() ) { evt->SetPassEvent(); continue; }
m_disambiguateTimer.Stop();
if( SCH_EDIT_FRAME* schframe = dynamic_cast<SCH_EDIT_FRAME*>( m_frame ) ) schframe->FocusOnItem( nullptr );
// Collect items at the clicked location (doesn't select them yet)
EE_COLLECTOR collector; CollectHits( collector, evt->Position() ); narrowSelection( collector, evt->Position(), false );
if( collector.GetCount() == 1 && !m_isSymbolEditor && !hasModifier() ) { OPT_TOOL_EVENT autostart = autostartEvent( evt, grid, collector[0] );
if( autostart ) { DRAW_SEGMENT_EVENT_PARAMS* params = new DRAW_SEGMENT_EVENT_PARAMS();
params->layer = autostart->Parameter<const DRAW_SEGMENT_EVENT_PARAMS*>()->layer; params->quitOnDraw = true; params->sourceSegment = dynamic_cast<SCH_LINE*>( collector[0] );
autostart->SetParameter<const DRAW_SEGMENT_EVENT_PARAMS*>( params ); m_toolMgr->ProcessEvent( *autostart );
selCancelled = true; } else if( collector[0]->IsHypertext() ) { collector[ 0 ]->DoHypertextAction( m_frame ); selCancelled = true; } else if( collector[0]->IsBrightened() ) { if( SCH_EDIT_FRAME* schframe = dynamic_cast<SCH_EDIT_FRAME*>( m_frame ) ) { NET_NAVIGATOR_ITEM_DATA itemData( schframe->GetCurrentSheet(), collector[0] );
schframe->SelectNetNavigatorItem( &itemData ); } } }
if( !selCancelled ) { selectPoint( collector, evt->Position(), nullptr, nullptr, m_additive, m_subtractive, m_exclusive_or ); m_selection.SetIsHover( false ); } } else if( evt->IsClick( BUT_RIGHT ) ) { m_disambiguateTimer.Stop();
// right click? if there is any object - show the context menu
if( m_selection.Empty() ) { ClearSelection(); SelectPoint( evt->Position(), { SCH_LOCATE_ANY_T }, nullptr, &selCancelled ); m_selection.SetIsHover( true ); } // If the cursor has moved off the bounding box of the selection by more than
// a grid square, check to see if there is another item available for selection
// under the cursor. If there is, the user likely meant to get the context menu
// for that item. If there is no new item, then keep the original selection and
// show the context menu for it.
else if( !m_selection.GetBoundingBox().Inflate( grid.GetGrid().x, grid.GetGrid().y ) .Contains( evt->Position() ) ) { EE_COLLECTOR collector;
if( CollectHits( collector, evt->Position(), { SCH_LOCATE_ANY_T } ) ) { ClearSelection();
SelectPoint( evt->Position(), { SCH_LOCATE_ANY_T }, nullptr, &selCancelled ); m_selection.SetIsHover( true ); } }
if( !selCancelled ) m_menu.ShowContextMenu( m_selection ); } else if( evt->IsDblClick( BUT_LEFT ) ) { m_disambiguateTimer.Stop();
// double click? Display the properties window
if( SCH_EDIT_FRAME* schframe = dynamic_cast<SCH_EDIT_FRAME*>( m_frame ) ) schframe->FocusOnItem( nullptr );
if( m_selection.Empty() ) SelectPoint( evt->Position() );
EDA_ITEM* item = m_selection.Front();
if( item && item->Type() == SCH_SHEET_T ) m_toolMgr->PostAction( EE_ACTIONS::enterSheet ); else m_toolMgr->PostAction( EE_ACTIONS::properties ); } else if( evt->IsDblClick( BUT_MIDDLE ) ) { m_disambiguateTimer.Stop();
// Middle double click? Do zoom to fit or zoom to objects
if( evt->Modifier( MD_CTRL ) ) // Is CTRL key down?
m_toolMgr->RunAction( ACTIONS::zoomFitObjects ); else m_toolMgr->RunAction( ACTIONS::zoomFitScreen ); } else if( evt->IsDrag( BUT_LEFT ) ) { m_disambiguateTimer.Stop();
// Is another tool already moving a new object? Don't allow a drag start
if( !m_selection.Empty() && m_selection[0]->HasFlag( IS_NEW | IS_MOVING ) ) { evt->SetPassEvent(); continue; }
// drag with LMB? Select multiple objects (or at least draw a selection box) or
// drag them
if( SCH_EDIT_FRAME* schframe = dynamic_cast<SCH_EDIT_FRAME*>( m_frame ) ) schframe->FocusOnItem( nullptr );
EE_COLLECTOR collector;
if( CollectHits( collector, evt->DragOrigin(), { SCH_TABLECELL_T } ) ) { selectTableCells( static_cast<SCH_TABLE*>( collector[0]->GetParent() ) ); } else if( hasModifier() || drag_action == MOUSE_DRAG_ACTION::SELECT ) { selectMultiple(); } else if( m_selection.Empty() && drag_action != MOUSE_DRAG_ACTION::DRAG_ANY ) { selectMultiple(); } else { if( m_isSymbolEditor ) { if( static_cast<SYMBOL_EDIT_FRAME*>( m_frame )->IsSymbolAlias() ) { m_selection = RequestSelection( { SCH_FIELD_T } ); } else { m_selection = RequestSelection( { SCH_SHAPE_T, SCH_TEXT_T, SCH_TEXTBOX_T, SCH_PIN_T, SCH_FIELD_T } ); } } else { m_selection = RequestSelection( EE_COLLECTOR::MovableItems ); }
// Check if dragging has started within any of selected items bounding box
if( selectionContains( evt->DragOrigin() ) ) { // drag_is_move option exists only in schematic editor, not in symbol editor
// (m_frame->eeconfig() returns nullptr in Symbol Editor)
if( m_isSymbolEditor || m_frame->eeconfig()->m_Input.drag_is_move ) m_toolMgr->RunAction( EE_ACTIONS::move ); else m_toolMgr->RunAction( EE_ACTIONS::drag ); } else { // No -> drag a selection box
selectMultiple(); } } } else if( evt->IsMouseDown( BUT_AUX1 ) ) { m_toolMgr->RunAction( EE_ACTIONS::navigateBack ); } else if( evt->IsMouseDown( BUT_AUX2 ) ) { m_toolMgr->RunAction( EE_ACTIONS::navigateForward ); } else if( evt->Category() == TC_COMMAND && evt->Action() == TA_CHOICE_MENU_CHOICE ) { m_disambiguateTimer.Stop();
// context sub-menu selection? Handle unit selection or bus unfolding
if( *evt->GetCommandId() >= ID_POPUP_SCH_SELECT_UNIT && *evt->GetCommandId() <= ID_POPUP_SCH_SELECT_UNIT_END ) { SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( m_selection.Front() ); int unit = *evt->GetCommandId() - ID_POPUP_SCH_SELECT_UNIT;
if( symbol ) static_cast<SCH_EDIT_FRAME*>( m_frame )->SelectUnit( symbol, unit ); } else if( *evt->GetCommandId() >= ID_POPUP_SCH_ALT_PIN_FUNCTION && *evt->GetCommandId() <= ID_POPUP_SCH_ALT_PIN_FUNCTION_END ) { SCH_PIN* pin = dynamic_cast<SCH_PIN*>( m_selection.Front() ); wxString alt = *evt->Parameter<wxString*>();
if( pin ) static_cast<SCH_EDIT_FRAME*>( m_frame )->SetAltPinFunction( pin, alt ); } else if( *evt->GetCommandId() >= ID_POPUP_SCH_PIN_TRICKS_START && *evt->GetCommandId() <= ID_POPUP_SCH_PIN_TRICKS_END ) { if( !m_selection.OnlyContains( { SCH_PIN_T, SCH_SHEET_PIN_T } ) || m_selection.Empty() ) { return 0; }
// Keep track of new items so we make them the new selection at the end
EDA_ITEMS newItems; SCH_COMMIT commit( static_cast<SCH_EDIT_FRAME*>( m_frame ) );
if( *evt->GetCommandId() == ID_POPUP_SCH_PIN_TRICKS_NO_CONNECT ) { for( EDA_ITEM* item : m_selection ) { SCH_NO_CONNECT* nc = new SCH_NO_CONNECT( item->GetPosition() ); commit.Add( nc, m_frame->GetScreen() ); newItems.push_back( nc ); }
commit.Push( wxS( "No Connect Pins" ) ); ClearSelection(); } else if( *evt->GetCommandId() == ID_POPUP_SCH_PIN_TRICKS_WIRE ) { VECTOR2I wireGrid = grid.GetGridSize( GRID_HELPER_GRIDS::GRID_WIRES );
for( EDA_ITEM* item : m_selection ) { SCH_LINE* wire = new SCH_LINE( item->GetPosition(), LAYER_WIRE );
// Add some length to the wire as nothing in our code base handles
// 0 length wires very well, least of all the ortho drag algorithm
VECTOR2I stub;
switch( pinOrientation( item ) ) { default: case PIN_ORIENTATION::PIN_RIGHT: stub = VECTOR2I( -1 * wireGrid.x, 0 ); break; case PIN_ORIENTATION::PIN_LEFT: stub = VECTOR2I( 1 * wireGrid.x, 0 ); break; case PIN_ORIENTATION::PIN_UP: stub = VECTOR2I( 0, 1 * wireGrid.y ); break; case PIN_ORIENTATION::PIN_DOWN: stub = VECTOR2I( 0, -1 * wireGrid.y ); break; }
wire->SetEndPoint( item->GetPosition() + stub );
m_frame->AddToScreen( wire, m_frame->GetScreen() ); commit.Added( wire, m_frame->GetScreen() ); newItems.push_back( wire ); }
ClearSelection(); AddItemsToSel( &newItems );
// Select only the ends so we can immediately start dragging them
for( EDA_ITEM* item : newItems ) static_cast<SCH_LINE*>( item )->SetFlags( ENDPOINT );
// Put the mouse on the nearest point of the first wire
SCH_LINE* first = static_cast<SCH_LINE*>( newItems[0] ); getViewControls()->SetCrossHairCursorPosition( first->GetEndPoint(), false ); getViewControls()->WarpMouseCursor( getViewControls()->GetCursorPosition(), true );
// Start the drag tool, canceling will remove the wires
if( m_toolMgr->RunSynchronousAction( EE_ACTIONS::drag, &commit, false ) ) commit.Push( wxS( "Wire Pins" ) ); else commit.Revert(); } else { // For every pin in the selection, add a label according to menu item
// selected by the user
for( EDA_ITEM* item : m_selection ) { SCH_PIN* pin = dynamic_cast<SCH_PIN*>( item ); SCH_SHEET_PIN* sheetPin = dynamic_cast<SCH_SHEET_PIN*>( item ); SCH_EDIT_FRAME* sf = dynamic_cast<SCH_EDIT_FRAME*>( m_frame ); SCH_LABEL_BASE* label = nullptr;
wxString labelText;
if( pin ) labelText = pin->GetShownName(); else if( sheetPin && sf ) labelText = sheetPin->GetShownText( &sf->GetCurrentSheet(), false );
switch( *evt->GetCommandId() ) { case ID_POPUP_SCH_PIN_TRICKS_NET_LABEL: label = new SCH_LABEL( item->GetPosition(), labelText ); break; case ID_POPUP_SCH_PIN_TRICKS_HIER_LABEL: label = new SCH_HIERLABEL( item->GetPosition(), labelText ); break; case ID_POPUP_SCH_PIN_TRICKS_GLOBAL_LABEL: label = new SCH_GLOBALLABEL( item->GetPosition(), labelText ); break; default: continue; }
switch( pinOrientation( item ) ) { default: case PIN_ORIENTATION::PIN_RIGHT: label->SetSpinStyle( SPIN_STYLE::SPIN::LEFT ); break; case PIN_ORIENTATION::PIN_LEFT: label->SetSpinStyle( SPIN_STYLE::SPIN::RIGHT ); break; case PIN_ORIENTATION::PIN_UP: label->SetSpinStyle( SPIN_STYLE::SPIN::BOTTOM ); break; case PIN_ORIENTATION::PIN_DOWN: label->SetSpinStyle( SPIN_STYLE::SPIN::UP ); break; }
ELECTRICAL_PINTYPE pinType = ELECTRICAL_PINTYPE::PT_UNSPECIFIED;
if( pin ) { pinType = pin->GetType(); } else if( sheetPin ) { switch( sheetPin->GetLabelShape() ) { case LABEL_INPUT: pinType = ELECTRICAL_PINTYPE::PT_INPUT; break; case LABEL_OUTPUT: pinType = ELECTRICAL_PINTYPE::PT_OUTPUT; break; case LABEL_BIDI: pinType = ELECTRICAL_PINTYPE::PT_BIDI; break; case LABEL_TRISTATE: pinType = ELECTRICAL_PINTYPE::PT_TRISTATE; break; case LABEL_PASSIVE: pinType = ELECTRICAL_PINTYPE::PT_PASSIVE; break; } }
switch( pinType ) { case ELECTRICAL_PINTYPE::PT_BIDI: label->SetShape( LABEL_FLAG_SHAPE::L_BIDI ); break; case ELECTRICAL_PINTYPE::PT_INPUT: label->SetShape( LABEL_FLAG_SHAPE::L_INPUT ); break; case ELECTRICAL_PINTYPE::PT_OUTPUT: label->SetShape( LABEL_FLAG_SHAPE::L_OUTPUT ); break; case ELECTRICAL_PINTYPE::PT_TRISTATE: label->SetShape( LABEL_FLAG_SHAPE::L_TRISTATE ); break; case ELECTRICAL_PINTYPE::PT_UNSPECIFIED: label->SetShape( LABEL_FLAG_SHAPE::L_UNSPECIFIED ); break; default: label->SetShape( LABEL_FLAG_SHAPE::L_INPUT ); }
commit.Add( label, m_frame->GetScreen() ); newItems.push_back( label ); }
commit.Push( wxS( "Label Pins" ) );
// Many users will want to drag these items to wire off of the pins, so
// pre-select them.
ClearSelection(); AddItemsToSel( &newItems ); } } else if( *evt->GetCommandId() >= ID_POPUP_SCH_UNFOLD_BUS && *evt->GetCommandId() <= ID_POPUP_SCH_UNFOLD_BUS_END ) { wxString* net = new wxString( *evt->Parameter<wxString*>() ); m_toolMgr->RunAction<wxString*>( EE_ACTIONS::unfoldBus, net ); } } else if( evt->IsCancelInteractive() ) { m_disambiguateTimer.Stop();
// We didn't set these, but we have reports that they leak out of some other tools,
// so we clear them here.
getViewControls()->SetAutoPan( false ); getViewControls()->CaptureCursor( false );
if( SCH_EDIT_FRAME* schframe = dynamic_cast<SCH_EDIT_FRAME*>( m_frame ) ) schframe->FocusOnItem( nullptr );
if( !GetSelection().Empty() ) { ClearSelection(); } else if( evt->FirstResponder() == this && evt->GetCommandId() == (int) WXK_ESCAPE ) { SCH_EDITOR_CONTROL* editor = m_toolMgr->GetTool<SCH_EDITOR_CONTROL>();
if( editor && m_frame->eeconfig()->m_Input.esc_clears_net_highlight ) editor->ClearHighlight( *evt ); } } else if( evt->Action() == TA_UNDO_REDO_PRE ) { if( SCH_EDIT_FRAME* schframe = dynamic_cast<SCH_EDIT_FRAME*>( m_frame ) ) schframe->FocusOnItem( nullptr ); } else if( evt->IsMotion() && !m_isSymbolEditor && evt->FirstResponder() == this ) { // Update cursor and rollover item
rolloverItem = niluuid; EE_COLLECTOR collector;
getViewControls()->ForceCursorPosition( false );
if( CollectHits( collector, evt->Position() ) ) { narrowSelection( collector, evt->Position(), false );
if( collector.GetCount() == 1 && !hasModifier() ) { OPT_TOOL_EVENT autostartEvt = autostartEvent( evt, grid, collector[0] );
if( autostartEvt ) { if( autostartEvt->Matches( EE_ACTIONS::drawBus.MakeEvent() ) ) displayBusCursor = true; else if( autostartEvt->Matches( EE_ACTIONS::drawWire.MakeEvent() ) ) displayWireCursor = true; else if( autostartEvt->Matches( EE_ACTIONS::drawLines.MakeEvent() ) ) displayLineCursor = true; } else if( collector[0]->IsHypertext() && !collector[0]->IsSelected() ) { rolloverItem = collector[0]->m_Uuid; } } } } else { evt->SetPassEvent(); }
if( rolloverItem != lastRolloverItem ) { if( EDA_ITEM* item = m_frame->GetItem( lastRolloverItem ) ) { item->ClearFlags( IS_ROLLOVER ); lastRolloverItem = niluuid;
if( item->Type() == SCH_FIELD_T ) m_frame->GetCanvas()->GetView()->Update( item->GetParent() ); else m_frame->GetCanvas()->GetView()->Update( item ); } }
if( rolloverItem != niluuid ) { EDA_ITEM* item = m_frame->GetItem( rolloverItem );
if( item && !( item->GetFlags() & IS_ROLLOVER ) ) { item->SetFlags( IS_ROLLOVER ); lastRolloverItem = rolloverItem;
if( item->Type() == SCH_FIELD_T ) m_frame->GetCanvas()->GetView()->Update( item->GetParent() ); else m_frame->GetCanvas()->GetView()->Update( item ); } }
if( m_frame->ToolStackIsEmpty() ) { if( displayWireCursor ) { m_nonModifiedCursor = KICURSOR::LINE_WIRE_ADD; } else if( displayBusCursor ) { m_nonModifiedCursor = KICURSOR::LINE_BUS; } else if( displayLineCursor ) { m_nonModifiedCursor = KICURSOR::LINE_GRAPHIC; } else if( rolloverItem != niluuid ) { m_nonModifiedCursor = KICURSOR::HAND; } else if( !m_selection.Empty() && drag_action == MOUSE_DRAG_ACTION::DRAG_SELECTED && evt->HasPosition() && selectionContains( evt->Position() ) ) //move/drag option prediction
{ m_nonModifiedCursor = KICURSOR::MOVING; } else { m_nonModifiedCursor = KICURSOR::ARROW; } } }
m_disambiguateTimer.Stop();
// Shutting down; clear the selection
m_selection.Clear();
return 0; }
OPT_TOOL_EVENT EE_SELECTION_TOOL::autostartEvent( TOOL_EVENT* aEvent, EE_GRID_HELPER& aGrid, SCH_ITEM* aItem ) { VECTOR2I pos = aGrid.BestSnapAnchor( aEvent->Position(), aGrid.GetItemGrid( aItem ) );
if( m_frame->eeconfig()->m_Drawing.auto_start_wires && !m_toolMgr->GetTool<EE_POINT_EDITOR>()->HasPoint() && aItem->IsPointClickableAnchor( pos ) ) { OPT_TOOL_EVENT newEvt = EE_ACTIONS::drawWire.MakeEvent();
if( aItem->Type() == SCH_BUS_BUS_ENTRY_T ) { newEvt = EE_ACTIONS::drawBus.MakeEvent(); } else if( aItem->Type() == SCH_BUS_WIRE_ENTRY_T ) { SCH_BUS_WIRE_ENTRY* busEntry = static_cast<SCH_BUS_WIRE_ENTRY*>( aItem );
if( !busEntry->m_connected_bus_item ) newEvt = EE_ACTIONS::drawBus.MakeEvent(); } else if( aItem->Type() == SCH_LINE_T ) { SCH_LINE* line = static_cast<SCH_LINE*>( aItem );
if( line->IsBus() ) newEvt = EE_ACTIONS::drawBus.MakeEvent(); else if( line->IsGraphicLine() ) newEvt = EE_ACTIONS::drawLines.MakeEvent(); } else if( aItem->Type() == SCH_LABEL_T || aItem->Type() == SCH_HIER_LABEL_T || aItem->Type() == SCH_SHEET_PIN_T ) { SCH_LABEL_BASE* label = static_cast<SCH_LABEL_BASE*>( aItem ); SCH_CONNECTION possibleConnection( label->Schematic()->ConnectionGraph() ); possibleConnection.ConfigureFromLabel( label->GetShownText( false ) );
if( possibleConnection.IsBus() ) newEvt = EE_ACTIONS::drawBus.MakeEvent(); } else if( aItem->Type() == SCH_SYMBOL_T ) { const SCH_SYMBOL* symbol = static_cast<const SCH_SYMBOL*>( aItem ); const SCH_PIN* pin = symbol->GetPin( pos );
if( !pin ) return OPT_TOOL_EVENT();
if( !pin->IsVisible() && !( m_frame->eeconfig()->m_Appearance.show_hidden_pins || m_frame->GetRenderSettings()->m_ShowHiddenPins ) ) { return OPT_TOOL_EVENT(); } }
newEvt->SetMousePosition( pos ); newEvt->SetHasPosition( true ); newEvt->SetForceImmediate( true );
getViewControls()->ForceCursorPosition( true, pos );
return newEvt; }
return OPT_TOOL_EVENT(); }
int EE_SELECTION_TOOL::disambiguateCursor( const TOOL_EVENT& aEvent ) { wxMouseState keyboardState = wxGetMouseState();
setModifiersState( keyboardState.ShiftDown(), keyboardState.ControlDown(), keyboardState.AltDown() );
m_skip_heuristics = true; SelectPoint( m_originalCursor, { SCH_LOCATE_ANY_T }, nullptr, &m_canceledMenu, false, m_additive, m_subtractive, m_exclusive_or ); m_skip_heuristics = false;
return 0; }
void EE_SELECTION_TOOL::OnIdle( wxIdleEvent& aEvent ) { if( m_frame->ToolStackIsEmpty() && !m_multiple ) { wxMouseState keyboardState = wxGetMouseState();
setModifiersState( keyboardState.ShiftDown(), keyboardState.ControlDown(), keyboardState.AltDown() );
if( m_additive ) m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ADD ); else if( m_subtractive ) m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::SUBTRACT ); else if( m_exclusive_or ) m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::XOR ); else m_frame->GetCanvas()->SetCurrentCursor( m_nonModifiedCursor ); } }
EE_SELECTION& EE_SELECTION_TOOL::GetSelection() { return m_selection; }
bool EE_SELECTION_TOOL::CollectHits( EE_COLLECTOR& aCollector, const VECTOR2I& aWhere, const std::vector<KICAD_T>& aScanTypes ) { int pixelThreshold = KiROUND( getView()->ToWorld( HITTEST_THRESHOLD_PIXELS ) ); int gridThreshold = KiROUND( getView()->GetGAL()->GetGridSize().EuclideanNorm() / 2 ); aCollector.m_Threshold = std::max( pixelThreshold, gridThreshold ); aCollector.m_ShowPinElectricalTypes = m_frame->GetRenderSettings()->m_ShowPinsElectricalType;
if( m_isSymbolEditor ) { LIB_SYMBOL* symbol = static_cast<SYMBOL_EDIT_FRAME*>( m_frame )->GetCurSymbol();
if( !symbol ) return false;
aCollector.Collect( symbol->GetDrawItems(), aScanTypes, aWhere, m_unit, m_bodyStyle ); } else { aCollector.Collect( m_frame->GetScreen(), aScanTypes, aWhere, m_unit, m_bodyStyle );
// If pins are disabled in the filter, they will be removed later. Let's add the parent
// so that people can use pins to select symbols in this case.
if( !m_filter.pins ) { int originalCount = aCollector.GetCount();
for( int ii = 0; ii < originalCount; ++ii ) { if( aCollector[ii]->Type() == SCH_PIN_T ) { SCH_PIN* pin = static_cast<SCH_PIN*>( aCollector[ii] );
if( !aCollector.HasItem( pin->GetParentSymbol() ) ) aCollector.Append( pin->GetParentSymbol() ); } } } }
return aCollector.GetCount() > 0; }
void EE_SELECTION_TOOL::narrowSelection( EE_COLLECTOR& collector, const VECTOR2I& aWhere, bool aCheckLocked, bool aSelectedOnly ) { SYMBOL_EDIT_FRAME* symbolEditorFrame = dynamic_cast<SYMBOL_EDIT_FRAME*>( m_frame );
for( int i = collector.GetCount() - 1; i >= 0; --i ) { if( symbolEditorFrame ) { // Do not select invisible items if they are not displayed
EDA_ITEM* item = collector[i];
if( item->Type() == SCH_FIELD_T ) { if( !static_cast<SCH_FIELD*>( item )->IsVisible() && !symbolEditorFrame->GetShowInvisibleFields() ) { collector.Remove( i ); continue; } } else if( item->Type() == SCH_PIN_T ) { if( !static_cast<SCH_PIN*>( item )->IsVisible() && !symbolEditorFrame->GetShowInvisiblePins() ) { collector.Remove( i ); continue; } } }
if( !Selectable( collector[i], &aWhere ) ) { collector.Remove( i ); continue; }
if( aCheckLocked && collector[i]->IsLocked() ) { collector.Remove( i ); continue; }
if( !itemPassesFilter( collector[i] ) ) { collector.Remove( i ); continue; }
if( aSelectedOnly && !collector[i]->IsSelected() ) { collector.Remove( i ); continue; } }
// Apply some ugly heuristics to avoid disambiguation menus whenever possible
if( collector.GetCount() > 1 && !m_skip_heuristics ) GuessSelectionCandidates( collector, aWhere ); }
bool EE_SELECTION_TOOL::selectPoint( EE_COLLECTOR& aCollector, const VECTOR2I& aWhere, EDA_ITEM** aItem, bool* aSelectionCancelledFlag, bool aAdd, bool aSubtract, bool aExclusiveOr ) { m_selection.ClearReferencePoint();
// If still more than one item we're going to have to ask the user.
if( aCollector.GetCount() > 1 ) { // Try to call selectionMenu via RunAction() to avoid event-loop contention
// But it we cannot handle the event, then we don't have an active tool loop, so
// handle it directly.
if( !m_toolMgr->RunAction<COLLECTOR*>( EE_ACTIONS::selectionMenu, &aCollector ) ) { if( !doSelectionMenu( &aCollector ) ) aCollector.m_MenuCancelled = true; }
if( aCollector.m_MenuCancelled ) { if( aSelectionCancelledFlag ) *aSelectionCancelledFlag = true;
return false; } }
if( !aAdd && !aSubtract && !aExclusiveOr ) ClearSelection();
int addedCount = 0; bool anySubtracted = false;
if( aCollector.GetCount() > 0 ) { for( int i = 0; i < aCollector.GetCount(); ++i ) { EDA_ITEM_FLAGS flags = 0; bool isLine = aCollector[i]->Type() == SCH_LINE_T;
// Handle line ends specially
if( isLine ) { SCH_LINE* line = (SCH_LINE*) aCollector[i];
if( line->GetStartPoint().Distance( aWhere ) <= aCollector.m_Threshold ) flags = STARTPOINT; else if( line->GetEndPoint().Distance( aWhere ) <= aCollector.m_Threshold ) flags = ENDPOINT; else flags = STARTPOINT | ENDPOINT; }
if( aSubtract || ( aExclusiveOr && aCollector[i]->IsSelected() && ( !isLine || ( isLine && aCollector[i]->HasFlag( flags ) ) ) ) ) { aCollector[i]->ClearFlags( flags );
// Need to update end shadows after ctrl-click unselecting one of two selected
// endpoints.
if( isLine ) getView()->Update( aCollector[i] );
if( !aCollector[i]->HasFlag( STARTPOINT ) && !aCollector[i]->HasFlag( ENDPOINT ) ) { unselect( aCollector[i] ); anySubtracted = true; } } else { aCollector[i]->SetFlags( flags ); select( aCollector[i] ); addedCount++; } } }
if( addedCount == 1 ) { m_toolMgr->ProcessEvent( EVENTS::PointSelectedEvent );
if( aItem && aCollector.GetCount() == 1 ) *aItem = aCollector[0];
return true; } else if( addedCount > 1 ) { m_toolMgr->ProcessEvent( EVENTS::SelectedEvent ); return true; } else if( anySubtracted ) { m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent ); return true; }
return false; }
bool EE_SELECTION_TOOL::SelectPoint( const VECTOR2I& aWhere, const std::vector<KICAD_T>& aScanTypes, EDA_ITEM** aItem, bool* aSelectionCancelledFlag, bool aCheckLocked, bool aAdd, bool aSubtract, bool aExclusiveOr ) { EE_COLLECTOR collector;
if( !CollectHits( collector, aWhere, aScanTypes ) ) return false;
narrowSelection( collector, aWhere, aCheckLocked, aSubtract );
return selectPoint( collector, aWhere, aItem, aSelectionCancelledFlag, aAdd, aSubtract, aExclusiveOr ); }
int EE_SELECTION_TOOL::SelectAll( const TOOL_EVENT& aEvent ) { m_multiple = true; // Multiple selection mode is active
KIGFX::VIEW* view = getView();
// hold all visible items
std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> selectedItems; std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> sheetPins;
// Filter the view items based on the selection box
BOX2I selectionBox;
selectionBox.SetMaximum(); view->Query( selectionBox, selectedItems ); // Get the list of selected items
// Sheet pins aren't in the view; add them by hand
for( KIGFX::VIEW::LAYER_ITEM_PAIR& pair : selectedItems ) { SCH_SHEET* sheet = dynamic_cast<SCH_SHEET*>( pair.first );
if( sheet ) { int layer = pair.second;
for( SCH_SHEET_PIN* pin : sheet->GetPins() ) sheetPins.emplace_back( KIGFX::VIEW::LAYER_ITEM_PAIR( pin, layer ) ); } }
selectedItems.insert( selectedItems.end(), sheetPins.begin(), sheetPins.end() );
for( const std::pair<KIGFX::VIEW_ITEM*, int>& item_pair : selectedItems ) { if( EDA_ITEM* item = dynamic_cast<EDA_ITEM*>( item_pair.first ) ) { if( Selectable( item ) && itemPassesFilter( item ) ) { if( item->Type() == SCH_LINE_T ) item->SetFlags( STARTPOINT | ENDPOINT );
select( item ); } } }
m_multiple = false;
m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
return 0; }
int EE_SELECTION_TOOL::UnselectAll( const TOOL_EVENT& aEvent ) { m_multiple = true; // Multiple selection mode is active
KIGFX::VIEW* view = getView();
// hold all visible items
std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> selectedItems;
// Filter the view items based on the selection box
BOX2I selectionBox;
selectionBox.SetMaximum(); view->Query( selectionBox, selectedItems ); // Get the list of selected items
for( KIGFX::VIEW::LAYER_ITEM_PAIR& pair : selectedItems ) { SCH_SHEET* sheet = dynamic_cast<SCH_SHEET*>( pair.first );
if( sheet ) { for( SCH_SHEET_PIN* pin : sheet->GetPins() ) { EDA_ITEM* item = dynamic_cast<EDA_ITEM*>( pin );
if( item && Selectable( item ) ) unselect( item ); } }
if( EDA_ITEM* item = dynamic_cast<EDA_ITEM*>( pair.first ) ) { if( Selectable( item ) ) unselect( item ); } }
m_multiple = false;
m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
return 0; }
void EE_SELECTION_TOOL::GuessSelectionCandidates( EE_COLLECTOR& collector, const VECTOR2I& aPos ) { // Prefer exact hits to sloppy ones
std::set<EDA_ITEM*> exactHits;
for( int i = collector.GetCount() - 1; i >= 0; --i ) { EDA_ITEM* item = collector[ i ]; SCH_LINE* line = dynamic_cast<SCH_LINE*>( item ); SCH_SHAPE* shape = dynamic_cast<SCH_SHAPE*>( item ); SCH_TABLE* table = dynamic_cast<SCH_TABLE*>( item );
// Lines are hard to hit. Give them a bit more slop to still be considered "exact".
if( line || ( shape && shape->GetShape() == SHAPE_T::POLY ) || ( shape && shape->GetShape() == SHAPE_T::ARC ) ) { int pixelThreshold = KiROUND( getView()->ToWorld( 6 ) );
if( item->HitTest( aPos, pixelThreshold ) ) exactHits.insert( item ); } else if( table ) { // Consider table cells exact, but not the table itself
} else {
if( m_frame->GetRenderSettings()->m_ShowPinsElectricalType ) item->SetFlags( SHOW_ELEC_TYPE );
if( item->HitTest( aPos, 0 ) ) exactHits.insert( item );
item->ClearFlags( SHOW_ELEC_TYPE ); } }
if( exactHits.size() > 0 && exactHits.size() < (unsigned) collector.GetCount() ) { for( int i = collector.GetCount() - 1; i >= 0; --i ) { EDA_ITEM* item = collector[ i ];
if( !exactHits.contains( item ) ) collector.Transfer( item ); } }
// Find the closest item. (Note that at this point all hits are either exact or non-exact.)
SEG poss( aPos, aPos ); EDA_ITEM* closest = nullptr; int closestDist = INT_MAX / 4;
for( EDA_ITEM* item : collector ) { BOX2I bbox = item->GetBoundingBox(); int dist = INT_MAX / 4;
// A dominating item is one that would unfairly win distance tests
// and mask out other items. For example, a filled rectangle "wins"
// with a zero distance over anything inside it.
bool dominating = false;
if( exactHits.contains( item ) ) { if( item->Type() == SCH_PIN_T || item->Type() == SCH_JUNCTION_T ) { closest = item; break; }
SCH_LINE* line = dynamic_cast<SCH_LINE*>( item ); SCH_FIELD* field = dynamic_cast<SCH_FIELD*>( item ); EDA_TEXT* text = dynamic_cast<EDA_TEXT*>( item ); EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( item ); SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( item );
if( line ) { dist = line->GetSeg().Distance( aPos ); } else if( field ) { BOX2I box = field->GetBoundingBox(); EDA_ANGLE orient = field->GetTextAngle();
if( field->GetParent() && field->GetParent()->Type() == SCH_SYMBOL_T ) { if( static_cast<SCH_SYMBOL*>( field->GetParent() )->GetTransform().y1 ) { if( orient.IsHorizontal() ) orient = ANGLE_VERTICAL; else orient = ANGLE_HORIZONTAL; } }
field->GetEffectiveTextShape( false, box, orient ) ->Collide( poss, INT_MAX / 4, &dist ); } else if( text ) { text->GetEffectiveTextShape( false )->Collide( poss, INT_MAX / 4, &dist ); } else if( shape ) { std::vector<SHAPE*> shapes = shape->MakeEffectiveShapes();
for( SHAPE* s : shapes ) { int shapeDist = dist; s->Collide( poss, INT_MAX / 4, &shapeDist );
if( shapeDist < dist ) dist = shapeDist;
delete s; }
// Filled shapes win hit tests anywhere inside them
dominating = shape->IsFilled(); } else if( symbol ) { bbox = symbol->GetBodyBoundingBox();
SHAPE_RECT rect( bbox.GetPosition(), bbox.GetWidth(), bbox.GetHeight() );
if( bbox.Contains( aPos ) ) dist = bbox.GetCenter().Distance( aPos ); else rect.Collide( poss, closestDist, &dist ); } else { dist = bbox.GetCenter().Distance( aPos ); } } else { SHAPE_RECT rect( bbox.GetPosition(), bbox.GetWidth(), bbox.GetHeight() ); rect.Collide( poss, collector.m_Threshold, &dist ); }
// Don't promote dominating items to be the closest item
// (they'll always win) - they'll still be available for selection, but they
// won't boot out worthy competitors.
if ( !dominating ) { if( dist == closestDist ) { if( item->GetParent() == closest ) closest = item; } else if( dist < closestDist ) { closestDist = dist; closest = item; } } }
// Construct a tight box (1/2 height and width) around the center of the closest item.
// All items which exist at least partly outside this box have sufficient other areas
// for selection and can be dropped.
if( closest ) // Don't try and get a tight bbox if nothing is near the mouse pointer
{ BOX2I tightBox = closest->GetBoundingBox(); tightBox.Inflate( -tightBox.GetWidth() / 4, -tightBox.GetHeight() / 4 );
for( int i = collector.GetCount() - 1; i >= 0; --i ) { EDA_ITEM* item = collector[i];
if( item == closest ) continue;
if( !item->HitTest( tightBox, true ) ) collector.Transfer( item ); } } }
EE_SELECTION& EE_SELECTION_TOOL::RequestSelection( const std::vector<KICAD_T>& aScanTypes, bool aPromoteCellSelections ) { bool anyUnselected = false; bool anySelected = false;
if( m_selection.Empty() ) { VECTOR2D cursorPos = getViewControls()->GetCursorPosition( true );
ClearSelection(); SelectPoint( cursorPos, aScanTypes ); m_selection.SetIsHover( true ); m_selection.ClearReferencePoint(); } else // Trim an existing selection by aFilterList
{ bool isMoving = false;
for( int i = (int) m_selection.GetSize() - 1; i >= 0; --i ) { EDA_ITEM* item = (EDA_ITEM*) m_selection.GetItem( i ); isMoving |= static_cast<SCH_ITEM*>( item )->IsMoving();
if( !item->IsType( aScanTypes ) ) { unselect( item ); anyUnselected = true; } }
if( !isMoving ) updateReferencePoint(); }
if( aPromoteCellSelections ) { std::set<EDA_ITEM*> parents;
for( int i = (int) m_selection.GetSize() - 1; i >= 0; --i ) { EDA_ITEM* item = (EDA_ITEM*) m_selection.GetItem( i );
if( item->Type() == SCH_TABLECELL_T ) { parents.insert( item->GetParent() ); unselect( item ); anyUnselected = true; } }
for( EDA_ITEM* parent : parents ) { if( !parent->IsSelected() ) { select( parent ); anySelected = true; } } }
if( anyUnselected ) m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
if( anySelected ) m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
return m_selection; }
bool EE_SELECTION_TOOL::itemPassesFilter( EDA_ITEM* aItem ) { if( !aItem ) return false;
// Locking is not yet exposed uniformly in the schematic
#if 0
if( SCH_ITEM* schItem = dynamic_cast<SCH_ITEM*>( aItem ) ) { if( schItem->IsLocked() && !m_filter.lockedItems ) return false; } #endif
switch( aItem->Type() ) { case SCH_SYMBOL_T: case SCH_SHEET_T: if( !m_filter.symbols ) return false;
break;
case SCH_PIN_T: case SCH_SHEET_PIN_T: if( !m_filter.pins ) return false;
break;
case SCH_LINE_T: { switch( static_cast<SCH_LINE*>( aItem )->GetLayer() ) { case LAYER_WIRE: case LAYER_BUS: if( !m_filter.wires ) return false;
break;
default: if( !m_filter.graphics ) return false; }
break; }
case SCH_SHAPE_T: if( !m_filter.graphics ) return false;
break;
case SCH_TEXT_T: case SCH_TEXTBOX_T: case SCH_TABLE_T: case SCH_TABLECELL_T: case SCH_FIELD_T: if( !m_filter.text ) return false;
break;
case SCH_LABEL_T: case SCH_GLOBAL_LABEL_T: case SCH_HIER_LABEL_T: if( !m_filter.labels ) return false;
break;
case SCH_BITMAP_T: if( !m_filter.images ) return false;
break;
default: if( !m_filter.otherItems ) return false;
break; }
return true; }
void EE_SELECTION_TOOL::updateReferencePoint() { VECTOR2I refP( 0, 0 );
if( m_selection.Size() > 0 ) refP = static_cast<SCH_ITEM*>( m_selection.GetTopLeftItem() )->GetPosition();
m_selection.SetReferencePoint( refP ); }
// Some navigation actions are allowed in selectMultiple
const TOOL_ACTION* allowedActions[] = { &ACTIONS::panUp, &ACTIONS::panDown, &ACTIONS::panLeft, &ACTIONS::panRight, &ACTIONS::cursorUp, &ACTIONS::cursorDown, &ACTIONS::cursorLeft, &ACTIONS::cursorRight, &ACTIONS::cursorUpFast, &ACTIONS::cursorDownFast, &ACTIONS::cursorLeftFast, &ACTIONS::cursorRightFast, &ACTIONS::zoomIn, &ACTIONS::zoomOut, &ACTIONS::zoomInCenter, &ACTIONS::zoomOutCenter, &ACTIONS::zoomCenter, &ACTIONS::zoomFitScreen, &ACTIONS::zoomFitObjects, nullptr };
bool EE_SELECTION_TOOL::selectMultiple() { bool cancelled = false; // Was the tool canceled while it was running?
m_multiple = true; // Multiple selection mode is active
KIGFX::VIEW* view = getView();
KIGFX::PREVIEW::SELECTION_AREA area; view->Add( &area );
while( TOOL_EVENT* evt = Wait() ) { int width = area.GetEnd().x - area.GetOrigin().x; int height = area.GetEnd().y - area.GetOrigin().y;
/* Selection mode depends on direction of drag-selection:
* Left > Right : Select objects that are fully enclosed by selection * Right > Left : Select objects that are crossed by selection */ bool isGreedy = width < 0;
if( view->IsMirroredX() ) isGreedy = !isGreedy;
m_frame->GetCanvas()->SetCurrentCursor( isGreedy ? KICURSOR::SELECT_LASSO : KICURSOR::SELECT_WINDOW );
if( evt->IsCancelInteractive() || evt->IsActivate() ) { cancelled = true; break; }
if( evt->IsDrag( BUT_LEFT ) ) { if( !m_drag_additive && !m_drag_subtractive ) ClearSelection();
// Start drawing a selection box
area.SetOrigin( evt->DragOrigin() ); area.SetEnd( evt->Position() ); area.SetAdditive( m_drag_additive ); area.SetSubtractive( m_drag_subtractive ); area.SetExclusiveOr( false );
view->SetVisible( &area, true ); view->Update( &area ); getViewControls()->SetAutoPan( true ); }
if( evt->IsMouseUp( BUT_LEFT ) ) { getViewControls()->SetAutoPan( false );
// End drawing the selection box
view->SetVisible( &area, false );
// Fetch items from the RTree that are in our area of interest
std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> nearbyViewItems; view->Query( area.ViewBBox(), nearbyViewItems );
// Build lists of nearby items and their children
std::unordered_set<EDA_ITEM*> nearbyItems; std::vector<EDA_ITEM*> nearbyChildren; std::vector<EDA_ITEM*> flaggedItems;
for( KIGFX::VIEW::LAYER_ITEM_PAIR& pair : nearbyViewItems ) { if( EDA_ITEM* item = dynamic_cast<EDA_ITEM*>( pair.first ) ) { if( nearbyItems.insert( item ).second ) { item->ClearFlags( CANDIDATE );
if( SCH_ITEM* sch_item = dynamic_cast<SCH_ITEM*>( item ) ) { sch_item->RunOnChildren( [&]( SCH_ITEM* aChild ) { // Filter pins by unit
if( SCH_PIN* pin = dynamic_cast<SCH_PIN*>( aChild ) ) { int unit = pin->GetLibPin()->GetUnit();
if( unit && unit != pin->GetParentSymbol()->GetUnit() ) return; }
nearbyChildren.push_back( aChild ); } ); } } } }
BOX2I selectionRect( area.GetOrigin(), VECTOR2I( width, height ) ); selectionRect.Normalize();
bool anyAdded = false; bool anySubtracted = false;
auto selectItem = [&]( EDA_ITEM* aItem, EDA_ITEM_FLAGS flags ) { if( m_subtractive || ( m_exclusive_or && aItem->IsSelected() ) ) { if ( m_exclusive_or ) aItem->XorFlags( flags ); else aItem->ClearFlags( flags );
if( !aItem->HasFlag( STARTPOINT ) && !aItem->HasFlag( ENDPOINT ) ) { unselect( aItem ); anySubtracted = true; }
// We changed one line endpoint on a selected line,
// update the view at least.
if( flags && !anySubtracted ) getView()->Update( aItem ); } else { aItem->SetFlags( flags ); select( aItem ); anyAdded = true; } };
for( EDA_ITEM* item : nearbyItems ) { bool selected = false; EDA_ITEM_FLAGS flags = 0;
if( m_frame->GetRenderSettings()->m_ShowPinsElectricalType ) item->SetFlags( SHOW_ELEC_TYPE );
if( Selectable( item ) && itemPassesFilter( item ) ) { if( item->Type() == SCH_LINE_T ) { SCH_LINE* line = static_cast<SCH_LINE*>( item );
if( ( isGreedy && line->HitTest( selectionRect, false ) ) || ( selectionRect.Contains( line->GetEndPoint() ) && selectionRect.Contains( line->GetStartPoint() ) ) ) { selected = true; flags |= STARTPOINT | ENDPOINT; } else if( !isGreedy ) { if( selectionRect.Contains( line->GetStartPoint() ) && line->IsStartDangling() ) { selected = true; flags |= STARTPOINT; }
if( selectionRect.Contains( line->GetEndPoint() ) && line->IsEndDangling() ) { selected = true; flags |= ENDPOINT; } } } else { selected = item->HitTest( selectionRect, !isGreedy ); } }
if( selected ) { item->SetFlags( CANDIDATE ); flaggedItems.push_back( item ); selectItem( item, flags ); }
item->ClearFlags( SHOW_ELEC_TYPE ); }
for( EDA_ITEM* item : nearbyChildren ) { if( m_frame->GetRenderSettings()->m_ShowPinsElectricalType ) item->SetFlags( SHOW_ELEC_TYPE );
if( Selectable( item ) && itemPassesFilter( item ) && !item->GetParent()->HasFlag( CANDIDATE ) && item->HitTest( selectionRect, !isGreedy ) ) { selectItem( item, 0 ); }
item->ClearFlags( SHOW_ELEC_TYPE ); }
for( EDA_ITEM* item : flaggedItems ) item->ClearFlags( CANDIDATE );
m_selection.SetIsHover( false );
// Inform other potentially interested tools
if( anyAdded ) m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
if( anySubtracted ) m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
break; // Stop waiting for events
}
// Allow some actions for navigation
for( int i = 0; allowedActions[i]; ++i ) { if( evt->IsAction( allowedActions[i] ) ) { evt->SetPassEvent(); break; } } }
getViewControls()->SetAutoPan( false );
// Stop drawing the selection box
view->Remove( &area ); m_multiple = false; // Multiple selection mode is inactive
if( !cancelled ) m_selection.ClearReferencePoint();
return cancelled; }
bool EE_SELECTION_TOOL::selectTableCells( SCH_TABLE* aTable ) { bool cancelled = false; // Was the tool canceled while it was running?
m_multiple = true; // Multiple selection mode is active
for( SCH_TABLECELL* cell : aTable->GetCells() ) { if( cell->IsSelected() ) cell->SetFlags( CANDIDATE ); else cell->ClearFlags( CANDIDATE ); }
auto wasSelected = []( EDA_ITEM* aItem ) { return ( aItem->GetFlags() & CANDIDATE ) > 0; };
while( TOOL_EVENT* evt = Wait() ) { if( evt->IsCancelInteractive() || evt->IsActivate() ) { cancelled = true; break; } else if( evt->IsDrag( BUT_LEFT ) ) { getViewControls()->SetAutoPan( true );
BOX2I selectionRect( evt->DragOrigin(), evt->Position() - evt->DragOrigin() ); selectionRect.Normalize();
for( SCH_TABLECELL* cell : aTable->GetCells() ) { bool doSelect = false;
if( cell->HitTest( selectionRect, false ) ) { if( m_subtractive ) doSelect = false; else if( m_exclusive_or ) doSelect = !wasSelected( cell ); else doSelect = true; } else if( wasSelected( cell ) ) { doSelect = m_additive || m_subtractive || m_exclusive_or; }
if( doSelect && !cell->IsSelected() ) select( cell ); else if( !doSelect && cell->IsSelected() ) unselect( cell ); } } else if( evt->IsMouseUp( BUT_LEFT ) ) { m_selection.SetIsHover( false );
bool anyAdded = false; bool anySubtracted = false;
for( SCH_TABLECELL* cell : aTable->GetCells() ) { if( cell->IsSelected() && !wasSelected( cell ) ) anyAdded = true; else if( wasSelected( cell ) && !cell->IsSelected() ) anySubtracted = true; }
// Inform other potentially interested tools
if( anyAdded ) m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
if( anySubtracted ) m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
break; // Stop waiting for events
} else { // Allow some actions for navigation
for( int i = 0; allowedActions[i]; ++i ) { if( evt->IsAction( allowedActions[i] ) ) { evt->SetPassEvent(); break; } } } }
getViewControls()->SetAutoPan( false );
m_multiple = false; // Multiple selection mode is inactive
if( !cancelled ) m_selection.ClearReferencePoint();
return cancelled; }
EDA_ITEM* EE_SELECTION_TOOL::GetNode( const VECTOR2I& aPosition ) { EE_COLLECTOR collector;
//TODO(snh): Reimplement after exposing KNN interface
int pixelThreshold = KiROUND( getView()->ToWorld( HITTEST_THRESHOLD_PIXELS ) ); int gridThreshold = KiROUND( getView()->GetGAL()->GetGridSize().EuclideanNorm() ); int thresholdMax = std::max( pixelThreshold, gridThreshold );
for( int threshold : { 0, thresholdMax/4, thresholdMax/2, thresholdMax } ) { collector.m_Threshold = threshold; collector.Collect( m_frame->GetScreen(), connectedTypes, aPosition );
if( collector.GetCount() > 0 ) break; }
return collector.GetCount() ? collector[ 0 ] : nullptr; }
int EE_SELECTION_TOOL::SelectNode( const TOOL_EVENT& aEvent ) { VECTOR2I cursorPos = getViewControls()->GetCursorPosition( false );
SelectPoint( cursorPos, connectedTypes ); return 0; }
int EE_SELECTION_TOOL::SelectConnection( const TOOL_EVENT& aEvent ) { RequestSelection( { SCH_ITEM_LOCATE_WIRE_T, SCH_ITEM_LOCATE_BUS_T, SCH_ITEM_LOCATE_GRAPHIC_LINE_T } );
if( m_selection.Empty() ) return 0;
unsigned done = false;
m_frame->GetScreen()->ClearDrawingState();
for( EDA_ITEM* selItem : m_selection.GetItems() ) { if( selItem->Type() != SCH_LINE_T ) continue;
SCH_LINE* line = static_cast<SCH_LINE*>( selItem );
std::set<SCH_ITEM*> conns = m_frame->GetScreen()->MarkConnections( line, false ); for( SCH_ITEM* item : conns ) { if( item->IsType( { SCH_ITEM_LOCATE_WIRE_T, SCH_ITEM_LOCATE_BUS_T, SCH_ITEM_LOCATE_GRAPHIC_LINE_T } ) && !item->IsSelected() ) { done = true; }
select( item ); }
if( !done ) { conns = m_frame->GetScreen()->MarkConnections( line, true );
for( SCH_ITEM* item : conns ) select( item ); } }
if( m_selection.GetSize() > 1 ) m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
return 0; }
int EE_SELECTION_TOOL::SelectColumns( const TOOL_EVENT& aEvent ) { std::set<std::pair<SCH_TABLE*, int>> columns; bool added = false;
for( EDA_ITEM* item : m_selection ) { if( SCH_TABLECELL* cell = dynamic_cast<SCH_TABLECELL*>( item ) ) { SCH_TABLE* table = static_cast<SCH_TABLE*>( cell->GetParent() ); columns.insert( std::make_pair( table, cell->GetColumn() ) ); } }
for( auto& [ table, col ] : columns ) { for( int row = 0; row < table->GetRowCount(); ++row ) { SCH_TABLECELL* cell = table->GetCell( row, col );
if( !cell->IsSelected() ) { select( table->GetCell( row, col ) ); added = true; } } }
if( added ) m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
return 0; }
int EE_SELECTION_TOOL::SelectRows( const TOOL_EVENT& aEvent ) { std::set<std::pair<SCH_TABLE*, int>> rows; bool added = false;
for( EDA_ITEM* item : m_selection ) { if( SCH_TABLECELL* cell = dynamic_cast<SCH_TABLECELL*>( item ) ) { SCH_TABLE* table = static_cast<SCH_TABLE*>( cell->GetParent() ); rows.insert( std::make_pair( table, cell->GetRow() ) ); } }
for( auto& [ table, row ] : rows ) { for( int col = 0; col < table->GetColCount(); ++col ) { SCH_TABLECELL* cell = table->GetCell( row, col );
if( !cell->IsSelected() ) { select( table->GetCell( row, col ) ); added = true; } } }
if( added ) m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
return 0; }
int EE_SELECTION_TOOL::SelectTable( const TOOL_EVENT& aEvent ) { std::set<SCH_TABLE*> tables; bool added = false;
for( EDA_ITEM* item : m_selection ) { if( SCH_TABLECELL* cell = dynamic_cast<SCH_TABLECELL*>( item ) ) tables.insert( static_cast<SCH_TABLE*>( cell->GetParent() ) ); }
ClearSelection();
for( SCH_TABLE* table : tables ) { if( !table->IsSelected() ) { select( table ); added = true; } }
if( added ) m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
return 0; }
int EE_SELECTION_TOOL::ClearSelection( const TOOL_EVENT& aEvent ) { ClearSelection(); return 0; }
void EE_SELECTION_TOOL::ZoomFitCrossProbeBBox( const BOX2I& aBBox ) { if( aBBox.GetWidth() == 0 ) return;
BOX2I bbox = aBBox; bbox.Normalize();
VECTOR2I bbSize = bbox.Inflate( KiROUND( bbox.GetWidth() * 0.2f ) ).GetSize(); VECTOR2D screenSize = getView()->GetViewport().GetSize();
// This code tries to come up with a zoom factor that doesn't simply zoom in to the cross
// probed symbol, but instead shows a reasonable amount of the circuit around it to provide
// context. This reduces the need to manually change the zoom because it's too close.
// Using the default text height as a constant to compare against, use the height of the
// bounding box of visible items for a footprint to figure out if this is a big symbol (like
// a processor) or a small symbol (like a resistor). This ratio is not useful by itself as a
// scaling factor. It must be "bent" to provide good scaling at varying symbol sizes. Bigger
// symbols need less scaling than small ones.
double currTextHeight = schIUScale.MilsToIU( DEFAULT_TEXT_SIZE );
double compRatio = bbSize.y / currTextHeight; // Ratio of symbol to text height
double compRatioBent = 1.0;
// LUT to scale zoom ratio to provide reasonable schematic context. Must work with symbols
// of varying sizes (e.g. 0402 package and 200 pin BGA).
// Each entry represents a compRatio (symbol height / default text height) and an amount to
// scale by.
std::vector<std::pair<double, double>> lut{ { 1.25, 16 }, { 2.5, 12 }, { 5, 8 }, { 6, 6 }, { 10, 4 }, { 20, 2 }, { 40, 1.5 }, { 100, 1 } };
std::vector<std::pair<double, double>>::iterator it;
// Large symbol default is last LUT entry (1:1).
compRatioBent = lut.back().second;
// Use LUT to do linear interpolation of "compRatio" within "first", then use that result to
// linearly interpolate "second" which gives the scaling factor needed.
if( compRatio >= lut.front().first ) { for( it = lut.begin(); it < lut.end() - 1; ++it ) { if( it->first <= compRatio && next( it )->first >= compRatio ) { double diffx = compRatio - it->first; double diffn = next( it )->first - it->first;
compRatioBent = it->second + ( next( it )->second - it->second ) * diffx / diffn; break; // We have our interpolated value
} } } else { compRatioBent = lut.front().second; // Small symbol default is first entry
}
// This is similar to the original KiCad code that scaled the zoom to make sure symbols were
// visible on screen. It's simply a ratio of screen size to symbol size, and its job is to
// zoom in to make the component fullscreen. Earlier in the code the symbol BBox is given a
// 20% margin to add some breathing room. We compare the height of this enlarged symbol bbox
// to the default text height. If a symbol will end up with the sides clipped, we adjust
// later to make sure it fits on screen.
screenSize.x = std::max( 10.0, screenSize.x ); screenSize.y = std::max( 10.0, screenSize.y ); double ratio = std::max( -1.0, fabs( bbSize.y / screenSize.y ) );
// Original KiCad code for how much to scale the zoom
double kicadRatio = std::max( fabs( bbSize.x / screenSize.x ), fabs( bbSize.y / screenSize.y ) );
// If the width of the part we're probing is bigger than what the screen width will be after
// the zoom, then punt and use the KiCad zoom algorithm since it guarantees the part's width
// will be encompassed within the screen.
if( bbSize.x > screenSize.x * ratio * compRatioBent ) { // Use standard KiCad zoom for parts too wide to fit on screen/
ratio = kicadRatio; compRatioBent = 1.0; // Reset so we don't modify the "KiCad" ratio
wxLogTrace( "CROSS_PROBE_SCALE", "Part TOO WIDE for screen. Using normal KiCad zoom ratio: %1.5f", ratio ); }
// Now that "compRatioBent" holds our final scaling factor we apply it to the original
// fullscreen zoom ratio to arrive at the final ratio itself.
ratio *= compRatioBent;
bool alwaysZoom = false; // DEBUG - allows us to minimize zooming or not
// Try not to zoom on every cross-probe; it gets very noisy
if( ( ratio < 0.5 || ratio > 1.0 ) || alwaysZoom ) getView()->SetScale( getView()->GetScale() / ratio ); }
void EE_SELECTION_TOOL::SyncSelection( const std::optional<SCH_SHEET_PATH>& targetSheetPath, SCH_ITEM* focusItem, const std::vector<SCH_ITEM*>& items ) { SCH_EDIT_FRAME* editFrame = dynamic_cast<SCH_EDIT_FRAME*>( m_frame );
if( !editFrame ) return;
if( targetSheetPath && targetSheetPath != editFrame->Schematic().CurrentSheet() ) { editFrame->Schematic().SetCurrentSheet( *targetSheetPath ); editFrame->DisplayCurrentSheet(); }
ClearSelection( items.size() > 0 ? true /*quiet mode*/ : false );
// Perform individual selection of each item before processing the event.
for( SCH_ITEM* item : items ) { SCH_ITEM* parent = dynamic_cast<SCH_ITEM*>( item->GetParent() );
// Make sure we only select items on the current screen
if( m_frame->GetScreen()->CheckIfOnDrawList( item ) || ( parent && m_frame->GetScreen()->CheckIfOnDrawList( parent ) ) ) { select( item ); } }
BOX2I bbox = m_selection.GetBoundingBox();
if( bbox.GetWidth() != 0 && bbox.GetHeight() != 0 ) { if( m_frame->eeconfig()->m_CrossProbing.center_on_items ) { if( m_frame->eeconfig()->m_CrossProbing.zoom_to_fit ) ZoomFitCrossProbeBBox( bbox );
editFrame->FocusOnItem( focusItem );
if( !focusItem ) editFrame->FocusOnLocation( bbox.Centre() ); } }
if( m_selection.Size() > 0 ) m_toolMgr->ProcessEvent( EVENTS::SelectedEvent ); }
void EE_SELECTION_TOOL::RebuildSelection() { m_selection.Clear();
if( m_isSymbolEditor ) { LIB_SYMBOL* start = static_cast<SYMBOL_EDIT_FRAME*>( m_frame )->GetCurSymbol();
for( SCH_ITEM& item : start->GetDrawItems() ) { if( item.IsSelected() ) select( &item ); } } else { for( SCH_ITEM* item : m_frame->GetScreen()->Items() ) { // If the field and symbol are selected, only use the symbol
if( item->IsSelected() ) { select( item ); } else { item->RunOnChildren( [&]( SCH_ITEM* aChild ) { if( aChild->IsSelected() ) select( aChild ); } ); } } }
updateReferencePoint();
// Inform other potentially interested tools
m_toolMgr->ProcessEvent( EVENTS::SelectedEvent ); }
bool EE_SELECTION_TOOL::Selectable( const EDA_ITEM* aItem, const VECTOR2I* aPos, bool checkVisibilityOnly ) const { // NOTE: in the future this is where Eeschema layer/itemtype visibility will be handled
SYMBOL_EDIT_FRAME* symEditFrame = dynamic_cast<SYMBOL_EDIT_FRAME*>( m_frame );
// Do not allow selection of anything except fields when the current symbol in the symbol
// editor is a derived symbol.
if( symEditFrame && symEditFrame->IsSymbolAlias() && aItem->Type() != SCH_FIELD_T ) return false;
switch( aItem->Type() ) { case SCH_PIN_T: { const SCH_PIN* pin = static_cast<const SCH_PIN*>( aItem );
if( symEditFrame ) { if( pin->GetUnit() && pin->GetUnit() != symEditFrame->GetUnit() ) return false;
if( pin->GetBodyStyle() && pin->GetBodyStyle() != symEditFrame->GetBodyStyle() ) return false; }
if( !pin->IsVisible() && !m_frame->GetShowAllPins() ) return false;
if( !m_filter.pins ) { // Pin anchors have to be allowed for auto-starting wires.
if( aPos ) { EE_GRID_HELPER grid( m_toolMgr ); GRID_HELPER_GRIDS pinGrid = grid.GetItemGrid( pin );
if( pin->IsPointClickableAnchor( grid.BestSnapAnchor( *aPos, pinGrid ) ) ) return true; }
return false; }
break; }
case SCH_DIRECTIVE_LABEL_T: if( !m_frame->eeconfig()->m_Appearance.show_directive_labels ) return false;
break;
case LIB_SYMBOL_T: // In symbol_editor we do not want to select the symbol itself.
return false;
case SCH_FIELD_T: // SCH_FIELD objects are not unit/body-style-specific.
{ const SCH_FIELD* field = static_cast<const SCH_FIELD*>( aItem );
if( !field->IsVisible() && !( symEditFrame && symEditFrame->GetShowInvisibleFields() ) ) return false;
break; }
case SCH_SHAPE_T: case SCH_TEXT_T: case SCH_TEXTBOX_T: if( symEditFrame ) { const SCH_ITEM* sch_item = static_cast<const SCH_ITEM*>( aItem );
if( sch_item->GetUnit() && sch_item->GetUnit() != symEditFrame->GetUnit() ) return false;
if( sch_item->GetBodyStyle() && sch_item->GetBodyStyle() != symEditFrame->GetBodyStyle() ) return false; }
break;
case SCH_MARKER_T: // Always selectable
return true;
case SCH_TABLECELL_T: { const SCH_TABLECELL* cell = static_cast<const SCH_TABLECELL*>( aItem );
if( cell->GetColSpan() == 0 || cell->GetRowSpan() == 0 ) return false;
break; }
default: // Suppress warnings
break; }
return true; }
void EE_SELECTION_TOOL::ClearSelection( bool aQuietMode ) { if( m_selection.Empty() ) return;
while( m_selection.GetSize() ) unhighlight( m_selection.Front(), SELECTED, &m_selection );
getView()->Update( &m_selection );
m_selection.SetIsHover( false ); m_selection.ClearReferencePoint();
// Inform other potentially interested tools
if( !aQuietMode ) m_toolMgr->ProcessEvent( EVENTS::ClearedEvent ); }
void EE_SELECTION_TOOL::select( EDA_ITEM* aItem ) { highlight( aItem, SELECTED, &m_selection ); }
void EE_SELECTION_TOOL::unselect( EDA_ITEM* aItem ) { unhighlight( aItem, SELECTED, &m_selection ); }
void EE_SELECTION_TOOL::highlight( EDA_ITEM* aItem, int aMode, SELECTION* aGroup ) { if( aMode == SELECTED ) aItem->SetSelected(); else if( aMode == BRIGHTENED ) aItem->SetBrightened();
if( aGroup ) aGroup->Add( aItem );
// Highlight pins and fields. (All the other symbol children are currently only
// represented in the LIB_SYMBOL and will inherit the settings of the parent symbol.)
if( SCH_ITEM* sch_item = dynamic_cast<SCH_ITEM*>( aItem ) ) { sch_item->RunOnChildren( [&]( SCH_ITEM* aChild ) { if( aMode == SELECTED ) { aChild->SetSelected(); getView()->Hide( aChild, true ); } else if( aMode == BRIGHTENED ) { aChild->SetBrightened(); } } ); }
if( aGroup && aMode != BRIGHTENED ) getView()->Hide( aItem, true );
if( aItem->GetParent() && aItem->GetParent()->Type() != SCHEMATIC_T ) getView()->Update( aItem->GetParent(), KIGFX::REPAINT );
getView()->Update( aItem, KIGFX::REPAINT ); }
void EE_SELECTION_TOOL::unhighlight( EDA_ITEM* aItem, int aMode, SELECTION* aGroup ) { if( aMode == SELECTED ) { aItem->ClearSelected(); // Lines need endpoints cleared here
if( aItem->Type() == SCH_LINE_T ) aItem->ClearFlags( STARTPOINT | ENDPOINT );
if( aMode != BRIGHTENED ) getView()->Hide( aItem, false ); } else if( aMode == BRIGHTENED ) { aItem->ClearBrightened(); }
if( aGroup ) aGroup->Remove( aItem );
// Unhighlight pins and fields. (All the other symbol children are currently only
// represented in the LIB_SYMBOL.)
if( SCH_ITEM* sch_item = dynamic_cast<SCH_ITEM*>( aItem ) ) { sch_item->RunOnChildren( [&]( SCH_ITEM* aChild ) { if( aMode == SELECTED ) { aChild->ClearSelected(); getView()->Hide( aChild, false ); } else if( aMode == BRIGHTENED ) { aChild->ClearBrightened(); }
if( aGroup ) aGroup->Remove( aChild ); } ); }
if( aItem->GetParent() && aItem->GetParent()->Type() != SCHEMATIC_T ) getView()->Update( aItem->GetParent(), KIGFX::REPAINT );
getView()->Update( aItem, KIGFX::REPAINT ); }
bool EE_SELECTION_TOOL::selectionContains( const VECTOR2I& aPoint ) const { const unsigned GRIP_MARGIN = 20; int margin = KiROUND( getView()->ToWorld( GRIP_MARGIN ) );
// Check if the point is located within any of the currently selected items bounding boxes
for( EDA_ITEM* item : m_selection ) { BOX2I itemBox = item->ViewBBox(); itemBox.Inflate( margin ); // Give some margin for gripping an item
if( itemBox.Contains( aPoint ) ) return true; }
return false; }
void EE_SELECTION_TOOL::setTransitions() { Go( &EE_SELECTION_TOOL::UpdateMenu, ACTIONS::updateMenu.MakeEvent() );
Go( &EE_SELECTION_TOOL::Main, EE_ACTIONS::selectionActivate.MakeEvent() ); Go( &EE_SELECTION_TOOL::SelectNode, EE_ACTIONS::selectNode.MakeEvent() ); Go( &EE_SELECTION_TOOL::SelectConnection, EE_ACTIONS::selectConnection.MakeEvent() ); Go( &EE_SELECTION_TOOL::SelectColumns, ACTIONS::selectColumns.MakeEvent() ); Go( &EE_SELECTION_TOOL::SelectRows, ACTIONS::selectRows.MakeEvent() ); Go( &EE_SELECTION_TOOL::SelectTable, ACTIONS::selectTable.MakeEvent() );
Go( &EE_SELECTION_TOOL::ClearSelection, EE_ACTIONS::clearSelection.MakeEvent() );
Go( &EE_SELECTION_TOOL::AddItemToSel, EE_ACTIONS::addItemToSel.MakeEvent() ); Go( &EE_SELECTION_TOOL::AddItemsToSel, EE_ACTIONS::addItemsToSel.MakeEvent() ); Go( &EE_SELECTION_TOOL::RemoveItemFromSel, EE_ACTIONS::removeItemFromSel.MakeEvent() ); Go( &EE_SELECTION_TOOL::RemoveItemsFromSel, EE_ACTIONS::removeItemsFromSel.MakeEvent() ); Go( &EE_SELECTION_TOOL::SelectionMenu, EE_ACTIONS::selectionMenu.MakeEvent() );
Go( &EE_SELECTION_TOOL::SelectAll, EE_ACTIONS::selectAll.MakeEvent() ); Go( &EE_SELECTION_TOOL::UnselectAll, EE_ACTIONS::unselectAll.MakeEvent() );
Go( &EE_SELECTION_TOOL::disambiguateCursor, EVENTS::DisambiguatePoint ); }
|