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.sitemap.client.control;
029
030import org.opencms.ade.detailpage.CmsDetailPageInfo;
031import org.opencms.ade.sitemap.client.CmsSitemapTreeItem;
032import org.opencms.ade.sitemap.client.CmsSitemapView;
033import org.opencms.ade.sitemap.client.toolbar.CmsSitemapToolbar;
034import org.opencms.ade.sitemap.client.ui.CmsCreatableListItem;
035import org.opencms.ade.sitemap.client.ui.css.I_CmsSitemapLayoutBundle;
036import org.opencms.ade.sitemap.shared.CmsClientSitemapEntry;
037import org.opencms.ade.sitemap.shared.CmsClientSitemapEntry.EntryType;
038import org.opencms.ade.sitemap.shared.CmsNewResourceInfo;
039import org.opencms.gwt.client.dnd.CmsDNDHandler;
040import org.opencms.gwt.client.dnd.CmsDNDHandler.Orientation;
041import org.opencms.gwt.client.dnd.I_CmsDNDController;
042import org.opencms.gwt.client.dnd.I_CmsDraggable;
043import org.opencms.gwt.client.dnd.I_CmsDropTarget;
044import org.opencms.gwt.client.property.CmsReloadMode;
045import org.opencms.gwt.client.ui.tree.CmsTree;
046import org.opencms.gwt.client.util.CmsDebugLog;
047import org.opencms.gwt.client.util.CmsDomUtil;
048import org.opencms.gwt.client.util.CmsDomUtil.Tag;
049import org.opencms.gwt.client.util.I_CmsSimpleCallback;
050import org.opencms.gwt.shared.property.CmsClientProperty;
051import org.opencms.gwt.shared.property.CmsPropertyModification;
052
053import java.util.Collections;
054import java.util.List;
055import java.util.Map;
056
057import com.google.common.collect.Maps;
058import com.google.gwt.core.client.Scheduler;
059import com.google.gwt.core.client.Scheduler.ScheduledCommand;
060import com.google.gwt.dom.client.Element;
061import com.google.gwt.dom.client.Style.Unit;
062
063/**
064 * The sitemap drag and drop controller.<p>
065 *
066 * @since 8.0.0
067 */
068public class CmsSitemapDNDController implements I_CmsDNDController {
069
070    /** The sitemap controller instance. */
071    CmsSitemapController m_controller;
072
073    /** The insert position of the draggable. */
074    int m_insertIndex;
075
076    /** The insert path of the draggable. */
077    String m_insertPath;
078
079    /** The original position of the draggable. */
080    private int m_originalIndex;
081
082    /** The original path of the draggable. */
083    private String m_originalPath;
084
085    /** The sitemap toolbar. */
086    private CmsSitemapToolbar m_toolbar;
087
088    /**
089     * Constructor.<p>
090     *
091     * @param controller the sitemap controller
092     * @param toolbar the sitemap toolbar
093     */
094    public CmsSitemapDNDController(CmsSitemapController controller, CmsSitemapToolbar toolbar) {
095
096        m_controller = controller;
097        m_toolbar = toolbar;
098    }
099
100    /**
101     * @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)
102     */
103    public void onAnimationStart(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) {
104
105        // nothing to do
106    }
107
108    /**
109     * @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)
110     */
111    public boolean onBeforeDrop(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) {
112
113        if (!(target instanceof CmsTree<?>)) {
114            // only dropping onto the tree allowed in sitemap editor
115            return false;
116        }
117        CmsTree<?> tree = (CmsTree<?>)target;
118        m_insertPath = tree.getPlaceholderPath();
119        m_insertIndex = tree.getPlaceholderIndex();
120        if (m_insertPath.equals(m_originalPath) && (m_insertIndex > m_originalIndex)) {
121            // new position has the same path and is below the original position, adjust insert index
122            m_insertIndex -= 1;
123            if (m_insertIndex == m_originalIndex) {
124                return false;
125            }
126        }
127        if (m_insertIndex == -1) {
128            return false;
129        }
130        return true;
131    }
132
133    /**
134     * @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)
135     */
136    public void onDragCancel(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) {
137
138        if (draggable instanceof CmsSitemapTreeItem) {
139            ((CmsSitemapTreeItem)draggable).resetEntry();
140        }
141        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
142
143            /**
144             * @see com.google.gwt.user.client.Command#execute()
145             */
146            public void execute() {
147
148                CmsSitemapView.getInstance().getTree().closeAllEmpty();
149            }
150        });
151    }
152
153    /**
154     * @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)
155     */
156    public boolean onDragStart(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) {
157
158        handler.setOrientation(Orientation.VERTICAL);
159        hideItemContent(handler.getPlaceholder());
160        handler.getDragHelper().getStyle().setOpacity(0.6);
161        m_insertIndex = -1;
162        m_insertPath = null;
163        m_originalIndex = -1;
164        m_originalPath = null;
165        if (draggable instanceof CmsCreatableListItem) {
166            m_toolbar.onButtonActivation(null);
167
168            // fixing placeholder indent not being present in non tree items
169            List<Element> elements = CmsDomUtil.getElementsByClass(
170                org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.floatDecoratedPanelCss().primary(),
171                Tag.div,
172                handler.getPlaceholder());
173            if ((elements != null) && (elements.size() > 0)) {
174                elements.get(0).getStyle().setMarginLeft(16, Unit.PX);
175            }
176        } else if (draggable instanceof CmsSitemapTreeItem) {
177            CmsSitemapTreeItem treeItem = (CmsSitemapTreeItem)draggable;
178            m_originalPath = treeItem.getParentItem().getPath();
179            if (treeItem.getParentItem() != null) {
180                m_originalIndex = treeItem.getParentItem().getItemPosition(treeItem);
181            }
182        }
183        CmsDebugLog.getInstance().printLine("Starting path: " + m_originalPath + ", Index: " + m_originalIndex);
184        return true;
185    }
186
187    /**
188     * @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)
189     */
190    public void onDrop(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) {
191
192        if (!(target instanceof CmsTree<?>)) {
193            // only dropping onto the tree allowed in sitemap
194            handler.cancel();
195            return;
196        }
197        CmsClientSitemapEntry parent = CmsSitemapView.getInstance().getController().getEntry(m_insertPath);
198        if (draggable instanceof CmsSitemapTreeItem) {
199            handleDropSitemapEntry((CmsSitemapTreeItem)draggable, target, parent);
200        }
201        if (draggable instanceof CmsCreatableListItem) {
202            handleDropNewEntry((CmsCreatableListItem)draggable, parent);
203        }
204        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
205
206            /**
207             * @see com.google.gwt.user.client.Command#execute()
208             */
209            public void execute() {
210
211                CmsSitemapView.getInstance().getTree().closeAllEmpty();
212            }
213        });
214    }
215
216    /**
217     * @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)
218     */
219    public void onPositionedPlaceholder(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) {
220
221        if (draggable instanceof CmsSitemapTreeItem) {
222            adjustOriginalPositionIndicator((CmsSitemapTreeItem)draggable, target, handler);
223        }
224    }
225
226    /**
227     * @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)
228     */
229    public boolean onTargetEnter(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) {
230
231        return true;
232    }
233
234    /**
235     * @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)
236     */
237    public void onTargetLeave(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) {
238
239        if (target instanceof CmsTree<?>) {
240            ((CmsTree<?>)target).cancelOpenTimer();
241        }
242    }
243
244    /**
245     * Checks whether the current placeholder position represents a change to the original draggable position within the tree.<p>
246     *
247     * @param draggable the draggable
248     * @param target the current drop target
249     * @param strict if <code>false</code> only the parent path is considered, the index position will be ignored
250     *
251     * @return <code>true</code> if the position changed
252     */
253    boolean isChangedPosition(I_CmsDraggable draggable, I_CmsDropTarget target, boolean strict) {
254
255        // if draggable is not a sitemap item, any valid position is a changed position
256        if (!((draggable instanceof CmsSitemapTreeItem) && (target instanceof CmsTree<?>))) {
257            return true;
258        }
259
260        String placeholderPath = ((CmsTree<?>)target).getPlaceholderPath();
261        if ((placeholderPath == null) && !strict) {
262            // first positioning, path has not changed yet
263            return false;
264        }
265        // if the the path differs, the position has changed
266        if ((m_originalPath == null) || !m_originalPath.equals(placeholderPath)) {
267            return true;
268        }
269        // if the new index is not next to the old one, the position has changed
270        if (!((target.getPlaceholderIndex() == (m_originalIndex + 1))
271            || (target.getPlaceholderIndex() == m_originalIndex)) && strict) {
272            return true;
273        }
274        return false;
275    }
276
277    /**
278     * Adjust the original position indicator by styling the draggable element for this purpose.<p>
279     *
280     * @param draggable the draggable
281     * @param target the current drop target
282     * @param handler the drag and drop handler
283     */
284    private void adjustOriginalPositionIndicator(
285        CmsSitemapTreeItem draggable,
286        I_CmsDropTarget target,
287        CmsDNDHandler handler) {
288
289        if (!isChangedPosition(draggable, target, true)) {
290            draggable.getElement().addClassName(I_CmsSitemapLayoutBundle.INSTANCE.sitemapItemCss().markUnchanged());
291            List<Element> itemWidget = CmsDomUtil.getElementsByClass(
292                org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().itemContainer(),
293                handler.getPlaceholder());
294            if ((itemWidget != null) && (itemWidget.size() > 0)) {
295                CmsDomUtil.addDisablingOverlay(itemWidget.get(0));
296            }
297        } else {
298            draggable.getElement().removeClassName(I_CmsSitemapLayoutBundle.INSTANCE.sitemapItemCss().markUnchanged());
299            CmsDomUtil.removeDisablingOverlay(handler.getPlaceholder());
300        }
301    }
302
303    /**
304     * Handles a dropped detail page.<p>
305     *
306     * @param createItem the detail page which was dropped into the sitemap
307     * @param parent the parent sitemap entry
308     */
309    private void handleDropNewEntry(CmsCreatableListItem createItem, final CmsClientSitemapEntry parent) {
310
311        final CmsNewResourceInfo typeInfo = createItem.getResourceTypeInfo();
312        final CmsClientSitemapEntry entry = new CmsClientSitemapEntry();
313        entry.setNew(true);
314        entry.setVfsPath(null);
315        entry.setPosition(m_insertIndex);
316        entry.setInNavigation(true);
317        Map<String, CmsClientProperty> defaultFileProps = Maps.newHashMap();
318        entry.setDefaultFileProperties(defaultFileProps);
319        String name;
320        final boolean appendSlash;
321        switch (createItem.getNewEntryType()) {
322            case detailpage:
323                name = CmsDetailPageInfo.removeFunctionPrefix(typeInfo.getResourceType());
324                appendSlash = true;
325                entry.setDetailpageTypeName(typeInfo.getResourceType());
326                entry.setVfsModeIcon(typeInfo.getBigIconClasses());
327                if (typeInfo.isFunction()) {
328
329                    CmsClientProperty titleProp = new CmsClientProperty(
330                        CmsClientProperty.PROPERTY_TITLE,
331                        typeInfo.getTitle(),
332                        null);
333                    CmsClientProperty navtextProp = new CmsClientProperty(
334                        CmsClientProperty.PROPERTY_NAVTEXT,
335                        typeInfo.getTitle(),
336                        null);
337                    entry.getOwnProperties().put(titleProp.getName(), titleProp);
338                    entry.getDefaultFileProperties().put(titleProp.getName(), titleProp);
339                    entry.getOwnProperties().put(navtextProp.getName(), navtextProp);
340                }
341                entry.setResourceTypeName("folder");
342                break;
343            case redirect:
344                name = typeInfo.getResourceType();
345                entry.setEntryType(EntryType.redirect);
346                entry.setVfsModeIcon(typeInfo.getBigIconClasses());
347                entry.setResourceTypeName(typeInfo.getResourceType());
348                appendSlash = false;
349                break;
350            default:
351                name = CmsSitemapController.NEW_ENTRY_NAME;
352                appendSlash = true;
353                entry.setResourceTypeName("folder");
354                entry.setVfsModeIcon(typeInfo.getBigIconClasses());
355        }
356
357        m_controller.ensureUniqueName(parent, name, new I_CmsSimpleCallback<String>() {
358
359            public void execute(String uniqueName) {
360
361                entry.setName(uniqueName);
362                String sitepath = m_insertPath + uniqueName;
363                if (appendSlash) {
364                    sitepath += "/";
365                }
366                entry.setSitePath(sitepath);
367                m_controller.create(
368                    entry,
369                    parent.getId(),
370                    typeInfo.getId(),
371                    typeInfo.getCopyResourceId(),
372                    typeInfo.getCreateParameter(),
373                    false);
374            }
375        });
376
377    }
378
379    /**
380     * Handles the drop for a sitemap item which was dragged to a different position.<p>
381     *
382     * @param sitemapEntry the dropped item
383     * @param target the drop target
384     * @param parent the parent sitemap entry
385     */
386    private void handleDropSitemapEntry(
387        final CmsSitemapTreeItem sitemapEntry,
388        final I_CmsDropTarget target,
389        CmsClientSitemapEntry parent) {
390
391        if (isChangedPosition(sitemapEntry, target, true)) {
392            // moving a tree entry around
393            final CmsClientSitemapEntry entry = sitemapEntry.getSitemapEntry();
394            m_controller.ensureUniqueName(parent, entry.getName(), new I_CmsSimpleCallback<String>() {
395
396                public void execute(String uniqueName) {
397
398                    if (!uniqueName.equals(entry.getName()) && isChangedPosition(sitemapEntry, target, false)) {
399                        m_controller.editAndChangeName(
400                            entry,
401                            uniqueName,
402                            Collections.<CmsPropertyModification> emptyList(),
403                            entry.isNew(),
404                            CmsReloadMode.none);
405                        m_controller.move(entry, m_insertPath + uniqueName + "/", m_insertIndex);
406                    } else {
407                        m_controller.move(entry, m_insertPath + entry.getName() + "/", m_insertIndex);
408                    }
409                }
410            });
411        } else {
412            sitemapEntry.resetEntry();
413        }
414    }
415
416    /**
417     * Hides the content of list items by setting a specific css class.<p>
418     *
419     * @param element the list item element
420     */
421    private void hideItemContent(Element element) {
422
423        List<Element> itemWidget = CmsDomUtil.getElementsByClass(
424            org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().itemContainer(),
425            element);
426        if ((itemWidget != null) && (itemWidget.size() > 0)) {
427            itemWidget.get(0).addClassName(I_CmsSitemapLayoutBundle.INSTANCE.sitemapItemCss().contentHide());
428        }
429    }
430}