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            if (!isDragging()) {
217                // this should never happen, as the preview handler should be removed after the dragging stopped
218                CmsDebugLog.getInstance().printLine("Preview handler still registered, even though dragging stopped.");
219                stopDragging();
220                return;
221            }
222            Event nativeEvent = Event.as(event.getNativeEvent());
223            switch (DOM.eventGetType(nativeEvent)) {
224                case Event.ONMOUSEMOVE:
225                    // dragging
226                    onMove(nativeEvent);
227                    break;
228                case Event.ONMOUSEUP:
229                    onUp(nativeEvent);
230                    break;
231                case Event.ONKEYDOWN:
232                    if (nativeEvent.getKeyCode() == 27) {
233                        cancel();
234                    }
235                    break;
236                case Event.ONMOUSEWHEEL:
237                    onMouseWheelScroll(nativeEvent);
238                    break;
239                default:
240                    // do nothing
241            }
242            event.cancel();
243            nativeEvent.preventDefault();
244            nativeEvent.stopPropagation();
245        }
246
247    }
248
249    /**
250     * Fancy animation fading out the helper and shrinking away the original element.<p>
251     */
252    class SpecialAnimation extends A_CmsAnimation {
253
254        /** The helper element. */
255        private Element m_helper;
256
257        /** If the original should be hidden. */
258        private boolean m_hideOriginal;
259
260        /** The original element. */
261        private Element m_original;
262
263        /** The original element height. */
264        private int m_originalHeight;
265
266        /** The original element opacity. */
267        private double m_originalOpacity;
268
269        /** The original  element width. */
270        private int m_originalWidth;
271
272        /** The optional overlay. */
273        private Element m_overlay;
274
275        /**
276         * Constructor.<p>
277         *
278         * @param helper the helper element
279         * @param original the original element
280         * @param overlay the optional overlay to fade away
281         * @param callback the on complete callback
282         * @param hideOriginal if the original should be hidden
283         */
284        public SpecialAnimation(
285            Element helper,
286            Element original,
287            Element overlay,
288            Command callback,
289            boolean hideOriginal) {
290
291            super(callback);
292            m_helper = helper;
293            m_original = original;
294            m_hideOriginal = hideOriginal;
295            m_overlay = overlay;
296        }
297
298        /**
299         * @see com.google.gwt.animation.client.Animation#run(int)
300         */
301        @Override
302        public void run(int duration) {
303
304            m_original.getStyle().setOverflow(Overflow.HIDDEN);
305
306            if (m_hideOriginal) {
307                m_original.addClassName(I_CmsLayoutBundle.INSTANCE.generalCss().clearFix());
308                m_originalHeight = CmsDomUtil.getCurrentStyleInt(m_original, Style.height);
309                m_originalWidth = CmsDomUtil.getCurrentStyleInt(m_original, Style.width);
310            } else {
311                m_originalOpacity = CmsDomUtil.getCurrentStyleFloat(m_original, Style.opacity);
312            }
313            super.run(duration);
314        }
315
316        /**
317         * @see org.opencms.gwt.client.util.A_CmsAnimation#onComplete()
318         */
319        @Override
320        protected void onComplete() {
321
322            super.onComplete();
323            m_original.getStyle().clearHeight();
324            m_original.getStyle().clearWidth();
325            m_original.getStyle().clearOverflow();
326            m_original.removeClassName(I_CmsLayoutBundle.INSTANCE.generalCss().clearFix());
327        }
328
329        /**
330         * @see com.google.gwt.animation.client.Animation#onUpdate(double)
331         */
332        @Override
333        protected void onUpdate(double progress) {
334
335            m_helper.getStyle().setOpacity(-progress + 1);
336            if (m_overlay != null) {
337                m_overlay.getStyle().setOpacity(-progress + 1);
338            }
339
340            if (m_hideOriginal) {
341                m_original.getStyle().setHeight(m_originalHeight - (m_originalHeight * progress), Unit.PX);
342                m_original.getStyle().setWidth(m_originalWidth - (m_originalWidth * progress), Unit.PX);
343            } else {
344                m_original.getStyle().setOpacity(m_originalOpacity + ((1 - m_originalOpacity) * progress));
345            }
346
347        }
348    }
349
350    /** Animation enabled flag. */
351    private AnimationType m_animationType = AnimationType.MOVE;
352
353    /** The mouse x position of the current mouse event. */
354    private int m_clientX;
355
356    /** The mouse y position of the current mouse event. */
357    private int m_clientY;
358
359    /** The Drag and drop controller. */
360    private I_CmsDNDController m_controller;
361
362    /** The current animation. */
363    private Animation m_currentAnimation;
364
365    /** The current drop target. */
366    private I_CmsDropTarget m_currentTarget;
367
368    /** The x cursor offset to the dragged element. */
369    private int m_cursorOffsetX;
370
371    /** The y cursor offset to the dragged element. */
372    private int m_cursorOffsetY;
373
374    /** The draggable. */
375    private I_CmsDraggable m_draggable;
376
377    /** The dragging flag. */
378    private boolean m_dragging;
379
380    /** The drag helper. */
381    private Element m_dragHelper;
382
383    /** The drag and drop orientation. Default is <code>ALL</code>. */
384    private Orientation m_orientation = Orientation.ALL;
385
386    /** The placeholder. */
387    private Element m_placeholder;
388
389    /** The event preview handler. */
390    private DNDEventPreviewHandler m_previewHandler;
391
392    /** The preview handler registration. */
393    private HandlerRegistration m_previewHandlerRegistration;
394
395    /** Current scroll direction. */
396    private Direction m_scrollDirection;
397
398    /** The scroll parent. */
399    private Element m_scrollElement;
400
401    /** Flag if automatic scrolling is enabled. */
402    private boolean m_scrollEnabled = true;
403
404    /** Scroll timer. */
405    private Timer m_scrollTimer;
406
407    /** The starting position absolute left. */
408    private int m_startLeft;
409
410    /** The starting position absolute top. */
411    private int m_startTop;
412
413    /** The registered drop targets. */
414    private List<I_CmsDropTarget> m_targets;
415
416    /** Flag indicating whether the CTRL key was pressed when drag started. */
417    private boolean m_modifierCTRL;
418
419    /**
420     * Constructor.<p>
421     *
422     * @param controller the drag and drop controller
423     **/
424    public CmsDNDHandler(I_CmsDNDController controller) {
425
426        m_targets = new ArrayList<I_CmsDropTarget>();
427        m_previewHandler = new DNDEventPreviewHandler();
428        m_controller = controller;
429    }
430
431    /**
432     * Adds a drop target.<p>
433     *
434     * @param target the target to add
435     */
436    public void addTarget(I_CmsDropTarget target) {
437
438        m_targets.add(target);
439    }
440
441    /**
442     * Cancels the dragging process.<p>
443     */
444    public void cancel() {
445
446        animateCancel(m_draggable, m_controller);
447    }
448
449    /**
450     * Clears the drop target register.<p>
451     */
452    public void clearTargets() {
453
454        m_targets.clear();
455    }
456
457    /**
458     * Drops the draggable.<p>
459     */
460    public void drop() {
461
462        // notifying controller, if false is returned, dropping will be canceled
463        if (!m_controller.onBeforeDrop(m_draggable, m_currentTarget, this)) {
464            cancel();
465            return;
466        }
467        animateDrop(m_draggable, m_currentTarget, m_controller);
468    }
469
470    /**
471     * Returns the drag and drop controller.<p>
472     *
473     * @return the drag and drop controller
474     */
475    public I_CmsDNDController getController() {
476
477        return m_controller;
478    }
479
480    /**
481     * Returns the current drop target.<p>
482     *
483     * @return the current drop target
484     */
485    public I_CmsDropTarget getCurrentTarget() {
486
487        return m_currentTarget;
488    }
489
490    /**
491     * Returns the cursor offset x.<p>
492     *
493     * @return the cursor offset x
494     */
495    public int getCursorOffsetX() {
496
497        return m_cursorOffsetX;
498    }
499
500    /**
501     * Returns the cursor offset y.<p>
502     *
503     * @return the cursor offset y
504     */
505    public int getCursorOffsetY() {
506
507        return m_cursorOffsetY;
508    }
509
510    /**
511     * Returns the current draggable.<p>
512     *
513     * @return the draggable
514     */
515    public I_CmsDraggable getDraggable() {
516
517        return m_draggable;
518    }
519
520    /**
521     * Returns the drag helper element.<p>
522     *
523     * @return the drag helper
524     */
525    public Element getDragHelper() {
526
527        return m_dragHelper;
528    }
529
530    /**
531     * Returns the allowed drag and drop orientation.<p>
532     *
533     * @return the drag and drop orientation
534     */
535    public Orientation getOrientation() {
536
537        return m_orientation;
538    }
539
540    /**
541     * Returns the place holder element.<p>
542     *
543     * @return the place holder element
544     */
545    public Element getPlaceholder() {
546
547        return m_placeholder;
548    }
549
550    /**
551     * Returns whether the CTRL key was pressed when drag started.<p>
552     *
553     * @return <code>true</code> if CTRL key was pressed when drag started
554     */
555    public boolean hasModifierCTRL() {
556
557        return m_modifierCTRL;
558    }
559
560    /**
561     * Returns if the animation is enabled.<p>
562     *
563     * @return <code>true</code> if the animation is enabled
564     */
565    public boolean isAnimationEnabled() {
566
567        return (m_animationType != null) && (m_animationType != AnimationType.NONE);
568    }
569
570    /**
571     * Returns if a dragging process is taking place.<p>
572     *
573     * @return <code>true</code> if the handler is currently dragging
574     */
575    public boolean isDragging() {
576
577        return m_dragging;
578    }
579
580    /**
581     * Returns if automated scrolling is enabled.<p>
582     *
583     * @return if automated scrolling is enabled
584     */
585    public boolean isScrollEnabled() {
586
587        return m_scrollEnabled;
588    }
589
590    /**
591     * @see com.google.gwt.event.dom.client.MouseDownHandler#onMouseDown(com.google.gwt.event.dom.client.MouseDownEvent)
592     */
593    public void onMouseDown(MouseDownEvent event) {
594
595        if ((event.getNativeButton() != NativeEvent.BUTTON_LEFT) || m_dragging || (m_currentAnimation != null)) {
596            // only act on left button down, ignore right click
597            // also ignore if the dragging flag is still true or an animation is still running
598            return;
599        }
600        Object source = event.getSource();
601        if (!(source instanceof I_CmsDragHandle)) {
602            // source is no drag handle, wrong DNDHandler assignment ignore
603            return;
604        }
605        I_CmsDragHandle dragHandle = (I_CmsDragHandle)source;
606        m_draggable = dragHandle.getDraggable();
607        if (m_draggable == null) {
608            // cancel dragging
609            return;
610        }
611        m_clientX = event.getClientX();
612        m_clientY = event.getClientY();
613        m_cursorOffsetX = CmsDomUtil.getRelativeX(m_clientX, m_draggable.getElement());
614        m_cursorOffsetY = CmsDomUtil.getRelativeY(m_clientY, m_draggable.getElement());
615        m_startLeft = m_draggable.getElement().getAbsoluteLeft();
616        m_startTop = m_draggable.getElement().getAbsoluteTop();
617        m_currentTarget = m_draggable.getParentTarget();
618        m_dragHelper = m_draggable.getDragHelper(m_currentTarget);
619        m_placeholder = m_draggable.getPlaceholder(m_currentTarget);
620        // notifying controller, if false is returned, dragging will be canceled
621        if (!m_controller.onDragStart(m_draggable, m_currentTarget, this)) {
622            cancel();
623            return;
624        }
625        m_draggable.onStartDrag(m_currentTarget);
626        m_dragging = true;
627        // add marker css class to enable drag and drop dependent styles
628        Document.get().getBody().addClassName(
629            org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.dragdropCss().dragStarted());
630        if (m_scrollElement == null) {
631            CmsDomUtil.getHtmlElement().addClassName(
632                org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.dragdropCss().fullWindowDrag());
633        }
634        if (m_previewHandlerRegistration != null) {
635            // this should never be the case
636            CmsDebugLog.getInstance().printLine("Preview handler already registered!!!");
637            m_previewHandlerRegistration.removeHandler();
638
639        }
640        CmsDebugLog.getInstance().printLine("Registering preview handler");
641        m_previewHandlerRegistration = Event.addNativePreviewHandler(m_previewHandler);
642        onMove((Event)event.getNativeEvent());
643    }
644
645    /**
646     * Removes a drop target from the register.<p>
647     *
648     * @param target the target to remove
649     */
650    public void removeTarget(I_CmsDropTarget target) {
651
652        m_targets.remove(target);
653    }
654
655    /**
656     * Sets the animation type.<p>
657     *
658     * @param animationType the animation type
659     */
660    public void setAnimationType(AnimationType animationType) {
661
662        m_animationType = animationType;
663    }
664
665    /**
666     * Sets the drag and drop controller.<p>
667     *
668     * @param controller the drag and drop controller to set
669     */
670    public void setController(I_CmsDNDController controller) {
671
672        m_controller = controller;
673    }
674
675    /**
676     * Sets the cursor offset x.<p>
677     *
678     * @param cursorOffsetX the cursor offset x to set
679     */
680    public void setCursorOffsetX(int cursorOffsetX) {
681
682        m_cursorOffsetX = cursorOffsetX;
683    }
684
685    /**
686     * Sets the cursor offset y.<p>
687     *
688     * @param cursorOffsetY the cursor offset y to set
689     */
690    public void setCursorOffsetY(int cursorOffsetY) {
691
692        m_cursorOffsetY = cursorOffsetY;
693    }
694
695    /**
696     * Sets the draggable.<p>
697     *
698     * @param draggable the draggable
699     */
700    public void setDraggable(I_CmsDraggable draggable) {
701
702        m_draggable = draggable;
703    }
704
705    /**
706     * Sets the drag helper element.<p>
707     *
708     * @param dragHelper the drag helper element
709     */
710    public void setDragHelper(Element dragHelper) {
711
712        m_dragHelper = dragHelper;
713    }
714
715    /**
716     * Sets the allowed drag and drop orientation.<p>
717     *
718     * @param orientation the drag and drop orientation to set
719     */
720    public void setOrientation(Orientation orientation) {
721
722        m_orientation = orientation;
723    }
724
725    /**
726     * Sets the placeholder element.<p>
727     *
728     * @param placeholder the placeholder element
729     */
730    public void setPlaceholder(Element placeholder) {
731
732        m_placeholder = placeholder;
733    }
734
735    /**
736     * Sets the scroll element in case not the window but another element needs scrolling.<p>
737     *
738     * @param scrollElement the scroll element
739     */
740    public void setScrollElement(Element scrollElement) {
741
742        m_scrollElement = scrollElement;
743    }
744
745    /**
746     * Sets the scrolling enabled.<p>
747     *
748     * @param scrollEnabled <code>true</code> to enable scrolling
749     */
750    public void setScrollEnabled(boolean scrollEnabled) {
751
752        m_scrollEnabled = scrollEnabled;
753    }
754
755    /**
756     * Sets the start position.<p>
757     * In case of a canceled drag and drop and enabled animation,
758     * the draggable helper element will be reverted to the start position.<p>
759     * Values <code>&lt;0</code> will be ignored.<p>
760     *
761     * @param left the left position
762     * @param top the top position
763     */
764    public void setStartPosition(int left, int top) {
765
766        if (left >= 0) {
767            m_startLeft = left;
768        }
769        if (top >= 0) {
770            m_startTop = top;
771        }
772    }
773
774    /**
775     * Updates the position of the helper within the the appropriate target.<p>
776     * Needs to be executed on mouse move or when the list of allowed targets changes.<p>
777     * Uses the currently stored cursor position.<p>
778     */
779    public void updatePosition() {
780
781        checkTargets();
782        positionHelper();
783        scrollAction();
784    }
785
786    /**
787     * Clears the drag process with a move animation of the drag element to it's original position.<p>
788     *
789     * @param draggable the draggable
790     * @param controller the drag and drop controller
791     */
792    protected void animateCancel(final I_CmsDraggable draggable, final I_CmsDNDController controller) {
793
794        controller.onAnimationStart(draggable, null, this);
795        stopDragging();
796        Command callback = new Command() {
797
798            /**
799             * @see com.google.gwt.user.client.Command#execute()
800             */
801            public void execute() {
802
803                controller.onDragCancel(draggable, null, CmsDNDHandler.this);
804                draggable.onDragCancel();
805                clear();
806            }
807        };
808        showEndAnimation(callback, m_startTop, m_startLeft, false);
809    }
810
811    /**
812     * Clears the drag process with a move animation of the drag element to the place-holder position.<p>
813     *
814     * @param draggable the draggable
815     * @param target the drop target
816     * @param controller the drag and drop controller
817     */
818    protected void animateDrop(
819        final I_CmsDraggable draggable,
820        final I_CmsDropTarget target,
821        final I_CmsDNDController controller) {
822
823        controller.onAnimationStart(draggable, target, this);
824        stopDragging();
825        Command callback = new Command() {
826
827            /**
828             * @see com.google.gwt.user.client.Command#execute()
829             */
830            public void execute() {
831
832                controller.onDrop(draggable, target, CmsDNDHandler.this);
833                target.onDrop(draggable);
834                draggable.onDrop(target);
835                clear();
836            }
837        };
838        CmsPositionBean placeholderPosition = CmsPositionBean.getBoundingClientRect(m_placeholder);
839        showEndAnimation(callback, placeholderPosition.getTop(), placeholderPosition.getLeft(), true);
840    }
841
842    /**
843     * Clears all references used within the current drag process.<p>
844     */
845    protected void clear() {
846
847        for (I_CmsDropTarget target : m_targets) {
848            target.removePlaceholder();
849        }
850        if (m_dragHelper != null) {
851            m_dragHelper.removeFromParent();
852            m_dragHelper = null;
853        }
854        m_placeholder = null;
855        m_currentTarget = null;
856        m_draggable = null;
857        Document.get().getBody().removeClassName(
858            org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.dragdropCss().dragStarted());
859
860        CmsDomUtil.getHtmlElement().removeClassName(
861            org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.dragdropCss().fullWindowDrag());
862
863        m_currentAnimation = null;
864        m_modifierCTRL = false;
865    }
866
867    /**
868     * Cancels the scroll timer and removes the timer reference.<p>
869     */
870    protected void clearScrollTimer() {
871
872        if (m_scrollTimer != null) {
873            m_scrollTimer.cancel();
874            m_scrollTimer = null;
875        }
876    }
877
878    /**
879     * Execute on mouse wheel event.<p>
880     *
881     * @param event the native event
882     */
883    protected void onMouseWheelScroll(Event event) {
884
885        int scrollStep = event.getMouseWheelVelocityY() * 5;
886        Element scrollTarget;
887        if (getCurrentTarget() != null) {
888            scrollTarget = getCurrentTarget().getElement();
889        } else {
890            scrollTarget = RootPanel.getBodyElement();
891        }
892        while ((scrollTarget.getScrollHeight() == scrollTarget.getClientHeight())
893            && (scrollTarget != RootPanel.getBodyElement())) {
894            scrollTarget = scrollTarget.getParentElement();
895        }
896        if (scrollTarget == RootPanel.getBodyElement()) {
897            int top = Window.getScrollTop() + scrollStep;
898            int left = Window.getScrollLeft();
899            if (top < 0) {
900                top = 0;
901            }
902            Window.scrollTo(left, top);
903        } else {
904            int top = scrollTarget.getScrollTop() + scrollStep;
905            if (top < 0) {
906                top = 0;
907            }
908            scrollTarget.setScrollTop(top);
909        }
910        onMove(event);
911    }
912
913    /**
914     * Executed on mouse move while dragging.<p>
915     *
916     * @param event the event
917     */
918    protected void onMove(Event event) {
919
920        m_clientX = event.getClientX();
921        m_clientY = event.getClientY();
922        updatePosition();
923    }
924
925    /**
926     * Executed on mouse up while dragging.<p>
927     *
928     * @param event the event
929     */
930    protected void onUp(Event event) {
931
932        m_clientX = event.getClientX();
933        m_clientY = event.getClientY();
934        m_modifierCTRL = event.getCtrlKey() || event.getMetaKey();
935        if ((m_currentTarget == null) || (m_currentTarget.getPlaceholderIndex() < 0)) {
936            cancel();
937        } else {
938            drop();
939        }
940    }
941
942    /**
943     * 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>
944     */
945    protected void positionHelper() {
946
947        if (m_dragHelper == null) {
948            // should never happen
949            CmsDebugLog.getInstance().printLine("Drag helper is null");
950            return;
951        }
952        Element parentElement = m_dragHelper.getParentElement();
953        int left = CmsDomUtil.getRelativeX(m_clientX, parentElement) - m_cursorOffsetX;
954        int top = CmsDomUtil.getRelativeY(m_clientY, parentElement) - m_cursorOffsetY;
955        m_dragHelper.getStyle().setLeft(left, Unit.PX);
956        m_dragHelper.getStyle().setTop(top, Unit.PX);
957    }
958
959    /**
960     * Sets dragging to false and removes the event preview handler.<p>
961     */
962    protected void stopDragging() {
963
964        clearScrollTimer();
965        m_dragging = false;
966        if (m_previewHandlerRegistration != null) {
967            m_previewHandlerRegistration.removeHandler();
968            m_previewHandlerRegistration = null;
969        }
970    }
971
972    /**
973     * Method will check all registered drop targets if the element is positioned over one of them.<p>
974     */
975    private void checkTargets() {
976
977        // checking current target first
978        if ((m_currentTarget != null) && m_currentTarget.checkPosition(m_clientX, m_clientY, m_orientation)) {
979            // in case of nested targets, check the children of the current target
980            if ((m_currentTarget instanceof I_CmsNestedDropTarget)
981                && ((I_CmsNestedDropTarget)m_currentTarget).hasDnDChildren()) {
982                for (I_CmsDropTarget target : ((I_CmsNestedDropTarget)m_currentTarget).getDnDChildren()) {
983                    if ((target != m_currentTarget) && target.checkPosition(m_clientX, m_clientY, m_orientation)) {
984                        // notifying controller, if false is returned, placeholder will not be positioned inside target
985                        if (m_controller.onTargetEnter(m_draggable, target, this)) {
986                            target.insertPlaceholder(m_placeholder, m_clientX, m_clientY, m_orientation);
987                            m_currentTarget = target;
988                            m_controller.onPositionedPlaceholder(m_draggable, m_currentTarget, this);
989                            return;
990                        }
991                    }
992                }
993            }
994            if (m_currentTarget.getPlaceholderIndex() < 0) {
995                m_currentTarget.insertPlaceholder(m_placeholder, m_clientX, m_clientY, m_orientation);
996            } else {
997                m_currentTarget.repositionPlaceholder(m_clientX, m_clientY, m_orientation);
998            }
999
1000            m_controller.onPositionedPlaceholder(m_draggable, m_currentTarget, this);
1001        } else {
1002            // leaving the current target
1003            if (m_currentTarget != null) {
1004                m_controller.onTargetLeave(m_draggable, m_currentTarget, this);
1005            }
1006            for (I_CmsDropTarget target : m_targets) {
1007                if ((target != m_currentTarget) && target.checkPosition(m_clientX, m_clientY, m_orientation)) {
1008                    // notifying controller, if false is returned, placeholder will not be positioned inside target
1009                    if (m_controller.onTargetEnter(m_draggable, target, this)) {
1010                        target.insertPlaceholder(m_placeholder, m_clientX, m_clientY, m_orientation);
1011                        m_currentTarget = target;
1012                        m_controller.onPositionedPlaceholder(m_draggable, m_currentTarget, this);
1013                        return;
1014                    }
1015                }
1016            }
1017            // mouse position is not over any registered target
1018            m_currentTarget = null;
1019        }
1020    }
1021
1022    /**
1023     * Convenience method to get the appropriate scroll direction.<p>
1024     *
1025     * @param offset the scroll parent border offset, if the cursor is within the border offset, scrolling should be triggered
1026     *
1027     * @return the scroll direction
1028     */
1029    private Direction getScrollDirection(int offset) {
1030
1031        if (m_scrollElement == null) {
1032            Element body = RootPanel.getBodyElement();
1033            int windowHeight = Window.getClientHeight();
1034            int bodyHeight = body.getClientHeight();
1035            if (windowHeight < bodyHeight) {
1036                if (((windowHeight - m_clientY) < offset) && (Window.getScrollTop() < (bodyHeight - windowHeight))) {
1037                    return Direction.down;
1038                }
1039                if ((m_clientY < offset) && (Window.getScrollTop() > 0)) {
1040                    return Direction.up;
1041                }
1042            }
1043
1044            int windowWidth = Window.getClientWidth();
1045            int bodyWidth = body.getClientWidth();
1046            if (windowWidth < bodyWidth) {
1047                if (((windowWidth - m_clientX) < offset) && (Window.getScrollLeft() < (bodyWidth - windowWidth))) {
1048                    return Direction.right;
1049                }
1050                if ((m_clientX < offset) && (Window.getScrollLeft() > 0)) {
1051                    return Direction.left;
1052                }
1053            }
1054
1055            return null;
1056        } else {
1057            int outerHeight = m_scrollElement.getClientHeight();
1058            int innerHeight = m_scrollElement.getScrollHeight();
1059            if (outerHeight < innerHeight) {
1060                if ((((outerHeight + m_scrollElement.getAbsoluteTop()) - m_clientY) < offset)
1061                    && (m_scrollElement.getScrollTop() < (innerHeight - outerHeight))) {
1062                    return Direction.down;
1063                }
1064                if (((m_clientY - m_scrollElement.getAbsoluteTop()) < offset) && (m_scrollElement.getScrollTop() > 0)) {
1065                    return Direction.up;
1066                }
1067            }
1068            int outerWidth = m_scrollElement.getClientWidth();
1069            int innerWidth = m_scrollElement.getScrollWidth();
1070            if (outerWidth < innerWidth) {
1071                if ((((outerWidth + m_scrollElement.getAbsoluteLeft()) - m_clientX) < offset)
1072                    && (m_scrollElement.getScrollLeft() < (innerWidth - outerWidth))) {
1073                    return Direction.right;
1074                }
1075                if (((m_clientX - m_scrollElement.getAbsoluteLeft()) < offset)
1076                    && (m_scrollElement.getScrollLeft() > 0)) {
1077                    return Direction.left;
1078                }
1079            }
1080            return null;
1081        }
1082    }
1083
1084    /**
1085     * Handles automated scrolling.<p>
1086     */
1087    private void scrollAction() {
1088
1089        if (m_scrollEnabled) {
1090
1091            Direction direction = getScrollDirection(50);
1092            if ((m_scrollTimer != null) && (m_scrollDirection != direction)) {
1093                clearScrollTimer();
1094            }
1095            if ((direction != null) && (m_scrollTimer == null)) {
1096                m_scrollTimer = new CmsScrollTimer(
1097                    m_scrollElement != null ? m_scrollElement : RootPanel.getBodyElement(),
1098                    20,
1099                    direction);
1100                m_scrollTimer.scheduleRepeating(10);
1101            }
1102
1103            m_scrollDirection = direction;
1104        }
1105    }
1106
1107    /**
1108     * Shows the end animation on drop or cancel. Executes the given callback afterwards.<p>
1109     *
1110     * @param callback the callback to execute
1111     * @param top absolute top of the animation end position
1112     * @param left absolute left of the animation end position
1113     * @param isDrop if the animation is done on drop
1114     */
1115    private void showEndAnimation(Command callback, int top, int left, boolean isDrop) {
1116
1117        if (!isAnimationEnabled() || (m_dragHelper == null)) {
1118            callback.execute();
1119            return;
1120        }
1121        switch (m_animationType) {
1122            case SPECIAL:
1123                List<Element> overlays = CmsDomUtil.getElementsByClass(
1124                    I_CmsLayoutBundle.INSTANCE.generalCss().disablingOverlay(),
1125                    m_draggable.getElement());
1126                Element overlay = overlays.size() > 0 ? overlays.get(0) : null;
1127                m_currentAnimation = new SpecialAnimation(
1128                    m_dragHelper,
1129                    m_draggable.getElement(),
1130                    overlay,
1131                    callback,
1132                    isDrop);
1133
1134                break;
1135
1136            case MOVE:
1137                Element parentElement = m_dragHelper.getParentElement();
1138                int endTop = top - parentElement.getAbsoluteTop();
1139                int endLeft = left - parentElement.getAbsoluteLeft();
1140                int startTop = CmsDomUtil.getCurrentStyleInt(m_dragHelper, Style.top);
1141                int startLeft = CmsDomUtil.getCurrentStyleInt(m_dragHelper, Style.left);
1142                m_currentAnimation = new CmsMoveAnimation(m_dragHelper, startTop, startLeft, endTop, endLeft, callback);
1143                break;
1144
1145            default:
1146                // nothing to do
1147        }
1148        m_currentAnimation.run(400);
1149    }
1150}