/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2019 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 // Selection tool actions TOOL_ACTION SCH_ACTIONS::selectionActivate( "eeschema.InteractiveSelection", AS_GLOBAL, 0, "", "", NULL, AF_ACTIVATE ); // No description, not shown anywhere TOOL_ACTION SCH_ACTIONS::selectionCursor( "eeschema.InteractiveSelection.Cursor", AS_GLOBAL, 0, "", "" ); // No description, it is not supposed to be shown anywhere TOOL_ACTION SCH_ACTIONS::selectionMenu( "eeschema.InteractiveSelection.SelectionMenu", AS_GLOBAL, 0, "", "" ); // No description, it is not supposed to be shown anywhere TOOL_ACTION SCH_ACTIONS::selectItem( "eeschema.InteractiveSelection.SelectItem", AS_GLOBAL, 0, "", "" ); // No description, it is not supposed to be shown anywhere TOOL_ACTION SCH_ACTIONS::selectItems( "eeschema.InteractiveSelection.SelectItems", AS_GLOBAL, 0, "", "" ); // No description, it is not supposed to be shown anywhere TOOL_ACTION SCH_ACTIONS::unselectItem( "eeschema.InteractiveSelection.UnselectItem", AS_GLOBAL, 0, "", "" ); // No description, it is not supposed to be shown anywhere TOOL_ACTION SCH_ACTIONS::unselectItems( "eeschema.InteractiveSelection.UnselectItems", AS_GLOBAL, 0, "", "" ); // No description, it is not supposed to be shown anywhere TOOL_ACTION SCH_ACTIONS::selectionClear( "eeschema.InteractiveSelection.Clear", AS_GLOBAL, 0, "", "" ); // No description, it is not supposed to be shown anywhere SCH_SELECTION_TOOL::SCH_SELECTION_TOOL() : TOOL_INTERACTIVE( "eeschema.InteractiveSelection" ), m_frame( NULL ), m_additive( false ), m_subtractive( false ), m_multiple( false ), m_skip_heuristics( false ), m_locked( true ), m_menu( *this ) { } SCH_SELECTION_TOOL::~SCH_SELECTION_TOOL() { getView()->Remove( &m_selection ); } bool SCH_SELECTION_TOOL::Init() { auto frame = getEditFrame(); if( frame ) m_menu.AddStandardSubMenus( *frame ); return true; } void SCH_SELECTION_TOOL::Reset( RESET_REASON aReason ) { m_frame = getEditFrame(); m_locked = true; 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 ); } 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 ); } int SCH_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent ) { // Main loop: keep receiving events while( OPT_TOOL_EVENT evt = Wait() ) { // Should selected items be added to the current selection or // become the new selection (discarding previously selected items) m_additive = evt->Modifier( MD_SHIFT ); // Should selected items be REMOVED from the current selection? // This will be ignored if the SHIFT modifier is pressed m_subtractive = !m_additive && evt->Modifier( MD_CTRL ); // Is the user requesting that the selection list include all possible // items without removing less likely selection candidates m_skip_heuristics = !!evt->Modifier( MD_ALT ); // Single click? Select single object if( evt->IsClick( BUT_LEFT ) ) { if( evt->Modifier( MD_CTRL ) && dynamic_cast( m_frame ) ) { m_toolMgr->RunAction( SCH_ACTIONS::highlightNet, true ); } else { // If no modifier keys are pressed, clear the selection if( !m_additive ) clearSelection(); SelectPoint( evt->Position()); } } // right click? if there is any object - show the context menu else if( evt->IsClick( BUT_RIGHT ) ) { bool selectionCancelled = false; if( m_selection.Empty() ) { SelectPoint( evt->Position(), SCH_COLLECTOR::AllItems, &selectionCancelled ); m_selection.SetIsHover( true ); } if( !selectionCancelled ) m_menu.ShowContextMenu( m_selection ); } // double click? Display the properties window else if( evt->IsDblClick( BUT_LEFT ) ) { if( m_selection.Empty() ) SelectPoint( evt->Position()); m_toolMgr->RunAction( SCH_ACTIONS::properties ); } // drag with LMB? Select multiple objects (or at least draw a selection box) or drag them else if( evt->IsDrag( BUT_LEFT ) ) { if( m_additive || m_subtractive || m_selection.Empty() ) { // JEY TODO: move block selection to SCH_SELECTION_TOOL //selectMultiple(); } else { // 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 m_toolMgr->InvokeTool( "eeschema.InteractiveEdit" ); } else { // No -> clear the selection list clearSelection(); } } } else if( evt->IsCancel() || evt->Action() == TA_UNDO_REDO_PRE ) { clearSelection(); if( evt->IsCancel() && dynamic_cast( m_frame ) ) m_toolMgr->RunAction( SCH_ACTIONS::clearHighlight, true ); } else if( evt->Action() == TA_CONTEXT_MENU_CLOSED ) { m_menu.CloseContextMenu( evt ); } } // This tool is supposed to be active forever assert( false ); return 0; } SELECTION& SCH_SELECTION_TOOL::GetSelection() { return m_selection; } SCH_ITEM* SCH_SELECTION_TOOL::SelectPoint( const VECTOR2I& aWhere, const KICAD_T* aFilterList, bool* aSelectionCancelledFlag, bool aCheckLocked ) { SCH_COLLECTOR collector; collector.Collect( m_frame->GetScreen()->GetDrawItems(), aFilterList, (wxPoint) aWhere ); bool anyCollected = collector.GetCount() != 0; // Remove unselectable items for( int i = collector.GetCount() - 1; i >= 0; --i ) { if( !selectable( collector[ i ] ) ) collector.Remove( i ); if( aCheckLocked && collector[ i ]->IsLocked() ) collector.Remove( i ); } m_selection.ClearReferencePoint(); // Apply some ugly heuristics to avoid disambiguation menus whenever possible if( collector.GetCount() > 1 && !m_skip_heuristics ) { guessSelectionCandidates( collector, aWhere ); } // If still more than one item we're going to have to ask the user. if( collector.GetCount() > 1 ) { collector.m_MenuTitle = _( "Clarify Selection" ); // Must call selectionMenu via RunAction() to avoid event-loop contention m_toolMgr->RunAction( SCH_ACTIONS::selectionMenu, true, &collector ); if( collector.m_MenuCancelled ) { if( aSelectionCancelledFlag ) *aSelectionCancelledFlag = true; m_frame->GetScreen()->SetCurItem( nullptr ); return nullptr; } } if( collector.GetCount() == 1 ) { SCH_ITEM* item = collector[ 0 ]; toggleSelection( item ); MSG_PANEL_ITEMS msgItems; item->GetMsgPanelInfo( m_frame->GetUserUnits(), msgItems ); m_frame->SetMsgPanel( msgItems ); return item; } if( !m_additive && anyCollected ) clearSelection(); m_frame->ClearMsgPanel(); return nullptr; } void SCH_SELECTION_TOOL::guessSelectionCandidates( SCH_COLLECTOR& collector, const VECTOR2I& aWhere ) { // There are certain parent/child and enclosure combinations that can be handled // automatically. Since schematics are meant to be human-readable we don't have // all the various overlap and coverage issues that we do in Pcbnew. if( collector.GetCount() == 2 ) { SCH_ITEM* a = collector[ 0 ]; SCH_ITEM* b = collector[ 1 ]; if( a->GetParent() == b ) collector.Remove( b ); else if( a == b->GetParent() ) collector.Remove( a ); else if( a->Type() == SCH_SHEET_T && b->Type() != SCH_SHEET_T ) collector.Remove( a ); else if( b->Type() == SCH_SHEET_T && a->Type() != SCH_SHEET_T ) collector.Remove( b ); } } SELECTION& SCH_SELECTION_TOOL::RequestSelection( const KICAD_T aFilterList[] ) { if( m_selection.Empty() ) { VECTOR2D cursorPos = getViewControls()->GetCursorPosition( false ); clearSelection(); SelectPoint( cursorPos, aFilterList ); return m_selection; } else { // Trim an existing selection by aFilterList SELECTION originalSelection( m_selection ); for( EDA_ITEM* item : originalSelection ) { KICAD_T matchType; bool match = false; for( const KICAD_T* p = aFilterList; (matchType = *p) != EOT && !match; ++p ) match = ( item->Type() == matchType ); if( !match ) toggleSelection( static_cast( item ) ); } return m_selection; } } bool SCH_SELECTION_TOOL::selectCursor( const KICAD_T aFilterList[], bool aForceSelect ) { if( aForceSelect || m_selection.Empty() ) { VECTOR2D cursorPos = getViewControls()->GetCursorPosition( false ); clearSelection(); SelectPoint( cursorPos, aFilterList ); } return !m_selection.Empty(); } int SCH_SELECTION_TOOL::SelectItems( const TOOL_EVENT& aEvent ) { std::vector* items = aEvent.Parameter*>(); if( items ) { // Perform individual selection of each item before processing the event. for( auto item : *items ) select( item ); m_toolMgr->ProcessEvent( EVENTS::SelectedEvent ); } return 0; } int SCH_SELECTION_TOOL::SelectItem( const TOOL_EVENT& aEvent ) { // Check if there is an item to be selected SCH_ITEM* item = aEvent.Parameter(); if( item ) { select( item ); // Inform other potentially interested tools m_toolMgr->ProcessEvent( EVENTS::SelectedEvent ); } return 0; } int SCH_SELECTION_TOOL::UnselectItems( const TOOL_EVENT& aEvent ) { std::vector* items = aEvent.Parameter*>(); if( items ) { // Perform individual unselection of each item before processing the event for( auto item : *items ) unselect( item ); m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent ); } return 0; } int SCH_SELECTION_TOOL::UnselectItem( const TOOL_EVENT& aEvent ) { // Check if there is an item to be selected SCH_ITEM* item = aEvent.Parameter(); if( item ) { unselect( item ); // Inform other potentially interested tools m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent ); } return 0; } int SCH_SELECTION_TOOL::ClearSelection( const TOOL_EVENT& aEvent ) { clearSelection(); return 0; } int SCH_SELECTION_TOOL::SelectionMenu( const TOOL_EVENT& aEvent ) { SCH_COLLECTOR* collector = aEvent.Parameter(); if( !doSelectionMenu( collector ) ) collector->m_MenuCancelled = true; return 0; } bool SCH_SELECTION_TOOL::doSelectionMenu( SCH_COLLECTOR* aCollector ) { SCH_ITEM* current = nullptr; #if 1 // ==================================================================================== // JEY TODO: use wxWidgets event loop for showing menu until we move to modern toolset event loop wxMenu selectMenu; AddMenuItem( &selectMenu, wxID_NONE, _( "Clarify Selection" ), KiBitmap( info_xpm ) ); selectMenu.AppendSeparator(); for( int i = 0; i < aCollector->GetCount() && i < MAX_SELECT_ITEM_IDS; i++ ) { SCH_ITEM* item = ( *aCollector )[i]; wxString text = item->GetSelectMenuText( m_frame->GetUserUnits() ); BITMAP_DEF xpm = item->GetMenuImage(); AddMenuItem( &selectMenu, i, text, KiBitmap( xpm ) ); } // Set to NULL in case the user aborts the clarification context menu. m_frame->GetScreen()->SetCurItem( nullptr ); int idx = m_frame->GetPopupMenuSelectionFromUser( selectMenu ); if( idx == wxID_NONE ) { m_frame->GetScreen()->SetCurItem( nullptr ); return false; } m_frame->GetCanvas()->MoveCursorToCrossHair(); current = ( *aCollector )[ idx ]; m_frame->GetScreen()->SetCurItem( current ); aCollector->Empty(); aCollector->Append( current ); return true; // ==================================================================================== #endif CONTEXT_MENU menu; int limit = std::min( MAX_SELECT_ITEM_IDS, aCollector->GetCount() ); for( int i = 0; i < limit; ++i ) { wxString text; SCH_ITEM* item = ( *aCollector )[i]; text = item->GetSelectMenuText( m_frame->GetUserUnits() ); wxString menuText = wxString::Format("&%d. %s", i + 1, text ); menu.Add( menuText, i + 1, item->GetMenuImage() ); } if( aCollector->m_MenuTitle.Length() ) menu.SetTitle( aCollector->m_MenuTitle ); menu.SetIcon( info_xpm ); menu.DisplayTitle( true ); SetContextMenu( &menu, CMENU_NOW ); while( OPT_TOOL_EVENT evt = Wait() ) { if( evt->Action() == TA_CONTEXT_MENU_UPDATE ) { if( current ) unhighlight( current, BRIGHTENED ); 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 ); } else { current = NULL; } } else if( evt->Action() == TA_CONTEXT_MENU_CHOICE ) { if( current ) unhighlight( current, BRIGHTENED ); OPT id = evt->GetCommandId(); // User has selected an item, so this one will be returned if( id && ( *id > 0 ) ) current = ( *aCollector )[*id - 1]; else current = NULL; break; } } if( current ) { unhighlight( current, BRIGHTENED ); aCollector->Empty(); aCollector->Append( current ); return true; } return false; } bool SCH_SELECTION_TOOL::selectable( const SCH_ITEM* aItem, bool checkVisibilityOnly ) const { // NOTE: in the future this is where eeschema layer/itemtype visibility will be handled switch( aItem->Type() ) { case SCH_PIN_T: if( !static_cast( aItem )->IsVisible() && !m_frame->GetShowAllPins() ) return false; break; case LIB_PART_T: // In libedit we do not want to select the symbol itself. return false; case SCH_MARKER_T: // Always selectable return true; default: // Suppress warnings break; } return true; } void SCH_SELECTION_TOOL::clearSelection() { if( m_selection.Empty() ) return; while( m_selection.GetSize() ) unhighlight( static_cast( m_selection.Front() ), SELECTED, &m_selection ); getView()->Update( &m_selection ); m_selection.SetIsHover( false ); m_selection.ClearReferencePoint(); if( m_frame ) m_frame->GetScreen()->SetCurItem( nullptr ); m_locked = true; // Inform other potentially interested tools m_toolMgr->ProcessEvent( EVENTS::ClearedEvent ); } void SCH_SELECTION_TOOL::toggleSelection( SCH_ITEM* aItem, bool aForce ) { if( aItem->IsSelected() ) { unselect( aItem ); // Inform other potentially interested tools m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent ); } else { if( !m_additive ) clearSelection(); // Prevent selection of invisible or inactive items if( aForce || selectable( aItem ) ) { select( aItem ); // Inform other potentially interested tools m_toolMgr->ProcessEvent( EVENTS::SelectedEvent ); } } if( m_frame ) m_frame->GetGalCanvas()->ForceRefresh(); } void SCH_SELECTION_TOOL::select( SCH_ITEM* aItem ) { if( aItem->IsSelected() ) return; highlight( aItem, SELECTED, &m_selection ); getView()->Update( &m_selection ); if( m_frame ) { if( m_selection.Size() == 1 ) { // Set as the current item, so the information about selection is displayed m_frame->GetScreen()->SetCurItem( aItem ); } else if( m_selection.Size() == 2 ) // Check only for 2, so it will not be { // called for every next selected item // If multiple items are selected, do not show the information about the selected item m_frame->GetScreen()->SetCurItem( nullptr ); } } } void SCH_SELECTION_TOOL::unselect( SCH_ITEM* aItem ) { unhighlight( aItem, SELECTED, &m_selection ); getView()->Update( &m_selection ); if( m_frame && m_frame->GetScreen()->GetCurItem() == aItem ) m_frame->GetScreen()->SetCurItem( nullptr ); if( m_selection.Empty() ) m_locked = true; } void SCH_SELECTION_TOOL::highlight( SCH_ITEM* aItem, int aMode, SELECTION* aGroup ) { if( aMode == SELECTED ) aItem->SetSelected(); else if( aMode == BRIGHTENED ) aItem->SetBrightened(); if( aGroup ) aGroup->Add( aItem ); // Highlight pins and fields. (All the other component children are currently only // represented in the LIB_PART.) if( aItem->Type() == SCH_COMPONENT_T ) { SCH_PINS pins = static_cast( aItem )->GetPins(); for( SCH_PIN& pin : pins ) { if( aMode == SELECTED ) pin.SetSelected(); else if( aMode == BRIGHTENED ) pin.SetBrightened(); } std::vector fields; static_cast( aItem )->GetFields( fields, false ); for( auto field : fields ) { if( aMode == SELECTED ) field->SetSelected(); else if( aMode == BRIGHTENED ) field->SetBrightened(); // JEY TODO: do these need hiding from view and adding to aGroup? } } // JEY TODO: Sheets and sheet pins? // Many selections are very temporal and updating the display each time just // creates noise. if( aMode == BRIGHTENED ) getView()->MarkTargetDirty( KIGFX::TARGET_OVERLAY ); } void SCH_SELECTION_TOOL::unhighlight( SCH_ITEM* aItem, int aMode, SELECTION* aGroup ) { if( aMode == SELECTED ) aItem->ClearSelected(); else if( aMode == BRIGHTENED ) aItem->ClearBrightened(); if( aGroup ) aGroup->Remove( aItem ); // Unhighlight pins and fields. (All the other component children are currently only // represented in the LIB_PART.) if( aItem->Type() == SCH_COMPONENT_T ) { SCH_PINS pins = static_cast( aItem )->GetPins(); for( SCH_PIN& pin : pins ) { if( aMode == SELECTED ) pin.ClearSelected(); else if( aMode == BRIGHTENED ) pin.ClearBrightened(); } std::vector fields; static_cast( aItem )->GetFields( fields, false ); for( auto field : fields ) { if( aMode == SELECTED ) field->ClearSelected(); else if( aMode == BRIGHTENED ) field->ClearBrightened(); // JEY TODO: do these need showing and updating? } } // JEY TODO: Sheets and sheet pins? // Many selections are very temporal and updating the display each time just // creates noise. if( aMode == BRIGHTENED ) getView()->MarkTargetDirty( KIGFX::TARGET_OVERLAY ); } bool SCH_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( auto 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 SCH_SELECTION_TOOL::setTransitions() { Go( &SCH_SELECTION_TOOL::Main, SCH_ACTIONS::selectionActivate.MakeEvent() ); Go( &SCH_SELECTION_TOOL::ClearSelection, SCH_ACTIONS::selectionClear.MakeEvent() ); Go( &SCH_SELECTION_TOOL::SelectItem, SCH_ACTIONS::selectItem.MakeEvent() ); Go( &SCH_SELECTION_TOOL::SelectItems, SCH_ACTIONS::selectItems.MakeEvent() ); Go( &SCH_SELECTION_TOOL::UnselectItem, SCH_ACTIONS::unselectItem.MakeEvent() ); Go( &SCH_SELECTION_TOOL::UnselectItems, SCH_ACTIONS::unselectItems.MakeEvent() ); Go( &SCH_SELECTION_TOOL::SelectionMenu, SCH_ACTIONS::selectionMenu.MakeEvent() ); }