001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)-
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.gwt.client.dnd;
029
030import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle;
031import org.opencms.gwt.client.util.A_CmsAnimation;
032import org.opencms.gwt.client.util.CmsDebugLog;
033import org.opencms.gwt.client.util.CmsDomUtil;
034import org.opencms.gwt.client.util.CmsDomUtil.Style;
035import org.opencms.gwt.client.util.CmsMoveAnimation;
036import org.opencms.gwt.client.util.CmsPositionBean;
037
038import java.util.ArrayList;
039import java.util.List;
040
041import com.google.gwt.animation.client.Animation;
042import com.google.gwt.dom.client.Document;
043import com.google.gwt.dom.client.Element;
044import com.google.gwt.dom.client.NativeEvent;
045import com.google.gwt.dom.client.Style.Overflow;
046import com.google.gwt.dom.client.Style.Unit;
047import com.google.gwt.event.dom.client.MouseDownEvent;
048import com.google.gwt.event.dom.client.MouseDownHandler;
049import com.google.gwt.event.shared.HandlerRegistration;
050import com.google.gwt.user.client.Command;
051import com.google.gwt.user.client.DOM;
052import com.google.gwt.user.client.Event;
053import com.google.gwt.user.client.Event.NativePreviewEvent;
054import com.google.gwt.user.client.Event.NativePreviewHandler;
055import com.google.gwt.user.client.Timer;
056import com.google.gwt.user.client.Window;
057import com.google.gwt.user.client.ui.RootPanel;
058
059/**
060 * Drag and drop handler.<p>
061 *
062 * @since 8.0.0
063 */
064public class CmsDNDHandler implements MouseDownHandler {
065
066    /** The animation types. */
067    public enum AnimationType {
068        /** Move animation. */
069        MOVE,
070        /** No animation. */
071        NONE,
072        /** Fade animation. */
073        SPECIAL
074    }
075
076    /** The allowed drag and drop orientation. */
077    public enum Orientation {
078        /** Drag and drop in all directions. */
079        ALL,
080        /** Only horizontal drag and drop, the client-y position will be ignored. */
081        HORIZONTAL,
082        /** Only vertical drag and drop, the client-x position will be ignored. */
083        VERTICAL
084    }
085
086    /**
087     * Timer to schedule automated scrolling.<p>
088     */
089    protected class CmsScrollTimer extends Timer {
090
091        /** The current scroll direction. */
092        private Direction m_direction;
093
094        /** Flag indicating if the scroll parent is the body element. */
095        private boolean m_isBody;
096
097        /** The element that should scrolled. */
098        private Element m_scrollParent;
099
100        /** The scroll speed. */
101        private int m_scrollSpeed;
102
103        /**
104         * Constructor.<p>
105         *
106         * @param scrollParent the element that should scrolled
107         * @param scrollSpeed the scroll speed
108         * @param direction the scroll direction
109         */
110        public CmsScrollTimer(Element scrollParent, int scrollSpeed, Direction direction) {
111
112            m_scrollParent = scrollParent;
113            m_scrollSpeed = scrollSpeed;
114            m_isBody = m_scrollParent.getTagName().equalsIgnoreCase(CmsDomUtil.Tag.body.name());
115            m_direction = direction;
116        }
117
118        /**
119         * @see com.google.gwt.user.client.Timer#run()
120         */
121        @Override
122        public void run() {
123
124            int top, left;
125            if (m_isBody) {
126                top = Window.getScrollTop();
127                left = Window.getScrollLeft();
128            } else {
129                top = m_scrollParent.getScrollTop();
130                left = m_scrollParent.getScrollLeft();
131            }
132            Element element = getDragHelper();
133
134            boolean abort = false;
135            switch (m_direction) {
136                case down:
137                    top += m_scrollSpeed;
138                    element.getStyle().setTop(
139                        CmsDomUtil.getCurrentStyleInt(element, Style.top) + m_scrollSpeed,
140                        Unit.PX);
141                    break;
142                case up:
143                    if (top <= m_scrollSpeed) {
144                        abort = true;
145                        top = 0;
146                        element.getStyle().setTop(CmsDomUtil.getCurrentStyleInt(element, Style.top) - top, Unit.PX);
147                        break;
148                    }
149                    top -= m_scrollSpeed;
150                    element.getStyle().setTop(
151                        CmsDomUtil.getCurrentStyleInt(element, Style.top) - m_scrollSpeed,
152                        Unit.PX);
153                    break;
154                case left:
155                    if (left <= m_scrollSpeed) {
156                        abort = true;
157                        element.getStyle().setLeft(CmsDomUtil.getCurrentStyleInt(element, Style.left) - left, Unit.PX);
158                        left = 0;
159                        break;
160                    }
161                    left -= m_scrollSpeed;
162                    element.getStyle().setLeft(
163                        CmsDomUtil.getCurrentStyleInt(element, Style.left) - m_scrollSpeed,
164                        Unit.PX);
165                    break;
166                case right:
167                    left += m_scrollSpeed;
168                    element.getStyle().setLeft(
169                        CmsDomUtil.getCurrentStyleInt(element, Style.left) + m_scrollSpeed,
170                        Unit.PX);
171                    break;
172                default:
173                    break;
174
175            }
176
177            if (m_isBody) {
178                Window.scrollTo(left, top);
179            } else {
180                m_scrollParent.setScrollLeft(left);
181                m_scrollParent.setScrollTop(top);
182            }
183            if (abort) {
184                clearScrollTimer();
185            }
186        }
187    }
188
189    /** Scroll direction enumeration. */
190    protected enum Direction {
191        /** Scroll direction. */
192        down,
193
194        /** Scroll direction. */
195        left,
196
197        /** Scroll direction. */
198        right,
199
200        /** Scroll direction. */
201        up
202    }
203
204    /**
205     * Drag and drop event preview handler.<p>
206     *
207     * To be used while dragging.<p>
208     */
209    protected class DNDEventPreviewHandler implements NativePreviewHandler {
210
211        /**
212         * @see com.google.gwt.user.client.Event.NativePreviewHandler#onPreviewNativeEvent(com.google.gwt.user.client.Event.NativePreviewEvent)
213         */
214        public void onPreviewNativeEvent(NativePreviewEvent event) {
215
216            Event nativeEvent = Event.as(event.getNativeEvent());
217
218            if (m_state == State.off) {
219                // this should never happen, as the preview handler should be removed after the dragging stopped
220                CmsDebugLog.getInstance().printLine("Preview handler still registered, even though dragging stopped.");
221                stopDragging();
222                return;
223            }
224            if (m_state == State.dragging) {
225                switch (DOM.eventGetType(nativeEvent)) {
226                    case Event.ONMOUSEMOVE:
227                        // dragging
228                        onMove(nativeEvent);
229                        break;
230                    case Event.ONMOUSEUP:
231                        onUp(nativeEvent);
232                        break;
233                    case Event.ONKEYDOWN:
234                        if (nativeEvent.getKeyCode() == 27) {
235                            cancel();
236                        }
237                        break;
238                    case Event.ONMOUSEWHEEL:
239                        onMouseWheelScroll(nativeEvent);
240                        break;
241                    default:
242                        // do nothing
243                }
244            } else if (m_state == State.mousedown) {
245                switch (DOM.eventGetType(nativeEvent)) {
246                    case Event.ONMOUSEMOVE:
247                        m_dragHelper = m_draggable.getDragHelper(m_currentTarget);
248                        m_placeholder = m_draggable.getPlaceholder(m_currentTarget);
249                        // notifying controller, if false is returned, dragging will be canceled
250
251                        if (!m_controller.onDragStart(m_draggable, m_currentTarget, CmsDNDHandler.this)) {
252                            cancel();
253                            return;
254                        }
255                        m_draggable.onStartDrag(m_currentTarget);
256                        setState(State.dragging);
257                        // add marker css class to enable drag and drop dependent styles
258                        Document.get().getBody().addClassName(
259                            org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.dragdropCss().dragStarted());
260                        if (m_scrollElement == null) {
261                            CmsDomUtil.getHtmlElement().addClassName(
262                                org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.dragdropCss().fullWindowDrag());
263                        }
264                        onMove(nativeEvent);
265                        break;
266                    case Event.ONMOUSEUP:
267                        stopDragging();
268                        DOM.releaseCapture(DOM.getCaptureElement());
269                        if (m_controller.startPlacementMode(m_draggable, CmsDNDHandler.this)) {
270                            m_placementMode = true;
271                        } else {
272                            cancel();
273                        }
274                        break;
275                    default:
276                        // do nothing
277                }
278
279            }
280            event.cancel();
281            nativeEvent.preventDefault();
282            nativeEvent.stopPropagation();
283        }
284
285    }
286
287    /**
288     * Fancy animation fading out the helper and shrinking away the original element.<p>
289     */
290    class SpecialAnimation extends A_CmsAnimation {
291
292        /** The helper element. */
293        private Element m_helper;
294
295        /** If the original should be hidden. */
296        private boolean m_hideOriginal;
297
298        /** The original element. */
299        private Element m_original;
300
301        /** The original element height. */
302        private int m_originalHeight;
303
304        /** The original element opacity. */
305        private double m_originalOpacity;
306
307        /** The original  element width. */
308        private int m_originalWidth;
309
310        /** The optional overlay. */
311        private Element m_overlay;
312
313        /**
314         * Constructor.<p>
315         *
316         * @param helper the helper element
317         * @param original the original element
318         * @param overlay the optional overlay to fade away
319         * @param callback the on complete callback
320         * @param hideOriginal if the original should be hidden
321         */
322        public SpecialAnimation(
323            Element helper,
324            Element original,
325            Element overlay,
326            Command callback,
327            boolean hideOriginal) {
328
329            super(callback);
330            m_helper = helper;
331            m_original = original;
332            m_hideOriginal = hideOriginal;
333            m_overlay = overlay;
334        }
335
336        /**
337         * @see com.google.gwt.animation.client.Animation#run(int)
338         */
339        @Override
340        public void run(int duration) {
341
342            m_original.getStyle().setOverflow(Overflow.HIDDEN);
343
344            if (m_hideOriginal) {
345                m_original.addClassName(I_CmsLayoutBundle.INSTANCE.generalCss().clearFix());
346                m_originalHeight = CmsDomUtil.getCurrentStyleInt(m_original, Style.height);
347                m_originalWidth = CmsDomUtil.getCurrentStyleInt(m_original, Style.width);
348            } else {
349                m_originalOpacity = CmsDomUtil.getCurrentStyleFloat(m_original, Style.opacity);
350            }
351            super.run(duration);
352        }
353
354        /**
355         * @see org.opencms.gwt.client.util.A_CmsAnimation#onComplete()
356         */
357        @Override
358        protected void onComplete() {
359
360            super.onComplete();
361            m_original.getStyle().clearHeight();
362            m_original.getStyle().clearWidth();
363            m_original.getStyle().clearOverflow();
364            m_original.removeClassName(I_CmsLayoutBundle.INSTANCE.generalCss().clearFix());
365        }
366
367        /**
368         * @see com.google.gwt.animation.client.Animation#onUpdate(double)
369         */
370        @Override
371        protected void onUpdate(double progress) {
372
373            m_helper.getStyle().setOpacity(-progress + 1);
374            if (m_overlay != null) {
375                m_overlay.getStyle().setOpacity(-progress + 1);
376            }
377
378            if (m_hideOriginal) {
379                m_original.getStyle().setHeight(m_originalHeight - (m_originalHeight * progress), Unit.PX);
380                m_original.getStyle().setWidth(m_originalWidth - (m_originalWidth * progress), Unit.PX);
381            } else {
382                m_original.getStyle().setOpacity(m_originalOpacity + ((1 - m_originalOpacity) * progress));
383            }
384
385        }
386    }
387
388    /**
389     * Enum for the DND handler state.
390     *
391     */
392    enum State {
393        /** Currently dragging something. */
394        dragging,
395
396        /** Mouse button has been pressed but not released, so we don't know if we are going to do drag/drop or placement mode next. */
397        mousedown,
398
399        /** Nothing is happening. */
400        off;
401    }
402
403    /** Animation enabled flag. */
404    private AnimationType m_animationType = AnimationType.MOVE;
405
406    /** The mouse x position of the current mouse event. */
407    private int m_clientX;
408
409    /** The mouse y position of the current mouse event. */
410    private int m_clientY;
411
412    /** The Drag and drop controller. */
413    private I_CmsDNDController m_controller;
414
415    /** The current animation. */
416    private Animation m_currentAnimation;
417
418    /** The current drop target. */
419    private I_CmsDropTarget m_currentTarget;
420
421    /** The x cursor offset to the dragged element. */
422    private int m_cursorOffsetX;
423
424    /** The y cursor offset to the dragged element. */
425    private int m_cursorOffsetY;
426
427    /** The draggable. */
428    private I_CmsDraggable m_draggable;
429
430    /** The drag helper. */
431    private Element m_dragHelper;
432
433    /** Flag indicating whether the CTRL key was pressed when drag started. */
434    private boolean m_modifierCTRL;
435
436    /** The drag and drop orientation. Default is <code>ALL</code>. */
437    private Orientation m_orientation = Orientation.ALL;
438
439    /** The placeholder. */
440    private Element m_placeholder;
441
442    /** True if we are in placement mode. */
443    private boolean m_placementMode;
444
445    /** The event preview handler. */
446    private DNDEventPreviewHandler m_previewHandler;
447
448    /** The preview handler registration. */
449    private HandlerRegistration m_previewHandlerRegistration;
450
451    /** Current scroll direction. */
452    private Direction m_scrollDirection;
453
454    /** The scroll parent. */
455    private Element m_scrollElement;
456
457    /** Flag if automatic scrolling is enabled. */
458    private boolean m_scrollEnabled = true;
459
460    /** Scroll timer. */
461    private Timer m_scrollTimer;
462
463    /** The starting position absolute left. */
464    private int m_startLeft;
465
466    /** The starting position absolute top. */
467    private int m_startTop;
468
469    /** The current state. */
470    private State m_state = State.off;
471
472    /** The registered drop targets. */
473    private List<I_CmsDropTarget> m_targets;
474
475    /**
476     * Constructor.<p>
477     *
478     * @param controller the drag and drop controller
479     **/
480    public CmsDNDHandler(I_CmsDNDController controller) {
481
482        m_targets = new ArrayList<I_CmsDropTarget>();
483        m_previewHandler = new DNDEventPreviewHandler();
484        m_controller = controller;
485    }
486
487    /**
488     * Adds a drop target.<p>
489     *
490     * @param target the target to add
491     */
492    public void addTarget(I_CmsDropTarget target) {
493
494        m_targets.add(target);
495    }
496
497    /**
498     * Cancels the dragging process.<p>
499     */
500    public void cancel() {
501
502        animateCancel(m_draggable, m_controller);
503    }
504
505    /**
506     * Resets the members used for placement mode.
507     */
508    public void clearPlacement() {
509
510        m_placementMode = false;
511    }
512
513    /**
514     * Clears the drop target register.<p>
515     */
516    public void clearTargets() {
517
518        m_targets.clear();
519    }
520
521    /**
522     * Drops the draggable.<p>
523     */
524    public void drop() {
525
526        // notifying controller, if false is returned, dropping will be canceled
527        if (!m_controller.onBeforeDrop(m_draggable, m_currentTarget, this)) {
528            cancel();
529            return;
530        }
531        animateDrop(m_draggable, m_currentTarget, m_controller);
532    }
533
534    /**
535     * Returns the drag and drop controller.<p>
536     *
537     * @return the drag and drop controller
538     */
539    public I_CmsDNDController getController() {
540
541        return m_controller;
542    }
543
544    /**
545     * Returns the current drop target.<p>
546     *
547     * @return the current drop target
548     */
549    public I_CmsDropTarget getCurrentTarget() {
550
551        return m_currentTarget;
552    }
553
554    /**
555     * Returns the cursor offset x.<p>
556     *
557     * @return the cursor offset x
558     */
559    public int getCursorOffsetX() {
560
561        return m_cursorOffsetX;
562    }
563
564    /**
565     * Returns the cursor offset y.<p>
566     *
567     * @return the cursor offset y
568     */
569    public int getCursorOffsetY() {
570
571        return m_cursorOffsetY;
572    }
573
574    /**
575     * Returns the current draggable.<p>
576     *
577     * @return the draggable
578     */
579    public I_CmsDraggable getDraggable() {
580
581        return m_draggable;
582    }
583
584    /**
585     * Returns the drag helper element.<p>
586     *
587     * @return the drag helper
588     */
589    public Element getDragHelper() {
590
591        return m_dragHelper;
592    }
593
594    /**
595     * Returns the allowed drag and drop orientation.<p>
596     *
597     * @return the drag and drop orientation
598     */
599    public Orientation getOrientation() {
600
601        return m_orientation;
602    }
603
604    /**
605     * Returns the place holder element.<p>
606     *
607     * @return the place holder element
608     */
609    public Element getPlaceholder() {
610
611        return m_placeholder;
612    }
613
614    /**
615     * Returns whether the CTRL key was pressed when drag started.<p>
616     *
617     * @return <code>true</code> if CTRL key was pressed when drag started
618     */
619    public boolean hasModifierCTRL() {
620
621        return m_modifierCTRL;
622    }
623
624    /**
625     * Returns if the animation is enabled.<p>
626     *
627     * @return <code>true</code> if the animation is enabled
628     */
629    public boolean isAnimationEnabled() {
630
631        return (m_animationType != null) && (m_animationType != AnimationType.NONE);
632    }
633
634    /**
635     * Returns if a dragging process is taking place.<p>
636     *
637     * @return <code>true</code> if the handler is currently dragging
638     */
639    public boolean isDragging() {
640
641        return m_state == State.dragging;
642    }
643
644    /**
645     * Checks if we are in placement mode.
646     *
647     * @return true if we are in placement mode
648     */
649    public boolean isPlacementMode() {
650
651        return m_placementMode;
652    }
653
654    /**
655     * Returns if automated scrolling is enabled.<p>
656     *
657     * @return if automated scrolling is enabled
658     */
659    public boolean isScrollEnabled() {
660
661        return m_scrollEnabled;
662    }
663
664    /**
665     * @see com.google.gwt.event.dom.client.MouseDownHandler#onMouseDown(com.google.gwt.event.dom.client.MouseDownEvent)
666     */
667    public void onMouseDown(MouseDownEvent event) {
668
669        m_placementMode = false;
670        if ((event.getNativeButton() != NativeEvent.BUTTON_LEFT)
671            || (m_state != State.off)
672            || (m_currentAnimation != null)) {
673            // only act on left button down, ignore right click
674            // also ignore if the dragging flag is still true or an animation is still running
675            return;
676
677        }
678
679        Object source = event.getSource();
680        if (!(source instanceof I_CmsDragHandle)) {
681            // source is no drag handle, wrong DNDHandler assignment ignore
682            return;
683        }
684        I_CmsDragHandle dragHandle = (I_CmsDragHandle)source;
685        m_draggable = dragHandle.getDraggable();
686        if (m_draggable == null) {
687            // cancel dragging
688            return;
689        }
690        setState(State.mousedown);
691        if (m_previewHandlerRegistration != null) {
692            // this should never be the case
693            CmsDebugLog.getInstance().printLine("Preview handler already registered!!!");
694            m_previewHandlerRegistration.removeHandler();
695
696        }
697        CmsDebugLog.getInstance().printLine("Registering preview handler");
698        m_previewHandlerRegistration = Event.addNativePreviewHandler(m_previewHandler);
699        m_clientX = event.getClientX();
700        m_clientY = event.getClientY();
701        m_cursorOffsetX = CmsDomUtil.getRelativeX(m_clientX, m_draggable.getElement());
702        m_cursorOffsetY = CmsDomUtil.getRelativeY(m_clientY, m_draggable.getElement());
703        m_startLeft = m_draggable.getElement().getAbsoluteLeft();
704        m_startTop = m_draggable.getElement().getAbsoluteTop();
705        m_currentTarget = m_draggable.getParentTarget();
706
707        // Actual drag mode is started when the preview handler receives a mousemove event
708
709    }
710
711    /**
712     * Removes a drop target from the register.<p>
713     *
714     * @param target the target to remove
715     */
716    public void removeTarget(I_CmsDropTarget target) {
717
718        m_targets.remove(target);
719    }
720
721    /**
722     * Sets the animation type.<p>
723     *
724     * @param animationType the animation type
725     */
726    public void setAnimationType(AnimationType animationType) {
727
728        m_animationType = animationType;
729    }
730
731    /**
732     * Sets the drag and drop controller.<p>
733     *
734     * @param controller the drag and drop controller to set
735     */
736    public void setController(I_CmsDNDController controller) {
737
738        m_controller = controller;
739    }
740
741    /**
742     * Sets the cursor offset x.<p>
743     *
744     * @param cursorOffsetX the cursor offset x to set
745     */
746    public void setCursorOffsetX(int cursorOffsetX) {
747
748        m_cursorOffsetX = cursorOffsetX;
749    }
750
751    /**
752     * Sets the cursor offset y.<p>
753     *
754     * @param cursorOffsetY the cursor offset y to set
755     */
756    public void setCursorOffsetY(int cursorOffsetY) {
757
758        m_cursorOffsetY = cursorOffsetY;
759    }
760
761    /**
762     * Sets the draggable.<p>
763     *
764     * @param draggable the draggable
765     */
766    public void setDraggable(I_CmsDraggable draggable) {
767
768        m_draggable = draggable;
769    }
770
771    /**
772     * Sets the drag helper element.<p>
773     *
774     * @param dragHelper the drag helper element
775     */
776    public void setDragHelper(Element dragHelper) {
777
778        m_dragHelper = dragHelper;
779    }
780
781    /**
782     * Sets the allowed drag and drop orientation.<p>
783     *
784     * @param orientation the drag and drop orientation to set
785     */
786    public void setOrientation(Orientation orientation) {
787
788        m_orientation = orientation;
789    }
790
791    /**
792     * Sets the placeholder element.<p>
793     *
794     * @param placeholder the placeholder element
795     */
796    public void setPlaceholder(Element placeholder) {
797
798        m_placeholder = placeholder;
799    }
800
801    /**
802     * Sets the scroll element in case not the window but another element needs scrolling.<p>
803     *
804     * @param scrollElement the scroll element
805     */
806    public void setScrollElement(Element scrollElement) {
807
808        m_scrollElement = scrollElement;
809    }
810
811    /**
812     * Sets the scrolling enabled.<p>
813     *
814     * @param scrollEnabled <code>true</code> to enable scrolling
815     */
816    public void setScrollEnabled(boolean scrollEnabled) {
817
818        m_scrollEnabled = scrollEnabled;
819    }
820
821    /**
822     * Sets the start position.<p>
823     * In case of a canceled drag and drop and enabled animation,
824     * the draggable helper element will be reverted to the start position.<p>
825     * Values <code>&lt;0</code> will be ignored.<p>
826     *
827     * @param left the left position
828     * @param top the top position
829     */
830    public void setStartPosition(int left, int top) {
831
832        if (left >= 0) {
833            m_startLeft = left;
834        }
835        if (top >= 0) {
836            m_startTop = top;
837        }
838    }
839
840    /**
841     * Updates the position of the helper within the the appropriate target.<p>
842     * Needs to be executed on mouse move or when the list of allowed targets changes.<p>
843     * Uses the currently stored cursor position.<p>
844     */
845    public void updatePosition() {
846
847        checkTargets();
848        positionHelper();
849        scrollAction();
850    }
851
852    /**
853     * Clears the drag process with a move animation of the drag element to it's original position.<p>
854     *
855     * @param draggable the draggable
856     * @param controller the drag and drop controller
857     */
858    protected void animateCancel(final I_CmsDraggable draggable, final I_CmsDNDController controller) {
859
860        controller.onAnimationStart(draggable, null, this);
861        stopDragging();
862        Command callback = new Command() {
863
864            /**
865             * @see com.google.gwt.user.client.Command#execute()
866             */
867            public void execute() {
868
869                controller.onDragCancel(draggable, null, CmsDNDHandler.this);
870                draggable.onDragCancel();
871                clear();
872                controller.postClear(draggable, null);
873            }
874        };
875        showEndAnimation(callback, m_startTop, m_startLeft, false);
876    }
877
878    /**
879     * Clears the drag process with a move animation of the drag element to the place-holder position.<p>
880     *
881     * @param draggable the draggable
882     * @param target the drop target
883     * @param controller the drag and drop controller
884     */
885    protected void animateDrop(
886        final I_CmsDraggable draggable,
887        final I_CmsDropTarget target,
888        final I_CmsDNDController controller) {
889
890        controller.onAnimationStart(draggable, target, this);
891        stopDragging();
892        Command callback = new Command() {
893
894            /**
895             * @see com.google.gwt.user.client.Command#execute()
896             */
897            public void execute() {
898
899                controller.onDrop(draggable, target, CmsDNDHandler.this);
900                target.onDrop(draggable);
901                draggable.onDrop(target);
902                clear();
903                controller.postClear(draggable, target);
904            }
905        };
906        CmsPositionBean placeholderPosition = CmsPositionBean.getBoundingClientRect(m_placeholder);
907        showEndAnimation(callback, placeholderPosition.getTop(), placeholderPosition.getLeft(), true);
908    }
909
910    /**
911     * Clears all references used within the current drag process.<p>
912     */
913    protected void clear() {
914
915        for (I_CmsDropTarget target : m_targets) {
916            target.removePlaceholder();
917        }
918        if (m_dragHelper != null) {
919            m_dragHelper.removeFromParent();
920            m_dragHelper = null;
921        }
922        m_placeholder = null;
923        m_currentTarget = null;
924        m_draggable = null;
925        Document.get().getBody().removeClassName(
926            org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.dragdropCss().dragStarted());
927
928        CmsDomUtil.getHtmlElement().removeClassName(
929            org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.dragdropCss().fullWindowDrag());
930
931        m_currentAnimation = null;
932        m_modifierCTRL = false;
933    }
934
935    /**
936     * Cancels the scroll timer and removes the timer reference.<p>
937     */
938    protected void clearScrollTimer() {
939
940        if (m_scrollTimer != null) {
941            m_scrollTimer.cancel();
942            m_scrollTimer = null;
943        }
944    }
945
946    /**
947     * Execute on mouse wheel event.<p>
948     *
949     * @param event the native event
950     */
951    protected void onMouseWheelScroll(Event event) {
952
953        int scrollStep = event.getMouseWheelVelocityY() * 5;
954        Element scrollTarget;
955        if (getCurrentTarget() != null) {
956            scrollTarget = getCurrentTarget().getElement();
957        } else {
958            scrollTarget = RootPanel.getBodyElement();
959        }
960        while ((scrollTarget.getScrollHeight() == scrollTarget.getClientHeight())
961            && (scrollTarget != RootPanel.getBodyElement())) {
962            scrollTarget = scrollTarget.getParentElement();
963        }
964        if (scrollTarget == RootPanel.getBodyElement()) {
965            int top = Window.getScrollTop() + scrollStep;
966            int left = Window.getScrollLeft();
967            if (top < 0) {
968                top = 0;
969            }
970            Window.scrollTo(left, top);
971        } else {
972            int top = scrollTarget.getScrollTop() + scrollStep;
973            if (top < 0) {
974                top = 0;
975            }
976            scrollTarget.setScrollTop(top);
977        }
978        onMove(event);
979    }
980
981    /**
982     * Executed on mouse move while dragging.<p>
983     *
984     * @param event the event
985     */
986    protected void onMove(Event event) {
987
988        m_clientX = event.getClientX();
989        m_clientY = event.getClientY();
990        updatePosition();
991    }
992
993    /**
994     * Executed on mouse up while dragging.<p>
995     *
996     * @param event the event
997     */
998    protected void onUp(Event event) {
999
1000        m_clientX = event.getClientX();
1001        m_clientY = event.getClientY();
1002        m_modifierCTRL = event.getCtrlKey() || event.getMetaKey();
1003        if ((m_currentTarget == null) || (m_currentTarget.getPlaceholderIndex() < 0)) {
1004            cancel();
1005        } else {
1006            drop();
1007        }
1008    }
1009
1010    /**
1011     * Positions an element depending on the current events client position and the cursor offset. This method assumes that the element parent is positioned relative.<p>
1012     */
1013    protected void positionHelper() {
1014
1015        if (m_dragHelper == null) {
1016            // should never happen
1017            CmsDebugLog.getInstance().printLine("Drag helper is null");
1018            return;
1019        }
1020        Element parentElement = m_dragHelper.getParentElement();
1021        int left = CmsDomUtil.getRelativeX(m_clientX, parentElement) - m_cursorOffsetX;
1022        int top = CmsDomUtil.getRelativeY(m_clientY, parentElement) - m_cursorOffsetY;
1023        m_dragHelper.getStyle().setLeft(left, Unit.PX);
1024        m_dragHelper.getStyle().setTop(top, Unit.PX);
1025    }
1026
1027    /**
1028     * Sets dragging to false and removes the event preview handler.<p>
1029     */
1030    protected void stopDragging() {
1031
1032        clearScrollTimer();
1033        setState(State.off);
1034        if (m_previewHandlerRegistration != null) {
1035            m_previewHandlerRegistration.removeHandler();
1036            m_previewHandlerRegistration = null;
1037        }
1038    }
1039
1040    /**
1041     * Method will check all registered drop targets if the element is positioned over one of them.<p>
1042     */
1043    private void checkTargets() {
1044
1045        // checking current target first
1046        if ((m_currentTarget != null) && m_currentTarget.checkPosition(m_clientX, m_clientY, m_orientation)) {
1047            // in case of nested targets, check the children of the current target
1048            if ((m_currentTarget instanceof I_CmsNestedDropTarget)
1049                && ((I_CmsNestedDropTarget)m_currentTarget).hasDnDChildren()) {
1050                for (I_CmsDropTarget target : ((I_CmsNestedDropTarget)m_currentTarget).getDnDChildren()) {
1051                    if ((target != m_currentTarget) && target.checkPosition(m_clientX, m_clientY, m_orientation)) {
1052                        // notifying controller, if false is returned, placeholder will not be positioned inside target
1053                        if (m_controller.onTargetEnter(m_draggable, target, this)) {
1054                            target.insertPlaceholder(m_placeholder, m_clientX, m_clientY, m_orientation);
1055                            m_currentTarget = target;
1056                            m_controller.onPositionedPlaceholder(m_draggable, m_currentTarget, this);
1057                            return;
1058                        }
1059                    }
1060                }
1061            }
1062            if (m_currentTarget.getPlaceholderIndex() < 0) {
1063                m_currentTarget.insertPlaceholder(m_placeholder, m_clientX, m_clientY, m_orientation);
1064            } else {
1065                m_currentTarget.repositionPlaceholder(m_clientX, m_clientY, m_orientation);
1066            }
1067
1068            m_controller.onPositionedPlaceholder(m_draggable, m_currentTarget, this);
1069        } else {
1070            // leaving the current target
1071            if (m_currentTarget != null) {
1072                m_controller.onTargetLeave(m_draggable, m_currentTarget, this);
1073            }
1074            for (I_CmsDropTarget target : m_targets) {
1075                if ((target != m_currentTarget) && target.checkPosition(m_clientX, m_clientY, m_orientation)) {
1076                    // notifying controller, if false is returned, placeholder will not be positioned inside target
1077                    if (m_controller.onTargetEnter(m_draggable, target, this)) {
1078                        target.insertPlaceholder(m_placeholder, m_clientX, m_clientY, m_orientation);
1079                        m_currentTarget = target;
1080                        m_controller.onPositionedPlaceholder(m_draggable, m_currentTarget, this);
1081                        return;
1082                    }
1083                }
1084            }
1085            // mouse position is not over any registered target
1086            m_currentTarget = null;
1087        }
1088    }
1089
1090    /**
1091     * Convenience method to get the appropriate scroll direction.<p>
1092     *
1093     * @param offset the scroll parent border offset, if the cursor is within the border offset, scrolling should be triggered
1094     *
1095     * @return the scroll direction
1096     */
1097    private Direction getScrollDirection(int offset) {
1098
1099        if (m_scrollElement == null) {
1100            Element body = RootPanel.getBodyElement();
1101            int windowHeight = Window.getClientHeight();
1102            int bodyHeight = body.getClientHeight();
1103            if (windowHeight < bodyHeight) {
1104                if (((windowHeight - m_clientY) < offset) && (Window.getScrollTop() < (bodyHeight - windowHeight))) {
1105                    return Direction.down;
1106                }
1107                if ((m_clientY < offset) && (Window.getScrollTop() > 0)) {
1108                    return Direction.up;
1109                }
1110            }
1111
1112            int windowWidth = Window.getClientWidth();
1113            int bodyWidth = body.getClientWidth();
1114            if (windowWidth < bodyWidth) {
1115                if (((windowWidth - m_clientX) < offset) && (Window.getScrollLeft() < (bodyWidth - windowWidth))) {
1116                    return Direction.right;
1117                }
1118                if ((m_clientX < offset) && (Window.getScrollLeft() > 0)) {
1119                    return Direction.left;
1120                }
1121            }
1122
1123            return null;
1124        } else {
1125            int outerHeight = m_scrollElement.getClientHeight();
1126            int innerHeight = m_scrollElement.getScrollHeight();
1127            if (outerHeight < innerHeight) {
1128                if ((((outerHeight + m_scrollElement.getAbsoluteTop()) - m_clientY) < offset)
1129                    && (m_scrollElement.getScrollTop() < (innerHeight - outerHeight))) {
1130                    return Direction.down;
1131                }
1132                if (((m_clientY - m_scrollElement.getAbsoluteTop()) < offset) && (m_scrollElement.getScrollTop() > 0)) {
1133                    return Direction.up;
1134                }
1135            }
1136            int outerWidth = m_scrollElement.getClientWidth();
1137            int innerWidth = m_scrollElement.getScrollWidth();
1138            if (outerWidth < innerWidth) {
1139                if ((((outerWidth + m_scrollElement.getAbsoluteLeft()) - m_clientX) < offset)
1140                    && (m_scrollElement.getScrollLeft() < (innerWidth - outerWidth))) {
1141                    return Direction.right;
1142                }
1143                if (((m_clientX - m_scrollElement.getAbsoluteLeft()) < offset)
1144                    && (m_scrollElement.getScrollLeft() > 0)) {
1145                    return Direction.left;
1146                }
1147            }
1148            return null;
1149        }
1150    }
1151
1152    /**
1153     * Handles automated scrolling.<p>
1154     */
1155    private void scrollAction() {
1156
1157        if (m_scrollEnabled) {
1158
1159            Direction direction = getScrollDirection(50);
1160            if ((m_scrollTimer != null) && (m_scrollDirection != direction)) {
1161                clearScrollTimer();
1162            }
1163            if ((direction != null) && (m_scrollTimer == null)) {
1164                m_scrollTimer = new CmsScrollTimer(
1165                    m_scrollElement != null ? m_scrollElement : RootPanel.getBodyElement(),
1166                    20,
1167                    direction);
1168                m_scrollTimer.scheduleRepeating(10);
1169            }
1170
1171            m_scrollDirection = direction;
1172        }
1173    }
1174
1175    /**
1176     * Sets the internal state.
1177     *
1178     * @param state the new state value
1179     */
1180    private void setState(State state) {
1181
1182        m_state = state;
1183    }
1184
1185    /**
1186     * Shows the end animation on drop or cancel. Executes the given callback afterwards.<p>
1187     *
1188     * @param callback the callback to execute
1189     * @param top absolute top of the animation end position
1190     * @param left absolute left of the animation end position
1191     * @param isDrop if the animation is done on drop
1192     */
1193    private void showEndAnimation(Command callback, int top, int left, boolean isDrop) {
1194
1195        if (!isAnimationEnabled() || (m_dragHelper == null)) {
1196            callback.execute();
1197            return;
1198        }
1199        switch (m_animationType) {
1200            case SPECIAL:
1201                List<Element> overlays = CmsDomUtil.getElementsByClass(
1202                    I_CmsLayoutBundle.INSTANCE.generalCss().disablingOverlay(),
1203                    m_draggable.getElement());
1204                Element overlay = overlays.size() > 0 ? overlays.get(0) : null;
1205                m_currentAnimation = new SpecialAnimation(
1206                    m_dragHelper,
1207                    m_draggable.getElement(),
1208                    overlay,
1209                    callback,
1210                    isDrop);
1211
1212                break;
1213
1214            case MOVE:
1215                Element parentElement = m_dragHelper.getParentElement();
1216                int endTop = top - parentElement.getAbsoluteTop();
1217                int endLeft = left - parentElement.getAbsoluteLeft();
1218                int startTop = CmsDomUtil.getCurrentStyleInt(m_dragHelper, Style.top);
1219                int startLeft = CmsDomUtil.getCurrentStyleInt(m_dragHelper, Style.left);
1220                m_currentAnimation = new CmsMoveAnimation(m_dragHelper, startTop, startLeft, endTop, endLeft, callback);
1221                break;
1222
1223            default:
1224                // nothing to do
1225        }
1226        m_currentAnimation.run(400);
1227    }
1228}