ADDED: Lasso support to Schematic Editor

This commit is contained in:
Seth Hillbrand 2025-09-03 06:45:49 -07:00
parent 876c905e2b
commit 173e02eff7
25 changed files with 555 additions and 229 deletions

View File

@ -27,6 +27,7 @@
#include <core/mirror.h>
#include <schematic.h>
#include <geometry/shape_segment.h>
#include <geometry/geometry_utils.h>
#include <sch_bus_entry.h>
#include <sch_edit_frame.h>
#include <sch_junction.h>
@ -461,6 +462,13 @@ bool SCH_BUS_ENTRY_BASE::HitTest( const BOX2I& aRect, bool aContained, int aAccu
}
bool SCH_BUS_ENTRY_BASE::HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
{
SHAPE_SEGMENT line( m_pos, GetEnd(), GetPenWidth() );
return KIGEOM::ShapeHitTest( aPoly, line, aContained );
}
void SCH_BUS_ENTRY_BASE::Plot( PLOTTER* aPlotter, bool aBackground, const SCH_PLOT_OPTS& aPlotOpts,
int aUnit, int aBodyStyle, const VECTOR2I& aOffset, bool aDimmed )
{

View File

@ -128,6 +128,7 @@ public:
}
bool HitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const override;
bool HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const override;
void Plot( PLOTTER* aPlotter, bool aBackground, const SCH_PLOT_OPTS& aPlotOpts,
int aUnit, int aBodyStyle, const VECTOR2I& aOffset, bool aDimmed ) override;

View File

@ -35,6 +35,7 @@
#include <symbol_library.h>
#include <settings/color_settings.h>
#include <string_utils.h>
#include <geometry/geometry_utils.h>
#include <trace_helpers.h>
#include <tool/tool_manager.h>
#include <tools/sch_navigate_tool.h>
@ -1199,6 +1200,26 @@ bool SCH_FIELD::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) co
}
bool SCH_FIELD::HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
{
if( GetShownText( true ).IsEmpty() )
return false;
if( m_flags & (STRUCT_DELETED | SKIP_STRUCT ) )
return false;
BOX2I bbox = GetBoundingBox();
if( GetParent() && GetParent()->Type() == SCH_GLOBAL_LABEL_T )
{
SCH_GLOBALLABEL* label = static_cast<SCH_GLOBALLABEL*>( GetParent() );
bbox.Offset( label->GetSchematicTextOffset( nullptr ) );
}
return KIGEOM::BoxHitTest( aPoly, bbox, aContained );
}
void SCH_FIELD::Plot( PLOTTER* aPlotter, bool aBackground, const SCH_PLOT_OPTS& aPlotOpts,
int aUnit, int aBodyStyle, const VECTOR2I& aOffset, bool aDimmed )
{

View File

@ -266,6 +266,7 @@ public:
bool HitTest( const VECTOR2I& aPosition, int aAccuracy = 0 ) const override;
bool HitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const override;
bool HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const override;
void Plot( PLOTTER* aPlotter, bool aBackground, const SCH_PLOT_OPTS& aPlotOpts,
int aUnit, int aBodyStyle, const VECTOR2I& aOffset, bool aDimmed ) override;

View File

@ -29,6 +29,7 @@
#include <bitmaps.h>
#include <core/mirror.h>
#include <geometry/shape_rect.h>
#include <geometry/geometry_utils.h>
#include <sch_painter.h>
#include <sch_junction.h>
#include <sch_edit_frame.h>
@ -213,6 +214,15 @@ bool SCH_JUNCTION::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy )
}
bool SCH_JUNCTION::HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
{
if( m_flags & STRUCT_DELETED || m_flags & SKIP_STRUCT )
return false;
return KIGEOM::ShapeHitTest( aPoly, getEffectiveShape(), aContained );
}
bool SCH_JUNCTION::HasConnectivityChanges( const SCH_ITEM* aItem,
const SCH_SHEET_PATH* aInstance ) const
{

View File

@ -121,6 +121,7 @@ public:
bool HitTest( const VECTOR2I& aPosition, int aAccuracy = 0 ) const override;
bool HitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const override;
bool HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const override;
void Plot( PLOTTER* aPlotter, bool aBackground, const SCH_PLOT_OPTS& aPlotOpts,
int aUnit, int aBodyStyle, const VECTOR2I& aOffset, bool aDimmed ) override;

View File

@ -32,6 +32,7 @@
#include <widgets/msgpanel.h>
#include <bitmaps.h>
#include <string_utils.h>
#include <geometry/geometry_utils.h>
#include <schematic.h>
#include <settings/color_settings.h>
#include <sch_painter.h>
@ -1135,6 +1136,36 @@ bool SCH_LABEL_BASE::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy
}
bool SCH_LABEL_BASE::HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
{
if( aContained )
{
return KIGEOM::BoxHitTest( aPoly, GetBoundingBox(), aContained );
}
else
{
if( KIGEOM::BoxHitTest( aPoly, GetBodyBoundingBox( nullptr ), aContained ) )
return true;
for( const SCH_FIELD& field : m_fields )
{
if( field.IsVisible() )
{
BOX2I fieldBBox = field.GetBoundingBox();
if( Type() == SCH_LABEL_T || Type() == SCH_GLOBAL_LABEL_T )
fieldBBox.Offset( GetSchematicTextOffset( nullptr ) );
if( KIGEOM::BoxHitTest( aPoly, fieldBBox, aContained ) )
return true;
}
}
return false;
}
}
bool SCH_LABEL_BASE::UpdateDanglingState( std::vector<DANGLING_END_ITEM>& aItemListByType,
std::vector<DANGLING_END_ITEM>& aItemListByPos,
const SCH_SHEET_PATH* aPath )

View File

@ -317,6 +317,7 @@ public:
bool HitTest( const VECTOR2I& aPosition, int aAccuracy = 0 ) const override;
bool HitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const override;
bool HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const override;
std::vector<VECTOR2I> GetConnectionPoints() const override;

View File

@ -29,6 +29,7 @@
#include <sch_painter.h>
#include <sch_plotter.h>
#include <geometry/shape_segment.h>
#include <geometry/geometry_utils.h>
#include <sch_line.h>
#include <sch_edit_frame.h>
#include <settings/color_settings.h>
@ -814,6 +815,16 @@ bool SCH_LINE::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) con
}
bool SCH_LINE::HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
{
if( m_flags & (STRUCT_DELETED | SKIP_STRUCT ) )
return false;
SHAPE_SEGMENT line( m_start, m_end, GetPenWidth() );
return KIGEOM::ShapeHitTest( aPoly, line, aContained );
}
void SCH_LINE::swapData( SCH_ITEM* aItem )
{
SCH_LINE* item = (SCH_LINE*) aItem;

View File

@ -326,6 +326,7 @@ public:
bool HitTest( const VECTOR2I& aPosition, int aAccuracy = 0 ) const override;
bool HitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const override;
bool HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const override;
void Plot( PLOTTER* aPlotter, bool aBackground, const SCH_PLOT_OPTS& aPlotOpts,
int aUnit, int aBodyStyle, const VECTOR2I& aOffset, bool aDimmed ) override;

View File

@ -31,6 +31,7 @@
#include <plotters/plotter.h>
#include <bitmaps.h>
#include <schematic.h>
#include <geometry/geometry_utils.h>
#include <sch_no_connect.h>
#include <settings/color_settings.h>
#include <default_values.h> // For some default values
@ -171,6 +172,12 @@ bool SCH_NO_CONNECT::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy
}
bool SCH_NO_CONNECT::HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
{
return KIGEOM::BoxHitTest( aPoly, GetBoundingBox(), aContained );
}
void SCH_NO_CONNECT::Plot( PLOTTER* aPlotter, bool aBackground, const SCH_PLOT_OPTS& aPlotOpts,
int aUnit, int aBodyStyle, const VECTOR2I& aOffset, bool aDimmed )
{

View File

@ -103,6 +103,7 @@ public:
bool HitTest( const VECTOR2I& aPosition, int aAccuracy = 0 ) const override;
bool HitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const override;
bool HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const override;
void Plot( PLOTTER* aPlotter, bool aBackground, const SCH_PLOT_OPTS& aPlotOpts,
int aUnit, int aBodyStyle, const VECTOR2I& aOffset, bool aDimmed ) override;

View File

@ -30,6 +30,7 @@
#include <bitmaps.h>
#include <eda_draw_frame.h>
#include <gr_basic.h>
#include <geometry/geometry_utils.h>
#include <schematic.h>
#include <sch_shape.h>
@ -134,6 +135,32 @@ bool SCH_SHAPE::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) co
}
bool SCH_SHAPE::HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
{
if( m_flags & (STRUCT_DELETED | SKIP_STRUCT ) )
return false;
std::vector<SHAPE*> shapes = MakeEffectiveShapes( false );
for( SHAPE* shape : shapes )
{
bool hit = KIGEOM::ShapeHitTest( aPoly, *shape, aContained );
if( hit )
{
for( SHAPE* s : shapes )
delete s;
return true;
}
}
for( SHAPE* shape : shapes )
delete shape;
return false;
}
bool SCH_SHAPE::IsEndPoint( const VECTOR2I& aPt ) const
{
SHAPE_T shape = GetShape();

View File

@ -48,6 +48,7 @@ public:
bool HitTest( const VECTOR2I& aPosition, int aAccuracy = 0 ) const override;
bool HitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const override;
bool HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const override;
bool IsEndPoint( const VECTOR2I& aPoint ) const override;

View File

@ -36,6 +36,7 @@
#include <string_utils.h>
#include <widgets/msgpanel.h>
#include <math/util.h> // for KiROUND
#include <geometry/geometry_utils.h>
#include <sch_sheet.h>
#include <sch_sheet_path.h>
#include <sch_sheet_pin.h>
@ -1203,6 +1204,12 @@ bool SCH_SHEET::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) co
}
bool SCH_SHEET::HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
{
return KIGEOM::BoxHitTest( aPoly, GetBodyBoundingBox(), aContained );
}
void SCH_SHEET::Plot( PLOTTER* aPlotter, bool aBackground, const SCH_PLOT_OPTS& aPlotOpts,
int aUnit, int aBodyStyle, const VECTOR2I& aOffset, bool aDimmed )
{

View File

@ -417,6 +417,7 @@ public:
bool HitTest( const VECTOR2I& aPosition, int aAccuracy ) const override;
bool HitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const override;
bool HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const override;
void Plot( PLOTTER* aPlotter, bool aBackground, const SCH_PLOT_OPTS& aPlotOpts,
int aUnit, int aBodyStyle, const VECTOR2I& aOffset, bool aDimmed ) override;

View File

@ -40,6 +40,7 @@
#include <settings/settings_manager.h>
#include <sch_plotter.h>
#include <string_utils.h>
#include <geometry/geometry_utils.h>
#include <sch_rule_area.h>
#include <utility>
@ -2476,6 +2477,15 @@ bool SCH_SYMBOL::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) c
}
bool SCH_SYMBOL::HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
{
if( m_flags & STRUCT_DELETED || m_flags & SKIP_STRUCT )
return false;
return KIGEOM::BoxHitTest( aPoly, GetBodyBoundingBox(), aContained );
}
bool SCH_SYMBOL::doIsConnected( const VECTOR2I& aPosition ) const
{
VECTOR2I new_pos = m_transform.InverseTransform().TransformCoordinate( aPosition - m_pos );

View File

@ -777,6 +777,7 @@ public:
bool HitTest( const VECTOR2I& aPosition, int aAccuracy = 0 ) const override;
bool HitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const override;
bool HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const override;
void Plot( PLOTTER* aPlotter, bool aBackground, const SCH_PLOT_OPTS& aPlotOpts,
int aUnit, int aBodyStyle, const VECTOR2I& aOffset, bool aDimmed ) override;

View File

@ -31,6 +31,7 @@
#include <widgets/msgpanel.h>
#include <bitmaps.h>
#include <string_utils.h>
#include <geometry/geometry_utils.h>
#include <sch_text.h>
#include <schematic.h>
#include <settings/color_settings.h>
@ -406,6 +407,15 @@ bool SCH_TEXT::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) con
}
bool SCH_TEXT::HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
{
if( m_flags & (STRUCT_DELETED | SKIP_STRUCT ) )
return false;
return KIGEOM::BoxHitTest( aPoly, GetBoundingBox(), aContained );
}
void SCH_TEXT::BeginEdit( const VECTOR2I& aPosition )
{
SetTextPos( aPosition );

View File

@ -146,6 +146,7 @@ public:
bool HitTest( const VECTOR2I& aPosition, int aAccuracy = 0 ) const override;
bool HitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const override;
bool HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const override;
void Plot( PLOTTER* aPlotter, bool aBackground, const SCH_PLOT_OPTS& aPlotOpts,
int aUnit, int aBodyStyle, const VECTOR2I& aOffset, bool aDimmed ) override;

View File

@ -36,6 +36,7 @@
#include <dialogs/html_message_box.h>
#include <project/project_file.h>
#include <trigo.h>
#include <geometry/geometry_utils.h>
#include <sch_textbox.h>
#include <tools/sch_navigate_tool.h>
@ -334,6 +335,12 @@ bool SCH_TEXTBOX::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy )
}
bool SCH_TEXTBOX::HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
{
return KIGEOM::BoxHitTest( aPoly, GetBoundingBox(), aContained );
}
bool SCH_TEXTBOX::IsHypertext() const
{
if( HasHyperlink() )

View File

@ -108,6 +108,7 @@ public:
bool HitTest( const VECTOR2I& aPosition, int aAccuracy = 0 ) const override;
bool HitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const override;
bool HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const override;
bool Matches( const EDA_SEARCH_DATA& aSearchData, void* aAuxData ) const override
{

View File

@ -93,7 +93,9 @@ std::optional<TOOLBAR_CONFIGURATION> SCH_EDIT_TOOLBAR_SETTINGS::DefaultToolbarCo
break;
case TOOLBAR_LOC::RIGHT:
config.AppendAction( ACTIONS::selectionTool )
config.AppendGroup( TOOLBAR_GROUP_CONFIG( _( "Selection modes" ) )
.AddAction( ACTIONS::selectSetRect )
.AddAction( ACTIONS::selectSetLasso ) )
.AppendAction( SCH_ACTIONS::highlightNetTool );
config.AppendSeparator()

View File

@ -35,7 +35,9 @@
#include <symbol_edit_frame.h>
#include <symbol_viewer_frame.h>
#include <math/util.h>
#include <geometry/geometry_utils.h>
#include <geometry/shape_rect.h>
#include <geometry/shape_line_chain.h>
#include <sch_painter.h>
#include <preview_items/selection_area.h>
#include <sch_commit.h>
@ -150,6 +152,19 @@ SELECTION_CONDITION SCH_CONDITIONS::AllPinsOrSheetPins = []( const SELECTION& aS
};
static void passEvent( TOOL_EVENT* const aEvent, const TOOL_ACTION* const aAllowedActions[] )
{
for( int i = 0; aAllowedActions[i]; ++i )
{
if( aEvent->IsAction( aAllowedActions[i] ) )
{
aEvent->SetPassEvent();
break;
}
}
}
#define HITTEST_THRESHOLD_PIXELS 5
@ -162,6 +177,7 @@ SCH_SELECTION_TOOL::SCH_SELECTION_TOOL() :
m_unit( 0 ),
m_bodyStyle( 0 ),
m_enteredGroup( nullptr ),
m_selectionMode( SELECTION_MODE::INSIDE_RECTANGLE ),
m_previous_first_cell( nullptr )
{
m_filter.SetDefaults();
@ -674,11 +690,19 @@ int SCH_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
}
else if( hasModifier() || drag_action == MOUSE_DRAG_ACTION::SELECT )
{
selectMultiple();
if( m_selectionMode == SELECTION_MODE::INSIDE_LASSO
|| m_selectionMode == SELECTION_MODE::TOUCHING_LASSO )
selectLasso();
else
selectMultiple();
}
else if( m_selection.Empty() && drag_action != MOUSE_DRAG_ACTION::DRAG_ANY )
{
selectMultiple();
if( m_selectionMode == SELECTION_MODE::INSIDE_LASSO
|| m_selectionMode == SELECTION_MODE::TOUCHING_LASSO )
selectLasso();
else
selectMultiple();
}
else
{
@ -715,7 +739,11 @@ int SCH_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
else
{
// No -> drag a selection box
selectMultiple();
if( m_selectionMode == SELECTION_MODE::INSIDE_LASSO
|| m_selectionMode == SELECTION_MODE::TOUCHING_LASSO )
selectLasso();
else
selectMultiple();
}
}
}
@ -2140,6 +2168,24 @@ void SCH_SELECTION_TOOL::updateReferencePoint()
}
int SCH_SELECTION_TOOL::SetSelectPoly( const TOOL_EVENT& aEvent )
{
m_selectionMode = SELECTION_MODE::INSIDE_LASSO;
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::SELECT_LASSO );
m_toolMgr->PostAction( ACTIONS::selectionTool );
return 0;
}
int SCH_SELECTION_TOOL::SetSelectRect( const TOOL_EVENT& aEvent )
{
m_selectionMode = SELECTION_MODE::INSIDE_RECTANGLE;
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
m_toolMgr->PostAction( ACTIONS::selectionTool );
return 0;
}
// Some navigation actions are allowed in selectMultiple
const TOOL_ACTION* allowedActions[] = { &ACTIONS::panUp, &ACTIONS::panDown,
&ACTIONS::panLeft, &ACTIONS::panRight,
@ -2209,234 +2255,13 @@ bool SCH_SELECTION_TOOL::selectMultiple()
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> candidates;
BOX2I selectionRect = area.ViewBBox();
view->Query( selectionRect, candidates );
// Ensure candidates only have unique items
std::set<SCH_ITEM*> uniqueCandidates;
for( const auto& [viewItem, layer] : candidates )
{
if( viewItem->IsSCH_ITEM() )
uniqueCandidates.insert( static_cast<SCH_ITEM*>( viewItem ) );
}
for( KIGFX::VIEW_ITEM* item : uniqueCandidates )
{
// If the item is a sheet or symbol, ensure we add its pins because they are not
// in the RTree and we need to check them against the selection box.
if( SCH_SHEET* sheet = dynamic_cast<SCH_SHEET*>( item ) )
{
for( SCH_SHEET_PIN* pin : sheet->GetPins() )
{
// If the pin is within the selection box, add it as a candidate
if( selectionRect.Intersects( pin->GetBoundingBox() ) )
uniqueCandidates.insert( pin );
}
}
else if( SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( item ) )
{
for( SCH_PIN* pin : symbol->GetPins() )
{
// If the pin is within the selection box, add it as a candidate
if( selectionRect.Intersects( pin->GetBoundingBox() ) )
uniqueCandidates.insert( pin );
}
}
}
// Build lists of nearby items and their children
SCH_COLLECTOR collector;
SCH_COLLECTOR pinsCollector;
std::set<EDA_ITEM*> group_items;
for( EDA_ITEM* item : m_frame->GetScreen()->Items().OfType( SCH_GROUP_T ) )
{
SCH_GROUP* group = static_cast<SCH_GROUP*>( item );
// The currently entered group does not get limited
if( m_enteredGroup == group )
continue;
std::unordered_set<EDA_ITEM*>& newset = group->GetItems();
// If we are not greedy and have selected the whole group, add just one item
// to allow it to be promoted to the group later
if( !isGreedy && selectionRect.Contains( group->GetBoundingBox() ) && newset.size() )
{
for( EDA_ITEM* group_item : newset )
{
if( !group_item->IsSCH_ITEM() )
continue;
if( Selectable( static_cast<SCH_ITEM*>( group_item ) ) )
collector.Append( *newset.begin() );
}
}
for( EDA_ITEM* group_item : newset )
group_items.emplace( group_item );
}
for( SCH_ITEM* item : uniqueCandidates )
{
// If the item is a line, add it even if it doesn't pass the hit test using the greedy
// flag as we handle partially selecting line ends later
if( item && Selectable( item )
&& ( item->HitTest( selectionRect, !isGreedy ) || item->Type() == SCH_LINE_T )
&& ( isGreedy || !group_items.count( item ) ) )
{
if( item->Type() == SCH_PIN_T && !m_isSymbolEditor )
pinsCollector.Append( item );
else
collector.Append( item );
}
}
// Apply the stateful filter
filterCollectedItems( collector, true );
filterCollectorForHierarchy( collector, true );
// If we selected nothing but pins, allow them to be selected
if( collector.GetCount() == 0 )
{
collector = pinsCollector;
filterCollectedItems( collector, true );
filterCollectorForHierarchy( collector, true );
}
// Sort the filtered selection by rows and columns to have a nice default
// for tools that can use it.
std::sort( collector.begin(), collector.end(),
[]( EDA_ITEM* a, EDA_ITEM* b )
{
VECTOR2I aPos = a->GetPosition();
VECTOR2I bPos = b->GetPosition();
if( aPos.y == bPos.y )
return aPos.x < bPos.x;
return aPos.y < bPos.y;
} );
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;
}
};
std::vector<EDA_ITEM*> flaggedItems;
for( EDA_ITEM* item : collector )
{
EDA_ITEM_FLAGS flags = 0;
item->SetFlags( SELECTION_CANDIDATE );
flaggedItems.push_back( item );
if( m_frame->GetRenderSettings()->m_ShowPinsElectricalType )
item->SetFlags( SHOW_ELEC_TYPE );
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() ) ) )
{
flags |= STARTPOINT | ENDPOINT;
}
else if( !isGreedy )
{
if( selectionRect.Contains( line->GetStartPoint() ) && line->IsStartDangling() )
flags |= STARTPOINT;
if( selectionRect.Contains( line->GetEndPoint() ) && line->IsEndDangling() )
flags |= ENDPOINT;
}
// Only select a line if it at least one point is selected
if( flags & ( STARTPOINT | ENDPOINT ) )
selectItem( item, flags );
}
else
selectItem( item, flags );
item->ClearFlags( SHOW_ELEC_TYPE );
}
for( EDA_ITEM* item : pinsCollector )
{
if( m_frame->GetRenderSettings()->m_ShowPinsElectricalType )
item->SetFlags( SHOW_ELEC_TYPE );
if( Selectable( item ) && itemPassesFilter( item, nullptr ) && !item->GetParent()->HasFlag( SELECTION_CANDIDATE )
&& item->HitTest( selectionRect, !isGreedy ) )
{
selectItem( item, 0 );
}
item->ClearFlags( SHOW_ELEC_TYPE );
}
for( EDA_ITEM* item : flaggedItems )
item->ClearFlags( SELECTION_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
SelectMultiple( area, m_drag_subtractive, false );
evt->SetPassEvent( false );
break;
}
// Allow some actions for navigation
for( int i = 0; allowedActions[i]; ++i )
{
if( evt->IsAction( allowedActions[i] ) )
{
evt->SetPassEvent();
break;
}
}
passEvent( evt, allowedActions );
}
getViewControls()->SetAutoPan( false );
@ -2452,6 +2277,327 @@ bool SCH_SELECTION_TOOL::selectMultiple()
}
bool SCH_SELECTION_TOOL::selectLasso()
{
bool cancelled = false;
m_multiple = true;
KIGFX::PREVIEW::SELECTION_AREA area;
getView()->Add( &area );
getView()->SetVisible( &area, true );
getViewControls()->SetAutoPan( true );
SHAPE_LINE_CHAIN points;
points.SetClosed( true );
SELECTION_MODE selectionMode = SELECTION_MODE::TOUCHING_LASSO;
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::SELECT_LASSO );
while( TOOL_EVENT* evt = Wait() )
{
double shapeArea = area.GetPoly().Area( false );
bool isClockwise = shapeArea > 0 ? true : false;
if( getView()->IsMirroredX() && shapeArea != 0 )
isClockwise = !isClockwise;
selectionMode = isClockwise ? SELECTION_MODE::INSIDE_LASSO : SELECTION_MODE::TOUCHING_LASSO;
if( evt->IsCancelInteractive() || evt->IsActivate() )
{
cancelled = true;
break;
}
else if( evt->IsDrag( BUT_LEFT )
|| evt->IsClick( BUT_LEFT )
|| evt->IsAction( &ACTIONS::cursorClick ) )
{
points.Append( evt->Position() );
}
else if( evt->IsDblClick( BUT_LEFT )
|| evt->IsAction( &ACTIONS::cursorDblClick )
|| evt->IsAction( &ACTIONS::finishInteractive ) )
{
area.GetPoly().GenerateBBoxCache();
SelectMultiple( area, m_drag_subtractive, false );
break;
}
else if( evt->IsAction( &ACTIONS::doDelete )
|| evt->IsAction( &ACTIONS::undo ) )
{
if( points.GetPointCount() > 0 )
{
getViewControls()->SetCursorPosition( points.CLastPoint() );
points.Remove( points.GetPointCount() - 1 );
}
}
else
{
passEvent( evt, allowedActions );
}
if( points.PointCount() > 0 )
{
if( !m_drag_additive && !m_drag_subtractive )
{
if( m_selection.GetSize() > 0 )
{
ClearSelection( true );
m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
}
}
}
area.SetPoly( points );
area.GetPoly().Append( m_toolMgr->GetMousePosition() );
area.SetAdditive( m_drag_additive );
area.SetSubtractive( m_drag_subtractive );
area.SetExclusiveOr( false );
area.SetMode( selectionMode );
getView()->Update( &area );
}
getViewControls()->SetAutoPan( false );
getView()->SetVisible( &area, false );
getView()->Remove( &area );
m_multiple = false;
if( !cancelled )
m_selection.ClearReferencePoint();
return cancelled;
}
void SCH_SELECTION_TOOL::SelectMultiple( KIGFX::PREVIEW::SELECTION_AREA& aArea, bool aSubtractive,
bool aExclusiveOr )
{
KIGFX::VIEW* view = getView();
SELECTION_MODE selectionMode = aArea.GetMode();
bool containedMode = ( selectionMode == SELECTION_MODE::INSIDE_RECTANGLE
|| selectionMode == SELECTION_MODE::INSIDE_LASSO );
bool boxMode = ( selectionMode == SELECTION_MODE::INSIDE_RECTANGLE
|| selectionMode == SELECTION_MODE::TOUCHING_RECTANGLE );
std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> candidates;
BOX2I selectionRect = aArea.ViewBBox();
view->Query( selectionRect, candidates );
std::set<SCH_ITEM*> uniqueCandidates;
for( const auto& [viewItem, layer] : candidates )
{
if( viewItem->IsSCH_ITEM() )
uniqueCandidates.insert( static_cast<SCH_ITEM*>( viewItem ) );
}
for( KIGFX::VIEW_ITEM* item : uniqueCandidates )
{
if( SCH_SHEET* sheet = dynamic_cast<SCH_SHEET*>( item ) )
{
for( SCH_SHEET_PIN* pin : sheet->GetPins() )
{
if( boxMode ? selectionRect.Intersects( pin->GetBoundingBox() )
: KIGEOM::BoxHitTest( aArea.GetPoly(), pin->GetBoundingBox(), true ) )
uniqueCandidates.insert( pin );
}
}
else if( SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( item ) )
{
for( SCH_PIN* pin : symbol->GetPins() )
{
if( boxMode ? selectionRect.Intersects( pin->GetBoundingBox() )
: KIGEOM::BoxHitTest( aArea.GetPoly(), pin->GetBoundingBox(), true ) )
uniqueCandidates.insert( pin );
}
}
}
SCH_COLLECTOR collector;
SCH_COLLECTOR pinsCollector;
std::set<EDA_ITEM*> group_items;
for( EDA_ITEM* item : m_frame->GetScreen()->Items().OfType( SCH_GROUP_T ) )
{
SCH_GROUP* group = static_cast<SCH_GROUP*>( item );
if( m_enteredGroup == group )
continue;
std::unordered_set<EDA_ITEM*>& newset = group->GetItems();
auto boxContained =
[&]( const BOX2I& aBox )
{
return boxMode ? selectionRect.Contains( aBox )
: KIGEOM::BoxHitTest( aArea.GetPoly(), aBox, true );
};
if( containedMode && boxContained( group->GetBoundingBox() ) && newset.size() )
{
for( EDA_ITEM* group_item : newset )
{
if( !group_item->IsSCH_ITEM() )
continue;
if( Selectable( static_cast<SCH_ITEM*>( group_item ) ) )
collector.Append( *newset.begin() );
}
}
for( EDA_ITEM* group_item : newset )
group_items.emplace( group_item );
}
auto hitTest =
[&]( SCH_ITEM* aItem )
{
return boxMode ? aItem->HitTest( selectionRect, containedMode )
: aItem->HitTest( aArea.GetPoly(), containedMode );
};
for( SCH_ITEM* item : uniqueCandidates )
{
if( Selectable( item ) && ( hitTest( item ) || item->Type() == SCH_LINE_T )
&& ( !containedMode || !group_items.count( item ) ) )
{
if( item->Type() == SCH_PIN_T && !m_isSymbolEditor )
pinsCollector.Append( item );
else
collector.Append( item );
}
}
filterCollectedItems( collector, true );
filterCollectorForHierarchy( collector, true );
if( collector.GetCount() == 0 )
{
collector = pinsCollector;
filterCollectedItems( collector, true );
filterCollectorForHierarchy( collector, true );
}
std::sort( collector.begin(), collector.end(),
[]( EDA_ITEM* a, EDA_ITEM* b )
{
VECTOR2I aPos = a->GetPosition();
VECTOR2I bPos = b->GetPosition();
if( aPos.y == bPos.y )
return aPos.x < bPos.x;
return aPos.y < bPos.y;
} );
bool anyAdded = false;
bool anySubtracted = false;
auto selectItem =
[&]( EDA_ITEM* aItem, EDA_ITEM_FLAGS flags )
{
if( aSubtractive || ( aExclusiveOr && aItem->IsSelected() ) )
{
if( aExclusiveOr )
aItem->XorFlags( flags );
else
aItem->ClearFlags( flags );
if( !aItem->HasFlag( STARTPOINT ) && !aItem->HasFlag( ENDPOINT ) )
{
unselect( aItem );
anySubtracted = true;
}
if( flags && !anySubtracted )
getView()->Update( aItem );
}
else
{
aItem->SetFlags( flags );
select( aItem );
anyAdded = true;
}
};
std::vector<EDA_ITEM*> flaggedItems;
auto shapeContains =
[&]( const VECTOR2I& aPoint )
{
return boxMode ? selectionRect.Contains( aPoint )
: aArea.GetPoly().PointInside( aPoint );
};
for( EDA_ITEM* item : collector )
{
EDA_ITEM_FLAGS flags = 0;
item->SetFlags( SELECTION_CANDIDATE );
flaggedItems.push_back( item );
if( m_frame->GetRenderSettings()->m_ShowPinsElectricalType )
item->SetFlags( SHOW_ELEC_TYPE );
if( item->Type() == SCH_LINE_T )
{
SCH_LINE* line = static_cast<SCH_LINE*>( item );
bool hits = false;
if( boxMode )
hits = line->HitTest( selectionRect, false );
else
hits = line->HitTest( aArea.GetPoly(), false );
if( ( !containedMode && hits )
|| ( shapeContains( line->GetEndPoint() ) && shapeContains( line->GetStartPoint() ) ) )
{
flags |= STARTPOINT | ENDPOINT;
}
else if( containedMode )
{
if( shapeContains( line->GetStartPoint() ) && line->IsStartDangling() )
flags |= STARTPOINT;
if( shapeContains( line->GetEndPoint() ) && line->IsEndDangling() )
flags |= ENDPOINT;
}
if( flags & ( STARTPOINT | ENDPOINT ) )
selectItem( item, flags );
}
else
selectItem( item, flags );
item->ClearFlags( SHOW_ELEC_TYPE );
}
for( EDA_ITEM* item : pinsCollector )
{
if( m_frame->GetRenderSettings()->m_ShowPinsElectricalType )
item->SetFlags( SHOW_ELEC_TYPE );
if( Selectable( item ) && itemPassesFilter( item, nullptr )
&& !item->GetParent()->HasFlag( SELECTION_CANDIDATE ) && hitTest( static_cast<SCH_ITEM*>( item ) ) )
{
selectItem( item, 0 );
}
item->ClearFlags( SHOW_ELEC_TYPE );
}
for( EDA_ITEM* item : flaggedItems )
item->ClearFlags( SELECTION_CANDIDATE );
m_selection.SetIsHover( false );
if( anyAdded )
m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
else if( anySubtracted )
m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
}
void SCH_SELECTION_TOOL::filterCollectorForHierarchy( SCH_COLLECTOR& aCollector,
bool aMultiselect ) const
{
@ -3358,6 +3504,9 @@ void SCH_SELECTION_TOOL::setTransitions()
Go( &SCH_SELECTION_TOOL::ClearSelection, ACTIONS::selectionClear.MakeEvent() );
Go( &SCH_SELECTION_TOOL::SetSelectPoly, ACTIONS::selectSetLasso.MakeEvent() );
Go( &SCH_SELECTION_TOOL::SetSelectRect, ACTIONS::selectSetRect.MakeEvent() );
Go( &SCH_SELECTION_TOOL::AddItemToSel, ACTIONS::selectItem.MakeEvent() );
Go( &SCH_SELECTION_TOOL::AddItemsToSel, ACTIONS::selectItems.MakeEvent() );
Go( &SCH_SELECTION_TOOL::RemoveItemFromSel, ACTIONS::unselectItem.MakeEvent() );

View File

@ -44,6 +44,11 @@ class SCH_TABLECELL;
namespace KIGFX
{
class GAL;
namespace PREVIEW
{
class SELECTION_AREA;
}
}
@ -266,6 +271,14 @@ private:
*/
bool selectMultiple();
bool selectLasso();
int SetSelectRect( const TOOL_EVENT& aEvent );
int SetSelectPoly( const TOOL_EVENT& aEvent );
void SelectMultiple( KIGFX::PREVIEW::SELECTION_AREA& aArea, bool aSubtractive = false,
bool aExclusiveOr = false );
/**
* Handle a table cell drag selection within a table.
*
@ -371,6 +384,8 @@ private:
SCH_SELECTION_FILTER_OPTIONS m_filter;
SELECTION_MODE m_selectionMode; // Current selection mode
SCH_TABLECELL* m_previous_first_cell; // First selected cell for shift+click selection range
};