mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-13 17:53:11 +02:00
ADDED: Lasso selection in pcbnew
Adds a lasso or freeform selection tool to KiCad in addition to standard rectangular selection. Adds supporting HitTest routines Fixes: https://gitlab.com/kicad/code/kicad/-/issues/1977
This commit is contained in:
parent
1306cb337d
commit
c73d555fe2
@ -35,6 +35,7 @@
|
||||
#include <geometry/shape_simple.h>
|
||||
#include <geometry/shape_segment.h>
|
||||
#include <geometry/shape_rect.h>
|
||||
#include <geometry/geometry_utils.h>
|
||||
#include <macros.h>
|
||||
#include <math/util.h> // for KiROUND
|
||||
#include <eda_item.h>
|
||||
@ -1587,6 +1588,14 @@ bool EDA_SHAPE::hitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) co
|
||||
}
|
||||
|
||||
|
||||
bool EDA_SHAPE::hitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
|
||||
{
|
||||
SHAPE_COMPOUND shape( MakeEffectiveShapes() );
|
||||
|
||||
return KIGEOM::ShapeHitTest( aPoly, shape, aContained );
|
||||
}
|
||||
|
||||
|
||||
std::vector<VECTOR2I> EDA_SHAPE::GetRectCorners() const
|
||||
{
|
||||
std::vector<VECTOR2I> pts;
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "marker_base.h"
|
||||
#include <core/arraydim.h>
|
||||
#include <geometry/shape_line_chain.h>
|
||||
#include <geometry/geometry_utils.h>
|
||||
#include "dialogs/dialog_display_html_text_base.h"
|
||||
|
||||
|
||||
@ -114,6 +115,16 @@ bool MARKER_BASE::HitTestMarker( const BOX2I& aRect, bool aContained, int aAccur
|
||||
}
|
||||
|
||||
|
||||
bool MARKER_BASE::HitTestMarker( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
|
||||
{
|
||||
SHAPE_LINE_CHAIN shape;
|
||||
ShapeToPolygon( shape );
|
||||
shape.Move( m_Pos );
|
||||
|
||||
return KIGEOM::ShapeHitTest( aPoly, shape, aContained );
|
||||
}
|
||||
|
||||
|
||||
void MARKER_BASE::ShapeToPolygon( SHAPE_LINE_CHAIN& aPolygon, int aScale ) const
|
||||
{
|
||||
if( aScale < 0 )
|
||||
|
@ -66,7 +66,8 @@ static const SELECTION_COLORS selectionColorScheme[2] = {
|
||||
SELECTION_AREA::SELECTION_AREA() :
|
||||
m_additive( false ),
|
||||
m_subtractive( false ),
|
||||
m_exclusiveOr( false )
|
||||
m_exclusiveOr( false ),
|
||||
m_mode( SELECTION_MODE::INSIDE_RECTANGLE )
|
||||
{
|
||||
|
||||
}
|
||||
@ -76,9 +77,23 @@ const BOX2I SELECTION_AREA::ViewBBox() const
|
||||
{
|
||||
BOX2I tmp;
|
||||
|
||||
tmp.SetOrigin( m_origin );
|
||||
tmp.SetEnd( m_end );
|
||||
switch( m_mode )
|
||||
{
|
||||
default:
|
||||
case SELECTION_MODE::INSIDE_RECTANGLE:
|
||||
case SELECTION_MODE::TOUCHING_RECTANGLE:
|
||||
tmp.SetOrigin( m_origin );
|
||||
tmp.SetEnd( m_end );
|
||||
break;
|
||||
case SELECTION_MODE::INSIDE_LASSO:
|
||||
case SELECTION_MODE::TOUCHING_LASSO:
|
||||
case SELECTION_MODE::TOUCHING_PATH:
|
||||
tmp = m_shape_poly.BBox();
|
||||
break;
|
||||
}
|
||||
|
||||
tmp.Normalize();
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
@ -91,10 +106,9 @@ void SELECTION_AREA::ViewDraw( int aLayer, KIGFX::VIEW* aView ) const
|
||||
const SELECTION_COLORS& scheme = settings->IsBackgroundDark() ? selectionColorScheme[0]
|
||||
: selectionColorScheme[1];
|
||||
|
||||
// Set the fill of the selection rectangle
|
||||
// based on the selection mode
|
||||
// Set the colors of the selection shape based on the selection mode
|
||||
if( m_additive )
|
||||
gal.SetFillColor( scheme.additive );
|
||||
gal.SetFillColor( scheme.additive );
|
||||
else if( m_subtractive )
|
||||
gal.SetFillColor( scheme.subtract );
|
||||
else if( m_exclusiveOr )
|
||||
@ -102,24 +116,44 @@ void SELECTION_AREA::ViewDraw( int aLayer, KIGFX::VIEW* aView ) const
|
||||
else
|
||||
gal.SetFillColor( scheme.normal );
|
||||
|
||||
gal.SetIsStroke( true );
|
||||
gal.SetIsFill( true );
|
||||
if( m_mode == SELECTION_MODE::INSIDE_RECTANGLE || m_mode == SELECTION_MODE::INSIDE_LASSO )
|
||||
gal.SetStrokeColor( scheme.outline_l2r );
|
||||
else
|
||||
gal.SetStrokeColor( scheme.outline_r2l );
|
||||
|
||||
auto drawSelectionShape =
|
||||
[&]()
|
||||
{
|
||||
switch( m_mode )
|
||||
{
|
||||
default:
|
||||
case SELECTION_MODE::INSIDE_RECTANGLE:
|
||||
case SELECTION_MODE::TOUCHING_RECTANGLE:
|
||||
gal.DrawRectangle( m_origin, m_end );
|
||||
break;
|
||||
case SELECTION_MODE::INSIDE_LASSO:
|
||||
case SELECTION_MODE::TOUCHING_LASSO:
|
||||
if( m_shape_poly.PointCount() > 1 )
|
||||
gal.DrawPolygon( m_shape_poly );
|
||||
break;
|
||||
case SELECTION_MODE::TOUCHING_PATH:
|
||||
if( m_shape_poly.PointCount() > 0 )
|
||||
gal.DrawPolyline( m_shape_poly );
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
gal.SetIsStroke( true );
|
||||
gal.SetIsFill( false );
|
||||
// force 1-pixel-wide line
|
||||
gal.SetLineWidth( 0.0 );
|
||||
|
||||
// Set the stroke color to indicate window or crossing selection
|
||||
bool windowSelection = ( m_origin.x <= m_end.x ) ? true : false;
|
||||
|
||||
if( aView->IsMirroredX() )
|
||||
windowSelection = !windowSelection;
|
||||
|
||||
gal.SetStrokeColor( windowSelection ? scheme.outline_l2r : scheme.outline_r2l );
|
||||
gal.SetIsFill( false );
|
||||
gal.DrawRectangle( m_origin, m_end );
|
||||
gal.SetIsFill( true );
|
||||
drawSelectionShape();
|
||||
|
||||
// draw the fill as the second object so that Z test will not clamp
|
||||
// the single-pixel-wide rectangle sides
|
||||
gal.DrawRectangle( m_origin, m_end );
|
||||
if( m_mode != SELECTION_MODE::TOUCHING_PATH )
|
||||
{
|
||||
gal.SetIsFill( true );
|
||||
drawSelectionShape();
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include <tool/actions.h>
|
||||
#include <tool/tool_action.h>
|
||||
#include <tool/tool_event.h>
|
||||
#include <tool/selection_tool.h>
|
||||
|
||||
// Actions, being statically-defined, require specialized I18N handling. We continue to
|
||||
// use the _() macro so that string harvesting by the I18N framework doesn't have to be
|
||||
@ -346,6 +347,51 @@ TOOL_ACTION ACTIONS::paste( TOOL_ACTION_ARGS()
|
||||
.Flags( AF_NONE )
|
||||
.UIId( wxID_PASTE ) );
|
||||
|
||||
TOOL_ACTION ACTIONS::selectInsideRectangle( TOOL_ACTION_ARGS()
|
||||
.Name( "common.Interactive.selectInsideRectangle" )
|
||||
.Scope( AS_GLOBAL )
|
||||
.FriendlyName( _( "Inside Rectangle" ) )
|
||||
.Tooltip( _( "Select all items fully contained within the rectangular area" ) )
|
||||
.Parameter( SELECTION_MODE::INSIDE_RECTANGLE ) );
|
||||
|
||||
TOOL_ACTION ACTIONS::selectTouchingRectangle( TOOL_ACTION_ARGS()
|
||||
.Name( "common.Interactive.selectTouchingRectangle" )
|
||||
.Scope( AS_GLOBAL )
|
||||
.FriendlyName( _( "Touching Rectangle" ) )
|
||||
.Tooltip( _( "Select all items that touch or intersect the rectangular area" ) )
|
||||
.Parameter( SELECTION_MODE::TOUCHING_RECTANGLE ) );
|
||||
|
||||
TOOL_ACTION ACTIONS::selectInsideLasso( TOOL_ACTION_ARGS()
|
||||
.Name( "common.Interactive.selectInsideLasso" )
|
||||
.Scope( AS_GLOBAL )
|
||||
.FriendlyName( _( "Inside Lasso" ) )
|
||||
.Tooltip( _( "Select all items fully contained within the lasso area" ) )
|
||||
.Icon( BITMAPS::add_graphical_polygon ) // TODO: add proper icon
|
||||
.Parameter( SELECTION_MODE::INSIDE_LASSO ) );
|
||||
|
||||
TOOL_ACTION ACTIONS::selectTouchingLasso( TOOL_ACTION_ARGS()
|
||||
.Name( "common.Interactive.selectTouchingLasso" )
|
||||
.Scope( AS_GLOBAL )
|
||||
.FriendlyName( _( "Touching Lasso" ) )
|
||||
.Tooltip( _( "Select all items that touch or intersect the lasso area" ) )
|
||||
.Icon( BITMAPS::add_dashed_line ) // TODO: add proper icon
|
||||
.Parameter( SELECTION_MODE::TOUCHING_LASSO ) );
|
||||
|
||||
TOOL_ACTION ACTIONS::selectAutoLasso( TOOL_ACTION_ARGS()
|
||||
.Name( "common.Interactive.selectAutoLasso" )
|
||||
.Scope( AS_GLOBAL )
|
||||
.FriendlyName( _( "Lasso" ) )
|
||||
.Tooltip( _( "Select all items fully contained within or touching the lasso area, depending on the drawing direction" ) )
|
||||
.Icon( BITMAPS::opt_show_polygon ) ); // TODO: add proper icon
|
||||
|
||||
TOOL_ACTION ACTIONS::selectTouchingPath( TOOL_ACTION_ARGS()
|
||||
.Name( "common.Interactive.selectTouchingPath" )
|
||||
.Scope( AS_GLOBAL )
|
||||
.FriendlyName( _( "Touching Path" ) )
|
||||
.Tooltip( _( "Select all items that touch or intersect the drawn path" ) )
|
||||
.Icon( BITMAPS::add_line ) // TODO: add proper icon
|
||||
.Parameter( SELECTION_MODE::TOUCHING_PATH ) );
|
||||
|
||||
TOOL_ACTION ACTIONS::selectAll( TOOL_ACTION_ARGS()
|
||||
.Name( "common.Interactive.selectAll" )
|
||||
.Scope( AS_GLOBAL )
|
||||
|
@ -2148,6 +2148,8 @@ bool SCH_SELECTION_TOOL::selectMultiple()
|
||||
area.SetAdditive( m_drag_additive );
|
||||
area.SetSubtractive( m_drag_subtractive );
|
||||
area.SetExclusiveOr( false );
|
||||
area.SetMode( isGreedy ? SELECTION_MODE::TOUCHING_RECTANGLE
|
||||
: SELECTION_MODE::INSIDE_RECTANGLE );
|
||||
|
||||
view->SetVisible( &area, true );
|
||||
view->Update( &area );
|
||||
|
@ -29,6 +29,7 @@
|
||||
|
||||
#include <deque>
|
||||
|
||||
#include <geometry/shape_line_chain.h>
|
||||
#include <api/serializable.h>
|
||||
#include <core/typeinfo.h>
|
||||
#include <eda_item_flags.h>
|
||||
@ -247,6 +248,18 @@ public:
|
||||
return false; // derived classes should override this function
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if \a aPoly intersects this item.
|
||||
*
|
||||
* @param aPoly A reference to a #SHAPE_LINE_CHAIN object containing the polygon or polyline to test.
|
||||
* @param aContained Set to true to test for containment instead of an intersection.
|
||||
* @return True if \a aPoly contains or intersects the item.
|
||||
*/
|
||||
virtual bool HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
|
||||
{
|
||||
return false; // derived classes should override this function
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the orthogonal bounding box of this object for display purposes.
|
||||
*
|
||||
|
@ -451,6 +451,7 @@ protected:
|
||||
|
||||
bool hitTest( const VECTOR2I& aPosition, int aAccuracy = 0 ) const;
|
||||
bool hitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const;
|
||||
bool hitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const;
|
||||
|
||||
const std::vector<VECTOR2I> buildBezierToSegmentsPointsList( int aMaxError ) const;
|
||||
|
||||
|
@ -119,6 +119,11 @@ public:
|
||||
*/
|
||||
bool HitTestMarker( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const;
|
||||
|
||||
/**
|
||||
* Test if the given #SHAPE_LINE_CHAIN intersects or contains the bounds of this object.
|
||||
*/
|
||||
bool HitTestMarker( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const;
|
||||
|
||||
/**
|
||||
* Return the orthogonal, bounding box of this object for display purposes.
|
||||
*
|
||||
|
@ -28,7 +28,8 @@
|
||||
#define PREVIEW_ITEMS_SELECTION_AREA_H
|
||||
|
||||
#include <preview_items/simple_overlay_item.h>
|
||||
|
||||
#include <geometry/shape_line_chain.h>
|
||||
#include <tool/selection_tool.h>
|
||||
|
||||
namespace KIGFX
|
||||
{
|
||||
@ -82,6 +83,12 @@ public:
|
||||
void SetSubtractive( bool aSubtractive ) { m_subtractive = aSubtractive; }
|
||||
void SetExclusiveOr( bool aExclusiveOr ) { m_exclusiveOr = aExclusiveOr; }
|
||||
|
||||
void SetMode( SELECTION_MODE aMode ) { m_mode = aMode; }
|
||||
SELECTION_MODE GetMode() const { return m_mode; }
|
||||
|
||||
void SetPoly( SHAPE_LINE_CHAIN& aPoly ) { m_shape_poly = aPoly; }
|
||||
SHAPE_LINE_CHAIN& GetPoly() { return m_shape_poly; }
|
||||
|
||||
void ViewDraw( int aLayer, KIGFX::VIEW* aView ) const override final;
|
||||
|
||||
private:
|
||||
@ -90,7 +97,10 @@ private:
|
||||
bool m_subtractive;
|
||||
bool m_exclusiveOr;
|
||||
|
||||
VECTOR2I m_origin, m_end;
|
||||
SELECTION_MODE m_mode;
|
||||
|
||||
VECTOR2I m_origin, m_end; // Used for box selection
|
||||
SHAPE_LINE_CHAIN m_shape_poly; // Used for lasso selection
|
||||
};
|
||||
|
||||
} // PREVIEW
|
||||
|
@ -213,6 +213,16 @@ public:
|
||||
/// Select a single item under the cursor position
|
||||
static TOOL_ACTION selectionCursor;
|
||||
|
||||
/// Run a box selection tool in a fixed mode
|
||||
static TOOL_ACTION selectInsideRectangle;
|
||||
static TOOL_ACTION selectTouchingRectangle;
|
||||
|
||||
/// Run a lasso selection tool
|
||||
static TOOL_ACTION selectInsideLasso;
|
||||
static TOOL_ACTION selectTouchingLasso;
|
||||
static TOOL_ACTION selectAutoLasso;
|
||||
static TOOL_ACTION selectTouchingPath;
|
||||
|
||||
/// Clear the current selection
|
||||
static TOOL_ACTION selectionClear;
|
||||
|
||||
|
@ -34,6 +34,16 @@ class COLLECTOR;
|
||||
class KIID;
|
||||
|
||||
|
||||
enum class SELECTION_MODE
|
||||
{
|
||||
INSIDE_RECTANGLE,
|
||||
TOUCHING_RECTANGLE,
|
||||
INSIDE_LASSO,
|
||||
TOUCHING_LASSO,
|
||||
TOUCHING_PATH
|
||||
};
|
||||
|
||||
|
||||
class SELECTION_TOOL : public TOOL_INTERACTIVE, public wxEvtHandler
|
||||
{
|
||||
public:
|
||||
|
@ -522,6 +522,11 @@ public:
|
||||
m_param = aParam;
|
||||
}
|
||||
|
||||
bool HasParameter() const
|
||||
{
|
||||
return m_param.has_value();
|
||||
}
|
||||
|
||||
std::optional<int> GetCommandId() const
|
||||
{
|
||||
return m_commandId;
|
||||
|
@ -34,6 +34,10 @@
|
||||
#include <stdlib.h> // for abs
|
||||
#include <math/box2.h>
|
||||
#include <geometry/eda_angle.h>
|
||||
#include <geometry/shape_line_chain.h>
|
||||
#include <geometry/shape_rect.h>
|
||||
#include <geometry/shape_simple.h>
|
||||
#include <geometry/shape_compound.h>
|
||||
|
||||
/**
|
||||
* @return the number of segments to approximate a arc by segments
|
||||
@ -226,4 +230,40 @@ bool BoxHitTest( const VECTOR2I& aHitPoint, const BOX2I& aHittee, int aAccuracy
|
||||
* @param aAccuracy - The accuracy of the hit test.
|
||||
*/
|
||||
bool BoxHitTest( const BOX2I& aHitter, const BOX2I& aHittee, bool aHitteeContained, int aAccuracy );
|
||||
}; // namespace KIGEOM
|
||||
|
||||
/**
|
||||
* Perform a shape-to-box hit test.
|
||||
*
|
||||
* @param aHitter - The selection shape that is either hitting or containing the hittee.
|
||||
* @param aHittee - The box that is either being hit or contained by the hitter
|
||||
* (this is possibly an object's bounding box).
|
||||
* @param aHitteeContained - True if the hittee is tested for total containment,
|
||||
* false if it is tested for intersection.
|
||||
*/
|
||||
bool BoxHitTest( const SHAPE_LINE_CHAIN& aHitter, const BOX2I& aHittee, bool aHitteeContained );
|
||||
|
||||
/**
|
||||
* Perform a shape-to-box hit test with rotated box.
|
||||
*
|
||||
* @param aHitter - The selection shape that is either hitting or containing the hittee.
|
||||
* @param aHittee - The box that is either being hit or contained by the hitter
|
||||
* (this is possibly an object's bounding box).
|
||||
* @param aHitteeRotation - The rotation of the hittee box.
|
||||
* @param aHitteeRotationCenter - The center of the hittee box rotation.
|
||||
* @param aHitteeContained - True if the hittee is tested for total containment,
|
||||
* false if it is tested for intersection.
|
||||
*/
|
||||
bool BoxHitTest( const SHAPE_LINE_CHAIN& aHitter, const BOX2I& aHittee, const EDA_ANGLE& aHitteeRotation,
|
||||
const VECTOR2I& aHitteeRotationCenter, bool aHitteeContained );
|
||||
|
||||
/**
|
||||
* Perform a shape-to-shape hit test.
|
||||
*
|
||||
* @param aHitter - The selection shape that is either hitting or containing the hittee.
|
||||
* @param aHittee - The shape that is either being hit or contained by the hitter
|
||||
* (this is possibly an object's bounding box).
|
||||
* @param aHitteeContained - True if the hittee is tested for total containment,
|
||||
* false if it is tested for intersection.
|
||||
*/
|
||||
bool ShapeHitTest( const SHAPE_LINE_CHAIN& aHitter, const SHAPE& aHittee, bool aHitteeContained );
|
||||
}; // namespace KIGEOM
|
||||
|
@ -660,6 +660,7 @@ public:
|
||||
VECTOR2I m_origin;
|
||||
};
|
||||
|
||||
bool Intersects( const SEG& aSeg) const;
|
||||
bool Intersects( const SHAPE_LINE_CHAIN& aChain ) const;
|
||||
|
||||
/**
|
||||
|
@ -215,4 +215,105 @@ bool KIGEOM::BoxHitTest( const BOX2I& aHitter, const BOX2I& aHittee, bool aHitte
|
||||
return hitter.Contains( aHittee );
|
||||
|
||||
return hitter.Intersects( aHittee );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool KIGEOM::BoxHitTest( const SHAPE_LINE_CHAIN& aHitter, const BOX2I& aHittee, bool aHitteeContained )
|
||||
{
|
||||
SHAPE_RECT bbox( aHittee );
|
||||
|
||||
return KIGEOM::ShapeHitTest( aHitter, bbox, aHitteeContained );
|
||||
}
|
||||
|
||||
|
||||
bool KIGEOM::BoxHitTest( const SHAPE_LINE_CHAIN& aHitter, const BOX2I& aHittee, const EDA_ANGLE& aHitteeRotation,
|
||||
const VECTOR2I& aHitteeRotationCenter, bool aHitteeContained )
|
||||
{
|
||||
// Optimization: use SHAPE_RECT collision test if possible
|
||||
if( aHitteeRotation.IsZero() )
|
||||
{
|
||||
return KIGEOM::BoxHitTest( aHitter, aHittee, aHitteeContained );
|
||||
}
|
||||
else if( aHitteeRotation.IsCardinal() )
|
||||
{
|
||||
BOX2I box = aHittee.GetBoundingBoxRotated( aHitteeRotationCenter, aHitteeRotation );
|
||||
return KIGEOM::BoxHitTest( aHitter, box, aHitteeContained );
|
||||
}
|
||||
|
||||
// Non-cardinal angle: convert to simple polygon and rotate
|
||||
const std::vector<VECTOR2I> corners =
|
||||
{
|
||||
aHittee.GetOrigin(),
|
||||
VECTOR2I( aHittee.GetRight(), aHittee.GetTop() ),
|
||||
aHittee.GetEnd(),
|
||||
VECTOR2I( aHittee.GetLeft(), aHittee.GetBottom() )
|
||||
};
|
||||
|
||||
SHAPE_SIMPLE shape( corners );
|
||||
shape.Rotate( aHitteeRotation, aHitteeRotationCenter );
|
||||
|
||||
return KIGEOM::ShapeHitTest( aHitter, shape, aHitteeContained );
|
||||
}
|
||||
|
||||
|
||||
bool KIGEOM::ShapeHitTest( const SHAPE_LINE_CHAIN& aHitter, const SHAPE& aHittee, bool aHitteeContained )
|
||||
{
|
||||
// Check if the selection polygon collides with any of the hittee's subshapes.
|
||||
auto collidesAny =
|
||||
[&]()
|
||||
{
|
||||
return aHittee.Collide( &aHitter );
|
||||
};
|
||||
|
||||
// Check if the selection polygon collides with all of the hittee's subshapes.
|
||||
auto collidesAll =
|
||||
[&]()
|
||||
{
|
||||
if( const auto compoundHittee = dynamic_cast<const SHAPE_COMPOUND*>( &aHittee ) )
|
||||
{
|
||||
// If the hittee is a compound shape, all subshapes must collide.
|
||||
return std::ranges::all_of(
|
||||
compoundHittee->Shapes(),
|
||||
[&]( const SHAPE* subshape )
|
||||
{
|
||||
return subshape && subshape->Collide( &aHitter );
|
||||
} );
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the hittee is a simple shape, we can check it directly.
|
||||
return aHittee.Collide( &aHitter );
|
||||
}
|
||||
};
|
||||
|
||||
// Check if the selection polygon outline collides with the hittee's shape.
|
||||
auto intersectsAny =
|
||||
[&]()
|
||||
{
|
||||
const int count = aHitter.SegmentCount();
|
||||
|
||||
for( int i = 0; i < count; ++i )
|
||||
{
|
||||
if( aHittee.Collide( aHitter.CSegment( i ) ) )
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
if( aHitter.IsClosed() )
|
||||
{
|
||||
if( aHitteeContained )
|
||||
// Containing polygon - all of the subshapes must collide with the selection polygon,
|
||||
// but none of them can intersect its outline.
|
||||
return collidesAll() && !intersectsAny();
|
||||
else
|
||||
// Touching polygon - any of the subshapes should collide with the selection polygon.
|
||||
return collidesAny();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Touching (poly)line - any of the subshapes should intersect the selection polyline.
|
||||
return intersectsAny();
|
||||
}
|
||||
}
|
||||
|
@ -1745,6 +1745,18 @@ int SHAPE_LINE_CHAIN::Intersect( const SEG& aSeg, INTERSECTIONS& aIp ) const
|
||||
}
|
||||
|
||||
|
||||
bool SHAPE_LINE_CHAIN::Intersects( const SEG& aSeg ) const
|
||||
{
|
||||
for( int s = 0; s < SegmentCount(); s++ )
|
||||
{
|
||||
if( CSegment( s ).Intersects( aSeg ) )
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static inline void addIntersection( SHAPE_LINE_CHAIN::INTERSECTIONS& aIps, int aPc,
|
||||
const SHAPE_LINE_CHAIN::INTERSECTION& aP )
|
||||
{
|
||||
|
@ -378,6 +378,8 @@ bool PL_SELECTION_TOOL::selectMultiple()
|
||||
area.SetAdditive( m_drag_additive );
|
||||
area.SetSubtractive( m_drag_subtractive );
|
||||
area.SetExclusiveOr( false );
|
||||
area.SetMode( windowSelection ? SELECTION_MODE::INSIDE_RECTANGLE
|
||||
: SELECTION_MODE::TOUCHING_RECTANGLE );
|
||||
|
||||
view->SetVisible( &area, true );
|
||||
view->Update( &area );
|
||||
|
@ -43,6 +43,7 @@
|
||||
#include <geometry/convex_hull.h>
|
||||
#include <geometry/shape_segment.h>
|
||||
#include <geometry/shape_simple.h>
|
||||
#include <geometry/geometry_utils.h>
|
||||
#include <i18n_utility.h>
|
||||
#include <lset.h>
|
||||
#include <macros.h>
|
||||
@ -1961,6 +1962,49 @@ bool FOOTPRINT::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) co
|
||||
}
|
||||
|
||||
|
||||
bool FOOTPRINT::HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
|
||||
{
|
||||
using std::ranges::all_of;
|
||||
using std::ranges::any_of;
|
||||
|
||||
// If there are no pads, zones, or drawings, test footprint text instead.
|
||||
if( m_pads.empty() && m_zones.empty() && m_drawings.empty() )
|
||||
return KIGEOM::BoxHitTest( aPoly, GetBoundingBox( true ), aContained );
|
||||
|
||||
auto hitTest =
|
||||
[&]( const auto* aItem )
|
||||
{
|
||||
return aItem && aItem->HitTest( aPoly, aContained );
|
||||
};
|
||||
|
||||
// Filter out text items from the drawings, since they are selectable on their own,
|
||||
// and we don't want to select the whole footprint when text is hit. TextBox items are NOT
|
||||
// selectable on their own, so they are not excluded here.
|
||||
auto drawings = m_drawings | std::views::filter( []( const auto* aItem )
|
||||
{
|
||||
return aItem && aItem->Type() != PCB_TEXT_T;
|
||||
} );
|
||||
|
||||
// Test pads, zones and drawings with text excluded. PCB fields are also selectable
|
||||
// on their own, so they don't get tested. Groups are not hit-tested, only their members.
|
||||
// Bitmaps aren't selectable since they aren't displayed.
|
||||
if( aContained )
|
||||
{
|
||||
// All items must be contained in the selection poly.
|
||||
return all_of( drawings, hitTest )
|
||||
&& all_of( m_pads, hitTest )
|
||||
&& all_of( m_zones, hitTest );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Any item intersecting the selection poly is sufficient.
|
||||
return any_of( drawings, hitTest )
|
||||
|| any_of( m_pads, hitTest )
|
||||
|| any_of( m_zones, hitTest );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PAD* FOOTPRINT::FindPadByNumber( const wxString& aPadNumber, PAD* aSearchAfterMe ) const
|
||||
{
|
||||
bool can_select = aSearchAfterMe ? false : true;
|
||||
|
@ -607,6 +607,8 @@ public:
|
||||
|
||||
bool HitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const override;
|
||||
|
||||
bool HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const override;
|
||||
|
||||
/**
|
||||
* Test if the point hits one or more of the footprint elements on a given layer.
|
||||
*
|
||||
|
@ -1181,6 +1181,7 @@ void FOOTPRINT_EDIT_FRAME::setupTools()
|
||||
m_toolManager->RegisterTool( new COMMON_CONTROL );
|
||||
m_toolManager->RegisterTool( new COMMON_TOOLS );
|
||||
m_toolManager->RegisterTool( new PCB_SELECTION_TOOL );
|
||||
m_toolManager->RegisterTool( new PCB_LASSO_SELECTION_TOOL );
|
||||
m_toolManager->RegisterTool( new ZOOM_TOOL );
|
||||
m_toolManager->RegisterTool( new EDIT_TOOL );
|
||||
m_toolManager->RegisterTool( new PCB_EDIT_TABLE_TOOL );
|
||||
@ -1316,6 +1317,12 @@ void FOOTPRINT_EDIT_FRAME::setupUIConditions()
|
||||
mgr->SetConditions( ACTIONS::pasteSpecial, ENABLE( SELECTION_CONDITIONS::Idle && cond.NoActiveTool() ) );
|
||||
mgr->SetConditions( ACTIONS::doDelete, ENABLE( cond.HasItems() ) );
|
||||
mgr->SetConditions( ACTIONS::duplicate, ENABLE( cond.HasItems() ) );
|
||||
mgr->SetConditions( ACTIONS::selectInsideRectangle, ENABLE( cond.HasItems() ) );
|
||||
mgr->SetConditions( ACTIONS::selectTouchingRectangle,ENABLE( cond.HasItems() ) );
|
||||
mgr->SetConditions( ACTIONS::selectInsideLasso, ENABLE( cond.HasItems() ) );
|
||||
mgr->SetConditions( ACTIONS::selectTouchingLasso, ENABLE( cond.HasItems() ) );
|
||||
mgr->SetConditions( ACTIONS::selectAutoLasso, ENABLE( cond.HasItems() ) );
|
||||
mgr->SetConditions( ACTIONS::selectTouchingPath, ENABLE( cond.HasItems() ) );
|
||||
mgr->SetConditions( ACTIONS::selectAll, ENABLE( cond.HasItems() ) );
|
||||
mgr->SetConditions( ACTIONS::unselectAll, ENABLE( cond.HasItems() ) );
|
||||
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include <wx/debug.h>
|
||||
#include <gal/graphics_abstraction_layer.h>
|
||||
#include <geometry/shape_circle.h>
|
||||
#include <geometry/geometry_utils.h>
|
||||
#include <kiplatform/ui.h>
|
||||
#include <dialogs/dialog_unit_entry.h>
|
||||
#include <collectors.h>
|
||||
@ -384,6 +385,11 @@ public:
|
||||
return sel.Intersects( GetBoundingBox() );
|
||||
}
|
||||
|
||||
bool HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const override
|
||||
{
|
||||
return KIGEOM::ShapeHitTest( aPoly, getOutline(), aContained );
|
||||
}
|
||||
|
||||
const BOX2I ViewBBox() const override { return GetBoundingBox(); }
|
||||
|
||||
EDA_ITEM* Clone() const override { return new PCB_TUNING_PATTERN( *this ); }
|
||||
|
@ -106,8 +106,22 @@ void FOOTPRINT_EDIT_FRAME::doReCreateMenuBar()
|
||||
editMenu->Add( ACTIONS::doDelete );
|
||||
editMenu->Add( ACTIONS::duplicate );
|
||||
|
||||
|
||||
editMenu->AppendSeparator();
|
||||
editMenu->Add( ACTIONS::selectAll );
|
||||
|
||||
// Select Submenu
|
||||
ACTION_MENU* selectSubMenu = new ACTION_MENU( false, selTool );
|
||||
selectSubMenu->SetTitle( _( "&Select" ) );
|
||||
selectSubMenu->Add( ACTIONS::selectInsideRectangle );
|
||||
selectSubMenu->Add( ACTIONS::selectTouchingRectangle );
|
||||
selectSubMenu->Add( ACTIONS::selectInsideLasso );
|
||||
selectSubMenu->Add( ACTIONS::selectTouchingLasso );
|
||||
selectSubMenu->Add( ACTIONS::selectTouchingPath );
|
||||
selectSubMenu->AppendSeparator();
|
||||
selectSubMenu->Add( ACTIONS::selectAll );
|
||||
selectSubMenu->Add( ACTIONS::unselectAll );
|
||||
|
||||
editMenu->Add( selectSubMenu );
|
||||
|
||||
editMenu->AppendSeparator();
|
||||
editMenu->Add( PCB_ACTIONS::editTextAndGraphics );
|
||||
|
@ -179,8 +179,21 @@ void PCB_EDIT_FRAME::doReCreateMenuBar()
|
||||
editMenu->Add( ACTIONS::doDelete );
|
||||
|
||||
editMenu->AppendSeparator();
|
||||
editMenu->Add( ACTIONS::selectAll );
|
||||
editMenu->Add( ACTIONS::unselectAll );
|
||||
|
||||
// Select Submenu
|
||||
ACTION_MENU* selectSubMenu = new ACTION_MENU( false, selTool );
|
||||
selectSubMenu->SetTitle( _( "&Select" ) );
|
||||
|
||||
selectSubMenu->Add( ACTIONS::selectInsideRectangle );
|
||||
selectSubMenu->Add( ACTIONS::selectTouchingRectangle );
|
||||
selectSubMenu->Add( ACTIONS::selectInsideLasso );
|
||||
selectSubMenu->Add( ACTIONS::selectTouchingLasso );
|
||||
selectSubMenu->Add( ACTIONS::selectTouchingPath );
|
||||
selectSubMenu->AppendSeparator();
|
||||
selectSubMenu->Add( ACTIONS::selectAll );
|
||||
selectSubMenu->Add( ACTIONS::unselectAll );
|
||||
|
||||
editMenu->Add( selectSubMenu );
|
||||
|
||||
editMenu->AppendSeparator();
|
||||
editMenu->Add( ACTIONS::find );
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include <geometry/shape_rect.h>
|
||||
#include <geometry/shape_compound.h>
|
||||
#include <geometry/shape_null.h>
|
||||
#include <geometry/geometry_utils.h>
|
||||
#include <layer_range.h>
|
||||
#include <string_utils.h>
|
||||
#include <i18n_utility.h>
|
||||
@ -1555,6 +1556,24 @@ bool PAD::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
|
||||
}
|
||||
|
||||
|
||||
bool PAD::HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
|
||||
{
|
||||
SHAPE_COMPOUND effectiveShape;
|
||||
|
||||
// Add padstack shapes
|
||||
Padstack().ForEachUniqueLayer(
|
||||
[&]( PCB_LAYER_ID aLayer )
|
||||
{
|
||||
effectiveShape.AddShape( GetEffectiveShape( aLayer ) );
|
||||
} );
|
||||
|
||||
// Add hole shape
|
||||
effectiveShape.AddShape( GetEffectiveHoleShape() );
|
||||
|
||||
return KIGEOM::ShapeHitTest( aPoly, effectiveShape, aContained );
|
||||
}
|
||||
|
||||
|
||||
int PAD::Compare( const PAD* aPadRef, const PAD* aPadCmp )
|
||||
{
|
||||
int diff;
|
||||
|
@ -823,7 +823,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;
|
||||
|
||||
/**
|
||||
* Recombines the pad with other graphical shapes in the footprint
|
||||
|
@ -36,6 +36,8 @@
|
||||
#include <geometry/shape_compound.h>
|
||||
#include <geometry/shape_circle.h>
|
||||
#include <geometry/shape_segment.h>
|
||||
#include <geometry/shape_rect.h>
|
||||
#include <geometry/geometry_utils.h>
|
||||
#include <settings/color_settings.h>
|
||||
#include <settings/settings_manager.h>
|
||||
#include <trigo.h>
|
||||
@ -725,6 +727,22 @@ bool PCB_DIMENSION_BASE::HitTest( const BOX2I& aRect, bool aContained, int aAccu
|
||||
}
|
||||
|
||||
|
||||
bool PCB_DIMENSION_BASE::HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
|
||||
{
|
||||
// Note: Can't use GetEffectiveShape() because we want text as BoundingBox, not as graphics.
|
||||
SHAPE_COMPOUND effShape;
|
||||
|
||||
// Add shapes
|
||||
for( const std::shared_ptr<SHAPE>& shape : GetShapes() )
|
||||
effShape.AddShape( shape );
|
||||
|
||||
if( aContained )
|
||||
return TextHitTest( aPoly, aContained ) && KIGEOM::ShapeHitTest( aPoly, effShape, aContained );
|
||||
else
|
||||
return TextHitTest( aPoly, aContained ) || KIGEOM::ShapeHitTest( aPoly, effShape, aContained );
|
||||
}
|
||||
|
||||
|
||||
const BOX2I PCB_DIMENSION_BASE::GetBoundingBox() const
|
||||
{
|
||||
BOX2I bBox;
|
||||
@ -1745,6 +1763,16 @@ const BOX2I PCB_DIM_CENTER::ViewBBox() const
|
||||
}
|
||||
|
||||
|
||||
void PCB_DIM_CENTER::updateText()
|
||||
{
|
||||
// Even if PCB_DIM_CENTER has no text, we still need to update its text position
|
||||
// so GetTextPos() users get a valid value. Required at least for lasso hit-testing.
|
||||
SetTextPos( m_start );
|
||||
|
||||
PCB_DIMENSION_BASE::updateText();
|
||||
}
|
||||
|
||||
|
||||
void PCB_DIM_CENTER::updateGeometry()
|
||||
{
|
||||
if( m_busy ) // Skeep reentrance that happens sometimes after calling updateText()
|
||||
|
@ -295,11 +295,12 @@ 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;
|
||||
|
||||
const BOX2I GetBoundingBox() const override;
|
||||
|
||||
std::shared_ptr<SHAPE> GetEffectiveShape( PCB_LAYER_ID aLayer,
|
||||
FLASHING aFlash = FLASHING::DEFAULT ) const override;
|
||||
std::shared_ptr<SHAPE> GetEffectiveShape( PCB_LAYER_ID aLayer = UNDEFINED_LAYER,
|
||||
FLASHING aFlash = FLASHING::DEFAULT ) const override;
|
||||
|
||||
wxString GetItemDescription( UNITS_PROVIDER* aUnitsProvider, bool aFull ) const override;
|
||||
|
||||
@ -719,6 +720,7 @@ public:
|
||||
protected:
|
||||
virtual void swapData( BOARD_ITEM* aImage ) override;
|
||||
|
||||
void updateText() override;
|
||||
void updateGeometry() override;
|
||||
};
|
||||
|
||||
|
@ -731,6 +731,7 @@ void PCB_EDIT_FRAME::setupTools()
|
||||
m_toolManager->RegisterTool( new COMMON_CONTROL );
|
||||
m_toolManager->RegisterTool( new COMMON_TOOLS );
|
||||
m_toolManager->RegisterTool( new PCB_SELECTION_TOOL );
|
||||
m_toolManager->RegisterTool( new PCB_LASSO_SELECTION_TOOL );
|
||||
m_toolManager->RegisterTool( new ZOOM_TOOL );
|
||||
m_toolManager->RegisterTool( new PCB_PICKER_TOOL );
|
||||
m_toolManager->RegisterTool( new ROUTER_TOOL );
|
||||
@ -832,6 +833,12 @@ void PCB_EDIT_FRAME::setupUIConditions()
|
||||
mgr->SetConditions( ACTIONS::copy, ENABLE( cond.HasItems() ) );
|
||||
mgr->SetConditions( ACTIONS::paste, ENABLE( SELECTION_CONDITIONS::Idle && cond.NoActiveTool() ) );
|
||||
mgr->SetConditions( ACTIONS::pasteSpecial, ENABLE( SELECTION_CONDITIONS::Idle && cond.NoActiveTool() ) );
|
||||
mgr->SetConditions( ACTIONS::selectInsideRectangle, ENABLE( cond.HasItems() ) );
|
||||
mgr->SetConditions( ACTIONS::selectTouchingRectangle, ENABLE( cond.HasItems() ) );
|
||||
mgr->SetConditions( ACTIONS::selectInsideLasso, ENABLE( cond.HasItems() ) );
|
||||
mgr->SetConditions( ACTIONS::selectTouchingLasso, ENABLE( cond.HasItems() ) );
|
||||
mgr->SetConditions( ACTIONS::selectAutoLasso, ENABLE( cond.HasItems() ) );
|
||||
mgr->SetConditions( ACTIONS::selectTouchingPath, ENABLE( cond.HasItems() ) );
|
||||
mgr->SetConditions( ACTIONS::selectAll, ENABLE( cond.HasItems() ) );
|
||||
mgr->SetConditions( ACTIONS::unselectAll, ENABLE( cond.HasItems() ) );
|
||||
mgr->SetConditions( ACTIONS::doDelete, ENABLE( cond.HasItems() ) );
|
||||
|
@ -256,6 +256,13 @@ bool PCB_GROUP::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) co
|
||||
}
|
||||
|
||||
|
||||
bool PCB_GROUP::HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
|
||||
{
|
||||
// Groups are selected by promoting a selection of one of their children
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
const BOX2I PCB_GROUP::GetBoundingBox() const
|
||||
{
|
||||
BOX2I bbox;
|
||||
|
@ -142,6 +142,9 @@ public:
|
||||
/// @copydoc EDA_ITEM::HitTest
|
||||
bool HitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const override;
|
||||
|
||||
/// @copydoc EDA_ITEM::HitTest
|
||||
bool HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const override;
|
||||
|
||||
/// @copydoc EDA_ITEM::GetBoundingBox
|
||||
const BOX2I GetBoundingBox() const override;
|
||||
|
||||
|
@ -90,6 +90,14 @@ public:
|
||||
return HitTestMarker( aRect, aContained, aAccuracy );
|
||||
}
|
||||
|
||||
bool HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const override
|
||||
{
|
||||
if( GetMarkerType() == MARKER_RATSNEST )
|
||||
return false;
|
||||
|
||||
return HitTestMarker( aPoly, aContained );
|
||||
}
|
||||
|
||||
EDA_ITEM* Clone() const override
|
||||
{
|
||||
return new PCB_MARKER( *this );
|
||||
|
@ -203,6 +203,12 @@ bool PCB_REFERENCE_IMAGE::HitTest( const BOX2I& aRect, bool aContained, int aAcc
|
||||
}
|
||||
|
||||
|
||||
bool PCB_REFERENCE_IMAGE::HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
|
||||
{
|
||||
return KIGEOM::BoxHitTest( aPoly, GetBoundingBox(), aContained );
|
||||
}
|
||||
|
||||
|
||||
BITMAPS PCB_REFERENCE_IMAGE::GetMenuImage() const
|
||||
{
|
||||
return BITMAPS::image;
|
||||
|
@ -100,6 +100,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;
|
||||
|
||||
EDA_ITEM* Clone() const override;
|
||||
|
||||
|
@ -130,6 +130,11 @@ public:
|
||||
return hitTest( aRect, aContained, aAccuracy );
|
||||
}
|
||||
|
||||
bool HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const override
|
||||
{
|
||||
return hitTest( aPoly, aContained );
|
||||
}
|
||||
|
||||
void Normalize() override;
|
||||
|
||||
/**
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include <geometry/shape_simple.h>
|
||||
#include <geometry/shape_segment.h>
|
||||
#include <geometry/shape_compound.h>
|
||||
#include <geometry/geometry_utils.h>
|
||||
|
||||
|
||||
PCB_TABLE::PCB_TABLE( BOARD_ITEM* aParent, int aLineWidth ) :
|
||||
@ -491,6 +492,12 @@ bool PCB_TABLE::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) co
|
||||
}
|
||||
|
||||
|
||||
bool PCB_TABLE::HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
|
||||
{
|
||||
return KIGEOM::ShapeHitTest( aPoly, *GetEffectiveShape(), aContained );
|
||||
}
|
||||
|
||||
|
||||
void PCB_TABLE::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
|
||||
{
|
||||
// Don't use GetShownText() here; we want to show the user the variable references
|
||||
|
@ -238,6 +238,8 @@ public:
|
||||
|
||||
bool HitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const override;
|
||||
|
||||
bool HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const override;
|
||||
|
||||
EDA_ITEM* Clone() const override
|
||||
{
|
||||
return new PCB_TABLE( *this );
|
||||
|
@ -38,6 +38,7 @@
|
||||
#include <trigo.h>
|
||||
#include <string_utils.h>
|
||||
#include <geometry/shape_compound.h>
|
||||
#include <geometry/geometry_utils.h>
|
||||
#include <callback_gal.h>
|
||||
#include <convert_basic_shapes_to_polygon.h>
|
||||
#include <api/api_enums.h>
|
||||
@ -381,6 +382,17 @@ bool PCB_TEXT::TextHitTest( const BOX2I& aRect, bool aContains, int aAccuracy )
|
||||
}
|
||||
|
||||
|
||||
bool PCB_TEXT::TextHitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
|
||||
{
|
||||
BOX2I rect = GetTextBox( nullptr );
|
||||
|
||||
if( IsKnockout() )
|
||||
rect.Inflate( getKnockoutMargin() );
|
||||
|
||||
return KIGEOM::BoxHitTest( aPoly, rect, GetDrawRotation(), GetDrawPos(), aContained );
|
||||
}
|
||||
|
||||
|
||||
void PCB_TEXT::Rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle )
|
||||
{
|
||||
VECTOR2I pt = GetTextPos();
|
||||
|
@ -106,6 +106,7 @@ public:
|
||||
|
||||
bool TextHitTest( const VECTOR2I& aPoint, int aAccuracy = 0 ) const override;
|
||||
bool TextHitTest( const BOX2I& aRect, bool aContains, int aAccuracy = 0 ) const override;
|
||||
bool TextHitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const;
|
||||
|
||||
bool HitTest( const VECTOR2I& aPosition, int aAccuracy ) const override
|
||||
{
|
||||
@ -117,6 +118,11 @@ public:
|
||||
return TextHitTest( aRect, aContained, aAccuracy );
|
||||
}
|
||||
|
||||
bool HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const override
|
||||
{
|
||||
return TextHitTest( aPoly, aContained );
|
||||
}
|
||||
|
||||
wxString GetClass() const override
|
||||
{
|
||||
return wxT( "PCB_TEXT" );
|
||||
|
@ -33,6 +33,8 @@
|
||||
#include <trigo.h>
|
||||
#include <string_utils.h>
|
||||
#include <geometry/shape_compound.h>
|
||||
#include <geometry/shape_rect.h>
|
||||
#include <geometry/geometry_utils.h>
|
||||
#include <callback_gal.h>
|
||||
#include <convert_basic_shapes_to_polygon.h>
|
||||
#include <macros.h>
|
||||
@ -583,6 +585,12 @@ bool PCB_TEXTBOX::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy )
|
||||
}
|
||||
|
||||
|
||||
bool PCB_TEXTBOX::HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
|
||||
{
|
||||
return PCB_SHAPE::HitTest( aPoly, aContained );
|
||||
}
|
||||
|
||||
|
||||
wxString PCB_TEXTBOX::GetItemDescription( UNITS_PROVIDER* aUnitsProvider, bool aFull ) const
|
||||
{
|
||||
return wxString::Format( _( "PCB Text Box '%s' on %s" ),
|
||||
|
@ -114,6 +114,8 @@ public:
|
||||
|
||||
bool HitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const override;
|
||||
|
||||
bool HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const override;
|
||||
|
||||
wxString GetClass() const override
|
||||
{
|
||||
return wxT( "PCB_TEXTBOX" );
|
||||
|
@ -2085,6 +2085,12 @@ bool PCB_VIA::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) cons
|
||||
}
|
||||
|
||||
|
||||
bool PCB_TRACK::HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
|
||||
{
|
||||
return KIGEOM::ShapeHitTest( aPoly, *GetEffectiveShape(), aContained );
|
||||
}
|
||||
|
||||
|
||||
wxString PCB_TRACK::GetItemDescription( UNITS_PROVIDER* aUnitsProvider, bool aFull ) const
|
||||
{
|
||||
return wxString::Format( Type() == PCB_ARC_T ? _("Track (arc) %s on %s, length %s" )
|
||||
|
@ -246,6 +246,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 ApproxCollinear( const PCB_TRACK& aTrack );
|
||||
|
||||
|
@ -83,7 +83,12 @@ std::optional<TOOLBAR_CONFIGURATION> FOOTPRINT_EDIT_TOOLBAR_SETTINGS::DefaultToo
|
||||
break;
|
||||
|
||||
case TOOLBAR_LOC::RIGHT:
|
||||
config.AppendAction( ACTIONS::selectionTool );
|
||||
config.AppendAction( ACTIONS::selectionTool )
|
||||
.AppendGroup( TOOLBAR_GROUP_CONFIG( _( "Lasso selection tools" ) )
|
||||
.AddAction( ACTIONS::selectAutoLasso )
|
||||
.AddAction( ACTIONS::selectInsideLasso )
|
||||
.AddAction( ACTIONS::selectTouchingLasso )
|
||||
.AddAction( ACTIONS::selectTouchingPath ) );
|
||||
|
||||
config.AppendSeparator()
|
||||
.AppendAction( PCB_ACTIONS::placePad )
|
||||
|
@ -188,6 +188,11 @@ std::optional<TOOLBAR_CONFIGURATION> PCB_EDIT_TOOLBAR_SETTINGS::DefaultToolbarCo
|
||||
|
||||
case TOOLBAR_LOC::RIGHT:
|
||||
config.AppendAction( ACTIONS::selectionTool )
|
||||
.AppendGroup( TOOLBAR_GROUP_CONFIG( _( "Lasso selection tools" ) )
|
||||
.AddAction( ACTIONS::selectAutoLasso )
|
||||
.AddAction( ACTIONS::selectInsideLasso )
|
||||
.AddAction( ACTIONS::selectTouchingLasso )
|
||||
.AddAction( ACTIONS::selectTouchingPath ) )
|
||||
.AppendAction( PCB_ACTIONS::localRatsnestTool );
|
||||
|
||||
config.AppendSeparator()
|
||||
|
@ -422,7 +422,7 @@ TOOL_ACTION PCB_ACTIONS::magneticSnapToggle( TOOL_ACTION_ARGS()
|
||||
|
||||
TOOL_ACTION PCB_ACTIONS::deleteLastPoint( TOOL_ACTION_ARGS()
|
||||
.Name( "pcbnew.InteractiveDrawing.deleteLastPoint" )
|
||||
.Scope( AS_CONTEXT )
|
||||
.Scope( AS_GLOBAL )
|
||||
.DefaultHotkey( WXK_BACK )
|
||||
.FriendlyName( _( "Delete Last Point" ) )
|
||||
.Tooltip( _( "Delete the last point added to the current item" ) )
|
||||
|
@ -54,7 +54,6 @@ using namespace std::placeholders;
|
||||
#include <dialogs/dialog_locked_items_query.h>
|
||||
#include <class_draw_panel_gal.h>
|
||||
#include <view/view_controls.h>
|
||||
#include <preview_items/selection_area.h>
|
||||
#include <gal/painter.h>
|
||||
#include <router/router_tool.h>
|
||||
#include <pcbnew_settings.h>
|
||||
@ -68,6 +67,7 @@ using namespace std::placeholders;
|
||||
#include <connectivity/connectivity_data.h>
|
||||
#include <ratsnest/ratsnest_data.h>
|
||||
#include <footprint_viewer_frame.h>
|
||||
#include <geometry/geometry_utils.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/timer.h>
|
||||
#include <wx/log.h>
|
||||
@ -452,11 +452,11 @@ int PCB_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
|
||||
}
|
||||
else if( hasModifier() || dragAction == MOUSE_DRAG_ACTION::SELECT )
|
||||
{
|
||||
selectMultiple();
|
||||
SelectRectArea( aEvent );
|
||||
}
|
||||
else if( m_selection.Empty() && dragAction != MOUSE_DRAG_ACTION::DRAG_ANY )
|
||||
{
|
||||
selectMultiple();
|
||||
SelectRectArea( aEvent );
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -485,7 +485,7 @@ int PCB_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
|
||||
aCollector.Remove( item );
|
||||
};
|
||||
|
||||
// See if we can drag before falling back to selectMultiple()
|
||||
// See if we can drag before falling back to SelectRectArea()
|
||||
bool doDrag = false;
|
||||
|
||||
if( evt->HasPosition() )
|
||||
@ -522,7 +522,7 @@ int PCB_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
|
||||
else
|
||||
{
|
||||
// Otherwise drag a selection box
|
||||
selectMultiple();
|
||||
SelectRectArea( aEvent );
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -946,6 +946,19 @@ const TOOL_ACTION* allowedActions[] = { &ACTIONS::panUp, &ACTIONS::panD
|
||||
&ACTIONS::zoomFitObjects, nullptr };
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool PCB_SELECTION_TOOL::selectTableCells( PCB_TABLE* aTable )
|
||||
{
|
||||
bool cancelled = false; // Was the tool canceled while it was running?
|
||||
@ -1030,14 +1043,7 @@ bool PCB_SELECTION_TOOL::selectTableCells( PCB_TABLE* aTable )
|
||||
else
|
||||
{
|
||||
// Allow some actions for navigation
|
||||
for( int i = 0; allowedActions[i]; ++i )
|
||||
{
|
||||
if( evt->IsAction( allowedActions[i] ) )
|
||||
{
|
||||
evt->SetPassEvent();
|
||||
break;
|
||||
}
|
||||
}
|
||||
passEvent( evt, allowedActions );
|
||||
}
|
||||
}
|
||||
|
||||
@ -1052,31 +1058,46 @@ bool PCB_SELECTION_TOOL::selectTableCells( PCB_TABLE* aTable )
|
||||
}
|
||||
|
||||
|
||||
bool PCB_SELECTION_TOOL::selectMultiple()
|
||||
int PCB_SELECTION_TOOL::SelectRectArea( const TOOL_EVENT& aEvent )
|
||||
{
|
||||
bool cancelled = false; // Was the tool canceled while it was running?
|
||||
m_multiple = true; // Multiple selection mode is active
|
||||
KIGFX::VIEW* view = getView();
|
||||
KIGFX::VIEW* view = getView();
|
||||
bool fixedMode = false;
|
||||
SELECTION_MODE selectionMode = SELECTION_MODE::INSIDE_RECTANGLE;
|
||||
|
||||
if( aEvent.HasParameter() )
|
||||
{
|
||||
fixedMode = true;
|
||||
selectionMode = aEvent.Parameter<SELECTION_MODE>();
|
||||
}
|
||||
|
||||
KIGFX::PREVIEW::SELECTION_AREA area;
|
||||
view->Add( &area );
|
||||
|
||||
bool anyAdded = false;
|
||||
bool anySubtracted = false;
|
||||
|
||||
while( TOOL_EVENT* evt = Wait() )
|
||||
{
|
||||
/* 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 greedySelection = area.GetEnd().x < area.GetOrigin().x;
|
||||
if( !fixedMode )
|
||||
{
|
||||
int width = area.GetEnd().x - area.GetOrigin().x;
|
||||
|
||||
if( view->IsMirroredX() )
|
||||
greedySelection = !greedySelection;
|
||||
/* 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 touchingSelection = width >= 0 ? false : true;
|
||||
|
||||
m_frame->GetCanvas()->SetCurrentCursor( !greedySelection ? KICURSOR::SELECT_WINDOW
|
||||
: KICURSOR::SELECT_LASSO );
|
||||
if( view->IsMirroredX() )
|
||||
touchingSelection = !touchingSelection;
|
||||
|
||||
selectionMode = touchingSelection ? SELECTION_MODE::TOUCHING_RECTANGLE
|
||||
: SELECTION_MODE::INSIDE_RECTANGLE;
|
||||
}
|
||||
|
||||
if( selectionMode == SELECTION_MODE::INSIDE_RECTANGLE )
|
||||
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::SELECT_WINDOW );
|
||||
else
|
||||
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::SELECT_LASSO );
|
||||
|
||||
if( evt->IsCancelInteractive() || evt->IsActivate() )
|
||||
{
|
||||
@ -1090,8 +1111,8 @@ bool PCB_SELECTION_TOOL::selectMultiple()
|
||||
{
|
||||
if( m_selection.GetSize() > 0 )
|
||||
{
|
||||
anySubtracted = true;
|
||||
ClearSelection( true /*quiet mode*/ );
|
||||
m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
|
||||
}
|
||||
}
|
||||
|
||||
@ -1101,6 +1122,7 @@ bool PCB_SELECTION_TOOL::selectMultiple()
|
||||
area.SetAdditive( m_drag_additive );
|
||||
area.SetSubtractive( m_drag_subtractive );
|
||||
area.SetExclusiveOr( false );
|
||||
area.SetMode( selectionMode );
|
||||
|
||||
view->SetVisible( &area, true );
|
||||
view->Update( &area );
|
||||
@ -1114,133 +1136,20 @@ bool PCB_SELECTION_TOOL::selectMultiple()
|
||||
// End drawing the selection box
|
||||
view->SetVisible( &area, false );
|
||||
|
||||
std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> candidates;
|
||||
BOX2I selectionRect = area.ViewBBox();
|
||||
view->Query( selectionRect, candidates ); // Get the list of nearby items
|
||||
SelectMultiple( area, m_subtractive, m_exclusive_or );
|
||||
|
||||
selectionRect.Normalize();
|
||||
|
||||
GENERAL_COLLECTOR collector;
|
||||
GENERAL_COLLECTOR padsCollector;
|
||||
std::set<EDA_ITEM*> group_items;
|
||||
|
||||
for( PCB_GROUP* group : board()->Groups() )
|
||||
{
|
||||
// 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( !greedySelection && selectionRect.Contains( group->GetBoundingBox() )
|
||||
&& newset.size() )
|
||||
{
|
||||
for( EDA_ITEM* group_item : newset )
|
||||
{
|
||||
if( !group_item->IsBOARD_ITEM() )
|
||||
continue;
|
||||
|
||||
if( Selectable( static_cast<BOARD_ITEM*>( group_item ) ) )
|
||||
collector.Append( *newset.begin() );
|
||||
}
|
||||
}
|
||||
|
||||
for( EDA_ITEM* group_item : newset )
|
||||
group_items.emplace( group_item );
|
||||
}
|
||||
|
||||
for( const auto& [item, layer] : candidates )
|
||||
{
|
||||
if( !item->IsBOARD_ITEM() )
|
||||
continue;
|
||||
|
||||
BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
|
||||
|
||||
if( Selectable( boardItem ) && boardItem->HitTest( selectionRect, !greedySelection )
|
||||
&& ( greedySelection || !group_items.count( boardItem ) ) )
|
||||
{
|
||||
if( boardItem->Type() == PCB_PAD_T && !m_isFootprintEditor )
|
||||
padsCollector.Append( boardItem );
|
||||
else
|
||||
collector.Append( boardItem );
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the stateful filter
|
||||
FilterCollectedItems( collector, true );
|
||||
|
||||
FilterCollectorForHierarchy( collector, true );
|
||||
|
||||
// If we selected nothing but pads, allow them to be selected
|
||||
if( collector.GetCount() == 0 )
|
||||
{
|
||||
collector = padsCollector;
|
||||
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;
|
||||
} );
|
||||
|
||||
for( EDA_ITEM* i : collector )
|
||||
{
|
||||
if( !i->IsBOARD_ITEM() )
|
||||
continue;
|
||||
|
||||
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( i );
|
||||
|
||||
if( m_subtractive || ( m_exclusive_or && item->IsSelected() ) )
|
||||
{
|
||||
unselect( item );
|
||||
anySubtracted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
select( item );
|
||||
anyAdded = true;
|
||||
}
|
||||
}
|
||||
|
||||
m_selection.SetIsHover( false );
|
||||
|
||||
// Inform other potentially interested tools
|
||||
if( anyAdded )
|
||||
m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
|
||||
else if( anySubtracted )
|
||||
m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
|
||||
|
||||
break; // Stop waiting for events
|
||||
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;
|
||||
}
|
||||
}
|
||||
passEvent( evt, allowedActions );
|
||||
}
|
||||
|
||||
getViewControls()->SetAutoPan( false );
|
||||
|
||||
// Stop drawing the selection box
|
||||
view->Remove( &area );
|
||||
m_multiple = false; // Multiple selection mode is inactive
|
||||
m_multiple = false; // Multiple selection mode is inactive
|
||||
|
||||
if( !cancelled )
|
||||
m_selection.ClearReferencePoint();
|
||||
@ -1251,6 +1160,141 @@ bool PCB_SELECTION_TOOL::selectMultiple()
|
||||
}
|
||||
|
||||
|
||||
void PCB_SELECTION_TOOL::SelectMultiple( KIGFX::PREVIEW::SELECTION_AREA& aArea, bool aSubtractive,
|
||||
bool aExclusiveOr )
|
||||
{
|
||||
KIGFX::VIEW* view = getView();
|
||||
|
||||
bool anyAdded = false;
|
||||
bool anySubtracted = false;
|
||||
|
||||
SELECTION_MODE selectionMode = aArea.GetMode();
|
||||
bool containedMode = ( selectionMode == SELECTION_MODE::INSIDE_RECTANGLE
|
||||
|| selectionMode == SELECTION_MODE::INSIDE_LASSO ) ? true : false;
|
||||
bool boxMode = ( selectionMode == SELECTION_MODE::INSIDE_RECTANGLE
|
||||
|| selectionMode == SELECTION_MODE::TOUCHING_RECTANGLE ) ? true : false;
|
||||
|
||||
std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> candidates;
|
||||
BOX2I selectionBox = aArea.ViewBBox();
|
||||
view->Query( selectionBox, candidates ); // Get the list of nearby items
|
||||
|
||||
GENERAL_COLLECTOR collector;
|
||||
GENERAL_COLLECTOR padsCollector;
|
||||
std::set<EDA_ITEM*> group_items;
|
||||
|
||||
for( PCB_GROUP* group : board()->Groups() )
|
||||
{
|
||||
// The currently entered group does not get limited
|
||||
if( m_enteredGroup == group )
|
||||
continue;
|
||||
|
||||
std::unordered_set<EDA_ITEM*>& newset = group->GetItems();
|
||||
|
||||
auto boxContained =
|
||||
[&]( const BOX2I& aBox )
|
||||
{
|
||||
return boxMode ? selectionBox.Contains( aBox )
|
||||
: KIGEOM::BoxHitTest( aArea.GetPoly(), aBox, true );
|
||||
};
|
||||
|
||||
// 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( containedMode && boxContained( group->GetBoundingBox() ) && newset.size() )
|
||||
{
|
||||
for( EDA_ITEM* group_item : newset )
|
||||
{
|
||||
if( !group_item->IsBOARD_ITEM() )
|
||||
continue;
|
||||
|
||||
if( Selectable( static_cast<BOARD_ITEM*>( group_item ) ) )
|
||||
collector.Append( *newset.begin() );
|
||||
}
|
||||
}
|
||||
|
||||
for( EDA_ITEM* group_item : newset )
|
||||
group_items.emplace( group_item );
|
||||
}
|
||||
|
||||
auto hitTest =
|
||||
[&]( const EDA_ITEM* aItem )
|
||||
{
|
||||
return boxMode ? aItem->HitTest( selectionBox, containedMode )
|
||||
: aItem->HitTest( aArea.GetPoly(), containedMode );
|
||||
};
|
||||
|
||||
for( const auto& [item, layer] : candidates )
|
||||
{
|
||||
if( !item->IsBOARD_ITEM() )
|
||||
continue;
|
||||
|
||||
BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
|
||||
|
||||
if( Selectable( boardItem ) && hitTest( boardItem )
|
||||
&& ( !containedMode || !group_items.count( boardItem ) ) )
|
||||
{
|
||||
if( boardItem->Type() == PCB_PAD_T && !m_isFootprintEditor )
|
||||
padsCollector.Append( boardItem );
|
||||
else
|
||||
collector.Append( boardItem );
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the stateful filter
|
||||
FilterCollectedItems( collector, true );
|
||||
|
||||
FilterCollectorForHierarchy( collector, true );
|
||||
|
||||
// If we selected nothing but pads, allow them to be selected
|
||||
if( collector.GetCount() == 0 )
|
||||
{
|
||||
collector = padsCollector;
|
||||
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;
|
||||
} );
|
||||
|
||||
for( EDA_ITEM* i : collector )
|
||||
{
|
||||
if( !i->IsBOARD_ITEM() )
|
||||
continue;
|
||||
|
||||
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( i );
|
||||
|
||||
if( aSubtractive || ( aExclusiveOr && item->IsSelected() ) )
|
||||
{
|
||||
unselect( item );
|
||||
anySubtracted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
select( item );
|
||||
anyAdded = true;
|
||||
}
|
||||
}
|
||||
|
||||
m_selection.SetIsHover( false );
|
||||
|
||||
// Inform other potentially interested tools
|
||||
if( anyAdded )
|
||||
m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
|
||||
else if( anySubtracted )
|
||||
m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
|
||||
}
|
||||
|
||||
|
||||
int PCB_SELECTION_TOOL::disambiguateCursor( const TOOL_EVENT& aEvent )
|
||||
{
|
||||
wxMouseState keyboardState = wxGetMouseState();
|
||||
@ -4205,8 +4249,205 @@ void PCB_SELECTION_TOOL::setTransitions()
|
||||
Go( &PCB_SELECTION_TOOL::SelectRows, ACTIONS::selectRows.MakeEvent() );
|
||||
Go( &PCB_SELECTION_TOOL::SelectTable, ACTIONS::selectTable.MakeEvent() );
|
||||
|
||||
Go( &PCB_SELECTION_TOOL::SelectRectArea, ACTIONS::selectInsideRectangle.MakeEvent() );
|
||||
Go( &PCB_SELECTION_TOOL::SelectRectArea, ACTIONS::selectTouchingRectangle.MakeEvent() );
|
||||
Go( &PCB_SELECTION_TOOL::SelectAll, ACTIONS::selectAll.MakeEvent() );
|
||||
Go( &PCB_SELECTION_TOOL::UnselectAll, ACTIONS::unselectAll.MakeEvent() );
|
||||
|
||||
Go( &PCB_SELECTION_TOOL::disambiguateCursor, EVENTS::DisambiguatePoint );
|
||||
}
|
||||
|
||||
|
||||
PCB_LASSO_SELECTION_TOOL::PCB_LASSO_SELECTION_TOOL() :
|
||||
PCB_TOOL_BASE( "common.InteractiveLassoSelection" )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
PCB_LASSO_SELECTION_TOOL::~PCB_LASSO_SELECTION_TOOL()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
bool PCB_LASSO_SELECTION_TOOL::Init()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void PCB_LASSO_SELECTION_TOOL::Reset( RESET_REASON aReason )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
int PCB_LASSO_SELECTION_TOOL::SelectPolyArea( const TOOL_EVENT& aEvent )
|
||||
{
|
||||
bool cancelled = false; // Was the tool canceled while it was running?
|
||||
bool fixedMode = false;
|
||||
bool additive = false;
|
||||
bool subtractive = false;
|
||||
bool exclusiveOr = false;
|
||||
PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
|
||||
SELECTION_MODE selectionMode = SELECTION_MODE::TOUCHING_LASSO;
|
||||
|
||||
auto updateModifiersAndCursor =
|
||||
[&]( wxTimerEvent& aEvent )
|
||||
{
|
||||
KICURSOR cursor;
|
||||
wxMouseState keyboardState = wxGetMouseState();
|
||||
|
||||
subtractive = keyboardState.ControlDown() && keyboardState.ShiftDown();
|
||||
additive = !keyboardState.ControlDown() && keyboardState.ShiftDown();
|
||||
exclusiveOr = keyboardState.ControlDown() && !keyboardState.ShiftDown();
|
||||
|
||||
if( additive )
|
||||
cursor = KICURSOR::ADD;
|
||||
else if( subtractive )
|
||||
cursor = KICURSOR::SUBTRACT;
|
||||
else if( exclusiveOr )
|
||||
cursor = KICURSOR::XOR ;
|
||||
else if( selectionMode == SELECTION_MODE::INSIDE_LASSO )
|
||||
cursor = KICURSOR::SELECT_WINDOW;
|
||||
else
|
||||
cursor = KICURSOR::SELECT_LASSO;
|
||||
|
||||
frame()->GetCanvas()->SetCurrentCursor( cursor );
|
||||
};
|
||||
|
||||
// No events are sent for modifier keys, so we need to poll them using a timer.
|
||||
wxTimer timer;
|
||||
timer.Bind( wxEVT_TIMER, updateModifiersAndCursor );
|
||||
timer.Start( 100 );
|
||||
|
||||
if( aEvent.HasParameter() )
|
||||
{
|
||||
fixedMode = true;
|
||||
selectionMode = aEvent.Parameter<SELECTION_MODE>();
|
||||
}
|
||||
|
||||
SHAPE_LINE_CHAIN points;
|
||||
|
||||
if( selectionMode != SELECTION_MODE::TOUCHING_PATH )
|
||||
points.SetClosed( true );
|
||||
|
||||
KIGFX::PREVIEW::SELECTION_AREA area;
|
||||
view()->Add( &area );
|
||||
view()->SetVisible( &area, true );
|
||||
|
||||
controls()->SetAutoPan( true );
|
||||
|
||||
frame()->PushTool( aEvent );
|
||||
Activate();
|
||||
|
||||
while( TOOL_EVENT* evt = Wait() )
|
||||
{
|
||||
if( !fixedMode )
|
||||
{
|
||||
// Auto Mode: The selection mode depends on the drawing direction of the selection shape:
|
||||
// - Clockwise: Contained selection
|
||||
// - Counterclockwise: Touching selection
|
||||
double shapeArea = area.GetPoly().Area( false );
|
||||
bool isClockwise = shapeArea > 0 ? true : false;
|
||||
|
||||
// Flip the selection mode if the view is mirrored, but only if the area is non-zero.
|
||||
// A zero area means the selection shape is a line, so the mode should always be "touching".
|
||||
if( view()->IsMirroredX() && shapeArea != 0 )
|
||||
isClockwise = !isClockwise;
|
||||
|
||||
selectionMode = isClockwise ? SELECTION_MODE::INSIDE_LASSO
|
||||
: SELECTION_MODE::TOUCHING_LASSO;
|
||||
}
|
||||
|
||||
if( evt->IsCancelInteractive() || evt->IsActivate() )
|
||||
{
|
||||
// Cancel the selection
|
||||
cancelled = true;
|
||||
evt->SetPassEvent( false );
|
||||
|
||||
break;
|
||||
}
|
||||
else if( evt->IsDrag( BUT_LEFT ) // Lasso selection
|
||||
|| evt->IsClick( BUT_LEFT ) // Polygon selection
|
||||
|| evt->IsAction( &ACTIONS::cursorClick ) ) // Return key
|
||||
{
|
||||
// Add a point to the selection shape
|
||||
points.Append( evt->Position() );
|
||||
}
|
||||
else if( evt->IsDblClick( BUT_LEFT )
|
||||
|| evt->IsAction( &ACTIONS::cursorDblClick ) // End key
|
||||
|| evt->IsAction( &ACTIONS::finishInteractive ) )
|
||||
{
|
||||
// Finish the selection
|
||||
area.GetPoly().GenerateBBoxCache();
|
||||
|
||||
selectionTool->SelectMultiple( area, subtractive, exclusiveOr );
|
||||
|
||||
evt->SetPassEvent( false );
|
||||
|
||||
break;
|
||||
}
|
||||
else if( evt->IsAction( &PCB_ACTIONS::deleteLastPoint )
|
||||
|| evt->IsAction( &ACTIONS::doDelete )
|
||||
|| evt->IsAction( &ACTIONS::undo ) )
|
||||
{
|
||||
// Delete the last point in the selection shape
|
||||
if( points.GetPointCount() > 0 )
|
||||
{
|
||||
controls()->SetCursorPosition( points.CLastPoint() );
|
||||
points.Remove( points.GetPointCount() - 1 );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Allow some actions for navigation
|
||||
passEvent( evt, allowedActions );
|
||||
}
|
||||
|
||||
if( points.PointCount() > 0 )
|
||||
{
|
||||
// Clear existing selection if not in add/sub/xor mode
|
||||
if( !additive && !subtractive && !exclusiveOr )
|
||||
{
|
||||
if( !selectionTool->GetSelection().Empty() )
|
||||
{
|
||||
selectionTool->ClearSelection( true /*quiet mode*/ );
|
||||
m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw selection shape
|
||||
area.SetPoly( points );
|
||||
area.GetPoly().Append( m_toolMgr->GetMousePosition() );
|
||||
|
||||
area.SetAdditive( additive );
|
||||
area.SetSubtractive( subtractive );
|
||||
area.SetExclusiveOr( exclusiveOr );
|
||||
area.SetMode( selectionMode );
|
||||
|
||||
view()->Update( &area );
|
||||
}
|
||||
|
||||
frame()->PopTool( aEvent );
|
||||
|
||||
controls()->SetAutoPan( false );
|
||||
view()->SetVisible( &area, false );
|
||||
view()->Remove( &area ); // Stop drawing the selection shape
|
||||
frame()->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW ); // Reset cursor to default
|
||||
|
||||
if( !cancelled )
|
||||
selectionTool->GetSelection().ClearReferencePoint();
|
||||
|
||||
m_toolMgr->ProcessEvent( EVENTS::UninhibitSelectionEditing );
|
||||
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
|
||||
void PCB_LASSO_SELECTION_TOOL::setTransitions()
|
||||
{
|
||||
Go( &PCB_LASSO_SELECTION_TOOL::SelectPolyArea, ACTIONS::selectInsideLasso.MakeEvent() );
|
||||
Go( &PCB_LASSO_SELECTION_TOOL::SelectPolyArea, ACTIONS::selectTouchingLasso.MakeEvent() );
|
||||
Go( &PCB_LASSO_SELECTION_TOOL::SelectPolyArea, ACTIONS::selectAutoLasso.MakeEvent() );
|
||||
Go( &PCB_LASSO_SELECTION_TOOL::SelectPolyArea, ACTIONS::selectTouchingPath.MakeEvent() );
|
||||
}
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include <tool/actions.h>
|
||||
#include <math/vector2d.h>
|
||||
#include <project/board_project_settings.h>
|
||||
#include <preview_items/selection_area.h>
|
||||
#include <tool/action_menu.h>
|
||||
#include <tool/selection_tool.h>
|
||||
#include <tool/tool_menu.h>
|
||||
@ -119,6 +120,19 @@ public:
|
||||
///< Unselect all items on the board
|
||||
int UnselectAll( const TOOL_EVENT& aEvent );
|
||||
|
||||
/**
|
||||
* Handles drawing a selection box that allows multiple items to be selected simultaneously.
|
||||
*
|
||||
* @return true if the operation was canceled (i.e. a CancelEvent was received).
|
||||
*/
|
||||
int SelectRectArea( const TOOL_EVENT& aEvent );
|
||||
|
||||
/**
|
||||
* Selects multiple PCB items within a specified area.
|
||||
*/
|
||||
void SelectMultiple( KIGFX::PREVIEW::SELECTION_AREA& aArea, bool aSubtractive = false,
|
||||
bool aExclusiveOr = false );
|
||||
|
||||
/**
|
||||
* Take necessary actions to mark an item as found.
|
||||
*
|
||||
@ -295,13 +309,6 @@ private:
|
||||
bool selectCursor( bool aForceSelect = false,
|
||||
CLIENT_SELECTION_FILTER aClientFilter = nullptr );
|
||||
|
||||
/**
|
||||
* Handle drawing a selection box that allows one to select many items at the same time.
|
||||
*
|
||||
* @return true if the function was canceled (i.e. CancelEvent was received).
|
||||
*/
|
||||
bool selectMultiple();
|
||||
|
||||
bool selectTableCells( PCB_TABLE* aTable );
|
||||
|
||||
/**
|
||||
@ -475,4 +482,48 @@ private:
|
||||
std::unique_ptr<PRIV> m_priv;
|
||||
};
|
||||
|
||||
/**
|
||||
* The PCB_LASSO_SELECTION_TOOL is a tool that allows the user to select multiple items
|
||||
* by drawing a polygon, lasso or polyline on the PCB.
|
||||
* It is used for selecting items in a more flexible way than the standard rectangle selection.
|
||||
*/
|
||||
class PCB_LASSO_SELECTION_TOOL : public PCB_TOOL_BASE
|
||||
{
|
||||
public:
|
||||
PCB_LASSO_SELECTION_TOOL();
|
||||
~PCB_LASSO_SELECTION_TOOL();
|
||||
|
||||
/// @copydoc TOOL_BASE::Init()
|
||||
bool Init() override;
|
||||
|
||||
/// @copydoc TOOL_BASE::Reset()
|
||||
void Reset( RESET_REASON aReason ) override;
|
||||
|
||||
///< Set up handlers for various events.
|
||||
void setTransitions() override;
|
||||
|
||||
/**
|
||||
* Handles drawing a selection polygon (lasso) or polyline (path) that allows multiple items
|
||||
* to be selectedsimultaneously.
|
||||
* @return true if the operation was canceled (i.e. a CancelEvent was received).
|
||||
*/
|
||||
int SelectPolyArea( const TOOL_EVENT& aEvent );
|
||||
|
||||
protected:
|
||||
KIGFX::PCB_VIEW* view() const
|
||||
{
|
||||
return static_cast<KIGFX::PCB_VIEW*>( getView() );
|
||||
}
|
||||
|
||||
KIGFX::VIEW_CONTROLS* controls() const
|
||||
{
|
||||
return getViewControls();
|
||||
}
|
||||
|
||||
PCB_BASE_FRAME* frame() const
|
||||
{
|
||||
return getEditFrame<PCB_BASE_FRAME>();
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* PCB_SELECTION_TOOL_H */
|
||||
|
@ -765,6 +765,52 @@ bool ZONE::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
|
||||
}
|
||||
|
||||
|
||||
bool ZONE::HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
|
||||
{
|
||||
if( aContained )
|
||||
{
|
||||
auto outlineIntersectingSelection =
|
||||
[&]()
|
||||
{
|
||||
for( auto segment = m_Poly->IterateSegments(); segment; segment++ )
|
||||
{
|
||||
if( aPoly.Intersects( *segment ) )
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// In the case of contained selection, all vertices of the zone outline must be inside
|
||||
// the selection polygon, so we can check only the first vertex.
|
||||
auto vertexInsideSelection =
|
||||
[&]()
|
||||
{
|
||||
return aPoly.PointInside( m_Poly->CVertex( 0 ) );
|
||||
};
|
||||
|
||||
return vertexInsideSelection() && !outlineIntersectingSelection();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Touching selection - check if any segment of the zone contours collides with the
|
||||
// selection shape.
|
||||
for( auto segment = m_Poly->IterateSegmentsWithHoles(); segment; segment++ )
|
||||
{
|
||||
if( aPoly.PointInside( ( *segment ).A ) )
|
||||
return true;
|
||||
|
||||
if( aPoly.Intersects( *segment ) )
|
||||
return true;
|
||||
|
||||
// Note: aPoly.Collide() could be used instead of two test above, but it is 3x slower.
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::optional<int> ZONE::GetLocalClearance() const
|
||||
{
|
||||
return m_isRuleArea ? 0 : m_ZoneClearance;
|
||||
|
@ -448,10 +448,15 @@ public:
|
||||
SHAPE_POLY_SET::VERTEX_INDEX* aCornerHit = nullptr ) const;
|
||||
|
||||
/**
|
||||
* @copydoc BOARD_ITEM::HitTest(const BOX2I& aRect, bool aContained, int aAccuracy) const
|
||||
* @copydoc EDA_ITEM::HitTest(const BOX2I& aRect, bool aContained, int aAccuracy) const
|
||||
*/
|
||||
bool HitTest( const BOX2I& aRect, bool aContained = true, int aAccuracy = 0 ) const override;
|
||||
|
||||
/**
|
||||
* @copydoc EDA_ITEM::HitTest(const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
|
||||
*/
|
||||
bool HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const;
|
||||
|
||||
/**
|
||||
* Removes the zone filling.
|
||||
*
|
||||
|
@ -38,6 +38,7 @@
|
||||
#include <dialog_find.h>
|
||||
#include <dialog_filter_selection.h>
|
||||
#include <zone_filler.h>
|
||||
#include <preview_items/selection_area.h>
|
||||
|
||||
FP_LIB_TABLE GFootprintTable;
|
||||
|
||||
@ -262,9 +263,9 @@ bool PCB_SELECTION_TOOL::selectCursor( bool aForceSelect, CLIENT_SELECTION_FILTE
|
||||
}
|
||||
|
||||
|
||||
bool PCB_SELECTION_TOOL::selectMultiple()
|
||||
void PCB_SELECTION_TOOL::SelectMultiple( KIGFX::PREVIEW::SELECTION_AREA& aArea, bool aSubtractive,
|
||||
bool aExclusiveOr )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user