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.gwt.client.ui.tree;
029
030import org.opencms.gwt.client.dnd.CmsDNDHandler.Orientation;
031import org.opencms.gwt.client.ui.CmsList;
032import org.opencms.gwt.client.util.CmsDebugLog;
033import org.opencms.gwt.client.util.CmsDomUtil;
034
035import com.google.gwt.dom.client.Element;
036import com.google.gwt.event.logical.shared.CloseEvent;
037import com.google.gwt.event.logical.shared.CloseHandler;
038import com.google.gwt.event.logical.shared.HasCloseHandlers;
039import com.google.gwt.event.logical.shared.HasOpenHandlers;
040import com.google.gwt.event.logical.shared.OpenEvent;
041import com.google.gwt.event.logical.shared.OpenHandler;
042import com.google.gwt.event.shared.GwtEvent;
043import com.google.gwt.event.shared.HandlerRegistration;
044import com.google.gwt.event.shared.SimpleEventBus;
045import com.google.gwt.user.client.Timer;
046import com.google.gwt.user.client.ui.HasAnimation;
047
048/**
049 * A tree of list items.<p>
050 *
051 * @param <I> the specific tree item implementation
052 *
053 * @since 8.0.0
054 */
055public class CmsTree<I extends CmsTreeItem> extends CmsList<I>
056implements HasOpenHandlers<I>, HasCloseHandlers<I>, HasAnimation {
057
058    /**
059     * Timer to set sub item list visible.<p>
060     */
061    private class OpenTimer extends Timer {
062
063        /** The tree item. */
064        private CmsTreeItem m_item;
065
066        /**
067         * Constructor.<p>
068         *
069         * @param item the tree item
070         */
071        protected OpenTimer(CmsTreeItem item) {
072
073            m_item = item;
074        }
075
076        /**
077         * @see com.google.gwt.user.client.Timer#run()
078         */
079        @Override
080        public void run() {
081
082            m_item.setOpen(true);
083            removeOpenTimer();
084        }
085
086        /**
087         * Checks if the timer is running for the given tree item.<p>
088         *
089         * @param item the tree item to check
090         * @return <code>true</code> if the given item matches the timer item
091         */
092        protected boolean checkTimer(CmsTreeItem item) {
093
094            return item == m_item;
095        }
096
097    }
098
099    /** The event bus for the tree. */
100    protected SimpleEventBus m_eventBus;
101
102    /** Flag to indicate is animations are enabled or not. */
103    private boolean m_animate;
104
105    /** The open timer if one is running. */
106    private OpenTimer m_openTimer;
107
108    /** The parent path of the current placeholder. */
109    private String m_placeholderPath;
110
111    /** Flag to indicate if dropping on root level is enabled or not. */
112    private boolean m_rootDropEnabled;
113
114    /**
115     * Constructor.<p>
116     */
117    public CmsTree() {
118
119        m_animate = false;
120        m_eventBus = new SimpleEventBus();
121    }
122
123    /**
124     * @see com.google.gwt.event.logical.shared.HasCloseHandlers#addCloseHandler(com.google.gwt.event.logical.shared.CloseHandler)
125     */
126    public HandlerRegistration addCloseHandler(CloseHandler<I> handler) {
127
128        return m_eventBus.addHandlerToSource(CloseEvent.getType(), this, handler);
129    }
130
131    /**
132     * @see com.google.gwt.event.logical.shared.HasOpenHandlers#addOpenHandler(com.google.gwt.event.logical.shared.OpenHandler)
133     */
134    public HandlerRegistration addOpenHandler(final OpenHandler<I> handler) {
135
136        return m_eventBus.addHandlerToSource(OpenEvent.getType(), this, handler);
137    }
138
139    /**
140     * Cancels the open timer if present.<p>
141     */
142    public void cancelOpenTimer() {
143
144        if (m_openTimer != null) {
145            m_openTimer.cancel();
146            m_openTimer = null;
147        }
148    }
149
150    /**
151     * Closes all empty entries.<p>
152     */
153    public void closeAllEmpty() {
154
155        CmsDebugLog.getInstance().printLine("closing all empty");
156        int childCount = getWidgetCount();
157        for (int index = 0; index < childCount; index++) {
158            CmsTreeItem item = getItem(index);
159            if (item.isOpen()) {
160                item.closeAllEmptyChildren();
161            }
162        }
163    }
164
165    /**
166     * Fires the close event for an item.<p>
167     *
168     * @param item the item for which to fire the close event
169     */
170    public void fireClose(I item) {
171
172        CloseEvent.fire(this, item);
173    }
174
175    /**
176     * @see com.google.gwt.user.client.ui.Widget#fireEvent(com.google.gwt.event.shared.GwtEvent)
177     */
178    @Override
179    public void fireEvent(GwtEvent<?> event) {
180
181        m_eventBus.fireEventFromSource(event, this);
182    }
183
184    /**
185     * Fires an open event for a tree item.<p>
186     *
187     * @param item the tree item for which the open event should be fired
188     */
189    public void fireOpen(I item) {
190
191        OpenEvent.fire(this, item);
192    }
193
194    /**
195     * Returns the placeholder path.<p>
196     *
197     * @return the path
198     */
199    public String getPlaceholderPath() {
200
201        return m_placeholderPath;
202    }
203
204    /**
205     * @see com.google.gwt.user.client.ui.HasAnimation#isAnimationEnabled()
206     */
207    public boolean isAnimationEnabled() {
208
209        return m_animate;
210    }
211
212    /**
213     * Returns if dropping on root level is enabled or not.<p>
214     *
215     * @return <code>true</code> if dropping on root level is enabled
216     */
217    public boolean isRootDropEnabled() {
218
219        return m_rootDropEnabled;
220    }
221
222    /**
223     * @see org.opencms.gwt.client.ui.CmsList#removePlaceholder()
224     */
225    @Override
226    public void removePlaceholder() {
227
228        super.removePlaceholder();
229        m_placeholderPath = null;
230    }
231
232    /**
233     * @see org.opencms.gwt.client.ui.CmsList#repositionPlaceholder(int, int, Orientation)
234     */
235    @Override
236    public void repositionPlaceholder(int x, int y, Orientation orientation) {
237
238        int widgetCount = getWidgetCount();
239        for (int index = 0; index < widgetCount; index++) {
240            CmsTreeItem item = getItem(index);
241            Element itemElement = item.getElement();
242            boolean over = false;
243            switch (orientation) {
244                case HORIZONTAL:
245                    over = CmsDomUtil.checkPositionInside(itemElement, x, -1);
246                    break;
247                case VERTICAL:
248                    over = CmsDomUtil.checkPositionInside(itemElement, -1, y);
249                    break;
250                case ALL:
251                default:
252                    over = CmsDomUtil.checkPositionInside(itemElement, x, y);
253            }
254
255            if (over) {
256                m_placeholderIndex = item.repositionPlaceholder(x, y, m_placeholder, orientation);
257                return;
258            }
259            if (isDNDTakeAll() && (index == (widgetCount - 1))) {
260                // last item of the list, no matching item was found and take-all is enabled
261                // check if cursor position is above or below
262                int relativeTop = CmsDomUtil.getRelativeY(y, getElement());
263                int elementHeight = getElement().getOffsetHeight();
264                if (relativeTop <= 0) {
265                    if (isRootDropEnabled()) {
266                        getElement().insertBefore(m_placeholder, getItem(0).getElement());
267                        setPlaceholderPath("/");
268                        m_placeholderIndex = 0;
269                    }
270                } else {
271                    if (relativeTop > elementHeight) {
272                        // insert as last into last opened tree-item
273                        if (item.isOpen() && (item.getChildCount() > 0)) {
274                            int originalPathLevel = -1;
275                            if ((getDnDHandler() != null) && (getDnDHandler().getDraggable() instanceof CmsTreeItem)) {
276                                originalPathLevel = CmsTreeItem.getPathLevel(
277                                    ((CmsTreeItem)getDnDHandler().getDraggable()).getPath()) - 1;
278                            }
279                            // insert into the tree as last visible item
280                            CmsTreeItem lastOpened = CmsTreeItem.getLastOpenedItem(item, originalPathLevel, true);
281                            m_placeholderIndex = lastOpened.insertPlaceholderAsLastChild(m_placeholder);
282                        } else if (isRootDropEnabled()) {
283                            getElement().insertAfter(m_placeholder, itemElement);
284                            setPlaceholderPath("/");
285                            m_placeholderIndex = widgetCount;
286                        }
287                    }
288                }
289            }
290        }
291    }
292
293    /**
294     * @see com.google.gwt.user.client.ui.HasAnimation#setAnimationEnabled(boolean)
295     */
296    public void setAnimationEnabled(boolean enable) {
297
298        m_animate = enable;
299    }
300
301    /**
302     * Here the meaning is enabling dropping on the root level.<p>
303     *
304     * Use {@link CmsTreeItem#setDropEnabled(boolean)} for dropping on tree items.<p>
305     *
306     * @see org.opencms.gwt.client.ui.CmsList#setDropEnabled(boolean)
307     */
308    @Override
309    public void setDropEnabled(boolean enabled) {
310
311        super.setDropEnabled(enabled);
312    }
313
314    /**
315     * Sets a timer to set a tree item open.<p>
316     *
317     * @param item the item to open
318     */
319    public void setOpenTimer(CmsTreeItem item) {
320
321        if (item.isOpen()) {
322            return;
323        }
324        if (m_openTimer != null) {
325            if (m_openTimer.checkTimer(item)) {
326                return;
327            }
328            m_openTimer.cancel();
329        }
330        m_openTimer = new OpenTimer(item);
331        m_openTimer.schedule(100);
332    }
333
334    /**
335     * Sets the drop on root enabled.<p>
336     *
337     * @param rootDropEnabled <code>true</code> to enable dropping on root level
338     */
339    public void setRootDropEnabled(boolean rootDropEnabled) {
340
341        m_rootDropEnabled = rootDropEnabled;
342    }
343
344    /**
345     * @see org.opencms.gwt.client.ui.CmsList#registerItem(org.opencms.gwt.client.ui.I_CmsListItem)
346     */
347    @Override
348    @SuppressWarnings("unchecked")
349    protected void registerItem(I item) {
350
351        super.registerItem(item);
352        item.setTree((CmsTree<CmsTreeItem>)this);
353    }
354
355    /**
356     * Sets the timer reference to <code>null</code>.<p>
357     */
358    protected void removeOpenTimer() {
359
360        m_openTimer = null;
361    }
362
363    /**
364     * @see org.opencms.gwt.client.ui.CmsList#setPlaceholder(com.google.gwt.dom.client.Element)
365     */
366    @Override
367    protected void setPlaceholder(Element placeholder) {
368
369        super.setPlaceholder(placeholder);
370    }
371
372    /**
373     * Sets the placeholder path.<p>
374     *
375     * @param path the path
376     */
377    protected void setPlaceholderPath(String path) {
378
379        m_placeholderPath = path;
380    }
381}