/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2019 Jean-Pierre Charras, jp.charras at wanadoo.fr
 * Copyright (C) 2007 Wayne Stambaugh <stambaughw@gmail.com>
 * Copyright (C) 1992-2019 KiCad Developers, see AUTHORS.txt for contributors.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you may find one here:
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * or you may search the http://www.gnu.org website for the version 2 license,
 * or you may write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

/**
 * @file draw_panel.cpp
 */

#include <fctsys.h>
#include <wx/timer.h>
#include <pgm_base.h>
#include <kiface_i.h>
#include <gr_basic.h>
#include <common.h>
#include <macros.h>
#include <id.h>
#include <class_drawpanel.h>
#include <class_draw_panel_gal.h>
#include <base_screen.h>
#include <draw_frame.h>
#include <view/view_controls.h>
#include <gal/gal_display_options.h>
#include <trace_helpers.h>

#include <kicad_device_context.h>

static const int CURSOR_SIZE = 12; ///< Cursor size in pixels

#define CLIP_BOX_PADDING 2

// Definitions for enabling and disabling debugging features in drawpanel.cpp.
// Please don't forget to turn these off before making any commits to Launchpad.
#define DEBUG_SHOW_CLIP_RECT       0  // Set to 1 to draw clipping rectangle.


// Events used by EDA_DRAW_PANEL
BEGIN_EVENT_TABLE( EDA_DRAW_PANEL, wxScrolledWindow )
    EVT_LEAVE_WINDOW( EDA_DRAW_PANEL::OnMouseLeaving )
    EVT_ENTER_WINDOW( EDA_DRAW_PANEL::OnMouseEntering )
    EVT_MOUSEWHEEL( EDA_DRAW_PANEL::OnMouseWheel )
#if wxCHECK_VERSION( 3, 1, 0 ) || defined( USE_OSX_MAGNIFY_EVENT )
    EVT_MAGNIFY( EDA_DRAW_PANEL::OnMagnify )
#endif
    EVT_MOUSE_EVENTS( EDA_DRAW_PANEL::OnMouseEvent )
    EVT_CHAR( EDA_DRAW_PANEL::OnKeyEvent )
    EVT_CHAR_HOOK( EDA_DRAW_PANEL::OnKeyEvent )
    EVT_PAINT( EDA_DRAW_PANEL::OnPaint )
    EVT_ERASE_BACKGROUND( EDA_DRAW_PANEL::OnEraseBackground )
    EVT_SCROLLWIN( EDA_DRAW_PANEL::OnScroll )
    EVT_ACTIVATE( EDA_DRAW_PANEL::OnActivate )
    EVT_TIMER( ID_MOUSE_DOUBLECLICK, EDA_DRAW_PANEL::OnTimer )
    EVT_MENU_RANGE( ID_PAN_UP, ID_PAN_RIGHT, EDA_DRAW_PANEL::OnPan )
END_EVENT_TABLE()


/***********************************************************************/
/* EDA_DRAW_PANEL base functions (EDA_DRAW_PANEL is the main panel)*/
/***********************************************************************/

#ifdef __WXMAC__
const int drawPanelStyle = wxHSCROLL | wxVSCROLL | wxALWAYS_SHOW_SB;
#else
const int drawPanelStyle = wxHSCROLL | wxVSCROLL;
#endif

EDA_DRAW_PANEL::EDA_DRAW_PANEL( EDA_DRAW_FRAME* parent, int id,
                                const wxPoint& pos, const wxSize& size ) :
    wxScrolledWindow( parent, id, pos, size, drawPanelStyle )
{
    wxASSERT( parent );

    ShowScrollbars( wxSHOW_SB_ALWAYS, wxSHOW_SB_ALWAYS );
    DisableKeyboardScrolling();

    m_scrollIncrementX = std::min( size.x / 8, 10 );
    m_scrollIncrementY = std::min( size.y / 8, 10 );

    SetLayoutDirection( wxLayout_LeftToRight );

    SetBackgroundColour( parent->GetDrawBgColor().ToColour() );

#if KICAD_USE_BUFFERED_DC || KICAD_USE_BUFFERED_PAINTDC
    SetBackgroundStyle( wxBG_STYLE_CUSTOM );
#endif

    m_ClipBox.SetSize( size );
    m_ClipBox.SetX( 0 );
    m_ClipBox.SetY( 0 );
    m_canStartBlock = -1;       // Command block can start if >= 0
    m_abortRequest = false;
    m_enableMousewheelPan = false;
    m_enableZoomNoCenter = false;
    m_enableAutoPan = true;
    m_ignoreMouseEvents = false;
    // Be sure a mouse release button event will be ignored when creating the canvas
    // if the mouse click was not made inside the canvas (can happen sometimes, when
    // launching a editor from a double click made in another frame)
    m_ignoreNextLeftButtonRelease = true;

    m_mouseCaptureCallback = NULL;
    m_endMouseCaptureCallback = NULL;

    Pgm().CommonSettings()->Read( ENBL_MOUSEWHEEL_PAN_KEY, &m_enableMousewheelPan, false );
    Pgm().CommonSettings()->Read( ENBL_ZOOM_NO_CENTER_KEY, &m_enableZoomNoCenter, false );
    Pgm().CommonSettings()->Read( ENBL_AUTO_PAN_KEY, &m_enableAutoPan, true );

    m_requestAutoPan = false;
    m_enableBlockCommands = false;
    m_minDragEventCount = 0;

#ifdef __WXMAC__
    m_defaultCursor = m_currentCursor = wxCURSOR_CROSS;
    m_showCrossHair = false;
#else
    m_defaultCursor = m_currentCursor = wxCURSOR_ARROW;
    m_showCrossHair = true;
#endif

    m_cursorLevel = 0;
    m_PrintIsMirrored = false;

    m_ClickTimer = (wxTimer*) NULL;
    m_doubleClickInterval = 250;
}


EDA_DRAW_PANEL::~EDA_DRAW_PANEL()
{
    wxConfigBase* cfg = Kiface().KifaceSettings();

    if( cfg )
    {
        cfg->Write( ENBL_MOUSEWHEEL_PAN_KEY, m_enableMousewheelPan );
        cfg->Write( ENBL_ZOOM_NO_CENTER_KEY, m_enableZoomNoCenter );
        cfg->Write( ENBL_AUTO_PAN_KEY, m_enableAutoPan );
    }

    wxDELETE( m_ClickTimer );
}


EDA_DRAW_FRAME* EDA_DRAW_PANEL::GetParent() const
{
    wxWindow* mom = wxScrolledWindow::GetParent();
    return (EDA_DRAW_FRAME*) mom;
}


void* EDA_DRAW_PANEL::GetDisplayOptions()
{
    return GetParent()->GetDisplayOptions();
}


BASE_SCREEN* EDA_DRAW_PANEL::GetScreen()
{
    EDA_DRAW_FRAME* parentFrame = GetParent();

    return parentFrame->GetScreen();
}


wxPoint EDA_DRAW_PANEL::ToDeviceXY( const wxPoint& pos )
{
    wxPoint ret;
    INSTALL_UNBUFFERED_DC( dc, this );
    ret.x = dc.LogicalToDeviceX( pos.x );
    ret.y = dc.LogicalToDeviceY( pos.y );
    return ret;
}


wxPoint EDA_DRAW_PANEL::ToLogicalXY( const wxPoint& pos )
{
    wxPoint ret;
    INSTALL_UNBUFFERED_DC( dc, this );
    ret.x = dc.DeviceToLogicalX( pos.x );
    ret.y = dc.DeviceToLogicalY( pos.y );
    return ret;
}


void EDA_DRAW_PANEL::DrawCrossHair( wxDC* aDC, COLOR4D aColor )
{
    if( m_cursorLevel != 0 || aDC == NULL || !m_showCrossHair )
        return;

    wxPoint cursor = GetParent()->GetCrossHairPosition();

#ifdef USE_WX_GRAPHICS_CONTEXT
    // Normally cursor color is set to white, so when it is xored with white
    // background, it is painted black effectively. wxGraphicsContext does not have
    // xor operation, so we need to invert the color manually.
    aColor.Invert();
#else
    GRSetDrawMode( aDC, GR_XOR );
#endif

    if( GetParent()->GetGalDisplayOptions().m_fullscreenCursor )
    {
        wxSize  clientSize = GetClientSize();

        // Y axis
        wxPoint lineStart( cursor.x, aDC->DeviceToLogicalY( 0 ) );
        wxPoint lineEnd(   cursor.x, aDC->DeviceToLogicalY( clientSize.y ) );

        GRLine( &m_ClipBox, aDC, lineStart, lineEnd, 0, aColor );

        // X axis
        lineStart = wxPoint( aDC->DeviceToLogicalX( 0 ), cursor.y );
        lineEnd   = wxPoint( aDC->DeviceToLogicalX( clientSize.x ), cursor.y );

        GRLine( &m_ClipBox, aDC, lineStart, lineEnd, 0, aColor );
    }
    else
    {
        int len = aDC->DeviceToLogicalXRel( CURSOR_SIZE );

        GRLine( &m_ClipBox, aDC, cursor.x - len, cursor.y,
                cursor.x + len, cursor.y, 0, aColor );
        GRLine( &m_ClipBox, aDC, cursor.x, cursor.y - len,
                cursor.x, cursor.y + len, 0, aColor );
    }
}


void EDA_DRAW_PANEL::CrossHairOff( wxDC* DC )
{
    DrawCrossHair( DC );
    --m_cursorLevel;
}


void EDA_DRAW_PANEL::CrossHairOn( wxDC* DC )
{
    ++m_cursorLevel;
    DrawCrossHair( DC );

    if( m_cursorLevel > 0 )  // Shouldn't happen, but just in case ..
        m_cursorLevel = 0;
}


double EDA_DRAW_PANEL::GetZoom()
{
    return GetScreen()->GetZoom();
}


void EDA_DRAW_PANEL::SetZoom( double zoom )
{
    GetScreen()->SetZoom( zoom );
}


wxRealPoint EDA_DRAW_PANEL::GetGrid()
{
    return GetScreen()->GetGridSize();
}


bool EDA_DRAW_PANEL::IsPointOnDisplay( const wxPoint& aPosition )
{
    wxPoint  pos;
    EDA_RECT display_rect;

    INSTALL_UNBUFFERED_DC( dc, this );  // Refresh the clip box to the entire screen size.
    SetClipBox( dc );

    display_rect = m_ClipBox;

    // Slightly decreased the size of the useful screen area to avoid drawing limits.
    #define PIXEL_MARGIN 8
    display_rect.Inflate( -PIXEL_MARGIN );

    return display_rect.Contains( aPosition );
}


void EDA_DRAW_PANEL::RefreshDrawingRect( const EDA_RECT& aRect, bool aEraseBackground )
{
    INSTALL_UNBUFFERED_DC( dc, this );

    wxRect rect = aRect;

    rect.x = dc.LogicalToDeviceX( rect.x );
    rect.y = dc.LogicalToDeviceY( rect.y );
    rect.width = dc.LogicalToDeviceXRel( rect.width );
    rect.height = dc.LogicalToDeviceYRel( rect.height );

    wxLogTrace( kicadTraceCoords,
                wxT( "Refresh area: drawing (%d, %d, %d, %d), device (%d, %d, %d, %d)" ),
                aRect.GetX(), aRect.GetY(), aRect.GetWidth(), aRect.GetHeight(),
                rect.x, rect.y, rect.width, rect.height );

    RefreshRect( rect, aEraseBackground );
}


void EDA_DRAW_PANEL::Refresh( bool eraseBackground, const wxRect* rect )
{
    if( GetParent()->IsGalCanvasActive() )
    {
        GetParent()->GetGalCanvas()->Refresh();
    }
    else
    {
        wxScrolledWindow::Refresh( eraseBackground, rect );
    }
}


wxPoint EDA_DRAW_PANEL::GetScreenCenterLogicalPosition()
{
    wxSize size = GetClientSize() / 2;
    INSTALL_UNBUFFERED_DC( dc, this );

    return wxPoint( dc.DeviceToLogicalX( size.x ), dc.DeviceToLogicalY( size.y ) );
}


void EDA_DRAW_PANEL::MoveCursorToCrossHair()
{
    MoveCursor( GetParent()->GetCrossHairPosition() );
}


void EDA_DRAW_PANEL::MoveCursor( const wxPoint& aPosition )
{
    if( GetParent()->IsGalCanvasActive() )
        return;

    int     x, y, xPpu, yPpu;
    wxPoint screenPos, drawingPos;
    wxRect  clientRect( wxPoint( 0, 0 ), GetClientSize() );

    INSTALL_UNBUFFERED_DC( dc, this );
    screenPos.x = dc.LogicalToDeviceX( aPosition.x );
    screenPos.y = dc.LogicalToDeviceY( aPosition.y );

    // Scroll if the requested mouse position cursor is outside the drawing area.
    if( !clientRect.Contains( screenPos ) )
    {
        GetViewStart( &x, &y );
        GetScrollPixelsPerUnit( &xPpu, &yPpu );
        CalcUnscrolledPosition( screenPos.x, screenPos.y, &drawingPos.x, &drawingPos.y );

        wxLogTrace( kicadTraceCoords,
                    wxT( "MoveCursor() initial screen position(%d, %d) " ) \
                    wxT( "rectangle(%d, %d, %d, %d) view(%d, %d)" ),
                    screenPos.x, screenPos.y, clientRect.x, clientRect.y,
                    clientRect.width, clientRect.height, x, y );

        if( screenPos.y < clientRect.GetTop() )
            y -= m_scrollIncrementY * yPpu;
        else if( screenPos.y > clientRect.GetBottom() )
            y += m_scrollIncrementY * yPpu;
        else if( clientRect.GetRight() < screenPos.x )
            x += m_scrollIncrementX * xPpu;
        else
            x -= m_scrollIncrementX * xPpu;

        Scroll( x, y );
        CalcScrolledPosition( drawingPos.x, drawingPos.y, &screenPos.x, &screenPos.y );

        wxLogTrace( kicadTraceCoords,
                    wxT( "MoveCursor() scrolled screen position(%d, %d) view(%d, %d)" ),
                    screenPos.x, screenPos.y, x, y );
    }

    WarpPointer( screenPos.x, screenPos.y );
}


void EDA_DRAW_PANEL::OnActivate( wxActivateEvent& event )
{
    m_canStartBlock = -1;   // Block Command can't start
    event.Skip();
}


void EDA_DRAW_PANEL::OnTimer( wxTimerEvent& event )
{
    INSTALL_UNBUFFERED_DC( DC, this );
    DC.SetBackground( *wxBLACK_BRUSH );
    GetParent()->OnLeftClick( &DC, m_CursorClickPos );
}


void EDA_DRAW_PANEL::OnScroll( wxScrollWinEvent& event )
{
    int id = event.GetEventType();
    int x, y;
    int ppux, ppuy;
    int csizeX, csizeY;
    int unitsX, unitsY;

    GetViewStart( &x, &y );
    GetScrollPixelsPerUnit( &ppux, &ppuy );
    GetClientSize( &csizeX, &csizeY );
    GetVirtualSize( &unitsX, &unitsY );

    int tmpX = x;
    int tmpY = y;

    csizeX /= ppux;
    csizeY /= ppuy;

    unitsX /= ppux;
    unitsY /= ppuy;

    int dir = event.GetOrientation();   // wxHORIZONTAL or wxVERTICAL

    // On windows and on wxWidgets >= 2.9.5 and < 3.1,
    // there is a bug in mousewheel event which always generates 2 scroll events
    // (should be the case only for the default mousewheel event)
    // with id = wxEVT_SCROLLWIN_LINEUP or wxEVT_SCROLLWIN_LINEDOWN
    // so we skip these events.
    // Note they are here just in case, because they are not actually used
    // in Kicad
#if wxCHECK_VERSION( 3, 1, 0 ) || !wxCHECK_VERSION( 2, 9, 5 ) || ( !defined (__WINDOWS__) && !defined (__WXMAC__) )
    int maxX = unitsX - csizeX;
    int maxY = unitsY - csizeY;

    if( id == wxEVT_SCROLLWIN_LINEUP )
    {
        if( dir == wxHORIZONTAL )
        {
            x -= m_scrollIncrementX;

            if( x < 0 )
                x = 0;
        }
        else
        {
            y -= m_scrollIncrementY;

            if( y < 0 )
                y = 0;
        }
    }
    else if( id == wxEVT_SCROLLWIN_LINEDOWN )
    {
        if( dir == wxHORIZONTAL )
        {
            x += m_scrollIncrementX;
            if( x > maxX )
                x = maxX;
        }
        else
        {
            y += m_scrollIncrementY;

            if( y > maxY )
                y = maxY;
        }
    }
    else
#endif
    if( id == wxEVT_SCROLLWIN_THUMBTRACK )
    {
        if( dir == wxHORIZONTAL )
            x = event.GetPosition();
        else
            y = event.GetPosition();
    }
    else
    {
        event.Skip();
        return;
    }

    wxLogTrace( kicadTraceCoords,
                wxT( "Setting scroll bars ppuX=%d, ppuY=%d, unitsX=%d, unitsY=%d, posX=%d, posY=%d" ),
                ppux, ppuy, unitsX, unitsY, x, y );

    double scale = GetParent()->GetScreen()->GetScalingFactor();

    wxPoint center = GetParent()->GetScrollCenterPosition();
    center.x += KiROUND( (double) ( x - tmpX ) / scale );
    center.y += KiROUND( (double) ( y - tmpY ) / scale );
    GetParent()->SetScrollCenterPosition( center );

    Scroll( x, y );
    event.Skip();
}


void EDA_DRAW_PANEL::SetClipBox( wxDC& aDC, const wxRect* aRect )
{
    wxRect clipBox;

    // Use the entire visible device area if no clip area was defined.
    if( aRect == NULL )
    {
        BASE_SCREEN* Screen = GetScreen();

        if( !Screen )
            return;

        Screen->m_StartVisu = CalcUnscrolledPosition( wxPoint( 0, 0 ) );
        clipBox.SetSize( GetClientSize() );

        int scrollX, scrollY;

        double scalar = Screen->GetScalingFactor();
        scrollX = KiROUND( Screen->GetGridSize().x * scalar );
        scrollY = KiROUND( Screen->GetGridSize().y * scalar );

        m_scrollIncrementX = std::max( GetClientSize().x / 8, scrollX );
        m_scrollIncrementY = std::max( GetClientSize().y / 8, scrollY );
        Screen->m_ScrollbarPos.x = GetScrollPos( wxHORIZONTAL );
        Screen->m_ScrollbarPos.y = GetScrollPos( wxVERTICAL );
    }
    else
    {
        clipBox = *aRect;
    }

    // Pad clip box in device units.
    clipBox.Inflate( CLIP_BOX_PADDING );

    // Convert from device units to drawing units.
    m_ClipBox.SetOrigin( wxPoint( aDC.DeviceToLogicalX( clipBox.x ),
                                  aDC.DeviceToLogicalY( clipBox.y ) ) );
    m_ClipBox.SetSize( wxSize( aDC.DeviceToLogicalXRel( clipBox.width ),
                               aDC.DeviceToLogicalYRel( clipBox.height ) ) );

    wxLogTrace( kicadTraceCoords,
                wxT( "Device clip box=(%d, %d, %d, %d), Logical clip box=(%d, %d, %d, %d)" ),
                clipBox.x, clipBox.y, clipBox.width, clipBox.height,
                m_ClipBox.GetX(), m_ClipBox.GetY(), m_ClipBox.GetWidth(), m_ClipBox.GetHeight() );
}


void EDA_DRAW_PANEL::EraseScreen( wxDC* DC )
{
    GRSetDrawMode( DC, GR_COPY );

    COLOR4D bgColor = GetParent()->GetDrawBgColor();

    GRSFilledRect( NULL, DC, m_ClipBox.GetX(), m_ClipBox.GetY(),
                   m_ClipBox.GetRight(), m_ClipBox.GetBottom(),
                   0, bgColor, bgColor );

    // Set to one (1) to draw bounding box validate bounding box calculation.
#if DEBUG_SHOW_CLIP_RECT
    EDA_RECT bBox = m_ClipBox;
    GRRect( NULL, DC, bBox.GetOrigin().x, bBox.GetOrigin().y,
            bBox.GetEnd().x, bBox.GetEnd().y, 0, LIGHTMAGENTA );
#endif
}


void EDA_DRAW_PANEL::DoPrepareDC( wxDC& dc )
{
    wxScrolledWindow::DoPrepareDC( dc );

    if( GetScreen() != NULL )
    {
        double scale = GetScreen()->GetScalingFactor();
        dc.SetUserScale( scale, scale );

        wxPoint pt = GetScreen()->m_DrawOrg;
        dc.SetLogicalOrigin( pt.x, pt.y );
    }

    SetClipBox( dc );                         // Reset the clip box to the entire screen.
    GRResetPenAndBrush( &dc );
    dc.SetBackgroundMode( wxTRANSPARENT );
}


void EDA_DRAW_PANEL::OnPaint( wxPaintEvent& event )
{
    if( GetScreen() == NULL )
    {
        event.Skip();
        return;
    }

    INSTALL_PAINTDC( paintDC, this );

    wxRect region = GetUpdateRegion().GetBox();
    SetClipBox( paintDC, &region );
    ReDraw( &paintDC, true );
}


void EDA_DRAW_PANEL::ReDraw( wxDC* DC, bool erasebg )
{
    BASE_SCREEN* Screen = GetScreen();

    if( Screen == NULL )
        return;

    COLOR4D bgColor = GetParent()->GetDrawBgColor();

    // TODO(JE): Is this correct?
    if( bgColor.GetBrightness() > 0.5 )
    {
        g_XorMode    = GR_NXOR;
        g_GhostColor = BLACK;
    }
    else
    {
        g_XorMode    = GR_XOR;
        g_GhostColor = WHITE;
    }

    GRResetPenAndBrush( DC );

    DC->SetBackground( wxBrush( bgColor.ToColour() ) );
    DC->SetBackgroundMode( wxSOLID );

    if( erasebg )
        EraseScreen( DC );

    GetParent()->RedrawActiveWindow( DC, erasebg );

    // Verfies that the clipping is working correctly.  If these two sets of numbers are
    // not the same or really close.  The clipping algorithms are broken.
    wxLogTrace( kicadTraceCoords,
                wxT( "Clip box: (%d, %d, %d, %d), Draw extents (%d, %d, %d, %d)" ),
                m_ClipBox.GetX(), m_ClipBox.GetY(), m_ClipBox.GetRight(), m_ClipBox.GetBottom(),
                DC->MinX(), DC->MinY(), DC->MaxX(), DC->MaxY() );
}


void EDA_DRAW_PANEL::SetEnableMousewheelPan( bool aEnable )
{
    m_enableMousewheelPan = aEnable;

    if( GetParent()->IsGalCanvasActive() )
        GetParent()->GetGalCanvas()->GetViewControls()->EnableMousewheelPan( aEnable );
}

void EDA_DRAW_PANEL::SetEnableAutoPan( bool aEnable )
{
    m_enableAutoPan = aEnable;

    if( GetParent()->IsGalCanvasActive() )
        GetParent()->GetGalCanvas()->GetViewControls()->EnableAutoPan( aEnable );
}


void EDA_DRAW_PANEL::SetEnableZoomNoCenter( bool aEnable )
{
    m_enableZoomNoCenter = aEnable;

    if( GetParent()->IsGalCanvasActive() )
        GetParent()->GetGalCanvas()->GetViewControls()->EnableCursorWarping( !aEnable );
}


void EDA_DRAW_PANEL::DrawBackGround( wxDC* DC )
{
    GRSetDrawMode( DC, GR_COPY );

    if( GetParent()->IsGridVisible() )
        DrawGrid( DC );

    // Draw axis
    if( GetParent()->GetShowAxis() )
    {
        COLOR4D axis_color = COLOR4D( BLUE );
        wxSize  pageSize = GetParent()->GetPageSizeIU();

        // Draw the Y axis
        GRLine( &m_ClipBox, DC, 0, -pageSize.y, 0, pageSize.y, 0, axis_color );

        // Draw the X axis
        GRLine( &m_ClipBox, DC, -pageSize.x, 0, pageSize.x, 0, 0, axis_color );
    }

    if( GetParent()->GetShowOriginAxis() )
        DrawAuxiliaryAxis( DC, GR_COPY );

    if( GetParent()->GetShowGridAxis() )
        DrawGridAxis( DC, GR_COPY, GetParent()->GetGridOrigin() );
}


void EDA_DRAW_PANEL::DrawGrid( wxDC* aDC )
{
    #define MIN_GRID_SIZE 10        // min grid size in pixels to allow drawing
    BASE_SCREEN* screen = GetScreen();
    wxRealPoint  gridSize;
    wxSize       screenSize;
    wxPoint      org;
    wxRealPoint  screenGridSize;

    /* The grid must be visible. this is possible only is grid value
     * and zoom value are sufficient
     */
    gridSize = screen->GetGridSize();
    screen->m_StartVisu = CalcUnscrolledPosition( wxPoint( 0, 0 ) );
    screenSize = GetClientSize();

    screenGridSize.x = aDC->LogicalToDeviceXRel( KiROUND( gridSize.x ) );
    screenGridSize.y = aDC->LogicalToDeviceYRel( KiROUND( gridSize.y ) );

    org = m_ClipBox.GetPosition();

    if( screenGridSize.x < MIN_GRID_SIZE || screenGridSize.y < MIN_GRID_SIZE )
    {
        screenGridSize.x *= 2.0;
        screenGridSize.y *= 2.0;
        gridSize.x *= 2.0;
        gridSize.y *= 2.0;
    }

    if( screenGridSize.x < MIN_GRID_SIZE || screenGridSize.y < MIN_GRID_SIZE )
        return;

    org = GetParent()->GetNearestGridPosition( org, &gridSize );

    // Setting the nearest grid position can select grid points outside the clip box.
    // Incrementing the start point by one grid step should prevent drawing grid points
    // outside the clip box.
    if( org.x < m_ClipBox.GetX() )
        org.x += KiROUND( gridSize.x );

    if( org.y < m_ClipBox.GetY() )
        org.y += KiROUND( gridSize.y );

    // Use a pixel based draw to display grid.  There are a lot of calls, so the cost is
    // high and grid is slowly drawn on some platforms. Another way using blit transfert was used,
    // a long time ago, but it did not give very good results.
    // The better way is highly dependent on the platform and the graphic card.
    int xpos;
    double right = ( double ) m_ClipBox.GetRight();
    double bottom = ( double ) m_ClipBox.GetBottom();

#if defined( USE_WX_GRAPHICS_CONTEXT )
    wxGCDC *gcdc = wxDynamicCast( aDC, wxGCDC );

    if( gcdc )
    {
        // Much faster grid drawing on systems using wxGraphicsContext
        wxGraphicsContext *gc = gcdc->GetGraphicsContext();

        // Grid point size
        const int gsz = 1;
        const double w = aDC->DeviceToLogicalXRel( gsz );
        const double h = aDC->DeviceToLogicalYRel( gsz );

        // Use our own pen
        wxPen pen( GetParent()->GetGridColor().ToColour(), h );
        pen.SetCap( wxCAP_BUTT );
        gc->SetPen( pen );

        // draw grid
        wxGraphicsPath path = gc->CreatePath();
        for( double x = (double) org.x - w/2.0; x <= right - w/2.0; x += gridSize.x )
        {
            for( double y = (double) org.y; y <= bottom; y += gridSize.y )
            {
                path.MoveToPoint( x, y );
                path.AddLineToPoint( x+w, y );
            }
        }
        gc->StrokePath( path );
    }
    else
#endif
    {
        GRSetColorPen( aDC, GetParent()->GetGridColor() );

        for( double x = (double) org.x; x <= right; x += gridSize.x )
        {
            xpos = KiROUND( x );

            for( double y = (double) org.y; y <= bottom; y += gridSize.y )
            {
                aDC->DrawPoint( xpos, KiROUND( y )  );
            }
        }
    }
}

// Set to 1 to draw auxirilary axis as lines, 0 to draw as target (circle with cross)
#define DRAW_AXIS_AS_LINES 0
// Size in pixels of the target shape
#define AXIS_SIZE_IN_PIXELS 15

void EDA_DRAW_PANEL::DrawAuxiliaryAxis( wxDC* aDC, GR_DRAWMODE aDrawMode )
{
    wxPoint origin = GetParent()->GetAuxOrigin();

    if( origin == wxPoint( 0, 0 ) )
        return;

    COLOR4D color = COLOR4D( RED );

    GRSetDrawMode( aDC, aDrawMode );

#if DRAW_AXIS_AS_LINES
    wxSize  pageSize = GetParent()->GetPageSizeIU();
    // Draw the Y axis
    GRLine( &m_ClipBox, aDC, origin.x, -pageSize.y,
            origin.x, pageSize.y, 0, color );

    // Draw the X axis
    GRLine( &m_ClipBox, aDC, -pageSize.x, origin.y,
            pageSize.x, origin.y, 0, color );
#else
    int radius = aDC->DeviceToLogicalXRel( AXIS_SIZE_IN_PIXELS );
    int linewidth = aDC->DeviceToLogicalXRel( 1 );

    GRSetColorPen( aDC, color, linewidth );

    GRLine( &m_ClipBox, aDC, origin.x, origin.y-radius,
            origin.x, origin.y+radius, 0, color );

    // Draw the + shape
    GRLine( &m_ClipBox, aDC, origin.x-radius, origin.y,
            origin.x+radius, origin.y, 0, color );

    GRCircle( &m_ClipBox, aDC, origin, radius, linewidth, color );
#endif
}


void EDA_DRAW_PANEL::DrawGridAxis( wxDC* aDC, GR_DRAWMODE aDrawMode, const wxPoint& aGridOrigin )
{
    if( !GetParent()->GetShowGridAxis() || ( !aGridOrigin.x && !aGridOrigin.y ) )
        return;

    COLOR4D color = GetParent()->GetGridColor();

    GRSetDrawMode( aDC, aDrawMode );

#if DRAW_AXIS_AS_LINES
    wxSize      pageSize = GetParent()->GetPageSizeIU();
    // Draw the Y axis
    GRLine( &m_ClipBox, aDC, aGridOrigin.x, -pageSize.y,
            aGridOrigin.x, pageSize.y, 0, color );

    // Draw the X axis
    GRLine( &m_ClipBox, aDC, -pageSize.x, aGridOrigin.y,
            pageSize.x, aGridOrigin.y, 0, color );
#else
    int radius = aDC->DeviceToLogicalXRel( AXIS_SIZE_IN_PIXELS );
    int linewidth = aDC->DeviceToLogicalXRel( 1 );

    GRSetColorPen( aDC, GetParent()->GetGridColor(), linewidth );

    GRLine( &m_ClipBox, aDC, aGridOrigin.x-radius, aGridOrigin.y-radius,
            aGridOrigin.x+radius, aGridOrigin.y+radius, 0, color );

    // Draw the X shape
    GRLine( &m_ClipBox, aDC, aGridOrigin.x+radius, aGridOrigin.y-radius,
            aGridOrigin.x-radius, aGridOrigin.y+radius, 0, color );

    GRCircle( &m_ClipBox, aDC, aGridOrigin, radius, linewidth, color );
#endif
}


bool EDA_DRAW_PANEL::OnRightClick( wxMouseEvent& event )
{
    wxPoint pos;
    wxMenu  MasterMenu;

    INSTALL_UNBUFFERED_DC( dc, this );

    pos = event.GetLogicalPosition( dc );

    if( !GetParent()->OnRightClick( pos, &MasterMenu ) )
        return false;

    GetParent()->AddMenuZoomAndGrid( &MasterMenu );

    pos = event.GetPosition();
    m_ignoreMouseEvents = true;
    PopupMenu( &MasterMenu, pos );

    // The ZoomAndGrid menu is only invoked over empty space so there's no point in warping
    // the cursor back to the crosshair, and it's very annoying if one clicked out of the menu.

    m_ignoreMouseEvents = false;

    return true;
}


void EDA_DRAW_PANEL::OnMouseEntering( wxMouseEvent& aEvent )
{
    // This is an ugly hack that fixes some cross hair display bugs when the mouse leaves the
    // canvas area during middle button mouse panning.
    if( m_cursorLevel != 0 )
        m_cursorLevel = 0;

    aEvent.Skip();
}


void EDA_DRAW_PANEL::OnMouseLeaving( wxMouseEvent& event )
{
    if( m_mouseCaptureCallback == NULL )          // No command in progress.
        SetAutoPanRequest( false );

    if( !m_enableAutoPan || !m_requestAutoPan || m_ignoreMouseEvents )
        return;

    // Auto pan when mouse has left the client window
    // Ensure the cross_hair position is updated,
    // because it will be used to center the screen.
    // We use a position inside the client window
    wxRect area( wxPoint( 0, 0 ), GetClientSize() );
    wxPoint cross_hair_pos = event.GetPosition();

    // Certain window managers (e.g. awesome wm) incorrectly trigger "on leave" event,
    // therefore test if the cursor has really left the panel area
    if( !area.Contains( cross_hair_pos ) )
    {
        INSTALL_UNBUFFERED_DC( dc, this );
        cross_hair_pos.x = dc.DeviceToLogicalX( cross_hair_pos.x );
        cross_hair_pos.y = dc.DeviceToLogicalY( cross_hair_pos.y );

        GetParent()->SetCrossHairPosition( cross_hair_pos );

        wxCommandEvent cmd( wxEVT_COMMAND_MENU_SELECTED, ID_POPUP_ZOOM_CENTER );
        cmd.SetEventObject( this );
        GetEventHandler()->ProcessEvent( cmd );
    }

    event.Skip();
}


void EDA_DRAW_PANEL::OnMouseWheel( wxMouseEvent& event )
{
    if( m_ignoreMouseEvents )
        return;

    wxRect rect = wxRect( wxPoint( 0, 0 ), GetClientSize() );

    // Ignore scroll events if the cursor is outside the drawing area.
    if( event.GetWheelRotation() == 0 || !GetParent()->IsEnabled()
       || !rect.Contains( event.GetPosition() ) )
    {
        wxLogTrace( kicadTraceCoords,
                    wxT( "OnMouseWheel() position(%d, %d) rectangle(%d, %d, %d, %d)" ),
                    event.GetPosition().x, event.GetPosition().y,
                    rect.x, rect.y, rect.width, rect.height );
        event.Skip();
        return;
    }

    INSTALL_UNBUFFERED_DC( dc, this );
    GetParent()->SetCrossHairPosition( event.GetLogicalPosition( dc ) );

    wxCommandEvent cmd( wxEVT_COMMAND_MENU_SELECTED );
    cmd.SetEventObject( this );

    bool offCenterReq = event.ControlDown() && event.ShiftDown();
    offCenterReq = offCenterReq || m_enableZoomNoCenter;

    int axis = event.GetWheelAxis();
    int wheelRotation = event.GetWheelRotation();

    if( m_enableMousewheelPan )
    {
        // MousewheelPAN + Ctrl = zooming
        if( event.ControlDown() && !event.ShiftDown() )
        {
            if( wheelRotation > 0 )
                cmd.SetId( ID_POPUP_ZOOM_IN );
            else if( wheelRotation < 0)
                cmd.SetId( ID_POPUP_ZOOM_OUT );
        }
        // MousewheelPAN + Shift = horizontal scrolling
        // Without modifiers MousewheelPAN - just pan
        else
        {
            if( event.ShiftDown() && !event.ControlDown() )
                axis = wxMOUSE_WHEEL_HORIZONTAL;

            wxPoint newStart = GetViewStart();
            wxPoint center = GetParent()->GetScrollCenterPosition();
            double scale = GetParent()->GetScreen()->GetScalingFactor();

            if( axis == wxMOUSE_WHEEL_HORIZONTAL )
            {
                newStart.x += wheelRotation;
                center.x += KiROUND( (double) wheelRotation / scale );
            }
            else
            {
                newStart.y -= wheelRotation;
                center.y -= KiROUND( (double) wheelRotation / scale );
            }
            Scroll( newStart );

            GetParent()->SetScrollCenterPosition( center );
            GetParent()->SetCrossHairPosition( center, true );
            GetParent()->RedrawScreen( center, false );
        }
    }
    else if( wheelRotation > 0 )
    {
        if( event.ShiftDown() && !event.ControlDown() )
            cmd.SetId( ID_PAN_UP );
        else if( event.ControlDown() && !event.ShiftDown() )
            cmd.SetId( ID_PAN_LEFT );
        else if( offCenterReq )
            cmd.SetId( ID_OFFCENTER_ZOOM_IN );
        else
            cmd.SetId( ID_POPUP_ZOOM_IN );
    }
    else if( wheelRotation < 0 )
    {
        if( event.ShiftDown() && !event.ControlDown() )
            cmd.SetId( ID_PAN_DOWN );
        else if( event.ControlDown() && !event.ShiftDown() )
            cmd.SetId( ID_PAN_RIGHT );
        else if( offCenterReq )
            cmd.SetId( ID_OFFCENTER_ZOOM_OUT );
        else
            cmd.SetId( ID_POPUP_ZOOM_OUT );
    }

    if( cmd.GetId() )
        GetEventHandler()->ProcessEvent( cmd );
    event.Skip();
}


#if wxCHECK_VERSION( 3, 1, 0 ) || defined( USE_OSX_MAGNIFY_EVENT )
void EDA_DRAW_PANEL::OnMagnify( wxMouseEvent& event )
{
    // Scale the panel around our cursor position.
    bool warpCursor = false;

    wxPoint cursorPosition = GetParent()->GetCursorPosition( false );
    wxPoint centerPosition = GetParent()->GetScrollCenterPosition();
    wxPoint vector = centerPosition - cursorPosition;

    double magnification = ( event.GetMagnification() + 1.0f );
    double scaleFactor = GetZoom() / magnification;

    // Scale the vector between the cursor and center point
    vector.x /= magnification;
    vector.y /= magnification;

    SetZoom(scaleFactor);

    // Recenter the window along our scaled vector such that the
    // cursor becomes our scale axis, remaining fixed on screen
    GetParent()->RedrawScreen( cursorPosition + vector, warpCursor );

    event.Skip();
}
#endif


void EDA_DRAW_PANEL::OnMouseEvent( wxMouseEvent& event )
{
    int          localbutt = 0;
    BASE_SCREEN* screen = GetScreen();

    if( !screen )
        return;

    /* Adjust value to filter mouse displacement before consider the drag
     * mouse is really a drag command, not just a movement while click
     */
#define MIN_DRAG_COUNT_FOR_START_BLOCK_COMMAND 5

    if( event.Leaving() )
        m_canStartBlock = -1;

    if( !IsMouseCaptured() )          // No mouse capture in progress.
        SetAutoPanRequest( false );

    if( GetParent()->IsActive() )
        SetFocus();
    else
        return;

    if( !event.IsButton() && !event.Moving() && !event.Dragging() )
        return;

    if( event.RightDown() )
    {
        OnRightClick( event );
        return;
    }

    if( m_ignoreMouseEvents )
        return;

    if( event.LeftDown() )
        localbutt = GR_M_LEFT_DOWN;

    if( event.ButtonDClick( 1 ) )
        localbutt = GR_M_LEFT_DOWN | GR_M_DCLICK;

    if( event.MiddleDown() )
        localbutt = GR_M_MIDDLE_DOWN;

    INSTALL_UNBUFFERED_DC( DC, this );
    DC.SetBackground( *wxBLACK_BRUSH );

    // Compute the cursor position in drawing (logical) units.
    GetParent()->SetMousePosition( event.GetLogicalPosition( DC ) );

    int kbstat = 0;

    if( event.ShiftDown() )
        kbstat |= GR_KB_SHIFT;

    if( event.ControlDown() )
        kbstat |= GR_KB_CTRL;

    if( event.AltDown() )
        kbstat |= GR_KB_ALT;

    // Calling Double Click and Click functions :
    if( localbutt == (int) ( GR_M_LEFT_DOWN | GR_M_DCLICK ) )
    {
        if( m_ClickTimer )
        {
            m_ClickTimer->Stop();
            wxDELETE( m_ClickTimer );
        }
        GetParent()->OnLeftDClick( &DC, GetParent()->RefPos( true ) );

        // inhibit a response to the mouse left button release,
        // because we have a double click, and we do not want a new
        // OnLeftClick command at end of this Double Click
        m_ignoreNextLeftButtonRelease = true;
    }
    else if( event.LeftUp() )
    {
        // A block command is in progress: a left up is the end of block
        // or this is the end of a double click, already seen
        // Note also m_ignoreNextLeftButtonRelease can be set by
        // the call to OnLeftClick(), so do not change it after calling OnLeftClick
        bool ignoreEvt = m_ignoreNextLeftButtonRelease;
        m_ignoreNextLeftButtonRelease = false;

        if( screen->m_BlockLocate.GetState() == STATE_NO_BLOCK && !ignoreEvt )
        {
            EDA_ITEM* item = screen->GetCurItem();
            m_CursorClickPos = GetParent()->RefPos( true );

            // If we have an item already selected, or we are using a tool,
            // we won't use the disambiguation menu so process the click immediately
            if( ( item && item->GetFlags() ) || GetParent()->GetToolId() != ID_NO_TOOL_SELECTED )
                GetParent()->OnLeftClick( &DC, m_CursorClickPos );
            else
            {
                wxDELETE( m_ClickTimer );
                m_ClickTimer = new wxTimer(this, ID_MOUSE_DOUBLECLICK);
                m_ClickTimer->StartOnce( m_doubleClickInterval );
            }
        }

    }
    else if( !event.LeftIsDown() )
    {
        /* be sure there is a response to a left button release command
         * even when a LeftUp event is not seen.  This happens when a
         * double click opens a dialog box, and the release mouse button
         * is made when the dialog box is opened.
         */
        m_ignoreNextLeftButtonRelease = false;
    }

    if( event.ButtonDown( wxMOUSE_BTN_MIDDLE ) )
    {
        m_PanStartCenter = GetParent()->GetScrollCenterPosition();
        m_PanStartEventPosition = event.GetPosition();

        INSTALL_UNBUFFERED_DC( dc, this );
        CrossHairOff( &dc );
        SetCursor( wxCURSOR_SIZING );
    }

    if( event.ButtonUp( wxMOUSE_BTN_MIDDLE ) )
    {
        INSTALL_UNBUFFERED_DC( dc, this );
        CrossHairOn( &dc );
        SetCursor( (wxStockCursor) m_currentCursor );
    }

    if( event.MiddleIsDown() )
    {
        wxPoint currentPosition = event.GetPosition();

        double scale = GetParent()->GetScreen()->GetScalingFactor();
        int x = m_PanStartCenter.x +
                KiROUND( (double) ( m_PanStartEventPosition.x - currentPosition.x ) / scale );
        int y = m_PanStartCenter.y +
                KiROUND( (double) ( m_PanStartEventPosition.y - currentPosition.y ) / scale );

        GetParent()->RedrawScreen( wxPoint( x, y ), false );
    }

    // Calling the general function on mouse changes (and pseudo key commands)
    GetParent()->GeneralControl( &DC, event.GetLogicalPosition( DC ), 0 );

    /*******************************/
    /* Control of block commands : */
    /*******************************/

    // Command block can't start if mouse is dragging a new panel
    static EDA_DRAW_PANEL* lastPanel;
    if( lastPanel != this )
    {
        m_minDragEventCount = 0;
        m_canStartBlock   = -1;
    }

    /* A new command block can start after a release buttons
     * and if the drag is enough
     * This is to avoid a false start block when a dialog box is dismissed,
     * or when changing panels in hierarchy navigation
     * or when clicking while and moving mouse
     */
    if( !event.LeftIsDown() && !event.MiddleIsDown() )
    {
        m_minDragEventCount = 0;
        m_canStartBlock   = 0;

        /* Remember the last cursor position when a drag mouse starts
         * this is the last position ** before ** clicking a button
         * this is useful to start a block command from the point where the
         * mouse was clicked first
         * (a filter creates a delay for the real block command start, and
         * we must remember this point)
         */
        m_CursorStartPos = GetParent()->GetCrossHairPosition();
    }

    if( m_enableBlockCommands && !(localbutt & GR_M_DCLICK) )
    {
        if( !screen->IsBlockActive() )
        {
            screen->m_BlockLocate.SetOrigin( m_CursorStartPos );
        }

        if( event.LeftDown() )
        {
            if( screen->m_BlockLocate.GetState() == STATE_BLOCK_MOVE )
            {
                SetAutoPanRequest( false );
                GetParent()->HandleBlockPlace( &DC );
                m_ignoreNextLeftButtonRelease = true;
            }
        }
        else if( ( m_canStartBlock >= 0 ) && event.LeftIsDown() && !IsMouseCaptured() )
        {
            // Mouse is dragging: if no block in progress,  start a block command.
            if( screen->m_BlockLocate.GetState() == STATE_NO_BLOCK )
            {
                //  Start a block command
                int cmd_type = kbstat;

                // A block command is started if the drag is enough.  A small
                // drag is ignored (it is certainly a little mouse move when
                // clicking) not really a drag mouse
                if( m_minDragEventCount < MIN_DRAG_COUNT_FOR_START_BLOCK_COMMAND )
                    m_minDragEventCount++;
                else
                {
                    auto cmd = (GetParent()->GetToolId() == ID_ZOOM_SELECTION) ? BLOCK_ZOOM : 0;

                    if( !GetParent()->HandleBlockBegin( &DC, cmd_type, m_CursorStartPos, cmd ) )
                    {
                        // should not occur: error
                        GetParent()->DisplayToolMsg(
                            wxT( "EDA_DRAW_PANEL::OnMouseEvent() Block Error" ) );
                    }
                    else
                    {
                        SetAutoPanRequest( true );
                        SetCursor( wxCURSOR_SIZING );
                    }
                }
            }
        }

        if( event.ButtonUp( wxMOUSE_BTN_LEFT ) )
        {
            /* Release the mouse button: end of block.
             * The command can finish (DELETE) or have a next command (MOVE,
             * COPY).  However the block command is canceled if the block
             * size is small because a block command filtering is already
             * made, this case happens, but only when the on grid cursor has
             * not moved.
             */
            #define BLOCK_MINSIZE_LIMIT 1
            bool BlockIsSmall =
                ( std::abs( screen->m_BlockLocate.GetWidth() ) < BLOCK_MINSIZE_LIMIT )
                && ( std::abs( screen->m_BlockLocate.GetHeight() ) < BLOCK_MINSIZE_LIMIT );

            if( (screen->m_BlockLocate.GetState() != STATE_NO_BLOCK) && BlockIsSmall )
            {
                if( m_endMouseCaptureCallback )
                {
                    m_endMouseCaptureCallback( this, &DC );
                    SetAutoPanRequest( false );
                }

                SetCursor( (wxStockCursor) m_currentCursor );
           }
            else if( screen->m_BlockLocate.GetState() == STATE_BLOCK_END )
            {
                SetAutoPanRequest( false );
                GetParent()->HandleBlockEnd( &DC );
                SetCursor( (wxStockCursor) m_currentCursor );
                if( screen->m_BlockLocate.GetState() == STATE_BLOCK_MOVE )
                {
                    SetAutoPanRequest( true );
                    SetCursor( wxCURSOR_HAND );
                }
           }
        }
    }

    // End of block command on a double click
    // To avoid an unwanted block move command if the mouse is moved while double clicking
    if( localbutt == (int) ( GR_M_LEFT_DOWN | GR_M_DCLICK ) )
    {
        if( !screen->IsBlockActive() && IsMouseCaptured() )
        {
            m_endMouseCaptureCallback( this, &DC );
        }
    }

    lastPanel = this;

#ifdef __WXGTK3__
    // Screen has to be updated on every operation, otherwise the cursor leaves a trail (when xor
    // operation is changed to copy) or is not updated at all.
    Refresh();
#endif
}


void EDA_DRAW_PANEL::OnCharHook( wxKeyEvent& event )
{
    wxLogTrace( kicadTraceKeyEvent, "EDA_DRAW_PANEL::OnCharHook %s", dump( event ) );
    event.Skip();
}


void EDA_DRAW_PANEL::OnKeyEvent( wxKeyEvent& event )
{
    int localkey;
    wxPoint pos;

    wxLogTrace( kicadTraceKeyEvent, "EDA_DRAW_PANEL::OnKeyEvent %s", dump( event ) );

    localkey = event.GetKeyCode();
    bool keyWasHandled = false;

    switch( localkey )
    {
    default:
        break;

    case WXK_ESCAPE:
        m_abortRequest = true;

        if( IsMouseCaptured() )
            EndMouseCapture();
        else
            EndMouseCapture( ID_NO_TOOL_SELECTED, m_defaultCursor, wxEmptyString );

        keyWasHandled = true;   // The key is captured: the key event must not be skipped
        break;
    }

    /* Normalize keys code to easily handle keys from Ctrl+A to Ctrl+Z
     * They have an ascii code from 1 to 27 remapped
     * to GR_KB_CTRL + 'A' to GR_KB_CTRL + 'Z'
     */
    if( event.ControlDown() && localkey >= WXK_CONTROL_A && localkey <= WXK_CONTROL_Z )
        localkey += 'A' - 1;

    /* Disallow shift for keys that have two keycodes on them (e.g. number and
     * punctuation keys) leaving only the "letter keys" of A-Z.
     * Then, you can have, e.g. Ctrl-5 and Ctrl-% (GB layout)
     * and Ctrl-( and Ctrl-5 (FR layout).
     * Otherwise, you'd have to have to say Ctrl-Shift-5 on a FR layout
     */
    bool keyIsLetter = ( localkey >= 'A' && localkey <= 'Z' ) ||
                       ( localkey >= 'a' && localkey <= 'z' );

    if( event.ShiftDown() && ( keyIsLetter || localkey > 256 ) )
        localkey |= GR_KB_SHIFT;

    if( event.ControlDown() )
        localkey |= GR_KB_CTRL;

    if( event.AltDown() )
        localkey |= GR_KB_ALT;

    INSTALL_UNBUFFERED_DC( DC, this );

    // Some key commands use the current mouse position: refresh it.
    pos = wxGetMousePosition() - GetScreenPosition();

    // Compute the cursor position in drawing units.  Also known as logical units to wxDC.
    pos = wxPoint( DC.DeviceToLogicalX( pos.x ), DC.DeviceToLogicalY( pos.y ) );

    GetParent()->SetMousePosition( pos );

    if( !GetParent()->GeneralControl( &DC, pos, localkey ) && !keyWasHandled )
        event.Skip();   // Skip this event only when the key was not handled
}


void EDA_DRAW_PANEL::OnPan( wxCommandEvent& event )
{
    int x, y;
    int ppux, ppuy;
    int unitsX, unitsY;
    int maxX, maxY;
    int tmpX, tmpY;

    GetViewStart( &x, &y );
    GetScrollPixelsPerUnit( &ppux, &ppuy );
    GetVirtualSize( &unitsX, &unitsY );
    tmpX = x;
    tmpY = y;
    maxX = unitsX;
    maxY = unitsY;
    unitsX /= ppux;
    unitsY /= ppuy;

    wxLogTrace( kicadTraceCoords,
                wxT( "Scroll center position before pan: (%d, %d)" ), tmpX, tmpY );

    switch( event.GetId() )
    {
    case ID_PAN_UP:
        y -= m_scrollIncrementY;
        break;

    case ID_PAN_DOWN:
        y += m_scrollIncrementY;
        break;

    case ID_PAN_LEFT:
        x -= m_scrollIncrementX;
        break;

    case ID_PAN_RIGHT:
        x += m_scrollIncrementX;
        break;

    default:
        wxLogDebug( wxT( "Unknown ID %d in EDA_DRAW_PANEL::OnPan()." ), event.GetId() );
    }

    bool updateCenterScrollPos = true;

    if( x < 0 )
    {
        x = 0;
        updateCenterScrollPos = false;
    }

    if( y < 0 )
    {
        y = 0;
        updateCenterScrollPos = false;
    }

    if( x > maxX )
    {
        x = maxX;
        updateCenterScrollPos = false;
    }

    if( y > maxY )
    {
        y = maxY;
        updateCenterScrollPos = false;
    }

    // Don't update the scroll position beyond the scroll limits.
    if( updateCenterScrollPos )
    {
        double scale = GetParent()->GetScreen()->GetScalingFactor();

        wxPoint center = GetParent()->GetScrollCenterPosition();
        center.x += KiROUND( (double) ( x - tmpX ) / scale );
        center.y += KiROUND( (double) ( y - tmpY ) / scale );
        GetParent()->SetScrollCenterPosition( center );

        wxLogTrace( kicadTraceCoords,
                    wxT( "Scroll center position after pan: (%d, %d)" ), center.x, center.y );
    }

    Scroll( x/ppux, y/ppuy );
}


void EDA_DRAW_PANEL::EndMouseCapture( int id, int cursor, const wxString& title,
                                      bool aCallEndFunc )
{
    if( m_mouseCaptureCallback && m_endMouseCaptureCallback && aCallEndFunc )
    {
        INSTALL_UNBUFFERED_DC( dc, this );
        m_endMouseCaptureCallback( this, &dc );
    }

    m_mouseCaptureCallback = NULL;
    m_endMouseCaptureCallback = NULL;
    SetAutoPanRequest( false );

    if( id != -1 && cursor != -1 )
    {
        wxASSERT( cursor > wxCURSOR_NONE && cursor < wxCURSOR_MAX );
        GetParent()->SetToolID( id, cursor, title );
    }
}


void EDA_DRAW_PANEL::CallMouseCapture( wxDC* aDC, const wxPoint& aPosition, bool aErase )
{
    wxCHECK_RET( aDC != NULL, wxT( "Invalid device context." ) );
    wxCHECK_RET( m_mouseCaptureCallback != NULL, wxT( "Mouse capture callback not set." ) );

    m_mouseCaptureCallback( this, aDC, aPosition, aErase );
}


void EDA_DRAW_PANEL::CallEndMouseCapture( wxDC* aDC )
{
    wxCHECK_RET( aDC != NULL, wxT( "Invalid device context." ) );

    // CallEndMouseCapture is sometimes called with m_endMouseCaptureCallback == NULL
    // for instance after an ABORT in block paste.
    if( m_endMouseCaptureCallback )
        m_endMouseCaptureCallback( this, aDC );
}
