/////////////////////////////////////////////////////////////////////////////
// Name:        dragimag.cpp
// Purpose:     wxDragImage sample
// Author:      Julian Smart
// Created:     28/2/2000
// Copyright:   (c) Julian Smart
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"


#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif

#include "wx/image.h"

// Under Windows, change this to 1
// to use wxGenericDragImage

#define wxUSE_GENERIC_DRAGIMAGE 0

#if wxUSE_GENERIC_DRAGIMAGE
#include "wx/generic/dragimgg.h"
#define wxDragImage wxGenericDragImage
#else
#include "wx/dragimag.h"
#endif

#include "dragimag.h"

#ifndef wxHAS_IMAGES_IN_RESOURCES
    #include "../sample.xpm"
    #include "dragicon.xpm"
#endif

// main program

wxIMPLEMENT_APP(MyApp);

// MyCanvas

wxIMPLEMENT_CLASS(MyCanvas, wxScrolledWindow);

wxBEGIN_EVENT_TABLE(MyCanvas, wxScrolledWindow)
  EVT_PAINT(MyCanvas::OnPaint)
  EVT_ERASE_BACKGROUND(MyCanvas::OnEraseBackground)
  EVT_MOUSE_EVENTS(MyCanvas::OnMouseEvent)
wxEND_EVENT_TABLE()

MyCanvas::MyCanvas( wxWindow *parent, wxWindowID id,
                    const wxPoint &pos, const wxSize &size )
        : wxScrolledWindow( parent, id, pos, size, wxSUNKEN_BORDER )
{
    SetBackgroundColour(* wxWHITE);

    SetCursor(wxCursor(wxCURSOR_ARROW));

    m_dragMode = TEST_DRAG_NONE;
    m_draggedShape = nullptr;
    m_dragImage = nullptr;
    m_currentlyHighlighted = nullptr;
}

MyCanvas::~MyCanvas()
{
    ClearShapes();

    if (m_dragImage)
        delete m_dragImage;
}

void MyCanvas::OnPaint( wxPaintEvent &WXUNUSED(event) )
{
    wxPaintDC dc( this );
    PrepareDC( dc );

    DrawShapes(dc);
}

void MyCanvas::OnEraseBackground(wxEraseEvent& event)
{
    if (wxGetApp().GetBackgroundBitmap().IsOk())
    {
        wxSize sz = GetClientSize();
        wxRect rect(0, 0, sz.x, sz.y);

        if (event.GetDC())
        {
            wxGetApp().TileBitmap(rect, *(event.GetDC()), wxGetApp().GetBackgroundBitmap());
        }
        else
        {
            wxClientDC dc(this);
            wxGetApp().TileBitmap(rect, dc, wxGetApp().GetBackgroundBitmap());
        }
    }
    else
        event.Skip(); // The official way of doing it
}

void MyCanvas::OnMouseEvent(wxMouseEvent& event)
{
    if (event.LeftDown())
    {
        DragShape* shape = FindShape(event.GetPosition());
        if (shape)
        {
            // We tentatively start dragging, but wait for
            // mouse movement before dragging properly.

            m_dragMode = TEST_DRAG_START;
            m_dragStartPos = event.GetPosition();
            m_draggedShape = shape;
        }
    }
    else if (event.LeftUp() && m_dragMode != TEST_DRAG_NONE)
    {
        // Finish dragging

        m_dragMode = TEST_DRAG_NONE;

        if (!m_draggedShape || !m_dragImage)
            return;

        m_draggedShape->SetPosition(m_draggedShape->GetPosition()
                                    + event.GetPosition() - m_dragStartPos);

        m_dragImage->Hide();
        m_dragImage->EndDrag();
        wxDELETE(m_dragImage);

        m_draggedShape->SetShow(true);

        m_currentlyHighlighted = nullptr;

        m_draggedShape = nullptr;

        Refresh(true);
    }
    else if (event.Dragging() && m_dragMode != TEST_DRAG_NONE)
    {
        if (m_dragMode == TEST_DRAG_START)
        {
            // We will start dragging if we've moved beyond a couple of pixels

            int tolerance = 2;
            int dx = abs(event.GetPosition().x - m_dragStartPos.x);
            int dy = abs(event.GetPosition().y - m_dragStartPos.y);
            if (dx <= tolerance && dy <= tolerance)
                return;

            // Start the drag.
            m_dragMode = TEST_DRAG_DRAGGING;

            if (m_dragImage)
                delete m_dragImage;

            // Erase the dragged shape from the canvas
            m_draggedShape->SetShow(false);

            // redraw immediately
            Refresh(true);
            Update();

            switch (m_draggedShape->GetDragMethod())
            {
                case SHAPE_DRAG_BITMAP:
                {
                    m_dragImage = new MyDragImage(this, m_draggedShape->GetBitmap(), wxCursor(wxCURSOR_HAND));
                    break;
                }
                case SHAPE_DRAG_TEXT:
                {
                    m_dragImage = new MyDragImage(this, wxString("Dragging some test text"), wxCursor(wxCURSOR_HAND));
                    break;
                }
                case SHAPE_DRAG_ICON:
                {
                    m_dragImage = new MyDragImage(this, wxICON(dragicon), wxCursor(wxCURSOR_HAND));
                    break;
                }
            }

            bool fullScreen = wxGetApp().GetUseScreen();

            // The offset between the top-left of the shape image and the current shape position
            wxPoint beginDragHotSpot = m_dragStartPos - m_draggedShape->GetPosition();

            // Now we do this inside the implementation: always assume
            // coordinates relative to the capture window (client coordinates)

            //if (fullScreen)
            //    beginDragHotSpot -= ClientToScreen(wxPoint(0, 0));

            if (!m_dragImage->BeginDrag(beginDragHotSpot, this, fullScreen))
            {
                wxDELETE(m_dragImage);
                m_dragMode = TEST_DRAG_NONE;

            } else
            {
                m_dragImage->Move(event.GetPosition());
                m_dragImage->Show();
            }
        }
        else if (m_dragMode == TEST_DRAG_DRAGGING)
        {
            // We're currently dragging. See if we're over another shape.
            DragShape* onShape = FindShape(event.GetPosition());

            bool mustUnhighlightOld = false;
            bool mustHighlightNew = false;

            if (m_currentlyHighlighted)
            {
                if ((onShape == nullptr) || (m_currentlyHighlighted != onShape))
                    mustUnhighlightOld = true;
            }

            if (onShape && (onShape != m_currentlyHighlighted) && onShape->IsShown())
                mustHighlightNew = true;

            if (mustUnhighlightOld || mustHighlightNew)
                m_dragImage->Hide();

            // Now with the drag image switched off, we can change the window contents.
            if (mustUnhighlightOld)
                m_currentlyHighlighted = nullptr;

            if (mustHighlightNew)
                m_currentlyHighlighted = onShape;

            if (mustUnhighlightOld || mustHighlightNew)
            {
                Refresh(mustUnhighlightOld);
                Update();
            }

            // Move and show the image again
            m_dragImage->Move(event.GetPosition());

            if (mustUnhighlightOld || mustHighlightNew)
                 m_dragImage->Show();
        }
    }
}

void MyCanvas::DrawShapes(wxDC& dc)
{
    wxList::compatibility_iterator node = m_displayList.GetFirst();
    while (node)
    {
        DragShape* shape = (DragShape*) node->GetData();
        if (shape->IsShown() && m_draggedShape != shape)
        {
            shape->Draw(dc, (m_currentlyHighlighted == shape));
        }
        node = node->GetNext();
    }
}

void MyCanvas::EraseShape(DragShape* shape, wxDC& dc)
{
    wxSize sz = GetClientSize();
    wxRect rect(0, 0, sz.x, sz.y);

    wxRect rect2(shape->GetRect());
    dc.SetClippingRegion(rect2.x, rect2.y, rect2.width, rect2.height);

    wxGetApp().TileBitmap(rect, dc, wxGetApp().GetBackgroundBitmap());

    dc.DestroyClippingRegion();
}

void MyCanvas::ClearShapes()
{
    wxList::compatibility_iterator node = m_displayList.GetFirst();
    while (node)
    {
        DragShape* shape = (DragShape*) node->GetData();
        delete shape;
        node = node->GetNext();
    }
    m_displayList.Clear();
}

DragShape* MyCanvas::FindShape(const wxPoint& pt) const
{
    wxList::compatibility_iterator node = m_displayList.GetFirst();
    while (node)
    {
        DragShape* shape = (DragShape*) node->GetData();
        if (shape->HitTest(pt))
            return shape;
        node = node->GetNext();
    }
    return nullptr;
}

// MyFrame
wxIMPLEMENT_DYNAMIC_CLASS(MyFrame, wxFrame);

wxBEGIN_EVENT_TABLE(MyFrame,wxFrame)
  EVT_MENU    (wxID_ABOUT, MyFrame::OnAbout)
  EVT_MENU    (wxID_EXIT,  MyFrame::OnQuit)
wxEND_EVENT_TABLE()

MyFrame::MyFrame()
: wxFrame(nullptr, wxID_ANY, "wxDragImage sample",
          wxPoint(20,20), wxSize(470,360) )
{
    wxMenu *file_menu = new wxMenu();
    file_menu->Append( wxID_ABOUT, "&About");
    file_menu->AppendCheckItem( TEST_USE_SCREEN, "&Use whole screen for dragging", "Use whole screen");
    file_menu->Append( wxID_EXIT, "E&xit");

    wxMenuBar *menu_bar = new wxMenuBar();
    menu_bar->Append(file_menu, "&File");

    SetIcon(wxICON(sample));
    SetMenuBar( menu_bar );

#if wxUSE_STATUSBAR
    CreateStatusBar(2);
    int widths[] = { -1, 100 };
    SetStatusWidths( 2, widths );
#endif // wxUSE_STATUSBAR

    m_canvas = new MyCanvas( this, wxID_ANY, wxPoint(0,0), wxSize(10,10) );
}

void MyFrame::OnQuit( wxCommandEvent &WXUNUSED(event) )
{
    Close( true );
}

void MyFrame::OnAbout( wxCommandEvent &WXUNUSED(event) )
{
    (void)wxMessageBox( "wxDragImage demo\n"
        "Julian Smart (c) 2000",
        "About wxDragImage Demo",
        wxICON_INFORMATION | wxOK );
}

//-----------------------------------------------------------------------------
// MyApp
//-----------------------------------------------------------------------------

wxBEGIN_EVENT_TABLE(MyApp, wxApp)
    EVT_MENU(TEST_USE_SCREEN, MyApp::OnUseScreen)
wxEND_EVENT_TABLE()

MyApp::MyApp()
{
    // Drag across whole screen
    m_useScreen = false;
}

bool MyApp::OnInit()
{
    if ( !wxApp::OnInit() )
        return false;

#if wxUSE_LIBPNG
    wxImage::AddHandler( new wxPNGHandler );
#endif

    wxImage image;
    if (image.LoadFile("backgrnd.png", wxBITMAP_TYPE_PNG))
    {
        m_background = wxBitmap(image);
    }

    MyFrame *frame = new MyFrame();

    wxString rootName("shape0");

    for (int i = 1; i < 4; i++)
    {
    /* For some reason under wxX11, the 2nd LoadFile in this loop fails, with
       a BadMatch inside CreateFromImage (inside ConvertToBitmap). This happens even if you copy
       the first file over the second file. */
        if (image.LoadFile(wxString::Format("%s%d.png", rootName, i), wxBITMAP_TYPE_PNG))
        {
            DragShape* newShape = new DragShape(wxBitmap(image));
            newShape->SetPosition(wxPoint(i*50, i*50));

            if (i == 2)
                newShape->SetDragMethod(SHAPE_DRAG_TEXT);
            else if (i == 3)
                newShape->SetDragMethod(SHAPE_DRAG_ICON);
            else
                newShape->SetDragMethod(SHAPE_DRAG_BITMAP);
            frame->GetCanvas()->GetDisplayList().Append(newShape);
        }
    }

    frame->Show( true );

    return true;
}

int MyApp::OnExit()
{
    return 0;
}

bool MyApp::TileBitmap(const wxRect& rect, wxDC& dc, const wxBitmap& bitmap)
{
    int w = bitmap.GetWidth();
    int h = bitmap.GetHeight();

    int i, j;
    for (i = rect.x; i < rect.x + rect.width; i += w)
    {
        for (j = rect.y; j < rect.y + rect.height; j+= h)
            dc.DrawBitmap(bitmap, i, j);
    }
    return true;
}

void MyApp::OnUseScreen(wxCommandEvent& WXUNUSED(event))
{
    m_useScreen = !m_useScreen;
}

// DragShape

DragShape::DragShape(const wxBitmap& bitmap)
{
    m_bitmap = bitmap;
    m_pos.x = 0;
    m_pos.y = 0;
    m_dragMethod = SHAPE_DRAG_BITMAP;
    m_show = true;
}

bool DragShape::HitTest(const wxPoint& pt) const
{
    wxRect rect(GetRect());
    return rect.Contains(pt.x, pt.y);
}

bool DragShape::Draw(wxDC& dc, bool highlight)
{
    if (m_bitmap.IsOk())
    {
        wxMemoryDC memDC;
        memDC.SelectObject(m_bitmap);

        dc.Blit(m_pos.x, m_pos.y, m_bitmap.GetWidth(), m_bitmap.GetHeight(),
            & memDC, 0, 0, wxCOPY, true);

        if (highlight)
        {
            dc.SetPen(*wxWHITE_PEN);
            dc.SetBrush(*wxTRANSPARENT_BRUSH);
            dc.DrawRectangle(m_pos.x, m_pos.y, m_bitmap.GetWidth(), m_bitmap.GetHeight());
        }

        return true;
    }
    else
        return false;
}

#if wxUSE_GENERIC_DRAGIMAGE
// MyDragImage

// On some platforms, notably Mac OS X with Core Graphics, we can't blit from
// a window, so we need to draw the background explicitly.
bool MyDragImage::UpdateBackingFromWindow(wxDC& WXUNUSED(windowDC), wxMemoryDC& destDC, const wxRect& WXUNUSED(sourceRect),
                    const wxRect& destRect) const
{
    destDC.SetClippingRegion(destRect);

    if (wxGetApp().GetBackgroundBitmap().IsOk())
        wxGetApp().TileBitmap(destRect, destDC, wxGetApp().GetBackgroundBitmap());

    m_canvas->DrawShapes(destDC);
    return true;
}
#endif
