#------------------------------------------------------------------------------
#
#  Copyright (c) 2006, Enthought, Inc.
#  All rights reserved.
#
#  This software is provided without warranty under the terms of the BSD
#  license included in enthought/LICENSE.txt and may be redistributed only
#  under the conditions described in the aforementioned license.  The license
#  is also available online at http://www.enthought.com/licenses/BSD.txt
#
#  Thanks for using Enthought open source!
#
#  Author: David C. Morrill
#  Date:   06/25/2006
#
#------------------------------------------------------------------------------

""" Defines the various editors for a drag-and-drop editor,
    for the wxPython user interface toolkit. A drag-and-drop editor represents
    its value as a simple image which, depending upon the editor style, can be
    a drag source only, a drop target only, or both a drag source and a drop
    target.
"""

#-------------------------------------------------------------------------
#  Imports:
#-------------------------------------------------------------------------

import wx
import numpy

from cPickle \
    import load

from traits.api \
    import Bool

# FIXME: ToolkitEditorFactory is a proxy class defined here just for backward
# compatibility. The class has been moved to the
# traitsui.editors.dnd_editor file.
from traitsui.editors.dnd_editor \
    import ToolkitEditorFactory

from pyface.wx.drag_and_drop \
    import PythonDropSource, PythonDropTarget, clipboard

try:
    from apptools.io import File
except ImportError:
    File = None

try:
    from apptools.naming.api import Binding
except ImportError:
    Binding = None

from pyface.image_resource \
    import ImageResource

from editor \
    import Editor

#-------------------------------------------------------------------------
#  Constants:
#-------------------------------------------------------------------------

# The image to use when the editor accepts files:
file_image = ImageResource('file').create_image()

# The image to use when the editor accepts objects:
object_image = ImageResource('object').create_image()

# The image to use when the editor is disabled:
inactive_image = ImageResource('inactive').create_image()

# String types:
string_type = (str, unicode)

#-------------------------------------------------------------------------
#  'SimpleEditor' class:
#-------------------------------------------------------------------------


class SimpleEditor(Editor):
    """ Simply style of editor for a drag-and-drop editor, which is both a drag
        source and a drop target.
    """

    #-------------------------------------------------------------------------
    #  Trait definitions:
    #-------------------------------------------------------------------------

    # Is the editor a drop target?
    drop_target = Bool(True)

    # Is the editor a drag source?
    drag_source = Bool(True)

    #-------------------------------------------------------------------------
    #  Finishes initializing the editor by creating the underlying toolkit
    #  widget:
    #-------------------------------------------------------------------------

    def init(self, parent):
        """ Finishes initializing the editor by creating the underlying toolkit
            widget.
        """
        # Determine the drag/drop type:
        value = self.value
        self._is_list = isinstance(value, list)
        self._is_file = (isinstance(value, string_type) or
                         (self._is_list and (len(value) > 0) and
                          isinstance(value[0], string_type)))

        # Get the right image to use:
        image = self.factory.image
        if image is not None:
            image = image.create_image()
            disabled_image = self.factory.disabled_image
            if disabled_image is not None:
                disabled_image = disabled_image.create_image()
        else:
            disabled_image = inactive_image
            image = object_image
            if self._is_file:
                image = file_image

        self._image = image.ConvertToBitmap()
        if disabled_image is not None:
            self._disabled_image = disabled_image.ConvertToBitmap()
        else:
            data = numpy.reshape(numpy.fromstring(image.GetData(),
                                                  numpy.uint8),
                                 (-1, 3)) * numpy.array([[0.297, 0.589, 0.114]])
            g = data[:, 0] + data[:, 1] + data[:, 2]
            data[:, 0] = data[:, 1] = data[:, 2] = g
            image.SetData(numpy.ravel(data.astype(numpy.uint8)).tostring())
            image.SetMaskColour(0, 0, 0)
            self._disabled_image = image.ConvertToBitmap()

        # Create the control and set up the event handlers:
        self.control = control = wx.Window(
            parent, -1, size=wx.Size(image.GetWidth(), image.GetHeight()))
        self.set_tooltip()

        if self.drop_target:
            control.SetDropTarget(PythonDropTarget(self))

        wx.EVT_LEFT_DOWN(control, self._left_down)
        wx.EVT_LEFT_UP(control, self._left_up)
        wx.EVT_MOTION(control, self._mouse_move)
        wx.EVT_PAINT(control, self._on_paint)

    #-------------------------------------------------------------------------
    #  Disposes of the contents of an editor:
    #-------------------------------------------------------------------------

    def dispose(self):
        """ Disposes of the contents of an editor.
        """
        control = self.control
        wx.EVT_LEFT_DOWN(control, None)
        wx.EVT_LEFT_UP(control, None)
        wx.EVT_MOTION(control, None)
        wx.EVT_PAINT(control, None)

        super(SimpleEditor, self).dispose()

    #-------------------------------------------------------------------------
    #  Updates the editor when the object trait changes external to the editor:
    #-------------------------------------------------------------------------

    def update_editor(self):
        """ Updates the editor when the object trait changes externally to the
            editor.
        """
        return

#-- Private Methods ------------------------------------------------------

    #-------------------------------------------------------------------------
    #  Returns the processed version of a drag request's data:
    #-------------------------------------------------------------------------

    def _get_drag_data(self, data):
        """ Returns the processed version of a drag request's data.
        """
        if isinstance(data, list):

            if Binding is not None and isinstance(data[0], Binding):
                data = [item.obj for item in data]

            if File is not None and isinstance(data[0], File):
                data = [item.absolute_path for item in data]
                if not self._is_file:
                    result = []
                    for file in data:
                        item = self._unpickle(file)
                        if item is not None:
                            result.append(item)
                    data = result

        else:
            if Binding is not None and isinstance(data, Binding):
                data = data.obj

            if File is not None and isinstance(data, File):
                data = data.absolute_path
                if not self._is_file:
                    object = self._unpickle(data)
                    if object is not None:
                        data = object

        return data

    #-------------------------------------------------------------------------
    #  Returns the unpickled version of a specified file (if possible):
    #-------------------------------------------------------------------------

    def _unpickle(self, file_name):
        """ Returns the unpickled version of a specified file (if possible).
        """
        fh = None
        try:
            fh = open(file_name, 'rb')
            object = load(fh)
        except:
            object = None

        if fh is not None:
            fh.close()

        return object

#-- wxPython Event Handlers ----------------------------------------------

    def _on_paint(self, event):
        """ Called when the control needs repainting.
        """
        image = self._image
        control = self.control
        if not control.IsEnabled():
            image = self._disabled_image

        wdx, wdy = control.GetClientSizeTuple()
        wx.PaintDC(control).DrawBitmap(
            image,
            (wdx - image.GetWidth()) / 2,
            (wdy - image.GetHeight()) / 2,
            True)

    def _left_down(self, event):
        """ Handles the left mouse button being pressed.
        """
        if self.control.IsEnabled() and self.drag_source:
            self._x, self._y = event.GetX(), event.GetY()
            self.control.CaptureMouse()

        event.Skip()

    def _left_up(self, event):
        """ Handles the left mouse button being released.
        """
        if self._x is not None:
            self._x = None
            self.control.ReleaseMouse()

        event.Skip()

    def _mouse_move(self, event):
        """ Handles the mouse being moved.
        """
        if self._x is not None:
            if ((abs(self._x - event.GetX()) +
                 abs(self._y - event.GetY())) >= 3):
                self.control.ReleaseMouse()
                self._x = None
                if self._is_file:
                    FileDropSource(self.control, self.value)
                else:
                    PythonDropSource(self.control, self.value)

        event.Skip()

#----- Drag and drop event handlers: -------------------------------------

    #-------------------------------------------------------------------------
    #  Handles a Python object being dropped on the control:
    #-------------------------------------------------------------------------

    def wx_dropped_on(self, x, y, data, drag_result):
        """ Handles a Python object being dropped on the tree.
        """
        try:
            self.value = self._get_drag_data(data)
            return drag_result
        except:
            return wx.DragNone

    #-------------------------------------------------------------------------
    #  Handles a Python object being dragged over the control:
    #-------------------------------------------------------------------------

    def wx_drag_over(self, x, y, data, drag_result):
        """ Handles a Python object being dragged over the tree.
        """
        try:
            self.object.base_trait(
                self.name).validate(
                self.object,
                self.name,
                self._get_drag_data(data))
            return drag_result
        except:
            return wx.DragNone

#-------------------------------------------------------------------------
#  'CustomEditor' class:
#-------------------------------------------------------------------------


class CustomEditor(SimpleEditor):
    """ Custom style of drag-and-drop editor, which is not a drag source.
    """
    #-------------------------------------------------------------------------
    #  Trait definitions:
    #-------------------------------------------------------------------------

    # Is the editor a drag source? This value overrides the default.
    drag_source = False

#-------------------------------------------------------------------------
#  'ReadonlyEditor' class:
#-------------------------------------------------------------------------


class ReadonlyEditor(SimpleEditor):
    """ Read-only style of drag-and-drop editor, which is not a drop target.
    """
    #-------------------------------------------------------------------------
    #  Trait definitions:
    #-------------------------------------------------------------------------

    # Is the editor a drop target? This value overrides the default.
    drop_target = False

#-------------------------------------------------------------------------
#  'FileDropSource' class:
#-------------------------------------------------------------------------


class FileDropSource(wx.DropSource):
    """ Represents a draggable file.
    """
    #-------------------------------------------------------------------------
    #  Initializes the object:
    #-------------------------------------------------------------------------

    def __init__(self, source, files):
        """ Initializes the object.
        """
        self.handler = None
        self.allow_move = True

        # Put the data to be dragged on the clipboard:
        clipboard.data = files
        clipboard.source = source
        clipboard.drop_source = self

        data_object = wx.FileDataObject()
        if isinstance(files, string_type):
            files = [files]

        for file in files:
            data_object.AddFile(file)

        # Create the drop source and begin the drag and drop operation:
        super(FileDropSource, self).__init__(source)
        self.SetData(data_object)
        self.result = self.DoDragDrop(True)

    #-------------------------------------------------------------------------
    #  Called when the data has been dropped:
    #-------------------------------------------------------------------------

    def on_dropped(self, drag_result):
        """ Called when the data has been dropped. """
        return

## EOF ########################################################################
