1  /*
     2   Copyright aswing.org, see the LICENCE.txt.
     3  */
     4  
     5  import org.aswing.ASFont;
     6  import org.aswing.ASTextExtent;
     7  import org.aswing.ASTextFormat;
     8  import org.aswing.ASWingUtils;
     9  import org.aswing.Component;
    10  import org.aswing.geom.Dimension;
    11  import org.aswing.geom.Rectangle;
    12  import org.aswing.plaf.TextUI;
    13  import org.aswing.utils.Delegate;
    14  
    15  /**
    16   * JTextComponent is the base class for text components. 
    17   * @author Tomato, iiley
    18   */
    19  class org.aswing.JTextComponent extends Component {
    20  	/**
    21  	 * When the text content was scrolled.
    22  	 *<br>
    23  	 * onTextScroll Event{source:JTextComponent}
    24  	 */	
    25  	public static var ON_TEXT_SCROLLED :String = "onTextScrolled";
    26  	/**
    27  	 * When the text content was changed.
    28  	 *<br>
    29  	 * onTextChanged Event{source:JTextComponent}
    30  	 */	
    31  	public static var ON_TEXT_CHANGED:String = "onTextChanged";	
    32  	
    33  	private var text:String;
    34  	private var htmlText:String;
    35  	private var textFormat:ASTextFormat;
    36  	private var html:Boolean;
    37  	private var password:Boolean;
    38  	private var maxChars:Number;
    39  	private var restrict:String;
    40  	private var editable:Boolean;
    41  	
    42  	private var textField:TextField;
    43  	private var textListener:Object;
    44  	
    45  	//dont access these properties directly in subclass, use get method.
    46  	private var columnWidth:Number;
    47  	private var rowHeight:Number;
    48  	private var widthMargin:Number;
    49  	private var heightMargin:Number;
    50  	
    51  	private var columnRowCounted:Boolean;
    52  	private var autoSizedToCountPrefferedSize:Boolean;
    53  	private var autoSizedSize:Dimension;
    54  	
    55  	private function JTextComponent() {
    56  		super();
    57  		setName("JTextComponent");
    58  		textFormat = ASTextFormat.getASTextFormatWithASFont(font);
    59  		text = "";
    60  		columnRowCounted = false;
    61  		autoSizedToCountPrefferedSize = false;
    62  		html = false;
    63  		password = false;
    64  		maxChars = null;
    65  		restrict = null;
    66  		editable = true;
    67  		
    68  		textListener = new Object();
    69  		textListener.onChanged = Delegate.create(this, ____uiTextChanged);
    70  		textListener.onScroller = Delegate.create(this, ____uiTextScrolled);
    71  	}
    72  	
    73      /**
    74       * Returns the L&F object that renders this component.
    75       * @return the TextUI object
    76       * @see #setUI()
    77       */
    78      public function getUI():TextUI {
    79          return TextUI(ui);
    80      }
    81      
    82      /**
    83       * Sets the L&F object that renders this component.
    84       * @param ui the <code>TextUI</code> L&F object
    85       * @see #getUI()
    86       */
    87      public function setUI(ui:TextUI):Void {
    88          super.setUI(ui);
    89      }
    90      
    91      /**
    92       * Resets the UI property to a value from the current look
    93       * and feel.  Subtypes of <code>JTextComponent</code>
    94       * should override this to update the UI. For
    95       * example, <code>JTextField</code> might do the following:
    96       * <pre>
    97       *      setUI(TextUI(UIManager.getUI(this)));
    98       * </pre>
    99       */
   100      public function updateUI():Void{
   101      }
   102      
   103      private function create():Void{
   104      	super.create();
   105      	textField = createTextField("textField");
   106      	textField.addListener(textListener);
   107      	
   108      	//call this first to validate current textfield scroll properties
   109  		applyPropertiesToText(textField, false);
   110  		applyBoundsToText(textField, getPaintBounds());
   111      	var t:TextField = textField;
   112      	if(isHtml()){
   113      		textField.htmlText = text;
   114      	}else{
   115      		textField.text = text;
   116      	}
   117      	textField.background = false;
   118      }
   119      
   120      private function paint(b:Rectangle):Void{
   121      	super.paint(b);
   122      	var t:TextField = textField;
   123      	applyPropertiesToText(t, false);
   124      	applyBoundsToText(t, b);
   125      }
   126      
   127      private function applyPropertiesToText(t:TextField, autoSize:Boolean):Void{
   128      	t.autoSize = autoSize;
   129      	t.wordWrap = isWordWrap();
   130      	if(isWordWrap()){
   131      		t._width = getWordWrapWidth();
   132      	}
   133  		t.background = false;
   134  		t.border = false;
   135  		t.type = isEditable() ? "input" : "dynamic";
   136  		t.selectable = isEnabled();
   137  		t.maxChars = getMaxChars();
   138  		t.html = isHtml();
   139  		t.restrict = getRestrict();
   140  		t.password = isPasswordField();
   141  		t.multiline = isMultiline();
   142  		if(isHtml()){
   143  			if(t.htmlText != text){
   144  				t.htmlText = (text == null ? "" : text);
   145  			}
   146  		}else{
   147  			if(t.text != text){
   148  				t.text = (text == null ? "" : text);
   149  			}
   150  			ASWingUtils.applyTextFormatAndColor(t, textFormat, getForeground());
   151  		}
   152      }
   153      
   154      private function applyBoundsToText(t:TextField, b:Rectangle):Void{
   155  		t._x = b.x;
   156  		t._y = b.y;
   157  		t._width = b.width;
   158  		t._height = b.height;
   159      }
   160      
   161      /**
   162       * @return the textField if created, null if not.
   163       */
   164      private function getTextField():TextField{
   165      	if(isDisplayable()){
   166      		return textField;
   167      	}else{
   168      		return null;
   169      	}
   170      }
   171      
   172      /**
   173       * Alwasy return false.
   174       * Subclass override this to change Text's multiline ability
   175       */
   176      public function isMultiline():Boolean{
   177      	return false;
   178      }
   179      /**
   180       * Subclass override this to change Text's wordWrap ability
   181       */
   182      public function isWordWrap():Boolean{
   183      	return false;
   184      }
   185      /**
   186       * If the isWordWrap method returned true, this method will be called to 
   187       * get the wrap width.
   188       * @see #isWordWrap()
   189       */    
   190      private function getWordWrapWidth():Number{
   191      	return 0;
   192      }
   193  	//-----------------------------------------------
   194  
   195  	/**
   196  	 * Set the text of this text field.(it the textfield enabled html, it will be htmlText)
   197  	 * @param t the text of this text field, if it is null or undefined, it will be set to "";
   198  	 * @see #isHtml()
   199  	 */
   200  	public function setText(t:String):Void{
   201  		if(t == null) t = "";
   202  		if(t != text){
   203  			text = t;
   204  			invalidateTextFieldAutoSizeToCountPrefferedSize();
   205  			if(getTextField()!= null){
   206  				//applyPropertiesToText(getTextField(), false);
   207  				//applyBoundsToText(getTextField(), getPaintBounds());
   208  				if(isHtml()){
   209  					getTextField().htmlText = text;
   210  				}else{
   211  					getTextField().text = text;					
   212  				}
   213  			}else{
   214  				repaint();
   215  				dispatchEvent(ON_TEXT_CHANGED, createEventObj(ON_TEXT_CHANGED));
   216  			}
   217  			revalidate();
   218  		}
   219  	}
   220  	
   221  	public function getText():String{
   222  		return text;
   223  	}
   224  	
   225  	/**
   226  	 * Appends a text to the text's end.
   227  	 * @param t the text to append.
   228  	 */
   229  	public function appendText(t:String):Void{
   230  		setText(text + t);
   231  	}
   232  	
   233  	/**
   234  	 * Inserts a text to the specified position of current text.
   235  	 * @param t the text to insert
   236  	 * @param position the position to insert, if it less or equals than 0 means the begin of current text.
   237  	 */
   238  	public function insertText(t:String, position:Number):Void{
   239  		if(position <= 0){
   240  			setText(t + text);
   241  		}else{
   242  			setText(text.substr(0, position) + t + text.substr(position));
   243  		}
   244  	}
   245  	
   246  	/**
   247  	 * Sets whether use html style. 
   248  	 * @see #getHtmlText()
   249  	 */
   250  	public function setHtml(enabled:Boolean):Void{
   251  		if(html != enabled){
   252  			html = enabled;
   253  			invalidateTextFieldAutoSizeToCountPrefferedSize();
   254  			if(getTextField()!= null){
   255  				applyPropertiesToText(getTextField(), false);
   256  				applyBoundsToText(getTextField(), getPaintBounds());
   257  			}else{
   258  				repaint();
   259  			}
   260  			revalidate();
   261  		}
   262  	}
   263  	
   264  	public function isHtml():Boolean{
   265  		return html;
   266  	}
   267  	
   268  	public function setPasswordField(p:Boolean):Void{
   269  		if(password != p){
   270  			password = p;
   271  			if(getTextField() != null){
   272  				getTextField().password = p;
   273  			}
   274  			repaint();
   275  		}
   276  	}
   277  	
   278  	public function isPasswordField():Boolean{
   279  		return password;
   280  	}
   281  	
   282  	public function setMaxChars(count:Number):Void{
   283  		if(maxChars != count){
   284  			maxChars = count;
   285  			if(getTextField() != null){
   286  				getTextField().maxChars = count;
   287  			}
   288  			repaint();
   289  			revalidate();
   290  		}
   291  	}
   292  	
   293  	public function getMaxChars():Number{
   294  		return maxChars;
   295  	}
   296  	
   297  	public function setRestrict(r:String):Void{
   298  		if(this.restrict !== r){
   299  			this.restrict = r;
   300  			if(getTextField() != null){
   301  				getTextField().restrict = r;
   302  			}
   303  			repaint();
   304  			revalidate();
   305  		}
   306  	}
   307  	
   308  	public function getRestrict():String{
   309  		return restrict;
   310  	}
   311  	
   312  	public function setEnabled(enabled:Boolean):Void{
   313  		var old:Boolean = isEnabled();
   314  		super.setEnabled(enabled);
   315  		if(old != enabled){
   316  			if(getTextField() != null){
   317  				getTextField().selectable = enabled;
   318  			}
   319  			repaint();
   320  		}
   321  	}
   322  	
   323  	public function setEditable(editable:Boolean):Void{
   324  		if(this.editable != editable){
   325  			this.editable = editable;
   326  			if(getTextField() != null){
   327  				getTextField().type = editable ? "input" : "dynamic";
   328  			}
   329  			repaint();
   330  		}
   331  	}
   332  	
   333  	public function isEditable():Boolean{
   334  		return editable;
   335  	}
   336  	
   337  	/**
   338  	 * Returns the chars of the text.
   339  	 */
   340  	public function getTextLength():Number{
   341  		return getText().length;
   342  	}
   343  	
   344  	/**
   345  	 * Returns is the textField is focused.
   346  	 */
   347  	public function isFocused():Boolean{
   348  		return eval(Selection.getFocus()) == textField;
   349  	}
   350  	//-------------------------------------------------
   351  	/**
   352  	 * Sets the text format properties for the text component's text format.
   353  	 * <p>The will casue the font changed.
   354  	 */
   355  	public function setTextFormat(tf:ASTextFormat):Void{
   356  		var t:ASTextFormat = tf.clone();
   357  		setFont(t.getASFont());
   358  		invalidateColumnRowSize();
   359  		textFormat = t;
   360  	}
   361  	
   362  	public function setFont(f:ASFont):Void{
   363  		if(getFont() != f){
   364  			invalidateColumnRowSize();
   365  			invalidateTextFieldAutoSizeToCountPrefferedSize();
   366  			super.setFont(f);
   367  			textFormat.setASFont(f);
   368  		}
   369  	}
   370  	
   371  	/**
   372  	 * Returns a copy of the text format of this component.
   373  	 */
   374  	public function getTextFormat():ASTextFormat{
   375  		return textFormat.clone();
   376  	}
   377  	
   378  	//------------------------For UIs---------------------------
   379      private function __uiTextChanged():Void{
   380      	if(isHtml()){
   381      		text = getTextField().htmlText;
   382      	}else{
   383      		text = getTextField().text;
   384      	}
   385      	invalidateTextFieldAutoSizeToCountPrefferedSize();
   386  		dispatchEvent(ON_TEXT_CHANGED, createEventObj(ON_TEXT_CHANGED));
   387      }
   388      
   389      private function __uiTextScrolled():Void{
   390      	dispatchEvent(ON_TEXT_SCROLLED, createEventObj(ON_TEXT_SCROLLED));
   391      }
   392      	
   393      //cant override this method in sub class
   394      private function ____uiTextChanged():Void{
   395      	__uiTextChanged();
   396      }
   397      //cant override this method in sub class
   398      private function ____uiTextScrolled():Void{
   399      	__uiTextScrolled();
   400      }
   401      //-------------------------------------------------------------------
   402      		
   403  	/**
   404  	 * JTextComponent need count preferred size itself.
   405  	 */
   406  	private function countPreferredSize():Dimension{
   407  		trace("Subclass of JTextComponent need implement this method : countPreferredSize!");
   408  		throw new Error("Subclass of JTextComponent need implement this method : countPreferredSize!");
   409  		return null;
   410  	}
   411  	
   412  	/**
   413  	 * Returns the column width. The meaning of what a column is can be considered a fairly weak notion for some fonts.
   414  	 * This method is used to define the width of a column. 
   415  	 * By default this is defined to be the width of the character m for the font used.
   416  	 * if the font size changed, the invalidateColumnRowSize will be called,
   417  	 * then next call get method about this will be counted first.
   418  	 */
   419  	private function getColumnWidth():Number{
   420  		if(!columnRowCounted) countColumnRowSize();
   421  		return columnWidth;
   422  	}
   423  	
   424  	/**
   425  	 * Returns the row height. The meaning of what a column is can be considered a fairly weak notion for some fonts.
   426  	 * This method is used to define the height of a row. 
   427  	 * By default this is defined to be the height of the character m for the font used.
   428  	 * if the font size changed, the invalidateColumnRowSize will be called,
   429  	 * then next call get method about this will be counted first.
   430  	 */
   431  	private function getRowHeight():Number{
   432  		if(!columnRowCounted) countColumnRowSize();
   433  		return rowHeight;
   434  	}
   435  	
   436  	/**
   437  	 * @see #getColumnWidth
   438  	 */
   439  	private function getWidthMargin():Number{
   440  		if(!columnRowCounted) countColumnRowSize();
   441  		return widthMargin;
   442  	}
   443  	
   444  	/**
   445  	 * @see #getRowHeight
   446  	 */	
   447  	private function getHeightMargin():Number{
   448  		if(!columnRowCounted) countColumnRowSize();
   449  		return heightMargin;
   450  	}
   451  	
   452  	private function countColumnRowSize():Void{
   453  		var textExtent:ASTextExtent = textFormat.getTextExtent("mmmmm");
   454  		columnWidth = textExtent.getWidth()/5;
   455  		rowHeight = textExtent.getHeight();
   456  		widthMargin = textExtent.getTextFieldWidth() - textExtent.getWidth();
   457  		heightMargin = textExtent.getTextFieldHeight() - textExtent.getHeight();
   458  		columnRowCounted = true;
   459  	}
   460  	
   461  	private function getTextFieldAutoSizedSize():Dimension{
   462  		if(!autoSizedToCountPrefferedSize){
   463  			 countAutoSizedSize();
   464  			 autoSizedToCountPrefferedSize = true;
   465  		}
   466  		return autoSizedSize;
   467  	}
   468  	private function countAutoSizedSize():Void{
   469  		var t:TextField = creater.createTF(_root, "tempText");
   470  		applyPropertiesToText(t, true);
   471  		autoSizedSize = new Dimension(t._width, t._height);
   472  		t.removeTextField();
   473  		delete _root[t._name];
   474  	}
   475  	
   476  	private function invalidateColumnRowSize():Void{
   477  		columnRowCounted = false;
   478  	}
   479  	
   480  	private function invalidateTextFieldAutoSizeToCountPrefferedSize():Void{
   481  		autoSizedToCountPrefferedSize = false;
   482  	}
   483  }
   484