1  /*
     2   Copyright aswing.org, see the LICENCE.txt.
     3  */
     4  
     5  import org.aswing.ComboBoxEditor;
     6  import org.aswing.Component;
     7  import org.aswing.Container;
     8  import org.aswing.DefaultComboBoxEditor;
     9  import org.aswing.EventDispatcher;
    10  import org.aswing.JList;
    11  import org.aswing.ListCellFactory;
    12  import org.aswing.ListModel;
    13  import org.aswing.plaf.ComboBoxUI;
    14  import org.aswing.UIManager;
    15  
    16  /**
    17   * A component that combines a button or editable field and a drop-down list.
    18   * The user can select a value from the drop-down list, which appears at the 
    19   * user's request. If you make the combo box editable, then the combo box
    20   * includes an editable field into which the user can type a value.
    21   * 
    22   * <p>
    23   * <code>JComboBox</code> use a <code>JList</code> to be the drop-down list, so of course you can operate 
    24   * list to do some thing.
    25   * <p>
    26   * By default <code>JComboBox</code> can't count its preffered width accurately 
    27   * like default JList, you have to set its preffered size if you want. 
    28   * Or you make a not shared cell factory to it. see <code>ListCellFactory</code> and <code>JList</code> for details.
    29   * @author iiley
    30   * @see JList
    31   * @see ComboBoxEditor
    32   * @see DefaultComboBoxEditor
    33   */
    34  class org.aswing.JComboBox extends Container {
    35  	
    36  	/**
    37  	 * When a selection has been made or a editing stoped. 
    38  	 *<br>
    39  	 * onActionPerformed Event{source:DefaultComboBoxEditor}
    40  	 */	
    41  	public static var ON_ACT:String = EventDispatcher.ON_ACT;
    42  	
    43  	private var editor:ComboBoxEditor;
    44  	private var editable:Boolean;
    45  	private var popupList:JList;
    46  	private var editorLisenter:Object;
    47  	private var maximumRowCount:Number;
    48  	
    49  	/**
    50  	 * JComboBox(listData:Array)<br>
    51  	 * JComboBox(model:ListModel)<br>
    52  	 * JComboBox()
    53  	 * <p>
    54  	 */
    55  	public function JComboBox(listData:Object) {
    56  		super();
    57  		
    58  		setName("JComboBox");
    59  		maximumRowCount = 7;
    60  		editable = false;
    61  		setEditor(new DefaultComboBoxEditor());
    62  		if(listData != undefined){
    63  			if(listData instanceof ListModel){
    64  				setModel(ListModel(listData));
    65  			}else{
    66  				var o = listData;//avoid Array casting
    67  				if(o instanceof Array){
    68  					setListData(o);
    69  				}else{
    70  					setListData(null); //create new
    71  				}
    72  			}
    73  		}
    74  		
    75  		updateUI();
    76  	}
    77  	
    78  	public function setUI(ui:ComboBoxUI):Void{
    79  		super.setUI(ui);
    80  	}
    81  	public function updateUI():Void{
    82  		setUI(ComboBoxUI(UIManager.getUI(this)));
    83  	}
    84  	public function getUIClassID():String{
    85  		return "ComboBoxUI";
    86  	}
    87  	public function getUI():ComboBoxUI{
    88  		return ComboBoxUI(ui);
    89  	}
    90  	
    91  	/**
    92  	 * addChangeListener(func:Function)<br>
    93  	 * addChangeListener(func:Function, obj:Object)
    94  	 * <p>
    95  	 * The ActionListener will receive an ActionEvent when a selection has been made. 
    96  	 * If the combo box is editable, then an ActionEvent will be fired when editing has stopped. 
    97  	 * @return the listener added.
    98  	 * @see #ON_ACT
    99  	 * @see #addEventListener()
   100  	 * @see #removeEventListener()
   101  	 */
   102  	public function addActionListener(func:Function, obj:Object):Object{
   103  		return addEventListener(ON_ACT, func, obj);
   104  	}
   105  	
   106  	private function fireActionPerformed():Void{
   107  		dispatchEvent(ON_ACT, createEventObj(ON_ACT));
   108  	}
   109  	
   110  	/**
   111  	 * Returns the popup list that display the items.
   112  	 */
   113  	public function getPopupList():JList{
   114  		if(popupList == null){
   115  			popupList = new JList();
   116  		}
   117  		return popupList;
   118  	}
   119  	/**
   120       * Sets the maximum number of rows the <code>JComboBox</code> displays.
   121       * If the number of objects in the model is greater than count,
   122       * the combo box uses a scrollbar.
   123       * @param count an integer specifying the maximum number of items to
   124       *              display in the list before using a scrollbar
   125       */
   126  	public function setMaximumRowCount(count:Number):Void{
   127  		maximumRowCount = count;
   128  	}
   129  	
   130  	/**
   131       * Returns the maximum number of items the combo box can display 
   132       * without a scrollbar
   133       * @return an integer specifying the maximum number of items that are 
   134       *         displayed in the list before using a scrollbar
   135       */
   136  	public function getMaximumRowCount():Number{
   137  		return maximumRowCount;
   138  	}
   139  	
   140  	
   141  	/**
   142  	 * @return the cellFactory for the popup List
   143  	 */
   144  	public function getListCellFactory():ListCellFactory{
   145  		return getPopupList().getCellFactory();
   146  	}
   147  	
   148  	/**
   149  	 * This will cause all cells recreating by new factory.
   150  	 * @param newFactory the new cell factory for the popup List
   151  	 */
   152  	public function setListCellFactory(newFactory:ListCellFactory):Void{
   153  		getPopupList().setCellFactory(newFactory);
   154  	}
   155  	
   156  	/**
   157       * Sets the editor used to paint and edit the selected item in the 
   158       * <code>JComboBox</code> field.  The editor is used both if the
   159       * receiving <code>JComboBox</code> is editable and not editable.
   160       * @param anEditor  the <code>ComboBoxEditor</code> that
   161       *			displays the selected item
   162       */
   163  	public function setEditor(anEditor:ComboBoxEditor):Void{
   164  		if(anEditor == null) return;
   165  		
   166  		var oldEditor:ComboBoxEditor = editor;
   167  		if (oldEditor != null)
   168  		{
   169  			oldEditor.removeEventListener(editorLisenter);
   170  			remove(oldEditor.getEditorComponent());
   171  		}
   172  		editor = anEditor;
   173  		editor.setEditable(isEditable());
   174  		append(editor.getEditorComponent());
   175  		editorLisenter = editor.addActionListener(__editorActed, this);
   176  		revalidate();
   177  	}
   178  	
   179  	/**
   180       * Returns the editor used to paint and edit the selected item in the 
   181       * <code>JComboBox</code> field.
   182       * @return the <code>ComboBoxEditor</code> that displays the selected item
   183       */
   184  	public function getEditor():ComboBoxEditor{
   185  		return editor;
   186  	}
   187  	
   188  	/**
   189       * Determines whether the <code>JComboBox</code> field is editable.
   190       * An editable <code>JComboBox</code> allows the user to type into the
   191       * field or selected an item from the list to initialize the field,
   192       * after which it can be edited. (The editing affects only the field,
   193       * the list item remains intact.) A non editable <code>JComboBox</code> 
   194       * displays the selected item in the field,
   195       * but the selection cannot be modified.
   196       * 
   197       * @param aFlag a boolean value, where true indicates that the
   198       *			field is editable
   199       */
   200  	public function setEditable(aFlag:Boolean):Void{
   201  		editable = aFlag;
   202  		getEditor().setEditable(aFlag);
   203  	}
   204  	/**
   205       * Returns true if the <code>JComboBox</code> is editable.
   206       * By default, a combo box is not editable.
   207       * @return true if the <code>JComboBox</code> is editable, else false
   208       */
   209  	public function isEditable():Boolean{
   210  		return editable;
   211  	}
   212  	
   213  	/**
   214       * Enables the combo box so that items can be selected. When the
   215       * combo box is disabled, items cannot be selected and values
   216       * cannot be typed into its field (if it is editable).
   217       *
   218       * @param b a boolean value, where true enables the component and
   219       *          false disables it
   220       */
   221  	public function setEnabled(b:Boolean):Void{
   222  		if(b != isEnabled()){
   223  			repaint();
   224  		}
   225  		super.setEnabled(b);
   226  		for(var i:Number=0; i<this.getComponentCount(); i++){
   227  			var com:Component = getComponent(i);
   228  			if(com == getEditor().getEditorComponent()){
   229  				getEditor().setEditable(b && isEditable());
   230  			}else{
   231  				com.setEnabled(b);
   232  			}
   233  		}
   234  	}
   235  	
   236  	/**
   237  	 * set a array to be the list data, but array is not a List Mode.
   238  	 * So when the array content was changed, you should call updateListView
   239  	 * to update the JList(the list for combo box).But this is not a good way, its slow.
   240  	 * So suggest you to create a ListMode eg. VectorListMode,
   241  	 * When you modify ListMode, it will automatic update JList.
   242  	 * @see #setMode()
   243  	 * @see org.aswing.ListModel
   244  	 */
   245  	public function setListData(ld:Array):Void{
   246  		getPopupList().setListData(ld);
   247  	}
   248  	
   249  	/**
   250  	 * Set the list mode to provide the data to JList.
   251  	 * @see org.aswing.ListModel
   252  	 */
   253  	public function setModel(m:ListModel):Void{
   254  		getPopupList().setModel(m);
   255  	}
   256  	
   257  	/**
   258  	 * @return the model of this List
   259  	 */
   260  	public function getModel():ListModel{
   261  		return getPopupList().getModel();
   262  	}	
   263  	/** 
   264       * Causes the combo box to display its popup window.
   265       * @see #setPopupVisible()
   266       */
   267  	public function showPopup():Void{
   268  		setPopupVisible(true);
   269  	}
   270  	/** 
   271       * Causes the combo box to close its popup window.
   272       * @see #setPopupVisible()
   273       */
   274  	public function hidePopup():Void{
   275  		setPopupVisible(false);
   276  	}
   277  	/**
   278       * Sets the visibility of the popup, open or close.
   279       */
   280  	public function setPopupVisible(v:Boolean):Void{
   281  		getUI().setPopupVisible(this, v);
   282  	}
   283  	/** 
   284       * Determines the visibility of the popup.
   285       *
   286       * @return true if the popup is visible, otherwise returns false
   287       */
   288  	public function isPopupVisible():Boolean{
   289  		return getUI().isPopupVisible(this);
   290  	}
   291  	
   292  	 /** 
   293       * Sets the selected item in the combo box display area to the object in 
   294       * the argument.
   295       * If <code>anObject</code> is in the list, the display area shows 
   296       * <code>anObject</code> selected.
   297       * <p>
   298       * If <code>anObject</code> is <i>not</i> in the list and the combo box is
   299       * uneditable, it will not change the current selection. For editable 
   300       * combo boxes, the selection will change to <code>anObject</code>.
   301       * <p>
   302       * <code>ON_ACT</code> (<code>addActionListener()</code>)events added to the combo box will be notified
   303       * when this method is called.
   304       *
   305       * @param anObject  the list object to select; use <code>null</code> to
   306                          clear the selection
   307       */
   308  	public function setSelectedItem(anObject:Object):Void{
   309  		getEditor().setValue(anObject);
   310  		var index:Number = indexInModel(anObject);
   311  		if(index >= 0){
   312  			getPopupList().setSelectedIndex(index);
   313  			getPopupList().ensureIndexIsVisible(index);
   314  		}
   315  		fireActionPerformed();
   316  	}
   317  	
   318  	/**
   319       * Returns the current selected item.
   320       * <p>
   321       * If the combo box is editable, then this value may not have been in 
   322       * the list model.
   323       * @return the current selected Object
   324       * @see #setSelectedItem()
   325       */
   326  	public function getSelectedItem():Object{
   327  		if(isEditable()){
   328  			return getEditor().getValue();
   329  		}else{
   330  			return getPopupList().getSelectedValue();
   331  		}
   332  	}
   333  	/**
   334       * Selects the item at index <code>anIndex</code>.
   335       *
   336       * @param anIndex an integer specifying the list item to select,
   337       *			where 0 specifies the first item in the list and -1 or greater than max index
   338       *			 indicates empty selection
   339       */
   340  	public function setSelectedIndex(anIndex:Number):Void{
   341  		var size:Number = getModel().getSize();
   342  		if(anIndex < 0 || anIndex >= size){
   343  			getEditor().setValue(null);
   344  		}else{
   345  			getEditor().setValue(getModel().getElementAt(anIndex));
   346  			getPopupList().setSelectedIndex(anIndex);
   347  		}
   348  	}
   349  	
   350  	/**
   351       * Returns the first item in the list that matches the given item.
   352       * The result is not always defined if the <code>JComboBox</code>
   353       * allows selected items that are not in the list. 
   354       * Returns -1 if there is no selected item or if the user specified
   355       * an item which is not in the list.
   356       * @return an integer specifying the currently selected list item,
   357       *			where 0 specifies
   358       *                	the first item in the list;
   359       *			or -1 if no item is selected or if
   360       *                	the currently selected item is not in the list
   361       */
   362  	public function getSelectedIndex():Number{
   363  		if(isEditable()){
   364  			return indexInModel(getEditor().getValue());
   365  		}else{
   366  			return getPopupList().getSelectedIndex();
   367  		}
   368  	}	
   369  	/**
   370       * Returns the number of items in the list.
   371       * @return an integer equal to the number of items in the list
   372       */
   373  	public function getItemCount():Number{
   374  		return getModel().getSize();
   375  	}
   376  	/**
   377       * Returns the list item at the specified index.  If <code>index</code>
   378       * is out of range (less than zero or greater than or equal to size)
   379       * it will return <code>undefined</code>.
   380       *
   381       * @param index  an integer indicating the list position, where the first
   382       *               item starts at zero
   383       * @return the <code>Object</code> at that list position; or
   384       *			<code>undefined</code> if out of range
   385       */
   386  	public function getItemAt(index:Number):Object{
   387  		return getModel().getElementAt(index);
   388  	}
   389  	
   390  	//----------------------------------------------------------
   391  	private function __editorActed():Void{
   392  		setSelectedItem(getEditor().getValue());
   393  	}
   394  	
   395  	private function indexInModel(value:Object):Number{
   396  		var model:ListModel = getModel();
   397  		var n:Number = model.getSize();
   398  		for(var i:Number=0; i<n; i++){
   399  			if(model.getElementAt(i) == value){
   400  				return i;
   401  			}
   402  		}
   403  		return -1;
   404  	}
   405  }
   406