2021-09-05 20:37:52 +00:00
|
|
|
/*
|
|
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
|
|
*
|
2023-10-06 12:03:09 +00:00
|
|
|
* Copyright (C) 2021-2023 KiCad Developers, see AUTHORS.TXT for contributors.
|
2021-09-05 20:37:52 +00:00
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 3
|
|
|
|
* of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, you may find one here:
|
|
|
|
* http://www.gnu.org/licenses/gpl-3.0.html
|
|
|
|
* or you may search the http://www.gnu.org website for the version 3 license,
|
|
|
|
* or you may write to the Free Software Foundation, Inc.,
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
|
|
*/
|
|
|
|
|
2022-07-12 01:03:45 +00:00
|
|
|
#include <bitmaps.h>
|
|
|
|
#include <widgets/ui_common.h>
|
|
|
|
#include <collector.h>
|
|
|
|
#include <tool/tool_manager.h>
|
|
|
|
#include <tool/actions.h>
|
2021-09-05 20:37:52 +00:00
|
|
|
#include <tool/selection_tool.h>
|
2022-07-12 01:03:45 +00:00
|
|
|
#include <view/view.h>
|
|
|
|
#include <eda_draw_frame.h>
|
2024-04-27 19:57:24 +00:00
|
|
|
#include <eda_item.h>
|
2021-09-05 20:37:52 +00:00
|
|
|
|
|
|
|
|
2022-07-12 01:03:45 +00:00
|
|
|
SELECTION_TOOL::SELECTION_TOOL( const std::string& aName ) :
|
|
|
|
TOOL_INTERACTIVE( aName ),
|
2021-09-05 20:37:52 +00:00
|
|
|
m_additive( false ),
|
|
|
|
m_subtractive( false ),
|
|
|
|
m_exclusive_or( false ),
|
|
|
|
m_multiple( false ),
|
|
|
|
m_skip_heuristics( false ),
|
|
|
|
m_highlight_modifier( false ),
|
|
|
|
m_drag_additive( false ),
|
2021-09-07 17:40:08 +00:00
|
|
|
m_drag_subtractive( false ),
|
|
|
|
m_canceledMenu( false )
|
2021-09-05 20:37:52 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void SELECTION_TOOL::setModifiersState( bool aShiftState, bool aCtrlState, bool aAltState )
|
|
|
|
{
|
2021-09-05 22:15:43 +00:00
|
|
|
// Set the configuration of m_additive, m_subtractive, m_exclusive_or from the state of
|
|
|
|
// modifier keys SHIFT and CTRL
|
2021-09-05 20:37:52 +00:00
|
|
|
|
|
|
|
// ALT key cannot be used on MSW because of a conflict with the system menu
|
|
|
|
|
2021-09-06 02:34:42 +00:00
|
|
|
m_subtractive = aCtrlState && aShiftState;
|
|
|
|
m_additive = !aCtrlState && aShiftState;
|
2022-06-16 17:05:48 +00:00
|
|
|
|
|
|
|
if( ctrlClickHighlights() )
|
|
|
|
{
|
|
|
|
m_exclusive_or = false;
|
|
|
|
m_highlight_modifier = aCtrlState && !aShiftState;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_exclusive_or = aCtrlState && !aShiftState;
|
|
|
|
m_highlight_modifier = false;
|
|
|
|
}
|
2021-09-05 20:37:52 +00:00
|
|
|
|
2021-09-05 22:15:43 +00:00
|
|
|
// Drag is more forgiving and allows either Ctrl+Drag or Shift+Drag to add to the selection
|
2021-09-06 02:34:42 +00:00
|
|
|
// Note, however that we cannot provide disambiguation at the same time as the box selection
|
|
|
|
m_drag_additive = ( aCtrlState || aShiftState ) && !aAltState;
|
2021-09-05 20:37:52 +00:00
|
|
|
m_drag_subtractive = aCtrlState && aShiftState && !aAltState;
|
2021-09-06 02:32:07 +00:00
|
|
|
|
|
|
|
// While the ALT key has some conflicts under MSW (and some flavors of Linux WMs), it remains
|
2021-09-25 16:04:22 +00:00
|
|
|
// useful for users who only use tap-click rather than holding the button. We disable it for
|
|
|
|
// windows because it flashes the disambiguation menu without showing data
|
|
|
|
#ifndef __WINDOWS__
|
2021-09-06 02:32:07 +00:00
|
|
|
m_skip_heuristics = aAltState;
|
2021-09-25 16:04:22 +00:00
|
|
|
#else
|
|
|
|
m_skip_heuristics = false;
|
|
|
|
#endif
|
|
|
|
|
2021-09-05 20:37:52 +00:00
|
|
|
}
|
2022-07-12 01:03:45 +00:00
|
|
|
|
|
|
|
|
2022-12-08 19:09:30 +00:00
|
|
|
bool SELECTION_TOOL::hasModifier()
|
|
|
|
{
|
|
|
|
return m_subtractive || m_additive || m_exclusive_or;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-07-12 01:03:45 +00:00
|
|
|
int SELECTION_TOOL::UpdateMenu( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
ACTION_MENU* actionMenu = aEvent.Parameter<ACTION_MENU*>();
|
|
|
|
CONDITIONAL_MENU* conditionalMenu = dynamic_cast<CONDITIONAL_MENU*>( actionMenu );
|
|
|
|
|
|
|
|
if( conditionalMenu )
|
|
|
|
conditionalMenu->Evaluate( selection() );
|
|
|
|
|
|
|
|
if( actionMenu )
|
|
|
|
actionMenu->UpdateAll();
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int SELECTION_TOOL::AddItemToSel( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
AddItemToSel( aEvent.Parameter<EDA_ITEM*>() );
|
|
|
|
selection().SetIsHover( false );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SELECTION_TOOL::AddItemToSel( EDA_ITEM* aItem, bool aQuietMode )
|
|
|
|
{
|
|
|
|
if( aItem )
|
|
|
|
{
|
|
|
|
select( aItem );
|
|
|
|
|
|
|
|
// Inform other potentially interested tools
|
|
|
|
if( !aQuietMode )
|
|
|
|
m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-10-06 12:03:09 +00:00
|
|
|
int SELECTION_TOOL::ReselectItem( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
RemoveItemFromSel( aEvent.Parameter<EDA_ITEM*>() );
|
|
|
|
selection().SetIsHover( false );
|
|
|
|
|
|
|
|
AddItemToSel( aEvent.Parameter<EDA_ITEM*>() );
|
|
|
|
selection().SetIsHover( false );
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-07-12 01:03:45 +00:00
|
|
|
int SELECTION_TOOL::AddItemsToSel( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
AddItemsToSel( aEvent.Parameter<EDA_ITEMS*>(), false );
|
|
|
|
selection().SetIsHover( false );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SELECTION_TOOL::AddItemsToSel( EDA_ITEMS* aList, bool aQuietMode )
|
|
|
|
{
|
|
|
|
if( aList )
|
|
|
|
{
|
|
|
|
for( EDA_ITEM* item : *aList )
|
|
|
|
select( item );
|
|
|
|
|
|
|
|
// Inform other potentially interested tools
|
|
|
|
if( !aQuietMode )
|
|
|
|
m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int SELECTION_TOOL::RemoveItemFromSel( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
RemoveItemFromSel( aEvent.Parameter<EDA_ITEM*>() );
|
|
|
|
selection().SetIsHover( false );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SELECTION_TOOL::RemoveItemFromSel( EDA_ITEM* aItem, bool aQuietMode )
|
|
|
|
{
|
|
|
|
if( aItem )
|
|
|
|
{
|
|
|
|
unselect( aItem );
|
|
|
|
|
|
|
|
// Inform other potentially interested tools
|
|
|
|
if( !aQuietMode )
|
|
|
|
m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int SELECTION_TOOL::RemoveItemsFromSel( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
RemoveItemsFromSel( aEvent.Parameter<EDA_ITEMS*>(), false );
|
|
|
|
selection().SetIsHover( false );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SELECTION_TOOL::RemoveItemsFromSel( EDA_ITEMS* aList, bool aQuietMode )
|
|
|
|
{
|
|
|
|
if( aList )
|
|
|
|
{
|
|
|
|
for( EDA_ITEM* item : *aList )
|
|
|
|
unselect( item );
|
|
|
|
|
|
|
|
// Inform other potentially interested tools
|
|
|
|
if( !aQuietMode )
|
|
|
|
m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SELECTION_TOOL::RemoveItemsFromSel( std::vector<KIID>* aList, bool aQuietMode )
|
|
|
|
{
|
|
|
|
EDA_ITEMS removeItems;
|
|
|
|
|
|
|
|
for( EDA_ITEM* item : selection() )
|
|
|
|
{
|
|
|
|
if( alg::contains( *aList, item->m_Uuid ) )
|
|
|
|
removeItems.push_back( item );
|
|
|
|
}
|
|
|
|
|
|
|
|
RemoveItemsFromSel( &removeItems, aQuietMode );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SELECTION_TOOL::BrightenItem( EDA_ITEM* aItem )
|
|
|
|
{
|
|
|
|
highlight( aItem, BRIGHTENED );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SELECTION_TOOL::UnbrightenItem( EDA_ITEM* aItem )
|
|
|
|
{
|
|
|
|
unhighlight( aItem, BRIGHTENED );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SELECTION_TOOL::onDisambiguationExpire( wxTimerEvent& aEvent )
|
|
|
|
{
|
|
|
|
// If there is a multiple selection then it's more likely that we're seeing a paused drag
|
|
|
|
// than a long-click.
|
2022-12-08 19:09:48 +00:00
|
|
|
if( selection().GetSize() >= 2 && !hasModifier() )
|
2022-07-12 01:03:45 +00:00
|
|
|
return;
|
|
|
|
|
2022-11-18 17:07:02 +00:00
|
|
|
// If another tool has since started running then we don't want to interrupt
|
|
|
|
if( !getEditFrame<EDA_DRAW_FRAME>()->ToolStackIsEmpty() )
|
|
|
|
return;
|
|
|
|
|
2022-07-12 01:03:45 +00:00
|
|
|
m_toolMgr->ProcessEvent( EVENTS::DisambiguatePoint );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int SELECTION_TOOL::SelectionMenu( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
COLLECTOR* collector = aEvent.Parameter<COLLECTOR*>();
|
|
|
|
|
|
|
|
if( !doSelectionMenu( collector ) )
|
|
|
|
collector->m_MenuCancelled = true;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool SELECTION_TOOL::doSelectionMenu( COLLECTOR* aCollector )
|
|
|
|
{
|
2022-09-19 16:09:59 +00:00
|
|
|
UNITS_PROVIDER* unitsProvider = getEditFrame<EDA_DRAW_FRAME>();
|
|
|
|
EDA_ITEM* current = nullptr;
|
|
|
|
SELECTION highlightGroup;
|
|
|
|
bool selectAll = false;
|
|
|
|
bool expandSelection = false;
|
2022-07-12 01:03:45 +00:00
|
|
|
|
|
|
|
highlightGroup.SetLayer( LAYER_SELECT_OVERLAY );
|
|
|
|
getView()->Add( &highlightGroup );
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
/// The user has requested the full, non-limited list of selection items
|
|
|
|
if( expandSelection )
|
|
|
|
aCollector->Combine();
|
|
|
|
|
|
|
|
expandSelection = false;
|
|
|
|
|
|
|
|
int limit = std::min( 100, aCollector->GetCount() );
|
|
|
|
ACTION_MENU menu( true );
|
|
|
|
|
|
|
|
for( int i = 0; i < limit; ++i )
|
|
|
|
{
|
|
|
|
EDA_ITEM* item = ( *aCollector )[i];
|
|
|
|
wxString menuText;
|
|
|
|
|
|
|
|
if( i < 9 )
|
|
|
|
{
|
|
|
|
#ifdef __WXMAC__
|
|
|
|
menuText = wxString::Format( "%s\t%d",
|
2023-01-12 03:27:44 +00:00
|
|
|
item->GetItemDescription( unitsProvider ),
|
2022-07-12 01:03:45 +00:00
|
|
|
i + 1 );
|
|
|
|
#else
|
|
|
|
menuText = wxString::Format( "&%d %s\t%d",
|
|
|
|
i + 1,
|
2023-01-12 03:27:44 +00:00
|
|
|
item->GetItemDescription( unitsProvider ),
|
2022-07-12 01:03:45 +00:00
|
|
|
i + 1 );
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-01-12 03:27:44 +00:00
|
|
|
menuText = item->GetItemDescription( unitsProvider );
|
2022-07-12 01:03:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
menu.Add( menuText, i + 1, item->GetMenuImage() );
|
|
|
|
}
|
|
|
|
|
|
|
|
menu.AppendSeparator();
|
|
|
|
menu.Add( _( "Select &All\tA" ), limit + 1, BITMAPS::INVALID_BITMAP );
|
|
|
|
|
|
|
|
if( !expandSelection && aCollector->HasAdditionalItems() )
|
|
|
|
menu.Add( _( "&Expand Selection\tE" ), limit + 2, BITMAPS::INVALID_BITMAP );
|
|
|
|
|
|
|
|
if( aCollector->m_MenuTitle.Length() )
|
|
|
|
{
|
|
|
|
menu.SetTitle( aCollector->m_MenuTitle );
|
|
|
|
menu.SetIcon( BITMAPS::info );
|
|
|
|
menu.DisplayTitle( true );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
menu.DisplayTitle( false );
|
|
|
|
}
|
|
|
|
|
|
|
|
SetContextMenu( &menu, CMENU_NOW );
|
|
|
|
|
|
|
|
while( TOOL_EVENT* evt = Wait() )
|
|
|
|
{
|
|
|
|
if( evt->Action() == TA_CHOICE_MENU_UPDATE )
|
|
|
|
{
|
|
|
|
if( selectAll )
|
|
|
|
{
|
|
|
|
for( int i = 0; i < aCollector->GetCount(); ++i )
|
|
|
|
unhighlight( ( *aCollector )[i], BRIGHTENED, &highlightGroup );
|
|
|
|
}
|
|
|
|
else if( current )
|
|
|
|
{
|
|
|
|
unhighlight( current, BRIGHTENED, &highlightGroup );
|
|
|
|
}
|
|
|
|
|
|
|
|
int id = *evt->GetCommandId();
|
|
|
|
|
|
|
|
// User has pointed an item, so show it in a different way
|
|
|
|
if( id > 0 && id <= limit )
|
|
|
|
{
|
|
|
|
current = ( *aCollector )[id - 1];
|
|
|
|
highlight( current, BRIGHTENED, &highlightGroup );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
current = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
// User has pointed on the "Select All" option
|
|
|
|
if( id == limit + 1 )
|
|
|
|
{
|
|
|
|
for( int i = 0; i < aCollector->GetCount(); ++i )
|
|
|
|
highlight( ( *aCollector )[i], BRIGHTENED, &highlightGroup );
|
|
|
|
|
|
|
|
selectAll = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
selectAll = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if( evt->Action() == TA_CHOICE_MENU_CHOICE )
|
|
|
|
{
|
|
|
|
if( selectAll )
|
|
|
|
{
|
|
|
|
for( int i = 0; i < aCollector->GetCount(); ++i )
|
|
|
|
unhighlight( ( *aCollector )[i], BRIGHTENED, &highlightGroup );
|
|
|
|
}
|
|
|
|
else if( current )
|
|
|
|
{
|
|
|
|
unhighlight( current, BRIGHTENED, &highlightGroup );
|
|
|
|
}
|
|
|
|
|
2022-08-25 22:50:47 +00:00
|
|
|
std::optional<int> id = evt->GetCommandId();
|
2022-07-12 01:03:45 +00:00
|
|
|
|
|
|
|
// User has selected the "Select All" option
|
|
|
|
if( id == limit + 1 )
|
|
|
|
{
|
|
|
|
selectAll = true;
|
|
|
|
current = nullptr;
|
|
|
|
}
|
|
|
|
// User has selected the "Expand Selection" option
|
|
|
|
else if( id == limit + 2 )
|
|
|
|
{
|
|
|
|
selectAll = false;
|
|
|
|
current = nullptr;
|
|
|
|
expandSelection = true;
|
|
|
|
}
|
|
|
|
// User has selected an item, so this one will be returned
|
|
|
|
else if( id && ( *id > 0 ) && ( *id <= limit ) )
|
|
|
|
{
|
|
|
|
selectAll = false;
|
|
|
|
current = ( *aCollector )[*id - 1];
|
|
|
|
}
|
|
|
|
// User has cancelled the menu (either by <esc> or clicking out of it)
|
|
|
|
else
|
|
|
|
{
|
|
|
|
selectAll = false;
|
|
|
|
current = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if( evt->Action() == TA_CHOICE_MENU_CLOSED )
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
2022-12-14 15:08:19 +00:00
|
|
|
|
|
|
|
getView()->UpdateItems();
|
|
|
|
getEditFrame<EDA_DRAW_FRAME>()->GetCanvas()->Refresh();
|
2022-07-12 01:03:45 +00:00
|
|
|
}
|
|
|
|
} while( expandSelection );
|
|
|
|
|
|
|
|
getView()->Remove( &highlightGroup );
|
|
|
|
|
|
|
|
if( selectAll )
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if( current )
|
|
|
|
{
|
|
|
|
aCollector->Empty();
|
|
|
|
aCollector->Append( current );
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|