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}