1  /*
     2   Copyright aswing.org, see the LICENCE.txt.
     3  */
     4   
     5  import org.aswing.Component;
     6  import org.aswing.geom.Dimension;
     7  import org.aswing.geom.Point;
     8  import org.aswing.geom.Rectangle;
     9  import org.aswing.JTextComponent;
    10  import org.aswing.plaf.TextUI;
    11  import org.aswing.UIManager;
    12  import org.aswing.Viewportable;
    13  
    14  /**
    15   * A JTextArea is a multi-line area that displays text.
    16   * <p>
    17   * With JScrollPane, it's easy to be a scrollable text area, for example:
    18   * <pre>
    19   * var ta:JTextArea = new JTextArea();
    20   * 
    21   * var sp:JScrollPane = new JScrollPane(ta); 
    22   * //or 
    23   * //var sp:JScrollPane = new JScrollPane(); 
    24   * //sp.setView(ta);
    25   * </pre>
    26   * @author Tomato, iiley
    27   * @see JScrollPane
    28   */
    29  class org.aswing.JTextArea extends JTextComponent implements Viewportable {
    30  	/**
    31  	 * When the JTextArea Viewportable state changed.
    32  	 *<br>
    33  	 * onStateChanged Event{source:JTextArea}
    34  	 */	
    35  	public static var ON_STATE_CHANGED:String = "onStateChanged";//Component.ON_STATE_CHANGED; 
    36  	
    37  	private var columns:Number;
    38  	private var rows:Number;
    39  	
    40  	private var viewPos:Point;
    41  	
    42  	private var wordWrap:Boolean;
    43  	private var multiline:Boolean;
    44  	private var viewportSizeTesting:Boolean;
    45  	
    46  	/**
    47  	 * JTextArea(text:String, rows:Number, columns:Number)<br>
    48  	 * JTextArea(text:String, rows:Number) columns default to 0<br> 
    49  	 * JTextArea(text:String) rows and columns default to 0<br>
    50  	 * JTextArea() text default to 0, rows and columns default to 0<br>
    51  	 * <p>
    52  	 * @see #setRows()
    53  	 * @see #setColumns()
    54  	 */
    55  	public function JTextArea(text:String, rows:Number, columns:Number) {
    56  		super();
    57  		setName("JTextArea");
    58  		this.text = (text == undefined ? "" : text);
    59  		
    60  		viewPos = new Point();
    61  		setRows(rows);
    62  		setColumns(columns);
    63  		wordWrap = false;
    64  		multiline = true;
    65  		viewportSizeTesting = false;
    66  		
    67  		updateUI();
    68  	}
    69  	
    70      public function updateUI():Void{
    71      	setUI(TextUI(UIManager.getUI(this)));
    72      }	
    73  	
    74  	public function getUIClassID():String{
    75  		return "TextAreaUI";
    76  	}
    77  	
    78  	public function setWordWrap(wrap:Boolean):Void{
    79  		if(wordWrap != wrap){
    80  			wordWrap = wrap;
    81  			if(getTextField()!= null){
    82  				getTextField().wordWrap = wrap;
    83  			}else{
    84  				repaint();
    85  			}
    86  			invalidateTextFieldAutoSizeToCountPrefferedSize();
    87  			revalidate();
    88  		}
    89  	}
    90  	
    91  	public function isWordWrap():Boolean{
    92  		return wordWrap;
    93  	}
    94  		
    95  	/**
    96  	 * Sets the number of columns in this JTextArea, if it changed then call parent to do layout. 
    97  	 * @param columns the number of columns to use to calculate the preferred width;
    98  	 * if columns is set to zero or min than zero, the preferred width will be matched just to view all of the text.
    99  	 * default value is zero if missed this param.
   100  	 */
   101  	public function setColumns(columns:Number):Void{
   102  		if(columns == undefined) columns = 0;
   103  		if(columns < 0) columns = 0;
   104  		if(this.columns != columns){
   105  			this.columns = columns;
   106  			if(isWordWrap()){
   107  				invalidateTextFieldAutoSizeToCountPrefferedSize();
   108  			}
   109  			if(displayable){
   110  				revalidate();
   111  			}
   112  		}
   113  	}
   114  	
   115  	/**
   116  	 * @see #setColumns
   117  	 */
   118  	public function getColumns():Number{
   119  		return columns;
   120  	}
   121  	
   122  	/**
   123  	 * Sets the number of rows in this JTextArea, if it changed then call parent to do layout. 
   124  	 * @param rows the number of rows to use to calculate the preferred width;
   125  	 * if columns is set to zero or min than zero, the preferred width will be matched just to view all of the text.
   126  	 * default value is zero if missed this param.
   127  	 */
   128  	public function setRows(rows:Number):Void{
   129  		if(rows == undefined) rows = 0;
   130  		if(rows < 0) rows = 0;
   131  		if(this.rows != rows){
   132  			this.rows = rows;
   133  			if(isWordWrap()){
   134  				invalidateTextFieldAutoSizeToCountPrefferedSize();
   135  			}
   136  			if(displayable){
   137  				revalidate();
   138  			}
   139  		}
   140  	}
   141  	
   142  	/**
   143  	 * @see #setRows
   144  	 */
   145  	public function getRows():Number{
   146  		return rows;
   147  	}
   148  	
   149  	private function size():Void{
   150  		super.size();
   151  		if(isWordWrap()){
   152  			//invalidateTextFieldAutoSizeToCountPrefferedSize();
   153  		}
   154  		if(getTextField() != null){
   155  			//trace("_|||||||__  Sized!!");
   156  			applyPropertiesToText(getTextField(), false);
   157  			applyBoundsToText(getTextField(), getPaintBounds());
   158  	    	//call this first to validate current textfield scroll properties
   159  	    	var t:TextField = getTextField();
   160  	    	if(isHtml()){
   161  	    		t.htmlText = text;
   162  	    	}else{
   163  	    		t.text = text;
   164  	    	}
   165  	    	t.background = false;
   166  		}
   167  	}
   168  				
   169  	private function countPreferredSize():Dimension{
   170  		var size:Dimension;
   171  		if(columns > 0 && rows > 0){
   172  			var width:Number = getColumnWidth() * columns + getWidthMargin();
   173  			var height:Number = getRowHeight() * rows + getHeightMargin();
   174  			size = new Dimension(width, height);
   175  		}else if(rows <=0 && columns <=0 ){
   176  			size = getTextFieldAutoSizedSize();
   177  		}else if(rows > 0){ // columns must <= 0
   178  			size = getTextFieldAutoSizedSize();
   179  			size.height = getRowHeight() * rows + getHeightMargin();
   180  		}else{ //must be columns > 0 and rows <= 0
   181  			size = getTextFieldAutoSizedSize();
   182  		}
   183  		return getInsets().roundsSize(size);
   184  	}
   185  		
   186      private function getWordWrapWidth():Number{
   187      	if(columns > 0){
   188      		return getColumnWidth() * columns + getWidthMargin();
   189      	}else{
   190      		return getPaintBounds().width;
   191      	}
   192      }
   193      
   194      /**
   195       * Sets if the text is multiline, by default it is true.
   196       * @param b true to support multiline, otherwise false.
   197       */
   198      public function setMultiline(b:Boolean):Void{
   199      	if(multiline != b){
   200      		multiline = b;
   201      		if(getTextField() != null){
   202      			getTextField().multiline = b;
   203      		}else{
   204      			repaint();
   205      		}
   206      		revalidate();
   207      	}
   208      }
   209      
   210      /**
   211       * Returns is the text support multiline.
   212       */
   213      public function isMultiline():Boolean{
   214      	return multiline;
   215      }
   216      
   217      //-------------------------------text listeners ---------------------
   218  	private function __uiTextChanged():Void{
   219      	if(viewportSizeTesting){
   220      		return;
   221      	}
   222  		super.__uiTextChanged();
   223  		revalidate();
   224  	}
   225  	
   226      private function __uiTextScrolled():Void{
   227      	if(viewportSizeTesting){
   228      		return;
   229      	}
   230      	var t:TextField = getTextField();
   231  		var newViewPos:Point = new Point(t.hscroll, t.scroll-1);
   232  		var isDiff:Boolean = Math.abs(viewPos.y-newViewPos.y)>=1 || Math.abs(viewPos.x - newViewPos.x) >= 1;
   233  		if(isDiff){
   234  			viewPos.setLocation(newViewPos);
   235  			//notify scroll bar to syn
   236  			fireStateChanged();
   237  		}
   238      	super.__uiTextScrolled();
   239      }
   240      
   241  	//---------------------------implementation of Viewportable---------------------------------
   242  	
   243  	/**
   244  	 * the <code>Viewportable</code> state change listener adding method implementation.
   245  	 */
   246  	public function addChangeListener(func:Function, obj:Object):Object{
   247  		return addEventListener(ON_STATE_CHANGED, func, obj);
   248  	}
   249  		
   250  	/**
   251  	 * Returns the unit value for the Vertical scrolling.
   252  	 */
   253      public function getVerticalUnitIncrement():Number{
   254      	return 1;
   255      }
   256      
   257      /**
   258       * Return the block value for the Vertical scrolling.
   259       */
   260      public function getVerticalBlockIncrement():Number{
   261      	return 10;
   262      }
   263      
   264  	/**
   265  	 * Returns the unit value for the Horizontal scrolling.
   266  	 */
   267      public function getHorizontalUnitIncrement():Number{
   268      	return getColumnWidth();
   269      }
   270      
   271      /**
   272       * Return the block value for the Horizontal scrolling.
   273       */
   274      public function getHorizontalBlockIncrement():Number{
   275      	return getColumnWidth()*10;
   276      }
   277      
   278      public function setViewportTestSize(s:Dimension):Void{
   279      	viewportSizeTesting = true;
   280      	setSize(s);
   281      	validateScroll();
   282      	viewportSizeTesting = false;
   283      }
   284     
   285      
   286      /**
   287       * Returns the size of the visible part of the view in view logic coordinates.
   288       *
   289       * @return a <code>Dimension</code> object giving the size of the view
   290       */
   291      public function getExtentSize():Dimension{    
   292      	var t:TextField = getTextField();
   293      	if(t == null){
   294      		return new Dimension(1, 1);//max than getViewSize
   295      	}
   296      	var wSize:Number, hSize:Number;
   297      	wSize = t._width;
   298      	var bottomscroll:Number = t.bottomScroll;
   299  		var scroll:Number = t.scroll;
   300  		var extent:Number = bottomscroll - scroll + 1;
   301  		hSize = extent;
   302      	return new Dimension(wSize, hSize);
   303      }
   304          
   305      /**
   306       * Returns the viewportable view's amount size if view all content in view logic coordinates.
   307       * @return the view's size.
   308       */
   309      public function getViewSize():Dimension{
   310      	var t:TextField = getTextField();
   311      	if(t == null){
   312      		return new Dimension(0, 0);
   313      	}
   314      	var wRange:Number, hRange:Number;
   315      	if(isWordWrap()){
   316      		wRange = t._width;
   317      		t.hscroll = 0;
   318      	}else{
   319  	    	if(t.textWidth + 5 > t._width){
   320      			wRange = t._width + t.maxhscroll;
   321  	    	}else{
   322      			wRange = t._width;
   323      			t.hscroll = 0;
   324      			t.background = false;
   325  	    	}
   326      	}
   327      	var bottomscroll:Number = t.bottomScroll;
   328  		var maxscroll:Number = t.maxscroll;
   329  		var scroll:Number = t.scroll;
   330  		var extent:Number = bottomscroll - scroll + 1;
   331  		var maxValue:Number = maxscroll + extent;
   332  		var minValue:Number = 1;
   333      	hRange = maxValue - minValue;
   334      	return new Dimension(wRange, hRange);
   335      }
   336          
   337      /**
   338       * Returns the view coordinates that appear in the upper left
   339       * hand corner of the viewport, or 0,0 if there's no view. in view logic coordinates.
   340       *
   341       * @return a <code>Point</code> object giving the upper left coordinates
   342       */
   343      public function getViewPosition():Point{
   344      	return new Point(viewPos);
   345      }
   346      
   347      /**
   348       * Sets the view coordinates that appear in the upper left
   349       * hand corner of the viewport. in view logic coordinates.
   350       *
   351       * @param p  a <code>Point</code> object giving the upper left coordinates
   352       */
   353      public function setViewPosition(p:Point):Void{
   354  		restrictionViewPos(p);
   355  		if(!viewPos.equals(p)){
   356  			viewPos.setLocation(p);
   357  			validateScroll();
   358  			fireStateChanged();
   359  		}
   360      }
   361          
   362      private function validateScroll():Void{
   363  		var xS:Number = Math.round(viewPos.x);
   364  		var yS:Number = Math.round(viewPos.y)+1;
   365      	var t:TextField = getTextField();
   366  		if(t.hscroll != xS){
   367  			t.hscroll = xS;
   368  		}
   369  		if(t.scroll != yS){
   370  			t.scroll = yS;
   371  		}
   372  		t.background = false; //avoid TextField background lose effect bug
   373      }
   374      
   375      /**
   376       * Returns visible row count.
   377       * @return how many rows can see
   378       */
   379      public function getVisibleRows():Number{
   380      	if(getTextField() != null){
   381      		return getExtentSize().height;
   382      	}else{
   383      		return Math.floor(getHeight()/getRowHeight());
   384      	}
   385      }
   386      
   387      /**
   388       * Scrolls the view so that <code>Rectangle</code>
   389       * within the view becomes visible. in view logic coordinates.
   390       * <p>
   391       * Note that this method will not scroll outside of the
   392       * valid viewport; for example, if <code>contentRect</code> is larger
   393       * than the viewport, scrolling will be confined to the viewport's
   394       * bounds.
   395       * @param contentRect the <code>Rectangle</code> to display
   396       */
   397  	public function scrollRectToVisible(contentRect : Rectangle) : Void {
   398  		setViewPosition(new Point(contentRect.x, contentRect.y));
   399  	}
   400  	
   401  	/**
   402  	 * Scrolls to view bottom left content. 
   403  	 * This will make the scrollbars of <code>JScrollPane</code> scrolled automatically, 
   404  	 * if it is located in a <code>JScrollPane</code>.
   405  	 */
   406  	public function scrollToBottomLeft():Void{
   407  		setViewPosition(new Point(0, Number.MAX_VALUE));
   408  	}
   409  	/**
   410  	 * Scrolls to view bottom right content. 
   411  	 * This will make the scrollbars of <code>JScrollPane</code> scrolled automatically, 
   412  	 * if it is located in a <code>JScrollPane</code>.
   413  	 */	
   414  	public function scrollToBottomRight():Void{
   415  		setViewPosition(new Point(Number.MAX_VALUE, Number.MAX_VALUE));
   416  	}
   417  	/**
   418  	 * Scrolls to view top left content. 
   419  	 * This will make the scrollbars of <code>JScrollPane</code> scrolled automatically, 
   420  	 * if it is located in a <code>JScrollPane</code>.
   421  	 */	
   422  	public function scrollToTopLeft():Void{
   423  		setViewPosition(new Point(0, 0));
   424  	}
   425  	/**
   426  	 * Scrolls to view to right content. 
   427  	 * This will make the scrollbars of <code>JScrollPane</code> scrolled automatically, 
   428  	 * if it is located in a <code>JScrollPane</code>.
   429  	 */	
   430  	public function scrollToTopRight():Void{
   431  		setViewPosition(new Point(Number.MAX_VALUE, 0));
   432  	}	
   433  	
   434  	private function restrictionViewPos(p:Point):Point{
   435  		var maxPos:Point = getViewMaxPos();
   436  		p.x = Math.max(0, Math.min(maxPos.x, p.x));
   437  		p.y = Math.max(0, Math.min(maxPos.y, p.y));
   438  		return p;
   439  	}
   440  	
   441  	private function getViewMaxPos():Point{
   442  		var showSize:Dimension = getExtentSize();
   443  		var viewSize:Dimension = getViewSize();
   444  		var p:Point = new Point(viewSize.width-showSize.width, viewSize.height-showSize.height);
   445  		if(p.x < 0) p.x = 0;
   446  		if(p.y < 0) p.y = 0;
   447  		return p;
   448  	}
   449      
   450      /**
   451       * Return the component of the viewportable's pane which would added to displayed on the stage.
   452       * @return the component of the viewportable pane.
   453       */
   454      public function getViewportPane():Component{
   455      	return this;
   456      }	
   457  }
   458