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.CmsContainerPageElementPanel; 031import org.opencms.ade.containerpage.shared.CmsContainerElement; 032import org.opencms.ade.contenteditor.client.CmsContentEditor; 033import org.opencms.ade.galleries.client.ui.CmsResultListItem; 034import org.opencms.gwt.client.dnd.CmsDNDHandler; 035import org.opencms.gwt.client.dnd.CmsDNDHandler.Orientation; 036import org.opencms.gwt.client.dnd.I_CmsDNDController; 037import org.opencms.gwt.client.dnd.I_CmsDraggable; 038import org.opencms.gwt.client.dnd.I_CmsDropTarget; 039import org.opencms.gwt.client.ui.CmsHighlightingBorder; 040import org.opencms.gwt.client.ui.CmsHighlightingBorder.BorderColor; 041import org.opencms.gwt.client.util.CmsDomUtil; 042import org.opencms.gwt.client.util.CmsPositionBean; 043import org.opencms.util.CmsStringUtil; 044import org.opencms.util.CmsUUID; 045 046import java.util.List; 047 048import com.google.common.base.Optional; 049import com.google.common.collect.Lists; 050import com.google.gwt.dom.client.Element; 051import com.google.gwt.user.client.Window; 052import com.google.gwt.user.client.rpc.AsyncCallback; 053import com.google.gwt.user.client.ui.RootPanel; 054 055/** 056 * DND controller for drag/drop of images from the gallery menu.<p> 057 * 058 * Since the image drag and drop logic is mostly separate from the container drag and drop logic, the container page DND controller 059 * delegates most of the drag and drop logic to this class if it detects that an image is being dragged.<p> 060 */ 061public class CmsImageDndController implements I_CmsDNDController { 062 063 /** 064 * Drop target for an image.<p> 065 */ 066 class ImageDropTarget implements I_CmsDropTarget { 067 068 /** The border to display around the drop target. */ 069 private CmsHighlightingBorder m_border; 070 071 /** The container element to which the drop target belongs (optional). */ 072 private Optional<CmsContainerPageElementPanel> m_containerElement; 073 074 /** The element which acts as the drop target. */ 075 private Element m_element; 076 077 /** 078 * Creates a new instance.<p> 079 * 080 * @param element the element to use as the drop target 081 * 082 * @param containerElement the optional container element to which the drop target belongs 083 */ 084 public ImageDropTarget(Element element, Optional<CmsContainerPageElementPanel> containerElement) { 085 086 m_element = element; 087 m_border = new CmsHighlightingBorder(CmsPositionBean.getBoundingClientRect(m_element), BorderColor.red); 088 m_containerElement = containerElement; 089 RootPanel.get().add(m_border); 090 091 } 092 093 /** 094 * @see org.opencms.gwt.client.dnd.I_CmsDropTarget#checkPosition(int, int, org.opencms.gwt.client.dnd.CmsDNDHandler.Orientation) 095 */ 096 public boolean checkPosition(int x, int y, Orientation orientation) { 097 098 CmsPositionBean position = getPosition(); 099 boolean result = position.containsPoint(x, y); 100 return result; 101 } 102 103 /** 104 * Cleans up widgets which are part of this drop target and are no longer needed.<p> 105 */ 106 public void cleanup() { 107 108 m_border.removeFromParent(); 109 } 110 111 /** 112 * @see org.opencms.gwt.client.dnd.I_CmsDropTarget#getElement() 113 */ 114 public Element getElement() { 115 116 return m_element; 117 } 118 119 /** 120 * @see org.opencms.gwt.client.dnd.I_CmsDropTarget#getPlaceholderIndex() 121 */ 122 public int getPlaceholderIndex() { 123 124 return 0; 125 } 126 127 /** 128 * @see org.opencms.gwt.client.dnd.I_CmsDropTarget#insertPlaceholder(com.google.gwt.dom.client.Element, int, int, org.opencms.gwt.client.dnd.CmsDNDHandler.Orientation) 129 */ 130 public void insertPlaceholder(Element placeholder, int x, int y, Orientation orientation) { 131 132 // do nothing 133 134 } 135 136 /** 137 * @see org.opencms.gwt.client.dnd.I_CmsDropTarget#onDrop(org.opencms.gwt.client.dnd.I_CmsDraggable) 138 */ 139 public void onDrop(I_CmsDraggable draggable) { 140 141 if (draggable instanceof CmsResultListItem) { 142 CmsResultListItem result = (CmsResultListItem)draggable; 143 String path = result.getResult().getPath(); 144 saveImage(path); 145 } 146 } 147 148 /** 149 * @see org.opencms.gwt.client.dnd.I_CmsDropTarget#removePlaceholder() 150 */ 151 public void removePlaceholder() { 152 153 // do nothing 154 } 155 156 /** 157 * @see org.opencms.gwt.client.dnd.I_CmsDropTarget#repositionPlaceholder(int, int, org.opencms.gwt.client.dnd.CmsDNDHandler.Orientation) 158 */ 159 public void repositionPlaceholder(int x, int y, Orientation orientation) { 160 161 // do nothing 162 } 163 164 /** 165 * Saves the image with the given path to the content to which this drop zone belongs.<p> 166 * 167 * @param path the path of the image to save 168 */ 169 @SuppressWarnings("synthetic-access") 170 public void saveImage(final String path) { 171 172 String attribute = m_element.getAttribute(ATTR_DATA_IMAGEDND); 173 if (attribute != null) { 174 List<String> tokens = CmsStringUtil.splitAsList(attribute, "|"); 175 if (tokens.size() == 3) { 176 final String contentId = tokens.get(0); 177 final String contentPath = tokens.get(1); 178 final String locale = tokens.get(2); 179 if ((new CmsUUID(contentId)).isNullUUID()) { 180 if (m_containerElement.isPresent() && m_containerElement.get().isNew()) { 181 m_pageController.createNewElement( 182 m_containerElement.get(), 183 new AsyncCallback<CmsContainerElement>() { 184 185 public void onFailure(Throwable caught) { 186 187 // do nothing 188 } 189 190 public void onSuccess(final CmsContainerElement result) { 191 192 m_containerElement.get().setNewType(null); 193 m_containerElement.get().setId(result.getClientId()); 194 m_containerElement.get().setSitePath(result.getSitePath()); 195 196 m_pageController.setPageChanged(new Runnable() { 197 198 public void run() { 199 200 String serverId = CmsContainerpageController.getServerId( 201 result.getClientId()); 202 saveAndReloadElement(serverId, contentPath, locale, path); 203 } 204 }); 205 } 206 }); 207 } else { 208 return; 209 } 210 } else { 211 saveAndReloadElement(contentId, contentPath, locale, path); 212 } 213 } 214 215 } 216 217 } 218 219 /** 220 * Sets the border color.<p> 221 * 222 * @param color the border color 223 */ 224 public void setBorderColor(BorderColor color) { 225 226 m_border.setColor(color); 227 } 228 229 /** 230 * Saves the value to the content and reloads the corresponding container element.<p> 231 * 232 * @param contentId the content structure id 233 * @param contentPath the content xpath 234 * @param locale the locale 235 * @param value the value to save 236 */ 237 protected void saveAndReloadElement(String contentId, String contentPath, String locale, String value) { 238 239 CmsContentEditor.getInstance().saveValue( 240 contentId, 241 contentPath, 242 locale, 243 value, 244 new AsyncCallback<String>() { 245 246 public void onFailure(Throwable caught) { 247 248 // TODO Auto-generated method stub 249 250 } 251 252 @SuppressWarnings("synthetic-access") 253 public void onSuccess(String result) { 254 255 if (m_containerElement.isPresent()) { 256 String clientId = m_containerElement.get().getId(); 257 m_pageController.reloadElements(new String[] {clientId}, () -> {/*do nothing*/}); 258 } else { 259 Window.Location.reload(); 260 } 261 } 262 }); 263 } 264 265 /** 266 * Gets the position.<p> 267 * 268 * @return the position 269 */ 270 CmsPositionBean getPosition() { 271 272 return CmsPositionBean.getBoundingClientRect(m_element, false); 273 } 274 } 275 276 /** The attribute used to mark image drop zones. */ 277 public static final String ATTR_DATA_IMAGEDND = "data-imagednd"; 278 279 /** The current list of image drop targets. */ 280 private List<ImageDropTarget> m_imageDropTargets = Lists.newArrayList(); 281 282 /** The container page controller. */ 283 private CmsContainerpageController m_pageController; 284 285 /** 286 * Creates a new instance.<p> 287 * 288 * @param controller the container page controller 289 */ 290 public CmsImageDndController(CmsContainerpageController controller) { 291 292 m_pageController = controller; 293 } 294 295 /** 296 * @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) 297 */ 298 public void onAnimationStart(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) { 299 300 // do nothing 301 } 302 303 /** 304 * @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) 305 */ 306 public boolean onBeforeDrop(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) { 307 308 return true; 309 } 310 311 /** 312 * @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) 313 */ 314 public void onDragCancel(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) { 315 316 cleanupTargets(); 317 } 318 319 /** 320 * @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) 321 */ 322 public boolean onDragStart(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) { 323 324 handler.clearTargets(); 325 m_imageDropTargets.clear(); 326 m_imageDropTargets.addAll(findImageTargets()); 327 CmsContainerpageController.get().getHandler().hideMenu(); 328 for (ImageDropTarget target1 : m_imageDropTargets) { 329 handler.addTarget(target1); 330 } 331 Optional<int[]> offsetDelta = handler.getDraggable().getCursorOffsetDelta(); 332 if (offsetDelta.isPresent()) { 333 handler.setCursorOffsetX(handler.getCursorOffsetX() + offsetDelta.get()[0]); 334 handler.setCursorOffsetY(handler.getCursorOffsetY() + offsetDelta.get()[1]); 335 } 336 return true; 337 } 338 339 /** 340 * @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) 341 */ 342 public void onDrop(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) { 343 344 // actual drop logic is handled by the ImageDropTarget class, we only do some cleanup here 345 cleanupTargets(); 346 handler.clearTargets(); 347 } 348 349 /** 350 * @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) 351 */ 352 public void onPositionedPlaceholder(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) { 353 354 // do nothing 355 356 } 357 358 /** 359 * @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) 360 */ 361 public boolean onTargetEnter(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) { 362 363 if (target instanceof ImageDropTarget) { 364 ImageDropTarget imageTarget = (ImageDropTarget)target; 365 imageTarget.setBorderColor(BorderColor.blue); 366 } 367 return true; 368 } 369 370 /** 371 * @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) 372 */ 373 public void onTargetLeave(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) { 374 375 if (target instanceof ImageDropTarget) { 376 ImageDropTarget imageTarget = (ImageDropTarget)target; 377 imageTarget.setBorderColor(BorderColor.red); 378 379 } 380 } 381 382 /** 383 * Collects the valid drop targets for images from the page and initializes them.<p> 384 * 385 * @return the list of drop targets 386 */ 387 protected List<ImageDropTarget> findImageTargets() { 388 389 List<ImageDropTarget> result = Lists.newArrayList(); 390 List<CmsContainerPageElementPanel> modelGroups = CmsContainerpageController.get().getModelGroups(); 391 elementLoop: for (Element element : CmsDomUtil.nodeListToList( 392 CmsDomUtil.querySelectorAll("*[" + ATTR_DATA_IMAGEDND + "]", RootPanel.getBodyElement()))) { 393 Optional<CmsContainerPageElementPanel> optElemWidget = CmsContainerpageController.get().getContainerElementWidgetForElement( 394 element); 395 if (optElemWidget.isPresent()) { 396 CmsContainerPageElementPanel elemWidget = optElemWidget.get(); 397 if (!elemWidget.hasViewPermission()) { 398 continue elementLoop; 399 } 400 String noEditReason = elemWidget.getNoEditReason(); 401 if ((noEditReason != null) && !elemWidget.hasWritePermission()) { 402 continue elementLoop; 403 } 404 } 405 if (!CmsContainerpageController.get().getData().isModelGroup()) { 406 // Don't make images in model groups into drop targets, except when we are in model group editing mode 407 for (CmsContainerPageElementPanel modelGroup : modelGroups) { 408 if (modelGroup.getElement().isOrHasChild(element)) { 409 continue elementLoop; 410 } 411 } 412 } 413 ImageDropTarget target = new ImageDropTarget(element, optElemWidget); 414 result.add(target); 415 } 416 return result; 417 418 } 419 420 /** 421 * Cleans up all the drop targets.<p> 422 */ 423 private void cleanupTargets() { 424 425 for (ImageDropTarget target : m_imageDropTargets) { 426 target.cleanup(); 427 } 428 m_imageDropTargets.clear(); 429 } 430 431}