1  /*
     2   Copyright aswing.org, see the LICENCE.txt.
     3  */
     4   
     5  import org.aswing.ASColor;
     6  import org.aswing.BoundedRangeModel;
     7  import org.aswing.Component;
     8  import org.aswing.Container;
     9  import org.aswing.Event;
    10  import org.aswing.geom.Dimension;
    11  import org.aswing.geom.Point;
    12  import org.aswing.geom.Rectangle;
    13  import org.aswing.graphices.Graphics;
    14  import org.aswing.graphices.Pen;
    15  import org.aswing.graphices.SolidBrush;
    16  import org.aswing.Icon;
    17  import org.aswing.JButton;
    18  import org.aswing.JScrollBar;
    19  import org.aswing.LayoutManager;
    20  import org.aswing.LookAndFeel;
    21  import org.aswing.plaf.basic.icon.ArrowIcon;
    22  import org.aswing.plaf.ScrollBarUI;
    23  import org.aswing.UIManager;
    24  import org.aswing.utils.Delegate;
    25  import org.aswing.utils.MCUtils;
    26  import org.aswing.utils.Timer;
    27   
    28  /**
    29   *
    30   * @author iiley
    31   */
    32  class org.aswing.plaf.basic.BasicScrollBarUI extends ScrollBarUI
    33  	implements LayoutManager{
    34  	
    35  	private var scrollBarWidth:Number;
    36  	private var minimumThumbLength:Number;
    37  	private var thumbRect:Rectangle;
    38  	private var isDragging:Boolean;
    39  	private var offset:Number;
    40  	
    41      private var thumbHighlightColor:ASColor;
    42      private var thumbLightShadowColor:ASColor;
    43      private var thumbDarkShadowColor:ASColor;
    44      private var thumbColor:ASColor;
    45      
    46      private var scrollbar:JScrollBar;
    47      private var incrButton:JButton;
    48      private var decrButton:JButton;
    49      
    50      private var adjusterListener:Object;
    51      private var incrButtonListener:Object;
    52      private var decrButtonListener:Object;
    53      private var blockListener:Object;
    54      
    55      private static var scrollSpeedThrottle:Number = 60; // delay in milli seconds
    56      private static var initialScrollSpeedThrottle:Number = 300; // first delay in milli seconds
    57      private var scrollTimer:Timer;
    58      private var scrollIncrement:Number;
    59      private var scrollContinueDestination:Number;
    60  	
    61  	public function BasicScrollBarUI(){
    62  		scrollBarWidth = 16;
    63  		minimumThumbLength = 16;
    64  		thumbRect = new Rectangle();
    65  		isDragging = false;
    66  		offset = 0;
    67  		scrollIncrement = 0;
    68  		scrollTimer = new Timer(scrollSpeedThrottle);
    69  		scrollTimer.setInitialDelay(initialScrollSpeedThrottle);
    70  		scrollTimer.addActionListener(__scrollTimerPerformed, this);
    71  	}
    72      	
    73      public function installUI(c:Component):Void{
    74  		scrollbar = JScrollBar(c);
    75  		installDefaults();
    76  		installComponents();
    77  		installListeners();
    78      }
    79      
    80  	public function uninstallUI(c:Component):Void{
    81  		scrollbar = JScrollBar(c);
    82  		uninstallDefaults();
    83  		uninstallComponents();
    84  		uninstallListeners();
    85  		removeMCs(c);
    86  		scrollTimer.stop();
    87      }
    88  	
    89  	private function installDefaults():Void{
    90  		configureScrollBarColors();
    91          scrollbar.setLayout(this);
    92  		LookAndFeel.installBasicProperties(scrollbar, "ScrollBar.border");
    93          LookAndFeel.installBorder(scrollbar, "ScrollBar.border");
    94  	}
    95      private function configureScrollBarColors():Void{
    96      	LookAndFeel.installColorsAndFont(scrollbar, "ScrollBar.background", "ScrollBar.foreground", "ScrollBar.font");
    97  		thumbHighlightColor = UIManager.getColor("ScrollBar.thumbHighlight");
    98  		thumbLightShadowColor = UIManager.getColor("ScrollBar.thumbShadow");
    99  		thumbDarkShadowColor = UIManager.getColor("ScrollBar.thumbDarkShadow");
   100  		thumbColor = UIManager.getColor("ScrollBar.thumb");
   101      }
   102      
   103      private function createArrowIcon(direction:Number):Icon{
   104      	var icon:Icon = new ArrowIcon(direction, scrollBarWidth/3-1,
   105  				    new ASColor(0x000000,100),
   106  				   new ASColor(0x666666,100),
   107  				   new ASColor(0x666666,100),
   108  				    new ASColor(0xFF0000,100));
   109  		return icon;
   110      }
   111      
   112      private function createArrowButton(direction:Number):JButton{
   113  		var b:JButton = new JButton(null, createArrowIcon(direction));
   114  		return b;
   115      }
   116      
   117      private function uninstallDefaults():Void{
   118      	LookAndFeel.uninstallBorder(scrollbar);
   119  		scrollbar.setLayout(null);
   120      }
   121      
   122  	private function installComponents():Void{
   123          if(isVertical()){
   124          	incrButton = createArrowButton(Math.PI/2);
   125          	decrButton = createArrowButton(-Math.PI/2);
   126          }else{
   127          	incrButton = createArrowButton(0);
   128          	decrButton = createArrowButton(-Math.PI);
   129          }
   130          
   131          scrollbar.append(incrButton);
   132          scrollbar.append(decrButton);
   133  		scrollbar.setEnabled(scrollbar.isEnabled());
   134      }
   135  	private function uninstallComponents():Void{
   136  		scrollbar.remove(incrButton);
   137  		scrollbar.remove(decrButton);
   138      }
   139  	
   140  	private function installListeners():Void{
   141  		adjusterListener = scrollbar.addAdjustmentListener(__adjustChanged, this);
   142  		
   143  		incrButtonListener = new Object();
   144  		incrButtonListener[Component.ON_PRESS] = Delegate.create(this, __incrButtonPress);
   145  		incrButtonListener[Component.ON_RELEASE] = Delegate.create(this, __incrButtonReleased);
   146  		incrButtonListener[Component.ON_RELEASEOUTSIDE] = incrButtonListener[Component.ON_RELEASE];
   147  		incrButton.addEventListener(incrButtonListener);
   148  		
   149  		decrButtonListener = new Object();
   150  		decrButtonListener[Component.ON_PRESS] = Delegate.create(this, __decrButtonPress);
   151  		decrButtonListener[Component.ON_RELEASE] = Delegate.create(this, __decrButtonReleased);
   152  		decrButtonListener[Component.ON_RELEASEOUTSIDE] = decrButtonListener[Component.ON_RELEASE];
   153  		decrButton.addEventListener(decrButtonListener);
   154  		
   155  		blockListener = new Object();
   156  		blockListener[Component.ON_PRESS] = Delegate.create(this, __trackPress);
   157  		blockListener[Component.ON_RELEASE] = Delegate.create(this, __trackReleased);
   158  		blockListener[Component.ON_RELEASEOUTSIDE] = blockListener[Component.ON_RELEASE];
   159  		scrollbar.addEventListener(blockListener);
   160  	}
   161      
   162      private function uninstallListeners():Void{
   163      	scrollbar.removeEventListener(adjusterListener);
   164      	scrollbar.removeEventListener(incrButtonListener);
   165      	scrollbar.removeEventListener(decrButtonListener);
   166      	scrollbar.removeEventListener(blockListener);
   167      }
   168  	
   169  	private function createButtonListener():Object{
   170  		var l:Object = new Object();
   171  		return l;
   172  	}
   173  	
   174      private var thumMC:MovieClip;
   175      
   176      private function removeMCs(c:Component):Void{
   177      	thumMC.removeMovieClip();
   178      }
   179      
   180      public function create(c:Component):Void{
   181      	thumMC = c.createMovieClip("ui");
   182      	thumMC.onPress = Delegate.create(this, __startDragThumb);
   183      	thumMC.onRelease = thumMC.onReleaseOutside = Delegate.create(this, __stopDragThumb);
   184      	thumbRect.setRect(0,0,0,0);//clear it make sure when draw it is diff, so will redraw
   185      }
   186      
   187      private function isVertical():Boolean{
   188      	return scrollbar.getOrientation() == JScrollBar.VERTICAL;
   189      }
   190      
   191      private function getThumbRect():Rectangle{
   192      	return new Rectangle(thumbRect);
   193      }
   194      
   195      //-------------------------listeners--------------------------
   196      
   197      private function __scrollTimerPerformed(event:Event):Void{
   198      	var value:Number = scrollbar.getValue() + scrollIncrement;
   199      	var finished:Boolean = false;
   200      	if(scrollIncrement > 0){
   201      		if(value >= scrollContinueDestination){
   202      			finished = true;
   203      		}
   204      	}else{
   205      		if(value <= scrollContinueDestination){
   206      			finished = true;
   207      		}
   208      	}
   209      	if(finished){
   210      		scrollbar.setValue(scrollContinueDestination);
   211      		scrollTimer.stop();
   212      	}else{
   213      		scrollByIncrement(scrollIncrement);
   214      	}
   215      }
   216      
   217      private function __adjustChanged(event:Event):Void{
   218      	if(!isDragging)
   219      		paintAndLocateThumb(scrollbar.getPaintBounds());
   220      }
   221      
   222      private function __incrButtonPress(event:Event):Void{
   223      	scrollIncrement = scrollbar.getUnitIncrement();
   224      	scrollByIncrement(scrollIncrement);
   225      	scrollContinueDestination = scrollbar.getMaximum() - scrollbar.getVisibleAmount();
   226      	scrollTimer.restart();
   227      }
   228      
   229      private function __incrButtonReleased(event:Event):Void{
   230      	scrollTimer.stop();
   231      }
   232      
   233      private function __decrButtonPress(event:Event):Void{
   234      	scrollIncrement = -scrollbar.getUnitIncrement();
   235      	scrollByIncrement(scrollIncrement);
   236      	scrollContinueDestination = scrollbar.getMinimum();
   237      	scrollTimer.restart();
   238      }
   239      
   240      private function __decrButtonReleased(event:Event):Void{
   241      	scrollTimer.stop();
   242      }
   243      
   244      private function __trackPress(event:Event):Void{
   245      	var aimPoint:Point = scrollbar.getMousePosition();
   246      	var isPressedInRange:Boolean = false;
   247      	var tr:Rectangle = getThumbRect();
   248      	if(isVertical()){
   249      		var mousePos:Number = aimPoint.y;
   250      		aimPoint.y -= tr.height/2;
   251      		if(mousePos < tr.y){
   252      			isPressedInRange = true;
   253      		}else if(mousePos > tr.y + tr.height){
   254      			isPressedInRange = true;
   255      		}
   256      	}else{
   257      		var mousePos:Number = aimPoint.x;
   258      		aimPoint.x -= tr.width/2;
   259      		if(mousePos < tr.x){
   260      			isPressedInRange = true;
   261      		}else if(mousePos > tr.x + tr.width){
   262      			isPressedInRange = true;
   263      		}
   264      	}
   265      	
   266      	if(isPressedInRange){
   267      		scrollContinueDestination = getValueWithPosition(aimPoint);
   268      		if(scrollContinueDestination > scrollbar.getValue()){
   269      			scrollIncrement = scrollbar.getBlockIncrement();
   270      		}else{
   271      			scrollIncrement = -scrollbar.getBlockIncrement();
   272      		}
   273      		scrollByIncrement(scrollIncrement);
   274      		scrollTimer.restart();
   275      	}
   276      }
   277      
   278      private function __trackReleased():Void{
   279      	scrollTimer.stop();
   280      }
   281          
   282      private function scrollByIncrement(increment:Number):Void{
   283      	scrollbar.setValue(scrollbar.getValue() + increment);
   284      }
   285      
   286      private function __startDragThumb():Void{
   287      	//pass a child pressed event to make sure when thumb mc pressed
   288      	//there will be a event passed to the root of scrollbar's parents
   289      	scrollbar.__onChildPressed(null);
   290      	
   291      	if(!scrollbar.isEnabled()){
   292      		return;
   293      	}
   294      	scrollbar.setValueIsAdjusting(true);
   295      	var mp:Point = scrollbar.getMousePosition();
   296      	var mx:Number = mp.x;
   297      	var my:Number = mp.y;
   298      	var tr:Rectangle = getThumbRect();
   299      	if(isVertical()){
   300      		offset = my - tr.y;
   301      	}else{
   302      		offset = mx - tr.x;
   303      	}
   304      	isDragging = true;
   305      	__startHandleDrag();
   306      }
   307      
   308      private function __stopDragThumb():Void{
   309      	if(!scrollbar.isEnabled()){
   310      		return;
   311      	}
   312      	if(isDragging){
   313      		scrollThumbToCurrentMousePosition();
   314      	}
   315      	offset = 0;
   316      	scrollbar.setValueIsAdjusting(false);
   317      	isDragging = false;
   318      	__stopHandleDrag();
   319      }
   320      
   321      private function __startHandleDrag():Void{
   322      	thumMC.onMouseMove = Delegate.create(this, __onMoveThumb);
   323      }
   324      private function __stopHandleDrag():Void{
   325      	thumMC.onMouseMove = undefined;
   326      }
   327      
   328      private function __onMoveThumb():Void{
   329      	if(!scrollbar.isEnabled()){
   330      		return;
   331      	}
   332      	scrollThumbToCurrentMousePosition();
   333      }
   334      
   335      private function scrollThumbToCurrentMousePosition():Void{
   336      	var mp:Point = scrollbar.getMousePosition();
   337      	var mx:Number = mp.x;
   338      	var my:Number = mp.y;
   339      	var thumbR:Rectangle = getThumbRect();
   340      	
   341  	    var thumbMin:Number, thumbMax:Number, thumbPos:Number;
   342  	    
   343      	if(isVertical()){
   344  			thumbMin = decrButton.getY() + decrButton.getHeight();
   345  			thumbMax = incrButton.getY() - thumbR.height;
   346  			thumbPos = Math.min(thumbMax, Math.max(thumbMin, (my - offset)));
   347  			setThumbRect(thumbR.x, thumbPos, thumbR.width, thumbR.height);	
   348      	}else{
   349  		    thumbMin = decrButton.getX() + decrButton.getWidth();
   350  		    thumbMax = incrButton.getX() - thumbR.width;
   351  			thumbPos = Math.min(thumbMax, Math.max(thumbMin, (mx - offset)));
   352  			setThumbRect(thumbPos, thumbR.y, thumbR.width, thumbR.height);
   353      	}
   354      	
   355      	var scrollBarValue:Number = getValueWithThumbMaxMinPos(thumbMin, thumbMax, thumbPos);
   356      	scrollbar.setValue(scrollBarValue);
   357      	updateAfterEvent();
   358      }
   359      
   360      private function getValueWithPosition(point:Point):Number{
   361      	var mx:Number = point.x;
   362      	var my:Number = point.y;
   363      	var thumbR:Rectangle = getThumbRect();
   364      	
   365  	    var thumbMin:Number, thumbMax:Number, thumbPos:Number;
   366  	    
   367      	if(isVertical()){
   368  			thumbMin = decrButton.getY() + decrButton.getHeight();
   369  			thumbMax = incrButton.getY() - thumbR.height;
   370  			thumbPos = my;
   371      	}else{
   372  		    thumbMin = decrButton.getX() + decrButton.getWidth();
   373  		    thumbMax = incrButton.getX() - thumbR.width;
   374  		    thumbPos = mx;
   375      	}
   376      	return getValueWithThumbMaxMinPos(thumbMin, thumbMax, thumbPos);
   377      }
   378      
   379      private function getValueWithThumbMaxMinPos(thumbMin:Number, thumbMax:Number, thumbPos:Number):Number{
   380      	var model:BoundedRangeModel = scrollbar.getModel();
   381      	var scrollBarValue:Number;
   382      	if (thumbPos >= thumbMax) {
   383      		scrollBarValue = model.getMaximum() - model.getExtent();
   384      	}else{
   385  			var valueMax:Number = model.getMaximum() - model.getExtent();
   386  			var valueRange:Number = valueMax - model.getMinimum();
   387  			var thumbValue:Number = thumbPos - thumbMin;
   388  			var thumbRange:Number = thumbMax - thumbMin;
   389  			var value:Number = (thumbValue / thumbRange) * valueRange;
   390  			scrollBarValue = value + model.getMinimum();
   391      	}
   392      	return scrollBarValue;    	
   393      }
   394      
   395      //--------------------------paints----------------------------
   396      
   397      public function paint(c:Component, g:Graphics, b:Rectangle):Void{
   398      	paintBackGround(c, g, b);
   399      	
   400      	paintAndLocateThumb(b);
   401      }
   402      
   403      private function paintAndLocateThumb(b:Rectangle):Void{
   404       	if(!scrollbar.isEnabled()){
   405      		if(isVertical()){
   406      			if(incrButton.isEnabled()){
   407      				trace("Logic Wrong : Scrollbar range is not enabled, but its button enabled ");
   408      			}
   409      		}
   410      		thumMC._visible = false;
   411      		return;
   412      	}
   413      	thumMC._visible = true;
   414      	var min:Number = scrollbar.getMinimum();
   415      	var extent:Number = scrollbar.getVisibleAmount();
   416      	var range:Number = scrollbar.getMaximum() - min;
   417      	var value:Number = scrollbar.getValue();
   418      	
   419      	if(range <= 0){
   420      		if(isVertical())
   421      			trace("Logic Wrong : Scrollbar range = " + range + ", max="+scrollbar.getMaximum()+", min="+min);
   422      		thumMC._visible = false;
   423      		return;
   424      	}
   425      	
   426      	var trackLength:Number;
   427      	var thumbLength:Number;
   428      	if(isVertical()){
   429      		trackLength = b.height - incrButton.getHeight() - decrButton.getHeight();
   430      		thumbLength = trackLength*(extent/range);
   431      	}else{
   432      		trackLength = b.width - incrButton.getWidth() - decrButton.getWidth();
   433      		thumbLength = trackLength*(extent/range);
   434      	}
   435      	thumbLength = Math.max(thumbLength, minimumThumbLength);
   436      	
   437      	if(thumbLength >= trackLength){
   438      		if(isVertical())
   439      			trace("Logic Wrong : Scrollbar exent="+extent+", range="+range);
   440      		thumMC._visible = false;
   441      		return;
   442      	}
   443      	
   444  		var thumbRange:Number = trackLength - thumbLength;
   445  		var thumbPos:Number = Math.round(thumbRange * ((value - min) / (range - extent)));
   446  		if(isVertical()){
   447  			setThumbRect(b.x, thumbPos + b.y + decrButton.getHeight(), 
   448  						scrollBarWidth, thumbLength);
   449  		}else{
   450  			setThumbRect(thumbPos + b.x + decrButton.getWidth(), b.y, 
   451  						thumbLength, scrollBarWidth);
   452  		}
   453      }
   454      
   455      private function setThumbRect(x:Number, y:Number, w:Number, h:Number):Void{
   456      	if(!MCUtils.isMovieClipExist(thumMC)){
   457      		return;
   458      	}
   459      	
   460      	var oldW:Number = thumbRect.width;
   461      	var oldH:Number = thumbRect.height;
   462      	
   463      	thumbRect.setRect(x, y, w, h);
   464      	
   465      	if(w != oldW || h != oldH){
   466      		paintThumb(thumMC, thumbRect.getSize());
   467      	}
   468      	thumMC._x = x;
   469      	thumMC._y = y;
   470      }
   471      
   472      /**
   473       * LAF notice.
   474       * 
   475       * Override this method to paint diff thumb in your LAF.
   476       */
   477      private function paintThumb(thumMC:MovieClip, size:Dimension):Void{
   478      	var w:Number = size.width;
   479      	var h:Number = size.height;
   480      	thumMC.clear();
   481      	var g:Graphics = new Graphics(thumMC);
   482      	var b:SolidBrush = new SolidBrush(thumbDarkShadowColor);
   483      	g.fillRectangle(b, 0, 0, w, h);
   484      	b.setASColor(this.thumbColor);
   485      	g.fillRectangle(b, 1, 1, w-2, h-2);
   486      	
   487      	var p:Pen = new Pen(thumbDarkShadowColor, 0);
   488      	if(isVertical()){
   489  	    	var ch:Number = h/2;
   490  	    	g.drawLine(p, 3, ch, w-3, ch);
   491  	    	g.drawLine(p, 3, ch+2, w-3, ch+2);
   492  	    	g.drawLine(p, 3, ch-2, w-3, ch-2);
   493      	}else{
   494  	    	var cw:Number = w/2;
   495  	    	g.drawLine(p, cw, 3, cw, h-3);
   496  	    	g.drawLine(p, cw+2, 3, cw+2, h-3);
   497  	    	g.drawLine(p, cw-2, 3, cw-2, h-3);
   498      	}
   499      }
   500      
   501      //--------------------------Dimensions----------------------------
   502      
   503      public function getPreferredSize(c:Component):Dimension{
   504      	return preferredLayoutSize(Container(c));
   505      }
   506  
   507      public function getMaximumSize(c:Component):Dimension{
   508  		return maximumLayoutSize(Container(c));
   509      }
   510  
   511      public function getMinimumSize(c:Component):Dimension{
   512  		return getPreferredSize(c);
   513      }
   514  	
   515  	//--------------------------Layout----------------------------
   516  	private function layoutVScrollbar(sb:JScrollBar):Void{
   517      	var rd:Rectangle = sb.getPaintBounds();
   518      	var w:Number = scrollBarWidth;
   519      	decrButton.setBounds(rd.x, rd.y, w, w);
   520      	incrButton.setBounds(rd.x, rd.y + rd.height - w, w, w);
   521  	}
   522  	
   523  	private function layoutHScrollbar(sb:JScrollBar):Void{
   524      	var rd:Rectangle = sb.getPaintBounds();
   525      	var w:Number = scrollBarWidth;
   526      	decrButton.setBounds(rd.x, rd.y, w, w);
   527      	incrButton.setBounds(rd.x + rd.width - w, rd.y, w, w);
   528  	}
   529  	    
   530  	public function layoutContainer(target:Container):Void{
   531  		if(isDragging){
   532  			return;
   533  		}
   534      	if(target == scrollbar){
   535      		if(isVertical()){
   536      			layoutVScrollbar(scrollbar);
   537      		}else{
   538      			layoutHScrollbar(scrollbar);
   539      		}
   540      	}else{
   541      		trace("BasicScrollBarUI just can be JScrollBar's Layout : " + target);
   542      	}
   543      }
   544  	
   545  	/**
   546  	 * @param target
   547  	 * @throws Error when the target is not a JScrollBar
   548  	 */
   549      public function preferredLayoutSize(target:Container):Dimension{
   550      	if(target == scrollbar){
   551      		var w:Number, h:Number;
   552      		if(isVertical()){
   553      			w = scrollBarWidth;
   554      			h = scrollBarWidth*2 + minimumThumbLength;
   555      		}else{
   556      			w = scrollBarWidth*2 + minimumThumbLength;
   557      			h = scrollBarWidth;
   558      		}
   559      		return scrollbar.getInsets().roundsSize(new Dimension(w, h));
   560      	}else{
   561      		trace("BasicScrollBarUI just can be JScrollBar's Layout : " + target);
   562      		throw new Error("BasicScrollBarUI just can be JScrollBar's Layout : " + target);
   563      	}
   564      }
   565      
   566  	/**
   567  	 * @param target
   568  	 * @throws Error when the target is not a JScrollBar
   569  	 */    
   570      public function maximumLayoutSize(target:Container):Dimension{
   571      	if(target == scrollbar){
   572      		var w:Number, h:Number;
   573      		if(isVertical()){
   574      			w = scrollBarWidth;
   575      			h = Number.MAX_VALUE;
   576      		}else{
   577      			w = Number.MAX_VALUE;
   578      			h = scrollBarWidth;
   579      		}
   580      		return scrollbar.getInsets().roundsSize(new Dimension(w, h));
   581      	}else{
   582      		trace("BasicScrollBarUI just can be JScrollBar's Layout : " + target);
   583      		throw new Error("BasicScrollBarUI just can be JScrollBar's Layout : " + target);
   584      	}
   585      }
   586      
   587      public function minimumLayoutSize(target:Container):Dimension{
   588      	return preferredLayoutSize(target);
   589      }
   590      public function getLayoutAlignmentX(target:Container):Number{
   591      	return 0;
   592      }
   593      public function getLayoutAlignmentY(target:Container):Number{
   594      	return 0;
   595      }
   596  	
   597      public function addLayoutComponent(comp:Component, constraints:Object):Void{}
   598      public function removeLayoutComponent(comp:Component):Void{}
   599      public function invalidateLayout(target:Container):Void{}
   600      
   601  }
   602