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}