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