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; 029 030import org.opencms.gwt.client.I_CmsDescendantResizeHandler; 031import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle; 032import org.opencms.gwt.client.util.CmsDomUtil; 033import org.opencms.gwt.client.util.CmsDomUtil.Style; 034import org.opencms.gwt.client.util.CmsFadeAnimation; 035 036import java.util.ArrayList; 037import java.util.Iterator; 038import java.util.List; 039 040import com.google.gwt.core.client.Scheduler; 041import com.google.gwt.core.client.Scheduler.ScheduledCommand; 042import com.google.gwt.dom.client.Element; 043import com.google.gwt.dom.client.Style.Display; 044import com.google.gwt.dom.client.Style.Overflow; 045import com.google.gwt.dom.client.Style.Unit; 046import com.google.gwt.event.dom.client.MouseOutEvent; 047import com.google.gwt.event.dom.client.MouseOutHandler; 048import com.google.gwt.event.dom.client.MouseOverEvent; 049import com.google.gwt.event.dom.client.MouseOverHandler; 050import com.google.gwt.event.logical.shared.ValueChangeEvent; 051import com.google.gwt.event.logical.shared.ValueChangeHandler; 052import com.google.gwt.event.shared.HandlerRegistration; 053import com.google.gwt.user.client.Command; 054import com.google.gwt.user.client.DOM; 055import com.google.gwt.user.client.Event; 056import com.google.gwt.user.client.Timer; 057import com.google.gwt.user.client.ui.AbstractNativeScrollbar; 058import com.google.gwt.user.client.ui.VerticalScrollbar; 059import com.google.gwt.user.client.ui.Widget; 060 061/** 062 * Scroll panel implementation with custom scroll bars. Works in all browsers but IE7.<p> 063 */ 064public class CmsScrollPanelImpl extends CmsScrollPanel { 065 066 /** 067 * Handler to show and hide the scroll bar on hover.<p> 068 */ 069 private class HoverHandler implements MouseOutHandler, MouseOverHandler { 070 071 /** The owner element. */ 072 Element m_owner; 073 074 /** The element to fade in and out. */ 075 private Element m_fadeElement; 076 077 /** The currently running hide animation. */ 078 private CmsFadeAnimation m_hideAnimation; 079 080 /** The timer to hide the scroll bar with a delay. */ 081 private Timer m_removeTimer; 082 083 /** 084 * Constructor.<p> 085 * 086 * @param owner the owner element 087 * @param fadeElement the element to fade in and out on hover 088 */ 089 HoverHandler(Element owner, Element fadeElement) { 090 091 m_owner = owner; 092 m_fadeElement = fadeElement; 093 } 094 095 /** 096 * @see com.google.gwt.event.dom.client.MouseOutHandler#onMouseOut(com.google.gwt.event.dom.client.MouseOutEvent) 097 */ 098 public void onMouseOut(MouseOutEvent event) { 099 100 m_removeTimer = new Timer() { 101 102 /** 103 * @see com.google.gwt.user.client.Timer#run() 104 */ 105 @Override 106 public void run() { 107 108 clearShowing(); 109 } 110 }; 111 m_removeTimer.schedule(1000); 112 } 113 114 /** 115 * @see com.google.gwt.event.dom.client.MouseOverHandler#onMouseOver(com.google.gwt.event.dom.client.MouseOverEvent) 116 */ 117 public void onMouseOver(MouseOverEvent event) { 118 119 if ((m_hideAnimation != null) 120 || !CmsDomUtil.hasClass(I_CmsLayoutBundle.INSTANCE.scrollBarCss().showBars(), m_owner)) { 121 if (m_hideAnimation != null) { 122 m_hideAnimation.cancel(); 123 m_hideAnimation = null; 124 } else { 125 CmsFadeAnimation.fadeIn(m_fadeElement, null, 100); 126 } 127 m_owner.addClassName(I_CmsLayoutBundle.INSTANCE.scrollBarCss().showBars()); 128 } 129 if (m_removeTimer != null) { 130 m_removeTimer.cancel(); 131 m_removeTimer = null; 132 } 133 } 134 135 /** 136 * Hides the scroll bar.<p> 137 */ 138 void clearShowing() { 139 140 m_hideAnimation = CmsFadeAnimation.fadeOut(m_fadeElement, new Command() { 141 142 public void execute() { 143 144 m_owner.removeClassName(I_CmsLayoutBundle.INSTANCE.scrollBarCss().showBars()); 145 146 } 147 }, 200); 148 149 m_removeTimer = null; 150 } 151 } 152 153 /** Hidden element to measure the appropriate size of the container element. */ 154 private Element m_hiddenSize; 155 156 /** The measured width of the native scroll bars. */ 157 private int m_nativeScrollbarWidth; 158 159 /** The vertical scroll bar. */ 160 private VerticalScrollbar m_scrollbar; 161 162 /** The scroll layer. */ 163 private Element m_scrollLayer; 164 165 /** The scroll bar change handler registration. */ 166 private HandlerRegistration m_verticalScrollbarHandlerRegistration; 167 168 /** The scroll bar width. */ 169 private int m_verticalScrollbarWidth; 170 171 /** 172 * Constructor.<p> 173 */ 174 public CmsScrollPanelImpl() { 175 176 super(DOM.createDiv(), DOM.createDiv(), DOM.createDiv()); 177 setStyleName(I_CmsLayoutBundle.INSTANCE.scrollBarCss().scrollPanel()); 178 Element scrollable = getScrollableElement(); 179 scrollable.getStyle().clearPosition(); 180 scrollable.getStyle().setOverflowX(Overflow.HIDDEN); 181 scrollable.setClassName(I_CmsLayoutBundle.INSTANCE.scrollBarCss().scrollable()); 182 getElement().appendChild(scrollable); 183 Element container = getContainerElement(); 184 container.setClassName(I_CmsLayoutBundle.INSTANCE.scrollBarCss().scrollContainer()); 185 scrollable.appendChild(container); 186 m_scrollLayer = DOM.createDiv(); 187 getElement().appendChild(m_scrollLayer); 188 m_scrollLayer.setClassName(I_CmsLayoutBundle.INSTANCE.scrollBarCss().scrollbarLayer()); 189 CmsScrollBar scrollbar = new CmsScrollBar(scrollable, container); 190 setVerticalScrollbar(scrollbar, 8); 191 m_hiddenSize = DOM.createDiv(); 192 m_hiddenSize.setClassName(I_CmsLayoutBundle.INSTANCE.scrollBarCss().hiddenSize()); 193 194 /* 195 * Listen for scroll events from the root element and the scrollable element 196 * so we can align the scrollbars with the content. Scroll events usually 197 * come from the scrollable element, but they can also come from the root 198 * element if the user clicks and drags the content, which reveals the 199 * hidden scrollbars. 200 */ 201 Event.sinkEvents(getElement(), Event.ONSCROLL); 202 Event.sinkEvents(scrollable, Event.ONSCROLL); 203 initHoverHandler(); 204 } 205 206 /** 207 * @see com.google.gwt.user.client.ui.SimplePanel#iterator() 208 */ 209 @Override 210 public Iterator<Widget> iterator() { 211 212 // Return a simple iterator that enumerates the 0 or 1 elements in this 213 // panel. 214 List<Widget> widgets = new ArrayList<Widget>(); 215 if (getWidget() != null) { 216 widgets.add(getWidget()); 217 } 218 if (getVerticalScrollBar() != null) { 219 widgets.add(getVerticalScrollBar().asWidget()); 220 } 221 final Iterator<Widget> internalIterator = widgets.iterator(); 222 return new Iterator<Widget>() { 223 224 public boolean hasNext() { 225 226 return internalIterator.hasNext(); 227 } 228 229 public Widget next() { 230 231 return internalIterator.next(); 232 } 233 234 @Override 235 public void remove() { 236 237 throw new UnsupportedOperationException(); 238 } 239 }; 240 } 241 242 /** 243 * @see com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user.client.Event) 244 */ 245 @Override 246 public void onBrowserEvent(Event event) { 247 248 // Align the scrollbars with the content. 249 if (Event.ONSCROLL == event.getTypeInt()) { 250 maybeUpdateScrollbarPositions(); 251 } 252 super.onBrowserEvent(event); 253 } 254 255 /** 256 * @see org.opencms.gwt.client.ui.CmsScrollPanel#onResizeDescendant() 257 */ 258 @Override 259 public void onResizeDescendant() { 260 261 int maxHeight = CmsDomUtil.getCurrentStyleInt(getElement(), Style.maxHeight); 262 if (maxHeight > 0) { 263 getScrollableElement().getStyle().setPropertyPx("maxHeight", maxHeight); 264 } 265 // appending div to measure panel width, doing it every time anew to avoid rendering bugs in Chrome 266 getElement().appendChild(m_hiddenSize); 267 int width = m_hiddenSize.getClientWidth(); 268 m_hiddenSize.removeFromParent(); 269 if (width > 0) { 270 getContainerElement().getStyle().setWidth(width, Unit.PX); 271 maybeUpdateScrollbars(); 272 } 273 } 274 275 /** 276 * @see org.opencms.gwt.client.ui.CmsScrollPanel#setResizable(boolean) 277 */ 278 @Override 279 public void setResizable(boolean resize) { 280 281 super.setResizable(resize); 282 if (resize) { 283 m_scrollbar.asWidget().getElement().getStyle().setMarginBottom(7, Unit.PX); 284 } else { 285 m_scrollbar.asWidget().getElement().getStyle().setMarginBottom(0, Unit.PX); 286 } 287 } 288 289 /** 290 * Returns the vertical scroll bar.<p> 291 * 292 * @return the vertical scroll bar 293 */ 294 protected VerticalScrollbar getVerticalScrollBar() { 295 296 return m_scrollbar; 297 } 298 299 /** 300 * @see com.google.gwt.user.client.ui.ScrollPanel#onAttach() 301 */ 302 @Override 303 protected void onAttach() { 304 305 super.onAttach(); 306 hideNativeScrollbars(); 307 onResizeDescendant(); 308 } 309 310 /** 311 * @see com.google.gwt.user.client.ui.Widget#onLoad() 312 */ 313 @Override 314 protected void onLoad() { 315 316 super.onLoad(); 317 hideNativeScrollbars(); 318 Scheduler.get().scheduleDeferred(new ScheduledCommand() { 319 320 public void execute() { 321 322 onResizeDescendant(); 323 } 324 }); 325 } 326 327 /** 328 * Hide the native scrollbars. We call this after attaching to ensure that we 329 * inherit the direction (rtl or ltr). 330 */ 331 private void hideNativeScrollbars() { 332 333 m_nativeScrollbarWidth = AbstractNativeScrollbar.getNativeScrollbarWidth(); 334 getScrollableElement().getStyle().setMarginRight(-(m_nativeScrollbarWidth + 10), Unit.PX); 335 } 336 337 /** 338 * Initializes the hover handler to hide and show the scroll bar on hover.<p> 339 */ 340 private void initHoverHandler() { 341 342 HoverHandler handler = new HoverHandler(getElement(), m_scrollbar.asWidget().getElement()); 343 addDomHandler(handler, MouseOverEvent.getType()); 344 addDomHandler(handler, MouseOutEvent.getType()); 345 } 346 347 /** 348 * Synchronize the scroll positions of the scrollbars with the actual scroll 349 * position of the content. 350 */ 351 private void maybeUpdateScrollbarPositions() { 352 353 if (!isAttached()) { 354 return; 355 } 356 357 if (m_scrollbar != null) { 358 int vPos = getVerticalScrollPosition(); 359 if (m_scrollbar.getVerticalScrollPosition() != vPos) { 360 m_scrollbar.setVerticalScrollPosition(vPos); 361 } 362 } 363 } 364 365 /** 366 * Update the position of the scrollbars.<p> 367 * If only the vertical scrollbar is present, it takes up the entire height of 368 * the right side. If only the horizontal scrollbar is present, it takes up 369 * the entire width of the bottom. If both scrollbars are present, the 370 * vertical scrollbar extends from the top to just above the horizontal 371 * scrollbar, and the horizontal scrollbar extends from the left to just right 372 * of the vertical scrollbar, leaving a small square in the bottom right 373 * corner.<p> 374 */ 375 private void maybeUpdateScrollbars() { 376 377 if (!isAttached()) { 378 return; 379 } 380 381 /* 382 * Measure the height and width of the content directly. Note that measuring 383 * the height and width of the container element (which should be the same) 384 * doesn't work correctly in IE. 385 */ 386 Widget w = getWidget(); 387 int contentHeight = (w == null) ? 0 : w.getOffsetHeight(); 388 389 // Determine which scrollbars to show. 390 int realScrollbarHeight = 0; 391 int realScrollbarWidth = 0; 392 if ((m_scrollbar != null) && (getElement().getClientHeight() < contentHeight)) { 393 // Vertical scrollbar is defined and required. 394 realScrollbarWidth = m_verticalScrollbarWidth; 395 } 396 397 if (realScrollbarWidth > 0) { 398 m_scrollLayer.getStyle().clearDisplay(); 399 400 m_scrollbar.setScrollHeight(Math.max(0, contentHeight - realScrollbarHeight)); 401 } else if (m_scrollLayer != null) { 402 m_scrollLayer.getStyle().setDisplay(Display.NONE); 403 } 404 if (m_scrollbar instanceof I_CmsDescendantResizeHandler) { 405 ((I_CmsDescendantResizeHandler)m_scrollbar).onResizeDescendant(); 406 } 407 maybeUpdateScrollbarPositions(); 408 } 409 410 /** 411 * Set the scrollbar used for vertical scrolling. 412 * 413 * @param scrollbar the scrollbar, or null to clear it 414 * @param width the width of the scrollbar in pixels 415 */ 416 private void setVerticalScrollbar(final CmsScrollBar scrollbar, int width) { 417 418 // Validate. 419 if ((scrollbar == m_scrollbar) || (scrollbar == null)) { 420 return; 421 } 422 // Detach new child. 423 424 scrollbar.asWidget().removeFromParent(); 425 // Remove old child. 426 if (m_scrollbar != null) { 427 if (m_verticalScrollbarHandlerRegistration != null) { 428 m_verticalScrollbarHandlerRegistration.removeHandler(); 429 m_verticalScrollbarHandlerRegistration = null; 430 } 431 remove(m_scrollbar); 432 } 433 m_scrollLayer.appendChild(scrollbar.asWidget().getElement()); 434 adopt(scrollbar.asWidget()); 435 436 // Logical attach. 437 m_scrollbar = scrollbar; 438 m_verticalScrollbarWidth = width; 439 440 // Initialize the new scrollbar. 441 m_verticalScrollbarHandlerRegistration = scrollbar.addValueChangeHandler(new ValueChangeHandler<Integer>() { 442 443 public void onValueChange(ValueChangeEvent<Integer> event) { 444 445 int vPos = scrollbar.getVerticalScrollPosition(); 446 int v = getVerticalScrollPosition(); 447 if (v != vPos) { 448 setVerticalScrollPosition(vPos); 449 } 450 451 } 452 }); 453 maybeUpdateScrollbars(); 454 } 455}