/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2019 CERN * Copyright (C) 2019-2022 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 2 * 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/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include SELECTION_CONDITION EE_CONDITIONS::SingleSymbol = []( const SELECTION& aSel ) { if( aSel.GetSize() == 1 ) { SCH_SYMBOL* symbol = dynamic_cast( aSel.Front() ); if( symbol ) return !symbol->GetLibSymbolRef() || !symbol->GetLibSymbolRef()->IsPower(); } return false; }; SELECTION_CONDITION EE_CONDITIONS::SingleSymbolOrPower = []( const SELECTION& aSel ) { return aSel.GetSize() == 1 && aSel.Front()->Type() == SCH_SYMBOL_T; }; SELECTION_CONDITION EE_CONDITIONS::SingleDeMorganSymbol = []( const SELECTION& aSel ) { if( aSel.GetSize() == 1 ) { SCH_SYMBOL* symbol = dynamic_cast( aSel.Front() ); if( symbol ) return symbol->GetLibSymbolRef() && symbol->GetLibSymbolRef()->HasConversion(); } return false; }; SELECTION_CONDITION EE_CONDITIONS::SingleMultiUnitSymbol = []( const SELECTION& aSel ) { if( aSel.GetSize() == 1 ) { SCH_SYMBOL* symbol = dynamic_cast( aSel.Front() ); if( symbol ) return symbol->GetLibSymbolRef() && symbol->GetLibSymbolRef()->GetUnitCount() >= 2; } return false; }; SELECTION_CONDITION EE_CONDITIONS::SingleNonExcludedMarker = []( const SELECTION& aSel ) { if( aSel.CountType( SCH_MARKER_T ) != 1 ) return false; return !static_cast( aSel.Front() )->IsExcluded(); }; #define HITTEST_THRESHOLD_PIXELS 5 EE_SELECTION_TOOL::EE_SELECTION_TOOL() : SELECTION_TOOL( "eeschema.InteractiveSelection" ), m_frame( nullptr ), m_nonModifiedCursor( KICURSOR::ARROW ), m_isSymbolEditor( false ), m_isSymbolViewer( false ), m_unit( 0 ), m_convert( 0 ) { m_selection.Clear(); } EE_SELECTION_TOOL::~EE_SELECTION_TOOL() { getView()->Remove( &m_selection ); } using E_C = EE_CONDITIONS; bool EE_SELECTION_TOOL::Init() { m_frame = getEditFrame(); SYMBOL_VIEWER_FRAME* symbolViewerFrame = dynamic_cast( m_frame ); SYMBOL_EDIT_FRAME* symbolEditorFrame = dynamic_cast( m_frame ); if( symbolEditorFrame ) { m_isSymbolEditor = true; m_unit = symbolEditorFrame->GetUnit(); m_convert = symbolEditorFrame->GetConvert(); } else { m_isSymbolViewer = symbolViewerFrame != nullptr; } static KICAD_T wireOrBusTypes[] = { SCH_ITEM_LOCATE_WIRE_T, SCH_ITEM_LOCATE_BUS_T, EOT }; static KICAD_T connectedTypes[] = { SCH_ITEM_LOCATE_WIRE_T, SCH_ITEM_LOCATE_BUS_T, SCH_GLOBAL_LABEL_T, SCH_HIER_LABEL_T, SCH_LABEL_T, SCH_DIRECTIVE_LABEL_T, SCH_SHEET_PIN_T, SCH_PIN_T, EOT }; static KICAD_T crossProbingTypes[] = { SCH_SYMBOL_T, SCH_PIN_T, SCH_SHEET_T, EOT }; auto wireSelection = E_C::Count( 1 ) && E_C::OnlyType( SCH_ITEM_LOCATE_WIRE_T ); auto busSelection = E_C::Count( 1 ) && E_C::OnlyType( SCH_ITEM_LOCATE_BUS_T ); auto wireOrBusSelection = E_C::Count( 1 ) && E_C::OnlyTypes( wireOrBusTypes ); auto connectedSelection = E_C::Count( 1 ) && E_C::OnlyTypes( connectedTypes ); auto sheetSelection = E_C::Count( 1 ) && E_C::OnlyType( SCH_SHEET_T ); auto crossProbingSelection = E_C::MoreThan( 0 ) && E_C::HasTypes( crossProbingTypes ); auto schEditSheetPageNumberCondition = [&] ( const SELECTION& aSel ) { if( m_isSymbolEditor || m_isSymbolViewer ) return false; return E_C::LessThan( 2 )( aSel ) && E_C::OnlyType( SCH_SHEET_T )( aSel ); }; auto schEditCondition = [this] ( const SELECTION& aSel ) { return !m_isSymbolEditor && !m_isSymbolViewer; }; auto belowRootSheetCondition = [&]( const SELECTION& aSel ) { SCH_EDIT_FRAME* editFrame = dynamic_cast( m_frame ); return editFrame && editFrame->GetCurrentSheet().Last() != &editFrame->Schematic().Root(); }; auto haveHighlight = [&]( const SELECTION& sel ) { SCH_EDIT_FRAME* editFrame = dynamic_cast( m_frame ); return editFrame && editFrame->GetHighlightedConnection() != nullptr; }; auto haveSymbol = [&]( const SELECTION& sel ) { return m_isSymbolEditor && static_cast( m_frame )->GetCurSymbol(); }; auto& menu = m_menu.GetMenu(); menu.AddItem( EE_ACTIONS::clearHighlight, haveHighlight && EE_CONDITIONS::Idle, 1 ); menu.AddSeparator( haveHighlight && EE_CONDITIONS::Idle, 1 ); menu.AddItem( EE_ACTIONS::enterSheet, sheetSelection && EE_CONDITIONS::Idle, 2 ); menu.AddItem( EE_ACTIONS::selectOnPCB, crossProbingSelection && EE_CONDITIONS::Idle, 2 ); menu.AddItem( EE_ACTIONS::leaveSheet, belowRootSheetCondition, 2 ); menu.AddSeparator( 100 ); menu.AddItem( EE_ACTIONS::drawWire, schEditCondition && EE_CONDITIONS::Empty, 100 ); menu.AddItem( EE_ACTIONS::drawBus, schEditCondition && EE_CONDITIONS::Empty, 100 ); menu.AddSeparator( 100 ); menu.AddItem( EE_ACTIONS::finishWire, SCH_LINE_WIRE_BUS_TOOL::IsDrawingWire, 100 ); menu.AddSeparator( 100 ); menu.AddItem( EE_ACTIONS::finishBus, SCH_LINE_WIRE_BUS_TOOL::IsDrawingBus, 100 ); menu.AddSeparator( 200 ); menu.AddItem( EE_ACTIONS::selectConnection, connectedSelection && EE_CONDITIONS::Idle, 250 ); menu.AddItem( EE_ACTIONS::placeJunction, wireOrBusSelection && EE_CONDITIONS::Idle, 250 ); menu.AddItem( EE_ACTIONS::placeLabel, wireOrBusSelection && EE_CONDITIONS::Idle, 250 ); menu.AddItem( EE_ACTIONS::placeClassLabel, wireOrBusSelection && EE_CONDITIONS::Idle, 250 ); menu.AddItem( EE_ACTIONS::placeGlobalLabel, wireOrBusSelection && EE_CONDITIONS::Idle, 250 ); menu.AddItem( EE_ACTIONS::placeHierLabel, wireOrBusSelection && EE_CONDITIONS::Idle, 250 ); menu.AddItem( EE_ACTIONS::breakWire, wireSelection && EE_CONDITIONS::Idle, 250 ); menu.AddItem( EE_ACTIONS::breakBus, busSelection && EE_CONDITIONS::Idle, 250 ); menu.AddItem( EE_ACTIONS::importSingleSheetPin, sheetSelection && EE_CONDITIONS::Idle, 250 ); menu.AddItem( EE_ACTIONS::assignNetclass, connectedSelection && EE_CONDITIONS::Idle, 250 ); menu.AddItem( EE_ACTIONS::editPageNumber, schEditSheetPageNumberCondition, 250 ); menu.AddSeparator( 400 ); menu.AddItem( EE_ACTIONS::symbolProperties, haveSymbol && EE_CONDITIONS::Empty, 400 ); menu.AddItem( EE_ACTIONS::pinTable, haveSymbol && EE_CONDITIONS::Empty, 400 ); menu.AddSeparator( 1000 ); m_frame->AddStandardSubMenus( m_menu ); m_disambiguateTimer.SetOwner( this ); Connect( wxEVT_TIMER, wxTimerEventHandler( EE_SELECTION_TOOL::onDisambiguationExpire ), nullptr, this ); return true; } void EE_SELECTION_TOOL::Reset( RESET_REASON aReason ) { m_frame = getEditFrame(); if( aReason == TOOL_BASE::MODEL_RELOAD ) { // Remove pointers to the selected items from containers without changing their // properties (as they are already deleted while a new sheet is loaded) m_selection.Clear(); getView()->GetPainter()->GetSettings()->SetHighlight( false ); SYMBOL_EDIT_FRAME* symbolEditFrame = dynamic_cast( m_frame ); SYMBOL_VIEWER_FRAME* symbolViewerFrame = dynamic_cast( m_frame ); if( symbolEditFrame ) { m_isSymbolEditor = true; m_unit = symbolEditFrame->GetUnit(); m_convert = symbolEditFrame->GetConvert(); } else { m_isSymbolViewer = symbolViewerFrame != nullptr; } } else { // Restore previous properties of selected items and remove them from containers ClearSelection(); } // Reinsert the VIEW_GROUP, in case it was removed from the VIEW getView()->Remove( &m_selection ); getView()->Add( &m_selection ); } const KICAD_T movableSymbolItems[] = { LIB_SHAPE_T, LIB_TEXT_T, LIB_TEXTBOX_T, LIB_PIN_T, LIB_FIELD_T, EOT }; const KICAD_T movableSymbolAliasItems[] = { LIB_FIELD_T, EOT }; int EE_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent ) { m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW ); KIID lastRolloverItem = niluuid; // Main loop: keep receiving events while( TOOL_EVENT* evt = Wait() ) { bool selCancelled = false; bool displayWireCursor = false; bool displayBusCursor = false; bool displayLineCursor = false; KIID rolloverItem = lastRolloverItem; // on left click, a selection is made, depending on modifiers ALT, SHIFT, CTRL: setModifiersState( evt->Modifier( MD_SHIFT ), evt->Modifier( MD_CTRL ), evt->Modifier( MD_ALT ) ); bool modifier_enabled = m_subtractive || m_additive || m_exclusive_or; MOUSE_DRAG_ACTION drag_action = m_frame->GetDragAction(); EE_GRID_HELPER grid( m_toolMgr ); if( evt->IsMouseDown( BUT_LEFT ) ) { // Avoid triggering when running under other tools if( m_frame->ToolStackIsEmpty() && m_toolMgr->GetTool() && !m_toolMgr->GetTool()->HasPoint() ) { m_originalCursor = m_toolMgr->GetMousePosition(); m_disambiguateTimer.StartOnce( 500 ); } } // Single click? Select single object else if( evt->IsClick( BUT_LEFT ) ) { // If the timer has stopped, then we have already run the disambiguate routine // and we don't want to register an extra click here if( !m_disambiguateTimer.IsRunning() ) { evt->SetPassEvent(); continue; } m_disambiguateTimer.Stop(); if( SCH_EDIT_FRAME* schframe = dynamic_cast( m_frame ) ) schframe->FocusOnItem( nullptr ); // Collect items at the clicked location (doesn't select them yet) EE_COLLECTOR collector; CollectHits( collector, evt->Position() ); narrowSelection( collector, evt->Position(), false ); if( collector.GetCount() == 1 && !m_isSymbolEditor && !modifier_enabled ) { OPT_TOOL_EVENT autostart = autostartEvent( evt, grid, collector[0] ); if( autostart ) { DRAW_SEGMENT_EVENT_PARAMS* params = new DRAW_SEGMENT_EVENT_PARAMS(); params->layer = autostart->Parameter()->layer; params->quitOnDraw = true; params->sourceSegment = dynamic_cast( collector[0] ); autostart->SetParameter( params ); m_toolMgr->ProcessEvent( *autostart ); selCancelled = true; } else if( collector[0]->IsHypertext() ) { collector[0]->DoHypertextMenu( m_frame ); selCancelled = true; } } if( !selCancelled ) { selectPoint( collector, evt->Position(), nullptr, nullptr, m_additive, m_subtractive, m_exclusive_or ); m_selection.SetIsHover( false ); } } else if( evt->IsClick( BUT_RIGHT ) ) { m_disambiguateTimer.Stop(); // right click? if there is any object - show the context menu if( m_selection.Empty() ) { ClearSelection(); SelectPoint( evt->Position(), EE_COLLECTOR::AllItems, nullptr, &selCancelled ); m_selection.SetIsHover( true ); } // If the cursor has moved off the bounding box of the selection by more than // a grid square, check to see if there is another item available for selection // under the cursor. If there is, the user likely meant to get the context menu // for that item. If there is no new item, then keep the original selection and // show the context menu for it. else if( !m_selection.GetBoundingBox().Inflate( grid.GetGrid().x, grid.GetGrid().y ) .Contains( evt->Position() ) ) { EE_SELECTION saved_selection = m_selection; for( EDA_ITEM* item : saved_selection ) RemoveItemFromSel( item, true ); SelectPoint( evt->Position(), EE_COLLECTOR::AllItems, nullptr, &selCancelled ); if( m_selection.Empty() ) { m_selection.SetIsHover( false ); for( EDA_ITEM* item : saved_selection ) AddItemToSel( item, true); } else { m_selection.SetIsHover( true ); } } if( !selCancelled ) m_menu.ShowContextMenu( m_selection ); } else if( evt->IsDblClick( BUT_LEFT ) ) { m_disambiguateTimer.Stop(); // double click? Display the properties window if( SCH_EDIT_FRAME* schframe = dynamic_cast( m_frame ) ) schframe->FocusOnItem( nullptr ); if( m_selection.Empty() ) SelectPoint( evt->Position() ); EDA_ITEM* item = m_selection.Front(); if( item && item->Type() == SCH_SHEET_T ) m_toolMgr->RunAction( EE_ACTIONS::enterSheet ); else m_toolMgr->RunAction( EE_ACTIONS::properties ); } else if( evt->IsDblClick( BUT_MIDDLE ) ) { m_disambiguateTimer.Stop(); // Middle double click? Do zoom to fit or zoom to objects if( evt->Modifier( MD_CTRL ) ) // Is CTRL key down? m_toolMgr->RunAction( ACTIONS::zoomFitObjects, true ); else m_toolMgr->RunAction( ACTIONS::zoomFitScreen, true ); } else if( evt->IsDrag( BUT_LEFT ) ) { m_disambiguateTimer.Stop(); // Is another tool already moving a new object? Don't allow a drag start if( !m_selection.Empty() && m_selection[0]->HasFlag( IS_NEW | IS_MOVING ) ) { evt->SetPassEvent(); continue; } // drag with LMB? Select multiple objects (or at least draw a selection box) or // drag them if( SCH_EDIT_FRAME* schframe = dynamic_cast( m_frame ) ) schframe->FocusOnItem( nullptr ); if( modifier_enabled || drag_action == MOUSE_DRAG_ACTION::SELECT ) { selectMultiple(); } else if( m_selection.Empty() && drag_action != MOUSE_DRAG_ACTION::DRAG_ANY ) { selectMultiple(); } else { if( m_isSymbolEditor ) { if( static_cast( m_frame )->IsSymbolAlias() ) m_selection = RequestSelection( movableSymbolAliasItems ); else m_selection = RequestSelection( movableSymbolItems ); } else { m_selection = RequestSelection( EE_COLLECTOR::MovableItems ); } // Check if dragging has started within any of selected items bounding box if( selectionContains( evt->Position() ) ) { // Yes -> run the move tool and wait till it finishes if( m_isSymbolEditor ) m_toolMgr->InvokeTool( "eeschema.SymbolMoveTool" ); else m_toolMgr->InvokeTool( "eeschema.InteractiveMove" ); } else { // No -> drag a selection box selectMultiple(); } } } else if( evt->IsMouseDown( BUT_AUX1 ) ) { m_toolMgr->RunAction( EE_ACTIONS::navigateBack, true ); } else if( evt->IsMouseDown( BUT_AUX2 ) ) { m_toolMgr->RunAction( EE_ACTIONS::navigateForward, true ); } else if( evt->Category() == TC_COMMAND && evt->Action() == TA_CHOICE_MENU_CHOICE ) { m_disambiguateTimer.Stop(); // context sub-menu selection? Handle unit selection or bus unfolding if( evt->GetCommandId().get() >= ID_POPUP_SCH_SELECT_UNIT_CMP && evt->GetCommandId().get() <= ID_POPUP_SCH_SELECT_UNIT_SYM_MAX ) { SCH_SYMBOL* symbol = dynamic_cast( m_selection.Front() ); int unit = evt->GetCommandId().get() - ID_POPUP_SCH_SELECT_UNIT_CMP; if( symbol ) static_cast( m_frame )->SelectUnit( symbol, unit ); } else if( evt->GetCommandId().get() >= ID_POPUP_SCH_UNFOLD_BUS && evt->GetCommandId().get() <= ID_POPUP_SCH_UNFOLD_BUS_END ) { wxString* net = new wxString( *evt->Parameter() ); m_toolMgr->RunAction( EE_ACTIONS::unfoldBus, true, net ); } } else if( evt->IsCancelInteractive() ) { m_disambiguateTimer.Stop(); if( SCH_EDIT_FRAME* schframe = dynamic_cast( m_frame ) ) schframe->FocusOnItem( nullptr ); if( !GetSelection().Empty() ) { ClearSelection(); } else if( evt->FirstResponder() == this && evt->GetCommandId() != (int) ID_SEL_TOOL ) { SCH_EDITOR_CONTROL* editor = m_toolMgr->GetTool(); if( editor ) editor->ClearHighlight( *evt ); } } else if( evt->Action() == TA_UNDO_REDO_PRE ) { if( SCH_EDIT_FRAME* schframe = dynamic_cast( m_frame ) ) schframe->FocusOnItem( nullptr ); ClearSelection(); } else if( evt->IsMotion() && !m_isSymbolEditor && evt->FirstResponder() == this ) { // Update cursor and rollover item rolloverItem = niluuid; EE_COLLECTOR collector; getViewControls()->ForceCursorPosition( false ); if( CollectHits( collector, evt->Position() ) ) { narrowSelection( collector, evt->Position(), false ); if( collector.GetCount() == 1 && !modifier_enabled ) { OPT_TOOL_EVENT autostartEvt = autostartEvent( evt, grid, collector[0] ); if( autostartEvt ) { if( autostartEvt->Matches( EE_ACTIONS::drawBus.MakeEvent() ) ) displayBusCursor = true; else if( autostartEvt->Matches( EE_ACTIONS::drawWire.MakeEvent() ) ) displayWireCursor = true; else if( autostartEvt->Matches( EE_ACTIONS::drawLines.MakeEvent() ) ) displayLineCursor = true; } else if( collector[0]->IsHypertext() && !collector[0]->IsSelected() ) { rolloverItem = collector[0]->m_Uuid; } } } } else { evt->SetPassEvent(); } if( rolloverItem != lastRolloverItem ) { EDA_ITEM* item = m_frame->GetItem( lastRolloverItem ); if( item ) { item->ClearFlags( IS_ROLLOVER ); lastRolloverItem = niluuid; if( item->Type() == SCH_FIELD_T ) m_frame->GetCanvas()->GetView()->Update( item->GetParent() ); else m_frame->GetCanvas()->GetView()->Update( item ); } item = m_frame->GetItem( rolloverItem ); if( item ) { item->SetFlags( IS_ROLLOVER ); lastRolloverItem = rolloverItem; if( item->Type() == SCH_FIELD_T ) m_frame->GetCanvas()->GetView()->Update( item->GetParent() ); else m_frame->GetCanvas()->GetView()->Update( item ); } } if( m_frame->ToolStackIsEmpty() ) { if( displayWireCursor ) { m_nonModifiedCursor = KICURSOR::LINE_WIRE_ADD; } else if( displayBusCursor ) { m_nonModifiedCursor = KICURSOR::LINE_BUS; } else if( displayLineCursor ) { m_nonModifiedCursor = KICURSOR::LINE_GRAPHIC; } else if( rolloverItem != niluuid ) { m_nonModifiedCursor = KICURSOR::HAND; } else if( !m_selection.Empty() && drag_action == MOUSE_DRAG_ACTION::DRAG_SELECTED && evt->HasPosition() && selectionContains( evt->Position() ) ) //move/drag option prediction { m_nonModifiedCursor = KICURSOR::MOVING; } else { m_nonModifiedCursor = KICURSOR::ARROW; } } } m_disambiguateTimer.Stop(); // Shutting down; clear the selection m_selection.Clear(); return 0; } OPT_TOOL_EVENT EE_SELECTION_TOOL::autostartEvent( TOOL_EVENT* aEvent, EE_GRID_HELPER& aGrid, SCH_ITEM* aItem ) { VECTOR2I pos = aGrid.BestSnapAnchor( aEvent->Position(), LAYER_CONNECTABLE ); if( m_frame->eeconfig()->m_Drawing.auto_start_wires && !m_toolMgr->GetTool()->HasPoint() && aItem->IsPointClickableAnchor( pos ) ) { OPT_TOOL_EVENT newEvt = EE_ACTIONS::drawWire.MakeEvent(); if( aItem->Type() == SCH_BUS_BUS_ENTRY_T ) { newEvt = EE_ACTIONS::drawBus.MakeEvent(); } else if( aItem->Type() == SCH_BUS_WIRE_ENTRY_T ) { SCH_BUS_WIRE_ENTRY* busEntry = static_cast( aItem ); if( !busEntry->m_connected_bus_item ) newEvt = EE_ACTIONS::drawBus.MakeEvent(); } else if( aItem->Type() == SCH_LINE_T ) { SCH_LINE* line = static_cast( aItem ); if( line->IsBus() ) newEvt = EE_ACTIONS::drawBus.MakeEvent(); else if( line->IsGraphicLine() ) newEvt = EE_ACTIONS::drawLines.MakeEvent(); } newEvt->SetMousePosition( pos ); newEvt->SetHasPosition( true ); newEvt->SetForceImmediate( true ); getViewControls()->ForceCursorPosition( true, pos ); return newEvt; } return OPT_TOOL_EVENT(); } int EE_SELECTION_TOOL::disambiguateCursor( const TOOL_EVENT& aEvent ) { wxMouseState keyboardState = wxGetMouseState(); setModifiersState( keyboardState.ShiftDown(), keyboardState.ControlDown(), keyboardState.AltDown() ); m_skip_heuristics = true; SelectPoint( m_originalCursor, EE_COLLECTOR::AllItems, nullptr, &m_canceledMenu, false, m_additive, m_subtractive, m_exclusive_or ); m_skip_heuristics = false; return 0; } void EE_SELECTION_TOOL::OnIdle( wxIdleEvent& aEvent ) { if( m_frame->ToolStackIsEmpty() && !m_multiple ) { wxMouseState keyboardState = wxGetMouseState(); setModifiersState( keyboardState.ShiftDown(), keyboardState.ControlDown(), keyboardState.AltDown() ); if( m_additive ) m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ADD ); else if( m_subtractive ) m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::SUBTRACT ); else if( m_exclusive_or ) m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::XOR ); else m_frame->GetCanvas()->SetCurrentCursor( m_nonModifiedCursor ); } } EE_SELECTION& EE_SELECTION_TOOL::GetSelection() { return m_selection; } bool EE_SELECTION_TOOL::CollectHits( EE_COLLECTOR& aCollector, const VECTOR2I& aWhere, const KICAD_T* aFilterList ) { int pixelThreshold = KiROUND( getView()->ToWorld( HITTEST_THRESHOLD_PIXELS ) ); int gridThreshold = KiROUND( getView()->GetGAL()->GetGridSize().EuclideanNorm() / 2 ); aCollector.m_Threshold = std::max( pixelThreshold, gridThreshold ); if( m_isSymbolEditor ) { LIB_SYMBOL* symbol = static_cast( m_frame )->GetCurSymbol(); if( !symbol ) return false; aCollector.Collect( symbol->GetDrawItems(), aFilterList, aWhere, m_unit, m_convert ); } else { aCollector.Collect( m_frame->GetScreen(), aFilterList, aWhere, m_unit, m_convert ); } return aCollector.GetCount() > 0; } void EE_SELECTION_TOOL::narrowSelection( EE_COLLECTOR& collector, const VECTOR2I& aWhere, bool aCheckLocked ) { for( int i = collector.GetCount() - 1; i >= 0; --i ) { if( !Selectable( collector[i], &aWhere ) ) { collector.Remove( i ); continue; } if( aCheckLocked && collector[i]->IsLocked() ) { collector.Remove( i ); continue; } } // Apply some ugly heuristics to avoid disambiguation menus whenever possible if( collector.GetCount() > 1 && !m_skip_heuristics ) GuessSelectionCandidates( collector, aWhere ); } bool EE_SELECTION_TOOL::selectPoint( EE_COLLECTOR& aCollector, const VECTOR2I& aWhere, EDA_ITEM** aItem, bool* aSelectionCancelledFlag, bool aAdd, bool aSubtract, bool aExclusiveOr ) { m_selection.ClearReferencePoint(); // If still more than one item we're going to have to ask the user. if( aCollector.GetCount() > 1 ) { // Try to call selectionMenu via RunAction() to avoid event-loop contention // But it we cannot handle the event, then we don't have an active tool loop, so // handle it directly. if( !m_toolMgr->RunAction( EE_ACTIONS::selectionMenu, true, &aCollector ) ) { if( !doSelectionMenu( &aCollector ) ) aCollector.m_MenuCancelled = true; } if( aCollector.m_MenuCancelled ) { if( aSelectionCancelledFlag ) *aSelectionCancelledFlag = true; return false; } } if( !aAdd && !aSubtract && !aExclusiveOr ) ClearSelection(); bool anyAdded = false; bool anySubtracted = false; if( aCollector.GetCount() > 0 ) { for( int i = 0; i < aCollector.GetCount(); ++i ) { EDA_ITEM_FLAGS flags = 0; // Handle line ends specially if( aCollector[i]->Type() == SCH_LINE_T ) { SCH_LINE* line = (SCH_LINE*) aCollector[i]; if( HitTestPoints( line->GetStartPoint(), aWhere, aCollector.m_Threshold ) ) flags = STARTPOINT; else if( HitTestPoints( line->GetEndPoint(), aWhere, aCollector.m_Threshold ) ) flags = ENDPOINT; else flags = STARTPOINT | ENDPOINT; } if( aSubtract || ( aExclusiveOr && aCollector[i]->IsSelected() ) ) { aCollector[i]->ClearFlags( flags ); if( !aCollector[i]->HasFlag( STARTPOINT ) && !aCollector[i]->HasFlag( ENDPOINT ) ) { unselect( aCollector[i] ); anySubtracted = true; } } else { aCollector[i]->SetFlags( flags ); select( aCollector[i] ); anyAdded = true; } } } if( anyAdded ) { m_toolMgr->ProcessEvent( EVENTS::SelectedEvent ); if( aItem && aCollector.GetCount() == 1 ) *aItem = aCollector[0]; return true; } else if( anySubtracted ) { m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent ); return true; } return false; } bool EE_SELECTION_TOOL::SelectPoint( const VECTOR2I& aWhere, const KICAD_T* aFilterList, EDA_ITEM** aItem, bool* aSelectionCancelledFlag, bool aCheckLocked, bool aAdd, bool aSubtract, bool aExclusiveOr ) { EE_COLLECTOR collector; if( !CollectHits( collector, aWhere, aFilterList ) ) return false; narrowSelection( collector, aWhere, aCheckLocked ); return selectPoint( collector, aWhere, aItem, aSelectionCancelledFlag, aAdd, aSubtract, aExclusiveOr ); } int EE_SELECTION_TOOL::SelectAll( const TOOL_EVENT& aEvent ) { m_multiple = true; // Multiple selection mode is active KIGFX::VIEW* view = getView(); // hold all visible items std::vector selectedItems; std::vector sheetPins; // Filter the view items based on the selection box BOX2I selectionBox; selectionBox.SetMaximum(); view->Query( selectionBox, selectedItems ); // Get the list of selected items // Sheet pins aren't in the view; add them by hand for( KIGFX::VIEW::LAYER_ITEM_PAIR& pair : selectedItems ) { SCH_SHEET* sheet = dynamic_cast( pair.first ); if( sheet ) { int layer = pair.second; for( SCH_SHEET_PIN* pin : sheet->GetPins() ) sheetPins.emplace_back( KIGFX::VIEW::LAYER_ITEM_PAIR( pin, layer ) ); } } selectedItems.insert( selectedItems.end(), sheetPins.begin(), sheetPins.end() ); for( const std::pair& item_pair : selectedItems ) { if( EDA_ITEM* item = dynamic_cast( item_pair.first ) ) { if( Selectable( item ) ) { if( item->Type() == SCH_LINE_T ) item->SetFlags( STARTPOINT | ENDPOINT ); select( item ); } } } m_multiple = false; return 0; } void EE_SELECTION_TOOL::GuessSelectionCandidates( EE_COLLECTOR& collector, const VECTOR2I& aPos ) { // Prefer exact hits to sloppy ones std::set exactHits; for( int i = collector.GetCount() - 1; i >= 0; --i ) { EDA_ITEM* item = collector[ i ]; SCH_LINE* line = dynamic_cast( item ); LIB_SHAPE* shape = dynamic_cast( item ); // Lines are hard to hit. Give them a bit more slop to still be considered "exact". if( line || ( shape && shape->GetShape() == SHAPE_T::POLY ) ) { if( item->HitTest( aPos, Mils2iu( DANGLING_SYMBOL_SIZE ) ) ) exactHits.insert( item ); } else { if( item->HitTest( aPos, 0 ) ) exactHits.insert( item ); } } if( exactHits.size() > 0 && exactHits.size() < (unsigned) collector.GetCount() ) { for( int i = collector.GetCount() - 1; i >= 0; --i ) { EDA_ITEM* item = collector[ i ]; if( !exactHits.count( item ) ) collector.Transfer( item ); } } // Find the closest item. (Note that at this point all hits are either exact or non-exact.) VECTOR2I pos( aPos ); SEG poss( m_isSymbolEditor ? mapCoords( pos ) : pos, m_isSymbolEditor ? mapCoords( pos ) : pos ); EDA_ITEM* closest = nullptr; int closestDist = INT_MAX / 2; for( EDA_ITEM* item : collector ) { EDA_RECT bbox = item->GetBoundingBox(); int dist = INT_MAX / 2; if( exactHits.count( item ) ) { if( item->Type() == SCH_PIN_T || item->Type() == SCH_JUNCTION_T ) { closest = item; break; } SCH_LINE* line = dynamic_cast( item ); EDA_TEXT* text = dynamic_cast( item ); SCH_SYMBOL* symbol = dynamic_cast( item ); if( line ) { dist = DistanceLinePoint( line->GetStartPoint(), line->GetEndPoint(), pos ); } else if( text ) { text->GetEffectiveTextShape()->Collide( poss, closestDist, &dist ); } else if( symbol ) { try { bbox = symbol->GetBodyBoundingBox(); } catch( const boost::bad_pointer& exc ) { // This may be overkill and could be an assertion but we are more likely to // find any boost pointer container errors this way. wxLogError( wxT( "Boost bad pointer exception '%s' occurred." ), exc.what() ); } SHAPE_RECT rect( bbox.GetPosition(), bbox.GetWidth(), bbox.GetHeight() ); if( bbox.Contains( pos ) ) dist = EuclideanNorm( bbox.GetCenter() - pos ); else rect.Collide( poss, closestDist, &dist ); } else { dist = EuclideanNorm( bbox.GetCenter() - pos ); } } else { SHAPE_RECT rect( bbox.GetPosition(), bbox.GetWidth(), bbox.GetHeight() ); rect.Collide( poss, collector.m_Threshold, &dist ); } if( dist == closestDist ) { if( item->GetParent() == closest ) closest = item; } else if( dist < closestDist ) { closestDist = dist; closest = item; } } // Construct a tight box (1/2 height and width) around the center of the closest item. // All items which exist at least partly outside this box have sufficient other areas // for selection and can be dropped. if( closest ) // Don't try and get a tight bbox if nothing is near the mouse pointer { EDA_RECT tightBox = closest->GetBoundingBox(); tightBox.Inflate( -tightBox.GetWidth() / 4, -tightBox.GetHeight() / 4 ); for( int i = collector.GetCount() - 1; i >= 0; --i ) { EDA_ITEM* item = collector[i]; if( item == closest ) continue; if( !item->HitTest( tightBox, true ) ) collector.Transfer( item ); } } } EE_SELECTION& EE_SELECTION_TOOL::RequestSelection( const KICAD_T aFilterList[] ) { if( m_selection.Empty() ) { VECTOR2D cursorPos = getViewControls()->GetCursorPosition( true ); ClearSelection(); SelectPoint( cursorPos, aFilterList ); m_selection.SetIsHover( true ); m_selection.ClearReferencePoint(); } else // Trim an existing selection by aFilterList { bool isMoving = false; for( int i = (int) m_selection.GetSize() - 1; i >= 0; --i ) { EDA_ITEM* item = (EDA_ITEM*) m_selection.GetItem( i ); isMoving |= static_cast( item )->IsMoving(); if( !item->IsType( aFilterList ) ) { unselect( item ); m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent ); } } if( !isMoving ) updateReferencePoint(); } return m_selection; } void EE_SELECTION_TOOL::updateReferencePoint() { VECTOR2I refP( 0, 0 ); if( m_selection.Size() > 0 ) { if( m_isSymbolEditor ) refP = static_cast( m_selection.GetTopLeftItem() )->GetPosition(); else refP = static_cast( m_selection.GetTopLeftItem() )->GetPosition(); } m_selection.SetReferencePoint( refP ); } // Some navigation actions are allowed in selectMultiple const TOOL_ACTION* allowedActions[] = { &ACTIONS::panUp, &ACTIONS::panDown, &ACTIONS::panLeft, &ACTIONS::panRight, &ACTIONS::cursorUp, &ACTIONS::cursorDown, &ACTIONS::cursorLeft, &ACTIONS::cursorRight, &ACTIONS::cursorUpFast, &ACTIONS::cursorDownFast, &ACTIONS::cursorLeftFast, &ACTIONS::cursorRightFast, &ACTIONS::zoomIn, &ACTIONS::zoomOut, &ACTIONS::zoomInCenter, &ACTIONS::zoomOutCenter, &ACTIONS::zoomCenter, &ACTIONS::zoomFitScreen, &ACTIONS::zoomFitObjects, nullptr }; bool EE_SELECTION_TOOL::selectMultiple() { bool cancelled = false; // Was the tool canceled while it was running? m_multiple = true; // Multiple selection mode is active KIGFX::VIEW* view = getView(); KIGFX::PREVIEW::SELECTION_AREA area; view->Add( &area ); while( TOOL_EVENT* evt = Wait() ) { int width = area.GetEnd().x - area.GetOrigin().x; int height = area.GetEnd().y - area.GetOrigin().y; /* 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 isWindowSelection = width >= 0; if( view->IsMirroredX() ) isWindowSelection = !isWindowSelection; m_frame->GetCanvas()->SetCurrentCursor( isWindowSelection ? KICURSOR::SELECT_WINDOW : KICURSOR::SELECT_LASSO ); if( evt->IsCancelInteractive() || evt->IsActivate() ) { cancelled = true; break; } if( evt->IsDrag( BUT_LEFT ) ) { if( !m_drag_additive && !m_drag_subtractive ) ClearSelection(); // Start drawing a selection box area.SetOrigin( evt->DragOrigin() ); area.SetEnd( evt->Position() ); area.SetAdditive( m_drag_additive ); area.SetSubtractive( m_drag_subtractive ); area.SetExclusiveOr( false ); view->SetVisible( &area, true ); view->Update( &area ); getViewControls()->SetAutoPan( true ); } if( evt->IsMouseUp( BUT_LEFT ) ) { getViewControls()->SetAutoPan( false ); // End drawing the selection box view->SetVisible( &area, false ); // Fetch items from the RTree that are in our area of interest std::vector nearbyViewItems; view->Query( area.ViewBBox(), nearbyViewItems ); // Build lists of nearby items and their children std::vector nearbyItems; std::vector nearbyChildren; std::vector flaggedItems; for( KIGFX::VIEW::LAYER_ITEM_PAIR& pair : nearbyViewItems ) { EDA_ITEM* item = dynamic_cast( pair.first ); if( item ) { item->ClearFlags( CANDIDATE ); nearbyItems.push_back( item ); } if( SCH_ITEM* sch_item = dynamic_cast( item ) ) { sch_item->RunOnChildren( [&]( SCH_ITEM* aChild ) { nearbyChildren.push_back( aChild ); } ); } } EDA_RECT selectionRect( area.GetOrigin(), wxSize( width, height ) ); selectionRect.Normalize(); bool anyAdded = false; bool anySubtracted = false; auto selectItem = [&]( EDA_ITEM* aItem ) { EDA_ITEM_FLAGS flags = 0; // Handle line ends specially if( aItem->Type() == SCH_LINE_T ) { SCH_LINE* line = (SCH_LINE*) aItem; if( selectionRect.Contains( line->GetStartPoint() ) ) flags |= STARTPOINT; if( selectionRect.Contains( line->GetEndPoint() ) ) flags |= ENDPOINT; // If no ends were selected, select whole line (both ends) // Also select both ends if the selection overlaps the midpoint if( ( !( flags & STARTPOINT ) && !( flags & ENDPOINT ) ) || selectionRect.Contains( line->GetMidPoint() ) ) { flags = STARTPOINT | ENDPOINT; } } if( m_subtractive || ( m_exclusive_or && aItem->IsSelected() ) ) { aItem->ClearFlags( flags ); if( !aItem->HasFlag( STARTPOINT ) && !aItem->HasFlag( ENDPOINT ) ) { unselect( aItem ); anySubtracted = true; } } else { aItem->SetFlags( flags ); select( aItem ); anyAdded = true; } }; for( EDA_ITEM* item : nearbyItems ) { if( Selectable( item ) && item->HitTest( selectionRect, isWindowSelection ) ) { item->SetFlags( CANDIDATE ); flaggedItems.push_back( item ); selectItem( item ); } } for( EDA_ITEM* item : nearbyChildren ) { if( Selectable( item ) && !item->GetParent()->HasFlag( CANDIDATE ) && item->HitTest( selectionRect, isWindowSelection ) ) { selectItem( item ); } } for( EDA_ITEM* item : flaggedItems ) item->ClearFlags( CANDIDATE ); m_selection.SetIsHover( false ); // Inform other potentially interested tools if( anyAdded ) m_toolMgr->ProcessEvent( EVENTS::SelectedEvent ); if( anySubtracted ) m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent ); 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; } } } getViewControls()->SetAutoPan( false ); // Stop drawing the selection box view->Remove( &area ); m_multiple = false; // Multiple selection mode is inactive if( !cancelled ) m_selection.ClearReferencePoint(); return cancelled; } static KICAD_T nodeTypes[] = { SCH_SYMBOL_LOCATE_POWER_T, SCH_PIN_T, SCH_ITEM_LOCATE_WIRE_T, SCH_ITEM_LOCATE_BUS_T, SCH_BUS_WIRE_ENTRY_T, SCH_BUS_BUS_ENTRY_T, SCH_LABEL_T, SCH_HIER_LABEL_T, SCH_GLOBAL_LABEL_T, SCH_SHEET_PIN_T, SCH_DIRECTIVE_LABEL_T, SCH_JUNCTION_T, EOT }; EDA_ITEM* EE_SELECTION_TOOL::GetNode( VECTOR2I aPosition ) { EE_COLLECTOR collector; //TODO(snh): Reimplement after exposing KNN interface int pixelThreshold = KiROUND( getView()->ToWorld( HITTEST_THRESHOLD_PIXELS ) ); int gridThreshold = KiROUND( getView()->GetGAL()->GetGridSize().EuclideanNorm() ); int thresholdMax = std::max( pixelThreshold, gridThreshold ); for( int threshold : { 0, thresholdMax/4, thresholdMax/2, thresholdMax } ) { collector.m_Threshold = threshold; collector.Collect( m_frame->GetScreen(), nodeTypes, aPosition ); if( collector.GetCount() > 0 ) break; } return collector.GetCount() ? collector[ 0 ] : nullptr; } int EE_SELECTION_TOOL::SelectNode( const TOOL_EVENT& aEvent ) { VECTOR2I cursorPos = getViewControls()->GetCursorPosition( false ); SelectPoint( cursorPos, nodeTypes ); return 0; } int EE_SELECTION_TOOL::SelectConnection( const TOOL_EVENT& aEvent ) { static KICAD_T wiresAndBuses[] = { SCH_ITEM_LOCATE_WIRE_T, SCH_ITEM_LOCATE_BUS_T, EOT }; RequestSelection( wiresAndBuses ); if( m_selection.Empty() ) return 0; SCH_LINE* line = (SCH_LINE*) m_selection.Front(); EDA_ITEMS items; unsigned done = false; m_frame->GetScreen()->ClearDrawingState(); std::set conns = m_frame->GetScreen()->MarkConnections( line, false ); for( SCH_ITEM* item : conns ) { if( item->IsType( wiresAndBuses ) && !item->IsSelected() ) done = true; select( item ); } if( !done ) { conns = m_frame->GetScreen()->MarkConnections( line, true ); for( SCH_ITEM* item : conns ) select( item ); } if( m_selection.GetSize() > 1 ) m_toolMgr->ProcessEvent( EVENTS::SelectedEvent ); return 0; } int EE_SELECTION_TOOL::ClearSelection( const TOOL_EVENT& aEvent ) { ClearSelection(); return 0; } void EE_SELECTION_TOOL::RebuildSelection() { m_selection.Clear(); if( m_isSymbolEditor ) { LIB_SYMBOL* start = static_cast( m_frame )->GetCurSymbol(); for( LIB_ITEM& item : start->GetDrawItems() ) { if( item.IsSelected() ) select( &item ); } } else { for( SCH_ITEM* item : m_frame->GetScreen()->Items() ) { // If the field and symbol are selected, only use the symbol if( item->IsSelected() ) { select( item ); } else { item->RunOnChildren( [&]( SCH_ITEM* aChild ) { if( aChild->IsSelected() ) select( aChild ); } ); } } } updateReferencePoint(); // Inform other potentially interested tools m_toolMgr->ProcessEvent( EVENTS::SelectedEvent ); } bool EE_SELECTION_TOOL::Selectable( const EDA_ITEM* aItem, const VECTOR2I* aPos, bool checkVisibilityOnly ) const { // NOTE: in the future this is where Eeschema layer/itemtype visibility will be handled SYMBOL_EDIT_FRAME* symEditFrame = dynamic_cast( m_frame ); // Do not allow selection of anything except fields when the current symbol in the symbol // editor is a derived symbol. if( symEditFrame && symEditFrame->IsSymbolAlias() && aItem->Type() != LIB_FIELD_T ) return false; switch( aItem->Type() ) { case SCH_PIN_T: { const SCH_PIN* pin = static_cast( aItem ); if( !pin->IsVisible() && !m_frame->GetShowAllPins() ) return false; if( m_frame->eeconfig()->m_Selection.select_pin_selects_symbol ) { // Pin anchors have to be allowed for auto-starting wires. if( aPos ) { EE_GRID_HELPER grid( m_toolMgr ); if( pin->IsPointClickableAnchor( grid.BestSnapAnchor( *aPos, LAYER_CONNECTABLE ) ) ) return true; } return false; } break; } case LIB_SYMBOL_T: // In symbol_editor we do not want to select the symbol itself. return false; case LIB_FIELD_T: // LIB_FIELD object can always be edited. break; case LIB_SHAPE_T: case LIB_TEXT_T: case LIB_PIN_T: if( symEditFrame ) { LIB_ITEM* lib_item = (LIB_ITEM*) aItem; if( lib_item->GetUnit() && lib_item->GetUnit() != symEditFrame->GetUnit() ) return false; if( lib_item->GetConvert() && lib_item->GetConvert() != symEditFrame->GetConvert() ) return false; } break; case SCH_MARKER_T: // Always selectable return true; default: // Suppress warnings break; } return true; } void EE_SELECTION_TOOL::ClearSelection() { if( m_selection.Empty() ) return; while( m_selection.GetSize() ) unhighlight( m_selection.Front(), SELECTED, &m_selection ); getView()->Update( &m_selection ); m_selection.SetIsHover( false ); m_selection.ClearReferencePoint(); // Inform other potentially interested tools m_toolMgr->ProcessEvent( EVENTS::ClearedEvent ); } void EE_SELECTION_TOOL::select( EDA_ITEM* aItem ) { highlight( aItem, SELECTED, &m_selection ); } void EE_SELECTION_TOOL::unselect( EDA_ITEM* aItem ) { unhighlight( aItem, SELECTED, &m_selection ); } void EE_SELECTION_TOOL::highlight( EDA_ITEM* aItem, int aMode, SELECTION* aGroup ) { KICAD_T itemType = aItem->Type(); if( aMode == SELECTED ) aItem->SetSelected(); else if( aMode == BRIGHTENED ) aItem->SetBrightened(); if( aGroup ) aGroup->Add( aItem ); // Highlight pins and fields. (All the other symbol children are currently only // represented in the LIB_SYMBOL and will inherit the settings of the parent symbol.) if( SCH_ITEM* sch_item = dynamic_cast( aItem ) ) { sch_item->RunOnChildren( [&]( SCH_ITEM* aChild ) { if( aMode == SELECTED ) aChild->SetSelected(); else if( aMode == BRIGHTENED ) aChild->SetBrightened(); } ); } if( itemType == SCH_PIN_T || itemType == SCH_FIELD_T || itemType == SCH_SHEET_PIN_T ) getView()->Update( aItem->GetParent() ); else getView()->Update( aItem ); } void EE_SELECTION_TOOL::unhighlight( EDA_ITEM* aItem, int aMode, SELECTION* aGroup ) { KICAD_T itemType = aItem->Type(); if( aMode == SELECTED ) { aItem->ClearSelected(); // Lines need endpoints cleared here if( aItem->Type() == SCH_LINE_T ) aItem->ClearFlags( STARTPOINT | ENDPOINT ); } else if( aMode == BRIGHTENED ) aItem->ClearBrightened(); if( aGroup ) aGroup->Remove( aItem ); // Unhighlight pins and fields. (All the other symbol children are currently only // represented in the LIB_SYMBOL.) if( SCH_ITEM* sch_item = dynamic_cast( aItem ) ) { sch_item->RunOnChildren( [&]( SCH_ITEM* aChild ) { if( aMode == SELECTED ) aChild->ClearSelected(); else if( aMode == BRIGHTENED ) aChild->ClearBrightened(); } ); } if( itemType == SCH_PIN_T || itemType == SCH_FIELD_T || itemType == SCH_SHEET_PIN_T ) getView()->Update( aItem->GetParent() ); else getView()->Update( aItem ); } bool EE_SELECTION_TOOL::selectionContains( const VECTOR2I& aPoint ) const { const unsigned GRIP_MARGIN = 20; VECTOR2I margin = getView()->ToWorld( VECTOR2I( GRIP_MARGIN, GRIP_MARGIN ), false ); // Check if the point is located within any of the currently selected items bounding boxes for( EDA_ITEM* item : m_selection ) { BOX2I itemBox = item->ViewBBox(); itemBox.Inflate( margin.x, margin.y ); // Give some margin for gripping an item if( itemBox.Contains( aPoint ) ) return true; } return false; } void EE_SELECTION_TOOL::setTransitions() { Go( &EE_SELECTION_TOOL::UpdateMenu, ACTIONS::updateMenu.MakeEvent() ); Go( &EE_SELECTION_TOOL::Main, EE_ACTIONS::selectionActivate.MakeEvent() ); Go( &EE_SELECTION_TOOL::SelectNode, EE_ACTIONS::selectNode.MakeEvent() ); Go( &EE_SELECTION_TOOL::SelectConnection, EE_ACTIONS::selectConnection.MakeEvent() ); Go( &EE_SELECTION_TOOL::ClearSelection, EE_ACTIONS::clearSelection.MakeEvent() ); Go( &EE_SELECTION_TOOL::AddItemToSel, EE_ACTIONS::addItemToSel.MakeEvent() ); Go( &EE_SELECTION_TOOL::AddItemsToSel, EE_ACTIONS::addItemsToSel.MakeEvent() ); Go( &EE_SELECTION_TOOL::RemoveItemFromSel, EE_ACTIONS::removeItemFromSel.MakeEvent() ); Go( &EE_SELECTION_TOOL::RemoveItemsFromSel, EE_ACTIONS::removeItemsFromSel.MakeEvent() ); Go( &EE_SELECTION_TOOL::SelectionMenu, EE_ACTIONS::selectionMenu.MakeEvent() ); Go( &EE_SELECTION_TOOL::SelectAll, EE_ACTIONS::selectAll.MakeEvent() ); Go( &EE_SELECTION_TOOL::disambiguateCursor, EVENTS::DisambiguatePoint ); }