/*!
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.gwt.widgets.client.listbox;
import java.util.ArrayList;
import java.util.List;
import org.pentaho.gwt.widgets.client.utils.ElementUtils;
import org.pentaho.gwt.widgets.client.utils.Rectangle;
import org.pentaho.gwt.widgets.client.utils.string.StringUtils;
import com.allen_sauer.gwt.dnd.client.DragController;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FocusListener;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.MouseListener;
import com.google.gwt.user.client.ui.PopupListener;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
/**
*
* ComplexListBox is a List-style widget can contain custom list-items made (images + text, text + checkboxes) This list
* is displayed as a drop-down style component by default. If the visibleRowCount property is set higher than 1
* (default), the list is rendered as a multi-line list box.
*
* <P>
* Usage:
*
* <p>
*
* <pre>
* ComplexListBox list = new ComplexListBox();
*
* list.addItem( "Alberta" );
* list.addItem( "Atlanta" );
* list.addItem( "San Francisco" );
* list.addItem( new DefaultListItem( "Testing", new Image( "16x16sample.png" ) ) );
* list.addItem( new DefaultListItem( "Testing 2", new CheckBox() ) );
*
* list.setVisibleRowCount( 6 ); // turns representation from drop-down to list
*
* list.addChangeListener( new ChangeListener() {
* public void onChange( Widget widget ) {
* System.out.println( "" + list.getSelectedIdex() );
* }
* } );
* </pre>
*
* User: NBaker Date: Mar 9, 2009 Time: 11:01:57 AM
*
*/
@SuppressWarnings( "deprecation" )
public class CustomListBox extends HorizontalPanel implements ChangeListener, PopupListener, MouseListener,
FocusListener, KeyboardListener, ListItemListener {
protected List<ListItem> items = new ArrayList<ListItem>();
protected int selectedIndex = -1;
protected DropDownArrow arrow = new DropDownArrow();
protected int visible = 1;
private int maxDropVisible = 15;
protected boolean editable = false;
private VerticalPanel listPanel = new VerticalPanel();
protected ScrollPanel listScrollPanel = new ScrollPanel();
// Members for drop-down style
protected FlexTable dropGrid = new FlexTable();
protected boolean popupShowing = false;
private DropPopupPanel popup;
private PopupList popupVbox = new PopupList();
protected FocusPanel fPanel = new FocusPanel();
private ScrollPanel popupScrollPanel = new ScrollPanel();
protected List<ChangeListener> listeners = new ArrayList<ChangeListener>();
private final int spacing = 1;
protected int maxHeight, maxWidth, averageHeight; // height and width of largest ListItem
private String primaryStyleName;
private String height, width;
protected String popupHeight;
private String popupWidth;
protected boolean suppressLayout;
private boolean enabled = true;
private String val;
private Command command;
protected DragController dragController;
protected boolean multiSelect;
public CustomListBox() {
dropGrid.getColumnFormatter().setWidth( 0, "100%" ); //$NON-NLS-1$
dropGrid.setWidget( 0, 1, arrow );
dropGrid.getElement().getStyle().setProperty( "tableLayout", "fixed" );
arrow.getElement().getParentElement().getStyle().setProperty( "width", "20px" );
dropGrid.setCellPadding( 0 );
dropGrid.setCellSpacing( 1 );
updateUI();
// Add List Panel to it's scrollPanel
listScrollPanel.add( listPanel );
listScrollPanel.setHeight( "100%" ); //$NON-NLS-1$
listScrollPanel.setWidth( "100%" ); //$NON-NLS-1$
listScrollPanel.getElement().getStyle().setProperty( "overflowX", "hidden" ); //$NON-NLS-1$ //$NON-NLS-2$
// listScrollPanel.getElement().getStyle().setProperty("padding",spacing+"px");
listPanel.setSpacing( spacing );
listPanel.setWidth( "100%" ); //$NON-NLS-1$
// default to drop-down
fPanel.add( dropGrid );
fPanel.setHeight( "100%" ); //$NON-NLS-1$
super.add( fPanel );
popupScrollPanel.add( popupVbox );
popupScrollPanel.getElement().getStyle().setProperty( "overflowX", "hidden" ); //$NON-NLS-1$ //$NON-NLS-2$
popupVbox.setWidth( "100%" ); //$NON-NLS-1$
popupVbox.setSpacing( spacing );
fPanel.addMouseListener( this );
fPanel.addFocusListener( this );
fPanel.addKeyboardListener( this );
this.setStylePrimaryName( "custom-list" ); //$NON-NLS-1$
setTdStyles( this.getElement() );
setTdStyles( listPanel.getElement() );
editableTextBox = new TextBox() {
@Override
public void onBrowserEvent( Event event ) {
// int code = event.getKeyCode();
switch ( DOM.eventGetType( event ) ) {
case Event.ONKEYUP:
onChange( editableTextBox );
val = editableTextBox.getText();
// event.cancelBubble(true);
break;
case Event.ONMOUSEUP:
super.onBrowserEvent( event );
event.cancelBubble( true );
default:
return;
}
}
};
editableTextBox.setStylePrimaryName( "custom-list-textbox" );
}
public void setTableLayout( String tableLayout ) {
if ( StringUtils.isEmpty( tableLayout ) ) {
dropGrid.getElement().getStyle().clearProperty( "tableLayout" );
} else {
dropGrid.getElement().getStyle().setProperty( "tableLayout", tableLayout );
}
}
private native void setTdStyles( Element ele )/*-{
var tds = ele.getElementsByTagName("td");
for( var i=0; i< tds.length; i++){
var td = tds[i];
if(!td.style){
td.className = "customListBoxTdFix";
} else {
td.style.padding = "0px";
td.style.border = "none";
}
}
}-*/;
/**
* Removes the passed in ListItem
*
* @param listItem
* item to remove
*/
public void remove( ListItem listItem ) {
this.items.remove( listItem );
setSelectedIndex( 0 );
if ( suppressLayout == false ) {
updateUI();
}
}
/**
* Removes all items from the list.
*/
public void removeAll() {
this.items.clear();
this.selectedIndex = -1;
this.selectedItems.clear();
if ( this.suppressLayout == false ) {
for ( ChangeListener l : listeners ) {
l.onChange( this );
}
}
if ( suppressLayout == false ) {
updateUI();
}
}
/**
* Removes all items from the list.
*/
public void clear() {
removeAll();
}
/**
* Convenience method to support the more conventional method of child attachment
*
* @param label
*/
public void add( String label ) {
this.addItem( label );
}
/**
* Adds the given ListItem to the list control.
*
* @param item
* ListItem
*/
public void addItem( ListItem item ) {
items.add( item );
item.setListItemListener( this );
// If first one added, set selectedIndex to 0
if ( items.size() == 1 && this.visible == 1 ) {
setSelectedIndex( 0 );
}
if ( suppressLayout == false ) {
updateUI();
}
if ( dragController != null ) {
dragController.makeDraggable( item.getWidget() );
}
}
@Override
public void add( Widget w ) {
addItem( (ListItem) w );
}
/**
* Call this method with true will suppress the re-laying out of the widget after every add/remove. This is useful
* when adding a large batch of items to the listbox.
*/
public void setSuppressLayout( boolean supress ) {
this.suppressLayout = supress;
if ( !suppressLayout ) {
if ( selectedIndex < 0 && this.items.size() > 0 ) {
this.setSelectedIndex( 0 ); // notifies listeners
} else {
// just notify listeners something has changed.
for ( ChangeListener l : listeners ) {
l.onChange( this );
}
}
updateUI();
}
}
/**
* Convenience method creates a {@link: DefaultListItem} with the given text and adds it to the list control
*
* @param label
*/
public void addItem( String label ) {
DefaultListItem item = new DefaultListItem( label );
items.add( item );
item.setListItemListener( this );
// If first one added, set selectedIndex to 0
if ( items.size() == 1 ) {
setSelectedIndex( 0 );
}
if ( suppressLayout == false ) {
updateUI();
}
if ( dragController != null ) {
dragController.makeDraggable( item.getWidget() );
}
}
/**
* Returns a list of current ListItems.
*
* @return List of ListItems
*/
public List<ListItem> getItems() {
return items;
}
/**
* Sets the number of items to be displayed at once in the lsit control. If set to 1 (default) the list is rendered as
* a drop-down
*
* @param visibleCount
* number of rows to be visible.
*/
public void setVisibleRowCount( int visibleCount ) {
int prevCount = visible;
this.visible = visibleCount;
if ( visible > 1 && prevCount == 1 ) {
// switched from drop-down to list
fPanel.remove( dropGrid );
fPanel.add( listScrollPanel );
} else if ( visible == 1 && prevCount > 1 ) {
// switched from list to drop-down
fPanel.remove( listScrollPanel );
fPanel.add( dropGrid );
}
if ( suppressLayout == false ) {
updateUI();
}
}
/**
* Returns the number of rows visible in the list
*
* @return number of visible rows.
*/
public int getVisibleRowCount() {
return visible;
}
protected void updateUI() {
if ( !this.isAttached() ) {
return;
}
if ( visible > 1 ) {
updateList();
} else {
updateDropDown();
}
}
/**
* Returns the number of rows to be displayed in the drop-down popup.
*
* @return number of visible popup items.
*/
public int getMaxDropVisible() {
return maxDropVisible;
}
/**
* Sets the number of items to be visible in the drop-down popup. If set lower than the number of items a scroll-bar
* with provide access to hidden items.
*
* @param maxDropVisible
* number of items visible in popup.
*/
public void setMaxDropVisible( int maxDropVisible ) {
this.maxDropVisible = maxDropVisible;
// Update the popup to respect this value
if ( maxHeight > 0 ) { // Items already added
this.popupHeight = this.maxDropVisible * maxHeight + "px"; //$NON-NLS-1$
}
}
protected TextBox editableTextBox;
private SimplePanel selectedItemWrapper = new SimplePanel();
protected void updateSelectedDropWidget() {
Widget selectedWidget = new Label( "" ); //Default to show in case of empty sets? //$NON-NLS-1$
boolean updateMade = true;
if ( editable == false ) { // only show their widget if editable is false
if ( selectedIndex >= 0 ) {
selectedWidget = items.get( selectedIndex ).getWidgetForDropdown();
} else if ( items.size() > 0 ) {
selectedWidget = items.get( 0 ).getWidgetForDropdown();
}
} else {
String previousVal = editableTextBox.getText();
String newVal = "";
if ( this.val != null ) {
newVal = this.val;
} else if ( selectedIndex >= 0 ) {
newVal = items.get( selectedIndex ).getValue().toString();
} else if ( items.size() > 0 ) {
newVal = items.get( 0 ).getValue().toString();
}
if ( previousVal.equals( newVal ) == false ) {
editableTextBox.setText( newVal );
}
if ( previousVal != null && previousVal.equals( newVal ) ) {
updateMade = false;
}
editableTextBox.setWidth( "100%" ); //$NON-NLS-1$
editableTextBox.sinkEvents( Event.KEYEVENTS );
editableTextBox.sinkEvents( Event.MOUSEEVENTS );
selectedWidget = editableTextBox;
}
this.setTdStyles( selectedWidget.getElement() );
// selectedItemWrapper.getElement().getStyle().setProperty("overflow", "hidden"); //$NON-NLS-1$ //$NON-NLS-2$
selectedItemWrapper.clear();
selectedItemWrapper.add( selectedWidget );
dropGrid.setWidget( 0, 0, selectedItemWrapper );
if ( editable && updateMade ) {
editableTextBox.setFocus( true );
editableTextBox.selectAll();
}
}
/**
* Called by updateUI when the list is not a drop-down (visible row count > 1)
*/
private void updateList() {
listPanel.clear();
maxHeight = 0;
maxWidth = 0;
// actually going to average up the heights
for ( ListItem li : this.items ) {
Widget w = li.getWidget();
Rectangle rect = ElementUtils.getSize( w.getElement() );
// we only care about this if the user hasn't specified a height.
if ( height == null ) {
maxHeight += rect.height;
}
maxWidth = Math.max( maxWidth, rect.width );
// Add it to the dropdown
listPanel.add( w );
listPanel.setCellWidth( w, "100%" ); //$NON-NLS-1$
}
if ( height == null && this.items.size() > 0 ) {
maxHeight = Math.round( maxHeight / this.items.size() );
}
// we only care about this if the user has specified a visible row count and no heihgt
if ( height == null ) {
int h = ( this.visible * ( maxHeight + spacing ) );
this.listScrollPanel.setHeight( h + "px" ); //$NON-NLS-1$
fPanel.setHeight( h + "px" );
} else {
this.listScrollPanel.setHeight( height );
fPanel.setHeight( height );
}
if ( width == null ) {
this.fPanel.setWidth( maxWidth + 40 + "px" ); //20 is scrollbar space //$NON-NLS-1$
}
}
/**
* Called by updateUI when the list is a drop-down (visible row count = 1)
*/
private void updateDropDown() {
// Update Shown selection in grid
updateSelectedDropWidget();
// Update popup panel,
// Calculate the size of the largest list item.
popupVbox.clear();
maxWidth = 0;
averageHeight = 0; // Actually used to set the width of the arrow
popupHeight = null;
int totalHeight = 0;
for ( ListItem li : this.items ) {
Widget w = li.getWidget();
Rectangle rect = ElementUtils.getSize( w.getElement() );
maxWidth = Math.max( maxWidth, rect.width );
maxHeight = Math.max( maxHeight, rect.height );
totalHeight += rect.height;
// Add it to the dropdown
popupVbox.add( w );
popupVbox.setCellWidth( w, "100%" ); //$NON-NLS-1$
}
// Average the height of the items
if ( items.size() > 0 ) {
averageHeight = Math.round( totalHeight / items.size() );
}
// Set the size of the drop-down based on the largest list item
if ( width == null ) {
dropGrid.setWidth( ( maxWidth + 60 ) + "px" );
this.popupWidth = maxWidth + 60 + "px"; //$NON-NLS-1$
} else if ( width.equals( "100%" ) ) { //$NON-NLS-1$
dropGrid.setWidth( "100%" ); //$NON-NLS-1$
this.popupWidth = maxWidth + ( spacing * 4 ) + maxHeight + "px"; //$NON-NLS-1$
} else {
dropGrid.setWidth( "100%" ); //$NON-NLS-1$
int w = -1;
if ( width.indexOf( "px" ) > 0 ) { //$NON-NLS-1$
w = Integer.parseInt( this.width.replace( "px", "" ) ); //$NON-NLS-1$ //$NON-NLS-2$
} else if ( width.indexOf( "%" ) > 0 ) { //$NON-NLS-1$
w = Integer.parseInt( this.width.replace( "%", "" ) ); //$NON-NLS-1$ //$NON-NLS-2$
}
selectedItemWrapper.setWidth( ( w - ( averageHeight + ( this.spacing * 6 ) ) ) + "px" ); //$NON-NLS-1$
}
// Store the the size of the popup to respect MaxDropVisible now that we know the item height
// This cannot be set here as the popup is not visible :(
if ( maxDropVisible > 0 && items.size() > maxDropVisible ) {
// (Lesser of maxDropVisible or items size) * (Average item height + spacing value)
this.popupHeight =
( Math.min( this.maxDropVisible, this.items.size() ) * ( averageHeight + ( this.spacing * 2 ) ) ) + "px"; //$NON-NLS-1$
} else {
this.popupHeight = null; //ElementUtils.getSize(popupVbox.getElement()).height+ "px";
}
}
/**
* Used internally to hide/show drop-down popup.
*/
protected void togglePopup() {
if ( popupShowing == false ) {
// This delayed instantiation works around a problem with the underlying GWT widgets that
// throw errors positioning when the GWT app is loaded in a frame that's not visible.
if ( popup == null ) {
popup = new DropPopupPanel();
popup.addPopupListener( this );
popup.add( popupScrollPanel );
}
int x = this.getElement().getAbsoluteLeft();
int y = this.getElement().getAbsoluteTop() + this.getElement().getOffsetHeight() + 1;
int windowH = Window.getClientHeight();
int windowW = Window.getClientWidth();
Rectangle popupSize = ElementUtils.getSize( popup.getElement() );
if ( y + popupSize.height > windowH ) {
y = windowH - popupSize.height;
}
if ( x + popupSize.width > windowW ) {
x = windowW - popupSize.width;
}
popup.setPopupPosition( x, y );
popup.show();
// Set the size of the popup calculated in updateDropDown().
if ( this.popupHeight != null ) {
this.popupScrollPanel.getElement().getStyle().setProperty( "height", this.popupHeight ); //$NON-NLS-1$
}
if ( this.popupWidth != null ) {
String w = Math.max( this.getElement().getOffsetWidth() - 2, this.maxWidth + 10 ) + "px";
this.popupScrollPanel.getElement().getStyle().setProperty( "width", w ); //$NON-NLS-1$ //$NON-NLS-2$
popup.getElement().getStyle().setProperty( "width", w );
}
scrollSelectedItemIntoView();
popupShowing = true;
} else {
popup.hide();
// fPanel.setFocus(true);
}
}
protected void scrollSelectedItemIntoView() {
// Scroll to view currently selected widget
// DOM.scrollIntoView(this.getSelectedItem().getWidget().getElement());
// Side effect of the previous call scrolls the scrollpanel to the right. Compensate here
// popupScrollPanel.setHorizontalScrollPosition(0);
if ( this.visible > 1 ) {
this.listScrollPanel.ensureVisible( items.get( selectedIndex ).getWidget() );
return;
}
// if the position of the selected item is greater than the height of the scroll area plus it's scroll offset
if ( ( ( this.selectedIndex + 1 ) * this.averageHeight ) > popupScrollPanel.getOffsetHeight()
+ popupScrollPanel.getScrollPosition() ) {
popupScrollPanel.setScrollPosition( ( ( ( this.selectedIndex ) * this.averageHeight ) - popupScrollPanel
.getOffsetHeight() )
+ averageHeight );
return;
}
// if the position of the selected item is Less than the scroll offset
if ( ( ( this.selectedIndex ) * this.averageHeight ) < popupScrollPanel.getScrollPosition() ) {
popupScrollPanel.setScrollPosition( ( ( this.selectedIndex ) * this.averageHeight ) );
}
}
/**
* Selects the given ListItem in the list.
*
* @param item
* ListItem to be selected.
*/
public void setSelectedItem( ListItem item ) {
if ( items.contains( item ) == false ) {
throw new RuntimeException( "Item not in collection" ); //$NON-NLS-1$
}
// Clear previously selected item
if ( selectedIndex > -1 ) {
items.get( selectedIndex ).onDeselect();
}
if ( visible == 1 ) { // Drop-down mode
if ( popupShowing ) {
togglePopup();
}
}
setSelectedIndex( items.indexOf( item ) );
}
protected List<ListItem> selectedItems = new ArrayList<ListItem>();
protected void handleSelection( ListItem item, Event evt ) {
if ( !evt.getCtrlKey() && !evt.getShiftKey() && !evt.getMetaKey() ) {
for ( ListItem itm : selectedItems ) {
itm.onDeselect();
}
if ( selectedIndex > -1 ) {
items.get( selectedIndex ).onDeselect();
}
selectedItems.clear();
item.onSelect();
if ( !selectedItems.contains( item ) ) {
selectedItems.add( item );
}
selectedIndex = items.indexOf( item );
scrollSelectedItemIntoView();
} else if ( evt.getShiftKey() ) {
int idxOfNewSelection = items.indexOf( item );
int startIdx = Math.min( selectedIndex, idxOfNewSelection );
int endIndex = Math.max( selectedIndex, idxOfNewSelection );
for ( int i = startIdx; i <= endIndex; i++ ) {
if ( !selectedItems.contains( items.get( i ) ) ) {
selectedItems.add( items.get( i ) );
}
items.get( i ).onSelect();
}
} else if ( evt.getCtrlKey() || evt.getMetaKey() ) {
if ( selectedItems.remove( item ) ) {
item.onDeselect();
} else {
item.onSelect();
if ( !selectedItems.contains( item ) ) {
selectedItems.add( item );
}
}
} else {
if ( !selectedItems.contains( item ) ) {
selectedItems.add( item );
}
}
if ( this.suppressLayout == false ) {
for ( ChangeListener l : listeners ) {
l.onChange( this );
}
}
}
/**
* Selects the ListItem at the given index (zero-based)
*
* @param idx
* index of ListItem to select
*/
public void setSelectedIndex( int idx ) {
if ( idx > items.size() ) {
throw new RuntimeException( "Index out of bounds: " + idx ); //$NON-NLS-1$
}
// De-Select the current
if ( selectedIndex > -1 ) {
items.get( selectedIndex ).onDeselect();
}
selectedItems.clear();
if ( idx >= 0 && items.size() > idx ) {
selectedItems.add( items.get( idx ) );
}
int prevIdx = selectedIndex;
if ( idx >= 0 ) {
selectedIndex = idx;
items.get( idx ).onSelect();
this.val = null;
if ( visible == 1 && this.isAttached() ) {
scrollSelectedItemIntoView();
}
updateSelectedDropWidget();
}
if ( this.suppressLayout == false && prevIdx != idx ) {
for ( ChangeListener l : listeners ) {
l.onChange( this );
}
}
}
public void setSelectedIndices( int[] indices ) {
if ( multiSelect == false ) {
// throw new IllegalStateException("Cannot select more than one item in a combobox");
if ( indices.length > 0 ) {
setSelectedIndex( indices[0] );
}
return;
}
for ( ListItem item : selectedItems ) {
item.onDeselect();
}
selectedItems.clear();
for ( int i = 0; i < indices.length; i++ ) {
int idx = indices[i];
if ( idx >= 0 && idx < items.size() ) {
items.get( idx ).onSelect();
selectedItems.add( items.get( idx ) );
}
}
if ( this.suppressLayout == false ) {
for ( ChangeListener l : listeners ) {
l.onChange( this );
}
}
}
/**
* Registers a ChangeListener with the list.
*
* @param listener
* ChangeListner
*/
public void addChangeListener( ChangeListener listener ) {
listeners.add( listener );
}
/**
* Removes to given ChangeListener from list.
*
* @param listener
* ChangeListener
*/
public void removeChangeListener( ChangeListener listener ) {
this.listeners.remove( listener );
}
/**
* Returns the selected index of the list (zero-based)
*
* @return Integer index
*/
public int getSelectedIndex() {
return selectedIndex;
}
/**
* Returns the number of listitems in the box
*
* @return number of children
*/
public int getSize() {
return this.items.size();
}
/**
* Returns the currently selected item
*
* @return currently selected Item
*/
public ListItem getSelectedItem() {
if ( selectedIndex < 0 || selectedIndex > items.size() ) {
return null;
}
return items.get( selectedIndex );
}
public List<ListItem> getSelectedItems() {
return new ArrayList<ListItem>( selectedItems );
}
public int[] getSelectedIndices() {
int[] selectedIndices = new int[selectedItems.size()];
for ( int i = 0; i < selectedItems.size(); i++ ) {
selectedIndices[i] = items.indexOf( selectedItems.get( i ) );
}
return selectedIndices;
}
@Override
public void setStylePrimaryName( String s ) {
super.setStylePrimaryName( s );
this.primaryStyleName = s;
// This may have came in late. Update ListItems
for ( ListItem item : items ) {
item.setStylePrimaryName( s );
}
}
@Override
protected void onAttach() {
super.onAttach();
updateUI();
}
@Override
/**
* Calling setHeight will implecitly change the list from a drop-down style to a list style.
*/
public void setHeight( String s ) {
this.height = s;
// user has specified height, focusPanel needs to be 100%;
this.fPanel.setHeight( s );
this.listScrollPanel.setHeight( "100%" ); //$NON-NLS-1$
if ( visible == 1 ) {
this.setVisibleRowCount( 15 );
}
super.setHeight( s );
updateUI();
}
@Override
public void setWidth( String s ) {
fPanel.setWidth( s );
this.listScrollPanel.setWidth( "100%" ); //$NON-NLS-1$
this.width = s;
this.popupWidth = s;
if ( s != null ) {
dropGrid.setWidth( "100%" ); //$NON-NLS-1$
}
super.setWidth( s );
updateUI();
}
// ======================================= Listener methods ===================================== //
public void onPopupClosed( PopupPanel popupPanel, boolean b ) {
this.popupShowing = false;
}
public void onMouseDown( Widget widget, int i, int i1 ) {
}
public void onMouseEnter( Widget widget ) {
}
public void onMouseLeave( Widget widget ) {
}
public void onMouseMove( Widget widget, int i, int i1 ) {
}
public void onMouseUp( Widget widget, int i, int i1 ) {
if ( isEnabled() == false ) {
return;
}
if ( visible == 1 ) { // drop-down mode
this.togglePopup();
}
}
public void onFocus( Widget widget ) {
if ( isEnabled() == false ) {
return;
}
// fPanel.setFocus(true);
}
public void onLostFocus( Widget widget ) {
}
private int shiftOriginIdx = -1;
public void onKeyDown( Widget widget, char c, int i ) {
if ( c == 16 ) { // shift
shiftOriginIdx = selectedIndex;
}
}
public void onKeyPress( Widget widget, char c, int i ) {
}
public void onKeyUp( Widget widget, char c, int i ) {
if ( isEnabled() == false ) {
return;
}
if ( c == 16 ) {
shiftOriginIdx = -1;
}
boolean fireEvents = false;
switch ( c ) {
case 38: // UP
if ( selectedIndex > 0 ) {
if ( multiSelect && !Event.getCurrentEvent().getShiftKey() ) {
for ( ListItem itm : selectedItems ) {
itm.onDeselect();
}
selectedItems.clear();
ListItem itm = items.get( selectedIndex - 1 );
selectedIndex = selectedIndex - 1;
itm.onSelect();
if ( !selectedItems.contains( itm ) ) {
selectedItems.add( itm );
}
fireEvents = true;
this.listScrollPanel.ensureVisible( itm.getWidget() );
} else if ( multiSelect && Event.getCurrentEvent().getShiftKey() ) {
ListItem itm = items.get( selectedIndex - 1 );
if ( !selectedItems.contains( itm ) ) {
selectedItems.add( itm );
}
itm.onSelect();
this.listScrollPanel.ensureVisible( itm.getWidget() );
ListItem prevItem = items.get( selectedIndex );
if ( selectedIndex != shiftOriginIdx
&& shiftOriginIdx < selectedIndex
&& selectedItems.contains( prevItem ) ) {
selectedItems.remove( prevItem );
prevItem.onDeselect();
}
selectedIndex = selectedIndex - 1;
fireEvents = true;
} else {
setSelectedIndex( selectedIndex - 1 );
scrollSelectedItemIntoView();
}
}
break;
case 40: // Down
if ( selectedIndex < items.size() - 1 ) {
if ( multiSelect && !Event.getCurrentEvent().getShiftKey() ) {
for ( ListItem itm : selectedItems ) {
itm.onDeselect();
}
selectedItems.clear();
ListItem itm = items.get( selectedIndex + 1 );
selectedIndex = selectedIndex + 1;
itm.onSelect();
if ( !selectedItems.contains( itm ) ) {
selectedItems.add( itm );
}
fireEvents = true;
this.listScrollPanel.ensureVisible( itm.getWidget() );
} else if ( multiSelect && Event.getCurrentEvent().getShiftKey() ) {
ListItem itm = items.get( selectedIndex + 1 );
if ( !selectedItems.contains( itm ) ) {
selectedItems.add( itm );
}
itm.onSelect();
ListItem prevItem = items.get( selectedIndex );
if ( selectedIndex != shiftOriginIdx
&& shiftOriginIdx > selectedIndex
&& selectedItems.contains( prevItem ) ) {
selectedItems.remove( prevItem );
prevItem.onDeselect();
}
this.listScrollPanel.ensureVisible( itm.getWidget() );
selectedIndex = selectedIndex + 1;
fireEvents = true;
} else {
setSelectedIndex( selectedIndex + 1 );
scrollSelectedItemIntoView();
}
}
break;
case 27: // ESC
case 13: // Enter
if ( popupShowing ) {
togglePopup();
}
break;
case 65: // A
if ( Event.getCurrentEvent().getCtrlKey() ) {
for ( ListItem item : items ) {
item.onSelect();
}
selectedItems.clear();
selectedItems.addAll( items );
fireEvents = true;
}
break;
}
if ( fireEvents && this.suppressLayout == false ) {
for ( ChangeListener l : listeners ) {
l.onChange( this );
}
}
}
public void setCommand( Command command ) {
this.command = command;
}
// ====================================== Listener Implementations =========================== //
public void itemSelected( ListItem listItem, Event event ) {
fPanel.setFocus( true );
if ( multiSelect ) {
handleSelection( listItem, event );
} else {
setSelectedItem( listItem );
}
}
public void doAction( ListItem listItem ) {
if ( command != null ) {
command.execute();
}
}
// ======================================= Inner Classes ===================================== //
/**
* Panel used as a drop-down popup.
*/
private class DropPopupPanel extends PopupPanel {
public DropPopupPanel() {
super( true );
setStyleName( "drop-popup" ); //$NON-NLS-1$
}
@Override
public boolean onEventPreview( Event event ) {
if ( DOM.isOrHasChild( CustomListBox.this.getElement(), DOM.eventGetTarget( event ) ) ) {
return true;
}
return super.onEventPreview( event );
}
}
/**
* Panel contained in the popup
*/
private class PopupList extends VerticalPanel {
public PopupList() {
this.sinkEvents( Event.MOUSEEVENTS );
}
@Override
public void onBrowserEvent( Event event ) {
super.onBrowserEvent( event );
}
}
/**
* This is the arrow rendered in the drop-down.
*/
protected class DropDownArrow extends SimplePanel {
private SimplePanel img;
private boolean enabled = true;
public DropDownArrow() {
img = new SimplePanel();
this.setStylePrimaryName( "combo-arrow" ); //$NON-NLS-1$
super.add( img );
ElementUtils.preventTextSelection( this.getElement() );
}
public void setEnabled( boolean enabled ) {
if ( this.enabled == enabled ) {
return;
}
this.enabled = enabled;
if ( enabled ) {
this.setStylePrimaryName( "combo-arrow" ); //$NON-NLS-1$
} else {
this.setStylePrimaryName( "combo-arrow-disabled" ); //$NON-NLS-1$
}
}
}
/**
* Setting editable to true allows the user to specify their own value for the combobox.
*
* @param editable
*/
public void setEditable( boolean editable ) {
this.editable = editable;
this.updateUI();
}
public boolean isEditable() {
return this.editable;
}
/**
* Returns the user-entered value in the case of an editable drop-down
*
* @return Value user has entered
*/
public String getValue() {
if ( !editable ) {
if( getSelectedItem() != null ) {
return getSelectedItem().getText();
} else {
return null;
}
} else {
return ( editableTextBox != null ) ? editableTextBox.getText() : null;
}
}
public void setValue( String text ) {
this.val = text;
if ( editable ) {
editableTextBox.setText( text );
selectedIndex = -1;
this.onChange( editableTextBox );
} else {
for( int i = 0; i < items.size(); i++ ) {
if( items.get( i ).getText().equals( text ) ) {
setSelectedIndex( i );
return;
}
}
}
}
public void onChange( Widget sender ) {
for ( ChangeListener l : listeners ) {
l.onChange( this );
}
}
public void setEnabled( boolean enabled ) {
this.enabled = enabled;
if ( editableTextBox != null ) {
editableTextBox.setEnabled( enabled );
}
arrow.setEnabled( enabled );
this.setStylePrimaryName( ( this.enabled ) ? "custom-list" : "custom-list-disabled" ); //$NON-NLS-1$ //$NON-NLS-2$
}
public boolean isEnabled() {
return this.enabled;
}
public Widget createProxy() {
DefaultListItem item = new DefaultListItem();
item.setText( getSelectedItem().getText() );
return item;
}
public void setDragController( DragController controller ) {
dragController = controller;
if ( this.items.size() > 0 ) {
for ( ListItem item : items ) {
dragController.makeDraggable( item.getWidget() );
}
}
}
public boolean isMultiSelect() {
return multiSelect;
}
public void setMultiSelect( boolean multiSelect ) {
this.multiSelect = multiSelect;
}
}