Avoid selection disambiguation menu when possible.

Some actions, such as select trivial connection, don't need the
user to choose which trace at a corner to start from -- either
one will do.  Same for moving a simple trace corner.

Likewise, Edit in Footprint Editor shouldn't ask if you mean the
footprint or the pad.  Obviously it's the footprint.

This change adds a CLIENT_SELECTION_FILTER which allows clients
to do tool- or action-specific filtering of the selection before
the disambiguation menu is run.

It also removes some tool- and action-specific code which was
in the selection_tool and shouldn't have been.

Fixes: lp:1708869
* https://bugs.launchpad.net/kicad/+bug/1708869
This commit is contained in:
Jeff Young 2018-01-05 23:44:37 +00:00 committed by Maciej Suminski
parent 17e6720bc9
commit 3f6af59cac
8 changed files with 222 additions and 120 deletions

View File

@ -42,6 +42,7 @@ using namespace std::placeholders;
#include <hotkeys.h>
#include <confirm.h>
#include <bitmaps.h>
#include <collectors.h>
#include <tool/context_menu.h>
#include <tool/tool_manager.h>
@ -980,10 +981,87 @@ void ROUTER_TOOL::performDragging( int aMode )
}
void ROUTER_TOOL::NeighboringSegmentFilter( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector )
{
/* If the collection contains a trivial line corner (two connected segments)
* or a non-fanout-via (a via with no more than two connected segments), then
* trim the collection down to a single item (which one won't matter since
* they're all connected).
*/
// First make sure we've got something that *might* match.
int vias = aCollector.CountType( PCB_VIA_T );
int traces = aCollector.CountType( PCB_TRACE_T );
if( vias > 1 || traces > 2 || vias + traces < 1 )
return;
// Fetch first TRACK (via or trace) as our reference
TRACK* reference = nullptr;
for( int i = 0; !reference && i < aCollector.GetCount(); i++ )
reference = dynamic_cast<TRACK*>( aCollector[i] );
int refNet = reference->GetNetCode();
wxPoint refPoint;
STATUS_FLAGS flags = reference->IsPointOnEnds( wxPoint( aPt.x, aPt.y ), -1 );
if( flags & STARTPOINT )
refPoint = reference->GetStart();
else if( flags & ENDPOINT )
refPoint = reference->GetEnd();
else
return;
// Check all items to ensure that they are TRACKs which are co-terminus
// with the reference, and on the same net. Ignore markers.
for( int i = 0; i < aCollector.GetCount(); i++ )
{
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( aCollector[i] );
if( item->Type() == PCB_MARKER_T )
continue;
TRACK* neighbor = dynamic_cast<TRACK*>( item );
if( !neighbor || neighbor->GetNetCode() != refNet )
return;
if( neighbor->GetStart() != refPoint && neighbor->GetEnd() != refPoint )
return;
}
// Selection meets criteria; trim it to the reference item.
for( int i = aCollector.GetCount()-1; i >= 0; i-- )
{
if( dynamic_cast<TRACK*>( aCollector[i] ) != reference )
aCollector.Remove( i );
}
}
bool ROUTER_TOOL::CanInlineDrag()
{
m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, true, NeighboringSegmentFilter );
const auto& selection = m_toolMgr->GetTool<SELECTION_TOOL>()->GetSelection();
if( selection.Size() == 1 )
{
const BOARD_CONNECTED_ITEM* item = static_cast<const BOARD_CONNECTED_ITEM*>( selection.Front() );
if( item->Type() == PCB_TRACE_T || item->Type() == PCB_VIA_T )
return true;
}
m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor ); // restore selection to unfiltered state
return false;
}
int ROUTER_TOOL::InlineDrag( const TOOL_EVENT& aEvent )
{
// Get the item under the cursor
m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, true );
m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, true, NeighboringSegmentFilter );
const auto& selection = m_toolMgr->GetTool<SELECTION_TOOL>()->GetSelection();
if( selection.Size() != 1 )

View File

@ -36,6 +36,7 @@ public:
int RouteSingleTrace( const TOOL_EVENT& aEvent );
int RouteDiffPair( const TOOL_EVENT& aEvent );
bool CanInlineDrag();
int InlineDrag( const TOOL_EVENT& aEvent );
// TODO make this private?
@ -45,6 +46,10 @@ public:
void setTransitions() override;
// A filter for narrowing a collection representing a simple corner
// or a non-fanout-via to a single TRACK item.
static void NeighboringSegmentFilter( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector );
private:
int mainLoop( PNS::ROUTER_MODE aMode );

View File

@ -330,13 +330,10 @@ bool EDIT_TOOL::invokeInlineRouter( int aDragMode )
return false;
// make sure we don't accidentally invoke inline routing mode while the router is already active!
if ( theRouter->IsToolActive() )
if( theRouter->IsToolActive() )
return false;
TRACK* track = uniqueSelected<TRACK>();
VIA* via = uniqueSelected<VIA>();
if( track || via )
if( theRouter->CanInlineDrag() )
{
m_toolMgr->RunAction( PCB_ACTIONS::routerInlineDrag, true, aDragMode );
return true;
@ -1087,9 +1084,21 @@ int EDIT_TOOL::CreateArray( const TOOL_EVENT& aEvent )
}
void EDIT_TOOL::FootprintFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector )
{
for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
{
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( aCollector[i] );
if( item->Type() != PCB_MODULE_T )
aCollector.Remove( i );
}
}
int EDIT_TOOL::ExchangeFootprints( const TOOL_EVENT& aEvent )
{
const auto& selection = m_selectionTool->RequestSelection();
const auto& selection = m_selectionTool->RequestSelection( 0, FootprintFilter );
if( selection.Empty() )
return 0;
@ -1265,7 +1274,7 @@ bool EDIT_TOOL::updateModificationPoint( SELECTION& aSelection )
int EDIT_TOOL::editFootprintInFpEditor( const TOOL_EVENT& aEvent )
{
const auto& selection = m_selectionTool->RequestSelection();
const auto& selection = m_selectionTool->RequestSelection( 0, FootprintFilter );
if( selection.Empty() )
return 0;
@ -1357,15 +1366,3 @@ int EDIT_TOOL::cutToClipboard( const TOOL_EVENT& aEvent )
return 0;
}
template<class T>
T* EDIT_TOOL::uniqueSelected()
{
auto& selection = m_selectionTool->RequestSelection( SELECTION_DEFAULT );
if( selection.Size() != 1 )
return nullptr;
auto item = selection[0];
return dyn_cast<T*>( item );
}

View File

@ -136,6 +136,14 @@ public:
///> Launches a tool to measure between points
int MeasureTool( const TOOL_EVENT& aEvent );
/**
* Function FootprintFilter()
*
* A selection filter which prunes the selection to contain only items
* of type PCB_MODULE_T
*/
static void FootprintFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector );
///> Sets up handlers for various events.
void setTransitions() override;
@ -197,38 +205,6 @@ private:
*/
bool hoverSelection( bool aSanitize = true );
/**
* Function uniqueSelected()
*
* Get a single selected item of a certain type
*
* @tparam T type of item to select
* @return pointer to the item (of type T), or nullptr if there isn't
* a single selected item, or it's not of the right type.
*/
template<class T> T* uniqueSelected();
/**
* Function uniqueHoverSelection()
*
* Get a single unique selection of an item, either from the
* current selection, or from the items under cursor via
* hoverSelection()
*
* @tparam T type of item to select
* @return pointer to a selected item, or nullptr if none could
* be found.
*/
template<class T>
T* uniqueHoverSelection( bool aSanitize = true )
{
if( !hoverSelection( aSanitize ) )
return nullptr;
T* item = uniqueSelected<T>();
return item;
}
std::unique_ptr<BOARD_COMMIT> m_commit;
};

View File

@ -31,6 +31,7 @@
#include <tool/tool_manager.h>
#include <wx/progdlg.h>
#include "edit_tool.h"
#include "selection_tool.h"
#include "drawing_tool.h"
#include "picker_tool.h"
@ -958,7 +959,7 @@ static bool showLocalRatsnest( TOOL_MANAGER* aToolMgr, const VECTOR2D& aPosition
auto selectionTool = aToolMgr->GetTool<SELECTION_TOOL>();
aToolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
aToolMgr->RunAction( PCB_ACTIONS::selectionCursor, true );
aToolMgr->RunAction( PCB_ACTIONS::selectionCursor, true, EDIT_TOOL::FootprintFilter );
const SELECTION& selection = selectionTool->GetSelection();

View File

@ -52,6 +52,7 @@ using namespace std::placeholders;
#include <tool/tool_event.h>
#include <tool/tool_manager.h>
#include <router/router_tool.h>
#include <connectivity_data.h>
#include "selection_tool.h"
@ -175,9 +176,12 @@ public:
SELECTION_TOOL::SELECTION_TOOL() :
PCB_TOOL( "pcbnew.InteractiveSelection" ),
m_frame( NULL ), m_additive( false ), m_subtractive( false ),
m_frame( NULL ),
m_additive( false ),
m_subtractive( false ),
m_multiple( false ),
m_locked( true ), m_menu( *this ),
m_locked( true ),
m_menu( *this ),
m_priv( std::make_unique<PRIV>() )
{
@ -355,14 +359,13 @@ SELECTION& SELECTION_TOOL::GetSelection()
}
SELECTION& SELECTION_TOOL::RequestSelection( int aFlags )
SELECTION& SELECTION_TOOL::RequestSelection( int aFlags, CLIENT_SELECTION_FILTER aClientFilter )
{
if( m_selection.Empty() )
{
if( aFlags & SELECTION_FORCE_UNLOCK )
m_locked = false;
m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, true, 0 );
m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, true, aClientFilter );
m_selection.SetIsHover( true );
m_selection.ClearReferencePoint();
}
@ -437,9 +440,9 @@ const GENERAL_COLLECTORS_GUIDE SELECTION_TOOL::getCollectorsGuide() const
bool SELECTION_TOOL::selectPoint( const VECTOR2I& aWhere, bool aOnDrag,
bool* aSelectionCancelledFlag )
bool* aSelectionCancelledFlag,
CLIENT_SELECTION_FILTER aClientFilter )
{
BOARD_ITEM* item;
auto guide = getCollectorsGuide();
GENERAL_COLLECTOR collector;
@ -458,63 +461,66 @@ bool SELECTION_TOOL::selectPoint( const VECTOR2I& aWhere, bool aOnDrag,
m_selection.ClearReferencePoint();
switch( collector.GetCount() )
// Allow the client to do tool- or action-specific filtering to see if we
// can get down to a single item
if( aClientFilter )
aClientFilter( aWhere, collector );
if( collector.GetCount() == 0 )
{
case 0:
if( !m_additive && anyCollected )
{
clearSelection();
}
return false;
}
case 1:
if( collector.GetCount() == 1 )
{
toggleSelection( collector[0] );
return true;
}
default:
// Apply some ugly heuristics to avoid disambiguation menus whenever possible
guessSelectionCandidates( collector );
// Apply some ugly heuristics to avoid disambiguation menus whenever possible
guessSelectionCandidates( collector );
// Let's see if there is still disambiguation in selection..
if( collector.GetCount() == 1 )
{
toggleSelection( collector[0] );
return true;
}
else if( collector.GetCount() > 1 )
{
if( aOnDrag )
{
Wait( TOOL_EVENT( TC_ANY, TA_MOUSE_UP, BUT_LEFT ) );
}
if( collector.GetCount() == 1 )
{
toggleSelection( collector[0] );
return true;
}
item = disambiguationMenu( &collector );
// Still more than one item. We're going to have to ask the user.
if( aOnDrag )
{
Wait( TOOL_EVENT( TC_ANY, TA_MOUSE_UP, BUT_LEFT ) );
}
if( item )
{
toggleSelection( item );
return true;
}
else
{
if( aSelectionCancelledFlag )
*aSelectionCancelledFlag = true;
return false;
}
}
break;
BOARD_ITEM* item = disambiguationMenu( &collector );
if( item )
{
toggleSelection( item );
return true;
}
else
{
if( aSelectionCancelledFlag )
*aSelectionCancelledFlag = true;
return false;
}
return false;
}
bool SELECTION_TOOL::selectCursor( bool aSelectAlways )
bool SELECTION_TOOL::selectCursor( bool aSelectAlways, CLIENT_SELECTION_FILTER aClientFilter )
{
if( aSelectAlways || m_selection.Empty() )
{
clearSelection();
selectPoint( getViewControls()->GetCursorPosition( false ) );
selectPoint( getViewControls()->GetCursorPosition( false ), false, NULL, aClientFilter );
}
return !m_selection.Empty();
@ -703,11 +709,11 @@ SELECTION_LOCK_FLAGS SELECTION_TOOL::CheckLock()
int SELECTION_TOOL::CursorSelection( const TOOL_EVENT& aEvent )
{
bool sanitize = (bool) aEvent.Parameter<intptr_t>();
CLIENT_SELECTION_FILTER aClientFilter = aEvent.Parameter<CLIENT_SELECTION_FILTER>();
if( m_selection.Empty() ) // Try to find an item that could be modified
{
selectCursor( true );
selectCursor( true, aClientFilter );
if( CheckLock() == SELECTION_LOCKED )
{
@ -716,9 +722,6 @@ int SELECTION_TOOL::CursorSelection( const TOOL_EVENT& aEvent )
}
}
if( sanitize )
SanitizeSelection();
return 0;
}
@ -805,9 +808,24 @@ int SELECTION_TOOL::UnselectItem( const TOOL_EVENT& aEvent )
}
void connectedTrackFilter( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector )
{
/* Narrow the collection down to a single TRACK item for a trivial
* connection, or multiple TRACK items for non-trivial connections.
*/
for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
{
if( !dynamic_cast<TRACK*>( aCollector[i] ) )
aCollector.Remove( i );
}
ROUTER_TOOL::NeighboringSegmentFilter( aPt, aCollector );
}
int SELECTION_TOOL::selectConnection( const TOOL_EVENT& aEvent )
{
if( !selectCursor() )
if( !selectCursor( true, connectedTrackFilter ) )
return 0;
// copy the selection, since we're going to iterate and modify
@ -815,12 +833,10 @@ int SELECTION_TOOL::selectConnection( const TOOL_EVENT& aEvent )
for( auto item : selection )
{
// only TRACK items can be checked for trivial connections
if( item->Type() == PCB_TRACE_T || item->Type() == PCB_VIA_T )
{
TRACK& trackItem = static_cast<TRACK&>( *item );
selectAllItemsConnectedToTrack( trackItem );
}
TRACK* trackItem = dynamic_cast<TRACK*>( item );
if( trackItem )
selectAllItemsConnectedToTrack( *trackItem );
}
// Inform other potentially interested tools
@ -831,25 +847,40 @@ int SELECTION_TOOL::selectConnection( const TOOL_EVENT& aEvent )
}
void connectedItemFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector )
{
/* Narrow the collection down to a single BOARD_CONNECTED_ITEM for each
* represented net. All other items types are removed.
*/
std::set<int> representedNets;
for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
{
BOARD_CONNECTED_ITEM* item = dynamic_cast<BOARD_CONNECTED_ITEM*>( aCollector[i] );
if( !item )
aCollector.Remove( i );
else if ( representedNets.count( item->GetNetCode() ) )
aCollector.Remove( i );
else
representedNets.insert( item->GetNetCode() );
}
}
int SELECTION_TOOL::selectCopper( const TOOL_EVENT& aEvent )
{
if( !selectCursor() )
if( !selectCursor( true, connectedItemFilter ) )
return 0;
// copy the selection, since we're going to iterate and modify
auto selection = m_selection.GetItems();
for( auto i : selection )
for( auto item : selection )
{
auto item = static_cast<BOARD_ITEM*>( i );
BOARD_CONNECTED_ITEM* connItem = dynamic_cast<BOARD_CONNECTED_ITEM*>( item );
// only connected items can be traversed in the ratsnest
if( item->IsConnected() )
{
auto& connItem = static_cast<BOARD_CONNECTED_ITEM&>( *item );
selectAllItemsConnectedToItem( connItem );
}
if( connItem )
selectAllItemsConnectedToItem( *connItem );
}
// Inform other potentially interested tools

View File

@ -47,6 +47,8 @@ namespace KIGFX
}
typedef void (*CLIENT_SELECTION_FILTER)( const VECTOR2I&, GENERAL_COLLECTOR& );
/**
* Class SELECTION_TOOL
*
@ -87,10 +89,12 @@ public:
/**
* Function RequestSelection()
*
* Returns the current selection set, filtered according to aFlags.
* Returns the current selection set, filtered according to aFlags
* and aClientFilter.
* If the set is empty, performs the legacy-style hover selection.
*/
SELECTION& RequestSelection( int aFlags = SELECTION_DEFAULT );
SELECTION& RequestSelection( int aFlags = SELECTION_DEFAULT,
CLIENT_SELECTION_FILTER aClientFilter = NULL );
inline TOOL_MENU& GetToolMenu()
@ -148,19 +152,23 @@ private:
* @param aOnDrag indicates whether a drag operation is being performed.
* @param aSelectionCancelledFlag allows the function to inform its caller that a selection
* was cancelled (for instance, by clicking outside of the disambiguation menu).
* @param aClientFilter allows the client to perform tool- or action-specific filtering.
* @return True if an item was selected, false otherwise.
*/
bool selectPoint( const VECTOR2I& aWhere, bool aOnDrag = false,
bool* aSelectionCancelledFlag = NULL );
bool* aSelectionCancelledFlag = NULL,
CLIENT_SELECTION_FILTER aClientFilter = NULL );
/**
* Function selectCursor()
* Selects an item under the cursor unless there is something already selected or aSelectAlways
* is true.
* @param aSelectAlways forces to select an item even if there is an item already selected.
* @param aClientFilter allows the client to perform tool- or action-specific filtering.
* @return true if eventually there is an item selected, false otherwise.
*/
bool selectCursor( bool aSelectAlways = false );
bool selectCursor( bool aSelectAlways = false,
CLIENT_SELECTION_FILTER aClientFilter = NULL );
/**
* Function selectMultiple()

View File

@ -65,6 +65,7 @@
#include <class_edge_mod.h>
#include <tools/pcb_actions.h>
#include <router/router_tool.h>
#include "pcb_tool.h"
#include <dialog_find.h>
@ -344,3 +345,8 @@ void DIALOG_BLOCK_OPTIONS::ExecuteCommand( wxCommandEvent& event )
void PCB_SCREEN::ClearUndoORRedoList( UNDO_REDO_CONTAINER& aList, int aItemCount )
{
}
void ROUTER_TOOL::NeighboringSegmentFilter( const VECTOR2I&, GENERAL_COLLECTOR& )
{
}