ADDED: Indication of filter blocking

If the selection filter has blocked all selections under the cursor,
show a subtle flash on which filter(s) did the blocking.  Helpful for
people getting frustrated by not being able to select locked items.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/20487
This commit is contained in:
Seth Hillbrand 2025-08-29 07:06:41 -07:00
parent 8a60893249
commit e8cec41355
17 changed files with 569 additions and 32 deletions

View File

@ -35,6 +35,7 @@
#include <project/project_file.h>
#include <symbol_editor/symbol_editor_settings.h>
#include <sch_draw_panel.h>
#include <widgets/panel_sch_selection_filter.h>
#include <sch_group.h>
#include <sch_view.h>
#include <sch_painter.h>
@ -942,3 +943,10 @@ SCH_SELECTION_TOOL* SCH_BASE_FRAME::GetSelectionTool()
return nullptr;
}
void SCH_BASE_FRAME::HighlightSelectionFilter( const SCH_SELECTION_FILTER_OPTIONS& aOptions )
{
SCH_SELECTION_FILTER_EVENT evt( aOptions );
wxPostEvent( this, evt );
}

View File

@ -55,6 +55,7 @@ class LIB_ID;
class SYMBOL_LIB_TABLE;
class EESCHEMA_SETTINGS;
class SYMBOL_EDITOR_SETTINGS;
struct SCH_SELECTION_FILTER_OPTIONS;
#ifndef __linux__
class NL_SCHEMATIC_PLUGIN;
@ -289,6 +290,8 @@ public:
void GetLibraryItemsForListDialog( wxArrayString& aHeaders, std::vector<wxArrayString>& aItemsToDisplay );
void HighlightSelectionFilter( const SCH_SELECTION_FILTER_OPTIONS& aOptions );
protected:
void handleActivateEvent( wxActivateEvent& aEvent ) override;

View File

@ -30,6 +30,7 @@
#include <sch_actions.h>
#include <sch_collectors.h>
#include <sch_selection_tool.h>
#include <sch_base_frame.h>
#include <eeschema_id.h>
#include <symbol_edit_frame.h>
#include <symbol_viewer_frame.h>
@ -480,9 +481,13 @@ int SCH_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
schframe->ClearFocus();
// Collect items at the clicked location (doesn't select them yet)
SCH_COLLECTOR collector;
SCH_COLLECTOR collector;
SCH_SELECTION_FILTER_OPTIONS rejected;
CollectHits( collector, evt->Position() );
narrowSelection( collector, evt->Position(), false );
size_t preFilterCount = collector.GetCount();
rejected.SetAll( false );
narrowSelection( collector, evt->Position(), false, false, &rejected );
if( m_selection.GetSize() != 0 && dynamic_cast<SCH_TABLECELL*>( m_selection.GetItem( 0 ) ) && m_additive
&& collector.GetCount() == 1 && dynamic_cast<SCH_TABLECELL*>( collector[0] ) )
@ -564,6 +569,12 @@ int SCH_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
if( !selCancelled )
{
if( collector.GetCount() == 0 && preFilterCount > 0 )
{
if( SCH_BASE_FRAME* frame = dynamic_cast<SCH_BASE_FRAME*>( m_frame ) )
frame->HighlightSelectionFilter( rejected );
}
selectPoint( collector, evt->Position(), nullptr, nullptr, m_additive,
m_subtractive, m_exclusive_or );
m_selection.SetIsHover( false );
@ -1028,7 +1039,7 @@ int SCH_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
if( CollectHits( collector, evt->Position() ) )
{
narrowSelection( collector, evt->Position(), false );
narrowSelection( collector, evt->Position(), false, false, nullptr );
if( collector.GetCount() == 1 && !hasModifier() )
{
@ -1339,7 +1350,8 @@ bool SCH_SELECTION_TOOL::CollectHits( SCH_COLLECTOR& aCollector, const VECTOR2I&
void SCH_SELECTION_TOOL::narrowSelection( SCH_COLLECTOR& collector, const VECTOR2I& aWhere,
bool aCheckLocked, bool aSelectedOnly )
bool aCheckLocked, bool aSelectedOnly,
SCH_SELECTION_FILTER_OPTIONS* aRejected )
{
SYMBOL_EDIT_FRAME* symbolEditorFrame = dynamic_cast<SYMBOL_EDIT_FRAME*>( m_frame );
@ -1378,11 +1390,13 @@ void SCH_SELECTION_TOOL::narrowSelection( SCH_COLLECTOR& collector, const VECTOR
if( aCheckLocked && collector[i]->IsLocked() )
{
if( aRejected )
aRejected->lockedItems = true;
collector.Remove( i );
continue;
}
if( !itemPassesFilter( collector[i] ) )
if( !itemPassesFilter( collector[i], aRejected ) )
{
collector.Remove( i );
continue;
@ -1539,7 +1553,18 @@ bool SCH_SELECTION_TOOL::SelectPoint( const VECTOR2I& aWhere,
if( !CollectHits( collector, aWhere, aScanTypes ) )
return false;
narrowSelection( collector, aWhere, aCheckLocked, aSubtract );
size_t preFilterCount = collector.GetCount();
SCH_SELECTION_FILTER_OPTIONS rejected;
rejected.SetAll( false );
narrowSelection( collector, aWhere, aCheckLocked, aSubtract, &rejected );
if( collector.GetCount() == 0 && preFilterCount > 0 )
{
if( SCH_BASE_FRAME* frame = dynamic_cast<SCH_BASE_FRAME*>( m_frame ) )
frame->HighlightSelectionFilter( rejected );
return false;
}
return selectPoint( collector, aWhere, aItem, aSelectionCancelledFlag, aAdd, aSubtract,
aExclusiveOr );
@ -1589,7 +1614,7 @@ int SCH_SELECTION_TOOL::SelectAll( const TOOL_EVENT& aEvent )
for( EDA_ITEM* item : collection )
{
if( Selectable( item ) && itemPassesFilter( item ) )
if( Selectable( item ) && itemPassesFilter( item, nullptr ) )
{
if( item->Type() == SCH_LINE_T )
item->SetFlags( STARTPOINT | ENDPOINT );
@ -1949,7 +1974,7 @@ void SCH_SELECTION_TOOL::filterCollectedItems( SCH_COLLECTOR& aCollector, bool a
for( EDA_ITEM* item : aCollector )
{
if( !itemPassesFilter( item ) )
if( !itemPassesFilter( item, nullptr ) )
rejected.insert( item );
}
@ -1958,7 +1983,7 @@ void SCH_SELECTION_TOOL::filterCollectedItems( SCH_COLLECTOR& aCollector, bool a
}
bool SCH_SELECTION_TOOL::itemPassesFilter( EDA_ITEM* aItem )
bool SCH_SELECTION_TOOL::itemPassesFilter( EDA_ITEM* aItem, SCH_SELECTION_FILTER_OPTIONS* aRejected )
{
if( !aItem )
return false;
@ -1977,20 +2002,32 @@ bool SCH_SELECTION_TOOL::itemPassesFilter( EDA_ITEM* aItem )
case SCH_SYMBOL_T:
case SCH_SHEET_T:
if( !m_filter.symbols )
{
if( aRejected )
aRejected->symbols = true;
return false;
}
break;
case SCH_PIN_T:
case SCH_SHEET_PIN_T:
if( !m_filter.pins )
{
if( aRejected )
aRejected->pins = true;
return false;
}
break;
case SCH_JUNCTION_T:
if( !m_filter.wires )
{
if( aRejected )
aRejected->wires = true;
return false;
}
break;
@ -2001,13 +2038,21 @@ bool SCH_SELECTION_TOOL::itemPassesFilter( EDA_ITEM* aItem )
case LAYER_WIRE:
case LAYER_BUS:
if( !m_filter.wires )
{
if( aRejected )
aRejected->wires = true;
return false;
}
break;
default:
if( !m_filter.graphics )
{
if( aRejected )
aRejected->graphics = true;
return false;
}
}
break;
@ -2015,7 +2060,11 @@ bool SCH_SELECTION_TOOL::itemPassesFilter( EDA_ITEM* aItem )
case SCH_SHAPE_T:
if( !m_filter.graphics )
{
if( aRejected )
aRejected->graphics = true;
return false;
}
break;
@ -2025,7 +2074,11 @@ bool SCH_SELECTION_TOOL::itemPassesFilter( EDA_ITEM* aItem )
case SCH_TABLECELL_T:
case SCH_FIELD_T:
if( !m_filter.text )
{
if( aRejected )
aRejected->text = true;
return false;
}
break;
@ -2033,25 +2086,41 @@ bool SCH_SELECTION_TOOL::itemPassesFilter( EDA_ITEM* aItem )
case SCH_GLOBAL_LABEL_T:
case SCH_HIER_LABEL_T:
if( !m_filter.labels )
{
if( aRejected )
aRejected->labels = true;
return false;
}
break;
case SCH_BITMAP_T:
if( !m_filter.images )
{
if( aRejected )
aRejected->images = true;
return false;
}
break;
case SCH_RULE_AREA_T:
if( !m_filter.ruleAreas )
{
if( aRejected )
aRejected->ruleAreas = true;
return false;
}
break;
default:
if( !m_filter.otherItems )
{
if( aRejected )
aRejected->otherItems = true;
return false;
}
break;
}
@ -2335,7 +2404,7 @@ bool SCH_SELECTION_TOOL::selectMultiple()
if( m_frame->GetRenderSettings()->m_ShowPinsElectricalType )
item->SetFlags( SHOW_ELEC_TYPE );
if( Selectable( item ) && itemPassesFilter( item ) && !item->GetParent()->HasFlag( SELECTION_CANDIDATE )
if( Selectable( item ) && itemPassesFilter( item, nullptr ) && !item->GetParent()->HasFlag( SELECTION_CANDIDATE )
&& item->HitTest( selectionRect, !isGreedy ) )
{
selectItem( item, 0 );

View File

@ -237,7 +237,8 @@ private:
* @param aSelectedOnly If true, remove non-selected items from #collector
*/
void narrowSelection( SCH_COLLECTOR& collector, const VECTOR2I& aWhere, bool aCheckLocked,
bool aSelectedOnly = false );
bool aSelectedOnly = false,
SCH_SELECTION_FILTER_OPTIONS* aRejected = nullptr );
/**
* Perform a click-type selection at a point (usually the cursor position).
@ -341,7 +342,7 @@ private:
/**
* Return true if the given item passes the stateful selection filter
*/
bool itemPassesFilter( EDA_ITEM* aItem );
bool itemPassesFilter( EDA_ITEM* aItem, SCH_SELECTION_FILTER_OPTIONS* aRejected = nullptr );
/**
* In general we don't want to select both a parent and any of it's children. This includes

View File

@ -23,8 +23,12 @@
#include <tool/tool_manager.h>
#include <tools/sch_selection_tool.h>
#include <widgets/panel_sch_selection_filter.h>
#include <wx/settings.h>
#include <wx/dcbuffer.h>
wxDEFINE_EVENT( EVT_SCH_SELECTION_FILTER_FLASH, SCH_SELECTION_FILTER_EVENT );
PANEL_SCH_SELECTION_FILTER::PANEL_SCH_SELECTION_FILTER( wxWindow* aParent ) :
PANEL_SCH_SELECTION_FILTER_BASE( aParent ),
m_frame( dynamic_cast<SCH_BASE_FRAME*>( aParent ) ),
@ -83,12 +87,27 @@ PANEL_SCH_SELECTION_FILTER::PANEL_SCH_SELECTION_FILTER( wxWindow* aParent ) :
}
m_frame->Bind( EDA_LANG_CHANGED, &PANEL_SCH_SELECTION_FILTER::OnLanguageChanged, this );
m_flashSteps = 10;
m_defaultBg = GetBackgroundColour();
m_flashTimer.SetOwner( this );
Bind( wxEVT_TIMER, &PANEL_SCH_SELECTION_FILTER::onFlashTimer, this );
aParent->Bind( EVT_SCH_SELECTION_FILTER_FLASH, &PANEL_SCH_SELECTION_FILTER::OnFlashEvent, this );
}
PANEL_SCH_SELECTION_FILTER::~PANEL_SCH_SELECTION_FILTER()
{
// Stop any active flashing
m_flashTimer.Stop();
if( !m_flashCounters.empty() )
{
Unbind( wxEVT_PAINT, &PANEL_SCH_SELECTION_FILTER::onPanelPaint, this );
}
m_flashCounters.clear();
m_frame->Unbind( EDA_LANG_CHANGED, &PANEL_SCH_SELECTION_FILTER::OnLanguageChanged, this );
m_frame->Unbind( EVT_SCH_SELECTION_FILTER_FLASH, &PANEL_SCH_SELECTION_FILTER::OnFlashEvent, this );
}
@ -220,3 +239,127 @@ void PANEL_SCH_SELECTION_FILTER::OnLanguageChanged( wxCommandEvent& aEvent )
aEvent.Skip();
}
void PANEL_SCH_SELECTION_FILTER::flashCheckbox( wxCheckBox* aBox )
{
if( !aBox )
return;
// If already flashing, just reset the counter
if( m_flashCounters.find( aBox ) != m_flashCounters.end() )
{
m_flashCounters[aBox] = m_flashSteps;
return;
}
m_flashCounters[aBox] = m_flashSteps;
m_defaultBg = aBox->GetBackgroundColour();
// Bind paint event to this panel if not already bound
if( m_flashCounters.size() == 1 )
{
Bind( wxEVT_PAINT, &PANEL_SCH_SELECTION_FILTER::onPanelPaint, this );
}
Refresh();
}
void PANEL_SCH_SELECTION_FILTER::onFlashTimer( wxTimerEvent& aEvent )
{
for( auto it = m_flashCounters.begin(); it != m_flashCounters.end(); )
{
int step = --( it->second );
if( step <= 0 )
it = m_flashCounters.erase( it );
else
++it;
}
if( m_flashCounters.empty() )
{
m_flashTimer.Stop();
// Unbind paint event when no more flashing
Unbind( wxEVT_PAINT, &PANEL_SCH_SELECTION_FILTER::onPanelPaint, this );
}
Refresh();
}
void PANEL_SCH_SELECTION_FILTER::onPanelPaint( wxPaintEvent& aEvent )
{
wxPaintDC dc( this );
// First, let the default painting happen
aEvent.Skip();
// Then draw our highlights on top
for( auto& pair : m_flashCounters )
{
wxCheckBox* checkbox = pair.first;
int step = pair.second;
if( step > 0 )
{
// Get the checkbox position relative to this panel
wxPoint checkboxPos = checkbox->GetPosition();
wxSize checkboxSize = checkbox->GetSize();
wxRect checkboxRect( checkboxPos, checkboxSize );
// Calculate blended color based on current flash step
wxColour highlight = wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHT );
wxColour blend(
( highlight.Red() * step + m_defaultBg.Red() * ( m_flashSteps - step ) ) / m_flashSteps,
( highlight.Green() * step + m_defaultBg.Green() * ( m_flashSteps - step ) ) / m_flashSteps,
( highlight.Blue() * step + m_defaultBg.Blue() * ( m_flashSteps - step ) ) / m_flashSteps );
// Draw semi-transparent overlay
wxColour overlayColor( blend.Red(), blend.Green(), blend.Blue(), 128 );
dc.SetBrush( wxBrush( overlayColor ) );
dc.SetPen( wxPen( overlayColor ) );
dc.DrawRectangle( checkboxRect );
}
}
}
void PANEL_SCH_SELECTION_FILTER::OnFlashEvent( SCH_SELECTION_FILTER_EVENT& aEvent )
{
const SCH_SELECTION_FILTER_OPTIONS& aOptions = aEvent.m_options;
if( aOptions.lockedItems )
flashCheckbox( m_cbLockedItems );
if( aOptions.symbols )
flashCheckbox( m_cbSymbols );
if( aOptions.text )
flashCheckbox( m_cbText );
if( aOptions.wires )
flashCheckbox( m_cbWires );
if( aOptions.labels )
flashCheckbox( m_cbLabels );
if( aOptions.pins )
flashCheckbox( m_cbPins );
if( aOptions.graphics )
flashCheckbox( m_cbGraphics );
if( aOptions.images )
flashCheckbox( m_cbImages );
if( aOptions.ruleAreas )
flashCheckbox( m_cbRuleAreas );
if( aOptions.otherItems )
flashCheckbox( m_cbOtherItems );
if( !m_flashCounters.empty() )
m_flashTimer.Start( 50 );
}

View File

@ -22,11 +22,31 @@
#define KICAD_PANEL_SCH_SELECTION_FILTER_H
#include <widgets/panel_sch_selection_filter_base.h>
#include <wx/timer.h>
#include <map>
class SCH_BASE_FRAME;
class SCH_SELECTION_TOOL;
struct SCH_SELECTION_FILTER_OPTIONS;
// Forward declare the event type
class SCH_SELECTION_FILTER_EVENT;
wxDECLARE_EVENT( EVT_SCH_SELECTION_FILTER_FLASH, SCH_SELECTION_FILTER_EVENT );
class SCH_SELECTION_FILTER_EVENT : public wxCommandEvent
{
public:
SCH_SELECTION_FILTER_EVENT( const SCH_SELECTION_FILTER_OPTIONS& aOptions = SCH_SELECTION_FILTER_OPTIONS(), int id = 0 ) :
wxCommandEvent( EVT_SCH_SELECTION_FILTER_FLASH, id ), m_options( aOptions ) {}
wxEvent* Clone() const override
{
return new SCH_SELECTION_FILTER_EVENT( *this );
}
SCH_SELECTION_FILTER_OPTIONS m_options;
};
class PANEL_SCH_SELECTION_FILTER : public PANEL_SCH_SELECTION_FILTER_BASE
{
@ -48,10 +68,19 @@ private:
void onPopupSelection( wxCommandEvent& aEvent );
void flashCheckbox( wxCheckBox* aBox );
void onFlashTimer( wxTimerEvent& aEvent );
void OnFlashEvent( SCH_SELECTION_FILTER_EVENT& aEvent );
void onPanelPaint( wxPaintEvent& aEvent );
private:
SCH_BASE_FRAME* m_frame;
SCH_SELECTION_TOOL* m_tool;
wxCheckBox* m_onlyCheckbox;
SCH_BASE_FRAME* m_frame;
SCH_SELECTION_TOOL* m_tool;
wxCheckBox* m_onlyCheckbox;
std::map<wxCheckBox*, int> m_flashCounters;
wxTimer m_flashTimer;
int m_flashSteps;
wxColour m_defaultBg;
};
#endif //KICAD_PANEL_SCH_SELECTION_FILTER_H

View File

@ -86,6 +86,21 @@ struct KICOMMON_API PCB_SELECTION_FILTER_OPTIONS
return ( footprints && text && tracks && vias && pads && graphics && zones
&& keepouts && dimensions && otherItems );
}
void SetAll( bool aState )
{
footprints = aState;
text = aState;
tracks = aState;
vias = aState;
pads = aState;
graphics = aState;
zones = aState;
keepouts = aState;
dimensions = aState;
otherItems = aState;
lockedItems = aState;
}
};
/**

View File

@ -77,6 +77,20 @@ struct SCH_SELECTION_FILTER_OPTIONS
ruleAreas = true;
otherItems = true;
}
void SetAll( bool aState )
{
lockedItems = aState;
symbols = aState;
text = aState;
wires = aState;
labels = aState;
pins = aState;
graphics = aState;
images = aState;
ruleAreas = aState;
otherItems = aState;
}
};
#endif //KICAD_SCH_PROJECT_SETTINGS_H

View File

@ -30,6 +30,7 @@
#include <tool/tool_manager.h>
#include <tools/pcb_actions.h>
#include <tools/pcb_selection_tool.h>
#include <widgets/panel_selection_filter.h>
#include <pgm_base.h>
#include <board.h>
#include <board_design_settings.h>
@ -406,3 +407,10 @@ void PCB_BASE_EDIT_FRAME::configureToolbars()
RegisterCustomToolbarControlFactory( ACTION_TOOLBAR_CONTROLS::layerSelector, layerSelectorFactory );
}
void PCB_BASE_EDIT_FRAME::HighlightSelectionFilter( const PCB_SELECTION_FILTER_OPTIONS& aOptions )
{
PCB_SELECTION_FILTER_EVENT evt( aOptions );
wxPostEvent( this, evt );
}

View File

@ -38,6 +38,7 @@ class PCB_TABLE;
class PCB_TEXT;
class PCB_SHAPE;
class FILEDLG_HOOK_NEW_LIBRARY;
struct PCB_SELECTION_FILTER_OPTIONS;
/**
* Common, abstract interface for edit frames.
@ -245,6 +246,8 @@ public:
void GetContextualTextVars( BOARD_ITEM* aSourceItem, const wxString& aCrossRef,
wxArrayString* aTokens );
void HighlightSelectionFilter( const PCB_SELECTION_FILTER_OPTIONS& aOptions );
protected:
void configureToolbars() override;

View File

@ -1806,7 +1806,7 @@ int BOARD_INSPECTION_TOOL::HighlightItem( const TOOL_EVENT& aEvent )
bool saved = filter.lockedItems;
filter.lockedItems = true;
selectionTool->FilterCollectedItems( collector, true );
selectionTool->FilterCollectedItems( collector, true, nullptr );
filter.lockedItems = saved;

View File

@ -879,7 +879,7 @@ int PCB_CONTROL::InteractiveDelete( const TOOL_EVENT& aEvent )
}
selectionTool->FilterCollectorForHierarchy( collector, false );
selectionTool->FilterCollectedItems( collector, false );
selectionTool->FilterCollectedItems( collector, false, nullptr );
if( collector.GetCount() > 1 )
selectionTool->GuessSelectionCandidates( collector, aPos );

View File

@ -38,6 +38,7 @@ using namespace std::placeholders;
#include <pcb_tablecell.h>
#include <pcb_marker.h>
#include <pcb_generator.h>
#include <pcb_base_edit_frame.h>
#include <zone.h>
#include <collectors.h>
#include <dialog_filter_selection.h>
@ -769,8 +770,20 @@ bool PCB_SELECTION_TOOL::selectPoint( const VECTOR2I& aWhere, bool aOnDrag, bool
m_selection.ClearReferencePoint();
size_t preFilterCount = collector.GetCount();
PCB_SELECTION_FILTER_OPTIONS rejected;
rejected.SetAll( false );
// Apply the stateful filter (remove items disabled by the Selection Filter)
FilterCollectedItems( collector, false );
FilterCollectedItems( collector, false, &rejected );
if( collector.GetCount() == 0 && preFilterCount > 0 )
{
if( PCB_BASE_EDIT_FRAME* editFrame = dynamic_cast<PCB_BASE_EDIT_FRAME*>( m_frame ) )
editFrame->HighlightSelectionFilter( rejected );
return false;
}
// Allow the client to do tool- or action-specific filtering to see if we can get down
// to a single item
@ -1288,7 +1301,7 @@ void PCB_SELECTION_TOOL::SelectMultiple( KIGFX::PREVIEW::SELECTION_AREA& aArea,
}
// Apply the stateful filter
FilterCollectedItems( collector, true );
FilterCollectedItems( collector, true, nullptr );
FilterCollectorForHierarchy( collector, true );
@ -1296,7 +1309,7 @@ void PCB_SELECTION_TOOL::SelectMultiple( KIGFX::PREVIEW::SELECTION_AREA& aArea,
if( collector.GetCount() == 0 )
{
collector = padsCollector;
FilterCollectedItems( collector, true );
FilterCollectedItems( collector, true, nullptr );
FilterCollectorForHierarchy( collector, true );
}
@ -1390,7 +1403,7 @@ int PCB_SELECTION_TOOL::SelectAll( const TOOL_EVENT& aEvent )
{
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( viewItem );
if( item && Selectable( item ) && itemPassesFilter( item, true ) )
if( item && Selectable( item ) && itemPassesFilter( item, true, nullptr ) )
collection.Append( item );
}
@ -2114,7 +2127,7 @@ void PCB_SELECTION_TOOL::SelectAllItemsOnNet( int aNetCode, bool aSelect )
PCB_VIA_T,
PCB_SHAPE_T } ) )
{
if( itemPassesFilter( item, true ) )
if( itemPassesFilter( item, true, nullptr ) )
aSelect ? select( item ) : unselect( item );
}
}
@ -2719,7 +2732,8 @@ int PCB_SELECTION_TOOL::filterSelection( const TOOL_EVENT& aEvent )
}
void PCB_SELECTION_TOOL::FilterCollectedItems( GENERAL_COLLECTOR& aCollector, bool aMultiSelect )
void PCB_SELECTION_TOOL::FilterCollectedItems( GENERAL_COLLECTOR& aCollector, bool aMultiSelect,
PCB_SELECTION_FILTER_OPTIONS* aRejected )
{
if( aCollector.GetCount() == 0 )
return;
@ -2733,7 +2747,7 @@ void PCB_SELECTION_TOOL::FilterCollectedItems( GENERAL_COLLECTOR& aCollector, bo
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( i );
if( !itemPassesFilter( item, aMultiSelect ) )
if( !itemPassesFilter( item, aMultiSelect, aRejected ) )
rejected.insert( item );
}
@ -2742,7 +2756,8 @@ void PCB_SELECTION_TOOL::FilterCollectedItems( GENERAL_COLLECTOR& aCollector, bo
}
bool PCB_SELECTION_TOOL::itemPassesFilter( BOARD_ITEM* aItem, bool aMultiSelect )
bool PCB_SELECTION_TOOL::itemPassesFilter( BOARD_ITEM* aItem, bool aMultiSelect,
PCB_SELECTION_FILTER_OPTIONS* aRejected )
{
if( !m_filter.lockedItems )
{
@ -2755,6 +2770,8 @@ bool PCB_SELECTION_TOOL::itemPassesFilter( BOARD_ITEM* aItem, bool aMultiSelect
}
else
{
if( aRejected )
aRejected->lockedItems = true;
return false;
}
}
@ -2770,7 +2787,11 @@ bool PCB_SELECTION_TOOL::itemPassesFilter( BOARD_ITEM* aItem, bool aMultiSelect
if( static_cast<PCB_GENERATOR*>( aItem )->GetItems().empty() )
{
if( !m_filter.otherItems )
{
if( aRejected )
aRejected->otherItems = true;
return false;
}
}
else
{
@ -2782,26 +2803,42 @@ bool PCB_SELECTION_TOOL::itemPassesFilter( BOARD_ITEM* aItem, bool aMultiSelect
{
case PCB_FOOTPRINT_T:
if( !m_filter.footprints )
{
if( aRejected )
aRejected->footprints = true;
return false;
}
break;
case PCB_PAD_T:
if( !m_filter.pads )
{
if( aRejected )
aRejected->pads = true;
return false;
}
break;
case PCB_TRACE_T:
case PCB_ARC_T:
if( !m_filter.tracks )
{
if( aRejected )
aRejected->tracks = true;
return false;
}
break;
case PCB_VIA_T:
if( !m_filter.vias )
{
if( aRejected )
aRejected->vias = true;
return false;
}
break;
@ -2812,6 +2849,13 @@ bool PCB_SELECTION_TOOL::itemPassesFilter( BOARD_ITEM* aItem, bool aMultiSelect
if( ( !m_filter.zones && !zone->GetIsRuleArea() )
|| ( !m_filter.keepouts && zone->GetIsRuleArea() ) )
{
if( aRejected )
{
if( zone->GetIsRuleArea() )
aRejected->keepouts = true;
else
aRejected->zones = true;
}
return false;
}
@ -2827,17 +2871,29 @@ bool PCB_SELECTION_TOOL::itemPassesFilter( BOARD_ITEM* aItem, bool aMultiSelect
case PCB_SHAPE_T:
case PCB_TARGET_T:
if( !m_filter.graphics )
{
if( aRejected )
aRejected->graphics = true;
return false;
}
break;
case PCB_REFERENCE_IMAGE_T:
if( !m_filter.graphics )
{
if( aRejected )
aRejected->graphics = true;
return false;
}
// a reference image living in a footprint must not be selected inside the board editor
if( !m_isFootprintEditor && aItem->GetParentFootprint() )
{
if( aRejected )
aRejected->text = true;
return false;
}
break;
@ -2857,13 +2913,21 @@ bool PCB_SELECTION_TOOL::itemPassesFilter( BOARD_ITEM* aItem, bool aMultiSelect
case PCB_DIM_ORTHOGONAL_T:
case PCB_DIM_LEADER_T:
if( !m_filter.dimensions )
{
if( aRejected )
aRejected->dimensions = true;
return false;
}
break;
default:
if( !m_filter.otherItems )
{
if( aRejected )
aRejected->otherItems = true;
return false;
}
}
return true;

View File

@ -250,7 +250,8 @@ public:
/**
* Apply the SELECTION_FITLER_OPTIONS to the collector.
*/
void FilterCollectedItems( GENERAL_COLLECTOR& aCollector, bool aMultiSelect );
void FilterCollectedItems( GENERAL_COLLECTOR& aCollector, bool aMultiSelect,
PCB_SELECTION_FILTER_OPTIONS* aRejected = nullptr );
/**
* Drop footprints that are not directly selected
@ -426,7 +427,8 @@ private:
int filterSelection( const TOOL_EVENT& aEvent );
///< Return true if the given item passes the current SELECTION_FILTER_OPTIONS.
bool itemPassesFilter( BOARD_ITEM* aItem, bool aMultiSelect );
bool itemPassesFilter( BOARD_ITEM* aItem, bool aMultiSelect,
PCB_SELECTION_FILTER_OPTIONS* aRejected = nullptr );
/**
* Take necessary action mark an item as unselected.

View File

@ -22,8 +22,12 @@
#include <tool/tool_manager.h>
#include <tools/pcb_selection_tool.h>
#include <widgets/panel_selection_filter.h>
#include <wx/settings.h>
#include <wx/dcbuffer.h>
wxDEFINE_EVENT( EVT_PCB_SELECTION_FILTER_FLASH, PCB_SELECTION_FILTER_EVENT );
PANEL_SELECTION_FILTER::PANEL_SELECTION_FILTER( wxWindow* aParent ) :
PANEL_SELECTION_FILTER_BASE( aParent ),
m_frame( dynamic_cast<PCB_BASE_EDIT_FRAME*>( aParent ) ),
@ -66,12 +70,27 @@ PANEL_SELECTION_FILTER::PANEL_SELECTION_FILTER( wxWindow* aParent ) :
m_frame->Bind( EDA_LANG_CHANGED, &PANEL_SELECTION_FILTER::OnLanguageChanged, this );
SetMinSize( GetBestSize() );
m_flashSteps = 10;
m_defaultBg = GetBackgroundColour();
m_flashTimer.SetOwner( this );
Bind( wxEVT_TIMER, &PANEL_SELECTION_FILTER::onFlashTimer, this );
aParent->Bind( EVT_PCB_SELECTION_FILTER_FLASH, &PANEL_SELECTION_FILTER::OnFlashEvent, this );
}
PANEL_SELECTION_FILTER::~PANEL_SELECTION_FILTER()
{
// Stop any active flashing
m_flashTimer.Stop();
if( !m_flashCounters.empty() )
{
Unbind( wxEVT_PAINT, &PANEL_SELECTION_FILTER::onPanelPaint, this );
}
m_flashCounters.clear();
m_frame->Unbind( EDA_LANG_CHANGED, &PANEL_SELECTION_FILTER::OnLanguageChanged, this );
m_frame->Unbind( EVT_PCB_SELECTION_FILTER_FLASH, &PANEL_SELECTION_FILTER::OnFlashEvent, this );
}
@ -208,3 +227,130 @@ void PANEL_SELECTION_FILTER::OnLanguageChanged( wxCommandEvent& aEvent )
aEvent.Skip();
}
void PANEL_SELECTION_FILTER::flashCheckbox( wxCheckBox* aBox )
{
if( !aBox )
return;
// If already flashing, just reset the counter
if( m_flashCounters.find( aBox ) != m_flashCounters.end() )
{
m_flashCounters[aBox] = m_flashSteps;
return;
}
m_flashCounters[aBox] = m_flashSteps;
m_defaultBg = aBox->GetBackgroundColour();
// Bind paint event to this panel if not already bound
if( m_flashCounters.size() == 1 )
{
Bind( wxEVT_PAINT, &PANEL_SELECTION_FILTER::onPanelPaint, this );
}
Refresh();
}
void PANEL_SELECTION_FILTER::onFlashTimer( wxTimerEvent& aEvent )
{
for( auto it = m_flashCounters.begin(); it != m_flashCounters.end(); )
{
int step = --( it->second );
if( step <= 0 )
it = m_flashCounters.erase( it );
else
++it;
}
if( m_flashCounters.empty() )
{
m_flashTimer.Stop();
// Unbind paint event when no more flashing
Unbind( wxEVT_PAINT, &PANEL_SELECTION_FILTER::onPanelPaint, this );
}
Refresh();
}
void PANEL_SELECTION_FILTER::onPanelPaint( wxPaintEvent& aEvent )
{
wxPaintDC dc( this );
// First, let the default painting happen
aEvent.Skip();
// Then draw our highlights on top
for( auto& pair : m_flashCounters )
{
wxCheckBox* checkbox = pair.first;
int step = pair.second;
if( step > 0 )
{
// Get the checkbox position relative to this panel
wxPoint checkboxPos = checkbox->GetPosition();
wxSize checkboxSize = checkbox->GetSize();
wxRect checkboxRect( checkboxPos, checkboxSize );
// Calculate blended color based on current flash step
wxColour highlight = wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHT );
wxColour blend(
( highlight.Red() * step + m_defaultBg.Red() * ( m_flashSteps - step ) ) / m_flashSteps,
( highlight.Green() * step + m_defaultBg.Green() * ( m_flashSteps - step ) ) / m_flashSteps,
( highlight.Blue() * step + m_defaultBg.Blue() * ( m_flashSteps - step ) ) / m_flashSteps );
// Draw semi-transparent overlay
wxColour overlayColor( blend.Red(), blend.Green(), blend.Blue(), 128 );
dc.SetBrush( wxBrush( overlayColor ) );
dc.SetPen( wxPen( overlayColor ) );
dc.DrawRectangle( checkboxRect );
}
}
}
void PANEL_SELECTION_FILTER::OnFlashEvent( PCB_SELECTION_FILTER_EVENT& aEvent )
{
const PCB_SELECTION_FILTER_OPTIONS& aOptions = aEvent.m_options;
if( aOptions.lockedItems )
flashCheckbox( m_cbLockedItems );
if( aOptions.footprints )
flashCheckbox( m_cbFootprints );
if( aOptions.text )
flashCheckbox( m_cbText );
if( aOptions.tracks )
flashCheckbox( m_cbTracks );
if( aOptions.vias )
flashCheckbox( m_cbVias );
if( aOptions.pads )
flashCheckbox( m_cbPads );
if( aOptions.graphics )
flashCheckbox( m_cbGraphics );
if( aOptions.zones )
flashCheckbox( m_cbZones );
if( aOptions.keepouts )
flashCheckbox( m_cbKeepouts );
if( aOptions.dimensions )
flashCheckbox( m_cbDimensions );
if( aOptions.otherItems )
flashCheckbox( m_cbOtherItems );
if( !m_flashCounters.empty() )
m_flashTimer.Start( 50 );
}

View File

@ -22,11 +22,31 @@
#define KICAD_PANEL_SELECTION_FILTER_H
#include <widgets/panel_selection_filter_base.h>
#include <wx/timer.h>
#include <map>
class PCB_SELECTION_TOOL;
struct PCB_SELECTION_FILTER_OPTIONS;
// Forward declare the event type
class PCB_SELECTION_FILTER_EVENT;
wxDECLARE_EVENT( EVT_PCB_SELECTION_FILTER_FLASH, PCB_SELECTION_FILTER_EVENT );
class PCB_SELECTION_FILTER_EVENT : public wxCommandEvent
{
public:
PCB_SELECTION_FILTER_EVENT( const PCB_SELECTION_FILTER_OPTIONS& aOptions = PCB_SELECTION_FILTER_OPTIONS(), int id = 0 ) :
wxCommandEvent( EVT_PCB_SELECTION_FILTER_FLASH, id ), m_options( aOptions ) {}
wxEvent* Clone() const override
{
return new PCB_SELECTION_FILTER_EVENT( *this );
}
PCB_SELECTION_FILTER_OPTIONS m_options;
};
class PANEL_SELECTION_FILTER : public PANEL_SELECTION_FILTER_BASE
{
@ -48,10 +68,19 @@ private:
void onPopupSelection( wxCommandEvent& aEvent );
void flashCheckbox( wxCheckBox* aBox );
void onFlashTimer( wxTimerEvent& aEvent );
void OnFlashEvent( PCB_SELECTION_FILTER_EVENT& aEvent );
void onPanelPaint( wxPaintEvent& aEvent );
private:
PCB_BASE_EDIT_FRAME* m_frame;
PCB_SELECTION_TOOL* m_tool;
wxCheckBox* m_onlyCheckbox;
PCB_BASE_EDIT_FRAME* m_frame;
PCB_SELECTION_TOOL* m_tool;
wxCheckBox* m_onlyCheckbox;
std::map<wxCheckBox*, int> m_flashCounters;
wxTimer m_flashTimer;
int m_flashSteps;
wxColour m_defaultBg;
};

View File

@ -38,6 +38,7 @@
#include <dialog_find.h>
#include <dialog_filter_selection.h>
#include <zone_filler.h>
struct PCB_SELECTION_FILTER_OPTIONS;
#include <preview_items/selection_area.h>
FP_LIB_TABLE GFootprintTable;
@ -360,12 +361,14 @@ int PCB_SELECTION_TOOL::filterSelection( const TOOL_EVENT& aEvent )
}
void PCB_SELECTION_TOOL::FilterCollectedItems( GENERAL_COLLECTOR& aCollector, bool aMultiSelect )
void PCB_SELECTION_TOOL::FilterCollectedItems( GENERAL_COLLECTOR& aCollector, bool aMultiSelect,
PCB_SELECTION_FILTER_OPTIONS* aRejected )
{
}
bool PCB_SELECTION_TOOL::itemPassesFilter( BOARD_ITEM* aItem, bool aMultiSelect )
bool PCB_SELECTION_TOOL::itemPassesFilter( BOARD_ITEM* aItem, bool aMultiSelect,
PCB_SELECTION_FILTER_OPTIONS* aRejected )
{
return true;
}