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><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}