DragDropComponentConf.java

package swingtree;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sprouts.Action;
import swingtree.api.Configurator;

import javax.swing.JComponent;
import java.awt.dnd.*;
import java.util.Objects;

/**
 *  A value based builder object for configuring drop target events for a component
 *  using the {@link UIForAnySwing#withDropSite(Configurator)} method, where an instance of this
 *  class is passed to the {@link Configurator} lambda defining how
 *  the component should behave and look when something is dragged and dropped
 *  on top of it. <br>
 *  <br>
 *  <b>For listening to drop events, the following methods are available:</b>
 *  <ul>
 *      <li>{@link #onDragEnter(Action)} - 
 *          Called while a drag operation is ongoing, when the mouse pointer enters
 *          the operable part of the drop site for the <code>DropTarget</code>
 *          registered with this listener.
 *      </li>
 *      <li>{@link #onDragOver(Action)} -
 *          Called when a drag operation is ongoing, while the mouse pointer is still
 *          over the operable part of the drop site for the <code>DropTarget</code>
 *          registered with this listener.
 *      </li>
 *      <li>{@link #onDropActionChanged(Action)} -
 *          Called if the user has modified
 *          the current drop gesture.
 *      </li>
 *      <li>{@link #onDragExit(Action)} -
 *          Called while a drag operation is ongoing, when the mouse pointer has
 *          exited the operable part of the drop site for the
 *          <code>DropTarget</code> registered with this listener.
 *      </li>
 *      <li>{@link #onDrop(Action)} -
 *          Called when the drag operation has terminated with a drop on
 *          the operable part of the drop site for the <code>DropTarget</code>
 *          registered with this listener.
 *          This is where the transfer of the data should take place.
 *      </li>
 *  </ul>
 *
 * @param <C> The type of the component to be dragged away.
 */
public final class DragDropComponentConf<C extends JComponent>
{
    private static final Action NO_ACTION = e -> {};
    private static final Logger log = LoggerFactory.getLogger(DragDropComponentConf.class);

    public static <C extends JComponent> DragDropComponentConf<C> of(
        C component
    ) {
        return new DragDropComponentConf<>(
                component,
                NO_ACTION, NO_ACTION, NO_ACTION, NO_ACTION, NO_ACTION
        );
    }

    private final C                           _component;
    private final Action<ComponentDelegate<C,DropTargetDragEvent>> _onDragEnter;
    private final Action<ComponentDelegate<C,DropTargetDragEvent>> _onDragOver;
    private final Action<ComponentDelegate<C,DropTargetDragEvent>> _onDropActionChanged;
    private final Action<ComponentDelegate<C,DropTargetEvent>>     _onDragExit;
    private final Action<ComponentDelegate<C,DropTargetDropEvent>> _onDragDropEnd;

    private DragDropComponentConf(
        C component,
        Action<ComponentDelegate<C,DropTargetDragEvent>> onDragEnter,
        Action<ComponentDelegate<C,DropTargetDragEvent>> onDragOver,
        Action<ComponentDelegate<C,DropTargetDragEvent>> onDropActionChanged,
        Action<ComponentDelegate<C,DropTargetEvent>>     onDragExit,
        Action<ComponentDelegate<C,DropTargetDropEvent>> onDragDropEnd
    ) {
        _component           = Objects.requireNonNull(component);
        _onDragEnter         = Objects.requireNonNull(onDragEnter);
        _onDragOver          = Objects.requireNonNull(onDragOver);
        _onDropActionChanged = Objects.requireNonNull(onDropActionChanged);
        _onDragExit          = Objects.requireNonNull(onDragExit);
        _onDragDropEnd       = Objects.requireNonNull(onDragDropEnd);
    }

    /**
     * Returns the component that this configuration is for
     * so that it can be used in the {@link UIForAnySwing#withDropSite(Configurator)}
     * method to configure the drop site based on the component state.
     *
     * @return The component that this configuration is for.
     */
    public C component() {
        return _component;
    }

    /**
     * The supplied action is called while a drag operation is ongoing, 
     * when the mouse pointer enters
     * the operable part of the drop site for the <code>DropTarget</code>
     * registered with this listener.
     *
     * @param action An {@link Action} with a <code>DropTargetDragEvent</code>
     *               wrapped in a {@link ComponentDelegate} passed to it.
     * @return A new {@link DragDropComponentConf} updated with the supplied action.
     */
    public DragDropComponentConf<C> onDragEnter(Action<ComponentDelegate<C,DropTargetDragEvent>> action) {
        return new DragDropComponentConf<>(
            _component,
            action,
            _onDragOver,
            _onDropActionChanged,
            _onDragExit,
            _onDragDropEnd
        );
    }

    /**
     * The supplied action is called when a drag operation is ongoing, while the mouse pointer is still
     * over the operable part of the drop site for the <code>DropTarget</code>
     * registered with this listener.
     *
     * @param action An {@link Action} with a <code>DropTargetDragEvent</code>
     *               wrapped in a {@link ComponentDelegate} passed to it.
     * @return A new {@link DragDropComponentConf} updated with the supplied action.
     */
    public DragDropComponentConf<C> onDragOver(Action<ComponentDelegate<C,DropTargetDragEvent>> action) {
        return new DragDropComponentConf<>(
            _component,
            _onDragEnter,
            action,
            _onDropActionChanged,
            _onDragExit,
            _onDragDropEnd
        );
    }

    /**
     * The supplied action is called if the user has modified
     * the current drop gesture.
     *
     * @param action An {@link Action} with a <code>DropTargetDragEvent</code>
     *               wrapped in a {@link ComponentDelegate} passed to it.
     * @return A new {@link DragDropComponentConf} updated with the supplied action.
     */
    public DragDropComponentConf<C> onDropActionChanged(Action<ComponentDelegate<C,DropTargetDragEvent>> action) {
        return new DragDropComponentConf<>(
            _component,
            _onDragEnter,
            _onDragOver,
            action,
            _onDragExit,
            _onDragDropEnd
        );
    }

    /**
     * The supplied action is called while a drag operation is ongoing, when the mouse pointer has
     * exited the operable part of the drop site for the
     * {@link DropTarget} registered with this listener.
     *
     * @param action An {@link Action} with a <code>DropTargetEvent</code>
     *               wrapped in a {@link ComponentDelegate} passed to it.
     * @return A new {@link DragDropComponentConf} updated with the supplied action.
     */
    public DragDropComponentConf<C> onDragExit(Action<ComponentDelegate<C,DropTargetEvent>> action) {
        return new DragDropComponentConf<>(
            _component,
            _onDragEnter,
            _onDragOver,
            _onDropActionChanged,
            action,
            _onDragDropEnd
        );
    }

    /**
     * The supplied {@link Action} is called when the drag operation 
     * has terminated with a drop on
     * the operable part of the drop site for the <code>DropTarget</code>
     * registered with this listener.
     * <p>
     * This method is responsible for undertaking
     * the transfer of the data associated with the
     * gesture. The {@link DropTargetDropEvent}
     * provides a means to obtain a {@link java.awt.datatransfer.Transferable}
     * object that represents the data object(s) to
     * be transfered.<P>
     * From this method, the <code>DropTargetListener</code>
     * shall accept or reject the drop via the
     * acceptDrop(int dropAction) or rejectDrop() methods of the
     * {@link DropTargetDropEvent} parameter.
     * <P>
     * After acceptDrop(), but not before,
     * {@link DropTargetDropEvent}'s getTransferable()
     * method may be invoked, and data transfer may be
     * performed via the returned {@link java.awt.datatransfer.Transferable}'s
     * getTransferData() method.
     * <P>
     * At the completion of a drop, an implementation
     * of this method is required to signal the success/failure
     * of the drop by passing an appropriate
     * <code>boolean</code> to the {@link DropTargetDropEvent}'s
     * dropComplete(boolean success) method.
     * <P>
     * Note: The data transfer should be completed before the call  to the
     * {@link DropTargetDropEvent}'s dropComplete(boolean success) method.
     * After that, a call to the getTransferData() method of the
     * {@link java.awt.datatransfer.Transferable} returned by
     * <code>DropTargetDropEvent.getTransferable()</code> is guaranteed to
     * succeed only if the data transfer is local; that is, only if
     * {@link DropTargetDropEvent#isLocalTransfer} returns
     * <code>true</code>. Otherwise, the behavior of the call is
     * implementation-dependent.
     * <P>
     * @param action An {@link Action} with a {@link DropTargetDropEvent}
     *               wrapped in a {@link ComponentDelegate} passed to it.
     * @return A new {@link DragDropComponentConf} updated with the supplied action.
     */
    public DragDropComponentConf<C> onDrop( Action<ComponentDelegate<C,DropTargetDropEvent>> action ) {
        return new DragDropComponentConf<>(
            _component,
            _onDragEnter,
            _onDragOver,
            _onDropActionChanged,
            _onDragExit,
            action
        );
    }


    DropTarget toNewDropTarget() {
        return new DropTarget(
            _component,
            new DropTargetListener() {
                @Override
                public void dragEnter(DropTargetDragEvent event) {
                    try {
                        _onDragEnter.accept(new ComponentDelegate<>(_component, event));
                    } catch (Exception e) {
                        log.error("Error occurred while processing drag enter event.", e);
                    }
                }

                @Override
                public void dragOver(DropTargetDragEvent event) {
                    try {
                        _onDragOver.accept(new ComponentDelegate<>(_component, event));
                    } catch (Exception e) {
                        log.error("Error occurred while processing drag over event.", e);
                    }
                }

                @Override
                public void dropActionChanged(DropTargetDragEvent event) {
                    try {
                        _onDropActionChanged.accept(new ComponentDelegate<>(_component, event));
                    } catch (Exception e) {
                        log.error("Error occurred while processing drop action changed event.", e);
                    }
                }

                @Override
                public void dragExit(DropTargetEvent event) {
                    try {
                        _onDragExit.accept(new ComponentDelegate<>(_component, event));
                    } catch (Exception e) {
                        log.error("Error occurred while processing drag exit event.", e);
                    }
                }

                @Override
                public void drop(DropTargetDropEvent event) {
                    try {
                        _onDragDropEnd.accept(new ComponentDelegate<>(_component, event));
                    } catch (Exception e) {
                        log.error("Error occurred while processing drop event.", e);
                    }
                }
            }
        );
    }
}