From 173e02eff703e14c4a0f2df341a2c4a1de833d2e Mon Sep 17 00:00:00 2001 From: Seth Hillbrand Date: Wed, 3 Sep 2025 06:45:49 -0700 Subject: [PATCH] ADDED: Lasso support to Schematic Editor --- eeschema/sch_bus_entry.cpp | 8 + eeschema/sch_bus_entry.h | 1 + eeschema/sch_field.cpp | 21 + eeschema/sch_field.h | 1 + eeschema/sch_junction.cpp | 10 + eeschema/sch_junction.h | 1 + eeschema/sch_label.cpp | 31 ++ eeschema/sch_label.h | 1 + eeschema/sch_line.cpp | 11 + eeschema/sch_line.h | 1 + eeschema/sch_no_connect.cpp | 7 + eeschema/sch_no_connect.h | 1 + eeschema/sch_shape.cpp | 27 ++ eeschema/sch_shape.h | 1 + eeschema/sch_sheet.cpp | 7 + eeschema/sch_sheet.h | 1 + eeschema/sch_symbol.cpp | 10 + eeschema/sch_symbol.h | 1 + eeschema/sch_text.cpp | 10 + eeschema/sch_text.h | 1 + eeschema/sch_textbox.cpp | 7 + eeschema/sch_textbox.h | 1 + eeschema/toolbars_sch_editor.cpp | 4 +- eeschema/tools/sch_selection_tool.cpp | 605 ++++++++++++++++---------- eeschema/tools/sch_selection_tool.h | 15 + 25 files changed, 555 insertions(+), 229 deletions(-) diff --git a/eeschema/sch_bus_entry.cpp b/eeschema/sch_bus_entry.cpp index c5732490a5..feec45bfc3 100644 --- a/eeschema/sch_bus_entry.cpp +++ b/eeschema/sch_bus_entry.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -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 ) { diff --git a/eeschema/sch_bus_entry.h b/eeschema/sch_bus_entry.h index 42de9f3438..6c22c182e7 100644 --- a/eeschema/sch_bus_entry.h +++ b/eeschema/sch_bus_entry.h @@ -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; diff --git a/eeschema/sch_field.cpp b/eeschema/sch_field.cpp index c6e39ddb58..04a2c77d68 100644 --- a/eeschema/sch_field.cpp +++ b/eeschema/sch_field.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -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( 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 ) { diff --git a/eeschema/sch_field.h b/eeschema/sch_field.h index 85d9e0c67d..4ba324d31f 100644 --- a/eeschema/sch_field.h +++ b/eeschema/sch_field.h @@ -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; diff --git a/eeschema/sch_junction.cpp b/eeschema/sch_junction.cpp index a94a913add..c5c124f4f8 100644 --- a/eeschema/sch_junction.cpp +++ b/eeschema/sch_junction.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -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 { diff --git a/eeschema/sch_junction.h b/eeschema/sch_junction.h index 0e0cb60175..aeb2220553 100644 --- a/eeschema/sch_junction.h +++ b/eeschema/sch_junction.h @@ -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; diff --git a/eeschema/sch_label.cpp b/eeschema/sch_label.cpp index 5d9a150285..144a924ba5 100644 --- a/eeschema/sch_label.cpp +++ b/eeschema/sch_label.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -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& aItemListByType, std::vector& aItemListByPos, const SCH_SHEET_PATH* aPath ) diff --git a/eeschema/sch_label.h b/eeschema/sch_label.h index ff1e612e0a..05dee153b8 100644 --- a/eeschema/sch_label.h +++ b/eeschema/sch_label.h @@ -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 GetConnectionPoints() const override; diff --git a/eeschema/sch_line.cpp b/eeschema/sch_line.cpp index 0c1b412c2c..3d0162f27f 100644 --- a/eeschema/sch_line.cpp +++ b/eeschema/sch_line.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -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; diff --git a/eeschema/sch_line.h b/eeschema/sch_line.h index df51e51e3c..1095d61329 100644 --- a/eeschema/sch_line.h +++ b/eeschema/sch_line.h @@ -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; diff --git a/eeschema/sch_no_connect.cpp b/eeschema/sch_no_connect.cpp index 8921063e34..d9fe49318c 100644 --- a/eeschema/sch_no_connect.cpp +++ b/eeschema/sch_no_connect.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include // 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 ) { diff --git a/eeschema/sch_no_connect.h b/eeschema/sch_no_connect.h index 77e137b218..a14bf4b59e 100644 --- a/eeschema/sch_no_connect.h +++ b/eeschema/sch_no_connect.h @@ -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; diff --git a/eeschema/sch_shape.cpp b/eeschema/sch_shape.cpp index 25a6c1ae31..8419cb5f9c 100644 --- a/eeschema/sch_shape.cpp +++ b/eeschema/sch_shape.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -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 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(); diff --git a/eeschema/sch_shape.h b/eeschema/sch_shape.h index d54eec660c..3bd2587456 100644 --- a/eeschema/sch_shape.h +++ b/eeschema/sch_shape.h @@ -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; diff --git a/eeschema/sch_sheet.cpp b/eeschema/sch_sheet.cpp index 557dd2f425..401fcc16af 100644 --- a/eeschema/sch_sheet.cpp +++ b/eeschema/sch_sheet.cpp @@ -36,6 +36,7 @@ #include #include #include // for KiROUND +#include #include #include #include @@ -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 ) { diff --git a/eeschema/sch_sheet.h b/eeschema/sch_sheet.h index 84aa2781f4..129306e3d2 100644 --- a/eeschema/sch_sheet.h +++ b/eeschema/sch_sheet.h @@ -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; diff --git a/eeschema/sch_symbol.cpp b/eeschema/sch_symbol.cpp index 0f2b3789b0..785ee126fe 100644 --- a/eeschema/sch_symbol.cpp +++ b/eeschema/sch_symbol.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -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 ); diff --git a/eeschema/sch_symbol.h b/eeschema/sch_symbol.h index 58b10267fb..2a7414b22c 100644 --- a/eeschema/sch_symbol.h +++ b/eeschema/sch_symbol.h @@ -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; diff --git a/eeschema/sch_text.cpp b/eeschema/sch_text.cpp index ce8660c4c1..63752b5d95 100644 --- a/eeschema/sch_text.cpp +++ b/eeschema/sch_text.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -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 ); diff --git a/eeschema/sch_text.h b/eeschema/sch_text.h index 48396aa18e..f64f8f1614 100644 --- a/eeschema/sch_text.h +++ b/eeschema/sch_text.h @@ -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; diff --git a/eeschema/sch_textbox.cpp b/eeschema/sch_textbox.cpp index 891d450578..0f11b007c6 100644 --- a/eeschema/sch_textbox.cpp +++ b/eeschema/sch_textbox.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -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() ) diff --git a/eeschema/sch_textbox.h b/eeschema/sch_textbox.h index 5674e99b5c..4e60172c4d 100644 --- a/eeschema/sch_textbox.h +++ b/eeschema/sch_textbox.h @@ -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 { diff --git a/eeschema/toolbars_sch_editor.cpp b/eeschema/toolbars_sch_editor.cpp index d99c48bcbf..7fa8dfa208 100644 --- a/eeschema/toolbars_sch_editor.cpp +++ b/eeschema/toolbars_sch_editor.cpp @@ -93,7 +93,9 @@ std::optional 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() diff --git a/eeschema/tools/sch_selection_tool.cpp b/eeschema/tools/sch_selection_tool.cpp index 4d51f2d75b..cbf5f7ff2e 100644 --- a/eeschema/tools/sch_selection_tool.cpp +++ b/eeschema/tools/sch_selection_tool.cpp @@ -35,7 +35,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -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 candidates; - BOX2I selectionRect = area.ViewBBox(); - view->Query( selectionRect, candidates ); - - // Ensure candidates only have unique items - std::set uniqueCandidates; - - for( const auto& [viewItem, layer] : candidates ) - { - if( viewItem->IsSCH_ITEM() ) - uniqueCandidates.insert( static_cast( 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( 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( 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 group_items; - - for( EDA_ITEM* item : m_frame->GetScreen()->Items().OfType( SCH_GROUP_T ) ) - { - SCH_GROUP* group = static_cast( item ); - - // The currently entered group does not get limited - if( m_enteredGroup == group ) - continue; - - std::unordered_set& 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( 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 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( 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 candidates; + BOX2I selectionRect = aArea.ViewBBox(); + view->Query( selectionRect, candidates ); + + std::set uniqueCandidates; + + for( const auto& [viewItem, layer] : candidates ) + { + if( viewItem->IsSCH_ITEM() ) + uniqueCandidates.insert( static_cast( viewItem ) ); + } + + for( KIGFX::VIEW_ITEM* item : uniqueCandidates ) + { + if( SCH_SHEET* sheet = dynamic_cast( 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( 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 group_items; + + for( EDA_ITEM* item : m_frame->GetScreen()->Items().OfType( SCH_GROUP_T ) ) + { + SCH_GROUP* group = static_cast( item ); + + if( m_enteredGroup == group ) + continue; + + std::unordered_set& 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( 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 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( 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( 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() ); diff --git a/eeschema/tools/sch_selection_tool.h b/eeschema/tools/sch_selection_tool.h index 9cf35d43b9..920537a591 100644 --- a/eeschema/tools/sch_selection_tool.h +++ b/eeschema/tools/sch_selection_tool.h @@ -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 };