| /******************************************************************************* |
| * Copyright (c) 2013, 2014 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 org.eclipse.rap.addons.dropdown; |
| |
| import org.eclipse.rap.json.JsonArray; |
| import org.eclipse.rap.json.JsonObject; |
| import org.eclipse.rap.json.JsonValue; |
| import org.eclipse.rap.rwt.RWT; |
| import org.eclipse.rap.rwt.internal.lifecycle.WidgetDataUtil; |
| import org.eclipse.rap.rwt.internal.protocol.JsonUtil; |
| import org.eclipse.rap.rwt.internal.scripting.ClientListenerUtil; |
| import org.eclipse.rap.rwt.lifecycle.WidgetAdapter; |
| import org.eclipse.rap.rwt.lifecycle.WidgetUtil; |
| import org.eclipse.rap.rwt.remote.AbstractOperationHandler; |
| import org.eclipse.rap.rwt.remote.RemoteObject; |
| import org.eclipse.rap.rwt.scripting.ClientListener; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.SWTException; |
| import org.eclipse.swt.internal.events.EventLCAUtil; |
| import org.eclipse.swt.internal.widgets.WidgetAdapterImpl; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Listener; |
| import org.eclipse.swt.widgets.Text; |
| import org.eclipse.swt.widgets.Widget; |
| |
| |
| /** |
| * Instances of this class represent a list of items that are attached to a control (parent). |
| * If made visible (using <code>show</code> or <code>setVisibility</code>), the list |
| * appears attached to the control. The user can select an item from the list |
| * using the arrows keys and Enter, or the mouse. |
| * |
| * The list disappears automatically if the control looses focus, if an item is clicked, |
| * or if the escape key is pressed. |
| * |
| * This class supports the {@link RWT#MARKUP_ENABLED} property the same way Table and Tree do. |
| * |
| * <p> |
| * <dl> |
| * <dt><b>Events:</b></dt> |
| * <dd>DefaultSelection, Selection</dd> |
| * </dl> |
| * <p> |
| * <p> |
| * NOTE: This widget is optimized for use with ClientScripting. The client representation |
| * implements nearly all API defined in this class, which can be safely used unless documented |
| * otherwise in the JsDoc. |
| * </p> |
| * <p> |
| * IMPORTANT: This class is <em>not</em> intended to be subclassed. |
| * </p> |
| * |
| */ |
| @SuppressWarnings({ |
| "restriction", "deprecation" |
| }) |
| public class DropDown extends Widget { |
| |
| private static final String COLUMNS = "columns"; |
| private static final String REMOTE_TYPE = "rwt.dropdown.DropDown"; |
| private static final String SELECTION = "Selection"; |
| private static final String DEFAULT_SELECTION = "DefaultSelection"; |
| |
| private RemoteObject remoteObject; |
| private Object widgetAdapter; |
| private final Control parent; |
| private final Listener disposeListener; |
| private boolean visibility = false; |
| private int selectionIndex = -1; |
| private int visibleItemCount = 5; |
| |
| /** |
| * Constructs a new instance of this class given its parent. |
| * |
| * @param parent a control, usually <code>Text</code>, |
| * which will be the parent of the new instance (cannot be null) |
| * |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> |
| * </ul> |
| * @exception SWTException <ul> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> |
| * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li> |
| * </ul> |
| * |
| * @see Text |
| * @see Widget#checkSubclass |
| */ |
| public DropDown( Control parent ) { |
| super( parent, 0 ); |
| this.parent = parent; |
| getRemoteObject().set( "parent", WidgetUtil.getId( parent ) ); |
| getRemoteObject().setHandler( new InternalOperationHandler() ); |
| disposeListener = new Listener() { |
| public void handleEvent( Event event ) { |
| DropDown.this.dispose(); |
| } |
| }; |
| parent.addListener( SWT.Dispose, disposeListener ); |
| } |
| |
| /** |
| * Sets the receiver's items to be the given array of items. |
| * |
| * @param items the array of items |
| * |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * </ul> |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the items array is null</li> |
| * <li>ERROR_INVALID_ARGUMENT - if an item in the items array is null</li> |
| * </ul> |
| * |
| * <p> |
| * NOTE: Due to optimized ClientScripting support there is currently no matching |
| * <code>getItems</code> method. |
| * </p> |
| */ |
| public void setItems( String[] items ) { |
| checkWidget(); |
| if( items == null ) { |
| SWT.error( SWT.ERROR_NULL_ARGUMENT ); |
| } |
| for( int i = 0; i < items.length; i++ ) { |
| if( items[ i ] == null ) { |
| SWT.error( SWT.ERROR_INVALID_ARGUMENT ); |
| } |
| } |
| remoteObject.set( "items", JsonUtil.createJsonArray( items ) ); |
| setSelectionIndexImpl( -1 ); |
| } |
| /** |
| * Returns the zero-relative index of the item which is currently |
| * selected in the receiver, or -1 if no item is selected. |
| * |
| * @return the index of the selected item or -1 |
| * |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * </ul> |
| * |
| * <p> |
| * NOTE: Due to optimized ClientScripting support there is currently no matching |
| * <code>setSelectionIndex</code> method. |
| * </p> |
| */ |
| public int getSelectionIndex() { |
| checkWidget(); |
| return selectionIndex; |
| } |
| |
| /** |
| * Sets the maximum number of items that are visible in the receiver's list. |
| * |
| * @param itemCount the new number of items to be visible (default is 5) |
| * |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * </ul> |
| */ |
| public void setVisibleItemCount( int itemCount ) { |
| checkWidget(); |
| if( visibleItemCount != itemCount ) { |
| visibleItemCount = itemCount; |
| remoteObject.set( "visibleItemCount", itemCount ); |
| } |
| } |
| |
| /** |
| * Gets the number of items that are visible in the receiver's list. |
| * |
| * @return the number of items that are visible |
| * |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * </ul> |
| */ |
| public int getVisibleItemCount() { |
| checkWidget(); |
| return visibleItemCount; |
| } |
| |
| public void show() { |
| setVisible( true ); |
| } |
| |
| public void hide() { |
| setVisible( false ); |
| } |
| |
| /** |
| * Marks the receiver as visible if the argument is <code>true</code>, |
| * and marks it invisible otherwise. |
| * |
| * @param visible the new visibility state |
| * |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * </ul> |
| */ |
| public void setVisible( boolean visible ) { |
| checkWidget(); |
| if( visibility != visible ) { |
| setVisibleImpl( visible ); |
| remoteObject.set( "visible", visible ); |
| } |
| } |
| |
| /** |
| * Returns <code>true</code> if the receiver is visible, and |
| * <code>false</code> otherwise. |
| * <p> |
| * The initial value is false. |
| * </p> |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * </ul> |
| * |
| * @return the receiver's visibility state |
| * |
| */ |
| public boolean getVisible() { |
| checkWidget(); |
| return visibility; |
| } |
| |
| /** |
| * Returns the receiver's parent, which must be a <code>Control</code>. |
| * |
| * @return the receiver's parent |
| * |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * </ul> |
| */ |
| public Control getParent() { |
| checkWidget(); |
| return parent; |
| } |
| |
| @Override |
| public void dispose() { |
| if( !isDisposed() ) { |
| super.dispose(); |
| parent.removeListener( SWT.Dispose, disposeListener ); |
| remoteObject.destroy(); |
| } |
| } |
| |
| @Override |
| public void setData( String key, Object value ) { |
| super.setData( key, value ); |
| renderData( key, value ); |
| if( RWT.MARKUP_ENABLED.equals( key ) && value instanceof Boolean ) { |
| remoteObject.set( "markupEnabled", ( ( Boolean )value ).booleanValue() ); |
| } else if( COLUMNS.equals( key ) ) { |
| if( value instanceof int[] ) { |
| int arr[] = ( int[] )value; |
| remoteObject.set( COLUMNS, createJsonArray( arr ) ); |
| } else { |
| remoteObject.set( COLUMNS, JsonValue.NULL ); |
| } |
| } |
| } |
| |
| @Override |
| public void addListener( int eventType, Listener listener ) { |
| boolean wasListening = EventLCAUtil.isListening( this, eventType ); |
| super.addListener( eventType, listener ); |
| boolean isListening = EventLCAUtil.isListening( this, eventType ); |
| String remoteType = eventTypeToString( eventType ); |
| if( listener instanceof ClientListener ) { |
| JsonObject parameters = new JsonObject() |
| .add( "eventType", ClientListenerUtil.getEventType( eventType ) ) |
| .add( "listenerId", ClientListenerUtil.getRemoteId( ( ClientListener )listener ) ); |
| remoteObject.call( "addListener", parameters ); |
| } else if( remoteType != null && !wasListening && isListening ) { |
| remoteObject.listen( remoteType, true ); |
| } |
| } |
| |
| @Override |
| public void removeListener( int eventType, Listener listener ) { |
| boolean wasListening = EventLCAUtil.isListening( this, eventType ); |
| super.removeListener( eventType, listener ); |
| boolean isListening = EventLCAUtil.isListening( this, eventType ); |
| String remoteType = eventTypeToString( eventType ); |
| if( listener instanceof ClientListener ) { |
| JsonObject parameters = new JsonObject() |
| .add( "eventType", ClientListenerUtil.getEventType( eventType ) ) |
| .add( "listenerId", ClientListenerUtil.getRemoteId( ( ClientListener )listener ) ); |
| remoteObject.call( "removeListener", parameters ); |
| } else if( remoteType != null && wasListening && !isListening ) { |
| remoteObject.listen( remoteType, false ); |
| } |
| } |
| |
| ////////////// |
| // overwritten |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public <T> T getAdapter( Class<T> adapter ) { |
| T result; |
| if( adapter == WidgetAdapter.class ) { |
| // TODO [tb] : This way of getting the right id into the WidgetAdapter is obviously |
| // not ideal. Revise once Bug 397602 (Render operations in the order of their |
| // occurrence) is fixed. |
| if( widgetAdapter == null ) { |
| widgetAdapter = new WidgetAdapterImpl( getProtocolId() ); |
| } |
| result = ( T )widgetAdapter; |
| } else { |
| result = super.getAdapter( adapter ); |
| } |
| return result; |
| } |
| |
| ////////// |
| // private |
| |
| |
| private class InternalOperationHandler extends AbstractOperationHandler { |
| |
| @Override |
| public void handleSet( JsonObject properties ) { |
| if( properties.get( "visible" ) != null ) { |
| setVisibleImpl( properties.get( "visible" ).asBoolean() ); |
| } |
| if( properties.get( "selectionIndex" ) != null ) { |
| setSelectionIndexImpl( properties.get( "selectionIndex" ).asInt() ); |
| } |
| } |
| |
| @Override |
| public void handleNotify( String type, JsonObject properties ) { |
| if( SELECTION.equals( type ) || DEFAULT_SELECTION.equals( type )) { |
| Event event = new Event(); |
| event.index = properties.get( "index" ).asInt(); |
| event.text = properties.get( "text" ).asString(); |
| notifyListeners( stringToEventType( type ), event ); |
| } |
| } |
| |
| } |
| |
| private void setVisibleImpl( boolean value ) { |
| visibility = value; |
| } |
| |
| private void setSelectionIndexImpl( int value ) { |
| selectionIndex = value; |
| } |
| |
| private String getProtocolId() { |
| return getRemoteObject().getId(); |
| } |
| |
| private RemoteObject getRemoteObject() { |
| if( remoteObject == null ) { |
| remoteObject = RWT.getUISession().getConnection().createRemoteObject( REMOTE_TYPE ); |
| } |
| return remoteObject; |
| } |
| |
| private void renderData( String key, Object value ) { |
| // TODO [tb] : could be optimized using a PhaseListener |
| // This implementation assumes the client merges the new values with the existing |
| // ones, which is the case in the WebClient |
| if( WidgetDataUtil.getDataKeys().contains( key ) ) { |
| JsonObject data = new JsonObject().add( key, JsonUtil.createJsonValue( value ) ); |
| remoteObject.set( "data", data ); |
| } |
| } |
| |
| private static String eventTypeToString( int type ) { |
| String result; |
| switch( type ) { |
| case SWT.Selection: |
| result = SELECTION; |
| break; |
| case SWT.DefaultSelection: |
| result = DEFAULT_SELECTION; |
| break; |
| default: |
| result = null; |
| break; |
| } |
| return result; |
| } |
| |
| private static int stringToEventType( String str ) { |
| int result = -1; |
| if( SELECTION.equals( str ) ) { |
| result = SWT.Selection; |
| } else if( DEFAULT_SELECTION.equals( str ) ) { |
| result = SWT.DefaultSelection; |
| } |
| return result; |
| } |
| |
| private static JsonArray createJsonArray( int[] arr ) { |
| JsonArray array = new JsonArray(); |
| for( int i = 0; i < arr.length; i++ ) { |
| array.add( arr[ i ] ); |
| } |
| return array; |
| } |
| |
| } |