/******************************************************************/
/* drawpanel.cpp - fonctions des classes du type WinEDA_DrawPanel */
/******************************************************************/

#ifdef __GNUG__
#pragma implementation
#endif

#include "fctsys.h"
#include "common.h"


/* defines locaux */
#define CURSOR_SIZE 12  /* taille de la croix du curseur PCB */

/*******************************************************/
/* Class BASE_SCREEN: classe de gestion d'un affichage */
/*******************************************************/
BASE_SCREEN::BASE_SCREEN( int idscreen, KICAD_T aType ) : 
    EDA_BaseStruct( aType )
{
    EEDrawList = NULL;   /* Schematic items list */
    m_Type     = idscreen;
    m_ZoomList = NULL;
    m_GridList = NULL;
    m_UndoList = NULL;
    m_RedoList = NULL;
    m_UndoRedoCountMax = 1;
    m_FirstRedraw = TRUE;
    InitDatas();
}


/******************************/
BASE_SCREEN::~BASE_SCREEN()
/******************************/
{
    if( m_ZoomList )
        free( m_ZoomList );
    
    if( m_GridList )
        free( m_GridList );
    
    ClearUndoRedoList();
}


/*******************************/
void BASE_SCREEN::InitDatas()
/*******************************/
{
    m_SheetNumber = m_NumberOfSheet = 1;    /* gestion hierarchie: Root: SheetNumber = 1 */
    m_Zoom            = 32;
    m_Grid            = wxSize( 50, 50 );   /* pas de la grille */
    m_UserGrid        = g_UserGrid;         /* pas de la grille "utilisateur" */
    m_UserGridIsON    = FALSE;
    m_UserGridUnit    = g_UserGrid_Unit;
    m_Diviseur_Grille = 1;
    m_Center          = TRUE;

    /* offsets pour tracer le circuit sur l'ecran */
    switch( m_Type ) // Init taille sheet par defaut
    {
    case SCHEMATIC_FRAME:
        m_Center = FALSE;
        m_CurrentSheet = &g_Sheet_A4;
        break;

    default:
    case CVPCB_DISPLAY_FRAME:
    case MODULE_EDITOR_FRAME:
    case PCB_FRAME:
        m_CurrentSheet = &g_Sheet_A4;
        break;

    case GERBER_FRAME:
        m_CurrentSheet = &g_Sheet_GERBER;
        break;
    }

    if( m_Center )
    {
        m_Curseur.x = m_Curseur.y = 0;
        m_DrawOrg.x = -ReturnPageSize().x / 2;
        m_DrawOrg.y = -ReturnPageSize().y / 2;
    }
    else
    {
        m_DrawOrg.x = m_DrawOrg.y = 0;
        m_Curseur.x = ReturnPageSize().x / 2;
        m_Curseur.y = ReturnPageSize().y / 2;
    }

    // DrawOrg est rendu multiple du zoom min :
    m_DrawOrg.x -= m_DrawOrg.x % 256; 
    m_DrawOrg.y -= m_DrawOrg.y % 256;

    m_O_Curseur = m_Curseur;

    SetCurItem( NULL );

    /* indicateurs divers */
    m_FlagRefreshReq = 0;               /* indique que l'ecran doit redessine */
    m_FlagModified   = 0;               // indique modif du PCB,utilise pour eviter une sortie sans sauvegarde
    m_FlagSave = 1;                     // indique sauvegarde auto faite
}


/******************************************************************/
wxPoint BASE_SCREEN::CursorRealPosition( const wxPoint& ScreenPos )
/******************************************************************/
{
    wxPoint curpos;

    curpos.x = ScreenPos.x * GetZoom();
    curpos.y = ScreenPos.y * GetZoom();

    curpos.x += m_DrawOrg.x;
    curpos.y += m_DrawOrg.y;

    return curpos;
}


/***************************************/
int BASE_SCREEN::GetInternalUnits()
/***************************************/
{
    switch( m_Type )
    {
    default:
    case SCHEMATIC_FRAME:
        return EESCHEMA_INTERNAL_UNIT;
        break;

    case GERBER_FRAME:
    case CVPCB_DISPLAY_FRAME:
    case MODULE_EDITOR_FRAME:
    case PCB_FRAME:
        return PCB_INTERNAL_UNIT;
    }
}


/*****************************************/
wxSize BASE_SCREEN::ReturnPageSize()
/*****************************************/

/* Retourne en unites internes la taille de la feuille de dessin
 *  (la taille de la feuille est connue en 1/1000 ")
 */
{
    wxSize PageSize;

    switch( m_Type )
    {
    default:
    case SCHEMATIC_FRAME:
        PageSize = m_CurrentSheet->m_Size;
        break;

    case GERBER_FRAME:
    case CVPCB_DISPLAY_FRAME:
    case MODULE_EDITOR_FRAME:
    case PCB_FRAME:
        PageSize.x = m_CurrentSheet->m_Size.x * (PCB_INTERNAL_UNIT / 1000);
        PageSize.y = m_CurrentSheet->m_Size.y * (PCB_INTERNAL_UNIT / 1000);
        break;
    }

    return PageSize;
}


/********************************************/
void BASE_SCREEN::SetZoomList( int* zoomlist )
/********************************************/

/* init liste des zoom (NULL terminated)
 */
{
    int ii, nbitems, * zoom;

    // Decompte des items
    for( nbitems = 1, zoom = zoomlist;  ; zoom++, nbitems++ )
    {
        if( *zoom == 0 )
            break;
    }

    // Init liste
    if( m_ZoomList )
        free( m_ZoomList );
    
    m_ZoomList = (int*) MyZMalloc( nbitems * sizeof( int) );

    for( ii = 0, zoom = zoomlist; ii < nbitems; zoom++, ii++ )
    {
        m_ZoomList[ii] = *zoom;
    }
}


/***********************************/
void BASE_SCREEN::SetFirstZoom()
/***********************************/
/* ajuste le coeff de zoom a 1*/
{
    m_Zoom = 1;
}


/****************************/
int BASE_SCREEN::GetZoom()
/****************************/
/* retourne le coeff de zoom */
{
    return m_Zoom;
}


/***********************************/
void BASE_SCREEN::SetZoom( int coeff )
/***********************************/
/* ajuste le coeff de zoom a coeff */
{
    m_Zoom = coeff;
    if( m_Zoom < 1 )
        m_Zoom = 1;
}


/********************************/
void BASE_SCREEN::SetNextZoom()
/********************************/

/* Selectionne le prochain coeff de zoom
 */
{
    m_Zoom *= 2;

    if( m_ZoomList == NULL )
        return;

    int ii, zoom_max = 512;
    for( ii = 0; m_ZoomList[ii] != 0; ii++ )
        zoom_max = m_ZoomList[ii];

    if( m_Zoom > zoom_max )
        m_Zoom = zoom_max;
}


/*************************************/
void BASE_SCREEN::SetPreviousZoom()
/*************************************/

/* Selectionne le precedent coeff de zoom
 */
{
    m_Zoom /= 2;
    if( m_Zoom < 1 )
        m_Zoom = 1;
}


/**********************************/
void BASE_SCREEN::SetLastZoom()
/**********************************/

/* ajuste le coeff de zoom au max
 */
{
    if( m_ZoomList == NULL )
        return;
    int ii;
    for( ii = 0; m_ZoomList[ii] != 0; ii++ )
        m_Zoom = m_ZoomList[ii];
}


/********************************************/
void BASE_SCREEN::SetGridList( wxSize* gridlist )
/********************************************/

/* init liste des zoom (NULL terminated)
 */
{
    int     ii, nbitems;
    wxSize* grid;

    // Decompte des items
    for( nbitems = 0, grid = gridlist;  ; grid++, nbitems++ )
    {
        if( (grid->x <= 0) || (grid->y <= 0) )
            break;
    }

    // Init liste
    if( m_GridList )
        free( m_GridList );
    m_GridList = (wxSize*) MyZMalloc( nbitems * sizeof(wxSize) );

    for( ii = 0, grid = gridlist; ii < nbitems; grid++, ii++ )
    {
        m_GridList[ii] = *grid;
    }
}


/**********************************************/
void BASE_SCREEN::SetGrid( const wxSize& size )
/**********************************************/
{
    if( m_GridList == NULL )
        return;

    if( (size.x <= 0) || (size.y <= 0) )
    {
        m_UserGrid     = g_UserGrid;
        m_UserGridIsON = TRUE;
    }
    else
    {
        m_Grid = size;
        m_UserGridIsON = FALSE;
    }
}


/*********************************/
wxSize BASE_SCREEN::GetGrid()
/*********************************/
{
    wxSize grid = m_Grid;
    double xx, scale;

    if( m_GridList == NULL )
        return wxSize( 1, 1 );

    if( m_UserGridIsON || m_Grid.x < 0 || m_Grid.y < 0 )
    {
        if( m_UserGridUnit == INCHES )
            scale = 10000;
        else
            scale = 10000 / 25.4;
        xx     = m_UserGrid.x * scale;
        grid.x = (int) (xx + 0.5);
        xx     = m_UserGrid.y * scale;
        grid.y = (int) (xx + 0.5);
    }
    return grid;
}


/*********************************/
void BASE_SCREEN::SetNextGrid()
/*********************************/

/* Selectionne la prochaine grille
 */
{
    int ii;

    if( m_GridList == NULL )
        return;

    for( ii = 0; ; ii++ )
    {
        if( m_GridList[ii].x <= 0 )
            break;
        if( (m_Grid.x == m_GridList[ii].x) && (m_Grid.y == m_GridList[ii].y) )
            break;
    }

    if( (m_GridList[ii].x > 0) && (ii > 0) )
        m_Grid = m_GridList[ii - 1];
}


/*************************************/
void BASE_SCREEN::SetPreviousGrid()
/*************************************/

/* Selectionne le precedent coeff de grille
 */
{
    int ii;

    if( m_GridList == NULL )
        return;

    for( ii = 0; ; ii++ )
    {
        if( m_GridList[ii].x <= 0 )
            break;
        if( (m_Grid.x == m_GridList[ii].x) && (m_Grid.y == m_GridList[ii].y) )
            break;
    }

    if( (m_GridList[ii].x > 0) && (m_GridList[ii + 1].x > 0) )
        m_Grid = m_GridList[ii + 1];
}


/**********************************/
void BASE_SCREEN::SetFirstGrid()
/**********************************/

/* ajuste le coeff de grille a 1
 */
{
    if( m_GridList == NULL )
        return;

    int ii = 0;
    while( m_GridList[ii].x > 0 )
        ii++;

    m_Grid = m_GridList[ii - 1];
}


/**********************************/
void BASE_SCREEN::SetLastGrid()
/**********************************/

/* ajuste le coeff de grille au max
 */
{
    if( m_GridList == NULL )
        return;
    m_Grid = m_GridList[0];
}


/*****************************************/
void BASE_SCREEN::ClearUndoRedoList()
/*****************************************/

/* free the undo and the redo lists
 */
{
    EDA_BaseStruct* nextitem;

    while( m_UndoList )
    {
        nextitem = m_UndoList->Pnext;
        delete m_UndoList;
        m_UndoList = nextitem;
    }

    while( m_RedoList )
    {
        nextitem = m_RedoList->Pnext;
        delete m_RedoList;
        m_RedoList = nextitem;
    }
}


/***********************************************************/
void BASE_SCREEN::AddItemToUndoList( EDA_BaseStruct* newitem )
/************************************************************/

/* Put newitem in head of undo list
 *  Deletes olds items if > count max.
 */
{
    int             ii;
    EDA_BaseStruct* item, * nextitem;

    if( newitem == NULL )
        return;

    newitem->Pnext = m_UndoList;
    m_UndoList = newitem;

    /* Free first items, if count max reached */
    for( ii = 0, item = m_UndoList; ii < m_UndoRedoCountMax; ii++ )
    {
        if( item->Pnext == NULL )
            return;
        item = item->Pnext;
    }

    if( item == NULL )
        return;

    nextitem    = item->Pnext;
    item->Pnext = NULL; // Set end of chain

    // Delete the extra  items
    for( item = nextitem; item != NULL; item = nextitem )
    {
        nextitem = item->Pnext;
        delete item;
    }
}


/***********************************************************/
void BASE_SCREEN::AddItemToRedoList( EDA_BaseStruct* newitem )
/***********************************************************/
{
    int             ii;
    EDA_BaseStruct* item, * nextitem;

    if( newitem == NULL )
        return;

    newitem->Pnext = m_RedoList;
    m_RedoList = newitem;
    /* Free first items, if count max reached */
    for( ii = 0, item = m_RedoList; ii < m_UndoRedoCountMax; ii++ )
    {
        if( item->Pnext == NULL )
            break;
        item = item->Pnext;
    }

    if( item == NULL )
        return;

    nextitem    = item->Pnext;
    item->Pnext = NULL; // Set end of chain

    // Delete the extra items
    for( item = nextitem; item != NULL; item = nextitem )
    {
        nextitem = item->Pnext;
        delete item;
    }
}


/*****************************************************/
EDA_BaseStruct* BASE_SCREEN::GetItemFromUndoList()
/*****************************************************/
{
    EDA_BaseStruct* item = m_UndoList;

    if( item )
        m_UndoList = item->Pnext;
    return item;
}


/******************************************************/
EDA_BaseStruct* BASE_SCREEN::GetItemFromRedoList()
/******************************************************/
{
    EDA_BaseStruct* item = m_RedoList;

    if( item )
        m_RedoList = item->Pnext;
    return item;
}


#if defined(DEBUG)
/**
 * Function Show
 * is used to output the object tree, currently for debugging only.
 * @param nestLevel An aid to prettier tree indenting, and is the level 
 *          of nesting of this object within the overall tree.
 * @param os The ostream& to output to.
 */
void BASE_SCREEN::Show( int nestLevel, std::ostream& os )
{
    EDA_BaseStruct* item = EEDrawList;
    
    // for now, make it look like XML, expand on this later.
    NestedSpace( nestLevel, os ) << '<' << GetClass().Lower().mb_str() <<
        ">\n";
    
    for(  ; item;  item = item->Next() )
    {
        item->Show( nestLevel+1, os );
    }
    
    NestedSpace( nestLevel, os ) << "</" << GetClass().Lower().mb_str() << ">\n";
}
#endif

