2019-05-20 10:23:32 +00:00
|
|
|
/*
|
|
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
|
|
*
|
2019-08-14 08:28:07 +00:00
|
|
|
* Copyright (C) 2019 CERN
|
2021-02-24 19:26:50 +00:00
|
|
|
* Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
|
2019-05-20 10:23:32 +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 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
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2019-05-25 00:34:44 +00:00
|
|
|
#include <bitmaps.h>
|
2019-05-20 10:23:32 +00:00
|
|
|
#include <view/view.h>
|
|
|
|
#include <view/view_controls.h>
|
|
|
|
#include <preview_items/selection_area.h>
|
|
|
|
#include <tool/tool_event.h>
|
|
|
|
#include <tool/tool_manager.h>
|
|
|
|
#include <tool/selection.h>
|
2021-09-29 17:50:22 +00:00
|
|
|
#include <tools/pl_point_editor.h>
|
|
|
|
#include <tools/pl_selection_tool.h>
|
|
|
|
#include <tools/pl_actions.h>
|
2021-02-22 23:47:17 +00:00
|
|
|
#include <drawing_sheet/ds_data_item.h>
|
|
|
|
#include <drawing_sheet/ds_data_model.h>
|
|
|
|
#include <drawing_sheet/ds_draw_item.h>
|
2019-05-20 10:23:32 +00:00
|
|
|
#include <collector.h>
|
2020-01-07 17:12:59 +00:00
|
|
|
#include <math/util.h> // for KiROUND
|
2019-05-20 10:23:32 +00:00
|
|
|
|
2020-10-13 05:04:31 +00:00
|
|
|
#include "pl_editor_frame.h"
|
|
|
|
|
2019-05-20 10:23:32 +00:00
|
|
|
/**
|
|
|
|
* The maximum number of items in the clarify selection context menu. The current
|
|
|
|
* setting of 40 is arbitrary.
|
|
|
|
*/
|
|
|
|
#define MAX_SELECT_ITEM_IDS 40
|
2020-05-30 10:25:52 +00:00
|
|
|
#define HITTEST_THRESHOLD_PIXELS 3
|
2019-05-20 10:23:32 +00:00
|
|
|
|
|
|
|
|
|
|
|
PL_SELECTION_TOOL::PL_SELECTION_TOOL() :
|
|
|
|
TOOL_INTERACTIVE( "plEditor.InteractiveSelection" ),
|
2021-09-05 20:37:52 +00:00
|
|
|
m_frame( nullptr )
|
2019-05-20 10:23:32 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool PL_SELECTION_TOOL::Init()
|
|
|
|
{
|
|
|
|
m_frame = getEditFrame<PL_EDITOR_FRAME>();
|
|
|
|
|
|
|
|
auto& menu = m_menu.GetMenu();
|
|
|
|
|
2019-06-15 16:40:14 +00:00
|
|
|
menu.AddSeparator( 200 );
|
2021-01-17 18:19:02 +00:00
|
|
|
menu.AddItem( PL_ACTIONS::drawLine, SELECTION_CONDITIONS::Empty, 200 );
|
|
|
|
menu.AddItem( PL_ACTIONS::drawRectangle, SELECTION_CONDITIONS::Empty, 200 );
|
|
|
|
menu.AddItem( PL_ACTIONS::placeText, SELECTION_CONDITIONS::Empty, 200 );
|
|
|
|
menu.AddItem( PL_ACTIONS::placeImage, SELECTION_CONDITIONS::Empty, 200 );
|
2019-05-20 10:23:32 +00:00
|
|
|
|
2019-06-15 16:40:14 +00:00
|
|
|
menu.AddSeparator( 1000 );
|
2019-06-11 14:38:21 +00:00
|
|
|
m_frame->AddStandardSubMenus( m_menu );
|
2019-05-20 10:23:32 +00:00
|
|
|
|
2021-09-05 20:37:52 +00:00
|
|
|
m_disambiguateTimer.SetOwner( this );
|
|
|
|
Connect( wxEVT_TIMER, wxTimerEventHandler( PL_SELECTION_TOOL::onDisambiguationExpire ), nullptr, this );
|
|
|
|
|
2019-05-20 10:23:32 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PL_SELECTION_TOOL::Reset( RESET_REASON aReason )
|
|
|
|
{
|
|
|
|
if( aReason == MODEL_RELOAD )
|
|
|
|
m_frame = getEditFrame<PL_EDITOR_FRAME>();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int PL_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( m_selection );
|
|
|
|
|
|
|
|
if( actionMenu )
|
|
|
|
actionMenu->UpdateAll();
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int PL_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
// Main loop: keep receiving events
|
2019-06-17 13:43:22 +00:00
|
|
|
while( TOOL_EVENT* evt = Wait() )
|
2019-05-20 10:23:32 +00:00
|
|
|
{
|
2021-02-24 19:26:50 +00:00
|
|
|
// on left click, a selection is made, depending on modifiers ALT, SHIFT, CTRL:
|
2021-09-05 20:37:52 +00:00
|
|
|
setModifiersState( evt->Modifier( MD_SHIFT ), evt->Modifier( MD_CTRL ),
|
|
|
|
evt->Modifier( MD_ALT ) );
|
2021-02-24 19:26:50 +00:00
|
|
|
|
|
|
|
bool modifier_enabled = m_subtractive || m_additive || m_exclusive_or;
|
2019-05-20 10:23:32 +00:00
|
|
|
|
2021-09-05 20:37:52 +00:00
|
|
|
if( evt->IsMouseDown( BUT_LEFT ) )
|
|
|
|
{
|
|
|
|
// Avoid triggering when running under other tools
|
2021-10-01 00:25:33 +00:00
|
|
|
PL_POINT_EDITOR *pt_tool = m_toolMgr->GetTool<PL_POINT_EDITOR>();
|
|
|
|
|
|
|
|
if( m_frame->ToolStackIsEmpty() && pt_tool && !pt_tool->HasPoint() )
|
2021-09-09 23:23:00 +00:00
|
|
|
{
|
|
|
|
m_originalCursor = m_toolMgr->GetMousePosition();
|
2021-09-05 20:37:52 +00:00
|
|
|
m_disambiguateTimer.StartOnce( 500 );
|
2021-09-09 23:23:00 +00:00
|
|
|
}
|
2021-09-05 20:37:52 +00:00
|
|
|
}
|
2019-05-20 10:23:32 +00:00
|
|
|
// Single click? Select single object
|
2021-09-05 20:37:52 +00:00
|
|
|
else if( evt->IsClick( BUT_LEFT ) )
|
2019-05-20 10:23:32 +00:00
|
|
|
{
|
2021-09-05 20:37:52 +00:00
|
|
|
// 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();
|
2020-11-24 22:16:41 +00:00
|
|
|
SelectPoint( evt->Position() );
|
2019-05-20 10:23:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// right click? if there is any object - show the context menu
|
|
|
|
else if( evt->IsClick( BUT_RIGHT ) )
|
|
|
|
{
|
2021-09-05 20:37:52 +00:00
|
|
|
m_disambiguateTimer.Stop();
|
2019-05-20 10:23:32 +00:00
|
|
|
bool selectionCancelled = false;
|
|
|
|
|
|
|
|
if( m_selection.Empty() )
|
|
|
|
{
|
|
|
|
SelectPoint( evt->Position(), &selectionCancelled );
|
|
|
|
m_selection.SetIsHover( true );
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !selectionCancelled )
|
|
|
|
m_menu.ShowContextMenu( m_selection );
|
|
|
|
}
|
|
|
|
|
|
|
|
// double click? Display the properties window
|
|
|
|
else if( evt->IsDblClick( BUT_LEFT ) )
|
|
|
|
{
|
|
|
|
// No double-click actions currently defined
|
|
|
|
}
|
|
|
|
|
|
|
|
// drag with LMB? Select multiple objects (or at least draw a selection box) or drag them
|
|
|
|
else if( evt->IsDrag( BUT_LEFT ) )
|
|
|
|
{
|
2021-09-05 20:37:52 +00:00
|
|
|
m_disambiguateTimer.Stop();
|
|
|
|
|
2020-10-08 00:50:28 +00:00
|
|
|
if( modifier_enabled || m_selection.Empty() )
|
2019-05-20 10:23:32 +00:00
|
|
|
{
|
|
|
|
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
|
2020-10-08 00:50:28 +00:00
|
|
|
m_toolMgr->RunAction( "plEditor.InteractiveMove.move", true );
|
2019-05-20 10:23:32 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// No -> clear the selection list
|
|
|
|
ClearSelection();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-22 23:19:24 +00:00
|
|
|
// Middle double click? Do zoom to fit or zoom to objects
|
|
|
|
else if( evt->IsDblClick( BUT_MIDDLE ) )
|
|
|
|
{
|
|
|
|
m_toolMgr->RunAction( ACTIONS::zoomFitScreen, true );
|
|
|
|
}
|
|
|
|
|
2019-07-01 21:01:33 +00:00
|
|
|
else if( evt->IsCancelInteractive() )
|
2019-05-20 10:23:32 +00:00
|
|
|
{
|
2021-09-05 20:37:52 +00:00
|
|
|
m_disambiguateTimer.Stop();
|
2019-05-20 10:23:32 +00:00
|
|
|
ClearSelection();
|
|
|
|
}
|
|
|
|
|
|
|
|
else if( evt->Action() == TA_UNDO_REDO_PRE )
|
|
|
|
{
|
|
|
|
ClearSelection();
|
|
|
|
}
|
|
|
|
|
2019-05-28 14:39:14 +00:00
|
|
|
else
|
2019-06-16 11:06:49 +00:00
|
|
|
evt->SetPassEvent();
|
2020-10-08 00:50:28 +00:00
|
|
|
|
|
|
|
|
|
|
|
if( m_frame->ToolStackIsEmpty() )
|
|
|
|
{
|
2021-01-11 11:48:51 +00:00
|
|
|
if( !modifier_enabled
|
|
|
|
&& !m_selection.Empty()
|
2021-01-11 15:46:41 +00:00
|
|
|
&& m_frame->GetDragAction() == MOUSE_DRAG_ACTION::DRAG_SELECTED
|
2021-01-11 11:48:51 +00:00
|
|
|
&& evt->HasPosition()
|
|
|
|
&& selectionContains( evt->Position() ) )
|
|
|
|
{
|
2020-10-08 00:50:28 +00:00
|
|
|
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::MOVING );
|
2021-01-11 11:48:51 +00:00
|
|
|
}
|
2020-10-08 00:50:28 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
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( KICURSOR::ARROW );
|
|
|
|
}
|
|
|
|
}
|
2019-05-20 10:23:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-09-05 20:37:52 +00:00
|
|
|
int PL_SELECTION_TOOL::disambiguateCursor( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
2022-02-26 00:14:41 +00:00
|
|
|
wxMouseState keyboardState = wxGetMouseState();
|
|
|
|
|
|
|
|
setModifiersState( keyboardState.ShiftDown(), keyboardState.ControlDown(),
|
|
|
|
keyboardState.AltDown() );
|
|
|
|
|
2021-09-05 20:37:52 +00:00
|
|
|
m_skip_heuristics = true;
|
2021-09-24 19:44:26 +00:00
|
|
|
SelectPoint( m_originalCursor, &m_canceledMenu );
|
2021-09-05 20:37:52 +00:00
|
|
|
m_skip_heuristics = false;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PL_SELECTION_TOOL::onDisambiguationExpire( wxTimerEvent& aEvent )
|
|
|
|
{
|
|
|
|
m_toolMgr->ProcessEvent( EVENTS::DisambiguatePoint );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-06-08 21:48:22 +00:00
|
|
|
PL_SELECTION& PL_SELECTION_TOOL::GetSelection()
|
2019-05-20 10:23:32 +00:00
|
|
|
{
|
|
|
|
return m_selection;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-08-10 13:24:40 +00:00
|
|
|
void PL_SELECTION_TOOL::SelectPoint( const VECTOR2I& aWhere, bool* aSelectionCancelledFlag )
|
2019-05-20 10:23:32 +00:00
|
|
|
{
|
|
|
|
int threshold = KiROUND( getView()->ToWorld( HITTEST_THRESHOLD_PIXELS ) );
|
|
|
|
|
|
|
|
// locate items.
|
|
|
|
COLLECTOR collector;
|
|
|
|
|
2021-02-22 23:47:17 +00:00
|
|
|
for( DS_DATA_ITEM* dataItem : DS_DATA_MODEL::GetTheInstance().GetItems() )
|
2019-05-20 10:23:32 +00:00
|
|
|
{
|
2021-02-22 23:47:17 +00:00
|
|
|
for( DS_DRAW_ITEM_BASE* drawItem : dataItem->GetDrawItems() )
|
2019-05-20 10:23:32 +00:00
|
|
|
{
|
|
|
|
if( drawItem->HitTest( (wxPoint) aWhere, threshold ) )
|
|
|
|
collector.Append( drawItem );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 )
|
|
|
|
{
|
2021-09-05 20:37:52 +00:00
|
|
|
doSelectionMenu( &collector );
|
2019-05-20 10:23:32 +00:00
|
|
|
|
|
|
|
if( collector.m_MenuCancelled )
|
|
|
|
{
|
|
|
|
if( aSelectionCancelledFlag )
|
|
|
|
*aSelectionCancelledFlag = true;
|
|
|
|
|
2020-08-10 13:24:40 +00:00
|
|
|
return;
|
2019-05-20 10:23:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-10 13:24:40 +00:00
|
|
|
bool anyAdded = false;
|
|
|
|
bool anySubtracted = false;
|
2019-05-20 10:23:32 +00:00
|
|
|
|
2021-09-05 20:37:52 +00:00
|
|
|
|
|
|
|
if( !m_additive && !m_subtractive && !m_exclusive_or )
|
|
|
|
{
|
|
|
|
if( collector.GetCount() == 0 )
|
|
|
|
anySubtracted = true;
|
|
|
|
|
|
|
|
ClearSelection();
|
|
|
|
}
|
|
|
|
|
2020-08-10 13:24:40 +00:00
|
|
|
if( collector.GetCount() > 0 )
|
|
|
|
{
|
|
|
|
for( int i = 0; i < collector.GetCount(); ++i )
|
2019-07-09 19:50:40 +00:00
|
|
|
{
|
2020-08-10 13:24:40 +00:00
|
|
|
if( m_subtractive || ( m_exclusive_or && collector[i]->IsSelected() ) )
|
|
|
|
{
|
|
|
|
unselect( collector[i] );
|
|
|
|
anySubtracted = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
select( collector[i] );
|
|
|
|
anyAdded = true;
|
|
|
|
}
|
2019-07-09 19:50:40 +00:00
|
|
|
}
|
2019-05-20 10:23:32 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 13:24:40 +00:00
|
|
|
if( anyAdded )
|
|
|
|
m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
|
|
|
|
|
|
|
|
if( anySubtracted )
|
|
|
|
m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
|
2019-05-20 10:23:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PL_SELECTION_TOOL::guessSelectionCandidates( COLLECTOR& collector, const VECTOR2I& aPos )
|
|
|
|
{
|
|
|
|
// There are certain conditions that can be handled automatically.
|
|
|
|
|
|
|
|
// Prefer an exact hit to a sloppy one
|
|
|
|
for( int i = 0; collector.GetCount() == 2 && i < 2; ++i )
|
|
|
|
{
|
|
|
|
EDA_ITEM* item = collector[ i ];
|
|
|
|
EDA_ITEM* other = collector[ ( i + 1 ) % 2 ];
|
|
|
|
|
|
|
|
if( item->HitTest( (wxPoint) aPos, 0 ) && !other->HitTest( (wxPoint) aPos, 0 ) )
|
2020-07-07 04:51:12 +00:00
|
|
|
collector.Transfer( other );
|
2019-05-20 10:23:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-06-08 21:48:22 +00:00
|
|
|
PL_SELECTION& PL_SELECTION_TOOL::RequestSelection()
|
2019-05-20 10:23:32 +00:00
|
|
|
{
|
|
|
|
// If nothing is selected do a hover selection
|
|
|
|
if( m_selection.Empty() )
|
|
|
|
{
|
|
|
|
VECTOR2D cursorPos = getViewControls()->GetCursorPosition( true );
|
|
|
|
|
|
|
|
ClearSelection();
|
|
|
|
SelectPoint( cursorPos );
|
|
|
|
m_selection.SetIsHover( true );
|
|
|
|
}
|
|
|
|
|
|
|
|
return m_selection;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool PL_SELECTION_TOOL::selectMultiple()
|
|
|
|
{
|
|
|
|
bool cancelled = false; // Was the tool cancelled while it was running?
|
|
|
|
m_multiple = true; // Multiple selection mode is active
|
|
|
|
KIGFX::VIEW* view = getView();
|
|
|
|
|
|
|
|
KIGFX::PREVIEW::SELECTION_AREA area;
|
|
|
|
view->Add( &area );
|
|
|
|
|
2019-06-17 13:43:22 +00:00
|
|
|
while( TOOL_EVENT* evt = Wait() )
|
2019-05-20 10:23:32 +00:00
|
|
|
{
|
2020-10-09 01:24:26 +00:00
|
|
|
int width = area.GetEnd().x - area.GetOrigin().x;
|
|
|
|
|
|
|
|
/* 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 windowSelection = width >= 0 ? true : false;
|
|
|
|
|
|
|
|
m_frame->GetCanvas()->SetCurrentCursor(
|
|
|
|
windowSelection ? KICURSOR::SELECT_WINDOW : KICURSOR::SELECT_LASSO );
|
|
|
|
|
2019-07-01 21:01:33 +00:00
|
|
|
if( evt->IsCancelInteractive() || evt->IsActivate() )
|
2019-05-20 10:23:32 +00:00
|
|
|
{
|
|
|
|
cancelled = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( evt->IsDrag( BUT_LEFT ) )
|
|
|
|
{
|
2021-09-05 20:37:52 +00:00
|
|
|
if( !m_drag_additive && !m_drag_subtractive )
|
2019-07-09 19:50:40 +00:00
|
|
|
ClearSelection();
|
|
|
|
|
2019-05-20 10:23:32 +00:00
|
|
|
// Start drawing a selection box
|
|
|
|
area.SetOrigin( evt->DragOrigin() );
|
|
|
|
area.SetEnd( evt->Position() );
|
2021-09-05 20:37:52 +00:00
|
|
|
area.SetAdditive( m_drag_additive );
|
|
|
|
area.SetSubtractive( m_drag_subtractive );
|
|
|
|
area.SetExclusiveOr( false );
|
2019-05-20 10:23:32 +00:00
|
|
|
|
|
|
|
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 );
|
|
|
|
|
|
|
|
int height = area.GetEnd().y - area.GetOrigin().y;
|
|
|
|
|
2019-07-09 19:50:40 +00:00
|
|
|
bool anyAdded = false;
|
|
|
|
bool anySubtracted = false;
|
2019-05-20 10:23:32 +00:00
|
|
|
|
|
|
|
// Construct an EDA_RECT to determine EDA_ITEM selection
|
|
|
|
EDA_RECT selectionRect( (wxPoint)area.GetOrigin(), wxSize( width, height ) );
|
|
|
|
|
|
|
|
selectionRect.Normalize();
|
|
|
|
|
2021-02-22 23:47:17 +00:00
|
|
|
for( DS_DATA_ITEM* dataItem : DS_DATA_MODEL::GetTheInstance().GetItems() )
|
2019-05-20 10:23:32 +00:00
|
|
|
{
|
2021-02-22 23:47:17 +00:00
|
|
|
for( DS_DRAW_ITEM_BASE* item : dataItem->GetDrawItems() )
|
2019-05-20 10:23:32 +00:00
|
|
|
{
|
|
|
|
if( item->HitTest( selectionRect, windowSelection ) )
|
|
|
|
{
|
2019-07-09 19:50:40 +00:00
|
|
|
if( m_subtractive || ( m_exclusive_or && item->IsSelected() ) )
|
|
|
|
{
|
2019-05-20 10:23:32 +00:00
|
|
|
unselect( item );
|
2019-07-09 19:50:40 +00:00
|
|
|
anySubtracted = true;
|
|
|
|
}
|
2019-05-20 10:23:32 +00:00
|
|
|
else
|
2019-07-09 19:50:40 +00:00
|
|
|
{
|
2019-05-20 10:23:32 +00:00
|
|
|
select( item );
|
2019-07-09 19:50:40 +00:00
|
|
|
anyAdded = true;
|
|
|
|
}
|
2019-05-20 10:23:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Inform other potentially interested tools
|
2019-07-09 19:50:40 +00:00
|
|
|
if( anyAdded )
|
2019-05-20 10:23:32 +00:00
|
|
|
m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
|
|
|
|
|
2019-07-09 19:50:40 +00:00
|
|
|
if( anySubtracted )
|
|
|
|
m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
|
|
|
|
|
2019-05-20 10:23:32 +00:00
|
|
|
break; // Stop waiting for events
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int PL_SELECTION_TOOL::AddItemToSel( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
AddItemToSel( aEvent.Parameter<EDA_ITEM*>() );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PL_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 PL_SELECTION_TOOL::AddItemsToSel( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
AddItemsToSel( aEvent.Parameter<EDA_ITEMS*>(), false );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PL_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 PL_SELECTION_TOOL::RemoveItemFromSel( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
RemoveItemFromSel( aEvent.Parameter<EDA_ITEM*>() );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PL_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 PL_SELECTION_TOOL::RemoveItemsFromSel( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
RemoveItemsFromSel( aEvent.Parameter<EDA_ITEMS*>(), false );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PL_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 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-06-25 14:12:40 +00:00
|
|
|
void PL_SELECTION_TOOL::BrightenItem( EDA_ITEM* aItem )
|
|
|
|
{
|
|
|
|
highlight( aItem, BRIGHTENED );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PL_SELECTION_TOOL::UnbrightenItem( EDA_ITEM* aItem )
|
|
|
|
{
|
|
|
|
unhighlight( aItem, BRIGHTENED );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-05-20 10:23:32 +00:00
|
|
|
int PL_SELECTION_TOOL::ClearSelection( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
ClearSelection();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-05-24 10:11:18 +00:00
|
|
|
void PL_SELECTION_TOOL::RebuildSelection()
|
|
|
|
{
|
|
|
|
m_selection.Clear();
|
|
|
|
|
2021-02-22 23:47:17 +00:00
|
|
|
for( DS_DATA_ITEM* dataItem : DS_DATA_MODEL::GetTheInstance().GetItems() )
|
2019-05-24 10:11:18 +00:00
|
|
|
{
|
2021-02-22 23:47:17 +00:00
|
|
|
for( DS_DRAW_ITEM_BASE* item : dataItem->GetDrawItems() )
|
2019-05-24 10:11:18 +00:00
|
|
|
{
|
|
|
|
if( item->IsSelected() )
|
|
|
|
select( item );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-05-20 10:23:32 +00:00
|
|
|
int PL_SELECTION_TOOL::SelectionMenu( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
|
|
|
COLLECTOR* collector = aEvent.Parameter<COLLECTOR*>();
|
|
|
|
|
|
|
|
if( !doSelectionMenu( collector ) )
|
|
|
|
collector->m_MenuCancelled = true;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool PL_SELECTION_TOOL::doSelectionMenu( COLLECTOR* aCollector )
|
|
|
|
{
|
|
|
|
EDA_ITEM* current = nullptr;
|
2019-06-15 00:29:42 +00:00
|
|
|
ACTION_MENU menu( true );
|
2019-05-20 10:23:32 +00:00
|
|
|
|
2020-08-10 13:24:40 +00:00
|
|
|
// ID limit is `MAX_SELECT_ITEM_IDS+1` because the last item is "select all"
|
|
|
|
// and the first item has ID of 1.
|
|
|
|
int limit = std::min( MAX_SELECT_ITEM_IDS + 1, aCollector->GetCount() );
|
2019-05-20 10:23:32 +00:00
|
|
|
|
|
|
|
for( int i = 0; i < limit; ++i )
|
|
|
|
{
|
|
|
|
wxString text;
|
|
|
|
EDA_ITEM* item = ( *aCollector )[i];
|
|
|
|
text = item->GetSelectMenuText( m_frame->GetUserUnits() );
|
|
|
|
|
2020-08-10 13:24:40 +00:00
|
|
|
wxString menuText = wxString::Format( "&%d. %s\t%d", i + 1, text, i + 1 );
|
2019-05-20 10:23:32 +00:00
|
|
|
menu.Add( menuText, i + 1, item->GetMenuImage() );
|
|
|
|
}
|
|
|
|
|
2020-08-10 13:24:40 +00:00
|
|
|
menu.AppendSeparator();
|
2021-03-08 02:59:07 +00:00
|
|
|
menu.Add( _( "Select &All\tA" ), limit + 1, BITMAPS::INVALID_BITMAP );
|
2020-08-10 13:24:40 +00:00
|
|
|
|
2019-05-20 10:23:32 +00:00
|
|
|
if( aCollector->m_MenuTitle.Length() )
|
2021-01-02 15:38:56 +00:00
|
|
|
{
|
2019-05-20 10:23:32 +00:00
|
|
|
menu.SetTitle( aCollector->m_MenuTitle );
|
2021-03-08 02:59:07 +00:00
|
|
|
menu.SetIcon( BITMAPS::info );
|
2021-01-02 15:38:56 +00:00
|
|
|
menu.DisplayTitle( true );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
menu.DisplayTitle( false );
|
|
|
|
}
|
2019-05-20 10:23:32 +00:00
|
|
|
|
|
|
|
SetContextMenu( &menu, CMENU_NOW );
|
|
|
|
|
2020-08-10 13:24:40 +00:00
|
|
|
bool selectAll = false;
|
|
|
|
|
2019-06-17 13:43:22 +00:00
|
|
|
while( TOOL_EVENT* evt = Wait() )
|
2019-05-20 10:23:32 +00:00
|
|
|
{
|
2019-06-15 00:29:42 +00:00
|
|
|
if( evt->Action() == TA_CHOICE_MENU_UPDATE )
|
2019-05-20 10:23:32 +00:00
|
|
|
{
|
2020-08-10 13:24:40 +00:00
|
|
|
if( selectAll )
|
|
|
|
{
|
|
|
|
for( int i = 0; i < aCollector->GetCount(); ++i )
|
|
|
|
unhighlight( ( *aCollector )[i], BRIGHTENED );
|
|
|
|
}
|
|
|
|
else if( current )
|
|
|
|
{
|
2019-05-20 10:23:32 +00:00
|
|
|
unhighlight( current, BRIGHTENED );
|
2020-08-10 13:24:40 +00:00
|
|
|
}
|
2019-05-20 10:23:32 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
{
|
2020-08-10 13:24:40 +00:00
|
|
|
current = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( id == limit + 1 )
|
|
|
|
{
|
|
|
|
for( int i = 0; i < aCollector->GetCount(); ++i )
|
|
|
|
highlight( ( *aCollector )[i], BRIGHTENED );
|
|
|
|
|
|
|
|
selectAll = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
selectAll = false;
|
2019-05-20 10:23:32 +00:00
|
|
|
}
|
|
|
|
}
|
2019-06-15 00:29:42 +00:00
|
|
|
else if( evt->Action() == TA_CHOICE_MENU_CHOICE )
|
2019-05-20 10:23:32 +00:00
|
|
|
{
|
2020-08-10 13:24:40 +00:00
|
|
|
if( selectAll )
|
|
|
|
{
|
|
|
|
for( int i = 0; i < aCollector->GetCount(); ++i )
|
|
|
|
unhighlight( ( *aCollector )[i], BRIGHTENED );
|
|
|
|
}
|
|
|
|
else if( current )
|
|
|
|
{
|
2019-05-20 10:23:32 +00:00
|
|
|
unhighlight( current, BRIGHTENED );
|
2020-08-10 13:24:40 +00:00
|
|
|
}
|
2019-05-20 10:23:32 +00:00
|
|
|
|
|
|
|
OPT<int> id = evt->GetCommandId();
|
|
|
|
|
|
|
|
// User has selected an item, so this one will be returned
|
2020-08-10 13:24:40 +00:00
|
|
|
if( id == limit + 1 )
|
|
|
|
{
|
|
|
|
selectAll = true;
|
|
|
|
current = nullptr;
|
|
|
|
}
|
|
|
|
else if( id && ( *id > 0 ) && ( *id <= limit ) )
|
|
|
|
{
|
|
|
|
selectAll = false;
|
2019-05-20 10:23:32 +00:00
|
|
|
current = ( *aCollector )[*id - 1];
|
2020-08-10 13:24:40 +00:00
|
|
|
}
|
2019-05-20 10:23:32 +00:00
|
|
|
else
|
2020-08-10 13:24:40 +00:00
|
|
|
{
|
|
|
|
selectAll = false;
|
|
|
|
current = nullptr;
|
|
|
|
}
|
2020-07-07 04:51:12 +00:00
|
|
|
}
|
|
|
|
else if( evt->Action() == TA_CHOICE_MENU_CLOSED )
|
|
|
|
{
|
2019-05-20 10:23:32 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
getView()->UpdateItems();
|
2019-06-13 17:28:55 +00:00
|
|
|
m_frame->GetCanvas()->Refresh();
|
2019-05-20 10:23:32 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 13:24:40 +00:00
|
|
|
if( selectAll )
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if( current )
|
2019-05-20 10:23:32 +00:00
|
|
|
{
|
|
|
|
unhighlight( current, BRIGHTENED );
|
|
|
|
|
|
|
|
getView()->UpdateItems();
|
2019-06-13 17:28:55 +00:00
|
|
|
m_frame->GetCanvas()->Refresh();
|
2019-05-20 10:23:32 +00:00
|
|
|
|
|
|
|
aCollector->Empty();
|
|
|
|
aCollector->Append( current );
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PL_SELECTION_TOOL::ClearSelection()
|
|
|
|
{
|
|
|
|
if( m_selection.Empty() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
while( m_selection.GetSize() )
|
|
|
|
unhighlight( (EDA_ITEM*) 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 PL_SELECTION_TOOL::select( EDA_ITEM* aItem )
|
|
|
|
{
|
|
|
|
highlight( aItem, SELECTED, &m_selection );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PL_SELECTION_TOOL::unselect( EDA_ITEM* aItem )
|
|
|
|
{
|
|
|
|
unhighlight( aItem, SELECTED, &m_selection );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-06-08 21:48:22 +00:00
|
|
|
void PL_SELECTION_TOOL::highlight( EDA_ITEM* aItem, int aMode, PL_SELECTION* aGroup )
|
2019-05-20 10:23:32 +00:00
|
|
|
{
|
|
|
|
if( aMode == SELECTED )
|
|
|
|
aItem->SetSelected();
|
|
|
|
else if( aMode == BRIGHTENED )
|
|
|
|
aItem->SetBrightened();
|
|
|
|
|
|
|
|
if( aGroup )
|
|
|
|
aGroup->Add( aItem );
|
|
|
|
|
|
|
|
getView()->Update( aItem );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-06-08 21:48:22 +00:00
|
|
|
void PL_SELECTION_TOOL::unhighlight( EDA_ITEM* aItem, int aMode, PL_SELECTION* aGroup )
|
2019-05-20 10:23:32 +00:00
|
|
|
{
|
|
|
|
if( aMode == SELECTED )
|
|
|
|
aItem->ClearSelected();
|
|
|
|
else if( aMode == BRIGHTENED )
|
|
|
|
aItem->ClearBrightened();
|
|
|
|
|
|
|
|
if( aGroup )
|
|
|
|
aGroup->Remove( aItem );
|
|
|
|
|
|
|
|
getView()->Update( aItem );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool PL_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 PL_SELECTION_TOOL::setTransitions()
|
|
|
|
{
|
|
|
|
Go( &PL_SELECTION_TOOL::UpdateMenu, ACTIONS::updateMenu.MakeEvent() );
|
|
|
|
|
|
|
|
Go( &PL_SELECTION_TOOL::Main, PL_ACTIONS::selectionActivate.MakeEvent() );
|
|
|
|
Go( &PL_SELECTION_TOOL::ClearSelection, PL_ACTIONS::clearSelection.MakeEvent() );
|
|
|
|
|
|
|
|
Go( &PL_SELECTION_TOOL::AddItemToSel, PL_ACTIONS::addItemToSel.MakeEvent() );
|
|
|
|
Go( &PL_SELECTION_TOOL::AddItemsToSel, PL_ACTIONS::addItemsToSel.MakeEvent() );
|
|
|
|
Go( &PL_SELECTION_TOOL::RemoveItemFromSel, PL_ACTIONS::removeItemFromSel.MakeEvent() );
|
|
|
|
Go( &PL_SELECTION_TOOL::RemoveItemsFromSel, PL_ACTIONS::removeItemsFromSel.MakeEvent() );
|
|
|
|
Go( &PL_SELECTION_TOOL::SelectionMenu, PL_ACTIONS::selectionMenu.MakeEvent() );
|
2021-09-05 20:37:52 +00:00
|
|
|
|
|
|
|
Go( &PL_SELECTION_TOOL::disambiguateCursor, EVENTS::DisambiguatePoint );
|
2019-05-20 10:23:32 +00:00
|
|
|
}
|