/*! * 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; } }