/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2019-2023 CERN
 * Copyright The 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 "sch_sheet_path.h"
#include <memory>

#include <kiplatform/ui.h>
#include <optional>
#include <project_sch.h>
#include <tools/sch_drawing_tools.h>
#include <tools/sch_line_wire_bus_tool.h>
#include <tools/sch_selection_tool.h>
#include <tools/ee_grid_helper.h>
#include <tools/rule_area_create_helper.h>
#include <gal/graphics_abstraction_layer.h>
#include <design_block_lib_table.h>
#include <sch_actions.h>
#include <sch_tool_utils.h>
#include <sch_edit_frame.h>
#include <pgm_base.h>
#include <design_block.h>
#include <design_block_pane.h>
#include <eeschema_id.h>
#include <confirm.h>
#include <view/view_controls.h>
#include <view/view.h>
#include <sch_symbol.h>
#include <sch_no_connect.h>
#include <sch_line.h>
#include <sch_junction.h>
#include <sch_bus_entry.h>
#include <sch_rule_area.h>
#include <sch_text.h>
#include <sch_textbox.h>
#include <sch_table.h>
#include <sch_tablecell.h>
#include <sch_sheet.h>
#include <sch_sheet_pin.h>
#include <sch_label.h>
#include <sch_bitmap.h>
#include <schematic.h>
#include <sch_commit.h>
#include <scoped_set_reset.h>
#include <symbol_library.h>
#include <eeschema_settings.h>
#include <dialogs/dialog_label_properties.h>
#include <dialogs/dialog_text_properties.h>
#include <dialogs/dialog_wire_bus_properties.h>
#include <dialogs/dialog_junction_props.h>
#include <dialogs/dialog_table_properties.h>
#include <import_gfx/dialog_import_gfx_sch.h>
#include <sync_sheet_pin/sheet_synchronization_agent.h>
#include <string_utils.h>
#include <wildcards_and_files_ext.h>
#include <wx/filedlg.h>
#include <wx/msgdlg.h>

SCH_DRAWING_TOOLS::SCH_DRAWING_TOOLS() :
        SCH_TOOL_BASE<SCH_EDIT_FRAME>( "eeschema.InteractiveDrawing" ),
        m_lastSheetPinType( LABEL_FLAG_SHAPE::L_INPUT ),
        m_lastGlobalLabelShape( LABEL_FLAG_SHAPE::L_INPUT ),
        m_lastNetClassFlagShape( LABEL_FLAG_SHAPE::F_ROUND ),
        m_lastTextOrientation( SPIN_STYLE::RIGHT ),
        m_lastTextBold( false ),
        m_lastTextItalic( false ),
        m_lastTextAngle( ANGLE_0 ),
        m_lastTextboxAngle( ANGLE_0 ),
        m_lastTextHJustify( GR_TEXT_H_ALIGN_CENTER ),
        m_lastTextVJustify( GR_TEXT_V_ALIGN_CENTER ),
        m_lastTextboxHJustify( GR_TEXT_H_ALIGN_LEFT ),
        m_lastTextboxVJustify( GR_TEXT_V_ALIGN_TOP ),
        m_lastFillStyle( FILL_T::NO_FILL ),
        m_lastTextboxFillStyle( FILL_T::NO_FILL ),
        m_lastFillColor( COLOR4D::UNSPECIFIED ),
        m_lastTextboxFillColor( COLOR4D::UNSPECIFIED ),
        m_lastStroke( 0, LINE_STYLE::DEFAULT, COLOR4D::UNSPECIFIED ),
        m_lastTextboxStroke( 0, LINE_STYLE::DEFAULT, COLOR4D::UNSPECIFIED ),
        m_mruPath( wxEmptyString ),
        m_lastAutoLabelRotateOnPlacement( false ),
        m_drawingRuleArea( false ),
        m_inDrawingTool( false )
{
}


bool SCH_DRAWING_TOOLS::Init()
{
    SCH_TOOL_BASE::Init();

    auto belowRootSheetCondition =
            [&]( const SELECTION& aSel )
            {
                return m_frame->GetCurrentSheet().Last() != &m_frame->Schematic().Root();
            };

    auto inDrawingRuleArea =
            [&]( const SELECTION& aSel )
            {
                return m_drawingRuleArea;
            };

    CONDITIONAL_MENU& ctxMenu = m_menu->GetMenu();
    ctxMenu.AddItem( SCH_ACTIONS::leaveSheet,      belowRootSheetCondition, 150 );
    ctxMenu.AddItem( SCH_ACTIONS::closeOutline,    inDrawingRuleArea,       200 );
    ctxMenu.AddItem( SCH_ACTIONS::deleteLastPoint, inDrawingRuleArea,       200 );

    return true;
}


int SCH_DRAWING_TOOLS::PlaceSymbol( const TOOL_EVENT& aEvent )
{
    const SCH_ACTIONS::PLACE_SYMBOL_PARAMS& toolParams =
            aEvent.Parameter<SCH_ACTIONS::PLACE_SYMBOL_PARAMS>();

    SCH_SYMBOL* symbol = toolParams.m_Symbol;
    // If we get an parameterised symbol, we probably just want to place
    // that and get out of the placmeent tool, rather than popping the
    // chooser afterwards
    bool placeOneOnly = symbol != nullptr;

    SYMBOL_LIBRARY_FILTER       filter;
    std::vector<PICKED_SYMBOL>* historyList = nullptr;
    bool                        ignorePrimePosition = false;
    COMMON_SETTINGS*            common_settings = Pgm().GetCommonSettings();
    SCH_SCREEN*                 screen = m_frame->GetScreen();

    if( m_inDrawingTool )
        return 0;

    REENTRANCY_GUARD guard( &m_inDrawingTool );

    KIGFX::VIEW_CONTROLS* controls = getViewControls();
    EE_GRID_HELPER        grid( m_toolMgr );
    VECTOR2I              cursorPos;

    // First we need to get all instances of this sheet so we can annotate
    // whatever symbols we place on all copies
    SCH_SHEET_LIST hierarchy = m_frame->Schematic().Hierarchy();
    SCH_SHEET_LIST newInstances =
            hierarchy.FindAllSheetsForScreen( m_frame->GetCurrentSheet().LastScreen() );
    newInstances.SortByPageNumbers();

    // Get a list of all references in the schematic to avoid duplicates wherever
    // they're placed
    SCH_REFERENCE_LIST existingRefs;
    hierarchy.GetSymbols( existingRefs );
    existingRefs.SortByReferenceOnly();

    if( aEvent.IsAction( &SCH_ACTIONS::placeSymbol ) )
    {
        historyList = &m_symbolHistoryList;
    }
    else if (aEvent.IsAction( &SCH_ACTIONS::placePower ) )
    {
        historyList = &m_powerHistoryList;
        filter.FilterPowerSymbols( true );
    }
    else
    {
        wxFAIL_MSG( "PlaceSymbol(): unexpected request" );
    }

    m_frame->PushTool( aEvent );

    auto addSymbol =
            [this]( SCH_SYMBOL* aSymbol )
            {
                m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );
                m_selectionTool->AddItemToSel( aSymbol );

                aSymbol->SetFlags( IS_NEW | IS_MOVING );

                m_view->ClearPreview();
                m_view->AddToPreview( aSymbol, false );   // Add, but not give ownership

                // Set IS_MOVING again, as AddItemToCommitAndScreen() will have cleared it.
                aSymbol->SetFlags( IS_MOVING );
                m_toolMgr->PostAction( ACTIONS::refreshPreview );
            };

    auto setCursor =
            [&]()
            {
                m_frame->GetCanvas()->SetCurrentCursor( symbol ? KICURSOR::MOVING
                                                               : KICURSOR::COMPONENT );
            };

    auto cleanup =
            [&]()
            {
                m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );
                m_view->ClearPreview();
                delete symbol;
                symbol = nullptr;

                existingRefs.Clear();
                hierarchy.GetSymbols( existingRefs );
                existingRefs.SortByReferenceOnly();
            };

    auto annotate =
            [&]()
            {
                EESCHEMA_SETTINGS* cfg = m_frame->eeconfig();

                // Then we need to annotate all instances by sheet
                for( SCH_SHEET_PATH& instance : newInstances )
                {
                    SCH_REFERENCE newReference( symbol, instance );
                    SCH_REFERENCE_LIST refs;
                    refs.AddItem( newReference );

                    if( cfg->m_AnnotatePanel.automatic || newReference.AlwaysAnnotate() )
                    {
                        refs.ReannotateByOptions( (ANNOTATE_ORDER_T) cfg->m_AnnotatePanel.sort_order,
                                                  (ANNOTATE_ALGO_T) cfg->m_AnnotatePanel.method,
                                                  m_frame->Schematic().Settings().m_AnnotateStartNum,
                                                  existingRefs, false, &hierarchy );

                        refs.UpdateAnnotation();

                        // Update existing refs for next iteration
                        for( size_t i = 0; i < refs.GetCount(); i++ )
                            existingRefs.AddItem( refs[i] );
                    }
                }

                m_frame->GetCurrentSheet().UpdateAllScreenReferences();
            };

    Activate();

    // Must be done after Activate() so that it gets set into the correct context
    getViewControls()->ShowCursor( true );

    // Set initial cursor
    setCursor();

    // Prime the pump
    if( symbol )
    {
        addSymbol( symbol );

        if( toolParams.m_Reannotate )
            annotate();

        getViewControls()->WarpMouseCursor( getViewControls()->GetMousePosition( false ) );
    }
    else if( aEvent.HasPosition() )
    {
        m_toolMgr->PrimeTool( aEvent.Position() );
    }
    else if( common_settings->m_Input.immediate_actions && !aEvent.IsReactivate() )
    {
        m_toolMgr->PrimeTool( { 0, 0 } );
        ignorePrimePosition = true;
    }

    // Main loop: keep receiving events
    while( TOOL_EVENT* evt = Wait() )
    {
        setCursor();
        grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
        grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );

        cursorPos = grid.Align( controls->GetMousePosition(), GRID_HELPER_GRIDS::GRID_CONNECTABLE );
        controls->ForceCursorPosition( true, cursorPos );

        // The tool hotkey is interpreted as a click when drawing
        bool isSyntheticClick = symbol && evt->IsActivate() && evt->HasPosition()
                                && evt->Matches( aEvent );

        if( evt->IsCancelInteractive() || ( symbol && evt->IsAction( &ACTIONS::undo ) ) )
        {
            m_frame->GetInfoBar()->Dismiss();

            if( symbol )
            {
                cleanup();
            }
            else
            {
                m_frame->PopTool( aEvent );
                break;
            }
        }
        else if( evt->IsActivate() && !isSyntheticClick )
        {
            if( symbol && evt->IsMoveTool() )
            {
                // we're already moving our own item; ignore the move tool
                evt->SetPassEvent( false );
                continue;
            }

            if( symbol )
            {
                m_frame->ShowInfoBarMsg( _( "Press <ESC> to cancel symbol creation." ) );
                evt->SetPassEvent( false );
                continue;
            }

            if( evt->IsMoveTool() )
            {
                // leave ourselves on the stack so we come back after the move
                break;
            }
            else
            {
                m_frame->PopTool( aEvent );
                break;
            }
        }
        else if( evt->IsClick( BUT_LEFT ) || evt->IsDblClick( BUT_LEFT ) || isSyntheticClick )
        {
            if( !symbol )
            {
                m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );

                SYMBOL_LIB_TABLE* libs = PROJECT_SCH::SchSymbolLibTable( &m_frame->Prj() );
                SYMBOL_LIB*       cache = PROJECT_SCH::SchLibs( &m_frame->Prj() )->GetCacheLibrary();

                std::set<UTF8>             unique_libid;
                std::vector<PICKED_SYMBOL> alreadyPlaced;

                for( SCH_SHEET_PATH& sheet : hierarchy )
                {
                    for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
                    {
                        SCH_SYMBOL* s = static_cast<SCH_SYMBOL*>( item );

                        if( !unique_libid.insert( s->GetLibId().Format() ).second )
                            continue;

                        LIB_SYMBOL* libSymbol = SchGetLibSymbol( s->GetLibId(), libs, cache );

                        if( libSymbol )
                        {
                            PICKED_SYMBOL pickedSymbol;
                            pickedSymbol.LibId = libSymbol->GetLibId();
                            alreadyPlaced.push_back( pickedSymbol );
                        }
                    }
                }

                // Pick the symbol to be placed
                bool footprintPreviews = m_frame->eeconfig()->m_Appearance.footprint_preview;
                PICKED_SYMBOL sel = m_frame->PickSymbolFromLibrary( &filter, *historyList,
                                                                    alreadyPlaced,
                                                                    footprintPreviews );

                LIB_SYMBOL* libSymbol = sel.LibId.IsValid() ? m_frame->GetLibSymbol( sel.LibId )
                                                            : nullptr;

                if( !libSymbol )
                    continue;

                // If we started with a hotkey which has a position then warp back to that.
                // Otherwise update to the current mouse position pinned inside the autoscroll
                // boundaries.
                if( evt->IsPrime() && !ignorePrimePosition )
                {
                    cursorPos = grid.Align( evt->Position(), GRID_HELPER_GRIDS::GRID_CONNECTABLE );
                    getViewControls()->WarpMouseCursor( cursorPos, true );
                }
                else
                {
                    getViewControls()->PinCursorInsideNonAutoscrollArea( true );
                    cursorPos = grid.Align( getViewControls()->GetMousePosition(),
                                            GRID_HELPER_GRIDS::GRID_CONNECTABLE );
                }

                symbol = new SCH_SYMBOL( *libSymbol, &m_frame->GetCurrentSheet(), sel, cursorPos,
                                         &m_frame->Schematic() );
                addSymbol( symbol );
                annotate();

                // Update the list of references for the next symbol placement.
                SCH_REFERENCE placedSymbolReference( symbol, m_frame->GetCurrentSheet() );
                existingRefs.AddItem( placedSymbolReference );
                existingRefs.SortByReferenceOnly();

                if( m_frame->eeconfig()->m_AutoplaceFields.enable )
                {
                    // Not placed yet, so pass a nullptr screen reference
                    symbol->AutoplaceFields( nullptr, AUTOPLACE_AUTO );
                }

                // Update cursor now that we have a symbol
                setCursor();
            }
            else
            {
                m_view->ClearPreview();
                m_frame->AddToScreen( symbol, screen );

                if( m_frame->eeconfig()->m_AutoplaceFields.enable )
                    symbol->AutoplaceFields( screen, AUTOPLACE_AUTO );

                m_frame->SaveCopyForRepeatItem( symbol );

                SCH_COMMIT commit( m_toolMgr );
                commit.Added( symbol, screen );

                SCH_LINE_WIRE_BUS_TOOL* lwbTool = m_toolMgr->GetTool<SCH_LINE_WIRE_BUS_TOOL>();
                lwbTool->TrimOverLappingWires( &commit, &m_selectionTool->GetSelection() );
                lwbTool->AddJunctionsIfNeeded( &commit, &m_selectionTool->GetSelection() );

                commit.Push( _( "Place Symbol" ) );

                if( placeOneOnly )
                {
                    m_frame->PopTool( aEvent );
                    break;
                }

                SCH_SYMBOL* nextSymbol = nullptr;

                if( m_frame->eeconfig()->m_SymChooserPanel.place_all_units
                        || m_frame->eeconfig()->m_SymChooserPanel.keep_symbol )
                {
                    int new_unit = symbol->GetUnit();

                    if( m_frame->eeconfig()->m_SymChooserPanel.place_all_units
                        && symbol->GetUnit() < symbol->GetUnitCount() )
                    {
                        new_unit++;
                    }
                    else
                    {
                        new_unit = 1;
                    }

                    // We are either stepping to the next unit or next symbol
                    if( m_frame->eeconfig()->m_SymChooserPanel.keep_symbol || new_unit > 1 )
                    {
                        nextSymbol = static_cast<SCH_SYMBOL*>( symbol->Duplicate() );
                        nextSymbol->SetUnit( new_unit );
                        nextSymbol->SetUnitSelection( new_unit );

                        // Start new annotation sequence at first unit
                        if( new_unit == 1 )
                            nextSymbol->ClearAnnotation( nullptr, false );

                        addSymbol( nextSymbol );
                        symbol = nextSymbol;
                        annotate();

                        // Update the list of references for the next symbol placement.
                        SCH_REFERENCE placedSymbolReference( symbol, m_frame->GetCurrentSheet() );
                        existingRefs.AddItem( placedSymbolReference );
                        existingRefs.SortByReferenceOnly();
                    }
                }

                symbol = nextSymbol;
            }
        }
        else if( evt->IsClick( BUT_RIGHT ) )
        {
            // Warp after context menu only if dragging...
            if( !symbol )
                m_toolMgr->VetoContextMenuMouseWarp();

            m_menu->ShowContextMenu( m_selectionTool->GetSelection() );
        }
        else if( evt->Category() == TC_COMMAND && evt->Action() == TA_CHOICE_MENU_CHOICE )
        {
            if( *evt->GetCommandId() >= ID_POPUP_SCH_SELECT_UNIT
                && *evt->GetCommandId() <= ID_POPUP_SCH_SELECT_UNIT_END )
            {
                int unit = *evt->GetCommandId() - ID_POPUP_SCH_SELECT_UNIT;

                if( symbol )
                {
                    m_frame->SelectUnit( symbol, unit );
                    m_toolMgr->PostAction( ACTIONS::refreshPreview );
                }
            }
            else if( *evt->GetCommandId() >= ID_POPUP_SCH_SELECT_BASE
                     && *evt->GetCommandId() <= ID_POPUP_SCH_SELECT_ALT )
            {
                int bodyStyle = ( *evt->GetCommandId() - ID_POPUP_SCH_SELECT_BASE ) + 1;

                if( symbol && symbol->GetBodyStyle() != bodyStyle )
                {
                    m_frame->FlipBodyStyle( symbol );
                    m_toolMgr->PostAction( ACTIONS::refreshPreview );
                }
            }
        }
        else if( evt->IsAction( &ACTIONS::duplicate )
                 || evt->IsAction( &SCH_ACTIONS::repeatDrawItem ) )
        {
            if( symbol )
            {
                // This doesn't really make sense; we'll just end up dragging a stack of
                // objects so we ignore the duplicate and just carry on.
                wxBell();
                continue;
            }

            // Exit.  The duplicate will run in its own loop.
            m_frame->PopTool( aEvent );
            break;
        }
        else if( symbol && ( evt->IsAction( &ACTIONS::refreshPreview ) || evt->IsMotion() ) )
        {
            symbol->SetPosition( cursorPos );
            m_view->ClearPreview();
            m_view->AddToPreview( symbol, false );   // Add, but not give ownership
            m_frame->SetMsgPanel( symbol );
        }
        else if( symbol && evt->IsAction( &ACTIONS::doDelete ) )
        {
            cleanup();
        }
        else if( symbol && evt->IsAction( &ACTIONS::redo ) )
        {
            wxBell();
        }
        else
        {
            evt->SetPassEvent();
        }

        // Enable autopanning and cursor capture only when there is a symbol to be placed
        getViewControls()->SetAutoPan( symbol != nullptr );
        getViewControls()->CaptureCursor( symbol != nullptr );
    }

    getViewControls()->SetAutoPan( false );
    getViewControls()->CaptureCursor( false );
    m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );

    return 0;
}


int SCH_DRAWING_TOOLS::PlaceNextSymbolUnit( const TOOL_EVENT& aEvent )
{
    SCH_SYMBOL* symbol = aEvent.Parameter<SCH_SYMBOL*>();

    // TODO: get from selection
    if( !symbol )
    {
        static const std::vector<KICAD_T> symbolTypes = { SCH_SYMBOL_T };
        SCH_SELECTION& selection = m_selectionTool->RequestSelection( symbolTypes );

        if( selection.Size() != 1 )
        {
            m_frame->ShowInfoBarMsg( _( "Select a single symbol to place the next unit." ) );
            return 0;
        }

        wxCHECK( selection.Front()->Type() == SCH_SYMBOL_T, 0 );
        symbol = static_cast<SCH_SYMBOL*>( selection.Front() );
    }

    if( !symbol )
        return 0;

    if( !symbol->IsMulti() )
    {
        m_frame->ShowInfoBarMsg( _( "This symbol has only one unit." ) );
        return 0;
    }

    const std::set<int> missingUnits = GetUnplacedUnitsForSymbol( *symbol );

    if( missingUnits.empty() )
    {
        m_frame->ShowInfoBarMsg( _( "All units of this symbol are already placed." ) );
        return 0;
    }

    // Find the lowest unit number that is missing
    const int nextMissing = *std::min_element( missingUnits.begin(), missingUnits.end() );

    std::unique_ptr<SCH_SYMBOL> newSymbol = std::make_unique<SCH_SYMBOL>( *symbol );
    const SCH_SHEET_PATH&       sheetPath = m_frame->GetCurrentSheet();

    newSymbol->SetUnitSelection( &sheetPath, nextMissing );
    newSymbol->SetUnit( nextMissing );
    newSymbol->SetRefProp( symbol->GetRef( &sheetPath, false ) );

    // Post the new symbol - don't reannotate it - we set the reference ourselves
    m_toolMgr->PostAction( SCH_ACTIONS::placeSymbol,
                           SCH_ACTIONS::PLACE_SYMBOL_PARAMS{ newSymbol.release(), false } );
    return 0;
}


int SCH_DRAWING_TOOLS::ImportSheet( const TOOL_EVENT& aEvent )
{
    bool          placingDesignBlock = aEvent.IsAction( &SCH_ACTIONS::placeDesignBlock );

    DESIGN_BLOCK* designBlock = nullptr;
    wxString      sheetFileName = wxEmptyString;

    if( placingDesignBlock )
    {
        if( m_frame->GetDesignBlockPane()->GetSelectedLibId().IsValid() )
        {
            designBlock =
                    m_frame->GetDesignBlock( m_frame->GetDesignBlockPane()->GetSelectedLibId() );

            if( !designBlock )
                return 0;

            sheetFileName = designBlock->GetSchematicFile();
        }
    }
    else
    {
        wxString* importSourceFile = aEvent.Parameter<wxString*>();

        if( importSourceFile != nullptr )
            sheetFileName = *importSourceFile;
    }

    COMMON_SETTINGS*            common_settings = Pgm().GetCommonSettings();
    EESCHEMA_SETTINGS*          cfg = m_frame->eeconfig();
    SCHEMATIC_SETTINGS&         schSettings = m_frame->Schematic().Settings();
    SCH_SCREEN*                 screen = m_frame->GetScreen();
    SCH_SHEET_PATH&             sheetPath = m_frame->GetCurrentSheet();

    KIGFX::VIEW_CONTROLS* controls = getViewControls();
    EE_GRID_HELPER        grid( m_toolMgr );
    VECTOR2I              cursorPos;

    if( !cfg || !common_settings )
        return 0;

    if( m_inDrawingTool )
        return 0;

    auto setCursor =
            [&]()
            {
                m_frame->GetCanvas()->SetCurrentCursor( designBlock ? KICURSOR::MOVING
                                                               : KICURSOR::COMPONENT );
            };

    auto placeSheetContents =
            [&]()
            {
                SCH_COMMIT          commit( m_toolMgr );
                SCH_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<SCH_SELECTION_TOOL>();

                EDA_ITEMS newItems;
                bool      keepAnnotations = cfg->m_DesignBlockChooserPanel.keep_annotations;

                selectionTool->ClearSelection();

                // Mark all existing items on the screen so we don't select them after appending
                for( EDA_ITEM* item : screen->Items() )
                    item->SetFlags( SKIP_STRUCT );

                if( !m_frame->LoadSheetFromFile( sheetPath.Last(), &sheetPath, sheetFileName, true,
                                                 placingDesignBlock ) )
                    return false;

                m_frame->SetSheetNumberAndCount();

                m_frame->SyncView();
                m_frame->OnModify();
                m_frame->HardRedraw(); // Full reinit of the current screen and the display.

                // Select all new items
                for( EDA_ITEM* item : screen->Items() )
                {
                    if( !item->HasFlag( SKIP_STRUCT ) )
                    {
                        if( item->Type() == SCH_SYMBOL_T && !keepAnnotations )
                            static_cast<SCH_SYMBOL*>( item )->ClearAnnotation( &sheetPath, false );

                        if( item->Type() == SCH_LINE_T )
                            item->SetFlags( STARTPOINT | ENDPOINT );

                        commit.Added( item, screen );
                        newItems.emplace_back( item );
                    }
                    else
                        item->ClearFlags( SKIP_STRUCT );
                }

                selectionTool->AddItemsToSel( &newItems, true );

                cursorPos = grid.Align( controls->GetMousePosition(),
                                        grid.GetSelectionGrid( selectionTool->GetSelection() ) );
                controls->ForceCursorPosition( true, cursorPos );

                // Move everything to our current mouse position now
                // that we have a selection to get a reference point
                VECTOR2I anchorPos = selectionTool->GetSelection().GetReferencePoint();
                VECTOR2I delta = cursorPos - anchorPos;

                // Will all be SCH_ITEMs as these were pulled from the screen->Items()
                for( EDA_ITEM* item : newItems )
                    static_cast<SCH_ITEM*>( item )->Move( delta );

                if( !keepAnnotations )
                {
                    EESCHEMA_SETTINGS::PANEL_ANNOTATE& annotate = cfg->m_AnnotatePanel;

                    if( annotate.automatic )
                    {
                        NULL_REPORTER reporter;
                        m_frame->AnnotateSymbols( &commit, ANNOTATE_SELECTION,
                                                  (ANNOTATE_ORDER_T) annotate.sort_order,
                                                  (ANNOTATE_ALGO_T) annotate.method, true /* recursive */,
                                                  schSettings.m_AnnotateStartNum, false, false, reporter );
                    }

                    // Annotation will clear selection, so we need to restore it
                    for( EDA_ITEM* item : newItems )
                    {
                        if( item->Type() == SCH_LINE_T )
                            item->SetFlags( STARTPOINT | ENDPOINT );
                    }

                    selectionTool->AddItemsToSel( &newItems, true );
                }

                // Start moving selection, cancel undoes the insertion
                bool placed = m_toolMgr->RunSynchronousAction( SCH_ACTIONS::move, &commit );

                // Update our cursor position to the new location in case we're placing repeated copies
                cursorPos = grid.Align( controls->GetMousePosition(), GRID_HELPER_GRIDS::GRID_CONNECTABLE );

                if( placed )
                    commit.Push( placingDesignBlock ? _( "Add design block" )
                                                    : _( "Import Schematic Sheet Content..." ) );
                else
                    commit.Revert();

                m_frame->UpdateHierarchyNavigator();

                return placed;
            };

    // Whether we are placing the sheet as a sheet, or as its contents, we need to get a filename
    // if we weren't provided one
    if( sheetFileName.IsEmpty() )
    {
            wxString path;
            wxString file;

            if (!placingDesignBlock) {

                if( sheetFileName.IsEmpty() )
                {
                    path = wxPathOnly( m_frame->Prj().GetProjectFullName() );
                    file = wxEmptyString;
                }
                else
                {
                    path = wxPathOnly( sheetFileName );
                    file = wxFileName( sheetFileName ).GetFullName();
                }

                // Open file chooser dialog even if we have been provided a file so the user
                // can select the options they want
                wxFileDialog dlg( m_frame, _( "Choose Schematic" ), path, file,
                                  FILEEXT::KiCadSchematicFileWildcard(),
                                  wxFD_OPEN | wxFD_FILE_MUST_EXIST );

                FILEDLG_IMPORT_SHEET_CONTENTS dlgHook( cfg );
                dlg.SetCustomizeHook( dlgHook );

                if( dlg.ShowModal() == wxID_CANCEL )
                    return 0;

                sheetFileName = dlg.GetPath();

                m_frame->UpdateDesignBlockOptions();
            }

            if( sheetFileName.IsEmpty() )
                return 0;
    }

    // If we're placing sheet contents, we don't even want to run our tool loop, just add the items
    // to the canvas and run the move tool
    if( !cfg->m_DesignBlockChooserPanel.place_as_sheet )
    {
            while( placeSheetContents() && cfg->m_DesignBlockChooserPanel.repeated_placement )
                ;

            m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );
            m_view->ClearPreview();
            delete designBlock;
            designBlock = nullptr;

            return 0;
    }

    // We're placing a sheet as a sheet, we need to run a small tool loop to get the starting
    // coordinate of the sheet drawing
    m_frame->PushTool( aEvent );

    Activate();

    // Must be done after Activate() so that it gets set into the correct context
    getViewControls()->ShowCursor( true );

    // Set initial cursor
    setCursor();

    if( common_settings->m_Input.immediate_actions && !aEvent.IsReactivate() )
    {
        m_toolMgr->PrimeTool( { 0, 0 } );
    }

    // Main loop: keep receiving events
    while( TOOL_EVENT* evt = Wait() )
    {
        setCursor();
        grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
        grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );

        cursorPos = grid.Align( controls->GetMousePosition(), GRID_HELPER_GRIDS::GRID_CONNECTABLE );
        controls->ForceCursorPosition( true, cursorPos );

        // The tool hotkey is interpreted as a click when drawing
        bool isSyntheticClick = designBlock && evt->IsActivate() && evt->HasPosition()
                                && evt->Matches( aEvent );

        if( evt->IsCancelInteractive() || ( designBlock && evt->IsAction( &ACTIONS::undo ) ) )
        {
            m_frame->GetInfoBar()->Dismiss();
            break;
        }
        else if( evt->IsClick( BUT_LEFT ) || evt->IsDblClick( BUT_LEFT ) || isSyntheticClick )
        {
            if( placingDesignBlock )
            {
                m_toolMgr->PostAction( SCH_ACTIONS::drawSheetFromDesignBlock, designBlock );
            }
            else
            {
                // drawSheet must delete
                m_toolMgr->PostAction( SCH_ACTIONS::drawSheetFromFile,
                                       new wxString( sheetFileName ) );
            }

            break;
        }
        else if( evt->IsClick( BUT_RIGHT ) )
        {
            // Warp after context menu only if dragging...
            if( !designBlock )
                m_toolMgr->VetoContextMenuMouseWarp();

            m_menu->ShowContextMenu( m_selectionTool->GetSelection() );
        }
        else if( evt->IsAction( &ACTIONS::duplicate )
                 || evt->IsAction( &SCH_ACTIONS::repeatDrawItem ) )
        {
            wxBell();
        }
        else
        {
            evt->SetPassEvent();
        }
    }

    m_frame->PopTool( aEvent );
    m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );

    return 0;
}


int SCH_DRAWING_TOOLS::PlaceImage( const TOOL_EVENT& aEvent )
{
    SCH_BITMAP*      image = aEvent.Parameter<SCH_BITMAP*>();
    bool             immediateMode = image != nullptr;
    bool             ignorePrimePosition = false;
    COMMON_SETTINGS* common_settings = Pgm().GetCommonSettings();

    if( m_inDrawingTool )
        return 0;

    REENTRANCY_GUARD guard( &m_inDrawingTool );

    EE_GRID_HELPER        grid( m_toolMgr );
    KIGFX::VIEW_CONTROLS* controls = getViewControls();
    VECTOR2I              cursorPos;

    m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );

    // Add all the drawable symbols to preview
    if( image )
    {
        image->SetPosition( getViewControls()->GetCursorPosition() );
        m_view->ClearPreview();
        m_view->AddToPreview( image, false );   // Add, but not give ownership
    }

    m_frame->PushTool( aEvent );

    auto setCursor =
            [&]()
            {
                if( image )
                    m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::MOVING );
                else
                    m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
            };

    auto cleanup =
            [&] ()
            {
                m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );
                m_view->ClearPreview();
                m_view->RecacheAllItems();
                delete image;
                image = nullptr;
            };

    Activate();

    // Must be done after Activate() so that it gets set into the correct context
    getViewControls()->ShowCursor( true );

    // Set initial cursor
    setCursor();

    // Prime the pump
    if( image )
    {
        m_toolMgr->PostAction( ACTIONS::refreshPreview );
    }
    else if( aEvent.HasPosition() )
    {
        m_toolMgr->PrimeTool( aEvent.Position() );
    }
    else if( common_settings->m_Input.immediate_actions && !aEvent.IsReactivate() )
    {
        m_toolMgr->PrimeTool( { 0, 0 } );
        ignorePrimePosition = true;
    }

    // Main loop: keep receiving events
    while( TOOL_EVENT* evt = Wait() )
    {
        setCursor();
        grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
        grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );

        cursorPos = grid.Align( controls->GetMousePosition(), GRID_HELPER_GRIDS::GRID_GRAPHICS );
        controls->ForceCursorPosition( true, cursorPos );

        // The tool hotkey is interpreted as a click when drawing
        bool isSyntheticClick = image && evt->IsActivate() && evt->HasPosition()
                                && evt->Matches( aEvent );

        if( evt->IsCancelInteractive() || ( image && evt->IsAction( &ACTIONS::undo ) ) )
        {
            m_frame->GetInfoBar()->Dismiss();

            if( image )
            {
                cleanup();
            }
            else
            {
                m_frame->PopTool( aEvent );
                break;
            }

            if( immediateMode )
            {
                m_frame->PopTool( aEvent );
                break;
            }
        }
        else if( evt->IsActivate() && !isSyntheticClick )
        {
            if( image && evt->IsMoveTool() )
            {
                // we're already moving our own item; ignore the move tool
                evt->SetPassEvent( false );
                continue;
            }

            if( image )
            {
                m_frame->ShowInfoBarMsg( _( "Press <ESC> to cancel image creation." ) );
                evt->SetPassEvent( false );
                continue;
            }

            if( evt->IsMoveTool() )
            {
                // leave ourselves on the stack so we come back after the move
                break;
            }
            else
            {
                m_frame->PopTool( aEvent );
                break;
            }
        }
        else if( evt->IsClick( BUT_LEFT ) || evt->IsDblClick( BUT_LEFT ) || isSyntheticClick )
        {
            if( !image )
            {
                m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );

                wxFileDialog dlg( m_frame, _( "Choose Image" ), m_mruPath, wxEmptyString,
                                  _( "Image Files" ) + wxS( " " ) + wxImage::GetImageExtWildcard(),
                                  wxFD_OPEN );

                if( dlg.ShowModal() != wxID_OK )
                    continue;

                // If we started with a hotkey which has a position then warp back to that.
                // Otherwise update to the current mouse position pinned inside the autoscroll
                // boundaries.
                if( evt->IsPrime() && !ignorePrimePosition )
                {
                    cursorPos = grid.Align( evt->Position() );
                    getViewControls()->WarpMouseCursor( cursorPos, true );
                }
                else
                {
                    getViewControls()->PinCursorInsideNonAutoscrollArea( true );
                    cursorPos = getViewControls()->GetMousePosition();
                }

                wxString fullFilename = dlg.GetPath();
                m_mruPath = wxPathOnly( fullFilename );

                if( wxFileExists( fullFilename ) )
                    image = new SCH_BITMAP( cursorPos );

                if( !image || !image->GetReferenceImage().ReadImageFile( fullFilename ) )
                {
                    wxMessageBox( wxString::Format( _( "Could not load image from '%s'." ), fullFilename ) );
                    delete image;
                    image = nullptr;
                    continue;
                }

                image->SetFlags( IS_NEW | IS_MOVING );

                m_frame->SaveCopyForRepeatItem( image );

                m_view->ClearPreview();
                m_view->AddToPreview( image, false );   // Add, but not give ownership
                m_view->RecacheAllItems();              // Bitmaps are cached in Opengl

                m_selectionTool->AddItemToSel( image );

                getViewControls()->SetCursorPosition( cursorPos, false );
                setCursor();
            }
            else
            {
                SCH_COMMIT commit( m_toolMgr );
                commit.Add( image, m_frame->GetScreen() );
                commit.Push( _( "Place Image" ) );

                image = nullptr;
                m_toolMgr->PostAction( ACTIONS::activatePointEditor );

                m_view->ClearPreview();

                if( immediateMode )
                {
                    m_frame->PopTool( aEvent );
                    break;
                }
            }
        }
        else if( evt->IsClick( BUT_RIGHT ) )
        {
            // Warp after context menu only if dragging...
            if( !image )
                m_toolMgr->VetoContextMenuMouseWarp();

            m_menu->ShowContextMenu( m_selectionTool->GetSelection() );
        }
        else if( evt->IsAction( &ACTIONS::duplicate )
                 || evt->IsAction( &SCH_ACTIONS::repeatDrawItem ) )
        {
            if( image )
            {
                // This doesn't really make sense; we'll just end up dragging a stack of
                // objects so we ignore the duplicate and just carry on.
                wxBell();
                continue;
            }

            // Exit.  The duplicate will run in its own loop.
            m_frame->PopTool( aEvent );
            break;
        }
        else if( image && ( evt->IsAction( &ACTIONS::refreshPreview ) || evt->IsMotion() ) )
        {
            image->SetPosition( cursorPos );
            m_view->ClearPreview();
            m_view->AddToPreview( image, false );   // Add, but not give ownership
            m_view->RecacheAllItems();              // Bitmaps are cached in Opengl
            m_frame->SetMsgPanel( image );
        }
        else if( image && evt->IsAction( &ACTIONS::doDelete ) )
        {
            cleanup();
        }
        else if( image && evt->IsAction( &ACTIONS::redo ) )
        {
            wxBell();
        }
        else
        {
            evt->SetPassEvent();
        }

        // Enable autopanning and cursor capture only when there is an image to be placed
        getViewControls()->SetAutoPan( image != nullptr );
        getViewControls()->CaptureCursor( image != nullptr );
    }

    getViewControls()->SetAutoPan( false );
    getViewControls()->CaptureCursor( false );
    m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );

    return 0;
}


int SCH_DRAWING_TOOLS::ImportGraphics( const TOOL_EVENT& aEvent )
{
    if( m_inDrawingTool )
        return 0;

    REENTRANCY_GUARD guard( &m_inDrawingTool );

    // Note: PlaceImportedGraphics() will convert PCB_SHAPE_T and PCB_TEXT_T to footprint
    // items if needed
    DIALOG_IMPORT_GFX_SCH dlg( m_frame );
    int                   dlgResult = dlg.ShowModal();

    std::list<std::unique_ptr<EDA_ITEM>>& list = dlg.GetImportedItems();

    if( dlgResult != wxID_OK )
        return 0;

    // Ensure the list is not empty:
    if( list.empty() )
    {
        wxMessageBox( _( "No graphic items found in file." ) );
        return 0;
    }

    m_toolMgr->RunAction( ACTIONS::cancelInteractive );

    KIGFX::VIEW_CONTROLS*  controls = getViewControls();
    std::vector<SCH_ITEM*> newItems;      // all new items, including group
    std::vector<SCH_ITEM*> selectedItems; // the group, or newItems if no group
    SCH_SELECTION          preview;
    SCH_COMMIT             commit( m_toolMgr );

    for( std::unique_ptr<EDA_ITEM>& ptr : list )
    {
        SCH_ITEM* item = dynamic_cast<SCH_ITEM*>( ptr.get() );
        wxCHECK2_MSG( item, continue, wxString::Format( "Bad item type: ", ptr->Type() ) );

        newItems.push_back( item );
        selectedItems.push_back( item );
        preview.Add( item );

        ptr.release();
    }

    if( !dlg.IsPlacementInteractive() )
    {
        // Place the imported drawings
        for( SCH_ITEM* item : newItems )
            commit.Add(item, m_frame->GetScreen());

        commit.Push( _( "Import Graphic" ) );
        return 0;
    }

    m_view->Add( &preview );

    // Clear the current selection then select the drawings so that edit tools work on them
    m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );

    EDA_ITEMS selItems( selectedItems.begin(), selectedItems.end() );
    m_toolMgr->RunAction<EDA_ITEMS*>( SCH_ACTIONS::addItemsToSel, &selItems );

    m_frame->PushTool( aEvent );

    auto setCursor = [&]()
    {
        m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::MOVING );
    };

    Activate();
    // Must be done after Activate() so that it gets set into the correct context
    controls->ShowCursor( true );
    controls->ForceCursorPosition( false );
    // Set initial cursor
    setCursor();

    //SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::DXF );
    EE_GRID_HELPER grid( m_toolMgr );

    // Now move the new items to the current cursor position:
    VECTOR2I cursorPos = controls->GetCursorPosition( !aEvent.DisableGridSnapping() );
    VECTOR2I delta = cursorPos;
    VECTOR2I currentOffset;

    for( SCH_ITEM* item : selectedItems )
        item->Move( delta );

    currentOffset += delta;

    m_view->Update( &preview );

    // Main loop: keep receiving events
    while( TOOL_EVENT* evt = Wait() )
    {
        setCursor();

        grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
        grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );

        cursorPos = grid.Align( controls->GetMousePosition(), GRID_GRAPHICS );
        controls->ForceCursorPosition( true, cursorPos );

        if( evt->IsCancelInteractive() || evt->IsActivate() )
        {
            m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );

            for( SCH_ITEM* item : newItems )
                delete item;

            break;
        }
        else if( evt->IsMotion() )
        {
            delta = cursorPos - currentOffset;

            for( SCH_ITEM* item : selectedItems )
                item->Move( delta );

            currentOffset += delta;

            m_view->Update( &preview );
        }
        else if( evt->IsClick( BUT_RIGHT ) )
        {
            m_menu->ShowContextMenu( m_selectionTool->GetSelection() );
        }
        else if( evt->IsClick( BUT_LEFT ) || evt->IsDblClick( BUT_LEFT ) )
        {
            // Place the imported drawings
            for( SCH_ITEM* item : newItems )
                commit.Add( item, m_frame->GetScreen() );

            commit.Push( _( "Import Graphic" ) );
            break; // This is a one-shot command, not a tool
        }
        else
        {
            evt->SetPassEvent();
        }
    }

    preview.Clear();
    m_view->Remove( &preview );

    m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
    controls->ForceCursorPosition( false );

    m_frame->PopTool( aEvent );

    return 0;
}


int SCH_DRAWING_TOOLS::SingleClickPlace( const TOOL_EVENT& aEvent )
{
    VECTOR2I              cursorPos;
    KICAD_T               type = aEvent.Parameter<KICAD_T>();
    EE_GRID_HELPER        grid( m_toolMgr );
    KIGFX::VIEW_CONTROLS* controls = getViewControls();
    SCH_ITEM*             previewItem;
    bool                  loggedInfoBarError = false;
    wxString              description;
    SCH_SCREEN*           screen = m_frame->GetScreen();
    bool                  allowRepeat = false;  // Set to true to allow new item repetition

    if( m_inDrawingTool )
        return 0;

    REENTRANCY_GUARD guard( &m_inDrawingTool );

    if( type == SCH_JUNCTION_T && aEvent.HasPosition() )
    {
        SCH_SELECTION& selection = m_selectionTool->GetSelection();
        SCH_LINE*      wire = dynamic_cast<SCH_LINE*>( selection.Front() );

        if( wire )
        {
            SEG seg( wire->GetStartPoint(), wire->GetEndPoint() );
            VECTOR2I nearest = seg.NearestPoint( getViewControls()->GetCursorPosition() );
            getViewControls()->SetCrossHairCursorPosition( nearest, false );
            getViewControls()->WarpMouseCursor( getViewControls()->GetCursorPosition(), true );
        }
    }

    switch( type )
    {
    case SCH_NO_CONNECT_T:
        previewItem = new SCH_NO_CONNECT( cursorPos );
        previewItem->SetParent( screen );
        description = _( "Add No Connect Flag" );
        allowRepeat = true;
        break;

    case SCH_JUNCTION_T:
        previewItem = new SCH_JUNCTION( cursorPos );
        previewItem->SetParent( screen );
        description = _( "Add Junction" );
        break;

    case SCH_BUS_WIRE_ENTRY_T:
        previewItem = new SCH_BUS_WIRE_ENTRY( cursorPos );
        previewItem->SetParent( screen );
        description = _( "Add Wire to Bus Entry" );
        allowRepeat = true;
        break;

    default:
        wxASSERT_MSG( false, "Unknown item type in SCH_DRAWING_TOOLS::SingleClickPlace" );
        return 0;
    }

    m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );

    cursorPos = aEvent.HasPosition() ? aEvent.Position() : controls->GetMousePosition();

    m_frame->PushTool( aEvent );

    auto setCursor =
            [&]()
            {
                m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::PLACE );
            };

    Activate();

    // Must be done after Activate() so that it gets set into the correct context
    getViewControls()->ShowCursor( true );

    // Set initial cursor
    setCursor();

    m_view->ClearPreview();
    m_view->AddToPreview( previewItem->Clone() );

    // Prime the pump
    if( aEvent.HasPosition() && type != SCH_SHEET_PIN_T )
        m_toolMgr->PrimeTool( aEvent.Position() );
    else
        m_toolMgr->PostAction( ACTIONS::refreshPreview );

    // Main loop: keep receiving events
    while( TOOL_EVENT* evt = Wait() )
    {
        setCursor();
        grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
        grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );

        cursorPos = evt->IsPrime() ? evt->Position() : controls->GetMousePosition();
        cursorPos = grid.BestSnapAnchor( cursorPos, grid.GetItemGrid( previewItem ), nullptr );
        controls->ForceCursorPosition( true, cursorPos );

        if( evt->IsCancelInteractive() )
        {
            m_frame->PopTool( aEvent );
            break;
        }
        else if( evt->IsActivate() )
        {
            if( evt->IsMoveTool() )
            {
                // leave ourselves on the stack so we come back after the move
                break;
            }
            else
            {
                m_frame->PopTool( aEvent );
                break;
            }
        }
        else if( evt->IsClick( BUT_LEFT ) || evt->IsDblClick( BUT_LEFT ) )
        {
            if( !screen->GetItem( cursorPos, 0, type ) )
            {
                if( type == SCH_JUNCTION_T )
                {
                    if( !screen->IsExplicitJunctionAllowed( cursorPos ) )
                    {
                        m_frame->ShowInfoBarError( _( "Junction location contains no joinable "
                                                      "wires and/or pins." ) );
                        loggedInfoBarError = true;
                        continue;
                    }
                    else if( loggedInfoBarError )
                    {
                        m_frame->GetInfoBar()->Dismiss();
                    }
                }

                SCH_ITEM* newItem = static_cast<SCH_ITEM*>( previewItem->Clone() );
                const_cast<KIID&>( newItem->m_Uuid ) = KIID();
                newItem->SetPosition( cursorPos );
                newItem->SetFlags( IS_NEW );
                m_frame->AddToScreen( newItem, screen );

                if( allowRepeat )
                    m_frame->SaveCopyForRepeatItem( newItem );

                SCH_COMMIT commit( m_toolMgr );
                commit.Added( newItem, screen );

                m_frame->SchematicCleanUp( &commit );

                commit.Push( description );
            }

            if( evt->IsDblClick( BUT_LEFT ) || type == SCH_SHEET_PIN_T )  // Finish tool.
            {
                m_frame->PopTool( aEvent );
                break;
            }
        }
        else if( evt->IsClick( BUT_RIGHT ) )
        {
            m_menu->ShowContextMenu( m_selectionTool->GetSelection() );
        }
        else if( evt->IsAction( &ACTIONS::refreshPreview ) || evt->IsMotion() )
        {
            previewItem->SetPosition( cursorPos );
            m_view->ClearPreview();
            m_view->AddToPreview( previewItem->Clone() );
            m_frame->SetMsgPanel( previewItem );
        }
        else if( evt->Category() == TC_COMMAND )
        {
            if( ( type == SCH_BUS_WIRE_ENTRY_T ) && (   evt->IsAction( &SCH_ACTIONS::rotateCW )
                                                     || evt->IsAction( &SCH_ACTIONS::rotateCCW )
                                                     || evt->IsAction( &SCH_ACTIONS::mirrorV )
                                                     || evt->IsAction( &SCH_ACTIONS::mirrorH ) ) )
            {
                SCH_BUS_ENTRY_BASE* busItem = static_cast<SCH_BUS_ENTRY_BASE*>( previewItem );

                if( evt->IsAction( &SCH_ACTIONS::rotateCW ) )
                {
                    busItem->Rotate( busItem->GetPosition(), false );
                }
                else if( evt->IsAction( &SCH_ACTIONS::rotateCCW ) )
                {
                    busItem->Rotate( busItem->GetPosition(), true );
                }
                else if( evt->IsAction( &SCH_ACTIONS::mirrorV ) )
                {
                    busItem->MirrorVertically( busItem->GetPosition().y );
                }
                else if( evt->IsAction( &SCH_ACTIONS::mirrorH ) )
                {
                    busItem->MirrorHorizontally( busItem->GetPosition().x );
                }

                m_view->ClearPreview();
                m_view->AddToPreview( previewItem->Clone() );
            }
            else if( evt->IsAction( &SCH_ACTIONS::properties ) )
            {
                switch( type )
                {
                case SCH_BUS_WIRE_ENTRY_T:
                {
                    std::deque<SCH_ITEM*> strokeItems;
                    strokeItems.push_back( previewItem );

                    DIALOG_WIRE_BUS_PROPERTIES dlg( m_frame, strokeItems );
                }
                    break;

                case SCH_JUNCTION_T:
                {
                    std::deque<SCH_JUNCTION*> junctions;
                    junctions.push_back( static_cast<SCH_JUNCTION*>( previewItem ) );

                    DIALOG_JUNCTION_PROPS dlg( m_frame, junctions );
                }
                    break;
                default:
                    // Do nothing
                    break;
                }

                m_view->ClearPreview();
                m_view->AddToPreview( previewItem->Clone() );
            }
            else
            {
                evt->SetPassEvent();
            }
        }
        else
        {
            evt->SetPassEvent();
        }
    }

    delete previewItem;
    m_view->ClearPreview();

    m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
    controls->ForceCursorPosition( false );

    return 0;
}


SCH_LINE* SCH_DRAWING_TOOLS::findWire( const VECTOR2I& aPosition )
{
    for( SCH_ITEM* item : m_frame->GetScreen()->Items().Overlapping( SCH_LINE_T, aPosition ) )
    {
        SCH_LINE* line = static_cast<SCH_LINE*>( item );

        if( line->GetEditFlags() & STRUCT_DELETED )
            continue;

        if( line->IsWire() )
            return line;
    }

    return nullptr;
}


wxString SCH_DRAWING_TOOLS::findWireLabelDriverName( SCH_LINE* aWire )
{
    wxASSERT( aWire->IsWire() );

    SCH_SHEET_PATH sheetPath = m_frame->GetCurrentSheet();

    if( SCH_CONNECTION* wireConnection = aWire->Connection( &sheetPath ) )
    {
        SCH_ITEM* wireDriver = wireConnection->Driver();

        if( wireDriver && wireDriver->IsType( { SCH_LABEL_T, SCH_GLOBAL_LABEL_T } ) )
            return wireConnection->LocalName();
    }

    return wxEmptyString;
}


bool SCH_DRAWING_TOOLS::createNewLabel( const VECTOR2I& aPosition, int aType,
                                       std::list<std::unique_ptr<SCH_LABEL_BASE>>& aLabelList )
{
    SCHEMATIC*          schematic = getModel<SCHEMATIC>();
    SCHEMATIC_SETTINGS& settings = schematic->Settings();
    SCH_LABEL_BASE*     labelItem = nullptr;
    wxString            netName;

    switch( aType )
    {
    case LAYER_LOCLABEL:
        labelItem = new SCH_LABEL( aPosition );

        if( SCH_LINE* wire = findWire( aPosition ) )
            netName = findWireLabelDriverName( wire );

        break;

    case LAYER_NETCLASS_REFS:
        labelItem = new SCH_DIRECTIVE_LABEL( aPosition );
        labelItem->SetShape( m_lastNetClassFlagShape );
        labelItem->GetFields().emplace_back( SCH_FIELD( {0,0}, 0, labelItem, wxT( "Netclass" ) ) );
        labelItem->GetFields().emplace_back(
                SCH_FIELD( { 0, 0 }, 0, labelItem, wxT( "Component Class" ) ) );
        labelItem->GetFields().back().SetItalic( true );
        labelItem->GetFields().back().SetVisible( true );
        break;

    case LAYER_HIERLABEL:
        labelItem = new SCH_HIERLABEL( aPosition );
        labelItem->SetShape( m_lastGlobalLabelShape );
        labelItem->SetAutoRotateOnPlacement( m_lastAutoLabelRotateOnPlacement );
        break;

    case LAYER_GLOBLABEL:
        labelItem = new SCH_GLOBALLABEL( aPosition );
        labelItem->SetShape( m_lastGlobalLabelShape );
        labelItem->GetFields()[0].SetVisible( settings.m_IntersheetRefsShow );
        labelItem->SetAutoRotateOnPlacement( m_lastAutoLabelRotateOnPlacement );

        if( SCH_LINE* wire = findWire( aPosition ) )
            netName = findWireLabelDriverName( wire );

        break;

    default:
        wxFAIL_MSG( "SCH_EDIT_FRAME::CreateNewText() unknown layer type" );
        return false;
    }

    labelItem->SetParent( schematic );

    labelItem->SetTextSize( VECTOR2I( settings.m_DefaultTextSize, settings.m_DefaultTextSize ) );

    if( aType != LAYER_NETCLASS_REFS )
    {
        // Must be after SetTextSize()
        labelItem->SetBold( m_lastTextBold );
        labelItem->SetItalic( m_lastTextItalic );
    }

    labelItem->SetSpinStyle( m_lastTextOrientation );
    labelItem->SetFlags( IS_NEW | IS_MOVING );

    if( !netName.IsEmpty() )
    {
        // Auto-create from attached wire
        labelItem->SetText( netName );
    }
    else
    {
        DIALOG_LABEL_PROPERTIES dlg( m_frame, labelItem, true );

        dlg.SetLabelList( &aLabelList );

        // QuasiModal required for syntax help and Scintilla auto-complete
        if( dlg.ShowQuasiModal() != wxID_OK )
        {
            dlg.GetFieldsGridTable()->DetachFields();
            delete labelItem;
            return false;
        }
    }

    if( aType != LAYER_NETCLASS_REFS )
    {
        m_lastTextBold = labelItem->IsBold();
        m_lastTextItalic = labelItem->IsItalic();
    }

    m_lastTextOrientation = labelItem->GetSpinStyle();

    if( aType == LAYER_GLOBLABEL || aType == LAYER_HIERLABEL )
    {
        m_lastGlobalLabelShape = labelItem->GetShape();
        m_lastAutoLabelRotateOnPlacement = labelItem->AutoRotateOnPlacement();
    }
    else if( aType == LAYER_NETCLASS_REFS )
    {
        m_lastNetClassFlagShape = labelItem->GetShape();
    }

    // Return elements are kept in aLabelList
    delete labelItem;
    return true;
}


SCH_TEXT* SCH_DRAWING_TOOLS::createNewText( const VECTOR2I& aPosition )
{
    SCHEMATIC*          schematic = getModel<SCHEMATIC>();
    SCHEMATIC_SETTINGS& settings = schematic->Settings();
    SCH_TEXT*           textItem = nullptr;

    textItem = new SCH_TEXT( aPosition );
    textItem->SetParent( schematic );
    textItem->SetTextSize( VECTOR2I( settings.m_DefaultTextSize, settings.m_DefaultTextSize ) );
    // Must be after SetTextSize()
    textItem->SetBold( m_lastTextBold );
    textItem->SetItalic( m_lastTextItalic );
    textItem->SetHorizJustify( m_lastTextHJustify );
    textItem->SetVertJustify( m_lastTextVJustify );
    textItem->SetTextAngle( m_lastTextAngle );
    textItem->SetFlags( IS_NEW | IS_MOVING );

    DIALOG_TEXT_PROPERTIES dlg( m_frame, textItem );

    // QuasiModal required for syntax help and Scintilla auto-complete
    if( dlg.ShowQuasiModal() != wxID_OK )
    {
        delete textItem;
        return nullptr;
    }

    m_lastTextBold = textItem->IsBold();
    m_lastTextItalic = textItem->IsItalic();
    m_lastTextHJustify = textItem->GetHorizJustify();
    m_lastTextVJustify = textItem->GetVertJustify();
    m_lastTextAngle = textItem->GetTextAngle();
    return textItem;
}


SCH_SHEET_PIN* SCH_DRAWING_TOOLS::createNewSheetPin( SCH_SHEET* aSheet, const VECTOR2I& aPosition )
{
    SCHEMATIC_SETTINGS& settings = aSheet->Schematic()->Settings();
    SCH_SHEET_PIN*      pin = new SCH_SHEET_PIN( aSheet );

    pin->SetFlags( IS_NEW | IS_MOVING );
    pin->SetText( std::to_string( aSheet->GetPins().size() + 1 ) );
    pin->SetTextSize( VECTOR2I( settings.m_DefaultTextSize, settings.m_DefaultTextSize ) );
    pin->SetPosition( aPosition );
    pin->ClearSelected();

    m_lastSheetPinType = pin->GetShape();

    return pin;
}


SCH_SHEET_PIN* SCH_DRAWING_TOOLS::createNewSheetPinFromLabel( SCH_SHEET*      aSheet,
                                                              const VECTOR2I& aPosition,
                                                              SCH_HIERLABEL*  aLabel )
{
    auto pin = createNewSheetPin( aSheet, aPosition );
    pin->SetText( aLabel->GetText() );
    pin->SetShape( aLabel->GetShape() );
    return pin;
}


int SCH_DRAWING_TOOLS::TwoClickPlace( const TOOL_EVENT& aEvent )
{
    SCH_ITEM*             item = nullptr;
    KIGFX::VIEW_CONTROLS* controls = getViewControls();
    EE_GRID_HELPER        grid( m_toolMgr );
    bool                  ignorePrimePosition = false;
    COMMON_SETTINGS*      common_settings = Pgm().GetCommonSettings();
    SCH_SHEET*            sheet = nullptr;
    wxString              description;

    if( m_inDrawingTool )
        return 0;

    REENTRANCY_GUARD guard( &m_inDrawingTool );

    bool isText        = aEvent.IsAction( &SCH_ACTIONS::placeSchematicText );
    bool isGlobalLabel = aEvent.IsAction( &SCH_ACTIONS::placeGlobalLabel );
    bool isHierLabel   = aEvent.IsAction( &SCH_ACTIONS::placeHierLabel );
    bool isClassLabel  = aEvent.IsAction( &SCH_ACTIONS::placeClassLabel );
    bool isNetLabel    = aEvent.IsAction( &SCH_ACTIONS::placeLabel );
    bool isSheetPin    = aEvent.IsAction( &SCH_ACTIONS::placeSheetPin );

    GRID_HELPER_GRIDS snapGrid = isText ? GRID_TEXT : GRID_CONNECTABLE;

    // If we have a selected sheet use it, otherwise try to get one under the cursor
    if( isSheetPin )
        sheet = dynamic_cast<SCH_SHEET*>( m_selectionTool->GetSelection().Front() );

    m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );

    m_frame->PushTool( aEvent );

    auto setCursor =
            [&]()
            {
                if( item )
                    m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::PLACE );
                else if( isText )
                    m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::TEXT );
                else if( isGlobalLabel )
                    m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::LABEL_GLOBAL );
                else if( isNetLabel )
                    m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::LABEL_NET );
                else if( isClassLabel )
                    m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::LABEL_NET );    // JEY TODO: netclass directive cursor
                else if( isHierLabel )
                    m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::LABEL_HIER );
                else
                    m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::PENCIL );
            };

    auto updatePreview =
            [&]()
            {
                m_view->ClearPreview();
                m_view->AddToPreview( item, false );
                item->RunOnChildren( [&]( SCH_ITEM* aChild )
                                     {
                                         m_view->AddToPreview( aChild, false );
                                     } );
                m_frame->SetMsgPanel( item );
            };

    auto cleanup =
            [&]()
            {
                m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );
                m_view->ClearPreview();
                delete item;
                item = nullptr;
            };

    auto prepItemForPlacement =
            [&]( SCH_ITEM* aItem, const VECTOR2I& cursorPos )
            {
                item->SetPosition( cursorPos );

                item->SetFlags( IS_NEW | IS_MOVING );

                // Not placed yet, so pass a nullptr screen reference
                item->AutoplaceFields( nullptr, AUTOPLACE_AUTO );

                updatePreview();
                m_selectionTool->AddItemToSel( item );
                m_toolMgr->PostAction( ACTIONS::refreshPreview );

                // update the cursor so it looks correct before another event
                setCursor();
            };


    Activate();

    // Must be done after Activate() so that it gets set into the correct context
    controls->ShowCursor( true );

    // Set initial cursor
    setCursor();

    if( aEvent.HasPosition() )
    {
        m_toolMgr->PrimeTool( aEvent.Position() );
    }
    else if( common_settings->m_Input.immediate_actions && !aEvent.IsReactivate()
                && ( isText || isGlobalLabel || isHierLabel || isClassLabel || isNetLabel ) )
    {
        m_toolMgr->PrimeTool( { 0, 0 } );
        ignorePrimePosition = true;
    }

    SCH_COMMIT commit( m_toolMgr );
    std::list<std::unique_ptr<SCH_LABEL_BASE>> itemsToPlace;

    // Main loop: keep receiving events
    while( TOOL_EVENT* evt = Wait() )
    {
        setCursor();
        grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
        grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );

        VECTOR2I cursorPos = controls->GetMousePosition();
        cursorPos = grid.BestSnapAnchor( cursorPos, snapGrid, item );
        controls->ForceCursorPosition( true, cursorPos );

        // The tool hotkey is interpreted as a click when drawing
        bool isSyntheticClick = item && evt->IsActivate() && evt->HasPosition()
                                && evt->Matches( aEvent );

        if( evt->IsCancelInteractive() || evt->IsAction( &ACTIONS::undo ) )
        {
            m_frame->GetInfoBar()->Dismiss();

            if( item )
            {
                cleanup();
            }
            else
            {
                m_frame->PopTool( aEvent );
                break;
            }
        }
        else if( evt->IsActivate() && !isSyntheticClick )
        {
            if( item && evt->IsMoveTool() )
            {
                // we're already moving our own item; ignore the move tool
                evt->SetPassEvent( false );
                continue;
            }

            if( item )
            {
                m_frame->ShowInfoBarMsg( _( "Press <ESC> to cancel item creation." ) );
                evt->SetPassEvent( false );
                continue;
            }

            if( evt->IsPointEditor() )
            {
                // don't exit (the point editor runs in the background)
            }
            else if( evt->IsMoveTool() )
            {
                // leave ourselves on the stack so we come back after the move
                break;
            }
            else
            {
                m_frame->PopTool( aEvent );
                break;
            }
        }
        else if( evt->IsClick( BUT_LEFT ) || evt->IsDblClick( BUT_LEFT ) || isSyntheticClick )
        {
        PLACE_NEXT:
            // First click creates...
            if( !item )
            {
                m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );

                if( isText )
                {
                    item = createNewText( cursorPos );
                    description = _( "Add Text" );
                }
                else if( isHierLabel )
                {
                    if( m_dialogSyncSheetPin && m_dialogSyncSheetPin->GetPlacementTemplate() )
                    {
                        auto pin = static_cast<SCH_HIERLABEL*>(
                                m_dialogSyncSheetPin->GetPlacementTemplate() );
                        SCH_HIERLABEL* label = new SCH_HIERLABEL( cursorPos );
                        SCHEMATIC*     schematic = getModel<SCHEMATIC>();
                        label->SetText( pin->GetText() );
                        label->SetShape( pin->GetShape() );
                        label->SetAutoRotateOnPlacement( m_lastAutoLabelRotateOnPlacement );
                        label->SetParent( schematic );
                        label->SetBold( m_lastTextBold );
                        label->SetItalic( m_lastTextItalic );
                        label->SetSpinStyle( m_lastTextOrientation );
                        label->SetTextSize( VECTOR2I( schematic->Settings().m_DefaultTextSize,
                                                      schematic->Settings().m_DefaultTextSize ) );
                        label->SetFlags( IS_NEW | IS_MOVING );
                        itemsToPlace.push_back( std::unique_ptr<SCH_LABEL_BASE>( label ) );
                    }
                    else
                    {
                        createNewLabel( cursorPos, LAYER_HIERLABEL, itemsToPlace );
                    }

                    description = _( "Add Hierarchical Label" );
                }
                else if( isNetLabel )
                {
                    createNewLabel( cursorPos, LAYER_LOCLABEL, itemsToPlace );
                    description = _( "Add Label" );
                }
                else if( isGlobalLabel )
                {
                    createNewLabel( cursorPos, LAYER_GLOBLABEL, itemsToPlace );
                    description = _( "Add Label" );
                }
                else if( isClassLabel )
                {
                    createNewLabel( cursorPos, LAYER_NETCLASS_REFS, itemsToPlace );
                    description = _( "Add Label" );
                }
                else if( isSheetPin )
                {
                    EDA_ITEM* i = nullptr;

                    // If we didn't have a sheet selected, try to find one under the cursor
                    if( !sheet && m_selectionTool->SelectPoint( cursorPos, { SCH_SHEET_T }, &i ) )
                        sheet = dynamic_cast<SCH_SHEET*>( i );

                    if( !sheet )
                    {
                        m_statusPopup = std::make_unique<STATUS_TEXT_POPUP>( m_frame );
                        m_statusPopup->SetText( _( "Click over a sheet." ) );
                        m_statusPopup->Move( KIPLATFORM::UI::GetMousePosition()
                                             + wxPoint( 20, 20 ) );
                        m_statusPopup->PopupFor( 2000 );
                        item = nullptr;
                    }
                    else
                    {
                        // User is using the 'Sync Sheet Pins' tool
                        if( m_dialogSyncSheetPin && m_dialogSyncSheetPin->GetPlacementTemplate() )
                        {
                            item = createNewSheetPinFromLabel(
                                    sheet, cursorPos,
                                    static_cast<SCH_HIERLABEL*>(
                                            m_dialogSyncSheetPin->GetPlacementTemplate() ) );
                        }
                        else
                        {
                            // User is using the 'Place Sheet Pins' tool
                            SCH_HIERLABEL* label = importHierLabel( sheet );

                            if( !label )
                            {
                                m_statusPopup = std::make_unique<STATUS_TEXT_POPUP>( m_frame );
                                m_statusPopup->SetText( _( "No new hierarchical labels found." ) );
                                m_statusPopup->Move( KIPLATFORM::UI::GetMousePosition()
                                                     + wxPoint( 20, 20 ) );
                                m_statusPopup->PopupFor( 2000 );
                                item = nullptr;

                                m_frame->PopTool( aEvent );
                                break;
                            }

                            item = createNewSheetPinFromLabel( sheet, cursorPos, label );
                        }
                    }

                    description = _( "Add Sheet Pin" );
                }

                // If we started with a hotkey which has a position then warp back to that.
                // Otherwise update to the current mouse position pinned inside the autoscroll
                // boundaries.
                if( evt->IsPrime() && !ignorePrimePosition )
                {
                    cursorPos = grid.Align( evt->Position() );
                    getViewControls()->WarpMouseCursor( cursorPos, true );
                }
                else
                {
                    getViewControls()->PinCursorInsideNonAutoscrollArea( true );
                    cursorPos = getViewControls()->GetMousePosition();
                    cursorPos = grid.BestSnapAnchor( cursorPos, snapGrid, item );
                }

                if( !itemsToPlace.empty() )
                {
                    item = itemsToPlace.front().release();
                    itemsToPlace.pop_front();
                }

                if( item )
                    prepItemForPlacement( item, cursorPos );

                controls->SetCursorPosition( cursorPos, false );
            }
            else            // ... and second click places:
            {
                item->ClearFlags( IS_MOVING );

                if( item->IsConnectable() )
                    m_frame->AutoRotateItem( m_frame->GetScreen(), item );

                if( isSheetPin )
                {
                    // Sheet pins are owned by their parent sheet.
                    commit.Modify( sheet, m_frame->GetScreen() );
                    sheet->AddPin( (SCH_SHEET_PIN*) item );
                }
                else
                {
                    m_frame->SaveCopyForRepeatItem( item );
                    m_frame->AddToScreen( item, m_frame->GetScreen() );
                    commit.Added( item, m_frame->GetScreen() );
                }

                item->AutoplaceFields( m_frame->GetScreen(), AUTOPLACE_AUTO );

                commit.Push( description );

                m_view->ClearPreview();

                if( m_dialogSyncSheetPin && m_dialogSyncSheetPin->GetPlacementTemplate() )
                {
                    m_dialogSyncSheetPin->EndPlaceItem( item );

                    if( m_dialogSyncSheetPin->CanPlaceMore() )
                    {
                        item = nullptr;
                        goto PLACE_NEXT;
                    }

                    m_frame->PopTool( aEvent );
                    m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );
                    m_dialogSyncSheetPin->Show( true );
                    break;
                }

                item = nullptr;

                if( isSheetPin )
                {
                    SCH_HIERLABEL* label = importHierLabel( sheet );

                    if( !label )
                    {
                        m_statusPopup = std::make_unique<STATUS_TEXT_POPUP>( m_frame );
                        m_statusPopup->SetText( _( "No new hierarchical labels found." ) );
                        m_statusPopup->Move( KIPLATFORM::UI::GetMousePosition()
                                             + wxPoint( 20, 20 ) );
                        m_statusPopup->PopupFor( 2000 );

                        m_frame->PopTool( aEvent );
                        break;
                    }

                    item = createNewSheetPinFromLabel( sheet, cursorPos, label );
                }
                else if( !itemsToPlace.empty() )
                {

                    item = itemsToPlace.front().release();
                    itemsToPlace.pop_front();
                    prepItemForPlacement( item, cursorPos );
                }
            }
        }
        else if( evt->IsClick( BUT_RIGHT ) )
        {
            // Warp after context menu only if dragging...
            if( !item )
                m_toolMgr->VetoContextMenuMouseWarp();

            m_menu->ShowContextMenu( m_selectionTool->GetSelection() );
        }
        else if( item && evt->IsSelectionEvent() )
        {
            // This happens if our text was replaced out from under us by ConvertTextType()
            SCH_SELECTION& selection = m_selectionTool->GetSelection();

            if( selection.GetSize() == 1 )
            {
                item = (SCH_ITEM*) selection.Front();
                updatePreview();
            }
            else
            {
                item = nullptr;
            }
        }
        else if( evt->IsAction( &ACTIONS::increment ) )
        {
            m_toolMgr->RunSynchronousAction( ACTIONS::increment, &commit,
                                             evt->Parameter<ACTIONS::INCREMENT>() );
        }
        else if( evt->IsAction( &ACTIONS::duplicate )
                 || evt->IsAction( &SCH_ACTIONS::repeatDrawItem ) )
        {
            if( item )
            {
                // This doesn't really make sense; we'll just end up dragging a stack of
                // objects so we ignore the duplicate and just carry on.
                wxBell();
                continue;
            }

            // Exit.  The duplicate will run in its own loop.
            m_frame->PopTool( aEvent );
            break;
        }
        else if( item && ( evt->IsAction( &ACTIONS::refreshPreview ) || evt->IsMotion() ) )
        {
            item->SetPosition( cursorPos );

            // Not placed yet, so pass a nullptr screen reference
            item->AutoplaceFields( nullptr, AUTOPLACE_AUTO );

            updatePreview();
        }
        else if( item && evt->IsAction( &ACTIONS::doDelete ) )
        {
            cleanup();
        }
        else if( evt->IsAction( &ACTIONS::redo ) )
        {
            wxBell();
        }
        else
        {
            evt->SetPassEvent();
        }

        // Enable autopanning and cursor capture only when there is an item to be placed
        controls->SetAutoPan( item != nullptr );
        controls->CaptureCursor( item != nullptr );
    }

    controls->SetAutoPan( false );
    controls->CaptureCursor( false );
    controls->ForceCursorPosition( false );
    m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );

    if( m_dialogSyncSheetPin && m_dialogSyncSheetPin->CanPlaceMore() )
    {
        m_dialogSyncSheetPin->EndPlacement();
        m_dialogSyncSheetPin->Show( true );
    }

    return 0;
}


int SCH_DRAWING_TOOLS::DrawShape( const TOOL_EVENT& aEvent )
{
    SCHEMATIC*          schematic = getModel<SCHEMATIC>();
    SCHEMATIC_SETTINGS& sch_settings = schematic->Settings();
    SCH_SHAPE*          item = nullptr;
    bool                isTextBox = aEvent.IsAction( &SCH_ACTIONS::drawTextBox );
    SHAPE_T             type = aEvent.Parameter<SHAPE_T>();
    wxString            description;

    if( m_inDrawingTool )
        return 0;

    REENTRANCY_GUARD guard( &m_inDrawingTool );

    KIGFX::VIEW_CONTROLS* controls = getViewControls();
    EE_GRID_HELPER        grid( m_toolMgr );
    VECTOR2I              cursorPos;

    // We might be running as the same shape in another co-routine.  Make sure that one
    // gets whacked.
    m_toolMgr->DeactivateTool();

    m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );

    m_frame->PushTool( aEvent );

    auto setCursor =
            [&]()
            {
                m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::PENCIL );
            };

    auto cleanup =
            [&] ()
            {
                m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );
                m_view->ClearPreview();
                delete item;
                item = nullptr;
            };

    Activate();

    // Must be done after Activate() so that it gets set into the correct context
    getViewControls()->ShowCursor( true );

    // Set initial cursor
    setCursor();

    if( aEvent.HasPosition() )
        m_toolMgr->PrimeTool( aEvent.Position() );

    // Main loop: keep receiving events
    while( TOOL_EVENT* evt = Wait() )
    {
        setCursor();
        grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
        grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );

        cursorPos = grid.Align( controls->GetMousePosition(), GRID_HELPER_GRIDS::GRID_GRAPHICS );
        controls->ForceCursorPosition( true, cursorPos );

        // The tool hotkey is interpreted as a click when drawing
        bool isSyntheticClick = item && evt->IsActivate() && evt->HasPosition()
                                && evt->Matches( aEvent );

        if( evt->IsCancelInteractive() || ( item && evt->IsAction( &ACTIONS::undo ) ) )
        {
            if( item )
            {
                cleanup();
            }
            else
            {
                m_frame->PopTool( aEvent );
                break;
            }
        }
        else if( evt->IsActivate() && !isSyntheticClick )
        {
            if( item && evt->IsMoveTool() )
            {
                // we're already drawing our own item; ignore the move tool
                evt->SetPassEvent( false );
                continue;
            }

            if( item )
                cleanup();

            if( evt->IsPointEditor() )
            {
                // don't exit (the point editor runs in the background)
            }
            else if( evt->IsMoveTool() )
            {
                // leave ourselves on the stack so we come back after the move
                break;
            }
            else
            {
                m_frame->PopTool( aEvent );
                break;
            }
        }
        else if( evt->IsClick( BUT_LEFT ) && !item )
        {
            m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );

            if( isTextBox )
            {
                SCH_TEXTBOX* textbox = new SCH_TEXTBOX( LAYER_NOTES, 0, m_lastTextboxFillStyle );

                textbox->SetTextSize( VECTOR2I( sch_settings.m_DefaultTextSize,
                                                sch_settings.m_DefaultTextSize ) );

                // Must come after SetTextSize()
                textbox->SetBold( m_lastTextBold );
                textbox->SetItalic( m_lastTextItalic );

                textbox->SetTextAngle( m_lastTextboxAngle );
                textbox->SetHorizJustify( m_lastTextboxHJustify );
                textbox->SetVertJustify( m_lastTextboxVJustify );
                textbox->SetStroke( m_lastTextboxStroke );
                textbox->SetFillColor( m_lastTextboxFillColor );
                textbox->SetParent( schematic );

                item = textbox;
                description = _( "Add Text Box" );
            }
            else
            {
                item = new SCH_SHAPE( type, LAYER_NOTES, 0, m_lastFillStyle );

                item->SetStroke( m_lastStroke );
                item->SetFillColor( m_lastFillColor );
                item->SetParent( schematic );
                description = wxString::Format( _( "Add %s" ), item->GetFriendlyName() );
            }

            item->SetFlags( IS_NEW );
            item->BeginEdit( cursorPos );

            m_view->ClearPreview();
            m_view->AddToPreview( item->Clone() );
        }
        else if( item && ( evt->IsClick( BUT_LEFT )
                        || evt->IsDblClick( BUT_LEFT )
                        || isSyntheticClick
                        || evt->IsAction( &ACTIONS::finishInteractive ) ) )
        {
            if( evt->IsDblClick( BUT_LEFT )
                    || evt->IsAction( &ACTIONS::finishInteractive )
                    || !item->ContinueEdit( cursorPos ) )
            {
                item->EndEdit();
                item->ClearEditFlags();
                item->SetFlags( IS_NEW );

                if( isTextBox )
                {
                    SCH_TEXTBOX*           textbox = static_cast<SCH_TEXTBOX*>( item );
                    DIALOG_TEXT_PROPERTIES dlg( m_frame, textbox );

                    getViewControls()->SetAutoPan( false );
                    getViewControls()->CaptureCursor( false );

                    // QuasiModal required for syntax help and Scintilla auto-complete
                    if( dlg.ShowQuasiModal() != wxID_OK )
                    {
                        cleanup();
                        continue;
                    }

                    m_lastTextBold = textbox->IsBold();
                    m_lastTextItalic = textbox->IsItalic();
                    m_lastTextboxAngle = textbox->GetTextAngle();
                    m_lastTextboxHJustify = textbox->GetHorizJustify();
                    m_lastTextboxVJustify = textbox->GetVertJustify();
                    m_lastTextboxStroke = textbox->GetStroke();
                    m_lastTextboxFillStyle = textbox->GetFillMode();
                    m_lastTextboxFillColor = textbox->GetFillColor();
                }
                else
                {
                    m_lastStroke = item->GetStroke();
                    m_lastFillStyle = item->GetFillMode();
                    m_lastFillColor = item->GetFillColor();
                }

                SCH_COMMIT commit( m_toolMgr );
                commit.Add( item, m_frame->GetScreen() );
                commit.Push( wxString::Format( _( "Draw %s" ), item->GetClass() ) );

                m_selectionTool->AddItemToSel( item );
                item = nullptr;

                m_view->ClearPreview();
                m_toolMgr->PostAction( ACTIONS::activatePointEditor );
            }
        }
        else if( evt->IsAction( &ACTIONS::duplicate )
                 || evt->IsAction( &SCH_ACTIONS::repeatDrawItem ) )
        {
            if( item )
            {
                // This doesn't really make sense; we'll just end up dragging a stack of
                // objects so we ignore the duplicate and just carry on.
                wxBell();
                continue;
            }

            // Exit.  The duplicate will run in its own loop.
            m_frame->PopTool( aEvent );
            break;
        }
        else if( item && ( evt->IsAction( &ACTIONS::refreshPreview ) || evt->IsMotion() ) )
        {
            item->CalcEdit( cursorPos );
            m_view->ClearPreview();
            m_view->AddToPreview( item->Clone() );
            m_frame->SetMsgPanel( item );
        }
        else if( evt->IsDblClick( BUT_LEFT ) && !item )
        {
            m_toolMgr->RunAction( SCH_ACTIONS::properties );
        }
        else if( evt->IsClick( BUT_RIGHT ) )
        {
            // Warp after context menu only if dragging...
            if( !item )
                m_toolMgr->VetoContextMenuMouseWarp();

            m_menu->ShowContextMenu( m_selectionTool->GetSelection() );
        }
        else if( item && evt->IsAction( &ACTIONS::redo ) )
        {
            wxBell();
        }
        else
        {
            evt->SetPassEvent();
        }

        // Enable autopanning and cursor capture only when there is a shape being drawn
        getViewControls()->SetAutoPan( item != nullptr );
        getViewControls()->CaptureCursor( item != nullptr );
    }

    getViewControls()->SetAutoPan( false );
    getViewControls()->CaptureCursor( false );
    m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
    return 0;
}


int SCH_DRAWING_TOOLS::DrawRuleArea( const TOOL_EVENT& aEvent )
{
    if( m_inDrawingTool )
        return 0;

    REENTRANCY_GUARD       guard( &m_inDrawingTool );
    SCOPED_SET_RESET<bool> scopedDrawMode( m_drawingRuleArea, true );

    KIGFX::VIEW_CONTROLS* controls = getViewControls();
    EE_GRID_HELPER        grid( m_toolMgr );
    VECTOR2I              cursorPos;

    RULE_AREA_CREATE_HELPER ruleAreaTool( *getView(), m_frame, m_toolMgr );
    POLYGON_GEOM_MANAGER    polyGeomMgr( ruleAreaTool );
    bool                    started = false;

    // We might be running as the same shape in another co-routine.  Make sure that one
    // gets whacked.
    m_toolMgr->DeactivateTool();

    m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );

    m_frame->PushTool( aEvent );

    auto setCursor =
            [&]()
            {
                m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::PENCIL );
            };

    auto cleanup = [&]()
    {
        polyGeomMgr.Reset();
        started = false;
        getViewControls()->SetAutoPan( false );
        getViewControls()->CaptureCursor( false );
        m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );
    };

    Activate();

    // Must be done after Activate() so that it gets set into the correct context
    getViewControls()->ShowCursor( true );
    //m_controls->ForceCursorPosition( false );

    // Set initial cursor
    setCursor();

    if( aEvent.HasPosition() )
        m_toolMgr->PrimeTool( aEvent.Position() );

    // Main loop: keep receiving events
    while( TOOL_EVENT* evt = Wait() )
    {
        setCursor();

        grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
        grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );

        cursorPos = grid.Align( controls->GetMousePosition(), GRID_HELPER_GRIDS::GRID_CONNECTABLE );
        controls->ForceCursorPosition( true, cursorPos );

        polyGeomMgr.SetLeaderMode( m_frame->eeconfig()->m_Drawing.line_mode == LINE_MODE_FREE
                                           ? POLYGON_GEOM_MANAGER::LEADER_MODE::DIRECT
                                           : POLYGON_GEOM_MANAGER::LEADER_MODE::DEG45 );

        if( evt->IsCancelInteractive() )
        {
            if( started )
            {
                cleanup();
            }
            else
            {
                m_frame->PopTool( aEvent );

                // We've handled the cancel event.  Don't cancel other tools
                evt->SetPassEvent( false );
                break;
            }
        }
        else if( evt->IsActivate() )
        {
            if( started )
                cleanup();

            if( evt->IsPointEditor() )
            {
                // don't exit (the point editor runs in the background)
            }
            else if( evt->IsMoveTool() )
            {
                // leave ourselves on the stack so we come back after the move
                break;
            }
            else
            {
                m_frame->PopTool( aEvent );
                break;
            }
        }
        else if( evt->IsClick( BUT_RIGHT ) )
        {
            if( !started )
                m_toolMgr->VetoContextMenuMouseWarp();

            m_menu->ShowContextMenu( m_selectionTool->GetSelection() );
        }
        // events that lock in nodes
        else if( evt->IsClick( BUT_LEFT ) || evt->IsDblClick( BUT_LEFT )
                 || evt->IsAction( &SCH_ACTIONS::closeOutline ) )
        {
            // Check if it is double click / closing line (so we have to finish the zone)
            const bool endPolygon = evt->IsDblClick( BUT_LEFT )
                                    || evt->IsAction( &SCH_ACTIONS::closeOutline )
                                    || polyGeomMgr.NewPointClosesOutline( cursorPos );

            if( endPolygon )
            {
                polyGeomMgr.SetFinished();
                polyGeomMgr.Reset();

                started = false;
                getViewControls()->SetAutoPan( false );
                getViewControls()->CaptureCursor( false );
            }
            // adding a corner
            else if( polyGeomMgr.AddPoint( cursorPos ) )
            {
                if( !started )
                {
                    started = true;

                    getViewControls()->SetAutoPan( true );
                    getViewControls()->CaptureCursor( true );
                }
            }
        }
        else if( started
                 && ( evt->IsAction( &SCH_ACTIONS::deleteLastPoint )
                      || evt->IsAction( &ACTIONS::doDelete )
                      || evt->IsAction( &ACTIONS::undo ) ) )
        {
            if( std::optional<VECTOR2I> last = polyGeomMgr.DeleteLastCorner() )
            {
                cursorPos = last.value();
                getViewControls()->WarpMouseCursor( cursorPos, true );
                getViewControls()->ForceCursorPosition( true, cursorPos );
                polyGeomMgr.SetCursorPosition( cursorPos );
            }
            else
            {
                cleanup();
            }
        }
        else if( started && ( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) ) )
        {
            polyGeomMgr.SetCursorPosition( cursorPos );
        }
        else
        {
            evt->SetPassEvent();
        }

    } // end while

    getViewControls()->SetAutoPan( false );
    getViewControls()->CaptureCursor( false );
    m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
    return 0;
}


int SCH_DRAWING_TOOLS::DrawTable( const TOOL_EVENT& aEvent )
{
    SCHEMATIC* schematic = getModel<SCHEMATIC>();
    SCH_TABLE* table = nullptr;

    if( m_inDrawingTool )
        return 0;

    REENTRANCY_GUARD guard( &m_inDrawingTool );

    KIGFX::VIEW_CONTROLS* controls = getViewControls();
    EE_GRID_HELPER        grid( m_toolMgr );
    VECTOR2I              cursorPos;

    // We might be running as the same shape in another co-routine.  Make sure that one
    // gets whacked.
    m_toolMgr->DeactivateTool();

    m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );

    m_frame->PushTool( aEvent );

    auto setCursor =
            [&]()
            {
                m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::PENCIL );
            };

    auto cleanup =
            [&] ()
            {
                m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );
                m_view->ClearPreview();
                delete table;
                table = nullptr;
            };

    Activate();

    // Must be done after Activate() so that it gets set into the correct context
    getViewControls()->ShowCursor( true );

    // Set initial cursor
    setCursor();

    if( aEvent.HasPosition() )
        m_toolMgr->PrimeTool( aEvent.Position() );

    // Main loop: keep receiving events
    while( TOOL_EVENT* evt = Wait() )
    {
        setCursor();
        grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
        grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );

        cursorPos = grid.Align( controls->GetMousePosition(), GRID_HELPER_GRIDS::GRID_GRAPHICS );
        controls->ForceCursorPosition( true, cursorPos );

        // The tool hotkey is interpreted as a click when drawing
        bool isSyntheticClick = table
                                    && evt->IsActivate()
                                    && evt->HasPosition()
                                    && evt->Matches( aEvent );

        if( evt->IsCancelInteractive() || ( table && evt->IsAction( &ACTIONS::undo ) ) )
        {
            if( table )
            {
                cleanup();
            }
            else
            {
                m_frame->PopTool( aEvent );
                break;
            }
        }
        else if( evt->IsActivate() && !isSyntheticClick )
        {
            if( table && evt->IsMoveTool() )
            {
                // we're already drawing our own item; ignore the move tool
                evt->SetPassEvent( false );
                continue;
            }

            if( table )
                cleanup();

            if( evt->IsPointEditor() )
            {
                // don't exit (the point editor runs in the background)
            }
            else if( evt->IsMoveTool() )
            {
                // leave ourselves on the stack so we come back after the move
                break;
            }
            else
            {
                m_frame->PopTool( aEvent );
                break;
            }
        }
        else if( evt->IsClick( BUT_LEFT ) && !table )
        {
            m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );

            table = new SCH_TABLE( 0 );
            table->SetColCount( 1 );

            SCH_TABLECELL* tableCell = new SCH_TABLECELL();
            int defaultTextSize = schematic->Settings().m_DefaultTextSize;

            tableCell->SetTextSize( VECTOR2I( defaultTextSize, defaultTextSize ) );
            table->AddCell( tableCell );

            table->SetParent( schematic );
            table->SetFlags( IS_NEW );
            table->SetPosition( cursorPos );

            m_view->ClearPreview();
            m_view->AddToPreview( table->Clone() );
        }
        else if( table && (   evt->IsClick( BUT_LEFT )
                           || evt->IsDblClick( BUT_LEFT )
                           || isSyntheticClick
                           || evt->IsAction( &SCH_ACTIONS::finishInteractive ) ) )
        {
            table->ClearEditFlags();
            table->SetFlags( IS_NEW );
            table->Normalize();

            DIALOG_TABLE_PROPERTIES dlg( m_frame, table );

            // QuasiModal required for Scintilla auto-complete
            if( dlg.ShowQuasiModal() == wxID_OK )
            {
                SCH_COMMIT commit( m_toolMgr );
                commit.Add( table, m_frame->GetScreen() );
                commit.Push( _( "Draw Table" ) );

                m_selectionTool->AddItemToSel( table );
                m_toolMgr->PostAction( ACTIONS::activatePointEditor );
            }
            else
            {
                delete table;
            }

            table = nullptr;
            m_view->ClearPreview();
        }
        else if( table && ( evt->IsAction( &ACTIONS::refreshPreview ) || evt->IsMotion() ) )
        {
            VECTOR2I gridSize = grid.GetGridSize( grid.GetItemGrid( table ) );
            int      fontSize = schematic->Settings().m_DefaultTextSize;
            VECTOR2I origin( table->GetPosition() );
            VECTOR2I requestedSize( cursorPos - origin );

            int colCount = std::max( 1, requestedSize.x / ( fontSize * 15 ) );
            int rowCount = std::max( 1, requestedSize.y / ( fontSize * 2  ) );

            VECTOR2I cellSize( std::max( gridSize.x * 5, requestedSize.x / colCount ),
                               std::max( gridSize.y * 2, requestedSize.y / rowCount ) );

            cellSize.x = KiROUND( (double) cellSize.x / gridSize.x ) * gridSize.x;
            cellSize.y = KiROUND( (double) cellSize.y / gridSize.y ) * gridSize.y;

            table->ClearCells();
            table->SetColCount( colCount );

            for( int col = 0; col < colCount; ++col )
                table->SetColWidth( col, cellSize.x );

            for( int row = 0; row < rowCount; ++row )
            {
                table->SetRowHeight( row, cellSize.y );

                for( int col = 0; col < colCount; ++col )
                {
                    SCH_TABLECELL* cell = new SCH_TABLECELL();
                    int defaultTextSize = schematic->Settings().m_DefaultTextSize;

                    cell->SetTextSize( VECTOR2I( defaultTextSize, defaultTextSize ) );
                    cell->SetPosition( origin + VECTOR2I( col * cellSize.x, row * cellSize.y ) );
                    cell->SetEnd( cell->GetPosition() + cellSize );
                    table->AddCell( cell );
                }
            }

            m_view->ClearPreview();
            m_view->AddToPreview( table->Clone() );
            m_frame->SetMsgPanel( table );
        }
        else if( evt->IsDblClick( BUT_LEFT ) && !table )
        {
            m_toolMgr->RunAction( SCH_ACTIONS::properties );
        }
        else if( evt->IsClick( BUT_RIGHT ) )
        {
            // Warp after context menu only if dragging...
            if( !table )
                m_toolMgr->VetoContextMenuMouseWarp();

            m_menu->ShowContextMenu( m_selectionTool->GetSelection() );
        }
        else if( table && evt->IsAction( &ACTIONS::redo ) )
        {
            wxBell();
        }
        else
        {
            evt->SetPassEvent();
        }

        // Enable autopanning and cursor capture only when there is a shape being drawn
        getViewControls()->SetAutoPan( table != nullptr );
        getViewControls()->CaptureCursor( table != nullptr );
    }

    getViewControls()->SetAutoPan( false );
    getViewControls()->CaptureCursor( false );
    m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
    return 0;
}


int SCH_DRAWING_TOOLS::DrawSheet( const TOOL_EVENT& aEvent )
{
    bool isDrawSheetCopy = aEvent.IsAction( &SCH_ACTIONS::drawSheetFromFile );
    bool isDrawSheetFromDesignBlock = aEvent.IsAction( &SCH_ACTIONS::drawSheetFromDesignBlock );

    DESIGN_BLOCK* designBlock = nullptr;
    SCH_SHEET*    sheet = nullptr;
    wxString      filename;

    if( isDrawSheetCopy )
    {
        wxString* ptr = aEvent.Parameter<wxString*>();
        wxCHECK( ptr, 0 );

        // We own the string if we're importing a sheet
        filename = *ptr;
        delete ptr;
    }
    else if( isDrawSheetFromDesignBlock )
    {
        designBlock = aEvent.Parameter<DESIGN_BLOCK*>();
        wxCHECK( designBlock, 0 );
        filename = designBlock->GetSchematicFile();
    }

    if( ( isDrawSheetCopy || isDrawSheetFromDesignBlock ) && !wxFileExists( filename ) )
    {
        wxMessageBox( wxString::Format( _( "File '%s' does not exist." ), filename ) );
        return 0;
    }

    if( m_inDrawingTool )
        return 0;

    REENTRANCY_GUARD guard( &m_inDrawingTool );

    EESCHEMA_SETTINGS*    cfg = m_frame->eeconfig();
    SCHEMATIC_SETTINGS&   schSettings = m_frame->Schematic().Settings();
    KIGFX::VIEW_CONTROLS* controls = getViewControls();
    EE_GRID_HELPER        grid( m_toolMgr );
    VECTOR2I              cursorPos;

    m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );

    m_frame->PushTool( aEvent );

    auto setCursor =
            [&]()
            {
                m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::PENCIL );
            };

    auto cleanup =
            [&] ()
            {
                m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );
                m_view->ClearPreview();
                delete sheet;
                sheet = nullptr;
            };

    Activate();

    // Must be done after Activate() so that it gets set into the correct context
    getViewControls()->ShowCursor( true );

    // Set initial cursor
    setCursor();

    if( aEvent.HasPosition() && !( isDrawSheetCopy || isDrawSheetFromDesignBlock ) )
        m_toolMgr->PrimeTool( aEvent.Position() );

    // Main loop: keep receiving events
    while( TOOL_EVENT* evt = Wait() )
    {
        setCursor();
        grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
        grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );

        cursorPos = grid.Align( controls->GetMousePosition(), GRID_HELPER_GRIDS::GRID_GRAPHICS );
        controls->ForceCursorPosition( true, cursorPos );

        // The tool hotkey is interpreted as a click when drawing
        bool isSyntheticClick = sheet && evt->IsActivate() && evt->HasPosition()
                                && evt->Matches( aEvent );

        if( evt->IsCancelInteractive() || ( sheet && evt->IsAction( &ACTIONS::undo ) ) )
        {
            m_frame->GetInfoBar()->Dismiss();

            if( sheet )
            {
                cleanup();
            }
            else
            {
                m_frame->PopTool( aEvent );
                break;
            }
        }
        else if( evt->IsActivate() && !isSyntheticClick )
        {
            if( sheet && evt->IsMoveTool() )
            {
                // we're already drawing our own item; ignore the move tool
                evt->SetPassEvent( false );
                continue;
            }

            if( sheet )
            {
                m_frame->ShowInfoBarMsg( _( "Press <ESC> to cancel sheet creation." ) );
                evt->SetPassEvent( false );
                continue;
            }

            if( evt->IsPointEditor() )
            {
                // don't exit (the point editor runs in the background)
            }
            else if( evt->IsMoveTool() )
            {
                // leave ourselves on the stack so we come back after the move
                break;
            }
            else
            {
                m_frame->PopTool( aEvent );
                break;
            }
        }
        else if( !sheet && ( evt->IsClick( BUT_LEFT ) || evt->IsDblClick( BUT_LEFT ) ) )
        {
            SCH_SELECTION& selection = m_selectionTool->GetSelection();

            if( selection.Size() == 1
                    && selection.Front()->Type() == SCH_SHEET_T
                    && selection.Front()->GetBoundingBox().Contains( cursorPos ) )
            {
                if( evt->IsClick( BUT_LEFT ) )
                {
                    // sheet already selected
                    continue;
                }
                else if( evt->IsDblClick( BUT_LEFT ) )
                {
                    m_toolMgr->PostAction( SCH_ACTIONS::enterSheet );
                    m_frame->PopTool( aEvent );
                    break;
                }
            }

            m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );

            sheet = new SCH_SHEET( m_frame->GetCurrentSheet().Last(), cursorPos );
            sheet->SetScreen( nullptr );

            if( isDrawSheetCopy )
            {
                wxFileName fn( filename );

                sheet->GetFields()[SHEETNAME].SetText( fn.GetName() );
                sheet->GetFields()[SHEETFILENAME].SetText( fn.GetName() + wxT( "." )
                                                           + FILEEXT::KiCadSchematicFileExtension );
            }
            else if( isDrawSheetFromDesignBlock )
            {
                wxFileName fn( filename );

                sheet->GetFields()[SHEETNAME].SetText( designBlock->GetLibId().GetLibItemName() );
                sheet->GetFields()[SHEETFILENAME].SetText( fn.GetName() + wxT( "." )
                                                           + FILEEXT::KiCadSchematicFileExtension );

                std::vector<SCH_FIELD>& sheetFields = sheet->GetFields();

                // Copy default fields into the sheet
                for( const auto& field : designBlock->GetFields() )
                {
                    SCH_FIELD newField( sheet, sheetFields.size(), field.first );
                    newField.SetText( field.second );
                    newField.SetVisible( false );

                    sheetFields.emplace_back( newField );
                }
            }
            else
            {
                sheet->GetFields()[SHEETNAME].SetText( wxT( "Untitled Sheet" ) );
                sheet->GetFields()[SHEETFILENAME].SetText(
                    wxString::Format( wxT( "untitled.%s" ), FILEEXT::KiCadSchematicFileExtension ) );
            }

            sheet->SetFlags( IS_NEW | IS_MOVING );
            sheet->SetBorderWidth( schIUScale.MilsToIU( cfg->m_Drawing.default_line_thickness ) );
            sheet->SetBorderColor( cfg->m_Drawing.default_sheet_border_color );
            sheet->SetBackgroundColor( cfg->m_Drawing.default_sheet_background_color );
            sizeSheet( sheet, cursorPos );

            SCH_SHEET_LIST hierarchy = m_frame->Schematic().Hierarchy();
            SCH_SHEET_PATH instance = m_frame->GetCurrentSheet();
            instance.push_back( sheet );
            wxString pageNumber;

            // Don't try to be too clever when assigning the next availabe page number.  Just use
            // the number of sheets plus one.
            pageNumber.Printf( wxT( "%d" ), static_cast<int>( hierarchy.size() ) + 1 );
            instance.SetPageNumber( pageNumber );

            m_view->ClearPreview();
            m_view->AddToPreview( sheet->Clone() );
        }
        else if( sheet && ( evt->IsClick( BUT_LEFT )
                         || evt->IsDblClick( BUT_LEFT )
                         || isSyntheticClick
                         || evt->IsAction( &ACTIONS::finishInteractive ) ) )
        {
            getViewControls()->SetAutoPan( false );
            getViewControls()->CaptureCursor( false );

            if( m_frame->EditSheetProperties( static_cast<SCH_SHEET*>( sheet ),
                                              &m_frame->GetCurrentSheet(), nullptr, nullptr,
                                              nullptr, &filename ) )
            {
                m_view->ClearPreview();

                sheet->AutoplaceFields( m_frame->GetScreen(), AUTOPLACE_AUTO );

                // Use the commit we were provided or make our own
                SCH_COMMIT  tempCommit = SCH_COMMIT( m_toolMgr );
                SCH_COMMIT& c = evt->Commit() ? *( (SCH_COMMIT*) evt->Commit() ) : tempCommit;

                // We need to manually add the sheet to the screen otherwise annotation will not be able to find
                // the sheet and its symbols to annotate.
                m_frame->AddToScreen( sheet );
                c.Added( sheet, m_frame->GetScreen() );

                // This convoluted logic means we always annotate unless we are drawing a copy/design block
                // and the user has explicitly requested we keep the annotations via checkbox
                EESCHEMA_SETTINGS::PANEL_ANNOTATE& annotate = cfg->m_AnnotatePanel;

                if( annotate.automatic
                    && !( ( isDrawSheetCopy || isDrawSheetFromDesignBlock )
                          && cfg->m_DesignBlockChooserPanel.keep_annotations ) )
                {
                    // Annotation will remove this from selection, but we add it back later
                    m_selectionTool->AddItemToSel( sheet );

                    NULL_REPORTER reporter;
                    m_frame->AnnotateSymbols(
                            &c, ANNOTATE_SELECTION, (ANNOTATE_ORDER_T) annotate.sort_order,
                            (ANNOTATE_ALGO_T) annotate.method, true /* recursive */,
                            schSettings.m_AnnotateStartNum, true, false, reporter );
                }

                c.Push( isDrawSheetCopy ? "Import Sheet Copy" : "Draw Sheet" );

                m_selectionTool->AddItemToSel( sheet );
            }
            else
            {
                m_view->ClearPreview();
                delete sheet;
            }

            sheet = nullptr;
        }
        else if( evt->IsAction( &ACTIONS::duplicate )
                 || evt->IsAction( &SCH_ACTIONS::repeatDrawItem ) )
        {
            if( sheet )
            {
                // This doesn't really make sense; we'll just end up dragging a stack of
                // objects so we ignore the duplicate and just carry on.
                wxBell();
                continue;
            }

            // Exit.  The duplicate will run in its own loop.
            m_frame->PopTool( aEvent );
            break;
        }
        else if( sheet && ( evt->IsAction( &ACTIONS::refreshPreview ) || evt->IsMotion() ) )
        {
            sizeSheet( sheet, cursorPos );
            m_view->ClearPreview();
            m_view->AddToPreview( sheet->Clone() );
            m_frame->SetMsgPanel( sheet );
        }
        else if( evt->IsClick( BUT_RIGHT ) )
        {
            // Warp after context menu only if dragging...
            if( !sheet )
                m_toolMgr->VetoContextMenuMouseWarp();

            m_menu->ShowContextMenu( m_selectionTool->GetSelection() );
        }
        else if( sheet && evt->IsAction( &ACTIONS::redo ) )
        {
            wxBell();
        }
        else
        {
            evt->SetPassEvent();
        }

        // Enable autopanning and cursor capture only when there is a sheet to be placed
        getViewControls()->SetAutoPan( sheet != nullptr );
        getViewControls()->CaptureCursor( sheet != nullptr );
    }

    getViewControls()->SetAutoPan( false );
    getViewControls()->CaptureCursor( false );
    m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );

    return 0;
}


void SCH_DRAWING_TOOLS::sizeSheet( SCH_SHEET* aSheet, const VECTOR2I& aPos )
{
    VECTOR2I pos = aSheet->GetPosition();
    VECTOR2I size = aPos - pos;

    size.x = std::max( size.x, schIUScale.MilsToIU( MIN_SHEET_WIDTH ) );
    size.y = std::max( size.y, schIUScale.MilsToIU( MIN_SHEET_HEIGHT ) );

    VECTOR2I grid = m_frame->GetNearestGridPosition( pos + size );
    aSheet->Resize( VECTOR2I( grid.x - pos.x, grid.y - pos.y ) );
}


int SCH_DRAWING_TOOLS::doSyncSheetsPins( std::list<SCH_SHEET_PATH> sheetPaths )
{
    if( !sheetPaths.size() )
    {
        return 0;
    }

    m_dialogSyncSheetPin = std::make_unique<DIALOG_SYNC_SHEET_PINS>(
            m_frame, std::move( sheetPaths ),
            std::make_shared<SHEET_SYNCHRONIZATION_AGENT>(
                    [&]( EDA_ITEM* aItem, SCH_SHEET_PATH aPath,
                         SHEET_SYNCHRONIZATION_AGENT::MODIFICATION const& aModify )
                    {
                        SCH_COMMIT commit( m_toolMgr );

                        if( auto pin = dynamic_cast<SCH_SHEET_PIN*>( aItem ) )
                        {
                            commit.Modify( pin->GetParent(), aPath.LastScreen() );
                            aModify();
                            commit.Push( _( "Modify sheet pin" ) );
                        }
                        else
                        {
                            commit.Modify( aItem, aPath.LastScreen() );
                            aModify();
                            commit.Push( _( "Modify schematic item" ) );
                        }

                        updateItem( aItem, true );
                        m_frame->OnModify();
                    },
                    [&]( EDA_ITEM* aItem, SCH_SHEET_PATH aPath )
                    {
                        m_frame->GetToolManager()->RunAction<SCH_SHEET_PATH*>(
                                SCH_ACTIONS::changeSheet, &aPath );
                        SCH_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<SCH_SELECTION_TOOL>();
                        selectionTool->UnbrightenItem( aItem );
                        selectionTool->AddItemToSel( aItem, true );
                        m_toolMgr->RunAction( ACTIONS::doDelete );
                    },
                    [&]( SCH_SHEET* aItem, SCH_SHEET_PATH aPath,
                         SHEET_SYNCHRONIZATION_AGENT::SHEET_SYNCHRONIZATION_PLACEMENT aOp,
                         std::set<EDA_ITEM*>                                          aTemplates )
                    {
                        switch( aOp )
                        {
                        case SHEET_SYNCHRONIZATION_AGENT::PLACE_HIERLABEL:
                        {
                            SCH_SHEET* sheet = static_cast<SCH_SHEET*>( aItem );
                            m_dialogSyncSheetPin->Hide();
                            m_dialogSyncSheetPin->PreparePlacementTemplate(
                                    sheet, DIALOG_SYNC_SHEET_PINS::PlaceItemKind::HIERLABEL,
                                    aTemplates );
                            m_frame->GetToolManager()->RunAction<SCH_SHEET_PATH*>(
                                    SCH_ACTIONS::changeSheet, &aPath );
                            m_toolMgr->RunAction( SCH_ACTIONS::placeHierLabel );
                            break;
                        }
                        case SHEET_SYNCHRONIZATION_AGENT::PLACE_SHEET_PIN:
                        {
                            SCH_SHEET* sheet = static_cast<SCH_SHEET*>( aItem );
                            m_dialogSyncSheetPin->Hide();
                            m_dialogSyncSheetPin->PreparePlacementTemplate(
                                    sheet, DIALOG_SYNC_SHEET_PINS::PlaceItemKind::SHEET_PIN,
                                    aTemplates );
                            m_frame->GetToolManager()->RunAction<SCH_SHEET_PATH*>(
                                    SCH_ACTIONS::changeSheet, &aPath );
                            m_toolMgr->GetTool<SCH_SELECTION_TOOL>()->SyncSelection( {}, nullptr,
                                                                                     { sheet } );
                            m_toolMgr->RunAction( SCH_ACTIONS::placeSheetPin );
                            break;
                        }
                        }
                    },
                    m_toolMgr, m_frame ) );
    m_dialogSyncSheetPin->Show( true );
    return 0;
}


int SCH_DRAWING_TOOLS::SyncSheetsPins( const TOOL_EVENT& aEvent )
{
    SCH_SHEET* sheet = dynamic_cast<SCH_SHEET*>( m_selectionTool->GetSelection().Front() );

    if( !sheet )
    {
        VECTOR2I cursorPos =  getViewControls()->GetMousePosition();

        if( EDA_ITEM* i = nullptr; static_cast<void>(m_selectionTool->SelectPoint( cursorPos, { SCH_SHEET_T }, &i ) ) , i != nullptr )
        {
            sheet = dynamic_cast<SCH_SHEET*>( i );
        }
    }

    if ( sheet )
    {
        SCH_SHEET_PATH current = m_frame->GetCurrentSheet();
        current.push_back( sheet );
        return doSyncSheetsPins( { current } );
    }

    return 0;
}


int SCH_DRAWING_TOOLS::SyncAllSheetsPins( const TOOL_EVENT& aEvent )
{
    static const std::function<void( std::list<SCH_SHEET_PATH>&, SCH_SCREEN*,
                                     std::set<SCH_SCREEN*>&, SCH_SHEET_PATH const& )>
            getSheetChildren = []( std::list<SCH_SHEET_PATH>& aPaths, SCH_SCREEN* aScene,
                                   std::set<SCH_SCREEN*>& aVisited, SCH_SHEET_PATH const& aCurPath )
    {
        if( ! aScene || aVisited.find(aScene) != aVisited.end() )
            return ;

        std::vector<SCH_ITEM*> sheetChildren;
        aScene->GetSheets( &sheetChildren );
        aVisited.insert( aScene );

        for( SCH_ITEM* child : sheetChildren )
        {
            SCH_SHEET_PATH cp = aCurPath;
            SCH_SHEET* sheet = static_cast<SCH_SHEET*>( child );
            cp.push_back( sheet );
            aPaths.push_back( cp );
            getSheetChildren( aPaths, sheet->GetScreen(), aVisited, cp );
        }
    };

    std::list<SCH_SHEET_PATH> sheetPaths;
    std::set<SCH_SCREEN*> visited;
    SCH_SHEET_PATH current;
    current.push_back( &m_frame->Schematic().Root() );
    getSheetChildren( sheetPaths, m_frame->Schematic().Root().GetScreen(), visited, current );

    if( sheetPaths.size() == 0 )
    {
        m_frame->ShowInfoBarMsg( _( "No sub schematic found in the current project" ) );
        return 0;
    }


    return doSyncSheetsPins( std::move( sheetPaths ) );
}

SCH_HIERLABEL* SCH_DRAWING_TOOLS::importHierLabel( SCH_SHEET* aSheet )
{
    if( !aSheet->GetScreen() )
        return nullptr;

    std::vector<SCH_HIERLABEL*> labels;
    for( EDA_ITEM* item : aSheet->GetScreen()->Items().OfType( SCH_HIER_LABEL_T ) )
    {
        SCH_HIERLABEL* label = static_cast<SCH_HIERLABEL*>( item );
        labels.push_back( label );
    }

    std::sort( labels.begin(), labels.end(),
               []( const SCH_HIERLABEL* label1, const SCH_HIERLABEL* label2 )
               {
                   return StrNumCmp( label1->GetText(), label2->GetText(), true ) < 0;
               } );

    for( SCH_HIERLABEL* label : labels )
    {
        if( !aSheet->HasPin( label->GetText() ) )
            return label;
    }

    return nullptr;
}


void SCH_DRAWING_TOOLS::setTransitions()
{
    // clang-format off
    Go( &SCH_DRAWING_TOOLS::PlaceSymbol,         SCH_ACTIONS::placeSymbol.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::PlaceSymbol,         SCH_ACTIONS::placePower.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::PlaceNextSymbolUnit, SCH_ACTIONS::placeNextSymbolUnit.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::SingleClickPlace,    SCH_ACTIONS::placeNoConnect.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::SingleClickPlace,    SCH_ACTIONS::placeJunction.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::SingleClickPlace,    SCH_ACTIONS::placeBusWireEntry.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::TwoClickPlace,       SCH_ACTIONS::placeLabel.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::TwoClickPlace,       SCH_ACTIONS::placeClassLabel.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::TwoClickPlace,       SCH_ACTIONS::placeHierLabel.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::TwoClickPlace,       SCH_ACTIONS::placeGlobalLabel.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::DrawSheet,           SCH_ACTIONS::drawSheet.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::DrawSheet,           SCH_ACTIONS::drawSheetFromFile.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::DrawSheet,           SCH_ACTIONS::drawSheetFromDesignBlock.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::TwoClickPlace,       SCH_ACTIONS::placeSheetPin.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::ImportSheet,         SCH_ACTIONS::placeDesignBlock.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::ImportSheet,         SCH_ACTIONS::importSheet.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::TwoClickPlace,       SCH_ACTIONS::placeSchematicText.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::DrawShape,           SCH_ACTIONS::drawRectangle.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::DrawShape,           SCH_ACTIONS::drawCircle.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::DrawShape,           SCH_ACTIONS::drawArc.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::DrawShape,           SCH_ACTIONS::drawBezier.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::DrawShape,           SCH_ACTIONS::drawTextBox.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::DrawRuleArea,        SCH_ACTIONS::drawRuleArea.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::DrawTable,           SCH_ACTIONS::drawTable.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::PlaceImage,          SCH_ACTIONS::placeImage.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::ImportGraphics,      SCH_ACTIONS::importGraphics.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::SyncSheetsPins,      SCH_ACTIONS::syncSheetPins.MakeEvent() );
    Go( &SCH_DRAWING_TOOLS::SyncAllSheetsPins,   SCH_ACTIONS::syncAllSheetsPins.MakeEvent() );
    // clang-format on
}
