/* Function to move components in a rectangular area format 4 / 3,
 * starting from the mouse cursor
 * The components with the FIXED status set are not moved
 */
void PCB_EDIT_FRAME::SpreadFootprints( bool aFootprintsOutsideBoardOnly )
{
    EDA_RECT bbox = GetBoard()->ComputeBoundingBox( true );
    bool     edgesExist = ( bbox.GetWidth() || bbox.GetHeight() );

    // no edges exist
    if( aFootprintsOutsideBoardOnly && !edgesExist )
    {
        DisplayError( this,
                      _( "Could not automatically place footprints. No board outlines detected." ) );
        return;
    }

    // if aFootprintsOutsideBoardOnly is true, and if board outline exists,
    // wue have to filter footprints to move:
    bool outsideBrdFilter = aFootprintsOutsideBoardOnly && edgesExist;

    // Build candidate list
    // calculate also the area needed by these footprints
    MODULE* module = GetBoard()->m_Modules;
    std::vector <MODULE*> moduleList;

    for( ; module != NULL; module = module->Next() )
    {
        module->CalculateBoundingBox();

        if( outsideBrdFilter )
        {
            if( bbox.Contains( module->GetPosition() ) )
                continue;
        }

        if( module->IsLocked() )
            continue;

        moduleList.push_back(module);
    }

    if( moduleList.size() == 0 )    // Nothing to do
        return;

    // sort footprints by sheet path. we group them later by sheet
    sort( moduleList.begin(), moduleList.end(), sortModulesbySheetPath );

    // Undo command: init undo list
    PICKED_ITEMS_LIST  undoList;
    undoList.m_Status = UR_CHANGED;
    ITEM_PICKER        picker( NULL, UR_CHANGED );

    for( unsigned ii = 0; ii < moduleList.size(); ii++ )
    {
        module = moduleList[ii];

        // Undo: add copy of module to undo list
        picker.SetItem( module );
        picker.SetLink( module->Clone() );
        undoList.PushItem( picker );
    }

    // Extract and place footprints by sheet
    std::vector <MODULE*> moduleListBySheet;
    std::vector <EDA_RECT> placementSheetAreas;
    double subsurface;
    double placementsurface = 0.0;

    wxPoint placementAreaPosition = GetCrossHairPosition();

    // We do not want to move footprints inside an existing board.
    // move the placement area position outside the board bounding box
    // to the left of the board
    if( edgesExist )
    {
        if( placementAreaPosition.x < bbox.GetEnd().x &&
            placementAreaPosition.y < bbox.GetEnd().y )
        {
            placementAreaPosition.x = bbox.GetEnd().x;
            placementAreaPosition.y = bbox.GetOrigin().y;
        }
    }

    // The placement uses 2 passes:
    // the first pass creates the rectangular areas to place footprints
    // each sheet in schematic creates one rectangular area.
    // the second pass moves footprints inside these areas
    for( int pass = 0; pass < 2; pass++ )
    {
        int subareaIdx = 0;
        moduleListBySheet.clear();
        subsurface = 0.0;

        for( unsigned ii = 0; ii < moduleList.size(); ii++ )
        {
            module = moduleList[ii];
            bool islastItem = false;

            if( ii == moduleList.size() - 1 ||
                ( moduleList[ii]->GetPath().BeforeLast( '/' ) !=
                  moduleList[ii+1]->GetPath().BeforeLast( '/' ) ) )
                islastItem = true;

            moduleListBySheet.push_back( module );
            subsurface += module->GetArea();

            if( islastItem )
            {
                // end of the footprint sublist relative to the same sheet path
                // calculate placement of the current sublist
                EDA_RECT freeArea;
                int Xsize_allowed = (int) ( sqrt( subsurface ) * 4.0 / 3.0 );
                int Ysize_allowed = (int) ( subsurface / Xsize_allowed );

                freeArea.SetWidth( Xsize_allowed );
                freeArea.SetHeight( Ysize_allowed );
                CRectPlacement placementArea;

                if( pass == 1 )
                {
                    wxPoint areapos = placementSheetAreas[subareaIdx].GetOrigin()
                                      + placementAreaPosition;
                    freeArea.SetOrigin( areapos );
                }

                bool findAreaOnly = pass == 0;
                moveFootprintsInArea( placementArea, moduleListBySheet,
                                      freeArea, findAreaOnly );

                if( pass == 0 )
                {
                    // Populate sheet placement areas list
                    EDA_RECT sub_area;
                    sub_area.SetWidth( placementArea.GetW()*scale );
                    sub_area.SetHeight( placementArea.GetH()*scale );
                    // Add a margin around the sheet placement area:
                    sub_area.Inflate( Millimeter2iu( 1.5 ) );

                    placementSheetAreas.push_back( sub_area );

                    placementsurface += (double) sub_area.GetWidth()*
                                        sub_area.GetHeight();
                }

                // Prepare buffers for next sheet
                subsurface  = 0.0;
                moduleListBySheet.clear();
                subareaIdx++;
            }
        }

        // End of pass:
        // At the end of the first pass, we have to find position of each sheet
        // placement area
        if( pass == 0 )
        {
            int Xsize_allowed = (int) ( sqrt( placementsurface ) * 4.0 / 3.0 );
            int Ysize_allowed = (int) ( placementsurface / Xsize_allowed );
            CRectPlacement placementArea;
            CSubRectArray  vecSubRects;

            fillRectList( vecSubRects, placementSheetAreas );
            spreadRectangles( placementArea, vecSubRects, Xsize_allowed, Ysize_allowed );

            for( unsigned it = 0; it < vecSubRects.size(); ++it )
            {
                TSubRect& srect = vecSubRects[it];
                wxPoint pos( srect.x*scale, srect.y*scale );
                wxSize size( srect.w*scale, srect.h*scale );
                placementSheetAreas[srect.n].SetOrigin( pos );
                placementSheetAreas[srect.n].SetSize( size );
            }
        }
    }   // End pass

    // Undo: commit list
    SaveCopyInUndoList( undoList, UR_CHANGED );
    OnModify();

    m_canvas->Refresh();
}