
import  cPickle
import  wx

#----------------------------------------------------------------------


class DoodlePad(wx.Window):
    def __init__(self, parent, log):
        wx.Window.__init__(self, parent, -1, style=wx.SUNKEN_BORDER)
        self.log = log
        self.SetBackgroundColour(wx.WHITE)
        self.lines = []
        self.x = self.y = 0
        self.SetMode("Draw")

        self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
        self.Bind(wx.EVT_LEFT_UP,  self.OnLeftUp)
        self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp)
        self.Bind(wx.EVT_MOTION, self.OnMotion)
        self.Bind(wx.EVT_PAINT, self.OnPaint)


    def SetMode(self, mode):
        self.mode = mode
        if self.mode == "Draw":
            self.SetCursor(wx.StockCursor(wx.CURSOR_PENCIL))
        else:
            self.SetCursor(wx.STANDARD_CURSOR)


    def OnPaint(self, event):
        dc = wx.PaintDC(self)
        self.DrawSavedLines(dc)

    def DrawSavedLines(self, dc):
        dc.BeginDrawing()
        dc.SetPen(wx.Pen(wx.BLUE, 3))
        for line in self.lines:
            for coords in line:
                dc.DrawLine(*coords)
        dc.EndDrawing()


    def OnLeftDown(self, event):
        if self.mode == "Drag":
            self.StartDragOpperation()
        elif self.mode == "Draw":
            self.curLine = []
            self.x, self.y = event.GetPositionTuple()
            self.CaptureMouse()
        else:
            wx.Bell()
            self.log.write("unknown mode!\n")


    def OnLeftUp(self, event):
        if self.HasCapture():
            self.lines.append(self.curLine)
            self.curLine = []
            self.ReleaseMouse()

    def OnRightUp(self, event):
        self.lines = []
        self.Refresh()

    def OnMotion(self, event):
        if self.HasCapture() and event.Dragging() and not self.mode == "Drag":
            dc = wx.ClientDC(self)
            dc.BeginDrawing()
            dc.SetPen(wx.Pen(wx.BLUE, 3))
            coords = (self.x, self.y) + event.GetPositionTuple()
            self.curLine.append(coords)
            dc.DrawLine(*coords)
            self.x, self.y = event.GetPositionTuple()
            dc.EndDrawing()


    def StartDragOpperation(self):
        # pickle the lines list
        linesdata = cPickle.dumps(self.lines, 1)

        # create our own data format and use it in a
        # custom data object
        ldata = wx.CustomDataObject("DoodleLines")
        ldata.SetData(linesdata)

        # Also create a Bitmap version of the drawing
        size = self.GetSize()
        bmp = wx.EmptyBitmap(size.width, size.height)
        dc = wx.MemoryDC()
        dc.SelectObject(bmp)
        dc.SetBackground(wx.WHITE_BRUSH)
        dc.Clear()
        self.DrawSavedLines(dc)
        dc.SelectObject(wx.NullBitmap)

        # Now make a data object for the bitmap and also a composite
        # data object holding both of the others.
        bdata = wx.BitmapDataObject(bmp)
        data = wx.DataObjectComposite()
        data.Add(ldata)
        data.Add(bdata)

        # And finally, create the drop source and begin the drag
        # and drop opperation
        dropSource = wx.DropSource(self)
        dropSource.SetData(data)
        self.log.WriteText("Begining DragDrop\n")
        result = dropSource.DoDragDrop(wx.Drag_AllowMove)
        self.log.WriteText("DragDrop completed: %d\n" % result)

        if result == wx.DragMove:
            self.lines = []
            self.Refresh()


#----------------------------------------------------------------------


class DoodleDropTarget(wx.DropTarget):
    def __init__(self, window, log):
        wx.DropTarget.__init__(self)
        self.log = log
        self.dv = window

        # specify the type of data we will accept
        self.data = wx.CustomDataObject("DoodleLines")
        self.SetDataObject(self.data)


    # some virtual methods that track the progress of the drag
    def OnEnter(self, x, y, d):
        self.log.WriteText("OnEnter: %d, %d, %d\n" % (x, y, d))
        return d

    def OnLeave(self):
        self.log.WriteText("OnLeave\n")

    def OnDrop(self, x, y):
        self.log.WriteText("OnDrop: %d %d\n" % (x, y))
        return True

    def OnDragOver(self, x, y, d):
        #self.log.WriteText("OnDragOver: %d, %d, %d\n" % (x, y, d))

        # The value returned here tells the source what kind of visual
        # feedback to give.  For example, if wxDragCopy is returned then
        # only the copy cursor will be shown, even if the source allows
        # moves.  You can use the passed in (x,y) to determine what kind
        # of feedback to give.  In this case we return the suggested value
        # which is based on whether the Ctrl key is pressed.
        return d



    # Called when OnDrop returns True.  We need to get the data and
    # do something with it.
    def OnData(self, x, y, d):
        self.log.WriteText("OnData: %d, %d, %d\n" % (x, y, d))

        # copy the data from the drag source to our data object
        if self.GetData():
            # convert it back to a list of lines and give it to the viewer
            linesdata = self.data.GetData()
            lines = cPickle.loads(linesdata)
            self.dv.SetLines(lines)
            
        # what is returned signals the source what to do
        # with the original data (move, copy, etc.)  In this
        # case we just return the suggested value given to us.
        return d  
        


class DoodleViewer(wx.Window):
    def __init__(self, parent, log):
        wx.Window.__init__(self, parent, -1, style=wx.SUNKEN_BORDER)
        self.log = log
        self.SetBackgroundColour(wx.WHITE)
        self.lines = []
        self.x = self.y = 0
        dt = DoodleDropTarget(self, log)
        self.SetDropTarget(dt)
        self.Bind(wx.EVT_PAINT, self.OnPaint)


    def SetLines(self, lines):
        self.lines = lines
        self.Refresh()

    def OnPaint(self, event):
        dc = wx.PaintDC(self)
        self.DrawSavedLines(dc)

    def DrawSavedLines(self, dc):
        dc.BeginDrawing()
        dc.SetPen(wx.Pen(wx.RED, 3))

        for line in self.lines:
            for coords in line:
                dc.DrawLine(*coords)
        dc.EndDrawing()

#----------------------------------------------------------------------

class CustomDnDPanel(wx.Panel):
    def __init__(self, parent, log):
        wx.Panel.__init__(self, parent, -1)

        self.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD, False))

        # Make the controls
        text1 = wx.StaticText(self, -1,
                            "Draw a little picture in this window\n"
                            "then switch the mode below and drag the\n"
                            "picture to the lower window or to another\n"
                            "application that accepts Bitmaps as a\n"
                             "drop target.\n"
                            )

        rb1 = wx.RadioButton(self, -1, "Draw", style=wx.RB_GROUP)
        rb1.SetValue(True)
        rb2 = wx.RadioButton(self, -1, "Drag")
        rb2.SetValue(False)

        text2 = wx.StaticText(self, -1,
                             "The lower window is accepting a\n"
                             "custom data type that is a pickled\n"
                             "Python list of lines data.")

        self.pad = DoodlePad(self, log)
        view = DoodleViewer(self, log)

        # put them in sizers
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        box = wx.BoxSizer(wx.VERTICAL)
        rbox = wx.BoxSizer(wx.HORIZONTAL)

        rbox.Add(rb1)
        rbox.Add(rb2)
        box.Add(text1, 0, wx.ALL, 10)
        box.Add(rbox, 0, wx.ALIGN_CENTER)
        box.Add((10,90))
        box.Add(text2, 0, wx.ALL, 10)

        sizer.Add(box)

        dndsizer = wx.BoxSizer(wx.VERTICAL)
        dndsizer.Add(self.pad, 1, wx.EXPAND|wx.ALL, 5)
        dndsizer.Add(view, 1, wx.EXPAND|wx.ALL, 5)

        sizer.Add(dndsizer, 1, wx.EXPAND)

        self.SetAutoLayout(True)
        self.SetSizer(sizer)

        # Events
        self.Bind(wx.EVT_RADIOBUTTON, self.OnRadioButton, rb1)
        self.Bind(wx.EVT_RADIOBUTTON, self.OnRadioButton, rb2)


    def OnRadioButton(self, evt):
        rb = self.FindWindowById(evt.GetId())
        self.pad.SetMode(rb.GetLabel())


#----------------------------------------------------------------------
#----------------------------------------------------------------------

class TestPanel(wx.Panel):
    def __init__(self, parent, log):
        wx.Panel.__init__(self, parent, -1)

        self.SetAutoLayout(True)
        sizer = wx.BoxSizer(wx.VERTICAL)

        msg = "Custom Drag-And-Drop"
        text = wx.StaticText(self, -1, "", style=wx.ALIGN_CENTRE)
        text.SetFont(wx.Font(24, wx.SWISS, wx.NORMAL, wx.BOLD, False))
        text.SetLabel(msg)
        w,h = text.GetTextExtent(msg)
        text.SetSize(wx.Size(w,h+1))
        text.SetForegroundColour(wx.BLUE)
        sizer.Add(text, 0, wx.EXPAND|wx.ALL, 5)
        sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND)

        sizer.Add(CustomDnDPanel(self, log), 1, wx.EXPAND)

        self.SetSizer(sizer)


#----------------------------------------------------------------------

def runTest(frame, nb, log):
    #win = TestPanel(nb, log)
    win = CustomDnDPanel(nb, log)
    return win


if __name__ == '__main__':
    import sys
    
    class DummyLog:
        def WriteText(self, text):
            sys.stdout.write(text)

    class TestApp(wx.App):
        def OnInit(self):
            self.MakeFrame()
            return True

        def MakeFrame(self, event=None):
            frame = wx.Frame(None, -1, "Custom Drag and Drop", size=(550,400))
            menu = wx.Menu()
            item = menu.Append(-1, "Window")
            mb = wx.MenuBar()
            mb.Append(menu, "New")
            frame.SetMenuBar(mb)
            frame.Bind(wx.EVT_MENU, self.MakeFrame, item)
            panel = TestPanel(frame, DummyLog())
            frame.Show(True)
            self.SetTopWindow(frame)

    #----------------------------------------------------------------------

    app = TestApp(0)
    app.MainLoop()

#----------------------------------------------------------------------


overview = """<html><body>
This demo shows Drag and Drop using a custom data type and a custom
data object.  A type called "DoodleLines" is created and a Python
Pickle of a list is actually transfered in the drag and drop
opperation.

A second data object is also created containing a bitmap of the image
and is made available to any drop target that accepts bitmaps, such as
MS Word.

The two data objects are combined in a wx.DataObjectComposite and the
rest is handled by the framework.
</body></html>
"""

