/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2015 Jean-Pierre Charras, jp.charras at wanadoo.fr
 * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
 * Copyright (C) 2012 Wayne Stambaugh <stambaughw@verizon.net>
 * Copyright (C) 1992-2017 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
 */

/**
 * @file zones_by_polygon.cpp
 */

#include <fctsys.h>
#include <kiface_i.h>
#include <class_drawpanel.h>
#include <confirm.h>
#include <pcb_edit_frame.h>
#include <board_commit.h>
#include <view/view.h>

#include <class_board.h>
#include <class_zone.h>

#include <pcbnew.h>
#include <zones.h>
#include <pcbnew_id.h>
#include <protos.h>
#include <zones_functions_for_undo_redo.h>
#include <drc.h>
#include <connectivity_data.h>

#include <widgets/progress_reporter.h>

#include <zone_filler.h>

// Outline creation:
static void Abort_Zone_Create_Outline( EDA_DRAW_PANEL* Panel, wxDC* DC );
static void Show_New_Edge_While_Move_Mouse( EDA_DRAW_PANEL* aPanel, wxDC* aDC,
                                            const wxPoint& aPosition, bool aErase );

// Corner moving
static void Abort_Zone_Move_Corner_Or_Outlines( EDA_DRAW_PANEL* Panel, wxDC* DC );
static void Show_Zone_Corner_Or_Outline_While_Move_Mouse( EDA_DRAW_PANEL* aPanel,
                                                          wxDC*           aDC,
                                                          const wxPoint&  aPosition,
                                                          bool            aErase );

// Local variables
static wxPoint         s_CornerInitialPosition;     // Used to abort a move corner command
static bool            s_CornerIsNew;               // Used to abort a move corner command (if it is a new corner, it must be deleted)
static bool            s_AddCutoutToCurrentZone;    // if true, the next outline will be added to s_CurrentZone
static ZONE_CONTAINER* s_CurrentZone;               // if != NULL, these ZONE_CONTAINER params will be used for the next zone
static wxPoint         s_CursorLastPosition;        // in move zone outline, last cursor position. Used to calculate the move vector
static PICKED_ITEMS_LIST s_PickedList;              // a picked list to save zones for undo/redo command
static PICKED_ITEMS_LIST s_AuxiliaryList;           // a picked list to store zones that are deleted or added when combined


void PCB_EDIT_FRAME::Add_Similar_Zone( wxDC* DC, ZONE_CONTAINER* aZone )
{
    if( !aZone )
        return;

    s_AddCutoutToCurrentZone = false;
    s_CurrentZone = aZone;

    // set zone settings to the current zone
    ZONE_SETTINGS  zoneInfo = GetZoneSettings();
    zoneInfo << *aZone;
    SetZoneSettings( zoneInfo );

    // Use the general event handler to set others params (like toolbar)
    wxCommandEvent evt;
    evt.SetId( aZone->GetIsKeepout() ? ID_PCB_KEEPOUT_AREA_BUTT : ID_PCB_ZONES_BUTT );
    OnSelectTool( evt );
}


void PCB_EDIT_FRAME::Add_Zone_Cutout( wxDC* DC, ZONE_CONTAINER* aZone )
{
    if( !aZone )
        return;

    s_AddCutoutToCurrentZone = true;
    s_CurrentZone = aZone;

    // set zones setup to the current zone
    ZONE_SETTINGS zoneInfo = GetZoneSettings();
    zoneInfo << *aZone;
    SetZoneSettings( zoneInfo );

    // Use the general event handle to set others params (like toolbar)
    wxCommandEvent evt;
    evt.SetId( aZone->GetIsKeepout() ? ID_PCB_KEEPOUT_AREA_BUTT : ID_PCB_ZONES_BUTT );
    OnSelectTool( evt );
}


void PCB_EDIT_FRAME::duplicateZone( wxDC* aDC, ZONE_CONTAINER* aZone )
{
    ZONE_CONTAINER* newZone = new ZONE_CONTAINER( *aZone );
    newZone->UnFill();
    ZONE_SETTINGS zoneSettings;
    zoneSettings << *aZone;

    bool success;

    if( aZone->GetIsKeepout() )
        success = InvokeKeepoutAreaEditor( this, &zoneSettings );
    else if( aZone->IsOnCopperLayer() )
        success = InvokeCopperZonesEditor( this, &zoneSettings );
    else
        success = InvokeNonCopperZonesEditor( this, aZone, &zoneSettings );

    // If the new zone is on the same layer as the the initial zone we'll end up combining
    // them which will result in a no-op.  Might as well exit here.
    if( success )
    {
        if( aZone->GetIsKeepout() && ( aZone->GetLayerSet() == zoneSettings.m_Layers ) )
        {
            DisplayErrorMessage(
                        this, _( "The duplicated zone cannot be on the same layers as the original zone." ) );
            success = false;
        }
        else if( !aZone->GetIsKeepout() && ( aZone->GetLayer() == zoneSettings.m_CurrentZone_Layer ) )
        {
            DisplayErrorMessage(
                    this, _(  "The duplicated zone cannot be on the same layer as the original zone." ) );
            success = false;
        }
    }

    if( success )
    {
        zoneSettings.ExportSetting( *newZone );
        newZone->Hatch();

        s_AuxiliaryList.ClearListAndDeleteItems();
        s_PickedList.ClearListAndDeleteItems();
        SaveCopyOfZones( s_PickedList, GetBoard(), newZone->GetNetCode(), newZone->GetLayer() );
        GetBoard()->Add( newZone );

        ITEM_PICKER picker( newZone, UR_NEW );
        s_PickedList.PushItem( picker );

        GetScreen()->SetCurItem( NULL );       // This outline may be deleted when merging outlines

        // Combine zones if possible
        GetBoard()->OnAreaPolygonModified( &s_AuxiliaryList, newZone );

        // Redraw zones
        GetBoard()->RedrawAreasOutlines( m_canvas, aDC, GR_OR, newZone->GetLayer() );
        GetBoard()->RedrawFilledAreas( m_canvas, aDC, GR_OR, newZone->GetLayer() );

        DRC drc( this );

        if( GetBoard()->GetAreaIndex( newZone ) >= 0
           && drc.TestZoneToZoneOutline( newZone, true ) )
        {
            DisplayInfoMessage( this, _( "Warning: The new zone fails DRC" ) );
        }

        UpdateCopyOfZonesList( s_PickedList, s_AuxiliaryList, GetBoard() );
        SaveCopyInUndoList( s_PickedList, UR_UNSPECIFIED );
        s_PickedList.ClearItemsList();

        OnModify();
    }
    else
        delete newZone;
}


int PCB_EDIT_FRAME::Delete_LastCreatedCorner( wxDC* DC )
{
    ZONE_CONTAINER* zone = GetBoard()->m_CurrentZoneContour;

    if( !zone )
        return 0;

    if( !zone->GetNumCorners() )
        return 0;

    zone->DrawWhileCreateOutline( m_canvas, DC, GR_XOR );

    if( zone->GetNumCorners() > 2 )
    {
        zone->Outline()->RemoveVertex( zone->GetNumCorners() - 1 );

        if( m_canvas->IsMouseCaptured() )
            m_canvas->CallMouseCapture( DC, wxDefaultPosition, false );
    }
    else
    {
        m_canvas->SetMouseCapture( NULL, NULL );
        SetCurItem( NULL );
        zone->RemoveAllContours();
        zone->ClearFlags();
    }

    return zone->GetNumCorners();
}


/**
 * Function Abort_Zone_Create_Outline
 * cancels the Begin_Zone command if at least one EDGE_ZONE was created.
 */
static void Abort_Zone_Create_Outline( EDA_DRAW_PANEL* Panel, wxDC* DC )
{
    PCB_EDIT_FRAME* pcbframe = (PCB_EDIT_FRAME*) Panel->GetParent();
    ZONE_CONTAINER* zone = pcbframe->GetBoard()->m_CurrentZoneContour;

    if( zone )
    {
        zone->DrawWhileCreateOutline( Panel, DC, GR_XOR );
        zone->RemoveAllContours();
        if( zone->IsNew() )
        {
            delete zone;
            pcbframe->GetBoard()->m_CurrentZoneContour = NULL;
        }
        else
            zone->ClearFlags();
    }

    pcbframe->SetCurItem( NULL );
    s_AddCutoutToCurrentZone = false;
    s_CurrentZone = NULL;
    Panel->SetMouseCapture( NULL, NULL );
}


void PCB_EDIT_FRAME::Start_Move_Zone_Corner( wxDC* DC, ZONE_CONTAINER* aZone,
                                             int corner_id, bool IsNewCorner )
{
    if( aZone->IsOnCopperLayer() ) // Show the Net
    {
        if( GetBoard()->IsHighLightNetON() && DC )
        {
            HighLight( DC );  // Remove old highlight selection
        }

        ZONE_SETTINGS zoneInfo = GetZoneSettings();
        zoneInfo.m_NetcodeSelection = aZone->GetNetCode();
        SetZoneSettings( zoneInfo );

        GetBoard()->SetHighLightNet( aZone->GetNetCode() );

        if( DC )
            HighLight( DC );
    }


    // Prepare copy of old zones, for undo/redo.
    // if the corner is new, remove it from list, save and insert it in list
    VECTOR2I corner = aZone->Outline()->Vertex( corner_id );

    if ( IsNewCorner )
        aZone->Outline()->RemoveVertex( corner_id );

    s_AuxiliaryList.ClearListAndDeleteItems();
    s_PickedList.ClearListAndDeleteItems();

    SaveCopyOfZones( s_PickedList, GetBoard(), aZone->GetNetCode(), aZone->GetLayer() );

    if ( IsNewCorner )
        aZone->Outline()->InsertVertex(corner_id-1, corner );

    aZone->SetFlags( IN_EDIT );
    m_canvas->SetMouseCapture( Show_Zone_Corner_Or_Outline_While_Move_Mouse,
                                Abort_Zone_Move_Corner_Or_Outlines );
    s_CornerInitialPosition = static_cast<wxPoint>( aZone->GetCornerPosition( corner_id ) );
    s_CornerIsNew = IsNewCorner;
    s_AddCutoutToCurrentZone = false;
    s_CurrentZone = NULL;
}


void PCB_EDIT_FRAME::Start_Move_Zone_Drag_Outline_Edge( wxDC*           DC,
                                                        ZONE_CONTAINER* aZone,
                                                        int             corner_id )
{
    aZone->SetFlags( IS_DRAGGED );
    aZone->SetSelectedCorner( corner_id );
    m_canvas->SetMouseCapture( Show_Zone_Corner_Or_Outline_While_Move_Mouse,
                                Abort_Zone_Move_Corner_Or_Outlines );
    s_CursorLastPosition     = s_CornerInitialPosition = GetCrossHairPosition();
    s_AddCutoutToCurrentZone = false;
    s_CurrentZone = NULL;

    s_PickedList.ClearListAndDeleteItems();
    s_AuxiliaryList.ClearListAndDeleteItems();
    SaveCopyOfZones( s_PickedList, GetBoard(), aZone->GetNetCode(), aZone->GetLayer() );
}


void PCB_EDIT_FRAME::Start_Move_Zone_Outlines( wxDC* DC, ZONE_CONTAINER* aZone )
{
    // Show the Net
    if( aZone->IsOnCopperLayer() ) // Show the Net
    {
        if( GetBoard()->IsHighLightNetON() )
        {
            HighLight( DC );  // Remove old highlight selection
        }

        ZONE_SETTINGS zoneInfo = GetZoneSettings();
        zoneInfo.m_NetcodeSelection = aZone->GetNetCode();
        SetZoneSettings( zoneInfo );

        GetBoard()->SetHighLightNet( aZone->GetNetCode() );
        HighLight( DC );
    }

    s_PickedList.ClearListAndDeleteItems();
    s_AuxiliaryList.ClearListAndDeleteItems();
    SaveCopyOfZones( s_PickedList, GetBoard(), aZone->GetNetCode(), aZone->GetLayer() );

    aZone->SetFlags( IS_MOVED );
    m_canvas->SetMouseCapture( Show_Zone_Corner_Or_Outline_While_Move_Mouse,
                                Abort_Zone_Move_Corner_Or_Outlines );
    s_CursorLastPosition = s_CornerInitialPosition = GetCrossHairPosition();
    s_CornerIsNew = false;
    s_AddCutoutToCurrentZone = false;
    s_CurrentZone = NULL;
}


void PCB_EDIT_FRAME::End_Move_Zone_Corner_Or_Outlines( wxDC* DC, ZONE_CONTAINER* aZone )
{
    aZone->ClearFlags();
    m_canvas->SetMouseCapture( NULL, NULL );

    if( DC )
        aZone->Draw( m_canvas, DC, GR_OR );

    OnModify();
    s_AddCutoutToCurrentZone = false;
    s_CurrentZone = NULL;

    SetCurItem( NULL );       // This outline can be deleted when merging outlines

    // Combine zones if possible
    GetBoard()->OnAreaPolygonModified( &s_AuxiliaryList, aZone );
    m_canvas->Refresh();

    int ii = GetBoard()->GetAreaIndex( aZone );     // test if aZone exists

    if( ii < 0 )
        aZone = NULL;                          // was removed by combining zones

    UpdateCopyOfZonesList( s_PickedList, s_AuxiliaryList, GetBoard() );
    SaveCopyInUndoList(s_PickedList, UR_UNSPECIFIED);
    s_PickedList.ClearItemsList(); // s_ItemsListPicker is no more owner of picked items

    DRC drc( this );
    int error_count = drc.TestZoneToZoneOutline( aZone, true );

    if( error_count )
    {
        DisplayErrorMessage( this, _( "Area: DRC outline error" ) );
    }
}


void PCB_EDIT_FRAME::Remove_Zone_Corner( wxDC* DC, ZONE_CONTAINER* aZone )
{
    OnModify();

    if( aZone->Outline()->TotalVertices() <= 3 )
    {
        m_canvas->RefreshDrawingRect( aZone->GetBoundingBox() );

        if( DC )
        {  // Remove the full zone because this is no more an area
            aZone->UnFill();
            aZone->DrawFilledArea( m_canvas, DC, GR_XOR );
        }

        GetBoard()->Delete( aZone );
        return;
    }

    PCB_LAYER_ID layer = aZone->GetLayer();

    if( DC )
    {
        GetBoard()->RedrawAreasOutlines( m_canvas, DC, GR_XOR, layer );
        GetBoard()->RedrawFilledAreas( m_canvas, DC, GR_XOR, layer );
    }

    s_AuxiliaryList.ClearListAndDeleteItems();
    s_PickedList. ClearListAndDeleteItems();
    SaveCopyOfZones( s_PickedList, GetBoard(), aZone->GetNetCode(), aZone->GetLayer() );
    aZone->Outline()->RemoveVertex( aZone->GetSelectedCorner() );

    // modify zones outlines according to the new aZone shape
    GetBoard()->OnAreaPolygonModified( &s_AuxiliaryList, aZone );

    if( DC )
    {
        GetBoard()->RedrawAreasOutlines( m_canvas, DC, GR_OR, layer );
        GetBoard()->RedrawFilledAreas( m_canvas, DC, GR_OR, layer );
    }

    UpdateCopyOfZonesList( s_PickedList, s_AuxiliaryList, GetBoard() );
    SaveCopyInUndoList(s_PickedList, UR_UNSPECIFIED);
    s_PickedList.ClearItemsList(); // s_ItemsListPicker is no more owner of picked items

    int ii = GetBoard()->GetAreaIndex( aZone );     // test if aZone exists

    if( ii < 0 )
        aZone = NULL;   // aZone does not exist anymore, after combining zones

    DRC drc( this );
    int error_count = drc.TestZoneToZoneOutline( aZone, true );

    if( error_count )
    {
        DisplayErrorMessage( this, _( "Area: DRC outline error" ) );
    }
}


/**
 * Function Abort_Zone_Move_Corner_Or_Outlines
 * cancels the Begin_Zone state if at least one EDGE_ZONE has been created.
 */
void Abort_Zone_Move_Corner_Or_Outlines( EDA_DRAW_PANEL* Panel, wxDC* DC )
{
    PCB_EDIT_FRAME* pcbframe = (PCB_EDIT_FRAME*) Panel->GetParent();
    ZONE_CONTAINER* zone     = (ZONE_CONTAINER*) pcbframe->GetCurItem();

    if( zone->IsMoving() )
    {
        wxPoint offset;
        offset = s_CornerInitialPosition - s_CursorLastPosition;
        zone->Move( offset );
    }
    else if( zone->IsDragging() )
    {
        wxPoint offset = s_CornerInitialPosition - s_CursorLastPosition;
        int selection = zone->GetSelectedCorner();
        zone->MoveEdge( offset, selection );
    }
    else
    {
        if( s_CornerIsNew )
        {
            zone->Outline()->RemoveVertex( zone->GetSelectedCorner() );
        }
        else
        {
            wxPoint pos = s_CornerInitialPosition;
            zone->Outline()->Vertex( zone->GetSelectedCorner() ) = pos;
        }
    }

    Panel->SetMouseCapture( NULL, NULL );
    s_AuxiliaryList.ClearListAndDeleteItems();
    s_PickedList. ClearListAndDeleteItems();
    Panel->Refresh();

    pcbframe->SetCurItem( NULL );
    zone->ClearFlags();
    s_AddCutoutToCurrentZone = false;
    s_CurrentZone = NULL;
}


/// Redraws the zone outline when moving a corner according to the cursor position
void Show_Zone_Corner_Or_Outline_While_Move_Mouse( EDA_DRAW_PANEL* aPanel, wxDC* aDC,
                                                   const wxPoint& aPosition, bool aErase )
{
    PCB_EDIT_FRAME* pcbframe = (PCB_EDIT_FRAME*) aPanel->GetParent();
    ZONE_CONTAINER* zone = (ZONE_CONTAINER*) pcbframe->GetCurItem();

    if( aErase )    // Undraw edge in old position
    {
        zone->Draw( aPanel, aDC, GR_XOR );
    }

    wxPoint pos = pcbframe->GetCrossHairPosition();

    if( zone->IsMoving() )
    {
        wxPoint offset;
        offset = pos - s_CursorLastPosition;
        zone->Move( offset );
        s_CursorLastPosition = pos;
    }
    else if( zone->IsDragging() )
    {
        wxPoint offset = pos - s_CursorLastPosition;
        int selection = zone->GetSelectedCorner();
        zone->MoveEdge( offset, selection );
        s_CursorLastPosition = pos;
    }
    else
    {
        zone->Outline()->Vertex( zone->GetSelectedCorner() ) = pos;
    }

    zone->Draw( aPanel, aDC, GR_XOR );
}



int PCB_EDIT_FRAME::Begin_Zone( wxDC* DC )
{
    ZONE_SETTINGS zoneInfo = GetZoneSettings();

    // verify if s_CurrentZone exists (could be deleted since last selection) :
    int ii;
    for( ii = 0; ii < GetBoard()->GetAreaCount(); ii++ )
    {
        if( s_CurrentZone == GetBoard()->GetArea( ii ) )
            break;
    }

    if( ii >= GetBoard()->GetAreaCount() ) // Not found: could be deleted since last selection
    {
        s_AddCutoutToCurrentZone = false;
        s_CurrentZone = NULL;
    }

    ZONE_CONTAINER* zone = GetBoard()->m_CurrentZoneContour;

    // Verify if a new zone is allowed on this layer:
    if( zone == NULL  )
    {
        if( GetToolId() == ID_PCB_KEEPOUT_AREA_BUTT && !IsCopperLayer( GetActiveLayer() ) )
        {
            DisplayErrorMessage( this,
                          _( "Error: a keepout area is allowed only on copper layers" ) );
            return 0;
        }
    }

    // If no zone contour in progress, a new zone is being created,
    if( zone == NULL )
    {
        zone = GetBoard()->m_CurrentZoneContour = new ZONE_CONTAINER( GetBoard() );
        zone->SetFlags( IS_NEW );
        zone->SetTimeStamp( GetNewTimeStamp() );
    }

    if( zone->GetNumCorners() == 0 )    // Start a new contour: init zone params (net, layer ...)
    {
        if( !s_CurrentZone )            // A new outline is created, from scratch
        {
            ZONE_EDIT_T edited;

            // Prompt user for parameters:
            m_canvas->SetIgnoreMouseEvents( true );

            if( IsCopperLayer( GetActiveLayer() ) )
            {
                // Put a zone on a copper layer
                if( GetBoard()->GetHighLightNetCode() > 0 )
                {
                    zoneInfo.m_NetcodeSelection = GetBoard()->GetHighLightNetCode();
                    zone->SetNetCode( zoneInfo.m_NetcodeSelection );
                }

                double tmp = ZONE_THERMAL_RELIEF_GAP_MIL;

                wxConfigBase* cfg = Kiface().KifaceSettings();
                cfg->Read( ZONE_THERMAL_RELIEF_GAP_STRING_KEY, &tmp );
                zoneInfo.m_ThermalReliefGap = KiROUND( tmp * IU_PER_MILS);

                tmp = ZONE_THERMAL_RELIEF_COPPER_WIDTH_MIL;
                cfg->Read( ZONE_THERMAL_RELIEF_COPPER_WIDTH_STRING_KEY, &tmp );
                zoneInfo.m_ThermalReliefCopperBridge = KiROUND( tmp * IU_PER_MILS );

                tmp = ZONE_CLEARANCE_MIL;
                cfg->Read( ZONE_CLEARANCE_WIDTH_STRING_KEY, &tmp );
                zoneInfo.m_ZoneClearance = KiROUND( tmp * IU_PER_MILS );

                tmp = ZONE_THICKNESS_MIL;
                cfg->Read( ZONE_MIN_THICKNESS_WIDTH_STRING_KEY, &tmp );
                zoneInfo.m_ZoneMinThickness = KiROUND( tmp * IU_PER_MILS );

                if( GetToolId() == ID_PCB_KEEPOUT_AREA_BUTT )
                {
                    zoneInfo.SetIsKeepout( true );
                    // Netcode, netname and some other settings are irrelevant,
                    // so ensure they are cleared
                    zone->SetNetCode( NETINFO_LIST::UNCONNECTED );
                    zoneInfo.SetCornerSmoothingType( ZONE_SETTINGS::SMOOTHING_NONE );
                    zoneInfo.SetCornerRadius( 0 );

                    edited = InvokeKeepoutAreaEditor( this, &zoneInfo );
                }
                else
                {
                    zoneInfo.m_CurrentZone_Layer = GetActiveLayer();    // Preselect a layer
                    zoneInfo.SetIsKeepout( false );
                    edited = InvokeCopperZonesEditor( this, &zoneInfo );
                }
            }
            else   // Put a zone on a non copper layer (technical layer)
            {
                zone->SetLayer( GetActiveLayer() );     // Preselect a layer
                zoneInfo.SetIsKeepout( false );
                zoneInfo.m_NetcodeSelection = 0;        // No net for non copper zones
                edited = InvokeNonCopperZonesEditor( this, zone, &zoneInfo );
            }

            m_canvas->MoveCursorToCrossHair();
            m_canvas->SetIgnoreMouseEvents( false );

            if( edited == ZONE_ABORT )
            {
                GetBoard()->m_CurrentZoneContour = NULL;
                delete zone;
                return 0;
            }

            // Switch active layer to the selected zone layer
            SetActiveLayer( zoneInfo.m_CurrentZone_Layer );
            SetZoneSettings( zoneInfo );
            OnModify();
        }
        else
        {
            // Start a new contour: init zone params (net and layer) from an existing
            // zone (add cutout or similar zone)

            zoneInfo.m_CurrentZone_Layer = s_CurrentZone->GetLayer();
            SetActiveLayer( s_CurrentZone->GetLayer() );

            zoneInfo << *s_CurrentZone;

            SetZoneSettings( zoneInfo );
            OnModify();
        }

        // Show the Net for zones on copper layers
        if( IsCopperLayer( zoneInfo.m_CurrentZone_Layer ) &&
            !zoneInfo.GetIsKeepout() )
        {
            if( s_CurrentZone )
            {
                zoneInfo.m_NetcodeSelection = s_CurrentZone->GetNetCode();
                GetBoard()->SetZoneSettings( zoneInfo );
            }

            if( GetBoard()->IsHighLightNetON() )
            {
                HighLight( DC );    // Remove old highlight selection
            }

            GetBoard()->SetHighLightNet( zoneInfo.m_NetcodeSelection );
            HighLight( DC );
        }

        if( !s_AddCutoutToCurrentZone )
            s_CurrentZone = NULL; // the zone is used only once ("add similar zone" command)
    }

    // if first segment
    if( zone->GetNumCorners() == 0 )
    {
        zoneInfo.ExportSetting( *zone );

        // A duplicated corner is needed; null segments are removed when the zone is finished.
        zone->AppendCorner( GetCrossHairPosition(), -1 );
        // Add the duplicate corner:
        zone->AppendCorner( GetCrossHairPosition(), -1, true );

        if( Settings().m_legacyDrcOn && (m_drc->Drc( zone, 0 ) == BAD_DRC) && zone->IsOnCopperLayer() )
        {
            zone->ClearFlags();
            zone->RemoveAllContours();

            // use the form of SetCurItem() which does not write to the msg panel,
            // SCREEN::SetCurItem(), so the DRC error remains on screen.
            // PCB_EDIT_FRAME::SetCurItem() calls DisplayInfo().
            GetScreen()->SetCurItem( NULL );
            DisplayErrorMessage( this,
                          _( "DRC error: this start point is inside or too close another area" ) );
            return 0;
        }

        SetCurItem( zone );
        m_canvas->SetMouseCapture( Show_New_Edge_While_Move_Mouse, Abort_Zone_Create_Outline );
    }
    else    // edge in progress:
    {
        ii = zone->GetNumCorners() - 1;

        // edge in progress : the current corner coordinate was set
        // by Show_New_Edge_While_Move_Mouse
        if( zone->GetCornerPosition( ii - 1 ) != zone->GetCornerPosition( ii ) )
        {
            if( !Settings().m_legacyDrcOn || !zone->IsOnCopperLayer() || ( m_drc->Drc( zone, ii - 1 ) == OK_DRC ) )
            {
                // Ok, we can add a new corner
                if( m_canvas->IsMouseCaptured() )
                    m_canvas->CallMouseCapture( DC, wxPoint(0,0), false );

                // It is necessary to allow duplication of the points, as we have to handle the
                // continuous drawing while creating the zone at the same time as we build it. Null
                // segments are removed when the zone is finished, in End_Zone.
                zone->AppendCorner( GetCrossHairPosition(), -1, true );

                SetCurItem( zone );     // calls DisplayInfo().

                if( m_canvas->IsMouseCaptured() )
                    m_canvas->CallMouseCapture( DC, wxPoint(0,0), false );
            }
        }
    }

    return zone->GetNumCorners();
}


bool PCB_EDIT_FRAME::End_Zone( wxDC* DC )
{
    ZONE_CONTAINER* zone = GetBoard()->m_CurrentZoneContour;

    if( !zone )
        return true;

    // Validate the current outline:
    if( zone->GetNumCorners() <= 2 )   // An outline must have 3 corners or more
    {
        Abort_Zone_Create_Outline( m_canvas, DC );
        return true;
    }

    // Remove the last corner if is is at the same location as the prevoius corner
    zone->Outline()->RemoveNullSegments();

    // Validate the current edge:
    int icorner = zone->GetNumCorners() - 1;
    if( zone->IsOnCopperLayer() )
    {
        if( Settings().m_legacyDrcOn && m_drc->Drc( zone, icorner - 1 ) == BAD_DRC )  // we can't validate last edge
            return false;

        if( Settings().m_legacyDrcOn && m_drc->Drc( zone, icorner ) == BAD_DRC )      // we can't validate the closing edge
        {
            DisplayErrorMessage( this,
                          _( "DRC error: closing this area creates a DRC error with another area" ) );
            m_canvas->MoveCursorToCrossHair();
            return false;
        }
    }

    zone->ClearFlags();

    zone->DrawWhileCreateOutline( m_canvas, DC, GR_XOR );

    m_canvas->SetMouseCapture( NULL, NULL );

    // Undraw old drawings, because they can have important changes
    PCB_LAYER_ID layer = zone->GetLayer();
    GetBoard()->RedrawAreasOutlines( m_canvas, DC, GR_XOR, layer );
    GetBoard()->RedrawFilledAreas( m_canvas, DC, GR_XOR, layer );

    // Save initial zones configuration, for undo/redo, before adding new zone
    s_AuxiliaryList.ClearListAndDeleteItems();
    s_PickedList.ClearListAndDeleteItems();
    SaveCopyOfZones(s_PickedList, GetBoard(), zone->GetNetCode(), zone->GetLayer() );

    // Put new zone in list
    if( !s_CurrentZone )
    {
        GetBoard()->Add( zone );

        // Add this zone in picked list, as new item
        ITEM_PICKER picker( zone, UR_NEW );
        s_PickedList.PushItem( picker );
    }
    else    // Append this outline as a cutout to an existing zone
    {
        s_CurrentZone->Outline()->AddHole( zone->Outline()->Outline( 0 ) );

        zone->RemoveAllContours();      // All corners are copied in s_CurrentZone. Free corner list.
        zone = s_CurrentZone;
    }

    s_AddCutoutToCurrentZone = false;
    s_CurrentZone = NULL;
    GetBoard()->m_CurrentZoneContour = NULL;

    GetScreen()->SetCurItem( NULL );       // This outline can be deleted when merging outlines

    // Combine zones if possible :
    GetBoard()->OnAreaPolygonModified( &s_AuxiliaryList, zone );

    // Redraw the real edge zone :
    GetBoard()->RedrawAreasOutlines( m_canvas, DC, GR_OR, layer );
    GetBoard()->RedrawFilledAreas( m_canvas, DC, GR_OR, layer );

    int ii = GetBoard()->GetAreaIndex( zone );   // test if zone exists

    if( ii < 0 )
        zone = NULL;                        // was removed by combining zones

    DRC drc( this );
    int error_count = drc.TestZoneToZoneOutline( zone, true );

    if( error_count )
    {
        DisplayErrorMessage( this, _( "Area: DRC outline error" ) );
    }

    UpdateCopyOfZonesList( s_PickedList, s_AuxiliaryList, GetBoard() );
    SaveCopyInUndoList(s_PickedList, UR_UNSPECIFIED);
    s_PickedList.ClearItemsList(); // s_ItemsListPicker is no more owner of picked items

    OnModify();
    return true;
}


/* Redraws the zone outlines when moving mouse
 */
static void Show_New_Edge_While_Move_Mouse( EDA_DRAW_PANEL* aPanel, wxDC* aDC,
                                            const wxPoint& aPosition, bool aErase )
{
    PCB_EDIT_FRAME* pcbframe = (PCB_EDIT_FRAME*) aPanel->GetParent();
    wxPoint         c_pos    = pcbframe->GetCrossHairPosition();
    ZONE_CONTAINER* zone = pcbframe->GetBoard()->m_CurrentZoneContour;

    if( !zone )
        return;

    int icorner = zone->GetNumCorners() - 1;

    if( icorner < 1 )
        return;     // We must have 2 (or more) corners

    if( aErase )    // Undraw edge in old position
    {
        zone->DrawWhileCreateOutline( aPanel, aDC );
    }

    // Redraw the current edge in its new position
    if( pcbframe->GetZoneSettings().m_Zone_45_Only )
    {
        // calculate the new position as allowed
        wxPoint StartPoint = static_cast<wxPoint>( zone->GetCornerPosition( icorner - 1 ) );
        c_pos = CalculateSegmentEndPoint( c_pos, StartPoint );
    }

    zone->SetCornerPosition( icorner, c_pos );

    zone->DrawWhileCreateOutline( aPanel, aDC );
}

void PCB_EDIT_FRAME::Edit_Zone_Params( wxDC* DC, ZONE_CONTAINER* aZone )
{
    ZONE_EDIT_T     edited;
    ZONE_SETTINGS   zoneInfo = GetZoneSettings();

    BOARD_COMMIT commit( this );
    m_canvas->SetIgnoreMouseEvents( true );

    // Save initial zones configuration, for undo/redo, before adding new zone
    // note the net name and the layer can be changed, so we must save all zones
    s_AuxiliaryList.ClearListAndDeleteItems();
    s_PickedList.ClearListAndDeleteItems();
    SaveCopyOfZones( s_PickedList, GetBoard(), -1, UNDEFINED_LAYER );

    if( aZone->GetIsKeepout() )
    {
        // edit a keepout area on a copper layer
        zoneInfo << *aZone;
        edited = InvokeKeepoutAreaEditor( this, &zoneInfo );
    }
    else if( IsCopperLayer( aZone->GetLayer() ) )
    {
        // edit a zone on a copper layer
        zoneInfo << *aZone;
        edited = InvokeCopperZonesEditor( this, &zoneInfo );
    }
    else
    {
        zoneInfo << *aZone;
        edited = InvokeNonCopperZonesEditor( this, aZone, &zoneInfo );
    }

    m_canvas->MoveCursorToCrossHair();
    m_canvas->SetIgnoreMouseEvents( false );

    if( edited == ZONE_ABORT )
    {
        s_AuxiliaryList.ClearListAndDeleteItems();
        s_PickedList.ClearListAndDeleteItems();
        return;
    }

    SetZoneSettings( zoneInfo );
    OnModify();

    if( edited == ZONE_EXPORT_VALUES )
    {
        UpdateCopyOfZonesList( s_PickedList, s_AuxiliaryList, GetBoard() );
        commit.Stage( s_PickedList );
        commit.Push( _( "Modify zone properties" ) );
        s_PickedList.ClearItemsList(); // s_ItemsListPicker is no more owner of picked items
        return;
    }

    wxBusyCursor dummy;

    // Undraw old zone outlines
    for( int ii = 0; ii < GetBoard()->GetAreaCount(); ii++ )
    {
        ZONE_CONTAINER* edge_zone = GetBoard()->GetArea( ii );
        edge_zone->Draw( m_canvas, DC, GR_XOR );

        if( IsGalCanvasActive() )
        {
            GetGalCanvas()->GetView()->Update( edge_zone );
        }
    }

    zoneInfo.ExportSetting( *aZone );

    NETINFO_ITEM* net = GetBoard()->FindNet( zoneInfo.m_NetcodeSelection );

    if( net )   // net == NULL should not occur
        aZone->SetNetCode( net->GetNet() );

    // Combine zones if possible
    GetBoard()->OnAreaPolygonModified( &s_AuxiliaryList, aZone );

    // Redraw the real new zone outlines
    GetBoard()->RedrawAreasOutlines( m_canvas, DC, GR_OR, UNDEFINED_LAYER );

    UpdateCopyOfZonesList( s_PickedList, s_AuxiliaryList, GetBoard() );

    // refill zones with the new properties applied
    std::vector<ZONE_CONTAINER*> zones_to_refill;

    for( unsigned i = 0; i < s_PickedList.GetCount(); ++i )
    {
        ZONE_CONTAINER* zone = dyn_cast<ZONE_CONTAINER*>( s_PickedList.GetPickedItem( i ) );

        if( zone == nullptr )
        {
            wxASSERT_MSG( false, "Expected a zone after zone properties edit" );
            continue;
        }

        if( zone->IsFilled() )
            zones_to_refill.push_back( zone );
    }

    if( zones_to_refill.size() )
    {
        ZONE_FILLER filler ( GetBoard() );
        wxString title;
        title.Printf( _( "Refill %d Zones" ), (int)zones_to_refill.size() );
        std::unique_ptr<WX_PROGRESS_REPORTER> progressReporter(
                                new WX_PROGRESS_REPORTER( this, title, 3 ) );

        filler.SetProgressReporter( progressReporter.get() );
        filler.Fill( zones_to_refill );
    }

    commit.Stage( s_PickedList );
    commit.Push( _( "Modify zone properties" ) );
    GetBoard()->GetConnectivity()->RecalculateRatsnest();

    s_PickedList.ClearItemsList();  // s_ItemsListPicker is no longer owner of picked items

    if( !IsGalCanvasActive() )
        m_canvas->Refresh();
}


void PCB_EDIT_FRAME::Delete_Zone_Contour( wxDC* DC, ZONE_CONTAINER* aZone )
{
    // Get contour in which the selected corner is
    SHAPE_POLY_SET::VERTEX_INDEX indices;

    // If the selected corner does not exist, abort
    if( !aZone->Outline()->GetRelativeIndices( aZone->GetSelectedCorner(), &indices ) )
        throw( std::out_of_range( "Zone selected corner does not exist" ) );

    EDA_RECT dirty = aZone->GetBoundingBox();

    // For compatibility with old boards: remove old SEGZONE fill segments
    Delete_OldZone_Fill( NULL, aZone->GetTimeStamp() );

    // Remove current filling:
    aZone->UnFill();

    if( indices.m_contour == 0 )    // This is the main outline: remove all
    {
        SaveCopyInUndoList( aZone, UR_DELETED );
        GetBoard()->Remove( aZone );
    }

    else
    {
        SaveCopyInUndoList( aZone, UR_CHANGED );
        aZone->Outline()->RemoveContour( indices.m_contour, indices.m_polygon );
    }

    m_canvas->RefreshDrawingRect( dirty );

    OnModify();
}
