kicad-source/gerbview/tools/gerbview_selection_tool.cpp
Ian McInerney 2fb6f19a84 Separate immediate and delayed action dispatch
Using a boolean argument just leads to a lot of trailing booleans in the
function calls and is not user friendly. Instead, introduce PostAction()
to send an action that runs after the coroutine (equivalent to passing
false or the default argument), and leave RunAction as the immediate
execution function.
2023-06-27 00:57:59 +01:00

431 lines
12 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2017 Jon Evans <jon@craftyjon.com>
* Copyright (C) 2017-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 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, see <http://www.gnu.org/licenses/>.
*/
#include <limits>
#include <functional>
using namespace std::placeholders;
#include <bitmaps.h>
#include <eda_item.h>
#include <gerber_collectors.h>
#include <gerbview_settings.h>
#include <class_draw_panel_gal.h>
#include <string_utils.h>
#include <view/view.h>
#include <view/view_group.h>
#include <painter.h>
#include <tool/tool_event.h>
#include <tool/tool_manager.h>
#include "gerbview_selection_tool.h"
#include "gerbview_actions.h"
class HIGHLIGHT_MENU : public ACTION_MENU
{
public:
HIGHLIGHT_MENU() :
ACTION_MENU( true )
{
SetIcon( BITMAPS::net_highlight_schematic );
SetTitle( _( "Highlight" ) );
}
private:
void update() override
{
bool addSeparator = false;
Clear();
const auto& selection = getToolManager()->GetTool<GERBVIEW_SELECTION_TOOL>()->GetSelection();
if( selection.Size() == 1 )
{
auto item = static_cast<GERBER_DRAW_ITEM*>( selection[0] );
const auto& net_attr = item->GetNetAttributes();
if( ( net_attr.m_NetAttribType & GBR_NETLIST_METADATA::GBR_NETINFO_PAD ) ||
( net_attr.m_NetAttribType & GBR_NETLIST_METADATA::GBR_NETINFO_CMP ) )
{
auto menuEntry = Add( GERBVIEW_ACTIONS::highlightComponent );
menuEntry->SetItemLabel( wxString::Format( _( "Highlight Items of Component '%s'" ),
net_attr.m_Cmpref ) );
addSeparator = true;
}
if( ( net_attr.m_NetAttribType & GBR_NETLIST_METADATA::GBR_NETINFO_NET ) )
{
auto menuEntry = Add( GERBVIEW_ACTIONS::highlightNet );
menuEntry->SetItemLabel( wxString::Format( _( "Highlight Items of Net '%s'" ),
UnescapeString( net_attr.m_Netname ) ) );
addSeparator = true;
}
D_CODE* apertDescr = item->GetDcodeDescr();
if( apertDescr && !apertDescr->m_AperFunction.IsEmpty() )
{
auto menuEntry = Add( GERBVIEW_ACTIONS::highlightAttribute );
menuEntry->SetItemLabel( wxString::Format( _( "Highlight Aperture Type '%s'" ),
apertDescr->m_AperFunction ) );
addSeparator = true;
}
if( apertDescr )
{
auto menuEntry = Add( GERBVIEW_ACTIONS::highlightDCode );
menuEntry->SetItemLabel( wxString::Format( _( "Highlight DCode D%d" ),
apertDescr->m_Num_Dcode ) );
addSeparator = true;
}
}
if( addSeparator )
AppendSeparator();
Add( GERBVIEW_ACTIONS::highlightClear );
}
ACTION_MENU* create() const override
{
return new HIGHLIGHT_MENU();
}
};
GERBVIEW_SELECTION_TOOL::GERBVIEW_SELECTION_TOOL() :
SELECTION_TOOL( "gerbview.InteractiveSelection" ),
m_frame( nullptr )
{
}
GERBVIEW_SELECTION_TOOL::~GERBVIEW_SELECTION_TOOL()
{
getView()->Remove( &m_selection );
}
bool GERBVIEW_SELECTION_TOOL::Init()
{
std::shared_ptr<HIGHLIGHT_MENU> highlightSubMenu = std::make_shared<HIGHLIGHT_MENU>();
highlightSubMenu->SetTool( this );
m_menu.RegisterSubMenu( highlightSubMenu );
m_menu.GetMenu().AddMenu( highlightSubMenu.get() );
m_menu.GetMenu().AddSeparator( 1000 );
getEditFrame<GERBVIEW_FRAME>()->AddStandardSubMenus( m_menu );
return true;
}
void GERBVIEW_SELECTION_TOOL::Reset( RESET_REASON aReason )
{
m_frame = getEditFrame<GERBVIEW_FRAME>();
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 file 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 GERBVIEW_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
{
// Main loop: keep receiving events
while( TOOL_EVENT* evt = Wait() )
{
if( m_frame->ToolStackIsEmpty() )
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
// 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 ) );
// single click? Select single object
if( evt->IsClick( BUT_LEFT ) )
{
selectPoint( evt->Position() );
}
else if( evt->IsClick( BUT_RIGHT ) )
{
// right click? if there is any object - show the context menu
if( m_selection.Empty() )
{
selectPoint( evt->Position() );
m_selection.SetIsHover( true );
}
m_menu.ShowContextMenu( m_selection );
}
else if( evt->IsDblClick( BUT_MIDDLE ) )
{
// Middle double click? Do zoom to fit
m_toolMgr->RunAction( ACTIONS::zoomFitScreen );
}
else if( evt->IsCancel() || evt->Action() == TA_UNDO_REDO_PRE )
{
clearSelection();
}
else
{
evt->SetPassEvent();
}
}
return 0;
}
GERBVIEW_SELECTION& GERBVIEW_SELECTION_TOOL::GetSelection()
{
return m_selection;
}
bool GERBVIEW_SELECTION_TOOL::selectPoint( const VECTOR2I& aWhere )
{
EDA_ITEM* item = nullptr;
GERBER_COLLECTOR collector;
EDA_ITEM* model = getModel<EDA_ITEM>();
collector.Collect( model, { GERBER_LAYOUT_T, GERBER_IMAGE_T, GERBER_DRAW_ITEM_T }, aWhere );
// Remove unselectable items
for( int i = collector.GetCount() - 1; i >= 0; --i )
{
if( !selectable( collector[i] ) )
collector.Remove( i );
}
if( collector.GetCount() > 1 )
{
doSelectionMenu( &collector );
if( collector.m_MenuCancelled )
return false;
}
if( !m_additive && !m_subtractive && !m_exclusive_or )
clearSelection();
if( collector.GetCount() == 1 )
{
item = collector[ 0 ];
if( m_subtractive || ( m_exclusive_or && item->IsSelected() ) )
{
unselect( item );
m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
return false;
}
else
{
select( item );
m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
return true;
}
}
return false;
}
void GERBVIEW_SELECTION_TOOL::setTransitions()
{
Go( &GERBVIEW_SELECTION_TOOL::UpdateMenu, ACTIONS::updateMenu.MakeEvent() );
Go( &GERBVIEW_SELECTION_TOOL::Main, GERBVIEW_ACTIONS::selectionActivate.MakeEvent() );
Go( &GERBVIEW_SELECTION_TOOL::ClearSelection, GERBVIEW_ACTIONS::selectionClear.MakeEvent() );
Go( &GERBVIEW_SELECTION_TOOL::SelectItem, GERBVIEW_ACTIONS::selectItem.MakeEvent() );
Go( &GERBVIEW_SELECTION_TOOL::UnselectItem, GERBVIEW_ACTIONS::unselectItem.MakeEvent() );
}
int GERBVIEW_SELECTION_TOOL::ClearSelection( const TOOL_EVENT& aEvent )
{
clearSelection();
return 0;
}
int GERBVIEW_SELECTION_TOOL::SelectItems( const TOOL_EVENT& aEvent )
{
std::vector<EDA_ITEM*>* items = aEvent.Parameter<std::vector<EDA_ITEM*>*>();
if( items )
{
// Perform individual selection of each item before processing the event.
for( EDA_ITEM* item : *items )
select( item );
m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
}
return 0;
}
int GERBVIEW_SELECTION_TOOL::SelectItem( const TOOL_EVENT& aEvent )
{
// Check if there is an item to be selected
EDA_ITEM* item = aEvent.Parameter<EDA_ITEM*>();
if( item )
{
select( item );
// Inform other potentially interested tools
m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
}
return 0;
}
int GERBVIEW_SELECTION_TOOL::UnselectItems( const TOOL_EVENT& aEvent )
{
std::vector<EDA_ITEM*>* items = aEvent.Parameter<std::vector<EDA_ITEM*>*>();
if( items )
{
// Perform individual unselection of each item before processing the event
for( EDA_ITEM* item : *items )
unselect( item );
m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
}
return 0;
}
int GERBVIEW_SELECTION_TOOL::UnselectItem( const TOOL_EVENT& aEvent )
{
// Check if there is an item to be selected
EDA_ITEM* item = aEvent.Parameter<EDA_ITEM*>();
if( item )
{
unselect( item );
// Inform other potentially interested tools
m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
}
return 0;
}
void GERBVIEW_SELECTION_TOOL::clearSelection()
{
if( m_selection.Empty() )
return;
for( EDA_ITEM* item : m_selection )
unselectVisually( item );
m_selection.Clear();
// Inform other potentially interested tools
m_toolMgr->ProcessEvent( EVENTS::ClearedEvent );
}
bool GERBVIEW_SELECTION_TOOL::selectable( const EDA_ITEM* aItem ) const
{
GERBVIEW_FRAME* frame = getEditFrame<GERBVIEW_FRAME>();
const GERBER_DRAW_ITEM* item = static_cast<const GERBER_DRAW_ITEM*>( aItem );
int layer = item->GetLayer();
if( !frame->gvconfig()->m_Appearance.show_negative_objects && item->GetLayerPolarity() )
return false;
// We do not want to select items that are in the background
if( frame->gvconfig()->m_Display.m_HighContrastMode && layer != frame->GetActiveLayer() )
return false;
return frame->IsLayerVisible( layer );
}
void GERBVIEW_SELECTION_TOOL::select( EDA_ITEM* aItem )
{
if( aItem->IsSelected() )
return;
m_selection.Add( aItem );
getView()->Add( &m_selection, std::numeric_limits<int>::max() );
selectVisually( aItem );
}
void GERBVIEW_SELECTION_TOOL::unselect( EDA_ITEM* aItem )
{
if( !aItem->IsSelected() )
return;
unselectVisually( aItem );
m_selection.Remove( aItem );
if( m_selection.Empty() )
getView()->Remove( &m_selection );
}
void GERBVIEW_SELECTION_TOOL::selectVisually( EDA_ITEM* aItem )
{
// Move the item's layer to the front
int layer = static_cast<GERBER_DRAW_ITEM*>( aItem )->GetLayer();
m_frame->SetActiveLayer( layer, true );
// Hide the original item, so it is shown only on overlay
aItem->SetSelected();
getView()->Hide( aItem, true );
getView()->Update( &m_selection );
}
void GERBVIEW_SELECTION_TOOL::unselectVisually( EDA_ITEM* aItem )
{
// Restore original item visibility
aItem->ClearSelected();
getView()->Hide( aItem, false );
getView()->Update( aItem, KIGFX::ALL );
getView()->Update( &m_selection );
}