/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2021-2023 KiCad Developers, see AUTHORS.TXT for contributors. * * 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 */ #include #include #include #include #include #include #include #include SELECTION_TOOL::SELECTION_TOOL( const std::string& aName ) : TOOL_INTERACTIVE( aName ), 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 ), m_drag_subtractive( false ), m_canceledMenu( false ) { } void SELECTION_TOOL::setModifiersState( bool aShiftState, bool aCtrlState, bool aAltState ) { // Set the configuration of m_additive, m_subtractive, m_exclusive_or from the state of // modifier keys SHIFT and CTRL // ALT key cannot be used on MSW because of a conflict with the system menu m_subtractive = aCtrlState && aShiftState; m_additive = !aCtrlState && aShiftState; if( ctrlClickHighlights() ) { m_exclusive_or = false; m_highlight_modifier = aCtrlState && !aShiftState; } else { m_exclusive_or = aCtrlState && !aShiftState; m_highlight_modifier = false; } // Drag is more forgiving and allows either Ctrl+Drag or Shift+Drag to add to the selection // Note, however that we cannot provide disambiguation at the same time as the box selection m_drag_additive = ( aCtrlState || aShiftState ) && !aAltState; m_drag_subtractive = aCtrlState && aShiftState && !aAltState; // While the ALT key has some conflicts under MSW (and some flavors of Linux WMs), it remains // 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__ m_skip_heuristics = aAltState; #else m_skip_heuristics = false; #endif } bool SELECTION_TOOL::hasModifier() { return m_subtractive || m_additive || m_exclusive_or; } int SELECTION_TOOL::UpdateMenu( const TOOL_EVENT& aEvent ) { ACTION_MENU* actionMenu = aEvent.Parameter(); CONDITIONAL_MENU* conditionalMenu = dynamic_cast( actionMenu ); if( conditionalMenu ) conditionalMenu->Evaluate( selection() ); if( actionMenu ) actionMenu->UpdateAll(); return 0; } int SELECTION_TOOL::AddItemToSel( const TOOL_EVENT& aEvent ) { AddItemToSel( aEvent.Parameter() ); 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 ); } } int SELECTION_TOOL::ReselectItem( const TOOL_EVENT& aEvent ) { RemoveItemFromSel( aEvent.Parameter() ); selection().SetIsHover( false ); AddItemToSel( aEvent.Parameter() ); selection().SetIsHover( false ); return 0; } int SELECTION_TOOL::AddItemsToSel( const TOOL_EVENT& aEvent ) { AddItemsToSel( aEvent.Parameter(), 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() ); 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(), 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* 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. if( selection().GetSize() >= 2 && !hasModifier() ) return; // If another tool has since started running then we don't want to interrupt if( !getEditFrame()->ToolStackIsEmpty() ) return; m_toolMgr->ProcessEvent( EVENTS::DisambiguatePoint ); } int SELECTION_TOOL::SelectionMenu( const TOOL_EVENT& aEvent ) { COLLECTOR* collector = aEvent.Parameter(); if( !doSelectionMenu( collector ) ) collector->m_MenuCancelled = true; return 0; } bool SELECTION_TOOL::doSelectionMenu( COLLECTOR* aCollector ) { UNITS_PROVIDER* unitsProvider = getEditFrame(); EDA_ITEM* current = nullptr; SELECTION highlightGroup; bool selectAll = false; bool expandSelection = false; 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", item->GetItemDescription( unitsProvider ), i + 1 ); #else menuText = wxString::Format( "&%d %s\t%d", i + 1, item->GetItemDescription( unitsProvider ), i + 1 ); #endif } else { menuText = item->GetItemDescription( unitsProvider ); } 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 ); } std::optional id = evt->GetCommandId(); // 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 or clicking out of it) else { selectAll = false; current = nullptr; } } else if( evt->Action() == TA_CHOICE_MENU_CLOSED ) { break; } getView()->UpdateItems(); getEditFrame()->GetCanvas()->Refresh(); } } while( expandSelection ); getView()->Remove( &highlightGroup ); if( selectAll ) { return true; } else if( current ) { aCollector->Empty(); aCollector->Append( current ); return true; } return false; }