/*******************************************************************************
* Copyright (c) 2013 EclipseSource and others. All rights reserved. This
* program and the accompanying materials are made available under the terms of
* the Eclipse Public License v1.0 which accompanies this distribution, and is
* available at http://www.eclipse.org/legal/epl-v10.html Contributors:
* EclipseSource - initial API and implementation
******************************************************************************/
package com.eclipsesource.tabris.widgets;
import static com.eclipsesource.tabris.internal.Clauses.when;
import static com.eclipsesource.tabris.internal.Clauses.whenNull;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import com.eclipsesource.tabris.internal.ScrollingCompositeUtil;
/**
* <p>
* A {@link ScrollingComposite} can be used the same way as a simple
* {@link Composite}. The difference between a {@link Composite} and a
* {@link ScrollingComposite} is that a {@link ScrollingComposite} shows
* scrollbars automatically when it's content becomes to big.
* </p>
* <p>
* You may noticed that there is a {@link ScrolledComposite}. A
* {@link ScrolledComposite} is not an easy widget. In most cases it's enough to
* have just a scrollable container for some content that grows dynamically.
* This task can not be accomplished easily with the {@link ScrolledComposite}.
* Thus the {@link ScrollingComposite} was created to make just the described
* task easy and nothing more.
* </p>
* <p>
* Please note: A {@link ScrollingComposite} uses a {@link ScrolledComposite} as
* it's parent. So, if you need to get the {@link ScrolledComposite} use the
* {@link ScrollingComposite#getParent()} method.
* </p>
* <p>
* <b>Styles:</b> H_SCROLL, V_SCROLL
* </p>
*
* @see Composite
* @see ScrolledComposite
*
* @since 1.0
*/
public class ScrollingComposite extends Composite {
private ScrolledComposite scrolledComposite;
/**
* <p>
* Constructs a new {@link ScrollingComposite}. See
* {@link Composite#Composite(Composite, int)} for a more detailed
* description.
* </p>
*
* @see Composite#Composite(Composite, int)
*/
public ScrollingComposite( Composite parent, int style ) {
super( createScrolledComposite( parent, checkStyle( style ) ), checkStyle( style ) );
scrolledComposite = ( ScrolledComposite )this.getParent();
initializeScrolledComposite();
}
private static int checkStyle( int style ) {
int mask = SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.LEFT_TO_RIGHT;
return style & mask;
}
private static ScrolledComposite createScrolledComposite( Composite parent, int style ) {
ScrolledComposite scrolledComposite = new ScrolledComposite( parent, style );
scrolledComposite.setExpandVertical( true );
scrolledComposite.setExpandHorizontal( true );
return scrolledComposite;
}
private void initializeScrolledComposite() {
scrolledComposite.setContent( this );
handleScrollbars();
scrolledComposite.addControlListener( new ControlAdapter() {
@Override
public void controlResized( ControlEvent event ) {
handleScrollbars();
scrolledComposite.layout( true, true );
}
} );
}
/**
* <p>
* Returns the inner {@link ScrolledComposite}.
* </p>
*
* @since 1.4
*/
public ScrolledComposite getScrolledComposite() {
return scrolledComposite;
}
@Override
public void layout( boolean changed, boolean all ) {
super.layout( changed, all );
handleScrollbars();
}
private void handleScrollbars() {
computeSize( SWT.DEFAULT, SWT.DEFAULT );
}
@Override
public Point computeSize( int wHint, int hHint, boolean changed ) {
Point resultSize = super.computeSize( SWT.DEFAULT, SWT.DEFAULT, changed );
if( hasStyle( SWT.V_SCROLL ) && hasStyle( SWT.H_SCROLL ) ) {
setMinSize( resultSize.x, resultSize.y );
} else if( hasStyle( SWT.H_SCROLL ) ) {
resultSize = calculateHorizontalSize( changed );
setMinSize( resultSize.x, SWT.DEFAULT );
} else if( hasStyle( SWT.V_SCROLL ) ) {
resultSize = calculateVerticalSize( changed );
setMinSize( scrolledComposite.getClientArea().width, resultSize.y );
}
return resultSize;
}
private boolean hasStyle( int flag ) {
return ( getStyle() & flag ) == flag;
}
private Point calculateVerticalSize( boolean changed ) {
Point resultSize = super.computeSize( SWT.DEFAULT, SWT.DEFAULT, changed );
int clientAreaWidth = scrolledComposite.getClientArea().width;
Point widthSize = super.computeSize( clientAreaWidth, SWT.DEFAULT, changed );
if( widthSize.y > resultSize.y ) {
resultSize = widthSize;
}
return resultSize;
}
private Point calculateHorizontalSize( boolean changed ) {
Point resultSize = super.computeSize( SWT.DEFAULT, SWT.DEFAULT, changed );
int clientAreaHeight = scrolledComposite.getClientArea().height;
Point heightSize = super.computeSize( SWT.DEFAULT, clientAreaHeight, changed );
if( heightSize.x > resultSize.x ) {
resultSize = heightSize;
}
return resultSize;
}
private void setMinSize( int width, int height ) {
scrolledComposite.setMinHeight( height );
scrolledComposite.setMinWidth( width );
}
@Override
public void setLayoutData( Object layoutData ) {
checkWidget();
scrolledComposite.setLayoutData( layoutData );
}
@Override
public Object getLayoutData() {
checkWidget();
return scrolledComposite.getLayoutData();
}
/**
* <p>
* Scrolls to the defined control until it's visible.
* </p>
*
* @param control the control to scroll to. Must not be <code>null</code>.
* @exception IllegalArgumentException when the defined control is
* <code>null</code> or not a children of this Composite.
*/
public void reveal( Control control ) {
checkRevealState( control );
scrolledComposite.showControl( control );
}
/**
* <p>
* Checks if a control is visible within the visible area. The control needs
* to be completely visible for a <code>true</code> result.
* </p>
*
* @param control the control to check the visibility. Must not be <code>null</code>.
*
* @throws IllegalArgumentException when the defined control is <code>null</code> or not a
* children of this Composite.
*/
public boolean isRevealed( Control control ) {
checkRevealState( control );
Point origin = scrolledComposite.getOrigin();
Rectangle clientArea = scrolledComposite.getClientArea();
Rectangle controlBounds = getDisplay().map( control.getParent(), this, control.getBounds() );
return ScrollingCompositeUtil.isRevealed( origin, clientArea, controlBounds );
}
private void checkRevealState( Control control ) {
whenNull( control ).throwIllegalArgument( "Child to reveal must not be null" );
when( !containsControl( control ) ).throwIllegalArgument( "Control is not a child" );
checkWidget();
}
private boolean containsControl( Control control ) {
boolean contains = false;
if( control != null && !control.isDisposed() ) {
Composite parent = control.getParent();
while( parent != null && !( parent instanceof Shell ) && !contains ) {
if( this == parent ) {
contains = true;
}
parent = parent.getParent();
}
}
return contains;
}
@Override
public void dispose() {
if( scrolledComposite != null ) {
scrolledComposite = null;
getParent().dispose();
}
super.dispose();
}
}