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.ade.containerpage.client; 029 030import org.opencms.ade.containerpage.client.ui.CmsContainerPageContainer; 031import org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel; 032import org.opencms.ade.containerpage.client.ui.CmsDroppedElementModeSelectionDialog; 033import org.opencms.ade.containerpage.client.ui.CmsElementOptionBar; 034import org.opencms.ade.containerpage.client.ui.CmsGroupContainerElementPanel; 035import org.opencms.ade.containerpage.client.ui.CmsToolbarAllGalleriesMenu; 036import org.opencms.ade.containerpage.client.ui.I_CmsDropContainer; 037import org.opencms.ade.containerpage.client.ui.css.I_CmsLayoutBundle; 038import org.opencms.ade.containerpage.shared.CmsCntPageData.ElementReuseMode; 039import org.opencms.ade.containerpage.shared.CmsContainer; 040import org.opencms.ade.containerpage.shared.CmsContainerElement; 041import org.opencms.ade.containerpage.shared.CmsContainerElementData; 042import org.opencms.ade.contenteditor.client.Messages; 043import org.opencms.ade.contenteditor.shared.CmsEditorConstants; 044import org.opencms.ade.galleries.client.ui.CmsResultListItem; 045import org.opencms.gwt.client.CmsCoreProvider; 046import org.opencms.gwt.client.dnd.CmsDNDHandler; 047import org.opencms.gwt.client.dnd.CmsDNDHandler.Orientation; 048import org.opencms.gwt.client.dnd.I_CmsDNDController; 049import org.opencms.gwt.client.dnd.I_CmsDraggable; 050import org.opencms.gwt.client.dnd.I_CmsDropTarget; 051import org.opencms.gwt.client.ui.CmsErrorDialog; 052import org.opencms.gwt.client.ui.CmsHighlightingBorder; 053import org.opencms.gwt.client.ui.CmsList; 054import org.opencms.gwt.client.ui.CmsListItem; 055import org.opencms.gwt.client.ui.CmsListItemWidget; 056import org.opencms.gwt.client.ui.CmsPushButton; 057import org.opencms.gwt.client.ui.CmsToolbar; 058import org.opencms.gwt.client.ui.I_CmsButton; 059import org.opencms.gwt.client.ui.I_CmsButton.ButtonStyle; 060import org.opencms.gwt.client.ui.I_CmsButton.Size; 061import org.opencms.gwt.client.util.CmsDebugLog; 062import org.opencms.gwt.client.util.CmsDomUtil; 063import org.opencms.gwt.client.util.CmsPositionBean; 064import org.opencms.gwt.client.util.I_CmsSimpleCallback; 065import org.opencms.gwt.shared.CmsGwtLog; 066import org.opencms.gwt.shared.CmsListInfoBean; 067import org.opencms.gwt.shared.CmsTemplateContextInfo; 068import org.opencms.util.CmsStringUtil; 069import org.opencms.util.CmsUUID; 070 071import java.util.ArrayList; 072import java.util.Comparator; 073import java.util.HashMap; 074import java.util.HashSet; 075import java.util.List; 076import java.util.Map; 077import java.util.Map.Entry; 078import java.util.Set; 079 080import com.google.common.base.Objects; 081import com.google.common.collect.ComparisonChain; 082import com.google.gwt.core.client.Scheduler; 083import com.google.gwt.core.client.Scheduler.ScheduledCommand; 084import com.google.gwt.dom.client.Document; 085import com.google.gwt.dom.client.Element; 086import com.google.gwt.dom.client.Style; 087import com.google.gwt.dom.client.Style.Display; 088import com.google.gwt.dom.client.Style.Position; 089import com.google.gwt.dom.client.Style.Unit; 090import com.google.gwt.dom.client.Style.Visibility; 091import com.google.gwt.event.dom.client.ClickEvent; 092import com.google.gwt.event.dom.client.ClickHandler; 093import com.google.gwt.event.dom.client.HasClickHandlers; 094import com.google.gwt.event.dom.client.HasMouseOutHandlers; 095import com.google.gwt.event.dom.client.HasMouseOverHandlers; 096import com.google.gwt.event.dom.client.KeyCodes; 097import com.google.gwt.event.dom.client.MouseOutEvent; 098import com.google.gwt.event.dom.client.MouseOutHandler; 099import com.google.gwt.event.dom.client.MouseOverEvent; 100import com.google.gwt.event.dom.client.MouseOverHandler; 101import com.google.gwt.event.shared.HandlerRegistration; 102import com.google.gwt.user.client.DOM; 103import com.google.gwt.user.client.Event; 104import com.google.gwt.user.client.Window; 105import com.google.gwt.user.client.rpc.AsyncCallback; 106import com.google.gwt.user.client.ui.FlowPanel; 107import com.google.gwt.user.client.ui.RootPanel; 108import com.google.gwt.user.client.ui.Widget; 109 110import elemental2.dom.DOMRect; 111import elemental2.dom.DomGlobal; 112import elemental2.dom.HTMLDivElement; 113import elemental2.dom.Node; 114import elemental2.dom.NodeList; 115import jsinterop.base.Any; 116import jsinterop.base.Js; 117import jsinterop.base.JsPropertyMap; 118 119/** 120 * The container-page editor drag and drop controller.<p> 121 * 122 * @since 8.0.0 123 */ 124public class CmsContainerpageDNDController implements I_CmsDNDController { 125 126 /** 127 * The Class CmsPlacementModeContext. 128 */ 129 class CmsPlacementModeContext { 130 131 /** 132 * Buttons used to place elements in placement mode. 133 */ 134 class PlacementButton extends FlowPanel implements HasClickHandlers, HasMouseOverHandlers, HasMouseOutHandlers { 135 136 /** The associated container. */ 137 private CmsContainerPageContainer m_container; 138 139 /** The height. */ 140 private int m_height; 141 142 /** The internal index (used for sorting). */ 143 private int m_index; 144 145 /** The left. */ 146 private int m_left; 147 148 /** Thetop. */ 149 private int m_top; 150 151 /** The width. */ 152 private int m_width; 153 154 /** 155 * Creates a new instance. 156 * 157 * @param size the size 158 */ 159 public PlacementButton(int size) { 160 161 addStyleName(OC_PLACEMENT_BUTTON); 162 String alpha = "abcdefghijklmnopqrstuvwxyz"; 163 String id = "pb_"; 164 for (int i = 0; i < 5; i++) { 165 int index = (int)Math.floor(Math.random() * alpha.length()); 166 id = id + alpha.charAt(index); 167 } 168 getElement().setId(id); 169 m_width = size; 170 m_height = size; 171 m_index = m_buttonCounter++; 172 173 } 174 175 /** 176 * @see com.google.gwt.event.dom.client.HasClickHandlers#addClickHandler(com.google.gwt.event.dom.client.ClickHandler) 177 */ 178 @Override 179 public HandlerRegistration addClickHandler(ClickHandler handler) { 180 181 ClickHandler handler2 = event -> { 182 event.stopPropagation(); 183 handler.onClick(event); 184 }; 185 return addDomHandler(handler2, ClickEvent.getType()); 186 } 187 188 /** 189 * @see com.google.gwt.event.dom.client.HasMouseOutHandlers#addMouseOutHandler(com.google.gwt.event.dom.client.MouseOutHandler) 190 */ 191 @Override 192 public HandlerRegistration addMouseOutHandler(MouseOutHandler handler) { 193 194 return addDomHandler(handler, MouseOutEvent.getType()); 195 } 196 197 /** 198 * @see com.google.gwt.event.dom.client.HasMouseOverHandlers#addMouseOverHandler(com.google.gwt.event.dom.client.MouseOverHandler) 199 */ 200 @Override 201 public HandlerRegistration addMouseOverHandler(MouseOverHandler handler) { 202 203 return addDomHandler(handler, MouseOverEvent.getType()); 204 205 } 206 207 /** 208 * Gets the associated container. 209 * 210 * @return the associated container 211 */ 212 public CmsContainerPageContainer getContainer() { 213 214 return m_container; 215 } 216 217 /** 218 * Gets the height. 219 * 220 * @return the height 221 */ 222 public int getHeight() { 223 224 return m_height; 225 } 226 227 /** 228 * Gets the index. 229 * 230 * @return the index 231 */ 232 public int getIndex() { 233 234 return m_index; 235 } 236 237 /** 238 * Gets the left. 239 * 240 * @return the left 241 */ 242 public int getLeft() { 243 244 return m_left; 245 } 246 247 /** 248 * Gets the top. 249 * 250 * @return the top 251 */ 252 public int getTop() { 253 254 return m_top; 255 } 256 257 /** 258 * Gets the width. 259 * 260 * @return the width 261 */ 262 public int getWidth() { 263 264 return m_width; 265 } 266 267 /** 268 * Checks if other placement button's position intersects this one's. 269 * 270 * @param other the other placement button 271 * @return true if the buttons intersect 272 */ 273 public boolean intersects(PlacementButton other) { 274 275 int t = m_collisionTolerance; 276 return segmentIntersect(m_left + t, m_width - (2 * t), other.m_left + t, other.m_width - (2 * t)) 277 && segmentIntersect(m_top + t, m_height - (2 * t), other.m_top + t, other.m_height - (2 * t)); 278 279 } 280 281 /** 282 * Checks if this button is fully before another (taking into account the tolerance zones of both). 283 * 284 * @param second the other button 285 * @return true if this button (except tolerance zones) is fully before the other button (except for its tolerance zones) 286 * 287 */ 288 public boolean isFullyBefore(PlacementButton second) { 289 290 return ((getLeft() + getWidth()) - m_collisionTolerance) <= (second.getLeft() + m_collisionTolerance); 291 } 292 293 /** 294 * Moves this button to the right of another button. 295 * 296 * @param second the other button 297 */ 298 public void moveToRightOf(PlacementButton second) { 299 300 m_left = (second.getLeft() + second.getWidth()) - (2 * m_collisionTolerance); 301 } 302 303 /** 304 * Sets the container. 305 * 306 * @param container the new container 307 */ 308 public void setContainer(CmsContainerPageContainer container) { 309 310 m_container = container; 311 } 312 313 /** 314 * Sets the left. 315 * 316 * @param left the new left 317 */ 318 public void setLeft(int left) { 319 320 m_left = left; 321 } 322 323 /** 324 * Actually sets the position on the DOM element. 325 */ 326 public void setPosition() { 327 328 getElement().getStyle().setLeft(m_left, Unit.PX); 329 getElement().getStyle().setTop(m_top, Unit.PX); 330 } 331 332 /** 333 * Sets the top. 334 * 335 * @param top the new top 336 */ 337 public void setTop(int top) { 338 339 m_top = top; 340 } 341 342 /** 343 * @see com.google.gwt.user.client.ui.UIObject#toString() 344 */ 345 public String toString() { 346 347 return getElement().getId() + ":(" + getLeft() + ", " + getTop() + ")"; 348 } 349 } 350 351 /** Single-element array holding the currently visible highlighting element. */ 352 private CmsHighlightingBorder m_activeBorder; 353 354 /** The list of active buttons. */ 355 private List<PlacementButton> m_buttons = new ArrayList<>(); 356 357 /** The placemenet button size (width or height). */ 358 private int m_buttonSize; 359 360 /** The callback to call when an element is placed. */ 361 private I_PlacementCallback m_callback; 362 363 /** The tolerance for button collisions. */ 364 private int m_collisionTolerance; 365 366 /** Numeric rank for containers, used for resolving button collisions. */ 367 private Map<String, Integer> m_containerIndexes = new HashMap<>(); 368 369 /** The set of available container names. */ 370 private Set<String> m_containers; 371 372 /** The DND handler. */ 373 private CmsDNDHandler m_dndHandler; 374 375 /** The current draggable. */ 376 private I_CmsDraggable m_draggable; 377 378 /** The special layer used to display placement buttons and block click events for the rest of the page. */ 379 private PlacementLayer m_layer; 380 381 /** The container page handler. */ 382 private CmsContainerpageHandler m_pageHandler; 383 384 /** The placement mode toolbar. */ 385 private CmsToolbar m_toolbar; 386 387 /** A widget to display in the toolbar. */ 388 private Widget m_toolbarWidget; 389 390 /** 391 * Creates a new placement mode context. 392 * 393 * @param containers the set of ids of available containers 394 * @param toolbarWidget the additional widget to display in the toolbar 395 * @param dndHandler the drag/drop handler 396 * @param draggable the current draggable 397 * @param callback the callback to call when placing an element 398 */ 399 public CmsPlacementModeContext( 400 Set<String> containers, 401 Widget toolbarWidget, 402 CmsDNDHandler dndHandler, 403 I_CmsDraggable draggable, 404 I_PlacementCallback callback) { 405 406 m_containers = containers; 407 m_callback = callback; 408 m_pageHandler = m_controller.getHandler(); 409 m_dndHandler = dndHandler; 410 m_toolbarWidget = toolbarWidget; 411 m_draggable = draggable; 412 m_collisionTolerance = getTolerance(); 413 414 } 415 416 /** 417 * Cleans up everything related to the placement mode. 418 */ 419 public void destroy() { 420 421 if (m_layer != null) { 422 m_layer.removeFromParent(); 423 } 424 NodeList<elemental2.dom.Element> placeholders = DomGlobal.document.querySelectorAll( 425 "." + OC_PLACEMENT_PLACEHOLDER); 426 for (int i = 0; i < placeholders.length; i++) { 427 elemental2.dom.Element placeholder = placeholders.getAt(i); 428 placeholder.remove(); 429 } 430 NodeList<elemental2.dom.Element> selectedContainerElements = DomGlobal.document.querySelectorAll( 431 "." + OC_PLACEMENT_SELECTED_ELEMENT); 432 for (int i = 0; i < selectedContainerElements.length; i++) { 433 selectedContainerElements.getAt(i).classList.remove(OC_PLACEMENT_SELECTED_ELEMENT); 434 } 435 RootPanel.get().removeStyleName(OC_PLACEMENT_MODE); 436 m_pageHandler.setEditButtonsVisible(true); 437 m_controller.getHandler().enableToolbarButtons(); 438 m_toolbar.removeFromParent(); 439 Map<String, CmsContainerPageContainer> containerMap = m_controller.getContainerTargets(); 440 for (CmsContainerPageContainer container : containerMap.values()) { 441 container.checkEmptyContainers(); 442 } 443 m_controller.setPreviewHandler(null); 444 } 445 446 /** 447 * Initializes the placement mode. 448 */ 449 public void init() { 450 451 m_controller.hideEditableListButtons(); 452 m_controller.getHandler().disableToolbarButtons(); 453 m_controller.getHandler().hideMenu(); 454 initToolbar(); 455 m_pageHandler.setEditButtonsVisible(false); 456 RootPanel.get().addStyleName(OC_PLACEMENT_MODE); 457 if (CmsCoreProvider.TOUCH_ONLY.matches()) { 458 m_buttonSize = PLACEMENT_BUTTON_BIG; 459 } else { 460 m_buttonSize = PLACEMENT_BUTTON_SMALL; 461 } 462 initPlacementLayer(); 463 m_controller.setPreviewHandler(event -> { 464 Event nativeEvent = Event.as(event.getNativeEvent()); 465 if (event.getTypeInt() == Event.ONKEYDOWN) { 466 int keyCode = nativeEvent.getKeyCode(); 467 if ((keyCode == KeyCodes.KEY_CTRL) 468 || (keyCode == KeyCodes.KEY_SHIFT) 469 || (keyCode == KeyCodes.KEY_ALT)) { 470 // In a VM, when the user presses Ctrl+E, the keydown event for the Ctrl event may or may not wait to be fired until the E key is pressed, depending on the settings. 471 // To get consistent behavior, we ignore keydown events with keycodes that are just modifier keys. 472 return; 473 } else { 474 stopDrag(m_dndHandler); 475 } 476 } else if ((event.getTypeInt() == Event.ONMOUSEOVER) 477 || (event.getTypeInt() == Event.ONMOUSEDOWN) 478 || (event.getTypeInt() == Event.ONCLICK) 479 || (event.getTypeInt() == Event.ONMOUSEUP)) { 480 // ignore likely mouse events on floating headers etc. 481 try { 482 boolean isChildOfLayer = (m_layer != null) 483 && m_layer.getElement().isOrHasChild(Element.as(event.getNativeEvent().getEventTarget())); 484 boolean isChildOfToolbar = (m_toolbar != null) 485 && m_toolbar.getElement().isOrHasChild(Element.as(event.getNativeEvent().getEventTarget())); 486 if (!isChildOfLayer && !isChildOfToolbar) { 487 event.cancel(); 488 event.getNativeEvent().preventDefault(); 489 event.getNativeEvent().stopPropagation(); 490 } 491 } catch (Exception e) { 492 493 } 494 } 495 }); 496 497 } 498 499 /** 500 * Adds a new placement button. 501 * 502 * @param container the container 503 * @return the placement button 504 */ 505 private PlacementButton addButton(CmsContainerPageContainer container) { 506 507 PlacementButton button = new PlacementButton(m_buttonSize); 508 m_buttons.add(button); 509 m_layer.add(button); 510 button.setContainer(container); 511 addHighlightingMouseHandlers(container, button); 512 return button; 513 514 } 515 516 /** 517 * Adds mouse handlers for showing/hiding container borders when hovering over placement buttons. 518 * 519 * @param container the container to which the button belongs 520 * @param button the button 521 */ 522 private void addHighlightingMouseHandlers(CmsContainerPageContainer container, PlacementButton button) { 523 524 button.addMouseOverHandler(event -> { 525 if (m_activeBorder != null) { 526 m_activeBorder.getElement().getStyle().setVisibility(Visibility.HIDDEN); 527 } 528 m_activeBorder = container.getHighlighting(); 529 m_activeBorder.getElement().getStyle().setVisibility(Visibility.VISIBLE); 530 }); 531 button.addMouseOutHandler(event -> { 532 if (m_activeBorder != null) { 533 m_activeBorder.getElement().getStyle().setVisibility(Visibility.HIDDEN); 534 m_activeBorder = null; 535 } 536 container.getHighlighting().getElement().getStyle().setVisibility(Visibility.HIDDEN); 537 }); 538 } 539 540 /** 541 * Creates a push button for the edit tool-bar.<p> 542 * 543 * @param title the button title 544 * @param imageClass the image class 545 * 546 * @return the button 547 */ 548 private CmsPushButton createButton(String title, String imageClass) { 549 550 CmsPushButton result = new CmsPushButton(); 551 result.setTitle(title); 552 result.setImageClass(imageClass); 553 result.setButtonStyle(ButtonStyle.FONT_ICON, null); 554 result.setSize(Size.big); 555 return result; 556 } 557 558 /** 559 * Gets the drag-and-drop handler. 560 * 561 * @return the drag and drop handler 562 */ 563 private CmsDNDHandler getDNDHandler() { 564 565 return m_dndHandler; 566 } 567 568 /** 569 * Gets the placement button size (Width or height). 570 * 571 * @return the placement button size 572 */ 573 private int getPlacementButtonSize() { 574 575 return m_buttonSize; 576 } 577 578 /** 579 * Gets the width of the 'tolerance zone' in pixels around the sides of the placement button which does not count for collision detection. 580 * 581 * @return the width of the tolerance zone 582 */ 583 private int getTolerance() { 584 585 if (CmsCoreProvider.TOUCH_ONLY.matches()) { 586 return 0; 587 } 588 JsPropertyMap<?> window = Js.cast(DomGlobal.window); 589 Any tolerance = window.getAsAny("ocPlacementButtonTolerance"); 590 if (tolerance != null) { 591 return tolerance.asInt(); 592 } else { 593 return 1; 594 } 595 } 596 597 /** 598 * Does all placement mode initializations related to the button layer. 599 */ 600 private void initPlacementLayer() { 601 602 if (m_layer != null) { 603 m_layer.removeFromParent(); 604 } 605 m_layer = new PlacementLayer(); 606 m_layer.addStyleName(OC_PLACEMENT_LAYER); 607 RootPanel.get().add(m_layer); 608 m_layer.addClickHandler(event -> { 609 stopDrag(m_dndHandler); 610 }); 611 Map<String, CmsContainerPageContainer> containerMap = m_controller.getContainerTargets(); 612 List<CmsContainerPageContainer> usedContainers = new ArrayList<>(); 613 for (CmsContainerPageContainer container : containerMap.values()) { 614 if (m_containers.contains(container.getContainerId())) { 615 if (container.getElement().getOffsetParent() == null) { 616 // offsetParent == null means element or its ancestor has display: none 617 continue; 618 } 619 if (container.isDetailView()) { 620 continue; 621 } 622 usedContainers.add(container); 623 } 624 625 } 626 // Sort containers in DOM pre-order. That means parents come before their children. 627 // The indexes of that list are used later for adjusting positions of colliding buttons. 628 usedContainers.sort(new Comparator<CmsContainerPageContainer>() { 629 630 @Override 631 public int compare(CmsContainerPageContainer o1, CmsContainerPageContainer o2) { 632 633 if (o1 == o2) { 634 return 0; 635 } 636 637 elemental2.dom.Element e1 = Js.cast(o1.getElement()); 638 elemental2.dom.Element e2 = Js.cast(o2.getElement()); 639 int cmpResult = e1.compareDocumentPosition(e2); 640 if ((cmpResult & Node.DOCUMENT_POSITION_PRECEDING) != 0) { 641 return 1; 642 } else { 643 return -1; 644 } 645 } 646 }); 647 int containerIndex = 0; 648 for (CmsContainerPageContainer container : usedContainers) { 649 m_containerIndexes.put(container.getContainerId(), Integer.valueOf(containerIndex)); 650 containerIndex += 1; 651 } 652 653 for (CmsContainerPageContainer container : usedContainers) { 654 655 List<Integer> offsets = null; 656 if (container.getHighlighting() != null) { 657 offsets = container.getHighlighting().getClientVerticalOffsets(); 658 } 659 List<CmsContainerPageElementPanel> elements = container.getAllDragElements(); 660 if (elements.size() == 0) { 661 installPlacementElement(container); 662 } else { 663 if ((offsets == null) || (offsets.size() != (elements.size() + 1))) { 664 for (int i = 0; i < elements.size(); i++) { 665 if (!isMovedElement(elements.get(i))) { 666 installButtons(container, i, elements.get(i)); 667 } 668 } 669 } else { 670 installPlacementButtonsWithMidpoints(container, offsets); 671 } 672 } 673 674 } 675 positionButtons(m_buttons); 676 } 677 678 /** 679 * Generates the button bar displayed beneath the editable fields.<p> 680 */ 681 private void initToolbar() { 682 683 m_toolbar = new CmsToolbar(); 684 m_toolbar.setAppTitle( 685 org.opencms.ade.containerpage.client.Messages.get().key( 686 org.opencms.ade.containerpage.client.Messages.GUI_TOOLBAR_PLACE_ELEMENT_0)); 687 m_toolbar.getToolbarCenter().clear(); 688 m_toolbar.getToolbarCenter().add(m_toolbarWidget); 689 690 CmsPushButton cancelButton = createButton( 691 Messages.get().key(Messages.GUI_TOOLBAR_RESET_0), 692 I_CmsButton.ButtonData.RESET_BUTTON.getIconClass()); 693 cancelButton.addClickHandler(new ClickHandler() { 694 695 public void onClick(ClickEvent event) { 696 697 stopDrag(m_dndHandler); 698 } 699 }); 700 m_toolbar.addRight(cancelButton); 701 m_toolbar.addStyleName( 702 org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.toolbarCss().toolbarPlacementMode()); 703 RootPanel.get().add(m_toolbar); 704 } 705 706 /** 707 * Installs the placement buttons for a container element in the button layer. 708 * 709 * @param container the container 710 * @param position the position 711 * @param element the container element 712 */ 713 private void installButtons( 714 CmsContainerPageContainer container, 715 int position, 716 CmsContainerPageElementPanel element) { 717 718 int bw = getPlacementButtonSize(); 719 720 container.getHighlighting().getElement().getStyle().setVisibility(Visibility.HIDDEN); 721 elemental2.dom.Element elem = Js.cast(element.getElement()); 722 elemental2.dom.Element layerElem = Js.cast(m_layer.getElement()); 723 DOMRect layerRect = layerElem.getBoundingClientRect(); 724 DOMRect elemRect = elem.getBoundingClientRect(); 725 if ((elemRect.width == 0) || (elemRect.height == 0)) { 726 return; 727 } 728 PlacementButton before = addButton(container); 729 PlacementButton after = addButton(container); 730 before.addClickHandler(e -> m_callback.place(container, element, 0)); 731 after.addClickHandler(e -> m_callback.place(container, element, 1)); 732 before.addStyleName(OC_PLACEMENT_BUTTON); 733 after.addStyleName(OC_PLACEMENT_BUTTON); 734 boolean leftRight = !CmsDomUtil.hasClass(OC_PLACEMENT_BUTTONS_VERTICAL, container.getElement()); 735 if (leftRight) { 736 before.addStyleName(OC_PLACEMENT_LEFT); 737 after.addStyleName(OC_PLACEMENT_RIGHT); 738 double top = ((elemRect.top - layerRect.top) + (elemRect.height / 2.0)) - (bw / 2.0); 739 double left = elemRect.left - layerRect.left; 740 before.setLeft((int)left); 741 before.setTop((int)top); 742 after.setLeft((int)Math.round((left + elemRect.width) - m_buttonSize)); 743 after.setTop((int)top); 744 } else { 745 before.addStyleName(OC_PLACEMENT_UP); 746 after.addStyleName(OC_PLACEMENT_DOWN); 747 int top = (int)Math.round(elemRect.top - layerRect.top); 748 int left = (int)Math.round(((elemRect.left - layerRect.left) + (0.5 * elemRect.width)) - (0.5 * bw)); 749 before.setLeft(left); 750 before.setTop(top); 751 752 after.setLeft(left); 753 after.setTop((int)Math.round(((top + elemRect.height) - bw))); 754 } 755 } 756 757 /** 758 * Installs placement buttons aligned with the highlighting separators between container elements (except for the first and last button). 759 * 760 * @param container the container for which to install the buttons 761 * @param offsets the offsets relative to the viewport of the midpoints 762 */ 763 private void installPlacementButtonsWithMidpoints(CmsContainerPageContainer container, List<Integer> offsets) { 764 765 int bw = getPlacementButtonSize(); 766 767 List<CmsContainerPageElementPanel> elements = container.getAllDragElements(); 768 elemental2.dom.Element layerElem = Js.cast(m_layer.getElement()); 769 DOMRect layerRect = layerElem.getBoundingClientRect(); 770 container.getHighlighting().getElement().getStyle().setVisibility(Visibility.HIDDEN); 771 772 elemental2.dom.Element containerElem = Js.cast(container.getElement()); 773 DOMRect containerRect = containerElem.getBoundingClientRect(); 774 double middle = (containerRect.left - layerRect.left) + (0.5 * containerRect.width); 775 for (int j = 0; j < offsets.size(); j++) { 776 if (j == 0) { 777 CmsContainerPageElementPanel element = elements.get(0); 778 if (!isMovedElement(element)) { 779 PlacementButton button = addButton(container); 780 elemental2.dom.Element realElement = Js.cast(element.getElement()); 781 button.addClickHandler(e -> m_callback.place(container, element, 0)); 782 button.addStyleName(OC_PLACEMENT_UP); 783 DOMRect elemRect = realElement.getBoundingClientRect(); 784 785 int top = (int)Math.round(elemRect.top - layerRect.top); 786 int left = (int)Math.round(middle - (0.5 * bw)); 787 button.setLeft(left); 788 button.setTop(top); 789 } 790 } else if (j == (offsets.size() - 1)) { 791 CmsContainerPageElementPanel element = elements.get(elements.size() - 1); 792 if (!isMovedElement(element)) { 793 PlacementButton button = addButton(container); 794 elemental2.dom.Element realElement = Js.cast(element.getElement()); 795 button.addClickHandler(e -> m_callback.place(container, element, 1)); 796 button.addStyleName(OC_PLACEMENT_DOWN); 797 DOMRect elemRect = realElement.getBoundingClientRect(); 798 int top = (int)Math.round((elemRect.top + elemRect.height) - layerRect.top - bw); 799 int left = (int)Math.round(middle - (0.5 * bw)); 800 button.setLeft(left); 801 button.setTop(top); 802 } 803 } else { 804 CmsContainerPageElementPanel element = elements.get(j); 805 CmsContainerPageElementPanel previousElement = elements.get(j - 1); 806 if (!isMovedElement(element) && !isMovedElement(previousElement)) { 807 PlacementButton button = addButton(container); 808 button.addClickHandler(e -> m_callback.place(container, element, 0)); 809 button.addStyleName(OC_PLACEMENT_MIDDLE); 810 int top = (int)Math.round(offsets.get(j) - layerRect.top - (0.5 * bw)); 811 int left = (int)Math.round(middle - (0.5 * bw)); 812 button.setLeft(left); 813 button.setTop(top); 814 } 815 } 816 } 817 } 818 819 /** 820 * Installs the placement button for an empty container in the button layer. 821 * 822 * @param container the container 823 */ 824 private void installPlacementElement(CmsContainerPageContainer container) { 825 826 int bw = getPlacementButtonSize(); 827 828 HTMLDivElement placeholder = Js.cast(DomGlobal.document.createElement("div")); 829 placeholder.classList.add(OC_PLACEMENT_PLACEHOLDER); 830 elemental2.dom.Element layerElem = Js.cast(m_layer.getElement()); 831 elemental2.dom.Element containerElem = Js.cast(container.getElement()); 832 container.getHighlighting().getElement().getStyle().setVisibility(Visibility.HIDDEN); 833 DOMRect containerRect = containerElem.getBoundingClientRect(); 834 if (containerRect.height < 50.0) { 835 containerElem.appendChild(placeholder); 836 } 837 DOMRect layerRect = layerElem.getBoundingClientRect(); 838 PlacementButton plus = addButton(container); 839 840 plus.addStyleName(OC_PLACEMENT_MIDDLE); 841 842 int top = (int)Math.round( 843 ((containerRect.top - layerRect.top) + (0.5 * containerRect.height)) - (0.5 * bw)); 844 int left = (int)Math.round( 845 ((containerRect.left - layerRect.left) + (0.5 * containerRect.width)) - (0.5 * bw)); 846 plus.setLeft(left); 847 plus.setTop(top); 848 plus.addClickHandler(e -> m_callback.place(container, null, 0)); 849 850 } 851 852 /** 853 * Checks if a container element is the container element for which placement mode was initially started. 854 * 855 * @param element a container element 856 * @return true if the given element is the element for which placement mode was started 857 */ 858 private boolean isMovedElement(CmsContainerPageElementPanel element) { 859 860 return element == m_draggable; 861 } 862 863 /** 864 * Positions buttons so that they don't collide. 865 * 866 * @param originalButtons the list of buttons to position 867 */ 868 private void positionButtons(List<PlacementButton> originalButtons) { 869 870 // First split buttons into vertically separated groups. Since we only move things around horizontally, we can do it independently for each of these groups, reducing the total amount of collision tests. 871 originalButtons.sort((a, b) -> Integer.compare(a.getTop(), b.getTop())); 872 List<List<PlacementButton>> groups = new ArrayList<>(); 873 groups.add(new ArrayList<>()); 874 PlacementButton lastButton = null; 875 for (PlacementButton button : originalButtons) { 876 // ignoring collision tolerance here, because the point of collision tolerance is mostly controlling horizontal separation 877 if ((lastButton != null) && ((lastButton.getTop() + lastButton.getHeight()) <= button.getTop())) { // because all buttons have the same height, we only need to check the last one 878 groups.add(new ArrayList<>()); 879 } 880 groups.get(groups.size() - 1).add(button); 881 lastButton = button; 882 } 883 for (List<PlacementButton> group : groups) { 884 int iterations = 0; 885 while ((group.size() > 1) && (iterations < 1000)) { 886 // In each iteration of the main loop, try to find and resolve one collision. Resolving one collision may cause further collisions in later iterations of the loop. 887 888 iterations += 1; 889 // Sort buttons by increasing x coordinate of left corner, so we can limit the potential candidates for collisions with a given button. 890 group.sort((a, b) -> { 891 return Integer.compare(a.getLeft(), b.getLeft()); 892 }); 893 int collisionIndex = -1; 894 PlacementButton[] collisionPair = null; 895 for (int i = 0; i < group.size(); i++) { 896 collisionPair = null; 897 PlacementButton first = group.get(i); 898 int j = i + 1; 899 for (j = i + 1; j < group.size(); j++) { 900 PlacementButton second = group.get(j); 901 if (first.isFullyBefore(second)) { 902 break; 903 } 904 if (first.intersects(second)) { 905 collisionPair = new PlacementButton[] {first, second}; 906 collisionIndex = i; 907 break; 908 } 909 } 910 if (collisionPair != null) { 911 break; 912 } 913 } 914 if (collisionIndex != -1) { 915 916 PlacementButton first = collisionPair[0]; 917 PlacementButton second = collisionPair[1]; 918 int ci1 = m_containerIndexes.get(first.getContainer().getContainerId()).intValue(); 919 int ci2 = m_containerIndexes.get(second.getContainer().getContainerId()).intValue(); 920 /* 921 * By using the combination of document position of the container and button index of the button, we impose a complete total ordering on the buttons 922 * so that buttons which are lower in the ordering are moved when they collide with buttons that are higher in the ordering. This means that for a button 923 * involved in any collisions, if it has the highest position in that ordering, all other buttons involved in collisions with it will move to its right, and 924 * after that, will never collide with it again. The same reasoning can be applied to the element with next-highest position now to its right, and so on. 925 * This means we can't run into 'infinite loops' where groups of elements keep pushing each other to the right ad infinitum. 926 */ 927 if (ComparisonChain.start().compare(ci1, ci2).compare( 928 second.getIndex(), /* Use reverse order for index - prefer moving 'insert after' buttons rather than 'insert before' for a single container if they collide */ 929 first.getIndex()).result() == -1) { 930 first.moveToRightOf(second); 931 } else { 932 second.moveToRightOf(first); 933 } 934 // everything before first collision becomes irrelevant for the next iteration; we only move stuff to the right 935 group = new ArrayList<>(group.subList(collisionIndex, group.size())); 936 } else { 937 // no collisions; we're done 938 group = new ArrayList<>(); 939 } 940 } 941 } 942 for (PlacementButton button : originalButtons) { 943 button.setPosition(); 944 } 945 } 946 947 } 948 949 /** 950 * Callback interface for the placement mode. 951 */ 952 interface I_PlacementCallback { 953 954 /** 955 * Called to place an element at a specific position. 956 * @param container the target container 957 * @param referenceElement the reference element (may be null for an empty container) 958 * @param offset the offset (0 means insert before the reference element, 1 means after) 959 */ 960 void place(CmsContainerPageContainer container, CmsContainerPageElementPanel referenceElement, int offset); 961 } 962 963 /** 964 * Layer for displaying placement buttons, covering everything else on the page. 965 */ 966 static class PlacementLayer extends FlowPanel implements HasClickHandlers { 967 968 /** 969 * @see com.google.gwt.event.dom.client.HasClickHandlers#addClickHandler(com.google.gwt.event.dom.client.ClickHandler) 970 */ 971 @Override 972 public HandlerRegistration addClickHandler(ClickHandler handler) { 973 974 return addDomHandler(handler, ClickEvent.getType()); 975 } 976 977 }; 978 979 /** The container highlighting offset. */ 980 public static final int HIGHLIGHTING_OFFSET = 4; 981 982 /** CSS class. */ 983 public static final String OC_PLACEMENT_BUTTON = "oc-placement-button"; 984 985 /** CSS class. */ 986 public static final String OC_PLACEMENT_BUTTONS_VERTICAL = "oc-placement-buttons-vertical"; 987 988 /** CSS class. */ 989 public static final String OC_PLACEMENT_DOWN = "oc-placement-down"; 990 991 /** CSS class. */ 992 public static final String OC_PLACEMENT_LAYER = "oc-placement-layer"; 993 994 /** CSS class. */ 995 public static final String OC_PLACEMENT_LEFT = "oc-placement-left"; 996 997 /** CSS class. */ 998 public static final String OC_PLACEMENT_MODE = "oc-placement-mode"; 999 1000 /** CSS class. */ 1001 public static final String OC_PLACEMENT_PLACEHOLDER = "oc-placement-placeholder"; 1002 1003 /** CSS class. */ 1004 public static final String OC_PLACEMENT_RIGHT = "oc-placement-right"; 1005 1006 /** CSS class. */ 1007 public static final String OC_PLACEMENT_SELECTED_ELEMENT = "oc-placement-selected-element"; 1008 1009 /** CSS class. */ 1010 public static final String OC_PLACEMENT_UP = "oc-placement-up"; 1011 1012 /** The bigger size for the placement buttons. */ 1013 public static final int PLACEMENT_BUTTON_BIG = 30; 1014 1015 /** The smaller size for the placement buttons. */ 1016 public static final int PLACEMENT_BUTTON_SMALL = 20; 1017 1018 /** The button counter for placement mode. */ 1019 private static int m_buttonCounter; 1020 1021 /** The minimum margin set to empty containers. */ 1022 private static final int MINIMUM_CONTAINER_MARGIN = 10; 1023 1024 /** CSS class. */ 1025 private static final String OC_PLACEMENT_MIDDLE = "oc-placement-middle"; 1026 1027 /** The container page controller. */ 1028 protected CmsContainerpageController m_controller; 1029 1030 /** The id of the dragged element. */ 1031 protected String m_draggableId; 1032 1033 /** The id of the container from which an element was dragged. */ 1034 String m_originalContainerId; 1035 1036 /** Tracks whether an element has been added to the page (rather than just moved around). */ 1037 private boolean m_added; 1038 1039 /** The copy group id. */ 1040 private String m_copyGroupId; 1041 1042 /** The current place holder index. */ 1043 private int m_currentIndex = -1; 1044 1045 /** The current drop target. */ 1046 private I_CmsDropTarget m_currentTarget; 1047 1048 /** Map of current drag info beans. */ 1049 private Map<I_CmsDropTarget, Element> m_dragInfos; 1050 1051 /** The drag overlay. */ 1052 private Element m_dragOverlay; 1053 1054 /** DND controller for images. We set this in onDragStart if we are dragging an image, and then delegate most of the other method calls to it if it is set.*/ 1055 private CmsImageDndController m_imageDndController; 1056 1057 /** The ionitial drop target. */ 1058 private I_CmsDropTarget m_initialDropTarget; 1059 1060 /** The original position of the draggable. */ 1061 private int m_originalIndex; 1062 1063 /** The placement context. */ 1064 private CmsPlacementModeContext m_placementContext; 1065 1066 /** 1067 * Constructor.<p> 1068 * 1069 * @param controller the container page controller 1070 */ 1071 public CmsContainerpageDNDController(CmsContainerpageController controller) { 1072 1073 m_controller = controller; 1074 m_dragInfos = new HashMap<I_CmsDropTarget, Element>(); 1075 Window.addResizeHandler(event -> { 1076 if (m_placementContext != null) { 1077 stopDrag(m_placementContext.getDNDHandler()); 1078 } 1079 }); 1080 } 1081 1082 /** 1083 * Placement button big. 1084 * 1085 * @return the string 1086 */ 1087 public static final String placementButtonBig() { 1088 1089 return PLACEMENT_BUTTON_BIG + "px"; 1090 } 1091 1092 /** 1093 * Placement button small. 1094 * 1095 * @return the string 1096 */ 1097 public static final String placementButtonSmall() { 1098 1099 return PLACEMENT_BUTTON_SMALL + "px"; 1100 } 1101 1102 /** 1103 * Checks if a 1D line segment comes before a position. 1104 * 1105 * @param start the start position of the segment 1106 * @param size the width of the segment 1107 * @param pos the position to check 1108 * @return true, if successful 1109 */ 1110 public static boolean segmentBefore(int start, int size, int pos) { 1111 1112 return (start + size) <= pos; 1113 } 1114 1115 /** 1116 * Checks if two 1D line segments with given start positions and sizes intersect. 1117 * 1118 * @param start1 start position of the first segment 1119 * @param size1 size of the first segment 1120 * @param start2 start position of the second segment 1121 * @param size2 size of the second segment 1122 * @return true, if successful 1123 */ 1124 public static boolean segmentIntersect(int start1, int size1, int start2, int size2) { 1125 1126 return !segmentBefore(start1, size1, start2) && !segmentBefore(start2, size2, start1); 1127 } 1128 1129 /** 1130 * Checks if the given id is a new id.<p> 1131 * 1132 * @param id the id 1133 * 1134 * @return <code>true</code> if the id is a new id 1135 */ 1136 private static boolean isNewId(String id) { 1137 1138 if (id.contains("#")) { 1139 id = id.substring(0, id.indexOf("#")); 1140 } 1141 return !CmsUUID.isValidUUID(id); 1142 } 1143 1144 /** 1145 * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onAnimationStart(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler) 1146 */ 1147 public void onAnimationStart(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) { 1148 1149 // remove highlighting 1150 for (I_CmsDropTarget dropTarget : m_dragInfos.keySet()) { 1151 if (dropTarget instanceof I_CmsDropContainer) { 1152 ((I_CmsDropContainer)dropTarget).removeHighlighting(); 1153 } 1154 } 1155 } 1156 1157 /** 1158 * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onBeforeDrop(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler) 1159 */ 1160 public boolean onBeforeDrop(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) { 1161 1162 return true; 1163 } 1164 1165 /** 1166 * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onDragCancel(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler) 1167 */ 1168 public void onDragCancel(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) { 1169 1170 if (m_imageDndController != null) { 1171 m_imageDndController.onDragCancel(draggable, target, handler); 1172 removeDragOverlay(); 1173 m_imageDndController = null; 1174 return; 1175 } 1176 stopDrag(handler); 1177 } 1178 1179 /** 1180 * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onDragStart(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler) 1181 */ 1182 public boolean onDragStart(final I_CmsDraggable draggable, I_CmsDropTarget target, final CmsDNDHandler handler) { 1183 1184 installDragOverlay(); 1185 m_currentTarget = null; 1186 m_currentIndex = -1; 1187 m_draggableId = draggable.getId(); 1188 m_originalIndex = -1; 1189 m_initialDropTarget = target; 1190 handler.setOrientation(Orientation.ALL); 1191 m_controller.hideEditableListButtons(); 1192 1193 if (isImage(draggable)) { 1194 if (m_controller.isGroupcontainerEditing()) { 1195 // don't allow image DND while editing group containers 1196 return false; 1197 } 1198 // We are going to delegate further method calls to this, and set it to null again if the image is dragged or the 1199 // DND operation is cancelled 1200 m_imageDndController = new CmsImageDndController(CmsContainerpageController.get()); 1201 return m_imageDndController.onDragStart(draggable, target, handler); 1202 1203 } 1204 1205 if (!m_controller.isGroupcontainerEditing()) { 1206 m_controller.lockContainerpage(new I_CmsSimpleCallback<Boolean>() { 1207 1208 public void execute(Boolean arg) { 1209 1210 if (!arg.booleanValue()) { 1211 handler.cancel(); 1212 } 1213 } 1214 }); 1215 } 1216 1217 String containerId = null; 1218 if (target instanceof CmsContainerPageContainer) { 1219 containerId = ((CmsContainerPageContainer)target).getContainerId(); 1220 } else { 1221 // set marker id 1222 containerId = CmsContainerElement.MENU_CONTAINER_ID; 1223 } 1224 m_originalContainerId = containerId; 1225 1226 if (target != null) { 1227 handler.addTarget(target); 1228 if (target instanceof I_CmsDropContainer) { 1229 prepareTargetContainer((I_CmsDropContainer)target, draggable, handler.getPlaceholder()); 1230 Element helper = handler.getDragHelper(); 1231 handler.setCursorOffsetX(helper.getOffsetWidth() - 15); 1232 handler.setCursorOffsetY(20); 1233 } 1234 } 1235 1236 m_dragInfos.put(target, handler.getPlaceholder()); 1237 m_controller.getHandler().hideMenu(); 1238 String clientId = draggable.getId(); 1239 if (CmsStringUtil.isEmptyOrWhitespaceOnly(clientId)) { 1240 CmsDebugLog.getInstance().printLine("draggable has no id, canceling drop"); 1241 return false; 1242 } 1243 final I_CmsSimpleCallback<CmsContainerElementData> callback = new I_CmsSimpleCallback<CmsContainerElementData>() { 1244 1245 /** 1246 * Execute on success.<p> 1247 * 1248 * @param arg the container element data 1249 */ 1250 public void execute(CmsContainerElementData arg) { 1251 1252 prepareHelperElements(arg, handler, draggable); 1253 handler.updatePosition(); 1254 } 1255 }; 1256 if (isNewId(clientId)) { 1257 // for new content elements dragged from the gallery menu, the given id contains the resource type name 1258 m_controller.getNewElement(clientId, callback); 1259 } else { 1260 m_controller.getElementForDragAndDropFromContainer(clientId, m_originalContainerId, false, callback); 1261 } 1262 if (!(target instanceof CmsContainerPageContainer)) { 1263 handler.setStartPosition(-1, 0); 1264 } 1265 m_controller.sendDragStarted(isNew(draggable)); 1266 return true; 1267 1268 } 1269 1270 /** 1271 * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onDrop(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler) 1272 */ 1273 public void onDrop(final I_CmsDraggable draggable, final I_CmsDropTarget target, CmsDNDHandler handler) { 1274 1275 if (m_imageDndController != null) { 1276 m_imageDndController.onDrop(draggable, target, handler); 1277 removeDragOverlay(); 1278 m_imageDndController = null; 1279 return; 1280 } 1281 m_added = (draggable instanceof CmsListItem) && (target instanceof CmsContainerPageContainer); 1282 1283 if (target != m_initialDropTarget) { 1284 if (target instanceof I_CmsDropContainer) { 1285 final I_CmsDropContainer container = (I_CmsDropContainer)target; 1286 final int index = container.getPlaceholderIndex(); 1287 final String modelReplaceId = container instanceof CmsContainerPageContainer 1288 ? ((CmsContainerPageContainer)container).getCopyModelReplaceId() 1289 : null; 1290 if (!isNew(draggable) && (draggable instanceof CmsListItem)) { 1291 // existing element from add menu 1292 final String copyGroupId = m_copyGroupId; 1293 AsyncCallback<String> modeCallback = new AsyncCallback<String>() { 1294 1295 public void onFailure(Throwable caught) { 1296 1297 if (caught != null) { 1298 CmsErrorDialog.handleException(caught); 1299 } 1300 } 1301 1302 public void onSuccess(String result) { 1303 1304 if (Objects.equal(result, CmsEditorConstants.MODE_COPY)) { 1305 final CmsContainerpageController controller = CmsContainerpageController.get(); 1306 if (copyGroupId == null) { 1307 1308 CmsContainerElementData data = controller.getCachedElement(m_draggableId); 1309 final Map<String, String> settings = data.getSettings(); 1310 1311 controller.copyElement( 1312 CmsContainerpageController.getServerId(m_draggableId), 1313 new I_CmsSimpleCallback<CmsUUID>() { 1314 1315 public void execute(CmsUUID resultId) { 1316 1317 controller.getElementWithSettings( 1318 "" + resultId, 1319 settings, 1320 new I_CmsSimpleCallback<CmsContainerElementData>() { 1321 1322 public void execute(CmsContainerElementData newData) { 1323 1324 insertDropElement( 1325 newData, 1326 container, 1327 index, 1328 draggable, 1329 modelReplaceId); 1330 container.removePlaceholder(); 1331 } 1332 }); 1333 } 1334 }); 1335 } else { 1336 controller.getElementForDragAndDropFromContainer( 1337 copyGroupId, 1338 m_originalContainerId, 1339 true, 1340 new I_CmsSimpleCallback<CmsContainerElementData>() { 1341 1342 public void execute(CmsContainerElementData arg) { 1343 1344 insertDropElement(arg, container, index, draggable, modelReplaceId); 1345 container.removePlaceholder(); 1346 } 1347 }); 1348 } 1349 1350 } else if (Objects.equal(result, CmsEditorConstants.MODE_REUSE)) { 1351 insertDropElement( 1352 m_controller.getCachedElement(m_draggableId), 1353 container, 1354 index, 1355 draggable, 1356 modelReplaceId); 1357 container.removePlaceholder(); 1358 } 1359 } 1360 }; 1361 1362 CmsUUID structureId = new CmsUUID(CmsContainerpageController.getServerId(m_draggableId)); 1363 CmsContainerElementData cachedElementData = m_controller.getCachedElement(m_draggableId); 1364 ElementReuseMode reuseMode = isCopyModel(draggable) 1365 ? ElementReuseMode.copy 1366 : (((cachedElementData != null) && !cachedElementData.isCopyInModels()) 1367 ? ElementReuseMode.reuse 1368 : CmsContainerpageController.get().getData().getElementReuseMode()); 1369 if ((!handler.isPlacementMode()) && handler.hasModifierCTRL()) { 1370 reuseMode = ElementReuseMode.ask; 1371 } 1372 if (reuseMode != ElementReuseMode.reuse) { 1373 1374 if ((cachedElementData != null) 1375 && (!cachedElementData.hasWritePermission() 1376 || cachedElementData.isModelGroup() 1377 || cachedElementData.isCopyDisabled() 1378 || cachedElementData.isWasModelGroup())) { 1379 // User is not allowed to create this element in current view, so reuse the element instead 1380 reuseMode = ElementReuseMode.reuse; 1381 } 1382 } 1383 switch (reuseMode) { 1384 case ask: 1385 // when dropping elements from the into the page, we ask the user if the dropped element should 1386 // be used, or a copy of it. If the user wants a copy, we copy the corresponding resource and replace the element 1387 // in the page 1388 CmsDroppedElementModeSelectionDialog.showDialog(structureId, modeCallback); 1389 break; 1390 case copy: 1391 modeCallback.onSuccess(CmsEditorConstants.MODE_COPY); 1392 break; 1393 case reuse: 1394 default: 1395 modeCallback.onSuccess(CmsEditorConstants.MODE_REUSE); 1396 break; 1397 } 1398 } else { 1399 // new article or moved from different container 1400 insertDropElement(m_controller.getCachedElement(m_draggableId), container, index, draggable, null); 1401 } 1402 } else if (target instanceof CmsList<?>) { 1403 m_controller.addToFavoriteList(m_draggableId); 1404 } 1405 } else if ((target instanceof I_CmsDropContainer) 1406 && (draggable instanceof CmsContainerPageElementPanel) 1407 && isChangedPosition(target)) { 1408 CmsDomUtil.showOverlay(draggable.getElement(), false); 1409 I_CmsDropContainer container = (I_CmsDropContainer)target; 1410 int count = container.getWidgetCount(); 1411 if (!handler.isPlacementMode()) { 1412 handler.getPlaceholder().getStyle().setDisplay(Display.NONE); 1413 } 1414 if (container.getPlaceholderIndex() >= count) { 1415 container.add((CmsContainerPageElementPanel)draggable); 1416 } else { 1417 container.insert((CmsContainerPageElementPanel)draggable, container.getPlaceholderIndex()); 1418 } 1419 m_controller.addToRecentList(m_draggableId, null); 1420 m_controller.sendElementMoved((CmsContainerPageElementPanel)draggable); 1421 // changes are only relevant to the container page if not group-container editing 1422 if (!m_controller.isGroupcontainerEditing()) { 1423 m_controller.setPageChanged(); 1424 } 1425 } else if (draggable instanceof CmsContainerPageElementPanel) { 1426 CmsDomUtil.showOverlay(draggable.getElement(), false); 1427 // to reset mouse over state remove and attach the option bar 1428 CmsContainerPageElementPanel containerElement = (CmsContainerPageElementPanel)draggable; 1429 CmsElementOptionBar optionBar = containerElement.getElementOptionBar(); 1430 optionBar.removeFromParent(); 1431 containerElement.setElementOptionBar(optionBar); 1432 } 1433 stopDrag(handler); 1434 } 1435 1436 /** 1437 * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onPositionedPlaceholder(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler) 1438 */ 1439 public void onPositionedPlaceholder(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) { 1440 1441 if (m_imageDndController != null) { 1442 m_imageDndController.onPositionedPlaceholder(draggable, target, handler); 1443 return; 1444 } 1445 1446 if (hasChangedPosition(target)) { 1447 checkPlaceholderVisibility(target); 1448 updateHighlighting(false, false); 1449 } 1450 } 1451 1452 /** 1453 * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onTargetEnter(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler) 1454 */ 1455 public boolean onTargetEnter(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) { 1456 1457 if (m_imageDndController != null) { 1458 return m_imageDndController.onTargetEnter(draggable, target, handler); 1459 } 1460 1461 if (target instanceof CmsContainerPageContainer) { 1462 CmsContainerPageContainer container = (CmsContainerPageContainer)target; 1463 if (container.isShowingEmptyContainerElement()) { 1464 CmsContainerPageContainer.newResizeHelper(container).initHeight(); 1465 } 1466 } 1467 1468 Element placeholder = m_dragInfos.get(target); 1469 if (placeholder != null) { 1470 handler.getPlaceholder().getStyle().setDisplay(Display.NONE); 1471 handler.setPlaceholder(placeholder); 1472 placeholder.getStyle().clearDisplay(); 1473 if ((target != m_initialDropTarget) && (target instanceof I_CmsDropContainer)) { 1474 ((I_CmsDropContainer)target).checkMaxElementsOnEnter(); 1475 } 1476 } 1477 return true; 1478 } 1479 1480 /** 1481 * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onTargetLeave(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler) 1482 */ 1483 public void onTargetLeave(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) { 1484 1485 if (m_imageDndController != null) { 1486 m_imageDndController.onTargetLeave(draggable, target, handler); 1487 return; 1488 } 1489 CmsContainerPageContainer.clearResizeHelper(); 1490 m_currentTarget = null; 1491 m_currentIndex = -1; 1492 Element placeholder = m_dragInfos.get(m_initialDropTarget); 1493 if (placeholder != null) { 1494 handler.getPlaceholder().getStyle().setDisplay(Display.NONE); 1495 handler.setPlaceholder(placeholder); 1496 placeholder.getStyle().setDisplay(Display.NONE); 1497 if ((target != m_initialDropTarget) && (target instanceof I_CmsDropContainer)) { 1498 ((I_CmsDropContainer)target).checkMaxElementsOnLeave(); 1499 } 1500 } 1501 updateHighlighting(false, false); 1502 } 1503 1504 /** 1505 * @see org.opencms.gwt.client.dnd.I_CmsDNDController#postClear(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget) 1506 */ 1507 @Override 1508 public void postClear(I_CmsDraggable draggable, I_CmsDropTarget target) { 1509 1510 Element dragElem = null; 1511 if (draggable != null) { 1512 dragElem = draggable.getElement(); 1513 } 1514 1515 Element targetElem = null; 1516 if (target != null) { 1517 targetElem = target.getElement(); 1518 } 1519 m_controller.sendDragFinished(dragElem, targetElem, isNew(draggable)); 1520 } 1521 1522 /** 1523 * Starts placement mode for the given draggable. 1524 * 1525 * @param draggable the draggable element 1526 * @param handler the handler 1527 * @return true, if successful 1528 */ 1529 public boolean startPlacementMode(I_CmsDraggable draggable, CmsDNDHandler handler) { 1530 1531 if (draggable instanceof CmsListItem) { 1532 // ensure button doesn't remain visible 1533 ((CmsListItem)draggable).getListItemWidget().forceMouseOut(); 1534 } else if (draggable instanceof CmsContainerPageElementPanel) { 1535 ((CmsContainerPageElementPanel)draggable).removeHighlighting(); 1536 } 1537 1538 if (!m_controller.getData().isPlacementModeEnabled()) { 1539 return false; 1540 } 1541 1542 if (isImage(draggable)) { 1543 return false; 1544 } 1545 1546 if (m_controller.isGroupcontainerEditing()) { 1547 return false; 1548 } 1549 1550 final I_CmsSimpleCallback<CmsContainerElementData> callback = new I_CmsSimpleCallback<CmsContainerElementData>() { 1551 1552 /** 1553 * Execute on success.<p> 1554 * 1555 * @param elem the container element data 1556 */ 1557 public void execute(CmsContainerElementData elem) { 1558 1559 Set<String> containerIds = new HashSet<>(); 1560 for (String containerId : elem.getContents().keySet()) { 1561 CmsContainerPageContainer container = m_controller.getContainerTarget(containerId); 1562 if (draggable instanceof CmsContainerPageElementPanel) { 1563 if ((container != null) && draggable.getElement().isOrHasChild(container.getElement())) { 1564 // can't move an element into one of its own nested containers 1565 continue; 1566 } 1567 } 1568 String content = elem.getContents().get(containerId); 1569 // check if content is valid HTML 1570 Element testElement = null; 1571 if (content != null) { 1572 try { 1573 testElement = CmsDomUtil.createElement(content); 1574 } catch (Exception e) { 1575 CmsGwtLog.log( 1576 "Invalid formatter output for element of type " 1577 + elem.getResourceType() 1578 + ": [" 1579 + content 1580 + "]"); 1581 } 1582 } 1583 if (testElement != null) { 1584 containerIds.add(containerId); 1585 } 1586 } 1587 1588 CmsListInfoBean info = null; 1589 if (draggable instanceof CmsListItem) { 1590 CmsListItem item = (CmsListItem)draggable; 1591 info = item.getListItemWidget().getInfoBean(); 1592 } else { 1593 info = elem.getListInfo(); 1594 } 1595 if (info == null) { 1596 // should never happen 1597 info = new CmsListInfoBean("???", "", new ArrayList<>()); 1598 } 1599 CmsListItemWidget newItem = new CmsListItemWidget(info); 1600 newItem.setWidth("500px"); 1601 if (newItem.getOpenClose() != null) { 1602 newItem.getOpenClose().removeFromParent(); 1603 } 1604 prepareHelperElements(elem, handler, draggable); 1605 setPlacementContext( 1606 new CmsPlacementModeContext(containerIds, newItem, handler, draggable, (cnt, reference, offset) -> { 1607 if (reference != null) { 1608 placeElement(handler, draggable, elem, reference, offset); 1609 } else { 1610 placeNewElement(handler, draggable, elem, cnt); 1611 } 1612 })); 1613 } 1614 }; 1615 // we need to reset this so highlighting works correctly 1616 m_initialDropTarget = null; 1617 String clientId = draggable.getId(); 1618 if (isNewId(clientId)) { 1619 // for new content elements dragged from the gallery menu, the given id contains the resource type name 1620 if (draggable instanceof CmsContainerPageElementPanel) { 1621 CmsContainerPageElementPanel containerElement = ((CmsContainerPageElementPanel)draggable); 1622 containerElement.addStyleName(OC_PLACEMENT_SELECTED_ELEMENT); 1623 } 1624 m_controller.getNewElement(clientId, callback); 1625 } else { 1626 String originContainer = CmsContainerElement.MENU_CONTAINER_ID; 1627 if (draggable instanceof CmsContainerPageElementPanel) { 1628 CmsContainerPageElementPanel containerElement = ((CmsContainerPageElementPanel)draggable); 1629 containerElement.addStyleName(OC_PLACEMENT_SELECTED_ELEMENT); 1630 I_CmsDropContainer dropContainer = containerElement.getParentTarget(); 1631 if (dropContainer instanceof CmsContainerPageContainer) { 1632 String realOrigin = ((CmsContainerPageContainer)dropContainer).getContainerId(); 1633 if (realOrigin != null) { 1634 originContainer = realOrigin; 1635 } 1636 } 1637 } 1638 1639 m_controller.getElementForDragAndDropFromContainer(clientId, originContainer, false, callback); 1640 } 1641 return true; 1642 } 1643 1644 /** 1645 * Prepares all helper elements for the different drop targets.<p> 1646 * 1647 * @param elementData the element data 1648 * @param handler the drag and drop handler 1649 * @param draggable the draggable 1650 */ 1651 protected void prepareHelperElements( 1652 CmsContainerElementData elementData, 1653 CmsDNDHandler handler, 1654 I_CmsDraggable draggable) { 1655 1656 if (elementData == null) { 1657 CmsDebugLog.getInstance().printLine("elementData == null!"); 1658 if (!handler.isPlacementMode()) { 1659 handler.cancel(); 1660 } 1661 return; 1662 } 1663 if (!handler.isPlacementMode() && !handler.isDragging()) { 1664 return; 1665 } 1666 if (elementData.isGroup()) { 1667 m_copyGroupId = m_draggableId; 1668 } else { 1669 m_copyGroupId = null; 1670 } 1671 1672 if ((elementData.getDndId() != null) && (m_controller.getCachedElement(elementData.getDndId()) != null)) { 1673 m_draggableId = elementData.getDndId(); 1674 elementData = m_controller.getCachedElement(m_draggableId); 1675 } else { 1676 m_draggableId = elementData.getClientId(); 1677 } 1678 if (!elementData.isGroupContainer() && !elementData.isInheritContainer()) { 1679 for (CmsContainerPageContainer container : m_controller.getContainerTargets().values()) { 1680 String containerId = container.getContainerId(); 1681 loadCss(elementData, containerId); 1682 } 1683 } 1684 1685 if (m_controller.isGroupcontainerEditing()) { 1686 CmsGroupContainerElementPanel groupContainer = m_controller.getGroupcontainer(); 1687 if ((groupContainer != m_initialDropTarget) 1688 && !(elementData.isGroupContainer() || elementData.isInheritContainer()) 1689 && (elementData.getContents().get(groupContainer.getContainerId()) != null)) { 1690 Element placeholder = null; 1691 String containerId = groupContainer.getContainerId(); 1692 loadCss(elementData, containerId); 1693 placeholder = createPlaceholderFromHtmlForContainer(elementData, placeholder, containerId); 1694 1695 if (placeholder != null) { 1696 prepareDragInfo(placeholder, groupContainer, handler); 1697 groupContainer.highlightContainer(false); 1698 } 1699 } 1700 return; 1701 } 1702 if (!m_controller.isEditingDisabled()) { 1703 for (CmsContainerPageContainer container : m_controller.getContainerTargets().values()) { 1704 1705 if (draggable.getElement().isOrHasChild(container.getElement())) { 1706 // skip containers that are children of the draggable element 1707 continue; 1708 } 1709 if ((container != m_initialDropTarget) 1710 && !container.isDetailView() 1711 && (m_controller.getData().isModelGroup() || !container.hasModelGroupParent()) 1712 && (elementData.getContents().get(container.getContainerId()) != null)) { 1713 1714 Element placeholder = null; 1715 if (elementData.isGroupContainer() || elementData.isInheritContainer()) { 1716 placeholder = DOM.createDiv(); 1717 String content = ""; 1718 for (String groupId : elementData.getSubItems()) { 1719 CmsContainerElementData subData = m_controller.getCachedElement(groupId); 1720 if (subData != null) { 1721 if (subData.isShowInContext( 1722 CmsContainerpageController.get().getData().getTemplateContextInfo().getCurrentContext())) { 1723 if ((subData.getContents().get(container.getContainerId()) != null)) { 1724 content += subData.getContents().get(container.getContainerId()); 1725 } 1726 } else { 1727 content += "<div class='" 1728 + CmsTemplateContextInfo.DUMMY_ELEMENT_MARKER 1729 + "' style='display: none !important;'></div>"; 1730 } 1731 } 1732 } 1733 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(content)) { 1734 placeholder.setInnerHTML(content); 1735 // ensure any embedded flash players are set opaque so UI elements may be placed above them 1736 CmsDomUtil.fixFlashZindex(placeholder); 1737 } else { 1738 placeholder.addClassName( 1739 I_CmsLayoutBundle.INSTANCE.containerpageCss().emptyGroupContainer()); 1740 } 1741 } else { 1742 1743 placeholder = createPlaceholderFromHtmlForContainer( 1744 elementData, 1745 placeholder, 1746 container.getContainerId()); 1747 } 1748 if (placeholder != null) { 1749 prepareDragInfo(placeholder, container, handler); 1750 } 1751 } 1752 } 1753 } 1754 initNestedContainers(); 1755 1756 // add highlighting after all drag targets have been initialized 1757 updateHighlighting(true, handler.isPlacementMode()); 1758 1759 } 1760 1761 /** 1762 * Inserts e new container element into the container once the drag and drop is finished.<p> 1763 * 1764 * @param elementData the element data to create the element from 1765 * @param container the target container 1766 * @param index the element index 1767 * @param draggable the original drag element 1768 * @param modelReplaceId the model replace id 1769 */ 1770 void insertDropElement( 1771 CmsContainerElementData elementData, 1772 I_CmsDropContainer container, 1773 int index, 1774 I_CmsDraggable draggable, 1775 String modelReplaceId) { 1776 1777 try { 1778 CmsContainerPageElementPanel containerElement = m_controller.getContainerpageUtil().createElement( 1779 elementData, 1780 container, 1781 isNew(draggable)); 1782 if (isNew(draggable)) { 1783 containerElement.setNewType(CmsContainerpageController.getServerId(m_draggableId)); 1784 } else { 1785 m_controller.addToRecentList(elementData.getClientId(), null); 1786 } 1787 1788 if (index >= container.getWidgetCount()) { 1789 container.add(containerElement); 1790 } else { 1791 container.insert(containerElement, index); 1792 } 1793 if (draggable instanceof CmsContainerPageElementPanel) { 1794 ((CmsContainerPageElementPanel)draggable).removeFromParent(); 1795 } 1796 m_controller.initializeSubContainers(containerElement); 1797 1798 if (modelReplaceId != null) { 1799 m_controller.executeCopyModelReplace( 1800 modelReplaceId, 1801 ((CmsContainerPageContainer)container).getFormerModelGroupParent(), 1802 m_controller.getCachedElement(m_draggableId)); 1803 } 1804 1805 if (!m_controller.isGroupcontainerEditing()) { 1806 if (containerElement.hasReloadMarker()) { 1807 m_controller.setPageChanged(new Runnable() { 1808 1809 public void run() { 1810 1811 CmsContainerpageController.get().reloadPage(); 1812 } 1813 }); 1814 } else { 1815 m_controller.setPageChanged(); 1816 } 1817 if (m_added) { 1818 m_controller.sendElementAdded(containerElement); 1819 } else { 1820 m_controller.sendElementMoved(containerElement); 1821 } 1822 } 1823 if (m_controller.isGroupcontainerEditing()) { 1824 container.getElement().removeClassName( 1825 I_CmsLayoutBundle.INSTANCE.containerpageCss().emptyGroupContainer()); 1826 } 1827 1828 } catch (Exception e) { 1829 CmsErrorDialog.handleException(e.getMessage(), e); 1830 } 1831 } 1832 1833 /** 1834 1835 * Checks and sets if the place holder should be visible.<p> 1836 * 1837 * @param target the target container 1838 */ 1839 private void checkPlaceholderVisibility(I_CmsDropTarget target) { 1840 1841 if (target instanceof I_CmsDropContainer) { 1842 I_CmsDropContainer container = (I_CmsDropContainer)target; 1843 container.setPlaceholderVisibility( 1844 (container != m_initialDropTarget) 1845 || ((m_currentIndex - m_originalIndex) > 1) 1846 || ((m_originalIndex - m_currentIndex) > 0)); 1847 } 1848 } 1849 1850 /** 1851 * Creates a placeholder for an element and a specific container. 1852 * 1853 * @param elementData the element data 1854 * @param placeholder the previous placeholder 1855 * @param containerId the container id 1856 * @return the element 1857 */ 1858 private Element createPlaceholderFromHtmlForContainer( 1859 CmsContainerElementData elementData, 1860 Element placeholder, 1861 String containerId) { 1862 1863 try { 1864 String htmlContent = elementData.getContents().get(containerId); 1865 placeholder = CmsDomUtil.createElement(htmlContent); 1866 // ensure any embedded flash players are set opaque so UI elements may be placed above them 1867 CmsDomUtil.fixFlashZindex(placeholder); 1868 placeholder.addClassName(I_CmsLayoutBundle.INSTANCE.dragdropCss().dragPlaceholder()); 1869 } catch (Exception e) { 1870 CmsDebugLog.getInstance().printLine(e.getMessage()); 1871 } 1872 return placeholder; 1873 } 1874 1875 /** 1876 * Checks if the placeholder position has changed.<p> 1877 * 1878 * @param target the current drop target 1879 * 1880 * @return <code>true</code> if the placeholder position has changed 1881 */ 1882 private boolean hasChangedPosition(I_CmsDropTarget target) { 1883 1884 if ((m_currentTarget != target) || (m_currentIndex != target.getPlaceholderIndex())) { 1885 m_currentTarget = target; 1886 m_currentIndex = target.getPlaceholderIndex(); 1887 return true; 1888 } 1889 return false; 1890 } 1891 1892 /** 1893 * Initializes the nested container infos.<p> 1894 */ 1895 private void initNestedContainers() { 1896 1897 for (CmsContainer container : m_controller.getContainers().values()) { 1898 if (container.isSubContainer()) { 1899 CmsContainerPageContainer containerWidget = m_controller.m_targetContainers.get(container.getName()); 1900 // check if the sub container is a valid drop targets 1901 if (m_dragInfos.keySet().contains(containerWidget)) { 1902 CmsContainer parentContainer = m_controller.getContainers().get(container.getParentContainerName()); 1903 // add the container to all it's ancestors as a dnd child 1904 while (parentContainer != null) { 1905 if (m_dragInfos.keySet().contains( 1906 m_controller.m_targetContainers.get(parentContainer.getName()))) { 1907 m_controller.m_targetContainers.get(parentContainer.getName()).addDndChild(containerWidget); 1908 } 1909 if (parentContainer.isSubContainer()) { 1910 parentContainer = m_controller.getContainers().get( 1911 parentContainer.getParentContainerName()); 1912 } else { 1913 parentContainer = null; 1914 } 1915 } 1916 } 1917 } 1918 1919 } 1920 } 1921 1922 /** 1923 * Installs the drag overlay to avoid any mouse over issues or similar.<p> 1924 */ 1925 private void installDragOverlay() { 1926 1927 if (m_dragOverlay != null) { 1928 m_dragOverlay.removeFromParent(); 1929 } 1930 m_dragOverlay = DOM.createDiv(); 1931 m_dragOverlay.addClassName(I_CmsLayoutBundle.INSTANCE.dragdropCss().dragOverlay()); 1932 Document.get().getBody().appendChild(m_dragOverlay); 1933 } 1934 1935 /** 1936 * Checks whether the current placeholder position represents a change to the original draggable position within the tree.<p> 1937 * 1938 * @param target the current drop target 1939 * 1940 * @return <code>true</code> if the position changed 1941 */ 1942 private boolean isChangedPosition(I_CmsDropTarget target) { 1943 1944 // if the new index is not next to the old one, the position has changed 1945 if ((target != m_initialDropTarget) 1946 || !((target.getPlaceholderIndex() == (m_originalIndex + 1)) 1947 || (target.getPlaceholderIndex() == m_originalIndex))) { 1948 return true; 1949 } 1950 return false; 1951 } 1952 1953 /** 1954 * Checks if the draggable item is a copy model.<p> 1955 * 1956 * @param draggable the draggable item 1957 * 1958 * @return true if the item is a copy model 1959 */ 1960 private boolean isCopyModel(I_CmsDraggable draggable) { 1961 1962 if (!(draggable instanceof CmsResultListItem)) { 1963 return false; 1964 } 1965 return ((CmsResultListItem)draggable).getResult().isCopyModel(); 1966 } 1967 1968 /** 1969 * Checks if the given draggable item is an image.<p> 1970 * 1971 * @param draggable the item to check 1972 * 1973 * @return true if the given item is an image 1974 */ 1975 private boolean isImage(I_CmsDraggable draggable) { 1976 1977 return (draggable instanceof CmsResultListItem) 1978 && CmsToolbarAllGalleriesMenu.DND_MARKER.equals(((CmsResultListItem)draggable).getData()); 1979 1980 } 1981 1982 /** 1983 * Checks if the given draggable is new. 1984 * 1985 * @param draggable the draggable 1986 * @return true if the given draggable is new 1987 */ 1988 private boolean isNew(I_CmsDraggable draggable) { 1989 1990 boolean result = isNewId(draggable.getId()); 1991 return result; 1992 } 1993 1994 /** 1995 * Ensures that the CSS is loaded for the given element data and container. 1996 * 1997 * @param elementData the element data 1998 * @param containerId he container id 1999 */ 2000 private void loadCss(CmsContainerElementData elementData, String containerId) { 2001 2002 Set<String> cssResources = elementData.getCssResources(containerId); 2003 if ((cssResources != null) && !cssResources.isEmpty()) { 2004 // the element requires certain CSS resources, check if present and include if necessary 2005 for (String cssResourceLink : cssResources) { 2006 CmsDomUtil.ensureStyleSheetIncluded(cssResourceLink); 2007 } 2008 } 2009 } 2010 2011 /** 2012 * Placces an element relative to an existing element in placement mode. 2013 * 2014 * @param handler the handler 2015 * @param draggable the element to be placed 2016 * @param elem the data for the element 2017 * @param reference the element relative to which the placed element should be inserted 2018 * @param offset the index offset for the new element relative to the reference element (0 means 'before the reference element'!) 2019 */ 2020 private void placeElement( 2021 CmsDNDHandler handler, 2022 I_CmsDraggable draggable, 2023 CmsContainerElementData elem, 2024 CmsContainerPageElementPanel reference, 2025 int offset) { 2026 2027 CmsContainerPageContainer cnt = (CmsContainerPageContainer)reference.getParentTarget(); 2028 int index = cnt.getWidgetIndex(reference) + offset; 2029 if (index < 0) { 2030 index = 0; 2031 } 2032 cnt.setPlaceholderIndex(index); 2033 onDrop(draggable, cnt, handler); 2034 // Placeholder index is not reset by onDrop for container -> container transfer 2035 cnt.removePlaceholder(); 2036 handler.clearPlacement(); 2037 } 2038 2039 /** 2040 * Places an element into an empty container in placement mode. 2041 * 2042 * @param handler the handler 2043 * @param draggable the draggable for the element 2044 * @param elem the data for the element 2045 * @param cnt the target container 2046 */ 2047 private void placeNewElement( 2048 CmsDNDHandler handler, 2049 I_CmsDraggable draggable, 2050 CmsContainerElementData elem, 2051 CmsContainerPageContainer cnt) { 2052 2053 cnt.setPlaceholderIndex(0); 2054 onDrop(draggable, cnt, handler); 2055 // Placeholder index is not reset by onDrop for container -> container transfer 2056 cnt.removePlaceholder(); 2057 handler.clearPlacement(); 2058 } 2059 2060 /** 2061 * Sets styles of helper elements, appends the to the drop target and puts them into a drag info bean.<p> 2062 * 2063 * @param placeholder the placeholder element 2064 * @param target the drop target 2065 * @param handler the drag and drop handler 2066 */ 2067 private void prepareDragInfo(Element placeholder, I_CmsDropContainer target, CmsDNDHandler handler) { 2068 2069 target.getElement().addClassName(I_CmsLayoutBundle.INSTANCE.dragdropCss().dragging()); 2070 String positioning = CmsDomUtil.getCurrentStyle( 2071 target.getElement(), 2072 org.opencms.gwt.client.util.CmsDomUtil.Style.position); 2073 // set target relative, if not absolute or fixed 2074 if (!Position.ABSOLUTE.getCssName().equals(positioning) && !Position.FIXED.getCssName().equals(positioning)) { 2075 target.getElement().getStyle().setPosition(Position.RELATIVE); 2076 // check for empty containers that don't have a minimum top and bottom margin to avoid containers overlapping 2077 if (target.getElement().getFirstChildElement() == null) { 2078 if (CmsDomUtil.getCurrentStyleInt( 2079 target.getElement(), 2080 CmsDomUtil.Style.marginTop) < MINIMUM_CONTAINER_MARGIN) { 2081 target.getElement().getStyle().setMarginTop(MINIMUM_CONTAINER_MARGIN, Unit.PX); 2082 } 2083 if (CmsDomUtil.getCurrentStyleInt( 2084 target.getElement(), 2085 CmsDomUtil.Style.marginBottom) < MINIMUM_CONTAINER_MARGIN) { 2086 target.getElement().getStyle().setMarginBottom(MINIMUM_CONTAINER_MARGIN, Unit.PX); 2087 } 2088 } 2089 } 2090 m_dragInfos.put(target, placeholder); 2091 if (!handler.isPlacementMode()) { 2092 handler.addTarget(target); 2093 } 2094 } 2095 2096 /** 2097 * Prepares the target container.<p> 2098 * 2099 * @param targetContainer the container 2100 * @param draggable the draggable 2101 * @param placeholder the placeholder 2102 */ 2103 private void prepareTargetContainer( 2104 I_CmsDropContainer targetContainer, 2105 I_CmsDraggable draggable, 2106 Element placeholder) { 2107 2108 String positioning = CmsDomUtil.getCurrentStyle( 2109 targetContainer.getElement(), 2110 org.opencms.gwt.client.util.CmsDomUtil.Style.position); 2111 // set target relative, if not absolute or fixed 2112 if (!Position.ABSOLUTE.getCssName().equals(positioning) && !Position.FIXED.getCssName().equals(positioning)) { 2113 targetContainer.getElement().getStyle().setPosition(Position.RELATIVE); 2114 } 2115 m_originalIndex = targetContainer.getWidgetIndex((Widget)draggable); 2116 targetContainer.getElement().insertBefore(placeholder, draggable.getElement()); 2117 targetContainer.getElement().addClassName(I_CmsLayoutBundle.INSTANCE.dragdropCss().dragging()); 2118 CmsDomUtil.showOverlay(draggable.getElement(), true); 2119 targetContainer.highlightContainer(false); 2120 } 2121 2122 /** 2123 * Removes the drag overlay.<p> 2124 */ 2125 private void removeDragOverlay() { 2126 2127 if (m_dragOverlay != null) { 2128 m_dragOverlay.removeFromParent(); 2129 m_dragOverlay = null; 2130 } 2131 } 2132 2133 /** 2134 * Sets the placement context and initializes it, but cleans up the previous placement context, if any. 2135 * 2136 * @param newContext the placement context (can be null) 2137 */ 2138 private void setPlacementContext(CmsPlacementModeContext newContext) { 2139 2140 CmsPlacementModeContext oldContext = m_placementContext; 2141 m_placementContext = null; 2142 if (oldContext != null) { 2143 oldContext.destroy(); 2144 } 2145 m_placementContext = newContext; 2146 if (m_placementContext != null) { 2147 m_placementContext.init(); 2148 } 2149 } 2150 2151 /** 2152 * Function which is called when the drag process is stopped, either by cancelling or dropping.<p> 2153 * 2154 * @param handler the drag and drop handler 2155 */ 2156 private void stopDrag(final CmsDNDHandler handler) { 2157 2158 removeDragOverlay(); 2159 setPlacementContext(null); 2160 CmsContainerPageContainer.clearResizeHelper(); 2161 for (I_CmsDropTarget target : m_dragInfos.keySet()) { 2162 target.getElement().removeClassName(I_CmsLayoutBundle.INSTANCE.dragdropCss().dragging()); 2163 target.getElement().removeClassName(I_CmsLayoutBundle.INSTANCE.dragdropCss().clearFix()); 2164 Style targetStyle = target.getElement().getStyle(); 2165 if (!(target instanceof CmsGroupContainerElementPanel)) { 2166 targetStyle.clearPosition(); 2167 } 2168 targetStyle.clearMarginTop(); 2169 targetStyle.clearMarginBottom(); 2170 if (target instanceof I_CmsDropContainer) { 2171 ((I_CmsDropContainer)target).removeHighlighting(); 2172 } 2173 } 2174 if ((!handler.isPlacementMode()) && (handler.getDragHelper() != null)) { 2175 handler.getDragHelper().removeFromParent(); 2176 } 2177 m_copyGroupId = null; 2178 m_currentTarget = null; 2179 m_currentIndex = -1; 2180 m_controller.getHandler().deactivateMenuButton(); 2181 final List<I_CmsDropTarget> dragTargets = new ArrayList<I_CmsDropTarget>(m_dragInfos.keySet()); 2182 m_dragInfos.clear(); 2183 Scheduler.get().scheduleDeferred(new ScheduledCommand() { 2184 2185 /** 2186 * @see com.google.gwt.user.client.Command#execute() 2187 */ 2188 public void execute() { 2189 2190 if (!handler.isPlacementMode()) { 2191 handler.clearTargets(); 2192 } 2193 m_controller.resetEditButtons(); 2194 // in case of group editing, this will refresh the container position and size 2195 for (I_CmsDropTarget target : dragTargets) { 2196 if (target instanceof I_CmsDropContainer) { 2197 ((I_CmsDropContainer)target).refreshHighlighting(); 2198 // reset the nested container infos 2199 ((I_CmsDropContainer)target).clearDnDChildren(); 2200 } 2201 } 2202 } 2203 }); 2204 if ((!handler.isPlacementMode()) && (handler.getDraggable() instanceof CmsContainerPageElementPanel)) { 2205 ((CmsContainerPageElementPanel)(handler.getDraggable())).removeHighlighting(); 2206 } 2207 if (handler.isPlacementMode()) { 2208 handler.clearPlacement(); 2209 } 2210 } 2211 2212 /** 2213 * Updates the drag target highlighting.<p> 2214 * 2215 * @param initial <code>true</code> when initially highlighting the drop containers 2216 * @param placementMode when we're in placement mode 2217 */ 2218 private void updateHighlighting(boolean initial, boolean placementMode) { 2219 2220 Map<I_CmsDropContainer, CmsPositionBean> containers = new HashMap<I_CmsDropContainer, CmsPositionBean>(); 2221 for (I_CmsDropTarget target : m_dragInfos.keySet()) { 2222 if ((target instanceof I_CmsDropContainer) && (target.getElement().getOffsetParent() != null)) { 2223 if (initial && (target != m_initialDropTarget)) { 2224 ((I_CmsDropContainer)target).highlightContainer(placementMode); 2225 } else { 2226 ((I_CmsDropContainer)target).updatePositionInfo(); 2227 } 2228 containers.put((I_CmsDropContainer)target, ((I_CmsDropContainer)target).getPositionInfo()); 2229 } 2230 } 2231 2232 List<I_CmsDropContainer> containersToMatch = new ArrayList<I_CmsDropContainer>(containers.keySet()); 2233 if (!placementMode) { 2234 // in placement mode, only one container is highlighted at a time, so we don't need to run the collision avoidance 2235 for (I_CmsDropContainer contA : containers.keySet()) { 2236 containersToMatch.remove(contA); 2237 for (I_CmsDropContainer contB : containersToMatch) { 2238 CmsPositionBean posA = containers.get(contA); 2239 CmsPositionBean posB = containers.get(contB); 2240 if (CmsPositionBean.checkCollision(posA, posB, HIGHLIGHTING_OFFSET * 3)) { 2241 if (contA.hasDnDChildren() && contA.getDnDChildren().contains(contB)) { 2242 if (!posA.isInside(posB, HIGHLIGHTING_OFFSET)) { 2243 // the nested container is not completely inside the other 2244 // increase the size of the outer container 2245 posA.ensureSurrounds(posB, HIGHLIGHTING_OFFSET); 2246 } 2247 } else if (contB.hasDnDChildren() && contB.getDnDChildren().contains(contA)) { 2248 if (!posB.isInside(posA, HIGHLIGHTING_OFFSET)) { 2249 // the nested container is not completely inside the other 2250 // increase the size of the outer container 2251 posB.ensureSurrounds(posA, HIGHLIGHTING_OFFSET); 2252 } 2253 } else { 2254 CmsPositionBean.avoidCollision(posA, posB, HIGHLIGHTING_OFFSET * 3); 2255 } 2256 } 2257 } 2258 } 2259 } 2260 2261 for (Entry<I_CmsDropContainer, CmsPositionBean> containerEntry : containers.entrySet()) { 2262 containerEntry.getKey().refreshHighlighting(containerEntry.getValue()); 2263 } 2264 } 2265}